Compare commits

..

137 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
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 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
117 changed files with 3426 additions and 1792 deletions
+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
+47 -45
View File
@@ -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:
+3 -3
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}}
@@ -212,7 +212,7 @@ iexdebug()
--erl `shell_escape \"$ERLANG_OPTS\"` \
--erl `shell_escape \"${ARGS[@]}\"` \
--erl `shell_escape_str \"$@\"`"
$EXEC_CMD "ERL_PATH=$\"$ERL\" $CMD"
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
}
# start interactive server
@@ -441,6 +441,6 @@ case "${ARGS[0]}" in
'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
@@ -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.
+5
View File
@@ -93,7 +93,12 @@
-type(subOptions() :: [mod_pubsub:subOption(),...]).
-type(pubOption() ::
{Option::binary(),
Values::[binary()]
}).
-type(pubOptions() :: [mod_pubsub:pubOption()]).
-type(affiliation() :: 'none'
| 'owner'
+3 -3
View File
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "16.04.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.4"},
"exrm": {:hex, :exrm, "1.0.3"},
"ezlib": {:hex, :ezlib, "1.0.1"},
"fast_tls": {:hex, :fast_tls, "1.0.3"},
"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.19.0"},
"sqlite3": {:hex, :sqlite3, "1.1.5"},
"stringprep": {:hex, :stringprep, "1.0.3"},
"stun": {:hex, :stun, "1.0.3"}}
%{"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]}]}}
+7 -6
View File
@@ -10,11 +10,11 @@
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.0.2"}}},
{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.3"}}},
{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.3"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.3"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.4"}}},
{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"}}},
@@ -68,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},
@@ -113,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"},
+1 -1
View File
@@ -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;
+312 -132
View File
@@ -29,12 +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_access/4, 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").
@@ -92,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) ->
@@ -188,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(
@@ -201,10 +200,21 @@ 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
@@ -225,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)};
@@ -255,130 +274,206 @@ normalize_spec(Spec) ->
end
end.
-spec match_access(global | binary(), access_name(),
jid() | ljid() | inet:ip_address(),
atom()) -> any().
match_access(_Host, all, _JID, _Default) ->
allow;
match_access(_Host, none, _JID, _Default) ->
deny;
match_access(_Host, {user, UserPattern}, JID, Default) ->
match_user_spec({user, UserPattern}, JID, Default);
match_access(Host, AccessRule, JID, _Default) ->
match_rule(Host, AccessRule, JID).
-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}).
match_user_spec(Spec, JID, Default) ->
case do_match_user_spec(Spec, jid:tolower(JID)) of
true -> Default;
false -> deny
end.
do_match_user_spec({user, {U, S}}, {User, Server, _Resource}) ->
U == User andalso S == Server.
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
@@ -450,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(
@@ -464,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
@@ -477,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
@@ -508,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].
+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, _, _, _, _}} =
+1 -5
View File
@@ -192,10 +192,6 @@ get_commands_spec() ->
module = ejabberd_piefxis, function = export_host,
args = [{dir, string}, {host, string}], result = {res, rescode}},
#ejabberd_commands{name = export_sql, tags = [mnesia, sql],
desc = "Export all tables as SQL queries to a file",
module = ejd2sql, function = export,
args = [{host, string}, {file, string}], result = {res, rescode}},
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
desc = "Export all tables as SQL queries to a file",
module = ejd2sql, function = delete,
@@ -228,7 +224,7 @@ get_commands_spec() ->
#ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL files",
module = ejd2sql, function = export,
args = [{host, string}, {directory, string}],
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 -7
View File
@@ -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,
+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,
+55 -45
View File
@@ -25,6 +25,8 @@
-module(ejabberd_auth_mnesia).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
@@ -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_sql:escape(LUser),
Pass = ejabberd_sql: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_sql:escape(LUser),
Pass = ejabberd_sql: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}].
+34 -26
View File
@@ -25,6 +25,8 @@
-module(ejabberd_auth_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
@@ -43,6 +45,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-define(SALT_LENGTH, 16).
@@ -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} ->
@@ -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) ->
sql_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,24 +455,24 @@ convert_to_scram(Server) ->
{error, {incorrect_server_name, Server}};
true ->
F = fun () ->
BatchSize = ?BATCH_SIZE,
case ejabberd_sql:sql_query_t(
[<<"select username, password from users where "
"iterationcount=0 limit ">>,
integer_to_binary(?BATCH_SIZE),
<<";">>]) of
{selected, [<<"username">>, <<"password">>], []} ->
?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_sql:escape(LUser),
fun({LUser, Password}) ->
Scram = password_to_scram(Password),
set_password_scram_t(
Username,
ejabberd_sql:escape(Scram#scram.storedkey),
ejabberd_sql:escape(Scram#scram.serverkey),
ejabberd_sql: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;
+111 -106
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
@@ -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
@@ -534,11 +540,11 @@ 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)),
{stop, normal, StateData};
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
send_header(StateData, ?MYNAME, StreamVersion, DefaultLang),
send_element(StateData, ?HOST_UNKNOWN_ERR),
{stop, normal, StateData}
end;
@@ -620,9 +626,9 @@ wait_for_auth({xmlstreamelement, El}, 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) ->
@@ -1053,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);
@@ -1095,8 +1105,10 @@ open_session(StateData) ->
R = StateData#state.resource,
JID = StateData#state.jid,
Lang = StateData#state.lang,
case acl:match_rule(StateData#state.server,
StateData#state.access, JID) of
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)]),
@@ -1143,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">>,
@@ -1277,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).
@@ -1563,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;
@@ -1756,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,
@@ -1782,8 +1799,20 @@ 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
@@ -1798,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).
@@ -2584,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).
@@ -2717,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;
@@ -2753,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)]),
@@ -2775,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;
@@ -2830,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 ->
@@ -2852,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) ->
@@ -2880,7 +2916,7 @@ handle_unacked_stanzas(StateData)
end;
false ->
fun(From, To, El, _Time) ->
Txt = <<"User session not found">>,
Txt = <<"User session terminated">>,
Err =
jlib:make_error_reply(
El,
@@ -2892,7 +2928,7 @@ handle_unacked_stanzas(StateData)
?DEBUG("Dropping presence stanza from ~s",
[jid:to_string(From)]);
(From, To, #xmlel{name = <<"iq">>} = El, _Time) ->
Txt = <<"User session not found">>,
Txt = <<"User session terminated">>,
Err = jlib:make_error_reply(
El, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err);
@@ -2956,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
@@ -2985,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,
@@ -3018,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
+40 -26
View File
@@ -230,6 +230,7 @@
execute_command/3,
execute_command/4,
execute_command/5,
execute_command/6,
opt_type/1,
get_commands_spec/0
]).
@@ -352,7 +353,7 @@ get_command_format(Name, Auth) ->
{[aterm()], rterm()}.
get_command_format(Name, Auth, Version) ->
Admin = is_admin(Name, Auth),
Admin = is_admin(Name, Auth, #{}),
#ejabberd_commands{args = Args,
result = Result,
policy = Policy} =
@@ -489,13 +490,16 @@ execute_command(AccessCommands, Auth, Name, Arguments) ->
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
Auth = case is_admin(Name, Auth1) of
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,
Command = get_command_definition(Name, Version),
AccessCommands = get_access_commands(AccessCommands1, Version),
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
ok -> execute_command2(Auth, Command, Arguments)
end.
@@ -573,9 +577,9 @@ get_tags_commands(Version) ->
%% 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, {_, _, _, _}} ->
@@ -590,7 +594,7 @@ 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,
@@ -600,7 +604,7 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
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,
@@ -637,31 +641,38 @@ 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.
@@ -737,29 +748,32 @@ get_commands(Version) ->
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].
+69 -23
View File
@@ -30,7 +30,7 @@
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,
@@ -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),
@@ -745,22 +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', _} ->
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;
_ ->
Res
Default
end.
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
@@ -813,6 +848,10 @@ 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
@@ -879,19 +918,26 @@ get_modules_with_options() ->
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"
+1 -1
View File
@@ -239,7 +239,7 @@ 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, admin, Version).
process2(Args, AccessCommands, noauth, Version).
+10 -1
View File
@@ -271,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].
+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
@@ -539,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
@@ -738,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].
+68 -31
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,
@@ -178,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)].
@@ -196,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 ->
@@ -211,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 ->
@@ -261,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{}].
@@ -279,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()).
@@ -288,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().
@@ -303,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}.
@@ -395,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) ->
@@ -409,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 ->
@@ -511,23 +547,23 @@ 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)
ejabberd_router:route(To, From, Err);
_ ->
route_message(From, To, Packet, normal)
end;
<<"iq">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
@@ -584,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 ->
@@ -602,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)
@@ -649,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,
@@ -666,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
@@ -724,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,
+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
+41 -39
View File
@@ -8,6 +8,8 @@
%%%-------------------------------------------------------------------
-module(ejabberd_sm_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_sm).
%% API
@@ -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_sql: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_sql:sql_query(
Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of
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_sql:escape(U),
Resource = ejabberd_sql:escape(R),
InfoS = ejabberd_sql: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_sql:escape(jlib:atom_to_binary(node(Pid))),
case sql_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 ->
@@ -72,14 +74,16 @@ delete_session(_LUser, LServer, _LResource, {Now, Pid}) ->
PidS = list_to_binary(erlang:pid_to_list(Pid)),
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_sql: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]),
@@ -94,9 +98,10 @@ get_sessions() ->
get_sessions(LServer) ->
case ejabberd_sql:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm">>]) of
{selected, _, Rows} ->
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_sql:escape(LUser),
case ejabberd_sql:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm where ">>,
<<"username='">>, Username, <<"'">>]) of
{selected, _, Rows} ->
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_sql:escape(LUser),
Resource = ejabberd_sql:escape(LResource),
case ejabberd_sql:sql_query(
LServer, [<<"select usec, pid, username, ">>,
<<"resource, priority, info from sm where ">>,
<<"username='">>, Username, <<"' and resource='">>,
Resource, <<"'">>]) of
{selected, _, Rows} ->
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,7 +160,7 @@ 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),
+46 -4
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>
%%%
%%%
@@ -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,
@@ -199,6 +202,7 @@ escape_like(S) when is_binary(S) ->
<< <<(escape_like(C))/binary>> || <<C>> <= S >>;
escape_like($%) -> <<"\\%">>;
escape_like($_) -> <<"\\_">>;
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) ->
@@ -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;
@@ -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,
+15 -9
View File
@@ -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) ->
@@ -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'
}]
@@ -462,7 +468,7 @@ check_upsert(ParseRes, Pos) ->
Set =
lists:filter(
fun({_Field, Match, _ST}) ->
not Match
Match /= key
end, ParseRes),
case Set of
[] ->
+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]);
+17 -2
View File
@@ -28,6 +28,7 @@
-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]).
@@ -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).
@@ -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 ->
@@ -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}} ->
+16 -8
View File
@@ -52,6 +52,7 @@
-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], <<", ">>),
@@ -301,7 +309,7 @@ validate_opts(Module, Opts) ->
db_type(Opts, Module) when is_list(Opts) ->
db_type(global, Opts, Module);
db_type(Host, Module) when is_atom(Module) ->
case Module:mod_opt_type(db_type) of
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);
@@ -314,7 +322,7 @@ db_type(Host, Module) when is_atom(Module) ->
-spec db_type(binary(), opts(), module()) -> db_type().
db_type(Host, Opts, Module) ->
case Module:mod_opt_type(db_type) of
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);
+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
+67 -8
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,14 +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">>, children = Els}) ->
Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els,
fxml:get_attr_s(<<"xmlns">>, Attrs) /= ?NS_CHATSTATES,
Name /= <<"thread">>],
Stripped == [];
is_standalone_chat_state(_El) -> false.
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.
-spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp())
-> xmlel().
@@ -890,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).
+28 -15
View File
@@ -863,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()}),
@@ -891,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) ->
@@ -1154,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) ->
+2 -2
View File
@@ -903,7 +903,7 @@ 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).
%%-------------------------------------------------------------------------
@@ -920,6 +920,6 @@ import(LServer, DBType, LA) ->
Mod:import(LServer, LA).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
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].
+30 -35
View File
@@ -9,6 +9,8 @@
-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,
@@ -16,6 +18,7 @@
-include("jlib.hrl").
-include("mod_announce.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
@@ -27,37 +30,35 @@ set_motd_users(LServer, USRs) ->
F = fun() ->
lists:foreach(
fun({U, _S, _R}) ->
Username = ejabberd_sql:escape(U),
sql_queries:update_t(
<<"motd">>,
[<<"username">>, <<"xml">>],
[Username, <<"">>],
[<<"username='">>, Username, <<"'">>])
?SQL_UPSERT_T(
"motd",
["!username=%(U)s",
"xml=''"])
end, USRs)
end,
ejabberd_sql:sql_transaction(LServer, F).
set_motd(LServer, Packet) ->
XML = ejabberd_sql:escape(fxml:element_to_binary(Packet)),
XML = fxml:element_to_binary(Packet),
F = fun() ->
sql_queries:update_t(
<<"motd">>,
[<<"username">>, <<"xml">>],
[<<"">>, XML],
[<<"username=''">>])
?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([<<"delete from motd;">>])
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, [<<"select xml from motd where username='';">>]) of
{selected, [<<"xml">>], [[XML]]} ->
LServer,
?SQL("select @(xml)s from motd where username=''")) of
{selected, [{XML}]} ->
case fxml_stream:parse_element(XML) of
{error, _} ->
error;
@@ -69,46 +70,40 @@ get_motd(LServer) ->
end.
is_motd_user(LUser, LServer) ->
Username = ejabberd_sql:escape(LUser),
case catch ejabberd_sql:sql_query(
LServer,
[<<"select username from motd "
"where username='">>, Username, <<"';">>]) of
{selected, [<<"username">>], [_|_]} ->
LServer,
?SQL("select @(username)s from motd"
" where username=%(LUser)s")) of
{selected, [_|_]} ->
true;
_ ->
false
end.
set_motd_user(LUser, LServer) ->
Username = ejabberd_sql:escape(LUser),
F = fun() ->
sql_queries:update_t(
<<"motd">>,
[<<"username">>, <<"xml">>],
[Username, <<"">>],
[<<"username='">>, Username, <<"'">>])
end,
?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 ->
[[<<"delete from motd where username='';">>],
[<<"insert into motd(username, xml) values ('', '">>,
ejabberd_sql:escape(fxml:element_to_binary(El)),
<<"');">>]];
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 /= <<"">> ->
Username = ejabberd_sql:escape(LUser),
[[<<"delete from motd where username='">>, Username, <<"';">>],
[<<"insert into motd(username, xml) values ('">>,
Username, <<"', '');">>]];
[?SQL("delete from motd where username=%(LUser)s;"),
?SQL("insert into motd(username, xml) values (%(LUser)s, '');")];
(_Host, _R) ->
[]
end}].
+6 -1
View File
@@ -25,7 +25,12 @@ process_blocklist_block(LUser, LServer, Filter) ->
Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
{selected, []} ->
Name = <<"Blocked contacts">>,
mod_privacy_sql:sql_add_privacy_list(LUser, Name),
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
+19 -21
View File
@@ -9,10 +9,13 @@
-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
@@ -21,21 +24,19 @@ init(_Host, _Opts) ->
ok.
caps_read(LServer, {Node, SubNode}) ->
SNode = ejabberd_sql:escape(Node),
SSubNode = ejabberd_sql:escape(SubNode),
case ejabberd_sql: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
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) ->
@@ -56,16 +57,13 @@ export(_Server) ->
%%% Internal functions
%%%===================================================================
sql_write_features_t({Node, SubNode}, Features) ->
SNode = ejabberd_sql:escape(Node),
SSubNode = ejabberd_sql: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_sql:escape(F), <<"');">>] || F <- NewFeatures]].
[?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]].
+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.
+6 -4
View File
@@ -1917,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, []};
+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) ->
+17 -17
View File
@@ -157,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;
@@ -173,7 +175,7 @@ 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
@@ -188,9 +190,8 @@ check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
true -> {allowed, Call, admin};
_ -> unauthorized_response()
end;
E ->
?DEBUG("Unauthorized: ~p", [E]),
unauthorized_response()
_E ->
{allowed, Call, noauth}
end;
check_permissions2(_Request, _Call, _Policy) ->
unauthorized_response().
@@ -209,7 +210,7 @@ oauth_check_token(Scope, Token) ->
process(_, #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
badrequest_response(<<"Missing POST data">>);
process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} = Req) ->
Version = get_api_version(Req),
try
Args = case jiffy:decode(Data) of
@@ -217,10 +218,10 @@ process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
{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, Version),
{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 ->
@@ -243,7 +244,7 @@ process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
log(Call, Args, IP),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
{Code, Result} = handle(Cmd, Auth, Args, Version),
{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 ->
@@ -279,7 +280,7 @@ get_api_version([]) ->
%% ----------------
% generic ejabberd command handler
handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
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],
@@ -296,7 +297,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
[{Key, undefined}|Acc]
end, [], ArgsSpec),
try
handle2(Call, Auth, match(Args2, Spec), Version)
handle2(Call, Auth, match(Args2, Spec), Version, IP)
catch throw:not_found ->
{404, <<"not_found">>};
throw:{not_found, Why} when is_atom(Why) ->
@@ -333,10 +334,10 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
{400, <<"Error">>}
end.
handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
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),
ejabberd_command(Auth, Call, ArgsFormatted, Version).
ejabberd_command(Auth, Call, ArgsFormatted, Version, IP).
get_elem_delete(A, L) ->
case proplists:get_all_values(A, L) of
@@ -416,12 +417,12 @@ process_unicode_codepoints(Str) ->
match(Args, Spec) ->
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
ejabberd_command(Auth, Cmd, Args, Version) ->
ejabberd_command(Auth, Cmd, Args, Version, IP) ->
Access = case Auth of
admin -> [];
_ -> undefined
end,
case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version) of
case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version, #{ip => IP}) of
{error, Error} ->
throw(Error);
Res ->
@@ -503,6 +504,5 @@ log(Call, Args, {Addr, 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].
+20 -19
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};
@@ -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",
+4 -4
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;
+2 -2
View File
@@ -117,7 +117,7 @@ init([Host, Opts]) ->
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,
@@ -1252,7 +1252,7 @@ import(LServer, DBType, Data) ->
Mod:import(LServer, Data).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
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;
+24 -31
View File
@@ -8,6 +8,8 @@
%%%-------------------------------------------------------------------
-module(mod_irc_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(mod_irc).
%% API
@@ -15,6 +17,7 @@
-include("jlib.hrl").
-include("mod_irc.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
@@ -23,31 +26,26 @@ init(_Host, _Opts) ->
ok.
get_data(LServer, Host, From) ->
LJID = jid:tolower(jid:remove_resource(From)),
SJID = ejabberd_sql:escape(jid:to_string(LJID)),
SHost = ejabberd_sql:escape(Host),
SJID = jid:to_string(jid:tolower(jid:remove_resource(From))),
case catch ejabberd_sql:sql_query(
LServer,
[<<"select data from irc_custom where jid='">>,
SJID, <<"' and host='">>, SHost,
<<"';">>]) of
{selected, [<<"data">>], [[SData]]} ->
mod_irc:data_to_binary(From, ejabberd_sql:decode_term(SData));
{'EXIT', _} -> error;
{selected, _, _} -> empty
LServer,
?SQL("select @(data)s from irc_custom"
" where jid=%(SJID)s and host=%(Host)s")) of
{selected, [{SData}]} ->
mod_irc:data_to_binary(From, ejabberd_sql:decode_term(SData));
{'EXIT', _} -> error;
{selected, _} -> empty
end.
set_data(LServer, Host, From, Data) ->
LJID = jid:tolower(jid:remove_resource(From)),
SJID = ejabberd_sql:escape(jid:to_string(LJID)),
SHost = ejabberd_sql:escape(Host),
SData = ejabberd_sql:encode_term(Data),
SJID = jid:to_string(jid:tolower(jid:remove_resource(From))),
SData = jlib:term_to_expr(Data),
F = fun () ->
sql_queries:update_t(<<"irc_custom">>,
[<<"jid">>, <<"host">>, <<"data">>],
[SJID, SHost, SData],
[<<"jid='">>, SJID, <<"' and host='">>,
SHost, <<"'">>]),
?SQL_UPSERT_T(
"irc_custom",
["!jid=%(SJID)s",
"!host=%(Host)s",
"data=%(SData)s"]),
ok
end,
ejabberd_sql:sql_transaction(LServer, F).
@@ -58,17 +56,12 @@ export(_Server) ->
data = Data}) ->
case str:suffix(Host, IRCHost) of
true ->
SJID = ejabberd_sql:escape(
jid:to_string(
jid:make(U, S, <<"">>))),
SIRCHost = ejabberd_sql:escape(IRCHost),
SData = ejabberd_sql:encode_term(Data),
[[<<"delete from irc_custom where jid='">>, SJID,
<<"' and host='">>, SIRCHost, <<"';">>],
[<<"insert into irc_custom(jid, host, "
"data) values ('">>,
SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
<<"');">>]];
SJID = jid:to_string(jid:make(U, S, <<"">>)),
SData = jlib:term_to_expr(Data),
[?SQL("delete from irc_custom"
" where jid=%(SJID)s and host=%(IRCHost)s;"),
?SQL("insert into irc_custom(jid, host, data)"
" values (%(SJID)s, %(IRCHost)s, %(SData)s);")];
false ->
[]
end
+12 -1
View File
@@ -37,7 +37,7 @@
process_sm_iq/3, on_presence_update/4, import/1,
import/3, store_last_info/4, get_last_info/2,
remove_user/2, transform_options/1, mod_opt_type/1,
opt_type/1]).
opt_type/1, register_user/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -64,12 +64,16 @@ start(Host, Opts) ->
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_LAST, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(register_user, Host, ?MODULE,
register_user, 50),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE,
on_presence_update, 50).
stop(Host) ->
ejabberd_hooks:delete(register_user, Host, ?MODULE,
register_user, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:delete(unset_presence_hook, Host,
@@ -198,6 +202,13 @@ get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) ->
children = []}]}
end.
register_user(User, Server) ->
on_presence_update(
User,
Server,
<<"RegisterResource">>,
<<"Registered but didn't login">>).
on_presence_update(User, Server, _Resource, Status) ->
TimeStamp = p1_time_compat:system_time(seconds),
store_last_info(User, Server, TimeStamp, Status).
+6 -9
View File
@@ -9,12 +9,15 @@
-module(mod_last_sql).
-behaviour(mod_last).
-compile([{parse_transform, ejabberd_sql_pt}]).
%% API
-export([init/2, get_last/2, store_last_info/4, remove_user/2,
import/1, import/2, export/1]).
-include("mod_last.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
@@ -48,15 +51,9 @@ export(_Server) ->
fun(Host, #last_activity{us = {LUser, LServer},
timestamp = TimeStamp, status = Status})
when LServer == Host ->
Username = ejabberd_sql:escape(LUser),
Seconds =
ejabberd_sql:escape(jlib:integer_to_binary(TimeStamp)),
State = ejabberd_sql:escape(Status),
[[<<"delete from last where username='">>, Username, <<"';">>],
[<<"insert into last(username, seconds, "
"state) values ('">>,
Username, <<"', '">>, Seconds, <<"', '">>, State,
<<"');">>]];
[?SQL("delete from last where username=%(LUser)s;"),
?SQL("insert into last(username, seconds, state)"
" values (%(LUser)s, %(TimeStamp)d, %(Status)s);")];
(_Host, _R) ->
[]
end}].
+39 -12
View File
@@ -25,7 +25,7 @@
%%%-------------------------------------------------------------------
-module(mod_mam).
-protocol({xep, 313, '0.4'}).
-protocol({xep, 313, '0.5.1'}).
-protocol({xep, 334, '0.2'}).
-behaviour(gen_mod).
@@ -33,7 +33,7 @@
%% API
-export([start/2, stop/1]).
-export([user_send_packet/4, user_receive_packet/5,
-export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5,
process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
@@ -89,9 +89,11 @@ start(Host, Opts) ->
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MAM_1, ?MODULE, process_iq_v0_3, IQDisc),
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
user_receive_packet, 500),
user_receive_packet, 88),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
user_send_packet, 500),
user_send_packet, 88),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
user_send_packet_strip_tag, 500),
ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
muc_filter_message, 50),
ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
@@ -128,9 +130,11 @@ init_cache(Opts) ->
stop(Host) ->
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
user_send_packet, 500),
user_send_packet, 88),
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
user_receive_packet, 500),
user_receive_packet, 88),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
user_send_packet_strip_tag, 500),
ejabberd_hooks:delete(muc_filter_message, Host, ?MODULE,
muc_filter_message, 50),
ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE,
@@ -205,13 +209,30 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
case should_archive(Pkt, LServer) of
true ->
NewPkt = strip_my_archived_tag(Pkt, LServer),
store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
LUser, LServer, Peer, send),
NewPkt;
case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
LUser, LServer, Peer, send) of
{ok, ID} ->
Archived = #xmlel{name = <<"archived">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_MAM_TMP},
{<<"id">>, ID}]},
StanzaID = #xmlel{name = <<"stanza-id">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_SID_0},
{<<"id">>, ID}]},
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
NewPkt#xmlel{children = NewEls};
_ ->
NewPkt
end;
false ->
Pkt
end.
user_send_packet_strip_tag(Pkt, _C2SState, JID, _Peer) ->
LServer = JID#jid.lserver,
strip_my_archived_tag(Pkt, LServer).
muc_filter_message(Pkt, #state{config = Config} = MUCState,
RoomJID, From, FromNick) ->
if Config#config.mam ->
@@ -316,7 +337,12 @@ message_is_archived(false, C2SState, Peer,
(never) -> never
end, never) of
if_enabled ->
get_prefs(LUser, LServer);
case get_prefs(LUser, LServer) of
#archive_prefs{} = P ->
{ok, P};
error ->
error
end;
on_request ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
cache_tab:lookup(archive_prefs, {LUser, LServer},
@@ -812,9 +838,10 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
_ ->
{Msgs, true, L}
end;
select(LServer, From, From, Start, End, With, RSM, MsgType) ->
select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, From, From, Start, End, With, RSM, MsgType).
Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM,
MsgType).
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
+63 -26
View File
@@ -16,6 +16,7 @@
-include_lib("stdlib/include/ms_transform.hrl").
-include("jlib.hrl").
-include("logger.hrl").
-include("mod_mam.hrl").
-define(BIN_GREATER_THAN(A, B),
@@ -25,6 +26,8 @@
((A < B andalso byte_size(A) == byte_size(B))
orelse byte_size(A) < byte_size(B))).
-define(TABLE_SIZE_LIMIT, 2000000000). % A bit less than 2 GiB.
%%%===================================================================
%%% API
%%%===================================================================
@@ -49,37 +52,71 @@ remove_room(_LServer, LName, LHost) ->
remove_user(LName, LHost).
delete_old_messages(global, TimeStamp, Type) ->
MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS,
type = MsgType} = Msg)
when MsgTS < TimeStamp,
MsgType == Type orelse Type == all ->
Msg
end),
OldMsgs = mnesia:dirty_select(archive_msg, MS),
lists:foreach(fun(Rec) ->
ok = mnesia:dirty_delete_object(Rec)
end, OldMsgs).
mnesia:change_table_copy_type(archive_msg, node(), disc_copies),
Result = delete_old_user_messages(mnesia:dirty_first(archive_msg), TimeStamp, Type),
mnesia:change_table_copy_type(archive_msg, node(), disc_only_copies),
Result.
delete_old_user_messages('$end_of_table', _TimeStamp, _Type) ->
ok;
delete_old_user_messages(User, TimeStamp, Type) ->
F = fun() ->
Msgs = mnesia:read(archive_msg, User),
Keep = lists:filter(
fun(#archive_msg{timestamp = MsgTS,
type = MsgType}) ->
MsgTS >= TimeStamp orelse (Type /= all andalso
Type /= MsgType)
end, Msgs),
if length(Keep) < length(Msgs) ->
mnesia:delete({archive_msg, User}),
lists:foreach(fun(Msg) -> mnesia:write(Msg) end, Keep);
true ->
ok
end
end,
case mnesia:transaction(F) of
{atomic, ok} ->
delete_old_user_messages(mnesia:dirty_next(archive_msg, User),
TimeStamp, Type);
{aborted, Err} ->
?ERROR_MSG("Cannot delete old MAM messages: ~s", [Err]),
Err
end.
extended_fields() ->
[].
store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir) ->
LPeer = {PUser, PServer, _} = jid:tolower(Peer),
TS = p1_time_compat:timestamp(),
ID = jlib:integer_to_binary(now_to_usec(TS)),
case mnesia:dirty_write(
#archive_msg{us = {LUser, LServer},
id = ID,
timestamp = TS,
peer = LPeer,
bare_peer = {PUser, PServer, <<>>},
type = Type,
nick = Nick,
packet = Pkt}) of
ok ->
{ok, ID};
Err ->
Err
case {mnesia:table_info(archive_msg, disc_only_copies),
mnesia:table_info(archive_msg, memory)} of
{[_|_], TableSize} when TableSize > ?TABLE_SIZE_LIMIT ->
?ERROR_MSG("MAM archives too large, won't store message for ~s@~s",
[LUser, LServer]),
{error, overflow};
_ ->
LPeer = {PUser, PServer, _} = jid:tolower(Peer),
TS = p1_time_compat:timestamp(),
ID = jlib:integer_to_binary(now_to_usec(TS)),
F = fun() ->
mnesia:write(
#archive_msg{us = {LUser, LServer},
id = ID,
timestamp = TS,
peer = LPeer,
bare_peer = {PUser, PServer, <<>>},
type = Type,
nick = Nick,
packet = Pkt})
end,
case mnesia:transaction(F) of
{atomic, ok} ->
{ok, ID};
{aborted, Err} ->
?ERROR_MSG("Cannot add message to MAM archive of ~s@~s: ~s",
[LUser, LServer, Err]),
Err
end
end.
write_prefs(_LUser, _LServer, Prefs, _ServerHost) ->
+40 -30
View File
@@ -8,6 +8,8 @@
%%%-------------------------------------------------------------------
-module(mod_mam_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(mod_mam).
%% API
@@ -18,6 +20,7 @@
-include("jlib.hrl").
-include("mod_mam.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
@@ -26,13 +29,12 @@ init(_Host, _Opts) ->
ok.
remove_user(LUser, LServer) ->
SUser = ejabberd_sql:escape(LUser),
ejabberd_sql:sql_query(
LServer,
[<<"delete from archive where username='">>, SUser, <<"';">>]),
?SQL("delete from archive where username=%(LUser)s")),
ejabberd_sql:sql_query(
LServer,
[<<"delete from archive_prefs where username='">>, SUser, <<"';">>]).
?SQL("delete from archive_prefs where username=%(LUser)s")).
remove_room(LServer, LName, LHost) ->
LUser = jid:to_string({LName, LHost, <<>>}),
@@ -55,7 +57,7 @@ extended_fields() ->
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
TSinteger = p1_time_compat:system_time(micro_seconds),
ID = TS = jlib:integer_to_binary(TSinteger),
ID = jlib:integer_to_binary(TSinteger),
SUser = case Type of
chat -> LUser;
groupchat -> jid:to_string({LUser, LHost, <<>>})
@@ -67,18 +69,19 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
jid:tolower(Peer)),
XML = fxml:element_to_binary(Pkt),
Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
SType = jlib:atom_to_binary(Type),
case ejabberd_sql:sql_query(
LServer,
[<<"insert into archive (username, timestamp, "
"peer, bare_peer, xml, txt, kind, nick) values (">>,
<<"'">>, ejabberd_sql:escape(SUser), <<"', ">>,
<<"'">>, TS, <<"', ">>,
<<"'">>, ejabberd_sql:escape(LPeer), <<"', ">>,
<<"'">>, ejabberd_sql:escape(BarePeer), <<"', ">>,
<<"'">>, ejabberd_sql:escape(XML), <<"', ">>,
<<"'">>, ejabberd_sql:escape(Body), <<"', ">>,
<<"'">>, jlib:atom_to_binary(Type), <<"', ">>,
<<"'">>, ejabberd_sql:escape(Nick), <<"');">>]) of
LServer,
?SQL("insert into archive (username, timestamp,"
" peer, bare_peer, xml, txt, kind, nick) values ("
"%(SUser)s, "
"%(TSinteger)d, "
"%(LPeer)s, "
"%(BarePeer)s, "
"%(XML)s, "
"%(Body)s, "
"%(SType)s, "
"%(Nick)s)")) of
{updated, _} ->
{ok, ID};
Err ->
@@ -89,14 +92,16 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default,
never = Never,
always = Always},
ServerHost) ->
SUser = ejabberd_sql:escape(LUser),
SDefault = erlang:atom_to_binary(Default, utf8),
SAlways = ejabberd_sql:encode_term(Always),
SNever = ejabberd_sql:encode_term(Never),
case update(ServerHost, <<"archive_prefs">>,
[<<"username">>, <<"def">>, <<"always">>, <<"never">>],
[SUser, SDefault, SAlways, SNever],
[<<"username='">>, SUser, <<"'">>]) of
SAlways = jlib:term_to_expr(Always),
SNever = jlib:term_to_expr(Never),
case ?SQL_UPSERT(
ServerHost,
"archive_prefs",
["!username=%(LUser)s",
"def=%(SDefault)s",
"always=%(SAlways)s",
"never=%(SNever)s"]) of
{updated, _} ->
ok;
Err ->
@@ -106,10 +111,9 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default,
get_prefs(LUser, LServer) ->
case ejabberd_sql:sql_query(
LServer,
[<<"select def, always, never from archive_prefs ">>,
<<"where username='">>,
ejabberd_sql:escape(LUser), <<"';">>]) of
{selected, _, [[SDefault, SAlways, SNever]]} ->
?SQL("select @(def)s, @(always)s, @(never)s from archive_prefs"
" where username=%(LUser)s")) of
{selected, [{SDefault, SAlways, SNever}]} ->
Default = erlang:binary_to_existing_atom(SDefault, utf8),
Always = ejabberd_sql:decode_term(SAlways),
Never = ejabberd_sql:decode_term(SNever),
@@ -208,6 +212,12 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
ODBCType = ejabberd_config:get_option(
{sql_type, LServer},
ejabberd_sql:opt_type(sql_type)),
Escape =
case ODBCType of
mssql -> fun ejabberd_sql:standard_escape/1;
sqlite -> fun ejabberd_sql:standard_escape/1;
_ -> fun ejabberd_sql:escape/1
end,
LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql ->
[<<" limit ">>, jlib:integer_to_binary(Max+1)];
true ->
@@ -223,14 +233,14 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
[];
{text, Txt} ->
[<<" and match (txt) against ('">>,
ejabberd_sql:escape(Txt), <<"')">>];
Escape(Txt), <<"')">>];
{_, _, <<>>} ->
[<<" and bare_peer='">>,
ejabberd_sql:escape(jid:to_string(With)),
Escape(jid:to_string(With)),
<<"'">>];
{_, _, _} ->
[<<" and peer='">>,
ejabberd_sql:escape(jid:to_string(With)),
Escape(jid:to_string(With)),
<<"'">>];
none ->
[]
@@ -262,7 +272,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
_ ->
[]
end,
SUser = ejabberd_sql:escape(User),
SUser = Escape(User),
Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
" FROM archive WHERE username='">>,
+4 -1
View File
@@ -39,7 +39,7 @@
s2s_send_packet, s2s_receive_packet,
remove_user, register_user]).
-export([start/2, stop/1, send_metrics/4, opt_type/1]).
-export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1]).
-export([offline_message_hook/3,
sm_register_connection_hook/3, sm_remove_connection_hook/3,
@@ -126,3 +126,6 @@ send_metrics(Host, Probe, Peer, Port) ->
opt_type(_) ->
[].
mod_opt_type(_) ->
[].
+8 -8
View File
@@ -193,14 +193,14 @@ init([Host, Opts]) ->
clean_table_from_bad_node(node(), MyHost),
mnesia:subscribe(system),
Access = gen_mod:get_opt(access, Opts,
fun(A) when is_atom(A) -> A end, all),
fun acl:access_rules_validator/1, all),
AccessCreate = gen_mod:get_opt(access_create, Opts,
fun(A) when is_atom(A) -> A end, all),
fun acl:access_rules_validator/1, all),
AccessAdmin = gen_mod:get_opt(access_admin, Opts,
fun(A) when is_atom(A) -> A end,
fun acl:access_rules_validator/1,
none),
AccessPersistent = gen_mod:get_opt(access_persistent, Opts,
fun(A) when is_atom(A) -> A end,
fun acl:access_rules_validator/1,
all),
HistorySize = gen_mod:get_opt(history_size, Opts,
fun(I) when is_integer(I), I>=0 -> I end,
@@ -925,13 +925,13 @@ import(LServer, DBType, Data) ->
Mod:import(LServer, Data).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(access_admin) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(access_create) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(access_persistent) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(default_room_options) ->
fun (L) when is_list(L) -> L end;
+1 -1
View File
@@ -141,7 +141,7 @@ init([Host, Opts]) ->
fun iolist_to_binary/1,
false),
AccessLog = gen_mod:get_opt(access_log, Opts,
fun(A) when is_atom(A) -> A end,
fun acl:access_rules_validator/1,
muc_admin),
Timezone = gen_mod:get_opt(timezone, Opts,
fun(local) -> local;
+64 -10
View File
@@ -266,6 +266,8 @@ normal_state({route, From, <<"">>,
none ->
NSD = set_affiliation(IJID, member,
StateData),
send_affiliation(IJID, member,
StateData),
case
(NSD#state.config)#config.persistent
of
@@ -1801,8 +1803,8 @@ add_new_user(From, Nick,
10),
Collision = nick_collision(From, Nick, StateData),
case {(ServiceAffiliation == owner orelse
(Affiliation == admin orelse Affiliation == owner)
andalso NUsers < MaxAdminUsers
((Affiliation == admin orelse Affiliation == owner)
andalso NUsers < MaxAdminUsers)
orelse NUsers < MaxUsers)
andalso NConferences < MaxConferences,
Collision,
@@ -2440,6 +2442,51 @@ send_nick_changing(JID, OldNick, StateData,
end,
(?DICT):to_list(StateData#state.users)).
maybe_send_affiliation(JID, Affiliation, StateData) ->
LJID = jid:tolower(JID),
IsOccupant = case LJID of
{LUser, LServer, <<"">>} ->
not (?DICT):is_empty(
(?DICT):filter(fun({U, S, _}, _) ->
U == LUser andalso
S == LServer
end, StateData#state.users));
{_LUser, _LServer, _LResource} ->
(?DICT):is_key(LJID, StateData#state.users)
end,
case IsOccupant of
true ->
ok; % The new affiliation is published via presence.
false ->
send_affiliation(LJID, Affiliation, StateData)
end.
send_affiliation(LJID, Affiliation, StateData) ->
ItemAttrs = [{<<"jid">>, jid:to_string(LJID)},
{<<"affiliation">>, affiliation_to_list(Affiliation)},
{<<"role">>, <<"none">>}],
Message = #xmlel{name = <<"message">>,
attrs = [{<<"id">>, randoms:get_string()}],
children =
[#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
children =
[#xmlel{name = <<"item">>,
attrs = ItemAttrs}]}]},
Recipients = case (StateData#state.config)#config.anonymous of
true ->
(?DICT):filter(fun(_, #user{role = moderator}) ->
true;
(_, _) ->
false
end, StateData#state.users);
false ->
StateData#state.users
end,
send_multiple(StateData#state.jid,
StateData#state.server_host,
Recipients, Message).
status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
Status = case IsInitialPresence of
true ->
@@ -2722,11 +2769,13 @@ process_item_change(E, SD, UJID) ->
<<"321">>,
none,
SD),
maybe_send_affiliation(JID, none, SD),
SD1 = set_affiliation(JID, none, SD),
set_role(JID, none, SD1);
_ ->
SD1 = set_affiliation(JID, none, SD),
send_update_presence(JID, SD1, SD),
maybe_send_affiliation(JID, none, SD1),
SD1
end;
{JID, affiliation, outcast, Reason} ->
@@ -2736,6 +2785,7 @@ process_item_change(E, SD, UJID) ->
<<"301">>,
outcast,
SD),
maybe_send_affiliation(JID, outcast, SD),
set_affiliation(JID,
outcast,
set_role(JID, none, SD),
@@ -2745,11 +2795,13 @@ process_item_change(E, SD, UJID) ->
SD1 = set_affiliation(JID, A, SD, Reason),
SD2 = set_role(JID, moderator, SD1),
send_update_presence(JID, Reason, SD2, SD),
maybe_send_affiliation(JID, A, SD2),
SD2;
{JID, affiliation, member, Reason} ->
SD1 = set_affiliation(JID, member, SD, Reason),
SD2 = set_role(JID, participant, SD1),
send_update_presence(JID, Reason, SD2, SD),
maybe_send_affiliation(JID, member, SD2),
SD2;
{JID, role, Role, Reason} ->
SD1 = set_role(JID, Role, SD),
@@ -2759,6 +2811,7 @@ process_item_change(E, SD, UJID) ->
{JID, affiliation, A, _Reason} ->
SD1 = set_affiliation(JID, A, SD),
send_update_presence(JID, SD1, SD),
maybe_send_affiliation(JID, A, SD1),
SD1
end
of
@@ -3092,14 +3145,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
StateData#state.users),
SAffiliation = affiliation_to_list(Affiliation),
BannedJIDString = jid:to_string(RealJID),
case MJID /= <<"">> of
true ->
{ok, #user{nick = ActorNick}} =
(?DICT):find(jid:tolower(MJID),
StateData#state.users);
false ->
ActorNick = <<"">>
end,
ActorNick = get_actor_nick(MJID, StateData),
lists:foreach(fun ({_LJID, Info}) ->
JidAttrList = case Info#user.role == moderator orelse
(StateData#state.config)#config.anonymous
@@ -3154,6 +3200,14 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
end,
(?DICT):to_list(StateData#state.users)).
get_actor_nick(<<"">>, _StateData) ->
<<"">>;
get_actor_nick(MJID, StateData) ->
case (?DICT):find(jid:tolower(MJID), StateData#state.users) of
{ok, #user{nick = ActorNick}} -> ActorNick;
_ -> <<"">>
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Owner stuff
+58 -81
View File
@@ -8,6 +8,8 @@
%%%-------------------------------------------------------------------
-module(mod_muc_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(mod_muc).
%% API
@@ -18,6 +20,7 @@
-include("jlib.hrl").
-include("mod_muc.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
@@ -26,61 +29,54 @@ init(_Host, _Opts) ->
ok.
store_room(LServer, Host, Name, Opts) ->
SName = ejabberd_sql:escape(Name),
SHost = ejabberd_sql:escape(Host),
SOpts = ejabberd_sql:encode_term(Opts),
SOpts = jlib:term_to_expr(Opts),
F = fun () ->
sql_queries:update_t(<<"muc_room">>,
[<<"name">>, <<"host">>, <<"opts">>],
[SName, SHost, SOpts],
[<<"name='">>, SName, <<"' and host='">>,
SHost, <<"'">>])
?SQL_UPSERT_T(
"muc_room",
["!name=%(Name)s",
"!host=%(Host)s",
"opts=%(SOpts)s"])
end,
ejabberd_sql:sql_transaction(LServer, F).
restore_room(LServer, Host, Name) ->
SName = ejabberd_sql:escape(Name),
SHost = ejabberd_sql:escape(Host),
case catch ejabberd_sql:sql_query(LServer,
[<<"select opts from muc_room where name='">>,
SName, <<"' and host='">>, SHost,
<<"';">>]) of
{selected, [<<"opts">>], [[Opts]]} ->
case catch ejabberd_sql:sql_query(
LServer,
?SQL("select @(opts)s from muc_room where name=%(Name)s"
" and host=%(Host)s")) of
{selected, [{Opts}]} ->
mod_muc:opts_to_binary(ejabberd_sql:decode_term(Opts));
_ ->
error
end.
forget_room(LServer, Host, Name) ->
SName = ejabberd_sql:escape(Name),
SHost = ejabberd_sql:escape(Host),
F = fun () ->
ejabberd_sql:sql_query_t([<<"delete from muc_room where name='">>,
SName, <<"' and host='">>, SHost,
<<"';">>])
ejabberd_sql:sql_query_t(
?SQL("delete from muc_room where name=%(Name)s"
" and host=%(Host)s"))
end,
ejabberd_sql:sql_transaction(LServer, F).
can_use_nick(LServer, Host, JID, Nick) ->
SJID = jid:to_string(jid:tolower(jid:remove_resource(JID))),
SNick = ejabberd_sql:escape(Nick),
SHost = ejabberd_sql:escape(Host),
case catch ejabberd_sql:sql_query(LServer,
[<<"select jid from muc_registered ">>,
<<"where nick='">>, SNick,
<<"' and host='">>, SHost, <<"';">>]) of
{selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
case catch ejabberd_sql:sql_query(
LServer,
?SQL("select @(jid)s from muc_registered "
"where nick=%(Nick)s"
" and host=%(Host)s")) of
{selected, [{SJID1}]} -> SJID == SJID1;
_ -> true
end.
get_rooms(LServer, Host) ->
SHost = ejabberd_sql:escape(Host),
case catch ejabberd_sql:sql_query(LServer,
[<<"select name, opts from muc_room ">>,
<<"where host='">>, SHost, <<"';">>]) of
{selected, [<<"name">>, <<"opts">>], RoomOpts} ->
case catch ejabberd_sql:sql_query(
LServer,
?SQL("select @(name)s, @(opts)s from muc_room"
" where host=%(Host)s")) of
{selected, RoomOpts} ->
lists:map(
fun([Room, Opts]) ->
fun({Room, Opts}) ->
#muc_room{name_host = {Room, Host},
opts = mod_muc:opts_to_binary(
ejabberd_sql:decode_term(Opts))}
@@ -91,49 +87,38 @@ get_rooms(LServer, Host) ->
end.
get_nick(LServer, Host, From) ->
SJID = ejabberd_sql:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
SHost = ejabberd_sql:escape(Host),
case catch ejabberd_sql:sql_query(LServer,
[<<"select nick from muc_registered where "
"jid='">>,
SJID, <<"' and host='">>, SHost,
<<"';">>]) of
{selected, [<<"nick">>], [[Nick]]} -> Nick;
SJID = jid:to_string(jid:tolower(jid:remove_resource(From))),
case catch ejabberd_sql:sql_query(
LServer,
?SQL("select @(nick)s from muc_registered where"
" jid=%(SJID)s and host=%(Host)s")) of
{selected, [{Nick}]} -> Nick;
_ -> error
end.
set_nick(LServer, Host, From, Nick) ->
JID = jid:to_string(jid:tolower(jid:remove_resource(From))),
SJID = ejabberd_sql:escape(JID),
SNick = ejabberd_sql:escape(Nick),
SHost = ejabberd_sql:escape(Host),
F = fun () ->
case Nick of
<<"">> ->
ejabberd_sql:sql_query_t(
[<<"delete from muc_registered where ">>,
<<"jid='">>, SJID,
<<"' and host='">>, Host,
<<"';">>]),
?SQL("delete from muc_registered where"
" jid=%(JID)s and host=%(Host)s")),
ok;
_ ->
Allow = case ejabberd_sql:sql_query_t(
[<<"select jid from muc_registered ">>,
<<"where nick='">>,
SNick,
<<"' and host='">>,
SHost, <<"';">>]) of
{selected, [<<"jid">>], [[J]]} -> J == JID;
?SQL("select @(jid)s from muc_registered"
" where nick=%(Nick)s"
" and host=%(Host)s")) of
{selected, [{J}]} -> J == JID;
_ -> true
end,
if Allow ->
sql_queries:update_t(<<"muc_registered">>,
[<<"jid">>, <<"host">>,
<<"nick">>],
[SJID, SHost, SNick],
[<<"jid='">>, SJID,
<<"' and host='">>, SHost,
<<"'">>]),
?SQL_UPSERT_T(
"muc_registered",
["!jid=%(JID)s",
"!host=%(Host)s",
"nick=%(Nick)s"]),
ok;
true ->
false
@@ -147,15 +132,12 @@ export(_Server) ->
fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
case str:suffix(Host, RoomHost) of
true ->
SName = ejabberd_sql:escape(Name),
SRoomHost = ejabberd_sql:escape(RoomHost),
SOpts = ejabberd_sql:encode_term(Opts),
[[<<"delete from muc_room where name='">>, SName,
<<"' and host='">>, SRoomHost, <<"';">>],
[<<"insert into muc_room(name, host, opts) ",
"values (">>,
<<"'">>, SName, <<"', '">>, SRoomHost,
<<"', '">>, SOpts, <<"');">>]];
SOpts = jlib:term_to_expr(Opts),
[?SQL("delete from muc_room where name=%(Name)s"
" and host=%(RoomHost)s;"),
?SQL("insert into muc_room(name, host, opts) "
"values ("
"%(Name)s, %(RoomHost)s, %(SOpts)s);")];
false ->
[]
end
@@ -165,17 +147,12 @@ export(_Server) ->
nick = Nick}) ->
case str:suffix(Host, RoomHost) of
true ->
SJID = ejabberd_sql:escape(
jid:to_string(
jid:make(U, S, <<"">>))),
SNick = ejabberd_sql:escape(Nick),
SRoomHost = ejabberd_sql:escape(RoomHost),
[[<<"delete from muc_registered where jid='">>,
SJID, <<"' and host='">>, SRoomHost, <<"';">>],
[<<"insert into muc_registered(jid, host, "
"nick) values ('">>,
SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
<<"');">>]];
SJID = jid:to_string(jid:make(U, S, <<"">>)),
[?SQL("delete from muc_registered where"
" jid=%(SJID)s and host=%(RoomHost)s;"),
?SQL("insert into muc_registered(jid, host, "
"nick) values ("
"%(SJID)s, %(RoomHost)s, %(Nick)s);")];
false ->
[]
end
+2 -2
View File
@@ -140,7 +140,7 @@ init([LServerS, Opts]) ->
LServiceS = gen_mod:get_opt_host(LServerS, Opts,
<<"multicast.@HOST@">>),
Access = gen_mod:get_opt(access, Opts,
fun (A) when is_atom(A) -> A end, all),
fun acl:access_rules_validator/1, all),
SLimits =
build_service_limit_record(gen_mod:get_opt(limits, Opts,
fun (A) when is_list(A) ->
@@ -1220,7 +1220,7 @@ stj(String) -> jid:from_string(String).
jts(String) -> jid:to_string(String).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(limits) ->
fun (A) when is_list(A) -> A end;
+3 -3
View File
@@ -162,7 +162,7 @@ init([Host, Opts]) ->
?MODULE, handle_offline_query, IQDisc),
AccessMaxOfflineMsgs =
gen_mod:get_opt(access_max_user_messages, Opts,
fun(A) when is_atom(A) -> A end,
fun acl:shaper_rules_validator/1,
max_user_offline_messages),
{ok,
#state{host = Host,
@@ -791,7 +791,7 @@ get_messages_subset(User, Host, MsgsAll) ->
fun(A) when is_atom(A) -> A end,
max_user_offline_messages),
MaxOfflineMsgs = case get_max_user_messages(Access,
User, Host)
{User, Host}, Host)
of
Number when is_integer(Number) -> Number;
_ -> 100
@@ -866,7 +866,7 @@ import(LServer, DBType, Data) ->
Mod:import(LServer, Data).
mod_opt_type(access_max_user_messages) ->
fun (A) -> A end;
fun acl:shaper_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(store_empty_body) ->
fun (V) when is_boolean(V) -> V;
+20 -28
View File
@@ -38,8 +38,7 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
true ->
Query = lists:map(
fun(M) ->
Username =
ejabberd_sql:escape((M#offline_msg.to)#jid.luser),
LUser = (M#offline_msg.to)#jid.luser,
From = M#offline_msg.from,
To = M#offline_msg.to,
Packet =
@@ -49,9 +48,8 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
jlib:add_delay_info(Packet, Host,
M#offline_msg.timestamp,
<<"Offline Storage">>),
XML =
ejabberd_sql:escape(fxml:element_to_binary(NewPacket)),
sql_queries:add_spool_sql(Username, XML)
XML = fxml:element_to_binary(NewPacket),
sql_queries:add_spool_sql(LUser, XML)
end,
Msgs),
sql_queries:add_spool(Host, Query)
@@ -82,8 +80,8 @@ remove_old_messages(Days, LServer) ->
LServer,
[<<"DELETE FROM spool"
" WHERE created_at < "
"DATE_SUB(CURDATE(), INTERVAL ">>,
integer_to_list(Days), <<" DAY);">>]) of
"NOW() - INTERVAL '">>,
integer_to_list(Days), <<"';">>]) of
{updated, N} ->
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
_Error ->
@@ -95,19 +93,18 @@ remove_user(LUser, LServer) ->
sql_queries:del_spool_msg(LServer, LUser).
read_message_headers(LUser, LServer) ->
Username = ejabberd_sql:escape(LUser),
case catch ejabberd_sql:sql_query(
LServer, [<<"select xml, seq from spool where username ='">>,
Username, <<"' order by seq;">>]) of
{selected, [<<"xml">>, <<"seq">>], Rows} ->
LServer,
?SQL("select @(xml)s, @(seq)d from spool"
" where username=%(LUser)s order by seq")) of
{selected, Rows} ->
lists:flatmap(
fun([XML, Seq]) ->
fun({XML, Seq}) ->
case xml_to_offline_msg(XML) of
{ok, #offline_msg{from = From,
to = To,
packet = El}} ->
Seq0 = binary_to_integer(Seq),
[{Seq0, From, To, El}];
[{Seq, From, To, El}];
_ ->
[]
end
@@ -117,13 +114,11 @@ read_message_headers(LUser, LServer) ->
end.
read_message(LUser, LServer, Seq) ->
Username = ejabberd_sql:escape(LUser),
SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
case ejabberd_sql:sql_query(
LServer,
[<<"select xml from spool where username='">>, Username,
<<"' and seq='">>, SSeq, <<"';">>]) of
{selected, [<<"xml">>], [[RawXML]|_]} ->
?SQL("select @(xml)s from spool where username=%(LUser)s"
" and seq=%(Seq)d")) of
{selected, [{RawXML}|_]} ->
case xml_to_offline_msg(RawXML) of
{ok, Msg} ->
{ok, Msg};
@@ -135,12 +130,10 @@ read_message(LUser, LServer, Seq) ->
end.
remove_message(LUser, LServer, Seq) ->
Username = ejabberd_sql:escape(LUser),
SSeq = ejabberd_sql:escape(integer_to_binary(Seq)),
ejabberd_sql:sql_query(
LServer,
[<<"delete from spool where username='">>, Username,
<<"' and seq='">>, SSeq, <<"';">>]),
?SQL("delete from spool where username=%(LUser)s"
" and seq=%(Seq)d")),
ok.
read_all_messages(LUser, LServer) ->
@@ -180,14 +173,13 @@ export(_Server) ->
timestamp = TimeStamp, from = From, to = To,
packet = Packet})
when LServer == Host ->
Username = ejabberd_sql:escape(LUser),
Packet1 = jlib:replace_from_to(From, To, Packet),
Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp,
<<"Offline Storage">>),
XML = ejabberd_sql:escape(fxml:element_to_binary(Packet2)),
[[<<"delete from spool where username='">>, Username, <<"';">>],
[<<"insert into spool(username, xml) values ('">>,
Username, <<"', '">>, XML, <<"');">>]];
XML = fxml:element_to_binary(Packet2),
[?SQL("delete from spool where username=%(LUser)s;"),
?SQL("insert into spool(username, xml) values ("
"%(LUser)s, %(XML)s);")];
(_Host, _R) ->
[]
end}].
+31 -32
View File
@@ -8,6 +8,8 @@
%%%-------------------------------------------------------------------
-module(mod_privacy_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(mod_privacy).
%% API
@@ -29,6 +31,7 @@
-include("jlib.hrl").
-include("mod_privacy.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
@@ -208,38 +211,38 @@ export(Server) ->
fun(Host, #privacy{us = {LUser, LServer}, lists = Lists,
default = Default})
when LServer == Host ->
Username = ejabberd_sql:escape(LUser),
if Default /= none ->
SDefault = ejabberd_sql:escape(Default),
[[<<"delete from privacy_default_list where ">>,
<<"username='">>, Username, <<"';">>],
[<<"insert into privacy_default_list(username, "
"name) ">>,
<<"values ('">>, Username, <<"', '">>,
SDefault, <<"');">>]];
[?SQL("delete from privacy_default_list where"
" username=%(LUser)s;"),
?SQL("insert into privacy_default_list(username, name) "
"values (%(LUser)s, %(Default)s);")];
true ->
[]
end ++
lists:flatmap(
fun({Name, List}) ->
SName = ejabberd_sql:escape(Name),
RItems = lists:map(fun item_to_raw/1, List),
ID = jlib:integer_to_binary(get_id()),
[[<<"delete from privacy_list where username='">>,
Username, <<"' and name='">>,
SName, <<"';">>],
[<<"insert into privacy_list(username, "
"name, id) values ('">>,
Username, <<"', '">>, SName,
<<"', '">>, ID, <<"');">>],
[<<"delete from privacy_list_data where "
"id='">>, ID, <<"';">>]] ++
[[<<"insert into privacy_list_data(id, t, "
"value, action, ord, match_all, match_iq, "
"match_message, match_presence_in, "
"match_presence_out) values ('">>,
ID, <<"', '">>, str:join(Items, <<"', '">>),
<<"');">>] || Items <- RItems]
ID = get_id(),
[?SQL("delete from privacy_list where"
" username=%(LUser)s and"
" name=%(Name)s;"),
?SQL("insert into privacy_list(username, "
"name, id) values ("
"%(LUser)s, %(Name)s, %(ID)d);"),
?SQL("delete from privacy_list_data where"
" id=%(ID)d;")] ++
[?SQL("insert into privacy_list_data(id, t, "
"value, action, ord, match_all, match_iq, "
"match_message, match_presence_in, "
"match_presence_out) "
"values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
" %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
" %(MatchMessage)b, %(MatchPresenceIn)b,"
" %(MatchPresenceOut)b)")
|| {SType, SValue, SAction, Order,
MatchAll, MatchIQ,
MatchMessage, MatchPresenceIn,
MatchPresenceOut} <- RItems]
end,
Lists);
(_Host, _R) ->
@@ -327,10 +330,8 @@ item_to_raw(#listitem{type = Type, value = Value,
match_presence_out = MatchPresenceOut}) ->
{SType, SValue} = case Type of
none -> {<<"n">>, <<"">>};
jid ->
{<<"j">>,
ejabberd_sql:escape(jid:to_string(Value))};
group -> {<<"g">>, ejabberd_sql:escape(Value)};
jid -> {<<"j">>, jid:to_string(Value)};
group -> {<<"g">>, Value};
subscription ->
case Value of
none -> {<<"s">>, <<"none">>};
@@ -368,9 +369,7 @@ sql_get_privacy_list_data(LUser, LServer, Name) ->
sql_queries:get_privacy_list_data(LServer, LUser, Name).
sql_get_privacy_list_data_t(LUser, Name) ->
Username = ejabberd_sql:escape(LUser),
SName = ejabberd_sql:escape(Name),
sql_queries:get_privacy_list_data_t(Username, SName).
sql_queries:get_privacy_list_data_t(LUser, Name).
sql_get_privacy_list_data_by_id(ID, LServer) ->
sql_queries:get_privacy_list_data_by_id(LServer, ID).
+2 -6
View File
@@ -71,12 +71,8 @@ export(_Server) ->
fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
xml = Data})
when LServer == Host ->
Username = ejabberd_sql:escape(LUser),
LXMLNS = ejabberd_sql:escape(XMLNS),
SData =
ejabberd_sql:escape(fxml:element_to_binary(Data)),
sql_queries:set_private_data_sql(Username, LXMLNS,
SData);
SData = fxml:element_to_binary(Data),
sql_queries:set_private_data_sql(LUser, XMLNS, SData);
(_Host, _R) ->
[]
end}].
+1 -1
View File
@@ -260,7 +260,7 @@ parse_options(ServerHost, Opts) ->
Port = gen_mod:get_opt(port, Opts,
fun(P) when is_integer(P), P>0, P<65536 -> P end,
7777),
ACL = gen_mod:get_opt(access, Opts, fun(A) when is_atom(A) -> A end,
ACL = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1,
all),
Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1,
<<"SOCKS5 Bytestreams">>),
+1 -1
View File
@@ -83,7 +83,7 @@ init([Socket, Host, Opts]) ->
(anonymous) -> anonymous
end, anonymous),
Shaper = gen_mod:get_opt(shaper, Opts,
fun(A) when is_atom(A) -> A end,
fun acl:shaper_rules_validator/1,
none),
RecvBuf = gen_mod:get_opt(recbuf, Opts,
fun(I) when is_integer(I), I>0 -> I end,
+24 -13
View File
@@ -107,6 +107,8 @@
nodeOptions/0,
subOption/0,
subOptions/0,
pubOption/0,
pubOptions/0,
%%
affiliation/0,
subscription/0,
@@ -1289,7 +1291,16 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
[#xmlel{name = <<"item">>, attrs = ItemAttrs,
children = Payload}] ->
ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs),
publish_item(Host, ServerHost, Node, From, ItemId, Payload, Access);
PubOpts = case [C || #xmlel{name = <<"publish-options">>,
children = [C]} <- Rest] of
[XEl] ->
case jlib:parse_xdata_submit(XEl) of
invalid -> [];
Form -> Form
end;
_ -> []
end,
publish_item(Host, ServerHost, Node, From, ItemId, Payload, PubOpts, Access);
[] ->
{error,
extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
@@ -2185,10 +2196,10 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
| {error, xmlel()}
).
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, all).
publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, Access) ->
publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, Access);
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) ->
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, [], all).
publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, PubOpts, Access) ->
publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, PubOpts, Access);
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access) ->
Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
Features = plugin_features(Host, Type),
PublishFeature = lists:member(<<"publish">>, Features),
@@ -2220,7 +2231,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) ->
extended_error(?ERR_BAD_REQUEST, <<"item-required">>)};
true ->
node_call(Host, Type, publish_item,
[Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload])
[Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, PubOpts])
end
end,
Reply = [#xmlel{name = <<"pubsub">>,
@@ -2281,7 +2292,8 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) ->
attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
children = [#xmlel{name = <<"create">>,
attrs = [{<<"node">>, NewNode}]}]}]} ->
publish_item(Host, ServerHost, NewNode, Publisher, ItemId, Payload);
publish_item(Host, ServerHost, NewNode, Publisher, ItemId,
Payload, PubOpts, Access);
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
end;
@@ -3172,11 +3184,9 @@ subscription_to_string(_) -> <<"none">>.
Host :: mod_pubsub:host())
-> jid()
).
service_jid(Host) ->
case Host of
{U, S, _} -> {jid, U, S, <<>>, U, S, <<>>};
_ -> {jid, <<>>, Host, <<>>, <<>>, Host, <<>>}
end.
service_jid(#jid{} = Jid) -> Jid;
service_jid({U, S, R}) -> jid:make(U, S, R);
service_jid(Host) -> jid:make(<<>>, Host, <<>>).
%% @spec (LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean()
%% LJID = jid()
@@ -3525,7 +3535,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType
end, SubIDsByJID).
broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
broadcast_stanza({LUser, LServer, LResource}, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM),
broadcast_stanza({LUser, LServer, <<>>}, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM),
%% Handles implicit presence subscriptions
SenderResource = user_resource(LUser, LServer, LResource),
case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
@@ -3863,6 +3873,7 @@ set_configure(Host, Node, From, Els, Lang) ->
set_node,
[N#pubsub_node{options = NewOpts}])
of
{result, Nidx} -> {result, ok};
ok -> {result, ok};
Err -> Err
end;
+23 -6
View File
@@ -74,7 +74,7 @@ stop(Host) ->
stream_feature_register(Acc, Host) ->
AF = gen_mod:get_module_opt(Host, ?MODULE, access_from,
fun(A) when is_atom(A) -> A end,
fun(A) -> A end,
all),
case (AF /= none) and lists:keymember(<<"mechanisms">>, 2, Acc) of
true ->
@@ -126,7 +126,7 @@ process_iq(From, To,
RTag = fxml:get_subtag(SubEl, <<"remove">>),
Server = To#jid.lserver,
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
fun(A) when is_atom(A) -> A end,
fun(A) -> A end,
all),
AllowRemove = allow ==
acl:match_rule(Server, Access, From),
@@ -386,6 +386,10 @@ try_set_password(User, Server, Password, IQ, SubEl,
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end;
error_preparing_password ->
ErrText = <<"The password contains unacceptable characters">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]};
false ->
ErrText = <<"The password is too weak">>,
IQ#iq{type = error,
@@ -398,7 +402,7 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
_ ->
JID = jid:make(User, Server, <<"">>),
Access = gen_mod:get_module_opt(Server, ?MODULE, access,
fun(A) when is_atom(A) -> A end,
fun(A) -> A end,
all),
IPAccess = get_ip_access(Server),
case {acl:match_rule(Server, Access, JID),
@@ -440,7 +444,12 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end
end;
error_preparing_password ->
remove_timeout(Source),
ErrText = <<"The password contains unacceptable characters">>,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
false ->
remove_timeout(Source),
ErrText = <<"The password is too weak">>,
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
end;
@@ -519,7 +528,7 @@ check_from(#jid{user = <<"">>, server = <<"">>},
allow;
check_from(JID, Server) ->
Access = gen_mod:get_module_opt(Server, ?MODULE, access_from,
fun(A) when is_atom(A) -> A end,
fun(A) -> A end,
none),
acl:match_rule(Server, Access, JID).
@@ -635,6 +644,14 @@ process_xdata_submit(El) ->
end.
is_strong_password(Server, Password) ->
case jid:resourceprep(Password) of
PP when is_binary(PP) ->
is_strong_password2(Server, Password);
error ->
error_preparing_password
end.
is_strong_password2(Server, Password) ->
LServer = jid:nameprep(Server),
case gen_mod:get_module_opt(LServer, ?MODULE, password_strength,
fun(N) when is_number(N), N>=0 -> N end,
@@ -719,13 +736,13 @@ check_ip_access(IPAddress, IPAccess) ->
acl:match_rule(global, IPAccess, IPAddress).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(access_from) ->
fun (A) when is_atom(A) -> A end;
mod_opt_type(captcha_protected) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(ip_access) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(password_strength) ->
fun (N) when is_number(N), N >= 0 -> N end;
+1 -1
View File
@@ -491,7 +491,7 @@ form_del_get(Host, Lang) ->
%% {error, invalid_jid}
register_account(Username, Host, Password) ->
Access = gen_mod:get_module_opt(Host, mod_register, access,
fun(A) when is_atom(A) -> A end,
fun(A) -> A end,
all),
case jid:make(Username, Host, <<"">>) of
error -> {error, invalid_jid};
+7 -7
View File
@@ -351,7 +351,7 @@ get_roster_by_jid_t(LUser, LServer, LJID) ->
try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
#jid{server = Server} = From,
Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) when is_atom(A) -> A end, all),
Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) -> A end, all),
case acl:match_rule(Server, Access, From) of
deny ->
Txt = <<"Denied by ACL">>,
@@ -382,14 +382,14 @@ process_item_set(From, To,
Item = get_roster_by_jid_t(LUser, LServer, LJID),
Item1 = process_item_attrs_managed(Item, Attrs, Managed),
Item2 = process_item_els(Item1, Els),
case Item2#roster.subscription of
remove -> del_roster_t(LUser, LServer, LJID);
_ -> update_roster_t(LUser, LServer, LJID, Item2)
end,
send_itemset_to_managers(From, Item2, Managed),
Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2,
[LServer]),
case Item3#roster.subscription of
remove -> del_roster_t(LUser, LServer, LJID);
_ -> update_roster_t(LUser, LServer, LJID, Item3)
end,
send_itemset_to_managers(From, Item3, Managed),
case roster_version_on_db(LServer) of
true -> write_roster_version_t(LUser, LServer);
false -> ok
@@ -1235,7 +1235,7 @@ import(LServer, DBType, R) ->
Mod:import(LServer, R).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;
fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(managers) ->
+12 -29
View File
@@ -8,6 +8,8 @@
%%%-------------------------------------------------------------------
-module(mod_roster_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(mod_roster).
%% API
@@ -20,6 +22,7 @@
-include("jlib.hrl").
-include("mod_roster.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
@@ -34,15 +37,13 @@ read_roster_version(LUser, LServer) ->
end.
write_roster_version(LUser, LServer, InTransaction, Ver) ->
Username = ejabberd_sql:escape(LUser),
EVer = ejabberd_sql:escape(Ver),
if InTransaction ->
sql_queries:set_roster_version(Username, EVer);
sql_queries:set_roster_version(LUser, Ver);
true ->
sql_queries:sql_transaction(
LServer,
fun () ->
sql_queries:set_roster_version(Username, EVer)
sql_queries:set_roster_version(LUser, Ver)
end)
end.
@@ -167,26 +168,20 @@ read_subscription_and_groups(LUser, LServer, LJID) ->
export(_Server) ->
[{roster,
fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
fun(Host, #roster{usj = {_LUser, LServer, _LJID}} = R)
when LServer == Host ->
Username = ejabberd_sql:escape(LUser),
SJID = ejabberd_sql:escape(jid:to_string(LJID)),
ItemVals = record_to_string(R),
ItemGroups = groups_to_string(R),
sql_queries:update_roster_sql(Username, SJID,
ItemVals, ItemGroups);
ItemVals = record_to_row(R),
ItemGroups = R#roster.groups,
sql_queries:update_roster_sql(ItemVals, ItemGroups);
(_Host, _R) ->
[]
end},
{roster_version,
fun(Host, #roster_version{us = {LUser, LServer}, version = Ver})
when LServer == Host ->
Username = ejabberd_sql:escape(LUser),
SVer = ejabberd_sql:escape(Ver),
[[<<"delete from roster_version where username='">>,
Username, <<"';">>],
[<<"insert into roster_version(username, version) values('">>,
Username, <<"', '">>, SVer, <<"');">>]];
[?SQL("delete from roster_version where username=%(LUser)s;"),
?SQL("insert into roster_version(username, version) values("
" %(LUser)s, %(Ver)s);")];
(_Host, _R) ->
[]
end}].
@@ -294,15 +289,3 @@ record_to_row(
none -> <<"N">>
end,
{LUser, SJID, Name, SSubscription, SAsk, AskMessage}.
groups_to_string(#roster{us = {User, _Server},
jid = JID, groups = Groups}) ->
Username = ejabberd_sql:escape(User),
SJID =
ejabberd_sql:escape(jid:to_string(jid:tolower(JID))),
lists:foldl(fun (<<"">>, Acc) -> Acc;
(Group, Acc) ->
G = ejabberd_sql:escape(Group),
[[Username, SJID, G] | Acc]
end,
[], Groups).
+59 -70
View File
@@ -8,6 +8,8 @@
%%%-------------------------------------------------------------------
-module(mod_shared_roster_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(mod_shared_roster).
%% API
@@ -21,6 +23,7 @@
-include("jlib.hrl").
-include("mod_roster.hrl").
-include("mod_shared_roster.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
@@ -30,38 +33,39 @@ init(_Host, _Opts) ->
list_groups(Host) ->
case ejabberd_sql:sql_query(
Host, [<<"select name from sr_group;">>]) of
{selected, [<<"name">>], Rs} -> [G || [G] <- Rs];
Host,
?SQL("select @(name)s from sr_group")) of
{selected, Rs} -> [G || {G} <- Rs];
_ -> []
end.
groups_with_opts(Host) ->
case ejabberd_sql:sql_query(Host,
[<<"select name, opts from sr_group;">>])
case ejabberd_sql:sql_query(
Host,
?SQL("select @(name)s, @(opts)s from sr_group"))
of
{selected, [<<"name">>, <<"opts">>], Rs} ->
{selected, Rs} ->
[{G, mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(Opts))}
|| [G, Opts] <- Rs];
|| {G, Opts} <- Rs];
_ -> []
end.
create_group(Host, Group, Opts) ->
SGroup = ejabberd_sql:escape(Group),
SOpts = ejabberd_sql:encode_term(Opts),
SOpts = jlib:term_to_expr(Opts),
F = fun () ->
sql_queries:update_t(<<"sr_group">>,
[<<"name">>, <<"opts">>], [SGroup, SOpts],
[<<"name='">>, SGroup, <<"'">>])
?SQL_UPSERT_T(
"sr_group",
["!name=%(Group)s",
"opts=%(SOpts)s"])
end,
ejabberd_sql:sql_transaction(Host, F).
delete_group(Host, Group) ->
SGroup = ejabberd_sql:escape(Group),
F = fun () ->
ejabberd_sql:sql_query_t([<<"delete from sr_group where name='">>,
SGroup, <<"';">>]),
ejabberd_sql:sql_query_t([<<"delete from sr_user where grp='">>,
SGroup, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("delete from sr_group where name=%(Group)s")),
ejabberd_sql:sql_query_t(
?SQL("delete from sr_user where grp=%(Group)s"))
end,
case ejabberd_sql:sql_transaction(Host, F) of
{atomic,{updated,_}} -> {atomic, ok};
@@ -69,23 +73,21 @@ delete_group(Host, Group) ->
end.
get_group_opts(Host, Group) ->
SGroup = ejabberd_sql:escape(Group),
case catch ejabberd_sql:sql_query(
Host,
[<<"select opts from sr_group where name='">>,
SGroup, <<"';">>]) of
{selected, [<<"opts">>], [[SOpts]]} ->
?SQL("select @(opts)s from sr_group where name=%(Group)s")) of
{selected, [{SOpts}]} ->
mod_shared_roster:opts_to_binary(ejabberd_sql:decode_term(SOpts));
_ -> error
end.
set_group_opts(Host, Group, Opts) ->
SGroup = ejabberd_sql:escape(Group),
SOpts = ejabberd_sql:encode_term(Opts),
SOpts = jlib:term_to_expr(Opts),
F = fun () ->
sql_queries:update_t(<<"sr_group">>,
[<<"name">>, <<"opts">>], [SGroup, SOpts],
[<<"name='">>, SGroup, <<"'">>])
?SQL_UPSERT_T(
"sr_group",
["!name=%(Group)s",
"opts=%(SOpts)s"])
end,
ejabberd_sql:sql_transaction(Host, F).
@@ -93,21 +95,18 @@ get_user_groups(US, Host) ->
SJID = make_jid_s(US),
case catch ejabberd_sql:sql_query(
Host,
[<<"select grp from sr_user where jid='">>,
SJID, <<"';">>]) of
{selected, [<<"grp">>], Rs} -> [G || [G] <- Rs];
?SQL("select @(grp)s from sr_user where jid=%(SJID)s")) of
{selected, Rs} -> [G || {G} <- Rs];
_ -> []
end.
get_group_explicit_users(Host, Group) ->
SGroup = ejabberd_sql:escape(Group),
case catch ejabberd_sql:sql_query(
Host,
[<<"select jid from sr_user where grp='">>,
SGroup, <<"';">>]) of
{selected, [<<"jid">>], Rs} ->
?SQL("select @(jid)s from sr_user where grp=%(Group)s")) of
{selected, Rs} ->
lists:map(
fun([JID]) ->
fun({JID}) ->
{U, S, _} = jid:tolower(jid:from_string(JID)),
{U, S}
end, Rs);
@@ -119,43 +118,36 @@ get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
SJID = make_jid_s(LUser, LServer),
case catch ejabberd_sql:sql_query(
LServer,
[<<"select grp from sr_user where jid='">>,
SJID, <<"';">>]) of
{selected, [<<"grp">>], Rs} ->
?SQL("select @(grp)s from sr_user where jid=%(SJID)s")) of
{selected, Rs} ->
[{Group, proplists:get_value(Group, GroupsOpts, [])}
|| [Group] <- Rs];
|| {Group} <- Rs];
_ -> []
end.
is_user_in_group(US, Group, Host) ->
SJID = make_jid_s(US),
SGroup = ejabberd_sql:escape(Group),
case catch ejabberd_sql:sql_query(Host,
[<<"select * from sr_user where jid='">>,
SJID, <<"' and grp='">>, SGroup,
<<"';">>]) of
{selected, _, []} -> false;
case catch ejabberd_sql:sql_query(
Host,
?SQL("select @(jid)s from sr_user where jid=%(SJID)s"
" and grp=%(Group)s")) of
{selected, []} -> false;
_ -> true
end.
add_user_to_group(Host, US, Group) ->
SJID = make_jid_s(US),
SGroup = ejabberd_sql:escape(Group),
F = fun () ->
sql_queries:update_t(<<"sr_user">>,
[<<"jid">>, <<"grp">>], [SJID, SGroup],
[<<"jid='">>, SJID, <<"' and grp='">>,
SGroup, <<"'">>])
end,
ejabberd_sql:sql_transaction(Host, F).
ejabberd_sql:sql_query(
Host,
?SQL("insert into sr_user(jid, grp) values ("
"%(SJID)s, %(Group)s)")).
remove_user_from_group(Host, US, Group) ->
SJID = make_jid_s(US),
SGroup = ejabberd_sql:escape(Group),
F = fun () ->
ejabberd_sql:sql_query_t([<<"delete from sr_user where jid='">>,
SJID, <<"' and grp='">>, SGroup,
<<"';">>]),
ejabberd_sql:sql_query_t(
?SQL("delete from sr_user where jid=%(SJID)s and"
" grp=%(Group)s")),
ok
end,
ejabberd_sql:sql_transaction(Host, F).
@@ -164,26 +156,23 @@ export(_Server) ->
[{sr_group,
fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
when LServer == Host ->
SGroup = ejabberd_sql:escape(Group),
SOpts = ejabberd_sql:encode_term(Opts),
[[<<"delete from sr_group where name='">>, Group, <<"';">>],
[<<"insert into sr_group(name, opts) values ('">>,
SGroup, <<"', '">>, SOpts, <<"');">>]];
SOpts = jlib:term_to_expr(Opts),
[?SQL("delete from sr_group where name=%(Group)s;"),
?SQL("insert into sr_group(name, opts) values ("
"%(Group)s, %(SOpts)s);")];
(_Host, _R) ->
[]
end},
{sr_user,
fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
when LServer == Host ->
SGroup = ejabberd_sql:escape(Group),
SJID = ejabberd_sql:escape(
jid:to_string(
jid:tolower(
jid:make(U, S, <<"">>)))),
[[<<"delete from sr_user where jid='">>, SJID,
<<"'and grp='">>, Group, <<"';">>],
[<<"insert into sr_user(jid, grp) values ('">>,
SJID, <<"', '">>, SGroup, <<"');">>]];
SJID = jid:to_string(
jid:tolower(
jid:make(U, S, <<"">>))),
[?SQL("select @(jid)s from sr_user where jid=%(SJID)s"
" and grp=%(Group)s;"),
?SQL("insert into sr_user(jid, grp) values ("
"%(SJID)s, %(Group)s);")];
(_Host, _R) ->
[]
end}].
@@ -207,6 +196,6 @@ import(_, _) ->
%%% Internal functions
%%%===================================================================
make_jid_s(U, S) ->
ejabberd_sql:escape(jid:to_string(jid:tolower(jid:make(U, S, <<"">>)))).
jid:to_string(jid:tolower(jid:make(U, S, <<"">>))).
make_jid_s({U, S}) -> make_jid_s(U, S).
+28 -54
View File
@@ -113,12 +113,10 @@ export(_Server) ->
[{vcard,
fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
when LServer == Host ->
Username = ejabberd_sql:escape(LUser),
SVCARD =
ejabberd_sql:escape(fxml:element_to_binary(VCARD)),
[[<<"delete from vcard where username='">>, Username, <<"';">>],
[<<"insert into vcard(username, vcard) values ('">>,
Username, <<"', '">>, SVCARD, <<"');">>]];
SVCARD = fxml:element_to_binary(VCARD),
[?SQL("delete from vcard where username=%(LUser)s;"),
?SQL("insert into vcard(username, vcard) values ("
"%(LUser)s, %(SVCARD)s);")];
(_Host, _R) ->
[]
end},
@@ -135,52 +133,26 @@ export(_Server) ->
orgname = OrgName, lorgname = LOrgName,
orgunit = OrgUnit, lorgunit = LOrgUnit})
when LServer == Host ->
Username = ejabberd_sql:escape(User),
LUsername = ejabberd_sql:escape(LUser),
SFN = ejabberd_sql:escape(FN),
SLFN = ejabberd_sql:escape(LFN),
SFamily = ejabberd_sql:escape(Family),
SLFamily = ejabberd_sql:escape(LFamily),
SGiven = ejabberd_sql:escape(Given),
SLGiven = ejabberd_sql:escape(LGiven),
SMiddle = ejabberd_sql:escape(Middle),
SLMiddle = ejabberd_sql:escape(LMiddle),
SNickname = ejabberd_sql:escape(Nickname),
SLNickname = ejabberd_sql:escape(LNickname),
SBDay = ejabberd_sql:escape(BDay),
SLBDay = ejabberd_sql:escape(LBDay),
SCTRY = ejabberd_sql:escape(CTRY),
SLCTRY = ejabberd_sql:escape(LCTRY),
SLocality = ejabberd_sql:escape(Locality),
SLLocality = ejabberd_sql:escape(LLocality),
SEMail = ejabberd_sql:escape(EMail),
SLEMail = ejabberd_sql:escape(LEMail),
SOrgName = ejabberd_sql:escape(OrgName),
SLOrgName = ejabberd_sql:escape(LOrgName),
SOrgUnit = ejabberd_sql:escape(OrgUnit),
SLOrgUnit = ejabberd_sql:escape(LOrgUnit),
[[<<"delete from vcard_search where lusername='">>,
LUsername, <<"';">>],
[<<"insert into vcard_search( username, "
"lusername, fn, lfn, family, lfamily, "
" given, lgiven, middle, lmiddle, "
"nickname, lnickname, bday, lbday, "
"ctry, lctry, locality, llocality, "
" email, lemail, orgname, lorgname, "
"orgunit, lorgunit)values (">>,
<<" '">>, Username, <<"', '">>, LUsername,
<<"', '">>, SFN, <<"', '">>, SLFN,
<<"', '">>, SFamily, <<"', '">>, SLFamily,
<<"', '">>, SGiven, <<"', '">>, SLGiven,
<<"', '">>, SMiddle, <<"', '">>, SLMiddle,
<<"', '">>, SNickname, <<"', '">>, SLNickname,
<<"', '">>, SBDay, <<"', '">>, SLBDay,
<<"', '">>, SCTRY, <<"', '">>, SLCTRY,
<<"', '">>, SLocality, <<"', '">>, SLLocality,
<<"', '">>, SEMail, <<"', '">>, SLEMail,
<<"', '">>, SOrgName, <<"', '">>, SLOrgName,
<<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit,
<<"');">>]];
[?SQL("delete from vcard_search where lusername=%(LUser)s;"),
?SQL("insert into vcard_search(username,"
" lusername, fn, lfn, family, lfamily,"
" given, lgiven, middle, lmiddle,"
" nickname, lnickname, bday, lbday,"
" ctry, lctry, locality, llocality,"
" email, lemail, orgname, lorgname,"
" orgunit, lorgunit) values ("
" %(LUser)s, %(User)s,"
" %(FN)s, %(LFN)s,"
" %(Family)s, %(LFamily)s,"
" %(Given)s, %(LGiven)s,"
" %(Middle)s, %(LMiddle)s,"
" %(Nickname)s, %(LNickname)s,"
" %(BDay)s, %(LBDay)s,"
" %(CTRY)s, %(LCTRY)s,"
" %(Locality)s, %(LLocality)s,"
" %(EMail)s, %(LEMail)s,"
" %(OrgName)s, %(LOrgName)s,"
" %(OrgUnit)s, %(LOrgUnit)s);")];
(_Host, _R) ->
[]
end}].
@@ -255,9 +227,11 @@ make_val(Match, Field, Val) ->
Condition = case str:suffix(<<"*">>, Val) of
true ->
Val1 = str:substr(Val, 1, byte_size(Val) - 1),
SVal = <<(ejabberd_sql:escape_like(Val1))/binary,
SVal = <<(ejabberd_sql:escape(
ejabberd_sql:escape_like_arg_circumflex(
Val1)))/binary,
"%">>,
[Field, <<" LIKE '">>, SVal, <<"'">>];
[Field, <<" LIKE '">>, SVal, <<"' ESCAPE '^'">>];
_ ->
SVal = ejabberd_sql:escape(Val),
[Field, <<" = '">>, SVal, <<"'">>]
+2
View File
@@ -8,6 +8,8 @@
%%%-------------------------------------------------------------------
-module(mod_vcard_xupdate_riak).
-behaviour(mod_vcard_xupdate).
%% API
-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
+21 -24
View File
@@ -8,11 +8,16 @@
%%%-------------------------------------------------------------------
-module(mod_vcard_xupdate_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(mod_vcard_xupdate).
%% API
-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2,
import/1, export/1]).
-include("mod_vcard_xupdate.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
%%% API
@@ -21,46 +26,38 @@ init(_Host, _Opts) ->
ok.
add_xupdate(LUser, LServer, Hash) ->
Username = ejabberd_sql:escape(LUser),
SHash = ejabberd_sql:escape(Hash),
F = fun () ->
sql_queries:update_t(<<"vcard_xupdate">>,
[<<"username">>, <<"hash">>],
[Username, SHash],
[<<"username='">>, Username, <<"'">>])
?SQL_UPSERT_T(
"vcard_xupdate",
["!username=%(LUser)s",
"hash=%(Hash)s"])
end,
ejabberd_sql:sql_transaction(LServer, F).
get_xupdate(LUser, LServer) ->
Username = ejabberd_sql:escape(LUser),
case ejabberd_sql:sql_query(LServer,
[<<"select hash from vcard_xupdate where "
"username='">>,
Username, <<"';">>])
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(hash)s from vcard_xupdate where"
" username=%(LUser)s"))
of
{selected, [<<"hash">>], [[Hash]]} -> Hash;
_ -> undefined
{selected, [{Hash}]} -> Hash;
_ -> undefined
end.
remove_xupdate(LUser, LServer) ->
Username = ejabberd_sql:escape(LUser),
F = fun () ->
ejabberd_sql:sql_query_t([<<"delete from vcard_xupdate where username='">>,
Username, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("delete from vcard_xupdate where username=%(LUser)s"))
end,
ejabberd_sql:sql_transaction(LServer, F).
export(_Server) ->
[{vcard_xupdate,
fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
when LServer == Host ->
Username = ejabberd_sql:escape(LUser),
SHash = ejabberd_sql:escape(Hash),
[[<<"delete from vcard_xupdate where username='">>,
Username, <<"';">>],
[<<"insert into vcard_xupdate(username, "
"hash) values ('">>,
Username, <<"', '">>, SHash, <<"');">>]];
when LServer == Host ->
[?SQL("delete from vcard_xupdate where username=%(LUser)s;"),
?SQL("insert into vcard_xupdate(username, hash) values ("
"%(LUser)s, %(Hash)s);")];
(_Host, _R) ->
[]
end}].
+4 -3
View File
@@ -33,7 +33,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -102,8 +102,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+4 -3
View File
@@ -33,7 +33,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -101,8 +101,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+3 -3
View File
@@ -33,7 +33,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -72,7 +72,7 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
case nodetree_dag:get_node(Nidx) of
#pubsub_node{options = Options} ->
case find_opt(node_type, Options) of
@@ -82,7 +82,7 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
?ERR_EXTENDED(?ERRT_NOT_ALLOWED(?MYLANG, Txt), <<"publish">>)};
_ ->
node_hometree:publish_item(Nidx, Publisher, Model,
MaxItems, ItemId, Payload)
MaxItems, ItemId, Payload, PubOpts)
end;
Err -> Err
end.
+4 -3
View File
@@ -39,7 +39,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -99,13 +99,14 @@ subscribe_node(_Nidx, _Sender, _Subscriber, _AccessModel, _SendLast, _PresenceSu
unsubscribe_node(_Nidx, _Sender, _Subscriber, _SubId) ->
{error, ?ERR_FORBIDDEN}.
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
PubOpts) ->
case nodetree_tree:get_node(Nidx) of
#pubsub_node{nodeid = {Host, Node}} ->
lists:foreach(fun (SubNode) ->
node_hometree:publish_item(SubNode#pubsub_node.id,
Publisher, PublishModel, MaxItems,
ItemId, Payload)
ItemId, Payload, PubOpts)
end,
nodetree_tree:get_subnodes(Host, Node, Publisher)),
{result, {default, broadcast, []}};
+3 -2
View File
@@ -39,7 +39,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -345,7 +345,8 @@ delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) ->
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
_PubOpts) ->
SubKey = jid:tolower(Publisher),
GenKey = jid:remove_resource(SubKey),
GenState = get_state(Nidx, GenKey),
+194 -171
View File
@@ -33,13 +33,16 @@
-behaviour(gen_pubsub_node).
-author('christophe.romain@process-one.net').
-compile([{parse_transform, ejabberd_sql_pt}]).
-include("pubsub.hrl").
-include("jlib.hrl").
-include("ejabberd_sql_pt.hrl").
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -51,9 +54,11 @@
get_entity_subscriptions_for_send_last/2, get_last_items/3]).
-export([decode_jid/1, encode_jid/1,
encode_jid_like/1,
decode_affiliation/1, decode_subscriptions/1,
encode_affiliation/1, encode_subscriptions/1,
encode_host/1]).
encode_host/1,
encode_host_like/1]).
init(_Host, _ServerHost, _Opts) ->
%%pubsub_subscription_sql:init(),
@@ -73,19 +78,25 @@ create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
create_node(Nidx, Owner) ->
{_U, _S, _R} = OwnerKey = jid:tolower(jid:remove_resource(Owner)),
State = #pubsub_state{stateid = {OwnerKey, Nidx}, affiliation = owner},
catch ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values(">>, state_to_raw(Nidx, State), <<");">>]),
J = encode_jid(OwnerKey),
A = encode_affiliation(owner),
S = encode_subscriptions([]),
catch ejabberd_sql:sql_query_t(
?SQL("insert into pubsub_state("
"nodeid, jid, affiliation, subscriptions) "
"values (%(Nidx)d, %(J)s, %(A)s, %(S)s)")),
{result, {default, broadcast}}.
delete_node(Nodes) ->
Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
Subscriptions = case catch
ejabberd_sql:sql_query_t([<<"select jid, subscriptions "
"from pubsub_state where nodeid='">>, Nidx, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(jid)s, @(subscriptions)s "
"from pubsub_state where nodeid=%(Nidx)d"))
of
{selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
[{decode_jid(SJID), decode_subscriptions(Subs)} || [SJID, Subs] <- RItems];
{selected, RItems} ->
[{decode_jid(SJID), decode_subscriptions(Subs)} ||
{SJID, Subs} <- RItems];
_ ->
[]
end,
@@ -215,7 +226,8 @@ delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscripti
_ -> update_subscription(Nidx, SubKey, NewSubs)
end.
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
_PubOpts) ->
SubKey = jid:tolower(Publisher),
GenKey = jid:remove_resource(SubKey),
{Affiliation, Subscriptions} = select_affiliation_subscriptions(Nidx, GenKey, SubKey),
@@ -296,13 +308,14 @@ get_entity_affiliations(Host, Owner) ->
H = encode_host(Host),
J = encode_jid(GenKey),
Reply = case catch
ejabberd_sql:sql_query_t([<<"select node, type, i.nodeid, affiliation "
"from pubsub_state i, pubsub_node n where "
"i.nodeid = n.nodeid and jid='">>, J, <<"' and host='">>, H, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(type)s, @(i.nodeid)d, @(affiliation)s "
"from pubsub_state i, pubsub_node n where "
"i.nodeid = n.nodeid and jid=%(J)s and host=%(H)s"))
of
{selected, [<<"node">>, <<"type">>, <<"nodeid">>, <<"affiliation">>], RItems} ->
[{nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]), decode_affiliation(A)}
|| [N, T, I, A] <- RItems];
{selected, RItems} ->
[{nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), decode_affiliation(A)}
|| {N, T, I, A} <- RItems];
_ ->
[]
end,
@@ -310,11 +323,12 @@ get_entity_affiliations(Host, Owner) ->
get_node_affiliations(Nidx) ->
Reply = case catch
ejabberd_sql:sql_query_t([<<"select jid, affiliation from pubsub_state "
"where nodeid='">>, Nidx, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(jid)s, @(affiliation)s from pubsub_state "
"where nodeid=%(Nidx)d"))
of
{selected, [<<"jid">>, <<"affiliation">>], RItems} ->
[{decode_jid(J), decode_affiliation(A)} || [J, A] <- RItems];
{selected, RItems} ->
[{decode_jid(J), decode_affiliation(A)} || {J, A} <- RItems];
_ ->
[]
end,
@@ -325,10 +339,11 @@ get_affiliation(Nidx, Owner) ->
GenKey = jid:remove_resource(SubKey),
J = encode_jid(GenKey),
Reply = case catch
ejabberd_sql:sql_query_t([<<"select affiliation from pubsub_state "
"where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(affiliation)s from pubsub_state "
"where nodeid=%(Nidx)d and jid=%(J)s"))
of
{selected, [<<"affiliation">>], [[A]]} ->
{selected, [{A}]} ->
decode_affiliation(A);
_ ->
none
@@ -350,21 +365,26 @@ get_entity_subscriptions(Host, Owner) ->
H = encode_host(Host),
SJ = encode_jid(SubKey),
GJ = encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select node, type, i.nodeid, jid, subscriptions "
"from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
_ ->
[<<"select node, type, i.nodeid, jid, subscriptions "
"from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
end,
GJLike = <<(encode_jid_like(GenKey))/binary, "%">>,
Query =
case SubKey of
GenKey ->
?SQL("select @(node)s, @(type)s, @(i.nodeid)d,"
" @(jid)s, @(subscriptions)s "
"from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid and jid like %(GJLike)s"
" escape '^' and host=%(H)s");
_ ->
?SQL("select @(node)s, @(type)s, @(i.nodeid)d,"
" @(jid)s, @(subscriptions)s "
"from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid and jid in"
" (%(SJ)s, %(GJ)s) and host=%(H)s")
end,
Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
lists:foldl(fun ([N, T, I, J, S], Acc) ->
Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]),
{selected, RItems} ->
lists:foldl(fun ({N, T, I, J, S}, Acc) ->
Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
Jid = decode_jid(J),
case decode_subscriptions(S) of
[] ->
@@ -399,23 +419,28 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
H = encode_host(Host),
SJ = encode_jid(SubKey),
GJ = encode_jid(GenKey),
Query = case SubKey of
GenKey ->
[<<"select node, type, i.nodeid, jid, subscriptions "
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
"and val='on_sub_and_presence' and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>];
_ ->
[<<"select node, type, i.nodeid, jid, subscriptions "
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
"and val='on_sub_and_presence' and jid in ('">>, SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>]
GJLike = <<(encode_jid_like(GenKey))/binary, "%">>,
Query =
case SubKey of
GenKey ->
?SQL("select @(node)s, @(type)s, @(i.nodeid)d,"
" @(jid)s, @(subscriptions)s "
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
"and val='on_sub_and_presence' and jid like %(GJLike)s"
" escape '^' and host=%(H)s");
_ ->
?SQL("select @(node)s, @(type)s, @(i.nodeid)d,"
" @(jid)s, @(subscriptions)s "
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid and name='send_last_published_item' "
"and val='on_sub_and_presence' and"
" jid in (%(SJ)s, %(GJ)s) and host=%(H)s")
end,
Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, <<"subscriptions">>], RItems} ->
lists:foldl(fun ([N, T, I, J, S], Acc) ->
Node = nodetree_tree_sql:raw_to_node(Host, [N, <<"">>, T, I]),
{selected, RItems} ->
lists:foldl(fun ({N, T, I, J, S}, Acc) ->
Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}),
Jid = decode_jid(J),
case decode_subscriptions(S) of
[] ->
@@ -435,11 +460,12 @@ get_entity_subscriptions_for_send_last(Host, Owner) ->
get_node_subscriptions(Nidx) ->
Reply = case catch
ejabberd_sql:sql_query_t([<<"select jid, subscriptions from pubsub_state "
"where nodeid='">>, Nidx, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(jid)s, @(subscriptions)s from pubsub_state "
"where nodeid=%(Nidx)d"))
of
{selected, [<<"jid">>, <<"subscriptions">>], RItems} ->
lists:foldl(fun ([J, S], Acc) ->
{selected, RItems} ->
lists:foldl(fun ({J, S}, Acc) ->
Jid = decode_jid(J),
case decode_subscriptions(S) of
[] ->
@@ -461,10 +487,11 @@ get_subscriptions(Nidx, Owner) ->
SubKey = jid:tolower(Owner),
J = encode_jid(SubKey),
Reply = case catch
ejabberd_sql:sql_query_t([<<"select subscriptions from pubsub_state where "
"nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(subscriptions)s from pubsub_state"
" where nodeid=%(Nidx)d and jid=%(J)s"))
of
{selected, [<<"subscriptions">>], [[S]]} ->
{selected, [{S}]} ->
decode_subscriptions(S);
_ ->
[]
@@ -561,15 +588,16 @@ get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}
get_states(Nidx) ->
case catch
ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions "
"from pubsub_state where nodeid='">>, Nidx, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
"from pubsub_state where nodeid=%(Nidx)d"))
of
{selected,
[<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} ->
{selected, RItems} ->
{result,
lists:map(fun ([SJID, Aff, Subs]) ->
#pubsub_state{stateid = {decode_jid(SJID), Nidx},
items = itemids(Nidx, SJID),
lists:map(fun ({SJID, Aff, Subs}) ->
JID = decode_jid(SJID),
#pubsub_state{stateid = {JID, Nidx},
items = itemids(Nidx, JID),
affiliation = decode_affiliation(Aff),
subscriptions = decode_subscriptions(Subs)}
end,
@@ -591,11 +619,12 @@ get_state(Nidx, JID) ->
get_state_without_itemids(Nidx, JID) ->
J = encode_jid(JID),
case catch
ejabberd_sql:sql_query_t([<<"select jid, affiliation, subscriptions "
"from pubsub_state where jid='">>, J, <<"' and nodeid='">>, Nidx, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s "
"from pubsub_state "
"where nodeid=%(Nidx)d and jid=%(J)s"))
of
{selected,
[<<"jid">>, <<"affiliation">>, <<"subscriptions">>], [[SJID, Aff, Subs]]} ->
{selected, [{SJID, Aff, Subs}]} ->
#pubsub_state{stateid = {decode_jid(SJID), Nidx},
affiliation = decode_affiliation(Aff),
subscriptions = decode_subscriptions(Subs)};
@@ -612,24 +641,20 @@ set_state(Nidx, State) ->
J = encode_jid(JID),
S = encode_subscriptions(State#pubsub_state.subscriptions),
A = encode_affiliation(State#pubsub_state.affiliation),
case catch
ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S, <<"', affiliation='">>, A,
<<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{updated, 1} ->
ok;
_ ->
catch
ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values('">>,
Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"');">>])
end,
?SQL_UPSERT_T(
"pubsub_state",
["!nodeid=%(Nidx)d",
"!jid=%(J)s",
"affiliation=%(A)s",
"subscriptions=%(S)s"
]),
ok.
del_state(Nidx, JID) ->
J = encode_jid(JID),
catch ejabberd_sql:sql_query_t([<<"delete from pubsub_state where jid='">>,
J, <<"' and nodeid='">>, Nidx, <<"';">>]),
catch ejabberd_sql:sql_query_t(
?SQL("delete from pubsub_state"
" where jid=%(J)s and nodeid=%(Nidx)d")),
ok.
%get_items(Nidx, _From) ->
@@ -647,12 +672,12 @@ del_state(Nidx, JID) ->
get_items(Nidx, From, none) ->
MaxItems = case catch
ejabberd_sql:sql_query_t([<<"select val from pubsub_node_option "
"where nodeid='">>, Nidx, <<"' and name='max_items';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(val)s from pubsub_node_option "
"where nodeid=%(Nidx)d and name='max_items'"))
of
{selected, [<<"val">>], [[Value]]} ->
Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))),
element(2, erl_parse:parse_term(Tokens));
{selected, [{Value}]} ->
jlib:expr_to_term(Value);
_ ->
?MAXITEMS
end,
@@ -661,20 +686,19 @@ get_items(Nidx, _From,
#rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) ->
Max = ejabberd_sql:escape(jlib:i2l(M)),
{Way, Order} = case Direction of
% aft -> {<<"<">>, <<"desc">>};
% before when I == <<>> -> {<<"is not">>, <<"asc">>};
% before -> {<<">">>, <<"asc">>};
% _ when IncIndex =/= undefined ->
% {<<"<">>, <<"desc">>}; % using index
_ ->
{<<"is not">>, <<"desc">>}% Can be better
aft when I == <<>> -> {<<"is not">>, <<"desc">>};
aft -> {<<"<">>, <<"desc">>};
before when I == <<>> -> {<<"is not">>, <<"asc">>};
before -> {<<">">>, <<"asc">>};
_ -> {<<"is not">>, <<"desc">>}
end,
SNidx = integer_to_binary(Nidx),
[AttrName, Id] = case I of
undefined when IncIndex =/= undefined ->
case catch
ejabberd_sql:sql_query_t([<<"select modification from pubsub_item pi "
"where exists ( select count(*) as count1 "
"from pubsub_item where nodeid='">>, Nidx,
"from pubsub_item where nodeid='">>, SNidx,
<<"' and modification > pi.modification having count1 = ">>,
ejabberd_sql:escape(jlib:i2l(IncIndex)), <<" );">>])
of
@@ -692,7 +716,7 @@ get_items(Nidx, _From,
[A, <<"'", B/binary, "'">>]
end,
Count = case catch
ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, Nidx, <<"';">>])
ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, SNidx, <<"';">>])
of
{selected, [_], [[C]]} -> C;
_ -> <<"0">>
@@ -701,13 +725,13 @@ get_items(Nidx, _From,
ejabberd_sql:sql_query_t(
[<<"select top ">>, jlib:i2l(Max),
<<" itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, Nidx,
"from pubsub_item where nodeid='">>, SNidx,
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
AttrName, <<" ">>, Order, <<";">>]);
(_, _) ->
ejabberd_sql:sql_query_t(
[<<"select itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, Nidx,
"from pubsub_item where nodeid='">>, SNidx,
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>])
end,
@@ -718,7 +742,7 @@ get_items(Nidx, _From,
[[_, _, _, F, _]|_] ->
Index = case catch
ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item "
"where nodeid='">>, Nidx, <<"' and ">>,
"where nodeid='">>, SNidx, <<"' and ">>,
AttrName, <<" > '">>, F, <<"';">>])
of
%{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")];
@@ -770,16 +794,17 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
get_last_items(Nidx, _From, Count) ->
Limit = jlib:i2l(Count),
SNidx = integer_to_binary(Nidx),
Query = fun(mssql, _) ->
ejabberd_sql:sql_query_t(
[<<"select top ">>, Limit,
<<" itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, Nidx,
"from pubsub_item where nodeid='">>, SNidx,
<<"' order by modification desc ;">>]);
(_, _) ->
ejabberd_sql:sql_query_t(
[<<"select itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, Nidx,
"from pubsub_item where nodeid='">>, SNidx,
<<"' order by modification desc limit ">>, Limit, <<";">>])
end,
case catch ejabberd_sql:sql_query_t(Query) of
@@ -791,16 +816,14 @@ get_last_items(Nidx, _From, Count) ->
end.
get_item(Nidx, ItemId) ->
I = ejabberd_sql:escape(ItemId),
case catch
ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, "
"modification, payload from pubsub_item "
"where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
case catch ejabberd_sql:sql_query_t(
?SQL("select @(itemid)s, @(publisher)s, @(creation)s,"
" @(modification)s, @(payload)s from pubsub_item"
" where nodeid=%(Nidx)d and itemid=%(ItemId)s"))
of
{selected,
[<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], [RItem]} ->
{selected, [RItem]} ->
{result, raw_to_item(Nidx, RItem)};
{selected, _, []} ->
{selected, []} ->
{error, ?ERR_ITEM_NOT_FOUND};
{'EXIT', _} ->
{error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
@@ -839,37 +862,31 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub
set_item(Item) ->
{ItemId, Nidx} = Item#pubsub_item.itemid,
I = ejabberd_sql:escape(ItemId),
{C, _} = Item#pubsub_item.creation,
{M, JID} = Item#pubsub_item.modification,
P = encode_jid(JID),
Payload = Item#pubsub_item.payload,
XML = ejabberd_sql:escape(str:join([fxml:element_to_binary(X) || X<-Payload], <<>>)),
XML = str:join([fxml:element_to_binary(X) || X<-Payload], <<>>),
S = fun ({T1, T2, T3}) ->
str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
end,
case catch
ejabberd_sql:sql_query_t([<<"update pubsub_item set publisher='">>, P,
<<"', modification='">>, S(M),
<<"', payload='">>, XML,
<<"' where nodeid='">>, Nidx, <<"' and itemid='">>, I, <<"';">>])
of
{updated, 1} ->
ok;
_ ->
catch
ejabberd_sql:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, "
"publisher, creation, modification, payload) "
"values('">>, Nidx, <<"', '">>, I, <<"', '">>, P,
<<"', '">>, S(C), <<"', '">>, S(M),
<<"', '">>, XML, <<"');">>])
end,
SM = S(M),
SC = S(C),
?SQL_UPSERT_T(
"pubsub_item",
["!nodeid=%(Nidx)d",
"!itemid=%(ItemId)s",
"publisher=%(P)s",
"modification=%(SM)s",
"payload=%(XML)s",
"-creation=%(SC)s"
]),
ok.
del_item(Nidx, ItemId) ->
I = ejabberd_sql:escape(ItemId),
catch ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid='">>,
I, <<"' and nodeid='">>, Nidx, <<"';">>]).
catch ejabberd_sql:sql_query_t(
?SQL("delete from pubsub_item where itemid=%(ItemId)s"
" and nodeid=%(Nidx)d")).
del_items(_, []) ->
ok;
@@ -877,9 +894,10 @@ del_items(Nidx, [ItemId]) ->
del_item(Nidx, ItemId);
del_items(Nidx, ItemIds) ->
I = str:join([[<<"'">>, ejabberd_sql:escape(X), <<"'">>] || X <- ItemIds], <<",">>),
SNidx = integer_to_binary(Nidx),
catch
ejabberd_sql:sql_query_t([<<"delete from pubsub_item where itemid in (">>,
I, <<") and nodeid='">>, Nidx, <<"';">>]).
I, <<") and nodeid='">>, SNidx, <<"';">>]).
get_item_name(_Host, _Node, Id) ->
Id.
@@ -899,16 +917,16 @@ first_in_list(Pred, [H | T]) ->
_ -> first_in_list(Pred, T)
end.
itemids(Nidx, {U, S, R}) ->
itemids(Nidx, encode_jid({U, S, R}));
itemids(Nidx, SJID) ->
itemids(Nidx, {_U, _S, _R} = JID) ->
SJID = <<(ejabberd_sql:escape(encode_jid_like(JID)))/binary, "%">>,
case catch
ejabberd_sql:sql_query_t([<<"select itemid from pubsub_item where "
"nodeid='">>, Nidx, <<"' and publisher like '">>, SJID,
<<"%' order by modification desc;">>])
ejabberd_sql:sql_query_t(
?SQL("select @(itemid)s from pubsub_item where "
"nodeid=%(Nidx)d and publisher like %(SJID)s escape '^' "
"order by modification desc"))
of
{selected, [<<"itemid">>], RItems} ->
[ItemId || [ItemId] <- RItems];
{selected, RItems} ->
[ItemId || {ItemId} <- RItems];
_ ->
[]
end.
@@ -916,11 +934,11 @@ itemids(Nidx, SJID) ->
select_affiliation_subscriptions(Nidx, JID) ->
J = encode_jid(JID),
case catch
ejabberd_sql:sql_query_t([<<"select affiliation,subscriptions from "
"pubsub_state where nodeid='">>,
Nidx, <<"' and jid='">>, J, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(affiliation)s, @(subscriptions)s from "
" pubsub_state where nodeid=%(Nidx)d and jid=%(J)s"))
of
{selected, [<<"affiliation">>, <<"subscriptions">>], [[A, S]]} ->
{selected, [{A, S}]} ->
{decode_affiliation(A), decode_subscriptions(S)};
_ ->
{none, []}
@@ -936,33 +954,24 @@ select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
update_affiliation(Nidx, JID, Affiliation) ->
J = encode_jid(JID),
A = encode_affiliation(Affiliation),
case catch
ejabberd_sql:sql_query_t([<<"update pubsub_state set affiliation='">>,
A, <<"' where nodeid='">>, Nidx,
<<"' and jid='">>, J, <<"';">>])
of
{updated, 1} ->
ok;
_ ->
catch
ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values('">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '');">>])
end.
?SQL_UPSERT_T(
"pubsub_state",
["!nodeid=%(Nidx)d",
"!jid=%(J)s",
"affiliation=%(A)s",
"-subscriptions=''"
]).
update_subscription(Nidx, JID, Subscription) ->
J = encode_jid(JID),
S = encode_subscriptions(Subscription),
case catch
ejabberd_sql:sql_query_t([<<"update pubsub_state set subscriptions='">>, S,
<<"' where nodeid='">>, Nidx, <<"' and jid='">>, J, <<"';">>])
of
{updated, 1} ->
ok;
_ ->
catch
ejabberd_sql:sql_query_t([<<"insert into pubsub_state(nodeid, jid, affiliation, subscriptions) "
"values('">>, Nidx, <<"', '">>, J, <<"', 'n', '">>, S, <<"');">>])
end.
?SQL_UPSERT_T(
"pubsub_state",
["!nodeid=%(Nidx)d",
"!jid=%(J)s",
"subscriptions=%(S)s",
"-affiliation='n'"
]).
-spec(decode_jid/1 ::
( SJID :: binary())
@@ -1009,14 +1018,26 @@ decode_subscriptions(Subscriptions) ->
-> binary()
).
encode_jid(JID) ->
ejabberd_sql:escape(jid:to_string(JID)).
jid:to_string(JID).
-spec(encode_jid_like/1 :: (JID :: ljid()) -> binary()).
encode_jid_like(JID) ->
ejabberd_sql:escape_like_arg_circumflex(jid:to_string(JID)).
-spec(encode_host/1 ::
( Host :: host())
-> binary()
).
encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID);
encode_host(Host) -> ejabberd_sql:escape(Host).
encode_host(Host) -> Host.
-spec(encode_host_like/1 ::
( Host :: host())
-> binary()
).
encode_host_like({_U, _S, _R} = LJID) -> ejabberd_sql:escape(encode_jid_like(LJID));
encode_host_like(Host) ->
ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(Host)).
-spec(encode_affiliation/1 ::
( Arg :: atom())
@@ -1050,12 +1071,14 @@ encode_subscriptions(Subscriptions) ->
state_to_raw(Nidx, State) ->
{JID, _} = State#pubsub_state.stateid,
J = encode_jid(JID),
J = ejabberd_sql:escape(encode_jid(JID)),
A = encode_affiliation(State#pubsub_state.affiliation),
S = encode_subscriptions(State#pubsub_state.subscriptions),
[<<"'">>, Nidx, <<"', '">>, J, <<"', '">>, A, <<"', '">>, S, <<"'">>].
raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML});
raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML}) ->
JID = decode_jid(SJID),
ToTime = fun (Str) ->
[T1, T2, T3] = str:tokens(Str, <<":">>),
+4 -3
View File
@@ -33,7 +33,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -99,8 +99,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+4 -3
View File
@@ -33,7 +33,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -77,8 +77,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
+4 -3
View File
@@ -46,7 +46,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -116,8 +116,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_pep:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_pep:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_pep:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_pep:remove_extra_items(Nidx, MaxItems, ItemIds).
+4 -3
View File
@@ -14,7 +14,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -89,8 +89,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload,
PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+4 -3
View File
@@ -14,7 +14,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -89,8 +89,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
+4 -3
View File
@@ -33,7 +33,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -99,8 +99,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+4 -3
View File
@@ -37,7 +37,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -130,8 +130,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+16 -13
View File
@@ -37,7 +37,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -86,8 +86,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
{result, _} -> {result, []}
end.
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
@@ -114,20 +115,21 @@ set_affiliation(Nidx, Owner, Affiliation) ->
get_entity_subscriptions(_Host, Owner) ->
SubKey = jid:tolower(Owner),
GenKey = jid:remove_resource(SubKey),
Host = node_flat_sql:encode_host(element(2, SubKey)),
SJ = node_flat_sql:encode_jid(SubKey),
GJ = node_flat_sql:encode_jid(GenKey),
HostLike = node_flat_sql:encode_host_like(element(2, SubKey)),
SJ = ejabberd_sql:escape(node_flat_sql:encode_jid(SubKey)),
GJ = ejabberd_sql:escape(node_flat_sql:encode_jid(GenKey)),
GJLike = ejabberd_sql:escape(node_flat_sql:encode_jid_like(GenKey)),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
"subscriptions from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid and jid "
"like '">>, GJ, <<"%' and host like '%@">>, Host, <<"';">>];
"like '">>, GJLike, <<"%' escape '^' and host like '%@">>, HostLike, <<"' escape '^';">>];
_ ->
[<<"select host, node, type, i.nodeid, jid, "
"subscriptions from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid and jid "
"in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>]
"in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, HostLike, <<"' escape '^';">>]
end,
Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
@@ -149,9 +151,10 @@ get_entity_subscriptions(_Host, Owner) ->
get_entity_subscriptions_for_send_last(_Host, Owner) ->
SubKey = jid:tolower(Owner),
GenKey = jid:remove_resource(SubKey),
Host = node_flat_sql:encode_host(element(2, SubKey)),
SJ = node_flat_sql:encode_jid(SubKey),
GJ = node_flat_sql:encode_jid(GenKey),
HostLike = node_flat_sql:encode_host_like(element(2, SubKey)),
SJ = ejabberd_sql:escape(node_flat_sql:encode_jid(SubKey)),
GJ = ejabberd_sql:escape(node_flat_sql:encode_jid(GenKey)),
GJLike = ejabberd_sql:escape(node_flat_sql:encode_jid_like(GenKey)),
Query = case SubKey of
GenKey ->
[<<"select host, node, type, i.nodeid, jid, "
@@ -159,14 +162,14 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
"pubsub_node_option o where i.nodeid = n.nodeid "
"and n.nodeid = o.nodeid and name='send_last_published_item' and "
"val='on_sub_and_presence' and jid like '">>,
GJ, <<"%' and host like '%@">>, Host, <<"';">>];
GJLike, <<"%' escape '^' and host like '%@">>, HostLike, <<"' escape '^';">>];
_ ->
[<<"select host, node, type, i.nodeid, jid, "
"subscriptions from pubsub_state i, pubsub_node n, "
"pubsub_node_option o where i.nodeid = n.nodeid "
"and n.nodeid = o.nodeid and name='send_last_published_item' and "
"val='on_sub_and_presence' and jid in ",
"('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>]
"('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, HostLike, <<"' escape '^';">>]
end,
Reply = case catch ejabberd_sql:sql_query_t(Query) of
{selected,
+4 -3
View File
@@ -33,7 +33,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -101,8 +101,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+4 -3
View File
@@ -33,7 +33,7 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/6, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -101,8 +101,9 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel,
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+60 -61
View File
@@ -37,8 +37,11 @@
-behaviour(gen_pubsub_nodetree).
-author('christophe.romain@process-one.net').
-compile([{parse_transform, ejabberd_sql_pt}]).
-include("pubsub.hrl").
-include("jlib.hrl").
-include("ejabberd_sql_pt.hrl").
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
@@ -65,27 +68,27 @@ set_node(Record) when is_record(Record, pubsub_node) ->
end,
Type = Record#pubsub_node.type,
H = node_flat_sql:encode_host(Host),
N = ejabberd_sql:escape(Node),
P = ejabberd_sql:escape(Parent),
Nidx = case nodeidx(Host, Node) of
{result, OldNidx} ->
catch
ejabberd_sql:sql_query_t([<<"delete from pubsub_node_option where "
"nodeid='">>, OldNidx, <<"';">>]),
ejabberd_sql:sql_query_t(
?SQL("delete from pubsub_node_option where "
"nodeid=%(OldNidx)d")),
catch
ejabberd_sql:sql_query_t([<<"update pubsub_node set host='">>,
H, <<"' node='">>, N,
<<"' parent='">>, P,
<<"' type='">>, Type,
<<"' where nodeid='">>,
OldNidx, <<"';">>]),
ejabberd_sql:sql_query_t(
?SQL("update pubsub_node set"
" host=%(H)s"
" node=%(Node)s"
" parent=%(Parent)s"
" type=%(Type)s "
"where nodeid=%(OldNidx)d")),
OldNidx;
_ ->
catch
ejabberd_sql:sql_query_t([<<"insert into pubsub_node(host, node, "
"parent, type) values('">>,
H, <<"', '">>, N, <<"', '">>, P,
<<"', '">>, Type, <<"');">>]),
ejabberd_sql:sql_query_t(
?SQL("insert into pubsub_node(host, node, "
"parent, type) values("
"%(H)s, %(Node)s, %(Parent)s, %(Type)s)")),
case nodeidx(Host, Node) of
{result, NewNidx} -> NewNidx;
_ -> none % this should not happen
@@ -98,14 +101,12 @@ set_node(Record) when is_record(Record, pubsub_node) ->
_ ->
lists:foreach(fun ({Key, Value}) ->
SKey = iolist_to_binary(atom_to_list(Key)),
SValue = ejabberd_sql:escape(
list_to_binary(
lists:flatten(io_lib:fwrite("~p", [Value])))),
SValue = jlib:term_to_expr(Value),
catch
ejabberd_sql:sql_query_t([<<"insert into pubsub_node_option(nodeid, "
"name, val) values('">>,
Nidx, <<"', '">>,
SKey, <<"', '">>, SValue, <<"');">>])
ejabberd_sql:sql_query_t(
?SQL("insert into pubsub_node_option(nodeid, "
"name, val) values ("
"%(Nidx)d, %(SKey)s, %(SValue)s)"))
end,
Record#pubsub_node.options),
{result, Nidx}
@@ -116,14 +117,12 @@ get_node(Host, Node, _From) ->
get_node(Host, Node) ->
H = node_flat_sql:encode_host(Host),
N = ejabberd_sql:escape(Node),
case catch
ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>,
H, <<"' and node='">>, N, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from "
"pubsub_node where host=%(H)s and node=%(Node)s"))
of
{selected,
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], [RItem]} ->
{selected, [RItem]} ->
raw_to_node(Host, RItem);
{'EXIT', _Reason} ->
{error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
@@ -133,13 +132,12 @@ get_node(Host, Node) ->
get_node(Nidx) ->
case catch
ejabberd_sql:sql_query_t([<<"select host, node, parent, type from "
"pubsub_node where nodeid='">>,
Nidx, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(host)s, @(node)s, @(parent)s, @(type)s from "
"pubsub_node where nodeid=%(Nidx)d"))
of
{selected,
[<<"host">>, <<"node">>, <<"parent">>, <<"type">>], [[Host, Node, Parent, Type]]} ->
raw_to_node(Host, [Node, Parent, Type, Nidx]);
{selected, [{Host, Node, Parent, Type}]} ->
raw_to_node(Host, {Node, Parent, Type, Nidx});
{'EXIT', _Reason} ->
{error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
_ ->
@@ -152,11 +150,11 @@ get_nodes(Host, _From) ->
get_nodes(Host) ->
H = node_flat_sql:encode_host(Host),
case catch
ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>, H, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from "
"pubsub_node where host=%(H)s"))
of
{selected,
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} ->
{selected, RItems} ->
[raw_to_node(Host, Item) || Item <- RItems];
_ ->
[]
@@ -178,14 +176,12 @@ get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node) ->
H = node_flat_sql:encode_host(Host),
N = ejabberd_sql:escape(Node),
case catch
ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>,
H, <<"' and parent='">>, N, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from "
"pubsub_node where host=%(H)s and parent=%(Node)s"))
of
{selected,
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} ->
{selected, RItems} ->
[raw_to_node(Host, Item) || Item <- RItems];
_ ->
[]
@@ -196,14 +192,14 @@ get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node) ->
H = node_flat_sql:encode_host(Host),
N = ejabberd_sql:escape(Node),
N = <<(ejabberd_sql:escape_like_arg_circumflex(Node))/binary, "%">>,
case catch
ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from "
"pubsub_node where host='">>,
H, <<"' and node like '">>, N, <<"%';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(type)s, @(nodeid)d from "
"pubsub_node where host=%(H)s"
" and node like %(N)s escape '^'"))
of
{selected,
[<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} ->
{selected, RItems} ->
[raw_to_node(Host, Item) || Item <- RItems];
_ ->
[]
@@ -256,20 +252,24 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
delete_node(Host, Node) ->
H = node_flat_sql:encode_host(Host),
N = ejabberd_sql:escape(Node),
N = <<(ejabberd_sql:escape_like_arg_circumflex(Node))/binary, "%">>,
Removed = get_subnodes_tree(Host, Node),
catch ejabberd_sql:sql_query_t([<<"delete from pubsub_node where host='">>,
H, <<"' and node like '">>, N, <<"%';">>]),
catch ejabberd_sql:sql_query_t(
?SQL("delete from pubsub_node where host=%(H)s"
" and node like %(N)s escape '^'")),
Removed.
%% helpers
raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
raw_to_node(Host, {Node, Parent, Type, binary_to_integer(Nidx)});
raw_to_node(Host, {Node, Parent, Type, Nidx}) ->
Options = case catch
ejabberd_sql:sql_query_t([<<"select name,val from pubsub_node_option "
"where nodeid='">>, Nidx, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(name)s, @(val)s from pubsub_node_option "
"where nodeid=%(Nidx)d"))
of
{selected, [<<"name">>, <<"val">>], ROptions} ->
DbOpts = lists:map(fun ([Key, Value]) ->
{selected, ROptions} ->
DbOpts = lists:map(fun ({Key, Value}) ->
RKey = jlib:binary_to_atom(Key),
Tokens = element(2, erl_scan:string(binary_to_list(<<Value/binary, ".">>))),
RValue = element(2, erl_parse:parse_term(Tokens)),
@@ -295,13 +295,12 @@ raw_to_node(Host, [Node, Parent, Type, Nidx]) ->
nodeidx(Host, Node) ->
H = node_flat_sql:encode_host(Host),
N = ejabberd_sql:escape(Node),
case catch
ejabberd_sql:sql_query_t([<<"select nodeid from pubsub_node where "
"host='">>,
H, <<"' and node='">>, N, <<"';">>])
ejabberd_sql:sql_query_t(
?SQL("select @(nodeid)d from pubsub_node where "
"host=%(H)s and node=%(Node)s"))
of
{selected, [<<"nodeid">>], [[Nidx]]} ->
{selected, [{Nidx}]} ->
{result, Nidx};
{'EXIT', _Reason} ->
{error, db_fail};
+5 -1
View File
@@ -124,9 +124,13 @@ update(#maxrate{} = State, Size) ->
true -> 0
end,
NextNow = p1_time_compat:system_time(micro_seconds) + Pause * 1000,
Div = case NextNow - State#maxrate.lasttime of
0 -> 1;
V -> V
end,
{State#maxrate{lastrate =
(State#maxrate.lastrate +
1000000 * Size / (NextNow - State#maxrate.lasttime))
1000000 * Size / Div)
/ 2,
lasttime = NextNow},
Pause}.
+36 -34
View File
@@ -42,7 +42,7 @@
get_roster_groups/3, del_user_roster_t/2,
get_roster_by_jid/3, get_rostergroup_by_jid/3,
del_roster/3, del_roster_sql/2, update_roster/5,
update_roster_sql/4, roster_subscribe/1,
update_roster_sql/2, roster_subscribe/1,
get_subscription/3, set_private_data/4,
set_private_data_sql/3, get_private_data/3,
get_private_data/2, del_user_private_storage/2,
@@ -231,12 +231,12 @@ list_users(LServer,
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and
is_integer(Offset) ->
SPrefix = ejabberd_sql:escape_like_arg(Prefix),
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
SPrefix2 = <<SPrefix/binary, $%>>,
ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s from users "
"where username like %(SPrefix2)s "
"where username like %(SPrefix2)s escape '^' "
"order by username "
"limit %(Limit)d offset %(Offset)d")).
@@ -264,18 +264,17 @@ users_number(LServer) ->
users_number(LServer, [{prefix, Prefix}])
when is_binary(Prefix) ->
SPrefix = ejabberd_sql:escape_like_arg(Prefix),
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
SPrefix2 = <<SPrefix/binary, $%>>,
ejabberd_sql:sql_query(
LServer,
?SQL("select @(count(*))d from users "
"where username like %(SPrefix2)s"));
"where username like %(SPrefix2)s escape '^'"));
users_number(LServer, []) ->
users_number(LServer).
add_spool_sql(Username, XML) ->
[<<"insert into spool(username, xml) values ('">>,
Username, <<"', '">>, XML, <<"');">>].
add_spool_sql(LUser, XML) ->
?SQL("insert into spool(username, xml) values (%(LUser)s, %(XML)s)").
add_spool(LServer, Queries) ->
ejabberd_sql:sql_transaction(LServer, Queries).
@@ -368,24 +367,27 @@ update_roster(_LServer, LUser, SJID, ItemVals,
end,
ItemGroups).
update_roster_sql(Username, SJID, ItemVals,
update_roster_sql({LUser, SJID, Name, SSubscription, SAsk, AskMessage},
ItemGroups) ->
[[<<"delete from rosterusers where "
"username='">>,
Username, <<"' and jid='">>, SJID, <<"';">>],
[<<"insert into rosterusers( "
" username, jid, nick, "
" subscription, ask, askmessage, "
" server, subscribe, type) "
"values ('">>,
join(ItemVals, <<"', '">>), <<"');">>],
[<<"delete from rostergroups where "
"username='">>,
Username, <<"' and jid='">>, SJID, <<"';">>]]
[?SQL("delete from rosterusers where"
" username=%(LUser)s and jid=%(SJID)s;"),
?SQL("insert into rosterusers("
" username, jid, nick,"
" subscription, ask, askmessage,"
" server, subscribe, type) "
"values ("
"%(LUser)s, "
"%(SJID)s, "
"%(Name)s, "
"%(SSubscription)s, "
"%(SAsk)s, "
"%(AskMessage)s, "
"'N', '', 'item');"),
?SQL("delete from rostergroups where"
" username=%(LUser)s and jid=%(SJID)s;")]
++
[[<<"insert into rostergroups( "
" username, jid, grp) values ('">>,
join(ItemGroup, <<"', '">>), <<"');">>]
[?SQL("insert into rostergroups(username, jid, grp) "
"values (%(LUser)s, %(SJID)s, %(ItemGroup)s)")
|| ItemGroup <- ItemGroups].
roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) ->
@@ -414,13 +416,12 @@ set_private_data(_LServer, LUser, XMLNS, SData) ->
"!namespace=%(XMLNS)s",
"data=%(SData)s"]).
set_private_data_sql(Username, LXMLNS, SData) ->
[[<<"delete from private_storage where username='">>,
Username, <<"' and namespace='">>, LXMLNS, <<"';">>],
[<<"insert into private_storage(username, "
"namespace, data) values ('">>,
Username, <<"', '">>, LXMLNS, <<"', '">>, SData,
<<"');">>]].
set_private_data_sql(LUser, XMLNS, SData) ->
[?SQL("delete from private_storage where"
" username=%(LUser)s and namespace=%(XMLNS)s;"),
?SQL("insert into private_storage(username, "
"namespace, data) values ("
"%(LUser)s, %(XMLNS)s, %(SData)s);")].
get_private_data(LServer, LUser, XMLNS) ->
ejabberd_sql:sql_query(
@@ -628,9 +629,10 @@ get_roster_version(LServer, LUser) ->
" where username = %(LUser)s")).
set_roster_version(LUser, Version) ->
update_t(<<"roster_version">>,
[<<"username">>, <<"version">>], [LUser, Version],
[<<"username = '">>, LUser, <<"'">>]).
?SQL_UPSERT_T(
"roster_version",
["!username=%(LUser)s",
"version=%(Version)s"]).
opt_type(sql_type) ->
fun (pgsql) -> pgsql;

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