Compare commits

...

257 Commits

Author SHA1 Message Date
Pablo Polvorin ce0d1704c6 Allow generation of oauth tokens from command line
Oauth tokens can be generated for commands (scopes) having admin|user|open
policy. Restricted commands are not available as those are only usable
from ejabberdctl command line.

Four new commands are available:

$ejabberdctl oauth_issue_token "stats;get_roster"
    Generates a token authorized to call both stats and get_roster
    commands.  Note scopes must be separated by semicolon.

$ejabberdctl oauth_list_tokens
    List tokens generated from the command line, with their scope
    and expirity time.

$ejabberdctl oauth_list_scopes
    List scopes available

$ejabberdctl oauth_revoke_token "Lbs7qdJfdKXOWzVrArgyckY055tE1xnt"
    Revokes the given token
2016-06-29 00:22:28 -03:00
Paweł Chmielowski 3446aba753 Include correct version in stream:stream when reporting errors
This fixes issue #1174
2016-06-27 16:40:57 +02:00
Paweł Chmielowski 75366ca2fd Inline muc access rules 2016-06-24 15:12:58 +02:00
Paweł Chmielowski f56cff925c acl: ACLName rule should match if any part of ACLName matches 2016-06-24 15:09:51 +02:00
Christophe Romain 94461948db Update dependencies for 16.06x 2016-06-24 11:12:18 +02:00
Christophe Romain 0b438d09d1 Fix typo from d8bb5d9c 2016-06-23 17:31:32 +02:00
Christophe Romain 06bf8cb032 Prepare hex.pm release 2016-06-23 15:23:28 +02:00
Holger Weiss 1794dd19d0 mod_pubsub: Fix matching of set_node/1 result
nodetree_tree_sql:set_node/1 returns {result, NodeIdx} on success, not
{ok, NodeIdx}.  Thanks to Christophe Romain for spotting this.
2016-06-23 14:23:24 +02:00
Paweł Chmielowski 1b5c50a384 When convertion of xmlrpc argument to type fails, report it as error 2016-06-23 10:41:16 +02:00
Jerome Sautret a9b456ccb3 xref was broken when Elixir wasn't enabled 2016-06-23 10:23:02 +02:00
Paweł Chmielowski 2ebdd8915e Compile gen_mod early to help with undefined behaviour warnings 2016-06-23 10:22:01 +02:00
Evgeny Khramtsov e54400a8e4 Merge pull request #1165 from weiss/default-blocking-list
mod_blocking_sql: Handle default list corner case
2016-06-23 10:44:47 +04:00
Holger Weiss 065f5272e6 mod_blocking_sql: Handle default list corner case
Handle the situation where a list of the name "Blocked contacts" was
created by an XEP-0016 client, but no default list exists.
2016-06-22 22:36:27 +02:00
Paweł Chmielowski 751be3cca6 Add some tests for {shaper,access}_rules_validator 2016-06-22 16:52:45 +02:00
Christophe Romain cd0244eb71 Merge pull request #1120 from anagromataf/feature/archive-id-in-message-carbons
Send unique stanza id and archived tag also in the message carbons
2016-06-22 15:47:27 +02:00
Christophe Romain f029488260 Restore get_items conditions when not using RSM (#1147) 2016-06-22 13:12:40 +02:00
Badlop eeeb190680 Set HTTP/1.0 so github accepts the request (#1157) 2016-06-22 12:43:24 +02:00
Christophe Romain 95ff94b054 Fix PubSub RSM on get_items (#1147) 2016-06-22 12:25:41 +02:00
Christophe Romain 7744339347 Update supported xep version 2016-06-22 11:24:01 +02:00
Christophe Romain 2efa8677c9 Fix pgsql compatibility on delete_old_messages (#1137) 2016-06-22 11:21:11 +02:00
Holger Weiss c928956d73 XEP-0198: Apply cosmetic changes 2016-06-21 23:17:17 +02:00
Holger Weiss 7ddeac38b6 XEP-0198: Also count stanzas when socket is closed
Don't forget to count stanzas received from the stream management client
that are processed right after the connection was lost.
2016-06-21 22:54:41 +02:00
Paweł Chmielowski 3a8da27d86 Use {access,shaper}_rules_validator in other places where access rules are used 2016-06-21 13:18:24 +02:00
Paweł Chmielowski 52d45604ba Use new access_rules_validator in couple places 2016-06-21 12:28:53 +02:00
Paweł Chmielowski 804190e4a8 Add acl:{access,shaper}_rules_validator for use in {mod_}opt_type() 2016-06-21 12:26:31 +02:00
Paweł Chmielowski 4b9613e8fe Allow {mod_}opt_type to transform values passed to it, and for better error reporting 2016-06-21 12:25:29 +02:00
Christophe Romain b2f53fb962 Avoid cleanup on bag when disc_only, switch in memory (#1161) 2016-06-21 10:43:19 +02:00
Holger Weiss c91c5aa352 Fix handling of queued stanzas on session timeout
Don't fail to resend or bounce unacknowledged stanzas if the stream
management session timed out.

Closes #1160.
2016-06-19 23:32:15 +02:00
Christophe Romain 6f2b0179e7 Give more time to stop and kill epmd (#882) 2016-06-17 17:09:45 +02:00
Christophe Romain 8583958268 Use shorter jid acl in config template 2016-06-17 11:58:51 +02:00
Paweł Chmielowski d1425f0d78 Use new short access rules in config template 2016-06-16 11:13:07 +02:00
Paweł Chmielowski f1138baa80 Add test for more allowed access_rules 2016-06-16 11:12:16 +02:00
Paweł Chmielowski 1fb1e8721b Allow using shaper defined by name like in in s2s_shaper: fast 2016-06-16 11:04:01 +02:00
Paweł Chmielowski 0a09f27373 Typo in option name 2016-06-16 11:00:38 +02:00
Paweł Chmielowski 7b308e0d41 Add shorter version of some common access rules definitions
This add conversion of

- allow
to
- allow: all

and

- allow: acl_name
to
- allow:
  - acl: acl_name

(this works also for deny, and number in shapers)
2016-06-15 19:23:55 +02:00
Badlop 9004608181 Check password with jid:resourceprep when registering account (#996) 2016-06-14 23:35:47 +02:00
Holger Weiss 26bce5dee3 mod_mam: Fix "assume_mam_usage: if_enabled" 2016-06-14 16:40:46 +02:00
Badlop 34cf693231 Recover ec6c58a which was reverted in 100827e (thanks to Alexey Shchepin) 2016-06-10 13:18:32 +02:00
Mickael Remond 0e61e57ed9 Preparing hex.pm release to fix lager bug 2016-06-10 11:02:45 +02:00
Mickael Remond 34cbed54cd Force use of lager 3.0.2 at most.
Lager 3.2.0 has a bug that prevent it to work with ejabberd.
Lager 3.2.0 bug is fixed in https://github.com/basho/lager/commit/4c87abcd4f9d70a1136fff8f573dc7adcc833e43
2016-06-10 11:01:46 +02:00
Badlop 4ccc40bce5 push_roster must convert read strings to binaries (#1075) 2016-06-08 21:16:30 +02:00
Badlop 53f3a45803 Recover fix of 907e239 lost in 9deb294 (thanks to Alexey Shchepin) 2016-06-08 19:34:05 +02:00
Badlop 858d880675 Allow again multiple fqdn values in configuration (EJAB-1578) 2016-06-08 19:28:17 +02:00
badlop a4f213837e Merge pull request #1125 from vthriller/roster-push
mod_roster should probably respect roster item changes introduced with roster_process_item hooks upon pushing
2016-06-08 13:57:52 +02:00
Badlop 5173de503c Produce mod_last entry on account creation (processone/ejabberd-contrib#62) 2016-06-08 13:02:20 +02:00
Badlop 8a7b31ca63 When stopping ejabberd, stop modules after broadcasting c2s shutdown (#1144) 2016-06-07 18:41:38 +02:00
Paweł Chmielowski f8d2589ee5 Add tests for new acl functions 2016-06-06 17:12:39 +02:00
Holger Weiss 78d4200f05 mod_pubsub: Fix node configuration changes for SQL
nodetree_tree_sql:set_node/1 returns {ok, NodeIdx} rather than 'ok' on
success.
2016-06-06 00:18:24 +02:00
Holger Weiss 60803f5780 Simplify check for carbon-copied chat states
Let jlib:is_standalone_chat_state/1 unwrap carbon copies rather than
leaving this to the caller.  We still export jlib:unwrap_carbon/1, as
this function might also be useful for other purposes.
2016-06-05 22:36:56 +02:00
Holger Weiss 5c3074c0fb mod_client_state: Fix handling of chat states
Fix the check for chat states sent from other resources of the same
user.
2016-06-05 22:04:38 +02:00
Holger Weiss 4789ddf1ee mod_client_state: Simplify handling of PEP stanzas
Let mod_client_state simply queue the most recent item of a given PEP
node (from a given contact) instead of also taking the payload namespace
into account.
2016-06-05 21:48:03 +02:00
Pablo Polvorin 41c3751fa1 Fix quicktest case
Missing initialization, required for ets table to exists
2016-06-05 16:35:51 -03:00
Holger Weiss 8305cc293b XEP-0352: Pass chat states of other resources
Don't hold back (carbon copies of) chat states from other resources, as
they might be used to sync the state of conversations across clients.
E.g., if one client becomes active, another one might want to remove a
notification (immediately).
2016-06-03 21:52:11 +02:00
Holger Weiss 4d5eab6662 Unwrap carbon copies when checking for chat states
Detect standalone chat states that were carbon-copied.
2016-06-03 20:28:48 +02:00
Holger Weiss 3a1fc6fb66 Ignore <delay/> when checking for chat states
Ignore XEP-0203 elements when checking whether a message stanza is a
standalone chat state.
2016-06-03 19:02:26 +02:00
Holger Weiss 5c1db176a9 Fix "unused variable" warning 2016-06-03 12:58:20 +02:00
Badlop 0503d899cf Fix problem in dfee843 when non-occupant admin kicks an occupant (#1135) 2016-06-03 00:10:25 +02:00
Alexey Shchepin 0093326f7d Fix ejabberd.ldif 2016-06-02 18:56:27 +03:00
Alexey Shchepin 9ef52b8c64 Fix a typo 2016-06-02 18:22:50 +03:00
Alexey Shchepin d201f013b2 Stronger tests in the test suite, SQL updates and fixes 2016-06-02 18:09:58 +03:00
Badlop 5352037680 Report in SQL when scram is enabled but the stored password isn't (#1096) 2016-06-01 20:48:52 +02:00
Holger Weiss bbb90b9928 Ignore offline sessions
Let mod_admin_extra and mod_configure ignore offline sessions when
querying the session table.
2016-06-01 01:01:54 +02:00
Paweł Chmielowski 9c27f31d72 Process cover information on travis only if cover support is enabled 2016-05-31 12:12:46 +02:00
Christophe Romain e7843bf92b Fix set_presence API 2016-05-31 11:47:08 +02:00
Paweł Chmielowski db240413ab Disable cover analyzys on R17 as this causes problems with elixir tests 2016-05-31 11:29:19 +02:00
Paweł Chmielowski 8e883a76e3 Update test after roster code reorganization 2016-05-31 00:09:26 +02:00
Paweł Chmielowski 622bff23a4 Update test 2016-05-31 00:07:26 +02:00
Paweł Chmielowski be0dd51e51 Fix mod_http_api_test.exs 2016-05-30 23:06:29 +02:00
Paweł Chmielowski fc2b7018cc More strict check for commands with policy user 2016-05-30 23:06:29 +02:00
Paweł Chmielowski 17f87eb899 Fix tests 2016-05-30 23:06:29 +02:00
Paweł Chmielowski 1ade88402c Better code for setting up ejabberd app location 2016-05-30 23:06:29 +02:00
Paweł Chmielowski f252b9d489 Update acl tests to new internal access rules syntax 2016-05-30 14:36:17 +02:00
Paweł Chmielowski 1d3959b5a2 Make tests run correctly even when ejabberd src in not in ejabberd-xxx dir 2016-05-30 14:35:53 +02:00
Paweł Chmielowski e81302dc79 Allow @ inside acl user{,_glob,_regexp} to pass both user and server in single string 2016-05-30 12:30:44 +02:00
Paweł Chmielowski 9e68c4c0d9 Convert example config to use new syntax for access rules 2016-05-26 11:08:53 +02:00
Paweł Chmielowski 1981e13326 Allow passing username and ip to ejabberd_comamnds, and use it in mod_http_api 2016-05-26 11:08:53 +02:00
Paweł Chmielowski fffae97940 Use acl:access_matches in c2s 2016-05-26 11:08:53 +02:00
Paweł Chmielowski 49658e1655 New ACL infrastructure 2016-05-26 11:08:53 +02:00
Paweł Chmielowski c55319c81e Do not call transform_terms multiple times on configs when merging them 2016-05-26 11:08:53 +02:00
Paweł Chmielowski 13ead140f4 Copy lite.sql to place where tests expect it in failback mode 2016-05-26 11:08:53 +02:00
Badlop ca329826cb Retrieve parenthesis for easy reading, lost in old commit 9deb294 2016-05-25 12:44:05 +02:00
Evgeny Khramtsov 14b53fbcb0 Merge pull request #1131 from weiss/failed-resume-h
XEP-0198: Indicate number of handled stanzas if resumption fails
2016-05-25 11:56:47 +04:00
Evgeny Khramtsov b055c2a13a Merge pull request #1126 from weiss/muc-send-affiliation
Notify on MUC affiliation changes of non-occupants
2016-05-25 11:55:06 +04:00
Christophe Romain 639e9fab4e Merge pull request #1132 from weiss/publish-options
Add support for PubSub publishing options
2016-05-25 07:49:55 +00:00
Holger Weiss c958fa2f06 Add support for PubSub publishing options
Add code necessary to support publishing options as described in
XEP-0060, #7.1.5.  A node plugin that expects publishing options must
add <<"publish-options">> to the features/0 list and then handle the
publishing options handed over to the publish_item/7 call.

Signed-off-by: Christian Ulrich <christian@rechenwerk.net>
2016-05-25 08:40:12 +02:00
Holger Weiss 30e814dd4b XEP-0198: Add 'h' attribute to <failed/> element
If a resume request is rejected because the session timed out, indicate
the number of handled stanzas as per version 1.5 of XEP-0198.
2016-05-24 22:20:58 +02:00
Holger Weiss 8c16fdf59f mod_mam_mnesia: Clarify error message 2016-05-24 07:58:07 +02:00
Holger Weiss a2f0e157bc ejabberd_auth*: Fix indentation 2016-05-24 00:40:25 +02:00
Holger Weiss 2a9dd548b5 mod_mam_mnesia: Don't exceed table size limit
Don't write MAM messages into an Mnesia archive if the size of the table
comes close to the 2 GB limit for tables with disc-only copies.  That
way, the table is at least not corrupted when the limit is reached.
2016-05-24 00:25:52 +02:00
Holger Weiss 3f3ecad981 mod_mam_mnesia: Use transactions when writing
Let mod_mam_mnesia use transactions when storing or deleting messages.

If old messages of a user are to be removed, delete the user's archive
and rewrite it from scratch, as that seems to be much faster than
removing individual records with delete_object/1.

Closes #1065.
2016-05-24 00:08:23 +02:00
Holger Weiss 70452ba25a mod_register: Only set timeout on success
Don't set the registration timeout if the password was rejected for
being too weak.
2016-05-23 23:27:42 +02:00
Holger Weiss 1b02c5fbf3 Merge remote-tracking branch 'processone/pr/1122'
* processone/pr/1122:
  mod_client_state: Add function specifications
  mod_client_state: Add "queue_pep" option
  mod_client_state: Queue chat state notifications
  Move CSI queue handling into mod_client_state
2016-05-20 09:57:07 +02:00
Holger Weiss 9d87a4a6d4 mod_muc_room: Notify on affiliation changes
Notify the current room occupants if the affiliation of a non-occupant
is changed as per example 195 of XEP-0045.  In anonymous rooms, only
moderators are notified, though.
2016-05-20 01:28:16 +02:00
vthriller f6ba91ff97 mod_roster should probably respect roster item changes introduced with roster_process_item hooks upon pushing 2016-05-19 13:45:42 +03:00
Holger Weiss 420ae65590 mod_client_state: Add function specifications
Add function specifications and apply cosmetic changes to
mod_client_state.
2016-05-18 21:30:38 +02:00
Holger Weiss 8f72c27b88 mod_client_state: Add "queue_pep" option
If the new "queue_pep" option is enabled and the client is inactive, PEP
notifications are throttled in a similar way to presence stanzas and
chat states.  Only the most recent notification of a given node and
payload type will be queued from a given contact.
2016-05-17 22:12:04 +02:00
Holger Weiss 4f009e64fc mod_client_state: Queue chat state notifications
Queue standalone chat states instead of simply dropping them when the
client is inactive.  Only the most recent chat state of a given client
is queued.
2016-05-17 20:55:45 +02:00
Holger Weiss ba74c1c367 Move CSI queue handling into mod_client_state
Let mod_client_state handle the queueing of stanzas, not just their
classification.  This simplifies the ejabberd_c2s code and gives
(custom) CSI modules more flexibility.
2016-05-17 19:27:18 +02:00
Badlop ba2680df61 Delete duplicated command export_sql, use export2sql instead (#1118) 2016-05-16 17:57:57 +02:00
Tobias Kräntzer 2529acc36c Send unique stanza id and archived tag also in the message carbons
- Change order of the hooks in mod_mam for sending and receiving packets. Messages are archived before a carbon copy is send to the other recourcces.
- Add archived tag and unique stanza id to the outgoing packet to have message carbons with the archive information.
- Add additional hook (in mod_mam) to strip the archive tag for outgoing packets after message carbons have been send.
2016-05-15 20:13:25 +02:00
Holger Weiss ff199a323d Fix jid:from_string/1 function specification 2016-05-15 16:19:13 +02:00
Alexey Shchepin 64bb371285 Fix a typo 2016-05-13 18:22:59 +03:00
Alexey Shchepin 9bd446e519 Less strict extauth.py 2016-05-13 17:56:52 +03:00
Alexey Shchepin 792f47b4bd Update SQL escaping 2016-05-13 17:56:48 +03:00
Evgeniy Khramtsov be2a9e35ae Fix C2S session data leak (#1078) 2016-05-09 14:18:47 +03:00
Evgeniy Khramtsov 068db1a2d9 Handle Redis connection in a separate module 2016-05-09 08:36:30 +03:00
Holger Weiss 4717d64d7a mod_client_state: Delete only the configured hooks 2016-05-08 16:45:31 +02:00
Holger Weiss f7f40cf9a6 Let client retry HTTP upload on file size mismatch
Let the main mod_http_upload process look at the size of an HTTP upload
rather than performing this check in the ejabberd_http handler.  This
way, the upload slot won't be invalidated if the size of the uploaded
file doesn't match the size requested for the slot.  The PUT request is
still rejected, but the client now has a chance to retry the upload.
2016-05-08 15:36:51 +02:00
Holger Weiss bcf07fd032 Avoid error bounces when testing stream management
The test suite sends messages to the server JID while checking whether
the stream management code counts outgoing stanzas correctly.  We now
set type='headline' for those messages to avoid error bounces.
2016-05-06 16:37:17 +02:00
Holger Weiss ff4a0e1808 XEP-0198: Use different error message for bounces
When stanzas are bounced from the stream management queue (because the
session timed out or was closed for some other reason), use a different
error message so that this situation can be distinguished from other
cases.
2016-05-06 14:12:22 +02:00
Holger Weiss 51238bff83 Bounce messages sent to server JID
If a message is sent to the server JID (without node part), generate an
error message rather than dropping the message silently.
2016-05-06 13:59:21 +02:00
Badlop 86d5cf6d6c Don't require ejabberd to be installed to run "make translations" 2016-05-06 13:47:02 +02:00
Alexey Shchepin b2ffa1db96 Add missed jlib:term_to_expr and jlib:expr_to_term functions 2016-05-05 16:42:48 +03:00
Alexey Shchepin 0ea0ba3004 Update more SQL queries 2016-05-05 15:51:58 +03:00
Holger Weiss d6700bdc5b Merge remote-tracking branch 'processone/pr/1088'
* processone/pr/1088:
  Process messages of unknown type consistently
2016-05-05 00:20:15 +02:00
Christophe Romain 13c6430341 Add missing odbc->sql in comment from commit 1aae8a9f 2016-05-04 09:11:18 +02:00
Holger Weiss 12a8d915cd Merge remote-tracking branch 'processone/pr/1087'
* processone/pr/1087:
  Return error when blocking last activity request
2016-05-04 00:16:56 +02:00
Holger Wei 6cb60aaff5 Cosmetic change: Make variable names consistent
Use the same variable names in both mod_mam:select/8 clauses to avoid
confusion.
2016-05-03 19:12:57 +02:00
Holger Weiss 575ef9c619 Merge remote-tracking branch 'processone/pr/1102'
* processone/pr/1102:
  Fix ejabberdctl.template duplication
2016-05-03 19:07:11 +02:00
Matthias Rieber 5e5328da4a Fix ejabberdctl.template duplication 2016-05-03 16:36:20 +02:00
Holger Weiss 6da07d78b5 Merge remote-tracking branch 'processone/pr/1086'
* processone/pr/1086:
  Return error when blocking message to offline user
2016-05-02 21:08:06 +02:00
Paweł Chmielowski 0c0ce17bc0 Add ability to configure server loglevel when running tests 2016-05-02 15:25:30 +02:00
Christophe Romain 8a6c51290a Pass noauth when auth isn't provided 2016-05-02 15:07:00 +02:00
Juan Pablo Carlino 671bc4e573 Use MEDIUMTEXT type for muc_room.opts in MySQL schema 2016-05-02 15:06:40 +02:00
Christophe Romain 07196b6c62 Fix sender in case of explicit pep subscriptions 2016-05-02 15:04:14 +02:00
Paweł Chmielowski 53e1100cc4 Don't halt program when include_config_file is missing/can't be read 2016-05-02 14:52:23 +02:00
Evgeniy Khramtsov 47050db6b8 Don't forget to import mod_opt_type/1 in mod_metrics 2016-05-02 12:18:18 +03:00
Holger Weiss d54f211514 Add mod_opt_type/1 callback to gen_mod behaviour 2016-05-01 22:09:40 +02:00
Holger Weiss b46ed7044a Cope with modules that don't export mod_opt_type/1 2016-05-01 22:06:15 +02:00
Holger Weiss b202004862 ejabberdctl: Fix path to epmd 2016-05-01 21:29:59 +02:00
Evgeniy Khramtsov 8700a3401e Add tests for MUC MAM 2016-05-01 12:48:23 +03:00
Evgeniy Khramtsov 82082f1799 Add behaviour to mod_vcard_xupdate DB modules 2016-05-01 11:03:20 +03:00
Evgeniy Khramtsov 3493a87469 Fix typo in mod_mam:select() (#1098) 2016-04-30 21:37:18 +03:00
Christophe Romain 6a10916dda Let shaper cope with low resolution system clock
We no longer rely on getting unique values from clock source, so we need
to handle cope with systems which does not have a microsecond resolution
on system clock (such as MS Windows)
2016-04-29 10:57:34 +02:00
Christophe Romain 639c2fb640 Add pubsub subscribe/unsubscribe hook 2016-04-28 15:57:55 +02:00
Evgeniy Khramtsov c585f74730 Better formatting of configuration problem log message 2016-04-28 09:03:05 +03:00
Mickael Remond d18fe138aa Update esip and stun to match Fast TLS version 2016-04-27 16:22:47 +02:00
Christophe Romain d6f02a51e4 Use fast_tls 1.0.3 2016-04-27 16:18:24 +02:00
Evgeniy Khramtsov 9c369b7a8c Improve detection of databases supported by modules (#1092) 2016-04-27 17:10:50 +03:00
Christophe Romain d8bb5d9c01 Force ERL_PATH for elixir 2016-04-27 12:32:01 +02:00
Mickael Remond 26d3a978cc Update stun and esip dependencies 2016-04-27 12:19:55 +02:00
Mickael Remond 0f06ed8a9b Prepare 16.04 release 2016-04-27 11:59:05 +02:00
Mickael Remond d1db9f92c4 We now use fast_tls 1.0.2 2016-04-27 11:54:50 +02:00
Evgeniy Khramtsov 52fde758b3 Get rid of "internal" DB type. This also fixes #1092 2016-04-27 09:44:32 +03:00
Christophe Romain b79aef3bbc SCRIPT_DIR needed for release 2016-04-25 15:26:23 +02:00
Christophe Romain bae24464d3 Remove useless variable and quote EPMD and SPOOL_DIR 2016-04-25 14:26:04 +02:00
Christophe Romain ef90a389c1 Fix use of pubsub node plugin when configured with default_node_config 2016-04-25 09:44:46 +02:00
Holger Weiss 36164d9446 Return error when blocking last activity request
As per XEP-0016 and XEP-0191, return a service-unavailable error when an
incoming last activity query was blocked by a privacy list (just as we
do for other IQ requests).
2016-04-25 09:33:47 +02:00
Holger Weiss 45321fa2e2 Process messages of unknown type consistently
If an incoming message sent to an unavailable resource has an unknown
type, handle it like messages of type "normal" (as mandated by RFC 6121,
section 5.2.2).  The same is already done for messages of unknown type
sent to the bare JID of an offline user.
2016-04-25 01:13:41 +02:00
Holger Weiss 65ad70d7dc Fix error text for message bounces 2016-04-25 00:53:48 +02:00
Holger Weiss a37cf33358 Drop headline messages sent to offline resources
Don't bounce an error when a message of type "headline" is sent to an
unavailable resource.  This is consistent with how headline messages
sent to the bare JID of an offline user are dropped, and it avoids a
presence leak.
2016-04-25 00:02:12 +02:00
Holger Weiss 58478e52bf Don't omit bounces for messages of type "result" 2016-04-24 22:47:53 +02:00
Holger Weiss cebdfb6523 Return error when blocking message to offline user
As per XEP-0016 and XEP-0191, return a service-unavailable error when an
incoming message sent to an offline user was blocked by a privacy list.
The same is done for a message sent to an online user, so this avoids a
presence leak.
2016-04-24 22:00:15 +02:00
Holger Weiss b79f09d0eb Match namespace when checking for chat states
When checking for standalone chat states, match the namespace rather
than the names of the elements defined in the current XEP-0085 revision.
2016-04-24 17:16:28 +02:00
Holger Weiss beeb1c82d9 Fix check for standalone chat state notifications
Ignore whitespace (and other XML CDATA) when checking whether a message
stanza is a standalone chat state notification.
2016-04-24 17:09:56 +02:00
badlop 2660dcabba Merge pull request #931 from cclam0827/dev/mod_ping
change mod_ping Timers using maps instead of dict
2016-04-22 13:07:03 +02:00
Holger Weiss f0e6def3ed Set default value for pubsub#itemreply option 2016-04-21 23:47:07 +02:00
Christophe Romain 8487b51ecd Fix node ping command 2016-04-21 12:16:21 +02:00
Christophe Romain f7d4aae64d Use UUID for ctl node name (#1021) 2016-04-21 12:00:51 +02:00
Paweł Chmielowski 97e3a33077 Accept commands: add_commands syntax (along commands: - add_commands) 2016-04-21 11:16:36 +02:00
Evgeniy Khramtsov 1aae8a9fda Rename odbc to sql everywhere 2016-04-20 13:25:42 +03:00
Evgeniy Khramtsov fafeeb80c2 Rename ejabberd_sm_odbc -> ejabberd_sm_sql 2016-04-20 09:11:02 +03:00
Christophe Romain 655c22021b Fix hometree root check (#1070) 2016-04-19 15:18:32 +02:00
Holger Weiss 382c6ce1fb Specify type of second terminate/2 parameter 2016-04-19 09:15:09 +02:00
Paweł Chmielowski 0bdcafdb02 Use erlang 18.3 in travis tests 2016-04-18 14:25:41 +02:00
Badlop e5e4f39c01 Remove --auth in ejabberd_ctl.erl as it's useless, still useful for mod_rest 2016-04-15 15:35:57 +02:00
Evgeniy Khramtsov 222572bd56 Clean mod_carboncopy.erl from DB specific code 2016-04-15 15:48:56 +03:00
Evgeniy Khramtsov 64fdbe7866 Add mod_mam header file 2016-04-15 15:13:38 +03:00
Evgeniy Khramtsov fb0ecf3361 Merge branch 'move-db-code' 2016-04-15 15:12:12 +03:00
Evgeniy Khramtsov 52571e8624 Clean mod_mam.erl from DB specific code 2016-04-15 15:11:31 +03:00
Evgeniy Khramtsov 901d2e0aed Clean mod_offline.erl from DB specific code 2016-04-15 13:44:33 +03:00
Evgeniy Khramtsov 860db2ddca Clean mod_blocking.erl from DB specific code 2016-04-14 14:17:20 +03:00
Evgeniy Khramtsov c8c4a41b66 Clean mod_privacy.erl from DB specific code 2016-04-14 14:16:32 +03:00
Evgeniy Khramtsov 79d64e0d71 Clean mod_irc.erl from DB specific code 2016-04-14 12:18:04 +03:00
Evgeniy Khramtsov f8e3560ad2 Clean mod_shared_roster.erl from DB specific code 2016-04-14 11:45:43 +03:00
Evgeniy Khramtsov 398f1de5ae Clean mod_roster.erl from DB specific code 2016-04-14 10:58:32 +03:00
Evgeniy Khramtsov 0b439a7d5b Clean mod_muc.erl from DB specific code 2016-04-13 21:07:32 +03:00
Evgeniy Khramtsov ae69f09257 Clean mod_vcard.erl from DB specific code 2016-04-13 17:37:52 +03:00
Evgeniy Khramtsov ef70ce65ab Clean mod_private.erl from DB specific code 2016-04-13 14:09:34 +03:00
Evgeniy Khramtsov b5d1ce795f Clean mod_announce.erl from DB specific code 2016-04-13 13:04:04 +03:00
Evgeniy Khramtsov cd094bc903 Clean mod_caps.erl from DB specific code 2016-04-13 11:41:04 +03:00
Evgeniy Khramtsov 2d7e03f5e1 Clean mod_vcard_xupdate.erl from DB specific code 2016-04-13 11:06:59 +03:00
Mickaël Rémond b2abc1edb7 Add preliminary tests on ACL module and prepare clean-up / refactor 2016-04-13 09:45:56 +02:00
Evgeniy Khramtsov 7fd4808cde Clean mod_last.erl from DB specific code 2016-04-13 09:59:39 +03:00
Evgeniy Khramtsov 5eef8a8bcf Make it possible to get DB backend of a module 2016-04-13 09:56:10 +03:00
Mickael Remond cd2e2b1a88 Synchronizing master changes 2016-04-12 10:34:24 +02:00
Mickael Remond 5958a91428 Fix typo in error message 2016-04-12 10:27:43 +02:00
Holger Weiss 15d184a909 Disable TLS compression for s2s by default
TLS compression is not recommended, and it's already disabled by default
for c2s connections and for ejabberd_http.
2016-04-11 22:50:11 +02:00
Badlop 8c8a6869be process2/2 is needed by mod_rest to provide its own AccessCommands
See processone/ejabberd-contrib#161
2016-04-11 17:21:44 +02:00
Badlop 695592a38c Update check_password tests are the command now is fixed 2016-04-11 13:39:35 +02:00
badlop bd3b7cd2df Merge pull request #1064 from sezuan/fix_check_password
Fix check_password
2016-04-11 13:38:22 +02:00
Matthias Rieber b67dc00db2 Fix check_password 2016-04-10 15:37:36 +02:00
Mickael Remond 127342449e Allow testing user pattern directly in access rules 2016-04-08 19:45:25 +02:00
Paweł Chmielowski b4739396ec Switch to varchar(64) in mysql user.server/salt as text can't have default values 2016-04-08 17:50:59 +02:00
Mickael Remond 1dbdd58b1b Add TODO to improve ACL 2016-04-08 12:55:35 +02:00
Evgeniy Khramtsov c5dbdfc71a 'serverkey' and 'salt' should have empty string as default 2016-04-08 13:02:08 +03:00
Paweł Chmielowski 86dfbe6ece Make sure that ejabberd_sm sid are unique 2016-04-08 10:52:29 +02:00
Evgeniy Khramtsov b83ec483e9 Send stream trailer at the very end 2016-04-08 11:49:50 +03:00
Paweł Chmielowski afd3accf75 Generate shorted jid for anonymous connections 2016-04-07 16:47:30 +02:00
Paweł Chmielowski 0935f493ee Add tests for anonymous and digest-md5 auth 2016-04-07 16:47:01 +02:00
Mickael Remond 373f9fb0eb Add tests on Access rules returning values 2016-04-07 13:04:58 +02:00
Mickael Remond 60c0c8e968 Add test when mixing ip / user rules 2016-04-07 12:35:29 +02:00
Paweł Chmielowski 18557fa3d6 Start of tests for cyrsasl module 2016-04-07 12:28:19 +02:00
Mickael Remond a938af4180 IP based ACL / Access rules and sequential evaluation of rules 2016-04-07 12:06:30 +02:00
Paweł Chmielowski d5c29360fb Fix anonymous auth 2016-04-07 10:02:37 +02:00
Mickael Remond 48a1d818d6 Rebase master 2016-04-06 18:14:47 +02:00
Mickael Remond eb36440c2e Variant for user ACL test 2016-04-06 18:13:08 +02:00
Mickael Remond 47039aed15 Allow clearing all ACL and access rules 2016-04-06 18:13:08 +02:00
Mickael Remond d45ad3e3a5 Add initial basic ACL test 2016-04-06 18:13:08 +02:00
Mickael Remond 5ad8c790c7 Export add_access/3 to allow setting ACL outside of yaml config file 2016-04-06 18:13:08 +02:00
Mickael Remond 5efcf0a175 Stringprep can already be started, do not check result 2016-04-06 18:11:46 +02:00
Mickael Remond 0916694e0e Merge branch 'master' of github.com:processone/ejabberd 2016-04-06 17:56:01 +02:00
Mickael Remond 2900aa208f Log Elixir test result for investigation and include this log file in travis for troubleshooting failed tests 2016-04-06 17:55:56 +02:00
Badlop 27b4217a9d Tweak srg_get_info result formatting (#1048) 2016-04-06 17:55:19 +02:00
Mickael Remond a9e50468b6 Better error message in logs 2016-04-06 15:07:44 +02:00
Mickael Remond abf768274a Fix error message paramater formatting 2016-04-06 15:05:19 +02:00
Mickael Remond f78b170c24 Add initial basic ACL test 2016-04-06 13:59:33 +02:00
Mickael Remond 991529a657 Export add_access/3 to allow setting ACL outside of yaml config file 2016-04-06 13:59:06 +02:00
Mickael Remond b2279d481d Merge branch 'master' of github.com:processone/ejabberd 2016-04-06 13:57:12 +02:00
Mickael Remond 267fdb2e95 Now we need to start stringprep before config 2016-04-06 13:51:05 +02:00
badlop d9f1061b8a Merge pull request #1051 from genric/patch-1
Fix mod_muc_admin:set_room_affiliation options persistence
2016-04-06 13:44:12 +02:00
badlop dd654fa794 Merge pull request #1052 from genric/patch-2
Fix mod_muc_admin:get_room_options
2016-04-06 13:25:46 +02:00
Mickael Remond 1b9b5f8e6a Merge branch 'master' of github.com:processone/ejabberd 2016-04-06 12:59:58 +02:00
Mickael Remond 67b9b82261 We need to set hosts in options to be able to retrieve 'MYHOSTS' 2016-04-06 12:59:27 +02:00
Mickaël Rémond 2eee2de6e2 Merge pull request #1054 from richp10/master
upgrade stringprep to 1.03
2016-04-06 12:05:26 +02:00
richp10 acc11195f8 upgrade stringprep to 1.03 2016-04-06 08:40:10 +00:00
genric be7f65da05 Fix mod_muc_admin:get_room_options
Fix mod_muc_admin:get_room_options to match the ejabberd_commands result spec.
2016-04-05 14:13:28 +02:00
Evgeniy Khramtsov 232915184c Merge branch 'add-error-reason' 2016-04-05 13:10:09 +03:00
Evgeniy Khramtsov 9ac6e4edf7 Replace more ?ERR_* macros with ?ERRT_* 2016-04-05 13:09:44 +03:00
genric 490aa2c6a6 Fix mod_muc_admin:set_room_affiliation
Add missing options so they are stored when set_room_affiliation is invoked, instead of being ignored and set to default values after muc restart.
2016-04-04 14:02:34 +02:00
Mickaël Rémond ca9ac019eb Merge pull request #1046 from processone/commands-update
Add support for versioning in ejabberd commands
2016-04-01 14:24:08 +02:00
Mickael Remond e24da5789e Apply fixes and remove tests for missing methods 2016-04-01 13:05:41 +02:00
Mickael Remond 47266de6d7 Do not use underscore variable 2016-04-01 12:24:49 +02:00
Mickael Remond f243c30847 Rollback mod_admin_extra 2016-04-01 12:24:00 +02:00
Mickael Remond 2a2a47b5c6 Fix merge issues 2016-04-01 12:12:19 +02:00
Mickael Remond b5f1479763 Fix tests, they are now running fine locally 2016-04-01 11:13:48 +02:00
Mickael Remond a8f92ae767 Add logger macro to help troubleshooting Elixir tests 2016-04-01 11:11:42 +02:00
Mickael Remond 97d345d287 Port mod_admin_extra test to work with new API 2016-03-31 22:01:57 +02:00
Mickael Remond ef2e2e45b3 Fix failing tests 2016-03-31 17:34:58 +02:00
Mickael Remond 3290a5ff15 Force protobuffs version to be able to use new meck release 2016-03-31 16:05:18 +02:00
Mickael Remond 7988e2e350 Merge lastest commits from master 2016-03-31 15:37:21 +02:00
Mickael Remond dc0ca51ef1 Try keeping names of test same as master to limit merge conflicts 2016-03-31 15:06:41 +02:00
Mickael Remond 78c4a0d65f Add test file whose name was conflicting during merge 2016-03-31 14:42:08 +02:00
Mickael Remond 91233eafaa Use version of moka that do not depend on proper 2016-03-31 14:36:30 +02:00
Mickael Remond da0751239c Rename conflicting test file after merge 2016-03-31 14:33:34 +02:00
Alexey Shchepin 3dc55c6d47 Commands refactor, first pass.
- add API versionning
- changed error handling, based on exception
- commands moved/merged from mod_admin_p1 to mod_admin_extra
- command bufixes
- add some elixir unit test cases

Squashed commit of the following:

commit dd59855b3486f78a9349756e4f102e79b3accff8
Merge: 14e8ffc 506e08e
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Fri Oct 30 11:43:18 2015 +0100

    Merge branch '3.2.x' into api

commit 14e8ffce78cbea6c8605371d1fc50a0c1d1e012c
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Tue Oct 27 16:35:17 2015 +0100

    Added OAuth tests to ejabberd_commands

commit f81c550c14628edfe4861c228576cb767924366a
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Tue Oct 27 16:34:55 2015 +0100

    Added some mod_http_api tests

commit 6a64578d5b2ba532a2feb6503ed98561e56d5d53
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Mon Oct 26 15:29:36 2015 +0100

    Fix get_last command test

    Previous version won't work with dst.

commit 27e0cde9e9c1f001effe68f8424a365ad947c068
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Fri Oct 23 17:59:34 2015 +0200

    Add tests on admin command policy

commit 19dad8d54f54c9fabd454280483cccfb06c8e78a
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Fri Oct 23 16:49:36 2015 +0200

    Added command related tests (http api & user policy)

commit e0e596ab4a3f3a70aba5f374f028939ab794de33
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Fri Oct 23 16:49:16 2015 +0200

    Fix command call.

commit 128cd7d1ede3c47a34f8ec3a750c980ccad2c61d
Merge: 60c4c4c 447313c
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Thu Oct 22 14:48:39 2015 +0200

    Merge branch '3.2.x' into api

commit 60c4c4c0751302524c14219c6bc8c56a6069a689
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Thu Oct 22 14:45:57 2015 +0200

    Fix ejabberd_commands spec.

commit 8e145c28c5da762c2b93ee32327eff1db94ebfed
Merge: 397273a f13dc94
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Wed Oct 21 18:26:07 2015 +0200

    Merge branch '3.2.x' into api

commit 397273a23ed415feac87aed33da6452229793387
Merge: c30e89b f289e27
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Wed Oct 21 15:27:45 2015 +0200

    Merge branch '3.2.x' into api

commit c30e89bb8a0013bff37e61e4c6953350c9c1f313
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Wed Oct 21 12:47:02 2015 +0200

    Merge mod_http_api

commit 7b0db22b4acd48ff6fabce41c1b2525e6580a3c5
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Fri Oct 16 11:55:48 2015 +0200

    Fix exunit tests to run with common_test suites

commit d8b1a89800ac7379a57a7eb4a09c3c93c3e1e5eb
Merge: 2879ae8 63455b3
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Thu Oct 15 11:39:45 2015 +0200

    Merge branch '3.2.x' into api

commit 2879ae87ff3eee369ef3d780136b96ecff5285d1
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Wed Oct 14 14:53:44 2015 +0200

    Fix update_roster command.

commit a1d453dd7a3afda9861a8d747494a45057ad574b
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Tue Oct 13 16:14:28 2015 +0200

    API commands refactor

    Moving and/or merging commands from mod_admin_p1 to mod_admin_extra

commit b709ed26b0fc0ca4f3bdd5a59fa58ec7e3db97fa
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Wed Oct 7 15:10:01 2015 +0200

    Add tests on commands

commit 6711687bee9c672cb3d5aed0744e13420ecf6dbd
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Tue Sep 29 15:58:16 2015 +0200

    Add ejabberd_commands tests

commit df8682f419cf3877e77e36a19bca0fc55dc991f8
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Mon Sep 28 14:54:39 2015 +0200

    Added API versioning for ejabberdctl and rest commands

commit cd017b0e3aac431bc3ee807ceb7f8641e1523ef5
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Fri Sep 18 11:21:45 2015 +0200

    Better error handling of HTTP API commands.

commit ca5cb6acd8e4643f9d6c484d2277b0d7e88471e5
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Tue Sep 15 15:03:05 2015 +0200

    add commands to mod_admin_extra:
    - get_offline_count
    - get_presence
    - change_password

commit 7f583fa099e30ac2b0915669fd8f102ac565b833
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Tue Sep 15 15:02:16 2015 +0200

    Improve REST API error handling

commit 14753b1c02cdce434a786b7f80f6c09f0d210075
Author: Jerome Sautret <jerome.sautret@process-one.net>
Date:   Mon Sep 14 10:51:17 2015 +0200

    Change REST API return codes for integer type.
2016-03-31 14:53:31 +03:00
Mickael Remond d35c5ebde5 Test / Document ejabberd_commands checks 2016-03-31 13:14:06 +02:00
Mickael Remond 3cfcdbb245 Check that various type of commands are properly rejected without auth 2016-03-31 12:38:53 +02:00
Mickaël Rémond 7c2998a55d Merge pull request #1044 from processone/http-api
Add ability to call open ejabberd commands through ReST API
2016-03-31 11:37:14 +02:00
Evgeniy Khramtsov fced8dc3d9 Replace some ?ERR_* macros with ?ERRT_* 2016-03-31 11:00:29 +03:00
Mickael Remond c00cfca8e7 mix version updated for 16.03 release 2016-03-30 19:21:12 +02:00
Mickael Remond 3c480a5b0b Fix Dialyzer inconsistency 2016-03-30 16:47:40 +02:00
Mickael Remond 809057678b Better error report when command is not exposed through API 2016-03-30 15:59:29 +02:00
Mickael Remond 36ac1cd6c7 Returns unauthorized error when we do not have correct credentials 2016-03-30 14:49:19 +02:00
Mickael Remond ead83b008c HTTP ReST API now supports 'open' ejabberd commands 2016-03-30 14:23:09 +02:00
Mickael Remond 6f25122f8c Support flagging so Elixir tests as pending 2016-03-30 13:59:01 +02:00
Richard ae77b1300a change mod_ping Timers using maps instead of dict 2016-01-29 00:07:38 +08:00
208 changed files with 14002 additions and 8093 deletions
+2 -1
View File
@@ -2,7 +2,7 @@ language: erlang
otp_release:
- 17.5
- 18.2.1
- 18.3
services:
- riak
@@ -59,6 +59,7 @@ after_script:
- find logs -name suite.log -exec cat '{}' ';'
after_failure:
- find logs -name exunit.log -exec cat '{}' ';'
# Try checking Riak database logs
- tail -n 100000 /var/log/riak/*.log
- find logs -name ejabberd.log -exec cat '{}' ';'
+2 -1
View File
@@ -184,7 +184,8 @@ install: all copy-files
-e "s*{{sysconfdir}}*@sysconfdir@*" \
-e "s*{{localstatedir}}*@localstatedir@*" \
-e "s*{{docdir}}*@docdir@*" \
-e "s*{{erl}}*@ERL@*" ejabberdctl.template \
-e "s*{{erl}}*@ERL@*" \
-e "s*{{epmd}}*@EPMD@*" ejabberdctl.template \
> ejabberdctl.example
[ -f $(ETCDIR)/ejabberdctl.cfg ] \
&& $(INSTALL) -b -m 640 $(G_USER) ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg-new \
+1
View File
@@ -30,6 +30,7 @@ fi
AC_PATH_TOOL(ERL, erl, , [${extra_erl_path}$PATH])
AC_PATH_TOOL(ERLC, erlc, , [${extra_erl_path}$PATH])
AC_PATH_TOOL(EPMD, epmd, , [${extra_erl_path}$PATH])
AC_ERLANG_NEED_ERL
AC_ERLANG_NEED_ERLC
@@ -242,9 +242,7 @@ print_usage() ->
print_po_header(File) ->
MsgProps = get_msg_header_props(File),
{Language, [LastT | AddT]} = prepare_props(MsgProps),
application:load(ejabberd),
{ok, Version} = application:get_key(ejabberd, vsn),
print_po_header(Version, Language, LastT, AddT).
print_po_header(Language, LastT, AddT).
get_msg_header_props(File) ->
{ok, F} = file:open(File, [read]),
@@ -274,12 +272,11 @@ prepare_props(MsgProps) ->
Authors = proplists:get_all_values("Author:", MsgProps),
{Language, Authors}.
print_po_header(Version, Language, LastTranslator, AdditionalTranslatorsList) ->
print_po_header(Language, LastTranslator, AdditionalTranslatorsList) ->
AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
HeaderString =
"msgid \"\"\n"
"msgstr \"\"\n"
"\"Project-Id-Version: " ++ Version ++ "\\n\"\n"
++ "\"X-Language: " ++ Language ++ "\\n\"\n"
"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
++ AdditionalTranslatorsString ++
@@ -157,7 +157,7 @@ extract_lang_srcmsg2po ()
echo $MSGS_PATH
cd $SRC_DIR
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
sed -e 's/ \[\]$/ \"\"/g;' $PO_PATH.1 > $PO_PATH.2
msguniq --sort-by-file $PO_PATH.2 --output-file=$PO_PATH
@@ -176,7 +176,7 @@ extract_lang_src2pot ()
echo "" >>$MSGS_PATH
cd $SRC_DIR
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
sed -e 's/ \[\]$/ \"\"/g;' $POT_PATH.1 > $POT_PATH.2
#msguniq --sort-by-file $POT_PATH.2 $EJA_MSGS_DIR --output-file=$POT_PATH
+67 -65
View File
@@ -254,10 +254,10 @@ auth_method: internal
## extauth_program: "/path/to/authentication/script"
##
## Authentication using ODBC
## Authentication using SQL
## Remember to setup a database in the next section.
##
## auth_method: odbc
## auth_method: sql
##
## Authentication using PAM
@@ -330,26 +330,26 @@ auth_method: internal
##
## MySQL server:
##
## odbc_type: mysql
## odbc_server: "server"
## odbc_database: "database"
## odbc_username: "username"
## odbc_password: "password"
## sql_type: mysql
## sql_server: "server"
## sql_database: "database"
## sql_username: "username"
## sql_password: "password"
##
## If you want to specify the port:
## odbc_port: 1234
## sql_port: 1234
##
## PostgreSQL server:
##
## odbc_type: pgsql
## odbc_server: "server"
## odbc_database: "database"
## odbc_username: "username"
## odbc_password: "password"
## sql_type: pgsql
## sql_server: "server"
## sql_database: "database"
## sql_username: "username"
## sql_password: "password"
##
## If you want to specify the port:
## odbc_port: 1234
## sql_port: 1234
##
## If you use PostgreSQL, have a large database, and need a
## faster but inexact replacement for "select count(*) from users"
@@ -359,25 +359,25 @@ auth_method: internal
##
## SQLite:
##
## odbc_type: sqlite
## odbc_database: "/path/to/database.db"
## sql_type: sqlite
## sql_database: "/path/to/database.db"
##
## ODBC compatible or MSSQL server:
##
## odbc_type: odbc
## odbc_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
## sql_type: odbc
## sql_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
##
## Number of connections to open to the database for each virtual host
##
## odbc_pool_size: 10
## sql_pool_size: 10
##
## Interval to make a dummy SQL request to keep the connections to the
## database alive. Specify in seconds: for example 28800 means 8 hours
##
## odbc_keepalive_interval: undefined
## sql_keepalive_interval: undefined
###. ===============
###' TRAFFIC SHAPERS
@@ -408,14 +408,14 @@ acl:
##
## admin:
## user:
## - "aleksey": "localhost"
## - "ermine": "example.org"
## - "aleksey@localhost"
## - "ermine@example.org"
##
## Blocked users
##
## blocked:
## user:
## - "baduser": "example.org"
## - "baduser@example.org"
## - "test"
## Local users: don't modify this.
@@ -431,7 +431,7 @@ acl:
## - "jabber.org"
## aleksey:
## user:
## - "aleksey": "jabber.ru"
## - "aleksey@jabber.ru"
## test:
## user_regexp: "^test"
## user_glob: "test*"
@@ -459,61 +459,61 @@ acl:
## acl:
## admin:
## user:
## - "bob-local": "localhost"
## - "bob-local@localhost"
###. ============
###' SHAPER RULES
shaper_rules:
## Maximum number of simultaneous sessions allowed for a single user:
max_user_sessions: 10
## Maximum number of offline messages that users can have:
max_user_offline_messages:
- 5000: admin
- 100
## For C2S connections, all users except admins use the "normal" shaper
c2s_shaper:
- none: admin
- normal
## All S2S connections use the "fast" shaper
s2s_shaper: fast
###. ============
###' ACCESS RULES
access:
## Maximum number of simultaneous sessions allowed for a single user:
max_user_sessions:
all: 10
## Maximum number of offline messages that users can have:
max_user_offline_messages:
admin: 5000
all: 100
access_rules:
## This rule allows access only for local users:
local:
local: allow
local:
- allow: local
## Only non-blocked users can use c2s connections:
c2s:
blocked: deny
all: allow
## For C2S connections, all users except admins use the "normal" shaper
c2s_shaper:
admin: none
all: normal
## All S2S connections use the "fast" shaper
s2s_shaper:
all: fast
c2s:
- deny: blocked
- allow
## Only admins can send announcement messages:
announce:
admin: allow
announce:
- allow: admin
## Only admins can use the configuration interface:
configure:
admin: allow
## Admins of this server are also admins of the MUC service:
muc_admin:
admin: allow
- allow: admin
## Only accounts of the local ejabberd server can create rooms:
muc_create:
local: allow
## All users are allowed to use the MUC service:
muc:
all: allow
- allow: local
## Only accounts on the local ejabberd server can create Pubsub nodes:
pubsub_createnode:
local: allow
- allow: local
## In-band registration allows registration of any possible username.
## To disable in-band registration, replace 'allow' with 'deny'.
register:
all: allow
- allow
## Only allow to register from localhost
trusted_network:
loopback: allow
- allow: loopback
## Do not establish S2S connections with bad servers
## s2s:
## bad_servers: deny
## all: allow
## - deny:
## - ip: "XXX.XXX.XXX.XXX/32"
## - deny:
## - ip: "XXX.XXX.XXX.XXX/32"
## - allow
## By default the frequency of account registrations from the same IP
## is limited to 1 account every 10 minutes. To disable, specify: infinity
@@ -526,10 +526,10 @@ access:
## "localhost":
## access:
## c2s:
## admin: allow
## all: deny
## - allow: admin
## - deny
## register:
## all: deny
## - deny
###. ================
###' DEFAULT LANGUAGE
@@ -590,10 +590,12 @@ modules:
mod_last: {}
mod_muc:
## host: "conference.@HOST@"
access: muc
access:
- allow
access_admin:
- allow: admin
access_create: muc_create
access_persistent: muc_create
access_admin: muc_admin
## mod_muc_log: {}
## mod_multicast: {}
mod_offline:
+44 -108
View File
@@ -13,7 +13,7 @@ ERLANG_NODE=ejabberd@localhost
SCRIPT_DIR=`cd ${0%/*} && pwd`
ERL={{erl}}
IEX={{bindir}}/iex
EPMD={{bindir}}/epmd
EPMD={{epmd}}
INSTALLUSER={{installuser}}
ERL_LIBS={{libdir}}
@@ -83,18 +83,9 @@ if [ "$EJABBERD_DOC_PATH" = "" ] ; then
fi
if [ "$ERLANG_NODE_ARG" != "" ] ; then
ERLANG_NODE=$ERLANG_NODE_ARG
NODE=${ERLANG_NODE%@*}
fi
if [ "{{release}}" != "true" ] ; then
if [ "$EJABBERDDIR" = "" ] ; then
EJABBERDDIR={{libdir}}/ejabberd
fi
if [ "$EJABBERD_PRIV_PATH" = "" ] ; then
EJABBERD_PRIV_PATH=$EJABBERDDIR/priv
fi
if [ "$EJABBERD_BIN_PATH" = "" ] ; then
EJABBERD_BIN_PATH=$EJABBERD_PRIV_PATH/bin
fi
if [ "{{release}}" != "true" -a "$EJABBERD_BIN_PATH" = "" ] ; then
EJABBERD_BIN_PATH={{libdir}}/ejabberd/priv/bin
fi
EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
DATETIME=`date "+%Y%m%d-%H%M%S"`
@@ -141,8 +132,8 @@ fi
[ -z "$date" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_date '$date'"
[ -z "$EJABBERD_OPTS" ] || EJABBERD_OPTS="-ejabberd ${EJABBERD_OPTS}"
[ -d $SPOOL_DIR ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
cd $SPOOL_DIR
[ -d "$SPOOL_DIR" ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
cd "$SPOOL_DIR"
# export global variables
export EJABBERD_CONFIG_PATH
@@ -199,8 +190,8 @@ start()
debug()
{
debugwarning
TTY=`tty | sed -e 's/.*\///g'`
CMD="`shell_escape \"$ERL\" \"$NAME\" \"debug-${TTY}-${ERLANG_NODE}\"` \
NID=$(uid debug)
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
-remsh $ERLANG_NODE \
-hidden \
$KERNEL_OPTS \
@@ -213,15 +204,15 @@ debug()
iexdebug()
{
debugwarning
TTY=`tty | sed -e 's/.*\///g'`
# Elixir shell is hidden as default
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"debug-${TTY}-${ERLANG_NODE}\"` \
NID=$(uid debug)
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"$NID\"` \
-remsh $ERLANG_NODE \
--erl `shell_escape \"$KERNEL_OPTS\"` \
--erl `shell_escape \"$ERLANG_OPTS\"` \
--erl `shell_escape \"${ARGS[@]}\"` \
--erl `shell_escape_str \"$@\"`"
$EXEC_CMD "$CMD"
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
}
# start interactive server
@@ -251,7 +242,7 @@ iexlive()
--erl `shell_escape \"$ERLANG_OPTS\"` \
--erl `shell_escape \"${ARGS[@]}\"` \
--erl `shell_escape_str \"$@\"`"
$EXEC_CMD "$CMD"
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
}
# start server in the foreground
@@ -319,27 +310,27 @@ livewarning()
etop()
{
TTY=`tty | sed -e 's/.*\///g'`
NID=$(uid top)
$EXEC_CMD "$ERL \
$NAME debug-${TTY}-${ERLANG_NODE} \
$NAME $NID \
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
}
ping()
{
TTY=`tty | sed -e 's/.*\///g'`
if [ "$1" = "${1%.*}" ] ; then
[ -z "$1" ] && PEER=${ERLANG_NODE} || PEER=$1
if [ "$PEER" = "${PEER%.*}" ] ; then
PING_NAME="-sname"
PING_NODE=$(hostname -s)
else
PING_NAME="-name"
PING_NODE=$(hostname)
fi
NID=$(uid ping ${PING_NODE})
$EXEC_CMD "$ERL \
$PING_NAME ping-${TTY}@${PING_NODE} \
-hidden \
$KERNEL_OPTS $ERLANG_OPTS \
-eval 'io:format(\"~p~n\",[net_adm:ping('\"'\"'$1'\"'\"')])' \
$PING_NAME $NID \
-hidden $KERNEL_OPTS $ERLANG_OPTS \
-eval 'io:format(\"~p~n\",[net_adm:ping('\"'\"'$PEER'\"'\"')])' \
-s erlang halt -output text -noinput"
}
@@ -367,97 +358,42 @@ help()
# common control function
ctl()
{
# Control number of connections identifiers
# using flock if available. Expects a linux-style
# flock that can lock a file descriptor.
MAXCONNID=100
CONNLOCKDIR={{localstatedir}}/lock/ejabberdctl
FLOCK=/usr/bin/flock
if [ ! -x "$FLOCK" ] || [ ! -d "$CONNLOCKDIR" ] ; then
JOT=/usr/bin/jot
if [ ! -x "$JOT" ] ; then
# no flock or jot, simply invoke ctlexec()
CTL_CONN="ctl-${ERLANG_NODE}"
ctlexec $CTL_CONN "$@"
result=$?
else
# no flock, but at least there is jot
RAND=`jot -r 1 0 $MAXCONNID`
CTL_CONN="ctl-${RAND}-${ERLANG_NODE}"
ctlexec $CTL_CONN "$@"
result=$?
fi
else
# we have flock so we get a lock
# on one of a limited number of
# conn names -- this allows
# concurrent invocations using a bound
# number of atoms
for N in `seq 1 $MAXCONNID`; do
CTL_CONN="ctl-$N-${ERLANG_NODE}"
CTL_LOCKFILE="$CONNLOCKDIR/$CTL_CONN"
(
exec 8>"$CTL_LOCKFILE"
if flock --nb 8; then
ctlexec $CTL_CONN "$@"
ssresult=$?
# segregate from possible flock exit(1)
ssresult=`expr $ssresult \* 10`
exit $ssresult
else
exit 1
fi
)
result=$?
if [ $result -eq 1 ] ; then
# means we errored out in flock
# rather than in the exec - stay in the loop
# trying other conn names...
badlock=1
else
badlock=""
break;
fi
done
result=`expr $result / 10`
fi
if [ "$badlock" ] ;then
echo "Ran out of connections to try. Your ejabberd processes" >&2
echo "may be stuck or this is a very busy server. For very" >&2
echo "busy servers, consider raising MAXCONNID in ejabberdctl">&2
exit 1;
fi
case $result in
0) :;;
1) :;;
2) help;;
3) help;;
esac
return $result
}
ctlexec()
{
CONN_NAME=$1; shift
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$CONN_NAME\"` \
NID=$(uid ctl)
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
-noinput -hidden $KERNEL_OPTS -s ejabberd_ctl \
-extra `shell_escape \"$ERLANG_NODE\"` $EJABBERD_NO_TIMEOUT \
`shell_escape \"$@\"`"
$EXEC_CMD "$CMD"
result=$?
case $result in
2) help;;
3) help;;
*) :;;
esac
return $result
}
uid()
{
uuid=$(uuidgen 2>/dev/null)
[ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(</proc/sys/kernel/random/uuid)
[ -z "$uuid" ] && uuid=$(printf "%X" $RANDOM$(date +%M%S)$$)
uuid=${uuid%%-*}
[ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE}
[ $# -eq 1 ] && echo ${uuid}-${1}-${ERLANG_NODE}
[ $# -eq 2 ] && echo ${uuid}-${1}@${2}
}
# stop epmd if there is no other running node
stop_epmd()
{
$EPMD -names 2>/dev/null | grep -q name || $EPMD -kill >/dev/null
"$EPMD" -names 2>/dev/null | grep -q name || "$EPMD" -kill >/dev/null
}
# make sure node not already running and node name unregistered
check_start()
{
$EPMD -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
ps ux | grep -v grep | grep -q " $ERLANG_NODE " && {
echo "ERROR: The ejabberd node '$ERLANG_NODE' is already running."
exit 4
@@ -468,7 +404,7 @@ check_start()
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
exit 5
} || {
$EPMD -kill >/dev/null
"$EPMD" -kill >/dev/null
}
}
}
@@ -502,9 +438,9 @@ case "${ARGS[0]}" in
'live') live;;
'iexlive') iexlive;;
'foreground') foreground;;
'ping'*) ping ${ARGS# ping};;
'ping'*) ping ${ARGS[1]};;
'etop') etop;;
'started') wait_for_status 0 30 2;; # wait 30x2s before timeout
'stopped') wait_for_status 3 15 2 && stop_epmd;; # wait 15x2s before timeout
'stopped') wait_for_status 3 30 2 && stop_epmd;; # wait 30x2s before timeout
*) ctl "${ARGS[@]}";;
esac
+3 -2
View File
@@ -31,8 +31,9 @@
tags = [] :: [atom()] | '_' | '$2',
desc = "" :: string() | '_' | '$3',
longdesc = "" :: string() | '_',
module :: atom(),
function :: atom(),
version = 0 :: integer(),
module :: atom() | '_',
function :: atom() | '_',
args = [] :: [aterm()] | '_' | '$1' | '$2',
policy = restricted :: open | restricted | admin | user,
result = {res, rescode} :: rterm() | '_' | '$2',
+1 -2
View File
@@ -23,8 +23,7 @@
path = [] :: [binary()],
q = [] :: [{binary() | nokey, binary()}],
us = {<<>>, <<>>} :: {binary(), binary()},
auth :: {binary(), binary()} |
{auth_jid, {binary(), binary()}, jlib:jid()},
auth :: {binary(), binary()} | {oauth, binary(), []} | undefined,
lang = <<"">> :: binary(),
data = <<"">> :: binary(),
ip :: {inet:ip_address(), inet:port_number()},
+3 -2
View File
@@ -3,10 +3,11 @@
-record(session, {sid, usr, us, priority, info}).
-record(session_counter, {vhost, count}).
-type sid() :: {erlang:timestamp(), pid()}.
-type sid() :: {erlang:timestamp(), pid()} | {erlang:timestamp(), undefined}.
-type ip() :: {inet:ip_address(), inet:port_number()} | undefined.
-type info() :: [{conn, atom()} | {ip, ip()} | {node, atom()}
| {oor, boolean()} | {auth_module, atom()}].
| {oor, boolean()} | {auth_module, atom()}
| {num_stanzas_in, non_neg_integer()}].
-type prio() :: undefined | integer().
-endif.
+2 -2
View File
@@ -23,9 +23,9 @@
-define(SQL_UPSERT_MARK, sql_upsert__mark_).
-define(SQL_UPSERT(Host, Table, Fields),
ejabberd_odbc:sql_query(Host, ?SQL_UPSERT_MARK(Table, Fields))).
ejabberd_sql:sql_query(Host, ?SQL_UPSERT_MARK(Table, Fields))).
-define(SQL_UPSERT_T(Table, Fields),
ejabberd_odbc:sql_query_t(?SQL_UPSERT_MARK(Table, Fields))).
ejabberd_sql:sql_query_t(?SQL_UPSERT_MARK(Table, Fields))).
-record(sql_query, {hash, format_query, format_res, args, loc}).
+7
View File
@@ -34,3 +34,10 @@
-define(CRITICAL_MSG(Format, Args),
lager:critical(Format, Args)).
%% Use only when trying to troubleshoot test problem with ExUnit
-define(EXUNIT_LOG(Format, Args),
case lists:keyfind(logger, 1, application:loaded_applications()) of
false -> ok;
_ -> 'Elixir.Logger':bare_log(error, io_lib:format(Format, Args), [?MODULE])
end).
+5
View File
@@ -0,0 +1,5 @@
-record(motd, {server = <<"">> :: binary(),
packet = #xmlel{} :: xmlel()}).
-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
dummy = [] :: [] | '_'}).
+4
View File
@@ -0,0 +1,4 @@
-record(caps_features,
{node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
features = [] :: [binary()] | pos_integer()
}).
+4
View File
@@ -0,0 +1,4 @@
-type matchspec_atom() :: '_' | '$1' | '$2' | '$3'.
-record(carboncopy, {us :: {binary(), binary()} | matchspec_atom(),
resource :: binary() | matchspec_atom(),
version :: binary() | matchspec_atom()}).
+15
View File
@@ -0,0 +1,15 @@
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
{binary(), binary(), inet:port_number()} |
{binary(), binary()} |
{binary()}.
-type irc_data() :: [{username, binary()} | {connections_params, [conn_param()]}].
-record(irc_connection,
{jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
pid = self() :: pid()}).
-record(irc_custom,
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
binary()},
data = [] :: irc_data()}).
+3
View File
@@ -0,0 +1,3 @@
-record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
timestamp = 0 :: non_neg_integer(),
status = <<"">> :: binary()}).
+15
View File
@@ -0,0 +1,15 @@
-record(archive_msg,
{us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2',
id = <<>> :: binary() | '_',
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1',
peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
packet = #xmlel{} :: xmlel() | '_',
nick = <<"">> :: binary(),
type = chat :: chat | groupchat}).
-record(archive_prefs,
{us = {<<"">>, <<"">>} :: {binary(), binary()},
default = never :: never | always | roster,
always = [] :: [ljid()],
never = [] :: [ljid()]}).
+4
View File
@@ -0,0 +1,4 @@
-record(private_storage,
{usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
'$1' | '_'},
xml = #xmlel{} :: xmlel() | '_' | '$1'}).
+5
View File
@@ -0,0 +1,5 @@
-record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()},
opts = [] :: list() | '_' | '$2'}).
-record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()},
group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).
+8
View File
@@ -0,0 +1,8 @@
-record(vcard_search,
{us, user, luser, fn, lfn, family, lfamily, given,
lgiven, middle, lmiddle, nickname, lnickname, bday,
lbday, ctry, lctry, locality, llocality, email, lemail,
orgname, lorgname, orgunit, lorgunit}).
-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
vcard = #xmlel{} :: xmlel()}).
+2
View File
@@ -0,0 +1,2 @@
-record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()},
hash = <<>> :: binary()}).
+6 -1
View File
@@ -65,7 +65,7 @@
%% note: pos_integer() should always be used, but we allow anything else coded
%% as binary, so one can have a custom implementation of nodetree with custom
%% indexing (see nodetree_virtual). this also allows to use any kind of key for
%% indexing nodes, as this can be usefull with external backends such as odbc.
%% indexing nodes, as this can be usefull with external backends such as sql.
-type(itemId() :: binary()).
%% @type itemId() = string().
@@ -93,7 +93,12 @@
-type(subOptions() :: [mod_pubsub:subOption(),...]).
-type(pubOption() ::
{Option::binary(),
Values::[binary()]
}).
-type(pubOptions() :: [mod_pubsub:pubOption()]).
-type(affiliation() :: 'none'
| 'owner'
+130
View File
@@ -0,0 +1,130 @@
defmodule ExUnit.CTFormatter do
@moduledoc false
use GenEvent
import ExUnit.Formatter, only: [format_time: 2, format_filters: 2, format_test_failure: 5,
format_test_case_failure: 5]
def init(opts) do
file = File.open! "exunit.log", [:append]
# We do not print filter in log file as exclusion of test with tag
# pending: true is always done
config = %{
file: file,
seed: opts[:seed],
trace: opts[:trace],
colors: Keyword.put_new(opts[:colors], :enabled, false),
width: 80,
tests_counter: 0,
failures_counter: 0,
skipped_counter: 0,
invalids_counter: 0
}
{:ok, config}
end
def handle_event({:suite_started, _opts}, config) do
{:ok, config}
end
def handle_event({:suite_finished, run_us, load_us}, config) do
print_suite(config, run_us, load_us)
File.close config[:file]
:remove_handler
end
def handle_event({:test_started, %ExUnit.Test{} = test}, config) do
if config.tests_counter == 0, do: IO.binwrite config[:file], "== Running #{test.case} ==\n\n"
{:ok, config}
end
def handle_event({:test_finished, %ExUnit.Test{state: nil} = _test}, config) do
IO.binwrite config[:file], "."
{:ok, %{config | tests_counter: config.tests_counter + 1}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:skip, _}} = _test}, config) do
{:ok, %{config | tests_counter: config.tests_counter + 1,
skipped_counter: config.skipped_counter + 1}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:invalid, _}} = _test}, config) do
IO.binwrite config[:file], "?"
{:ok, %{config | tests_counter: config.tests_counter + 1,
invalids_counter: config.invalids_counter + 1}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:failed, failures}} = test}, config) do
formatted = format_test_failure(test, failures, config.failures_counter + 1,
config.width, &formatter(&1, &2, config))
print_failure(formatted, config)
print_logs(test.logs)
{:ok, %{config | tests_counter: config.tests_counter + 1,
failures_counter: config.failures_counter + 1}}
end
def handle_event({:case_started, %ExUnit.TestCase{}}, config) do
{:ok, config}
end
def handle_event({:case_finished, %ExUnit.TestCase{state: nil}}, config) do
{:ok, config}
end
def handle_event({:case_finished, %ExUnit.TestCase{state: {:failed, failures}} = test_case}, config) do
formatted = format_test_case_failure(test_case, failures, config.failures_counter + 1,
config.width, &formatter(&1, &2, config))
print_failure(formatted, config)
{:ok, %{config | failures_counter: config.failures_counter + 1}}
end
## Printing
defp print_suite(config, run_us, load_us) do
IO.binwrite config[:file], "\n\n"
IO.binwrite config[:file], format_time(run_us, load_us)
IO.binwrite config[:file], "\n\n"
# singular/plural
test_pl = pluralize(config.tests_counter, "test", "tests")
failure_pl = pluralize(config.failures_counter, "failure", "failures")
message =
"#{config.tests_counter} #{test_pl}, #{config.failures_counter} #{failure_pl}"
|> if_true(config.skipped_counter > 0, & &1 <> ", #{config.skipped_counter} skipped")
|> if_true(config.invalids_counter > 0, & &1 <> ", #{config.invalids_counter} invalid")
cond do
config.failures_counter > 0 -> IO.binwrite config[:file], message
config.invalids_counter > 0 -> IO.binwrite config[:file], message
true -> IO.binwrite config[:file], message
end
IO.binwrite config[:file], "\nRandomized with seed #{config.seed}\n\n\n\n"
end
defp if_true(value, false, _fun), do: value
defp if_true(value, true, fun), do: fun.(value)
defp print_failure(formatted, config) do
IO.binwrite config[:file], "\n"
IO.binwrite config[:file], formatted
IO.binwrite config[:file], "\n"
end
defp formatter(_, msg, _config),
do: msg
defp pluralize(1, singular, _plural), do: singular
defp pluralize(_, _singular, plural), do: plural
defp print_logs(""), do: nil
defp print_logs(output) do
indent = "\n "
output = String.replace(output, "\n", indent)
IO.puts([" The following output was logged:", indent | output])
end
end
+3 -3
View File
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "16.02.0",
version: "16.06.0",
description: description,
elixir: "~> 1.2",
elixirc_paths: ["lib"],
@@ -38,7 +38,7 @@ defmodule Ejabberd.Mixfile do
end
defp deps do
[{:lager, "~> 3.0"},
[{:lager, "~> 3.0.0"},
{:p1_utils, "~> 1.0"},
{:cache_tab, "~> 1.0"},
{:stringprep, "~> 1.0"},
@@ -51,7 +51,7 @@ defmodule Ejabberd.Mixfile do
{:p1_oauth2, "~> 0.6.1"},
{:p1_xmlrpc, "~> 1.15"},
{:p1_mysql, "~> 1.0"},
{:p1_pgsql, "~> 1.0"},
{:p1_pgsql, "~> 1.1"},
{:sqlite3, "~> 1.1"},
{:ezlib, "~> 1.0"},
{:iconv, "~> 1.0"},
+26 -26
View File
@@ -1,26 +1,26 @@
%{"bbmustache": {:hex, :bbmustache, "1.0.4"},
"cache_tab": {:hex, :cache_tab, "1.0.2"},
"cf": {:hex, :cf, "0.2.1"},
"eredis": {:hex, :eredis, "1.0.8"},
"erlware_commons": {:hex, :erlware_commons, "0.19.0"},
"esip": {:hex, :esip, "1.0.2"},
"exrm": {:hex, :exrm, "1.0.3"},
"ezlib": {:hex, :ezlib, "1.0.1"},
"fast_tls": {:hex, :fast_tls, "1.0.1"},
"fast_xml": {:hex, :fast_xml, "1.1.11"},
"fast_yaml": {:hex, :fast_yaml, "1.0.3"},
"getopt": {:hex, :getopt, "0.8.2"},
"goldrush": {:hex, :goldrush, "0.1.7"},
"iconv": {:hex, :iconv, "1.0.0"},
"jiffy": {:hex, :jiffy, "0.14.7"},
"lager": {:hex, :lager, "3.0.2"},
"p1_mysql": {:hex, :p1_mysql, "1.0.1"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.0"},
"p1_utils": {:hex, :p1_utils, "1.0.3"},
"p1_xmlrpc": {:hex, :p1_xmlrpc, "1.15.1"},
"providers": {:hex, :providers, "1.6.0"},
"relx": {:hex, :relx, "3.18.0"},
"sqlite3": {:hex, :sqlite3, "1.1.5"},
"stringprep": {:hex, :stringprep, "1.0.3"},
"stun": {:hex, :stun, "1.0.1"}}
%{"bbmustache": {:hex, :bbmustache, "1.0.4", "7ba94f971c5afd7b6617918a4bb74705e36cab36eb84b19b6a1b7ee06427aa38", [:rebar], []},
"cache_tab": {:hex, :cache_tab, "1.0.2", "c12099fff8b0f7011e7656844f5f67b958b7033a521c69595dbf2a5d7c8bb34f", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}]},
"cf": {:hex, :cf, "0.2.1", "69d0b1349fd4d7d4dc55b7f407d29d7a840bf9a1ef5af529f1ebe0ce153fc2ab", [:rebar3], []},
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
"erlware_commons": {:hex, :erlware_commons, "0.19.0", "7b43caf2c91950c5f60dc20451e3c3afba44d3d4f7f27bcdc52469285a5a3e70", [:rebar3], [{:cf, "0.2.1", [hex: :cf, optional: false]}]},
"esip": {:hex, :esip, "1.0.4", "47426c264f6ea5a2a74b53ecf825593b689b47ed3eab873ff8a595ea35aa8507", [:rebar3], [{:stun, "1.0.3", [hex: :stun, optional: false]}, {:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}, {:fast_tls, "1.0.3", [hex: :fast_tls, optional: false]}]},
"exrm": {:hex, :exrm, "1.0.5", "53ecb20da2f4e5b4c82ea6776824fbc677c8d287bf20efc9fc29cacc2cca124f", [:mix], [{:relx, "~> 3.5", [hex: :relx, optional: false]}]},
"ezlib": {:hex, :ezlib, "1.0.1", "add8b2770a1a70c174aaea082b4a8668c0c7fdb03ee6cc81c6c68d3a6c3d767d", [:rebar3], []},
"fast_tls": {:hex, :fast_tls, "1.0.3", "7ccd02ffcba24f8792dd88bd71fa543ea438c58bd0f132576031533083ede578", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.11", "1d9aedbe367a3d42ba2c6d9d4ee5808c0846e06a28e366e262e41d3ce462cbdf", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.3", "862e3f89d52aa6a72eef3121edf303aac2f3c559cbaba2d2a1fd0c09d5f15f9a", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}]},
"getopt": {:hex, :getopt, "0.8.2", "b17556db683000ba50370b16c0619df1337e7af7ecbf7d64fbf8d1d6bce3109b", [:rebar], []},
"goldrush": {:hex, :goldrush, "0.1.7", "349a351d17c71c2fdaa18a6c2697562abe136fec945f147b381f0cf313160228", [:rebar3], []},
"iconv": {:hex, :iconv, "1.0.0", "5ff1c54e5b3b9a8235de872632e9612c7952acdf89bc21db2f2efae0e72647be", [:rebar3], []},
"jiffy": {:hex, :jiffy, "0.14.7", "9f33b893edd6041ceae03bc1e50b412e858cc80b46f3d7535a7a9940a79a1c37", [:rebar, :make], []},
"lager": {:hex, :lager, "3.0.2", "25dc81bc3659b62f5ab9bd073e97ddd894fc4c242019fccef96f3889d7366c97", [:rebar3], [{:goldrush, "0.1.7", [hex: :goldrush, optional: false]}]},
"p1_mysql": {:hex, :p1_mysql, "1.0.1", "d2be1cfc71bb4f1391090b62b74c3f5cb8e7a45b0076b8cb290cd6b2856c581b", [:rebar3], []},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.0", "ca525c42878eac095e5feb19563acc9915c845648f48fdec7ba6266c625d4ac7", [:rebar3], []},
"p1_utils": {:hex, :p1_utils, "1.0.3", "8d469a34e8fe3898dda9dfda545fdb69cabfee144ebe31732d2c7905420603ec", [:rebar3], []},
"p1_xmlrpc": {:hex, :p1_xmlrpc, "1.15.1", "a382b62dc21bb372281c2488f99294d84f2b4020ed0908a1c4ad710ace3cf35a", [:rebar3], []},
"providers": {:hex, :providers, "1.6.0", "db0e2f9043ae60c0155205fcd238d68516331d0e5146155e33d1e79dc452964a", [:rebar3], [{:getopt, "0.8.2", [hex: :getopt, optional: false]}]},
"relx": {:hex, :relx, "3.19.0", "286dd5244b4786f56aac75d5c8e2d1fb4cfd306810d4ec8548f3ae1b3aadb8f7", [:rebar3], [{:providers, "1.6.0", [hex: :providers, optional: false]}, {:getopt, "0.8.2", [hex: :getopt, optional: false]}, {:erlware_commons, "0.19.0", [hex: :erlware_commons, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}]},
"sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
"stringprep": {:hex, :stringprep, "1.0.3", "0fc697484a83c868817c5c6d74c310a1f821b3cf8b6c0eb105335421d0b94d99", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.3", "d8dcf8beb38939f3fcded73e89e5753cb5a2fed2e74fa5b658124849a9e97fe9", [:rebar3], [{:p1_utils, "1.0.3", [hex: :p1_utils, optional: false]}, {:fast_tls, "1.0.3", [hex: :fast_tls, optional: false]}]}}
+19 -12
View File
@@ -8,13 +8,13 @@
%%%-------------------------------------------------------------------
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.0.2"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.3"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.4"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.2"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.1"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.2"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.3"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.1"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.2"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.4"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.3"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.12"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.4"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.5"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.3"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.7"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
@@ -30,10 +30,14 @@
{tag, "1.0.0"}}}},
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
{tag, "1.0.1"}}}},
{if_var_true, riak, {hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang",
"908a24fda4a46776a5135db60ca071e3d783f9f6"}}}, % for riak_pb-2.1.0.7
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
"527722d12d0433b837cdb92a60900c2cb5df8942"}}},
%% Forces correct dependency for riakc and allow using newer meck version)
{if_var_true, riak, {hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang",
"908a24fda4a46776a5135db60ca071e3d783f9f6"}}}, % for riak_pb-2.1.0.7
{if_var_true, riak, {protobuffs, ".*", {git, "https://github.com/basho/erlang_protobuffs",
"6e7fc924506e2dc166a6170e580ce1d95ebbd5bd"}}}, % for riak_pb-2.1.0.7 with correct meck dependency
%% Elixir support, needed to run tests
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
{tag, "v1.1.1"}}}},
%% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
@@ -41,8 +45,10 @@
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}},
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
{tag, "1.0.0"}}}},
{if_var_true, tools, {meck, "0.8.2", {git, "https://github.com/eproxus/meck",
{tag, "0.8.2"}}}},
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
{tag, "0.8.4"}}}},
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
{tag, "1.0.5b"}}}},
{if_var_true, redis, {eredis, ".*", {git, "https://github.com/wooga/eredis",
{tag, "v1.0.8"}}}}]}.
@@ -62,7 +68,7 @@
ezlib,
iconv]}}.
{erl_first_files, ["src/ejabberd_config.erl"]}.
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl"]}.
{erl_opts, [nowarn_deprecated_function,
{if_var_false, debug, no_debug_info},
@@ -107,11 +113,12 @@
{if_var_false, iconv, "(\"iconv\":_/_)"},
{if_var_false, odbc, "(\"odbc\":_/_)"},
{if_var_false, sqlite, "(\"sqlite3\":_/_)"},
{if_var_false, elixir, "(\"Elixir.Logger.*\":_/_)"},
{if_var_false, redis, "(\"eredis\":_/_)"}]}.
{eunit_compile_opts, [{i, "tools"}]}.
{cover_enabled, true}.
{if_version_above, "17", {cover_enabled, true}}.
{cover_export_enabled, true}.
{post_hook_configure, [{"fast_tls", []},
+16 -2
View File
@@ -30,6 +30,20 @@ Cfg = case file:consult(filename:join(filename:dirname(SCRIPT), "vars.config"))
ProcessVars = fun(_F, [], Acc) ->
lists:reverse(Acc);
(F, [{Type, Ver, Value} | Tail], Acc) when
Type == if_version_above orelse
Type == if_version_below ->
SysVer = erlang:system_info(otp_release),
Include = if Type == if_version_above ->
SysVer > Ver;
true ->
SysVer < Ver
end,
if Include ->
F(F, Tail, [Value | Acc]);
true ->
F(F, Tail, Acc)
end;
(F, [{Type, Var, Value} | Tail], Acc) when
Type == if_var_true orelse
Type == if_var_false ->
@@ -122,8 +136,8 @@ Conf5 = case lists:keytake(floating_deps, 1, Conf3) of
end,
%% When running Travis test, upload test coverage result to coveralls:
Conf6 = case os:getenv("TRAVIS") of
"true" ->
Conf6 = case {lists:keyfind(cover_enabled, 1, Conf5), os:getenv("TRAVIS")} of
{{cover_enabled, true}, "true"} ->
JobId = os:getenv("TRAVIS_JOB_ID"),
CfgTemp = ModCfg(Conf5, [deps], fun(V) -> [{coveralls, ".*", {git, "https://github.com/markusn/coveralls-erl.git", "master"}}|V] end, []),
ModCfg(CfgTemp, [post_hooks], fun(V) -> V ++ [{ct, "echo '\n%%! -pa ebin/ deps/coveralls/ebin\nmain(_)->{ok,F}=file:open(\"erlang.json\",[write]),io:fwrite(F,\"~s\",[coveralls:convert_file(\"logs/all.coverdata\", \""++JobId++"\", \"travis-ci\")]).' > getcover.erl"},
+2 -2
View File
@@ -362,8 +362,8 @@ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW
CREATE TABLE [dbo].[users] (
[username] [varchar] (250) NOT NULL,
[password] [text] NOT NULL,
[serverkey] [text] NOT NULL,
[salt] [text] NOT NULL,
[serverkey] [text] NOT NULL DEFAULT '',
[salt] [text] NOT NULL DEFAULT '',
[iterationcount] [smallint] NOT NULL DEFAULT 0,
[created_at] [datetime] NOT NULL DEFAULT GETDATE(),
CONSTRAINT [users_PRIMARY] PRIMARY KEY CLUSTERED
+5 -5
View File
@@ -19,15 +19,15 @@
CREATE TABLE users (
username varchar(191) PRIMARY KEY,
password text NOT NULL,
serverkey text NOT NULL DEFAULT '',
salt text NOT NULL DEFAULT '',
serverkey varchar(64) NOT NULL DEFAULT '',
salt varchar(64) NOT NULL DEFAULT '',
iterationcount integer NOT NULL DEFAULT 0,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
-- ALTER TABLE users ADD COLUMN serverkey text NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN salt text NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN serverkey varchar(64) NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN salt varchar(64) NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN iterationcount integer NOT NULL DEFAULT 0;
CREATE TABLE last (
@@ -275,7 +275,7 @@ CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt(subid(3
CREATE TABLE muc_room (
name text NOT NULL,
host text NOT NULL,
opts text NOT NULL,
opts mediumtext NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+321 -109
View File
@@ -29,10 +29,13 @@
-author('alexey@process-one.net').
-export([start/0, to_record/3, add/3, add_list/3,
add_local/3, add_list_local/3, load_from_config/0,
match_rule/3, match_acl/3, transform_options/1,
opt_type/1]).
-export([add_access/3, clear/0]).
-export([start/0, add/3, add_list/3, add_local/3, add_list_local/3,
load_from_config/0, match_rule/3,
transform_options/1, opt_type/1, acl_rule_matches/3,
acl_rule_verify/1, access_matches/3,
transform_access_rules_config/1,
access_rules_validator/1, shaper_rules_validator/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -43,6 +46,7 @@
rules = [] :: [access_rule()]}).
-type regexp() :: binary().
-type iprange() :: {inet:ip_address(), integer()} | binary().
-type glob() :: binary().
-type access_name() :: atom().
-type access_rule() :: {atom(), any()}.
@@ -61,7 +65,7 @@
{user_glob, {glob(), host()} | glob()} |
{server_glob, glob()} |
{resource_glob, glob()} |
{ip, {inet:ip_address(), integer()}} |
{ip, iprange()} |
{node_glob, {glob(), glob()}}.
-type acl() :: #acl{aclname :: aclname(),
@@ -89,12 +93,6 @@ start() ->
load_from_config(),
ok.
-spec to_record(binary(), atom(), aclspec()) -> acl().
to_record(Host, ACLName, ACLSpec) ->
#acl{aclname = {ACLName, Host},
aclspec = normalize_spec(ACLSpec)}.
-spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}.
add(Host, ACLName, ACLSpec) ->
@@ -185,6 +183,10 @@ load_from_config() ->
{acl, Host}, fun(V) -> V end, []),
AccessRules = ejabberd_config:get_option(
{access, Host}, fun(V) -> V end, []),
AccessRulesNew = ejabberd_config:get_option(
{access_rules, Host}, fun(V) -> V end, []),
ShaperRules = ejabberd_config:get_option(
{shaper_rules, Host}, fun(V) -> V end, []),
lists:foreach(
fun({ACLName, SpecList}) ->
lists:foreach(
@@ -198,12 +200,29 @@ load_from_config() ->
add(Host, ACLName, {ACLType, ACLSpecs})
end, lists:flatten(SpecList))
end, ACLs),
lists:foreach(
fun({Access, Rules}) ->
NRules = lists:map(fun({ACL, Type}) ->
{Type, [{acl, ACL}]}
end, Rules),
add_access(Host, Access, NRules ++ [{deny, [all]}])
end, AccessRules),
lists:foreach(
fun({Access, Rules}) ->
add_access(Host, Access, Rules)
end, AccessRules)
end, AccessRulesNew),
lists:foreach(
fun({Access, Rules}) ->
add_access(Host, Access, Rules)
end, ShaperRules)
end, Hosts).
%% Delete all previous set ACLs and Access rules
clear() ->
mnesia:clear_table(acl),
mnesia:clear_table(access),
ok.
b(S) ->
iolist_to_binary(S).
@@ -216,19 +235,28 @@ nameprep(S) ->
resourceprep(S) ->
jid:resourceprep(b(S)).
split_user_server(Str, NormFunUsr, NormFunSrv) ->
case binary:split(Str, <<"@">>) of
[U, S] ->
{NormFunUsr(U), NormFunSrv(S)};
_ ->
NormFunUsr(Str)
end.
normalize_spec(Spec) ->
case Spec of
all -> all;
none -> none;
{acl, N} -> {acl, N};
{user, {U, S}} -> {user, {nodeprep(U), nameprep(S)}};
{user, U} -> {user, nodeprep(U)};
{user, U} -> {user, split_user_server(U, fun nodeprep/1, fun nameprep/1)};
{shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}};
{shared_group, G} -> {shared_group, b(G)};
{shared_group, G} -> {shared_group, split_user_server(G, fun b/1, fun nameprep/1)};
{user_regexp, {UR, S}} -> {user_regexp, {b(UR), nameprep(S)}};
{user_regexp, UR} -> {user_regexp, b(UR)};
{user_regexp, UR} -> {user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_regexp, {UR, SR}} -> {node_regexp, {b(UR), b(SR)}};
{user_glob, {UR, S}} -> {user_glob, {b(UR), nameprep(S)}};
{user_glob, UR} -> {user_glob, b(UR)};
{user_glob, UR} -> {user_glob, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_glob, {UR, SR}} -> {node_glob, {b(UR), b(SR)}};
{server, S} -> {server, nameprep(S)};
{resource, R} -> {resource, resourceprep(R)};
@@ -249,104 +277,203 @@ normalize_spec(Spec) ->
-spec match_rule(global | binary(), access_name(),
jid() | ljid() | inet:ip_address()) -> any().
match_rule(_Host, all, _JID) ->
allow;
match_rule(_Host, none, _JID) ->
deny;
match_rule(Host, Access, IP) when tuple_size(IP) == 4;
tuple_size(IP) == 8 ->
access_matches(Access, #{ip => IP}, Host);
match_rule(Host, Access, JID) ->
GAccess = ets:lookup(access, {Access, global}),
LAccess = if Host /= global ->
ets:lookup(access, {Access, Host});
true ->
[]
end,
case GAccess ++ LAccess of
[] ->
deny;
AccessList ->
Rules = lists:flatmap(
fun(#access{rules = Rs}) ->
Rs
end, AccessList),
match_acls(Rules, JID, Host)
end.
access_matches(Access, #{usr => jid:tolower(JID)}, Host).
match_acls([], _, _Host) -> deny;
match_acls([{ACL, Access} | ACLs], JID, Host) ->
case match_acl(ACL, JID, Host) of
true -> Access;
_ -> match_acls(ACLs, JID, Host)
end.
-spec acl_rule_verify(aclspec()) -> boolean().
-spec match_acl(atom(),
jid() | ljid() | inet:ip_address(),
binary()) -> boolean().
match_acl(all, _JID, _Host) ->
acl_rule_verify(all) ->
true;
match_acl(none, _JID, _Host) ->
acl_rule_verify(none) ->
true;
acl_rule_verify({ip, {{A,B,C,D}, Mask}})
when is_integer(A), is_integer(B), is_integer(C), is_integer(D),
A >= 0, A =< 255, B >= 0, B =< 255, C >= 0, C =< 255, D >= 0, D =< 255,
is_integer(Mask), Mask >= 0, Mask =< 32 ->
true;
acl_rule_verify({ip, {{A,B,C,D,E,F,G,H}, Mask}}) when
is_integer(A), is_integer(B), is_integer(C), is_integer(D),
is_integer(E), is_integer(F), is_integer(G), is_integer(H),
A >= 0, A =< 65535, B >= 0, B =< 65535, C >= 0, C =< 65535, D >= 0, D =< 65535,
E >= 0, E =< 65535, F >= 0, F =< 65535, G >= 0, G =< 65535, H >= 0, H =< 65535,
is_integer(Mask), Mask >= 0, Mask =< 64 ->
true;
acl_rule_verify({user, {U, S}}) when is_binary(U), is_binary(S) ->
true;
acl_rule_verify({user, U}) when is_binary(U) ->
true;
acl_rule_verify({server, S}) when is_binary(S) ->
true;
acl_rule_verify({resource, R}) when is_binary(R) ->
true;
acl_rule_verify({shared_group, {G, H}}) when is_binary(G), is_binary(H) ->
true;
acl_rule_verify({shared_group, G}) when is_binary(G) ->
true;
acl_rule_verify({user_regexp, {UR, S}}) when is_binary(UR), is_binary(S) ->
true;
acl_rule_verify({user_regexp, UR}) when is_binary(UR) ->
true;
acl_rule_verify({server_regexp, SR}) when is_binary(SR) ->
true;
acl_rule_verify({resource_regexp, RR}) when is_binary(RR) ->
true;
acl_rule_verify({node_regexp, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
true;
acl_rule_verify({user_glob, {UR, S}}) when is_binary(UR), is_binary(S) ->
true;
acl_rule_verify({user_glob, UR}) when is_binary(UR) ->
true;
acl_rule_verify({server_glob, SR}) when is_binary(SR) ->
true;
acl_rule_verify({resource_glob, RR}) when is_binary(RR) ->
true;
acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
true;
acl_rule_verify(_Spec) ->
false.
invalid_syntax(Msg, Data) ->
throw({invalid_syntax, iolist_to_binary(io_lib:format(Msg, Data))}).
acl_rules_verify([{acl, Name} | Rest], true) when is_atom(Name) ->
acl_rules_verify(Rest, true);
acl_rules_verify([{acl, Name} = Rule | _Rest], false) when is_atom(Name) ->
invalid_syntax(<<"Using acl: rules not allowed: ~p">>, [Rule]);
acl_rules_verify([Rule | Rest], AllowAcl) ->
case acl_rule_verify(Rule) of
false ->
invalid_syntax(<<"Invalid rule: ~p">>, [Rule]);
true ->
acl_rules_verify(Rest, AllowAcl)
end;
acl_rules_verify([], _AllowAcl) ->
true;
acl_rules_verify(Rules, _AllowAcl) ->
invalid_syntax(<<"Not a acl rules list: ~p">>, [Rules]).
all_acl_rules_matches([], _Data, _Host) ->
false;
match_acl(ACL, IP, Host) when tuple_size(IP) == 4;
tuple_size(IP) == 8 ->
lists:any(
fun(#acl{aclspec = {ip, {Net, Mask}}}) ->
is_ip_match(IP, Net, Mask);
(_) ->
false
end, get_aclspecs(ACL, Host));
match_acl(ACL, JID, Host) ->
{User, Server, Resource} = jid:tolower(JID),
lists:any(
fun(#acl{aclspec = Spec}) ->
case Spec of
all -> true;
{user, {U, S}} -> U == User andalso S == Server;
{user, U} ->
U == User andalso
lists:member(Server, ?MYHOSTS);
{server, S} -> S == Server;
{resource, R} -> R == Resource;
{shared_group, {G, H}} ->
Mod = loaded_shared_roster_module(H),
Mod:is_user_in_group({User, Server}, G, H);
{shared_group, G} ->
Mod = loaded_shared_roster_module(Host),
Mod:is_user_in_group({User, Server}, G, Host);
{user_regexp, {UR, S}} ->
S == Server andalso is_regexp_match(User, UR);
{user_regexp, UR} ->
lists:member(Server, ?MYHOSTS)
andalso is_regexp_match(User, UR);
{server_regexp, SR} ->
is_regexp_match(Server, SR);
{resource_regexp, RR} ->
is_regexp_match(Resource, RR);
{node_regexp, {UR, SR}} ->
is_regexp_match(Server, SR) andalso
is_regexp_match(User, UR);
{user_glob, {UR, S}} ->
S == Server andalso is_glob_match(User, UR);
{user_glob, UR} ->
lists:member(Server, ?MYHOSTS)
andalso is_glob_match(User, UR);
{server_glob, SR} -> is_glob_match(Server, SR);
{resource_glob, RR} ->
is_glob_match(Resource, RR);
{node_glob, {UR, SR}} ->
is_glob_match(Server, SR) andalso
is_glob_match(User, UR);
WrongSpec ->
?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
"config file and reload it with the override_a"
"cls option enabled",
[WrongSpec]),
false
end
end,
get_aclspecs(ACL, Host)).
all_acl_rules_matches(Rules, Data, Host) ->
all_acl_rules_matches2(Rules, Data, Host).
all_acl_rules_matches2([Rule | Tail], Data, Host) ->
case acl_rule_matches(Rule, Data, Host) of
true ->
all_acl_rules_matches2(Tail, Data, Host);
false ->
false
end;
all_acl_rules_matches2([], _Data, _Host) ->
true.
any_acl_rules_matches([], _Data, _Host) ->
false;
any_acl_rules_matches([Rule|Tail], Data, Host) ->
case acl_rule_matches(Rule, Data, Host) of
true ->
true;
false ->
any_acl_rules_matches(Tail, Data, Host)
end.
-spec acl_rule_matches(aclspec(), any(), global|binary()) -> boolean().
acl_rule_matches(all, _Data, _Host) ->
true;
acl_rule_matches({acl, all}, _Data, _Host) ->
true;
acl_rule_matches({acl, Name}, Data, Host) ->
ACLs = get_aclspecs(Name, Host),
RawACLs = lists:map(fun(#acl{aclspec = R}) -> R end, ACLs),
any_acl_rules_matches(RawACLs, Data, Host);
acl_rule_matches({ip, {Net, Mask}}, #{ip := {IP, _Port}}, _Host) ->
is_ip_match(IP, Net, Mask);
acl_rule_matches({ip, {Net, Mask}}, #{ip := IP}, _Host) ->
is_ip_match(IP, Net, Mask);
acl_rule_matches({user, {U, S}}, #{usr := {U, S, _}}, _Host) ->
true;
acl_rule_matches({user, U}, #{usr := {U, S, _}}, _Host) ->
lists:member(S, ?MYHOSTS);
acl_rule_matches({server, S}, #{usr := {_, S, _}}, _Host) ->
true;
acl_rule_matches({resource, R}, #{usr := {_, _, R}}, _Host) ->
true;
acl_rule_matches({shared_group, {G, H}}, #{usr := {U, S, _}}, _Host) ->
Mod = loaded_shared_roster_module(H),
Mod:is_user_in_group({U, S}, G, H);
acl_rule_matches({shared_group, G}, #{usr := {U, S, _}}, Host) ->
Mod = loaded_shared_roster_module(Host),
Mod:is_user_in_group({U, S}, G, Host);
acl_rule_matches({user_regexp, {UR, S}}, #{usr := {U, S, _}}, _Host) ->
is_regexp_match(U, UR);
acl_rule_matches({user_regexp, UR}, #{usr := {U, S, _}}, _Host) ->
lists:member(S, ?MYHOSTS) andalso is_regexp_match(U, UR);
acl_rule_matches({server_regexp, SR}, #{usr := {_, S, _}}, _Host) ->
is_regexp_match(S, SR);
acl_rule_matches({resource_regexp, RR}, #{usr := {_, _, R}}, _Host) ->
is_regexp_match(R, RR);
acl_rule_matches({node_regexp, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
is_regexp_match(U, UR) andalso is_regexp_match(S, SR);
acl_rule_matches({user_glob, {UR, S}}, #{usr := {U, S, _}}, _Host) ->
is_glob_match(U, UR);
acl_rule_matches({user_glob, UR}, #{usr := {U, S, _}}, _Host) ->
lists:member(S, ?MYHOSTS) andalso is_glob_match(U, UR);
acl_rule_matches({server_glob, SR}, #{usr := {_, S, _}}, _Host) ->
is_glob_match(S, SR);
acl_rule_matches({resource_glob, RR}, #{usr := {_, _, R}}, _Host) ->
is_glob_match(R, RR);
acl_rule_matches({node_glob, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
is_glob_match(U, UR) andalso is_glob_match(S, SR);
acl_rule_matches(_ACL, _Data, _Host) ->
false.
-spec access_matches(atom()|list(), any(), global|binary()) -> any().
access_matches(all, _Data, _Host) ->
allow;
access_matches(none, _Data, _Host) ->
deny;
access_matches(Name, Data, Host) when is_atom(Name) ->
GAccess = ets:lookup(access, {Name, global}),
LAccess =
if Host /= global -> ets:lookup(access, {Name, Host});
true -> []
end,
case GAccess ++ LAccess of
[] ->
deny;
AccessList ->
Rules = lists:flatmap(
fun(#access{rules = Rs}) ->
Rs
end, AccessList),
access_rules_matches(Rules, Data, Host)
end;
access_matches(Rules, Data, Host) when is_list(Rules) ->
access_rules_matches(Rules, Data, Host).
-spec access_rules_matches(list(), any(), global|binary()) -> any().
access_rules_matches(AR, Data, Host) ->
access_rules_matches(AR, Data, Host, deny).
access_rules_matches([{Type, Acls} | Rest], Data, Host, Default) ->
case all_acl_rules_matches(Acls, Data, Host) of
false ->
access_rules_matches(Rest, Data, Host, Default);
true ->
Type
end;
access_rules_matches([], _Data, _Host, Default) ->
Default.
get_aclspecs(ACL, Host) ->
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
@@ -418,6 +545,63 @@ parse_ip_netmask(S) ->
_ -> error
end.
transform_access_rules_config(Config) when is_list(Config) ->
lists:map(fun transform_access_rules_config2/1, lists:flatten(Config));
transform_access_rules_config(Config) ->
transform_access_rules_config([Config]).
transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) ->
{Type, [all]};
transform_access_rules_config2({Type, ACL}) when is_atom(ACL) ->
{Type, [{acl, ACL}]};
transform_access_rules_config2({Res, Rules}) when is_list(Rules) ->
T = lists:map(fun({Type, Args}) when is_list(Args) ->
normalize_spec({Type, hd(lists:flatten(Args))});
(V) -> normalize_spec(V)
end, lists:flatten(Rules)),
{Res, T};
transform_access_rules_config2({Res, Rule}) ->
{Res, [Rule]}.
access_rules_validator(Name) when is_atom(Name) ->
Name;
access_rules_validator(Rules0) ->
Rules = transform_access_rules_config(Rules0),
access_shaper_rules_validator(Rules, fun(allow) -> true;
(deny) -> true;
(_) -> false
end),
throw({replace_with, Rules}).
shaper_rules_validator(Name) when is_atom(Name) ->
Name;
shaper_rules_validator(Rules0) ->
Rules = transform_access_rules_config(Rules0),
access_shaper_rules_validator(Rules, fun(V) when is_atom(V) -> true;
(V2) when is_integer(V2) -> true;
(_) -> false
end),
throw({replace_with, Rules}).
access_shaper_rules_validator([{Type, Acls} = Rule | Rest], RuleTypeCheck) ->
case RuleTypeCheck(Type) of
true ->
case acl_rules_verify(Acls, true) of
true ->
access_shaper_rules_validator(Rest, RuleTypeCheck);
Err ->
Err
end;
false ->
invalid_syntax(<<"Invalid rule type: ~p in rule ~p">>, [Type, Rule])
end;
access_shaper_rules_validator([], _RuleTypeCheck) ->
true;
access_shaper_rules_validator(Value, _RuleTypeCheck) ->
invalid_syntax(<<"Not a rule definition: ~p">>, [Value]).
transform_options(Opts) ->
Opts1 = lists:foldl(fun transform_options/2, [], Opts),
{ACLOpts, Opts2} = lists:mapfoldl(
@@ -432,6 +616,18 @@ transform_options(Opts) ->
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts2),
{NewAccessOpts, Opts4} = lists:mapfoldl(
fun({access_rules, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts3),
{ShaperOpts, Opts5} = lists:mapfoldl(
fun({shaper_rules, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts4),
ACLOpts1 = ejabberd_config:collect_options(lists:flatten(ACLOpts)),
AccessOpts1 = case ejabberd_config:collect_options(
lists:flatten(AccessOpts)) of
@@ -445,7 +641,21 @@ transform_options(Opts) ->
[] -> [];
L2 -> [{acl, L2}]
end,
ACLOpts2 ++ AccessOpts1 ++ Opts3.
NewAccessOpts1 = case lists:map(
fun({NAName, Os}) ->
{NAName, transform_access_rules_config(Os)}
end, lists:flatten(NewAccessOpts)) of
[] -> [];
L3 -> [{access_rules, L3}]
end,
ShaperOpts1 = case lists:map(
fun({SName, Ss}) ->
{SName, transform_access_rules_config(Ss)}
end, lists:flatten(ShaperOpts)) of
[] -> [];
L4 -> [{shaper_rules, L4}]
end,
ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5.
transform_options({acl, Name, Type}, Opts) ->
T = case Type of
@@ -476,5 +686,7 @@ transform_options(Opt, Opts) ->
[Opt|Opts].
opt_type(access) -> fun (V) -> V end;
opt_type(access_rules) -> fun (V) -> V end;
opt_type(shaper_rules) -> fun (V) -> V end;
opt_type(acl) -> fun (V) -> V end;
opt_type(_) -> [access, acl].
opt_type(_) -> [access, acl, access_rules, shaper_rules].
+3 -1
View File
@@ -68,7 +68,9 @@ parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}
xdata = XData,
others = Others
};
parse_request(_) -> {error, ?ERR_BAD_REQUEST}.
parse_request(#iq{lang = Lang}) ->
Text = <<"Failed to parse ad-hoc command request">>,
{error, ?ERRT_BAD_REQUEST(Lang, Text)}.
%% Borrowed from mod_vcard.erl
find_xdata_el(#xmlel{children = SubEls}) ->
+2 -3
View File
@@ -45,9 +45,8 @@ mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
mech_step(#state{server = Server} = S, ClientIn) ->
User = iolist_to_binary([randoms:get_string(),
randoms:get_string(),
randoms:get_string()]),
jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]),
case ejabberd_auth:is_user_exists(User, Server) of
true -> mech_step(S, ClientIn);
false -> {ok, [{username, User}, {auth_module, ejabberd_auth_anonymous}]}
false -> {ok, [{username, User}, {authzid, User}, {auth_module, ejabberd_auth_anonymous}]}
end.
+9 -6
View File
@@ -53,11 +53,11 @@
check_password = fun(_, _, _, _, _) -> false end :: check_password_fun(),
auth_module :: atom(),
host = <<"">> :: binary(),
hostfqdn = <<"">> :: binary()}).
hostfqdn = <<"">> :: binary() | [binary()]}).
start(_Opts) ->
Fqdn = get_local_fqdn(),
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~p",
[Fqdn]),
cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
digest).
@@ -183,16 +183,16 @@ is_digesturi_valid(DigestURICase, JabberDomain,
DigestURI = stringprep:tolower(DigestURICase),
case catch str:tokens(DigestURI, <<"/">>) of
[<<"xmpp">>, Host] ->
IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
(Host == JabberDomain) or IsHostFqdn;
[<<"xmpp">>, Host, ServName] ->
IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
(ServName == JabberDomain) and IsHostFqdn;
_ ->
false
end.
is_host_fqdn(Host, [Letter | _Tail] = Fqdn) when not is_list(Letter) ->
is_host_fqdn(Host, Fqdn) when is_binary(Fqdn) ->
Host == Fqdn;
is_host_fqdn(_Host, []) ->
false;
@@ -204,6 +204,7 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
get_local_fqdn() ->
case catch get_local_fqdn2() of
Str when is_binary(Str) -> Str;
List when is_list(List) -> List;
_ ->
<<"unknown-fqdn, please configure fqdn "
"option in ejabberd.yml!">>
@@ -211,9 +212,11 @@ get_local_fqdn() ->
get_local_fqdn2() ->
case ejabberd_config:get_option(
fqdn, fun iolist_to_binary/1) of
fqdn, fun(X) -> X end) of
ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
ConfiguredFqdn;
[A | _] = ConfiguredFqdns when is_binary(A) ->
ConfiguredFqdns;
undefined ->
{ok, Hostname} = inet:gethostname(),
{ok, {hostent, Fqdn, _, _, _, _}} =
+8 -12
View File
@@ -192,20 +192,16 @@ get_commands_spec() ->
module = ejabberd_piefxis, function = export_host,
args = [{dir, string}, {host, string}], result = {res, rescode}},
#ejabberd_commands{name = export_odbc, tags = [mnesia, odbc],
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
desc = "Export all tables as SQL queries to a file",
module = ejd2odbc, function = export,
args = [{host, string}, {file, string}], result = {res, rescode}},
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, odbc],
desc = "Export all tables as SQL queries to a file",
module = ejd2odbc, function = delete,
module = ejd2sql, function = delete,
args = [{host, string}], result = {res, rescode}},
#ejabberd_commands{name = convert_to_scram, tags = [odbc],
#ejabberd_commands{name = convert_to_scram, tags = [sql],
desc = "Convert the passwords in 'users' ODBC table to SCRAM",
module = ejabberd_auth_odbc, function = convert_to_scram,
module = ejabberd_auth_sql, function = convert_to_scram,
args = [{host, binary}], result = {res, rescode}},
#ejabberd_commands{name = import_prosody, tags = [mnesia, odbc, riak],
#ejabberd_commands{name = import_prosody, tags = [mnesia, sql, riak],
desc = "Import data from Prosody",
module = prosody2ejabberd, function = from_dir,
args = [{dir, string}], result = {res, rescode}},
@@ -225,10 +221,10 @@ get_commands_spec() ->
module = ?MODULE, function = delete_old_messages,
args = [{days, integer}], result = {res, rescode}},
#ejabberd_commands{name = export2odbc, tags = [mnesia],
#ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL files",
module = ejd2odbc, function = export,
args = [{host, string}, {directory, string}],
module = ejd2sql, function = export,
args = [{host, string}, {file, string}],
result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [mnesia],
desc = "Set master node of the clustered Mnesia tables",
+2 -1
View File
@@ -63,6 +63,7 @@ start(normal, _Args) ->
Sup = ejabberd_sup:start_link(),
ejabberd_rdbms:start(),
ejabberd_riak_sup:start(),
ejabberd_redis:start(),
ejabberd_sm:start(),
cyrsasl:start(),
% Profiling
@@ -83,9 +84,9 @@ start(_, _) ->
%% before shutting down the processes of the application.
prep_stop(State) ->
ejabberd_listener:stop_listeners(),
gen_mod:stop_modules(),
ejabberd_admin:stop(),
broadcast_c2s_shutdown(),
gen_mod:stop_modules(),
timer:sleep(5000),
State.
+9 -18
View File
@@ -136,8 +136,8 @@ check_password(User, AuthzId, Server, Password, Digest,
%% {true, AuthModule} | false
%% where
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
%% | ejabberd_auth_internal | ejabberd_auth_ldap
%% | ejabberd_auth_odbc | ejabberd_auth_pam | ejabberd_auth_riak
%% | ejabberd_auth_mnesia | ejabberd_auth_ldap
%% | ejabberd_auth_sql | ejabberd_auth_pam | ejabberd_auth_riak
-spec check_password_with_authmodule(binary(), binary(), binary(), binary()) -> false |
{true, atom()}.
@@ -428,30 +428,21 @@ auth_modules() ->
%% Return the list of authenticated modules for a given host
auth_modules(Server) ->
LServer = jid:nameprep(Server),
Default = case gen_mod:default_db(LServer) of
mnesia -> internal;
DBType -> DBType
end,
Default = ejabberd_config:default_db(LServer, ?MODULE),
Methods = ejabberd_config:get_option(
{auth_method, LServer},
fun(V) when is_list(V) ->
true = lists:all(fun is_atom/1, V),
V;
(V) when is_atom(V) ->
[V]
end, [Default]),
{auth_method, LServer}, opt_type(auth_method), [Default]),
[jlib:binary_to_atom(<<"ejabberd_auth_",
(jlib:atom_to_binary(M))/binary>>)
|| M <- Methods].
export(Server) ->
ejabberd_auth_internal:export(Server).
ejabberd_auth_mnesia:export(Server).
import(Server) ->
ejabberd_auth_internal:import(Server).
ejabberd_auth_mnesia:import(Server).
import(Server, mnesia, Passwd) ->
ejabberd_auth_internal:import(Server, mnesia, Passwd);
ejabberd_auth_mnesia:import(Server, mnesia, Passwd);
import(Server, riak, Passwd) ->
ejabberd_auth_riak:import(Server, riak, Passwd);
import(_, _, _) ->
@@ -459,7 +450,7 @@ import(_, _, _) ->
opt_type(auth_method) ->
fun (V) when is_list(V) ->
true = lists:all(fun is_atom/1, V), V;
(V) when is_atom(V) -> [V]
lists:map(fun(M) -> ejabberd_config:v_db(?MODULE, M) end, V);
(V) -> [ejabberd_config:v_db(?MODULE, V)]
end;
opt_type(_) -> [auth_method].
+1 -1
View File
@@ -56,7 +56,7 @@
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
sid = {p1_time_compat:timestamp(), self()} :: ejabberd_sm:sid()}).
sid = ejabberd_sm:make_sid() :: ejabberd_sm:sid()}).
start(Host) ->
%% TODO: Check cluster mode
+30 -28
View File
@@ -56,7 +56,7 @@ start(Host) ->
"extauth"),
extauth:start(Host, Cmd),
check_cache_last_options(Host),
ejabberd_auth_internal:start(Host).
ejabberd_auth_mnesia:start(Host).
check_cache_last_options(Server) ->
case get_cache_option(Server) of
@@ -78,13 +78,15 @@ store_type() -> external.
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
case get_cache_option(Server) of
false -> check_password_extauth(User, AuthzId, Server, Password);
{true, CacheTime} ->
check_password_cache(User, AuthzId, Server, Password, CacheTime)
end
false;
true ->
case get_cache_option(Server) of
false ->
check_password_extauth(User, AuthzId, Server, Password);
{true, CacheTime} ->
check_password_cache(User, AuthzId, Server, Password,
CacheTime)
end
end.
check_password(User, AuthzId, Server, Password, _Digest,
@@ -94,7 +96,7 @@ check_password(User, AuthzId, Server, Password, _Digest,
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
true ->
set_password_internal(User, Server, Password), ok;
set_password_mnesia(User, Server, Password), ok;
_ -> {error, unknown_problem}
end.
@@ -106,20 +108,20 @@ try_register(User, Server, Password) ->
end.
dirty_get_registered_users() ->
ejabberd_auth_internal:dirty_get_registered_users().
ejabberd_auth_mnesia:dirty_get_registered_users().
get_vh_registered_users(Server) ->
ejabberd_auth_internal:get_vh_registered_users(Server).
ejabberd_auth_mnesia:get_vh_registered_users(Server).
get_vh_registered_users(Server, Data) ->
ejabberd_auth_internal:get_vh_registered_users(Server,
ejabberd_auth_mnesia:get_vh_registered_users(Server,
Data).
get_vh_registered_users_number(Server) ->
ejabberd_auth_internal:get_vh_registered_users_number(Server).
ejabberd_auth_mnesia:get_vh_registered_users_number(Server).
get_vh_registered_users_number(Server, Data) ->
ejabberd_auth_internal:get_vh_registered_users_number(Server,
ejabberd_auth_mnesia:get_vh_registered_users_number(Server,
Data).
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
@@ -151,7 +153,7 @@ remove_user(User, Server) ->
case get_cache_option(Server) of
false -> false;
{true, _CacheTime} ->
ejabberd_auth_internal:remove_user(User, Server)
ejabberd_auth_mnesia:remove_user(User, Server)
end
end.
@@ -162,7 +164,7 @@ remove_user(User, Server, Password) ->
case get_cache_option(Server) of
false -> false;
{true, _CacheTime} ->
ejabberd_auth_internal:remove_user(User, Server,
ejabberd_auth_mnesia:remove_user(User, Server,
Password)
end
end.
@@ -197,7 +199,7 @@ check_password_cache(User, AuthzId, Server, Password,
CacheTime) ->
case get_last_access(User, Server) of
online ->
check_password_internal(User, AuthzId, Server, Password);
check_password_mnesia(User, AuthzId, Server, Password);
never ->
check_password_external_cache(User, AuthzId, Server, Password);
mod_last_required ->
@@ -210,7 +212,7 @@ check_password_cache(User, AuthzId, Server, Password,
case is_fresh_enough(TimeStamp, CacheTime) of
%% If no need to refresh, check password against Mnesia
true ->
case check_password_internal(User, AuthzId, Server, Password) of
case check_password_mnesia(User, AuthzId, Server, Password) of
%% If password valid in Mnesia, accept it
true -> true;
%% Else (password nonvalid in Mnesia), check in extauth and cache result
@@ -223,13 +225,13 @@ check_password_cache(User, AuthzId, Server, Password,
end
end.
get_password_internal(User, Server) ->
ejabberd_auth_internal:get_password(User, Server).
get_password_mnesia(User, Server) ->
ejabberd_auth_mnesia:get_password(User, Server).
-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false.
get_password_cache(User, Server, CacheTime) ->
case get_last_access(User, Server) of
online -> get_password_internal(User, Server);
online -> get_password_mnesia(User, Server);
never -> false;
mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled "
@@ -239,7 +241,7 @@ get_password_cache(User, Server, CacheTime) ->
false;
TimeStamp ->
case is_fresh_enough(TimeStamp, CacheTime) of
true -> get_password_internal(User, Server);
true -> get_password_mnesia(User, Server);
false -> false
end
end.
@@ -248,7 +250,7 @@ get_password_cache(User, Server, CacheTime) ->
check_password_external_cache(User, AuthzId, Server, Password) ->
case check_password_extauth(User, AuthzId, Server, Password) of
true ->
set_password_internal(User, Server, Password), true;
set_password_mnesia(User, Server, Password), true;
false -> false
end.
@@ -256,21 +258,21 @@ check_password_external_cache(User, AuthzId, Server, Password) ->
try_register_external_cache(User, Server, Password) ->
case try_register_extauth(User, Server, Password) of
{atomic, ok} = R ->
set_password_internal(User, Server, Password), R;
set_password_mnesia(User, Server, Password), R;
_ -> {error, not_allowed}
end.
%% @spec (User, AuthzId, Server, Password) -> true | false
check_password_internal(User, AuthzId, Server, Password) ->
ejabberd_auth_internal:check_password(User, AuthzId, Server,
check_password_mnesia(User, AuthzId, Server, Password) ->
ejabberd_auth_mnesia:check_password(User, AuthzId, Server,
Password).
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
set_password_internal(User, Server, Password) ->
set_password_mnesia(User, Server, Password) ->
%% @spec (TimeLast, CacheTime) -> true | false
%% TimeLast = online | never | integer()
%% CacheTime = integer() | false
ejabberd_auth_internal:set_password(User, Server,
ejabberd_auth_mnesia:set_password(User, Server,
Password).
is_fresh_enough(TimeStampLast, CacheTime) ->
+8 -9
View File
@@ -118,16 +118,15 @@ store_type() -> external.
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
if Password == <<"">> -> false;
false;
true ->
case catch check_password_ldap(User, Server, Password)
of
{'EXIT', _} -> false;
Result -> Result
end
end
if Password == <<"">> -> false;
true ->
case catch check_password_ldap(User, Server, Password) of
{'EXIT', _} -> false;
Result -> Result
end
end
end.
check_password(User, AuthzId, Server, Password, _Digest,
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_auth_internal.erl
%%% File : ejabberd_auth_mnesia.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Authentification via mnesia
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
@@ -23,7 +23,9 @@
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_auth_internal).
-module(ejabberd_auth_mnesia).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_config).
@@ -43,6 +45,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
password = <<"">> :: binary() | scram() | '_'}).
@@ -88,51 +91,48 @@ store_type() ->
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Password}]
when is_binary(Password) ->
Password /= <<"">>;
[#passwd{password = Scram}]
when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram);
_ -> false
end
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Password}] when is_binary(Password) ->
Password /= <<"">>;
[#passwd{password = Scram}] when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram);
_ -> false
end
end.
check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#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;
[#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
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#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;
[#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
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
@@ -473,12 +473,22 @@ is_password_scram_valid(Password, Scram) ->
export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
when LServer == Host,
is_binary(Password) ->
[?SQL("delete from users where username=%(LUser)s;"),
?SQL("insert into users(username, password) "
"values (%(LUser)s, %(Password)s);")];
(Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
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, <<"');">>]];
StoredKey = Scram#scram.storedkey,
ServerKey = Scram#scram.serverkey,
Salt = Scram#scram.salt,
IterationCount = Scram#scram.iterationcount,
[?SQL("delete from users where username=%(LUser)s;"),
?SQL("insert into users(username, password, serverkey, salt, "
"iterationcount) "
"values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
" %(Salt)s, %(IterationCount)d);")];
(_Host, _R) ->
[]
end}].
+7 -6
View File
@@ -25,6 +25,8 @@
-module(ejabberd_auth_riak).
-compile([{parse_transform, ejabberd_sql_pt}]).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
@@ -42,6 +44,7 @@
-export([passwd_schema/0]).
-include("ejabberd.hrl").
-include("ejabberd_sql_pt.hrl").
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
password = <<"">> :: binary() | scram() | '_'}).
@@ -290,12 +293,10 @@ is_password_scram_valid(Password, Scram) ->
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, <<"');">>]];
when LServer == Host ->
[?SQL("delete from users where username=%(LUser)s;"),
?SQL("insert into users(username, password) "
"values (%(LUser)s, %(Password)s);")];
(_Host, _R) ->
[]
end}].
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_auth_odbc.erl
%%% File : ejabberd_auth_sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Authentification via ODBC
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
@@ -23,7 +23,9 @@
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_auth_odbc).
-module(ejabberd_auth_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_config).
@@ -43,6 +45,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-define(SALT_LENGTH, 16).
@@ -77,7 +80,7 @@ check_password(User, AuthzId, Server, Password) ->
true ->
case is_scrammed() of
true ->
try odbc_queries:get_password_scram(LServer, LUser) of
try sql_queries:get_password_scram(LServer, LUser) of
{selected,
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
Scram =
@@ -85,7 +88,7 @@ check_password(User, AuthzId, Server, Password) ->
serverkey = ServerKey,
salt = Salt,
iterationcount = IterationCount},
is_password_scram_valid(Password, Scram);
is_password_scram_valid_stored(Password, Scram, LUser, LServer);
{selected, []} ->
false; %% Account does not exist
{error, _Error} ->
@@ -95,7 +98,7 @@ check_password(User, AuthzId, Server, Password) ->
false %% Typical error is database not accessible
end;
false ->
try odbc_queries:get_password(LServer, LUser) of
try sql_queries:get_password(LServer, LUser) of
{selected, [{Password}]} ->
Password /= <<"">>;
{selected, [{_Password2}]} ->
@@ -127,7 +130,7 @@ check_password(User, AuthzId, Server, Password, Digest,
true ->
case is_scrammed() of
false ->
try odbc_queries:get_password(LServer, LUser) of
try sql_queries:get_password(LServer, LUser) of
%% Account exists, check if password is valid
{selected, [{Passwd}]} ->
DigRes = if Digest /= <<"">> ->
@@ -164,7 +167,7 @@ set_password(User, Server, Password) ->
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
case catch odbc_queries:set_password_scram_t(
case catch sql_queries:set_password_scram_t(
LServer,
LUser,
Scram#scram.storedkey,
@@ -177,7 +180,7 @@ set_password(User, Server, Password) ->
Other -> {error, Other}
end;
false ->
case catch odbc_queries:set_password_t(LServer,
case catch sql_queries:set_password_t(LServer,
LUser, Password)
of
{atomic, ok} -> ok;
@@ -198,7 +201,7 @@ try_register(User, Server, Password) ->
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
case catch odbc_queries:add_user_scram(
case catch sql_queries:add_user_scram(
LServer,
LUser,
Scram#scram.storedkey,
@@ -210,7 +213,7 @@ try_register(User, Server, Password) ->
_ -> {atomic, exists}
end;
false ->
case catch odbc_queries:add_user(LServer, LUser,
case catch sql_queries:add_user(LServer, LUser,
Password) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
@@ -219,7 +222,7 @@ try_register(User, Server, Password) ->
end.
dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(odbc),
Servers = ejabberd_config:get_vh_by_auth_method(sql),
lists:flatmap(fun (Server) ->
get_vh_registered_users(Server)
end,
@@ -230,7 +233,7 @@ get_vh_registered_users(Server) ->
error -> [];
<<>> -> [];
LServer ->
case catch odbc_queries:list_users(LServer) of
case catch sql_queries:list_users(LServer) of
{selected, Res} ->
[{U, LServer} || {U} <- Res];
_ -> []
@@ -242,7 +245,7 @@ get_vh_registered_users(Server, Opts) ->
error -> [];
<<>> -> [];
LServer ->
case catch odbc_queries:list_users(LServer, Opts) of
case catch sql_queries:list_users(LServer, Opts) of
{selected, Res} ->
[{U, LServer} || {U} <- Res];
_ -> []
@@ -254,7 +257,7 @@ get_vh_registered_users_number(Server) ->
error -> 0;
<<>> -> 0;
LServer ->
case catch odbc_queries:users_number(LServer) of
case catch sql_queries:users_number(LServer) of
{selected, [{Res}]} ->
Res;
_ -> 0
@@ -266,7 +269,7 @@ get_vh_registered_users_number(Server, Opts) ->
error -> 0;
<<>> -> 0;
LServer ->
case catch odbc_queries:users_number(LServer, Opts) of
case catch sql_queries:users_number(LServer, Opts) of
{selected, [{Res}]} ->
Res;
_Other -> 0
@@ -283,7 +286,7 @@ get_password(User, Server) ->
true ->
case is_scrammed() of
true ->
case catch odbc_queries:get_password_scram(
case catch sql_queries:get_password_scram(
LServer, LUser) of
{selected,
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
@@ -294,7 +297,7 @@ get_password(User, Server) ->
_ -> false
end;
false ->
case catch odbc_queries:get_password(LServer, LUser)
case catch sql_queries:get_password(LServer, LUser)
of
{selected, [{Password}]} -> Password;
_ -> false
@@ -312,7 +315,7 @@ get_password_s(User, Server) ->
true ->
case is_scrammed() of
false ->
case catch odbc_queries:get_password(LServer, LUser) of
case catch sql_queries:get_password(LServer, LUser) of
{selected, [{Password}]} -> Password;
_ -> <<"">>
end;
@@ -329,7 +332,7 @@ is_user_exists(User, Server) ->
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
try odbc_queries:get_password(LServer, LUser) of
try sql_queries:get_password(LServer, LUser) of
{selected, [{_Password}]} ->
true; %% Account exists
{selected, []} ->
@@ -351,7 +354,7 @@ remove_user(User, Server) ->
(LUser == <<>>) or (LServer == <<>>) ->
error;
true ->
catch odbc_queries:del_user(LServer, LUser),
catch sql_queries:del_user(LServer, LUser),
ok
end.
@@ -375,7 +378,7 @@ remove_user(User, Server, Password) ->
end;
false ->
F = fun () ->
Result = odbc_queries:del_user_return_password(
Result = sql_queries:del_user_return_password(
LServer, LUser, Password),
case Result of
{selected, [{Password}]} -> ok;
@@ -383,7 +386,7 @@ remove_user(User, Server, Password) ->
_ -> not_allowed
end
end,
{atomic, Result} = odbc_queries:sql_transaction(
{atomic, Result} = sql_queries:sql_transaction(
LServer, F),
Result
end
@@ -414,6 +417,15 @@ password_to_scram(Password, IterationCount) ->
salt = jlib:encode_base64(Salt),
iterationcount = IterationCount}.
is_password_scram_valid_stored(Pass, {scram,Pass,<<>>,<<>>,0}, LUser, LServer) ->
?INFO_MSG("Apparently, SQL auth method and scram password formatting are "
"enabled, but the password of user '~s' in the 'users' table is not "
"scrammed. You may want to execute this command: "
"ejabberdctl convert_to_scram ~s", [LUser, LServer]),
false;
is_password_scram_valid_stored(Password, Scram, _, _) ->
is_password_scram_valid(Password, Scram).
is_password_scram_valid(Password, Scram) ->
IterationCount = Scram#scram.iterationcount,
Salt = jlib:decode_base64(Scram#scram.salt),
@@ -425,19 +437,15 @@ is_password_scram_valid(Password, Scram) ->
-define(BATCH_SIZE, 1000).
set_password_scram_t(Username,
set_password_scram_t(LUser,
StoredKey, ServerKey, Salt, IterationCount) ->
odbc_queries:update_t(<<"users">>,
[<<"username">>,
<<"password">>,
<<"serverkey">>,
<<"salt">>,
<<"iterationcount">>],
[Username, StoredKey,
ServerKey, Salt,
IterationCount],
[<<"username='">>, Username,
<<"'">>]).
?SQL_UPSERT_T(
"users",
["!username=%(LUser)s",
"password=%(StoredKey)s",
"serverkey=%(ServerKey)s",
"salt=%(Salt)s",
"iterationcount=%(IterationCount)d"]).
convert_to_scram(Server) ->
LServer = jid:nameprep(Server),
@@ -447,31 +455,31 @@ convert_to_scram(Server) ->
{error, {incorrect_server_name, Server}};
true ->
F = fun () ->
case ejabberd_odbc:sql_query_t(
[<<"select username, password from users where "
"iterationcount=0 limit ">>,
integer_to_binary(?BATCH_SIZE),
<<";">>]) of
{selected, [<<"username">>, <<"password">>], []} ->
BatchSize = ?BATCH_SIZE,
case ejabberd_sql:sql_query_t(
?SQL("select @(username)s, @(password)s"
" from users"
" where iterationcount=0"
" limit %(BatchSize)d")) of
{selected, []} ->
ok;
{selected, [<<"username">>, <<"password">>], Rs} ->
{selected, Rs} ->
lists:foreach(
fun([LUser, Password]) ->
Username = ejabberd_odbc:escape(LUser),
fun({LUser, Password}) ->
Scram = password_to_scram(Password),
set_password_scram_t(
Username,
ejabberd_odbc:escape(Scram#scram.storedkey),
ejabberd_odbc:escape(Scram#scram.serverkey),
ejabberd_odbc:escape(Scram#scram.salt),
integer_to_binary(Scram#scram.iterationcount)
LUser,
Scram#scram.storedkey,
Scram#scram.serverkey,
Scram#scram.salt,
Scram#scram.iterationcount
)
end, Rs),
continue;
Err -> {bad_reply, Err}
end
end,
case odbc_queries:sql_transaction(LServer, F) of
case sql_queries:sql_transaction(LServer, F) of
{atomic, ok} -> ok;
{atomic, continue} -> convert_to_scram(Server);
{atomic, Error} -> {error, Error};
+169 -167
View File
@@ -104,7 +104,6 @@
ip,
aux_fields = [],
csi_state = active,
csi_queue = [],
mgmt_state,
mgmt_xmlns,
mgmt_queue,
@@ -167,27 +166,32 @@
(Xmlns == ?NS_STREAM_MGMT_2) or
(Xmlns == ?NS_STREAM_MGMT_3)).
-define(MGMT_FAILED(Condition, Xmlns),
-define(MGMT_FAILED(Condition, Attrs),
#xmlel{name = <<"failed">>,
attrs = [{<<"xmlns">>, Xmlns}],
attrs = Attrs,
children = [#xmlel{name = Condition,
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
children = []}]}).
-define(MGMT_BAD_REQUEST(Xmlns),
?MGMT_FAILED(<<"bad-request">>, Xmlns)).
-define(MGMT_ITEM_NOT_FOUND(Xmlns),
?MGMT_FAILED(<<"item-not-found">>, Xmlns)).
?MGMT_FAILED(<<"bad-request">>, [{<<"xmlns">>, Xmlns}])).
-define(MGMT_SERVICE_UNAVAILABLE(Xmlns),
?MGMT_FAILED(<<"service-unavailable">>, Xmlns)).
?MGMT_FAILED(<<"service-unavailable">>, [{<<"xmlns">>, Xmlns}])).
-define(MGMT_UNEXPECTED_REQUEST(Xmlns),
?MGMT_FAILED(<<"unexpected-request">>, Xmlns)).
?MGMT_FAILED(<<"unexpected-request">>, [{<<"xmlns">>, Xmlns}])).
-define(MGMT_UNSUPPORTED_VERSION(Xmlns),
?MGMT_FAILED(<<"unsupported-version">>, Xmlns)).
?MGMT_FAILED(<<"unsupported-version">>, [{<<"xmlns">>, Xmlns}])).
-define(MGMT_ITEM_NOT_FOUND(Xmlns),
?MGMT_FAILED(<<"item-not-found">>, [{<<"xmlns">>, Xmlns}])).
-define(MGMT_ITEM_NOT_FOUND_H(Xmlns, NumStanzasIn),
?MGMT_FAILED(<<"item-not-found">>,
[{<<"xmlns">>, Xmlns},
{<<"h">>, jlib:integer_to_binary(NumStanzasIn)}])).
%%%----------------------------------------------------------------------
%%% API
@@ -255,14 +259,10 @@ close(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed).
%%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
Access = case lists:keysearch(access, 1, Opts) of
{value, {_, A}} -> A;
_ -> all
end,
Shaper = case lists:keysearch(shaper, 1, Opts) of
{value, {_, S}} -> S;
_ -> none
end,
Access = gen_mod:get_opt(access, Opts,
fun acl:access_rules_validator/1, all),
Shaper = gen_mod:get_opt(shaper, Opts,
fun acl:shaper_rules_validator/1, none),
XMLSocket = case lists:keysearch(xml_socket, 1, Opts) of
{value, {_, XS}} -> XS;
_ -> false
@@ -327,7 +327,7 @@ init([{SockMod, Socket}, Opts]) ->
xml_socket = XMLSocket, zlib = Zlib, tls = TLS,
tls_required = StartTLSRequired,
tls_enabled = TLSEnabled, tls_options = TLSOpts,
sid = {p1_time_compat:timestamp(), self()}, streamid = new_id(),
sid = ejabberd_sm:make_sid(), streamid = new_id(),
access = Access, shaper = Shaper, ip = IP,
mgmt_state = StreamMgmtState,
mgmt_max_queue = MaxAckQueue,
@@ -364,11 +364,17 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
%% avoid possible DoS/flood attacks
<<"">>
end,
StreamVersion = case fxml:get_attr_s(<<"version">>, Attrs) of
<<"1.0">> ->
<<"1.0">>;
_ ->
<<"">>
end,
IsBlacklistedIP = is_ip_blacklisted(StateData#state.ip, Lang),
case lists:member(Server, ?MYHOSTS) of
true when IsBlacklistedIP == false ->
change_shaper(StateData, jid:make(<<"">>, Server, <<"">>)),
case fxml:get_attr_s(<<"version">>, Attrs) of
case StreamVersion of
<<"1.0">> ->
send_header(StateData, Server, <<"1.0">>, DefaultLang),
case StateData#state.authenticated of
@@ -522,7 +528,6 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
<<"Use of STARTTLS required">>)),
send_trailer(StateData),
{stop, normal, StateData};
true ->
fsm_next_state(wait_for_auth,
@@ -535,36 +540,30 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
{true, LogReason, ReasonT} = IsBlacklistedIP,
?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
[jlib:ip_to_list(IP), LogReason]),
send_header(StateData, Server, <<"">>, DefaultLang),
send_header(StateData, Server, StreamVersion, DefaultLang),
send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
send_trailer(StateData),
{stop, normal, StateData};
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
send_header(StateData, ?MYNAME, StreamVersion, DefaultLang),
send_element(StateData, ?HOST_UNKNOWN_ERR),
send_trailer(StateData),
{stop, normal, StateData}
end;
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
send_element(StateData, ?INVALID_NS_ERR),
send_trailer(StateData),
{stop, normal, StateData}
end;
wait_for_stream(timeout, StateData) ->
{stop, normal, StateData};
wait_for_stream({xmlstreamelement, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamend, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>),
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData};
@@ -619,16 +618,17 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
send_element(StateData, Res),
fsm_next_state(wait_for_auth, StateData);
{auth, _ID, set, {_U, _P, _D, <<"">>}} ->
Err = jlib:make_error_reply(El,
?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))),
Lang = StateData#state.lang,
Txt = <<"No resource provided">>,
Err = jlib:make_error_reply(El, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)),
send_element(StateData, Err),
fsm_next_state(wait_for_auth, StateData);
{auth, _ID, set, {U, P, D, R}} ->
JID = jid:make(U, StateData#state.server, R),
case JID /= error andalso
acl:match_rule(StateData#state.server,
StateData#state.access, JID)
== allow
acl:access_matches(StateData#state.access,
#{usr => jid:split(JID), ip => StateData#state.ip},
StateData#state.server) == allow
of
true ->
DGen = fun (PW) ->
@@ -685,7 +685,10 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[false, U, StateData#state.server,
StateData#state.ip]),
Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
Lang = StateData#state.lang,
Txt = <<"Legacy authentication failed">>,
Err = jlib:make_error_reply(
El, ?ERRT_NOT_AUTHORIZED(Lang, Txt)),
send_element(StateData, Err),
fsm_next_state(wait_for_auth, StateData)
end;
@@ -706,7 +709,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
[false, U, StateData#state.server,
StateData#state.ip]),
Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
Lang = StateData#state.lang,
Txt = <<"Legacy authentication forbidden">>,
Err = jlib:make_error_reply(El, ?ERRT_NOT_ALLOWED(Lang, Txt)),
send_element(StateData, Err),
fsm_next_state(wait_for_auth, StateData)
end
@@ -718,10 +723,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
wait_for_auth(timeout, StateData) ->
{stop, normal, StateData};
wait_for_auth({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
wait_for_auth({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_auth(closed, StateData) ->
{stop, normal, StateData};
@@ -837,7 +841,6 @@ wait_for_feature_request({xmlstreamelement, El},
send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
<<"Use of STARTTLS required">>)),
send_trailer(StateData),
{stop, normal, StateData};
true ->
process_unauthenticated_stanza(StateData, El),
@@ -848,11 +851,10 @@ wait_for_feature_request(timeout, StateData) ->
{stop, normal, StateData};
wait_for_feature_request({xmlstreamend, _Name},
StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
wait_for_feature_request({xmlstreamerror, _},
StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
{stop, normal, StateData};
@@ -957,11 +959,10 @@ wait_for_sasl_response(timeout, StateData) ->
{stop, normal, StateData};
wait_for_sasl_response({xmlstreamend, _Name},
StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
wait_for_sasl_response({xmlstreamerror, _},
StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_sasl_response(closed, StateData) ->
{stop, normal, StateData};
@@ -1013,7 +1014,7 @@ wait_for_bind({xmlstreamelement, #xmlel{name = Name, attrs = Attrs} = El},
end;
wait_for_bind({xmlstreamelement, El}, StateData) ->
case jlib:iq_query_info(El) of
#iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} =
#iq{type = set, lang = Lang, xmlns = ?NS_BIND, sub_el = SubEl} =
IQ ->
U = StateData#state.user,
R1 = fxml:get_path_s(SubEl,
@@ -1025,7 +1026,8 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
end,
case R of
error ->
Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST),
Txt = <<"Malformed resource">>,
Err = jlib:make_error_reply(El, ?ERRT_BAD_REQUEST(Lang, Txt)),
send_element(StateData, Err),
fsm_next_state(wait_for_bind, StateData);
_ ->
@@ -1057,7 +1059,11 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
children =
[{xmlcdata,
jid:to_string(JID)}]}]}]},
send_element(StateData3, jlib:iq_to_xml(Res)),
try
send_element(StateData3, jlib:iq_to_xml(Res))
catch exit:normal ->
close(self())
end,
fsm_next_state_pack(
session_established,
StateData3);
@@ -1085,10 +1091,9 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
wait_for_bind(timeout, StateData) ->
{stop, normal, StateData};
wait_for_bind({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
wait_for_bind({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_bind(closed, StateData) ->
{stop, normal, StateData};
@@ -1099,8 +1104,11 @@ open_session(StateData) ->
U = StateData#state.user,
R = StateData#state.resource,
JID = StateData#state.jid,
case acl:match_rule(StateData#state.server,
StateData#state.access, JID) of
Lang = StateData#state.lang,
IP = StateData#state.ip,
case acl:access_matches(StateData#state.access,
#{usr => jid:split(JID), ip => IP},
StateData#state.server) of
allow ->
?INFO_MSG("(~w) Opened session for ~s",
[StateData#state.socket, jid:to_string(JID)]),
@@ -1136,7 +1144,8 @@ open_session(StateData) ->
StateData#state.server, [JID]),
?INFO_MSG("(~w) Forbidden session for ~s",
[StateData#state.socket, jid:to_string(JID)]),
{error, ?ERR_NOT_ALLOWED}
Txt = <<"Denied by ACL">>,
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end.
session_established({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
@@ -1146,7 +1155,7 @@ session_established({xmlstreamelement,
#xmlel{name = <<"active">>,
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
StateData) ->
NewStateData = csi_queue_flush(StateData),
NewStateData = csi_flush_queue(StateData),
fsm_next_state(session_established, NewStateData#state{csi_state = active});
session_established({xmlstreamelement,
#xmlel{name = <<"inactive">>,
@@ -1159,7 +1168,6 @@ session_established({xmlstreamelement, El},
case check_from(El, FromJID) of
'invalid-from' ->
send_element(StateData, ?INVALID_FROM),
send_trailer(StateData),
{stop, normal, StateData};
_NewEl ->
session_established2(El, StateData)
@@ -1172,17 +1180,15 @@ session_established(timeout, StateData) ->
[?MODULE, Options, session_established, StateData]),
fsm_next_state(session_established, StateData);
session_established({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
session_established({xmlstreamerror,
<<"XML stanza is too big">> = E},
StateData) ->
send_element(StateData,
?POLICY_VIOLATION_ERR((StateData#state.lang), E)),
send_trailer(StateData),
{stop, normal, StateData};
session_established({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
session_established(closed, #state{mgmt_state = active} = StateData) ->
catch (StateData#state.sockmod):close(StateData#state.socket),
@@ -1283,7 +1289,7 @@ wait_for_resume({xmlstreamelement, _El} = Event, StateData) ->
wait_for_resume(timeout, StateData) ->
?DEBUG("Timed out waiting for resumption of stream for ~s",
[jid:to_string(StateData#state.jid)]),
{stop, normal, StateData};
{stop, normal, StateData#state{mgmt_state = timeout}};
wait_for_resume(Event, StateData) ->
?DEBUG("Ignoring event while waiting for resumption: ~p", [Event]),
fsm_next_state(wait_for_resume, StateData).
@@ -1337,7 +1343,6 @@ handle_info(kick, StateName, StateData) ->
handle_info({kick, kicked_by_admin, Xmlelement}, StateName, StateData);
handle_info({kick, Reason, Xmlelement}, _StateName, StateData) ->
send_element(StateData, Xmlelement),
send_trailer(StateData),
{stop, normal,
StateData#state{authenticated = Reason}};
handle_info({route, _From, _To, {broadcast, Data}},
@@ -1350,7 +1355,6 @@ handle_info({route, _From, _To, {broadcast, Data}},
{exit, Reason} ->
Lang = StateData#state.lang,
send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)),
catch send_trailer(StateData),
{stop, normal, StateData};
{privacy_list, PrivList, PrivListName} ->
case ejabberd_hooks:run_fold(privacy_updated_list,
@@ -1571,6 +1575,12 @@ handle_info({route, From, To,
{true, Attrs,
StateData};
deny ->
Err =
jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To,
From,
Err),
{false, Attrs,
StateData}
end;
@@ -1621,7 +1631,6 @@ handle_info({route, From, To,
<<"error">> -> ok;
<<"groupchat">> -> ok;
<<"headline">> -> ok;
<<"result">> -> ok;
_ ->
Err =
jlib:make_error_reply(Packet,
@@ -1664,11 +1673,9 @@ handle_info(system_shutdown, StateName, StateData) ->
wait_for_stream ->
send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>),
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
send_trailer(StateData),
ok;
_ ->
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
send_trailer(StateData),
ok
end,
{stop, normal, StateData};
@@ -1767,8 +1774,7 @@ terminate(_Reason, StateName, StateData) ->
StateData#state.resource,
<<"Replaced by new connection">>),
presence_broadcast(StateData, From,
StateData#state.pres_a, Packet),
handle_unacked_stanzas(StateData);
StateData#state.pres_a, Packet);
_ ->
?INFO_MSG("(~w) Close session for ~s",
[StateData#state.socket,
@@ -1793,13 +1799,26 @@ terminate(_Reason, StateName, StateData) ->
presence_broadcast(StateData, From,
StateData#state.pres_a, Packet)
end,
handle_unacked_stanzas(StateData)
case StateData#state.mgmt_state of
timeout ->
Info = [{num_stanzas_in,
StateData#state.mgmt_stanzas_in}],
ejabberd_sm:set_offline_info(StateData#state.sid,
StateData#state.user,
StateData#state.server,
StateData#state.resource,
Info);
_ ->
ok
end
end,
handle_unacked_stanzas(StateData),
bounce_messages();
true ->
ok
end
end,
catch send_trailer(StateData),
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@@ -1808,8 +1827,9 @@ terminate(_Reason, StateName, StateData) ->
%%%----------------------------------------------------------------------
change_shaper(StateData, JID) ->
Shaper = acl:match_rule(StateData#state.server,
StateData#state.shaper, JID),
Shaper = acl:access_matches(StateData#state.shaper,
#{usr => jid:split(JID), ip => StateData#state.ip},
StateData#state.server),
(StateData#state.sockmod):change_shaper(StateData#state.socket,
Shaper).
@@ -2275,30 +2295,32 @@ get_priority_from_presence(PresencePacket) ->
end.
process_privacy_iq(From, To,
#iq{type = Type, sub_el = SubEl} = IQ, StateData) ->
{Res, NewStateData} = case Type of
get ->
R = ejabberd_hooks:run_fold(privacy_iq_get,
StateData#state.server,
{error,
?ERR_FEATURE_NOT_IMPLEMENTED},
[From, To, IQ,
StateData#state.privacy_list]),
{R, StateData};
set ->
case ejabberd_hooks:run_fold(privacy_iq_set,
StateData#state.server,
{error,
?ERR_FEATURE_NOT_IMPLEMENTED},
[From, To, IQ])
of
{result, R, NewPrivList} ->
{{result, R},
StateData#state{privacy_list =
NewPrivList}};
R -> {R, StateData}
end
end,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ, StateData) ->
Txt = <<"No module is handling this query">>,
{Res, NewStateData} =
case Type of
get ->
R = ejabberd_hooks:run_fold(
privacy_iq_get,
StateData#state.server,
{error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)},
[From, To, IQ,
StateData#state.privacy_list]),
{R, StateData};
set ->
case ejabberd_hooks:run_fold(
privacy_iq_set,
StateData#state.server,
{error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)},
[From, To, IQ])
of
{result, R, NewPrivList} ->
{{result, R},
StateData#state{privacy_list =
NewPrivList}};
R -> {R, StateData}
end
end,
IQRes = case Res of
{result, Result} ->
IQ#iq{type = result, sub_el = Result};
@@ -2365,15 +2387,16 @@ process_unauthenticated_stanza(StateData, El) ->
_ -> El
end,
case jlib:iq_query_info(NewEl) of
#iq{} = IQ ->
#iq{lang = L} = IQ ->
Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
StateData#state.server, empty,
[StateData#state.server, IQ,
StateData#state.ip]),
case Res of
empty ->
Txt = <<"Authentication required">>,
ResIQ = IQ#iq{type = error,
sub_el = [?ERR_SERVICE_UNAVAILABLE]},
sub_el = [?ERRT_SERVICE_UNAVAILABLE(L, Txt)]},
Res1 = jlib:replace_from_to(jid:make(<<"">>,
StateData#state.server,
<<"">>),
@@ -2419,7 +2442,6 @@ fsm_next_state(session_established, #state{mgmt_max_queue = exceeded} =
Err = ?SERRT_POLICY_VIOLATION(StateData#state.lang,
<<"Too many unacked stanzas">>),
send_element(StateData, Err),
send_trailer(StateData),
{stop, normal, StateData#state{mgmt_resend = false}};
fsm_next_state(session_established, #state{mgmt_state = pending} = StateData) ->
fsm_next_state(wait_for_resume, StateData);
@@ -2592,9 +2614,9 @@ stream_mgmt_enabled(#state{mgmt_state = disabled}) ->
stream_mgmt_enabled(_StateData) ->
true.
dispatch_stream_mgmt(El, StateData)
when StateData#state.mgmt_state == active;
StateData#state.mgmt_state == pending ->
dispatch_stream_mgmt(El, #state{mgmt_state = MgmtState} = StateData)
when MgmtState == active;
MgmtState == pending ->
perform_stream_mgmt(El, StateData);
dispatch_stream_mgmt(El, StateData) ->
negotiate_stream_mgmt(El, StateData).
@@ -2725,6 +2747,8 @@ handle_resume(StateData, Attrs) ->
case inherit_session_state(StateData, PrevID) of
{ok, InheritedState} ->
{ok, InheritedState, H};
{error, Err, InH} ->
{error, ?MGMT_ITEM_NOT_FOUND_H(Xmlns, InH), Err};
{error, Err} ->
{error, ?MGMT_ITEM_NOT_FOUND(Xmlns), Err}
end;
@@ -2761,7 +2785,7 @@ handle_resume(StateData, Attrs) ->
#xmlel{name = <<"r">>,
attrs = [{<<"xmlns">>, AttrXmlns}],
children = []}),
FlushedState = csi_queue_flush(NewState),
FlushedState = csi_flush_queue(NewState),
NewStateData = FlushedState#state{csi_state = active},
?INFO_MSG("Resumed session for ~s",
[jid:to_string(NewStateData#state.jid)]),
@@ -2783,7 +2807,9 @@ check_h_attribute(#state{mgmt_stanzas_out = NumStanzasOut} = StateData, H) ->
[jid:to_string(StateData#state.jid), H, NumStanzasOut]),
mgmt_queue_drop(StateData, H).
update_num_stanzas_in(#state{mgmt_state = active} = StateData, El) ->
update_num_stanzas_in(#state{mgmt_state = MgmtState} = StateData, El)
when MgmtState == active;
MgmtState == pending ->
NewNum = case {is_stanza(El), StateData#state.mgmt_stanzas_in} of
{true, 4294967295} ->
0;
@@ -2838,9 +2864,10 @@ check_queue_length(#state{mgmt_queue = Queue,
StateData
end.
handle_unacked_stanzas(StateData, F)
when StateData#state.mgmt_state == active;
StateData#state.mgmt_state == pending ->
handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData, F)
when MgmtState == active;
MgmtState == pending;
MgmtState == timeout ->
Queue = StateData#state.mgmt_queue,
case queue:len(Queue) of
0 ->
@@ -2860,9 +2887,10 @@ handle_unacked_stanzas(StateData, F)
handle_unacked_stanzas(_StateData, _F) ->
ok.
handle_unacked_stanzas(StateData)
when StateData#state.mgmt_state == active;
StateData#state.mgmt_state == pending ->
handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
when MgmtState == active;
MgmtState == pending;
MgmtState == timeout ->
ResendOnTimeout =
case StateData#state.mgmt_resend of
Resend when is_boolean(Resend) ->
@@ -2879,6 +2907,7 @@ handle_unacked_stanzas(StateData)
false
end
end,
Lang = StateData#state.lang,
ReRoute = case ResendOnTimeout of
true ->
fun(From, To, El, Time) ->
@@ -2887,9 +2916,11 @@ handle_unacked_stanzas(StateData)
end;
false ->
fun(From, To, El, _Time) ->
Txt = <<"User session terminated">>,
Err =
jlib:make_error_reply(El,
?ERR_SERVICE_UNAVAILABLE),
jlib:make_error_reply(
El,
?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end
end,
@@ -2897,7 +2928,9 @@ handle_unacked_stanzas(StateData)
?DEBUG("Dropping presence stanza from ~s",
[jid:to_string(From)]);
(From, To, #xmlel{name = <<"iq">>} = El, _Time) ->
Err = jlib:make_error_reply(El, ?ERR_SERVICE_UNAVAILABLE),
Txt = <<"User session terminated">>,
Err = jlib:make_error_reply(
El, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err);
(From, To, El, Time) ->
%% We'll drop the stanza if it was <forwarded/> by some
@@ -2959,7 +2992,17 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
{term, {R, Time}} ->
case ejabberd_sm:get_session_pid(U, S, R) of
none ->
{error, <<"Previous session PID not found">>};
case ejabberd_sm:get_offline_info(Time, U, S, R) of
none ->
{error, <<"Previous session PID not found">>};
Info ->
case proplists:get_value(num_stanzas_in, Info) of
undefined ->
{error, <<"Previous session timed out">>};
H ->
{error, <<"Previous session timed out">>, H}
end
end;
OldPID ->
OldSID = {Time, OldPID},
case catch resume_session(OldSID) of
@@ -2988,7 +3031,6 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
privacy_list = OldStateData#state.privacy_list,
aux_fields = OldStateData#state.aux_fields,
csi_state = OldStateData#state.csi_state,
csi_queue = OldStateData#state.csi_queue,
mgmt_xmlns = OldStateData#state.mgmt_xmlns,
mgmt_queue = OldStateData#state.mgmt_queue,
mgmt_timeout = OldStateData#state.mgmt_timeout,
@@ -3021,65 +3063,25 @@ add_resent_delay_info(#state{server = From}, El, Time) ->
%%% XEP-0352
%%%----------------------------------------------------------------------
csi_filter_stanza(#state{csi_state = CsiState, jid = JID} = StateData,
csi_filter_stanza(#state{csi_state = CsiState, server = Server} = StateData,
Stanza) ->
Action = ejabberd_hooks:run_fold(csi_filter_stanza,
StateData#state.server,
send, [Stanza]),
?DEBUG("Going to ~p stanza for inactive client ~p",
[Action, jid:to_string(JID)]),
case Action of
queue -> csi_queue_add(StateData, Stanza);
drop -> StateData;
send ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
StateData1 = csi_queue_send(StateData, From),
StateData2 = send_stanza(StateData1#state{csi_state = active},
Stanza),
StateData2#state{csi_state = CsiState}
end.
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_filter_stanza, Server,
{StateData, [Stanza]},
[Server, Stanza]),
StateData2 = lists:foldl(fun(CurStanza, AccState) ->
send_stanza(AccState, CurStanza)
end, StateData1#state{csi_state = active},
Stanzas),
StateData2#state{csi_state = CsiState}.
csi_queue_add(#state{csi_queue = Queue} = StateData, Stanza) ->
case length(StateData#state.csi_queue) >= csi_max_queue(StateData) of
true -> csi_queue_add(csi_queue_flush(StateData), Stanza);
false ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
NewQueue = lists:keystore(From, 1, Queue, {From, p1_time_compat:timestamp(), Stanza}),
StateData#state{csi_queue = NewQueue}
end.
csi_queue_send(#state{csi_queue = Queue, csi_state = CsiState, server = Host} =
StateData, From) ->
case lists:keytake(From, 1, Queue) of
{value, {From, Time, Stanza}, NewQueue} ->
NewStanza = jlib:add_delay_info(Stanza, Host, Time,
<<"Client Inactive">>),
NewStateData = send_stanza(StateData#state{csi_state = active},
NewStanza),
NewStateData#state{csi_queue = NewQueue, csi_state = CsiState};
false -> StateData
end.
csi_queue_flush(#state{csi_queue = Queue, csi_state = CsiState, jid = JID,
server = Host} = StateData) ->
?DEBUG("Flushing CSI queue for ~s", [jid:to_string(JID)]),
NewStateData =
lists:foldl(fun({_From, Time, Stanza}, AccState) ->
NewStanza =
jlib:add_delay_info(Stanza, Host, Time,
<<"Client Inactive">>),
send_stanza(AccState, NewStanza)
end, StateData#state{csi_state = active}, Queue),
NewStateData#state{csi_queue = [], csi_state = CsiState}.
%% Make sure we won't push too many messages to the XEP-0198 queue when the
%% client becomes 'active' again. Otherwise, the client might not manage to
%% acknowledge the message flood in time. Also, don't let the queue grow to
%% more than 100 stanzas.
csi_max_queue(#state{mgmt_max_queue = infinity}) -> 100;
csi_max_queue(#state{mgmt_max_queue = Max}) when Max > 200 -> 100;
csi_max_queue(#state{mgmt_max_queue = Max}) when Max < 2 -> 1;
csi_max_queue(#state{mgmt_max_queue = Max}) -> Max div 2.
csi_flush_queue(#state{csi_state = CsiState, server = Server} = StateData) ->
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_flush_queue, Server,
{StateData, []}, [Server]),
StateData2 = lists:foldl(fun(CurStanza, AccState) ->
send_stanza(AccState, CurStanza)
end, StateData1#state{csi_state = active},
Stanzas),
StateData2#state{csi_state = CsiState}.
%%%----------------------------------------------------------------------
%%% JID Set memory footprint reduction code
+255 -121
View File
@@ -90,7 +90,8 @@
%%% PowFloat = math:pow(Base, Exponent),
%%% round(PowFloat).</pre>
%%%
%%% Since this function will be called by ejabberd_commands, it must be exported.
%%% Since this function will be called by ejabberd_commands, it must
%%% be exported.
%%% Add to your module:
%%% <pre>-export([calc_power/2]).</pre>
%%%
@@ -201,24 +202,35 @@
%%% TODO: consider this feature:
%%% All commands are catched. If an error happens, return the restuple:
%%% {error, flattened error string}
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc) need to allows this.
%%% And ejabberd_xmlrpc must be prepared to handle such an unexpected response.
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc)
%%% need to allows this. And ejabberd_xmlrpc must be prepared to
%%% handle such an unexpected response.
-module(ejabberd_commands).
-author('badlop@process-one.net').
-define(DEFAULT_VERSION, 1000000).
-export([init/0,
list_commands/0,
list_commands/1,
get_command_format/1,
get_command_format/2,
get_command_format/2,
get_command_format/3,
get_command_policy/1,
get_command_definition/1,
get_command_definition/2,
get_tags_commands/0,
get_tags_commands/1,
get_commands/0,
register_commands/1,
unregister_commands/1,
execute_command/2,
execute_command/4,
execute_command/3,
execute_command/4,
execute_command/5,
execute_command/6,
opt_type/1,
get_commands_spec/0
]).
@@ -226,6 +238,7 @@
-include("ejabberd_commands.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-define(POLICY_ACCESS, '$policy').
@@ -260,23 +273,26 @@ get_commands_spec() ->
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result_example = ok}].
init() ->
ets:new(ejabberd_commands, [named_table, set, public,
{keypos, #ejabberd_commands.name}]),
mnesia:delete_table(ejabberd_commands),
mnesia:create_table(ejabberd_commands,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, ejabberd_commands)},
{type, bag}]),
mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
register_commands(get_commands_spec()).
-spec register_commands([ejabberd_commands()]) -> ok.
%% @doc Register ejabberd commands.
%% If a command is already registered, a warning is printed and the old command is preserved.
%% If a command is already registered, a warning is printed and the
%% old command is preserved.
register_commands(Commands) ->
lists:foreach(
fun(Command) ->
case ets:insert_new(ejabberd_commands, Command) of
true ->
ok;
false ->
?DEBUG("This command is already defined:~n~p", [Command])
end
% XXX check if command exists
mnesia:dirty_write(Command)
% ?DEBUG("This command is already defined:~n~p", [Command])
end,
Commands).
@@ -286,7 +302,7 @@ register_commands(Commands) ->
unregister_commands(Commands) ->
lists:foreach(
fun(Command) ->
ets:delete_object(ejabberd_commands, Command)
mnesia:dirty_delete_object(Command)
end,
Commands).
@@ -294,94 +310,197 @@ unregister_commands(Commands) ->
%% @doc Get a list of all the available commands, arguments and description.
list_commands() ->
Commands = ets:match(ejabberd_commands,
#ejabberd_commands{name = '$1',
args = '$2',
desc = '$3',
_ = '_'}),
[{A, B, C} || [A, B, C] <- Commands].
list_commands(?DEFAULT_VERSION).
-spec list_commands_policy() -> [{atom(), [aterm()], string(), atom()}].
-spec list_commands(integer()) -> [{atom(), [aterm()], string()}].
%% @doc Get a list of all the available commands, arguments, description, and
%% policy.
list_commands_policy() ->
Commands = ets:match(ejabberd_commands,
#ejabberd_commands{name = '$1',
args = '$2',
desc = '$3',
policy = '$4',
_ = '_'}),
[{A, B, C, D} || [A, B, C, D] <- Commands].
%% @doc Get a list of all the available commands, arguments and
%% description in a given API verion.
list_commands(Version) ->
Commands = get_commands_definition(Version),
[{Name, Args, Desc} || #ejabberd_commands{name = Name,
args = Args,
desc = Desc} <- Commands].
-spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
-spec list_commands_policy(integer()) ->
[{atom(), [aterm()], string(), atom()}].
%% @doc Get a list of all the available commands, arguments,
%% description, and policy in a given API version.
list_commands_policy(Version) ->
Commands = get_commands_definition(Version),
[{Name, Args, Desc, Policy} ||
#ejabberd_commands{name = Name,
args = Args,
desc = Desc,
policy = Policy} <- Commands].
-spec get_command_format(atom()) -> {[aterm()], rterm()}.
%% @doc Get the format of arguments and result of a command.
get_command_format(Name) ->
get_command_format(Name, noauth).
get_command_format(Name, noauth, ?DEFAULT_VERSION).
get_command_format(Name, Version) when is_integer(Version) ->
get_command_format(Name, noauth, Version);
get_command_format(Name, Auth) ->
get_command_format(Name, Auth, ?DEFAULT_VERSION).
get_command_format(Name, Auth) ->
Admin = is_admin(Name, Auth),
Matched = ets:match(ejabberd_commands,
#ejabberd_commands{name = Name,
args = '$1',
result = '$2',
policy = '$3',
_ = '_'}),
case Matched of
[] ->
{error, command_unknown};
[[Args, Result, user]] when Admin;
Auth == noauth ->
-spec get_command_format(atom(),
{binary(), binary(), binary(), boolean()} |
noauth | admin,
integer()) ->
{[aterm()], rterm()}.
get_command_format(Name, Auth, Version) ->
Admin = is_admin(Name, Auth, #{}),
#ejabberd_commands{args = Args,
result = Result,
policy = Policy} =
get_command_definition(Name, Version),
case Policy of
user when Admin;
Auth == noauth ->
{[{user, binary}, {server, binary} | Args], Result};
[[Args, Result, _]] ->
_ ->
{Args, Result}
end.
-spec get_command_definition(atom()) -> ejabberd_commands() | command_not_found.
-spec get_command_policy(atom()) -> {ok, open|user|admin|restricted} | {error, command_not_found}.
%% @doc return command policy.
get_command_policy(Name) ->
case get_command_definition(Name) of
#ejabberd_commands{policy = Policy} ->
{ok, Policy};
command_not_found ->
{error, command_not_found}
end.
-spec get_command_definition(atom()) -> ejabberd_commands().
%% @doc Get the definition record of a command.
get_command_definition(Name) ->
case ets:lookup(ejabberd_commands, Name) of
[E] -> E;
[] -> command_not_found
get_command_definition(Name, ?DEFAULT_VERSION).
-spec get_command_definition(atom(), integer()) -> ejabberd_commands().
%% @doc Get the definition record of a command in a given API version.
get_command_definition(Name, Version) ->
case lists:reverse(
lists:sort(
mnesia:dirty_select(
ejabberd_commands,
ets:fun2ms(
fun(#ejabberd_commands{name = N, version = V} = C)
when N == Name, V =< Version ->
{V, C}
end)))) of
[{_, Command} | _ ] -> Command;
_E -> throw(unknown_command)
end.
%% @spec (Name::atom(), Arguments) -> ResultTerm | {error, command_unknown}
%% @doc Execute a command.
execute_command(Name, Arguments) ->
execute_command([], noauth, Name, Arguments).
-spec get_commands_definition(integer()) -> [ejabberd_commands()].
-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
{binary(), binary(), binary(), boolean()} |
noauth | admin,
atom(),
[any()]
% @doc Returns all commands for a given API version
get_commands_definition(Version) ->
L = lists:reverse(
lists:sort(
mnesia:dirty_select(
ejabberd_commands,
ets:fun2ms(
fun(#ejabberd_commands{name = Name, version = V} = C)
when V =< Version ->
{Name, V, C}
end)))),
F = fun({_Name, _V, Command}, []) ->
[Command];
({Name, _V, _Command}, [#ejabberd_commands{name=Name}|_T] = Acc) ->
Acc;
({_Name, _V, Command}, Acc) -> [Command | Acc]
end,
lists:foldl(F, [], L).
%% @spec (Name::atom(), Arguments) -> ResultTerm
%% where
%% Arguments = [any()]
%% @doc Execute a command.
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data |
%% no_auth_provided
execute_command(Name, Arguments) ->
execute_command(Name, Arguments, ?DEFAULT_VERSION).
-spec execute_command(atom(),
[any()],
integer() |
{binary(), binary(), binary(), boolean()} |
noauth | admin
) -> any().
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
%% where
%% Auth = {User::string(), Server::string(), Password::string(),
%% Admin::boolean()}
%% | noauth
%% | admin
%% Arguments = [any()]
%%
%% @doc Execute a command in a given API version
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data |
%% no_auth_provided
execute_command(Name, Arguments, Version) when is_integer(Version) ->
execute_command([], noauth, Name, Arguments, Version);
execute_command(Name, Arguments, Auth) ->
execute_command([], Auth, Name, Arguments, ?DEFAULT_VERSION).
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) ->
%% ResultTerm | {error, Error}
%% where
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
%% | noauth
%% | admin
%% Method = atom()
%% Arguments = [any()]
%% Error = command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
execute_command(AccessCommands1, Auth1, Name, Arguments) ->
Auth = case is_admin(Name, Auth1) of
%%
%% @doc Execute a command
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
execute_command(AccessCommands, Auth, Name, Arguments) ->
execute_command(AccessCommands, Auth, Name, Arguments, ?DEFAULT_VERSION).
-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
{binary(), binary(), binary(), boolean()} |
noauth | admin,
atom(),
[any()],
integer()
) -> any().
%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
%% where
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
%% | noauth
%% | admin
%% Arguments = [any()]
%%
%% @doc Execute a command in a given API version
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}).
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) ->
Auth = case is_admin(Name, Auth1, CallerInfo) of
true -> admin;
false -> Auth1
end,
case ets:lookup(ejabberd_commands, Name) of
[Command] ->
AccessCommands = get_access_commands(AccessCommands1),
try check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
ok -> execute_command2(Auth, Command, Arguments)
catch
{error, Error} -> {error, Error}
end;
[] -> {error, command_unknown}
Command = get_command_definition(Name, Version),
AccessCommands = get_access_commands(AccessCommands1, Version),
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
ok -> execute_command2(Auth, Command, Arguments)
end.
execute_command2(
@@ -407,26 +526,25 @@ execute_command2(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
Function = Command#ejabberd_commands.function,
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
try apply(Module, Function, Arguments) of
Response ->
Response
catch
Problem ->
{error, Problem}
end.
apply(Module, Function, Arguments).
-spec get_tags_commands() -> [{string(), [string()]}].
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
%% @doc Get all the tags and associated commands.
get_tags_commands() ->
CommandTags = ets:match(ejabberd_commands,
#ejabberd_commands{
name = '$1',
tags = '$2',
_ = '_'}),
get_tags_commands(?DEFAULT_VERSION).
-spec get_tags_commands(integer()) -> [{string(), [string()]}].
%% @spec (integer) -> [{Tag::string(), [CommandName::string()]}]
%% @doc Get all the tags and associated commands in a given API version
get_tags_commands(Version) ->
CommandTags = [{Name, Tags} ||
#ejabberd_commands{name = Name, tags = Tags}
<- get_commands_definition(Version)],
Dict = lists:foldl(
fun([CommandNameAtom, CTags], D) ->
fun({CommandNameAtom, CTags}, D) ->
CommandName = atom_to_list(CommandNameAtom),
case CTags of
[] ->
@@ -445,7 +563,6 @@ get_tags_commands() ->
CommandTags),
orddict:to_list(Dict).
%% -----------------------------
%% Access verification
%% -----------------------------
@@ -460,9 +577,9 @@ get_tags_commands() ->
%% At least one AccessCommand must be satisfied.
%% It may throw {error, Error} where:
%% Error = account_unprivileged | invalid_account_data
check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
check_access_commands([], _Auth, _Method, _Command, _Arguments, _CallerInfo) ->
ok;
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerInfo) ->
Command =
case {Command1#ejabberd_commands.policy, Auth} of
{user, {_, _, _, _}} ->
@@ -477,18 +594,20 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
AccessCommandsAllowed =
lists:filter(
fun({Access, Commands, ArgumentRestrictions}) ->
case check_access(Command, Access, Auth) of
case check_access(Command, Access, Auth, CallerInfo) of
true ->
check_access_command(Commands, Command, ArgumentRestrictions,
check_access_command(Commands, Command,
ArgumentRestrictions,
Method, Arguments);
false ->
false
end;
({Access, Commands}) ->
ArgumentRestrictions = [],
case check_access(Command, Access, Auth) of
case check_access(Command, Access, Auth, CallerInfo) of
true ->
check_access_command(Commands, Command, ArgumentRestrictions,
check_access_command(Commands, Command,
ArgumentRestrictions,
Method, Arguments);
false ->
false
@@ -522,38 +641,47 @@ check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
_ -> throw({error, invalid_account_data})
end.
check_access(Command, ?POLICY_ACCESS, _)
check_access(Command, ?POLICY_ACCESS, _, _)
when Command#ejabberd_commands.policy == open ->
true;
check_access(_Command, _Access, admin) ->
check_access(_Command, _Access, admin, _) ->
true;
check_access(_Command, _Access, {_User, _Server, _, true}) ->
check_access(_Command, _Access, {_User, _Server, _, true}, _) ->
false;
check_access(Command, Access, Auth)
check_access(Command, Access, Auth, CallerInfo)
when Access =/= ?POLICY_ACCESS;
Command#ejabberd_commands.policy == open;
Command#ejabberd_commands.policy == user ->
case check_auth(Command, Auth) of
{ok, User, Server} ->
check_access2(Access, User, Server);
check_access2(Access, CallerInfo#{usr => jid:split(jid:make(User, Server, <<>>))}, Server);
no_auth_provided ->
case Command#ejabberd_commands.policy of
user ->
false;
_ ->
check_access2(Access, CallerInfo, global)
end;
_ ->
false
end;
check_access(_Command, _Access, _Auth) ->
check_access(_Command, _Access, _Auth, _CallerInfo) ->
false.
check_access2(?POLICY_ACCESS, _User, _Server) ->
check_access2(?POLICY_ACCESS, _CallerInfo, _Server) ->
true;
check_access2(Access, User, Server) ->
check_access2(Access, AccessInfo, Server) ->
%% Check this user has access permission
case acl:match_rule(Server, Access, jid:make(User, Server, <<"">>)) of
case acl:access_matches(Access, AccessInfo, Server) of
allow -> true;
deny -> false
end.
check_access_command(Commands, Command, ArgumentRestrictions, Method, Arguments) ->
check_access_command(Commands, Command, ArgumentRestrictions,
Method, Arguments) ->
case Commands==all orelse lists:member(Method, Commands) of
true -> check_access_arguments(Command, ArgumentRestrictions, Arguments);
true -> check_access_arguments(Command, ArgumentRestrictions,
Arguments);
false -> false
end.
@@ -577,18 +705,21 @@ tag_arguments(ArgsDefs, Args) ->
Args).
get_access_commands(undefined) ->
Cmds = get_commands(),
get_access_commands(undefined, Version) ->
Cmds = get_commands(Version),
[{?POLICY_ACCESS, Cmds, []}];
get_access_commands(AccessCommands) ->
get_access_commands(AccessCommands, _Version) ->
AccessCommands.
get_commands() ->
Opts = ejabberd_config:get_option(
get_commands(?DEFAULT_VERSION).
get_commands(Version) ->
Opts0 = ejabberd_config:get_option(
commands,
fun(V) when is_list(V) -> V end,
[]),
CommandsList = list_commands_policy(),
Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0),
CommandsList = list_commands_policy(Version),
OpenCmds = [N || {N, _, _, open} <- CommandsList],
RestrictedCmds = [N || {N, _, _, restricted} <- CommandsList],
AdminCmds = [N || {N, _, _, admin} <- CommandsList],
@@ -617,29 +748,32 @@ get_commands() ->
end, AdminCmds ++ UserCmds, Opts),
Cmds.
is_admin(_Name, noauth) ->
false;
is_admin(_Name, admin) ->
is_admin(_Name, admin, _Extra) ->
true;
is_admin(_Name, {_User, _Server, _, false}) ->
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
false;
is_admin(Name, {User, Server, _, true} = Auth) ->
is_admin(Name, Auth, Extra) ->
{ACLInfo, Server} = case Auth of
{U, S, _, _} ->
{Extra#{usr=>jid:split(jid:make(U, S, <<>>))}, S};
_ ->
{Extra, global}
end,
AdminAccess = ejabberd_config:get_option(
commands_admin_access,
fun(A) when is_atom(A) -> A end,
fun(V) -> V end,
none),
case acl:match_rule(Server, AdminAccess,
jid:make(User, Server, <<"">>)) of
case acl:access_matches(AdminAccess, ACLInfo, Server) of
allow ->
case catch check_auth(get_command_definition(Name), Auth) of
{ok, _, _} -> true;
no_auth_provided -> true;
_ -> false
end;
deny -> false
end.
opt_type(commands_admin_access) ->
fun(A) when is_atom(A) -> A end;
opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
opt_type(commands) ->
fun(V) when is_list(V) -> V end;
opt_type(_) -> [commands, commands_admin_access].
+189 -55
View File
@@ -30,12 +30,12 @@
add_global_option/2, add_local_option/2,
get_global_option/2, get_local_option/2,
get_global_option/3, get_local_option/3,
get_option/2, get_option/3, add_option/2,
get_option/2, get_option/3, add_option/2, has_option/1,
get_vh_by_auth_method/1, is_file_readable/1,
get_version/0, get_myhosts/0, get_mylang/0,
prepare_opt_val/4, convert_table_to_binary/5,
transform_options/1, collect_options/1,
convert_to_yaml/1, convert_to_yaml/2,
transform_options/1, collect_options/1, default_db/2,
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
env_binary_to_list/2, opt_type/1, may_hide_data/1]).
-export([start/2]).
@@ -93,7 +93,7 @@ hosts_to_start(State) ->
-spec(start/2 :: (Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok).
start(Hosts, Opts) ->
mnesia_init(),
set_opts(#state{hosts = Hosts, opts = Opts}).
set_opts(set_hosts_in_options(Hosts, #state{opts = Opts})).
mnesia_init() ->
case catch mnesia:table_info(local_config, storage_type) of
@@ -227,6 +227,7 @@ get_plain_terms_file(File, Opts) when is_binary(File) ->
get_plain_terms_file(binary_to_list(File), Opts);
get_plain_terms_file(File1, Opts) ->
File = get_absolute_path(File1),
DontStopOnError = lists:member(dont_halt_on_error, Opts),
case consult(File) of
{ok, Terms} ->
BinTerms1 = strings_to_binary(Terms),
@@ -246,9 +247,21 @@ get_plain_terms_file(File1, Opts) ->
false ->
BinTerms
end;
{error, Reason} ->
{error, enoent, Reason} ->
case DontStopOnError of
true ->
?WARNING_MSG(Reason, []),
[];
_ ->
?ERROR_MSG(Reason, []),
exit_or_halt(Reason)
end;
{error, Reason} ->
?ERROR_MSG(Reason, []),
case DontStopOnError of
true -> [];
_ -> exit_or_halt(Reason)
end
end.
consult(File) ->
@@ -262,17 +275,29 @@ consult(File) ->
{error, Err} ->
Msg1 = "Cannot load " ++ File ++ ": ",
Msg2 = fast_yaml:format_error(Err),
case Err of
enoent ->
{error, enoent, Msg1 ++ Msg2};
_ ->
{error, Msg1 ++ Msg2}
end
end;
_ ->
case file:consult(File) of
{ok, Terms} ->
{ok, Terms};
{error, enoent} ->
{error, enoent};
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
{error, describe_config_problem(File, Reason, LineNumber)};
{error, Reason} ->
case Reason of
enoent ->
{error, enoent, describe_config_problem(File, Reason)};
_ ->
{error, describe_config_problem(File, Reason)}
end
end
end.
parserl(<<"> ", Term/binary>>) ->
@@ -473,8 +498,8 @@ include_config_files(Terms) ->
include_config_file(File, Opts)
end, lists:flatten(FileOpts)),
M1 = merge_configs(transform_terms(Terms1), #{}),
M2 = merge_configs(transform_terms(Terms2), M1),
M1 = merge_configs(Terms1, #{}),
M2 = merge_configs(Terms2, M1),
maps_to_lists(M2).
transform_include_option({include_config_file, File}) when is_list(File) ->
@@ -488,7 +513,7 @@ transform_include_option({include_config_file, Filename, Options}) ->
{Filename, Options}.
include_config_file(Filename, Options) ->
Included_terms = get_plain_terms_file(Filename),
Included_terms = get_plain_terms_file(Filename, [{include_files, true}, dont_halt_on_error]),
Disallow = proplists:get_value(disallow, Options, []),
Included_terms2 = delete_disallowed(Disallow, Included_terms),
Allow_only = proplists:get_value(allow_only, Options, all),
@@ -651,14 +676,42 @@ process_host_term(Term, Host, State, Action) ->
{hosts, _} ->
State;
{Opt, Val} when Action == set ->
set_option({Opt, Host}, Val, State);
set_option({rename_option(Opt), Host}, change_val(Opt, Val), State);
{Opt, Val} when Action == append ->
append_option({Opt, Host}, Val, State);
append_option({rename_option(Opt), Host}, change_val(Opt, Val), State);
Opt ->
?WARNING_MSG("Ignore invalid (outdated?) option ~p", [Opt]),
State
end.
rename_option(Option) when is_atom(Option) ->
case atom_to_list(Option) of
"odbc_" ++ T ->
NewOption = list_to_atom("sql_" ++ T),
?WARNING_MSG("Option '~s' is obsoleted, use '~s' instead",
[Option, NewOption]),
NewOption;
_ ->
Option
end;
rename_option(Option) ->
Option.
change_val(auth_method, Val) ->
prepare_opt_val(auth_method, Val,
fun(V) ->
L = if is_list(V) -> V;
true -> [V]
end,
lists:map(
fun(odbc) -> sql;
(internal) -> mnesia;
(A) when is_atom(A) -> A
end, L)
end, [mnesia]);
change_val(_Opt, Val) ->
Val.
set_option(Opt, Val, State) ->
State#state{opts = [#local_config{key = Opt, value = Val} |
State#state.opts]}.
@@ -717,24 +770,32 @@ add_option(Opt, Val) ->
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
prepare_opt_val(Opt, Val, F, Default) ->
Res = case F of
{Mod, Fun} ->
catch Mod:Fun(Val);
_ ->
catch F(Val)
end,
case Res of
{'EXIT', _} ->
?INFO_MSG("Configuration problem:~n"
"** Option: ~s~n"
"** Invalid value: ~s~n"
"** Using as fallback: ~s",
[format_term(Opt),
format_term(Val),
format_term(Default)]),
Default;
_ ->
Res
Call = case F of
{Mod, Fun} ->
fun() -> Mod:Fun(Val) end;
_ ->
fun() -> F(Val) end
end,
try Call() of
Res ->
Res
catch {replace_with, NewRes} ->
NewRes;
{invalid_syntax, Error} ->
?WARNING_MSG("incorrect value '~s' of option '~s', "
"using '~s' as fallback: ~s",
[format_term(Val),
format_term(Opt),
format_term(Default),
Error]),
Default;
_:_ ->
?WARNING_MSG("incorrect value '~s' of option '~s', "
"using '~s' as fallback",
[format_term(Val),
format_term(Opt),
format_term(Default)]),
Default
end.
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
@@ -787,9 +848,61 @@ get_option(Opt, F, Default) ->
end
end.
-spec has_option(atom() | {atom(), global | binary()}) -> any().
has_option(Opt) ->
get_option(Opt, fun(_) -> true end, false).
init_module_db_table(Modules) ->
catch ets:new(module_db, [named_table, public, bag]),
%% Dirty hack for mod_pubsub
ets:insert(module_db, {mod_pubsub, mnesia}),
ets:insert(module_db, {mod_pubsub, sql}),
lists:foreach(
fun(M) ->
case re:split(atom_to_list(M), "_", [{return, list}]) of
[_] ->
ok;
Parts ->
[Suffix|T] = lists:reverse(Parts),
BareMod = string:join(lists:reverse(T), "_"),
ets:insert(module_db, {list_to_atom(BareMod),
list_to_atom(Suffix)})
end
end, Modules).
-spec v_db(module(), atom()) -> atom().
v_db(Mod, internal) -> v_db(Mod, mnesia);
v_db(Mod, odbc) -> v_db(Mod, sql);
v_db(Mod, Type) ->
case ets:match_object(module_db, {Mod, Type}) of
[_|_] -> Type;
[] -> erlang:error(badarg)
end.
-spec default_db(binary(), module()) -> atom().
default_db(Host, Module) ->
case ejabberd_config:get_option(
{default_db, Host}, fun(T) when is_atom(T) -> T end) of
undefined ->
mnesia;
DBType ->
try
v_db(Module, DBType)
catch error:badarg ->
?WARNING_MSG("Module '~s' doesn't support database '~s' "
"defined in option 'default_db', using "
"'mnesia' as fallback", [Module, DBType]),
mnesia
end
end.
get_modules_with_options() ->
{ok, Mods} = application:get_key(ejabberd, modules),
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
AllMods = [?MODULE|ExtMods++Mods],
init_module_db_table(AllMods),
lists:foldl(
fun(Mod, D) ->
case catch Mod:opt_type('') of
@@ -801,23 +914,30 @@ get_modules_with_options() ->
{'EXIT', {undef, _}} ->
D
end
end, dict:new(), [?MODULE|ExtMods++Mods]).
end, dict:new(), AllMods).
validate_opts(#state{opts = Opts} = State) ->
ModOpts = get_modules_with_options(),
NewOpts = lists:filter(
fun(#local_config{key = {Opt, _Host}, value = Val}) ->
NewOpts = lists:filtermap(
fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
case dict:find(Opt, ModOpts) of
{ok, [Mod|_]} ->
VFun = Mod:opt_type(Opt),
case catch VFun(Val) of
{'EXIT', _} ->
try VFun(Val) of
_ ->
true
catch {replace_with, NewVal} ->
{true, In#local_config{value = NewVal}};
{invalid_syntax, Error} ->
?ERROR_MSG("ignoring option '~s' with "
"invalid value: ~p: ~s",
[Opt, Val, Error]),
false;
_:_ ->
?ERROR_MSG("ignoring option '~s' with "
"invalid value: ~p",
[Opt, Val]),
false;
_ ->
true
false
end;
_ ->
?ERROR_MSG("unknown option '~s' will be likely"
@@ -829,11 +949,25 @@ validate_opts(#state{opts = Opts} = State) ->
-spec get_vh_by_auth_method(atom()) -> [binary()].
%% Return the list of hosts handled by a given module
%% Return the list of hosts with a given auth method
get_vh_by_auth_method(AuthMethod) ->
mnesia:dirty_select(local_config,
[{#local_config{key = {auth_method, '$1'},
value=AuthMethod},[],['$1']}]).
Cfgs = mnesia:dirty_match_object(local_config,
#local_config{key = {auth_method, '_'},
_ = '_'}),
lists:flatmap(
fun(#local_config{key = {auth_method, Host}, value = M}) ->
Methods = if not is_list(M) -> [M];
true -> M
end,
case lists:member(AuthMethod, Methods) of
true when Host == global ->
get_myhosts();
true ->
[Host];
false ->
[]
end
end, Cfgs).
%% @spec (Path::string()) -> true | false
is_file_readable(Path) ->
@@ -867,20 +1001,20 @@ get_mylang() ->
fun iolist_to_binary/1,
<<"en">>).
replace_module(mod_announce_odbc) -> {mod_announce, odbc};
replace_module(mod_blocking_odbc) -> {mod_blocking, odbc};
replace_module(mod_caps_odbc) -> {mod_caps, odbc};
replace_module(mod_irc_odbc) -> {mod_irc, odbc};
replace_module(mod_last_odbc) -> {mod_last, odbc};
replace_module(mod_muc_odbc) -> {mod_muc, odbc};
replace_module(mod_offline_odbc) -> {mod_offline, odbc};
replace_module(mod_privacy_odbc) -> {mod_privacy, odbc};
replace_module(mod_private_odbc) -> {mod_private, odbc};
replace_module(mod_roster_odbc) -> {mod_roster, odbc};
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
replace_module(mod_pubsub_odbc) -> {mod_pubsub, odbc};
replace_module(mod_announce_odbc) -> {mod_announce, sql};
replace_module(mod_blocking_odbc) -> {mod_blocking, sql};
replace_module(mod_caps_odbc) -> {mod_caps, sql};
replace_module(mod_irc_odbc) -> {mod_irc, sql};
replace_module(mod_last_odbc) -> {mod_last, sql};
replace_module(mod_muc_odbc) -> {mod_muc, sql};
replace_module(mod_offline_odbc) -> {mod_offline, sql};
replace_module(mod_privacy_odbc) -> {mod_privacy, sql};
replace_module(mod_private_odbc) -> {mod_private, sql};
replace_module(mod_roster_odbc) -> {mod_roster, sql};
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql};
replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
replace_module(Module) ->
case is_elixir_module(Module) of
true -> expand_elixir_module(Module);
@@ -985,7 +1119,7 @@ transform_terms(Terms) ->
mod_last,
ejabberd_s2s,
ejabberd_listener,
ejabberd_odbc_sup,
ejabberd_sql_sup,
shaper,
ejabberd_s2s_out,
acl,
+86 -56
View File
@@ -57,6 +57,8 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-define(DEFAULT_VERSION, 1000000).
%%-----------------------------
%% Module
@@ -69,7 +71,7 @@ start() ->
[SNode3 | Args3] ->
[SNode3, 60000, Args3];
_ ->
print_usage(),
print_usage(?DEFAULT_VERSION),
halt(?STATUS_USAGE)
end,
SNode1 = case string:tokens(SNode, "@") of
@@ -93,6 +95,9 @@ start() ->
[Node, Reason]),
%% TODO: show minimal start help
?STATUS_BADRPC;
{invalid_version, V} ->
print("Invalid API version number: ~p~n", [V]),
?STATUS_ERROR;
S ->
S
end,
@@ -126,11 +131,17 @@ unregister_commands(CmdDescs, Module, Function) ->
%% Process
%%-----------------------------
-spec process([string()]) -> non_neg_integer().
process(Args) ->
process(Args, ?DEFAULT_VERSION).
-spec process([string()], non_neg_integer()) -> non_neg_integer().
%% The commands status, stop and restart are defined here to ensure
%% they are usable even if ejabberd is completely stopped.
process(["status"]) ->
process(["status"], _Version) ->
{InternalStatus, ProvidedStatus} = init:get_status(),
print("The node ~p is ~p with status: ~p~n",
[node(), InternalStatus, ProvidedStatus]),
@@ -146,24 +157,24 @@ process(["status"]) ->
?STATUS_SUCCESS
end;
process(["stop"]) ->
process(["stop"], _Version) ->
%%ejabberd_cover:stop(),
init:stop(),
?STATUS_SUCCESS;
process(["restart"]) ->
process(["restart"], _Version) ->
init:restart(),
?STATUS_SUCCESS;
process(["mnesia"]) ->
process(["mnesia"], _Version) ->
print("~p~n", [mnesia:system_info(all)]),
?STATUS_SUCCESS;
process(["mnesia", "info"]) ->
process(["mnesia", "info"], _Version) ->
mnesia:info(),
?STATUS_SUCCESS;
process(["mnesia", Arg]) ->
process(["mnesia", Arg], _Version) ->
case catch mnesia:system_info(list_to_atom(Arg)) of
{'EXIT', Error} -> print("Error: ~p~n", [Error]);
Return -> print("~p~n", [Return])
@@ -172,23 +183,23 @@ process(["mnesia", Arg]) ->
%% The arguments --long and --dual are not documented because they are
%% automatically selected depending in the number of columns of the shell
process(["help" | Mode]) ->
process(["help" | Mode], Version) ->
{MaxC, ShCode} = get_shell_info(),
case Mode of
[] ->
print_usage(dual, MaxC, ShCode),
print_usage(dual, MaxC, ShCode, Version),
?STATUS_USAGE;
["--dual"] ->
print_usage(dual, MaxC, ShCode),
print_usage(dual, MaxC, ShCode, Version),
?STATUS_USAGE;
["--long"] ->
print_usage(long, MaxC, ShCode),
print_usage(long, MaxC, ShCode, Version),
?STATUS_USAGE;
["--tags"] ->
print_usage_tags(MaxC, ShCode),
print_usage_tags(MaxC, ShCode, Version),
?STATUS_SUCCESS;
["--tags", Tag] ->
print_usage_tags(Tag, MaxC, ShCode),
print_usage_tags(Tag, MaxC, ShCode, Version),
?STATUS_SUCCESS;
["help"] ->
print_usage_help(MaxC, ShCode),
@@ -196,13 +207,22 @@ process(["help" | Mode]) ->
[CmdString | _] ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
print_usage_commands(binary_to_list(CmdStringU), MaxC, ShCode),
print_usage_commands2(binary_to_list(CmdStringU), MaxC, ShCode, Version),
?STATUS_SUCCESS
end;
process(Args) ->
process(["--version", Arg | Args], _) ->
Version =
try
list_to_integer(Arg)
catch _:_ ->
throw({invalid_version, Arg})
end,
process(Args, Version);
process(Args, Version) ->
AccessCommands = get_accesscommands(),
{String, Code} = process2(Args, AccessCommands),
{String, Code} = process2(Args, AccessCommands, Version),
case String of
[] -> ok;
_ ->
@@ -211,18 +231,25 @@ process(Args) ->
Code.
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
process2(["--auth", User, Server, Pass | Args], AccessCommands) ->
process2(Args, {list_to_binary(User), list_to_binary(Server), list_to_binary(Pass), true}, AccessCommands);
process2(Args, AccessCommands) ->
process2(Args, noauth, AccessCommands).
process2(Args, AccessCommands, ?DEFAULT_VERSION).
process2(Args, Auth, AccessCommands) ->
case try_run_ctp(Args, Auth, AccessCommands) of
%% @spec (Args::[string()], AccessCommands, Version) -> {String::string(), Code::integer()}
process2(["--auth", User, Server, Pass | Args], AccessCommands, Version) ->
process2(Args, AccessCommands, {list_to_binary(User), list_to_binary(Server),
list_to_binary(Pass), true}, Version);
process2(Args, AccessCommands, Version) ->
process2(Args, AccessCommands, noauth, Version).
process2(Args, AccessCommands, Auth, Version) ->
case try_run_ctp(Args, Auth, AccessCommands, Version) of
{String, wrong_command_arguments}
when is_list(String) ->
io:format(lists:flatten(["\n" | String]++["\n"])),
[CommandString | _] = Args,
process(["help" | [CommandString]]),
process(["help" | [CommandString]], Version),
{lists:flatten(String), ?STATUS_ERROR};
{String, Code}
when is_list(String) and is_integer(Code) ->
@@ -246,29 +273,29 @@ get_accesscommands() ->
%%-----------------------------
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
try_run_ctp(Args, Auth, AccessCommands) ->
try_run_ctp(Args, Auth, AccessCommands, Version) ->
try ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
false when Args /= [] ->
try_call_command(Args, Auth, AccessCommands);
try_call_command(Args, Auth, AccessCommands, Version);
false ->
print_usage(),
print_usage(Version),
{"", ?STATUS_USAGE};
Status ->
{"", Status}
catch
exit:Why ->
print_usage(),
print_usage(Version),
{io_lib:format("Error in ejabberd ctl process: ~p", [Why]), ?STATUS_USAGE};
Error:Why ->
%% In this case probably ejabberd is not started, so let's show Status
process(["status"]),
process(["status"], Version),
print("~n", []),
{io_lib:format("Error in ejabberd ctl process: '~p' ~p", [Error, Why]), ?STATUS_USAGE}
end.
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
try_call_command(Args, Auth, AccessCommands) ->
try call_command(Args, Auth, AccessCommands) of
try_call_command(Args, Auth, AccessCommands, Version) ->
try call_command(Args, Auth, AccessCommands, Version) of
{error, command_unknown} ->
{io_lib:format("Error: command ~p not known.", [hd(Args)]), ?STATUS_ERROR};
{error, wrong_command_arguments} ->
@@ -276,24 +303,28 @@ try_call_command(Args, Auth, AccessCommands) ->
Res ->
Res
catch
throw:Error ->
{io_lib:format("~p", [Error]), ?STATUS_ERROR};
A:Why ->
Stack = erlang:get_stacktrace(),
{io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", [A, Why, Stack]), ?STATUS_ERROR}
end.
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
call_command([CmdString | Args], Auth, AccessCommands) ->
call_command([CmdString | Args], Auth, AccessCommands, Version) ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
Command = list_to_atom(binary_to_list(CmdStringU)),
case ejabberd_commands:get_command_format(Command, Auth) of
case ejabberd_commands:get_command_format(Command, Auth, Version) of
{error, command_unknown} ->
{error, command_unknown};
{ArgsFormat, ResultFormat} ->
case (catch format_args(Args, ArgsFormat)) of
ArgsFormatted when is_list(ArgsFormatted) ->
Result = ejabberd_commands:execute_command(AccessCommands, Auth, Command,
ArgsFormatted),
Result = ejabberd_commands:execute_command(AccessCommands,
Auth, Command,
ArgsFormatted,
Version),
format_result(Result, ResultFormat);
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
{NumCompa, TextCompa} =
@@ -404,8 +435,8 @@ make_status(ok) -> ?STATUS_SUCCESS;
make_status(true) -> ?STATUS_SUCCESS;
make_status(_Error) -> ?STATUS_ERROR.
get_list_commands() ->
try ejabberd_commands:list_commands() of
get_list_commands(Version) ->
try ejabberd_commands:list_commands(Version) of
Commands ->
[tuple_command_help(Command)
|| {N,_,_}=Command <- Commands,
@@ -458,10 +489,10 @@ get_list_ctls() ->
-define(U2, "\e[24m").
-define(U(S), case ShCode of true -> [?U1, S, ?U2]; false -> S end).
print_usage() ->
print_usage(Version) ->
{MaxC, ShCode} = get_shell_info(),
print_usage(dual, MaxC, ShCode).
print_usage(HelpMode, MaxC, ShCode) ->
print_usage(dual, MaxC, ShCode, Version).
print_usage(HelpMode, MaxC, ShCode, Version) ->
AllCommands =
[
{"status", [], "Get ejabberd status"},
@@ -469,12 +500,11 @@ print_usage(HelpMode, MaxC, ShCode) ->
{"restart", [], "Restart ejabberd"},
{"help", ["[--tags [tag] | com?*]"], "Show help (try: ejabberdctl help help)"},
{"mnesia", ["[info]"], "show information of Mnesia system"}] ++
get_list_commands() ++
get_list_commands(Version) ++
get_list_ctls(),
print(
["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--auth ",
?U("user"), " ", ?U("host"), " ", ?U("password"), "] ",
["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--version ", ?U("api_version"), "] ",
?U("command"), " [", ?U("options"), "]\n"
"\n"
"Available commands in this ejabberd node:\n"], []),
@@ -598,9 +628,9 @@ format_command_lines(CALD, _MaxCmdLen, MaxC, ShCode, long) ->
%% Print Tags
%%-----------------------------
print_usage_tags(MaxC, ShCode) ->
print_usage_tags(MaxC, ShCode, Version) ->
print("Available tags and commands:", []),
TagsCommands = ejabberd_commands:get_tags_commands(),
TagsCommands = ejabberd_commands:get_tags_commands(Version),
lists:foreach(
fun({Tag, Commands} = _TagCommands) ->
print(["\n\n ", ?B(Tag), "\n "], []),
@@ -611,10 +641,10 @@ print_usage_tags(MaxC, ShCode) ->
TagsCommands),
print("\n\n", []).
print_usage_tags(Tag, MaxC, ShCode) ->
print_usage_tags(Tag, MaxC, ShCode, Version) ->
print(["Available commands with tag ", ?B(Tag), ":", "\n"], []),
HelpMode = long,
TagsCommands = ejabberd_commands:get_tags_commands(),
TagsCommands = ejabberd_commands:get_tags_commands(Version),
CommandsNames = case lists:keysearch(Tag, 1, TagsCommands) of
{value, {Tag, CNs}} -> CNs;
false -> []
@@ -622,7 +652,7 @@ print_usage_tags(Tag, MaxC, ShCode) ->
CommandsList = lists:map(
fun(NameString) ->
C = ejabberd_commands:get_command_definition(
list_to_atom(NameString)),
list_to_atom(NameString), Version),
#ejabberd_commands{name = Name,
args = Args,
desc = Desc} = C,
@@ -673,20 +703,20 @@ print_usage_help(MaxC, ShCode) ->
%%-----------------------------
%% @spec (CmdSubString::string(), MaxC::integer(), ShCode::boolean()) -> ok
print_usage_commands(CmdSubString, MaxC, ShCode) ->
print_usage_commands2(CmdSubString, MaxC, ShCode, Version) ->
%% Get which command names match this substring
AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands()],
AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands(Version)],
Cmds = filter_commands(AllCommandsNames, CmdSubString),
case Cmds of
[] -> io:format("Error: not command found that match: ~p~n", [CmdSubString]);
_ -> print_usage_commands2(lists:sort(Cmds), MaxC, ShCode)
[] -> io:format("Error: no command found that match: ~p~n", [CmdSubString]);
_ -> print_usage_commands3(lists:sort(Cmds), MaxC, ShCode, Version)
end.
print_usage_commands2(Cmds, MaxC, ShCode) ->
print_usage_commands3(Cmds, MaxC, ShCode, Version) ->
%% Then for each one print it
lists:mapfoldl(
fun(Cmd, Remaining) ->
print_usage_command(Cmd, MaxC, ShCode),
print_usage_command(Cmd, MaxC, ShCode, Version),
case Remaining > 1 of
true -> print([" ", lists:duplicate(MaxC, 126), " \n"], []);
false -> ok
@@ -716,16 +746,16 @@ filter_commands_regexp(All, Glob) ->
All).
%% @spec (Cmd::string(), MaxC::integer(), ShCode::boolean()) -> ok
print_usage_command(Cmd, MaxC, ShCode) ->
print_usage_command(Cmd, MaxC, ShCode, Version) ->
Name = list_to_atom(Cmd),
case ejabberd_commands:get_command_definition(Name) of
case ejabberd_commands:get_command_definition(Name, Version) of
command_not_found ->
io:format("Error: command ~p not known.~n", [Cmd]);
C ->
print_usage_command(Cmd, C, MaxC, ShCode)
print_usage_command2(Cmd, C, MaxC, ShCode)
end.
print_usage_command(Cmd, C, MaxC, ShCode) ->
print_usage_command2(Cmd, C, MaxC, ShCode) ->
#ejabberd_commands{
tags = TagsAtoms,
desc = Desc,
+1
View File
@@ -753,6 +753,7 @@ code_to_phrase(503) -> <<"Service Unavailable">>;
code_to_phrase(504) -> <<"Gateway Timeout">>;
code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
parse_auth(<<"Basic ", Auth64/binary>>) ->
Auth = jlib:decode_base64(Auth64),
%% Auth should be a string with the format: user@server:password
+18 -5
View File
@@ -74,7 +74,7 @@ start_link() ->
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
#iq{xmlns = XMLNS} ->
#iq{xmlns = XMLNS, lang = Lang} ->
Host = To#jid.lserver,
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function}] ->
@@ -87,8 +87,10 @@ process_iq(From, To, Packet) ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
[] ->
Err = jlib:make_error_reply(Packet,
?ERR_FEATURE_NOT_IMPLEMENTED),
Txt = <<"No module is handling this query">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end;
reply ->
@@ -166,8 +168,10 @@ refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers.
bounce_resource_packet(From, To, Packet) ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"No available resource found">>,
Err = jlib:make_error_reply(Packet,
?ERR_ITEM_NOT_FOUND),
?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
ejabberd_router:route(To, From, Err),
stop.
@@ -267,7 +271,16 @@ do_route(From, To, Packet) ->
#xmlel{name = Name} = Packet,
case Name of
<<"iq">> -> process_iq(From, To, Packet);
<<"message">> -> ok;
<<"message">> ->
#xmlel{attrs = Attrs} = Packet,
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"headline">> -> ok;
<<"error">> -> ok;
_ ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
end;
<<"presence">> -> ok;
_ -> ok
end;
+118 -10
View File
@@ -39,6 +39,7 @@
authenticate_user/2,
authenticate_client/2,
verify_resowner_scope/3,
verify_client_scope/3,
associate_access_code/3,
associate_access_token/3,
associate_refresh_token/3,
@@ -47,6 +48,8 @@
process/2,
opt_type/1]).
-export([oauth_issue_token/1, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
-include("jlib.hrl").
-include("ejabberd.hrl").
@@ -55,9 +58,16 @@
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("ejabberd_commands.hrl").
%% There are two ways to obtain an oauth token:
%% * Using the web form/api results in the token being generated in behalf of the user providing the user/pass
%% * Using the command line and oauth_issue_token command, the token is generated in behalf of ejabberd' sysadmin
%% (as it has access to ejabberd command line).
-record(oauth_token, {
token = {<<"">>, <<"">>} :: {binary(), binary()},
us = {<<"">>, <<"">>} :: {binary(), binary()},
us = {<<"">>, <<"">>} :: {binary(), binary()} | server_admin,
scope = [] :: [binary()],
expire :: integer()
}).
@@ -73,8 +83,77 @@ start() ->
ChildSpec = {?MODULE, {?MODULE, start_link, []},
temporary, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec),
ejabberd_commands:register_commands(get_commands_spec()),
ok.
get_commands_spec() ->
[
#ejabberd_commands{name = oauth_issue_token, tags = [oauth],
desc = "Issue an oauth token. Available scopes are the ones usable by ejabberd admins",
module = ?MODULE, function = oauth_issue_token,
args = [{scopes, string}],
policy = restricted,
args_example = ["connected_users_number;muc_online_rooms"],
args_desc = ["List of scopes to allow, separated by ';'"],
result = {result, {tuple, [{token, string}, {scopes, string}, {expires_in, string}]}}
},
#ejabberd_commands{name = oauth_list_tokens, tags = [oauth],
desc = "List oauth tokens, their scope, and how many seconds remain until expirity",
module = ?MODULE, function = oauth_list_tokens,
args = [],
policy = restricted,
result = {tokens, {list, {token, {tuple, [{token, string}, {scope, string}, {expires_in, string}]}}}}
},
#ejabberd_commands{name = oauth_list_scopes, tags = [oauth],
desc = "List scopes that can be granted to tokens generated through the command line",
module = ?MODULE, function = oauth_list_scopes,
args = [],
policy = restricted,
result = {scopes, {list, {scope, string}}}
},
#ejabberd_commands{name = oauth_revoke_token, tags = [oauth],
desc = "Revoke authorization for a token",
module = ?MODULE, function = oauth_revoke_token,
args = [{token, string}],
policy = restricted,
result = {tokens, {list, {token, {tuple, [{token, string}, {scope, string}, {expires_in, string}]}}}},
result_desc = "List of remaining tokens"
}
].
oauth_issue_token(ScopesString) ->
Scopes = [list_to_binary(Scope) || Scope <- string:tokens(ScopesString, ";")],
case oauth2:authorize_client_credentials(ejabberd_ctl, Scopes, none) of
{ok, {_AppCtx, Authorization}} ->
{ok, {_AppCtx2, Response}} = oauth2:issue_token(Authorization, none),
{ok, AccessToken} = oauth2_response:access_token(Response),
{ok, Expires} = oauth2_response:expires_in(Response),
{ok, VerifiedScope} = oauth2_response:scope(Response),
{AccessToken, VerifiedScope, integer_to_list(Expires) ++ " seconds"};
{error, Error} ->
{error, Error}
end.
oauth_list_tokens() ->
Tokens = mnesia:dirty_match_object(#oauth_token{us = server_admin, _ = '_'}),
{MegaSecs, Secs, _MiniSecs} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs,
[{Token, Scope, integer_to_list(Expires - TS) ++ " seconds"} ||
#oauth_token{token=Token, scope=Scope, expire=Expires} <- Tokens].
oauth_revoke_token(Token) ->
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
oauth_list_tokens().
oauth_list_scopes() ->
get_cmd_scopes().
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@@ -130,7 +209,7 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
Access =
ejabberd_config:get_option(
{oauth_access, JID#jid.lserver},
fun(A) when is_atom(A) -> A end,
fun(A) -> A end,
none),
case acl:match_rule(JID#jid.lserver, Access, JID) of
allow ->
@@ -164,20 +243,46 @@ verify_resowner_scope(_, _, _) ->
{error, badscope}.
get_cmd_scopes() ->
Cmds = lists:filter(fun(Cmd) -> case ejabberd_commands:get_command_policy(Cmd) of
{ok, Policy} when Policy =/= restricted -> true;
_ -> false
end end,
ejabberd_commands:get_commands()),
[atom_to_binary(C, utf8) || C <- Cmds].
%% This is callback for oauth tokens generated through the command line. Only open and admin commands are
%% made available.
verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
RegisteredScope = get_cmd_scopes(),
case oauth2_priv_set:is_subset(oauth2_priv_set:new(Scope),
oauth2_priv_set:new(RegisteredScope)) of
true ->
{ok, {Ctx, Scope}};
false ->
{error, badscope}
end.
associate_access_code(_AccessCode, _Context, AppContext) ->
%put(?ACCESS_CODE_TABLE, AccessCode, Context),
{ok, AppContext}.
associate_access_token(AccessToken, Context, AppContext) ->
{user, User, Server} =
proplists:get_value(<<"resource_owner">>, Context, <<"">>),
%% Tokens generated using the API/WEB belongs to users and always include the user, server pair.
%% Tokens generated form command line aren't tied to an user, and instead belongs to the ejabberd sysadmin
US = case proplists:get_value(<<"resource_owner">>, Context, <<"">>) of
{user, User, Server} -> {jid:nodeprep(User), jid:nodeprep(Server)};
undefined -> server_admin
end,
Scope = proplists:get_value(<<"scope">>, Context, []),
Expire = proplists:get_value(<<"expiry_time">>, Context, 0),
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
R = #oauth_token{
token = AccessToken,
us = {LUser, LServer},
us = US,
scope = Scope,
expire = Expire
},
@@ -207,7 +312,7 @@ check_token(User, Server, Scope, Token) ->
check_token(Scope, Token) ->
case catch mnesia:dirty_read(oauth_token, Token) of
[#oauth_token{us = {LUser, LServer},
[#oauth_token{us = US,
scope = TokenScope,
expire = Expire}] ->
{MegaSecs, Secs, _} = os:timestamp(),
@@ -215,7 +320,10 @@ check_token(Scope, Token) ->
case oauth2_priv_set:is_member(
Scope, oauth2_priv_set:new(TokenScope)) andalso
Expire > TS of
true -> {ok, LUser, LServer};
true -> case US of
{LUser, LServer} -> {ok, user, {LUser, LServer}};
server_admin -> {ok, server_admin}
end;
false -> false
end;
_ ->
@@ -486,5 +594,5 @@ logo() ->
opt_type(oauth_expire) ->
fun(I) when is_integer(I), I >= 0 -> I end;
opt_type(oauth_access) ->
fun(A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
opt_type(_) -> [oauth_expire, oauth_access].
+2 -2
View File
@@ -28,8 +28,8 @@
%%% Not implemented:
%%% - write mod_piefxis with ejabberdctl commands
%%% - Export from mod_offline_odbc.erl
%%% - Export from mod_private_odbc.erl
%%% - Export from mod_offline_sql.erl
%%% - Export from mod_private_sql.erl
%%% - XEP-227: 6. Security Considerations
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
%%% - If a host has many users, split that host in XML files with 50 users each.
+17 -17
View File
@@ -35,10 +35,10 @@
-include("logger.hrl").
start() ->
file:delete(ejabberd_odbc:freetds_config()),
file:delete(ejabberd_odbc:odbc_config()),
file:delete(ejabberd_odbc:odbcinst_config()),
case lists:any(fun(H) -> needs_odbc(H) /= false end,
file:delete(ejabberd_sql:freetds_config()),
file:delete(ejabberd_sql:odbc_config()),
file:delete(ejabberd_sql:odbcinst_config()),
case lists:any(fun(H) -> needs_sql(H) /= false end,
?MYHOSTS) of
true ->
start_hosts();
@@ -49,34 +49,34 @@ start() ->
%% Start relationnal DB module on the nodes where it is needed
start_hosts() ->
lists:foreach(fun (Host) ->
case needs_odbc(Host) of
{true, App} -> start_odbc(Host, App);
case needs_sql(Host) of
{true, App} -> start_sql(Host, App);
false -> ok
end
end,
?MYHOSTS).
%% Start the ODBC module on the given host
start_odbc(Host, App) ->
%% Start the SQL module on the given host
start_sql(Host, App) ->
ejabberd:start_app(App),
Supervisor_name = gen_mod:get_module_proc(Host,
ejabberd_odbc_sup),
ejabberd_sql_sup),
ChildSpec = {Supervisor_name,
{ejabberd_odbc_sup, start_link, [Host]}, transient,
infinity, supervisor, [ejabberd_odbc_sup]},
{ejabberd_sql_sup, start_link, [Host]}, transient,
infinity, supervisor, [ejabberd_sql_sup]},
case supervisor:start_child(ejabberd_sup, ChildSpec) of
{ok, _PID} -> ok;
_Error ->
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying."
"..~n",
[Supervisor_name, _Error]),
start_odbc(Host, App)
start_sql(Host, App)
end.
%% Returns {true, App} if we have configured odbc for the given host
needs_odbc(Host) ->
%% Returns {true, App} if we have configured sql for the given host
needs_sql(Host) ->
LHost = jid:nameprep(Host),
case ejabberd_config:get_option({odbc_type, LHost},
case ejabberd_config:get_option({sql_type, LHost},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
@@ -91,11 +91,11 @@ needs_odbc(Host) ->
undefined -> false
end.
opt_type(odbc_type) ->
opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end;
opt_type(_) -> [odbc_type].
opt_type(_) -> [sql_type].
+179
View File
@@ -0,0 +1,179 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 8 May 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(ejabberd_redis).
-behaviour(gen_server).
-behaviour(ejabberd_config).
%% API
-export([start/0, start_link/0, q/1, qp/1, opt_type/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-define(PROCNAME, 'ejabberd_redis_client').
-include("logger.hrl").
-include("ejabberd.hrl").
-record(state, {}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
start() ->
case lists:any(
fun(Host) ->
is_redis_configured(Host)
end, ?MYHOSTS) of
true ->
Spec = {?MODULE, {?MODULE, start_link, []},
permanent, 2000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, Spec);
false ->
ok
end.
q(Command) ->
try eredis:q(?PROCNAME, Command)
catch _:Reason -> {error, Reason}
end.
qp(Pipeline) ->
try eredis:qp(?PROCNAME, Pipeline)
catch _:Reason -> {error, Reason}
end.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
process_flag(trap_exit, true),
connect(),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(connect, State) ->
connect(),
{noreply, State};
handle_info({'DOWN', _MRef, _Type, _Pid, Reason}, State) ->
?INFO_MSG("Redis connection has failed: ~p", [Reason]),
connect(),
{noreply, State};
handle_info({'EXIT', _, _}, State) ->
{noreply, State};
handle_info(Info, State) ->
?INFO_MSG("unexpected info = ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
is_redis_configured(Host) ->
ServerConfigured = ejabberd_config:has_option({redis_server, Host}),
PortConfigured = ejabberd_config:has_option({redis_port, Host}),
DBConfigured = ejabberd_config:has_option({redis_db, Host}),
PassConfigured = ejabberd_config:has_option({redis_password, Host}),
ReconnTimeoutConfigured = ejabberd_config:has_option(
{redis_reconnect_timeout, Host}),
ConnTimeoutConfigured = ejabberd_config:has_option(
{redis_connect_timeout, Host}),
Modules = ejabberd_config:get_option(
{modules, Host},
fun(L) when is_list(L) -> L end, []),
SMConfigured = ejabberd_config:get_option(
{sm_db_type, Host},
fun(V) -> V end) == redis,
ModuleWithRedisDBConfigured =
lists:any(
fun({Module, Opts}) ->
gen_mod:db_type(Host, Opts, Module) == redis
end, Modules),
ServerConfigured or PortConfigured or DBConfigured or PassConfigured or
ReconnTimeoutConfigured or ConnTimeoutConfigured or
SMConfigured or ModuleWithRedisDBConfigured.
iolist_to_list(IOList) ->
binary_to_list(iolist_to_binary(IOList)).
connect() ->
Server = ejabberd_config:get_option(redis_server,
fun iolist_to_list/1,
"localhost"),
Port = ejabberd_config:get_option(redis_port,
fun(P) when is_integer(P),
P>0, P<65536 ->
P
end, 6379),
DB = ejabberd_config:get_option(redis_db,
fun(I) when is_integer(I), I >= 0 ->
I
end, 0),
Pass = ejabberd_config:get_option(redis_password,
fun iolist_to_list/1,
""),
ReconnTimeout = timer:seconds(
ejabberd_config:get_option(
redis_reconnect_timeout,
fun(I) when is_integer(I), I>0 -> I end,
1)),
ConnTimeout = timer:seconds(
ejabberd_config:get_option(
redis_connect_timeout,
fun(I) when is_integer(I), I>0 -> I end,
1)),
try case eredis:start_link(Server, Port, DB, Pass,
ReconnTimeout, ConnTimeout) of
{ok, Client} ->
?INFO_MSG("Connected to Redis at ~s:~p", [Server, Port]),
unlink(Client),
erlang:monitor(process, Client),
register(?PROCNAME, Client),
{ok, Client};
{error, Why} ->
erlang:error(Why)
end
catch _:Reason ->
Timeout = 10,
?ERROR_MSG("Redis connection at ~s:~p has failed: ~p; "
"reconnecting in ~p seconds",
[Server, Port, Reason, Timeout]),
erlang:send_after(timer:seconds(Timeout), self(), connect)
end.
opt_type(redis_connect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(redis_db) ->
fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(redis_password) -> fun iolist_to_list/1;
opt_type(redis_port) ->
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
opt_type(redis_reconnect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(redis_server) -> fun iolist_to_list/1;
opt_type(_) ->
[redis_connect_timeout, redis_db, redis_password,
redis_port, redis_reconnect_timeout, redis_server].
+2 -2
View File
@@ -70,8 +70,8 @@ is_riak_configured(Host) ->
{modules, Host},
fun(L) when is_list(L) -> L end, []),
ModuleWithRiakDBConfigured = lists:any(
fun({_Module, Opts}) ->
gen_mod:db_type(Host, Opts) == riak
fun({Module, Opts}) ->
gen_mod:db_type(Host, Opts, Module) == riak
end, Modules),
ServerConfigured or PortConfigured
or AuthConfigured or ModuleWithRiakDBConfigured.
+6 -4
View File
@@ -312,8 +312,10 @@ do_route(From, To, Packet) ->
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"No s2s connection found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end,
false
@@ -537,7 +539,7 @@ allow_host2(MyServer, S2SHost) ->
allow_host1(MyHost, S2SHost) ->
Rule = ejabberd_config:get_option(
s2s_access,
fun(A) when is_atom(A) -> A end,
fun(A) -> A end,
all),
JID = jid:make(<<"">>, S2SHost, <<"">>),
case acl:match_rule(MyHost, Rule, JID) of
@@ -736,5 +738,5 @@ opt_type(route_subdomains) ->
(local) -> local
end;
opt_type(s2s_access) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
opt_type(_) -> [route_subdomains, s2s_access].
+1 -1
View File
@@ -325,7 +325,7 @@ wait_for_feature_request({xmlstreamelement, El},
{s2s_tls_compression, StateData#state.server},
fun(true) -> true;
(false) -> false
end, true) of
end, false) of
true -> lists:delete(compression_none, TLSOpts1);
false -> [compression_none | TLSOpts1]
end,
+1 -1
View File
@@ -192,7 +192,7 @@ init([From, Server, Type]) ->
{s2s_tls_compression, From},
fun(true) -> true;
(false) -> false
end, true) of
end, false) of
false -> [compression_none | TLSOpts4];
true -> TLSOpts4
end,
+6 -2
View File
@@ -280,7 +280,9 @@ stream_established({xmlstreamelement, El}, StateData) ->
and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl);
true ->
Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
Txt = <<"Incorrect stanza name or from/to JID">>,
Err = jlib:make_error_reply(NewEl, ?ERRT_BAD_REQUEST(Lang, Txt)),
send_element(StateData, Err),
error
end,
@@ -360,7 +362,9 @@ handle_info({route, From, To, Packet}, StateName,
attrs = Attrs2, children = Els}),
send_text(StateData, Text);
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
ejabberd_router:route_error(To, From, Err, Packet)
end,
{next_state, StateName, StateData};
+99 -54
View File
@@ -47,6 +47,8 @@
set_presence/7,
unset_presence/6,
close_session_unset_presence/5,
set_offline_info/5,
get_offline_info/4,
dirty_get_sessions_list/0,
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
@@ -66,7 +68,8 @@
get_max_user_sessions/2,
get_all_pids/0,
is_existing_resource/3,
get_commands_spec/0
get_commands_spec/0,
make_sid/0
]).
-export([init/1, handle_call/3, handle_cast/2,
@@ -159,8 +162,10 @@ check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
bounce_offline_message(From, To, Packet) ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err),
stop.
@@ -175,14 +180,14 @@ get_user_resources(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
Ss = online(Mod:get_sessions(LUser, LServer)),
[element(3, S#session.usr) || S <- clean_session_list(Ss)].
-spec get_user_present_resources(binary(), binary()) -> [tuple()].
get_user_present_resources(LUser, LServer) ->
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
Ss = online(Mod:get_sessions(LUser, LServer)),
[{S#session.priority, element(3, S#session.usr)}
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
@@ -193,7 +198,7 @@ get_user_ip(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
case online(Mod:get_sessions(LUser, LServer, LResource)) of
[] ->
undefined;
Ss ->
@@ -208,7 +213,7 @@ get_user_info(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
case online(Mod:get_sessions(LUser, LServer, LResource)) of
[] ->
offline;
Ss ->
@@ -258,17 +263,42 @@ get_session_pid(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
case online(Mod:get_sessions(LUser, LServer, LResource)) of
[#session{sid = {_, Pid}}] -> Pid;
_ -> none
end.
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
set_offline_info({Time, _Pid}, User, Server, Resource, Info) ->
SID = {Time, undefined},
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
set_session(SID, LUser, LServer, LResource, undefined, Info).
-spec get_offline_info(erlang:timestamp(), binary(), binary(),
binary()) -> none | info().
get_offline_info(Time, User, Server, Resource) ->
SID = {Time, undefined},
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[#session{sid = SID, info = Info}] ->
Info;
_ ->
none
end.
-spec dirty_get_sessions_list() -> [ljid()].
dirty_get_sessions_list() ->
lists:flatmap(
fun(Mod) ->
[S#session.usr || S <- Mod:get_sessions()]
[S#session.usr || S <- online(Mod:get_sessions())]
end, get_sm_backends()).
-spec dirty_get_my_sessions_list() -> [#session{}].
@@ -276,7 +306,7 @@ dirty_get_sessions_list() ->
dirty_get_my_sessions_list() ->
lists:flatmap(
fun(Mod) ->
[S || S <- Mod:get_sessions(),
[S || S <- online(Mod:get_sessions()),
node(element(2, S#session.sid)) == node()]
end, get_sm_backends()).
@@ -285,14 +315,14 @@ dirty_get_my_sessions_list() ->
get_vh_session_list(Server) ->
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
[S#session.usr || S <- Mod:get_sessions(LServer)].
[S#session.usr || S <- online(Mod:get_sessions(LServer))].
-spec get_all_pids() -> [pid()].
get_all_pids() ->
lists:flatmap(
fun(Mod) ->
[element(2, S#session.sid) || S <- Mod:get_sessions()]
[element(2, S#session.sid) || S <- online(Mod:get_sessions())]
end, get_sm_backends()).
-spec get_vh_session_number(binary()) -> non_neg_integer().
@@ -300,7 +330,7 @@ get_all_pids() ->
get_vh_session_number(Server) ->
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
length(Mod:get_sessions(LServer)).
length(online(Mod:get_sessions(LServer))).
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
@@ -392,6 +422,15 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
Mod:set_session(#session{sid = SID, usr = USR, us = US,
priority = Priority, info = Info}).
-spec online([#session{}]) -> [#session{}].
online(Sessions) ->
lists:filter(fun(#session{sid = {_, undefined}}) ->
false;
(_) ->
true
end, Sessions).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
do_route(From, To, {broadcast, _} = Packet) ->
@@ -406,7 +445,7 @@ do_route(From, To, {broadcast, _} = Packet) ->
_ ->
{U, S, R} = jid:tolower(To),
Mod = get_sm_backend(S),
case Mod:get_sessions(U, S, R) of
case online(Mod:get_sessions(U, S, R)) of
[] ->
?DEBUG("packet dropped~n", []);
Ss ->
@@ -423,6 +462,7 @@ do_route(From, To, #xmlel{} = Packet) ->
#jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To,
#xmlel{name = Name, attrs = Attrs} = Packet,
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case LResource of
<<"">> ->
case Name of
@@ -496,8 +536,9 @@ do_route(From, To, #xmlel{} = Packet) ->
<<"headline">> -> route_message(From, To, Packet, headline);
<<"error">> -> ok;
<<"groupchat">> ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err);
_ ->
route_message(From, To, Packet, normal)
@@ -506,28 +547,33 @@ do_route(From, To, #xmlel{} = Packet) ->
_ -> ok
end;
_ ->
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
Mod = get_sm_backend(LServer),
case online(Mod:get_sessions(LUser, LServer, LResource)) of
[] ->
case Name of
<<"message">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"normal">> -> route_message(From, To, Packet, normal);
<<"">> -> route_message(From, To, Packet, normal);
<<"headline">> -> ok;
<<"error">> -> ok;
<<"groupchat">> ->
ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err);
_ ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
route_message(From, To, Packet, normal)
end;
<<"iq">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err)
end;
_ -> ?DEBUG("packet dropped~n", [])
@@ -574,8 +620,8 @@ route_message(From, To, Packet, Type) ->
(P >= 0) and (Type == headline) ->
LResource = jid:resourceprep(R),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer,
LResource) of
case online(Mod:get_sessions(LUser, LServer,
LResource)) of
[] ->
ok; % Race condition
Ss ->
@@ -592,15 +638,12 @@ route_message(From, To, Packet, Type) ->
case Type of
headline -> ok;
_ ->
case ejabberd_auth:is_user_exists(LUser, LServer) of
case ejabberd_auth:is_user_exists(LUser, LServer) andalso
is_privacy_allow(From, To, Packet) of
true ->
case is_privacy_allow(From, To, Packet) of
true ->
ejabberd_hooks:run(offline_message_hook, LServer,
[From, To, Packet]);
false -> ok
end;
_ ->
ejabberd_hooks:run(offline_message_hook, LServer,
[From, To, Packet]);
false ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
@@ -639,7 +682,11 @@ check_existing_resources(LUser, LServer, LResource) ->
if SIDs == [] -> ok;
true ->
MaxSID = lists:max(SIDs),
lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->
lists:foreach(fun ({_, undefined} = S) ->
Mod = get_sm_backend(LServer),
Mod:delete_session(LUser, LServer, LResource,
S);
({_, Pid} = S) when S /= MaxSID ->
Pid ! replaced;
(_) -> ok
end,
@@ -656,11 +703,11 @@ get_resource_sessions(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
[S#session.sid || S <- Mod:get_sessions(LUser, LServer, LResource)].
[S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))].
check_max_sessions(LUser, LServer) ->
Mod = get_sm_backend(LServer),
SIDs = [S#session.sid || S <- Mod:get_sessions(LUser, LServer)],
SIDs = [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer))],
MaxSessions = get_max_user_sessions(LUser, LServer),
if length(SIDs) =< MaxSessions -> ok;
true -> {_, Pid} = lists:min(SIDs), Pid ! replaced
@@ -684,7 +731,7 @@ get_max_user_sessions(LUser, Host) ->
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
#iq{xmlns = XMLNS} ->
#iq{xmlns = XMLNS, lang = Lang} ->
Host = To#jid.lserver,
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function}] ->
@@ -697,8 +744,10 @@ process_iq(From, To, Packet) ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
[] ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
Txt = <<"No module is handling this query">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end;
reply -> ok;
@@ -712,7 +761,7 @@ process_iq(From, To, Packet) ->
force_update_presence({LUser, LServer}) ->
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
Ss = online(Mod:get_sessions(LUser, LServer)),
lists:foreach(fun (#session{sid = {_, Pid}}) ->
Pid ! {force_update_presence, LUser, LServer}
end,
@@ -721,12 +770,10 @@ force_update_presence({LUser, LServer}) ->
-spec get_sm_backend(binary()) -> module().
get_sm_backend(Host) ->
DBType = ejabberd_config:get_option({sm_db_type, Host},
fun(mnesia) -> mnesia;
(internal) -> mnesia;
(odbc) -> odbc;
(redis) -> redis
end, mnesia),
DBType = ejabberd_config:get_option(
{sm_db_type, Host},
fun(T) -> ejabberd_config:v_db(?MODULE, T) end,
mnesia),
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
-spec get_sm_backends() -> [module()].
@@ -796,10 +843,8 @@ kick_user(User, Server) ->
end, Resources),
length(Resources).
opt_type(sm_db_type) ->
fun (mnesia) -> mnesia;
(internal) -> mnesia;
(odbc) -> odbc;
(redis) -> redis
end;
make_sid() ->
{p1_time_compat:unique_timestamp(), self()}.
opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(_) -> [sm_db_type].
+11 -47
View File
@@ -21,48 +21,12 @@
-include("logger.hrl").
-include("jlib.hrl").
-define(PROCNAME, 'ejabberd_redis_client').
%%%===================================================================
%%% API
%%%===================================================================
-spec init() -> ok | {error, any()}.
init() ->
Server = ejabberd_config:get_option(redis_server,
fun iolist_to_list/1,
"localhost"),
Port = ejabberd_config:get_option(redis_port,
fun(P) when is_integer(P),
P>0, P<65536 ->
P
end, 6379),
DB = ejabberd_config:get_option(redis_db,
fun(I) when is_integer(I), I >= 0 ->
I
end, 0),
Pass = ejabberd_config:get_option(redis_password,
fun iolist_to_list/1,
""),
ReconnTimeout = timer:seconds(
ejabberd_config:get_option(
redis_reconnect_timeout,
fun(I) when is_integer(I), I>0 -> I end,
1)),
ConnTimeout = timer:seconds(
ejabberd_config:get_option(
redis_connect_timeout,
fun(I) when is_integer(I), I>0 -> I end,
1)),
case eredis:start_link(Server, Port, DB, Pass,
ReconnTimeout, ConnTimeout) of
{ok, Client} ->
register(?PROCNAME, Client),
clean_table(),
ok;
{error, _} = Err ->
?ERROR_MSG("failed to start redis client: ~p", [Err]),
Err
end.
clean_table().
-spec set_session(#session{}) -> ok.
set_session(Session) ->
@@ -71,8 +35,8 @@ set_session(Session) ->
SIDKey = sid_to_key(Session#session.sid),
ServKey = server_to_key(element(2, Session#session.us)),
USSIDKey = us_sid_to_key(Session#session.us, Session#session.sid),
case eredis:qp(?PROCNAME, [["HSET", USKey, SIDKey, T],
["HSET", ServKey, USSIDKey, T]]) of
case ejabberd_redis:qp([["HSET", USKey, SIDKey, T],
["HSET", ServKey, USSIDKey, T]]) of
[{ok, _}, {ok, _}] ->
ok;
Err ->
@@ -83,7 +47,7 @@ set_session(Session) ->
{ok, #session{}} | {error, notfound}.
delete_session(LUser, LServer, _LResource, SID) ->
USKey = us_to_key({LUser, LServer}),
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
case ejabberd_redis:q(["HGETALL", USKey]) of
{ok, Vals} ->
Ss = decode_session_list(Vals),
case lists:keyfind(SID, #session.sid, Ss) of
@@ -93,8 +57,8 @@ delete_session(LUser, LServer, _LResource, SID) ->
SIDKey = sid_to_key(SID),
ServKey = server_to_key(element(2, Session#session.us)),
USSIDKey = us_sid_to_key(Session#session.us, SID),
eredis:qp(?PROCNAME, [["HDEL", USKey, SIDKey],
["HDEL", ServKey, USSIDKey]]),
ejabberd_redis:qp([["HDEL", USKey, SIDKey],
["HDEL", ServKey, USSIDKey]]),
{ok, Session}
end;
Err ->
@@ -112,7 +76,7 @@ get_sessions() ->
-spec get_sessions(binary()) -> [#session{}].
get_sessions(LServer) ->
ServKey = server_to_key(LServer),
case eredis:q(?PROCNAME, ["HGETALL", ServKey]) of
case ejabberd_redis:q(["HGETALL", ServKey]) of
{ok, Vals} ->
decode_session_list(Vals);
Err ->
@@ -123,7 +87,7 @@ get_sessions(LServer) ->
-spec get_sessions(binary(), binary()) -> [#session{}].
get_sessions(LUser, LServer) ->
USKey = us_to_key({LUser, LServer}),
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
case ejabberd_redis:q(["HGETALL", USKey]) of
{ok, Vals} when is_list(Vals) ->
decode_session_list(Vals);
Err ->
@@ -135,7 +99,7 @@ get_sessions(LUser, LServer) ->
[#session{}].
get_sessions(LUser, LServer, LResource) ->
USKey = us_to_key({LUser, LServer}),
case eredis:q(?PROCNAME, ["HGETALL", USKey]) of
case ejabberd_redis:q(["HGETALL", USKey]) of
{ok, Vals} when is_list(Vals) ->
[S || S <- decode_session_list(Vals),
element(3, S#session.usr) == LResource];
@@ -172,7 +136,7 @@ clean_table() ->
lists:foreach(
fun(LServer) ->
ServKey = server_to_key(LServer),
case eredis:q(?PROCNAME, ["HKEYS", ServKey]) of
case ejabberd_redis:q(["HKEYS", ServKey]) of
{ok, []} ->
ok;
{ok, Vals} ->
@@ -189,7 +153,7 @@ clean_table() ->
SIDKey = sid_to_key(SID),
["HDEL", USKey, SIDKey]
end, Vals1),
Res = eredis:qp(?PROCNAME, [Q1|Q2]),
Res = ejabberd_redis:qp([Q1|Q2]),
case lists:filter(
fun({ok, _}) -> false;
(_) -> true
@@ -6,7 +6,9 @@
%%% @end
%%% Created : 9 Mar 2015 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(ejabberd_sm_odbc).
-module(ejabberd_sm_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_sm).
@@ -23,18 +25,19 @@
-include("ejabberd_sm.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
%%%===================================================================
-spec init() -> ok | {error, any()}.
init() ->
Node = ejabberd_odbc:escape(jlib:atom_to_binary(node())),
Node = jlib:atom_to_binary(node()),
?INFO_MSG("Cleaning SQL SM table...", []),
lists:foldl(
fun(Host, ok) ->
case ejabberd_odbc:sql_query(
Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of
case ejabberd_sql:sql_query(
Host, ?SQL("delete from sm where node=%(Node)s")) of
{updated, _} ->
ok;
Err ->
@@ -47,20 +50,19 @@ init() ->
set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
priority = Priority, info = Info}) ->
Username = ejabberd_odbc:escape(U),
Resource = ejabberd_odbc:escape(R),
InfoS = ejabberd_odbc:encode_term(Info),
InfoS = jlib:term_to_expr(Info),
PrioS = enc_priority(Priority),
TS = now_to_timestamp(Now),
PidS = list_to_binary(erlang:pid_to_list(Pid)),
Node = ejabberd_odbc:escape(jlib:atom_to_binary(node(Pid))),
case odbc_queries:update(
LServer,
<<"sm">>,
[<<"usec">>, <<"pid">>, <<"node">>, <<"username">>,
<<"resource">>, <<"priority">>, <<"info">>],
[TS, PidS, Node, Username, Resource, PrioS, InfoS],
[<<"usec='">>, TS, <<"' and pid='">>, PidS, <<"'">>]) of
Node = jlib:atom_to_binary(node(Pid)),
case ?SQL_UPSERT(LServer, "sm",
["!usec=%(TS)d",
"!pid=%(PidS)s",
"node=%(Node)s",
"username=%(U)s",
"resource=%(R)s",
"priority=%(PrioS)s",
"info=%(InfoS)s"]) of
ok ->
ok;
Err ->
@@ -70,16 +72,18 @@ set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
delete_session(_LUser, LServer, _LResource, {Now, Pid}) ->
TS = now_to_timestamp(Now),
PidS = list_to_binary(erlang:pid_to_list(Pid)),
case ejabberd_odbc:sql_query(
case ejabberd_sql:sql_query(
LServer,
[<<"select usec, pid, username, resource, priority, info ">>,
<<"from sm where usec='">>, TS, <<"' and pid='">>,PidS, <<"'">>]) of
{selected, _, [Row]} ->
ejabberd_odbc:sql_query(
LServer, [<<"delete from sm where usec='">>,
TS, <<"' and pid='">>, PidS, <<"'">>]),
?SQL("select @(usec)d, @(pid)s, @(username)s,"
" @(resource)s, @(priority)s, @(info)s "
"from sm where usec=%(TS)d and pid=%(PidS)s")) of
{selected, [Row]} ->
ejabberd_sql:sql_query(
LServer,
?SQL("delete from sm"
" where usec=%(TS)d and pid=%(PidS)s")),
{ok, row_to_session(LServer, Row)};
{selected, _, []} ->
{selected, []} ->
{error, notfound};
Err ->
?ERROR_MSG("failed to delete from 'sm' table: ~p", [Err]),
@@ -93,10 +97,11 @@ get_sessions() ->
end, ejabberd_sm:get_vh_by_backend(?MODULE)).
get_sessions(LServer) ->
case ejabberd_odbc:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm">>]) of
{selected, _, Rows} ->
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(usec)d, @(pid)s, @(username)s,"
" @(resource)s, @(priority)s, @(info)s from sm")) of
{selected, Rows} ->
[row_to_session(LServer, Row) || Row <- Rows];
Err ->
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
@@ -104,12 +109,12 @@ get_sessions(LServer) ->
end.
get_sessions(LUser, LServer) ->
Username = ejabberd_odbc:escape(LUser),
case ejabberd_odbc:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm where ">>,
<<"username='">>, Username, <<"'">>]) of
{selected, _, Rows} ->
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(usec)d, @(pid)s, @(username)s,"
" @(resource)s, @(priority)s, @(info)s from sm"
" where username=%(LUser)s")) of
{selected, Rows} ->
[row_to_session(LServer, Row) || Row <- Rows];
Err ->
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
@@ -117,14 +122,12 @@ get_sessions(LUser, LServer) ->
end.
get_sessions(LUser, LServer, LResource) ->
Username = ejabberd_odbc:escape(LUser),
Resource = ejabberd_odbc:escape(LResource),
case ejabberd_odbc:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm where ">>,
<<"username='">>, Username, <<"' and resource='">>,
Resource, <<"'">>]) of
{selected, _, Rows} ->
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(usec)d, @(pid)s, @(username)s,"
" @(resource)s, @(priority)s, @(info)s from sm"
" where username=%(LUser)s and resource=%(LResource)s")) of
{selected, Rows} ->
[row_to_session(LServer, Row) || Row <- Rows];
Err ->
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
@@ -135,10 +138,9 @@ get_sessions(LUser, LServer, LResource) ->
%%% Internal functions
%%%===================================================================
now_to_timestamp({MSec, Sec, USec}) ->
jlib:integer_to_binary((MSec * 1000000 + Sec) * 1000000 + USec).
(MSec * 1000000 + Sec) * 1000000 + USec.
timestamp_to_now(TS) ->
I = jlib:binary_to_integer(TS),
timestamp_to_now(I) ->
Head = I div 1000000,
USec = I rem 1000000,
MSec = Head div 1000000,
@@ -158,11 +160,11 @@ enc_priority(undefined) ->
enc_priority(Int) when is_integer(Int) ->
jlib:integer_to_binary(Int).
row_to_session(LServer, [USec, PidS, User, Resource, PrioS, InfoS]) ->
row_to_session(LServer, {USec, PidS, User, Resource, PrioS, InfoS}) ->
Now = timestamp_to_now(USec),
Pid = erlang:list_to_pid(binary_to_list(PidS)),
Priority = dec_priority(PrioS),
Info = ejabberd_odbc:decode_term(InfoS),
Info = ejabberd_sql:decode_term(InfoS),
#session{sid = {Now, Pid}, us = {User, LServer},
usr = {User, LServer, Resource},
priority = Priority,
+78 -36
View File
@@ -1,7 +1,7 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_odbc.erl
%%% File : ejabberd_sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Serve ODBC connection
%%% Purpose : Serve SQL connection
%%% Created : 8 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_odbc).
-module(ejabberd_sql).
-behaviour(ejabberd_config).
@@ -39,9 +39,12 @@
sql_query_t/1,
sql_transaction/2,
sql_bloc/2,
sql_query_to_iolist/1,
escape/1,
standard_escape/1,
escape_like/1,
escape_like_arg/1,
escape_like_arg_circumflex/1,
to_bool/1,
sqlite_db/1,
sqlite_file/1,
@@ -75,9 +78,9 @@
max_pending_requests_len :: non_neg_integer(),
pending_requests = {0, queue:new()} :: {non_neg_integer(), ?TQUEUE}}).
-define(STATE_KEY, ejabberd_odbc_state).
-define(STATE_KEY, ejabberd_sql_state).
-define(NESTING_KEY, ejabberd_odbc_nesting_level).
-define(NESTING_KEY, ejabberd_sql_nesting_level).
-define(TOP_LEVEL_TXN, 0).
@@ -95,7 +98,7 @@
-define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]).
-define(PREPARE_KEY, ejabberd_odbc_prepare).
-define(PREPARE_KEY, ejabberd_sql_prepare).
%%-define(DBGFSM, true).
@@ -113,11 +116,11 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
(?GEN_FSM):start(ejabberd_odbc, [Host],
(?GEN_FSM):start(ejabberd_sql, [Host],
fsm_limit_opts() ++ (?FSMOPTS)).
start_link(Host, StartInterval) ->
(?GEN_FSM):start_link(ejabberd_odbc,
(?GEN_FSM):start_link(ejabberd_sql,
[Host, StartInterval],
fsm_limit_opts() ++ (?FSMOPTS)).
@@ -157,7 +160,7 @@ sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}).
sql_call(Host, Msg) ->
case get(?STATE_KEY) of
undefined ->
case ejabberd_odbc_sup:get_random_pid(Host) of
case ejabberd_sql_sup:get_random_pid(Host) of
none -> {error, <<"Unknown Host">>};
Pid ->
(?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
@@ -190,7 +193,7 @@ sql_query_t(Query) ->
%% Escape character that will confuse an SQL engine
escape(S) ->
<< <<(odbc_queries:escape(Char))/binary>> || <<Char>> <= S >>.
<< <<(sql_queries:escape(Char))/binary>> || <<Char>> <= S >>.
%% Escape character that will confuse an SQL engine
%% Percent and underscore only need to be escaped for pattern matching like
@@ -199,7 +202,8 @@ escape_like(S) when is_binary(S) ->
<< <<(escape_like(C))/binary>> || <<C>> <= S >>;
escape_like($%) -> <<"\\%">>;
escape_like($_) -> <<"\\_">>;
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> odbc_queries:escape(C).
escape_like($\\) -> <<"\\\\\\\\">>;
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C).
escape_like_arg(S) when is_binary(S) ->
<< <<(escape_like_arg(C))/binary>> || <<C>> <= S >>;
@@ -208,6 +212,15 @@ escape_like_arg($_) -> <<"\\_">>;
escape_like_arg($\\) -> <<"\\\\">>;
escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>.
escape_like_arg_circumflex(S) when is_binary(S) ->
<< <<(escape_like_arg_circumflex(C))/binary>> || <<C>> <= S >>;
escape_like_arg_circumflex($%) -> <<"^%">>;
escape_like_arg_circumflex($_) -> <<"^_">>;
escape_like_arg_circumflex($^) -> <<"^^">>;
escape_like_arg_circumflex($[) -> <<"^[">>; % For MSSQL
escape_like_arg_circumflex($]) -> <<"^]">>;
escape_like_arg_circumflex(C) when is_integer(C), C >= 0, C =< 255 -> <<C>>.
to_bool(<<"t">>) -> true;
to_bool(<<"true">>) -> true;
to_bool(<<"1">>) -> true;
@@ -232,7 +245,7 @@ sqlite_db(Host) ->
-spec sqlite_file(binary()) -> string().
sqlite_file(Host) ->
case ejabberd_config:get_option({odbc_database, Host},
case ejabberd_config:get_option({sql_database, Host},
fun iolist_to_binary/1) of
undefined ->
{ok, Cwd} = file:get_cwd(),
@@ -247,7 +260,7 @@ sqlite_file(Host) ->
%%%----------------------------------------------------------------------
init([Host, StartInterval]) ->
case ejabberd_config:get_option(
{odbc_keepalive_interval, Host},
{sql_keepalive_interval, Host},
fun(I) when is_integer(I), I>0 -> I end) of
undefined ->
ok;
@@ -257,7 +270,7 @@ init([Host, StartInterval]) ->
end,
[DBType | _] = db_opts(Host),
(?GEN_FSM):send_event(self(), connect),
ejabberd_odbc_sup:add_pid(Host, self()),
ejabberd_sql_sup:add_pid(Host, self()),
{ok, connecting,
#state{db_type = DBType, host = Host,
max_pending_requests_len = max_fsm_queue(),
@@ -371,7 +384,7 @@ handle_info(Info, StateName, State) ->
{next_state, StateName, State}.
terminate(_Reason, _StateName, State) ->
ejabberd_odbc_sup:remove_pid(State#state.host, self()),
ejabberd_sql_sup:remove_pid(State#state.host, self()),
case State#state.db_type of
mysql -> catch p1_mysql_conn:stop(State#state.db_ref);
sqlite -> catch sqlite3:close(sqlite_db(State#state.host));
@@ -507,7 +520,7 @@ sql_query_internal(#sql_query{} = Query) ->
odbc ->
generic_sql_query(Query);
mssql ->
generic_sql_query(Query);
mssql_sql_query(Query);
pgsql ->
Key = {?PREPARE_KEY, Query#sql_query.hash},
case get(Key) of
@@ -533,7 +546,7 @@ sql_query_internal(#sql_query{} = Query) ->
mysql ->
generic_sql_query(Query);
sqlite ->
generic_sql_query(Query)
sqlite_sql_query(Query)
end
catch
Class:Reason ->
@@ -622,6 +635,32 @@ generic_escape() ->
end
}.
sqlite_sql_query(SQLQuery) ->
sql_query_format_res(
sql_query_internal(sqlite_sql_query_format(SQLQuery)),
SQLQuery).
sqlite_sql_query_format(SQLQuery) ->
Args = (SQLQuery#sql_query.args)(sqlite_escape()),
(SQLQuery#sql_query.format_query)(Args).
sqlite_escape() ->
#sql_escape{string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end,
integer = fun(X) -> integer_to_binary(X) end,
boolean = fun(true) -> <<"1">>;
(false) -> <<"0">>
end
}.
standard_escape(S) ->
<< <<(case Char of
$' -> << "''" >>;
_ -> << Char >>
end)/binary>> || <<Char>> <= S >>.
mssql_sql_query(SQLQuery) ->
sqlite_sql_query(SQLQuery).
pgsql_prepare(SQLQuery, State) ->
Escape = #sql_escape{_ = fun(X) -> X end},
N = length((SQLQuery#sql_query.args)(Escape)),
@@ -668,6 +707,9 @@ sql_query_format_res({selected, _, Rows}, SQLQuery) ->
sql_query_format_res(Res, _SQLQuery) ->
Res.
sql_query_to_iolist(SQLQuery) ->
generic_sql_query_format(SQLQuery).
%% Generate the OTP callback return tuple depending on the driver result.
abort_on_driver_error({error, <<"query timed out">>} =
Reply,
@@ -869,14 +911,14 @@ log(Level, Format, Args) ->
end.
db_opts(Host) ->
Type = ejabberd_config:get_option({odbc_type, Host},
Type = ejabberd_config:get_option({sql_type, Host},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end, odbc),
Server = ejabberd_config:get_option({odbc_server, Host},
Server = ejabberd_config:get_option({sql_server, Host},
fun iolist_to_binary/1,
<<"localhost">>),
case Type of
@@ -886,20 +928,20 @@ db_opts(Host) ->
[sqlite, Host];
_ ->
Port = ejabberd_config:get_option(
{odbc_port, Host},
{sql_port, Host},
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
case Type of
mssql -> ?MSSQL_PORT;
mysql -> ?MYSQL_PORT;
pgsql -> ?PGSQL_PORT
end),
DB = ejabberd_config:get_option({odbc_database, Host},
DB = ejabberd_config:get_option({sql_database, Host},
fun iolist_to_binary/1,
<<"ejabberd">>),
User = ejabberd_config:get_option({odbc_username, Host},
User = ejabberd_config:get_option({sql_username, Host},
fun iolist_to_binary/1,
<<"ejabberd">>),
Pass = ejabberd_config:get_option({odbc_password, Host},
Pass = ejabberd_config:get_option({sql_password, Host},
fun iolist_to_binary/1,
<<"">>),
case Type of
@@ -912,14 +954,14 @@ db_opts(Host) ->
end.
init_mssql(Host) ->
Server = ejabberd_config:get_option({odbc_server, Host},
Server = ejabberd_config:get_option({sql_server, Host},
fun iolist_to_binary/1,
<<"localhost">>),
Port = ejabberd_config:get_option(
{odbc_port, Host},
{sql_port, Host},
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
?MSSQL_PORT),
DB = ejabberd_config:get_option({odbc_database, Host},
DB = ejabberd_config:get_option({sql_database, Host},
fun iolist_to_binary/1,
<<"ejabberd">>),
FreeTDS = io_lib:fwrite("[~s]~n"
@@ -1004,22 +1046,22 @@ check_error(Result, _Query) ->
opt_type(max_fsm_queue) ->
fun (N) when is_integer(N), N > 0 -> N end;
opt_type(odbc_database) -> fun iolist_to_binary/1;
opt_type(odbc_keepalive_interval) ->
opt_type(sql_database) -> fun iolist_to_binary/1;
opt_type(sql_keepalive_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(odbc_password) -> fun iolist_to_binary/1;
opt_type(odbc_port) ->
opt_type(sql_password) -> fun iolist_to_binary/1;
opt_type(sql_port) ->
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
opt_type(odbc_server) -> fun iolist_to_binary/1;
opt_type(odbc_type) ->
opt_type(sql_server) -> fun iolist_to_binary/1;
opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end;
opt_type(odbc_username) -> fun iolist_to_binary/1;
opt_type(sql_username) -> fun iolist_to_binary/1;
opt_type(_) ->
[max_fsm_queue, odbc_database, odbc_keepalive_interval,
odbc_password, odbc_port, odbc_server, odbc_type,
odbc_username].
[max_fsm_queue, sql_database, sql_keepalive_interval,
sql_password, sql_port, sql_server, sql_type,
sql_username].
+19 -13
View File
@@ -163,7 +163,7 @@ parse1([$@, $( | S], Acc, State) ->
EVar;
boolean ->
erl_syntax:application(
erl_syntax:atom(ejabberd_odbc),
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(to_bool),
[EVar])
end,
@@ -305,20 +305,24 @@ parse_upsert(Fields) ->
"a constant string"})
end
end, {[], 0}, Fields),
%io:format("asd ~p~n", [{Fields, Fs}]),
%io:format("upsert ~p~n", [{Fields, Fs}]),
Fs.
%% key | {Update}
parse_upsert_field([$! | S], ParamPos, Loc) ->
{Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
{Name, true, ParseState};
{Name, key, ParseState};
parse_upsert_field([$- | S], ParamPos, Loc) ->
{Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
{Name, {false}, ParseState};
parse_upsert_field(S, ParamPos, Loc) ->
{Name, ParseState} = parse_upsert_field1(S, [], ParamPos, Loc),
{Name, false, ParseState}.
{Name, {true}, ParseState}.
parse_upsert_field1([], _Acc, _ParamPos, Loc) ->
throw({error, Loc,
"?SQL_UPSERT fields must have the "
"following form: \"[!]name=value\""});
"following form: \"[!-]name=value\""});
parse_upsert_field1([$= | S], Acc, ParamPos, Loc) ->
{lists:reverse(Acc), parse(S, ParamPos, Loc)};
parse_upsert_field1([C | S], Acc, ParamPos, Loc) ->
@@ -348,7 +352,7 @@ make_sql_upsert_generic(Table, ParseRes) ->
InsertBranch =
erl_syntax:case_expr(
erl_syntax:application(
erl_syntax:atom(ejabberd_odbc),
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(sql_query_t),
[Insert]),
[erl_syntax:clause(
@@ -361,7 +365,7 @@ make_sql_upsert_generic(Table, ParseRes) ->
[erl_syntax:variable("__UpdateRes")])]),
erl_syntax:case_expr(
erl_syntax:application(
erl_syntax:atom(ejabberd_odbc),
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(sql_query_t),
[Update]),
[erl_syntax:clause(
@@ -376,9 +380,9 @@ make_sql_upsert_generic(Table, ParseRes) ->
make_sql_upsert_update(Table, ParseRes) ->
WPairs =
lists:flatmap(
fun({_Field, false, _ST}) ->
fun({_Field, {_}, _ST}) ->
[];
({Field, true, ST}) ->
({Field, key, ST}) ->
[ST#state{
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
}]
@@ -386,9 +390,11 @@ make_sql_upsert_update(Table, ParseRes) ->
Where = join_states(WPairs, " AND "),
SPairs =
lists:flatmap(
fun({_Field, true, _ST}) ->
fun({_Field, key, _ST}) ->
[];
({Field, false, ST}) ->
({_Field, {false}, _ST}) ->
[];
({Field, {true}, ST}) ->
[ST#state{
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
}]
@@ -453,7 +459,7 @@ make_sql_upsert_pgsql901(Table, ParseRes) ->
]),
Upsert = make_sql_query(State),
erl_syntax:application(
erl_syntax:atom(ejabberd_odbc),
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(sql_query_t),
[Upsert]).
@@ -462,7 +468,7 @@ check_upsert(ParseRes, Pos) ->
Set =
lists:filter(
fun({_Field, Match, _ST}) ->
not Match
Match /= key
end, ParseRes),
case Set of
[] ->
@@ -1,7 +1,7 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_odbc_sup.erl
%%% File : ejabberd_sql_sup.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : ODBC connections supervisor
%%% Purpose : SQL connections supervisor
%%% Created : 22 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@@ -23,7 +23,7 @@
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_odbc_sup).
-module(ejabberd_sql_sup).
-behaviour(ejabberd_config).
@@ -42,7 +42,7 @@
-define(DEFAULT_POOL_SIZE, 10).
-define(DEFAULT_ODBC_START_INTERVAL, 30).
-define(DEFAULT_SQL_START_INTERVAL, 30).
-define(CONNECT_TIMEOUT, 500).
@@ -62,14 +62,14 @@ start_link(Host) ->
init([Host]) ->
PoolSize = ejabberd_config:get_option(
{odbc_pool_size, Host},
{sql_pool_size, Host},
fun(I) when is_integer(I), I>0 -> I end,
?DEFAULT_POOL_SIZE),
StartInterval = ejabberd_config:get_option(
{odbc_start_interval, Host},
{sql_start_interval, Host},
fun(I) when is_integer(I), I>0 -> I end,
?DEFAULT_ODBC_START_INTERVAL),
Type = ejabberd_config:get_option({odbc_type, Host},
?DEFAULT_SQL_START_INTERVAL),
Type = ejabberd_config:get_option({sql_type, Host},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
@@ -80,7 +80,7 @@ init([Host]) ->
sqlite ->
check_sqlite_db(Host);
mssql ->
ejabberd_odbc:init_mssql(Host);
ejabberd_sql:init_mssql(Host);
_ ->
ok
end,
@@ -89,7 +89,7 @@ init([Host]) ->
{{one_for_one, PoolSize * 10, 1},
lists:map(fun (I) ->
{I,
{ejabberd_odbc, start_link,
{ejabberd_sql, start_link,
[Host, StartInterval * 1000]},
transient, 2000, worker, [?MODULE]}
end,
@@ -121,12 +121,12 @@ transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
transform_options({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) ->
[{odbc_type, Type},
{odbc_server, Server},
{odbc_port, Port},
{odbc_database, DB},
{odbc_username, User},
{odbc_password, Pass}|Opts];
[{sql_type, Type},
{sql_server, Server},
{sql_port, Port},
{sql_database, DB},
{sql_username, User},
{sql_password, Pass}|Opts];
transform_options({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) ->
transform_options({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts);
transform_options({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) ->
@@ -137,8 +137,8 @@ transform_options(Opt, Opts) ->
[Opt|Opts].
check_sqlite_db(Host) ->
DB = ejabberd_odbc:sqlite_db(Host),
File = ejabberd_odbc:sqlite_file(Host),
DB = ejabberd_sql:sqlite_db(Host),
File = ejabberd_sql:sqlite_file(Host),
Ret = case filelib:ensure_dir(File) of
ok ->
case sqlite3:open(DB, [{file, File}]) of
@@ -211,11 +211,11 @@ read_lines(Fd, File, Acc) ->
[]
end.
opt_type(odbc_pool_size) ->
opt_type(sql_pool_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(odbc_start_interval) ->
opt_type(sql_start_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(odbc_type) ->
opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
@@ -223,4 +223,4 @@ opt_type(odbc_type) ->
(odbc) -> odbc
end;
opt_type(_) ->
[odbc_pool_size, odbc_start_interval, odbc_type].
[sql_pool_size, sql_start_interval, sql_type].
+1 -1
View File
@@ -2414,7 +2414,7 @@ node_backup_parse_query(Node, Query) ->
lists:keysearch(<<Action/binary,
"host">>,
1, Query),
ejabberd_cluster:call(Node, ejd2odbc,
ejabberd_cluster:call(Node, ejd2sql,
export, [Host, Path]);
<<"import_file">> ->
ejabberd_cluster:call(Node, ejabberd_admin,
+5 -1
View File
@@ -373,6 +373,10 @@ try_do_command(AccessCommands, Auth, Command, AttrL,
"The call provided additional unused "
"arguments:~n~p",
[ExitAtL]);
exit:{invalid_arg_type, Arg, Type} ->
build_fault_response(-122,
"Parameter '~p' can't be coerced to type '~p'",
[Arg, Type]);
Why ->
build_fault_response(-118,
"A problem '~p' occurred executing the "
@@ -472,7 +476,7 @@ format_arg(undefined, binary) -> <<>>;
format_arg(undefined, string) -> "";
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
error.
exit({invalid_arg_type, Arg, Format}).
process_unicode_codepoints(Str) ->
iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
+27 -12
View File
@@ -1,5 +1,5 @@
%%%----------------------------------------------------------------------
%%% File : ejd2odbc.erl
%%% File : ejd2sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Export some mnesia tables to SQL DB
%%% Created : 22 Aug 2005 by Alexey Shchepin <alexey@process-one.net>
@@ -23,11 +23,12 @@
%%%
%%%----------------------------------------------------------------------
-module(ejd2odbc).
-module(ejd2sql).
-author('alexey@process-one.net').
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-export([export/2, export/3, import_file/2, import/2,
import/3, delete/1]).
@@ -43,7 +44,7 @@
%%% A table can be converted from Mnesia to an ODBC database by calling
%%% one of the API function with the following parameters:
%%% - Server is the server domain you want to convert
%%% - Output can be either odbc to export to the configured relational
%%% - Output can be either sql to export to the configured relational
%%% database or "Filename" to export to text file.
modules() ->
@@ -76,7 +77,12 @@ export(Server, Output, Module) ->
IO = prepare_output(Output),
lists:foreach(
fun({Table, ConvertFun}) ->
export(LServer, Table, IO, ConvertFun)
case export(LServer, Table, IO, ConvertFun) of
{atomic, ok} -> ok;
{aborted, Reason} ->
?ERROR_MSG("Failed export for module ~p: ~p",
[Module, Reason])
end
end, Module:export(Server)),
close_output(Output, IO).
@@ -104,7 +110,7 @@ import_file(Server, FileName) ->
LServer = jid:nameprep(Server),
Mods = [{Mod, gen_mod:db_type(LServer, Mod)}
|| Mod <- modules(), gen_mod:is_loaded(LServer, Mod)],
AuthMods = case lists:member(ejabberd_auth_internal,
AuthMods = case lists:member(ejabberd_auth_mnesia,
ejabberd_auth:auth_modules(LServer)) of
true ->
[{ejabberd_auth, mnesia}];
@@ -150,7 +156,8 @@ export(LServer, Table, IO, ConvertFun) ->
case ConvertFun(LServer, R) of
[] ->
Acc;
SQL ->
SQL1 ->
SQL = format_queries(SQL1),
if N < (?MAX_RECORDS_PER_TRANSACTION) - 1 ->
{N + 1, [SQL | SQLs]};
true ->
@@ -168,8 +175,8 @@ export(LServer, Table, IO, ConvertFun) ->
output(_LServer, _Table, _IO, []) ->
ok;
output(LServer, _Table, odbc, SQLs) ->
ejabberd_odbc:sql_transaction(LServer, SQLs);
output(LServer, _Table, sql, SQLs) ->
ejabberd_sql:sql_transaction(LServer, SQLs);
output(_LServer, Table, Fd, SQLs) ->
file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
"\n--\n", SQLs]).
@@ -197,7 +204,7 @@ import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
F = case proplists:get_bool(fast, Opts) of
true ->
fun() ->
case ejabberd_odbc:sql_query_t(SelectQuery) of
case ejabberd_sql:sql_query_t(SelectQuery) of
{selected, _, Rows} ->
lists:foldl(fun process_sql_row/2,
{IO, ConvertFun, undefined}, Rows);
@@ -207,16 +214,16 @@ import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
end;
false ->
fun() ->
ejabberd_odbc:sql_query_t(
ejabberd_sql:sql_query_t(
[iolist_to_binary(
[<<"declare c cursor for ">>, SelectQuery])]),
fetch(IO, ConvertFun, undefined)
end
end,
ejabberd_odbc:sql_transaction(LServer, F).
ejabberd_sql:sql_transaction(LServer, F).
fetch(IO, ConvertFun, PrevRow) ->
case ejabberd_odbc:sql_query_t([<<"fetch c;">>]) of
case ejabberd_sql:sql_query_t([<<"fetch c;">>]) of
{selected, _, [Row]} ->
process_sql_row(Row, {IO, ConvertFun, PrevRow}),
fetch(IO, ConvertFun, Row);
@@ -313,3 +320,11 @@ flatten1([H|T], Acc) ->
flatten1(T, [[H, $\n]|Acc]);
flatten1([], Acc) ->
Acc.
format_queries(SQLs) ->
lists:map(
fun(#sql_query{} = SQL) ->
ejabberd_sql:sql_query_to_iolist(SQL);
(SQL) ->
SQL
end, SQLs).
+1 -1
View File
@@ -271,7 +271,7 @@ geturl(Url, Hdrs, UsrOpts) ->
[U, Pass] -> [{proxy_user, U}, {proxy_password, Pass}];
_ -> []
end,
case httpc:request(get, {Url, Hdrs}, Host++User++UsrOpts, []) of
case httpc:request(get, {Url, Hdrs}, Host++User++UsrOpts++[{version, "HTTP/1.0"}], []) of
{ok, {{_, 200, _}, Headers, Response}} ->
{ok, Headers, Response};
{ok, {{_, Code, _}, _Headers, Response}} ->
+54 -29
View File
@@ -31,12 +31,12 @@
-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_opt/4, get_opt_host/3, db_type/2, db_type/3,
get_module_opt/4, get_module_opt/5, get_module_opt_host/3,
loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2,
start_modules/0, start_modules/1, stop_modules/0, stop_modules/1,
default_db/1, v_db/1, opt_type/1]).
opt_type/1, db_mod/2, db_mod/3]).
%%-export([behaviour_info/1]).
@@ -48,10 +48,11 @@
opts = [] :: opts() | '_' | '$2'}).
-type opts() :: [{atom(), any()}].
-type db_type() :: odbc | mnesia | riak.
-type db_type() :: sql | mnesia | riak.
-callback start(binary(), opts()) -> any().
-callback stop(binary()) -> any().
-callback mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
-export_type([opts/0]).
-export_type([db_type/0]).
@@ -265,18 +266,25 @@ get_opt_host(Host, Opts, Default) ->
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
validate_opts(Module, Opts) ->
lists:filter(
lists:filtermap(
fun({Opt, Val}) ->
case catch Module:mod_opt_type(Opt) of
VFun when is_function(VFun) ->
case catch VFun(Val) of
{'EXIT', _} ->
try VFun(Val) of
_ ->
true
catch {replace_with, NewVal} ->
{true, {Opt, NewVal}};
{invalid_syntax, Error} ->
?ERROR_MSG("ignoring invalid value '~p' for "
"option '~s' of module '~s': ~s",
[Val, Opt, Module, Error]),
false;
_:_ ->
?ERROR_MSG("ignoring invalid value '~p' for "
"option '~s' of module '~s'",
[Val, Opt, Module]),
false;
_ ->
true
false
end;
L when is_list(L) ->
SOpts = str:join([[$', atom_to_list(A), $'] || A <- L], <<", ">>),
@@ -295,29 +303,46 @@ validate_opts(Module, Opts) ->
false
end, Opts).
-spec v_db(db_type() | internal) -> db_type().
v_db(odbc) -> odbc;
v_db(internal) -> mnesia;
v_db(mnesia) -> mnesia;
v_db(riak) -> riak.
-spec db_type(opts()) -> db_type().
db_type(Opts) ->
db_type(global, Opts).
-spec db_type(binary() | global, atom() | opts()) -> db_type().
-spec db_type(binary() | global, module()) -> db_type();
(opts(), module()) -> db_type().
db_type(Opts, Module) when is_list(Opts) ->
db_type(global, Opts, Module);
db_type(Host, Module) when is_atom(Module) ->
get_module_opt(Host, Module, db_type, fun v_db/1, default_db(Host));
db_type(Host, Opts) when is_list(Opts) ->
get_opt(db_type, Opts, fun v_db/1, default_db(Host)).
case catch Module:mod_opt_type(db_type) of
F when is_function(F) ->
case get_module_opt(Host, Module, db_type, F) of
undefined -> ejabberd_config:default_db(Host, Module);
Type -> Type
end;
_ ->
undefined
end.
-spec default_db(binary() | global) -> db_type().
-spec db_type(binary(), opts(), module()) -> db_type().
default_db(Host) ->
ejabberd_config:get_option({default_db, Host}, fun v_db/1, mnesia).
db_type(Host, Opts, Module) ->
case catch Module:mod_opt_type(db_type) of
F when is_function(F) ->
case get_opt(db_type, Opts, F) of
undefined -> ejabberd_config:default_db(Host, Module);
Type -> Type
end;
_ ->
undefined
end.
-spec db_mod(binary() | global | db_type(), module()) -> module().
db_mod(Type, Module) when is_atom(Type) ->
list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type));
db_mod(Host, Module) when is_binary(Host) orelse Host == global ->
db_mod(db_type(Host, Module), Module).
-spec db_mod(binary() | global, opts(), module()) -> module().
db_mod(Host, Opts, Module) when is_list(Opts) ->
db_mod(db_type(Host, Opts, Module), Module).
-spec loaded_modules(binary()) -> [atom()].
@@ -365,6 +390,6 @@ get_module_proc(Host, Base) ->
is_loaded(Host, Module) ->
ets:member(ejabberd_modules, {Module, Host}).
opt_type(default_db) -> fun v_db/1;
opt_type(default_db) -> fun(T) when is_atom(T) -> T end;
opt_type(modules) -> fun (L) when is_list(L) -> L end;
opt_type(_) -> [default_db, modules].
+3 -1
View File
@@ -35,6 +35,7 @@
-type(pubsubState() :: mod_pubsub:pubsubState()).
-type(pubsubItem() :: mod_pubsub:pubsubItem()).
-type(subOptions() :: mod_pubsub:subOptions()).
-type(pubOptions() :: mod_pubsub:pubOptions()).
-type(affiliation() :: mod_pubsub:affiliation()).
-type(subscription() :: mod_pubsub:subscription()).
-type(subId() :: mod_pubsub:subId()).
@@ -109,7 +110,8 @@
PublishModel :: publishModel(),
Max_Items :: non_neg_integer(),
ItemId :: <<>> | itemId(),
Payload :: payload()) ->
Payload :: payload(),
Options :: pubOptions()) ->
{result, {default, broadcast, [itemId()]}} |
{error, xmlel()}.
+1 -1
View File
@@ -87,7 +87,7 @@ split(#jid{user = U, server = S, resource = R}) ->
split(_) ->
error.
-spec from_string([binary()|string()]) -> jid() | error.
-spec from_string(binary() | string()) -> jid() | error.
from_string(S) when is_list(S) ->
%% We do not accept list because we want to enforce good practice of
%% using binaries for string. However, we do not let it crash to avoid
+66 -18
View File
@@ -43,7 +43,7 @@
get_iq_namespace/1, iq_query_info/1,
iq_query_or_response_info/1, is_iq_request_type/1,
iq_to_xml/1, parse_xdata_submit/1,
is_standalone_chat_state/1,
unwrap_carbon/1, is_standalone_chat_state/1,
add_delay_info/3, add_delay_info/4,
timestamp_to_legacy/1, timestamp_to_iso_basic/1, timestamp_to_iso/2,
now_to_utc_string/1, now_to_local_string/1,
@@ -54,7 +54,8 @@
binary_to_integer/1, binary_to_integer/2,
integer_to_binary/1, integer_to_binary/2,
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
l2i/1, i2l/1, i2l/2, queue_drop_while/2]).
l2i/1, i2l/1, i2l/2, queue_drop_while/2,
expr_to_term/1, term_to_expr/1]).
%% The following functions are deprecated and will be removed soon
%% Use corresponding functions from jid.erl instead
@@ -528,25 +529,64 @@ rsm_encode_count(Count, Arr) ->
children = [{xmlcdata, i2l(Count)}]}
| Arr].
-spec unwrap_carbon(xmlel()) -> xmlel().
unwrap_carbon(#xmlel{name = <<"message">>} = Stanza) ->
case unwrap_carbon(Stanza, <<"sent">>) of
#xmlel{} = Payload ->
Payload;
false ->
case unwrap_carbon(Stanza, <<"received">>) of
#xmlel{} = Payload ->
Payload;
false ->
Stanza
end
end;
unwrap_carbon(Stanza) -> Stanza.
-spec unwrap_carbon(xmlel(), binary()) -> xmlel() | false.
unwrap_carbon(Stanza, Direction) ->
case fxml:get_subtag(Stanza, Direction) of
#xmlel{name = Direction, attrs = Attrs} = El ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
NS when NS == ?NS_CARBONS_2;
NS == ?NS_CARBONS_1 ->
case fxml:get_subtag_with_xmlns(El, <<"forwarded">>,
?NS_FORWARD) of
#xmlel{children = Els} ->
case fxml:remove_cdata(Els) of
[#xmlel{} = Payload] ->
Payload;
_ ->
false
end;
false ->
false
end;
_NS ->
false
end;
false ->
false
end.
-spec is_standalone_chat_state(xmlel()) -> boolean().
is_standalone_chat_state(#xmlel{name = <<"message">>} = El) ->
ChatStates = [<<"active">>, <<"inactive">>, <<"gone">>, <<"composing">>,
<<"paused">>],
Stripped =
lists:foldl(fun(ChatState, AccEl) ->
fxml:remove_subtags(AccEl, ChatState,
{<<"xmlns">>, ?NS_CHATSTATES})
end, El, ChatStates),
case Stripped of
#xmlel{children = [#xmlel{name = <<"thread">>}]} ->
true;
#xmlel{children = []} ->
true;
_ ->
is_standalone_chat_state(Stanza) ->
case unwrap_carbon(Stanza) of
#xmlel{name = <<"message">>, children = Els} ->
IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY],
Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els,
not lists:member(fxml:get_attr_s(<<"xmlns">>,
Attrs),
IgnoreNS),
Name /= <<"thread">>],
Stripped == [];
#xmlel{} ->
false
end;
is_standalone_chat_state(_El) -> false.
end.
-spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp())
-> xmlel().
@@ -901,6 +941,14 @@ tuple_to_binary(T) ->
atom_to_binary(A) ->
erlang:atom_to_binary(A, utf8).
expr_to_term(Expr) ->
Str = binary_to_list(<<Expr/binary, ".">>),
{ok, Tokens, _} = erl_scan:string(Str),
{ok, Term} = erl_parse:parse_term(Tokens),
Term.
term_to_expr(Term) ->
list_to_binary(io_lib:print(Term)).
l2i(I) when is_integer(I) -> I;
l2i(L) when is_binary(L) -> binary_to_integer(L).
+6 -3
View File
@@ -233,7 +233,7 @@ process_sm_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_sm_commands).
process_adhoc_request(From, To,
#iq{sub_el = SubEl} = IQ, Hook) ->
#iq{sub_el = SubEl, lang = Lang} = IQ, Hook) ->
?DEBUG("About to parse ~p...", [IQ]),
case adhoc:parse_request(IQ) of
{error, Error} ->
@@ -245,8 +245,9 @@ process_adhoc_request(From, To,
of
ignore -> ignore;
empty ->
Txt = <<"No hook has processed this command">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]};
Command -> IQ#iq{type = result, sub_el = [Command]}
@@ -277,7 +278,9 @@ ping_command(_Acc, _From, _To,
[{<<"info">>,
translate:translate(Lang,
<<"Pong">>)}]});
true -> {error, ?ERR_BAD_REQUEST}
true ->
Txt = <<"Incorrect value of 'action' attribute">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
ping_command(Acc, _From, _To, _Request) -> Acc.
+65 -36
View File
@@ -31,7 +31,7 @@
-include("logger.hrl").
-export([start/2, stop/1, compile/1, get_cookie/0,
remove_node/1, set_password/3,
remove_node/1, set_password/3, check_password/3,
check_password_hash/4, delete_old_users/1,
delete_old_users_vhost/2, ban_account/3,
num_active_users/2, num_resources/2, resource_num/3,
@@ -162,7 +162,7 @@ get_commands_spec() ->
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password, tags = [accounts],
desc = "Check if a password is correct",
module = ejabberd_auth, function = check_password,
module = ?MODULE, function = check_password,
args = [{user, binary}, {host, binary}, {password, binary}],
args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
args_desc = ["User name to check", "Server to check", "Password to check"],
@@ -590,36 +590,38 @@ remove_node(Node) ->
%%%
set_password(User, Host, Password) ->
case ejabberd_auth:set_password(User, Host, Password) of
ok ->
ok;
_ ->
error
end.
Fun = fun () -> ejabberd_auth:set_password(User, Host, Password) end,
user_action(User, Host, Fun, ok).
check_password(User, Host, Password) ->
ejabberd_auth:check_password(User, <<>>, Host, Password).
%% Copied some code from ejabberd_commands.erl
check_password_hash(User, Host, PasswordHash, HashMethod) ->
AccountPass = ejabberd_auth:get_password_s(User, Host),
AccountPassHash = case {AccountPass, HashMethod} of
{A, _} when is_tuple(A) -> scrammed;
{_, "md5"} -> get_md5(AccountPass);
{_, "sha"} -> get_sha(AccountPass);
_ -> undefined
{_, <<"md5">>} -> get_md5(AccountPass);
{_, <<"sha">>} -> get_sha(AccountPass);
{_, Method} ->
?ERROR_MSG("check_password_hash called "
"with hash method: ~p", [Method]),
undefined
end,
case AccountPassHash of
scrammed ->
?ERROR_MSG("Passwords are scrammed, and check_password_hash can not work.", []),
?ERROR_MSG("Passwords are scrammed, and check_password_hash cannot work.", []),
throw(passwords_scrammed_command_cannot_work);
undefined -> error;
undefined -> throw(unkown_hash_method);
PasswordHash -> ok;
_ -> error
_ -> false
end.
get_md5(AccountPass) ->
lists:flatten([io_lib:format("~.16B", [X])
|| X <- binary_to_list(erlang:md5(AccountPass))]).
iolist_to_binary([io_lib:format("~2.16.0B", [X])
|| X <- binary_to_list(erlang:md5(AccountPass))]).
get_sha(AccountPass) ->
lists:flatten([io_lib:format("~.16B", [X])
|| X <- binary_to_list(p1_sha:sha1(AccountPass))]).
iolist_to_binary([io_lib:format("~2.16.0B", [X])
|| X <- binary_to_list(p1_sha:sha1(AccountPass))]).
num_active_users(Host, Days) ->
list_last_activity(Host, true, Days).
@@ -782,7 +784,8 @@ resource_num(User, Host, Num) ->
true ->
lists:nth(Num, Resources);
false ->
lists:flatten(io_lib:format("Error: Wrong resource number: ~p", [Num]))
throw({bad_argument,
lists:flatten(io_lib:format("Wrong resource number: ~p", [Num]))})
end.
kick_session(User, Server, Resource, ReasonText) ->
@@ -860,26 +863,36 @@ connected_users_vhost(Host) ->
dirty_get_sessions_list2() ->
mnesia:dirty_select(
session,
[{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4', _ = '_'},
[],
[['$1', '$2', '$3', '$4']]}]).
[{#session{usr = '$1', sid = {'$2', '$3'}, priority = '$4', info = '$5',
_ = '_'},
[{is_pid, '$3'}],
[['$1', {{'$2', '$3'}}, '$4', '$5']]}]).
%% Make string more print-friendly
stringize(String) ->
%% Replace newline characters with other code
ejabberd_regexp:greplace(String, <<"\n">>, <<"\\n">>).
set_presence(User, Host, Resource, Type, Show, Status, Priority)
when is_integer(Priority) ->
BPriority = integer_to_binary(Priority),
set_presence(User, Host, Resource, Type, Show, Status, BPriority);
set_presence(User, Host, Resource, Type, Show, Status, Priority) ->
Pid = ejabberd_sm:get_session_pid(User, Host, Resource),
USR = jid:to_string(jid:make(User, Host, Resource)),
US = jid:to_string(jid:make(User, Host, <<>>)),
Message = {route_xmlstreamelement,
{xmlel, <<"presence">>,
[{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}],
[{xmlel, <<"show">>, [], [{xmlcdata, Show}]},
{xmlel, <<"status">>, [], [{xmlcdata, Status}]},
{xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}},
Pid ! Message.
case ejabberd_sm:get_session_pid(User, Host, Resource) of
none ->
error;
Pid ->
USR = jid:to_string(jid:make(User, Host, Resource)),
US = jid:to_string(jid:make(User, Host, <<>>)),
Message = {route_xmlstreamelement,
{xmlel, <<"presence">>,
[{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}],
[{xmlel, <<"show">>, [], [{xmlcdata, Show}]},
{xmlel, <<"status">>, [], [{xmlcdata, Status}]},
{xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}},
Pid ! Message,
ok
end.
user_sessions_info(User, Host) ->
CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
@@ -888,7 +901,9 @@ user_sessions_info(User, Host) ->
{'EXIT', _Reason} ->
[];
Ss ->
Ss
lists:filter(fun(#session{sid = {_, Pid}}) ->
is_pid(Pid)
end, Ss)
end,
lists:map(
fun(Session) ->
@@ -1151,7 +1166,8 @@ subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) -
subscribe_roster({Name, Server, Group, Nick}, Roster);
%% Subscribe Name2 to Name1
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
subscribe(Name1, Server1, Name2, Server2, Nick2, Group2, <<"both">>, []),
subscribe(Name1, Server1, list_to_binary(Name2), list_to_binary(Server2),
list_to_binary(Nick2), list_to_binary(Group2), <<"both">>, []),
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
push_alltoall(S, G) ->
@@ -1309,8 +1325,7 @@ srg_get_info(Group, Host) ->
Os when is_list(Os) -> Os;
error -> []
end,
[{jlib:atom_to_binary(Title),
io_lib:format("~p", [btl(Value)])} || {Title, Value} <- Opts].
[{jlib:atom_to_binary(Title), btl(Value)} || {Title, Value} <- Opts].
btl([]) -> [];
btl([B|L]) -> [btl(B)|btl(L)];
@@ -1569,6 +1584,20 @@ decide_rip_jid({UName, UServer}, Match_list) ->
end,
Match_list).
user_action(User, Server, Fun, OK) ->
case ejabberd_auth:is_user_exists(User, Server) of
true ->
case catch Fun() of
OK -> ok;
{error, Error} -> throw(Error);
Error ->
?ERROR_MSG("Command returned: ~p", [Error]),
1
end;
false ->
throw({not_found, "unknown_user"})
end.
%% Copied from ejabberd-2.0.0/src/acl.erl
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
+105 -330
View File
@@ -41,11 +41,16 @@
-include("logger.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
-include("mod_announce.hrl").
-record(motd, {server = <<"">> :: binary(),
packet = #xmlel{} :: xmlel()}).
-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
dummy = [] :: [] | '_'}).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #motd{} | #motd_users{}) -> ok | pass.
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}.
-callback set_motd(binary(), xmlel()) -> {atomic, any()}.
-callback delete_motd(binary()) -> {atomic, any()}.
-callback get_motd(binary()) -> {ok, xmlel()} | error.
-callback is_motd_user(binary(), binary()) -> boolean().
-callback set_motd_user(binary(), binary()) -> {atomic, any()}.
-define(PROCNAME, ejabberd_announce).
@@ -55,20 +60,8 @@
tokenize(Node) -> str:tokens(Node, <<"/#">>).
start(Host, Opts) ->
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(motd,
[{disc_copies, [node()]},
{attributes,
record_info(fields, motd)}]),
mnesia:create_table(motd_users,
[{disc_copies, [node()]},
{attributes,
record_info(fields, motd_users)}]),
update_tables();
_ ->
ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, announce, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
@@ -211,15 +204,15 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
%%-------------------------------------------------------------------------
-define(INFO_RESULT(Allow, Feats),
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny ->
{error, ?ERR_FORBIDDEN};
{error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
{result, Feats}
end).
disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang) ->
disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -229,13 +222,14 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, _Lang)
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
{error, ?ERR_FORBIDDEN};
Txt = <<"Denied by ACL">>,
{error, ?ERRT_FORBIDDEN(Lang, Txt)};
_ ->
{result, []}
end
end;
disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -246,25 +240,25 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
?NS_ADMIN_ANNOUNCE ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_ANNOUNCE_ALL ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_SET_MOTD ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_EDIT_MOTD ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_DELETE_MOTD ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_SET_MOTD_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
_ ->
Acc
end
@@ -283,10 +277,10 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
}
)).
-define(ITEMS_RESULT(Allow, Items),
-define(ITEMS_RESULT(Allow, Items, Lang),
case Allow of
deny ->
{error, ?ERR_FORBIDDEN};
{error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
{result, Items}
end).
@@ -320,7 +314,7 @@ disco_items(Acc, From, #jid{lserver = LServer} = To, <<"announce">>, Lang) ->
announce_items(Acc, From, To, Lang)
end;
disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@@ -331,25 +325,25 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
?NS_ADMIN_ANNOUNCE ->
?ITEMS_RESULT(Allow, []);
?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_ANNOUNCE_ALL ->
?ITEMS_RESULT(Allow, []);
?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_SET_MOTD ->
?ITEMS_RESULT(Allow, []);
?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_EDIT_MOTD ->
?ITEMS_RESULT(Allow, []);
?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_DELETE_MOTD ->
?ITEMS_RESULT(Allow, []);
?ITEMS_RESULT(Allow, [], Lang);
?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_SET_MOTD_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
?ITEMS_RESULT(AllowGlobal, [], Lang);
?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
?ITEMS_RESULT(AllowGlobal, []);
?ITEMS_RESULT(AllowGlobal, [], Lang);
_ ->
Acc
end
@@ -396,7 +390,8 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang)
commands_result(Allow, From, To, Request) ->
case Allow of
deny ->
{error, ?ERR_FORBIDDEN};
Lang = Request#adhoc_request.lang,
{error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
announce_commands(From, To, Request)
end.
@@ -463,12 +458,13 @@ announce_commands(From, To,
%% User returns form.
case jlib:parse_xdata_submit(XData) of
invalid ->
{error, ?ERR_BAD_REQUEST};
{error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
Fields ->
handle_adhoc_form(From, To, Request, Fields)
end;
true ->
{error, ?ERR_BAD_REQUEST}
Txt = <<"Incorrect action or data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end.
-define(VVALUE(Val),
@@ -688,7 +684,9 @@ announce_all(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Local = jid:make(<<>>, To#jid.server, <<>>),
@@ -703,7 +701,9 @@ announce_all_hosts_all(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Local = jid:make(<<>>, To#jid.server, <<>>),
@@ -719,7 +719,9 @@ announce_online(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:get_vh_session_list(Host),
@@ -731,7 +733,9 @@ announce_all_hosts_online(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
@@ -752,7 +756,9 @@ announce_motd(From, To, Packet) ->
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd(Host, Packet)
@@ -762,7 +768,9 @@ announce_all_hosts_motd(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Hosts = ?MYHOSTS,
@@ -774,48 +782,17 @@ announce_motd(Host, Packet) ->
announce_motd_update(LServer, Packet),
Sessions = ejabberd_sm:get_vh_session_list(LServer),
announce_online1(Sessions, LServer, Packet),
case gen_mod:db_type(LServer, ?MODULE) of
mnesia ->
F = fun() ->
lists:foreach(
fun({U, S, _R}) ->
mnesia:write(#motd_users{us = {U, S}})
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(
fun({U, _S, _R}) ->
Username = ejabberd_odbc:escape(U),
odbc_queries:update_t(
<<"motd">>,
[<<"username">>, <<"xml">>],
[Username, <<"">>],
[<<"username='">>, Username, <<"'">>])
end, Sessions)
end,
ejabberd_odbc:sql_transaction(LServer, F)
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_motd_users(LServer, Sessions).
announce_motd_update(From, To, Packet) ->
Host = To#jid.lserver,
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_update(Host, Packet)
@@ -825,7 +802,9 @@ announce_all_hosts_motd_update(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Hosts = ?MYHOSTS,
@@ -834,34 +813,17 @@ announce_all_hosts_motd_update(From, To, Packet) ->
announce_motd_update(LServer, Packet) ->
announce_motd_delete(LServer),
case gen_mod:db_type(LServer, ?MODULE) of
mnesia ->
F = fun() ->
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(fxml:element_to_binary(Packet)),
F = fun() ->
odbc_queries:update_t(
<<"motd">>,
[<<"username">>, <<"xml">>],
[<<"">>, XML],
[<<"username=''">>])
end,
ejabberd_odbc:sql_transaction(LServer, F)
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_motd(LServer, Packet).
announce_motd_delete(From, To, Packet) ->
Host = To#jid.lserver,
Access = get_access(Host),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_delete(Host)
@@ -871,7 +833,9 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
Access = get_access(global),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)),
ejabberd_router:route(To, From, Err);
allow ->
Hosts = ?MYHOSTS,
@@ -879,112 +843,30 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
end.
announce_motd_delete(LServer) ->
case gen_mod:db_type(LServer, ?MODULE) of
mnesia ->
F = fun() ->
mnesia:delete({motd, LServer}),
mnesia:write_lock_table(motd_users),
Users = mnesia:select(
motd_users,
[{#motd_users{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]),
lists:foreach(fun(US) ->
mnesia:delete({motd_users, US})
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;">>])
end,
ejabberd_odbc:sql_transaction(LServer, F)
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:delete_motd(LServer).
send_motd(JID) ->
send_motd(JID, gen_mod:db_type(JID#jid.lserver, ?MODULE)).
send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
case catch mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] ->
US = {LUser, LServer},
case catch mnesia:dirty_read({motd_users, US}) of
[#motd_users{}] ->
ok;
_ ->
send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= <<>> ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of
{ok, Packet} ->
case Mod:is_motd_user(LUser, LServer) of
false ->
Local = jid:make(<<>>, LServer, <<>>),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
mnesia:write(#motd_users{us = US})
end,
mnesia:transaction(F)
Mod:set_motd_user(LUser, LServer);
true ->
ok
end;
_ ->
error ->
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 = jid:make(<<>>, 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
{selected, [<<"xml">>], [[XML]]} ->
case fxml_stream:parse_element(XML) of
{error, _} ->
ok;
Packet ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
LServer,
[<<"select username from motd "
"where username='">>, Username, <<"';">>]) of
{selected, [<<"username">>], []} ->
Local = jid:make(<<"">>, LServer, <<"">>),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
odbc_queries:update_t(
<<"motd">>,
[<<"username">>, <<"xml">>],
[Username, <<"">>],
[<<"username='">>, Username, <<"'">>])
end,
ejabberd_odbc:sql_transaction(LServer, F);
_ ->
ok
end
end;
_ ->
ok
end;
send_motd(_, odbc) ->
send_motd(_) ->
ok.
get_stored_motd(LServer) ->
case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of
{ok, Packet} ->
{fxml:get_subtag_cdata(Packet, <<"subject">>),
fxml:get_subtag_cdata(Packet, <<"body">>)};
@@ -992,34 +874,6 @@ get_stored_motd(LServer) ->
{<<>>, <<>>}
end.
get_stored_motd_packet(LServer, mnesia) ->
case catch mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] ->
{ok, Packet};
_ ->
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
{selected, [<<"xml">>], [[XML]]} ->
case fxml_stream:parse_element(XML) of
{error, _} ->
error;
Packet ->
{ok, Packet}
end;
_ ->
error
end.
%% This function is similar to others, but doesn't perform any ACL verification
send_announcement_to_all(Host, SubjectS, BodyS) ->
SubjectEls = if SubjectS /= <<>> ->
@@ -1049,102 +903,23 @@ send_announcement_to_all(Host, SubjectS, BodyS) ->
get_access(Host) ->
gen_mod:get_module_opt(Host, ?MODULE, access,
fun(A) when is_atom(A) -> A end,
fun(A) -> A end,
none).
%%-------------------------------------------------------------------------
update_tables() ->
update_motd_table(),
update_motd_users_table().
update_motd_table() ->
Fields = record_info(fields, motd),
case mnesia:table_info(motd, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
motd, Fields, set,
fun(#motd{server = S}) -> S end,
fun(#motd{server = S, packet = P} = R) ->
NewS = iolist_to_binary(S),
NewP = fxml:to_xmlel(P),
R#motd{server = NewS, packet = NewP}
end);
_ ->
?INFO_MSG("Recreating motd table", []),
mnesia:transform_table(motd, ignore, Fields)
end.
update_motd_users_table() ->
Fields = record_info(fields, motd_users),
case mnesia:table_info(motd_users, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
motd_users, Fields, set,
fun(#motd_users{us = {U, _}}) -> U end,
fun(#motd_users{us = {U, S}} = R) ->
NewUS = {iolist_to_binary(U),
iolist_to_binary(S)},
R#motd_users{us = NewUS}
end);
_ ->
?INFO_MSG("Recreating 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})
when LServer == Host ->
[[<<"delete from motd where username='';">>],
[<<"insert into motd(username, xml) values ('', '">>,
ejabberd_odbc:escape(fxml:element_to_binary(El)),
<<"');">>]];
(_Host, _R) ->
[]
end},
{motd_users,
fun(Host, #motd_users{us = {LUser, LServer}})
when LServer == Host, LUser /= <<"">> ->
Username = ejabberd_odbc:escape(LUser),
[[<<"delete from motd where username='">>, Username, <<"';">>],
[<<"insert into motd(username, xml) values ('">>,
Username, <<"', '');">>]];
(_Host, _R) ->
[]
end}].
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
import(LServer) ->
[{<<"select xml from motd where username='';">>,
fun([XML]) ->
El = fxml_stream:parse_element(XML),
#motd{server = LServer, packet = El}
end},
{<<"select username from motd where xml='';">>,
fun([LUser]) ->
#motd_users{us = {LUser, LServer}}
end}].
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
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.
import(LServer, DBType, LA) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, LA).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) -> [access, db_type].
+129
View File
@@ -0,0 +1,129 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_announce_mnesia).
-behaviour(mod_announce).
%% API
-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
get_motd/1, is_motd_user/2, set_motd_user/2, import/2]).
-include("jlib.hrl").
-include("mod_announce.hrl").
-include("logger.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
mnesia:create_table(motd,
[{disc_copies, [node()]},
{attributes,
record_info(fields, motd)}]),
mnesia:create_table(motd_users,
[{disc_copies, [node()]},
{attributes,
record_info(fields, motd_users)}]),
update_tables().
set_motd_users(_LServer, USRs) ->
F = fun() ->
lists:foreach(
fun({U, S, _R}) ->
mnesia:write(#motd_users{us = {U, S}})
end, USRs)
end,
mnesia:transaction(F).
set_motd(LServer, Packet) ->
F = fun() ->
mnesia:write(#motd{server = LServer, packet = Packet})
end,
mnesia:transaction(F).
delete_motd(LServer) ->
F = fun() ->
mnesia:delete({motd, LServer}),
mnesia:write_lock_table(motd_users),
Users = mnesia:select(
motd_users,
[{#motd_users{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]),
lists:foreach(fun(US) ->
mnesia:delete({motd_users, US})
end, Users)
end,
mnesia:transaction(F).
get_motd(LServer) ->
case mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] ->
{ok, Packet};
_ ->
error
end.
is_motd_user(LUser, LServer) ->
case mnesia:dirty_read({motd_users, {LUser, LServer}}) of
[#motd_users{}] -> true;
_ -> false
end.
set_motd_user(LUser, LServer) ->
F = fun() ->
mnesia:write(#motd_users{us = {LUser, LServer}})
end,
mnesia:transaction(F).
import(_LServer, #motd{} = Motd) ->
mnesia:dirty_write(Motd);
import(_LServer, #motd_users{} = Users) ->
mnesia:dirty_write(Users).
%%%===================================================================
%%% Internal functions
%%%===================================================================
update_tables() ->
update_motd_table(),
update_motd_users_table().
update_motd_table() ->
Fields = record_info(fields, motd),
case mnesia:table_info(motd, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
motd, Fields, set,
fun(#motd{server = S}) -> S end,
fun(#motd{server = S, packet = P} = R) ->
NewS = iolist_to_binary(S),
NewP = fxml:to_xmlel(P),
R#motd{server = NewS, packet = NewP}
end);
_ ->
?INFO_MSG("Recreating motd table", []),
mnesia:transform_table(motd, ignore, Fields)
end.
update_motd_users_table() ->
Fields = record_info(fields, motd_users),
case mnesia:table_info(motd_users, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
motd_users, Fields, set,
fun(#motd_users{us = {U, _}}) -> U end,
fun(#motd_users{us = {U, S}} = R) ->
NewUS = {iolist_to_binary(U),
iolist_to_binary(S)},
R#motd_users{us = NewUS}
end);
_ ->
?INFO_MSG("Recreating motd_users table", []),
mnesia:transform_table(motd_users, ignore, Fields)
end.
+87
View File
@@ -0,0 +1,87 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_announce_riak).
-behaviour(mod_announce).
%% API
-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
get_motd/1, is_motd_user/2, set_motd_user/2, import/2]).
-include("jlib.hrl").
-include("mod_announce.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
ok.
set_motd_users(_LServer, USRs) ->
try
lists:foreach(
fun({U, S, _R}) ->
ok = ejabberd_riak:put(#motd_users{us = {U, S}},
motd_users_schema(),
[{'2i', [{<<"server">>, S}]}])
end, USRs),
{atomic, ok}
catch _:{badmatch, Err} ->
{atomic, Err}
end.
set_motd(LServer, Packet) ->
{atomic, ejabberd_riak:put(#motd{server = LServer,
packet = Packet},
motd_schema())}.
delete_motd(LServer) ->
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.
get_motd(LServer) ->
case ejabberd_riak:get(motd, motd_schema(), LServer) of
{ok, #motd{packet = Packet}} ->
{ok, Packet};
_ ->
error
end.
is_motd_user(LUser, LServer) ->
case ejabberd_riak:get(motd_users, motd_users_schema(),
{LUser, LServer}) of
{ok, #motd_users{}} -> true;
_ -> false
end.
set_motd_user(LUser, LServer) ->
{atomic, ejabberd_riak:put(
#motd_users{us = {LUser, LServer}}, motd_users_schema(),
[{'2i', [{<<"server">>, LServer}]}])}.
import(_LServer, #motd{} = Motd) ->
ejabberd_riak:put(Motd, motd_schema());
import(_LServer, #motd_users{us = {_, S}} = Users) ->
ejabberd_riak:put(Users, motd_users_schema(),
[{'2i', [{<<"server">>, S}]}]).
%%%===================================================================
%%% Internal functions
%%%===================================================================
motd_schema() ->
{record_info(fields, motd), #motd{}}.
motd_users_schema() ->
{record_info(fields, motd_users), #motd_users{}}.
+127
View File
@@ -0,0 +1,127 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_announce_sql).
-behaviour(mod_announce).
-compile([{parse_transform, ejabberd_sql_pt}]).
%% API
-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1,
get_motd/1, is_motd_user/2, set_motd_user/2, import/1,
import/2, export/1]).
-include("jlib.hrl").
-include("mod_announce.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
ok.
set_motd_users(LServer, USRs) ->
F = fun() ->
lists:foreach(
fun({U, _S, _R}) ->
?SQL_UPSERT_T(
"motd",
["!username=%(U)s",
"xml=''"])
end, USRs)
end,
ejabberd_sql:sql_transaction(LServer, F).
set_motd(LServer, Packet) ->
XML = fxml:element_to_binary(Packet),
F = fun() ->
?SQL_UPSERT_T(
"motd",
["!username=''",
"xml=%(XML)s"])
end,
ejabberd_sql:sql_transaction(LServer, F).
delete_motd(LServer) ->
F = fun() ->
ejabberd_sql:sql_query_t(?SQL("delete from motd"))
end,
ejabberd_sql:sql_transaction(LServer, F).
get_motd(LServer) ->
case catch ejabberd_sql:sql_query(
LServer,
?SQL("select @(xml)s from motd where username=''")) of
{selected, [{XML}]} ->
case fxml_stream:parse_element(XML) of
{error, _} ->
error;
Packet ->
{ok, Packet}
end;
_ ->
error
end.
is_motd_user(LUser, LServer) ->
case catch ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s from motd"
" where username=%(LUser)s")) of
{selected, [_|_]} ->
true;
_ ->
false
end.
set_motd_user(LUser, LServer) ->
F = fun() ->
?SQL_UPSERT_T(
"motd",
["!username=%(LUser)s",
"xml=''"])
end,
ejabberd_sql:sql_transaction(LServer, F).
export(_Server) ->
[{motd,
fun(Host, #motd{server = LServer, packet = El})
when LServer == Host ->
XML = fxml:element_to_binary(El),
[?SQL("delete from motd where username='';"),
?SQL("insert into motd(username, xml) values ('', %(XML)s);")];
(_Host, _R) ->
[]
end},
{motd_users,
fun(Host, #motd_users{us = {LUser, LServer}})
when LServer == Host, LUser /= <<"">> ->
[?SQL("delete from motd where username=%(LUser)s;"),
?SQL("insert into motd(username, xml) values (%(LUser)s, '');")];
(_Host, _R) ->
[]
end}].
import(LServer) ->
[{<<"select xml from motd where username='';">>,
fun([XML]) ->
El = fxml_stream:parse_element(XML),
#motd{server = LServer, packet = El}
end},
{<<"select username from motd where xml='';">>,
fun([LUser]) ->
#motd_users{us = {LUser, LServer}}
end}].
import(_, _) ->
pass.
%%%===================================================================
%%% Internal functions
%%%===================================================================
+36 -223
View File
@@ -39,6 +39,10 @@
-include("mod_privacy.hrl").
-callback process_blocklist_block(binary(), binary(), function()) -> {atomic, any()}.
-callback unblock_by_filter(binary(), binary(), function()) -> {atomic, any()}.
-callback process_blocklist_get(binary(), binary()) -> [listitem()] | error.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
@@ -64,29 +68,33 @@ process_iq(_From, _To, IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
process_iq_get(_, From, _To,
#iq{xmlns = ?NS_BLOCKING,
#iq{xmlns = ?NS_BLOCKING, lang = Lang,
sub_el = #xmlel{name = <<"blocklist">>}},
_) ->
#jid{luser = LUser, lserver = LServer} = From,
{stop, process_blocklist_get(LUser, LServer)};
{stop, process_blocklist_get(LUser, LServer, Lang)};
process_iq_get(Acc, _, _, _, _) -> Acc.
process_iq_set(_, From, _To,
#iq{xmlns = ?NS_BLOCKING,
#iq{xmlns = ?NS_BLOCKING, lang = Lang,
sub_el =
#xmlel{name = SubElName, children = SubEls}}) ->
#jid{luser = LUser, lserver = LServer} = From,
Res = case {SubElName, fxml:remove_cdata(SubEls)} of
{<<"block">>, []} -> {error, ?ERR_BAD_REQUEST};
{<<"block">>, []} ->
Txt = <<"No items found in this query">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{<<"block">>, Els} ->
JIDs = parse_blocklist_items(Els, []),
process_blocklist_block(LUser, LServer, JIDs);
process_blocklist_block(LUser, LServer, JIDs, Lang);
{<<"unblock">>, []} ->
process_blocklist_unblock_all(LUser, LServer);
process_blocklist_unblock_all(LUser, LServer, Lang);
{<<"unblock">>, Els} ->
JIDs = parse_blocklist_items(Els, []),
process_blocklist_unblock(LUser, LServer, JIDs);
_ -> {error, ?ERR_BAD_REQUEST}
process_blocklist_unblock(LUser, LServer, JIDs, Lang);
_ ->
Txt = <<"Unknown blocking command">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end,
{stop, Res};
process_iq_set(Acc, _, _, _) -> Acc.
@@ -125,7 +133,7 @@ parse_blocklist_items([#xmlel{name = <<"item">>,
parse_blocklist_items([_ | Els], JIDs) ->
parse_blocklist_items(Els, JIDs).
process_blocklist_block(LUser, LServer, JIDs) ->
process_blocklist_block(LUser, LServer, JIDs, Lang) ->
Filter = fun (List) ->
AlreadyBlocked = list_to_blocklist_jids(List, []),
lists:foldr(fun (JID, List1) ->
@@ -143,9 +151,8 @@ process_blocklist_block(LUser, LServer, JIDs) ->
end,
List, JIDs)
end,
case process_blocklist_block(LUser, LServer, Filter,
gen_mod:db_type(LServer, mod_privacy))
of
Mod = db_mod(LServer),
case Mod:process_blocklist_block(LUser, LServer, Filter) of
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
broadcast_list_update(LUser, LServer, Default,
@@ -155,105 +162,17 @@ process_blocklist_block(LUser, LServer, JIDs) ->
{result, [], UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end.
process_blocklist_block(LUser, LServer, Filter,
mnesia) ->
F = fun () ->
case mnesia:wread({privacy, {LUser, LServer}}) of
[] ->
P = #privacy{us = {LUser, LServer}},
NewDefault = <<"Blocked contacts">>,
NewLists1 = [],
List = [];
[#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
end,
NewList = Filter(List),
NewLists = [{NewDefault, NewList} | NewLists1],
mnesia:write(P#privacy{default = NewDefault,
lists = NewLists}),
{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
mod_privacy:sql_get_default_privacy_list_t(LUser)
of
{selected, []} ->
Name = <<"Blocked contacts">>,
mod_privacy:sql_add_privacy_list(LUser, Name),
mod_privacy:sql_set_default_privacy_list(LUser,
Name),
Name;
{selected, [{Name}]} -> Name
end,
{selected, [{ID}]} =
mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
{selected, RItems = [_ | _]} ->
List = lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
_ -> List = []
end,
NewList = Filter(List),
NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
NewList),
mod_privacy:sql_set_privacy_list(ID, NewRItems),
{ok, Default, NewList}
end,
ejabberd_odbc:sql_transaction(LServer, F).
process_blocklist_unblock_all(LUser, LServer) ->
process_blocklist_unblock_all(LUser, LServer, Lang) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = A}) -> A =/= deny
end,
List)
end,
DBType = gen_mod:db_type(LServer, mod_privacy),
case unblock_by_filter(LUser, LServer, Filter, DBType) of
Mod = db_mod(LServer),
case Mod:unblock_by_filter(LUser, LServer, Filter) of
{atomic, ok} -> {result, []};
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
@@ -263,10 +182,10 @@ process_blocklist_unblock_all(LUser, LServer) ->
{result, [], UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]),
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end.
process_blocklist_unblock(LUser, LServer, JIDs) ->
process_blocklist_unblock(LUser, LServer, JIDs, Lang) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = deny, type = jid,
value = JID}) ->
@@ -275,8 +194,8 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
end,
List)
end,
DBType = gen_mod:db_type(LServer, mod_privacy),
case unblock_by_filter(LUser, LServer, Filter, DBType) of
Mod = db_mod(LServer),
case Mod:unblock_by_filter(LUser, LServer, Filter) of
{atomic, ok} -> {result, []};
{atomic, {ok, Default, List}} ->
UserList = make_userlist(Default, List),
@@ -287,79 +206,9 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
{result, [], UserList};
_Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}
end.
unblock_by_filter(LUser, LServer, Filter, mnesia) ->
F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
[] ->
% No lists, nothing to unblock
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],
mnesia:write(P#privacy{lists = NewLists}),
{ok, Default, NewList};
false ->
% No default list, nothing to unblock
ok
end
end
end,
mnesia:transaction(F);
unblock_by_filter(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};
unblock_by_filter(LUser, LServer, Filter, odbc) ->
F = fun () ->
case mod_privacy:sql_get_default_privacy_list_t(LUser)
of
{selected, []} -> ok;
{selected, [{Default}]} ->
{selected, [{ID}]} =
mod_privacy:sql_get_privacy_list_id_t(LUser, Default),
case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of
{selected, RItems = [_ | _]} ->
List = lists:flatmap(fun mod_privacy:raw_to_item/1,
RItems),
NewList = Filter(List),
NewRItems = lists:map(fun mod_privacy:item_to_raw/1,
NewList),
mod_privacy:sql_set_privacy_list(ID, NewRItems),
{ok, Default, NewList};
_ -> ok
end;
_ -> ok
end
end,
ejabberd_odbc:sql_transaction(LServer, F).
make_userlist(Name, List) ->
NeedDb = mod_privacy:is_list_needdb(List),
#userlist{name = Name, list = List, needdb = NeedDb}.
@@ -375,11 +224,11 @@ broadcast_blocklist_event(LUser, LServer, Event) ->
ejabberd_sm:route(JID, JID,
{broadcast, {blocking, Event}}).
process_blocklist_get(LUser, LServer) ->
case process_blocklist_get(LUser, LServer,
gen_mod:db_type(LServer, mod_privacy))
of
error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
process_blocklist_get(LUser, LServer, Lang) ->
Mod = db_mod(LServer),
case Mod:process_blocklist_get(LUser, LServer) of
error ->
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)};
List ->
JIDs = list_to_blocklist_jids(List, []),
Items = lists:map(fun (JID) ->
@@ -397,45 +246,9 @@ process_blocklist_get(LUser, LServer) ->
children = Items}]}
end.
process_blocklist_get(LUser, LServer, mnesia) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer})
of
{'EXIT', _Reason} -> error;
[] -> [];
[#privacy{default = Default, lists = Lists}] ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> List;
_ -> []
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)
of
{selected, []} -> [];
{selected, [{Default}]} ->
case catch mod_privacy:sql_get_privacy_list_data(LUser,
LServer, Default)
of
{selected, RItems} ->
lists:flatmap(fun mod_privacy:raw_to_item/1, RItems);
{'EXIT', _} -> error
end;
{'EXIT', _} -> error
end.
db_mod(LServer) ->
DBType = gen_mod:db_type(LServer, mod_privacy),
gen_mod:db_mod(DBType, ?MODULE).
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc].
+85
View File
@@ -0,0 +1,85 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_blocking_mnesia).
-behaviour(mod_blocking).
%% API
-export([process_blocklist_block/3, unblock_by_filter/3,
process_blocklist_get/2]).
-include("jlib.hrl").
-include("mod_privacy.hrl").
%%%===================================================================
%%% API
%%%===================================================================
process_blocklist_block(LUser, LServer, Filter) ->
F = fun () ->
case mnesia:wread({privacy, {LUser, LServer}}) of
[] ->
P = #privacy{us = {LUser, LServer}},
NewDefault = <<"Blocked contacts">>,
NewLists1 = [],
List = [];
[#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
end,
NewList = Filter(List),
NewLists = [{NewDefault, NewList} | NewLists1],
mnesia:write(P#privacy{default = NewDefault,
lists = NewLists}),
{ok, NewDefault, NewList}
end,
mnesia:transaction(F).
unblock_by_filter(LUser, LServer, Filter) ->
F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
[] ->
%% No lists, nothing to unblock
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],
mnesia:write(P#privacy{lists = NewLists}),
{ok, Default, NewList};
false ->
%% No default list, nothing to unblock
ok
end
end
end,
mnesia:transaction(F).
process_blocklist_get(LUser, LServer) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
{'EXIT', _Reason} -> error;
[] -> [];
[#privacy{default = Default, lists = Lists}] ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> List;
_ -> []
end
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================
+98
View File
@@ -0,0 +1,98 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_blocking_riak).
-behaviour(mod_blocking).
%% API
-export([process_blocklist_block/3, unblock_by_filter/3,
process_blocklist_get/2]).
-include("jlib.hrl").
-include("mod_privacy.hrl").
%%%===================================================================
%%% API
%%%===================================================================
process_blocklist_block(LUser, LServer, Filter) ->
{atomic,
begin
case ejabberd_riak:get(privacy, mod_privacy_riak: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_riak:privacy_schema()) of
ok ->
{ok, NewDefault, NewList};
Err ->
Err
end
end}.
unblock_by_filter(LUser, LServer, Filter) ->
{atomic,
case ejabberd_riak:get(privacy, mod_privacy_riak: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_riak:privacy_schema()) of
ok ->
{ok, Default, NewList};
Err ->
Err
end;
false ->
%% No default list, nothing to unblock
ok
end
end}.
process_blocklist_get(LUser, LServer) ->
case ejabberd_riak:get(privacy, mod_privacy_riak: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.
%%%===================================================================
%%% Internal functions
%%%===================================================================
+92
View File
@@ -0,0 +1,92 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_blocking_sql).
-behaviour(mod_blocking).
%% API
-export([process_blocklist_block/3, unblock_by_filter/3,
process_blocklist_get/2]).
-include("jlib.hrl").
-include("mod_privacy.hrl").
%%%===================================================================
%%% API
%%%===================================================================
process_blocklist_block(LUser, LServer, Filter) ->
F = fun () ->
Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
{selected, []} ->
Name = <<"Blocked contacts">>,
case mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Name) of
{selected, []} ->
mod_privacy_sql:sql_add_privacy_list(LUser, Name);
{selected, [{_ID}]} ->
ok
end,
mod_privacy_sql:sql_set_default_privacy_list(LUser, Name),
Name;
{selected, [{Name}]} -> Name
end,
{selected, [{ID}]} =
mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
{selected, RItems = [_ | _]} ->
List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
_ ->
List = []
end,
NewList = Filter(List),
NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
NewList),
mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
{ok, Default, NewList}
end,
ejabberd_sql:sql_transaction(LServer, F).
unblock_by_filter(LUser, LServer, Filter) ->
F = fun () ->
case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
{selected, []} -> ok;
{selected, [{Default}]} ->
{selected, [{ID}]} =
mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
{selected, RItems = [_ | _]} ->
List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1,
RItems),
NewList = Filter(List),
NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
NewList),
mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
{ok, Default, NewList};
_ -> ok
end;
_ -> ok
end
end,
ejabberd_sql:sql_transaction(LServer, F).
process_blocklist_get(LUser, LServer) ->
case catch mod_privacy_sql:sql_get_default_privacy_list(LUser, LServer) of
{selected, []} -> [];
{selected, [{Default}]} ->
case catch mod_privacy_sql:sql_get_privacy_list_data(
LUser, LServer, Default) of
{selected, RItems} ->
lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
{'EXIT', _} -> error
end;
{'EXIT', _} -> error
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================
+20 -126
View File
@@ -80,6 +80,12 @@
-record(state, {host = <<"">> :: binary()}).
-callback init(binary(), gen_mod:opts()) -> any().
-callback caps_read(binary(), {binary(), binary()}) ->
{ok, non_neg_integer() | [binary()]} | error.
-callback caps_write(binary(), {binary(), binary()},
non_neg_integer() | [binary()]) -> any().
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE,
@@ -300,28 +306,9 @@ c2s_broadcast_recipients(InAcc, Host, C2SState,
end;
c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
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)
end,
mnesia:create_table(caps_features,
[{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(Host, Opts), Host),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
MaxSize = gen_mod:get_opt(cache_size, Opts,
fun(I) when is_integer(I), I>0 -> I end,
1000),
@@ -450,65 +437,13 @@ feature_response(_IQResult, Host, From, Caps,
caps_read_fun(Host, Node) ->
LServer = jid: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.
Mod = gen_mod:db_mod(LServer, ?MODULE),
fun() -> Mod:caps_read(LServer, Node) end.
caps_write_fun(Host, Node, Features) ->
LServer = jid: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.
Mod = gen_mod:db_mod(LServer, ?MODULE),
fun() -> Mod:caps_write(LServer, Node, Features) end.
make_my_disco_hash(Host) ->
JID = jid:make(<<"">>, Host, <<"">>),
@@ -658,64 +593,23 @@ 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}].
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
import_info() ->
[{<<"caps_features">>, 4}].
import_start(LServer, DBType) ->
ets:new(caps_features_tmp, [private, named_table, bag]),
init_db(DBType, LServer),
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:init(LServer, []),
ok.
import(_LServer, {odbc, _}, _DBType, <<"caps_features">>,
import(_LServer, {sql, _}, _DBType, <<"caps_features">>,
[Node, SubNode, Feature, _TimeStamp]) ->
Feature1 = case catch jlib:binary_to_integer(Feature) of
I when is_integer(I), I>0 -> I;
@@ -748,7 +642,7 @@ import_next(LServer, DBType, NodePair) ->
ejabberd_riak:put(
#caps_features{node_pair = NodePair, features = Features},
caps_features_schema());
_ when DBType == odbc ->
_ when DBType == sql ->
ok
end,
import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)).
@@ -757,6 +651,6 @@ mod_opt_type(cache_life_time) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(cache_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) ->
[cache_life_time, cache_size, db_type].
+73
View File
@@ -0,0 +1,73 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_caps_mnesia).
-behaviour(mod_caps).
%% API
-export([init/2, caps_read/2, caps_write/3]).
-include("mod_caps.hrl").
-include("logger.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
case catch mnesia:table_info(caps_features, storage_type) of
{'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)}]),
update_table(),
mnesia:add_table_copy(caps_features, node(),
disc_only_copies).
caps_read(_LServer, Node) ->
case mnesia:dirty_read({caps_features, Node}) of
[#caps_features{features = Features}] -> {ok, Features};
_ -> error
end.
caps_write(_LServer, Node, Features) ->
mnesia:dirty_write(#caps_features{node_pair = Node,
features = Features}).
%%%===================================================================
%%% Internal functions
%%%===================================================================
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.
+38
View File
@@ -0,0 +1,38 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_caps_riak).
-behaviour(mod_caps).
%% API
-export([init/2, caps_read/2, caps_write/3]).
-include("mod_caps.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
ok.
caps_read(_LServer, Node) ->
case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of
{ok, #caps_features{features = Features}} -> {ok, Features};
_ -> error
end.
caps_write(_LServer, Node, Features) ->
ejabberd_riak:put(#caps_features{node_pair = Node,
features = Features},
caps_features_schema()).
%%%===================================================================
%%% Internal functions
%%%===================================================================
caps_features_schema() ->
{record_info(fields, caps_features), #caps_features{}}.
+69
View File
@@ -0,0 +1,69 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_caps_sql).
-behaviour(mod_caps).
-compile([{parse_transform, ejabberd_sql_pt}]).
%% API
-export([init/2, caps_read/2, caps_write/3, export/1]).
-include("mod_caps.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
ok.
caps_read(LServer, {Node, SubNode}) ->
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(feature)s from caps_features where"
" node=%(Node)s and subnode=%(SubNode)s")) of
{selected, [{H}|_] = Fs} ->
case catch jlib:binary_to_integer(H) of
Int when is_integer(Int), Int>=0 ->
{ok, Int};
_ ->
{ok, [F || {F} <- Fs]}
end;
_ ->
error
end.
caps_write(LServer, NodePair, Features) ->
ejabberd_sql:sql_transaction(
LServer,
sql_write_features_t(NodePair, Features)).
export(_Server) ->
[{caps_features,
fun(_Host, #caps_features{node_pair = NodePair,
features = Features}) ->
sql_write_features_t(NodePair, Features);
(_Host, _R) ->
[]
end}].
%%%===================================================================
%%% Internal functions
%%%===================================================================
sql_write_features_t({Node, SubNode}, Features) ->
NewFeatures = if is_integer(Features) ->
[jlib:integer_to_binary(Features)];
true ->
Features
end,
[?SQL("delete from caps_features where node=%(Node)s"
" and subnode=%(SubNode)s;") |
[?SQL("insert into caps_features(node, subnode, feature)"
" values (%(Node)s, %(SubNode)s, %(F)s);") || F <- NewFeatures]].
+23 -33
View File
@@ -43,12 +43,11 @@
-include("logger.hrl").
-include("jlib.hrl").
-define(PROCNAME, ?MODULE).
-define(TABLE, carboncopy).
-type matchspec_atom() :: '_' | '$1' | '$2' | '$3'.
-record(carboncopy,{us :: {binary(), binary()} | matchspec_atom(),
resource :: binary() | matchspec_atom(),
version :: binary() | matchspec_atom()}).
-callback init(binary(), gen_mod:opts()) -> any().
-callback enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
-callback disable(binary(), binary(), binary()) -> ok | {error, any()}.
-callback list(binary(), binary()) -> [{binary(), binary()}].
is_carbon_copy(Packet) ->
is_carbon_copy(Packet, <<"sent">>) orelse
@@ -69,17 +68,8 @@ start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts,fun gen_iq_handler:check_type/1, one_queue),
mod_disco:register_feature(Host, ?NS_CARBONS_1),
mod_disco:register_feature(Host, ?NS_CARBONS_2),
Fields = record_info(fields, ?TABLE),
try mnesia:table_info(?TABLE, attributes) of
Fields -> ok;
_ -> mnesia:delete_table(?TABLE) %% recreate..
catch _:_Error -> ok %%probably table don't exist
end,
mnesia:create_table(?TABLE,
[{ram_copies, [node()]},
{attributes, record_info(fields, ?TABLE)},
{type, bag}]),
mnesia:add_table_copy(?TABLE, node(), ram_copies),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:init(Host, Opts),
ejabberd_hooks:add(unset_presence_hook,Host, ?MODULE, remove_connection, 10),
%% 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),
@@ -102,7 +92,9 @@ iq_handler2(From, To, IQ) ->
iq_handler1(From, To, IQ) ->
iq_handler(From, To, IQ, ?NS_CARBONS_1).
iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children = []}} = IQ, CC)->
iq_handler(From, _To,
#iq{type=set, lang = Lang,
sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)->
?DEBUG("carbons IQ received: ~p", [IQ]),
{U, S, R} = jid:tolower(From),
Result = case Operation of
@@ -118,12 +110,14 @@ iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children
?DEBUG("carbons IQ result: ok", []),
IQ#iq{type=result, sub_el=[]};
{error,_Error} ->
?WARNING_MSG("Error enabling / disabling carbons: ~p", [Result]),
IQ#iq{type=error,sub_el = [?ERR_BAD_REQUEST]}
?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
Txt = <<"Database failure">>,
IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
end;
iq_handler(_From, _To, IQ, _CC)->
IQ#iq{type=error, sub_el = [?ERR_NOT_ALLOWED]}.
iq_handler(_From, _To, #iq{lang = Lang, sub_el = SubEl} = IQ, _CC)->
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
IQ#iq{type=error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}.
user_send_packet(Packet, _C2SState, From, To) ->
check_and_forward(From, To, Packet, sent).
@@ -240,18 +234,13 @@ build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
enable(Host, U, R, CC)->
?DEBUG("enabling for ~p", [U]),
try mnesia:dirty_write(#carboncopy{us = {U, Host}, resource=R, version = CC}) of
ok -> ok
catch _:Error -> {error, Error}
end.
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:enable(U, Host, R, CC).
disable(Host, U, R)->
?DEBUG("disabling for ~p", [U]),
ToDelete = mnesia:dirty_match_object(?TABLE, #carboncopy{us = {U, Host}, resource = R, version = '_'}),
try lists:foreach(fun mnesia:dirty_delete_object/1, ToDelete) of
ok -> ok
catch _:Error -> {error, Error}
end.
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:disable(U, Host, R).
complete_packet(From, #xmlel{name = <<"message">>, attrs = OrigAttrs} = Packet, sent) ->
%% if this is a packet sent by user on this host, then Packet doesn't
@@ -286,8 +275,9 @@ has_non_empty_body(Packet) ->
%% list {resource, cc_version} with carbons enabled for given user and host
list(User, Server) ->
mnesia:dirty_select(?TABLE, [{#carboncopy{us = {User, Server}, resource = '$2', version = '$3'}, [], [{{'$2','$3'}}]}]).
Mod = gen_mod:db_mod(Server, ?MODULE),
Mod:list(User, Server).
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc].
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) -> [db_type, iqdisc].
+68
View File
@@ -0,0 +1,68 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_carboncopy_mnesia).
-behaviour(mod_carboncopy).
%% API
-export([init/2, enable/4, disable/3, list/2]).
-include("mod_carboncopy.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
Fields = record_info(fields, carboncopy),
try mnesia:table_info(carboncopy, attributes) of
Fields ->
ok;
_ ->
%% recreate..
mnesia:delete_table(carboncopy)
catch _:_Error ->
%% probably table don't exist
ok
end,
mnesia:create_table(carboncopy,
[{ram_copies, [node()]},
{attributes, record_info(fields, carboncopy)},
{type, bag}]),
mnesia:add_table_copy(carboncopy, node(), ram_copies).
enable(LUser, LServer, LResource, NS) ->
try mnesia:dirty_write(
#carboncopy{us = {LUser, LServer},
resource = LResource,
version = NS}) of
ok -> ok
catch _:Error ->
{error, Error}
end.
disable(LUser, LServer, LResource) ->
ToDelete = mnesia:dirty_match_object(
#carboncopy{us = {LUser, LServer},
resource = LResource,
version = '_'}),
try lists:foreach(fun mnesia:dirty_delete_object/1, ToDelete) of
ok -> ok
catch _:Error ->
{error, Error}
end.
list(LUser, LServer) ->
mnesia:dirty_select(
carboncopy,
[{#carboncopy{us = {LUser, LServer}, resource = '$2', version = '$3'},
[], [{{'$2','$3'}}]}]).
%%%===================================================================
%%% Internal functions
%%%===================================================================
+243 -53
View File
@@ -30,24 +30,44 @@
-behavior(gen_mod).
-export([start/2, stop/1, add_stream_feature/2,
filter_presence/2, filter_chat_states/2,
mod_opt_type/1]).
%% gen_mod callbacks.
-export([start/2, stop/1, mod_opt_type/1]).
%% ejabberd_hooks callbacks.
-export([filter_presence/3, filter_chat_states/3, filter_pep/3, filter_other/3,
flush_queue/2, add_stream_feature/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-define(CSI_QUEUE_MAX, 100).
-type csi_type() :: presence | chatstate | {pep, binary()}.
-type csi_key() :: {ljid(), csi_type()}.
-type csi_stanza() :: {csi_key(), erlang:timestamp(), xmlel()}.
-type csi_queue() :: [csi_stanza()].
%%--------------------------------------------------------------------
%% gen_mod callbacks.
%%--------------------------------------------------------------------
-spec start(binary(), gen_mod:opts()) -> ok.
start(Host, Opts) ->
QueuePresence = gen_mod:get_opt(queue_presence, Opts,
fun(true) -> true;
(false) -> false
end, true),
DropChatStates = gen_mod:get_opt(drop_chat_states, Opts,
fun(true) -> true;
(false) -> false
end, true),
if QueuePresence; DropChatStates ->
QueuePresence =
gen_mod:get_opt(queue_presence, Opts,
fun(B) when is_boolean(B) -> B end,
true),
QueueChatStates =
gen_mod:get_opt(queue_chat_states, Opts,
fun(B) when is_boolean(B) -> B end,
true),
QueuePEP =
gen_mod:get_opt(queue_pep, Opts,
fun(B) when is_boolean(B) -> B end,
false),
if QueuePresence; QueueChatStates; QueuePEP ->
ejabberd_hooks:add(c2s_post_auth_features, Host, ?MODULE,
add_stream_feature, 50),
if QueuePresence ->
@@ -55,23 +75,145 @@ start(Host, Opts) ->
filter_presence, 50);
true -> ok
end,
if DropChatStates ->
if QueueChatStates ->
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
filter_chat_states, 50);
true -> ok
end;
end,
if QueuePEP ->
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
filter_pep, 50);
true -> ok
end,
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
filter_other, 100),
ejabberd_hooks:add(csi_flush_queue, Host, ?MODULE,
flush_queue, 50);
true -> ok
end,
ok.
end.
-spec stop(binary()) -> ok.
stop(Host) ->
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
filter_presence, 50),
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
filter_chat_states, 50),
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
add_stream_feature, 50),
ok.
QueuePresence =
gen_mod:get_module_opt(Host, ?MODULE, queue_presence,
fun(B) when is_boolean(B) -> B end,
true),
QueueChatStates =
gen_mod:get_module_opt(Host, ?MODULE, queue_chat_states,
fun(B) when is_boolean(B) -> B end,
true),
QueuePEP =
gen_mod:get_module_opt(Host, ?MODULE, queue_pep,
fun(B) when is_boolean(B) -> B end,
false),
if QueuePresence; QueueChatStates; QueuePEP ->
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
add_stream_feature, 50),
if QueuePresence ->
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
filter_presence, 50);
true -> ok
end,
if QueueChatStates ->
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
filter_chat_states, 50);
true -> ok
end,
if QueuePEP ->
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
filter_pep, 50);
true -> ok
end,
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
filter_other, 100),
ejabberd_hooks:delete(csi_flush_queue, Host, ?MODULE,
flush_queue, 50);
true -> ok
end.
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
mod_opt_type(queue_presence) ->
fun(B) when is_boolean(B) -> B end;
mod_opt_type(queue_chat_states) ->
fun(B) when is_boolean(B) -> B end;
mod_opt_type(queue_pep) ->
fun(B) when is_boolean(B) -> B end;
mod_opt_type(_) -> [queue_presence, queue_chat_states, queue_pep].
%%--------------------------------------------------------------------
%% ejabberd_hooks callbacks.
%%--------------------------------------------------------------------
-spec filter_presence({term(), [xmlel()]}, binary(), xmlel())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
filter_presence({C2SState, _OutStanzas} = Acc, Host,
#xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) ->
case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} when Type /= <<"unavailable">> ->
Acc;
_ ->
?DEBUG("Got availability presence stanza", []),
queue_add(presence, Stanza, Host, C2SState)
end;
filter_presence(Acc, _Host, _Stanza) -> Acc.
-spec filter_chat_states({term(), [xmlel()]}, binary(), xmlel())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
filter_chat_states({C2SState, _OutStanzas} = Acc, Host,
#xmlel{name = <<"message">>} = Stanza) ->
case jlib:is_standalone_chat_state(Stanza) of
true ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
To = fxml:get_tag_attr_s(<<"to">>, Stanza),
case {jid:from_string(From), jid:from_string(To)} of
{#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
%% Don't queue (carbon copies of) chat states from other
%% resources, as they might be used to sync the state of
%% conversations across clients.
Acc;
_ ->
?DEBUG("Got standalone chat state notification", []),
queue_add(chatstate, Stanza, Host, C2SState)
end;
false ->
Acc
end;
filter_chat_states(Acc, _Host, _Stanza) -> Acc.
-spec filter_pep({term(), [xmlel()]}, binary(), xmlel())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
filter_pep({C2SState, _OutStanzas} = Acc, Host,
#xmlel{name = <<"message">>} = Stanza) ->
case get_pep_node(Stanza) of
{value, Node} ->
?DEBUG("Got PEP notification", []),
queue_add({pep, Node}, Stanza, Host, C2SState);
false ->
Acc
end;
filter_pep(Acc, _Host, _Stanza) -> Acc.
-spec filter_other({term(), [xmlel()]}, binary(), xmlel())
-> {stop, {term(), [xmlel()]}}.
filter_other({C2SState, _OutStanzas}, Host, Stanza) ->
?DEBUG("Won't add stanza to CSI queue", []),
queue_take(Stanza, Host, C2SState).
-spec flush_queue({term(), [xmlel()]}, binary()) -> {term(), [xmlel()]}.
flush_queue({C2SState, _OutStanzas}, Host) ->
?DEBUG("Going to flush CSI queue", []),
Queue = get_queue(C2SState),
NewState = set_queue([], C2SState),
{NewState, get_stanzas(Queue, Host)}.
-spec add_stream_feature([xmlel()], binary) -> [xmlel()].
add_stream_feature(Features, _Host) ->
Feature = #xmlel{name = <<"csi">>,
@@ -79,34 +221,82 @@ add_stream_feature(Features, _Host) ->
children = []},
[Feature | Features].
filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} when Type /= <<"unavailable">> ->
?DEBUG("Got important presence stanza", []),
{stop, send};
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
-spec queue_add(csi_type(), xmlel(), binary(), term())
-> {stop, {term(), [xmlel()]}}.
queue_add(Type, Stanza, Host, C2SState) ->
case get_queue(C2SState) of
Queue when length(Queue) >= ?CSI_QUEUE_MAX ->
?DEBUG("CSI queue too large, going to flush it", []),
NewState = set_queue([], C2SState),
{stop, {NewState, get_stanzas(Queue, Host) ++ [Stanza]}};
Queue ->
?DEBUG("Adding stanza to CSI queue", []),
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
Key = {jid:tolower(jid:from_string(From)), Type},
Entry = {Key, p1_time_compat:timestamp(), Stanza},
NewQueue = lists:keystore(Key, 1, Queue, Entry),
NewState = set_queue(NewQueue, C2SState),
{stop, {NewState, []}}
end.
-spec queue_take(xmlel(), binary(), term()) -> {stop, {term(), [xmlel()]}}.
queue_take(Stanza, Host, C2SState) ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
{LUser, LServer, _LResource} = jid:tolower(jid:from_string(From)),
{Selected, Rest} = lists:partition(
fun({{{U, S, _R}, _Type}, _Time, _Stanza}) ->
U == LUser andalso S == LServer
end, get_queue(C2SState)),
NewState = set_queue(Rest, C2SState),
{stop, {NewState, get_stanzas(Selected, Host) ++ [Stanza]}}.
-spec set_queue(csi_queue(), term()) -> term().
set_queue(Queue, C2SState) ->
ejabberd_c2s:set_aux_field(csi_queue, Queue, C2SState).
-spec get_queue(term()) -> csi_queue().
get_queue(C2SState) ->
case ejabberd_c2s:get_aux_field(csi_queue, C2SState) of
{ok, Queue} ->
Queue;
error ->
[]
end.
-spec get_stanzas(csi_queue(), binary()) -> [xmlel()].
get_stanzas(Queue, Host) ->
lists:map(fun({_Key, Time, Stanza}) ->
jlib:add_delay_info(Stanza, Host, Time,
<<"Client Inactive">>)
end, Queue).
-spec get_pep_node(xmlel()) -> {value, binary()} | false.
get_pep_node(#xmlel{name = <<"message">>} = Stanza) ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
case jid:from_string(From) of
#jid{luser = <<>>} -> % It's not PEP.
false;
_ ->
?DEBUG("Got availability presence stanza", []),
{stop, queue}
end;
filter_presence(Action, _Stanza) -> Action.
filter_chat_states(_Action, #xmlel{name = <<"message">>} = Stanza) ->
case jlib:is_standalone_chat_state(Stanza) of
true ->
?DEBUG("Got standalone chat state notification", []),
{stop, drop};
false ->
?DEBUG("Got message stanza", []),
{stop, send}
end;
filter_chat_states(Action, _Stanza) -> Action.
mod_opt_type(drop_chat_states) ->
fun (true) -> true;
(false) -> false
end;
mod_opt_type(queue_presence) ->
fun (true) -> true;
(false) -> false
end;
mod_opt_type(_) -> [drop_chat_states, queue_presence].
case fxml:get_subtag_with_xmlns(Stanza, <<"event">>,
?NS_PUBSUB_EVENT) of
#xmlel{children = Els} ->
case fxml:remove_cdata(Els) of
[#xmlel{name = <<"items">>, attrs = ItemsAttrs}] ->
fxml:get_attr(<<"node">>, ItemsAttrs);
_ ->
false
end;
false ->
false
end
end.
+233 -158
View File
@@ -198,81 +198,81 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
%%%-----------------------------------------------------------------------
-define(INFO_RESULT(Allow, Feats),
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny -> {error, ?ERR_FORBIDDEN};
deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow -> {result, Feats}
end).
get_sm_features(Acc, From,
#jid{lserver = LServer} = _To, Node, _Lang) ->
#jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
Allow = acl:match_rule(LServer, configure, From),
case Node of
<<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS]);
<<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
_ -> Acc
end
end.
get_local_features(Acc, From,
#jid{lserver = LServer} = _To, Node, _Lang) ->
#jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
case LNode of
[<<"config">>] -> ?INFO_RESULT(Allow, []);
[<<"user">>] -> ?INFO_RESULT(Allow, []);
[<<"online users">>] -> ?INFO_RESULT(Allow, []);
[<<"all users">>] -> ?INFO_RESULT(Allow, []);
[<<"config">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"user">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"online users">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"all users">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"all users">>, <<$@, _/binary>>] ->
?INFO_RESULT(Allow, []);
[<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, []);
[<<"running nodes">>] -> ?INFO_RESULT(Allow, []);
[<<"stopped nodes">>] -> ?INFO_RESULT(Allow, []);
?INFO_RESULT(Allow, [], Lang);
[<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"stopped nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode] ->
?INFO_RESULT(Allow, [?NS_STATS]);
?INFO_RESULT(Allow, [?NS_STATS], Lang);
[<<"running nodes">>, _ENode, <<"DB">>] ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"modules">>] ->
?INFO_RESULT(Allow, []);
?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"backup">>] ->
?INFO_RESULT(Allow, []);
?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"import">>] ->
?INFO_RESULT(Allow, []);
?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"import">>, _] ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"restart">>] ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"config">>, _] ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"add-user">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"delete-user">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"end-user-session">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-user-password">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"change-user-password">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-user-lastlogin">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"user-stats">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-registered-users-num">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-online-users-num">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
_ -> Acc
end
end.
@@ -318,7 +318,8 @@ get_sm_items(Acc, From,
{result,
Items ++ Nodes ++ get_user_resources(User, Server)};
{allow, <<"config">>} -> {result, []};
{_, <<"config">>} -> {error, ?ERR_FORBIDDEN};
{_, <<"config">>} ->
{error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
_ -> Acc
end
end.
@@ -448,63 +449,64 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To,
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
Err = ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>),
case LNode of
[<<"config">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"user">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"online users">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"all users">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"all users">>, <<$@, _/binary>>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"outgoing s2s">> | _] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"stopped nodes">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"DB">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"modules">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"backup">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"import">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"import">>, _] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"restart">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"config">>, _] ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"add-user">>) ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"delete-user">>) ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"end-user-session">>) ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-user-password">>) ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"change-user-password">>) ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-user-lastlogin">>) ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"user-stats">>) ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-registered-users-num">>) ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-online-users-num">>) ->
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
?ITEMS_RESULT(Allow, LNode, {error, Err});
_ -> Acc
end
end.
@@ -562,33 +564,29 @@ get_local_items({_, Host}, [<<"all users">>], _Server,
get_local_items({_, Host},
[<<"all users">>, <<$@, Diap/binary>>], _Server,
_Lang) ->
case catch ejabberd_auth:get_vh_registered_users(Host)
of
{'EXIT', _Reason} -> ?ERR_INTERNAL_SERVER_ERROR;
Users ->
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
case catch begin
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
N1 = jlib:binary_to_integer(S1),
N2 = jlib:binary_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
lists:map(fun ({S, U}) ->
#xmlel{name = <<"item">>,
attrs =
[{<<"jid">>,
<<U/binary, "@",
S/binary>>},
{<<"name">>,
<<U/binary, "@",
S/binary>>}],
children = []}
end,
Sub)
end
of
{'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE;
Res -> {result, Res}
end
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
case catch begin
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
N1 = jlib:binary_to_integer(S1),
N2 = jlib:binary_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
lists:map(fun ({S, U}) ->
#xmlel{name = <<"item">>,
attrs =
[{<<"jid">>,
<<U/binary, "@",
S/binary>>},
{<<"name">>,
<<U/binary, "@",
S/binary>>}],
children = []}
end,
Sub)
end
of
{'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE;
Res -> {result, Res}
end;
get_local_items({_, Host}, [<<"outgoing s2s">>],
_Server, Lang) ->
@@ -826,33 +824,33 @@ get_stopped_nodes(_Lang) ->
%%-------------------------------------------------------------------------
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
Request),
Request, Lang),
case acl:match_rule(LServerOrGlobal, configure, From) of
deny -> {error, ?ERR_FORBIDDEN};
deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow -> adhoc_local_commands(From, To, Request)
end).
adhoc_local_commands(Acc, From,
#jid{lserver = LServer} = To,
#adhoc_request{node = Node} = Request) ->
#adhoc_request{node = Node, lang = Lang} = Request) ->
LNode = tokenize(Node),
case LNode of
[<<"running nodes">>, _ENode, <<"DB">>] ->
?COMMANDS_RESULT(global, From, To, Request);
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"modules">>, _] ->
?COMMANDS_RESULT(LServer, From, To, Request);
?COMMANDS_RESULT(LServer, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
?COMMANDS_RESULT(global, From, To, Request);
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"import">>, _] ->
?COMMANDS_RESULT(global, From, To, Request);
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"restart">>] ->
?COMMANDS_RESULT(global, From, To, Request);
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
?COMMANDS_RESULT(global, From, To, Request);
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"config">>, _] ->
?COMMANDS_RESULT(LServer, From, To, Request);
?COMMANDS_RESULT(LServer, From, To, Request, Lang);
?NS_ADMINL(_) ->
?COMMANDS_RESULT(LServer, From, To, Request);
?COMMANDS_RESULT(LServer, From, To, Request, Lang);
_ -> Acc
end.
@@ -882,7 +880,8 @@ adhoc_local_commands(From,
end;
XData /= false, ActionIsExecute ->
case jlib:parse_xdata_submit(XData) of
invalid -> {error, ?ERR_BAD_REQUEST};
invalid ->
{error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
Fields ->
case catch set_form(From, LServer, LNode, Lang, Fields)
of
@@ -898,7 +897,8 @@ adhoc_local_commands(From,
{error, Error} -> {error, Error}
end
end;
true -> {error, ?ERR_BAD_REQUEST}
true ->
{error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect action or data form">>)}
end.
-define(TVFIELD(Type, Var, Val),
@@ -980,10 +980,14 @@ adhoc_local_commands(From,
get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
Lang) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
{badrpc, _Reason} ->
{badrpc, Reason} ->
?ERROR_MSG("RPC call mnesia:system_info(tables) on node "
"~s failed: ~p", [Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
Tables ->
STables = lists:sort(Tables),
@@ -1023,10 +1027,14 @@ get_form(Host,
[<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
Lang) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case ejabberd_cluster:call(Node, gen_mod, loaded_modules, [Host]) of
{badrpc, _Reason} ->
{badrpc, Reason} ->
?ERROR_MSG("RPC call gen_mod:loaded_modules(~s) on node "
"~s failed: ~p", [Host, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
Modules ->
SModules = lists:sort(Modules),
@@ -1562,9 +1570,11 @@ get_form(_Host, _, _Lang) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"DB">>], _Lang, XData) ->
[<<"running nodes">>, ENode, <<"DB">>], Lang, XData) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
lists:foreach(fun ({SVar, SVals}) ->
Table = jlib:binary_to_atom(SVar),
@@ -1596,9 +1606,11 @@ set_form(_From, _Host,
end;
set_form(_From, Host,
[<<"running nodes">>, ENode, <<"modules">>, <<"stop">>],
_Lang, XData) ->
Lang, XData) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
lists:foreach(fun ({Var, Vals}) ->
case Vals of
@@ -1615,12 +1627,16 @@ set_form(_From, Host,
set_form(_From, Host,
[<<"running nodes">>, ENode, <<"modules">>,
<<"start">>],
_Lang, XData) ->
Lang, XData) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"modules">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
false ->
Txt = <<"No 'modules' found in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, Strings}} ->
String = lists:foldl(fun (S, Res) ->
<<Res/binary, S/binary, "\n">>
@@ -1637,98 +1653,143 @@ set_form(_From, Host,
end,
Modules),
{result, []};
_ -> {error, ?ERR_BAD_REQUEST}
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
end;
_ -> {error, ?ERR_BAD_REQUEST}
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
end
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"backup">>],
_Lang, XData) ->
Lang, XData) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
false ->
Txt = <<"No 'path' found in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
case ejabberd_cluster:call(Node, mnesia, backup, [String]) of
{badrpc, _Reason} ->
{badrpc, Reason} ->
?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
"failed: ~p", [String, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, Reason} ->
?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s "
"failed: ~p", [String, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
_ -> {result, []}
end;
_ -> {error, ?ERR_BAD_REQUEST}
_ ->
Txt = <<"Incorrect value of 'path' in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"restore">>],
_Lang, XData) ->
Lang, XData) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
false ->
Txt = <<"No 'path' found in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
case ejabberd_cluster:call(Node, ejabberd_admin, restore, [String])
of
{badrpc, _Reason} ->
{badrpc, Reason} ->
?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
"~s failed: ~p", [String, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, Reason} ->
?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node "
"~s failed: ~p", [String, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
_ -> {result, []}
end;
_ -> {error, ?ERR_BAD_REQUEST}
_ ->
Txt = <<"Incorrect value of 'path' in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"textfile">>],
_Lang, XData) ->
Lang, XData) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
false ->
Txt = <<"No 'path' found in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
case ejabberd_cluster:call(Node, ejabberd_admin, dump_to_textfile,
[String])
of
{badrpc, _Reason} ->
{badrpc, Reason} ->
?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
"to node ~s failed: ~p", [String, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, Reason} ->
?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) "
"to node ~s failed: ~p", [String, Node, Reason]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, _Reason} -> {error, ?ERR_INTERNAL_SERVER_ERROR};
_ -> {result, []}
end;
_ -> {error, ?ERR_BAD_REQUEST}
_ ->
Txt = <<"Incorrect value of 'path' in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"file">>],
_Lang, XData) ->
Lang, XData) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
false ->
Txt = <<"No 'path' found in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
ejabberd_cluster:call(Node, jd2ejd, import_file, [String]),
{result, []};
_ -> {error, ?ERR_BAD_REQUEST}
_ ->
Txt = <<"Incorrect value of 'path' in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
_Lang, XData) ->
Lang, XData) ->
case search_running_node(ENode) of
false -> {error, ?ERR_ITEM_NOT_FOUND};
false ->
Txt = <<"No running node found">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)};
Node ->
case lists:keysearch(<<"path">>, 1, XData) of
false -> {error, ?ERR_BAD_REQUEST};
false ->
Txt = <<"No 'path' found in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
{value, {_, [String]}} ->
ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]),
{result, []};
_ -> {error, ?ERR_BAD_REQUEST}
_ ->
Txt = <<"Incorrect value of 'path' in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
set_form(From, Host,
@@ -1739,7 +1800,7 @@ set_form(From, Host,
[<<"running nodes">>, ENode, <<"shutdown">>], _Lang,
XData) ->
stop_node(From, Host, ENode, stop, XData);
set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang,
set_form(_From, Host, [<<"config">>, <<"acls">>], Lang,
XData) ->
case lists:keysearch(<<"acls">>, 1, XData) of
{value, {_, Strings}} ->
@@ -1753,14 +1814,16 @@ set_form(_From, Host, [<<"config">>, <<"acls">>], _Lang,
{ok, ACLs} ->
acl:add_list(Host, ACLs, true),
{result, []};
_ -> {error, ?ERR_BAD_REQUEST}
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
end;
_ -> {error, ?ERR_BAD_REQUEST}
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
end;
_ -> {error, ?ERR_BAD_REQUEST}
_ ->
Txt = <<"No 'acls' found in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
set_form(_From, Host, [<<"config">>, <<"access">>],
_Lang, XData) ->
Lang, XData) ->
SetAccess = fun (Rs) ->
mnesia:transaction(fun () ->
Os = mnesia:select(local_config,
@@ -1803,11 +1866,13 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
{atomic, _} -> {result, []};
_ -> {error, ?ERR_BAD_REQUEST}
end;
_ -> {error, ?ERR_BAD_REQUEST}
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)}
end;
_ -> {error, ?ERR_BAD_REQUEST}
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)}
end;
_ -> {error, ?ERR_BAD_REQUEST}
_ ->
Txt = <<"No 'access' found in data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end;
set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
XData) ->
@@ -1852,17 +1917,19 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
case JID#jid.lresource of
<<>> ->
SIDs = mnesia:dirty_select(session,
[{#session{sid = '$1',
[{#session{sid = {'$1', '$2'},
usr = {LUser, LServer, '_'},
_ = '_'},
[], ['$1']}]),
[{is_pid, '$2'}],
[{{'$1', '$2'}}]}]),
[Pid ! {kick, kicked_by_admin, Xmlelement} || {_, Pid} <- SIDs];
R ->
[{_, Pid}] = mnesia:dirty_select(session,
[{#session{sid = '$1',
[{#session{sid = {'$1', '$2'},
usr = {LUser, LServer, R},
_ = '_'},
[], ['$1']}]),
[{is_pid, '$2'}],
[{{'$1', '$2'}}]}]),
Pid ! {kick, kicked_by_admin, Xmlelement}
end,
{result, []};
@@ -2052,7 +2119,7 @@ adhoc_sm_commands(_Acc, From,
action = Action, xdata = XData} =
Request) ->
case acl:match_rule(LServer, configure, From) of
deny -> {error, ?ERR_FORBIDDEN};
deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)};
allow ->
ActionIsExecute = lists:member(Action,
[<<"">>, <<"execute">>,
@@ -2071,11 +2138,15 @@ adhoc_sm_commands(_Acc, From,
end;
XData /= false, ActionIsExecute ->
case jlib:parse_xdata_submit(XData) of
invalid -> {error, ?ERR_BAD_REQUEST};
invalid ->
Txt = <<"Incorrect data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
Fields ->
set_sm_form(User, Server, <<"config">>, Request, Fields)
end;
true -> {error, ?ERR_BAD_REQUEST}
true ->
Txt = <<"Incorrect action or data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end
end;
adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc.
@@ -2135,12 +2206,16 @@ set_sm_form(User, Server, <<"config">>,
{value, {_, [Password]}} ->
ejabberd_auth:set_password(User, Server, Password),
adhoc:produce_response(Response);
_ -> {error, ?ERR_NOT_ACCEPTABLE}
_ ->
Txt = <<"No 'password' found in data form">>,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)}
end;
{value, {_, [<<"remove">>]}} ->
catch ejabberd_auth:remove_user(User, Server),
adhoc:produce_response(Response);
_ -> {error, ?ERR_NOT_ACCEPTABLE}
_ ->
Txt = <<"Incorrect value of 'action' in data form">>,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)}
end;
set_sm_form(_User, _Server, _Node, _Request, _Fields) ->
{error, ?ERR_SERVICE_UNAVAILABLE}.
+15 -12
View File
@@ -56,15 +56,17 @@ stop(Host) ->
?NS_ECONFIGURE).
process_local_iq(From, To,
#iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case acl:match_rule(To#jid.lserver, configure, From) of
deny ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
Txt = <<"Denied by ACL">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
allow ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
sub_el = [SubEl, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)]};
%%case fxml:get_tag_attr_s("type", SubEl) of
%% "cancel" ->
%% IQ#iq{type = result,
@@ -98,7 +100,7 @@ process_local_iq(From, To,
%% sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
%%end;
get ->
case process_get(SubEl) of
case process_get(SubEl, Lang) of
{result, Res} -> IQ#iq{type = result, sub_el = [Res]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
@@ -106,7 +108,7 @@ process_local_iq(From, To,
end
end.
process_get(#xmlel{name = <<"info">>}) ->
process_get(#xmlel{name = <<"info">>}, _Lang) ->
S2SConns = ejabberd_s2s:dirty_get_connections(),
TConns = lists:usort([element(2, C) || C <- S2SConns]),
Attrs = [{<<"registered-users">>,
@@ -130,7 +132,7 @@ process_get(#xmlel{name = <<"info">>}) ->
attrs = [{<<"xmlns">>, ?NS_ECONFIGURE} | Attrs],
children = []}};
process_get(#xmlel{name = <<"welcome-message">>,
attrs = Attrs}) ->
attrs = Attrs}, _Lang) ->
{Subj, Body} = ejabberd_config:get_option(
welcome_message,
fun({Subj, Body}) ->
@@ -146,7 +148,7 @@ process_get(#xmlel{name = <<"welcome-message">>,
#xmlel{name = <<"body">>, attrs = [],
children = [{xmlcdata, Body}]}]}};
process_get(#xmlel{name = <<"registration-watchers">>,
attrs = Attrs}) ->
attrs = Attrs}, _Lang) ->
SubEls = ejabberd_config:get_option(
registration_watchers,
fun(JIDs) when is_list(JIDs) ->
@@ -160,14 +162,14 @@ process_get(#xmlel{name = <<"registration-watchers">>,
{result,
#xmlel{name = <<"registration_watchers">>,
attrs = Attrs, children = SubEls}};
process_get(#xmlel{name = <<"acls">>, attrs = Attrs}) ->
process_get(#xmlel{name = <<"acls">>, attrs = Attrs}, _Lang) ->
Str = iolist_to_binary(io_lib:format("~p.",
[ets:tab2list(acl)])),
{result,
#xmlel{name = <<"acls">>, attrs = Attrs,
children = [{xmlcdata, Str}]}};
process_get(#xmlel{name = <<"access">>,
attrs = Attrs}) ->
attrs = Attrs}, _Lang) ->
Str = iolist_to_binary(io_lib:format("~p.",
[ets:select(local_config,
[{{local_config, {access, '$1'},
@@ -178,13 +180,14 @@ process_get(#xmlel{name = <<"access">>,
{result,
#xmlel{name = <<"access">>, attrs = Attrs,
children = [{xmlcdata, Str}]}};
process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
process_get(#xmlel{name = <<"last">>, attrs = Attrs}, Lang) ->
case catch mnesia:dirty_select(last_activity,
[{{last_activity, '_', '$1', '_'}, [],
['$1']}])
of
{'EXIT', _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
Txt = <<"Database failure">>,
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)};
Vals ->
TimeStamp = p1_time_compat:system_time(seconds),
Str = list_to_binary(
@@ -196,7 +199,7 @@ process_get(#xmlel{name = <<"last">>, attrs = Attrs}) ->
end;
%%process_get({xmlelement, Name, Attrs, SubEls}) ->
%% {result, };
process_get(_) -> {error, ?ERR_BAD_REQUEST}.
process_get(_, _) -> {error, ?ERR_BAD_REQUEST}.
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc].
+26 -14
View File
@@ -150,7 +150,8 @@ process_local_iq_items(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Host = To#jid.lserver,
@@ -177,7 +178,8 @@ process_local_iq_info(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
Host = To#jid.lserver,
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
@@ -229,10 +231,12 @@ get_local_features(Acc, _From, To, <<>>, _Lang) ->
ets:select(disco_features,
[{{{'_', Host}}, [], ['$_']}])
++ Feats};
get_local_features(Acc, _From, _To, _Node, _Lang) ->
get_local_features(Acc, _From, _To, _Node, Lang) ->
case Acc of
{result, _Features} -> Acc;
empty -> {error, ?ERR_ITEM_NOT_FOUND}
empty ->
Txt = <<"No features available">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}
end.
features_to_xml(FeatureList) ->
@@ -271,8 +275,8 @@ get_local_services(Acc, _From, To, <<>>, _Lang) ->
get_local_services({result, _} = Acc, _From, _To, _Node,
_Lang) ->
Acc;
get_local_services(empty, _From, _To, _Node, _Lang) ->
{error, ?ERR_ITEM_NOT_FOUND}.
get_local_services(empty, _From, _To, _Node, Lang) ->
{error, ?ERRT_ITEM_NOT_FOUND(Lang, <<"No services available">>)}.
get_vh_services(Host) ->
Hosts = lists:sort(fun (H1, H2) ->
@@ -300,7 +304,8 @@ process_sm_iq_items(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
case is_presence_subscribed(From, To) of
true ->
@@ -325,8 +330,9 @@ process_sm_iq_items(From, To,
IQ#iq{type = error, sub_el = [SubEl, Error]}
end;
false ->
Txt = <<"Not subscribed">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
end
end.
@@ -347,12 +353,14 @@ get_sm_items(Acc, From,
get_sm_items({result, _} = Acc, _From, _To, _Node,
_Lang) ->
Acc;
get_sm_items(empty, From, To, _Node, _Lang) ->
get_sm_items(empty, From, To, _Node, Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
_ -> {error, ?ERR_NOT_ALLOWED}
_ ->
Txt = <<"Query to another users is forbidden">>,
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end.
is_presence_subscribed(#jid{luser = User, lserver = Server},
@@ -373,7 +381,8 @@ process_sm_iq_info(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
case is_presence_subscribed(From, To) of
true ->
@@ -405,8 +414,9 @@ process_sm_iq_info(From, To,
IQ#iq{type = error, sub_el = [SubEl, Error]}
end;
false ->
Txt = <<"Not subscribed">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
end
end.
@@ -423,12 +433,14 @@ get_sm_identity(Acc, _From,
_ -> []
end.
get_sm_features(empty, From, To, _Node, _Lang) ->
get_sm_features(empty, From, To, _Node, Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND};
_ -> {error, ?ERR_NOT_ALLOWED}
_ ->
Txt = <<"Query to another users is forbidden">>,
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
end;
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
+4 -1
View File
@@ -118,7 +118,10 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, From, To, Packet}, State) ->
Packet2 = case From#jid.user of
<<"">> ->
jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"User part of JID in 'from' is empty">>,
jlib:make_error_reply(
Packet, ?ERRT_BAD_REQUEST(Lang, Txt));
_ -> Packet
end,
do_client_version(disabled, To, From),
+2 -2
View File
@@ -167,7 +167,7 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
is_whitelisted(Host, Addr) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access,
fun(A) when is_atom(A) -> A end,
fun(A) -> A end,
none),
acl:match_rule(Host, Access, Addr) == allow.
@@ -187,7 +187,7 @@ format_date({{Year, Month, Day}, {Hour, Minute, Second}}) ->
[Hour, Minute, Second, Day, Month, Year]).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(c2s_auth_ban_lifetime) ->
fun (T) when is_integer(T), T > 0 -> T end;
mod_opt_type(c2s_max_auth_failures) ->
+145 -78
View File
@@ -29,6 +29,11 @@
%% request_handlers:
%% "/api": mod_http_api
%%
%% To use a specific API version N, add a vN element in the URL path:
%% in ejabberd_http listener
%% request_handlers:
%% "/api/v2": mod_http_api
%%
%% Access rights are defined with:
%% commands_admin_access: configure
%% commands:
@@ -76,6 +81,8 @@
-include("logger.hrl").
-include("ejabberd_http.hrl").
-define(DEFAULT_API_VERSION, 0).
-define(CT_PLAIN,
{<<"Content-Type">>, <<"text/plain">>}).
@@ -116,7 +123,6 @@ start(_Host, _Opts) ->
stop(_Host) ->
ok.
%% ----------
%% basic auth
%% ----------
@@ -124,12 +130,13 @@ stop(_Host) ->
check_permissions(Request, Command) ->
case catch binary_to_existing_atom(Command, utf8) of
Call when is_atom(Call) ->
check_permissions2(Request, Call);
{ok, CommandPolicy} = ejabberd_commands:get_command_policy(Call),
check_permissions2(Request, Call, CommandPolicy);
_ ->
unauthorized_response()
end.
check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call)
check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
when HTTPAuth /= undefined ->
Admin =
case lists:keysearch(<<"X-Admin">>, 1, Headers) of
@@ -150,8 +157,10 @@ check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call)
end;
{oauth, Token, _} ->
case oauth_check_token(Call, Token) of
{ok, User, Server} ->
{ok, user, {User, Server}} ->
{ok, {User, Server, {oauth, Token}, Admin}};
{ok, server_admin} -> %% token whas generated using issue_token command line
{ok, admin};
false ->
false
end;
@@ -162,9 +171,11 @@ check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call)
{ok, A} -> {allowed, Call, A};
_ -> unauthorized_response()
end;
check_permissions2(#request{ip={IP, _Port}}, Call) ->
check_permissions2(_Request, Call, open) ->
{allowed, Call, noauth};
check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
mod_opt_type(admin_ip_access),
fun(V) -> V end,
none),
Res = acl:match_rule(global, Access, IP),
case Res of
@@ -179,9 +190,11 @@ check_permissions2(#request{ip={IP, _Port}}, Call) ->
true -> {allowed, Call, admin};
_ -> unauthorized_response()
end;
_ ->
unauthorized_response()
end.
_E ->
{allowed, Call, noauth}
end;
check_permissions2(_Request, _Call, _Policy) ->
unauthorized_response().
oauth_check_token(Scope, Token) when is_atom(Scope) ->
oauth_check_token(atom_to_binary(Scope, utf8), Token);
@@ -192,44 +205,53 @@ oauth_check_token(Scope, Token) ->
%% command processing
%% ------------------
%process(Call, Request) ->
% ?DEBUG("~p~n~p", [Call, Request]), ok;
process(_, #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
badrequest_response();
process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
badrequest_response(<<"Missing POST data">>);
process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} = Req) ->
Version = get_api_version(Req),
try
Args = case jiffy:decode(Data) of
List when is_list(List) -> List;
{List} when is_list(List) -> List;
Other -> [Other]
end,
log(Call, Args, IP),
log(Call, Args, IPPort),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
{Code, Result} = handle(Cmd, Auth, Args),
json_response(Code, jiffy:encode(Result));
ErrorResponse -> %% Should we reply 403 ?
ErrorResponse
end
catch _:Error ->
?DEBUG("Bad Request: ~p", [Error]),
badrequest_response()
end;
process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
try
Args = case Data of
[{nokey, <<>>}] -> [];
_ -> Data
end,
log(Call, Args, IP),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
{Code, Result} = handle(Cmd, Auth, Args),
{Code, Result} = handle(Cmd, Auth, Args, Version, IP),
json_response(Code, jiffy:encode(Result));
%% Warning: check_permission direcly formats 401 reply if not authorized
ErrorResponse ->
ErrorResponse
end
catch _:Error ->
?DEBUG("Bad Request: ~p", [Error]),
catch _:{error,{_,invalid_json}} = _Err ->
?DEBUG("Bad Request: ~p", [_Err]),
badrequest_response(<<"Invalid JSON input">>);
_:_Error ->
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
badrequest_response()
end;
process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
Version = get_api_version(Req),
try
Args = case Data of
[{nokey, <<>>}] -> [];
_ -> Data
end,
log(Call, Args, IP),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
{Code, Result} = handle(Cmd, Auth, Args, Version, IP),
json_response(Code, jiffy:encode(Result));
%% Warning: check_permission direcly formats 401 reply if not authorized
ErrorResponse ->
ErrorResponse
end
catch _:_Error ->
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
badrequest_response()
end;
process([], #request{method = 'OPTIONS', data = <<>>}) ->
@@ -238,13 +260,28 @@ process(_Path, Request) ->
?DEBUG("Bad Request: no handler ~p", [Request]),
badrequest_response().
% get API version N from last "vN" element in URL path
get_api_version(#request{path = Path}) ->
get_api_version(lists:reverse(Path));
get_api_version([<<"v", String/binary>> | Tail]) ->
case catch jlib:binary_to_integer(String) of
N when is_integer(N) ->
N;
_ ->
get_api_version(Tail)
end;
get_api_version([_Head | Tail]) ->
get_api_version(Tail);
get_api_version([]) ->
?DEFAULT_API_VERSION.
%% ----------------
%% command handlers
%% ----------------
% generic ejabberd command handler
handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
case ejabberd_commands:get_command_format(Call, Auth) of
handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
case ejabberd_commands:get_command_format(Call, Auth, Version) of
{ArgsSpec, _} when is_list(ArgsSpec) ->
Args2 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args],
Spec = lists:foldr(
@@ -259,23 +296,48 @@ handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
({Key, atom}, Acc) ->
[{Key, undefined}|Acc]
end, [], ArgsSpec),
handle2(Call, Auth, match(Args2, Spec));
try
handle2(Call, Auth, match(Args2, Spec), Version, IP)
catch throw:not_found ->
{404, <<"not_found">>};
throw:{not_found, Why} when is_atom(Why) ->
{404, jlib:atom_to_binary(Why)};
throw:{not_found, Msg} ->
{404, iolist_to_binary(Msg)};
throw:not_allowed ->
{401, <<"not_allowed">>};
throw:{not_allowed, Why} when is_atom(Why) ->
{401, jlib:atom_to_binary(Why)};
throw:{not_allowed, Msg} ->
{401, iolist_to_binary(Msg)};
throw:{error, account_unprivileged} ->
{401, iolist_to_binary(<<"Unauthorized: Account Unpriviledged">>)};
throw:{invalid_parameter, Msg} ->
{400, iolist_to_binary(Msg)};
throw:{error, Why} when is_atom(Why) ->
{400, jlib:atom_to_binary(Why)};
throw:{error, Msg} ->
{400, iolist_to_binary(Msg)};
throw:Error when is_atom(Error) ->
{400, jlib:atom_to_binary(Error)};
throw:Msg when is_list(Msg); is_binary(Msg) ->
{400, iolist_to_binary(Msg)};
_Error ->
?ERROR_MSG("REST API Error: ~p ~p", [_Error, erlang:get_stacktrace()]),
{500, <<"internal_error">>}
end;
{error, Msg} ->
?ERROR_MSG("REST API Error: ~p", [Msg]),
{400, Msg};
_Error ->
?ERROR_MSG("REST API Error: ~p", [_Error]),
{400, <<"Error">>}
end.
handle2(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
{ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth),
handle2(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
{ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
ArgsFormatted = format_args(Args, ArgsF),
case ejabberd_command(Auth, Call, ArgsFormatted, 400) of
0 -> {200, <<"OK">>};
1 -> {500, <<"500 Internal server error">>};
400 -> {400, <<"400 Bad Request">>};
404 -> {404, <<"404 Not found">>};
Res -> format_command_result(Call, Auth, Res)
end.
ejabberd_command(Auth, Call, ArgsFormatted, Version, IP).
get_elem_delete(A, L) ->
case proplists:get_all_values(A, L) of
@@ -339,7 +401,9 @@ format_arg(undefined, binary) -> <<>>;
format_arg(undefined, string) -> <<>>;
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
error.
throw({invalid_parameter,
io_lib:format("Arg ~p is not in format ~p",
[Arg, Format])}).
process_unicode_codepoints(Str) ->
iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
@@ -353,36 +417,37 @@ process_unicode_codepoints(Str) ->
match(Args, Spec) ->
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
ejabberd_command(Auth, Cmd, Args, Default) ->
ejabberd_command(Auth, Cmd, Args, Version, IP) ->
Access = case Auth of
admin -> [];
_ -> undefined
end,
case catch ejabberd_commands:execute_command(Access, Auth, Cmd, Args) of
{'EXIT', _} -> Default;
{error, _} -> Default;
Result -> Result
case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version, #{ip => IP}) of
{error, Error} ->
throw(Error);
Res ->
format_command_result(Cmd, Auth, Res, Version)
end.
format_command_result(Cmd, Auth, Result) ->
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth),
format_command_result(Cmd, Auth, Result, Version) ->
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
case {ResultFormat, Result} of
{{_, rescode}, V} when V == true; V == ok ->
{200, <<"">>};
{{_, rescode}, _} ->
{500, <<"">>};
{{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
{200, iolist_to_binary(Text1)};
{{_, restuple}, {_, Text2}} ->
{500, iolist_to_binary(Text2)};
{{_, {list, _}}, _V} ->
{_, L} = format_result(Result, ResultFormat),
{200, L};
{{_, {tuple, _}}, _V} ->
{_, T} = format_result(Result, ResultFormat),
{200, T};
_ ->
{200, {[format_result(Result, ResultFormat)]}}
{{_, rescode}, V} when V == true; V == ok ->
{200, 0};
{{_, rescode}, _} ->
{200, 1};
{{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
{200, iolist_to_binary(Text1)};
{{_, restuple}, {_, Text2}} ->
{500, iolist_to_binary(Text2)};
{{_, {list, _}}, _V} ->
{_, L} = format_result(Result, ResultFormat),
{200, L};
{{_, {tuple, _}}, _V} ->
{_, T} = format_result(Result, ResultFormat),
{200, T};
_ ->
{200, {[format_result(Result, ResultFormat)]}}
end.
format_result(Atom, {Name, atom}) ->
@@ -421,21 +486,23 @@ format_result(404, {_Name, _}) ->
"not_found".
unauthorized_response() ->
{401, ?HEADER(?CT_XML),
#xmlel{name = <<"h1">>, attrs = [],
children = [{xmlcdata, <<"401 Unauthorized">>}]}}.
unauthorized_response(<<"401 Unauthorized">>).
unauthorized_response(Body) ->
json_response(401, jiffy:encode(Body)).
badrequest_response() ->
{400, ?HEADER(?CT_XML),
#xmlel{name = <<"h1">>, attrs = [],
children = [{xmlcdata, <<"400 Bad Request">>}]}}.
badrequest_response(<<"400 Bad Request">>).
badrequest_response(Body) ->
json_response(400, jiffy:encode(Body)).
json_response(Code, Body) when is_integer(Code) ->
{Code, ?HEADER(?CT_JSON), Body}.
log(Call, Args, {Addr, Port}) ->
AddrS = jlib:ip_to_list({Addr, Port}),
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]).
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
log(Call, Args, IP) ->
?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
mod_opt_type(admin_ip_access) ->
fun(Access) when is_atom(Access) -> Access end;
mod_opt_type(admin_ip_access) -> fun acl:access_rules_validator/1;
mod_opt_type(_) -> [admin_ip_access].
+28 -24
View File
@@ -178,7 +178,7 @@ mod_opt_type(host) ->
mod_opt_type(name) ->
fun iolist_to_binary/1;
mod_opt_type(access) ->
fun(A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(max_size) ->
fun(I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
@@ -235,7 +235,7 @@ init({ServerHost, Opts}) ->
fun iolist_to_binary/1,
<<"HTTP File Upload">>),
Access = gen_mod:get_opt(access, Opts,
fun(A) when is_atom(A) -> A end,
fun acl:access_rules_validator/1,
local),
MaxSize = gen_mod:get_opt(max_size, Opts,
fun(I) when is_integer(I), I > 0 -> I;
@@ -321,22 +321,24 @@ init({ServerHost, Opts}) ->
-> {reply, {ok, pos_integer(), binary(),
pos_integer() | undefined,
pos_integer() | undefined}, state()} |
{reply, {error, binary()}, state()} | {noreply, state()}.
{reply, {error, atom()}, state()} | {noreply, state()}.
handle_call({use_slot, Slot}, _From, #state{file_mode = FileMode,
dir_mode = DirMode,
get_url = GetPrefix,
thumbnail = Thumbnail,
docroot = DocRoot} = State) ->
handle_call({use_slot, Slot, Size}, _From, #state{file_mode = FileMode,
dir_mode = DirMode,
get_url = GetPrefix,
thumbnail = Thumbnail,
docroot = DocRoot} = State) ->
case get_slot(Slot, State) of
{ok, {Size, Timer}} ->
timer:cancel(Timer),
NewState = del_slot(Slot, State),
Path = str:join([DocRoot | Slot], <<$/>>),
{reply, {ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail},
{reply, {ok, Path, FileMode, DirMode, GetPrefix, Thumbnail},
NewState};
{ok, {_WrongSize, _Timer}} ->
{reply, {error, size_mismatch}, State};
error ->
{reply, {error, <<"Invalid slot">>}, State}
{reply, {error, invalid_slot}, State}
end;
handle_call(get_docroot, _From, #state{docroot = DocRoot} = State) ->
{reply, {ok, DocRoot}, State};
@@ -375,7 +377,7 @@ handle_info(Info, State) ->
?ERROR_MSG("Got unexpected info: ~p", [Info]),
{noreply, State}.
-spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
terminate(Reason, #state{server_host = ServerHost, host = Host}) ->
?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
@@ -406,9 +408,8 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP})
process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
data = Data} = Request) ->
{Proc, Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, {use_slot, Slot}) of
{ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail}
when byte_size(Data) == Size ->
case catch gen_server:call(Proc, {use_slot, Slot, byte_size(Data)}) of
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail} ->
?DEBUG("Storing file from ~s for ~s: ~s",
[?ADDR_TO_STR(IP), Host, Path]),
case store_file(Path, Data, FileMode, DirMode,
@@ -422,13 +423,13 @@ process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
[Path, ?ADDR_TO_STR(IP), Host, ?FORMAT(Error)]),
http_response(Host, 500)
end;
{ok, Size, Path, _FileMode, _DirMode, _GetPrefix, _Thumbnail} ->
?INFO_MSG("Rejecting file ~s from ~s for ~s: Size is ~B, not ~B",
[Path, ?ADDR_TO_STR(IP), Host, byte_size(Data), Size]),
{error, size_mismatch} ->
?INFO_MSG("Rejecting file from ~s for ~s: Unexpected size (~B)",
[?ADDR_TO_STR(IP), Host, byte_size(Data)]),
http_response(Host, 413);
{error, Error} ->
?INFO_MSG("Rejecting file from ~s for ~s: ~p",
[?ADDR_TO_STR(IP), Host, Error]),
{error, invalid_slot} ->
?INFO_MSG("Rejecting file from ~s for ~s: Invalid slot",
[?ADDR_TO_STR(IP), Host]),
http_response(Host, 403);
Error ->
?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p",
@@ -569,7 +570,8 @@ process_iq(From,
deny ->
?DEBUG("Denying HTTP upload slot request from ~s",
[jid:to_string(From)]),
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
Txt = <<"Denied by ACL">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}
end;
process_iq(_From, #iq{sub_el = SubEl} = IQ, _State) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
@@ -601,7 +603,8 @@ parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) ->
{error, ?ERRT_BAD_REQUEST(Lang, Text)}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
Text = <<"No or invalid XML namespace">>,
{error, ?ERRT_BAD_REQUEST(Lang, Text)}
end;
parse_request(_El, _Lang) -> {error, ?ERR_BAD_REQUEST}.
@@ -639,7 +642,7 @@ create_slot(#state{service_url = undefined,
end;
create_slot(#state{service_url = ServiceURL},
#jid{luser = U, lserver = S} = JID, File, Size, ContentType,
_Lang) ->
Lang) ->
Options = [{body_format, binary}, {full_result, false}],
HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}],
SizeStr = jlib:integer_to_binary(Size),
@@ -659,7 +662,8 @@ create_slot(#state{service_url = ServiceURL},
Lines ->
?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p",
[jid:to_string(JID), ServiceURL, Lines]),
{error, ?ERR_SERVICE_UNAVAILABLE}
Txt = <<"Failed to parse HTTP response">>,
{error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)}
end;
{ok, {402, _Body}} ->
?INFO_MSG("Got status code 402 for ~s from <~s>",
+5 -5
View File
@@ -99,9 +99,9 @@ stop(ServerHost) ->
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
mod_opt_type(access_soft_quota) ->
fun(A) when is_atom(A) -> A end;
fun acl:shaper_rules_validator/1;
mod_opt_type(access_hard_quota) ->
fun(A) when is_atom(A) -> A end;
fun acl:shaper_rules_validator/1;
mod_opt_type(max_days) ->
fun(I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
@@ -118,10 +118,10 @@ mod_opt_type(_) ->
init({ServerHost, Opts}) ->
process_flag(trap_exit, true),
AccessSoftQuota = gen_mod:get_opt(access_soft_quota, Opts,
fun(A) when is_atom(A) -> A end,
fun acl:shaper_rules_validator/1,
soft_upload_quota),
AccessHardQuota = gen_mod:get_opt(access_hard_quota, Opts,
fun(A) when is_atom(A) -> A end,
fun acl:shaper_rules_validator/1,
hard_upload_quota),
MaxDays = gen_mod:get_opt(max_days, Opts,
fun(I) when is_integer(I), I > 0 -> I;
@@ -239,7 +239,7 @@ handle_info(Info, State) ->
?ERROR_MSG("Got unexpected info: ~p", [Info]),
{noreply, State}.
-spec terminate(normal | shutdown | {shutdown, _} | _, _) -> ok.
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
?DEBUG("Stopping upload quota process for ~s: ~p", [ServerHost, Reason]),
+65 -172
View File
@@ -33,7 +33,8 @@
%% API
-export([start_link/2, start/2, stop/1, export/1, import/1,
import/3, closed_connection/3, get_connection_params/3]).
import/3, closed_connection/3, get_connection_params/3,
data_to_binary/2]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@@ -46,6 +47,8 @@
-include("adhoc.hrl").
-include("mod_irc.hrl").
-define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>).
-define(DEFAULT_IRC_PORT, 6667).
@@ -58,27 +61,19 @@
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
<<"utf-8">>, <<"utf-8+latin-1">>]).
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
{binary(), binary(), inet:port_number()} |
{binary(), binary()} |
{binary()}.
-record(irc_connection,
{jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
pid = self() :: pid()}).
-record(irc_custom,
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
binary()},
data = [] :: [{username, binary()} |
{connections_params, [conn_param()]}]}).
-record(state, {host = <<"">> :: binary(),
server_host = <<"">> :: binary(),
access = all :: atom()}).
-define(PROCNAME, ejabberd_mod_irc).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #irc_custom{}) -> ok | pass.
-callback get_data(binary(), binary(), {binary(), binary()}) ->
error | empty | irc_data().
-callback set_data(binary(), binary(), {binary(), binary()}, irc_data()) ->
{atomic, any()}.
%%====================================================================
%% API
%%====================================================================
@@ -119,16 +114,10 @@ init([Host, Opts]) ->
ejabberd:start_app(iconv),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"irc.@HOST@">>),
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(irc_custom,
[{disc_copies, [node()]},
{attributes, record_info(fields, irc_custom)}]),
update_table();
_ -> ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
Access = gen_mod:get_opt(access, Opts,
fun(A) when is_atom(A) -> A end,
fun acl:access_rules_validator/1,
all),
catch ets:new(irc_connection,
[named_table, public,
@@ -304,8 +293,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
Lang)}]},
Res = jlib:iq_to_xml(ResIQ);
_ ->
Res = jlib:make_error_reply(Packet,
?ERR_ITEM_NOT_FOUND)
Txt = <<"Node not found">>,
Res = jlib:make_error_reply(
Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt))
end,
ejabberd_router:route(To, From, Res);
#iq{xmlns = ?NS_REGISTER} = IQ ->
@@ -319,7 +309,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
attrs = [{<<"xmlns">>, XMLNS}],
children = iq_get_vcard(Lang)}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
#iq{type = set, xmlns = ?NS_COMMANDS, lang = _Lang,
#iq{type = set, xmlns = ?NS_COMMANDS, lang = Lang,
sub_el = SubEl} =
IQ ->
Request = adhoc:parse_request(IQ),
@@ -348,8 +338,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
true -> ok
end;
_ ->
Err = jlib:make_error_reply(Packet,
?ERR_ITEM_NOT_FOUND),
Txt = <<"Node not found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end;
#iq{} = _IQ ->
@@ -407,12 +398,14 @@ do_route1(Host, ServerHost, From, To, Packet) ->
ok
end;
_ ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
case str:tokens(ChanServ, <<"!">>) of
[<<_, _/binary>> = Nick, <<_, _/binary>> = Server] ->
case ets:lookup(irc_connection, {From, Server, Host}) of
[] ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
Txt = <<"IRC connection not found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err);
[R] ->
Pid = R#irc_connection.pid,
@@ -421,7 +414,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
ok
end;
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
Txt = <<"Failed to parse chanserv">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end
end
@@ -532,8 +527,9 @@ process_irc_register(ServerHost, Host, From, _To,
XDataEl = find_xdata_el(SubEl),
case XDataEl of
false ->
Txt1 = <<"No data form found">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]};
sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, Txt1)]};
#xmlel{attrs = Attrs} ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"cancel">> ->
@@ -546,8 +542,9 @@ process_irc_register(ServerHost, Host, From, _To,
XData = jlib:parse_xdata_submit(XDataEl),
case XData of
invalid ->
Txt2 = <<"Incorrect data form">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_BAD_REQUEST]};
sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt2)]};
_ ->
Node = str:tokens(fxml:get_tag_attr_s(<<"node">>,
SubEl),
@@ -567,7 +564,9 @@ process_irc_register(ServerHost, Host, From, _To,
end
end;
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
Txt3 = <<"Incorrect value of 'type' attribute">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt3)]}
end
end;
get ->
@@ -587,49 +586,16 @@ process_irc_register(ServerHost, Host, From, _To,
get_data(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
get_data(LServer, Host, From,
gen_mod:db_type(LServer, ?MODULE)).
get_data(_LServer, Host, From, mnesia) ->
#jid{luser = LUser, lserver = LServer} = From,
US = {LUser, LServer},
case catch mnesia:dirty_read({irc_custom, {US, Host}})
of
{'EXIT', _Reason} -> error;
[] -> 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(jid:to_string(jid:tolower(jid:remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
case catch ejabberd_odbc:sql_query(LServer,
[<<"select data from irc_custom where jid='">>,
SJID, <<"' and host='">>, SHost,
<<"';">>])
of
{selected, [<<"data">>], [[SData]]} ->
data_to_binary(From, ejabberd_odbc:decode_term(SData));
{'EXIT', _} -> error;
{selected, _, _} -> empty
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_data(LServer, Host, From).
get_form(ServerHost, Host, From, [], Lang) ->
#jid{user = User, server = Server} = From,
DefaultEncoding = get_default_encoding(Host),
Customs = case get_data(ServerHost, Host, From) of
error -> {error, ?ERR_INTERNAL_SERVER_ERROR};
error ->
Txt1 = <<"Database failure">>,
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt1)};
empty -> {User, []};
Data -> get_username_and_connection_params(Data)
end,
@@ -731,39 +697,10 @@ get_form(_ServerHost, _Host, _, _, _Lang) ->
set_data(ServerHost, Host, From, Data) ->
LServer = jid:nameprep(ServerHost),
set_data(LServer, Host, From, data_to_binary(From, Data),
gen_mod:db_type(LServer, ?MODULE)).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_data(LServer, Host, From, data_to_binary(From, Data)).
set_data(_LServer, Host, From, Data, mnesia) ->
{LUser, LServer, _} = jid:tolower(From),
US = {LUser, LServer},
F = fun () ->
mnesia:write(#irc_custom{us_host = {US, Host},
data = Data})
end,
mnesia:transaction(F);
set_data(LServer, Host, From, Data, riak) ->
{LUser, LServer, _} = 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(jid:to_string(jid:tolower(jid:remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
SData = ejabberd_odbc:encode_term(Data),
F = fun () ->
odbc_queries:update_t(<<"irc_custom">>,
[<<"jid">>, <<"host">>, <<"data">>],
[SJID, SHost, SData],
[<<"jid='">>, SJID, <<"' and host='">>,
SHost, <<"'">>]),
ok
end,
ejabberd_odbc:sql_transaction(LServer, F).
set_form(ServerHost, Host, From, [], _Lang, XData) ->
set_form(ServerHost, Host, From, [], Lang, XData) ->
case {lists:keysearch(<<"username">>, 1, XData),
lists:keysearch(<<"connections_params">>, 1, XData)}
of
@@ -781,11 +718,11 @@ set_form(ServerHost, Host, From, [], _Lang, XData) ->
{connections_params, ConnectionsParams}])
of
{atomic, _} -> {result, []};
_ -> {error, ?ERR_NOT_ACCEPTABLE}
_ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Database failure">>)}
end;
_ -> {error, ?ERR_NOT_ACCEPTABLE}
_ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Parse error">>)}
end;
_ -> {error, ?ERR_NOT_ACCEPTABLE}
_ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Scan error">>)}
end;
_ -> {error, ?ERR_NOT_ACCEPTABLE}
end;
@@ -909,7 +846,9 @@ adhoc_join(From, To,
elements = [Form]});
true ->
case jlib:parse_xdata_submit(XData) of
invalid -> {error, ?ERR_BAD_REQUEST};
invalid ->
Txt1 = <<"Incorrect data form">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt1)};
Fields ->
Channel = case lists:keysearch(<<"channel">>, 1, Fields)
of
@@ -998,7 +937,8 @@ adhoc_register(ServerHost, From, To,
true ->
case jlib:parse_xdata_submit(XData) of
invalid ->
Error = {error, ?ERR_BAD_REQUEST},
Txt1 = <<"Incorrect data form">>,
Error = {error, ?ERRT_BAD_REQUEST(Lang, Txt1)},
Username = false,
ConnectionsParams = false;
Fields ->
@@ -1021,7 +961,9 @@ adhoc_register(ServerHost, From, To,
{atomic, _} ->
adhoc:produce_response(Request,
#adhoc_response{status = completed});
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
_ ->
Txt2 = <<"Database failure">>,
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt2)}
end;
true ->
Form = generate_adhoc_register_form(Lang, Username,
@@ -1297,70 +1239,21 @@ conn_params_to_list(Params) ->
Port, binary_to_list(P)}
end, Params).
irc_custom_schema() ->
{record_info(fields, irc_custom), #irc_custom{}}.
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
update_table() ->
Fields = record_info(fields, irc_custom),
case mnesia:table_info(irc_custom, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
irc_custom, Fields, set,
fun(#irc_custom{us_host = {_, H}}) -> H end,
fun(#irc_custom{us_host = {{U, S}, H},
data = Data} = R) ->
JID = jid:make(U, S, <<"">>),
R#irc_custom{us_host = {{iolist_to_binary(U),
iolist_to_binary(S)},
iolist_to_binary(H)},
data = data_to_binary(JID, Data)}
end);
_ ->
?INFO_MSG("Recreating irc_custom table", []),
mnesia:transform_table(irc_custom, ignore, Fields)
end.
import(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
export(_Server) ->
[{irc_custom,
fun(Host, #irc_custom{us_host = {{U, S}, IRCHost},
data = Data}) ->
case str:suffix(Host, IRCHost) of
true ->
SJID = ejabberd_odbc:escape(
jid:to_string(
jid:make(U, S, <<"">>))),
SIRCHost = ejabberd_odbc:escape(IRCHost),
SData = ejabberd_odbc:encode_term(Data),
[[<<"delete from irc_custom where jid='">>, SJID,
<<"' and host='">>, SIRCHost, <<"';">>],
[<<"insert into irc_custom(jid, host, "
"data) values ('">>,
SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
<<"');">>]];
false ->
[]
end
end}].
import(_LServer) ->
[{<<"select jid, host, data from irc_custom;">>,
fun([SJID, IRCHost, SData]) ->
#jid{luser = U, lserver = S} = jid:from_string(SJID),
Data = ejabberd_odbc:decode_term(SData),
#irc_custom{us_host = {{U, S}, IRCHost},
data = Data}
end}].
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.
import(LServer, DBType, Data) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, Data).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(default_encoding) ->
fun iolist_to_binary/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
+69
View File
@@ -0,0 +1,69 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_irc_mnesia).
-behaviour(mod_irc).
%% API
-export([init/2, get_data/3, set_data/4, import/2]).
-include("jlib.hrl").
-include("mod_irc.hrl").
-include("logger.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
mnesia:create_table(irc_custom,
[{disc_copies, [node()]},
{attributes, record_info(fields, irc_custom)}]),
update_table().
get_data(_LServer, Host, From) ->
{U, S, _} = jid:tolower(From),
case catch mnesia:dirty_read({irc_custom, {{U, S}, Host}}) of
{'EXIT', _Reason} -> error;
[] -> empty;
[#irc_custom{data = Data}] -> Data
end.
set_data(_LServer, Host, From, Data) ->
{U, S, _} = jid:tolower(From),
F = fun () ->
mnesia:write(#irc_custom{us_host = {{U, S}, Host},
data = Data})
end,
mnesia:transaction(F).
import(_LServer, #irc_custom{} = R) ->
mnesia:dirty_write(R).
%%%===================================================================
%%% Internal functions
%%%===================================================================
update_table() ->
Fields = record_info(fields, irc_custom),
case mnesia:table_info(irc_custom, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
irc_custom, Fields, set,
fun(#irc_custom{us_host = {_, H}}) -> H end,
fun(#irc_custom{us_host = {{U, S}, H},
data = Data} = R) ->
JID = jid:make(U, S, <<"">>),
R#irc_custom{us_host = {{iolist_to_binary(U),
iolist_to_binary(S)},
iolist_to_binary(H)},
data = mod_irc:data_to_binary(JID, Data)}
end);
_ ->
?INFO_MSG("Recreating irc_custom table", []),
mnesia:transform_table(irc_custom, ignore, Fields)
end.
+49
View File
@@ -0,0 +1,49 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_irc_riak).
-behaviour(mod_irc).
%% API
-export([init/2, get_data/3, set_data/4, import/2]).
-include("jlib.hrl").
-include("mod_irc.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
ok.
get_data(_LServer, Host, From) ->
{U, S, _} = jid:tolower(From),
case ejabberd_riak:get(irc_custom, irc_custom_schema(), {{U, S}, Host}) of
{ok, #irc_custom{data = Data}} ->
Data;
{error, notfound} ->
empty;
_Err ->
error
end.
set_data(_LServer, Host, From, Data) ->
{U, S, _} = jid:tolower(From),
{atomic, ejabberd_riak:put(#irc_custom{us_host = {{U, S}, Host},
data = Data},
irc_custom_schema())}.
import(_LServer, #irc_custom{} = R) ->
ejabberd_riak:put(R, irc_custom_schema()).
%%%===================================================================
%%% Internal functions
%%%===================================================================
irc_custom_schema() ->
{record_info(fields, irc_custom), #irc_custom{}}.

Some files were not shown because too many files have changed in this diff Show More