Compare commits

..

173 Commits

Author SHA1 Message Date
Christophe Romain 405a0a21c1 Merge pull request #1249 from weiss:push-requirements 2016-09-13 14:31:39 +02:00
Christophe Romain c39501a48d Merge branch 'push-requirements' of https://github.com/weiss/ejabberd into weiss-push-requirements 2016-09-13 14:29:14 +02:00
Christophe Romain c3543e002d Allow to create room with custom config 2016-09-13 11:52:59 +02:00
Paweł Chmielowski 2f596b0e10 Expand parsing of json input to be able to handle update_roster command 2016-09-13 11:27:59 +02:00
Paweł Chmielowski 054382f074 Add X-Admin and basic auth header to CORS allowed headers in http_api 2016-09-12 15:40:38 +02:00
Paweł Chmielowski 96d05dad8f Properly process OPTIONS header in http_api for all paths 2016-09-12 15:40:38 +02:00
Evgeniy Khramtsov 1aca541639 Fix nick-to-jid mapping for MUC subscribers 2016-09-12 14:41:33 +03:00
Badlop d0761039ff Support multiple room invitations (#1285) 2016-09-09 12:18:27 +02:00
Evgeniy Khramtsov fe1bf27ef3 Fix subscribed rooms list retreivement 2016-09-09 13:04:47 +03:00
Holger Weiss d222fed228 XEP-0198: Cancel timer when waiting for resumption
If an ACK timer is active while going into the 'wait_for_resume' state,
cancel that timer.
2016-09-09 00:21:36 +02:00
Holger Weiss 8fd888eb2b Revert "Don't log an [error] message if Elixir is missing"
This reverts commit 41386d718d.  The issue
was fixed with commit 4bd45bada7.
2016-09-08 18:11:54 +02:00
Holger Weiss 41386d718d Don't log an [error] message if Elixir is missing
The Elixir support is still optional.

Closes #1250.
2016-09-08 17:59:40 +02:00
Christophe Romain 26a040e2d5 Fix typo on previous commit (#1284) 2016-09-08 16:32:16 +02:00
Paweł Chmielowski 4bd45bada7 Start elixir config code only if elixir was enabled in configure script 2016-09-08 16:29:45 +02:00
Christophe Romain ad39da0b0a Full jid entity subscriptions should include bare jid records (#1284) 2016-09-08 16:27:16 +02:00
Evgeniy Khramtsov 36ab9cc2ea Fix message routing from subscribers 2016-09-08 16:39:34 +03:00
Paweł Chmielowski 6c943aa293 Merge pull request #1287 from weiss/ack-timeout
New stream management option: ack_timeout
2016-09-08 12:45:16 +02:00
gabrielgatu 803270fc6b Support for Elixir configuration file #1208
Contribution for Google Summer of code 2016 by Gabriel Gatu
2016-09-08 11:37:14 +02:00
Mickael Remond e6f7233351 Support for publishing to hex.pm with latest Elixir mix 2016-09-08 10:52:43 +02:00
Mickael Remond 58a72bd395 Fix compilation with Elixir 1.3 / R19 2016-09-08 10:29:44 +02:00
Paweł Chmielowski d2621130a3 Typo 2016-09-08 10:27:14 +02:00
Paweł Chmielowski a8368278ec Properly normalize resource_regexp acl rule
This fixes issue #1288.
2016-09-08 08:45:10 +02:00
Holger Weiss 621f0e2b7c New stream management option: ack_timeout
Close the connection if a stream management client fails to respond to
an acknowledgement request within 60 seconds.  This number of seconds
can be changed with the new "ack_timeout" option, and the mechanism can
be disabled by specifying 'infinity'.

As a side effect of this change, a new acknowledgement is no longer
requested before the response to the previous request is received.
2016-09-07 23:16:54 +02:00
Christophe Romain 7a538bb88b Enforce pathtype use with config path (#1264) 2016-09-07 17:38:35 +02:00
Christophe Romain af0a493c66 Merge pull request #1253 from Amuhar/xep0356 2016-09-07 14:34:31 +02:00
Evgeniy Khramtsov f304149615 Create room on configuration request as per XEP-0045, 10.1.3 2016-09-07 11:15:19 +03:00
Evgeniy Khramtsov 3803a8de3c Link MUC subscription to bare JID 2016-09-07 10:33:37 +03:00
Holger Weiss 1edca899ff Add SQL support for microblogging node plugin 2016-09-07 07:15:12 +02:00
Evgeniy Khramtsov c6afb9731b Handle <subscriptions/> request to list MUC subscribers 2016-09-06 19:06:02 +03:00
Evgeniy Khramtsov 5ec2874a96 Do not update muc_online_users table on MUC/Sub operations 2016-09-06 18:17:30 +03:00
Evgeniy Khramtsov 417284a921 Add get_subscribers command to list MUC subscribers 2016-09-06 17:55:18 +03:00
Holger Weiss af2999a783 node_mb: Call node_pep instead of node_hometree 2016-09-06 00:30:46 +02:00
Holger Weiss 48ce34987d node_mb: Fix configuration documentation
A node plugin used in a 'pep_mapping' must explicitly be added to the
list of 'plugins'.
2016-09-06 00:08:43 +02:00
Holger Weiss e29f47893f mod_pubsub: Remove outdated comment 2016-09-06 00:05:54 +02:00
Paweł Chmielowski c770a54aac Clean ejabberd_commands before tests 2016-09-05 17:18:27 +02:00
Paweł Chmielowski 96a748d34f ejabberd_oauth requiere working cache_tab, initialize it before tests 2016-09-05 16:42:05 +02:00
Holger Weiss 31592fe51f Merge remote-tracking branch 'processone/pr/1262'
* processone/pr/1262:
  Fixed typo in  Stream Management option name
2016-08-22 22:35:17 +02:00
Igor Manturov Jr f1afea223b Fixed typo in Stream Management option name 2016-08-23 02:21:09 +06:00
Holger Weiss 1bfa1c613b Merge remote-tracking branch 'processone/pr/1261'
* processone/pr/1261:
  Fixed #1260 Stream Management feature for the websocket connections
2016-08-22 21:22:08 +02:00
Igor Manturov Jr d5659735b3 Fixed #1260 Stream Management feature for the websocket connections 2016-08-23 00:59:39 +06:00
Holger Weiss 23d9fb0592 mod_muc_admin: Accept 'allow_subscription' option
Allow for setting the new 'allow_subscription' option using the
'change_room_option' command.
2016-08-22 01:17:32 +02:00
badlop 8dd2044a27 Merge pull request #1254 from lemenkov/remove_p1_xmlrpc
Remove no longer necessary p1_xmlrpc
2016-08-19 11:53:20 +02:00
Peter Lemenkov e13edff6ae Remove no longer necessary p1_xmlrpc
Ths module was superceded by fast_xml in commit
processone/ejabberd@dfc29ea03c. So let's remove it entirely to
avoid any confusion.

Signed-off-by: Peter Lemenkov <lemenkov@gmail.com>
2016-08-17 18:32:09 +03:00
Badlop 8af85d913f Erlang R17 has a Time limit in erlang:send_after (#1246) 2016-08-16 18:32:06 +02:00
Holger Weiss 20a510d877 mod_mam: Add 'store_mam_message' hook
The new 'store_mam_message' hook is invoked whenever a MAM message is
stored.
2016-08-15 23:28:36 +02:00
Holger Weiss 8821cf8b27 mod_offline: Add 'store_offline_message' hook
The new 'store_offline_message' hook is invoked whenever an offline
message is stored.
2016-08-15 22:30:08 +02:00
Holger Weiss 4d19fb518f ejabberd_c2s: Add XEP-0198 resumption hooks
The new 'c2s_session_pending' and 'c2s_session_resumed' hooks are
invoked when a stream management session is pending and resumed,
respectively.
2016-08-15 21:49:58 +02:00
Holger Weiss e7217e6320 Add functions to get/set some c2s state elements 2016-08-15 20:24:43 +02:00
Badlop 5b4f347da8 Support sql backend in mod_shared_roster commands (#1244) 2016-08-15 15:53:35 +02:00
Evgeny Khramtsov 38666cfd58 Merge pull request #1245 from hamano/clean_redis_table
fix clean redis table
2016-08-15 15:12:20 +04:00
HAMANO Tsukasa 877d0752e2 fix clean redis table 2016-08-15 17:51:06 +09:00
Badlop 0ab08f4eeb Don't delete ejabberd_commands table, it's problematic in cluster (#1210) 2016-08-14 20:35:25 +02:00
Holger Weiss 4ee8af633b Store announcements for offline users
Add a <store/> hint to announcements (unless they are explicitly sent to
online users).  Without that hint, announcements weren't delivered to
offline users, since they are sent as messages of type "headline".
2016-08-13 00:07:27 +02:00
Holger Weiss bf9d6b5534 Honor <store/> hint for any non-"error" message
XEP-0334 says: "A message containing the <store/> hint that is not of
type 'error' SHOULD be stored by the entity."
2016-08-12 21:13:10 +02:00
Holger Weiss 28dde294e5 mod_mam: Don't store messages of type "headline"
XEP-0313 says: "a server SHOULD include in a user archive all of the
messages a user sends or receives of type 'normal' or 'chat' that
contain a <body> element."
2016-08-12 20:38:17 +02:00
Evgeniy Khramtsov ffba664f2c Add a requirement for full JID in subscribe_room command 2016-08-11 17:13:20 +03:00
Paweł Chmielowski 50596dc4d3 Provide proper args_desc in oauth_issue_token command 2016-08-10 11:26:04 +02:00
Paweł Chmielowski e63fe5c216 Fix result type of subscribe_room command 2016-08-10 11:22:19 +02:00
Evgeniy Khramtsov 1fc58ace2f Add commands for MUC subscriptions management 2016-08-09 13:36:43 +03:00
Mickael Remond c4b14d045a Update to released version 2016-08-07 18:31:55 +02:00
Mickael Remond 9c6ee60f1a Update moka dependency 2016-08-07 18:28:14 +02:00
Mickael Remond efc744092b We do not force yet access rules check on register command 2016-08-07 18:27:16 +02:00
Mickael Remond a0c8012c66 Do not force command line to pass a credentials 2016-08-07 18:24:08 +02:00
Holger Weiss b62aa3d2dc mod_client_state: Let other modules filter stanzas
Don't stop execution of the 'csi_filter_stanza' hook if mod_client_state
won't queue the stanza.
2016-08-06 13:36:27 +02:00
Holger Weiss 91e26fbf7a Add user's JID to CSI hook arguments
Add the JID of the CSI user to the arguments of the 'csi_filter_stanza'
and 'csi_flush_queue' hooks.
2016-08-05 23:47:18 +02:00
Holger Weiss c2ef55a075 Cosmetic change: Set CSI state 'active' on resume
The CSI state is always set to 'active' when a stream management session
is resumed; so there's no need to apply the CSI state of the old c2s
process, first.
2016-08-05 20:09:52 +02:00
Paweł Chmielowski d969e917c6 Use newer samerlib/moka 2016-08-05 13:03:22 +02:00
Holger Weiss 9a5f0751be mod_mam: Simplify "assume_mam_usage" option
The "assume_mam_usage" option now takes a boolean value.  Setting it to
"true" has the same effect as "if_enabled" had before.  The "on_request"
behavior is no longer offered, as it made the option (and its
documentation) overly complex.
2016-08-05 01:57:01 +02:00
Christophe Romain 72b0fb49e8 Fix type convertion bug injected by 4ccc40b (#1229) 2016-08-04 09:49:23 +02:00
Alexey Shchepin 111aa83f5e Add tokens cache to ejabberd_oauth 2016-08-04 01:59:28 +03:00
Holger Weiss 78fa9e08a5 XEP-0198: Handle timeouts during stream resumption
If session resumption failed because requesting the #state from the old
c2s process took too long, the new c2s process will usually receive the
response.  Let the new process handle that case gracefully.
2016-08-03 02:28:46 +02:00
Holger Weiss 3c1e4f0dfd XEP-0198: Increase timeout for stream resumption
During stream resumption, the #state is transferred from the old c2s
process to the new one.  This is usually very fast, but under certain
conditions, it can take longer than five seconds.
2016-08-03 02:15:15 +02:00
Alexey Shchepin 4add262090 Add OAUTH SQL backend 2016-08-01 16:55:43 +03:00
Mickael Remond 76eba3647a Implement gen_mod callback in ModPresenceDemo module 2016-08-01 15:46:14 +02:00
Mickaël Rémond 2ef58a33a9 Merge pull request #1223 from processone/expand_api
More API fixes and improvements
2016-08-01 15:36:47 +02:00
Mickael Remond d02d7b2b6a Remove compile warning 2016-08-01 15:35:54 +02:00
Mickael Remond 90ea3ca361 Improve error message when try to call api on api root 2016-08-01 15:29:47 +02:00
Christophe Romain bf45c9eeee Switch mix worker to transient 2016-08-01 14:09:16 +02:00
Christophe Romain a9c6748ec7 Add missing comas in sql statement (#1219) 2016-08-01 10:55:02 +02:00
Mickael Remond 4982639d05 Fix error return expectation in command test 2016-08-01 09:28:54 +02:00
Mickael Remond c5c394e929 Fix HTTP process return formatting 2016-08-01 08:58:49 +02:00
Mickael Remond 6ea7153e31 Improve error handling 2016-07-31 22:48:24 +02:00
Mickael Remond 2a49f8cae7 Change name of result key for offline count to value
This is more user friendly and should be more consistent with other commands.
2016-07-30 20:12:04 +02:00
Mickael Remond 674a8039ef Add support for sending back missing scope error to API ReST command calls 2016-07-30 18:51:54 +02:00
Mickael Remond 4bf8ce7681 Make s2s stats commands more robust 2016-07-30 18:50:58 +02:00
Mickael Remond 19ad6e6145 Ensure ejabberdctl status result is in valid shell supported range 2016-07-30 13:18:39 +02:00
Mickael Remond 39640b67c7 Add support for rich error reporting for API 2016-07-30 13:08:30 +02:00
Mickael Remond fb2603d3cd Return 409 conflict error code on register if user already exists 2016-07-30 11:50:04 +02:00
Mickaël Rémond 4a49dfecf3 Merge pull request #1221 from processone/expand_api
Do not crash on check when we do not have JID
2016-07-30 10:55:39 +02:00
Mickael Remond 42e6f72ee9 Do not crash on check when we do not have JID 2016-07-29 20:38:05 +02:00
Christophe Romain 3c58a93eb8 Merge pull request #1178 from candrews/patch-1
Harden the systemd unit
2016-07-29 11:33:32 +02:00
Christophe Romain a080322055 Switch workers from temporary to transient 2016-07-29 11:18:42 +02:00
Paweł Chmielowski fd365b2893 Display data that is send to websocket connection in debug log level
This should help with detecting problems like in #1097
2016-07-28 16:20:28 +02:00
Christophe Romain fad088a3c4 Merge pull request #1193 from gabrielgatu/support-elixir-module-installer
Fix issue #625: Writing Elixir modules
2016-07-28 16:06:12 +02:00
gabrielgatu 91865c66c0 Start elixir application after ejabberd_app:start_apps() 2016-07-28 15:57:35 +02:00
Mickaël Rémond 7a74a4836a Merge pull request #1211 from processone/expand_api
There is still work to do, be we reached a stable state and can merge up to this point.
2016-07-28 14:57:48 +02:00
Holger Weiss 72445bb374 mod_http_upload_quota: Apply cosmetic changes
Use "fun f/1" syntax in place of "fun(X) -> f(X) end".
2016-07-27 00:28:47 +02:00
Evgeniy Khramtsov 984c4cf6bd Add 'allow_subscription' MUC configuration option 2016-07-26 14:37:28 +03:00
Mickael Remond 2a8005e47f Add ability to run test with Elixir mix 2016-07-26 12:17:37 +02:00
Mickael Remond 7781f39b74 Clarify command module API 2016-07-26 12:15:03 +02:00
Mickael Remond e5fd1ee4f6 Avoid starting several time the owner process 2016-07-26 12:12:48 +02:00
Mickael Remond 9ff7257287 Make jlib ETS table more resilient 2016-07-26 11:58:14 +02:00
Mickael Remond 12f74b4aa7 Fix list appending bug 2016-07-26 11:57:38 +02:00
Mickael Remond fede85c9bd Remove unused import 2016-07-26 11:53:34 +02:00
Alexey Shchepin 839490b0d9 Add DB backend support for ejabberd_oauth 2016-07-25 20:08:30 +03:00
Mickael Remond dbc0498279 Fix tests, command need to be properly added to list of exposed commands 2016-07-25 18:28:40 +02:00
Mickael Remond c183092aa4 Simplify code for command policy group expansion 2016-07-25 18:28:05 +02:00
Badlop 5d4f8bcf0d Export acl:parse_ip_netmask/1 for mod_rest (ejabberd-contrib#175) 2016-07-25 16:57:05 +02:00
Mickael Remond d7ad99f147 Initial attempt on access on commands
May change and will require more work / test / refactor
2016-07-25 11:43:49 +02:00
Holger Weiss 4b0d71d402 Don't return error for blocked MUC PMs
If a message stanza is blocked as per XEP-0016 or XEP-0191 and the
stanza is marked as a private MUC message, don't return an error.  This
makes sure users won't be kicked from MUC rooms when blocking other
participants.
2016-07-24 20:55:11 +02:00
Mickael Remond b4a430541d Return more user friendly, human readable error description 2016-07-24 14:10:12 +02:00
Mickael Remond bfa61eaa46 Make default OAuth token TTL values more user friendly 2016-07-23 18:57:57 +02:00
Mickael Remond 68555ff466 Add support for checking access rules conformance for commands 2016-07-23 18:21:45 +02:00
Mickael Remond caf2c20210 Error when not authorized should be 403 2016-07-23 18:21:45 +02:00
Mickael Remond 1485b56211 Move any access rules check to ACL module 2016-07-23 18:21:45 +02:00
Mickael Remond 2c70c572c8 Clean-up of error codes and format json structure 2016-07-23 18:21:45 +02:00
Holger Weiss d4d1941133 XEP-0198: Log debug message when dropping stanza
Log a debug message when an unacknowledged message is neither resent nor
bounced because it's archived.
2016-07-23 01:23:24 +02:00
Holger Weiss 814b80c644 Preserve PID for offline sessions
Don't set the PID to 'undefined' when a session goes offline, as this
looses the information which node created the session table entry.

Fixes #1196.
2016-07-23 01:08:05 +02:00
Pablo Polvorin 4332dddbc4 Support oauth password grant type
As in https://tools.ietf.org/html/rfc6749#section-4.3
2016-07-22 19:17:12 -03:00
Pablo Polvorin 57aeef74d5 stringprep might already be started
Depending on the way the test us ran
(full test suite or the elixir quicktest one)
the stringprep might already be loaded.
2016-07-22 19:15:56 -03:00
Pablo Polvorin 12b58b9870 Fix elixir test case: stringprep was required 2016-07-22 16:25:54 -03:00
Pablo Polvorin caf7b54305 oauth: single jid field instead of username/password fields 2016-07-22 15:37:48 -03:00
Badlop c5d9d35e7b Convert password provided by web form to UTF8 before passing it (#375) 2016-07-22 16:52:13 +02:00
Jerome Sautret ffbe97d988 Quote postgresql database name (#1136) 2016-07-22 16:33:40 +02:00
Paweł Chmielowski bdfef09c0f Fix handling of complex values as arguments in http_api 2016-07-22 15:26:27 +02:00
Evgeny Khramtsov dd38bef8b1 Merge pull request #1201 from xmppjingle/master
External Component Connection Hooks
2016-07-22 00:35:43 +04:00
xmppjingle 6983dfa21f External Component Hook
Changed Hook Trigger Event and included a Reason upon
component_disconnected/2 Hook
2016-07-21 14:03:01 -03:00
Pablo Polvorin cbfab687e8 Oauth callback must pass expires_in as ttl instead of epoch 2016-07-20 14:47:11 -03:00
Paweł Chmielowski c2753cd51c Use different version of elixir depending on erlang version 2016-07-20 10:12:00 +02:00
Paweł Chmielowski 5458d8bfcb Add else branch to if_version_{above,below} 2016-07-20 10:11:34 +02:00
Paweł Chmielowski 7748dd4e5d Make processing of if_* clauses in rebar.config recursive 2016-07-20 10:11:08 +02:00
Pablo Polvorin 0c0c6465ba Fix test for changes in oauth expiry 2016-07-19 20:36:02 -03:00
Pablo Polvorin b5a90be3cb Merge branch 'master' of github.com:processone/ejabberd 2016-07-19 20:19:17 -03:00
Pablo Polvorin 1d317e8068 Let user choose the desired oauth token TTL 2016-07-19 20:18:07 -03:00
Holger Weiss 8f8c499cfa mod_mam: Fix handling of result set page limit
Restore function clause for handling a client-specified result set page
limit that doesn't exceed mod_mam's upper threshold.
2016-07-19 21:23:30 +02:00
Holger Weiss 9fcb81dea9 mod_mam: Always limit result set page size
Limit the number of messages returned for a given MAM request even if
the client didn't specify an RSM set (not just if the client specified
an RSM set without a limit).

This is still not done for MAM v0.2 requests though, as that version of
the XEP doesn't require clients to support RSM.
2016-07-19 21:08:13 +02:00
Mickael Remond 490a758050 Upgrade Elixir version to 1.2 in rebar config
This matches the version used in mix.exs
2016-07-19 13:05:01 +02:00
Mickael Remond f79ac6874e Lock relx version as newer version does not compile fine 2016-07-19 12:35:45 +02:00
Paweł Chmielowski 655cbf6055 Make access rules in ejabberd_web_admin configurable 2016-07-19 11:27:45 +02:00
Pablo Polvorin 483ef09263 Fix command argument formatting 2016-07-19 00:51:04 -03:00
Pablo Polvorin 33e0283f0d Add 'ejabberd:user' and 'ejabberd:admin' oauth scopes
'ejabberd:user' includes all commands defined with policy "user".
'ejabberd:admin' includes commands defined with policy "admin".
2016-07-19 00:24:06 -03:00
Pablo Polvorin 673a654c47 Fix ce0d1704c6
Original request was to allow ejabberd sysadmin to generate
tokens for specific users.  JIDs must not be passed as argument
when requesting the tokens.
2016-07-18 20:25:23 -03:00
xmppjingle 48c88b61b6 Merge remote-tracking branch 'processone/master' 2016-07-18 17:55:31 -03:00
xmppjingle fca2f24231 External Component Connection Hooks 2016-07-18 17:55:10 -03:00
Holger Weiss 8bc3dc9c49 jlib: Don't try to keep just one <delay/> tag
It seems unclear whether XEP-0203 really mandates that stanzas may not
have multiple <delay/> tags.  Editing/removing existing tags doesn't
seem worth the effort, especially as we'd have to take more care which
tag to keep if the stanza already has more than one.
2016-07-18 22:31:08 +02:00
Holger Weiss 749033598d Omit [info] message with number of queued stanzas
Just log a debug message if a stream management session times out and
some stanzas weren't acknowledged.
2016-07-10 22:21:57 +02:00
Evgeniy Khramtsov f6e960d326 Fix compilation error 2016-07-10 08:45:24 +03:00
Evgeniy Khramtsov 786bd4f26c Use hooks instead of direct calls to mod_mam 2016-07-09 12:43:01 +03:00
Holger Weiss 5f48d2641b mod_http_upload_quota: Depend on mod_http_upload
mod_http_upload_quota uses mod_http_upload's "docroot" option, so the
mod_http_upload configuration must be parsed, first.  Fixes #1025.
2016-07-08 20:47:02 +02:00
Mickael Remond 1a62d4e04b Update stringprep and iconv 2016-07-08 18:28:51 +02:00
Christophe Romain 6b38d19085 Do send last items only for subscription on current plugin type 2016-07-08 15:18:39 +02:00
Evgeniy Khramtsov 661b041302 Rename MUC/Sub's namespace 2016-07-08 15:07:26 +03:00
Evgeniy Khramtsov 368b202144 Handle MUC/Sub subscriptions list request 2016-07-08 15:07:10 +03:00
Evgeniy Khramtsov caaf02eaa0 Advertise MUC/Sub support in MUC service disco#info 2016-07-08 15:06:08 +03:00
Evgeniy Khramtsov 32de9a56a5 Experimental MUC/Sub support 2016-07-08 15:05:50 +03:00
Mickael Remond febbc2bb5a Update dependencies 2016-07-08 11:40:28 +02:00
Evgeniy Khramtsov 71f27ee7d4 Get rid of warnings 2016-07-07 12:17:38 +03:00
Evgeniy Khramtsov c718cbbd9f Warn on cyclic modules dependencies 2016-07-07 11:34:17 +03:00
Badlop 12c0d888b1 Revert "Recover fix of 907e239 lost in 9deb294 (thanks to Alexey Shchepin)" (#1183)
This reverts commit 53f3a45803.
2016-07-06 17:54:37 +02:00
Evgeniy Khramtsov 4220a2b98c Make modules loading in a dependent order (#1191) 2016-07-06 14:58:48 +03:00
Christophe Romain de9f80f2ce Add missing '/' for jid matching from commit e300f80 2016-07-06 10:06:17 +02:00
Alexey Shchepin be3a4acb55 Fix missed escaping in node_flat_sql.erl 2016-07-05 17:45:37 +03:00
Christophe Romain 3820aaa421 Quote reserver 'type' keyword for pgsql to fix e300f80 2016-07-05 16:16:40 +02:00
Christophe Romain e300f8095d Fix use of like parameter in sql pubsub's requests 2016-07-05 15:43:59 +02:00
gabrielgatu b31c0d9e2e Support elixir module installer 2016-07-05 12:36:49 +02:00
Holger Weiss 8e04a7ef4d mod_configure: Fix editing of access rules 2016-07-03 22:58:54 +02:00
Paweł Chmielowski 16b1d8541a Grab new p1_utils that has fix for R19 2016-07-01 21:41:12 +02:00
Paweł Chmielowski 0737958b45 Fix compilation issues on R19 2016-07-01 21:20:10 +02:00
Mickael Remond 024124decb Fix dependencies when using Elixir Mix 2016-06-30 11:35:42 +02:00
Mickael Remond 88ac1dc56b Update dependencies 2016-06-30 11:33:38 +02:00
Holger Weiss 8be1d49961 mod_mam_mnesia: Force garbage collection
The VM fails to collect the garbage generated during MAM lookups
automatically, so mod_mam_mnesia's memory usage easily goes up to
several gigabytes if we don't force garbage collection.
2016-06-29 22:32:59 +02:00
Holger Weiss 10d4c16a97 mod_client_state: Throttle PEP stanzas by default 2016-06-29 22:22:49 +02:00
Craig Andrews 2e28d06744 Harden the systemd unit
Restrict capabilities, have a private tmp directory, private /dev, and don't accessing file system locations that really shouldn't be accessed.
2016-06-28 17:02:41 -04:00
141 changed files with 6467 additions and 1982 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ use Mix.Config
config :ejabberd,
file: "config/ejabberd.yml",
log_path: 'log/ejabberd.log'
# Customize Mnesia directory:
config :mnesia,
dir: 'mnesiadb/'
+169
View File
@@ -0,0 +1,169 @@
defmodule Ejabberd.ConfigFile do
use Ejabberd.Config
def start do
[loglevel: 4,
log_rotate_size: 10485760,
log_rotate_date: "",
log_rotate_count: 1,
log_rate_limit: 100,
auth_method: :internal,
max_fsm_queue: 1000,
language: "en",
allow_contrib_modules: true,
hosts: ["localhost"],
shaper: shaper,
acl: acl,
access: access]
end
defp shaper do
[normal: 1000,
fast: 50000,
max_fsm_queue: 1000]
end
defp acl do
[local:
[user_regexp: "", loopback: [ip: "127.0.0.0/8"]]]
end
defp access do
[max_user_sessions: [all: 10],
max_user_offline_messages: [admin: 5000, all: 100],
local: [local: :allow],
c2s: [blocked: :deny, all: :allow],
c2s_shaper: [admin: :none, all: :normal],
s2s_shaper: [all: :fast],
announce: [admin: :allow],
configure: [admin: :allow],
muc_admin: [admin: :allow],
muc_create: [local: :allow],
muc: [all: :allow],
pubsub_createnode: [local: :allow],
register: [all: :allow],
trusted_network: [loopback: :allow]]
end
listen :ejabberd_c2s do
@opts [
port: 5222,
max_stanza_size: 65536,
shaper: :c2s_shaper,
access: :c2s]
end
listen :ejabberd_s2s_in do
@opts [port: 5269]
end
listen :ejabberd_http do
@opts [
port: 5280,
web_admin: true,
http_poll: true,
http_bind: true,
captcha: true]
end
module :mod_adhoc do
end
module :mod_announce do
@opts [access: :announce]
end
module :mod_blocking do
end
module :mod_caps do
end
module :mod_carboncopy do
end
module :mod_client_state do
@opts [
drop_chat_states: true,
queue_presence: false]
end
module :mod_configure do
end
module :mod_disco do
end
module :mod_irc do
end
module :mod_http_bind do
end
module :mod_last do
end
module :mod_muc do
@opts [
access: :muc,
access_create: :muc_create,
access_persistent: :muc_create,
access_admin: :muc_admin]
end
module :mod_offline do
@opts [access_max_user_messages: :max_user_offline_messages]
end
module :mod_ping do
end
module :mod_privacy do
end
module :mod_private do
end
module :mod_pubsub do
@opts [
access_createnode: :pubsub_createnode,
ignore_pep_from_offline: true,
last_item_cache: true,
plugins: ["flat", "hometree", "pep"]]
end
module :mod_register do
@opts [welcome_message: [
subject: "Welcome!",
body: "Hi.\nWelcome to this XMPP Server",
ip_access: :trusted_network,
access: :register]]
end
module :mod_roster do
end
module :mod_shared_roster do
end
module :mod_stats do
end
module :mod_time do
end
module :mod_version do
end
# Example of how to define a hook, called when the event
# specified is triggered.
#
# @event: Name of the event
# @opts: Params are optional. Available: :host and :priority.
# If missing, defaults are used. (host: :global | priority: 50)
# @callback Could be an anonymous function or a callback from a module,
# use the &ModuleName.function/arity format for that.
hook :register_user, [host: "localhost"], fn(user, server) ->
info("User registered: #{user} on #{server}")
end
end
+667
View File
@@ -0,0 +1,667 @@
###
### ejabberd configuration file
###
###
### The parameters used in this configuration file are explained in more detail
### in the ejabberd Installation and Operation Guide.
### Please consult the Guide in case of doubts, it is included with
### your copy of ejabberd, and is also available online at
### http://www.process-one.net/en/ejabberd/docs/
### The configuration file is written in YAML.
### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
### However, ejabberd treats different literals as different types:
###
### - unquoted or single-quoted strings. They are called "atoms".
### Example: dog, 'Jupiter', '3.14159', YELLOW
###
### - numeric literals. Example: 3, -45.0, .0
###
### - quoted or folded strings.
### Examples of quoted string: "Lizzard", "orange".
### Example of folded string:
### > Art thou not Romeo,
### and a Montague?
### =======
### LOGGING
##
## loglevel: Verbosity of log files generated by ejabberd.
## 0: No ejabberd log at all (not recommended)
## 1: Critical
## 2: Error
## 3: Warning
## 4: Info
## 5: Debug
##
loglevel: 4
##
## rotation: Describe how to rotate logs. Either size and/or date can trigger
## log rotation. Setting count to N keeps N rotated logs. Setting count to 0
## does not disable rotation, it instead rotates the file and keeps no previous
## versions around. Setting size to X rotate log when it reaches X bytes.
## To disable rotation set the size to 0 and the date to ""
## Date syntax is taken from the syntax newsyslog uses in newsyslog.conf.
## Some examples:
## $D0 rotate every night at midnight
## $D23 rotate every day at 23:00 hr
## $W0D23 rotate every week on Sunday at 23:00 hr
## $W5D16 rotate every week on Friday at 16:00 hr
## $M1D0 rotate on the first day of every month at midnight
## $M5D6 rotate on every 5th day of the month at 6:00 hr
##
log_rotate_size: 10485760
log_rotate_date: ""
log_rotate_count: 1
##
## overload protection: If you want to limit the number of messages per second
## allowed from error_logger, which is a good idea if you want to avoid a flood
## of messages when system is overloaded, you can set a limit.
## 100 is ejabberd's default.
log_rate_limit: 100
##
## watchdog_admins: Only useful for developers: if an ejabberd process
## consumes a lot of memory, send live notifications to these XMPP
## accounts.
##
## watchdog_admins:
## - "bob@example.com"
### ================
### SERVED HOSTNAMES
##
## hosts: Domains served by ejabberd.
## You can define one or several, for example:
## hosts:
## - "example.net"
## - "example.com"
## - "example.org"
##
hosts:
- "localhost"
##
## route_subdomains: Delegate subdomains to other XMPP servers.
## For example, if this ejabberd serves example.org and you want
## to allow communication with an XMPP server called im.example.org.
##
## route_subdomains: s2s
### ===============
### LISTENING PORTS
##
## listen: The ports ejabberd will listen on, which service each is handled
## by and what options to start it with.
##
listen:
-
port: 5222
module: ejabberd_c2s
##
## If TLS is compiled in and you installed a SSL
## certificate, specify the full path to the
## file and uncomment these lines:
##
## certfile: "/path/to/ssl.pem"
## starttls: true
##
## To enforce TLS encryption for client connections,
## use this instead of the "starttls" option:
##
## starttls_required: true
##
## Custom OpenSSL options
##
## protocol_options:
## - "no_sslv3"
## - "no_tlsv1"
max_stanza_size: 65536
shaper: c2s_shaper
access: c2s
-
port: 5269
module: ejabberd_s2s_in
##
## ejabberd_service: Interact with external components (transports, ...)
##
## -
## port: 8888
## module: ejabberd_service
## access: all
## shaper_rule: fast
## ip: "127.0.0.1"
## hosts:
## "icq.example.org":
## password: "secret"
## "sms.example.org":
## password: "secret"
##
## ejabberd_stun: Handles STUN Binding requests
##
## -
## port: 3478
## transport: udp
## module: ejabberd_stun
##
## To handle XML-RPC requests that provide admin credentials:
##
## -
## port: 4560
## module: ejabberd_xmlrpc
-
port: 5280
module: ejabberd_http
## request_handlers:
## "/pub/archive": mod_http_fileserver
web_admin: true
http_poll: true
http_bind: true
## register: true
captcha: true
##
## s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
## Allowed values are: false optional required required_trusted
## You must specify a certificate file.
##
## s2s_use_starttls: optional
##
## s2s_certfile: Specify a certificate file.
##
## s2s_certfile: "/path/to/ssl.pem"
## Custom OpenSSL options
##
## s2s_protocol_options:
## - "no_sslv3"
## - "no_tlsv1"
##
## domain_certfile: Specify a different certificate for each served hostname.
##
## host_config:
## "example.org":
## domain_certfile: "/path/to/example_org.pem"
## "example.com":
## domain_certfile: "/path/to/example_com.pem"
##
## S2S whitelist or blacklist
##
## Default s2s policy for undefined hosts.
##
## s2s_access: s2s
##
## Outgoing S2S options
##
## Preferred address families (which to try first) and connect timeout
## in milliseconds.
##
## outgoing_s2s_families:
## - ipv4
## - ipv6
## outgoing_s2s_timeout: 10000
### ==============
### AUTHENTICATION
##
## auth_method: Method used to authenticate the users.
## The default method is the internal.
## If you want to use a different method,
## comment this line and enable the correct ones.
##
auth_method: internal
##
## Store the plain passwords or hashed for SCRAM:
## auth_password_format: plain
## auth_password_format: scram
##
## Define the FQDN if ejabberd doesn't detect it:
## fqdn: "server3.example.com"
##
## Authentication using external script
## Make sure the script is executable by ejabberd.
##
## auth_method: external
## extauth_program: "/path/to/authentication/script"
##
## Authentication using ODBC
## Remember to setup a database in the next section.
##
## auth_method: odbc
##
## Authentication using PAM
##
## auth_method: pam
## pam_service: "pamservicename"
##
## Authentication using LDAP
##
## auth_method: ldap
##
## List of LDAP servers:
## ldap_servers:
## - "localhost"
##
## Encryption of connection to LDAP servers:
## ldap_encrypt: none
## ldap_encrypt: tls
##
## Port to connect to on LDAP servers:
## ldap_port: 389
## ldap_port: 636
##
## LDAP manager:
## ldap_rootdn: "dc=example,dc=com"
##
## Password of LDAP manager:
## ldap_password: "******"
##
## Search base of LDAP directory:
## ldap_base: "dc=example,dc=com"
##
## LDAP attribute that holds user ID:
## ldap_uids:
## - "mail": "%u@mail.example.org"
##
## LDAP filter:
## ldap_filter: "(objectClass=shadowAccount)"
##
## Anonymous login support:
## auth_method: anonymous
## anonymous_protocol: sasl_anon | login_anon | both
## allow_multiple_connections: true | false
##
## host_config:
## "public.example.org":
## auth_method: anonymous
## allow_multiple_connections: false
## anonymous_protocol: sasl_anon
##
## To use both anonymous and internal authentication:
##
## host_config:
## "public.example.org":
## auth_method:
## - internal
## - anonymous
### ==============
### DATABASE SETUP
## ejabberd by default uses the internal Mnesia database,
## so you do not necessarily need this section.
## This section provides configuration examples in case
## you want to use other database backends.
## Please consult the ejabberd Guide for details on database creation.
##
## MySQL server:
##
## odbc_type: mysql
## odbc_server: "server"
## odbc_database: "database"
## odbc_username: "username"
## odbc_password: "password"
##
## If you want to specify the port:
## odbc_port: 1234
##
## PostgreSQL server:
##
## odbc_type: pgsql
## odbc_server: "server"
## odbc_database: "database"
## odbc_username: "username"
## odbc_password: "password"
##
## If you want to specify the port:
## odbc_port: 1234
##
## If you use PostgreSQL, have a large database, and need a
## faster but inexact replacement for "select count(*) from users"
##
## pgsql_users_number_estimate: true
##
## ODBC compatible or MSSQL server:
##
## odbc_type: odbc
## odbc_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
##
## Number of connections to open to the database for each virtual host
##
## odbc_pool_size: 10
##
## Interval to make a dummy SQL request to keep the connections to the
## database alive. Specify in seconds: for example 28800 means 8 hours
##
## odbc_keepalive_interval: undefined
### ===============
### TRAFFIC SHAPERS
shaper:
##
## The "normal" shaper limits traffic speed to 1000 B/s
##
normal: 1000
##
## The "fast" shaper limits traffic speed to 50000 B/s
##
fast: 50000
##
## This option specifies the maximum number of elements in the queue
## of the FSM. Refer to the documentation for details.
##
max_fsm_queue: 1000
###. ====================
###' ACCESS CONTROL LISTS
acl:
##
## The 'admin' ACL grants administrative privileges to XMPP accounts.
## You can put here as many accounts as you want.
##
## admin:
## user:
## - "aleksey": "localhost"
## - "ermine": "example.org"
##
## Blocked users
##
## blocked:
## user:
## - "baduser": "example.org"
## - "test"
## Local users: don't modify this.
##
local:
user_regexp: ""
##
## More examples of ACLs
##
## jabberorg:
## server:
## - "jabber.org"
## aleksey:
## user:
## - "aleksey": "jabber.ru"
## test:
## user_regexp: "^test"
## user_glob: "test*"
##
## Loopback network
##
loopback:
ip:
- "127.0.0.0/8"
##
## Bad XMPP servers
##
## bad_servers:
## server:
## - "xmpp.zombie.org"
## - "xmpp.spam.com"
##
## Define specific ACLs in a virtual host.
##
## host_config:
## "localhost":
## acl:
## admin:
## user:
## - "bob-local": "localhost"
### ============
### 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
## This rule allows access only for local users:
local:
local: allow
## 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
## Only admins can send announcement messages:
announce:
admin: allow
## 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
## 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
## Only accounts on the local ejabberd server can create Pubsub nodes:
pubsub_createnode:
local: allow
## In-band registration allows registration of any possible username.
## To disable in-band registration, replace 'allow' with 'deny'.
register:
all: allow
## Only allow to register from localhost
trusted_network:
loopback: allow
## Do not establish S2S connections with bad servers
## s2s:
## bad_servers: deny
## all: allow
## By default the frequency of account registrations from the same IP
## is limited to 1 account every 10 minutes. To disable, specify: infinity
## registration_timeout: 600
##
## Define specific Access Rules in a virtual host.
##
## host_config:
## "localhost":
## access:
## c2s:
## admin: allow
## all: deny
## register:
## all: deny
### ================
### DEFAULT LANGUAGE
##
## language: Default language used for server messages.
##
language: "en"
##
## Set a different default language in a virtual host.
##
## host_config:
## "localhost":
## language: "ru"
### =======
### CAPTCHA
##
## Full path to a script that generates the image.
##
## captcha_cmd: "/lib/ejabberd/priv/bin/captcha.sh"
##
## Host for the URL and port where ejabberd listens for CAPTCHA requests.
##
## captcha_host: "example.org:5280"
##
## Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
##
## captcha_limit: 5
### =======
### MODULES
##
## Modules enabled in all ejabberd virtual hosts.
##
modules:
mod_adhoc: {}
## mod_admin_extra: {}
mod_announce: # recommends mod_adhoc
access: announce
mod_blocking: {} # requires mod_privacy
mod_caps: {}
mod_carboncopy: {}
mod_client_state:
drop_chat_states: true
queue_presence: false
mod_configure: {} # requires mod_adhoc
mod_disco: {}
## mod_echo: {}
mod_irc: {}
mod_http_bind: {}
## mod_http_fileserver:
## docroot: "/var/www"
## accesslog: "/var/log/ejabberd/access.log"
mod_last: {}
mod_muc:
## host: "conference.@HOST@"
access: muc
access_create: muc_create
access_persistent: muc_create
access_admin: muc_admin
## mod_muc_log: {}
mod_offline:
access_max_user_messages: max_user_offline_messages
mod_ping: {}
## mod_pres_counter:
## count: 5
## interval: 60
mod_privacy: {}
mod_private: {}
## mod_proxy65: {}
mod_pubsub:
access_createnode: pubsub_createnode
## reduces resource comsumption, but XEP incompliant
ignore_pep_from_offline: true
## XEP compliant, but increases resource comsumption
## ignore_pep_from_offline: false
last_item_cache: false
plugins:
- "flat"
- "hometree"
- "pep" # pep requires mod_caps
mod_register:
##
## Protect In-Band account registrations with CAPTCHA.
##
## captcha_protected: true
##
## Set the minimum informational entropy for passwords.
##
## password_strength: 32
##
## After successful registration, the user receives
## a message with this subject and body.
##
welcome_message:
subject: "Welcome!"
body: |-
Hi.
Welcome to this XMPP server.
##
## When a user registers, send a notification to
## these XMPP accounts.
##
## registration_watchers:
## - "admin1@example.org"
##
## Only clients in the server machine can register accounts
##
ip_access: trusted_network
##
## Local c2s or remote s2s users cannot register accounts
##
## access_from: deny
access: register
mod_roster: {}
mod_shared_roster: {}
mod_stats: {}
mod_time: {}
mod_vcard: {}
mod_version: {}
##
## Enable modules with custom options in a specific virtual host
##
## host_config:
## "localhost":
## modules:
## mod_echo:
## host: "mirror.localhost"
##
## Enable modules management via ejabberdctl for installation and
## uninstallation of public/private contributed modules
## (enabled by default)
##
allow_contrib_modules: true
### Local Variables:
### mode: yaml
### End:
### vim: set filetype=yaml tabstop=8
+7
View File
@@ -12,6 +12,13 @@ ExecStop=@ctlscriptpath@/ejabberdctl stop
ExecReload=@ctlscriptpath@/ejabberdctl reload_config
Type=oneshot
RemainAfterExit=yes
# The CAP_DAC_OVERRIDE capability is required for pam authentication to work
CapabilityBoundingSet=CAP_DAC_OVERRIDE
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=full
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
+10
View File
@@ -147,6 +147,15 @@ listen:
## access: all
## shaper_rule: fast
## ip: "127.0.0.1"
## privilege_access:
## roster: "both"
## message: "outgoing"
## presence: "roster"
## delegations:
## "urn:xmpp:mam:1":
## filtering: ["node"]
## "http://jabber.org/protocol/pubsub":
## filtering: []
## hosts:
## "icq.example.org":
## password: "secret"
@@ -580,6 +589,7 @@ modules:
mod_carboncopy: {}
mod_client_state: {}
mod_configure: {} # requires mod_adhoc
##mod_delegation: {} # for xep0356
mod_disco: {}
## mod_echo: {}
mod_irc: {}
+25
View File
@@ -26,6 +26,25 @@
{tuple, [rterm()]} | {list, rterm()} |
rescode | restuple.
-type oauth_scope() :: atom().
%% ejabberd_commands OAuth ReST ACL definition:
%% Two fields exist that are used to control access on a command from ReST API:
%% 1. Policy
%% If policy is:
%% - restricted: command is not exposed as OAuth Rest API.
%% - admin: Command is allowed for user that have Admin Rest command enabled by access rule: commands_admin_access
%% - user: Command might be called by any server user.
%% - open: Command can be called by anyone.
%%
%% Policy is just used to control who can call the command. A specific additional access rules can be performed, as
%% defined by access option.
%% Access option can be a list of:
%% - {Module, accessName, DefaultValue}: Reference and existing module access to limit who can use the command.
%% - AccessRule name: direct name of the access rule to check in config file.
%% TODO: Access option could be atom command (not a list). In the case, User performing the command, will be added as first parameter
%% to command, so that the command can perform additional check.
-record(ejabberd_commands,
{name :: atom(),
tags = [] :: [atom()] | '_' | '$2',
@@ -36,19 +55,25 @@
function :: atom() | '_',
args = [] :: [aterm()] | '_' | '$1' | '$2',
policy = restricted :: open | restricted | admin | user,
%% access is: [accessRuleName] or [{Module, AccessOption, DefaultAccessRuleName}]
access = [] :: [{atom(),atom(),atom()}|atom()],
result = {res, rescode} :: rterm() | '_' | '$2',
args_desc = none :: none | [string()] | '_',
result_desc = none :: none | string() | '_',
args_example = none :: none | [any()] | '_',
result_example = none :: any()}).
%% TODO Fix me: Type is not up to date
-type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
tags :: [atom()],
desc :: string(),
longdesc :: string(),
version :: integer(),
module :: atom(),
function :: atom(),
args :: [aterm()],
policy :: open | restricted | admin | user,
access :: [{atom(),atom(),atom()}|atom()],
result :: rterm()}.
%% @type ejabberd_commands() = #ejabberd_commands{
+26
View File
@@ -0,0 +1,26 @@
%%%----------------------------------------------------------------------
%%%
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-record(oauth_token, {
token = <<"">> :: binary() | '_',
us = {<<"">>, <<"">>} :: {binary(), binary()} | '_',
scope = [] :: [binary()] | '_',
expire :: integer() | '$1'
}).
+20
View File
@@ -0,0 +1,20 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-type filter_attr() :: {binary(), [binary()]}.
-record(state,
{socket :: ejabberd_socket:socket_state(),
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
streamid = <<"">> :: binary(),
host_opts = dict:new() :: ?TDICT,
host = <<"">> :: binary(),
access :: atom(),
check_from = true :: boolean(),
server_hosts = ?MYHOSTS :: [binary()],
privilege_access :: [attr()],
delegations :: [filter_attr()],
last_pres = dict:new() :: ?TDICT}).
-type(state() :: #state{} ).
+2 -2
View File
@@ -1,9 +1,9 @@
-ifndef(EJABBERD_SM_HRL).
-define(EJABBERD_SM_HRL, true).
-record(session, {sid, usr, us, priority, info}).
-record(session, {sid, usr, us, priority, info = []}).
-record(session_counter, {vhost, count}).
-type sid() :: {erlang:timestamp(), pid()} | {erlang:timestamp(), undefined}.
-type sid() :: {erlang:timestamp(), pid()}.
-type ip() :: {inet:ip_address(), inet:port_number()} | undefined.
-type info() :: [{conn, atom()} | {ip, ip()} | {node, atom()}
| {oor, boolean()} | {auth_module, atom()}
+9
View File
@@ -53,6 +53,7 @@
members_by_default = true :: boolean(),
members_only = false :: boolean(),
allow_user_invites = false :: boolean(),
allow_subscription = false :: boolean(),
password_protected = false :: boolean(),
password = <<"">> :: binary(),
anonymous = true :: boolean(),
@@ -76,9 +77,15 @@
jid :: jid(),
nick :: binary(),
role :: role(),
%%is_subscriber = false :: boolean(),
%%subscriptions = [] :: [binary()],
last_presence :: xmlel()
}).
-record(subscriber, {jid :: jid(),
nick = <<>> :: binary(),
nodes = [] :: [binary()]}).
-record(activity,
{
message_time = 0 :: integer(),
@@ -98,6 +105,8 @@
jid = #jid{} :: jid(),
config = #config{} :: config(),
users = (?DICT):new() :: ?TDICT,
subscribers = (?DICT):new() :: ?TDICT,
subscriber_nicks = (?DICT):new() :: ?TDICT,
last_voice_request_time = treap:empty() :: treap:treap(),
robots = (?DICT):new() :: ?TDICT,
nicks = (?DICT):new() :: ?TDICT,
+10
View File
@@ -164,3 +164,13 @@
-define(NS_MIX_NODES_PARTICIPANTS, <<"urn:xmpp:mix:nodes:participants">>).
-define(NS_MIX_NODES_SUBJECT, <<"urn:xmpp:mix:nodes:subject">>).
-define(NS_MIX_NODES_CONFIG, <<"urn:xmpp:mix:nodes:config">>).
-define(NS_PRIVILEGE, <<"urn:xmpp:privilege:1">>).
-define(NS_DELEGATION, <<"urn:xmpp:delegation:1">>).
-define(NS_MUCSUB, <<"urn:xmpp:mucsub:0">>).
-define(NS_MUCSUB_NODES_PRESENCE, <<"urn:xmpp:mucsub:nodes:presence">>).
-define(NS_MUCSUB_NODES_MESSAGES, <<"urn:xmpp:mucsub:nodes:messages">>).
-define(NS_MUCSUB_NODES_PARTICIPANTS, <<"urn:xmpp:mucsub:nodes:participants">>).
-define(NS_MUCSUB_NODES_AFFILIATIONS, <<"urn:xmpp:mucsub:nodes:affiliations">>).
-define(NS_MUCSUB_NODES_SUBJECT, <<"urn:xmpp:mucsub:nodes:subject">>).
-define(NS_MUCSUB_NODES_CONFIG, <<"urn:xmpp:mucsub:nodes:config">>).
-define(NS_MUCSUB_NODES_SYSTEM, <<"urn:xmpp:mucsub:nodes:system">>).
+1 -1
View File
@@ -3,7 +3,7 @@ defmodule ExUnit.CTFormatter do
use GenEvent
import ExUnit.Formatter, only: [format_time: 2, format_filters: 2, format_test_failure: 5,
import ExUnit.Formatter, only: [format_time: 2, format_test_failure: 5,
format_test_case_failure: 5]
def init(opts) do
+119
View File
@@ -0,0 +1,119 @@
defmodule Ejabberd.Config.Attr do
@moduledoc """
Module used to work with the attributes parsed from
an elixir block (do...end).
Contains functions for extracting attrs from a block
and validation.
"""
@type attr :: {atom(), any()}
@attr_supported [
active:
[type: :boolean, default: true],
git:
[type: :string, default: ""],
name:
[type: :string, default: ""],
opts:
[type: :list, default: []],
dependency:
[type: :list, default: []]
]
@doc """
Takes a block with annotations and extracts the list
of attributes.
"""
@spec extract_attrs_from_block_with_defaults(any()) :: [attr]
def extract_attrs_from_block_with_defaults(block) do
block
|> extract_attrs_from_block
|> put_into_list_if_not_already
|> insert_default_attrs_if_missing
end
@doc """
Takes an attribute or a list of attrs and validate them.
Returns a {:ok, attr} or {:error, attr, cause} for each of the attributes.
"""
@spec validate([attr]) :: [{:ok, attr}] | [{:error, attr, atom()}]
def validate(attrs) when is_list(attrs), do: Enum.map(attrs, &valid_attr?/1)
def validate(attr), do: validate([attr]) |> List.first
@doc """
Returns the type of an attribute, given its name.
"""
@spec get_type_for_attr(atom()) :: atom()
def get_type_for_attr(attr_name) do
@attr_supported
|> Keyword.get(attr_name)
|> Keyword.get(:type)
end
@doc """
Returns the default value for an attribute, given its name.
"""
@spec get_default_for_attr(atom()) :: any()
def get_default_for_attr(attr_name) do
@attr_supported
|> Keyword.get(attr_name)
|> Keyword.get(:default)
end
# Private API
# Given an elixir block (do...end) returns a list with the annotations
# or a single annotation.
@spec extract_attrs_from_block(any()) :: [attr] | attr
defp extract_attrs_from_block({:__block__, [], attrs}), do: Enum.map(attrs, &extract_attrs_from_block/1)
defp extract_attrs_from_block({:@, _, [attrs]}), do: extract_attrs_from_block(attrs)
defp extract_attrs_from_block({attr_name, _, [value]}), do: {attr_name, value}
defp extract_attrs_from_block(nil), do: []
# In case extract_attrs_from_block returns a single attribute,
# then put it into a list. (Ensures attrs are always into a list).
@spec put_into_list_if_not_already([attr] | attr) :: [attr]
defp put_into_list_if_not_already(attrs) when is_list(attrs), do: attrs
defp put_into_list_if_not_already(attr), do: [attr]
# Given a list of attributes, it inserts the missing attribute with their
# default value.
@spec insert_default_attrs_if_missing([attr]) :: [attr]
defp insert_default_attrs_if_missing(attrs) do
Enum.reduce @attr_supported, attrs, fn({attr_name, _}, acc) ->
case Keyword.has_key?(acc, attr_name) do
true -> acc
false -> Keyword.put(acc, attr_name, get_default_for_attr(attr_name))
end
end
end
# Given an attribute, validates it and return a tuple with
# {:ok, attr} or {:error, attr, cause}
@spec valid_attr?(attr) :: {:ok, attr} | {:error, attr, atom()}
defp valid_attr?({attr_name, param} = attr) do
case Keyword.get(@attr_supported, attr_name) do
nil -> {:error, attr, :attr_not_supported}
[{:type, param_type} | _] -> case is_of_type?(param, param_type) do
true -> {:ok, attr}
false -> {:error, attr, :type_not_supported}
end
end
end
# Given an attribute value and a type, it returns a true
# if the value its of the type specified, false otherwise.
# Usefoul for checking if an attr value respects the type
# specified for the annotation.
@spec is_of_type?(any(), atom()) :: boolean()
defp is_of_type?(param, type) when type == :boolean and is_boolean(param), do: true
defp is_of_type?(param, type) when type == :string and is_bitstring(param), do: true
defp is_of_type?(param, type) when type == :list and is_list(param), do: true
defp is_of_type?(param, type) when type == :atom and is_atom(param), do: true
defp is_of_type?(_param, type) when type == :any, do: true
defp is_of_type?(_, _), do: false
end
+145
View File
@@ -0,0 +1,145 @@
defmodule Ejabberd.Config do
@moduledoc """
Base module for configuration file.
Imports macros for the config DSL and contains functions
for working/starting the configuration parsed.
"""
alias Ejabberd.Config.EjabberdModule
alias Ejabberd.Config.Attr
alias Ejabberd.Config.EjabberdLogger
defmacro __using__(_opts) do
quote do
import Ejabberd.Config, only: :macros
import Ejabberd.Logger
@before_compile Ejabberd.Config
end
end
# Validate the modules parsed and log validation errors at compile time.
# Could be also possible to interrupt the compilation&execution by throwing
# an exception if necessary.
def __before_compile__(_env) do
get_modules_parsed_in_order
|> EjabberdModule.validate
|> EjabberdLogger.log_errors
end
@doc """
Given the path of the config file, it evaluates it.
"""
def init(file_path, force \\ false) do
init_already_executed = Ejabberd.Config.Store.get(:module_name) != []
case force do
true ->
Ejabberd.Config.Store.stop
Ejabberd.Config.Store.start_link
do_init(file_path)
false ->
if not init_already_executed, do: do_init(file_path)
end
end
@doc """
Returns a list with all the opts, formatted for ejabberd.
"""
def get_ejabberd_opts do
get_general_opts
|> Dict.put(:modules, get_modules_parsed_in_order())
|> Dict.put(:listeners, get_listeners_parsed_in_order())
|> Ejabberd.Config.OptsFormatter.format_opts_for_ejabberd
end
@doc """
Register the hooks defined inside the elixir config file.
"""
def start_hooks do
get_hooks_parsed_in_order()
|> Enum.each(&Ejabberd.Config.EjabberdHook.start/1)
end
###
### MACROS
###
defmacro listen(module, do: block) do
attrs = Attr.extract_attrs_from_block_with_defaults(block)
quote do
Ejabberd.Config.Store.put(:listeners, %EjabberdModule{
module: unquote(module),
attrs: unquote(attrs)
})
end
end
defmacro module(module, do: block) do
attrs = Attr.extract_attrs_from_block_with_defaults(block)
quote do
Ejabberd.Config.Store.put(:modules, %EjabberdModule{
module: unquote(module),
attrs: unquote(attrs)
})
end
end
defmacro hook(hook_name, opts, fun) do
quote do
Ejabberd.Config.Store.put(:hooks, %Ejabberd.Config.EjabberdHook{
hook: unquote(hook_name),
opts: unquote(opts),
fun: unquote(fun)
})
end
end
# Private API
defp do_init(file_path) do
# File evaluation
Code.eval_file(file_path) |> extract_and_store_module_name()
# Getting start/0 config
Ejabberd.Config.Store.get(:module_name)
|> case do
nil -> IO.puts "[ ERR ] Configuration module not found."
[module] -> call_start_func_and_store_data(module)
end
# Fetching git modules and install them
get_modules_parsed_in_order()
|> EjabberdModule.fetch_git_repos
end
# Returns the modules from the store
defp get_modules_parsed_in_order,
do: Ejabberd.Config.Store.get(:modules) |> Enum.reverse
# Returns the listeners from the store
defp get_listeners_parsed_in_order,
do: Ejabberd.Config.Store.get(:listeners) |> Enum.reverse
defp get_hooks_parsed_in_order,
do: Ejabberd.Config.Store.get(:hooks) |> Enum.reverse
# Returns the general config options
defp get_general_opts,
do: Ejabberd.Config.Store.get(:general) |> List.first
# Gets the general ejabberd options calling
# the start/0 function and stores them.
defp call_start_func_and_store_data(module) do
opts = apply(module, :start, [])
Ejabberd.Config.Store.put(:general, opts)
end
# Stores the configuration module name
defp extract_and_store_module_name({{:module, mod, _bytes, _}, _}) do
Ejabberd.Config.Store.put(:module_name, mod)
end
end
+23
View File
@@ -0,0 +1,23 @@
defmodule Ejabberd.Config.EjabberdHook do
@moduledoc """
Module containing functions for manipulating
ejabberd hooks.
"""
defstruct hook: nil, opts: [], fun: nil
alias Ejabberd.Config.EjabberdHook
@type t :: %EjabberdHook{}
@doc """
Register a hook to ejabberd.
"""
@spec start(EjabberdHook.t) :: none
def start(%EjabberdHook{hook: hook, opts: opts, fun: fun}) do
host = Keyword.get(opts, :host, :global)
priority = Keyword.get(opts, :priority, 50)
:ejabberd_hooks.add(hook, host, fun, priority)
end
end
+70
View File
@@ -0,0 +1,70 @@
defmodule Ejabberd.Config.EjabberdModule do
@moduledoc """
Module representing a module block in the configuration file.
It offers functions for validation and for starting the modules.
Warning: The name is EjabberdModule to not collide with
the already existing Elixir.Module.
"""
@type t :: %{module: atom, attrs: [Attr.t]}
defstruct [:module, :attrs]
alias Ejabberd.Config.EjabberdModule
alias Ejabberd.Config.Attr
alias Ejabberd.Config.Validation
@doc """
Given a list of modules / single module
it runs different validators on them.
For each module, returns a {:ok, mod} or {:error, mod, errors}
"""
def validate(modules) do
Validation.validate(modules)
end
@doc """
Given a list of modules, it takes only the ones with
a git attribute and tries to fetch the repo,
then, it install them through :ext_mod.install/1
"""
@spec fetch_git_repos([EjabberdModule.t]) :: none()
def fetch_git_repos(modules) do
modules
|> Enum.filter(&is_git_module?/1)
|> Enum.each(&fetch_and_install_git_module/1)
end
# Private API
defp is_git_module?(%EjabberdModule{attrs: attrs}) do
case Keyword.get(attrs, :git) do
"" -> false
repo -> String.match?(repo, ~r/((git|ssh|http(s)?)|(git@[\w\.]+))(:(\/\/)?)([\w\.@\:\/\-~]+)(\.git)(\/)?/)
end
end
defp fetch_and_install_git_module(%EjabberdModule{attrs: attrs}) do
repo = Keyword.get(attrs, :git)
mod_name = case Keyword.get(attrs, :name) do
"" -> infer_mod_name_from_git_url(repo)
name -> name
end
path = "#{:ext_mod.modules_dir()}/sources/ejabberd-contrib\/#{mod_name}"
fetch_and_store_repo_source_if_not_exists(path, repo)
:ext_mod.install(mod_name) # Have to check if overwrites an already present mod
end
defp fetch_and_store_repo_source_if_not_exists(path, repo) do
unless File.exists?(path) do
IO.puts "[info] Fetching: #{repo}"
:os.cmd('git clone #{repo} #{path}')
end
end
defp infer_mod_name_from_git_url(repo),
do: String.split(repo, "/") |> List.last |> String.replace(".git", "")
end
@@ -0,0 +1,32 @@
defmodule Ejabberd.Config.EjabberdLogger do
@moduledoc """
Module used to log validation errors given validated modules
given validated modules.
"""
alias Ejabberd.Config.EjabberdModule
@doc """
Given a list of modules validated, in the form of {:ok, mod} or
{:error, mod, errors}, it logs to the user the errors found.
"""
@spec log_errors([EjabberdModule.t]) :: [EjabberdModule.t]
def log_errors(modules_validated) when is_list(modules_validated) do
Enum.each modules_validated, &do_log_errors/1
modules_validated
end
defp do_log_errors({:ok, _mod}), do: nil
defp do_log_errors({:error, _mod, errors}), do: Enum.each errors, &do_log_errors/1
defp do_log_errors({:attribute, errors}), do: Enum.each errors, &log_attribute_error/1
defp do_log_errors({:dependency, errors}), do: Enum.each errors, &log_dependency_error/1
defp log_attribute_error({{attr_name, val}, :attr_not_supported}), do:
IO.puts "[ WARN ] Annotation @#{attr_name} is not supported."
defp log_attribute_error({{attr_name, val}, :type_not_supported}), do:
IO.puts "[ WARN ] Annotation @#{attr_name} with value #{inspect val} is not supported (type mismatch)."
defp log_dependency_error({module, :not_found}), do:
IO.puts "[ WARN ] Module #{inspect module} was not found, but is required as a dependency."
end
+46
View File
@@ -0,0 +1,46 @@
defmodule Ejabberd.Config.OptsFormatter do
@moduledoc """
Module for formatting options parsed into the format
ejabberd uses.
"""
alias Ejabberd.Config.EjabberdModule
@doc """
Takes a keyword list with keys corresponding to
the keys requested by the ejabberd config (ex: modules: mods)
and formats them to be correctly evaluated by ejabberd.
Look at how Config.get_ejabberd_opts/0 is constructed for
more informations.
"""
@spec format_opts_for_ejabberd([{atom(), any()}]) :: list()
def format_opts_for_ejabberd(opts) do
opts
|> format_attrs_for_ejabberd
end
defp format_attrs_for_ejabberd(opts) when is_list(opts),
do: Enum.map opts, &format_attrs_for_ejabberd/1
defp format_attrs_for_ejabberd({:listeners, mods}),
do: {:listen, format_listeners_for_ejabberd(mods)}
defp format_attrs_for_ejabberd({:modules, mods}),
do: {:modules, format_mods_for_ejabberd(mods)}
defp format_attrs_for_ejabberd({key, opts}) when is_atom(key),
do: {key, opts}
defp format_mods_for_ejabberd(mods) do
Enum.map mods, fn %EjabberdModule{module: mod, attrs: attrs} ->
{mod, attrs[:opts]}
end
end
defp format_listeners_for_ejabberd(mods) do
Enum.map mods, fn %EjabberdModule{module: mod, attrs: attrs} ->
Keyword.put(attrs[:opts], :module, mod)
end
end
end
+55
View File
@@ -0,0 +1,55 @@
defmodule Ejabberd.Config.Store do
@moduledoc """
Module used for storing the modules parsed from
the configuration file.
Example:
- Store.put(:modules, mod1)
- Store.put(:modules, mod2)
- Store.get(:modules) :: [mod1, mod2]
Be carefoul: when retrieving data you get them
in the order inserted into the store, which normally
is the reversed order of how the modules are specified
inside the configuration file. To resolve this just use
a Enum.reverse/1.
"""
@name __MODULE__
def start_link do
Agent.start_link(fn -> %{} end, name: @name)
end
@doc """
Stores a value based on the key. If the key already exists,
then it inserts the new element, maintaining all the others.
It uses a list for this.
"""
@spec put(atom, any) :: :ok
def put(key, val) do
Agent.update @name, &Map.update(&1, key, [val], fn coll ->
[val | coll]
end)
end
@doc """
Gets a value based on the key passed.
Returns always a list.
"""
@spec get(atom) :: [any]
def get(key) do
Agent.get @name, &Map.get(&1, key, [])
end
@doc """
Stops the store.
It uses Agent.stop underneath, so be aware that exit
could be called.
"""
@spec stop() :: :ok
def stop do
Agent.stop @name
end
end
@@ -0,0 +1,40 @@
defmodule Ejabberd.Config.Validation do
@moduledoc """
Module used to validate a list of modules.
"""
@type mod_validation :: {[EjabberdModule.t], EjabberdModule.t, map}
@type mod_validation_result :: {:ok, EjabberdModule.t} | {:error, EjabberdModule.t, map}
alias Ejabberd.Config.EjabberdModule
alias Ejabberd.Config.Attr
alias Ejabberd.Config.Validator
alias Ejabberd.Config.ValidatorUtility
@doc """
Given a module or a list of modules it runs validators on them
and returns {:ok, mod} or {:error, mod, errors}, for each
of them.
"""
@spec validate([EjabberdModule.t] | EjabberdModule.t) :: [mod_validation_result]
def validate(modules) when is_list(modules), do: Enum.map(modules, &do_validate(modules, &1))
def validate(module), do: validate([module])
# Private API
@spec do_validate([EjabberdModule.t], EjabberdModule.t) :: mod_validation_result
defp do_validate(modules, mod) do
{modules, mod, %{}}
|> Validator.Attrs.validate
|> Validator.Dependencies.validate
|> resolve_validation_result
end
@spec resolve_validation_result(mod_validation) :: mod_validation_result
defp resolve_validation_result({_modules, mod, errors}) do
case errors do
err when err == %{} -> {:ok, mod}
err -> {:error, mod, err}
end
end
end
@@ -0,0 +1,28 @@
defmodule Ejabberd.Config.Validator.Attrs do
@moduledoc """
Validator module used to validate attributes.
"""
# TODO: Duplicated from validator.ex !!!
@type mod_validation :: {[EjabberdModule.t], EjabberdModule.t, map}
import Ejabberd.Config.ValidatorUtility
alias Ejabberd.Config.Attr
@doc """
Given a module (with the form used for validation)
it runs Attr.validate/1 on each attribute and
returns the validation tuple with the errors updated, if found.
"""
@spec validate(mod_validation) :: mod_validation
def validate({modules, mod, errors}) do
errors = Enum.reduce mod.attrs, errors, fn(attr, err) ->
case Attr.validate(attr) do
{:ok, attr} -> err
{:error, attr, cause} -> put_error(err, :attribute, {attr, cause})
end
end
{modules, mod, errors}
end
end
@@ -0,0 +1,30 @@
defmodule Ejabberd.Config.Validator.Dependencies do
@moduledoc """
Validator module used to validate dependencies specified
with the @dependency annotation.
"""
# TODO: Duplicated from validator.ex !!!
@type mod_validation :: {[EjabberdModule.t], EjabberdModule.t, map}
import Ejabberd.Config.ValidatorUtility
@doc """
Given a module (with the form used for validation)
it checks if the @dependency annotation is respected and
returns the validation tuple with the errors updated, if found.
"""
@spec validate(mod_validation) :: mod_validation
def validate({modules, mod, errors}) do
module_names = extract_module_names(modules)
dependencies = mod.attrs[:dependency]
errors = Enum.reduce dependencies, errors, fn(req_module, err) ->
case req_module in module_names do
true -> err
false -> put_error(err, :dependency, {req_module, :not_found})
end
end
{modules, mod, errors}
end
end
@@ -0,0 +1,30 @@
defmodule Ejabberd.Config.ValidatorUtility do
@moduledoc """
Module used as a base validator for validation modules.
Imports utility functions for working with validation structures.
"""
alias Ejabberd.Config.EjabberdModule
@doc """
Inserts an error inside the errors collection, for the given key.
If the key doesn't exists then it creates an empty collection
and inserts the value passed.
"""
@spec put_error(map, atom, any) :: map
def put_error(errors, key, val) do
Map.update errors, key, [val], fn coll ->
[val | coll]
end
end
@doc """
Given a list of modules it extracts and returns a list
of the module names (which are Elixir.Module).
"""
@spec extract_module_names(EjabberdModule.t) :: [atom]
def extract_module_names(modules) when is_list(modules) do
modules
|> Enum.map(&Map.get(&1, :module))
end
end
+18
View File
@@ -0,0 +1,18 @@
defmodule Ejabberd.ConfigUtil do
@moduledoc """
Module containing utility functions for
the config file.
"""
@doc """
Returns true when the config file is based on elixir.
"""
@spec is_elixir_config(list) :: boolean
def is_elixir_config(filename) when is_list(filename) do
is_elixir_config(to_string(filename))
end
def is_elixir_config(filename) do
String.ends_with?(filename, "exs")
end
end
+19
View File
@@ -0,0 +1,19 @@
defmodule Ejabberd.Module do
defmacro __using__(opts) do
logger_enabled = Keyword.get(opts, :logger, true)
quote do
@behaviour :gen_mod
import Ejabberd.Module
unquote(if logger_enabled do
quote do: import Ejabberd.Logger
end)
end
end
# gen_mod callbacks
def depends(_host, _opts), do: []
def mod_opt_type(_), do: []
end
+94
View File
@@ -0,0 +1,94 @@
defmodule Mix.Tasks.Ejabberd.Deps.Tree do
use Mix.Task
alias Ejabberd.Config.EjabberdModule
@shortdoc "Lists all ejabberd modules and their dependencies"
@moduledoc """
Lists all ejabberd modules and their dependencies.
The project must have ejabberd as a dependency.
"""
def run(_argv) do
# First we need to start manually the store to be available
# during the compilation of the config file.
Ejabberd.Config.Store.start_link
Ejabberd.Config.init(:ejabberd_config.get_ejabberd_config_path())
Mix.shell.info "ejabberd modules"
Ejabberd.Config.Store.get(:modules)
|> Enum.reverse # Because of how mods are stored inside the store
|> format_mods
|> Mix.shell.info
end
defp format_mods(mods) when is_list(mods) do
deps_tree = build_dependency_tree(mods)
mods_used_as_dependency = get_mods_used_as_dependency(deps_tree)
keep_only_mods_not_used_as_dep(deps_tree, mods_used_as_dependency)
|> format_mods_into_string
end
defp build_dependency_tree(mods) do
Enum.map mods, fn %EjabberdModule{module: mod, attrs: attrs} ->
deps = attrs[:dependency]
build_dependency_tree(mods, mod, deps)
end
end
defp build_dependency_tree(mods, mod, []), do: %{module: mod, dependency: []}
defp build_dependency_tree(mods, mod, deps) when is_list(deps) do
dependencies = Enum.map deps, fn dep ->
dep_deps = get_dependencies_of_mod(mods, dep)
build_dependency_tree(mods, dep, dep_deps)
end
%{module: mod, dependency: dependencies}
end
defp get_mods_used_as_dependency(mods) when is_list(mods) do
Enum.reduce mods, [], fn(mod, acc) ->
case mod do
%{dependency: []} -> acc
%{dependency: deps} -> get_mod_names(deps) ++ acc
end
end
end
defp get_mod_names([]), do: []
defp get_mod_names(mods) when is_list(mods), do: Enum.map(mods, &get_mod_names/1) |> List.flatten
defp get_mod_names(%{module: mod, dependency: deps}), do: [mod | get_mod_names(deps)]
defp keep_only_mods_not_used_as_dep(mods, mods_used_as_dep) do
Enum.filter mods, fn %{module: mod} ->
not mod in mods_used_as_dep
end
end
defp get_dependencies_of_mod(deps, mod_name) do
Enum.find(deps, &(Map.get(&1, :module) == mod_name))
|> Map.get(:attrs)
|> Keyword.get(:dependency)
end
defp format_mods_into_string(mods), do: format_mods_into_string(mods, 0)
defp format_mods_into_string([], _indentation), do: ""
defp format_mods_into_string(mods, indentation) when is_list(mods) do
Enum.reduce mods, "", fn(mod, acc) ->
acc <> format_mods_into_string(mod, indentation)
end
end
defp format_mods_into_string(%{module: mod, dependency: deps}, 0) do
"\n├── #{mod}" <> format_mods_into_string(deps, 2)
end
defp format_mods_into_string(%{module: mod, dependency: deps}, indentation) do
spaces = Enum.reduce 0..indentation, "", fn(_, acc) -> " " <> acc end
"\n#{spaces}└── #{mod}" <> format_mods_into_string(deps, indentation + 4)
end
end
+6 -7
View File
@@ -1,21 +1,20 @@
defmodule ModPresenceDemo do
import Ejabberd.Logger # this allow using info, error, etc for logging
@behaviour :gen_mod
use Ejabberd.Module
def start(host, _opts) do
info('Starting ejabberd module Presence Demo')
Ejabberd.Hooks.add(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
Ejabberd.Hooks.add(:set_presence_hook, host, __MODULE__, :on_presence, 50)
:ok
end
def stop(host) do
info('Stopping ejabberd module Presence Demo')
Ejabberd.Hooks.delete(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
Ejabberd.Hooks.delete(:set_presence_hook, host, __MODULE__, :on_presence, 50)
:ok
end
def on_presence(user, _server, _resource, _packet) do
info('Receive presence for #{user}')
:none
end
end
end
+12 -5
View File
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "16.06.0",
version: "16.08.0",
description: description,
elixir: "~> 1.2",
elixirc_paths: ["lib"],
@@ -11,6 +11,8 @@ defmodule Ejabberd.Mixfile do
compilers: [:asn1] ++ Mix.compilers,
erlc_options: erlc_options,
erlc_paths: ["asn1", "src"],
# Elixir tests are starting the part of ejabberd they need
aliases: [test: "test --no-start"],
package: package,
deps: deps]
end
@@ -27,7 +29,7 @@ defmodule Ejabberd.Mixfile do
included_applications: [:lager, :mnesia, :p1_utils, :cache_tab,
:fast_tls, :stringprep, :fast_xml,
:stun, :fast_yaml, :ezlib, :iconv,
:esip, :jiffy, :p1_oauth2, :p1_xmlrpc, :eredis,
:esip, :jiffy, :p1_oauth2, :eredis,
:p1_mysql, :p1_pgsql, :sqlite3]]
end
@@ -38,7 +40,7 @@ defmodule Ejabberd.Mixfile do
end
defp deps do
[{:lager, "~> 3.0.0"},
[{:lager, "~> 3.2"},
{:p1_utils, "~> 1.0"},
{:cache_tab, "~> 1.0"},
{:stringprep, "~> 1.0"},
@@ -49,14 +51,19 @@ defmodule Ejabberd.Mixfile do
{:esip, "~> 1.0"},
{:jiffy, "~> 0.14.7"},
{:p1_oauth2, "~> 0.6.1"},
{:p1_xmlrpc, "~> 1.15"},
{:p1_mysql, "~> 1.0"},
{:p1_pgsql, "~> 1.1"},
{:sqlite3, "~> 1.1"},
{:ezlib, "~> 1.0"},
{:iconv, "~> 1.0"},
{:eredis, "~> 1.0"},
{:exrm, "~> 1.0.0-rc7", only: :dev}]
{:exrm, "~> 1.0.0", only: :dev},
# relx is used by exrm. Lock version as for now, ejabberd doesn not compile fine with
# version 3.20:
{:relx, "~> 3.21", only: :dev},
{:ex_doc, ">= 0.0.0", only: :dev},
{:meck, "~> 0.8.4", only: :test},
{:moka, github: "processone/moka", tag: "1.0.5c", only: :test}]
end
defp package do
+19 -15
View File
@@ -1,26 +1,30 @@
%{"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]}]},
"cache_tab": {:hex, :cache_tab, "1.0.4", "3fd2b1ab40c36e7830a4e09e836c6b0fa89191cd4e5fd471873e4eb42f5cd37c", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"cf": {:hex, :cf, "0.2.1", "69d0b1349fd4d7d4dc55b7f407d29d7a840bf9a1ef5af529f1ebe0ce153fc2ab", [:rebar3], []},
"earmark": {:hex, :earmark, "1.0.1", "2c2cd903bfdc3de3f189bd9a8d4569a075b88a8981ded9a0d95672f6e2b63141", [:mix], []},
"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]}]},
"erlware_commons": {:hex, :erlware_commons, "0.21.0", "a04433071ad7d112edefc75ac77719dd3e6753e697ac09428fc83d7564b80b15", [:rebar3], [{:cf, "0.2.1", [hex: :cf, optional: false]}]},
"esip": {:hex, :esip, "1.0.8", "69885a6c07964aabc6c077fe1372aa810a848bd3d9a415b160dabdce9c7a79b5", [:rebar3], [{:fast_tls, "1.0.7", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}, {:stun, "1.0.7", [hex: :stun, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.13.0", "aa2f8fe4c6136a2f7cfc0a7e06805f82530e91df00e2bff4b4362002b43ada65", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"exrm": {:hex, :exrm, "1.0.8", "5aa8990cdfe300282828b02cefdc339e235f7916388ce99f9a1f926a9271a45d", [: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]}]},
"fast_tls": {:hex, :fast_tls, "1.0.7", "9b72ecfcdcad195ab072c196fab8334f49d8fea76bf1a51f536d69e7527d902a", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.15", "6d23eb7f874e1357cf80a48d75a7bd0c8f6318029dc4b70122e9f54911f57f83", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.6", "3fe6feb7935ae8028b337e53e1db29e73ad3bca8041108f6a8f73b7175ece75c", [:rebar3], [{:p1_utils, "1.0.5", [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], []},
"goldrush": {:hex, :goldrush, "0.1.8", "2024ba375ceea47e27ea70e14d2c483b2d8610101b4e852ef7f89163cdb6e649", [:rebar3], []},
"iconv": {:hex, :iconv, "1.0.2", "a0792f06ab4b5ea1b5bb49789405739f1281a91c44cf3879cb70e4d777666217", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"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]}]},
"lager": {:hex, :lager, "3.2.1", "eef4e18b39e4195d37606d9088ea05bf1b745986cf8ec84f01d332456fe88d17", [:rebar3], [{:goldrush, "0.1.8", [hex: :goldrush, optional: false]}]},
"meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:rebar, :make], []},
"moka": {:git, "https://github.com/processone/moka.git", "3eed3a6dd7dedb70a6cd18f86c7561a18626eb3b", [tag: "1.0.5c"]},
"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], []},
"p1_utils": {:hex, :p1_utils, "1.0.5", "3e698354fdc1fea5491d991457b0cb986c0a00a47d224feb841dc3ec82b9f721", [: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]}]},
"relx": {:hex, :relx, "3.21.0", "91e1ea9f09b4edfda8461901f4b5c5e0226e43ec161e147eeab29f7761df6eb5", [:rebar3], [{:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:erlware_commons, "0.21.0", [hex: :erlware_commons, optional: false]}, {:getopt, "0.8.2", [hex: :getopt, optional: false]}, {:providers, "1.6.0", [hex: :providers, optional: false]}]},
"samerlib": {:git, "https://github.com/processone/samerlib", "fbbba035b1548ac4e681df00d61bf609645333a0", [tag: "0.8.0c"]},
"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]}]}}
"stringprep": {:hex, :stringprep, "1.0.6", "1cf1c439eb038aa590da5456e019f86afbfbfeb5a2d37b6e5f873041624c6701", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.7", "904dc6f26a3c30c54881c4c3003699f2a4968067ee6b3aecdf9895aad02df75e", [:rebar3], [{:fast_tls, "1.0.7", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]}}
+14 -14
View File
@@ -7,18 +7,17 @@
%%% Created : 1 May 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
{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.4"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.3"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.12"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.4"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.5"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.3"}}},
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.2.1"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.5"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.4"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.7"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.6"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.15"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.7"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.8"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.6"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.7"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
{p1_xmlrpc, ".*", {git, "https://github.com/processone/p1_xmlrpc", {tag, "1.15.1"}}},
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
{tag, "1.0.1"}}}},
@@ -34,21 +33,21 @@
"527722d12d0433b837cdb92a60900c2cb5df8942"}}},
%% Forces correct dependency for riakc and allow using newer meck version)
{if_var_true, riak, {hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang",
"908a24fda4a46776a5135db60ca071e3d783f9f6"}}}, % for riak_pb-2.1.0.7
"13f9bfb9b27d216e8e033b0e0a9a29097ed923dd"}}}, % for riak_pb-2.1.0.7
{if_var_true, riak, {protobuffs, ".*", {git, "https://github.com/basho/erlang_protobuffs",
"6e7fc924506e2dc166a6170e580ce1d95ebbd5bd"}}}, % for riak_pb-2.1.0.7 with correct meck dependency
%% Elixir support, needed to run tests
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
{tag, "v1.1.1"}}}},
{tag, {if_version_above, "17", "v1.2.6", "v1.1.1"}}}}},
%% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
{if_var_true, elixir, {rebar_elixir_plugin, ".*",
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}},
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
{tag, "1.0.0"}}}},
{tag, "1.0.2"}}}},
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
{tag, "0.8.4"}}}},
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
{tag, "1.0.5b"}}}},
{tag, "1.0.5c"}}}},
{if_var_true, redis, {eredis, ".*", {git, "https://github.com/wooga/eredis",
{tag, "v1.0.8"}}}}]}.
@@ -75,6 +74,7 @@
{if_var_true, debug, debug_info},
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
{if_var_match, db_type, mssql, {d, 'mssql'}},
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
{if_var_true, hipe, native},
{src_dirs, [asn1, src,
+26 -5
View File
@@ -19,7 +19,7 @@ ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) ->
[{Key, F(F, OldVal, Tail, Op, Default)} | PartCfg]
end
end,
ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end.
ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end,
Cfg = case file:consult(filename:join(filename:dirname(SCRIPT), "vars.config")) of
{ok, Terms} ->
@@ -28,6 +28,13 @@ Cfg = case file:consult(filename:join(filename:dirname(SCRIPT), "vars.config"))
[]
end,
ProcessSingleVar = fun(F, Var, Tail) ->
case F(F, [Var], []) of
[] -> Tail;
[Val] -> [Val | Tail]
end
end,
ProcessVars = fun(_F, [], Acc) ->
lists:reverse(Acc);
(F, [{Type, Ver, Value} | Tail], Acc) when
@@ -40,17 +47,31 @@ ProcessVars = fun(_F, [], Acc) ->
SysVer < Ver
end,
if Include ->
F(F, Tail, [Value | Acc]);
F(F, Tail, ProcessSingleVar(F, Value, Acc));
true ->
F(F, Tail, Acc)
end;
(F, [{Type, Ver, Value, ElseValue} | 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, ProcessSingleVar(F, Value, Acc));
true ->
F(F, Tail, ProcessSingleVar(F, ElseValue, Acc))
end;
(F, [{Type, Var, Value} | Tail], Acc) when
Type == if_var_true orelse
Type == if_var_false ->
Flag = Type == if_var_true,
case proplists:get_bool(Var, Cfg) of
V when V == Flag ->
F(F, Tail, [Value | Acc]);
F(F, Tail, ProcessSingleVar(F, Value, Acc));
_ ->
F(F, Tail, Acc)
end;
@@ -59,7 +80,7 @@ ProcessVars = fun(_F, [], Acc) ->
Type == if_var_no_match ->
case proplists:get_value(Var, Cfg) of
V when V == Match ->
F(F, Tail, [Value | Acc]);
F(F, Tail, ProcessSingleVar(F, Value, Acc));
_ ->
F(F, Tail, Acc)
end;
@@ -146,7 +167,7 @@ Conf6 = case {lists:keyfind(cover_enabled, 1, Conf5), os:getenv("TRAVIS")} of
Conf5
end,
%io:format("ejabberd configuration:~n ~p~n", [Conf5]),
%io:format("ejabberd configuration:~n ~p~n", [Conf6]),
Conf6.
+7
View File
@@ -313,3 +313,10 @@ CREATE TABLE sm (
CREATE UNIQUE INDEX i_sm_sid ON sm(usec, pid);
CREATE INDEX i_sm_node ON sm(node);
CREATE INDEX i_sm_username ON sm(username);
CREATE TABLE oauth_token (
token text NOT NULL PRIMARY KEY,
jid text NOT NULL,
scope text NOT NULL,
expire bigint NOT NULL
);
+10
View File
@@ -480,3 +480,13 @@ ON DELETE CASCADE;
ALTER TABLE [dbo].[pubsub_state] CHECK CONSTRAINT [pubsub_state_ibfk_1];
CREATE TABLE [dbo].[oauth_token] (
[token] [varchar] (250) NOT NULL,
[jid] [text] NOT NULL,
[scope] [text] NOT NULL,
[expire] [bigint] NOT NULL,
CONSTRAINT [oauth_token_PRIMARY] PRIMARY KEY CLUSTERED
(
[token] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
) TEXTIMAGE_ON [PRIMARY];
+7
View File
@@ -328,3 +328,10 @@ CREATE TABLE sm (
CREATE UNIQUE INDEX i_sid ON sm(usec, pid(75));
CREATE INDEX i_node ON sm(node(75));
CREATE INDEX i_username ON sm(username);
CREATE TABLE oauth_token (
token varchar(191) NOT NULL PRIMARY KEY,
jid text NOT NULL,
scope text NOT NULL,
expire bigint NOT NULL
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+9
View File
@@ -330,3 +330,12 @@ CREATE TABLE sm (
CREATE UNIQUE INDEX i_sm_sid ON sm USING btree (usec, pid);
CREATE INDEX i_sm_node ON sm USING btree (node);
CREATE INDEX i_sm_username ON sm USING btree (username);
CREATE TABLE oauth_token (
token text NOT NULL,
jid text NOT NULL,
scope text NOT NULL,
expire bigint NOT NULL
);
CREATE UNIQUE INDEX i_oauth_token_token ON oauth_token USING btree (token);
+14 -2
View File
@@ -31,10 +31,11 @@
-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,
load_from_config/0, match_rule/3, any_rules_allowed/3,
transform_options/1, opt_type/1, acl_rule_matches/3,
acl_rule_verify/1, access_matches/3,
transform_access_rules_config/1,
parse_ip_netmask/1,
access_rules_validator/1, shaper_rules_validator/1]).
-include("ejabberd.hrl").
@@ -261,6 +262,7 @@ normalize_spec(Spec) ->
{server, S} -> {server, nameprep(S)};
{resource, R} -> {resource, resourceprep(R)};
{server_regexp, SR} -> {server_regexp, b(SR)};
{resource_regexp, R} -> {resource_regexp, b(R)};
{server_glob, S} -> {server_glob, b(S)};
{resource_glob, R} -> {resource_glob, b(R)};
{ip, {Net, Mask}} -> {ip, {Net, Mask}};
@@ -274,6 +276,15 @@ normalize_spec(Spec) ->
end
end.
-spec any_rules_allowed(global | binary(), access_name(),
jid() | ljid() | inet:ip_address()) -> boolean().
any_rules_allowed(Host, Access, Entity) ->
lists:any(fun (Rule) ->
allow == acl:match_rule(Host, Rule, Entity)
end,
Access).
-spec match_rule(global | binary(), access_name(),
jid() | ljid() | inet:ip_address()) -> any().
@@ -676,7 +687,8 @@ transform_options({acl, Name, Type}, Opts) ->
{server_regexp, SR} -> {server_regexp, [b(SR)]};
{server_glob, S} -> {server_glob, [b(S)]};
{ip, S} -> {ip, [b(S)]};
{resource_glob, R} -> {resource_glob, [b(R)]}
{resource_glob, R} -> {resource_glob, [b(R)]};
{resource_regexp, R} -> {resource_regexp, [b(R)]}
end,
[{acl, [{Name, [T]}]}|Opts];
transform_options({access, Name, Rules}, Opts) ->
+5 -18
View File
@@ -41,13 +41,7 @@
%% Parse an ad-hoc request. Return either an adhoc_request record or
%% an {error, ErrorType} tuple.
%%
-spec(parse_request/1 ::
(
IQ :: iq_request())
-> adhoc_response()
%%
| {error, _}
).
-spec parse_request(IQ :: iq_request()) -> adhoc_response() | {error, _}.
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
?DEBUG("entering parse_request...", []),
@@ -88,12 +82,9 @@ find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
%% record, filling in values for language, node and session id from
%% the request.
%%
-spec(produce_response/2 ::
(
Adhoc_Request :: adhoc_request(),
Adhoc_Response :: adhoc_response())
-> Xmlel::xmlel()
).
-spec produce_response(Adhoc_Request :: adhoc_request(),
Adhoc_Response :: adhoc_response()) ->
Xmlel::xmlel().
%% Produce a <command/> node to use as response from an adhoc_response
%% record.
@@ -104,11 +95,7 @@ produce_response(#adhoc_request{lang = Lang, node = Node, sessionid = SessionID}
}).
%%
-spec(produce_response/1 ::
(
Adhoc_Response::adhoc_response())
-> Xmlel::xmlel()
).
-spec produce_response(Adhoc_Response::adhoc_response()) -> Xmlel::xmlel().
produce_response(
#adhoc_response{
+5 -23
View File
@@ -88,13 +88,8 @@ start() ->
ok.
%%
-spec(register_mechanism/3 ::
(
Mechanim :: mechanism(),
Module :: module(),
PasswordType :: password_type())
-> any()
).
-spec register_mechanism(Mechanim :: mechanism(), Module :: module(),
PasswordType :: password_type()) -> any().
register_mechanism(Mechanism, Module, PasswordType) ->
case is_disabled(Mechanism) of
@@ -139,11 +134,7 @@ check_credentials(_State, Props) ->
_LUser -> ok
end.
-spec(listmech/1 ::
(
Host ::binary())
-> Mechanisms::mechanisms()
).
-spec listmech(Host ::binary()) -> Mechanisms::mechanisms().
listmech(Host) ->
Mechs = ets:select(sasl_mechanism,
@@ -213,12 +204,7 @@ server_step(State, ClientIn) ->
%% Remove the anonymous mechanism from the list if not enabled for the given
%% host
%%
-spec(filter_anonymous/2 ::
(
Host :: binary(),
Mechs :: mechanisms())
-> mechanisms()
).
-spec filter_anonymous(Host :: binary(), Mechs :: mechanisms()) -> mechanisms().
filter_anonymous(Host, Mechs) ->
case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of
@@ -226,11 +212,7 @@ filter_anonymous(Host, Mechs) ->
false -> Mechs -- [<<"ANONYMOUS">>]
end.
-spec(is_disabled/1 ::
(
Mechanism :: mechanism())
-> boolean()
).
-spec is_disabled(Mechanism :: mechanism()) -> boolean().
is_disabled(Mechanism) ->
Disabled = ejabberd_config:get_option(
+1 -1
View File
@@ -51,7 +51,7 @@ mech_step(State, ClientIn) ->
{ok,
[{username, User}, {authzid, AuthzId},
{auth_module, ejabberd_oauth}]};
false ->
_ ->
{error, <<"not-authorized">>, User}
end;
_ -> {error, <<"bad-protocol">>}
+7 -6
View File
@@ -87,6 +87,7 @@ get_commands_spec() ->
args = [], result = {res, rescode}},
#ejabberd_commands{name = reopen_log, tags = [logs, server],
desc = "Reopen the log files",
policy = admin,
module = ?MODULE, function = reopen_log,
args = [], result = {res, rescode}},
#ejabberd_commands{name = rotate_log, tags = [logs, server],
@@ -129,6 +130,7 @@ get_commands_spec() ->
#ejabberd_commands{name = register, tags = [accounts],
desc = "Register a user",
policy = admin,
module = ?MODULE, function = register,
args = [{user, binary}, {host, binary}, {password, binary}],
result = {res, restuple}},
@@ -166,7 +168,7 @@ get_commands_spec() ->
#ejabberd_commands{name = list_cluster, tags = [cluster],
desc = "List nodes that are part of the cluster handled by Node",
module = ?MODULE, function = list_cluster,
args = [],
args = [],
result = {nodes, {list, {node, atom}}}},
#ejabberd_commands{name = import_file, tags = [mnesia],
@@ -220,7 +222,7 @@ get_commands_spec() ->
desc = "Delete offline messages older than DAYS",
module = ?MODULE, function = delete_old_messages,
args = [{days, integer}], result = {res, rescode}},
#ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL files",
module = ejd2sql, function = export,
@@ -378,13 +380,12 @@ register(User, Host, Password) ->
{atomic, ok} ->
{ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
{atomic, exists} ->
String = io_lib:format("User ~s@~s already registered at node ~p",
[User, Host, node()]),
{exists, String};
Msg = io_lib:format("User ~s@~s already registered", [User, Host]),
{error, conflict, 10090, Msg};
{error, Reason} ->
String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
[User, Host, node(), Reason]),
{cannot_register, String}
{error, cannot_register, 10001, String}
end.
unregister(User, Host) ->
+27
View File
@@ -45,6 +45,7 @@ start(normal, _Args) ->
write_pid_file(),
jid:start(),
start_apps(),
start_elixir_application(),
ejabberd:check_app(ejabberd),
randoms:start(),
db_init(),
@@ -55,6 +56,7 @@ start(normal, _Args) ->
ejabberd_admin:start(),
gen_mod:start(),
ext_mod:start(),
setup_if_elixir_conf_used(),
ejabberd_config:start(),
set_settings_from_config(),
acl:start(),
@@ -74,6 +76,8 @@ start(normal, _Args) ->
ejabberd_oauth:start(),
gen_mod:start_modules(),
ejabberd_listener:start_listeners(),
ejabberd_service:start(),
register_elixir_config_hooks(),
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
Sup;
start(_, _) ->
@@ -237,3 +241,26 @@ opt_type(modules) ->
Mods)
end;
opt_type(_) -> [cluster_nodes, loglevel, modules, net_ticktime].
setup_if_elixir_conf_used() ->
case ejabberd_config:is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config.Store':start_link();
false -> ok
end.
register_elixir_config_hooks() ->
case ejabberd_config:is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config':start_hooks();
false -> ok
end.
start_elixir_application() ->
case ejabberd_config:is_elixir_enabled() of
true ->
case application:ensure_started(elixir) of
ok -> ok;
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
end;
_ ->
ok
end.
+167 -42
View File
@@ -32,6 +32,7 @@
-protocol({xep, 78, '2.5'}).
-protocol({xep, 138, '2.0'}).
-protocol({xep, 198, '1.3'}).
-protocol({xep, 356, '7.1'}).
-update_info({update, 0}).
@@ -48,10 +49,16 @@
send_element/2,
socket_type/0,
get_presence/1,
get_last_presence/1,
get_aux_field/2,
set_aux_field/3,
del_aux_field/2,
get_subscription/2,
get_queued_stanzas/1,
get_csi_state/1,
set_csi_state/2,
get_resume_timeout/1,
set_resume_timeout/2,
send_filtered/5,
broadcast/4,
get_subscribed/1,
@@ -111,9 +118,12 @@
mgmt_pending_since,
mgmt_timeout,
mgmt_max_timeout,
mgmt_ack_timeout,
mgmt_ack_timer,
mgmt_resend,
mgmt_stanzas_in = 0,
mgmt_stanzas_out = 0,
mgmt_stanzas_req = 0,
ask_offline = true,
lang = <<"">>}).
@@ -212,6 +222,9 @@ socket_type() -> xml_stream.
get_presence(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef,
{get_presence}, 1000).
get_last_presence(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef,
{get_last_presence}, 1000).
get_aux_field(Key, #state{aux_fields = Opts}) ->
case lists:keysearch(Key, 1, Opts) of
@@ -244,6 +257,27 @@ get_subscription(LFrom, StateData) ->
true -> none
end.
get_queued_stanzas(#state{mgmt_queue = Queue} = StateData) ->
lists:map(fun({_N, Time, El}) ->
add_resent_delay_info(StateData, El, Time)
end, queue:to_list(Queue)).
get_csi_state(#state{csi_state = CsiState}) ->
CsiState.
set_csi_state(#state{} = StateData, CsiState) ->
StateData#state{csi_state = CsiState};
set_csi_state(FsmRef, CsiState) ->
FsmRef ! {set_csi_state, CsiState}.
get_resume_timeout(#state{mgmt_timeout = Timeout}) ->
Timeout.
set_resume_timeout(#state{} = StateData, Timeout) ->
StateData#state{mgmt_timeout = Timeout};
set_resume_timeout(FsmRef, Timeout) ->
FsmRef ! {set_resume_timeout, Timeout}.
send_filtered(FsmRef, Feature, From, To, Packet) ->
FsmRef ! {send_filtered, Feature, From, To, Packet}.
@@ -303,13 +337,18 @@ init([{SockMod, Socket}, Opts]) ->
_ -> 1000
end,
ResumeTimeout = case proplists:get_value(resume_timeout, Opts) of
Timeout when is_integer(Timeout), Timeout >= 0 -> Timeout;
RTimeo when is_integer(RTimeo), RTimeo >= 0 -> RTimeo;
_ -> 300
end,
MaxResumeTimeout = case proplists:get_value(max_resume_timeout, Opts) of
Max when is_integer(Max), Max >= ResumeTimeout -> Max;
_ -> ResumeTimeout
end,
AckTimeout = case proplists:get_value(ack_timeout, Opts) of
ATimeo when is_integer(ATimeo), ATimeo > 0 -> ATimeo * 1000;
infinity -> undefined;
_ -> 60000
end,
ResendOnTimeout = case proplists:get_value(resend_on_timeout, Opts) of
Resend when is_boolean(Resend) -> Resend;
if_offline -> if_offline;
@@ -333,6 +372,7 @@ init([{SockMod, Socket}, Opts]) ->
mgmt_max_queue = MaxAckQueue,
mgmt_timeout = ResumeTimeout,
mgmt_max_timeout = MaxResumeTimeout,
mgmt_ack_timeout = AckTimeout,
mgmt_resend = ResendOnTimeout},
{ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}.
@@ -1306,6 +1346,15 @@ handle_sync_event({get_presence}, _From, StateName,
Resource = StateData#state.resource,
Reply = {User, Resource, Show, Status},
fsm_reply(Reply, StateName, StateData);
handle_sync_event({get_last_presence}, _From, StateName,
StateData) ->
User = StateData#state.user,
Server = StateData#state.server,
PresLast = StateData#state.pres_last,
Resource = StateData#state.resource,
Reply = {User, Server, Resource, PresLast},
fsm_reply(Reply, StateName, StateData);
handle_sync_event(get_subscribed, _From, StateName,
StateData) ->
Subscribed = (?SETS):to_list(StateData#state.pres_f),
@@ -1318,7 +1367,7 @@ handle_sync_event({resume_session, Time}, _From, _StateName,
StateData#state.user,
StateData#state.server,
StateData#state.resource),
{stop, normal, {ok, StateData}, StateData#state{mgmt_state = resumed}};
{stop, normal, {resume, StateData}, StateData#state{mgmt_state = resumed}};
handle_sync_event({resume_session, _Time}, _From, StateName,
StateData) ->
{reply, {error, <<"Previous session not found">>}, StateName, StateData};
@@ -1632,11 +1681,18 @@ handle_info({route, From, To,
<<"groupchat">> -> ok;
<<"headline">> -> ok;
_ ->
Err =
jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From,
Err)
case fxml:get_subtag_with_xmlns(Packet,
<<"x">>,
?NS_MUC_USER)
of
false ->
Err =
jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From,
Err);
_ -> ok
end
end,
{false, Attrs, StateData}
end;
@@ -1736,8 +1792,24 @@ handle_info({broadcast, Type, From, Packet}, StateName, StateData) ->
From, jid:make(USR), Packet)
end, lists:usort(Recipients)),
fsm_next_state(StateName, StateData);
handle_info({set_csi_state, CsiState}, StateName, StateData) ->
fsm_next_state(StateName, StateData#state{csi_state = CsiState});
handle_info({set_resume_timeout, Timeout}, StateName, StateData) ->
fsm_next_state(StateName, StateData#state{mgmt_timeout = Timeout});
handle_info(dont_ask_offline, StateName, StateData) ->
fsm_next_state(StateName, StateData#state{ask_offline = false});
handle_info(close, StateName, StateData) ->
?DEBUG("Timeout waiting for stream management acknowledgement of ~s",
[jid:to_string(StateData#state.jid)]),
close(self()),
fsm_next_state(StateName, StateData#state{mgmt_ack_timer = undefined});
handle_info({_Ref, {resume, OldStateData}}, StateName, StateData) ->
%% This happens if the resume_session/1 request timed out; the new session
%% now receives the late response.
?DEBUG("Received old session state for ~s after failed resumption",
[jid:to_string(OldStateData#state.jid)]),
handle_unacked_stanzas(OldStateData#state{mgmt_resend = false}),
fsm_next_state(StateName, StateData);
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
fsm_next_state(StateName, StateData).
@@ -1855,6 +1927,7 @@ send_text(StateData, Text) ->
send_element(StateData, El) when StateData#state.mgmt_state == pending ->
?DEBUG("Cannot send element while waiting for resumption: ~p", [El]);
send_element(StateData, El) when StateData#state.xml_socket ->
?DEBUG("Send XML on stream = ~p", [fxml:element_to_binary(El)]),
(StateData#state.sockmod):send_xml(StateData#state.socket,
{xmlstreamelement, El});
send_element(StateData, El) ->
@@ -1865,8 +1938,8 @@ send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive ->
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == pending ->
mgmt_queue_add(StateData, Stanza);
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == active ->
NewStateData = send_stanza_and_ack_req(StateData, Stanza),
mgmt_queue_add(NewStateData, Stanza);
NewStateData = mgmt_queue_add(StateData, Stanza),
mgmt_send_stanza(NewStateData, Stanza);
send_stanza(StateData, Stanza) ->
send_element(StateData, Stanza),
StateData.
@@ -2450,13 +2523,25 @@ fsm_next_state(session_established, StateData) ->
?C2S_HIBERNATE_TIMEOUT};
fsm_next_state(wait_for_resume, #state{mgmt_timeout = 0} = StateData) ->
{stop, normal, StateData};
fsm_next_state(wait_for_resume, #state{mgmt_pending_since = undefined} =
StateData) ->
fsm_next_state(wait_for_resume, #state{mgmt_pending_since = undefined,
sid = SID, jid = JID, ip = IP,
conn = Conn, auth_module = AuthModule,
server = Host} = StateData) ->
case StateData of
#state{mgmt_ack_timer = undefined} ->
ok;
#state{mgmt_ack_timer = Timer} ->
erlang:cancel_timer(Timer)
end,
?INFO_MSG("Waiting for resumption of stream for ~s",
[jid:to_string(StateData#state.jid)]),
[jid:to_string(JID)]),
Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthModule}],
NewStateData = ejabberd_hooks:run_fold(c2s_session_pending, Host, StateData,
[SID, JID, Info]),
{next_state, wait_for_resume,
StateData#state{mgmt_state = pending, mgmt_pending_since = os:timestamp()},
StateData#state.mgmt_timeout};
NewStateData#state{mgmt_state = pending,
mgmt_pending_since = os:timestamp()},
NewStateData#state.mgmt_timeout};
fsm_next_state(wait_for_resume, StateData) ->
Diff = timer:now_diff(os:timestamp(), StateData#state.mgmt_pending_since),
Timeout = max(StateData#state.mgmt_timeout - Diff div 1000, 1),
@@ -2728,7 +2813,8 @@ handle_r(StateData) ->
handle_a(StateData, Attrs) ->
case catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs)) of
H when is_integer(H), H >= 0 ->
check_h_attribute(StateData, H);
NewStateData = check_h_attribute(StateData, H),
maybe_renew_ack_request(NewStateData);
_ ->
?DEBUG("Ignoring invalid ACK element from ~s",
[jid:to_string(StateData#state.jid)]),
@@ -2745,8 +2831,8 @@ handle_resume(StateData, Attrs) ->
of
{{value, PrevID}, H} when is_integer(H), H >= 0 ->
case inherit_session_state(StateData, PrevID) of
{ok, InheritedState} ->
{ok, InheritedState, H};
{ok, InheritedState, Info} ->
{ok, InheritedState, Info, H};
{error, Err, InH} ->
{error, ?MGMT_ITEM_NOT_FOUND_H(Xmlns, InH), Err};
{error, Err} ->
@@ -2765,7 +2851,7 @@ handle_resume(StateData, Attrs) ->
<<"Invalid XMLNS">>}
end,
case R of
{ok, ResumedState, NumHandled} ->
{ok, ResumedState, ResumedInfo, NumHandled} ->
NewState = check_h_attribute(ResumedState, NumHandled),
AttrXmlns = NewState#state.mgmt_xmlns,
AttrId = make_resume_id(NewState),
@@ -2785,11 +2871,16 @@ handle_resume(StateData, Attrs) ->
#xmlel{name = <<"r">>,
attrs = [{<<"xmlns">>, AttrXmlns}],
children = []}),
FlushedState = csi_flush_queue(NewState),
NewStateData = FlushedState#state{csi_state = active},
NewState1 = csi_flush_queue(NewState),
NewState2 = ejabberd_hooks:run_fold(c2s_session_resumed,
StateData#state.server,
NewState1,
[NewState1#state.sid,
NewState1#state.jid,
ResumedInfo]),
?INFO_MSG("Resumed session for ~s",
[jid:to_string(NewStateData#state.jid)]),
{ok, NewStateData};
[jid:to_string(NewState2#state.jid)]),
{ok, NewState2};
{error, El, Msg} ->
send_element(StateData, El),
?INFO_MSG("Cannot resume session for ~s@~s: ~s",
@@ -2822,18 +2913,47 @@ update_num_stanzas_in(#state{mgmt_state = MgmtState} = StateData, El)
update_num_stanzas_in(StateData, _El) ->
StateData.
send_stanza_and_ack_req(StateData, Stanza) ->
AckReq = #xmlel{name = <<"r">>,
attrs = [{<<"xmlns">>, StateData#state.mgmt_xmlns}],
children = []},
case send_element(StateData, Stanza) == ok andalso
send_element(StateData, AckReq) == ok of
true ->
StateData;
false ->
mgmt_send_stanza(StateData, Stanza) ->
case send_element(StateData, Stanza) of
ok ->
maybe_request_ack(StateData);
_ ->
StateData#state{mgmt_state = pending}
end.
maybe_request_ack(#state{mgmt_ack_timer = undefined} = StateData) ->
request_ack(StateData);
maybe_request_ack(StateData) ->
StateData.
request_ack(#state{mgmt_xmlns = Xmlns,
mgmt_ack_timeout = AckTimeout} = StateData) ->
AckReq = #xmlel{name = <<"r">>, attrs = [{<<"xmlns">>, Xmlns}]},
case {send_element(StateData, AckReq), AckTimeout} of
{ok, undefined} ->
ok;
{ok, Timeout} ->
Timer = erlang:send_after(Timeout, self(), close),
StateData#state{mgmt_ack_timer = Timer,
mgmt_stanzas_req = StateData#state.mgmt_stanzas_out};
_ ->
StateData#state{mgmt_state = pending}
end.
maybe_renew_ack_request(#state{mgmt_ack_timer = undefined} = StateData) ->
StateData;
maybe_renew_ack_request(#state{mgmt_ack_timer = Timer,
mgmt_queue = Queue,
mgmt_stanzas_out = NumStanzasOut,
mgmt_stanzas_req = NumStanzasReq} = StateData) ->
erlang:cancel_timer(Timer),
case NumStanzasReq < NumStanzasOut andalso not queue:is_empty(Queue) of
true ->
request_ack(StateData#state{mgmt_ack_timer = undefined});
false ->
StateData#state{mgmt_ack_timer = undefined}
end.
mgmt_queue_add(StateData, El) ->
NewNum = case StateData#state.mgmt_stanzas_out of
4294967295 ->
@@ -2873,8 +2993,8 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData, F)
0 ->
ok;
N ->
?INFO_MSG("~B stanzas were not acknowledged by ~s",
[N, jid:to_string(StateData#state.jid)]),
?DEBUG("~B stanza(s) were not acknowledged by ~s",
[N, jid:to_string(StateData#state.jid)]),
lists:foreach(
fun({_, Time, #xmlel{attrs = Attrs} = El}) ->
From_s = fxml:get_attr_s(<<"from">>, Attrs),
@@ -2951,6 +3071,9 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
[StateData, From,
StateData#state.jid, El]) of
true ->
?DEBUG("Dropping archived message stanza from ~s",
[fxml:get_attr_s(<<"from">>,
El#xmlel.attrs)]),
ok;
false ->
ReRoute(From, To, El, Time)
@@ -3006,7 +3129,7 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
OldPID ->
OldSID = {Time, OldPID},
case catch resume_session(OldSID) of
{ok, OldStateData} ->
{resume, OldStateData} ->
NewSID = {Time, self()}, % Old time, new PID
Priority = case OldStateData#state.pres_last of
undefined ->
@@ -3030,13 +3153,13 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
pres_timestamp = OldStateData#state.pres_timestamp,
privacy_list = OldStateData#state.privacy_list,
aux_fields = OldStateData#state.aux_fields,
csi_state = OldStateData#state.csi_state,
mgmt_xmlns = OldStateData#state.mgmt_xmlns,
mgmt_queue = OldStateData#state.mgmt_queue,
mgmt_timeout = OldStateData#state.mgmt_timeout,
mgmt_stanzas_in = OldStateData#state.mgmt_stanzas_in,
mgmt_stanzas_out = OldStateData#state.mgmt_stanzas_out,
mgmt_state = active}};
mgmt_state = active,
csi_state = active}, Info};
{error, Msg} ->
{error, Msg};
_ ->
@@ -3048,7 +3171,7 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
end.
resume_session({Time, PID}) ->
(?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 5000).
(?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 15000).
make_resume_id(StateData) ->
{Time, _} = StateData#state.sid,
@@ -3063,20 +3186,22 @@ add_resent_delay_info(#state{server = From}, El, Time) ->
%%% XEP-0352
%%%----------------------------------------------------------------------
csi_filter_stanza(#state{csi_state = CsiState, server = Server} = StateData,
Stanza) ->
csi_filter_stanza(#state{csi_state = CsiState, jid = JID, server = Server} =
StateData, Stanza) ->
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_filter_stanza, Server,
{StateData, [Stanza]},
[Server, Stanza]),
[Server, JID, Stanza]),
StateData2 = lists:foldl(fun(CurStanza, AccState) ->
send_stanza(AccState, CurStanza)
end, StateData1#state{csi_state = active},
Stanzas),
StateData2#state{csi_state = CsiState}.
csi_flush_queue(#state{csi_state = CsiState, server = Server} = StateData) ->
csi_flush_queue(#state{csi_state = CsiState, jid = JID, server = Server} =
StateData) ->
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_flush_queue, Server,
{StateData, []}, [Server]),
{StateData, []},
[Server, JID]),
StateData2 = lists:foldl(fun(CurStanza, AccState) ->
send_stanza(AccState, CurStanza)
end, StateData1#state{csi_state = active},
+183 -124
View File
@@ -218,14 +218,15 @@
get_command_format/1,
get_command_format/2,
get_command_format/3,
get_command_policy/1,
get_command_policy_and_scope/1,
get_command_definition/1,
get_command_definition/2,
get_tags_commands/0,
get_tags_commands/1,
get_commands/0,
get_exposed_commands/0,
register_commands/1,
unregister_commands/1,
unregister_commands/1,
expose_commands/1,
execute_command/2,
execute_command/3,
execute_command/4,
@@ -273,12 +274,11 @@ get_commands_spec() ->
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result_example = ok}].
init() ->
mnesia:delete_table(ejabberd_commands),
mnesia:create_table(ejabberd_commands,
[{ram_copies, [node()]},
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, ejabberd_commands)},
{type, bag}]),
{attributes, record_info(fields, ejabberd_commands)},
{type, bag}]),
mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
register_commands(get_commands_spec()).
@@ -287,12 +287,14 @@ init() ->
%% @doc Register ejabberd commands.
%% If a command is already registered, a warning is printed and the
%% old command is preserved.
%% A registered command is not directly available to be called through
%% ejabberd ReST API. It need to be exposed to be available through API.
register_commands(Commands) ->
lists:foreach(
fun(Command) ->
% XXX check if command exists
mnesia:dirty_write(Command)
% ?DEBUG("This command is already defined:~n~p", [Command])
%% XXX check if command exists
mnesia:dirty_write(Command)
%% ?DEBUG("This command is already defined:~n~p", [Command])
end,
Commands).
@@ -306,6 +308,25 @@ unregister_commands(Commands) ->
end,
Commands).
%% @doc Expose command through ejabberd ReST API.
%% Pass a list of command names or policy to expose.
-spec expose_commands([ejabberd_commands()|atom()|open|user|admin|restricted]) -> ok | {error, atom()}.
expose_commands(Commands) ->
Names = lists:map(fun(#ejabberd_commands{name = Name}) ->
Name;
(Name) when is_atom(Name) ->
Name
end,
Commands),
case ejabberd_config:add_local_option(commands, [{add_commands, Names}]) of
{aborted, Reason} ->
{error, Reason};
{atomic, Result} ->
Result
end.
-spec list_commands() -> [{atom(), [aterm()], string()}].
%% @doc Get a list of all the available commands, arguments and description.
@@ -319,8 +340,8 @@ list_commands() ->
list_commands(Version) ->
Commands = get_commands_definition(Version),
[{Name, Args, Desc} || #ejabberd_commands{name = Name,
args = Args,
desc = Desc} <- Commands].
args = Args,
desc = Desc} <- Commands].
-spec list_commands_policy(integer()) ->
@@ -331,10 +352,10 @@ list_commands(Version) ->
list_commands_policy(Version) ->
Commands = get_commands_definition(Version),
[{Name, Args, Desc, Policy} ||
#ejabberd_commands{name = Name,
args = Args,
desc = Desc,
policy = Policy} <- Commands].
#ejabberd_commands{name = Name,
args = Args,
desc = Desc,
policy = Policy} <- Commands].
-spec get_command_format(atom()) -> {[aterm()], rterm()}.
@@ -356,27 +377,33 @@ get_command_format(Name, Auth, Version) ->
Admin = is_admin(Name, Auth, #{}),
#ejabberd_commands{args = Args,
result = Result,
policy = Policy} =
get_command_definition(Name, Version),
policy = Policy} =
get_command_definition(Name, Version),
case Policy of
user when Admin;
Auth == noauth ->
{[{user, binary}, {server, binary} | Args], Result};
_ ->
{Args, Result}
user when Admin;
Auth == noauth ->
{[{user, binary}, {server, binary} | Args], Result};
_ ->
{Args, Result}
end.
-spec get_command_policy(atom()) -> {ok, open|user|admin|restricted} | {error, command_not_found}.
-spec get_command_policy_and_scope(atom()) -> {ok, open|user|admin|restricted, [oauth_scope()]} | {error, command_not_found}.
%% @doc return command policy.
get_command_policy(Name) ->
get_command_policy_and_scope(Name) ->
case get_command_definition(Name) of
#ejabberd_commands{policy = Policy} ->
{ok, Policy};
#ejabberd_commands{policy = Policy} = Cmd ->
{ok, Policy, cmd_scope(Cmd)};
command_not_found ->
{error, command_not_found}
end.
%% The oauth scopes for a command are the command name itself,
%% also might include either 'ejabberd:user' or 'ejabberd:admin'
cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) ->
[erlang:atom_to_binary(Name,utf8)] ++ [<<"ejabberd:user">> || Policy == user] ++ [<<"ejabberd:admin">> || Policy == admin].
-spec get_command_definition(atom()) -> ejabberd_commands().
%% @doc Get the definition record of a command.
@@ -388,16 +415,16 @@ get_command_definition(Name) ->
%% @doc Get the definition record of a command in a given API version.
get_command_definition(Name, Version) ->
case lists:reverse(
lists:sort(
mnesia:dirty_select(
ejabberd_commands,
ets:fun2ms(
fun(#ejabberd_commands{name = N, version = V} = C)
when N == Name, V =< Version ->
{V, C}
end)))) of
[{_, Command} | _ ] -> Command;
_E -> throw(unknown_command)
lists:sort(
mnesia:dirty_select(
ejabberd_commands,
ets:fun2ms(
fun(#ejabberd_commands{name = N, version = V} = C)
when N == Name, V =< Version ->
{V, C}
end)))) of
[{_, Command} | _ ] -> Command;
_E -> throw({error, unknown_command})
end.
-spec get_commands_definition(integer()) -> [ejabberd_commands()].
@@ -405,20 +432,20 @@ get_command_definition(Name, Version) ->
% @doc Returns all commands for a given API version
get_commands_definition(Version) ->
L = lists:reverse(
lists:sort(
mnesia:dirty_select(
ejabberd_commands,
ets:fun2ms(
fun(#ejabberd_commands{name = Name, version = V} = C)
when V =< Version ->
{Name, V, C}
end)))),
lists:sort(
mnesia:dirty_select(
ejabberd_commands,
ets:fun2ms(
fun(#ejabberd_commands{name = Name, version = V} = C)
when V =< Version ->
{Name, V, C}
end)))),
F = fun({_Name, _V, Command}, []) ->
[Command];
({Name, _V, _Command}, [#ejabberd_commands{name=Name}|_T] = Acc) ->
Acc;
({_Name, _V, Command}, Acc) -> [Command | Acc]
end,
[Command];
({Name, _V, _Command}, [#ejabberd_commands{name=Name}|_T] = Acc) ->
Acc;
({_Name, _V, Command}, Acc) -> [Command | Acc]
end,
lists:foldl(F, [], L).
%% @spec (Name::atom(), Arguments) -> ResultTerm
@@ -427,7 +454,7 @@ get_commands_definition(Version) ->
%% @doc Execute a command.
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data |
%% no_auth_provided
%% no_auth_provided | access_rules_unauthorized
execute_command(Name, Arguments) ->
execute_command(Name, Arguments, ?DEFAULT_VERSION).
@@ -488,41 +515,64 @@ execute_command(AccessCommands, Auth, Name, Arguments) ->
%%
%% @doc Execute a command in a given API version
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided | access_rules_unauthorized
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}).
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}).
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) ->
Auth = case is_admin(Name, Auth1, CallerInfo) of
true -> admin;
false -> Auth1
end,
TokenJID = oauth_token_user(Auth1),
Command = get_command_definition(Name, Version),
AccessCommands = get_access_commands(AccessCommands1, Version),
AccessCommands = get_all_access_commands(AccessCommands1),
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
ok -> execute_command2(Auth, Command, Arguments)
ok -> execute_check_policy(Auth, TokenJID, Command, Arguments)
end.
execute_command2(
_Auth, #ejabberd_commands{policy = open} = Command, Arguments) ->
execute_command2(Command, Arguments);
execute_command2(
_Auth, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
execute_command2(Command, Arguments);
execute_command2(
_Auth, #ejabberd_commands{policy = admin} = Command, Arguments) ->
execute_command2(Command, Arguments);
execute_command2(
admin, #ejabberd_commands{policy = user} = Command, Arguments) ->
execute_command2(Command, Arguments);
execute_command2(
noauth, #ejabberd_commands{policy = user} = Command, Arguments) ->
execute_command2(Command, Arguments);
execute_command2(
{User, Server, _, _}, #ejabberd_commands{policy = user} = Command, Arguments) ->
execute_command2(Command, [User, Server | Arguments]).
execute_command2(Command, Arguments) ->
execute_check_policy(
_Auth, _JID, #ejabberd_commands{policy = open} = Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_policy(
noauth, _JID, Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_policy(
_Auth, _JID, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_policy(
_Auth, JID, #ejabberd_commands{policy = admin} = Command, Arguments) ->
execute_check_access(JID, Command, Arguments);
execute_check_policy(
admin, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
execute_check_access(JID, Command, Arguments);
execute_check_policy(
{User, Server, _, _}, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
execute_check_access(JID, Command, [User, Server | Arguments]).
execute_check_access(_FromJID, #ejabberd_commands{access = []} = Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_access(undefined, _Command, _Arguments) ->
throw({error, access_rules_unauthorized});
execute_check_access(FromJID, #ejabberd_commands{access = AccessRefs} = Command, Arguments) ->
%% TODO Review: Do we have smarter / better way to check rule on other Host than global ?
Host = global,
Rules = lists:map(fun({Mod, AccessName, Default}) ->
gen_mod:get_module_opt(Host, Mod,
AccessName, fun(A) -> A end, Default);
(Default) ->
Default
end, AccessRefs),
case acl:any_rules_allowed(Host, Rules, FromJID) of
true ->
do_execute_command(Command, Arguments);
false ->
throw({error, access_rules_unauthorized})
end.
do_execute_command(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
Function = Command#ejabberd_commands.function,
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
@@ -592,31 +642,31 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerI
Command1
end,
AccessCommandsAllowed =
lists:filter(
fun({Access, Commands, ArgumentRestrictions}) ->
case check_access(Command, Access, Auth, CallerInfo) of
true ->
check_access_command(Commands, Command,
ArgumentRestrictions,
Method, Arguments);
false ->
false
end;
({Access, Commands}) ->
ArgumentRestrictions = [],
case check_access(Command, Access, Auth, CallerInfo) of
true ->
check_access_command(Commands, Command,
ArgumentRestrictions,
Method, Arguments);
false ->
false
end
end,
AccessCommands),
lists:filter(
fun({Access, Commands, ArgumentRestrictions}) ->
case check_access(Command, Access, Auth, CallerInfo) of
true ->
check_access_command(Commands, Command,
ArgumentRestrictions,
Method, Arguments);
false ->
false
end;
({Access, Commands}) ->
ArgumentRestrictions = [],
case check_access(Command, Access, Auth, CallerInfo) of
true ->
check_access_command(Commands, Command,
ArgumentRestrictions,
Method, Arguments);
false ->
false
end
end,
AccessCommands),
case AccessCommandsAllowed of
[] -> throw({error, account_unprivileged});
L when is_list(L) -> ok
[] -> throw({error, account_unprivileged});
L when is_list(L) -> ok
end.
-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
@@ -627,11 +677,11 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerI
check_auth(_Command, noauth) ->
no_auth_provided;
check_auth(Command, {User, Server, {oauth, Token}, _}) ->
Scope = erlang:atom_to_binary(Command#ejabberd_commands.name, utf8),
case ejabberd_oauth:check_token(User, Server, Scope, Token) of
ScopeList = cmd_scope(Command),
case ejabberd_oauth:check_token(User, Server, ScopeList, Token) of
true ->
{ok, User, Server};
false ->
_ ->
throw({error, invalid_account_data})
end;
check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
@@ -680,9 +730,9 @@ check_access2(Access, AccessInfo, Server) ->
check_access_command(Commands, Command, ArgumentRestrictions,
Method, Arguments) ->
case Commands==all orelse lists:member(Method, Commands) of
true -> check_access_arguments(Command, ArgumentRestrictions,
Arguments);
false -> false
true -> check_access_arguments(Command, ArgumentRestrictions,
Arguments);
false -> false
end.
check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
@@ -705,19 +755,23 @@ tag_arguments(ArgsDefs, Args) ->
Args).
%% Get commands for all version
get_all_access_commands(AccessCommands) ->
get_access_commands(AccessCommands, ?DEFAULT_VERSION).
get_access_commands(undefined, Version) ->
Cmds = get_commands(Version),
Cmds = get_exposed_commands(Version),
[{?POLICY_ACCESS, Cmds, []}];
get_access_commands(AccessCommands, _Version) ->
AccessCommands.
get_commands() ->
get_commands(?DEFAULT_VERSION).
get_commands(Version) ->
get_exposed_commands() ->
get_exposed_commands(?DEFAULT_VERSION).
get_exposed_commands(Version) ->
Opts0 = ejabberd_config:get_option(
commands,
fun(V) when is_list(V) -> V end,
[]),
[]),
Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0),
CommandsList = list_commands_policy(Version),
OpenCmds = [N || {N, _, _, open} <- CommandsList],
@@ -727,27 +781,32 @@ get_commands(Version) ->
Cmds =
lists:foldl(
fun([{add_commands, L}], Acc) ->
Cmds = case L of
open -> OpenCmds;
restricted -> RestrictedCmds;
admin -> AdminCmds;
user -> UserCmds;
_ when is_list(L) -> L
end,
Cmds = expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds),
lists:usort(Cmds ++ Acc);
([{remove_commands, L}], Acc) ->
Cmds = case L of
open -> OpenCmds;
restricted -> RestrictedCmds;
admin -> AdminCmds;
user -> UserCmds;
_ when is_list(L) -> L
end,
Cmds = expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds),
Acc -- Cmds;
(_, Acc) -> Acc
end, AdminCmds ++ UserCmds, Opts),
end, [], Opts),
Cmds.
%% This is used to allow mixing command policy (like open, user, admin, restricted), with command entry
expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_list(L) ->
lists:foldl(fun(open, Acc) -> OpenCmds ++ Acc;
(user, Acc) -> UserCmds ++ Acc;
(admin, Acc) -> AdminCmds ++ Acc;
(restricted, Acc) -> RestrictedCmds ++ Acc;
(Command, Acc) when is_atom(Command) ->
[Command|Acc]
end, [], L).
oauth_token_user(noauth) ->
undefined;
oauth_token_user(admin) ->
undefined;
oauth_token_user({User, Server, _, _}) ->
jid:make(User, Server, <<>>).
is_admin(_Name, admin, _Extra) ->
true;
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
+36 -7
View File
@@ -33,10 +33,12 @@
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,
get_ejabberd_config_path/0, is_using_elixir_config/0,
prepare_opt_val/4, convert_table_to_binary/5,
transform_options/1, collect_options/1, default_db/2,
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
env_binary_to_list/2, opt_type/1, may_hide_data/1]).
env_binary_to_list/2, opt_type/1, may_hide_data/1,
is_elixir_enabled/0]).
-export([start/2]).
@@ -90,7 +92,7 @@ hosts_to_start(State) ->
%% @private
%% At the moment, these functions are mainly used to setup unit tests.
-spec(start/2 :: (Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok).
-spec start(Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok.
start(Hosts, Opts) ->
mnesia_init(),
set_opts(set_hosts_in_options(Hosts, #state{opts = Opts})).
@@ -147,7 +149,18 @@ read_file(File) ->
{include_modules_configs, true}]).
read_file(File, Opts) ->
Terms1 = get_plain_terms_file(File, Opts),
Terms1 = case is_elixir_enabled() of
true ->
case 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(File) of
true ->
'Elixir.Ejabberd.Config':init(File),
'Elixir.Ejabberd.Config':get_ejabberd_opts();
false ->
get_plain_terms_file(File, Opts)
end;
false ->
get_plain_terms_file(File, Opts)
end,
Terms_macros = case proplists:get_bool(replace_macros, Opts) of
true -> replace_macros(Terms1);
false -> Terms1
@@ -220,9 +233,6 @@ env_binary_to_list(Application, Parameter) ->
%% in which the options 'include_config_file' were parsed
%% and the terms in those files were included.
%% @spec(iolist()) -> [term()]
get_plain_terms_file(File) ->
get_plain_terms_file(File, [{include_files, true}]).
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) ->
@@ -321,7 +331,9 @@ get_absolute_path(File) ->
File;
relative ->
{ok, Dir} = file:get_cwd(),
filename:absname_join(Dir, File)
filename:absname_join(Dir, File);
volumerelative ->
filename:absname(File)
end.
@@ -1043,6 +1055,23 @@ replace_modules(Modules) ->
%% Elixir module naming
%% ====================
-ifdef(ELIXIR_ENABLED).
is_elixir_enabled() ->
true.
-else.
is_elixir_enabled() ->
false.
-endif.
is_using_elixir_config() ->
case is_elixir_enabled() of
true ->
Config = get_ejabberd_config_path(),
'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config);
false ->
false
end.
%% If module name start with uppercase letter, this is an Elixir module:
is_elixir_module(Module) ->
case atom_to_list(Module) of
+10 -2
View File
@@ -212,7 +212,7 @@ process(["help" | Mode], Version) ->
end;
process(["--version", Arg | Args], _) ->
Version =
Version =
try
list_to_integer(Arg)
catch _:_ ->
@@ -321,7 +321,7 @@ call_command([CmdString | Args], Auth, AccessCommands, Version) ->
{ArgsFormat, ResultFormat} ->
case (catch format_args(Args, ArgsFormat)) of
ArgsFormatted when is_list(ArgsFormatted) ->
Result = ejabberd_commands:execute_command(AccessCommands,
Result = ejabberd_commands:execute_command(AccessCommands,
Auth, Command,
ArgsFormatted,
Version),
@@ -374,6 +374,12 @@ format_arg2(Arg, Parse)->
format_result({error, ErrorAtom}, _) ->
{io_lib:format("Error: ~p", [ErrorAtom]), make_status(error)};
%% An error should always be allowed to return extended error to help with API.
%% Extended error is of the form:
%% {error, type :: atom(), code :: int(), Desc :: string()}
format_result({error, ErrorAtom, Code, _Msg}, _) ->
{io_lib:format("Error: ~p", [ErrorAtom]), make_status(Code)};
format_result(Atom, {_Name, atom}) ->
io_lib:format("~p", [Atom]);
@@ -433,6 +439,8 @@ format_result(404, {_Name, _}) ->
make_status(ok) -> ?STATUS_SUCCESS;
make_status(true) -> ?STATUS_SUCCESS;
make_status(Code) when is_integer(Code), Code > 255 -> ?STATUS_ERROR;
make_status(Code) when is_integer(Code), Code > 0 -> Code;
make_status(_Error) -> ?STATUS_ERROR.
get_list_commands(Version) ->
+8 -2
View File
@@ -145,9 +145,14 @@ init({SockMod, Socket}, Opts) ->
DefinedHandlers = gen_mod:get_opt(
request_handlers, Opts,
fun(Hs) ->
Hs1 = lists:map(fun
({Mod, Path}) when is_atom(Mod) -> {Path, Mod};
({Path, Mod}) -> {Path, Mod}
end, Hs),
[{str:tokens(
iolist_to_binary(Path), <<"/">>),
Mod} || {Path, Mod} <- Hs]
Mod} || {Path, Mod} <- Hs1]
end, []),
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
Admin ++ Bind ++ XMLRPC,
@@ -763,7 +768,8 @@ parse_auth(<<"Basic ", Auth64/binary>>) ->
undefined;
Pos ->
{User, <<$:, Pass/binary>>} = erlang:split_binary(Auth, Pos-1),
{User, Pass}
PassUtf8 = unicode:characters_to_binary(binary_to_list(Pass), utf8),
{User, PassUtf8}
end;
parse_auth(<<"Bearer ", SToken/binary>>) ->
Token = str:strip(SToken),
+2 -1
View File
@@ -338,8 +338,9 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
init([Sid, Key, IP, HOpts]) ->
?DEBUG("started: ~p", [{Sid, Key, IP}]),
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
SOpts = lists:filtermap(fun({stream_managment, _}) -> true;
SOpts = lists:filtermap(fun({stream_management, _}) -> true;
({max_ack_queue, _}) -> true;
({ack_timeout, _}) -> true;
({resume_timeout, _}) -> true;
({max_resume_timeout, _}) -> true;
({resend_on_timeout, _}) -> true;
+2 -1
View File
@@ -112,8 +112,9 @@ socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) ->
%%% Internal
init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
SOpts = lists:filtermap(fun({stream_managment, _}) -> true;
SOpts = lists:filtermap(fun({stream_management, _}) -> true;
({max_ack_queue, _}) -> true;
({ack_timeout, _}) -> true;
({resume_timeout, _}) -> true;
({max_resume_timeout, _}) -> true;
({resend_on_timeout, _}) -> true;
+261 -125
View File
@@ -39,7 +39,6 @@
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,
@@ -48,7 +47,7 @@
process/2,
opt_type/1]).
-export([oauth_issue_token/1, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
-include("jlib.hrl").
@@ -57,6 +56,7 @@
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("ejabberd_oauth.hrl").
-include("ejabberd_commands.hrl").
@@ -65,23 +65,30 @@
%% * 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()} | server_admin,
scope = [] :: [binary()],
expire :: integer()
}).
-define(EXPIRE, 3600).
-define(EXPIRE, 4294967).
start() ->
init_db(mnesia, ?MYNAME),
DBMod = get_db_backend(),
DBMod:init(),
MaxSize =
ejabberd_config:get_option(
oauth_cache_size,
fun(I) when is_integer(I), I>0 -> I end,
1000),
LifeTime =
ejabberd_config:get_option(
oauth_cache_life_time,
fun(I) when is_integer(I), I>0 -> I end,
timer:hours(1) div 1000),
cache_tab:new(oauth_token,
[{max_size, MaxSize}, {life_time, LifeTime}]),
Expire = expire(),
application:set_env(oauth2, backend, ejabberd_oauth),
application:set_env(oauth2, expiry_time, Expire),
application:start(oauth2),
ChildSpec = {?MODULE, {?MODULE, start_link, []},
temporary, 1000, worker, [?MODULE]},
transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec),
ejabberd_commands:register_commands(get_commands_spec()),
ok.
@@ -90,57 +97,63 @@ start() ->
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",
desc = "Issue an oauth token for the given jid",
module = ?MODULE, function = oauth_issue_token,
args = [{scopes, string}],
args = [{jid, string},{ttl, integer}, {scopes, string}],
policy = restricted,
args_example = ["connected_users_number;muc_online_rooms"],
args_desc = ["List of scopes to allow, separated by ';'"],
args_example = ["user@server.com", "connected_users_number;muc_online_rooms"],
args_desc = ["Jid for which issue token",
"Time to live of generated token in seconds",
"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",
desc = "List oauth tokens, their user and 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}]}}}}
result = {tokens, {list, {token, {tuple, [{token, string}, {user, 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",
desc = "List scopes that can be granted to tokens generated through the command line, together with the commands they allow",
module = ?MODULE, function = oauth_list_scopes,
args = [],
policy = restricted,
result = {scopes, {list, {scope, string}}}
result = {scopes, {list, {scope, {tuple, [{scope, string}, {commands, 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 = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}},
result_desc = "List of remaining tokens"
}
].
oauth_issue_token(ScopesString) ->
oauth_issue_token(Jid, TTLSeconds, 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}
case jid:from_string(list_to_binary(Jid)) of
#jid{luser =Username, lserver = Server} ->
case oauth2:authorize_password({Username, Server}, Scopes, admin_generated) of
{ok, {_Ctx,Authorization}} ->
{ok, {_AppCtx2, Response}} = oauth2:issue_token(Authorization, [{expiry_time, TTLSeconds}]),
{ok, AccessToken} = oauth2_response:access_token(Response),
{ok, VerifiedScope} = oauth2_response:scope(Response),
{AccessToken, VerifiedScope, integer_to_list(TTLSeconds) ++ " seconds"};
{error, Error} ->
{error, Error}
end;
error ->
{error, "Invalid JID: " ++ Jid}
end.
oauth_list_tokens() ->
Tokens = mnesia:dirty_match_object(#oauth_token{us = server_admin, _ = '_'}),
Tokens = mnesia:dirty_match_object(#oauth_token{_ = '_'}),
{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].
[{Token, jid:to_string(jid:make(U,S,<<>>)), Scope, integer_to_list(Expires - TS) ++ " seconds"} ||
#oauth_token{token=Token, scope=Scope, us= {U,S},expire=Expires} <- Tokens].
oauth_revoke_token(Token) ->
@@ -148,8 +161,7 @@ oauth_revoke_token(Token) ->
oauth_list_tokens().
oauth_list_scopes() ->
get_cmd_scopes().
[ {Scope, string:join([atom_to_list(Cmd) || Cmd <- Cmds], ",")} || {Scope, Cmds} <- dict:to_list(get_cmd_scopes())].
@@ -170,15 +182,8 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info(clean, State) ->
{MegaSecs, Secs, MiniSecs} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs,
F = fun() ->
Ts = mnesia:select(
oauth_token,
[{#oauth_token{expire = '$1', _ = '_'},
[{'<', '$1', TS}],
['$_']}]),
lists:foreach(fun mnesia:delete_object/1, Ts)
end,
mnesia:async_dirty(F),
DBMod = get_db_backend(),
DBMod:clean(TS),
erlang:send_after(trunc(expire() * 1000 * (1 + MiniSecs / 1000000)),
self(), clean),
{noreply, State};
@@ -189,21 +194,11 @@ terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
init_db(mnesia, _Host) ->
mnesia:create_table(oauth_token,
[{disc_copies, [node()]},
{attributes,
record_info(fields, oauth_token)}]),
mnesia:add_table_copy(oauth_token, node(), disc_copies);
init_db(_, _) ->
ok.
get_client_identity(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
verify_redirection_uri(_, _, Ctx) -> {ok, Ctx}.
authenticate_user({User, Server}, {password, Password} = Ctx) ->
authenticate_user({User, Server}, Ctx) ->
case jid:make(User, Server, <<"">>) of
#jid{} = JID ->
Access =
@@ -213,11 +208,16 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
none),
case acl:match_rule(JID#jid.lserver, Access, JID) of
allow ->
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
true ->
{ok, {Ctx, {user, User, Server}}};
false ->
{error, badpass}
case Ctx of
{password, Password} ->
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
true ->
{ok, {Ctx, {user, User, Server}}};
false ->
{error, badpass}
end;
admin_generated ->
{ok, {Ctx, {user, User, Server}}}
end;
deny ->
{error, badpass}
@@ -229,8 +229,8 @@ authenticate_user({User, Server}, {password, Password} = Ctx) ->
authenticate_client(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
verify_resowner_scope({user, _User, _Server}, Scope, Ctx) ->
Cmds = ejabberd_commands:get_commands(),
Cmds1 = [sasl_auth | Cmds],
Cmds = ejabberd_commands:get_exposed_commands(),
Cmds1 = ['ejabberd:user', 'ejabberd:admin', sasl_auth | Cmds],
RegisteredScope = [atom_to_binary(C, utf8) || C <- Cmds1],
case oauth2_priv_set:is_subset(oauth2_priv_set:new(Scope),
oauth2_priv_set:new(RegisteredScope)) of
@@ -244,92 +244,132 @@ verify_resowner_scope(_, _, _) ->
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].
ScopeMap = lists:foldl(fun(Cmd, Accum) ->
case ejabberd_commands:get_command_policy_and_scope(Cmd) of
{ok, Policy, Scopes} when Policy =/= restricted ->
lists:foldl(fun(Scope, Accum2) ->
dict:append(Scope, Cmd, Accum2)
end, Accum, Scopes);
_ -> Accum
end end, dict:new(), ejabberd_commands:get_exposed_commands()),
ScopeMap.
%% 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.
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
% RegisteredScope = dict:fetch_keys(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.
-spec seconds_since_epoch(integer()) -> non_neg_integer().
seconds_since_epoch(Diff) ->
{Mega, Secs, _} = os:timestamp(),
Mega * 1000000 + Secs + Diff.
associate_access_code(_AccessCode, _Context, AppContext) ->
%put(?ACCESS_CODE_TABLE, AccessCode, Context),
{ok, AppContext}.
associate_access_token(AccessToken, Context, AppContext) ->
%% 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,
{user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>),
Expire = case proplists:get_value(expiry_time, AppContext, undefined) of
undefined ->
proplists:get_value(<<"expiry_time">>, Context, 0);
ExpiresIn ->
%% There is no clean way in oauth2 lib to actually override the TTL of the generated token.
%% It always pass the global configured value. Here we use the app context to pass the per-case
%% ttl if we want to override it.
seconds_since_epoch(ExpiresIn)
end,
{user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>),
Scope = proplists:get_value(<<"scope">>, Context, []),
Expire = proplists:get_value(<<"expiry_time">>, Context, 0),
R = #oauth_token{
token = AccessToken,
us = US,
us = {jid:nodeprep(User), jid:nodeprep(Server)},
scope = Scope,
expire = Expire
},
mnesia:dirty_write(R),
store(R),
{ok, AppContext}.
associate_refresh_token(_RefreshToken, _Context, AppContext) ->
%put(?REFRESH_TOKEN_TABLE, RefreshToken, Context),
{ok, AppContext}.
check_token(User, Server, Scope, Token) ->
check_token(User, Server, ScopeList, Token) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case catch mnesia:dirty_read(oauth_token, Token) of
[#oauth_token{us = {LUser, LServer},
scope = TokenScope,
expire = Expire}] ->
case lookup(Token) of
{ok, #oauth_token{us = {LUser, LServer},
scope = TokenScope,
expire = Expire}} ->
{MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs,
oauth2_priv_set:is_member(
Scope, oauth2_priv_set:new(TokenScope)) andalso
Expire > TS;
_ ->
false
end.
check_token(Scope, Token) ->
case catch mnesia:dirty_read(oauth_token, Token) of
[#oauth_token{us = US,
scope = TokenScope,
expire = Expire}] ->
{MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs,
case oauth2_priv_set:is_member(
Scope, oauth2_priv_set:new(TokenScope)) andalso
Expire > TS of
true -> case US of
{LUser, LServer} -> {ok, user, {LUser, LServer}};
server_admin -> {ok, server_admin}
end;
false -> false
if
Expire > TS ->
TokenScopeSet = oauth2_priv_set:new(TokenScope),
lists:any(fun(Scope) ->
oauth2_priv_set:is_member(Scope, TokenScopeSet) end,
ScopeList);
true ->
{false, expired}
end;
_ ->
false
{false, not_found}
end.
check_token(ScopeList, Token) ->
case lookup(Token) of
{ok, #oauth_token{us = US,
scope = TokenScope,
expire = Expire}} ->
{MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs,
if
Expire > TS ->
TokenScopeSet = oauth2_priv_set:new(TokenScope),
case lists:any(fun(Scope) ->
oauth2_priv_set:is_member(Scope, TokenScopeSet) end,
ScopeList) of
true -> {ok, user, US};
false -> {false, no_matching_scope}
end;
true ->
{false, expired}
end;
_ ->
{false, not_found}
end.
store(R) ->
cache_tab:insert(
oauth_token, R#oauth_token.token, R,
fun() ->
DBMod = get_db_backend(),
DBMod:store(R)
end).
lookup(Token) ->
cache_tab:lookup(
oauth_token, Token,
fun() ->
DBMod = get_db_backend(),
case DBMod:lookup(Token) of
#oauth_token{} = R -> {ok, R};
_ -> error
end
end).
expire() ->
ejabberd_config:get_option(
@@ -358,12 +398,9 @@ process(_Handlers,
?XAE(<<"form">>,
[{<<"action">>, <<"authorization_token">>},
{<<"method">>, <<"post">>}],
[?LABEL(<<"username">>, [?CT(<<"User">>), ?C(<<": ">>)]),
[?LABEL(<<"username">>, [?CT(<<"User (jid)">>), ?C(<<": ">>)]),
?INPUTID(<<"text">>, <<"username">>, <<"">>),
?BR,
?LABEL(<<"server">>, [?CT(<<"Server">>), ?C(<<": ">>)]),
?INPUTID(<<"text">>, <<"server">>, <<"">>),
?BR,
?LABEL(<<"password">>, [?CT(<<"Password">>), ?C(<<": ">>)]),
?INPUTID(<<"password">>, <<"password">>, <<"">>),
?INPUT(<<"hidden">>, <<"response_type">>, ResponseType),
@@ -372,6 +409,15 @@ process(_Handlers,
?INPUT(<<"hidden">>, <<"scope">>, Scope),
?INPUT(<<"hidden">>, <<"state">>, State),
?BR,
?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?CT(<<": ">>)]),
?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}],
[
?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>),
?XAC(<<"option">>, [{<<"value">>, <<"86400">>}],<<"1 Day">>),
?XAC(<<"option">>, [{<<"value">>, <<"2592000">>}],<<"1 Month">>),
?XAC(<<"option">>, [{<<"selected">>, <<"selected">>},{<<"value">>, <<"31536000">>}],<<"1 Year">>),
?XAC(<<"option">>, [{<<"value">>, <<"315360000">>}],<<"10 Years">>)]),
?BR,
?INPUTT(<<"submit">>, <<"">>, <<"Accept">>)
]),
Top =
@@ -415,11 +461,16 @@ process(_Handlers,
ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
SScope = proplists:get_value(<<"scope">>, Q, <<"">>),
Username = proplists:get_value(<<"username">>, Q, <<"">>),
Server = proplists:get_value(<<"server">>, Q, <<"">>),
StringJID = proplists:get_value(<<"username">>, Q, <<"">>),
#jid{user = Username, server = Server} = jid:from_string(StringJID),
Password = proplists:get_value(<<"password">>, Q, <<"">>),
State = proplists:get_value(<<"state">>, Q, <<"">>),
Scope = str:tokens(SScope, <<" ">>),
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
ExpiresIn = case TTL of
<<>> -> undefined;
_ -> jlib:binary_to_integer(TTL)
end,
case oauth2:authorize_password({Username, Server},
ClientId,
RedirectURI,
@@ -427,10 +478,18 @@ process(_Handlers,
{password, Password}) of
{ok, {_AppContext, Authorization}} ->
{ok, {_AppContext2, Response}} =
oauth2:issue_token(Authorization, none),
oauth2:issue_token(Authorization, [{expiry_time, ExpiresIn} || ExpiresIn /= undefined ]),
{ok, AccessToken} = oauth2_response:access_token(Response),
{ok, Type} = oauth2_response:token_type(Response),
{ok, Expires} = oauth2_response:expires_in(Response),
%%Ugly: workardound to return the correct expirity time, given than oauth2 lib doesn't really have
%%per-case expirity time.
Expires = case ExpiresIn of
undefined ->
{ok, Ex} = oauth2_response:expires_in(Response),
Ex;
_ ->
ExpiresIn
end,
{ok, VerifiedScope} = oauth2_response:scope(Response),
%oauth2_wrq:redirected_access_token_response(ReqData,
% RedirectURI,
@@ -459,11 +518,82 @@ process(_Handlers,
}],
ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])}
end;
process(_Handlers,
#request{method = 'POST', q = Q, lang = _Lang,
path = [_, <<"token">>]}) ->
case proplists:get_value(<<"grant_type">>, Q, <<"">>) of
<<"password">> ->
SScope = proplists:get_value(<<"scope">>, Q, <<"">>),
StringJID = proplists:get_value(<<"username">>, Q, <<"">>),
#jid{user = Username, server = Server} = jid:from_string(StringJID),
Password = proplists:get_value(<<"password">>, Q, <<"">>),
Scope = str:tokens(SScope, <<" ">>),
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
ExpiresIn = case TTL of
<<>> -> undefined;
_ -> jlib:binary_to_integer(TTL)
end,
case oauth2:authorize_password({Username, Server},
Scope,
{password, Password}) of
{ok, {_AppContext, Authorization}} ->
{ok, {_AppContext2, Response}} =
oauth2:issue_token(Authorization, [{expiry_time, ExpiresIn} || ExpiresIn /= undefined ]),
{ok, AccessToken} = oauth2_response:access_token(Response),
{ok, Type} = oauth2_response:token_type(Response),
%%Ugly: workardound to return the correct expirity time, given than oauth2 lib doesn't really have
%%per-case expirity time.
Expires = case ExpiresIn of
undefined ->
{ok, Ex} = oauth2_response:expires_in(Response),
Ex;
_ ->
ExpiresIn
end,
{ok, VerifiedScope} = oauth2_response:scope(Response),
json_response(200, {[
{<<"access_token">>, AccessToken},
{<<"token_type">>, Type},
{<<"scope">>, str:join(VerifiedScope, <<" ">>)},
{<<"expires_in">>, Expires}]});
{error, Error} when is_atom(Error) ->
json_error(400, <<"invalid_grant">>, Error)
end;
_OtherGrantType ->
json_error(400, <<"unsupported_grant_type">>, unsupported_grant_type)
end;
process(_Handlers, _Request) ->
ejabberd_web:error(not_found).
-spec get_db_backend() -> module().
get_db_backend() ->
DBType = ejabberd_config:get_option(
oauth_db_type,
fun(T) -> ejabberd_config:v_db(?MODULE, T) end,
mnesia),
list_to_atom("ejabberd_oauth_" ++ atom_to_list(DBType)).
%% Headers as per RFC 6749
json_response(Code, Body) ->
{Code, [{<<"Content-Type">>, <<"application/json;charset=UTF-8">>},
{<<"Cache-Control">>, <<"no-store">>},
{<<"Pragma">>, <<"no-cache">>}],
jiffy:encode(Body)}.
%% OAauth error are defined in:
%% https://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-5.2
json_error(Code, Error, Reason) ->
Desc = json_error_desc(Reason),
Body = {[{<<"error">>, Error},
{<<"error_description">>, Desc}]},
json_response(Code, Body).
json_error_desc(access_denied) -> <<"Access denied">>;
json_error_desc(unsupported_grant_type) -> <<"Unsupported grant type">>;
json_error_desc(invalid_scope) -> <<"Invalid scope">>.
web_head() ->
[?XA(<<"meta">>, [{<<"http-equiv">>, <<"X-UA-Compatible">>},
@@ -577,7 +707,7 @@ css() ->
text-decoration: underline;
}
.container > .section {
.container > .section {
background: #424A55;
}
@@ -595,4 +725,10 @@ opt_type(oauth_expire) ->
fun(I) when is_integer(I), I >= 0 -> I end;
opt_type(oauth_access) ->
fun acl:access_rules_validator/1;
opt_type(_) -> [oauth_expire, oauth_access].
opt_type(oauth_db_type) ->
fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(oauth_cache_life_time) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(oauth_cache_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(_) -> [oauth_expire, oauth_access, oauth_db_type].
+65
View File
@@ -0,0 +1,65 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_oauth_mnesia.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : OAUTH2 mnesia backend
%%% Created : 20 Jul 2016 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_oauth_mnesia).
-export([init/0,
store/1,
lookup/1,
clean/1]).
-include("ejabberd_oauth.hrl").
init() ->
mnesia:create_table(oauth_token,
[{disc_copies, [node()]},
{attributes,
record_info(fields, oauth_token)}]),
mnesia:add_table_copy(oauth_token, node(), disc_copies),
ok.
store(R) ->
mnesia:dirty_write(R).
lookup(Token) ->
case catch mnesia:dirty_read(oauth_token, Token) of
[R] ->
R;
_ ->
false
end.
clean(TS) ->
F = fun() ->
Ts = mnesia:select(
oauth_token,
[{#oauth_token{expire = '$1', _ = '_'},
[{'<', '$1', TS}],
['$_']}]),
lists:foreach(fun mnesia:delete_object/1, Ts)
end,
mnesia:async_dirty(F).
+78
View File
@@ -0,0 +1,78 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_oauth_sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : OAUTH2 SQL backend
%%% Created : 27 Jul 2016 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_oauth_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-export([init/0,
store/1,
lookup/1,
clean/1]).
-include("ejabberd_oauth.hrl").
-include("ejabberd.hrl").
-include("ejabberd_sql_pt.hrl").
-include("jlib.hrl").
init() ->
ok.
store(R) ->
Token = R#oauth_token.token,
{User, Server} = R#oauth_token.us,
SJID = jid:to_string({User, Server, <<"">>}),
Scope = str:join(R#oauth_token.scope, <<" ">>),
Expire = R#oauth_token.expire,
?SQL_UPSERT(
?MYNAME,
"oauth_token",
["!token=%(Token)s",
"jid=%(SJID)s",
"scope=%(Scope)s",
"expire=%(Expire)d"]).
lookup(Token) ->
case ejabberd_sql:sql_query(
?MYNAME,
?SQL("select @(jid)s, @(scope)s, @(expire)d"
" from oauth_token where token=%(Token)s")) of
{selected, [{SJID, Scope, Expire}]} ->
JID = jid:from_string(SJID),
US = {JID#jid.luser, JID#jid.lserver},
#oauth_token{token = Token,
us = US,
scope = str:tokens(Scope, <<" ">>),
expire = Expire};
_ ->
false
end.
clean(TS) ->
ejabberd_sql:sql_query(
?MYNAME,
?SQL("delete from oauth_token where expire < %(TS)d")).
+24 -18
View File
@@ -473,28 +473,34 @@ send_element(Pid, El) ->
%%% ejabberd commands
get_commands_spec() ->
[#ejabberd_commands{name = incoming_s2s_number,
tags = [stats, s2s],
desc =
"Number of incoming s2s connections on "
"the node",
policy = admin,
module = ?MODULE, function = incoming_s2s_number,
args = [], result = {s2s_incoming, integer}},
#ejabberd_commands{name = outgoing_s2s_number,
tags = [stats, s2s],
desc =
"Number of outgoing s2s connections on "
"the node",
policy = admin,
module = ?MODULE, function = outgoing_s2s_number,
args = [], result = {s2s_outgoing, integer}}].
[#ejabberd_commands{
name = incoming_s2s_number,
tags = [stats, s2s],
desc = "Number of incoming s2s connections on the node",
policy = admin,
module = ?MODULE, function = incoming_s2s_number,
args = [], result = {s2s_incoming, integer}},
#ejabberd_commands{
name = outgoing_s2s_number,
tags = [stats, s2s],
desc = "Number of outgoing s2s connections on the node",
policy = admin,
module = ?MODULE, function = outgoing_s2s_number,
args = [], result = {s2s_outgoing, integer}}].
%% TODO Move those stats commands to ejabberd stats command ?
incoming_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_in_sup)).
supervisor_count(ejabberd_s2s_in_sup).
outgoing_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_out_sup)).
supervisor_count(ejabberd_s2s_out_sup).
supervisor_count(Supervisor) ->
case catch supervisor:which_children(Supervisor) of
{'EXIT', _} -> 0;
Result ->
length(Result)
end.
%%%----------------------------------------------------------------------
%%% Update Mnesia tables
+141 -26
View File
@@ -36,7 +36,7 @@
-behaviour(?GEN_FSM).
%% External exports
-export([start/2, start_link/2, send_text/2,
-export([start/0, start/2, start_link/2, send_text/2,
send_element/2, socket_type/0, transform_listen_option/2]).
-export([init/1, wait_for_stream/2,
@@ -44,19 +44,10 @@
handle_event/3, handle_sync_event/4, code_change/4,
handle_info/3, terminate/3, print_state/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_service.hrl").
-include("mod_privacy.hrl").
-include("jlib.hrl").
-record(state,
{socket :: ejabberd_socket:socket_state(),
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
streamid = <<"">> :: binary(),
host_opts = dict:new() :: ?TDICT,
host = <<"">> :: binary(),
access :: atom(),
check_from = true :: boolean()}).
-export([get_delegated_ns/1]).
%-define(DBGFSM, true).
@@ -99,6 +90,15 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
%% for xep-0355
%% table contans records like {namespace, fitering attributes, pid(),
%% host, disco info for general case, bare jid disco info }
start() ->
ets:new(delegated_namespaces, [named_table, public]),
ets:new(hooks_tmp, [named_table, public]).
start(SockData, Opts) ->
supervisor:start_child(ejabberd_service_sup,
[SockData, Opts]).
@@ -109,6 +109,9 @@ start_link(SockData, Opts) ->
socket_type() -> xml_stream.
get_delegated_ns(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef, {get_delegated_ns}).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
@@ -141,6 +144,21 @@ init([{SockMod, Socket}, Opts]) ->
p1_sha:sha(crypto:rand_bytes(20))),
dict:from_list([{global, Pass}])
end,
%% privilege access to entities data
PrivAccess = case lists:keysearch(privilege_access, 1, Opts) of
{value, {_, PrivAcc}} -> PrivAcc;
_ -> []
end,
Delegations = case lists:keyfind(delegations, 1, Opts) of
{delegations, Del} ->
lists:foldl(
fun({Ns, FiltAttr}, D) when Ns /= ?NS_DELEGATION ->
Attr = proplists:get_value(filtering, FiltAttr, []),
D ++ [{Ns, Attr}];
(_Deleg, D) -> D
end, [], Del);
false -> []
end,
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
{value, {_, S}} -> S;
_ -> none
@@ -154,8 +172,9 @@ init([{SockMod, Socket}, Opts]) ->
SockMod:change_shaper(Socket, Shaper),
{ok, wait_for_stream,
#state{socket = Socket, sockmod = SockMod,
streamid = new_id(), host_opts = HostOpts,
access = Access, check_from = CheckFrom}}.
streamid = new_id(), host_opts = HostOpts, access = Access,
check_from = CheckFrom, privilege_access = PrivAccess,
delegations = Delegations}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@@ -224,9 +243,34 @@ wait_for_handshake({xmlstreamelement, El}, StateData) ->
fun (H) ->
ejabberd_router:register_route(H, ?MYNAME),
?INFO_MSG("Route registered for service ~p~n",
[H])
[H]),
ejabberd_hooks:run(component_connected,
[H])
end, dict:fetch_keys(StateData#state.host_opts)),
{next_state, stream_established, StateData};
mod_privilege:advertise_permissions(StateData),
DelegatedNs = mod_delegation:advertise_delegations(StateData),
RosterAccess = proplists:get_value(roster,
StateData#state.privilege_access),
case proplists:get_value(presence,
StateData#state.privilege_access) of
<<"managed_entity">> ->
mod_privilege:initial_presences(StateData),
Fun = mod_privilege:process_presence(self()),
add_hooks(user_send_packet, Fun);
<<"roster">> when (RosterAccess == <<"both">>) or
(RosterAccess == <<"get">>) ->
mod_privilege:initial_presences(StateData),
Fun = mod_privilege:process_presence(self()),
add_hooks(user_send_packet, Fun),
Fun2 = mod_privilege:process_roster_presence(self()),
add_hooks(s2s_receive_packet, Fun2);
_ -> ok
end,
{next_state, stream_established,
StateData#state{delegations = DelegatedNs}};
_ ->
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
{stop, normal, StateData}
@@ -274,11 +318,12 @@ stream_established({xmlstreamelement, El}, StateData) ->
<<"">> -> error;
_ -> jid:from_string(To)
end,
if ((Name == <<"iq">>) or (Name == <<"message">>) or
(Name == <<"presence">>))
and (ToJID /= error)
and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl);
if (Name == <<"iq">>) and (ToJID /= error) and (FromJID /= error) ->
mod_privilege:process_iq(StateData, FromJID, ToJID, NewEl);
(Name == <<"presence">>) and (ToJID /= error) and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl);
(Name == <<"message">>) and (ToJID /= error) and (FromJID /= error) ->
mod_privilege:process_message(StateData, FromJID, ToJID, NewEl);
true ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
Txt = <<"Incorrect stanza name or from/to JID">>,
@@ -328,8 +373,11 @@ handle_event(_Event, StateName, StateData) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event(_Event, _From, StateName,
StateData) ->
handle_sync_event({get_delegated_ns}, _From, StateName, StateData) ->
Reply = {StateData#state.host, StateData#state.delegations},
{reply, Reply, StateName, StateData};
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
@@ -368,6 +416,36 @@ handle_info({route, From, To, Packet}, StateName,
ejabberd_router:route_error(To, From, Err, Packet)
end,
{next_state, StateName, StateData};
handle_info({user_presence, Packet, From},
stream_established, StateData) ->
To = jid:from_string(StateData#state.host),
PacketNew = jlib:replace_from_to(From, To, Packet),
send_element(StateData, PacketNew),
{next_state, stream_established, StateData};
handle_info({roster_presence, Packet, From},
stream_established, StateData) ->
%% check that current presence stanza is equivalent to last
PresenceNew = jlib:remove_attr(<<"to">>, Packet),
Dict = StateData#state.last_pres,
LastPresence =
try dict:fetch(From, Dict)
catch _:_ ->
undefined
end,
case mod_privilege:compare_presences(LastPresence, PresenceNew) of
false ->
#xmlel{attrs = Attrs} = PresenceNew,
Presence = PresenceNew#xmlel{attrs = [{<<"to">>, StateData#state.host} | Attrs]},
send_element(StateData, Presence),
DictNew = dict:store(From, PresenceNew, Dict),
StateDataNew = StateData#state{last_pres = DictNew},
{next_state, stream_established, StateDataNew};
_ ->
{next_state, stream_established, StateData}
end;
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{next_state, StateName, StateData}.
@@ -382,9 +460,30 @@ terminate(Reason, StateName, StateData) ->
case StateName of
stream_established ->
lists:foreach(fun (H) ->
ejabberd_router:unregister_route(H)
ejabberd_router:unregister_route(H),
ejabberd_hooks:run(component_disconnected,
[StateData#state.host, Reason])
end,
dict:fetch_keys(StateData#state.host_opts));
dict:fetch_keys(StateData#state.host_opts)),
lists:foreach(fun({Ns, _FilterAttr}) ->
ets:delete(delegated_namespaces, Ns),
remove_iq_handlers(Ns)
end, StateData#state.delegations),
RosterAccess = proplists:get_value(roster, StateData#state.privilege_access),
case proplists:get_value(presence, StateData#state.privilege_access) of
<<"managed_entity">> ->
Fun = mod_privilege:process_presence(self()),
remove_hooks(user_send_packet, Fun);
<<"roster">> when (RosterAccess == <<"both">>) or
(RosterAccess == <<"get">>) ->
Fun = mod_privilege:process_presence(self()),
remove_hooks(user_send_packet, Fun),
Fun2 = mod_privilege:process_roster_presence(self()),
remove_hooks(s2s_receive_packet, Fun2);
_ -> ok
end;
_ -> ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
@@ -444,3 +543,19 @@ fsm_limit_opts(Opts) ->
opt_type(max_fsm_queue) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(_) -> [max_fsm_queue].
remove_iq_handlers(Ns) ->
lists:foreach(fun(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, Ns),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, Ns)
end, ?MYHOSTS).
add_hooks(Hook, Fun) ->
lists:foreach(fun(Host) ->
ejabberd_hooks:add(Hook, Host,Fun, 100)
end, ?MYHOSTS).
remove_hooks(Hook, Fun) ->
lists:foreach(fun(Host) ->
ejabberd_hooks:delete(Hook, Host, Fun, 100)
end, ?MYHOSTS).
+24 -18
View File
@@ -270,25 +270,28 @@ get_session_pid(User, Server, Resource) ->
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
set_offline_info({Time, _Pid}, User, Server, Resource, Info) ->
SID = {Time, undefined},
set_offline_info(SID, User, Server, Resource, Info) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
set_session(SID, LUser, LServer, LResource, undefined, Info).
set_session(SID, LUser, LServer, LResource, undefined, [offline | 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;
[#session{sid = {Time, _}, info = Info}] ->
case proplists:get_bool(offline, Info) of
true ->
Info;
false ->
none
end;
_ ->
none
end.
@@ -425,11 +428,12 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
-spec online([#session{}]) -> [#session{}].
online(Sessions) ->
lists:filter(fun(#session{sid = {_, undefined}}) ->
false;
(_) ->
true
end, Sessions).
lists:filter(fun is_online/1, Sessions).
-spec is_online(#session{}) -> boolean().
is_online(#session{info = Info}) ->
not proplists:get_bool(offline, Info).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -678,15 +682,17 @@ check_for_sessions_to_replace(User, Server, Resource) ->
check_max_sessions(LUser, LServer).
check_existing_resources(LUser, LServer, LResource) ->
SIDs = get_resource_sessions(LUser, LServer, LResource),
if SIDs == [] -> ok;
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer, LResource),
{OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
lists:foreach(fun(#session{sid = S}) ->
Mod:delete_session(LUser, LServer, LResource, S)
end, OfflineSs),
if OnlineSs == [] -> ok;
true ->
SIDs = [SID || #session{sid = SID} <- OnlineSs],
MaxSID = lists:max(SIDs),
lists:foreach(fun ({_, undefined} = S) ->
Mod = get_sm_backend(LServer),
Mod:delete_session(LUser, LServer, LResource,
S);
({_, Pid} = S) when S /= MaxSID ->
lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->
Pid ! replaced;
(_) -> ok
end,
+5 -2
View File
@@ -145,7 +145,10 @@ clean_table() ->
{_, SID} = binary_to_term(USSIDKey),
node(element(2, SID)) == node()
end, Vals),
Q1 = ["HDEL", ServKey | Vals1],
Q1 = case Vals1 of
[] -> [];
_ -> ["HDEL", ServKey | Vals1]
end,
Q2 = lists:map(
fun(USSIDKey) ->
{US, SID} = binary_to_term(USSIDKey),
@@ -153,7 +156,7 @@ clean_table() ->
SIDKey = sid_to_key(SID),
["HDEL", USKey, SIDKey]
end, Vals1),
Res = ejabberd_redis:qp([Q1|Q2]),
Res = ejabberd_redis:qp(lists:delete([], [Q1|Q2])),
case lists:filter(
fun({ok, _}) -> false;
(_) -> true
+1 -1
View File
@@ -790,7 +790,7 @@ pgsql_connect(Server, Port, DB, Username, Password) ->
{port, Port},
{as_binary, true}]) of
{ok, Ref} ->
pgsql:squery(Ref, [<<"alter database ">>, DB, <<" set ">>,
pgsql:squery(Ref, [<<"alter database \"">>, DB, <<"\" set ">>,
<<"standard_conforming_strings='off';">>]),
pgsql:squery(Ref, [<<"set standard_conforming_strings to 'off';">>]),
{ok, Ref};
+24 -16
View File
@@ -74,20 +74,27 @@ get_acl_rule([<<"vhosts">>], _) ->
%% The pages of a vhost are only accesible if the user is admin of that vhost:
get_acl_rule([<<"server">>, VHost | _RPath], Method)
when Method =:= 'GET' orelse Method =:= 'HEAD' ->
{VHost, [configure, webadmin_view]};
AC = gen_mod:get_module_opt(VHost, ejabberd_web_admin,
access, fun(A) -> A end, configure),
ACR = gen_mod:get_module_opt(VHost, ejabberd_web_admin,
access_readonly, fun(A) -> A end, webadmin_view),
{VHost, [AC, ACR]};
get_acl_rule([<<"server">>, VHost | _RPath], 'POST') ->
{VHost, [configure]};
AC = gen_mod:get_module_opt(VHost, ejabberd_web_admin,
access, fun(A) -> A end, configure),
{VHost, [AC]};
%% Default rule: only global admins can access any other random page
get_acl_rule(_RPath, Method)
when Method =:= 'GET' orelse Method =:= 'HEAD' ->
{global, [configure, webadmin_view]};
get_acl_rule(_RPath, 'POST') -> {global, [configure]}.
is_acl_match(Host, Rules, Jid) ->
lists:any(fun (Rule) ->
allow == acl:match_rule(Host, Rule, Jid)
end,
Rules).
AC = gen_mod:get_module_opt(global, ejabberd_web_admin,
access, fun(A) -> A end, configure),
ACR = gen_mod:get_module_opt(global, ejabberd_web_admin,
access_readonly, fun(A) -> A end, webadmin_view),
{global, [AC, ACR]};
get_acl_rule(_RPath, 'POST') ->
AC = gen_mod:get_module_opt(global, ejabberd_web_admin,
access, fun(A) -> A end, configure),
{global, [AC]}.
%%%==================================
%%%% Menu Items Access
@@ -138,7 +145,7 @@ is_allowed_path([<<"admin">> | Path], JID) ->
is_allowed_path(Path, JID);
is_allowed_path(Path, JID) ->
{HostOfRule, AccessRule} = get_acl_rule(Path, 'GET'),
is_acl_match(HostOfRule, AccessRule, JID).
acl:any_rules_allowed(HostOfRule, AccessRule, JID).
%% @spec(Path) -> URL
%% where Path = [string()]
@@ -266,8 +273,8 @@ get_auth_account(HostOfRule, AccessRule, User, Server,
Pass) ->
case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
true ->
case is_acl_match(HostOfRule, AccessRule,
jid:make(User, Server, <<"">>))
case acl:any_rules_allowed(HostOfRule, AccessRule,
jid:make(User, Server, <<"">>))
of
false -> {unauthorized, <<"unprivileged-account">>};
true -> {ok, {User, Server}}
@@ -1333,7 +1340,7 @@ parse_access_rule(Text) ->
list_vhosts(Lang, JID) ->
Hosts = (?MYHOSTS),
HostsAllowed = lists:filter(fun (Host) ->
is_acl_match(Host,
acl:any_rules_allowed(Host,
[configure, webadmin_view],
JID)
end,
@@ -2965,7 +2972,8 @@ make_menu_item(item, 3, URI, Name, Lang) ->
%%%==================================
opt_type(access) -> fun (V) -> V end;
opt_type(_) -> [access].
opt_type(access) -> fun acl:access_rules_validator/1;
opt_type(access_readonly) -> fun acl:access_rules_validator/1;
opt_type(_) -> [access, access_readonly].
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=:
+1 -1
View File
@@ -186,7 +186,7 @@ delete(LServer, Table, ConvertFun) ->
mnesia:write_lock_table(Table),
{_N, SQLs} =
mnesia:foldl(
fun(R, {N, SQLs} = Acc) ->
fun(R, Acc) ->
case ConvertFun(LServer, R) of
[] ->
Acc;
+34 -3
View File
@@ -484,17 +484,28 @@ compile_deps(_Module, _Spec, DestDir) ->
filelib:ensure_dir(filename:join(Ebin, ".")),
Result = lists:foldl(fun(Dep, Acc) ->
Inc = filename:join(Dep, "include"),
Lib = filename:join(Dep, "lib"),
Src = filename:join(Dep, "src"),
Options = [{outdir, Ebin}, {i, Inc}],
[file:copy(App, Ebin) || App <- filelib:wildcard(Src++"/*.app")],
Acc++[case compile:file(File, Options) of
%% Compile erlang files
Acc1 = Acc ++ [case compile:file(File, Options) of
{ok, _} -> ok;
{ok, _, _} -> ok;
{ok, _, _, _} -> ok;
error -> {error, {compilation_failed, File}};
Error -> Error
end
|| File <- filelib:wildcard(Src++"/*.erl")]
|| File <- filelib:wildcard(Src++"/*.erl")],
%% Compile elixir files
Acc1 ++ [case compile_elixir_file(Ebin, File) of
{ok, _} -> ok;
{error, File} -> {error, {compilation_failed, File}}
end
|| File <- filelib:wildcard(Lib ++ "/*.ex")]
end, [], filelib:wildcard("deps/*")),
case lists:dropwhile(
fun(ok) -> true;
@@ -515,6 +526,8 @@ compile(_Module, _Spec, DestDir) ->
verbose, report_errors, report_warnings]
++ ExtLib,
[file:copy(App, Ebin) || App <- filelib:wildcard("src/*.app")],
%% Compile erlang files
Result = [case compile:file(File, Options) of
{ok, _} -> ok;
{ok, _, _} -> ok;
@@ -523,14 +536,32 @@ compile(_Module, _Spec, DestDir) ->
Error -> Error
end
|| File <- filelib:wildcard("src/*.erl")],
%% Compile elixir files
Result1 = Result ++ [case compile_elixir_file(Ebin, File) of
{ok, _} -> ok;
{error, File} -> {error, {compilation_failed, File}}
end
|| File <- filelib:wildcard("lib/*.ex")],
case lists:dropwhile(
fun(ok) -> true;
(_) -> false
end, Result) of
end, Result1) of
[] -> ok;
[Error|_] -> Error
end.
compile_elixir_file(Dest, File) when is_list(Dest) and is_list(File) ->
compile_elixir_file(list_to_binary(Dest), list_to_binary(File));
compile_elixir_file(Dest, File) ->
try 'Elixir.Kernel.ParallelCompiler':files_to_path([File], Dest, []) of
[Module] -> {ok, Module}
catch
_ -> {error, File}
end.
install(Module, Spec, DestDir) ->
Errors = lists:dropwhile(fun({_, {ok, _}}) -> true;
(_) -> false
+59 -16
View File
@@ -53,6 +53,7 @@
-callback start(binary(), opts()) -> any().
-callback stop(binary()) -> any().
-callback mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
-callback depends(binary(), opts()) -> [{module(), hard | soft}].
-export_type([opts/0]).
-export_type([db_type/0]).
@@ -77,18 +78,56 @@ start_modules() ->
get_modules_options(Host) ->
ejabberd_config:get_option(
{modules, Host},
fun(Mods) ->
lists:map(
{modules, Host},
fun(Mods) ->
lists:map(
fun({M, A}) when is_atom(M), is_list(A) ->
{M, A}
{M, A}
end, Mods)
end, []).
end, []).
sort_modules(Host, ModOpts) ->
G = digraph:new([acyclic]),
lists:foreach(
fun({Mod, Opts}) ->
digraph:add_vertex(G, Mod, Opts),
Deps = try Mod:depends(Host, Opts) catch _:undef -> [] end,
lists:foreach(
fun({DepMod, Type}) ->
case lists:keyfind(DepMod, 1, ModOpts) of
false when Type == hard ->
ErrTxt = io_lib:format(
"failed to load module '~s' "
"because it depends on module '~s' "
"which is not found in the config",
[Mod, DepMod]),
?ERROR_MSG(ErrTxt, []),
digraph:del_vertex(G, Mod),
maybe_halt_ejabberd(ErrTxt);
false when Type == soft ->
?WARNING_MSG("module '~s' is recommended for "
"module '~s' but is not found in "
"the config",
[DepMod, Mod]);
{DepMod, DepOpts} ->
digraph:add_vertex(G, DepMod, DepOpts),
case digraph:add_edge(G, DepMod, Mod) of
{error, {bad_edge, Path}} ->
?WARNING_MSG("cyclic dependency detected "
"between modules: ~p",
[Path]);
_ ->
ok
end
end
end, Deps)
end, ModOpts),
[digraph:vertex(G, V) || V <- digraph_utils:topsort(G)].
-spec start_modules(binary()) -> any().
start_modules(Host) ->
Modules = get_modules_options(Host),
Modules = sort_modules(Host, get_modules_options(Host)),
lists:foreach(
fun({Module, Opts}) ->
start_module(Host, Module, Opts)
@@ -121,16 +160,20 @@ start_module(Host, Module, Opts0) ->
[Module, Host, Opts, Class, Reason,
erlang:get_stacktrace()]),
?CRITICAL_MSG(ErrorText, []),
case is_app_running(ejabberd) of
true ->
erlang:raise(Class, Reason, erlang:get_stacktrace());
false ->
?CRITICAL_MSG("ejabberd initialization was aborted "
"because a module start failed.",
[]),
timer:sleep(3000),
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
end
maybe_halt_ejabberd(ErrorText),
erlang:raise(Class, Reason, erlang:get_stacktrace())
end.
maybe_halt_ejabberd(ErrorText) ->
case is_app_running(ejabberd) of
false ->
?CRITICAL_MSG("ejabberd initialization was aborted "
"because a module start failed.",
[]),
timer:sleep(3000),
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199));
true ->
ok
end.
is_app_running(AppName) ->
+25 -1
View File
@@ -50,11 +50,35 @@
-spec start() -> ok.
start() ->
{ok, Owner} = ets_owner(),
SplitPattern = binary:compile_pattern([<<"@">>, <<"/">>]),
catch ets:new(jlib, [named_table, protected, set, {keypos, 1}]),
%% Table is public to allow ETS insert to fix / update the table even if table already exist
%% with another owner.
catch ets:new(jlib, [named_table, public, set, {keypos, 1}, {heir, Owner, undefined}]),
ets:insert(jlib, {string_to_jid_pattern, SplitPattern}),
ok.
ets_owner() ->
case whereis(jlib_ets) of
undefined ->
Pid = spawn(fun() -> ets_keepalive() end),
case catch register(jlib_ets, Pid) of
true ->
{ok, Pid};
Error -> Error
end;
Pid ->
{ok,Pid}
end.
%% Process used to keep jlib ETS table alive in case the original owner dies.
%% The table need to be public, otherwise subsequent inserts would fail.
ets_keepalive() ->
receive
_ ->
ets_keepalive()
end.
-spec make(binary(), binary(), binary()) -> jid() | error.
make(User, Server, Resource) ->
+22 -63
View File
@@ -307,21 +307,16 @@ get_iq_namespace(#xmlel{name = <<"iq">>, children = Els}) ->
get_iq_namespace(_) -> <<"">>.
%%
-spec(iq_query_info/1 ::
(
Xmlel :: xmlel())
-> iq_request() | 'reply' | 'invalid' | 'not_iq'
).
-spec iq_query_info(Xmlel :: xmlel()) ->
iq_request() | 'reply' | 'invalid' | 'not_iq'.
%% @spec (xmlelement()) -> iq() | reply | invalid | not_iq
iq_query_info(El) -> iq_info_internal(El, request).
%%
-spec(iq_query_or_response_info/1 ::
(
Xmlel :: xmlel())
-> iq_request() | iq_reply() | 'reply' | 'invalid' | 'not_iq'
).
-spec iq_query_or_response_info(Xmlel :: xmlel()) ->
iq_request() | iq_reply() |
'reply' | 'invalid' | 'not_iq'.
iq_query_or_response_info(El) ->
iq_info_internal(El, any).
@@ -373,31 +368,27 @@ iq_type_to_string(get) -> <<"get">>;
iq_type_to_string(result) -> <<"result">>;
iq_type_to_string(error) -> <<"error">>.
-spec(iq_to_xml/1 ::
(
IQ :: iq())
-> xmlel()
).
-spec iq_to_xml(IQ :: iq()) -> xmlel().
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
Children =
if
is_list(SubEl) -> SubEl;
true -> [SubEl]
end,
if ID /= <<"">> ->
#xmlel{name = <<"iq">>,
attrs =
[{<<"id">>, ID}, {<<"type">>, iq_type_to_string(Type)}],
children = SubEl};
children = Children};
true ->
#xmlel{name = <<"iq">>,
attrs = [{<<"type">>, iq_type_to_string(Type)}],
children = SubEl}
children = Children}
end.
-spec(parse_xdata_submit/1 ::
(
El :: xmlel())
-> [{Var::binary(), Values::[binary()]}]
%%
| 'invalid'
).
-spec parse_xdata_submit(El :: xmlel()) ->
[{Var::binary(), Values::[binary()]}] | 'invalid'.
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
case fxml:get_attr_s(<<"type">>, Attrs) of
@@ -409,12 +400,9 @@ parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
invalid
end.
-spec(parse_xdata_fields/2 ::
(
Xmlels :: [xmlel() | cdata()],
Res :: [{Var::binary(), Values :: [binary()]}])
-> [{Var::binary(), Values::[binary()]}]
).
-spec parse_xdata_fields(Xmlels :: [xmlel() | cdata()],
Res :: [{Var::binary(), Values :: [binary()]}]) ->
[{Var::binary(), Values::[binary()]}].
parse_xdata_fields([], Res) -> Res;
parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls}
@@ -429,12 +417,8 @@ parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls}
parse_xdata_fields([_ | Els], Res) ->
parse_xdata_fields(Els, Res).
-spec(parse_xdata_values/2 ::
(
Xmlels :: [xmlel() | cdata()],
Res :: [binary()])
-> [binary()]
).
-spec parse_xdata_values(Xmlels :: [xmlel() | cdata()],
Res :: [binary()]) -> [binary()].
parse_xdata_values([], Res) -> Res;
parse_xdata_values([#xmlel{name = <<"value">>, children = SubEls} | Els], Res) ->
@@ -598,33 +582,8 @@ add_delay_info(El, From, Time) ->
binary()) -> xmlel().
add_delay_info(El, From, Time, Desc) ->
case fxml:get_subtag_with_xmlns(El, <<"delay">>, ?NS_DELAY) of
false ->
%% Add new tag
DelayTag = create_delay_tag(Time, From, Desc),
fxml:append_subtags(El, [DelayTag]);
DelayTag ->
%% Update existing tag
NewDelayTag =
case {fxml:get_tag_cdata(DelayTag), Desc} of
{<<"">>, <<"">>} ->
DelayTag;
{OldDesc, <<"">>} ->
DelayTag#xmlel{children = [{xmlcdata, OldDesc}]};
{<<"">>, NewDesc} ->
DelayTag#xmlel{children = [{xmlcdata, NewDesc}]};
{OldDesc, NewDesc} ->
case binary:match(OldDesc, NewDesc) of
nomatch ->
FinalDesc = <<OldDesc/binary, ", ", NewDesc/binary>>,
DelayTag#xmlel{children = [{xmlcdata, FinalDesc}]};
_ ->
DelayTag#xmlel{children = [{xmlcdata, OldDesc}]}
end
end,
NewEl = fxml:remove_subtags(El, <<"delay">>, {<<"xmlns">>, ?NS_DELAY}),
fxml:append_subtags(NewEl, [NewDelayTag])
end.
DelayTag = create_delay_tag(Time, From, Desc),
fxml:append_subtags(El, [DelayTag]).
-spec create_delay_tag(erlang:timestamp(), jid() | ljid() | binary(), binary())
-> xmlel() | error.
+4 -1
View File
@@ -35,7 +35,7 @@
process_sm_iq/3, get_local_commands/5,
get_local_identity/5, get_local_features/5,
get_sm_commands/5, get_sm_identity/5, get_sm_features/5,
ping_item/4, ping_command/4, mod_opt_type/1]).
ping_item/4, ping_command/4, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -284,6 +284,9 @@ ping_command(_Acc, _From, _To,
end;
ping_command(Acc, _From, _To, _Request) -> Acc.
depends(_Host, _Opts) ->
[].
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(report_commands_node) ->
fun (B) when is_boolean(B) -> B end;
+21 -16
View File
@@ -47,7 +47,7 @@
srg_delete/2, srg_list/1, srg_get_info/2,
srg_get_members/2, srg_user_add/4, srg_user_del/4,
send_message/5, send_stanza/3, send_stanza_c2s/4, privacy_set/3,
stats/1, stats/2, mod_opt_type/1, get_commands_spec/0]).
stats/1, stats/2, mod_opt_type/1, get_commands_spec/0, depends/2]).
-include("ejabberd.hrl").
@@ -66,6 +66,8 @@ start(_Host, _Opts) ->
stop(_Host) ->
ejabberd_commands:unregister_commands(get_commands_spec()).
depends(_Host, _Opts) ->
[].
%%%
%%% Register commands
@@ -533,7 +535,7 @@ get_commands_spec() ->
policy = user,
module = mod_offline, function = count_offline_messages,
args = [],
result = {res, integer}},
result = {value, integer}},
#ejabberd_commands{name = send_message, tags = [stanza],
desc = "Send a message to a local or remote bare of full JID",
module = ?MODULE, function = send_message,
@@ -861,12 +863,15 @@ connected_users_vhost(Host) ->
%% Code copied from ejabberd_sm.erl and customized
dirty_get_sessions_list2() ->
mnesia:dirty_select(
session,
[{#session{usr = '$1', sid = {'$2', '$3'}, priority = '$4', info = '$5',
_ = '_'},
[{is_pid, '$3'}],
[['$1', {{'$2', '$3'}}, '$4', '$5']]}]).
Ss = mnesia:dirty_select(
session,
[{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4',
_ = '_'},
[],
[['$1', '$2', '$3', '$4']]}]),
lists:filter(fun([_USR, _SID, _Priority, Info]) ->
not proplists:get_bool(offline, Info)
end, Ss).
%% Make string more print-friendly
stringize(String) ->
@@ -901,8 +906,8 @@ user_sessions_info(User, Host) ->
{'EXIT', _Reason} ->
[];
Ss ->
lists:filter(fun(#session{sid = {_, Pid}}) ->
is_pid(Pid)
lists:filter(fun(#session{info = Info}) ->
not proplists:get_bool(offline, Info)
end, Ss)
end,
lists:map(
@@ -1166,8 +1171,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, list_to_binary(Name2), list_to_binary(Server2),
list_to_binary(Nick2), list_to_binary(Group2), <<"both">>, []),
subscribe(Name1, Server1, iolist_to_binary(Name2), iolist_to_binary(Server2),
iolist_to_binary(Nick2), iolist_to_binary(Group2), <<"both">>, []),
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
push_alltoall(S, G) ->
@@ -1310,11 +1315,11 @@ srg_create(Group, Host, Name, Description, Display) ->
Opts = [{name, Name},
{displayed_groups, DisplayList},
{description, Description}],
{atomic, ok} = mod_shared_roster:create_group(Host, Group, Opts),
{atomic, _} = mod_shared_roster:create_group(Host, Group, Opts),
ok.
srg_delete(Group, Host) ->
{atomic, ok} = mod_shared_roster:delete_group(Host, Group),
{atomic, _} = mod_shared_roster:delete_group(Host, Group),
ok.
srg_list(Host) ->
@@ -1337,11 +1342,11 @@ srg_get_members(Group, Host) ->
|| {MUser, MServer} <- Members].
srg_user_add(User, Host, Group, GroupHost) ->
{atomic, ok} = mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group),
{atomic, _} = mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group),
ok.
srg_user_del(User, Host, Group, GroupHost) ->
{atomic, ok} = mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group),
{atomic, _} = mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group),
ok.
+13 -4
View File
@@ -33,7 +33,7 @@
-export([start/2, init/0, stop/1, export/1, import/1,
import/3, announce/3, send_motd/1, disco_identity/5,
disco_features/5, disco_items/5,
disco_features/5, disco_items/5, depends/2,
send_announcement_to_all/3, announce_commands/4,
announce_items/4, mod_opt_type/1]).
@@ -74,6 +74,9 @@ start(Host, Opts) ->
register(gen_mod:get_module_proc(Host, ?PROCNAME),
proc_lib:spawn(?MODULE, init, [])).
depends(_Host, _Opts) ->
[{mod_adhoc, hard}].
init() ->
loop().
@@ -693,7 +696,7 @@ announce_all(From, To, Packet) ->
lists:foreach(
fun({User, Server}) ->
Dest = jid:make(User, Server, <<>>),
ejabberd_router:route(Local, Dest, Packet)
ejabberd_router:route(Local, Dest, add_store_hint(Packet))
end, ejabberd_auth:get_vh_registered_users(Host))
end.
@@ -710,7 +713,7 @@ announce_all_hosts_all(From, To, Packet) ->
lists:foreach(
fun({User, Server}) ->
Dest = jid:make(User, Server, <<>>),
ejabberd_router:route(Local, Dest, Packet)
ejabberd_router:route(Local, Dest, add_store_hint(Packet))
end, ejabberd_auth:dirty_get_registered_users())
end.
@@ -896,7 +899,7 @@ send_announcement_to_all(Host, SubjectS, BodyS) ->
lists:foreach(
fun({U, S, R}) ->
Dest = jid:make(U, S, R),
ejabberd_router:route(Local, Dest, Packet)
ejabberd_router:route(Local, Dest, add_store_hint(Packet))
end, Sessions).
-spec get_access(global | binary()) -> atom().
@@ -906,6 +909,12 @@ get_access(Host) ->
fun(A) -> A end,
none).
-spec add_store_hint(xmlel()) -> xmlel().
add_store_hint(El) ->
Hint = #xmlel{name = <<"store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]},
fxml:append_subtags(El, [Hint]).
%%-------------------------------------------------------------------------
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
+4 -1
View File
@@ -30,7 +30,7 @@
-protocol({xep, 191, '1.2'}).
-export([start/2, stop/1, process_iq/3,
process_iq_set/4, process_iq_get/5, mod_opt_type/1]).
process_iq_set/4, process_iq_get/5, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -63,6 +63,9 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_BLOCKING).
depends(_Host, _Opts) ->
[{mod_privacy, hard}].
process_iq(_From, _To, IQ) ->
SubEl = IQ#iq.sub_el,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
+4 -1
View File
@@ -41,7 +41,7 @@
import_start/2, import_stop/2]).
%% gen_mod callbacks
-export([start/2, start_link/2, stop/1]).
-export([start/2, start_link/2, stop/1, depends/2]).
%% gen_server callbacks
-export([init/1, handle_info/2, handle_call/3,
@@ -306,6 +306,9 @@ c2s_broadcast_recipients(InAcc, Host, C2SState,
end;
c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
depends(_Host, _Opts) ->
[].
init([Host, Opts]) ->
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
+4 -1
View File
@@ -37,7 +37,7 @@
-export([user_send_packet/4, user_receive_packet/5,
iq_handler2/3, iq_handler1/3, remove_connection/4,
is_carbon_copy/1, mod_opt_type/1]).
is_carbon_copy/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -278,6 +278,9 @@ list(User, Server) ->
Mod = gen_mod:db_mod(Server, ?MODULE),
Mod:list(User, Server).
depends(_Host, _Opts) ->
[].
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) -> [db_type, iqdisc].
+34 -28
View File
@@ -31,11 +31,11 @@
-behavior(gen_mod).
%% gen_mod callbacks.
-export([start/2, stop/1, mod_opt_type/1]).
-export([start/2, stop/1, mod_opt_type/1, depends/2]).
%% ejabberd_hooks callbacks.
-export([filter_presence/3, filter_chat_states/3, filter_pep/3, filter_other/3,
flush_queue/2, add_stream_feature/2]).
-export([filter_presence/4, filter_chat_states/4, filter_pep/4, filter_other/4,
flush_queue/3, add_stream_feature/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -66,7 +66,7 @@ start(Host, Opts) ->
QueuePEP =
gen_mod:get_opt(queue_pep, Opts,
fun(B) when is_boolean(B) -> B end,
false),
true),
if QueuePresence; QueueChatStates; QueuePEP ->
ejabberd_hooks:add(c2s_post_auth_features, Host, ?MODULE,
add_stream_feature, 50),
@@ -106,7 +106,7 @@ stop(Host) ->
QueuePEP =
gen_mod:get_module_opt(Host, ?MODULE, queue_pep,
fun(B) when is_boolean(B) -> B end,
false),
true),
if QueuePresence; QueueChatStates; QueuePEP ->
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
add_stream_feature, 50),
@@ -142,73 +142,79 @@ mod_opt_type(queue_pep) ->
fun(B) when is_boolean(B) -> B end;
mod_opt_type(_) -> [queue_presence, queue_chat_states, queue_pep].
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].
%%--------------------------------------------------------------------
%% ejabberd_hooks callbacks.
%%--------------------------------------------------------------------
-spec filter_presence({term(), [xmlel()]}, binary(), xmlel())
-spec filter_presence({term(), [xmlel()]}, binary(), jid(), xmlel())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
filter_presence({C2SState, _OutStanzas} = Acc, Host,
filter_presence({C2SState, _OutStanzas} = Acc, Host, To,
#xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) ->
case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} when Type /= <<"unavailable">> ->
Acc;
_ ->
?DEBUG("Got availability presence stanza", []),
?DEBUG("Got availability presence stanza for ~s",
[jid:to_string(To)]),
queue_add(presence, Stanza, Host, C2SState)
end;
filter_presence(Acc, _Host, _Stanza) -> Acc.
filter_presence(Acc, _Host, _To, _Stanza) -> Acc.
-spec filter_chat_states({term(), [xmlel()]}, binary(), xmlel())
-spec filter_chat_states({term(), [xmlel()]}, binary(), jid(), xmlel())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
filter_chat_states({C2SState, _OutStanzas} = Acc, Host,
filter_chat_states({C2SState, _OutStanzas} = Acc, Host, To,
#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
case {jid:from_string(From), To} of
{#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
%% Don't queue (carbon copies of) chat states from other
%% resources, as they might be used to sync the state of
%% conversations across clients.
Acc;
_ ->
?DEBUG("Got standalone chat state notification", []),
?DEBUG("Got standalone chat state notification for ~s",
[jid:to_string(To)]),
queue_add(chatstate, Stanza, Host, C2SState)
end;
false ->
Acc
end;
filter_chat_states(Acc, _Host, _Stanza) -> Acc.
filter_chat_states(Acc, _Host, _To, _Stanza) -> Acc.
-spec filter_pep({term(), [xmlel()]}, binary(), xmlel())
-spec filter_pep({term(), [xmlel()]}, binary(), jid(), xmlel())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
filter_pep({C2SState, _OutStanzas} = Acc, Host,
filter_pep({C2SState, _OutStanzas} = Acc, Host, To,
#xmlel{name = <<"message">>} = Stanza) ->
case get_pep_node(Stanza) of
{value, Node} ->
?DEBUG("Got PEP notification", []),
?DEBUG("Got PEP notification for ~s", [jid:to_string(To)]),
queue_add({pep, Node}, Stanza, Host, C2SState);
false ->
Acc
end;
filter_pep(Acc, _Host, _Stanza) -> Acc.
filter_pep(Acc, _Host, _To, _Stanza) -> Acc.
-spec filter_other({term(), [xmlel()]}, binary(), xmlel())
-> {stop, {term(), [xmlel()]}}.
-spec filter_other({term(), [xmlel()]}, binary(), jid(), xmlel())
-> {term(), [xmlel()]}.
filter_other({C2SState, _OutStanzas}, Host, Stanza) ->
?DEBUG("Won't add stanza to CSI queue", []),
filter_other({C2SState, _OutStanzas}, Host, To, Stanza) ->
?DEBUG("Won't add stanza for ~s to CSI queue", [jid:to_string(To)]),
queue_take(Stanza, Host, C2SState).
-spec flush_queue({term(), [xmlel()]}, binary()) -> {term(), [xmlel()]}.
-spec flush_queue({term(), [xmlel()]}, binary(), jid()) -> {term(), [xmlel()]}.
flush_queue({C2SState, _OutStanzas}, Host) ->
?DEBUG("Going to flush CSI queue", []),
flush_queue({C2SState, _OutStanzas}, Host, JID) ->
?DEBUG("Going to flush CSI queue of ~s", [jid:to_string(JID)]),
Queue = get_queue(C2SState),
NewState = set_queue([], C2SState),
{NewState, get_stanzas(Queue, Host)}.
@@ -244,7 +250,7 @@ queue_add(Type, Stanza, Host, C2SState) ->
{stop, {NewState, []}}
end.
-spec queue_take(xmlel(), binary(), term()) -> {stop, {term(), [xmlel()]}}.
-spec queue_take(xmlel(), binary(), term()) -> {term(), [xmlel()]}.
queue_take(Stanza, Host, C2SState) ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
@@ -254,7 +260,7 @@ queue_take(Stanza, Host, C2SState) ->
U == LUser andalso S == LServer
end, get_queue(C2SState)),
NewState = set_queue(Rest, C2SState),
{stop, {NewState, get_stanzas(Selected, Host) ++ [Stanza]}}.
{NewState, get_stanzas(Selected, Host) ++ [Stanza]}.
-spec set_queue(csi_queue(), term()) -> term().
+35 -26
View File
@@ -35,7 +35,8 @@
get_local_features/5, get_local_items/5,
adhoc_local_items/4, adhoc_local_commands/4,
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
adhoc_sm_items/4, adhoc_sm_commands/4, mod_opt_type/1]).
adhoc_sm_items/4, adhoc_sm_commands/4, mod_opt_type/1,
depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -95,6 +96,9 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_COMMANDS).
depends(_Host, _Opts) ->
[{mod_adhoc, hard}, {mod_last, soft}].
%%%-----------------------------------------------------------------------
-define(INFO_IDENTITY(Category, Type, Name, Lang),
@@ -1368,10 +1372,9 @@ get_form(Host, [<<"config">>, <<"access">>], Lang) ->
[{xmlcdata, S}]}
end,
str:tokens(iolist_to_binary(io_lib:format("~p.",
[ets:select(local_config,
[{{local_config,
{access,
'$1',
[ets:select(access,
[{{access,
{'$1',
'$2'},
'$3'},
[{'==',
@@ -1826,10 +1829,9 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
Lang, XData) ->
SetAccess = fun (Rs) ->
mnesia:transaction(fun () ->
Os = mnesia:select(local_config,
[{{local_config,
{access,
'$1',
Os = mnesia:select(access,
[{{access,
{'$1',
'$2'},
'$3'},
[{'==',
@@ -1843,9 +1845,8 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
lists:foreach(fun ({access,
Name,
Rules}) ->
mnesia:write({local_config,
{access,
Name,
mnesia:write({access,
{Name,
Host},
Rules})
end,
@@ -1916,21 +1917,29 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
Xmlelement = ?SERRT_POLICY_VIOLATION(Lang, <<"has been kicked">>),
case JID#jid.lresource of
<<>> ->
SIDs = mnesia:dirty_select(session,
[{#session{sid = {'$1', '$2'},
usr = {LUser, LServer, '_'},
_ = '_'},
[{is_pid, '$2'}],
[{{'$1', '$2'}}]}]),
[Pid ! {kick, kicked_by_admin, Xmlelement} || {_, Pid} <- SIDs];
SIs = mnesia:dirty_select(session,
[{#session{usr = {LUser, LServer, '_'},
sid = '$1',
info = '$2',
_ = '_'},
[], [{{'$1', '$2'}}]}]),
Pids = [P || {{_, P}, Info} <- SIs,
not proplists:get_bool(offline, Info)],
lists:foreach(fun(Pid) ->
Pid ! {kick, kicked_by_admin, Xmlelement}
end, Pids);
R ->
[{_, Pid}] = mnesia:dirty_select(session,
[{#session{sid = {'$1', '$2'},
usr = {LUser, LServer, R},
_ = '_'},
[{is_pid, '$2'}],
[{{'$1', '$2'}}]}]),
Pid ! {kick, kicked_by_admin, Xmlelement}
[{{_, Pid}, Info}] = mnesia:dirty_select(
session,
[{#session{usr = {LUser, LServer, R},
sid = '$1',
info = '$2',
_ = '_'},
[], [{{'$1', '$2'}}]}]),
case proplists:get_bool(offline, Info) of
true -> ok;
false -> Pid ! {kick, kicked_by_admin, Xmlelement}
end
end,
{result, []};
set_form(From, Host,
+4 -1
View File
@@ -32,7 +32,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, process_local_iq/3,
mod_opt_type/1, opt_type/1]).
mod_opt_type/1, opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -201,6 +201,9 @@ process_get(#xmlel{name = <<"last">>, attrs = Attrs}, Lang) ->
%% {result, };
process_get(_, _) -> {error, ?ERR_BAD_REQUEST}.
depends(_Host, _Opts) ->
[].
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc].
+538
View File
@@ -0,0 +1,538 @@
%%%--------------------------------------------------------------------------------------
%%% File : mod_delegation.erl
%%% Author : Anna Mukharram <amuhar3@gmail.com>
%%% Purpose : This module is an implementation for XEP-0355: Namespace Delegation
%%%--------------------------------------------------------------------------------------
-module(mod_delegation).
-author('amuhar3@gmail.com').
-behaviour(gen_mod).
-protocol({xep, 0355, '0.3'}).
-export([start/2, stop/1, depends/2, mod_opt_type/1]).
-export([advertise_delegations/1, process_iq/3,
disco_local_features/5, disco_sm_features/5,
disco_local_identity/5, disco_sm_identity/5, disco_info/5, clean/0]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("ejabberd_service.hrl").
-define(CLEAN_INTERVAL, timer:minutes(10)).
%%%--------------------------------------------------------------------------------------
%%% API
%%%--------------------------------------------------------------------------------------
start(Host, _Opts) ->
mod_disco:register_feature(Host, ?NS_DELEGATION),
%% start timer for hooks_tmp table cleaning
timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_local_features, 500), %% This hook should be the last
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
disco_local_identity, 500),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
disco_sm_identity, 500),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
disco_sm_features, 500),
ejabberd_hooks:add(disco_info, Host, ?MODULE,
disco_info, 500).
stop(Host) ->
mod_disco:unregister_feature(Host, ?NS_DELEGATION),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
disco_local_features, 500),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE,
disco_local_identity, 500),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
disco_sm_identity, 500),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
disco_sm_features, 500),
ejabberd_hooks:delete(disco_info, Host, ?MODULE,
disco_info, 500).
depends(_Host, _Opts) -> [].
mod_opt_type(_Opt) -> [].
%%%--------------------------------------------------------------------------------------
%%% 4.2 Functions to advertise service of delegated namespaces
%%%--------------------------------------------------------------------------------------
attribute_tag(Attrs) ->
lists:map(fun(Attr) ->
#xmlel{name = <<"attribute">>, attrs = [{<<"name">> , Attr}]}
end, Attrs).
delegations(From, To, Delegations) ->
{Elem0, DelegatedNs} =
lists:foldl(fun({Ns, FiltAttr}, {Acc, AccNs}) ->
case ets:insert_new(delegated_namespaces,
{Ns, FiltAttr, self(), To, {}, {}}) of
true ->
Attrs =
if
FiltAttr == [] ->
?DEBUG("namespace ~s is delegated to ~s with"
" no filtering attributes ~n",[Ns, To]),
[];
true ->
?DEBUG("namespace ~s is delegated to ~s with"
" ~p filtering attributes ~n",[Ns, To, FiltAttr]),
attribute_tag(FiltAttr)
end,
add_iq_handlers(Ns),
{[#xmlel{name = <<"delegated">>,
attrs = [{<<"namespace">>, Ns}],
children = Attrs}| Acc], [{Ns, FiltAttr}|AccNs]};
false -> {Acc, AccNs}
end
end, {[], []}, Delegations),
case Elem0 of
[] -> {ignore, DelegatedNs};
_ ->
Elem1 = #xmlel{name = <<"delegation">>,
attrs = [{<<"xmlns">>, ?NS_DELEGATION}],
children = Elem0},
Id = randoms:get_string(),
{#xmlel{name = <<"message">>,
attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}],
children = [Elem1]}, DelegatedNs}
end.
add_iq_handlers(Ns) ->
lists:foreach(fun(Host) ->
IQDisc =
gen_mod:get_module_opt(Host, ?MODULE, iqdisc,
fun gen_iq_handler:check_type/1, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
Ns, ?MODULE,
process_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
Ns, ?MODULE,
process_iq, IQDisc)
end, ?MYHOSTS).
advertise_delegations(#state{delegations = []}) -> [];
advertise_delegations(StateData) ->
{Delegated, DelegatedNs} =
delegations(?MYNAME, StateData#state.host, StateData#state.delegations),
if
Delegated /= ignore ->
ejabberd_service:send_element(StateData, Delegated),
% server asks available features for delegated namespaces
disco_info(StateData#state{delegations = DelegatedNs});
true -> ok
end,
DelegatedNs.
%%%--------------------------------------------------------------------------------------
%%% Delegated namespaces hook
%%%--------------------------------------------------------------------------------------
check_filter_attr([], _Children) -> true;
check_filter_attr(_FilterAttr, []) -> false;
check_filter_attr(FilterAttr, [#xmlel{} = Stanza|_]) ->
Attrs = proplists:get_keys(Stanza#xmlel.attrs),
lists:all(fun(Attr) ->
lists:member(Attr, Attrs)
end, FilterAttr);
check_filter_attr(_FilterAttr, _Children) -> false.
-spec get_client_server([attr()]) -> {jid(), jid()}.
get_client_server(Attrs) ->
Client = fxml:get_attr_s(<<"from">>, Attrs),
ClientJID = jid:from_string(Client),
ServerJID = jid:from_string(ClientJID#jid.lserver),
{ClientJID, ServerJID}.
decapsulate_result(#xmlel{children = []}) -> ok;
decapsulate_result(#xmlel{children = Children}) ->
decapsulate_result0(Children).
decapsulate_result0([]) -> ok;
decapsulate_result0([#xmlel{name = <<"delegation">>,
attrs = [{<<"xmlns">>, ?NS_DELEGATION}]} = Packet]) ->
decapsulate_result1(Packet#xmlel.children);
decapsulate_result0(_Children) -> ok.
decapsulate_result1([]) -> ok;
decapsulate_result1([#xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}]} = Packet]) ->
decapsulate_result2(Packet#xmlel.children);
decapsulate_result1(_Children) -> ok.
decapsulate_result2([]) -> ok;
decapsulate_result2([#xmlel{name = <<"iq">>, attrs = Attrs} = Packet]) ->
Ns = fxml:get_attr_s(<<"xmlns">>, Attrs),
if
Ns /= <<"jabber:client">> ->
ok;
true -> Packet
end;
decapsulate_result2(_Children) -> ok.
-spec check_iq(xmlel(), xmlel()) -> xmlel() | ignore.
check_iq(#xmlel{attrs = Attrs} = Packet,
#xmlel{attrs = AttrsOrigin} = OriginPacket) ->
% Id attribute of OriginPacket Must be equil to Packet Id attribute
Id1 = fxml:get_attr_s(<<"id">>, Attrs),
Id2 = fxml:get_attr_s(<<"id">>, AttrsOrigin),
% From attribute of OriginPacket Must be equil to Packet To attribute
From = fxml:get_attr_s(<<"from">>, AttrsOrigin),
To = fxml:get_attr_s(<<"to">>, Attrs),
% Type attribute Must be error or result
Type = fxml:get_attr_s(<<"type">>, Attrs),
if
((Type == <<"result">>) or (Type == <<"error">>)),
Id1 == Id2, To == From ->
NewPacket = jlib:remove_attr(<<"xmlns">>, Packet),
%% We can send the decapsulated stanza from Server to Client (To)
NewPacket;
true ->
%% service-unavailable
Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
Err
end;
check_iq(_Packet, _OriginPacket) -> ignore.
-spec manage_service_result(atom(), atom(), binary(), xmlel()) -> ok.
manage_service_result(HookRes, HookErr, Service, OriginPacket) ->
fun(Packet) ->
{ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
Server = ClientJID#jid.lserver,
ets:delete(hooks_tmp, {HookRes, Server}),
ets:delete(hooks_tmp, {HookErr, Server}),
% Check Packet "from" attribute
% It Must be equil to current service host
From = fxml:get_attr_s(<<"from">> , Packet#xmlel.attrs),
if
From == Service ->
% decapsulate iq result
ResultIQ = decapsulate_result(Packet),
ServResponse = check_iq(ResultIQ, OriginPacket),
if
ServResponse /= ignore ->
ejabberd_router:route(ServerJID, ClientJID, ServResponse);
true -> ok
end;
true ->
% service unavailable
Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(ServerJID, ClientJID, Err)
end
end.
-spec manage_service_error(atom(), atom(), xmlel()) -> ok.
manage_service_error(HookRes, HookErr, OriginPacket) ->
fun(_Packet) ->
{ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
Server = ClientJID#jid.lserver,
ets:delete(hooks_tmp, {HookRes, Server}),
ets:delete(hooks_tmp, {HookErr, Server}),
Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(ServerJID, ClientJID, Err)
end.
-spec forward_iq(binary(), binary(), xmlel()) -> ok.
forward_iq(Server, Service, Packet) ->
Elem0 = #xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}], children = [Packet]},
Elem1 = #xmlel{name = <<"delegation">>,
attrs = [{<<"xmlns">>, ?NS_DELEGATION}], children = [Elem0]},
Id = randoms:get_string(),
Elem2 = #xmlel{name = <<"iq">>,
attrs = [{<<"from">>, Server}, {<<"to">>, Service},
{<<"type">>, <<"set">>}, {<<"id">>, Id}],
children = [Elem1]},
HookRes = {iq, result, Id},
HookErr = {iq, error, Id},
FunRes = manage_service_result(HookRes, HookErr, Service, Packet),
FunErr = manage_service_error(HookRes, HookErr, Packet),
Timestamp = p1_time_compat:system_time(seconds),
ets:insert(hooks_tmp, {{HookRes, Server}, FunRes, Timestamp}),
ets:insert(hooks_tmp, {{HookErr, Server}, FunErr, Timestamp}),
From = jid:make(<<"">>, Server, <<"">>),
To = jid:make(<<"">>, Service, <<"">>),
ejabberd_router:route(From, To, Elem2).
process_iq(From, #jid{lresource = <<"">>} = To,
#iq{type = Type, xmlns = XMLNS} = IQ) ->
%% check if stanza directed to server
%% or directed to the bare JID of the sender
case ((Type == get) or (Type == set)) of
true ->
Packet = jlib:iq_to_xml(IQ),
#xmlel{name = <<"iq">>, attrs = Attrs, children = Children} = Packet,
AttrsNew = [{<<"xmlns">>, <<"jabber:client">>} | Attrs],
AttrsNew2 = jlib:replace_from_to_attrs(jid:to_string(From),
jid:to_string(To), AttrsNew),
case ets:lookup(delegated_namespaces, XMLNS) of
[{XMLNS, FiltAttr, _Pid, ServiceHost, _, _}] ->
case check_filter_attr(FiltAttr, Children) of
true ->
forward_iq(From#jid.server, ServiceHost,
Packet#xmlel{attrs = AttrsNew2});
_ -> ok
end;
[] -> ok
end,
ignore;
_ ->
ignore
end;
process_iq(_From, _To, _IQ) -> ignore.
%%%--------------------------------------------------------------------------------------
%%% 7. Discovering Support
%%%--------------------------------------------------------------------------------------
decapsulate_features(#xmlel{attrs = Attrs} = Packet, Node) ->
case fxml:get_attr_s(<<"node">>, Attrs) of
Node ->
PREFIX = << ?NS_DELEGATION/binary, "::" >>,
Size = byte_size(PREFIX),
BARE_PREFIX = << ?NS_DELEGATION/binary, ":bare:" >>,
SizeBare = byte_size(BARE_PREFIX),
Features = [Feat || #xmlel{attrs = [{<<"var">>, Feat}]} <-
fxml:get_subtags(Packet, <<"feature">>)],
Identity = [I || I <- fxml:get_subtags(Packet, <<"identity">>)],
Exten = [I || I <- fxml:get_subtags_with_xmlns(Packet, <<"x">>, ?NS_XDATA)],
case Node of
<< PREFIX:Size/binary, NS/binary >> ->
ets:update_element(delegated_namespaces, NS,
{5, {Features, Identity, Exten}});
<< BARE_PREFIX:SizeBare/binary, NS/binary >> ->
ets:update_element(delegated_namespaces, NS,
{6, {Features, Identity, Exten}});
_ -> ok
end;
_ -> ok
end;
decapsulate_features(_Packet, _Node) -> ok.
-spec disco_result(atom(), atom(), binary()) -> ok.
disco_result(HookRes, HookErr, Node) ->
fun(Packet) ->
Tag = fxml:get_subtag_with_xmlns(Packet, <<"query">>, ?NS_DISCO_INFO),
decapsulate_features(Tag, Node),
ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
ets:delete(hooks_tmp, {HookErr, ?MYNAME})
end.
-spec disco_error(atom(), atom()) -> ok.
disco_error(HookRes, HookErr) ->
fun(_Packet) ->
ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
ets:delete(hooks_tmp, {HookErr, ?MYNAME})
end.
-spec disco_info(state()) -> ok.
disco_info(StateData) ->
disco_info(StateData, <<"::">>),
disco_info(StateData, <<":bare:">>).
-spec disco_info(state(), binary()) -> ok.
disco_info(StateData, Sep) ->
lists:foreach(fun({Ns, _FilterAttr}) ->
Id = randoms:get_string(),
Node = << ?NS_DELEGATION/binary, Sep/binary, Ns/binary >>,
HookRes = {iq, result, Id},
HookErr = {iq, error, Id},
FunRes = disco_result(HookRes, HookErr, Node),
FunErr = disco_error(HookRes, HookErr),
Timestamp = p1_time_compat:system_time(seconds),
ets:insert(hooks_tmp, {{HookRes, ?MYNAME}, FunRes, Timestamp}),
ets:insert(hooks_tmp, {{HookErr, ?MYNAME}, FunErr, Timestamp}),
Tag = #xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_INFO},
{<<"node">>, Node}],
children = []},
DiscoReq = #xmlel{name = <<"iq">>,
attrs = [{<<"type">>, <<"get">>}, {<<"id">>, Id},
{<<"from">>, ?MYNAME},
{<<"to">>, StateData#state.host }],
children = [Tag]},
ejabberd_service:send_element(StateData, DiscoReq)
end, StateData#state.delegations).
disco_features(Acc, Bare) ->
Fun = fun(Feat) ->
ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
A or str:prefix(Ns, Feat)
end, false, delegated_namespaces)
end,
% delete feature namespace which is delegated to service
Features = lists:filter(fun ({{Feature, _Host}}) ->
not Fun(Feature);
(Feature) when is_binary(Feature) ->
not Fun(Feature)
end, Acc),
% add service features
FeaturesList =
ets:foldl(fun({_, _, _, _, {Feats, _, _}, {FeatsBare, _, _}}, A) ->
if
Bare -> A ++ FeatsBare;
true -> A ++ Feats
end;
(_, A) -> A
end, Features, delegated_namespaces),
{result, FeaturesList}.
disco_identity(Acc, Bare) ->
% filter delegated identites
Fun = fun(Ident) ->
ets:foldl(fun({_, _, _, _, {_ , I, _}, {_ , IBare, _}}, A) ->
Identity =
if
Bare -> IBare;
true -> I
end,
(fxml:get_attr_s(<<"category">> , Ident) ==
fxml:get_attr_s(<<"category">>, Identity)) and
(fxml:get_attr_s(<<"type">> , Ident) ==
fxml:get_attr_s(<<"type">>, Identity)) or A;
(_, A) -> A
end, false, delegated_namespaces)
end,
Identities =
lists:filter(fun (#xmlel{attrs = Attrs}) ->
not Fun(Attrs)
end, Acc),
% add service features
ets:foldl(fun({_, _, _, _, {_, I, _}, {_, IBare, _}}, A) ->
if
Bare -> A ++ IBare;
true -> A ++ I
end;
(_, A) -> A
end, Identities, delegated_namespaces).
%% xmlns from value element
-spec get_field_value([xmlel()]) -> binary().
get_field_value([]) -> <<"">>;
get_field_value([Elem| Elems]) ->
case (fxml:get_attr_s(<<"var">>, Elem#xmlel.attrs) == <<"FORM_TYPE">>) and
(fxml:get_attr_s(<<"type">>, Elem#xmlel.attrs) == <<"hidden">>) of
true ->
Ns = fxml:get_subtag_cdata(Elem, <<"value">>),
if
Ns /= <<"">> -> Ns;
true -> get_field_value(Elems)
end;
_ -> get_field_value(Elems)
end.
get_info(Acc, Bare) ->
Fun = fun(Feat) ->
ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
(A or str:prefix(Ns, Feat))
end, false, delegated_namespaces)
end,
Exten = lists:filter(fun(Xmlel) ->
Tags = fxml:get_subtags(Xmlel, <<"field">>),
case get_field_value(Tags) of
<<"">> -> true;
Value -> not Fun(Value)
end
end, Acc),
ets:foldl(fun({_, _, _, _, {_, _, Ext}, {_, _, ExtBare}}, A) ->
if
Bare -> A ++ ExtBare;
true -> A ++ Ext
end;
(_, A) -> A
end, Exten, delegated_namespaces).
%% 7.2.1 General Case
disco_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
Acc;
disco_local_features(Acc, _From, _To, <<>>, _Lang) ->
FeatsOld = case Acc of
{result, I} -> I;
_ -> []
end,
disco_features(FeatsOld, false);
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
disco_local_identity(Acc, _From, _To, <<>>, _Lang) ->
disco_identity(Acc, false);
disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
%% 7.2.2 Rediction Of Bare JID Disco Info
disco_sm_features({error, ?ERR_ITEM_NOT_FOUND}, _From,
#jid{lresource = <<"">>}, <<>>, _Lang) ->
disco_features([], true);
disco_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
Acc;
disco_sm_features(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
FeatsOld = case Acc of
{result, I} -> I;
_ -> []
end,
disco_features(FeatsOld, true);
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
disco_sm_identity(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
disco_identity(Acc, true);
disco_sm_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
disco_info(Acc, #jid{}, #jid{lresource = <<"">>}, <<>>, _Lang) ->
get_info(Acc, true);
disco_info(Acc, _Host, _Mod, <<>>, _Lang) ->
get_info(Acc, false);
disco_info(Acc, _Host, _Mod, _Node, _Lang) ->
Acc.
%% clean hooks_tmp table
clean() ->
?DEBUG("cleaning ~p ETS table~n", [hooks_tmp]),
Now = p1_time_compat:system_time(seconds),
catch ets:select_delete(hooks_tmp,
ets:fun2ms(fun({_, _, Timestamp}) ->
Now - 300 >= Timestamp
end)),
%% start timer for table cleaning
timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []).
+4 -1
View File
@@ -39,7 +39,7 @@
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
get_info/5, register_feature/2, unregister_feature/2,
register_extra_domain/2, unregister_extra_domain/2,
transform_module_options/1, mod_opt_type/1]).
transform_module_options/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -534,6 +534,9 @@ values_to_xml(Values) ->
end,
Values).
depends(_Host, _Opts) ->
[].
mod_opt_type(extra_domains) ->
fun (Hs) -> [iolist_to_binary(H) || H <- Hs] end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+5 -2
View File
@@ -37,7 +37,7 @@
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1]).
mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -63,7 +63,7 @@ start_link(Host, Opts) ->
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
temporary, 1000, worker, [?MODULE]},
transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -200,5 +200,8 @@ do_client_version(enabled, From, To) ->
?INFO_MSG("Information of the client: ~s~s",
[ToS, Values_string2]).
depends(_Host, _Opts) ->
[].
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(_) -> [host].
+4 -1
View File
@@ -33,7 +33,7 @@
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1]).
mod_opt_type/1, depends/2]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("ejabberd.hrl").
@@ -120,6 +120,9 @@ stop(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
depends(_Host, _Opts) ->
[].
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
+147 -80
View File
@@ -74,7 +74,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, process/2, mod_opt_type/1]).
-export([start/2, stop/1, process/2, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@@ -101,7 +101,7 @@
-define(AC_ALLOW_HEADERS,
{<<"Access-Control-Allow-Headers">>,
<<"Content-Type">>}).
<<"Content-Type, Authorization, X-Admin">>}).
-define(AC_MAX_AGE,
{<<"Access-Control-Max-Age">>, <<"86400">>}).
@@ -123,6 +123,9 @@ start(_Host, _Opts) ->
stop(_Host) ->
ok.
depends(_Host, _Opts) ->
[].
%% ----------
%% basic auth
%% ----------
@@ -130,13 +133,13 @@ stop(_Host) ->
check_permissions(Request, Command) ->
case catch binary_to_existing_atom(Command, utf8) of
Call when is_atom(Call) ->
{ok, CommandPolicy} = ejabberd_commands:get_command_policy(Call),
check_permissions2(Request, Call, CommandPolicy);
{ok, CommandPolicy, Scope} = ejabberd_commands:get_command_policy_and_scope(Call),
check_permissions2(Request, Call, CommandPolicy, Scope);
_ ->
unauthorized_response()
json_error(404, 40, <<"Endpoint not found.">>)
end.
check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _, ScopeList)
when HTTPAuth /= undefined ->
Admin =
case lists:keysearch(<<"X-Admin">>, 1, Headers) of
@@ -156,24 +159,23 @@ check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
false
end;
{oauth, Token, _} ->
case oauth_check_token(Call, Token) of
case oauth_check_token(ScopeList, Token) of
{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
{false, Reason} ->
{false, Reason}
end;
_ ->
false
end,
case Auth of
{ok, A} -> {allowed, Call, A};
{false, no_matching_scope} -> outofscope_response();
_ -> unauthorized_response()
end;
check_permissions2(_Request, Call, open) ->
check_permissions2(_Request, Call, open, _Scope) ->
{allowed, Call, noauth};
check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
check_permissions2(#request{ip={IP, _Port}}, Call, _Policy, _Scope) ->
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
fun(V) -> V end,
none),
@@ -188,18 +190,16 @@ check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
Commands when is_list(Commands) ->
case lists:member(Call, Commands) of
true -> {allowed, Call, admin};
_ -> unauthorized_response()
_ -> outofscope_response()
end;
_E ->
{allowed, Call, noauth}
end;
check_permissions2(_Request, _Call, _Policy) ->
check_permissions2(_Request, _Call, _Policy, _Scope) ->
unauthorized_response().
oauth_check_token(Scope, Token) when is_atom(Scope) ->
oauth_check_token(atom_to_binary(Scope, utf8), Token);
oauth_check_token(Scope, Token) ->
ejabberd_oauth:check_token(Scope, Token).
oauth_check_token(ScopeList, Token) when is_list(ScopeList) ->
ejabberd_oauth:check_token(ScopeList, Token).
%% ------------------
%% command processing
@@ -213,24 +213,24 @@ process(_, #request{method = 'POST', data = <<>>}) ->
process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} = Req) ->
Version = get_api_version(Req),
try
Args = case jiffy:decode(Data) of
List when is_list(List) -> List;
{List} when is_list(List) -> List;
Other -> [Other]
end,
Args = extract_args(Data),
log(Call, Args, IPPort),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
{Code, Result} = handle(Cmd, Auth, Args, Version, IP),
json_response(Code, jiffy:encode(Result));
Result = handle(Cmd, Auth, Args, Version, IP),
json_format(Result);
%% Warning: check_permission direcly formats 401 reply if not authorized
ErrorResponse ->
ErrorResponse
end
catch _:{error,{_,invalid_json}} = _Err ->
?DEBUG("Bad Request: ~p", [_Err]),
badrequest_response(<<"Invalid JSON input">>);
_:_Error ->
catch
%% TODO We need to refactor to remove redundant error return formatting
throw:{error, unknown_command} ->
{404, 40, <<"Command not found.">>};
_:{error,{_,invalid_json}} = _Err ->
?DEBUG("Bad Request: ~p", [_Err]),
badrequest_response(<<"Invalid JSON input">>);
_:_Error ->
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
badrequest_response()
end;
@@ -244,31 +244,47 @@ 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, IP),
json_response(Code, jiffy:encode(Result));
Result = handle(Cmd, Auth, Args, Version, IP),
json_format(Result);
%% Warning: check_permission direcly formats 401 reply if not authorized
ErrorResponse ->
ErrorResponse
end
catch _:_Error ->
catch
%% TODO We need to refactor to remove redundant error return formatting
throw:{error, unknown_command} ->
json_format({404, 44, <<"Command not found.">>});
_:_Error ->
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
badrequest_response()
end;
process([], #request{method = 'OPTIONS', data = <<>>}) ->
process([_Call], #request{method = 'OPTIONS', data = <<>>}) ->
{200, ?OPTIONS_HEADER, []};
process(_, #request{method = 'OPTIONS'}) ->
{400, ?OPTIONS_HEADER, []};
process(_Path, Request) ->
?DEBUG("Bad Request: no handler ~p", [Request]),
badrequest_response().
json_error(400, 40, <<"Missing command name.">>).
%% Be tolerant to make API more easily usable from command-line pipe.
extract_args(<<"\n">>) -> [];
extract_args(Data) ->
case jiffy:decode(Data) of
List when is_list(List) -> List;
{List} when is_list(List) -> List;
Other -> [Other]
end.
% get API version N from last "vN" element in URL path
get_api_version(#request{path = Path}) ->
get_api_version(lists:reverse(Path));
get_api_version([<<"v", String/binary>> | Tail]) ->
case catch jlib:binary_to_integer(String) of
N when is_integer(N) ->
N;
_ ->
get_api_version(Tail)
N when is_integer(N) ->
N;
_ ->
get_api_version(Tail)
end;
get_api_version([_Head | Tail]) ->
get_api_version(Tail);
@@ -279,6 +295,8 @@ get_api_version([]) ->
%% command handlers
%% ----------------
%% TODO Check accept types of request before decided format of reply.
% generic ejabberd command handler
handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
case ejabberd_commands:get_command_format(Call, Auth, Version) of
@@ -297,7 +315,7 @@ handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
[{Key, undefined}|Acc]
end, [], ArgsSpec),
try
handle2(Call, Auth, match(Args2, Spec), Version, IP)
handle2(Call, Auth, match(Args2, Spec), Version, IP)
catch throw:not_found ->
{404, <<"not_found">>};
throw:{not_found, Why} when is_atom(Why) ->
@@ -310,8 +328,10 @@ handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
{401, jlib:atom_to_binary(Why)};
throw:{not_allowed, Msg} ->
{401, iolist_to_binary(Msg)};
throw:{error, account_unprivileged} ->
{401, iolist_to_binary(<<"Unauthorized: Account Unpriviledged">>)};
throw:{error, account_unprivileged} ->
{403, 31, <<"Command need to be run with admin priviledge.">>};
throw:{error, access_rules_unauthorized} ->
{403, 32, <<"AccessRules: Account associated to token does not have the right to perform the operation.">>};
throw:{invalid_parameter, Msg} ->
{400, iolist_to_binary(Msg)};
throw:{error, Why} when is_atom(Why) ->
@@ -367,28 +387,47 @@ format_args(Args, ArgsFormat) ->
L when is_list(L) -> exit({additional_unused_args, L})
end.
format_arg({array, Elements},
{list, {ElementDefName, ElementDefFormat}})
format_arg({Elements},
{list, {_ElementDefName, {tuple, [{_Tuple1N, Tuple1S}, {_Tuple2N, Tuple2S}]} = Tuple}})
when is_list(Elements) andalso
(Tuple1S == binary orelse Tuple1S == string) ->
lists:map(fun({F1, F2}) ->
{format_arg(F1, Tuple1S), format_arg(F2, Tuple2S)};
({Val}) when is_list(Val) ->
format_arg({Val}, Tuple)
end, Elements);
format_arg(Elements,
{list, {_ElementDefName, {list, _} = ElementDefFormat}})
when is_list(Elements) ->
lists:map(fun ({struct, [{ElementName, ElementValue}]}) when
ElementDefName == ElementName ->
format_arg(ElementValue, ElementDefFormat)
end,
Elements);
format_arg({array, [{struct, Elements}]},
{list, {ElementDefName, ElementDefFormat}})
[{format_arg(Element, ElementDefFormat)}
|| Element <- Elements];
format_arg(Elements,
{list, {_ElementDefName, ElementDefFormat}})
when is_list(Elements) ->
lists:map(fun ({ElementName, ElementValue}) ->
true = ElementDefName == ElementName,
format_arg(ElementValue, ElementDefFormat)
end,
Elements);
format_arg({array, [{struct, Elements}]},
[format_arg(Element, ElementDefFormat)
|| Element <- Elements];
format_arg({[{Name, Value}]},
{tuple, [{_Tuple1N, Tuple1S}, {_Tuple2N, Tuple2S}]})
when Tuple1S == binary;
Tuple1S == string ->
{format_arg(Name, Tuple1S), format_arg(Value, Tuple2S)};
format_arg({Elements},
{tuple, ElementsDef})
when is_list(Elements) ->
FormattedList = format_args(Elements, ElementsDef),
list_to_tuple(FormattedList);
format_arg({array, Elements}, {list, ElementsDef})
F = lists:map(fun({TElName, TElDef}) ->
case lists:keyfind(atom_to_binary(TElName, latin1), 1, Elements) of
{_, Value} ->
format_arg(Value, TElDef);
_ when TElDef == binary; TElDef == string ->
<<"">>;
_ ->
?ERROR_MSG("missing field ~p in tuple ~p", [TElName, Elements]),
throw({invalid_parameter,
io_lib:format("Missing field ~w in tuple ~w", [TElName, Elements])})
end
end, ElementsDef),
list_to_tuple(F);
format_arg(Elements, {list, ElementsDef})
when is_list(Elements) and is_atom(ElementsDef) ->
[format_arg(Element, ElementsDef)
|| Element <- Elements];
@@ -402,7 +441,7 @@ format_arg(undefined, string) -> <<>>;
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
throw({invalid_parameter,
io_lib:format("Arg ~p is not in format ~p",
io_lib:format("Arg ~w is not in format ~w",
[Arg, Format])}).
process_unicode_codepoints(Str) ->
@@ -432,22 +471,24 @@ ejabberd_command(Auth, Cmd, Args, Version, IP) ->
format_command_result(Cmd, Auth, Result, Version) ->
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
case {ResultFormat, Result} of
{{_, rescode}, V} when V == true; V == ok ->
{200, 0};
{{_, rescode}, _} ->
{200, 1};
{{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
{200, iolist_to_binary(Text1)};
{{_, restuple}, {_, Text2}} ->
{500, iolist_to_binary(Text2)};
{{_, {list, _}}, _V} ->
{_, L} = format_result(Result, ResultFormat),
{200, L};
{{_, {tuple, _}}, _V} ->
{_, T} = format_result(Result, ResultFormat),
{200, T};
_ ->
{200, {[format_result(Result, ResultFormat)]}}
{{_, rescode}, V} when V == true; V == ok ->
{200, 0};
{{_, rescode}, _} ->
{200, 1};
{_, {error, ErrorAtom, Code, Msg}} ->
format_error_result(ErrorAtom, Code, Msg);
{{_, restuple}, {V, Text}} when V == true; V == ok ->
{200, iolist_to_binary(Text)};
{{_, restuple}, {ErrorAtom, Msg}} ->
format_error_result(ErrorAtom, 0, Msg);
{{_, {list, _}}, _V} ->
{_, L} = format_result(Result, ResultFormat),
{200, L};
{{_, {tuple, _}}, _V} ->
{_, T} = format_result(Result, ResultFormat),
{200, T};
_ ->
{200, {[format_result(Result, ResultFormat)]}}
end.
format_result(Atom, {Name, atom}) ->
@@ -467,6 +508,11 @@ format_result({Code, Text}, {Name, restuple}) ->
{[{<<"res">>, Code == true orelse Code == ok},
{<<"text">>, iolist_to_binary(Text)}]}};
format_result(Code, {Name, restuple}) ->
{jlib:atom_to_binary(Name),
{[{<<"res">>, Code == true orelse Code == ok},
{<<"text">>, <<"">>}]}};
format_result(Els, {Name, {list, {_, {tuple, [{_, atom}, _]}} = Fmt}}) ->
{jlib:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
@@ -485,19 +531,40 @@ format_result(Tuple, {Name, {tuple, Def}}) ->
format_result(404, {_Name, _}) ->
"not_found".
format_error_result(conflict, Code, Msg) ->
{409, Code, iolist_to_binary(Msg)};
format_error_result(_ErrorAtom, Code, Msg) ->
{500, Code, iolist_to_binary(Msg)}.
unauthorized_response() ->
unauthorized_response(<<"401 Unauthorized">>).
unauthorized_response(Body) ->
json_response(401, jiffy:encode(Body)).
json_error(401, 10, <<"Oauth Token is invalid or expired.">>).
outofscope_response() ->
json_error(401, 11, <<"Token does not grant usage to command required scope.">>).
badrequest_response() ->
badrequest_response(<<"400 Bad Request">>).
badrequest_response(Body) ->
json_response(400, jiffy:encode(Body)).
json_format({Code, Result}) ->
json_response(Code, jiffy:encode(Result));
json_format({HTMLCode, JSONErrorCode, Message}) ->
json_error(HTMLCode, JSONErrorCode, Message).
json_response(Code, Body) when is_integer(Code) ->
{Code, ?HEADER(?CT_JSON), Body}.
%% HTTPCode, JSONCode = integers
%% message is binary
json_error(HTTPCode, JSONCode, Message) ->
{HTTPCode, ?HEADER(?CT_JSON),
jiffy:encode({[{<<"status">>, <<"error">>},
{<<"code">>, JSONCode},
{<<"message">>, Message}]})
}.
log(Call, Args, {Addr, Port}) ->
AddrS = jlib:ip_to_list({Addr, Port}),
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
+3 -1
View File
@@ -37,7 +37,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, process/2, mod_opt_type/1]).
-export([start/2, stop/1, process/2, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -109,6 +109,8 @@ mod_opt_type(max_pause) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(_) -> [max_inactivity, max_pause].
depends(_Host, _Opts) ->
[].
%%%----------------------------------------------------------------------
%%% Help Web Page
+4 -1
View File
@@ -46,7 +46,7 @@
%% utility for other http modules
-export([content_type/3]).
-export([reopen_log/1, mod_opt_type/1]).
-export([reopen_log/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -109,6 +109,9 @@ stop(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
depends(_Host, _Opts) ->
[].
%%====================================================================
%% API
%%====================================================================
+6
View File
@@ -68,6 +68,7 @@
-export([start_link/3,
start/2,
stop/1,
depends/2,
mod_opt_type/1]).
%% gen_server callbacks.
@@ -222,6 +223,11 @@ mod_opt_type(_) ->
dir_mode, docroot, put_url, get_url, service_url, custom_headers,
rm_on_unregister, thumbnail].
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].
%%--------------------------------------------------------------------
%% gen_server callbacks.
%%--------------------------------------------------------------------
+9 -3
View File
@@ -39,6 +39,7 @@
-export([start_link/3,
start/2,
stop/1,
depends/2,
mod_opt_type/1]).
%% gen_server callbacks.
@@ -109,6 +110,11 @@ mod_opt_type(max_days) ->
mod_opt_type(_) ->
[access_soft_quota, access_hard_quota, max_days].
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[{mod_http_upload, hard}].
%%--------------------------------------------------------------------
%% gen_server callbacks.
%%--------------------------------------------------------------------
@@ -245,7 +251,7 @@ terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
?DEBUG("Stopping upload quota process for ~s: ~p", [ServerHost, Reason]),
ejabberd_hooks:delete(http_upload_slot_request, ServerHost, ?MODULE,
handle_slot_request, 50),
lists:foreach(fun(Timer) -> timer:cancel(Timer) end, Timers).
lists:foreach(fun timer:cancel/1, Timers).
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
@@ -293,7 +299,7 @@ enforce_quota(UserDir, SlotSize, _OldSize, MinSize, MaxSize) ->
{[Path | AccFiles], AccSize + Size, NewSize}
end, {[], 0, 0}, Files),
if OldSize + SlotSize > MaxSize ->
lists:foreach(fun(File) -> del_file_and_dir(File) end, DelFiles),
lists:foreach(fun del_file_and_dir/1, DelFiles),
file:del_dir(UserDir), % In case it's empty, now.
NewSize + SlotSize;
true ->
@@ -308,7 +314,7 @@ delete_old_files(UserDir, CutOff) ->
[] ->
ok;
OldFiles ->
lists:foreach(fun(File) -> del_file_and_dir(File) end, OldFiles),
lists:foreach(fun del_file_and_dir/1, OldFiles),
file:del_dir(UserDir) % In case it's empty, now.
end.
+4 -1
View File
@@ -36,7 +36,7 @@
-export([update_bl_c2s/0]).
-export([is_ip_in_c2s_blacklist/3, mod_opt_type/1]).
-export([is_ip_in_c2s_blacklist/3, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -65,6 +65,9 @@ preinit(Parent, State) ->
error:_ -> Parent ! {ok, Pid, true}
end.
depends(_Host, _Opts) ->
[].
%% TODO:
stop(_Host) -> ok.
+5 -2
View File
@@ -38,7 +38,7 @@
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1]).
mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -90,7 +90,7 @@ start(Host, Opts) ->
start_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
temporary, 1000, worker, [?MODULE]},
transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -99,6 +99,9 @@ stop(Host) ->
gen_server:call(Proc, stop),
supervisor:delete_child(ejabberd_sup, Proc).
depends(_Host, _Opts) ->
[].
%%====================================================================
%% gen_server callbacks
%%====================================================================
+4 -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, register_user/2]).
opt_type/1, register_user/2, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -255,6 +255,9 @@ transform_options({node_start, {_, _, _} = Now}, Opts) ->
transform_options(Opt, Opts) ->
[Opt|Opts].
depends(_Host, _Opts) ->
[].
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(_) -> [db_type, iqdisc].
+108 -72
View File
@@ -31,13 +31,13 @@
-behaviour(gen_mod).
%% API
-export([start/2, stop/1]).
-export([start/2, stop/1, depends/2]).
-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,
get_commands_spec/0, msg_to_el/4]).
get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]).
-include("jlib.hrl").
-include("logger.hrl").
@@ -102,18 +102,21 @@ start(Host, Opts) ->
disco_sm_features, 50),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(remove_room, Host, ?MODULE,
remove_room, 50),
ejabberd_hooks:add(get_room_config, Host, ?MODULE,
get_room_config, 50),
ejabberd_hooks:add(set_room_option, Host, ?MODULE,
set_room_option, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
remove_user, 50),
case gen_mod:get_opt(assume_mam_usage, Opts,
fun(if_enabled) -> if_enabled;
(on_request) -> on_request;
(never) -> never
end, never) of
never ->
ok;
_ ->
fun(B) when is_boolean(B) -> B end, false) of
true ->
ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
message_is_archived, 50)
message_is_archived, 50);
false ->
ok
end,
ejabberd_commands:register_commands(get_commands_spec()),
ok.
@@ -149,22 +152,28 @@ stop(Host) ->
disco_sm_features, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:delete(remove_room, Host, ?MODULE,
remove_room, 50),
ejabberd_hooks:delete(get_room_config, Host, ?MODULE,
get_room_config, 50),
ejabberd_hooks:delete(set_room_option, Host, ?MODULE,
set_room_option, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage,
fun(if_enabled) -> if_enabled;
(on_request) -> on_request;
(never) -> never
end, never) of
never ->
ok;
_ ->
fun(B) when is_boolean(B) -> B end, false) of
true ->
ejabberd_hooks:delete(message_is_archived, Host, ?MODULE,
message_is_archived, 50)
message_is_archived, 50);
false ->
ok
end,
ejabberd_commands:unregister_commands(get_commands_spec()),
ok.
depends(_Host, _Opts) ->
[].
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -177,6 +186,41 @@ remove_room(LServer, Name, Host) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_room(LServer, LName, LHost).
get_room_config(X, RoomState, _From, Lang) ->
Config = RoomState#state.config,
Label = <<"Enable message archiving">>,
Var = <<"muc#roomconfig_mam">>,
Val = case Config#config.mam of
true -> <<"1">>;
_ -> <<"0">>
end,
XField = #xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"boolean">>},
{<<"label">>, translate:translate(Lang, Label)},
{<<"var">>, Var}],
children =
[#xmlel{name = <<"value">>, attrs = [],
children = [{xmlcdata, Val}]}]},
X ++ [XField].
set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
try
Val = case Vals of
[<<"0">>|_] -> false;
[<<"false">>|_] -> false;
[<<"1">>|_] -> true;
[<<"true">>|_] -> true
end,
{#config.mam, Val}
catch _:{case_clause, _} ->
Txt = <<"Value of '~s' should be boolean">>,
ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
{error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
end;
set_room_option(Acc, _Opt, _Vals, _Lang) ->
Acc.
user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
@@ -331,32 +375,13 @@ message_is_archived(true, _C2SState, _Peer, _JID, _Pkt) ->
true;
message_is_archived(false, C2SState, Peer,
#jid{luser = LUser, lserver = LServer}, Pkt) ->
Res = case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage,
fun(if_enabled) -> if_enabled;
(on_request) -> on_request;
(never) -> never
end, never) of
if_enabled ->
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},
fun() ->
Mod:get_prefs(LUser, LServer)
end);
never ->
error
end,
case Res of
{ok, Prefs} ->
case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage,
fun(B) when is_boolean(B) -> B end, false) of
true ->
should_archive(strip_my_archived_tag(Pkt, LServer), LServer)
andalso should_archive_peer(C2SState, Prefs, Peer);
error ->
andalso should_archive_peer(C2SState, get_prefs(LUser, LServer),
Peer);
false ->
false
end.
@@ -530,29 +555,29 @@ parse_query_v0_2(Query) ->
end, Query#xmlel.children).
should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
<<"error">> ->
case is_resent(Pkt, LServer) of
true ->
false;
<<"groupchat">> ->
false;
_ ->
case is_resent(Pkt, LServer) of
true ->
false ->
case {check_store_hint(Pkt),
fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs)} of
{_Hint, <<"error">>} ->
false;
false ->
case check_store_hint(Pkt) of
store ->
true;
no_store ->
{store, _Type} ->
true;
{no_store, _Type} ->
false;
{none, <<"groupchat">>} ->
false;
{none, <<"headline">>} ->
false;
{none, _Type} ->
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
%% Empty body
false;
none ->
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
%% Empty body
false;
_ ->
true
end
_ ->
true
end
end
end;
@@ -693,8 +718,14 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
case should_archive_peer(C2SState, Prefs, Peer) of
true ->
US = {LUser, LServer},
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir);
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
[LUser, LServer, Peer, chat, Dir]) of
drop ->
pass;
NewPkt ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(NewPkt, LServer, US, chat, Peer, <<"">>, Dir)
end;
false ->
pass
end.
@@ -702,10 +733,16 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
case should_archive_muc(Pkt) of
true ->
LServer = MUCState#state.server_host,
{U, S, _} = jid:tolower(RoomJID),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv);
LServer = MUCState#state.server_host,
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
[U, S, Peer, groupchat, recv]) of
drop ->
pass;
NewPkt ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(NewPkt, LServer, {U, S}, groupchat, Peer, Nick, recv)
end;
false ->
pass
end.
@@ -982,6 +1019,8 @@ filter_by_max(_Msgs, _Junk) ->
limit_max(RSM, ?NS_MAM_TMP) ->
RSM; % XEP-0313 v0.2 doesn't require clients to support RSM.
limit_max(none, _NS) ->
#rsm_in{max = ?DEF_PAGE_SIZE};
limit_max(#rsm_in{max = Max} = RSM, _NS) when not is_integer(Max) ->
RSM#rsm_in{max = ?DEF_PAGE_SIZE};
limit_max(#rsm_in{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
@@ -1036,10 +1075,7 @@ get_commands_spec() ->
result = {res, rescode}}].
mod_opt_type(assume_mam_usage) ->
fun(if_enabled) -> if_enabled;
(on_request) -> on_request;
(never) -> never
end;
fun (B) when is_boolean(B) -> B end;
mod_opt_type(cache_life_time) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(cache_size) ->
+9 -6
View File
@@ -138,12 +138,15 @@ select(_LServer, JidRequestor,
SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
{FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM),
Count = length(Msgs),
{lists:map(
fun(Msg) ->
{Msg#archive_msg.id,
jlib:binary_to_integer(Msg#archive_msg.id),
mod_mam:msg_to_el(Msg, MsgType, JidRequestor, JidArchive)}
end, FilteredMsgs), IsComplete, Count}.
Result = {lists:map(
fun(Msg) ->
{Msg#archive_msg.id,
jlib:binary_to_integer(Msg#archive_msg.id),
mod_mam:msg_to_el(Msg, MsgType, JidRequestor,
JidArchive)}
end, FilteredMsgs), IsComplete, Count},
erlang:garbage_collect(),
Result.
%%%===================================================================
%%% Internal functions
-22
View File
@@ -295,25 +295,3 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
{QueryPage,
[<<"SELECT COUNT(*) FROM archive WHERE username='">>,
SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
update(LServer, Table, Fields, Vals, Where) ->
UPairs = lists:zipwith(fun (A, B) ->
<<A/binary, "='", B/binary, "'">>
end,
Fields, Vals),
case ejabberd_sql:sql_query(LServer,
[<<"update ">>, Table, <<" set ">>,
join(UPairs, <<", ">>), <<" where ">>, Where,
<<";">>])
of
{updated, 1} -> {updated, 1};
_ ->
ejabberd_sql:sql_query(LServer,
[<<"insert into ">>, Table, <<"(">>,
join(Fields, <<", ">>), <<") values ('">>,
join(Vals, <<"', '">>), <<"');">>])
end.
%% Almost a copy of string:join/2.
join([], _Sep) -> [];
join([H | T], Sep) -> [H, [[Sep, X] || X <- T]].
+5 -1
View File
@@ -39,7 +39,8 @@
s2s_send_packet, s2s_receive_packet,
remove_user, register_user]).
-export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1]).
-export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1,
depends/2]).
-export([offline_message_hook/3,
sm_register_connection_hook/3, sm_remove_connection_hook/3,
@@ -59,6 +60,9 @@ stop(Host) ->
[ejabberd_hooks:delete(Hook, Host, ?MODULE, Hook, 20)
|| Hook <- ?HOOKS].
depends(_Host, _Opts) ->
[].
%%====================================================================
%% Hooks handlers
%%====================================================================
+5 -2
View File
@@ -14,7 +14,7 @@
%% API
-export([start_link/2, start/2, stop/1, process_iq/3,
disco_items/5, disco_identity/5, disco_info/5,
disco_features/5, mod_opt_type/1]).
disco_features/5, mod_opt_type/1, depends/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -44,7 +44,7 @@ start_link(Host, Opts) ->
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
temporary, 5000, worker, [?MODULE]},
transient, 5000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -343,6 +343,9 @@ is_not_subscribed({error, ErrEl}) ->
_ -> false
end.
depends(_Host, _Opts) ->
[{mod_pubsub, hard}].
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(_) -> [host, iqdisc].
+54 -15
View File
@@ -53,7 +53,7 @@
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1]).
mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -95,7 +95,7 @@ start_link(Host, Opts) ->
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
temporary, 1000, worker, [?MODULE]},
transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -105,6 +105,9 @@ stop(Host) ->
supervisor:delete_child(ejabberd_sup, Proc),
{wait, Rooms}.
depends(_Host, _Opts) ->
[{mod_mam, soft}].
shutdown_rooms(Host) ->
MyHost = gen_mod:get_module_opt_host(Host, mod_muc,
<<"conference.@HOST@">>),
@@ -147,18 +150,10 @@ restore_room(ServerHost, Host, Name) ->
forget_room(ServerHost, Host, Name) ->
LServer = jid:nameprep(ServerHost),
remove_room_mam(LServer, Host, Name),
ejabberd_hooks:run(remove_room, LServer, [LServer, Name, Host]),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:forget_room(LServer, Host, Name).
remove_room_mam(LServer, Host, Name) ->
case gen_mod:is_loaded(LServer, mod_mam) of
true ->
mod_mam:remove_room(LServer, Name, Host);
false ->
ok
end.
process_iq_disco_items(Host, From, To,
#iq{lang = Lang} = IQ) ->
Rsm = jlib:rsm_decode(IQ),
@@ -230,6 +225,7 @@ init([Host, Opts]) ->
public -> Bool;
public_list -> Bool;
mam -> Bool;
allow_subscription -> Bool;
password -> fun iolist_to_binary/1;
title -> fun iolist_to_binary/1;
allow_private_messages_from_visitors ->
@@ -426,6 +422,18 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
iq_get_vcard(Lang)}]},
ejabberd_router:route(To, From,
jlib:iq_to_xml(Res));
#iq{type = get, xmlns = ?NS_MUCSUB,
sub_el = #xmlel{name = <<"subscriptions">>} = SubEl} = IQ ->
RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
Subs = lists:map(
fun(J) ->
#xmlel{name = <<"subscription">>,
attrs = [{<<"jid">>,
jid:to_string(J)}]}
end, RoomJIDs),
Res = IQ#iq{type = result,
sub_el = [SubEl#xmlel{children = Subs}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
#iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
Res = IQ#iq{type = result,
sub_el =
@@ -480,9 +488,8 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
_ ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
Type = fxml:get_attr_s(<<"type">>, Attrs),
case {Name, Type} of
{<<"presence">>, <<"">>} ->
case is_create_request(Packet) of
true ->
case check_user_can_create_room(ServerHost,
AccessCreate, From, Room) and
check_create_roomid(ServerHost, Room) of
@@ -500,7 +507,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route(To, From, Err)
end;
_ ->
false ->
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Conference room does not exist">>,
Err = jlib:make_error_reply(Packet,
@@ -515,6 +522,22 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
end
end.
-spec is_create_request(xmlel()) -> boolean().
is_create_request(#xmlel{name = <<"presence">>} = Packet) ->
<<"">> == fxml:get_tag_attr_s(<<"type">>, Packet);
is_create_request(#xmlel{name = <<"iq">>} = Packet) ->
case jlib:iq_query_info(Packet) of
#iq{type = set, xmlns = ?NS_MUCSUB,
sub_el = #xmlel{name = <<"subscribe">>}} ->
true;
#iq{type = get, xmlns = ?NS_MUC_OWNER, sub_el = SubEl} ->
[] == fxml:remove_cdata(SubEl#xmlel.children);
_ ->
false
end;
is_create_request(_) ->
false.
check_user_can_create_room(ServerHost, AccessCreate,
From, _RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of
@@ -597,6 +620,8 @@ iq_disco_info(ServerHost, Lang) ->
attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_RSM}], children = []},
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_MUCSUB}], children = []},
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++
case gen_mod:is_loaded(ServerHost, mod_mam) of
@@ -693,6 +718,20 @@ get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
index = NewIndex}}
end.
get_subscribed_rooms(ServerHost, Host, From) ->
Rooms = get_rooms(ServerHost, Host),
BareFrom = jid:remove_resource(From),
lists:flatmap(
fun(#muc_room{name_host = {Name, _}, opts = Opts}) ->
Subscribers = proplists:get_value(subscribers, Opts, []),
case lists:keymember(BareFrom, 1, Subscribers) of
true -> [jid:make(Name, Host, <<>>)];
false -> []
end;
(_) ->
[]
end, Rooms).
%% @doc Return the position of desired room in the list of rooms.
%% The room must exist in the list. The count starts in 0.
%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
+127 -15
View File
@@ -11,8 +11,9 @@
-behaviour(gen_mod).
-export([start/2, stop/1, muc_online_rooms/1,
-export([start/2, stop/1, depends/2, muc_online_rooms/1,
muc_unregister_nick/1, create_room/3, destroy_room/2,
create_room_with_opts/4,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
get_user_rooms/2, get_room_occupants/2,
@@ -20,6 +21,7 @@
change_room_option/4, get_room_options/2,
set_room_affiliation/4, get_room_affiliations/2,
web_menu_main/2, web_page_main/2, web_menu_host/3,
subscribe_room/4, unsubscribe_room/2, get_subscribers/2,
web_page_host/3, mod_opt_type/1, get_commands_spec/0]).
-include("ejabberd.hrl").
@@ -49,6 +51,9 @@ stop(Host) ->
ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50).
depends(_Host, _Opts) ->
[{mod_muc, hard}].
%%%
%%% Register commands
%%%
@@ -84,6 +89,18 @@ get_commands_spec() ->
module = ?MODULE, function = create_rooms_file,
args = [{file, string}],
result = {res, rescode}},
#ejabberd_commands{name = create_room_with_opts, tags = [muc_room],
desc = "Create a MUC room name@service in host with given options",
module = ?MODULE, function = create_room_with_opts,
args = [{name, binary}, {service, binary},
{host, binary},
{options, {list,
{option, {tuple,
[{name, binary},
{value, binary}
]}}
}}],
result = {res, rescode}},
#ejabberd_commands{name = destroy_rooms_file, tags = [muc],
desc = "Destroy the rooms indicated in file",
longdesc = "Provide one room JID per line.",
@@ -148,7 +165,22 @@ get_commands_spec() ->
{value, string}
]}}
}}},
#ejabberd_commands{name = subscribe_room, tags = [muc_room],
desc = "Subscribe to a MUC conference",
module = ?MODULE, function = subscribe_room,
args = [{user, binary}, {nick, binary}, {room, binary},
{nodes, binary}],
result = {nodes, {list, {node, string}}}},
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room],
desc = "Unsubscribe from a MUC conference",
module = ?MODULE, function = unsubscribe_room,
args = [{user, binary}, {room, binary}],
result = {res, rescode}},
#ejabberd_commands{name = get_subscribers, tags = [muc_room],
desc = "List subscribers of a MUC conference",
module = ?MODULE, function = get_subscribers,
args = [{name, binary}, {service, binary}],
result = {subscribers, {list, {jid, string}}}},
#ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
desc = "Change an affiliation in a MUC room",
module = ?MODULE, function = set_room_affiliation,
@@ -397,15 +429,23 @@ prepare_room_info(Room_info) ->
%% ok | error
%% @doc Create a room immediately with the default options.
create_room(Name1, Host1, ServerHost) ->
create_room_with_opts(Name1, Host1, ServerHost, []).
create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) ->
Name = jid:nodeprep(Name1),
Host = jid:nodeprep(Host1),
%% Get the default room options from the muc configuration
DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc,
default_room_options, fun(X) -> X end, []),
%% Change default room options as required
FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts],
RoomOpts = lists:ukeymerge(1,
lists:keysort(1, FormattedRoomOpts),
lists:keysort(1, DefRoomOpts)),
%% Store the room on the server, it is not started yet though at this point
mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts),
mod_muc:store_room(ServerHost, Host, Name, RoomOpts),
%% Get all remaining mod_muc parameters that might be utilized
Access = gen_mod:get_module_opt(ServerHost, mod_muc, access, fun(X) -> X end, all),
@@ -426,7 +466,7 @@ create_room(Name1, Host1, ServerHost) ->
Name,
HistorySize,
RoomShaper,
DefRoomOpts),
RoomOpts),
{atomic, ok} = register_room(Host, Name, Pid),
ok;
_ ->
@@ -745,12 +785,20 @@ send_direct_invitation(FromJid, UserJid, XmlEl) ->
%% the option to change (for example title or max_users),
%% and the value to assign to the new option.
%% For example:
%% change_room_option("testroom", "conference.localhost", "title", "Test Room")
change_room_option(Name, Service, Option, Value) when is_atom(Option) ->
Pid = get_room_pid(Name, Service),
{ok, _} = change_room_option(Pid, Option, Value),
ok;
%% change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)
change_room_option(Name, Service, OptionString, ValueString) ->
case get_room_pid(Name, Service) of
room_not_found ->
room_not_found;
Pid ->
{Option, Value} = format_room_option(OptionString, ValueString),
Config = get_room_config(Pid),
Config2 = change_option(Option, Value, Config),
{ok, _} = gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
ok
end.
format_room_option(OptionString, ValueString) ->
Option = jlib:binary_to_atom(OptionString),
Value = case Option of
title -> ValueString;
@@ -761,12 +809,7 @@ change_room_option(Name, Service, OptionString, ValueString) ->
max_users -> jlib:binary_to_integer(ValueString);
_ -> jlib:binary_to_atom(ValueString)
end,
change_room_option(Name, Service, Option, Value).
change_room_option(Pid, Option, Value) ->
Config = get_room_config(Pid),
Config2 = change_option(Option, Value, Config),
gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}).
{Option, Value}.
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
get_room_pid(Name, Service) ->
@@ -786,6 +829,7 @@ change_option(Option, Value, Config) ->
allow_private_messages -> Config#config{allow_private_messages = Value};
allow_private_messages_from_visitors -> Config#config{allow_private_messages_from_visitors = Value};
allow_query_users -> Config#config{allow_query_users = Value};
allow_subscription -> Config#config{allow_subscription = Value};
allow_user_invites -> Config#config{allow_user_invites = Value};
allow_visitor_nickchange -> Config#config{allow_visitor_nickchange = Value};
allow_visitor_status -> Config#config{allow_visitor_status = Value};
@@ -881,6 +925,74 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
error
end.
%%%
%%% MUC Subscription
%%%
subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
throw({error, "Nickname must be set"});
subscribe_room(User, Nick, Room, Nodes) ->
NodeList = re:split(Nodes, "\\h*,\\h*"),
case jid:from_string(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
case jid:from_string(User) of
error ->
throw({error, "Malformed user JID"});
#jid{lresource = <<"">>} ->
throw({error, "User's JID should have a resource"});
UserJID ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
case gen_fsm:sync_send_all_state_event(
Pid,
{muc_subscribe, UserJID, Nick, NodeList}) of
{ok, SubscribedNodes} ->
SubscribedNodes;
{error, Reason} ->
throw({error, binary_to_list(Reason)})
end;
_ ->
throw({error, "The room does not exist"})
end
end;
_ ->
throw({error, "Malformed room JID"})
end.
unsubscribe_room(User, Room) ->
case jid:from_string(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
case jid:from_string(User) of
error ->
throw({error, "Malformed user JID"});
UserJID ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
case gen_fsm:sync_send_all_state_event(
Pid,
{muc_unsubscribe, UserJID}) of
ok ->
ok;
{error, Reason} ->
throw({error, binary_to_list(Reason)})
end;
_ ->
throw({error, "The room does not exist"})
end
end;
_ ->
throw({error, "Malformed room JID"})
end.
get_subscribers(Name, Host) ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
{ok, JIDList} = gen_fsm:sync_send_all_state_event(Pid, get_subscribers),
[jid:to_string(jid:remove_resource(J)) || J <- JIDList];
_ ->
throw({error, "The room does not exist"})
end.
make_opts(StateData) ->
Config = StateData#state.config,
[
+5 -2
View File
@@ -41,7 +41,7 @@
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1, opt_type/1]).
mod_opt_type/1, opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -81,7 +81,7 @@ start_link(Host, Opts) ->
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
temporary, 1000, worker, [?MODULE]},
transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -109,6 +109,9 @@ transform_module_options(Opts) ->
Opt
end, Opts).
depends(_Host, _Opts) ->
[{mod_muc, hard}].
%%====================================================================
%% gen_server callbacks
%%====================================================================
+894 -448
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -40,7 +40,7 @@
-export([init/1, handle_info/2, handle_call/3,
handle_cast/2, terminate/2, code_change/3]).
-export([purge_loop/1, mod_opt_type/1]).
-export([purge_loop/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -1219,6 +1219,9 @@ stj(String) -> jid:from_string(String).
jts(String) -> jid:to_string(String).
depends(_Host, _Opts) ->
[].
mod_opt_type(access) ->
fun acl:access_rules_validator/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
+48 -37
View File
@@ -66,7 +66,7 @@
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1]).
mod_opt_type/1, depends/2]).
-deprecated({get_queue_length,2}).
@@ -125,6 +125,8 @@ stop(Host) ->
supervisor:delete_child(ejabberd_sup, Proc),
ok.
depends(_Host, _Opts) ->
[].
%%====================================================================
%% gen_server callbacks
@@ -435,35 +437,36 @@ remove_msg_by_node(To, Seq) ->
end.
need_to_store(LServer, Packet) ->
Type = fxml:get_tag_attr_s(<<"type">>, Packet),
if (Type /= <<"error">>) and (Type /= <<"groupchat">>)
and (Type /= <<"headline">>) ->
case has_offline_tag(Packet) of
false ->
case check_store_hint(Packet) of
store ->
case has_offline_tag(Packet) of
false ->
case {check_store_hint(Packet),
fxml:get_tag_attr_s(<<"type">>, Packet)} of
{_Hint, <<"error">>} ->
false;
{store, _Type} ->
true;
{no_store, _Type} ->
false;
{none, <<"groupchat">>} ->
false;
{none, <<"headline">>} ->
false;
{none, _Type} ->
case gen_mod:get_module_opt(
LServer, ?MODULE, store_empty_body,
fun(V) when is_boolean(V) -> V;
(unless_chat_state) -> unless_chat_state
end,
unless_chat_state) of
true ->
true;
no_store ->
false;
none ->
case gen_mod:get_module_opt(
LServer, ?MODULE, store_empty_body,
fun(V) when is_boolean(V) -> V;
(unless_chat_state) -> unless_chat_state
end,
unless_chat_state) of
false ->
fxml:get_subtag(Packet, <<"body">>) /= false;
unless_chat_state ->
not jlib:is_standalone_chat_state(Packet);
true ->
true
end
end;
true ->
false
false ->
fxml:get_subtag(Packet, <<"body">>) /= false;
unless_chat_state ->
not jlib:is_standalone_chat_state(Packet)
end
end;
true ->
true ->
false
end.
@@ -473,14 +476,22 @@ store_packet(From, To, Packet) ->
case check_event(From, To, Packet) of
true ->
#jid{luser = LUser, lserver = LServer} = To,
TimeStamp = p1_time_compat:timestamp(),
#xmlel{children = Els} = Packet,
Expire = find_x_expire(TimeStamp, Els),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
#offline_msg{us = {LUser, LServer},
timestamp = TimeStamp, expire = Expire,
from = From, to = To, packet = Packet},
stop;
case ejabberd_hooks:run_fold(store_offline_message, LServer,
Packet, [From, To]) of
drop ->
ok;
NewPacket ->
TimeStamp = p1_time_compat:timestamp(),
#xmlel{children = Els} = NewPacket,
Expire = find_x_expire(TimeStamp, Els),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
#offline_msg{us = {LUser, LServer},
timestamp = TimeStamp,
expire = Expire,
from = From, to = To,
packet = NewPacket},
stop
end;
_ -> ok
end;
false -> ok
@@ -791,7 +802,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}, Host)
User, Host)
of
Number when is_integer(Number) -> Number;
_ -> 100
+4 -1
View File
@@ -55,7 +55,7 @@
handle_cast/2, handle_info/2, code_change/3]).
-export([iq_ping/3, user_online/3, user_offline/3,
user_send/4, mod_opt_type/1]).
user_send/4, mod_opt_type/1, depends/2]).
-record(state,
{host = <<"">>,
@@ -253,6 +253,9 @@ cancel_timer(TRef) ->
_ -> ok
end.
depends(_Host, _Opts) ->
[].
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(ping_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
+4 -1
View File
@@ -28,7 +28,7 @@
-behavior(gen_mod).
-export([start/2, stop/1, check_packet/6,
mod_opt_type/1]).
mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -48,6 +48,9 @@ stop(Host) ->
?MODULE, check_packet, 25),
ok.
depends(_Host, _Opts) ->
[].
check_packet(_, _User, Server, _PrivacyList,
{From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) ->
case Name of
+4 -1
View File
@@ -36,7 +36,7 @@
check_packet/6, remove_user/2,
is_list_needdb/1, updated_list/3,
item_to_xml/1, get_user_lists/2, import/3,
set_privacy_list/1, mod_opt_type/1]).
set_privacy_list/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -593,6 +593,9 @@ import(LServer, DBType, Data) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, Data).
depends(_Host, _Opts) ->
[].
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(_) -> [db_type, iqdisc].
+4 -1
View File
@@ -33,7 +33,7 @@
-export([start/2, stop/1, process_sm_iq/3, import/3,
remove_user/2, get_data/2, export/1, import/1,
mod_opt_type/1, set_data/3]).
mod_opt_type/1, set_data/3, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -173,6 +173,9 @@ import(LServer, DBType, PD) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, PD).
depends(_Host, _Opts) ->
[].
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(_) -> [db_type, iqdisc].
+363
View File
@@ -0,0 +1,363 @@
%%%--------------------------------------------------------------------------------------
%%% File : mod_privilege.erl
%%% Author : Anna Mukharram <amuhar3@gmail.com>
%%% Purpose : This module is an implementation for XEP-0356: Privileged Entity
%%%--------------------------------------------------------------------------------------
-module(mod_privilege).
-author('amuhar3@gmail.com').
-protocol({xep, 0356, '0.2.1'}).
-export([advertise_permissions/1, initial_presences/1, process_presence/1,
process_roster_presence/1, compare_presences/2,
process_message/4, process_iq/4]).
-include("ejabberd_service.hrl").
-include("mod_privacy.hrl").
%%%--------------------------------------------------------------------------------------
%%% Functions to advertise services of allowed permission
%%%--------------------------------------------------------------------------------------
-spec permissions(binary(), binary(), list()) -> xmlel().
permissions(From, To, PrivAccess) ->
Perms = lists:map(fun({Access, Type}) ->
?DEBUG("Advertise service ~s of allowed permission: ~s = ~s~n",
[To, Access, Type]),
#xmlel{name = <<"perm">>,
attrs = [{<<"access">>,
atom_to_binary(Access,latin1)},
{<<"type">>, Type}]}
end, PrivAccess),
Stanza = #xmlel{name = <<"privilege">>,
attrs = [{<<"xmlns">> ,?NS_PRIVILEGE}],
children = Perms},
Id = randoms:get_string(),
#xmlel{name = <<"message">>,
attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}],
children = [Stanza]}.
advertise_permissions(#state{privilege_access = []}) -> ok;
advertise_permissions(StateData) ->
Stanza =
permissions(?MYNAME, StateData#state.host, StateData#state.privilege_access),
ejabberd_service:send_element(StateData, Stanza).
%%%--------------------------------------------------------------------------------------
%%% Process presences
%%%--------------------------------------------------------------------------------------
initial_presences(StateData) ->
Pids = ejabberd_sm:get_all_pids(),
lists:foreach(
fun(Pid) ->
{User, Server, Resource, PresenceLast} = ejabberd_c2s:get_last_presence(Pid),
From = #jid{user = User, server = Server, resource = Resource},
To = jid:from_string(StateData#state.host),
PacketNew = jlib:replace_from_to(From, To, PresenceLast),
ejabberd_service:send_element(StateData, PacketNew)
end, Pids).
%% hook user_send_packet(Packet, C2SState, From, To) -> Packet
%% for Managed Entity Presence
process_presence(Pid) ->
fun(#xmlel{name = <<"presence">>} = Packet, _C2SState, From, _To) ->
case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of
T when (T == <<"">>) or (T == <<"unavailable">>) ->
Pid ! {user_presence, Packet, From};
_ -> ok
end,
Packet;
(Packet, _C2SState, _From, _To) ->
Packet
end.
%% s2s_receive_packet(From, To, Packet) -> ok
%% for Roster Presence
%% From subscription "from" or "both"
process_roster_presence(Pid) ->
fun(From, To, #xmlel{name = <<"presence">>} = Packet) ->
case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of
T when (T == <<"">>) or (T == <<"unavailable">>) ->
Server = To#jid.server,
User = To#jid.user,
PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
Server, #userlist{}, [User, Server]),
case privacy_check_packet(Server, User, PrivList, From, To, Packet, in) of
allow ->
Pid ! {roster_presence, Packet, From};
_ -> ok
end,
ok;
_ -> ok
end;
(_From, _To, _Packet) -> ok
end.
%%%--------------------------------------------------------------------------------------
%%% Manage Roster
%%%--------------------------------------------------------------------------------------
process_iq(StateData, FromJID, ToJID, Packet) ->
IQ = jlib:iq_query_or_response_info(Packet),
case IQ of
#iq{xmlns = ?NS_ROSTER} ->
case (ToJID#jid.luser /= <<"">>) and
(FromJID#jid.luser == <<"">>) and
lists:member(ToJID#jid.lserver, ?MYHOSTS) of
true ->
AccessType =
proplists:get_value(roster, StateData#state.privilege_access, none),
case IQ#iq.type of
get when (AccessType == <<"both">>) or (AccessType == <<"get">>) ->
RosterIQ = roster_management(ToJID, FromJID, IQ),
ejabberd_service:send_element(StateData, RosterIQ);
set when (AccessType == <<"both">>) or (AccessType == <<"set">>) ->
%% check if user ToJID exist
#jid{lserver = Server, luser = User} = ToJID,
case ejabberd_auth:is_user_exists(User,Server) of
true ->
ResIQ = roster_management(ToJID, FromJID, IQ),
ejabberd_service:send_element(StateData, ResIQ);
_ -> ok
end;
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end;
#iq{type = Type, id = Id} when (Type == error) or (Type == result) -> % for XEP-0355
Hook = {iq, Type, Id},
Host = ToJID#jid.lserver,
case (ToJID#jid.luser == <<"">>) and
(FromJID#jid.luser == <<"">>) and
lists:member(ToJID#jid.lserver, ?MYHOSTS) of
true ->
case ets:lookup(hooks_tmp, {Hook, Host}) of
[{_, Function, _Timestamp}] ->
catch apply(Function, [Packet]);
[] ->
ejabberd_router:route(FromJID, ToJID, Packet)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end.
roster_management(FromJID, ToJID, IQ) ->
ResIQ = mod_roster:process_iq(FromJID, FromJID, IQ),
ResXml = jlib:iq_to_xml(ResIQ),
jlib:replace_from_to(FromJID, ToJID, ResXml).
%%%--------------------------------------------------------------------------------------
%%% Message permission
%%%--------------------------------------------------------------------------------------
process_message(StateData, FromJID, ToJID, #xmlel{children = Children} = Packet) ->
%% if presence was send from service to server,
case lists:member(ToJID#jid.lserver, ?MYHOSTS) and
(ToJID#jid.luser == <<"">>) and
(FromJID#jid.luser == <<"">>) of %% service
true ->
%% if stanza contains privilege element
case Children of
[#xmlel{name = <<"privilege">>,
attrs = [{<<"xmlns">>, ?NS_PRIVILEGE}],
children = [#xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
children = Children2}]}] ->
%% 1 case : privilege service send subscription message
%% on behalf of the client
%% 2 case : privilege service send message on behalf
%% of the client
case Children2 of
%% it isn't case of 0356 extension
[#xmlel{name = <<"presence">>} = Child] ->
forward_subscribe(StateData, Child, Packet);
[#xmlel{name = <<"message">>} = Child] -> %% xep-0356
forward_message(StateData, Child, Packet);
_ ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"invalid forwarded element">>,
Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_service:send_element(StateData, Err)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end.
forward_subscribe(StateData, Presence, Packet) ->
PrivAccess = StateData#state.privilege_access,
T = proplists:get_value(roster, PrivAccess, none),
Type = fxml:get_attr_s(<<"type">>, Presence#xmlel.attrs),
if
((T == <<"both">>) or (T == <<"set">>)) and (Type == <<"subscribe">>) ->
From = fxml:get_attr_s(<<"from">>, Presence#xmlel.attrs),
FromJ = jid:from_string(From),
To = fxml:get_attr_s(<<"to">>, Presence#xmlel.attrs),
ToJ = case To of
<<"">> -> error;
_ -> jid:from_string(To)
end,
if
(ToJ /= error) and (FromJ /= error) ->
Server = FromJ#jid.lserver,
User = FromJ#jid.luser,
case (FromJ#jid.lresource == <<"">>) and
lists:member(Server, ?MYHOSTS) of
true ->
if
(Server /= ToJ#jid.lserver) or
(User /= ToJ#jid.luser) ->
%% 0356 server MUST NOT allow the privileged entity
%% to do anything that the managed entity could not do
try_roster_subscribe(Server,User, FromJ, ToJ, Presence);
true -> %% we don't want presence sent to self
ok
end;
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end;
true ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Incorrect stanza from/to JID">>,
Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_service:send_element(StateData, Err)
end;
true ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end.
forward_message(StateData, Message, Packet) ->
PrivAccess = StateData#state.privilege_access,
T = proplists:get_value(message, PrivAccess, none),
if
(T == <<"outgoing">>) ->
From = fxml:get_attr_s(<<"from">>, Message#xmlel.attrs),
FromJ = jid:from_string(From),
To = fxml:get_attr_s(<<"to">>, Message#xmlel.attrs),
ToJ = case To of
<<"">> -> FromJ;
_ -> jid:from_string(To)
end,
if
(ToJ /= error) and (FromJ /= error) ->
Server = FromJ#jid.server,
User = FromJ#jid.user,
case (FromJ#jid.lresource == <<"">>) and
lists:member(Server, ?MYHOSTS) of
true ->
%% there are no restriction on to attribute
PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
Server, #userlist{},
[User, Server]),
check_privacy_route(Server, User, PrivList,
FromJ, ToJ, Message);
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end;
true ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Incorrect stanza from/to JID">>,
Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_service:send_element(StateData, Err)
end;
true ->
Err = jlib:make_error_reply(Packet,?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end.
%%%--------------------------------------------------------------------------------------
%%% helper functions
%%%--------------------------------------------------------------------------------------
compare_presences(undefined, _Presence) -> false;
compare_presences(#xmlel{attrs = Attrs, children = Child},
#xmlel{attrs = Attrs2, children = Child2}) ->
Id1 = fxml:get_attr_s(<<"id">>, Attrs),
Id2 = fxml:get_attr_s(<<"id">>, Attrs2),
if
(Id1 /= Id2) ->
false;
(Id1 /= <<"">>) and (Id1 == Id2) ->
true;
true ->
case not compare_attrs(Attrs, Attrs2) of
true -> false;
_ ->
compare_elements(Child, Child2)
end
end.
compare_elements([],[]) -> true;
compare_elements(Tags1, Tags2) when length(Tags1) == length(Tags2) ->
compare_tags(Tags1,Tags2);
compare_elements(_Tags1, _Tags2) -> false.
compare_tags([],[]) -> true;
compare_tags([{xmlcdata, CData}|Tags1], [{xmlcdata, CData}|Tags2]) ->
compare_tags(Tags1, Tags2);
compare_tags([{xmlcdata, _CData1}|_Tags1], [{xmlcdata, _CData2}|_Tags2]) ->
false;
compare_tags([#xmlel{} = Stanza1|Tags1], [#xmlel{} = Stanza2|Tags2]) ->
case (Stanza1#xmlel.name == Stanza2#xmlel.name) and
compare_attrs(Stanza1#xmlel.attrs, Stanza2#xmlel.attrs) and
compare_tags(Stanza1#xmlel.children, Stanza2#xmlel.children) of
true ->
compare_tags(Tags1,Tags2);
false ->
false
end.
%% attr() :: {Name, Value}
-spec compare_attrs([attr()], [attr()]) -> boolean().
compare_attrs([],[]) -> true;
compare_attrs(Attrs1, Attrs2) when length(Attrs1) == length(Attrs2) ->
lists:foldl(fun(Attr,Acc) -> lists:member(Attr, Attrs2) and Acc end, true, Attrs1);
compare_attrs(_Attrs1, _Attrs2) -> false.
%% Check if privacy rules allow this delivery
%% from ejabberd_c2s.erl
privacy_check_packet(Server, User, PrivList, From, To, Packet , Dir) ->
ejabberd_hooks:run_fold(privacy_check_packet,
Server, allow, [User, Server, PrivList,
{From, To, Packet}, Dir]).
check_privacy_route(Server, User, PrivList, From, To, Packet) ->
case privacy_check_packet(Server, User, PrivList, From, To, Packet, out) of
allow ->
ejabberd_router:route(From, To, Packet);
_ -> ok %% who should receive error : service or user?
end.
try_roster_subscribe(Server,User, From, To, Packet) ->
Access =
gen_mod:get_module_opt(Server, mod_roster, access,
fun(A) when is_atom(A) -> A end, all),
case acl:match_rule(Server, Access, From) of
deny ->
ok;
allow ->
ejabberd_hooks:run(roster_out_subscription, Server,
[User, Server, To, subscribe]),
PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
Server,
#userlist{},
[User, Server]),
check_privacy_route(Server, User, PrivList, From, To, Packet)
end.
+4 -1
View File
@@ -39,7 +39,7 @@
%% supervisor callbacks.
-export([init/1]).
-export([start_link/2, mod_opt_type/1]).
-export([start_link/2, mod_opt_type/1, depends/2]).
-define(PROCNAME, ejabberd_mod_proxy65).
@@ -84,6 +84,9 @@ init([Host, Opts]) ->
{{one_for_one, 10, 1},
[StreamManager, StreamSupervisor, Service]}}.
depends(_Host, _Opts) ->
[].
mod_opt_type(auth_type) ->
fun (plain) -> plain;
(anonymous) -> anonymous

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