Compare commits

...

242 Commits

Author SHA1 Message Date
Christophe Romain b30b70db70 Prepare mix for 16.12 2016-12-22 20:41:01 +01:00
Paweł Chmielowski 08f73a5aeb Update deps 2016-12-22 15:10:57 +01:00
Badlop 3811a61573 Extract correctly the text from #stanza_error (#1425) 2016-12-17 11:46:16 +01:00
Badlop cc14e223c6 Describe command arguments and results in ejabberd_admin 2016-12-12 13:19:17 +01:00
Badlop 1a4f63f058 Copy recent make_opts function from mod_muc_room.erl 2016-12-09 13:07:48 +01:00
Christophe Romain bcc04d93e1 Enforce pubsub node removal, revert previous commit (#1320)
This reverts commit 2976c2d921.
and enforce node purge/removal instead
2016-12-09 12:51:08 +01:00
Christophe Romain 2976c2d921 Enforce affiliation removal, remove corresponding items (#1320) 2016-12-09 11:35:51 +01:00
Paweł Chmielowski a50247c20d Improve handling on acl rules in api_permissions 2016-12-08 18:08:54 +01:00
Mickael Remond fdf69dcd0d API call does not necessary use token, it could use basic auth 2016-12-08 16:28:47 +01:00
Paweł Chmielowski a3b12fd745 Recognize "- who:" not only "who:" syntax in api_permissions 2016-12-08 12:01:24 +01:00
Christophe Romain 8a7ea85a7e Cleanup ext_mod and fix compilation path 2016-12-07 09:27:21 +01:00
Paweł Chmielowski a681874f67 Try to fix interminent failures in cyrsasl tests 2016-12-06 18:33:16 +01:00
Mickael Remond 1c8edd07a1 Update xmpp lib version and fix path when using deps.get and compile in same commande 2016-12-06 17:54:21 +01:00
Christophe Romain 1883a98d1c Fix compilation of external module with new xmpp lib 2016-12-06 12:01:04 +01:00
Christophe Romain 0c2491d9ea Remove obsolete remove_node api (use leave_cluster) 2016-12-05 14:17:57 +01:00
Christophe Romain 23f7075313 Fix reload_config 2016-12-02 16:29:46 +01:00
Paweł Chmielowski 23786c95c9 Grab new version of xmpp dep 2016-12-02 16:28:14 +01:00
Badlop 847376924e Add password support in muc_subscribe (#1306) 2016-12-02 16:18:35 +01:00
Badlop d7e1f6d7b3 When unsubscribes, check if room should get closed (#1396) 2016-12-01 22:09:57 +01:00
Badlop 34f2a8a4f2 Fix error formatting, which closed client connection (#1389) 2016-12-01 21:02:54 +01:00
Paweł Chmielowski 68cf6845e1 Fix mod_http_api_mock_test on 19.1 2016-12-01 18:51:23 +01:00
Paweł Chmielowski 294d58a393 Add more tests for digest-md5 cyrsasl 2016-12-01 18:51:23 +01:00
Mickael Remond 732eecac43 Move to latest Elixir version 2016-12-01 15:44:52 +01:00
Mickael Remond a3c134c43b We can now use dependencies published in hex.pm 2016-12-01 15:10:00 +01:00
Mickael Remond 309fd56fb4 Merge branch 'master' of github.com:processone/ejabberd 2016-12-01 15:05:09 +01:00
Badlop 8618af4e34 Fixes pt-br translation (thanks to Rodrigues)(#1393) 2016-11-30 16:51:42 +01:00
Christophe Romain 32f484a349 Fix typo introduced by 92db9ff changes 2016-11-30 13:50:46 +01:00
Christophe Romain 92db9ff105 Improve handling of mnesia schema 2016-11-30 11:09:17 +01:00
Christophe Romain 95a4b1b266 Cleanup admin_extra, add few functions 2016-11-30 10:31:36 +01:00
Christophe Romain 2cd70280d2 Merge branch 'docker' from Rafael Römhild 2016-11-30 10:30:19 +01:00
Christophe Romain fc7e52df71 Adds optional post_install and pre_uninstall hooks (thanks Igor Manturov Jr.)(#1300) 2016-11-28 17:15:57 +01:00
Evgeniy Khramtsov 56b30ab598 Improve translation of some messages 2016-11-26 10:05:22 +03:00
Christophe Romain 3ac73f9607 Update dependencies 2016-11-25 13:02:31 +01:00
Christophe Romain 18609befa4 Merge branch 'master' of github.com:processone/ejabberd 2016-11-25 10:07:04 +01:00
Christophe Romain ae297a4ae6 Merge branch 'ukutaht-enable-elixir-in-mix' 2016-11-25 10:05:28 +01:00
Christophe Romain 9432a16893 Merge branch 'enable-elixir-in-mix' of https://github.com/ukutaht/ejabberd into ukutaht-enable-elixir-in-mix 2016-11-25 10:05:01 +01:00
Evgeniy Khramtsov ca1b22bdd4 Use ejabberd_router:route_error/4 wherever possible 2016-11-25 11:41:24 +03:00
Christophe Romain 4d6eb31264 Use new version of xmpp in mix 2016-11-25 09:31:49 +01:00
Evgeniy Khramtsov e1539e5769 Get rid of compile warnings 2016-11-25 09:48:26 +03:00
Evgeniy Khramtsov 993cbcb133 Use new version of xmpp 2016-11-25 09:39:09 +03:00
Evgeniy Khramtsov d554827ebc Don't check for faked carbons 2016-11-24 20:16:07 +03:00
Evgeniy Khramtsov 0f11b1be36 Don't forget to erase cache on user removal 2016-11-24 18:40:20 +03:00
Paweł Chmielowski 28d0a1b9d2 Make compatible with rebar3 2016-11-24 14:23:20 +01:00
Evgeniy Khramtsov 49f1275e20 Get rid of excessive (io)list_to_binary/1 calls 2016-11-24 15:06:06 +03:00
Paweł Chmielowski b14843d098 Add missing -callbacks 2016-11-24 12:44:21 +01:00
Evgeny Khramtsov 8f692f51d8 Merge pull request #1391 from weiss/mark-copies
Mark messages as copies by attaching metadata
2016-11-24 15:43:38 +04:00
Holger Weiss 560038c808 Use xmpp:put_meta/3 to update metadata 2016-11-24 10:47:26 +01:00
Holger Weiss d12210f4e1 Use new versions of fast_xml and xmpp 2016-11-24 10:45:57 +01:00
Holger Weiss 9c3ebb7d22 Don't make ejabberd.service file executable 2016-11-24 07:59:27 +01:00
Holger Weiss b6ddcf3e58 Makefile.in: Substitute all @variables@ in a line
There are now lines with multiple occurrences of the @ctlscriptpath@
variable in the ejabberd.service template.
2016-11-24 07:55:06 +01:00
Christophe Romain cbda2e038e Merge branch 'weiss-systemd-unit-improvements' (#1346) 2016-11-23 14:37:34 +01:00
Christophe Romain 6a4f2a78ff Merge branch 'systemd-unit-improvements' of https://github.com/weiss/ejabberd into weiss-systemd-unit-improvements 2016-11-23 14:36:50 +01:00
Christophe Romain 12683b4aaf Fix typo in copyright date 2016-11-23 14:36:11 +01:00
Evgeniy Khramtsov 5d434c1aea Move copyright definition to ejabberd.hrl 2016-11-23 15:51:48 +03:00
Evgeniy Khramtsov 6f2f1e87c9 Don't use deprecated functions from jlib.erl 2016-11-23 10:41:26 +03:00
Holger Weiss 3325e69ae6 Let mod_carboncopy mark copied messages
Carbon copies are now marked with a 'carbon_copy' flag.  This makes it
easier to identify them.
2016-11-22 22:21:34 +01:00
Holger Weiss 114ca786ee Let ejabberd_sm mark copied messages
When multiple resources have the same (highest) priority, ejabberd_sm
dispatches messages addressed to the bare JID (or to an unavailable
resource) to each of these resources.  Such messages are now marked with
an 'sm_copy' flag for all but one of the resources.  This makes it
easier for other modules to identify those duplicates.

Resolves #1356.
2016-11-22 19:25:20 +01:00
Christophe Romain ee8cc1dac2 Fix xref issue injected by fbfbb96 2016-11-22 17:51:21 +01:00
Christophe Romain 9ab169bc63 Fix get_roster issue injected by fbfbb96 2016-11-22 16:59:02 +01:00
Christophe Romain 24ef90c556 Fix vcard_ldap exports 2016-11-22 16:23:02 +01:00
Christophe Romain c0e7b298db Add missing export 2016-11-22 16:12:19 +01:00
Christophe Romain cc63bcc997 Fix issues on import improvements 2016-11-22 16:01:08 +01:00
Paweł Chmielowski a2fb493f91 Add missing include file 2016-11-22 15:35:47 +01:00
Paweł Chmielowski ebadcf71c2 New bosh module 2016-11-22 15:26:15 +01:00
Christophe Romain fbfbb96872 Improve ODBC import 2016-11-22 14:48:01 +01:00
Christophe Romain 577eeb642f Add new xmpp repo as dependency in mix.exs 2016-11-22 14:43:10 +01:00
Paweł Chmielowski 7ffab38b44 Remove now() from mod_offline.hrl 2016-11-22 13:15:43 +01:00
Evgeniy Khramtsov 2786df651a Add OTP 19.1 to Travis' testing platforms 2016-11-22 09:52:26 +03:00
Evgeniy Khramtsov c0b5c6e9d4 Fix logging in tests on R17 2016-11-21 22:04:25 +03:00
Evgeniy Khramtsov 3189bb3bb9 Set 'sql_pool_size' to 1 by default for sqlite 2016-11-21 21:34:56 +03:00
Evgeniy Khramtsov 3908a80ac9 Merge branch 'master' of github.com:processone/ejabberd 2016-11-21 16:55:28 +03:00
Evgeniy Khramtsov 1a02a2a51d Set 'sql_pool_size' to 1 for sqlite tests 2016-11-21 16:55:18 +03:00
Paweł Chmielowski b8e8e4b971 Fix elixir tests 2016-11-21 12:50:51 +01:00
Evgeniy Khramtsov 04fdf69737 Fix non-empty disco-nodes processing 2016-11-21 14:21:34 +03:00
Paweł Chmielowski e57de02e0f Fix s2s test 2016-11-21 12:06:58 +01:00
Badlop c5e7b4738f Fix handling mod_http_upload disco#info queries: need decoded elements
As reported in
https://www.ejabberd.im/forum/28605/ejabberd-modhttpupload-error-405-not-allowed
2016-11-21 12:06:11 +01:00
Evgeniy Khramtsov e6365979bd Merge branch 'master' of github.com:processone/ejabberd 2016-11-21 12:14:37 +03:00
Evgeniy Khramtsov 274303f248 Fix conference disco#items when running multiple virtual hosts 2016-11-21 12:14:24 +03:00
Evgeniy Khramtsov 507e756b69 Do not send empty <after/> tag 2016-11-21 12:13:34 +03:00
Paweł Chmielowski 3f91668c46 Log more data for failed s2s connection 2016-11-21 09:52:36 +01:00
Evgeniy Khramtsov c7ae916afc Don't forget to start XMPP application 2016-11-21 10:23:09 +03:00
Evgeniy Khramtsov 049a6d97f1 Fix RSM for conference disco#items 2016-11-20 18:08:49 +03:00
Evgeniy Khramtsov 13c6039700 Use xmpp_util.erl from XMPP library 2016-11-19 13:57:25 +03:00
Evgeniy Khramtsov de7a143a2c Transform ejabberd_commands on the start 2016-11-19 13:05:13 +03:00
Evgeniy Khramtsov 9aff7e52a8 Switch to stand-alone XMPP library 2016-11-19 13:03:33 +03:00
Paweł Chmielowski d00a634025 Fix includes in eunit compilation flags 2016-11-18 14:02:47 +01:00
Paweł Chmielowski daab95e3b5 Fix elixir tests 2016-11-18 12:54:06 +01:00
Christophe Romain 45e77ea483 Remove useless NO_EXT_LIB flag 2016-11-18 12:25:01 +01:00
Paweł Chmielowski e69937d93a Get rid of substitute_forwarded 2016-11-18 11:51:57 +01:00
Evgeniy Khramtsov f57f267c54 Merge branch 'master' of github.com:processone/ejabberd
Conflicts:
	test/ejabberd_SUITE.erl
2016-11-18 13:39:10 +03:00
Evgeniy Khramtsov b8dcc911a3 Make common tests working again 2016-11-18 13:38:08 +03:00
Paweł Chmielowski 995c97671d Add auth to mod_http_fileserver 2016-11-17 12:59:46 +01:00
Christophe Romain 1c90b19d74 Fix typo 2016-11-16 18:24:12 +01:00
Christophe Romain 8ced3bdbe9 Remove obsolete/temp file 2016-11-16 14:18:51 +01:00
Badlop 3cd174cb56 Ensure that presence_broadcast room option is stored (#1380) 2016-11-16 13:35:50 +01:00
Badlop e69ddd981f Tell git to ignore the example file ejabberd.service 2016-11-16 13:12:57 +01:00
Badlop b1723c6e2d Handle correctly p1_http:request result 2016-11-16 13:11:23 +01:00
Rafael Römhild 5a01b5f1fc add docker support 2016-11-16 11:48:50 +01:00
Christophe Romain 2929f5b5bc Minor cosmetic changes on pubsub code 2016-11-15 18:35:20 +01:00
Christophe Romain 4d4ad922a2 Cosmetic validator changes 2016-11-15 18:14:21 +01:00
Christophe Romain 8df68266f2 Add missing verbs for RESTfull operation 2016-11-15 14:35:26 +01:00
Christophe Romain 2b93de6912 apply string optimizations 2016-11-15 14:32:22 +01:00
Christophe Romain 909e0eb5dd Add configurable weight for commands 2016-11-15 14:18:34 +01:00
Paweł Chmielowski e75dd17e2c Fix tests that use #forwarded 2016-11-15 10:02:21 +01:00
Paweł Chmielowski 41794c57d6 Use new version of fast_xml 2016-11-15 10:02:21 +01:00
Paweł Chmielowski 5ffc01db53 Fix types in check_password_hash 2016-11-15 10:02:21 +01:00
Paweł Chmielowski 717159a98f Make string args in http_api be list strings 2016-11-15 10:02:21 +01:00
Christophe Romain 309962fb8b Use p1_http from p1_utils 1.0.6 2016-11-14 16:52:03 +01:00
Evgeniy Khramtsov 3765210698 Fix IQ result processing 2016-11-13 16:56:05 +03:00
Evgeniy Khramtsov 13d5da4da6 Add some copyright notices 2016-11-13 16:46:04 +03:00
Evgeniy Khramtsov 75c15d3853 Make xref working again if elixir is disabled 2016-11-13 14:29:52 +03:00
Evgeniy Khramtsov ebefd0d8d6 Add more control for decoding IQ payloads 2016-11-13 14:17:21 +03:00
Evgeniy Khramtsov 7e9f1a6dc1 Don't auto-decode forwarded payload 2016-11-13 13:41:04 +03:00
Evgeniy Khramtsov 132033d01a Remove unused header file 2016-11-13 10:57:53 +03:00
Evgeniy Khramtsov 21d78ed7f4 Don't use jlib.hrl outside of jlib.erl 2016-11-13 10:56:36 +03:00
Evgeniy Khramtsov b8f22ff538 Deprecate most of the functions from jlib.erl 2016-11-13 10:44:53 +03:00
Evgeniy Khramtsov 534e73f732 Uncomment forgotten block of code 2016-11-12 14:51:43 +03:00
Evgeniy Khramtsov de81c50199 Revert "Support to provide password when subscribing to a room (#1306)"
This reverts commit 566ac872fe.
2016-11-12 14:47:29 +03:00
Evgeniy Khramtsov 5d552c8463 Merge branch 'master' into xml-ng 2016-11-12 14:41:37 +03:00
Evgeniy Khramtsov 78a44e0176 Merge branch 'master' into xml-ng
Conflicts:
	src/adhoc.erl
	src/cyrsasl_oauth.erl
	src/ejabberd_c2s.erl
	src/ejabberd_config.erl
	src/ejabberd_service.erl
	src/gen_mod.erl
	src/mod_admin_extra.erl
	src/mod_announce.erl
	src/mod_carboncopy.erl
	src/mod_client_state.erl
	src/mod_configure.erl
	src/mod_echo.erl
	src/mod_mam.erl
	src/mod_muc.erl
	src/mod_muc_room.erl
	src/mod_offline.erl
	src/mod_pubsub.erl
	src/mod_stats.erl
	src/node_flat_sql.erl
	src/randoms.erl
2016-11-12 13:27:15 +03:00
Badlop 566ac872fe Support to provide password when subscribing to a room (#1306) 2016-11-10 20:48:43 +01:00
Mickael Remond 42bede77a1 Merge branch 'master' of github.com:processone/ejabberd 2016-11-09 09:04:58 +01:00
Mickael Remond 35506f5470 Expose unregister API command 2016-11-09 09:04:52 +01:00
Evgeniy Khramtsov e841a6ec34 Add more tests for offline storage 2016-11-08 15:15:19 +03:00
Evgeniy Khramtsov e9dd3ffe9c Merge branch 'xml-ng' of github.com:processone/ejabberd into xml-ng 2016-11-07 10:11:40 +03:00
Evgeniy Khramtsov 56c91d3c58 Add roster tests 2016-11-07 10:10:57 +03:00
Badlop bd060bc1bb Support several groups separated by ; in add_rosteritem command 2016-11-04 18:45:27 +01:00
Badlop 49d3b7ec1d Throw error if room name or host has invalid characters (#1360) 2016-11-04 17:28:28 +01:00
Badlop 5e723bc90e Fix reading room jids from file for create and destroy_rooms_file commands 2016-11-04 16:54:31 +01:00
Badlop bab71f0832 Replace ctlscriptpath and produce ejabberd.service file (#434) 2016-11-04 16:35:59 +01:00
Paweł Chmielowski 62db030942 Merge mod_opt_type from db sub-modules to main module mod_opt_type 2016-11-04 12:58:08 +01:00
Holger Weiss 2a63d0e95a mod_mam: Use user JID for stanza ID 'by' attribute
Use the user (or room) JID instead of the server JID for the 'by'
attribute of <stanza-id/> and <archived/> tags.  That's what the
examples in XEP-0313 v0.2 and XEP-0359 v0.3.0 suggest.
2016-11-01 08:47:08 +01:00
Badlop 149cc9654f Append ; to privacy_list_data exporting lines (thanks to Marcio Luciano Donada) 2016-10-24 13:42:33 +02:00
Evgeniy Khramtsov 9d977e484a Use base64:mime_decode/1 for SASL packets
It will be now possible to accept SASL packets with only
single '=' character set as required by RFC6120
2016-10-22 13:09:11 +03:00
Evgeniy Khramtsov f6236d456d Add more tests for privacy lists and blocking command 2016-10-22 13:01:45 +03:00
badlop ed6bc9081b Merge pull request #1349 from prasadvaidya/master
Fix: Replace erlang function with fail-safe jlib function.
2016-10-21 13:32:29 +02:00
Prasad Vaidya cdafd3254b Rollback minor change, to avoid redundant use of fail-safe function 2016-10-21 15:02:39 +05:30
Prasad Vaidya 509776a0d1 Fix: Replace erlang function with fail-safe jlib function. 2016-10-21 13:57:47 +05:30
Badlop 2ab72bcd00 Nidx may be integer or binary, so use jlib:i2l instead
As reported in
https://www.ejabberd.im/forum/28580/erlang-function-integertobinary1-throwing-badargs-exception
2016-10-20 21:56:19 +02:00
Badlop 0212559ca7 If a participant can change subject, let asubscriber too (#1345) 2016-10-20 20:35:00 +02:00
Holger Weiss 1bdbe54442 Let systemd stop ejabberd gracefully
Make sure the "ExecStop" command line blocks until ejabberd is actually
stopped.  This prevents systemd from killing the ejabberd process(es)
immediately.

Also, let the "ExecStart" command line block until ejabberd's startup is
completed.  This makes sure that services which depend on ejabberd
aren't started up too early.
2016-10-20 00:27:50 +02:00
Holger Weiss a5e737157c Increase file descriptor limit in systemd unit
16,000 file descriptors will only suffice for small setups.
2016-10-20 00:12:02 +02:00
Holger Weiss 0a3fcc9ade Don't specify "ExecReload" command in systemd unit
The "reload_config" command doesn't work the way admins would typically
expect, so it shouldn't be exposed via systemd.  Those who understand
the behavior can execute the command using ejabberdctl.
2016-10-19 23:37:26 +02:00
Holger Weiss 7621564839 Let systemd restart ejabberd on failure
The "RestartSec=5" setting has no effect if "Restart" is not also
specified.
2016-10-19 23:35:22 +02:00
Holger Weiss 686305bb21 Use "Type=forking" in systemd unit
ejabberd is not a "oneshot" process.
2016-10-19 23:32:07 +02:00
Holger Weiss c3b62d2f75 Don't set "NoNewPrivileges" in systemd unit
The "NoNewPrivileges" setting breaks some PAM and extauth setups.

Fixes #1281.
2016-10-19 23:29:46 +02:00
Holger Weiss f56840a682 Don't let systemd hide /home and /tmp
Admins might expect ejabberd to be able to access data below /home or
/tmp.  For example, they might use those locations to dump/restore
Mnesia backups, or as a document root for mod_http_fileserver or
mod_http_upload.

Fixes #1297.
2016-10-19 23:11:26 +02:00
Christophe Romain 059a806bb0 Let mix be able to cope with configured deps 2016-10-19 13:57:19 +02:00
badlop 3ec68a4ecf Merge pull request #1343 from gardenia/mod_muc_configurable_max_discoitems
New option max_rooms_discoitems instead of constant (#1236)
2016-10-19 12:32:09 +02:00
colm 3b876875e9 mod_muc: made the constant MAX_ROOMS_DISCOITEMS configurable 2016-10-18 21:59:34 +01:00
Evgeniy Khramtsov d19552f464 Fix randoms:uniform/1 return
Make sure randoms:uniform/1 returns values from the same interval
as deprecated random:uniform/1
2016-10-18 08:35:47 +03:00
Evgeniy Khramtsov 4c5460f0bd Get rid of compile warnings for random/crypto modules on R19 2016-10-18 08:17:21 +03:00
badlop 90acec8a2b Merge pull request #1338 from marcphilipp/muc_invite_hook
Introduce muc_invite hook
2016-10-17 17:46:25 +02:00
Jerome Sautret 305d4c05dc Fix delete_old_messages command for SQL backends 2016-10-17 17:02:23 +02:00
Evgeniy Khramtsov 67720c7713 Add more MUC tests 2016-10-17 13:37:23 +03:00
Paweł Chmielowski fd6f0f94b5 Convert ejabberd_xmlrpc to new api_permissions 2016-10-14 13:55:50 +02:00
Marc Philipp a1faecc4c9 Introduce muc_invite hook
This adds a new hook that is triggered for each invite to an MUC room:

- muc_invite(RoomJID, RoomConfig, From, To, Reason) -> ok

where

- RoomJID = From = To = #jid (see jlib.h)
- RoomConfig = #config (see mod_muc_room.hrl)
- Reason = binary()
2016-10-14 12:52:59 +02:00
Holger Weiss d97e777c9b Always include <actions/> with ad-hoc responses
XEP-0050 says: "The result for each stage (other than the last) of a
command's execution SHOULD include an <actions/> element."  Some clients
insist on this.
2016-10-13 22:34:29 +02:00
Holger Weiss b693601dd1 Don't let MAM messages go into offline storage 2016-10-12 23:10:25 +02:00
badlop 4935ac8866 Merge pull request #1331 from weiss/send-message-omit-copies
Don't let "send_message" duplicate the message
2016-10-12 13:52:27 +02:00
Holger Weiss ead7e21037 Ignore offline sessions in statistics
Offline sessions should not be counted when reporting the number of
connected resources.

Apart from that, this number is now also reported when using a
non-default session management backend.
2016-10-11 22:20:22 +02:00
Christophe Romain 0f67a8f98b Update riakc to support r19 2016-10-10 10:57:04 +03:00
Evgeniy Khramtsov a915fd161e Create room on configuration request as per XEP-0045, 10.1.3 2016-10-10 10:51:39 +03:00
Holger Weiss dffcfe74d4 Don't let "send_message" duplicate the message
In the past, the "send_message" command sent a copy of the message to
each resource if the message was addressed to the bare JID of a local
online user.  When message carbons are enabled, this creates duplicates;
and with MAM enabled, each copy is archived.  Therefore, "send_message"
no longer creates copies of the message.
2016-10-10 00:17:17 +02:00
Uku Taht 5c48263579 Enable elixir when ejabberd used as a mix dependency 2016-10-07 10:47:20 +01:00
Evgeniy Khramtsov 6a3691ef7c Add xdata generator and make some code using it 2016-10-07 10:31:03 +03:00
Holger Weiss d701230555 Make map syntax compatible with Erlang/OTP 17 2016-10-07 00:36:47 +02:00
Holger Weiss e54ba3db5b XEP-0198: Cope with invalid 'from'/'to' attributes
Check whether the 'from' and 'to' attributes are valid before bouncing
or resending a stanza from the stream management queue.  They might be
invalid in certain corner cases.

Thanks to Evgeniy for spotting this.
2016-10-06 23:20:45 +02:00
Paweł Chmielowski 0ae84a646f Disable one test for now, we may change how this part is handled 2016-10-06 11:17:10 +02:00
Paweł Chmielowski b01fbfadf3 Use correct field for oauth scope 2016-10-06 11:03:26 +02:00
Paweł Chmielowski 438dbc8bda Make handling of oauth clauses be more consistent with other rules 2016-10-06 10:59:43 +02:00
Paweł Chmielowski 8accb8ee0c Use proper default value for api_permissions 2016-10-06 10:59:43 +02:00
Badlop a78c3422cd Fix typos in Czech translation (#1318) 2016-10-05 15:04:42 +02:00
Paweł Chmielowski 695d22ef95 Initialize ejabberd_access_permissions in elixir tests 2016-10-05 13:54:29 +02:00
Paweł Chmielowski 98e0123ca4 New api permissions framework 2016-10-05 13:21:11 +02:00
Holger Weiss 9cee3760db ejabberd_sm: Clean up old offline session entries
If the number of offline sessions exceeds the 'max_user_sessions' limit,
remove the oldest entry from the table.
2016-10-02 22:01:03 +02:00
Christophe Romain 1de0bb83a0 PubSub: creation jid must be bare jid 2016-09-30 07:51:33 +03:00
Christophe Romain 7566e267a7 PubSub: fix error type on item deletion with insufficient priviledge 2016-09-30 07:51:17 +03:00
Christophe Romain 15ebb79160 PubSub: creation jid must be bare jid 2016-09-29 16:10:11 +02:00
Christophe Romain 767dba8f3b PubSub: fix notification on subscription change 2016-09-29 12:00:59 +02:00
Christophe Romain a42bf67957 PubSub: fix error type on item deletion with insufficient priviledge 2016-09-29 11:20:56 +02:00
Badlop 6f538545b4 Fix 404 response formatting (thanks to Kaggggggga)(#1306) 2016-09-28 11:03:46 +02:00
Holger Weiss d4b4f35a0e ejabberd_http: Handle missing POST data gracefully
Return a "bad request" error instead of crashing if receiving POST/PUT
data fails.
2016-09-27 23:22:30 +02:00
Alexey Shchepin acab2270f1 Use inets instead of lhttpc in http_p1 2016-09-27 07:12:10 +03:00
Alexey Shchepin ac6f701033 Add http_p1.erl, rest.erl, and oauth2 ReST backend for tokens. 2016-09-27 05:57:14 +03:00
Evgeniy Khramtsov d327119cf7 Text legacy IQ handler support 2016-09-25 10:17:03 +03:00
Evgeniy Khramtsov 3112a7187f Test anonymous auth 2016-09-25 09:57:56 +03:00
Evgeniy Khramtsov 7100c67be6 Get rid of compile warnings in test suite 2016-09-25 08:38:41 +03:00
Evgeniy Khramtsov fa31e3ef23 Deprecate jlib:integer_to_binary/1 and jlib:binary_to_integer/1 2016-09-24 23:34:28 +03:00
Evgeniy Khramtsov 58969fb854 Improve namespace handling 2016-09-24 14:17:21 +03:00
Evgeniy Khramtsov 53209b9ab1 Add tests for s2s code 2016-09-23 12:30:33 +03:00
Evgeniy Khramtsov ceda073766 Add tests for external component 2016-09-21 10:45:11 +03:00
Evgeniy Khramtsov a4ec064455 Add more tests for C2S 2016-09-20 14:04:07 +03:00
Holger Weiss e7787e2f33 mod_carboncopy: Don't copy MUC PMs
Carbon copies of private MUC message are generally not desired,
especially not when multiple clients joined the room with the same nick.
In this case, the MUC service usually sends PMs to all joined resources
anyway, so carbon-copying those PMs would create duplicates.
2016-09-19 22:46:36 +02:00
Badlop 5bcfcf4c5e When getting list of subscribed rooms, check all including temporary ones (#1242) 2016-09-19 13:46:01 +02:00
Badlop 9fa92092bf Revert "Fix getting of subscribed rooms: consider also temporary ones (#1242)"
This reverts commit f2cc81dfea.
2016-09-19 12:54:19 +02:00
Badlop f2cc81dfea Fix getting of subscribed rooms: consider also temporary ones (#1242) 2016-09-19 12:35:53 +02:00
Badlop 8244c1fa8c Store the Allow Subscription room option (#1301) 2016-09-19 12:35:32 +02:00
Badlop ed62d705d8 Don't worry about storage_type of the Acl mnesia table (#1206) 2016-09-19 11:59:40 +02:00
Badlop da291d804c Use mnesia calls instead of ets for Acl and Access tables (#1206) 2016-09-19 11:56:22 +02:00
Christophe Romain 662b6f1020 Update riakc to support r19 2016-09-16 14:59:06 +02:00
Holger Weiss 27999a122f node_mb_sql: Add missing (SQL-specific) functions 2016-09-15 23:02:04 +02:00
Evgeniy Khramtsov 151668ac10 Fix dialyzer warnings for mod_mam 2016-09-13 16:56:34 +03:00
Evgeniy Khramtsov e987b88848 Make common tests working again 2016-09-13 12:30:05 +03:00
Evgeniy Khramtsov c29a48695d Rename #error{} record to #stanza_error{} 2016-09-08 17:08:48 +03:00
Evgeniy Khramtsov 5ec972b00f Improve pubsub code 2016-09-08 15:49:27 +03:00
Evgeniy Khramtsov 45eb49125b Rewrite mod_pubsub to use XML codec 2016-08-30 09:48:08 +03:00
Evgeniy Khramtsov 31faa4eb9a Add more type specs 2016-08-12 13:17:42 +03:00
Evgeniy Khramtsov 522a186a38 Improve some type specs 2016-08-09 10:56:32 +03:00
Evgeniy Khramtsov 4ff8d7918a Change code to reflect recent changes in fxml_gen 2016-08-05 08:41:08 +03:00
Evgeniy Khramtsov b487ccfb34 Delete mod_configure2
The module doesn't work properly for many years and nobody reported
this, which means nobody is using it. Also, mod_configure does
the same (and more) in a standard compliant way (XEP-0133).
2016-08-04 16:45:33 +03:00
Evgeniy Khramtsov ca217dc105 Rewrite HTTP Bind and Web Admin code to use XML generator 2016-08-04 13:41:38 +03:00
Evgeniy Khramtsov 8b81b9ecb1 Rewrite PIEFXIS code to use XML generator 2016-08-04 13:37:07 +03:00
Evgeniy Khramtsov cbdc106427 Rewrite jd2ejd to use XML generator 2016-08-04 12:34:12 +03:00
Evgeniy Khramtsov bc33a3873d Rewrite multicast code to use XML generator 2016-08-04 11:49:17 +03:00
Evgeniy Khramtsov abb4446b51 Fix calls to undefined functions 2016-08-03 21:00:22 +03:00
Evgeniy Khramtsov a417fbf45b Rewrite mod_irc to use XML generator 2016-08-03 20:57:05 +03:00
Evgeniy Khramtsov 5d06c6acbf Rewrite mod_configure to use XML generator 2016-08-03 10:34:54 +03:00
Evgeniy Khramtsov c0272ae766 Rewrite mod_stats to use XML generator 2016-07-31 14:51:16 +03:00
Evgeniy Khramtsov e258462b6b Improve vCard creation from LDAP result 2016-07-31 14:17:17 +03:00
Evgeniy Khramtsov 0bcbd12776 Rewrite mod_mix to use XML generator 2016-07-31 08:51:47 +03:00
Evgeniy Khramtsov eb1d385d4e Get rid of "jlib.hrl" dependency in mod_service_log 2016-07-30 18:42:17 +03:00
Evgeniy Khramtsov 5cd1cf5096 Get rid of "jlib.hrl" dependency in some modules 2016-07-30 18:37:57 +03:00
Evgeniy Khramtsov 792e6a7c1c Rewrite mod_http_upload to use XML generator 2016-07-30 17:48:52 +03:00
Evgeniy Khramtsov d2d3b961eb Rewrite mod_sic to use XML generator 2016-07-30 13:30:29 +03:00
Evgeniy Khramtsov f19d2fdcff Rewrite mod_shared_roster backends module to use XML generator 2016-07-30 08:39:30 +03:00
Evgeniy Khramtsov a093e9d441 Rewrite mod_shared_roster to use XML generator 2016-07-30 08:37:34 +03:00
Evgeniy Khramtsov 9bf1bac7df Rewrite mod_vcard_ldap to use XML generator 2016-07-29 17:39:13 +03:00
Evgeniy Khramtsov f91f2bc3d2 Rewrite several modules to use XML generator 2016-07-29 13:21:00 +03:00
Evgeniy Khramtsov b31ebd2ea0 Rewrite captcha to use XML generator 2016-07-28 15:10:41 +03:00
Evgeniy Khramtsov 96e912b09a Rewrite mod_register to use XML generator 2016-07-27 18:06:54 +03:00
Evgeniy Khramtsov 8275e95a16 Swap variables in their correct places 2016-07-27 18:06:34 +03:00
Evgeniy Khramtsov 1097d31d63 Fix type spec for set_from_to/3 2016-07-27 18:05:11 +03:00
Evgeniy Khramtsov c409ed2f2c Rewrite S2S and ejabberd_service code to use XML generator 2016-07-27 10:45:08 +03:00
Evgeniy Khramtsov 23858469b7 Get rid of "jlib.hrl" dependency in some files 2016-07-26 11:29:17 +03:00
Evgeniy Khramtsov c26d38a893 Remove jlib dependency from acl.erl 2016-07-26 10:01:59 +03:00
Evgeniy Khramtsov da310a5173 Rewrite mod_adhoc and mod_announce to use XML generator 2016-07-26 09:52:29 +03:00
Evgeniy Khramtsov 179fcd9521 Rewrite mod_mam and mod_muc to use XML generator 2016-07-25 13:50:30 +03:00
Evgeniy Khramtsov 5d90292849 Fix hooks de-registration 2016-07-19 15:33:17 +03:00
Evgeniy Khramtsov bc802a4049 Rewrite mod_blocking to use XML generator 2016-07-19 10:07:04 +03:00
Evgeniy Khramtsov a4a9dd7f03 Rewrite mod_offline to use XML generator 2016-07-19 07:56:14 +03:00
Evgeniy Khramtsov 9a8e197d7e Initial version based on XML generator 2016-07-18 15:01:32 +03:00
274 changed files with 26711 additions and 46672 deletions
+3
View File
@@ -0,0 +1,3 @@
.git
.win32
.examples
+1
View File
@@ -29,6 +29,7 @@
/doc/version.tex
/ebin/
/ejabberd.init
/ejabberd.service
/ejabberdctl.example
XmppAddr.hrl
/rel/ejabberd/
+1
View File
@@ -3,6 +3,7 @@ language: erlang
otp_release:
- 17.5
- 18.3
- 19.1
services:
- riak
+25
View File
@@ -0,0 +1,25 @@
FROM debian:jessie
MAINTAINER Rafael Römhild <rafael@roemhild.de>
ENV XMPP_DOMAIN=localhost \
EJABBERD_HOME=/opt/ejabberd \
PATH=/opt/ejabberd/bin:/usr/sbin:/usr/bin:/sbin:/bin \
LC_ALL=C.UTF-8 \
LANG=en_US.UTF-8 \
LANGUAGE=en_US.UTF-8
# bootstrap
COPY . /tmp/ejabberd
RUN /tmp/ejabberd/docker/bootstrap.sh
# Continue as user
USER ejabberd
# Set workdir to ejabberd root
WORKDIR /opt/ejabberd
VOLUME ["/opt/ejabberd/conf", "/opt/ejabberd/database", "/opt/ejabberd/ssl", "/opt/ejabberd/backup", "/opt/ejabberd/upload", "/opt/ejabberd/modules"]
EXPOSE 4560 5222 5269 5280 5443
ENTRYPOINT ["/opt/ejabberd/docker/start.sh"]
+17 -16
View File
@@ -108,10 +108,6 @@ edoc:
$(ERL) -noinput +B -eval \
'case edoc:application(ejabberd, ".", []) of ok -> halt(0); error -> halt(1) end.'
spec:
$(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \
'case fxml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.'
JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1))
VERSIONED_DEP=$(if $(DEP_$(1)_VERSION),$(DEP_$(1)_VERSION),$(1))
@@ -177,15 +173,15 @@ install: all copy-files
[ -f $(ETCDIR)/ejabberd.yml ] \
&& $(INSTALL) -b -m 640 $(G_USER) ejabberd.yml.example $(ETCDIR)/ejabberd.yml-new \
|| $(INSTALL) -b -m 640 $(G_USER) ejabberd.yml.example $(ETCDIR)/ejabberd.yml
$(SED) -e "s*{{rootdir}}*@prefix@*" \
-e "s*{{installuser}}*@INSTALLUSER@*" \
-e "s*{{bindir}}*@bindir@*" \
-e "s*{{libdir}}*@libdir@*" \
-e "s*{{sysconfdir}}*@sysconfdir@*" \
-e "s*{{localstatedir}}*@localstatedir@*" \
-e "s*{{docdir}}*@docdir@*" \
-e "s*{{erl}}*@ERL@*" \
-e "s*{{epmd}}*@EPMD@*" ejabberdctl.template \
$(SED) -e "s*{{rootdir}}*@prefix@*g" \
-e "s*{{installuser}}*@INSTALLUSER@*g" \
-e "s*{{bindir}}*@bindir@*g" \
-e "s*{{libdir}}*@libdir@*g" \
-e "s*{{sysconfdir}}*@sysconfdir@*g" \
-e "s*{{localstatedir}}*@localstatedir@*g" \
-e "s*{{docdir}}*@docdir@*g" \
-e "s*{{erl}}*@ERL@*g" \
-e "s*{{epmd}}*@EPMD@*g" ejabberdctl.template \
> ejabberdctl.example
[ -f $(ETCDIR)/ejabberdctl.cfg ] \
&& $(INSTALL) -b -m 640 $(G_USER) ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg-new \
@@ -202,11 +198,16 @@ install: all copy-files
[ -f deps/elixir/bin/mix ] && $(INSTALL) -m 550 $(G_USER) deps/elixir/bin/mix $(BINDIR)/mix || true
#
# Init script
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*" \
-e "s*@installuser@*$(INIT_USER)*" ejabberd.init.template \
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*g" \
-e "s*@installuser@*$(INIT_USER)*g" ejabberd.init.template \
> ejabberd.init
chmod 755 ejabberd.init
#
# Service script
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*g" ejabberd.service.template \
> ejabberd.service
chmod 644 ejabberd.service
#
# Spool directory
$(INSTALL) -d -m 750 $(O_USER) $(SPOOLDIR)
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(SPOOLDIR) >$(CHOWN_OUTPUT)
@@ -338,5 +339,5 @@ quicktest:
$(REBAR) skip_deps=true ct suites=elixir
.PHONY: src edoc dialyzer Makefile TAGS clean clean-rel distclean rel \
install uninstall uninstall-binary uninstall-all translations deps test spec \
install uninstall uninstall-binary uninstall-all translations deps test \
quicktest erlang_plt deps_plt ejabberd_plt
-301
View File
@@ -1,301 +0,0 @@
-- LDAPv3 ASN.1 specification, taken from RFC 2251
-- Lightweight-Directory-Access-Protocol-V3 DEFINITIONS
ELDAPv3 DEFINITIONS
IMPLICIT TAGS ::=
BEGIN
LDAPMessage ::= SEQUENCE {
messageID MessageID,
protocolOp CHOICE {
bindRequest BindRequest,
bindResponse BindResponse,
unbindRequest UnbindRequest,
searchRequest SearchRequest,
searchResEntry SearchResultEntry,
searchResDone SearchResultDone,
searchResRef SearchResultReference,
modifyRequest ModifyRequest,
modifyResponse ModifyResponse,
addRequest AddRequest,
addResponse AddResponse,
delRequest DelRequest,
delResponse DelResponse,
modDNRequest ModifyDNRequest,
modDNResponse ModifyDNResponse,
compareRequest CompareRequest,
compareResponse CompareResponse,
abandonRequest AbandonRequest,
extendedReq ExtendedRequest,
extendedResp ExtendedResponse },
controls [0] Controls OPTIONAL }
MessageID ::= INTEGER (0 .. maxInt)
maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
LDAPString ::= OCTET STRING
LDAPOID ::= OCTET STRING
LDAPDN ::= LDAPString
RelativeLDAPDN ::= LDAPString
AttributeType ::= LDAPString
AttributeDescription ::= LDAPString
-- Wahl, et. al. Standards Track [Page 44]
--
-- RFC 2251 LDAPv3 December 1997
AttributeDescriptionList ::= SEQUENCE OF
AttributeDescription
AttributeValue ::= OCTET STRING
AttributeValueAssertion ::= SEQUENCE {
attributeDesc AttributeDescription,
assertionValue AssertionValue }
AssertionValue ::= OCTET STRING
Attribute ::= SEQUENCE {
type AttributeDescription,
vals SET OF AttributeValue }
MatchingRuleId ::= LDAPString
LDAPResult ::= SEQUENCE {
resultCode ENUMERATED {
success (0),
operationsError (1),
protocolError (2),
timeLimitExceeded (3),
sizeLimitExceeded (4),
compareFalse (5),
compareTrue (6),
authMethodNotSupported (7),
strongAuthRequired (8),
-- 9 reserved --
referral (10), -- new
adminLimitExceeded (11), -- new
unavailableCriticalExtension (12), -- new
confidentialityRequired (13), -- new
saslBindInProgress (14), -- new
noSuchAttribute (16),
undefinedAttributeType (17),
inappropriateMatching (18),
constraintViolation (19),
attributeOrValueExists (20),
invalidAttributeSyntax (21),
-- 22-31 unused --
noSuchObject (32),
aliasProblem (33),
invalidDNSyntax (34),
-- 35 reserved for undefined isLeaf --
aliasDereferencingProblem (36),
-- 37-47 unused --
inappropriateAuthentication (48),
-- Wahl, et. al. Standards Track [Page 45]
--
-- RFC 2251 LDAPv3 December 1997
invalidCredentials (49),
insufficientAccessRights (50),
busy (51),
unavailable (52),
unwillingToPerform (53),
loopDetect (54),
-- 55-63 unused --
namingViolation (64),
objectClassViolation (65),
notAllowedOnNonLeaf (66),
notAllowedOnRDN (67),
entryAlreadyExists (68),
objectClassModsProhibited (69),
-- 70 reserved for CLDAP --
affectsMultipleDSAs (71), -- new
-- 72-79 unused --
other (80) },
-- 81-90 reserved for APIs --
matchedDN LDAPDN,
errorMessage LDAPString,
referral [3] Referral OPTIONAL }
Referral ::= SEQUENCE OF LDAPURL
LDAPURL ::= LDAPString -- limited to characters permitted in URLs
Controls ::= SEQUENCE OF Control
Control ::= SEQUENCE {
controlType LDAPOID,
criticality BOOLEAN DEFAULT FALSE,
controlValue OCTET STRING OPTIONAL }
BindRequest ::= [APPLICATION 0] SEQUENCE {
version INTEGER (1 .. 127),
name LDAPDN,
authentication AuthenticationChoice }
AuthenticationChoice ::= CHOICE {
simple [0] OCTET STRING,
-- 1 and 2 reserved
sasl [3] SaslCredentials }
SaslCredentials ::= SEQUENCE {
mechanism LDAPString,
credentials OCTET STRING OPTIONAL }
BindResponse ::= [APPLICATION 1] SEQUENCE {
-- Wahl, et. al. Standards Track [Page 46]
--
-- RFC 2251 LDAPv3 December 1997
COMPONENTS OF LDAPResult,
serverSaslCreds [7] OCTET STRING OPTIONAL }
UnbindRequest ::= [APPLICATION 2] NULL
SearchRequest ::= [APPLICATION 3] SEQUENCE {
baseObject LDAPDN,
scope ENUMERATED {
baseObject (0),
singleLevel (1),
wholeSubtree (2) },
derefAliases ENUMERATED {
neverDerefAliases (0),
derefInSearching (1),
derefFindingBaseObj (2),
derefAlways (3) },
sizeLimit INTEGER (0 .. maxInt),
timeLimit INTEGER (0 .. maxInt),
typesOnly BOOLEAN,
filter Filter,
attributes AttributeDescriptionList }
Filter ::= CHOICE {
and [0] SET OF Filter,
or [1] SET OF Filter,
not [2] Filter,
equalityMatch [3] AttributeValueAssertion,
substrings [4] SubstringFilter,
greaterOrEqual [5] AttributeValueAssertion,
lessOrEqual [6] AttributeValueAssertion,
present [7] AttributeDescription,
approxMatch [8] AttributeValueAssertion,
extensibleMatch [9] MatchingRuleAssertion }
SubstringFilter ::= SEQUENCE {
type AttributeDescription,
-- at least one must be present
substrings SEQUENCE OF CHOICE {
initial [0] LDAPString,
any [1] LDAPString,
final [2] LDAPString } }
MatchingRuleAssertion ::= SEQUENCE {
matchingRule [1] MatchingRuleId OPTIONAL,
type [2] AttributeDescription OPTIONAL,
matchValue [3] AssertionValue,
dnAttributes [4] BOOLEAN DEFAULT FALSE }
-- Wahl, et. al. Standards Track [Page 47]
--
-- RFC 2251 LDAPv3 December 1997
SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
objectName LDAPDN,
attributes PartialAttributeList }
PartialAttributeList ::= SEQUENCE OF SEQUENCE {
type AttributeDescription,
vals SET OF AttributeValue }
SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LDAPURL
SearchResultDone ::= [APPLICATION 5] LDAPResult
ModifyRequest ::= [APPLICATION 6] SEQUENCE {
object LDAPDN,
modification SEQUENCE OF SEQUENCE {
operation ENUMERATED {
add (0),
delete (1),
replace (2) },
modification AttributeTypeAndValues } }
AttributeTypeAndValues ::= SEQUENCE {
type AttributeDescription,
vals SET OF AttributeValue }
ModifyResponse ::= [APPLICATION 7] LDAPResult
AddRequest ::= [APPLICATION 8] SEQUENCE {
entry LDAPDN,
attributes AttributeList }
AttributeList ::= SEQUENCE OF SEQUENCE {
type AttributeDescription,
vals SET OF AttributeValue }
AddResponse ::= [APPLICATION 9] LDAPResult
DelRequest ::= [APPLICATION 10] LDAPDN
DelResponse ::= [APPLICATION 11] LDAPResult
ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
entry LDAPDN,
newrdn RelativeLDAPDN,
deleteoldrdn BOOLEAN,
newSuperior [0] LDAPDN OPTIONAL }
ModifyDNResponse ::= [APPLICATION 13] LDAPResult
-- Wahl, et. al. Standards Track [Page 48]
--
-- RFC 2251 LDAPv3 December 1997
CompareRequest ::= [APPLICATION 14] SEQUENCE {
entry LDAPDN,
ava AttributeValueAssertion }
CompareResponse ::= [APPLICATION 15] LDAPResult
AbandonRequest ::= [APPLICATION 16] MessageID
ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
requestName [0] LDAPOID,
requestValue [1] OCTET STRING OPTIONAL }
ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
COMPONENTS OF LDAPResult,
responseName [10] LDAPOID OPTIONAL,
response [11] OCTET STRING OPTIONAL }
passwdModifyOID LDAPOID ::= "1.3.6.1.4.1.4203.1.11.1"
PasswdModifyRequestValue ::= SEQUENCE {
userIdentity [0] OCTET STRING OPTIONAL,
oldPasswd [1] OCTET STRING OPTIONAL,
newPasswd [2] OCTET STRING OPTIONAL }
PasswdModifyResponseValue ::= SEQUENCE {
genPasswd [0] OCTET STRING OPTIONAL }
END
+360
View File
@@ -0,0 +1,360 @@
ejabberd container
- [Introduction](#introduction)
- [Quick Start](#quick-start)
- [Usage](#usage)
- [Persistence](#persistence)
- [SSL Certificates](#ssl-certificates)
- [Base Image](#base-image)
- [Ejabberd Configuration](#ejabberd-configuration)
- [Served Hostnames](#served-hostnames)
- [Authentication](#authentication)
- [Admins](#admins)
- [Users](#users)
- [SSL](#ssl)
- [Modules](#modules)
- [Logging](#logging)
- [Mount Configurations](#mount-configurations)
- [Erlang Configuration](#erlang-configuration)
- [Maintenance](#maintenance)
- [Register Users](#register-users)
- [Creating Backups](#creating-backups)
- [Restoring Backups](#restoring-backups)
- [Debug](#debug)
- [Erlang Shell](#erlang-shell)
- [System Shell](#system-shell)
- [System Commands](#system-commands)
- [Exposed Ports](#exposed-ports)
# Introduction
Dockerfile to build an [ejabberd](https://www.ejabberd.im/) container image.
Docker Tag Names are based on ejabberd versions in git [tags][]. The image tag `:latest` is based on the master branch.
[tags]: https://github.com/rroemhild/ejabberd/tags
# Quick Start
You can start of with the following container:
```bash
docker run -d \
--name "ejabberd" \
-p 5222:5222 \
-p 5269:5269 \
-p 5280:5280 \
-h 'xmpp.example.de' \
-e "XMPP_DOMAIN=example.de" \
-e "ERLANG_NODE=nodename" \
-e "EJABBERD_ADMINS=admin@example.de admin2@example.de" \
-e "EJABBERD_USERS=admin@example.de:password1234 admin2@example.de" \
-e "TZ=Europe/Berlin" \
rroemhild/ejabberd
```
# Usage
## Persistence
For storage of the application data, you can mount volumes at
* `/opt/ejabberd/ssl`
* `/opt/ejabberd/backup`
* `/opt/ejabberd/upload`
* `/opt/ejabberd/database`
or use a data container
```bash
docker create --name ejabberd-data rroemhild/ejabberd-data
docker run -d --name ejabberd --volumes-from processone-data rroemhild/ejabberd
```
## SSL Certificates
TLS is enabled by default and the run script will auto-generate two snake-oil certificates during boot if you don't provide your SSL certificates.
To use your own certificates, there are two options.
1. Mount the volume `/opt/ejabberd/ssl` to a local directory with the `.pem` files:
* /tmp/ssl/host.pem (SERVER_HOSTNAME)
* /tmp/ssl/xmpp_domain.pem (XMPP_DOMAIN)
Make sure that the certificate and private key are in one `.pem` file. If one file is missing it will be auto-generated. I.e. you can provide your certificate for your **XMMP_DOMAIN** and use a snake-oil certificate for the `SERVER_HOSTNAME`.
2. Specify the certificates via environment variables: **EJABBERD_SSLCERT_HOST** and **EJABBERD_SSLCERT_EXAMPLE_COM**. For the
domain certificates, make sure you match the domain names given in **XMPP_DOMAIN** and replace dots and dashes with underscore.
## Base Image
Build your own ejabberd container image and add your config templates, certificates or [extend](#cluster-example) it for your needs.
```
FROM rroemhild/ejabberd
ADD ./ejabberd.yml.tpl /opt/ejabberd/conf/ejabberd.yml.tpl
ADD ./ejabberdctl.cfg.tpl /opt/ejabberd/conf/ejabberdctl.cfg.tpl
ADD ./example.com.pem /opt/ejabberd/ssl/example.com.pem
```
If you need root privileges switch to `USER root` and go back to `USER ejabberd` when you're done.
# Ejabberd Configuration
You can additionally provide extra runtime configuration in a downstream image by replacing the config template `ejabberd.yml.tpl` with one based on this image's template and include extra interpolation of environment variables. The template is parsed by Jinja2 with the runtime environment (equivalent to Python's `os.environ` available as `env`).
## Served Hostnames
By default the container will serve the XMPP domain `localhost`. In order to serve a different domain at runtime, provide the **XMPP_DOMAIN** variable with a domain name. You can add more domains separated with whitespace.
```
XMPP_DOMAIN=example.ninja xyz.io test.com
```
## Authentication
Authentication methods can be set with the **EJABBERD_AUTH_METHOD** environment variable. The default authentication mode is `internal`.
Supported authentication methods:
* anonymous
* internal
* external
* ldap
Internal and anonymous authentication example:
```
EJABBERD_AUTH_METHOD=internal anonymous
```
[External authentication](http://docs.ejabberd.im/admin/guide/configuration/#external-script) example:
```
EJABBERD_AUTH_METHOD=external
EJABBERD_EXTAUTH_PROGRAM="/opt/ejabberd/scripts/authenticate-user.sh"
EJABBERD_EXTAUTH_INSTANCES=3
EJABBERD_EXTAUTH_CACHE=600
```
**EJABBERD_EXTAUTH_INSTANCES** must be an integer with a minimum value of 1. **EJABBERD_EXTAUTH_CACHE** can be set to "false" or an integer value representing cache time in seconds. Note that caching should not be enabled if internal auth is also enabled.
### MySQL Authentication
Set `EJABBERD_AUTH_METHOD=external` and `EJABBERD_EXTAUTH_PROGRAM=/opt/ejabberd/scripts/lib/auth_mysql.py` to enable MySQL authentication. Use the following environment variables to configure the database connection and the layout of the database. Password changing, registration, and unregistration are optional features and are enabled only if the respective queries are provided.
- **AUTH_MYSQL_HOST**: The MySQL host
- **AUTH_MYSQL_USER**: Username to connect to the MySQL host
- **AUTH_MYSQL_PASSWORD**: Password to connect to the MySQL host
- **AUTH_MYSQL_DATABASE**: Database name where to find the user information
- **AUTH_MYSQL_HASHALG**: Format of the password in the database. Default is cleartext. Options are `crypt`, `md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`. `crypt` is recommended, as it is salted. When setting the password, `crypt` uses SHA-512 (prefix `$6$`).
- **AUTH_MYSQL_QUERY_GETPASS**: Get the password for a user. Use the placeholders `%(user)s`, `%(host)s`. Example: `SELECT password FROM users WHERE username = CONCAT(%(user)s, '@', %(host)s)`
- **AUTH_MYSQL_QUERY_SETPASS**: Update the password for a user. Leave empty to disable. Placeholder `%(password)s` contains the hashed password. Example: `UPDATE users SET password = %(password)s WHERE username = CONCAT(%(user)s, '@', %(host)s)`
- **AUTH_MYSQL_QUERY_REGISTER**: Register a new user. Leave empty to disable. Example: `INSERT INTO users ( username, password ) VALUES ( CONCAT(%(user)s, '@', %(host)s), %(password)s )`
- **AUTH_MYSQL_QUERY_UNREGISTER**: Removes a user. Leave empty to disable. Example: `DELETE FROM users WHERE username = CONCAT(%(user)s, '@', %(host)s)`
Note that the MySQL authentication script writes a debug log into the file `/var/log/ejabberd/extauth.log`. To get its content, execute the following command:
```bash
docker exec -ti ejabberd tail -n50 -f /var/log/ejabberd/extauth.log
```
To find out more about the mysql authentication script, check out the [ejabberd-auth-mysql](https://github.com/rankenstein/ejabberd-auth-mysql) repository.
### LDAP Auth
Full documentation http://docs.ejabberd.im/admin/guide/configuration/#ldap.
Connection
- **EJABBERD_LDAP_SERVERS**: List of IP addresses or DNS names of your LDAP servers. This option is required.
- **EJABBERD_LDAP_ENCRYPT**: The value `tls` enables encryption by using LDAP over SSL. The default value is: `none`.
- **EJABBERD_LDAP_TLS_VERIFY**: `false|soft|hard` This option specifies whether to verify LDAP server certificate or not when TLS is enabled. The default is `false` which means no checks are performed.
- **EJABBERD_LDAP_TLS_CACERTFILE**: Path to file containing PEM encoded CA certificates.
- **EJABBERD_LDAP_TLS_DEPTH**: Specifies the maximum verification depth when TLS verification is enabled. The default value is 1.
- **EJABBERD_LDAP_PORT**: The default port is `389` if encryption is disabled; and `636` if encryption is enabled.
- **EJABBERD_LDAP_ROOTDN**: Bind DN. The default value is "" which means anonymous connection.
- **EJABBERD_LDAP_PASSWORD**: Bind password. The default value is "".
- **EJABBERD_LDAP_DEREF_ALIASES**: `never|always|finding|searching`
Whether or not to dereference aliases. The default is `never`.
Authentication
- **EJABBERD_LDAP_BASE**: LDAP base directory which stores users accounts. This option is required.
- **EJABBERD_LDAP_UIDS**: `ldap_uidattr:ldap_uidattr_format` The default attributes are `uid:%u`.
- **EJABBERD_LDAP_FILTER**: RFC 4515 LDAP filter. The default Filter value is undefined.
- **EJABBERD_LDAP_DN_FILTER**: `{ Filter: FilterAttrs }` This filter is applied on the results returned by the main filter. By default ldap_dn_filter is undefined.
## Admins
Set one or more admin user (seperated by whitespace) with the **EJABBERD_ADMINS** environment variable. You can register admin users with the **EJABBERD_USERS** environment variable during container startup, use you favorite XMPP client or the `ejabberdctl` command line utility.
```
EJABBERD_ADMINS=admin@example.ninja
```
## Users
Automatically register users during container startup. Uses random password if you don't provide a password for the user. Format is `JID:PASSWORD`. Register more users separated with whitespace.
Register the admin user from **EJABBERD_ADMINS** with a give password:
```
EJABBERD_USERS=admin@example.ninja:password1234
```
Or without a random password printed to stdout (check container logs):
```
EJABBERD_USERS=admin@example.ninja
```
Register more than one user:
```
EJABBERD_USERS=admin@example.ninja:password1234 user1@test.com user1@xyz.io
```
## SSL
- **EJABBERD_SSLCERT_HOST**: SSL Certificate for the hostname.
- **EJABBERD_SSLCERT_EXAMPLE_COM**: SSL Certificates for XMPP domains.
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Default: `true`.
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Default: `true`.
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Default: `true`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Default: `false`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Default: `true`.
- **EJABBERD_CIPHERS**: Cipher suite. Default: `HIGH:!aNULL:!3DES`.
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Default: `false`.
## Modules
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Default: `false`.
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Default: `false`.
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Default: `true`.
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Default: `true`.
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Default: `true`.
## Logging
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Default: `4` (Info).
```
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
```
## Mount Configurations
If you prefer to use your own configuration files and avoid passing docker environment variables (```-e```), you can do so by mounting a host directory.
Pass in an additional ```-v``` to the ```docker run``` command, like so:
```
docker run -d \
--name "ejabberd" \
-p 5222:5222 \
-p 5269:5269 \
-p 5280:5280 \
-h 'xmpp.example.de' \
-v /<host_path>/conf:/opt/ejabberd/conf \
rroemhild/ejabberd
```
Your ```/<host_path>/conf``` folder should look like so:
```
/<host_path>/conf/
├── ejabberdctl.cfg
├── ejabberd.yml
└── inetrc
```
Example configuration files can be downloaded from the ejabberd [github](https://github.com/rroemhild/ejabberd) page.
When these files exist in ```/opt/ejabberd/conf```, the run script will ignore the configuration templates.
## Erlang Configuration
With the following environment variables you can configure options that are passed by ejabberdctl to the erlang runtime system when starting ejabberd.
- **POLL**: Set to `false` to disable Kernel polling. Default: `true`.
- **SMP**: SMP support `enable`, `auto`, `disable`. Default: `auto`.
- **ERL_MAX_PORTS**: Maximum number of simultaneously open Erlang ports. Default: `32000`.
- **FIREWALL_WINDOW**: Range of allowed ports to pass through a firewall. Default: `not defined`.
- **INET_DIST_INTERFACE**: IP address where this Erlang node listens other nodes. Default: `0.0.0.0`.
- **ERL_EPMD_ADDRESS**: IP addresses where epmd listens for connections. Default: `0.0.0.0`.
- **ERL_PROCESSES**: Maximum number of Erlang processes. Default: `250000`.
- **ERL_MAX_ETS_TABLES**: Maximum number of Erlang processes. Default: `1400`.
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd. Default: `-noshell`
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `nodename` lets erlang add the hostname. Default: `ejabberd@localhost`.
- **EJABBERD_CONFIG_PATH**: ejabberd configuration file. Default: `/opt/ejabberd/conf/ejabberd.yml`.
- **CONTRIB_MODULES_PATH**: contributed ejabberd modules path. Default: `/opt/ejabberd/modules`.
- **CONTRIB_MODULES_CONF_DIR**: configuration directory for contributed modules. Default: `/opt/ejabberd/modules/conf`.
- **ERLANG_COOKIE**: Set erlang cookie. Default is to auto-generated cookie.
# Maintenance
The `ejabberdctl` command is in the search path and can be run by:
```bash
docker exec CONTAINER ejabberdctl help
```
## Register Users
```bash
docker exec CONTAINER ejabberdctl register user XMPP_DOMAIN PASSWORD
```
## Creating Backups
Create a backupfile with ejabberdctl and copy the file from the container to localhost
```bash
docker exec CONTAINER ejabberdctl backup /opt/ejabberd/backup/ejabberd.backup
docker cp CONTAINER:/opt/ejabberd/backup/ejabberd.backup /tmp/ejabberd.backup
```
## Restoring Backups
Copy the backupfile from localhost to the running container and restore with ejabberdctl
```bash
docker cp /tmp/ejabberd.backup CONTAINER:/opt/ejabberd/backup/ejabberd.backup
docker exec CONTAINER ejabberdctl restore /opt/ejabberd/backup/ejabberd.backup
```
# Debug
## Erlang Shell
Set `-i` and `-t` option and append `live` to get an interactive erlang shell:
```bash
docker run -i -t -P rroemhild/ejabberd live
```
You can terminate the erlang shell with `q().`.
## System Shell
```bash
docker run -i -t rroemhild/ejabberd shell
```
## System Commands
```bash
docker run -i -t rroemhild/ejabberd env
```
# Exposed Ports
* 4560 (XMLRPC)
* 5222 (Client 2 Server)
* 5269 (Server 2 Server)
* 5280 (HTTP admin/websocket/http-bind)
* 5443 (HTTP Upload)
+75
View File
@@ -0,0 +1,75 @@
#!/bin/sh
set -ex
export DEBIAN_FRONTEND="noninteractive"
readonly buildDeps='
git-core
build-essential
automake
libssl-dev
zlib1g-dev
libexpat-dev
libyaml-dev
libsqlite3-dev
erlang-src erlang-dev'
readonly requiredAptPackages='
locales
ldnsutils
python2.7
python-jinja2
ca-certificates
libyaml-0-2
erlang-base erlang-snmp erlang-ssl erlang-ssh erlang-webtool
erlang-tools erlang-xmerl erlang-corba erlang-diameter erlang-eldap
erlang-eunit erlang-ic erlang-odbc erlang-os-mon
erlang-parsetools erlang-percept erlang-typer
python-mysqldb
imagemagick'
apt-key adv \
--keyserver keys.gnupg.net \
--recv-keys 434975BD900CCBE4F7EE1B1ED208507CA14F4FCA
apt-get update
apt-get install -y $buildDeps $requiredAptPackages --no-install-recommends
dpkg-reconfigure locales && locale-gen C.UTF-8
/usr/sbin/update-locale LANG=C.UTF-8
echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen
locale-gen
# add ejabberd user
useradd --home $EJABBERD_HOME -M --system ejabberd
mkdir $EJABBERD_HOME
cd /tmp/ejabberd
chmod +x ./autogen.sh
./autogen.sh
./configure --enable-user=ejabberd \
--enable-all \
--disable-tools \
--disable-pam
make debug=$EJABBERD_DEBUG_MODE
make install
cd $EJABBERD_HOME
mkdir -p logs ssl backup upload module_source modules/conf
mv /tmp/ejabberd/docker $EJABBERD_HOME
# Move config to homedir
mv /etc/ejabberd conf
ln -s $EJABBERD_HOME/conf /etc/ejabberd
# rename original configs
mv conf/ejabberd.yml conf/ejabberd.yml.orig
mv conf/ejabberdctl.cfg conf/ejabberdctl.cfg.orig
# clean up
rm -rf /tmp/ejabberd
rm -rf /var/lib/apt/lists/*
apt-get purge -y --auto-remove $buildDeps
# change owner for ejabberd home
chown -R ejabberd $EJABBERD_HOME
+434
View File
@@ -0,0 +1,434 @@
###
### 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/
### =======
### LOGGING
loglevel: {{ env['EJABBERD_LOGLEVEL'] or 4 }}
log_rotate_size: 10485760
log_rotate_count: 0
log_rate_limit: 100
## watchdog_admins:
## - "bob@example.com"
### ================
### SERVED HOSTNAMES
hosts:
{%- for xmpp_domain in env['XMPP_DOMAIN'].split() %}
- "{{ xmpp_domain }}"
{%- endfor %}
##
## 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:
-
port: 5222
module: ejabberd_c2s
{%- if env['EJABBERD_STARTTLS'] == "true" %}
starttls_required: true
{%- endif %}
protocol_options:
- "no_sslv3"
{%- if env.get('EJABBERD_PROTOCOL_OPTIONS_TLSV1', "false") == "false" %}
- "no_tlsv1"
{%- endif %}
{%- if env.get('EJABBERD_PROTOCOL_OPTIONS_TLSV1_1', "true") == "false" %}
- "no_tlsv1_1"
{%- endif %}
max_stanza_size: 65536
shaper: c2s_shaper
access: c2s
ciphers: "{{ env.get('EJABBERD_CIPHERS', 'HIGH:!aNULL:!3DES') }}"
{%- if env.get('EJABBERD_DHPARAM', false) == "true" %}
dhfile: "/opt/ejabberd/ssl/dh.pem"
{%- endif %}
-
port: 5269
module: ejabberd_s2s_in
-
port: 4560
module: ejabberd_xmlrpc
access_commands:
configure:
all: []
-
port: 5280
module: ejabberd_http
request_handlers:
"/websocket": ejabberd_http_ws
## "/pub/archive": mod_http_fileserver
web_admin: true
http_bind: true
## register: true
captcha: true
{%- if env['EJABBERD_HTTPS'] == "true" %}
tls: true
certfile: "/opt/ejabberd/ssl/host.pem"
{% endif %}
-
port: 5443
module: ejabberd_http
request_handlers:
"": mod_http_upload
{%- if env['EJABBERD_HTTPS'] == "true" %}
tls: true
certfile: "/opt/ejabberd/ssl/host.pem"
{% endif %}
### SERVER TO SERVER
### ================
{%- if env['EJABBERD_S2S_SSL'] == "true" %}
s2s_use_starttls: required
s2s_certfile: "/opt/ejabberd/ssl/host.pem"
s2s_protocol_options:
- "no_sslv3"
{%- if env.get('EJABBERD_PROTOCOL_OPTIONS_TLSV1', "false") == "false" %}
- "no_tlsv1"
{%- endif %}
{%- if env.get('EJABBERD_PROTOCOL_OPTIONS_TLSV1_1', "true") == "false" %}
- "no_tlsv1_1"
{%- endif %}
s2s_ciphers: "{{ env.get('EJABBERD_CIPHERS', 'HIGH:!aNULL:!3DES') }}"
{%- if env.get('EJABBERD_DHPARAM', false) == "true" %}
s2s_dhfile: "/opt/ejabberd/ssl/dh.pem"
{%- endif %}
{% endif %}
### ==============
### AUTHENTICATION
auth_method:
{%- for auth_method in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
- {{ auth_method }}
{%- endfor %}
{%- if 'anonymous' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
anonymous_protocol: login_anon
allow_multiple_connections: true
{%- endif %}
## LDAP authentication
{%- if 'ldap' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
ldap_servers:
{%- for ldap_server in env.get('EJABBERD_LDAP_SERVERS', 'internal').split() %}
- "{{ ldap_server }}"
{%- endfor %}
ldap_encrypt: {{ env.get('EJABBERD_LDAP_ENCRYPT', 'none') }}
ldap_tls_verify: {{ env.get('EJABBERD_LDAP_TLS_VERIFY', 'false') }}
{%- if env['EJABBERD_LDAP_TLS_CACERTFILE'] %}
ldap_tls_cacertfile: "{{ env['EJABBERD_LDAP_TLS_CACERTFILE'] }}"
{%- endif %}
ldap_tls_depth: {{ env.get('EJABBERD_LDAP_TLS_DEPTH', 1) }}
{%- if env['EJABBERD_LDAP_PORT'] %}
ldap_port: {{ env['EJABBERD_LDAP_PORT'] }}
{%- endif %}
{%- if env['EJABBERD_LDAP_ROOTDN'] %}
ldap_rootdn: "{{ env['EJABBERD_LDAP_ROOTDN'] }}"
{%- endif %}
{%- if env['EJABBERD_LDAP_PASSWORD'] %}
ldap_password: "{{ env['EJABBERD_LDAP_PASSWORD'] }}"
{%- endif %}
ldap_deref_aliases: {{ env.get('EJABBERD_LDAP_DEREF_ALIASES', 'never') }}
ldap_base: "{{ env['EJABBERD_LDAP_BASE'] }}"
{%- if env['EJABBERD_LDAP_UIDS'] %}
ldap_uids:
{%- for ldap_uid in env['EJABBERD_LDAP_UIDS'].split() %}
"{{ ldap_uid.split(':')[0] }}": "{{ ldap_uid.split(':')[1] }}"
{%- endfor %}
{%- endif %}
{%- if env['EJABBERD_LDAP_FILTER'] %}
ldap_filter: "{{ env['EJABBERD_LDAP_FILTER'] }}"
{%- endif %}
{%- if env['EJABBERD_LDAP_DN_FILTER'] %}
ldap_dn_filter:
{%- for dn_filter in env['EJABBERD_LDAP_DN_FILTER'].split() %}
"{{ dn_filter.split(':')[0] }}": ["{{ dn_filter.split(':')[1] }}"]
{%- endfor %}
{%- endif %}
{%- endif %}
{%- if 'external' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
{%- if env['EJABBERD_EXTAUTH_PROGRAM'] %}
extauth_program: "{{ env['EJABBERD_EXTAUTH_PROGRAM'] }}"
{%- endif %}
{%- if env['EJABBERD_EXTAUTH_INSTANCES'] %}
extauth_instances: {{ env['EJABBERD_EXTAUTH_INSTANCES'] }}
{%- endif %}
{%- if 'internal' in env.get('EJABBERD_AUTH_METHOD').split() %}
extauth_cache: false
{%- elif env['EJABBERD_EXTAUTH_CACHE'] %}
extauth_cache: {{ env['EJABBERD_EXTAUTH_CACHE'] }}
{%- endif %}
{% endif %}
### ===============
### TRAFFIC SHAPERS
shaper:
normal: 1000
fast: 50000
max_fsm_queue: 1000
### ====================
### ACCESS CONTROL LISTS
acl:
admin:
user:
{%- if env['EJABBERD_ADMINS'] %}
{%- for admin in env['EJABBERD_ADMINS'].split() %}
- "{{ admin.split('@')[0] }}": "{{ admin.split('@')[1] }}"
{%- endfor %}
{%- else %}
- "admin": "{{ env['XMPP_DOMAIN'].split()[0] }}"
{%- endif %}
local:
user_regexp: ""
### ============
### 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, or only admins can create rooms, depending on environment variable:
muc_create:
{%- if env['EJABBERD_MUC_CREATE_ADMIN_ONLY'] == "true" %}
admin: allow
{% else %}
local: allow
{% endif %}
## 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.
register:
{%- if env['EJABBERD_REGISTER_ADMIN_ONLY'] == "true" %}
all: deny
admin: allow
{% else %}
all: allow
{% endif %}
## Only allow to register from localhost
trusted_network:
loopback: allow
soft_upload_quota:
all: 400 # MiB
hard_upload_quota:
all: 500 # MiB
language: "en"
### =======
### MODULES
modules:
mod_adhoc: {}
{%- if env['EJABBERD_MOD_ADMIN_EXTRA'] == "true" %}
mod_admin_extra: {}
{% endif %}
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
history_size: 50
default_room_options:
persistent: true
{%- if env['EJABBERD_MOD_MUC_ADMIN'] == "true" %}
mod_muc_admin: {}
{% endif %}
## mod_muc_log: {}
## mod_multicast: {}
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.
##
## Only clients in the server machine can register accounts
##
{%- if env['EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY'] == "true" %}
ip_access: trusted_network
{% endif %}
access: register
mod_roster: {}
mod_shared_roster: {}
mod_stats: {}
mod_time: {}
mod_vcard: {}
{% if env.get('EJABBERD_MOD_VERSION', true) == "true" %}
mod_version: {}
{% endif %}
mod_http_upload:
docroot: "/opt/ejabberd/upload"
{%- if env['EJABBERD_HTTPS'] == "true" %}
put_url: "https://@HOST@:5443"
{%- else %}
put_url: "http://@HOST@:5443"
{% endif %}
mod_http_upload_quota:
max_days: 10
### ============
### HOST CONFIG
host_config:
{%- for xmpp_domain in env['XMPP_DOMAIN'].split() %}
"{{ xmpp_domain }}":
domain_certfile: "/opt/ejabberd/ssl/{{ xmpp_domain }}.pem"
{%- endfor %}
{%- if env['EJABBERD_CONFIGURE_ODBC'] == "true" %}
### ====================
### ODBC DATABASE CONFIG
odbc_type: {{ env['EJABBERD_ODBC_TYPE'] }}
odbc_server: {{ env['EJABBERD_ODBC_SERVER'] }}
odbc_database: {{ env['EJABBERD_ODBC_DATABASE'] }}
odbc_username: {{ env['EJABBERD_ODBC_USERNAME'] }}
odbc_password: {{ env['EJABBERD_ODBC_PASSWORD'] }}
odbc_pool_size: {{ env['EJABBERD_ODBC_POOL_SIZE'] }}
{% endif %}
{%- if env['EJABBERD_DEFAULT_DB'] is defined %}
default_db: {{ env['EJABBERD_DEFAULT_DB'] }}
{% endif %}
### =====================
### SESSION MANAGEMENT DB
sm_db_type: {{ env['EJABBERD_SESSION_DB'] or "mnesia" }}
{%- if env['EJABBERD_CONFIGURE_REDIS'] == "true" %}
### ====================
### REDIS DATABASE CONFIG
redis_server: {{ env['EJABBERD_REDIS_SERVER'] or "localhost" }}
redis_port: {{ env['EJABBERD_REDIS_PORT'] or 6379 }}
{%- if env['EJABBERD_REDIS_PASSWORD'] is defined %}
redis_password: {{ env['EJABBERD_REDIS_PASSWORD'] }}
{% endif %}
redis_db: {{ env['EJABBERD_REDIS_DB'] or 0}}
redis_reconnect_timeout: {{ env['EJABBERD_REDIS_RECONNECT_TIMEOUT'] or 1 }}
redis_connect_timeout: {{ env['EJABBERD_REDIS_CONNECT_TIMEOUT'] or 1 }}
{% endif %}
+199
View File
@@ -0,0 +1,199 @@
#
# In this file you can configure options that are passed by ejabberdctl
# to the erlang runtime system when starting ejabberd
#
#' POLL: Kernel polling ([true|false])
#
# The kernel polling option requires support in the kernel.
# Additionally, you need to enable this feature while compiling Erlang.
#
# Default: true
#
POLL={{ env['POLL'] or 'true' }}
#.
#' SMP: SMP support ([enable|auto|disable])
#
# Explanation in Erlang/OTP documentation:
# enable: starts the Erlang runtime system with SMP support enabled.
# This may fail if no runtime system with SMP support is available.
# auto: starts the Erlang runtime system with SMP support enabled if it
# is available and more than one logical processor are detected.
# disable: starts a runtime system without SMP support.
#
# Default: auto
#
SMP={{ env['SMP'] or 'auto' }}
#.
#' ERL_MAX_PORTS: Maximum number of simultaneously open Erlang ports
#
# ejabberd consumes two or three ports for every connection, either
# from a client or from another Jabber server. So take this into
# account when setting this limit.
#
# Default: 32000
# Maximum: 268435456
#
ERL_MAX_PORTS={{ env['ERL_MAX_PORTS'] or '32000' }}
#.
#' FIREWALL_WINDOW: Range of allowed ports to pass through a firewall
#
# If Ejabberd is configured to run in cluster, and a firewall is blocking ports,
# it's possible to make Erlang use a defined range of port (instead of dynamic
# ports) for node communication.
#
# Default: not defined
# Example: 4200-4210
#
{%- if env['FIREWALL_WINDOW'] %}
FIREWALL_WINDOW={{ env['FIREWALL_WINDOW'] }}
{%- endif %}
#.
#' INET_DIST_INTERFACE: IP address where this Erlang node listens other nodes
#
# This communication is used by ejabberdctl command line tool,
# and in a cluster of several ejabberd nodes.
#
# Default: 0.0.0.0
#
{%- if env['INET_DIST_INTERFACE'] %}
INET_DIST_INTERFACE={{ env['INET_DIST_INTERFACE'] }}
{%- endif %}
#.
#' ERL_EPMD_ADDRESS: IP addresses where epmd listens for connections
#
# IMPORTANT: This option works only in Erlang/OTP R14B03 and newer.
#
# This environment variable may be set to a comma-separated
# list of IP addresses, in which case the epmd daemon
# will listen only on the specified address(es) and on the
# loopback address (which is implicitly added to the list if it
# has not been specified). The default behaviour is to listen on
# all available IP addresses.
#
# Default: 0.0.0.0
#
{%- if env['ERL_EPMD_ADDRESS'] %}
ERL_EPMD_ADDRESS={{ env['ERL_EPMD_ADDRESS'] }}
{%- endif %}
#.
#' ERL_PROCESSES: Maximum number of Erlang processes
#
# Erlang consumes a lot of lightweight processes. If there is a lot of activity
# on ejabberd so that the maximum number of processes is reached, people will
# experience greater latency times. As these processes are implemented in
# Erlang, and therefore not related to the operating system processes, you do
# not have to worry about allowing a huge number of them.
#
# Default: 250000
# Maximum: 268435456
#
ERL_PROCESSES={{ env['ERL_PROCESSES'] or '250000' }}
#.
#' ERL_MAX_ETS_TABLES: Maximum number of ETS and Mnesia tables
#
# The number of concurrent ETS and Mnesia tables is limited. When the limit is
# reached, errors will appear in the logs:
# ** Too many db tables **
# You can safely increase this limit when starting ejabberd. It impacts memory
# consumption but the difference will be quite small.
#
# Default: 1400
#
ERL_MAX_ETS_TABLES={{ env['ERL_MAX_ETS_TABLES'] or '1400' }}
#.
#' ERL_OPTIONS: Additional Erlang options
#
# The next variable allows to specify additional options passed to erlang while
# starting ejabberd. Some useful options are -noshell, -detached, -heart. When
# ejabberd is started from an init.d script options -noshell and -detached are
# added implicitly. See erl(1) for more info.
#
# It might be useful to add "-pa /usr/local/lib/ejabberd/ebin" if you
# want to add local modules in this path.
#
# Default: ""
#
ERL_OPTIONS="{{ env['ERL_OPTIONS'] or '-noshell' }}"
#.
#' ERLANG_NODE: Erlang node name
#
# The next variable allows to explicitly specify erlang node for ejabberd
# It can be given in different formats:
# ERLANG_NODE=ejabberd
# Lets erlang add hostname to the node (ejabberd uses short name in this case)
# ERLANG_NODE=ejabberd@hostname
# Erlang uses node name as is (so make sure that hostname is a real
# machine hostname or you'll not be able to control ejabberd)
# ERLANG_NODE=ejabberd@hostname.domainname
# The same as previous, but erlang will use long hostname
# (see erl (1) manual for details)
#
# Default: ejabberd@localhost
#
ERLANG_NODE={{ env['ERLANG_NODE'] or 'ejabberd@localhost' }}
#.
#' EJABBERD_PID_PATH: ejabberd PID file
#
# Indicate the full path to the ejabberd Process identifier (PID) file.
# If this variable is defined, ejabberd writes the PID file when starts,
# and deletes it when stops.
# Remember to create the directory and grant write permission to ejabberd.
#
# Default: don't write PID file
#
#EJABBERD_PID_PATH=/var/run/ejabberd/ejabberd.pid
#.
#' EJABBERD_CONFIG_PATH: ejabberd configuration file
#
# Specify the full path to the ejabberd configuration file. If the file name has
# yml or yaml extension, it is parsed as a YAML file; otherwise, Erlang syntax is
# expected.
#
# Default: $ETC_DIR/ejabberd.yml
#
EJABBERD_CONFIG_PATH={{ env['EJABBERD_CONFIG_PATH'] or '/opt/ejabberd/conf/ejabberd.yml' }}
#.
#' CONTRIB_MODULES_PATH: contributed ejabberd modules path
#
# Specify the full path to the contributed ejabberd modules. If the path is not
# defined, ejabberd will use ~/.ejabberd-modules in home of user running ejabberd.
#
# Default: $HOME/.ejabberd-modules
#
CONTRIB_MODULES_PATH={{ env['CONTRIB_MODULES_PATH'] or '/opt/ejabberd/modules' }}
#.
#' CONTRIB_MODULES_CONF_DIR: configuration directory for contributed modules
#
# Specify the full path to the configuration directory for contributed ejabberd
# modules. In order to configure a module named mod_foo, a mod_foo.yml file can
# be created in this directory. This file will then be used instead of the
# default configuration file provided with the module.
#
# Default: $CONTRIB_MODULES_PATH/conf
#
CONTRIB_MODULES_CONF_DIR={{ env['CONTRIB_MODULES_CONF_DIR'] or '/opt/ejabberd/modules/conf' }}
#.
#' EJABBERD_BYPASS_WARNINGS: Bypass LIVE warning
#
# Default: don't bypass the warning
#
EJABBERD_BYPASS_WARNINGS=true
#.
#'
# vim: foldmarker=#',#. foldmethod=marker:
+22
View File
@@ -0,0 +1,22 @@
readonly HOSTIP=$(hostname -i)
readonly HOSTNAME=$(hostname -f)
readonly DOMAINNAME=$(hostname -d)
readonly DOCKER_LIB="${EJABBERD_HOME}/docker/lib"
readonly ERLANGCOOKIEFILE="${EJABBERD_HOME}/.erlang.cookie"
readonly EJABBERDCTL="/sbin/ejabberdctl"
readonly CONFIGDIR="${EJABBERD_HOME}/conf"
readonly CONFIGTMPDIR="${EJABBERD_HOME}/docker/conf"
readonly SSLCERTDIR="${EJABBERD_HOME}/ssl"
readonly SSLCERTHOST="${SSLCERTDIR}/host.pem"
readonly LOGDIR="/var/log/ejabberd"
readonly FIRST_START_DONE_FILE="${EJABBERD_HOME}/first-start-done"
readonly CLUSTER_NODE_FILE="${EJABBERD_HOME}/cluster-done"
readonly PYTHON_JINJA2="import os;
import sys;
import jinja2;
sys.stdout.write(
jinja2.Template
(sys.stdin.read()
).render(env=os.environ))"
+72
View File
@@ -0,0 +1,72 @@
is_set() {
local var=$1
[[ -n $var ]]
}
is_zero() {
local var=$1
[[ -z $var ]]
}
file_exist() {
local file=$1
[[ -e $file ]]
}
is_true() {
local var=${1,,}
local choices=("yes" "1" "y" "true")
for ((i=0;i < ${#choices[@]};i++)) {
[[ "${choices[i]}" == $var ]] && return 0
}
return 1
}
log() {
local message=$1
echo $message
}
# overwrite this function to get hostname from other sources
# like dns or etcd
get_nodename() {
log ${HOSTNAME}
}
join_cluster() {
local cluster_node=$1
is_zero ${cluster_node} \
&& exit 0
log "Join cluster..."
local erlang_node_name=${ERLANG_NODE%@*}
local erlang_cluster_node="${erlang_node_name}@${cluster_node}"
response=$(${EJABBERDCTL} ping ${erlang_cluster_node})
while [ "$response" != "pong" ]; do
log "Waiting for ${erlang_cluster_node}..."
sleep 2
response=$(${EJABBERDCTL} ping ${erlang_cluster_node})
done
log "Join cluster at ${erlang_cluster_node}... "
NO_WARNINGS=true ${EJABBERDCTL} join_cluster $erlang_cluster_node
if [ $? -eq 0 ]; then
touch ${CLUSTER_NODE_FILE}
else
log "cloud not join cluster"
exit 1
fi
}
+1
View File
@@ -0,0 +1 @@
# Overridable file
+1
View File
@@ -0,0 +1 @@
# Overridable file
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
set -e
# Updates the known modules as to be found in https://github.com/processone/ejabberd-contrib
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
run_modules_update_specs() {
log "Updating module specs... "
${EJABBERDCTL} modules_update_specs
}
is_true ${EJABBERD_SKIP_MODULES_UPDATE} \
&& exit 0
run_modules_update_specs
exit 0
+144
View File
@@ -0,0 +1,144 @@
#!/bin/bash
set -e
# Installs modules as defined in environment variables
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
install_module_from_source() {
local module_name=$1
local module_source_path=${EJABBERD_HOME}/module_source/${module_name}
local module_install_folder=${EJABBERD_HOME}/.ejabberd-modules/sources/${module_name}
log "Analyzing module ${module_name} for installation"
# Make sure that the module exists in the source folder before attempting a copy
if [ ! -d ${module_source_path} ]; then
log "Error: Module ${module_name} not found in ${EJABBERD_HOME}/module_source"
log "Please use a shared volume to populate your module in ${EJABBERD_HOME}/module_source"
return 1;
fi
# Check to see if the module is already installed
local install_count=$(${EJABBERDCTL} modules_installed | grep -ce "^${module_name}[[:space:]]")
if [ $install_count -gt 0 ]; then
log "Error: Module already installed: ${module_name}"
return 1;
fi
# Copy the module into the shared folder
log "Copying module to ejabberd folder ${module_install_folder}"
mkdir -p ${module_install_folder}
cp -R ${module_source_path} ${module_install_folder}
# Run the ejabberdctl module_check on the module
log "Running module_check on ${module_name}"
${EJABBERDCTL} module_check ${module_name}
if [ $? -ne 0 ]; then
log "Module check failed for ${module_name}"
return 1;
fi
log "Module check succeeded for ${module_name}"
# Install the module
log "Running module_install on ${module_name}"
${EJABBERDCTL} module_install ${module_name}
if [ $? -ne 0 ]; then
log "Module installation failed for ${module_name}"
return 1;
fi
log "Module installation succeeded for ${module_name}"
return 0;
}
install_module_from_ejabberd_contrib() {
local module_name=$1
# Check to see if the module is already installed
local install_count=$(${EJABBERDCTL} modules_installed | grep -ce "^${module_name}[[:space:]]")
if [ $install_count -gt 0 ]; then
log "Error: Module already installed: ejabberd_contrib ${module_name}"
return 1;
fi
# Install the module
log "Running module_install on ejabberd_contrib ${module_name}"
${EJABBERDCTL} module_install ${module_name}
if [ $? -ne 0 ]; then
log "Module installation failed for ejabberd_contrib ${module_name}"
return 1;
fi
log "Module installation succeeded for ejabberd_contrib ${module_name}"
return 0;
}
enable_custom_auth_module_override() {
module_name=$1;
# When using custom authentication modules, the module name must be
# in the following pattern: ejabberd_auth_foo, where foo is the
# value you will use for your auth_method yml configuration.
required_prefix="ejabberd_auth_"
if [[ "${module_name}" != "${required_prefix}"* ]]; then
log "Error: module_name must begin with ${required_prefix}"
exit 1;
fi
log "Checking custom auth module: ${module_name}"
# Make sure the auth module is installed
local install_count=$(${EJABBERDCTL} modules_installed | grep -ce "^${module_name}[[:space:]]")
if [ $install_count -eq 0 ]; then
log "Error: custom auth_module not installed: ${module_name}"
return 1;
fi
custom_auth_method=${module_name#$required_prefix}
echo -e "\nauth_method: [${custom_auth_method}]" >> ${CONFIGFILE}
log "Custom auth module ${module_name} configuration complete."
}
file_exist ${FIRST_START_DONE_FILE} \
&& exit 0
is_restart_needed=0;
if [ -n "${EJABBERD_SOURCE_MODULES}" ]; then
for module_name in ${EJABBERD_SOURCE_MODULES} ; do
install_module_from_source ${module_name}
done
is_restart_needed=1;
fi
# Check the EJABBERD_CONTRIB_MODULES variable for any ejabberd_contrib modules
if [ -n "${EJABBERD_CONTRIB_MODULES}" ]; then
for module_name in ${EJABBERD_CONTRIB_MODULES} ; do
install_module_from_ejabberd_contrib ${module_name}
done
is_restart_needed=1;
fi
# If a custom module was defined for handling auth, we need to override
# the pre-defined auth methods in the config.
if [ -n "${EJABBERD_CUSTOM_AUTH_MODULE_OVERRIDE}" ]; then
enable_custom_auth_module_override "${EJABBERD_CUSTOM_AUTH_MODULE_OVERRIDE}"
is_restart_needed=1;
fi
# If any modules were installed, restart the server, if the option is enabled
if [ ${is_restart_needed} -eq 1 ]; then
if is_true ${EJABBERD_RESTART_AFTER_MODULE_INSTALL} ; then
log "Restarting ejabberd after successful module installation(s)"
${EJABBERDCTL} restart
child=$!
${EJABBERDCTL} "started"
wait $child
fi
fi
exit 0
+72
View File
@@ -0,0 +1,72 @@
#!/bin/bash
set -e
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
# Do not exit if users already registered
set +e
randpw() {
< /dev/urandom tr -dc A-Z-a-z-0-9 | head -c ${1:-16};
echo;
}
register_user() {
local user=$1
local domain=$2
local password=$3
${EJABBERDCTL} register ${user} ${domain} ${password}
return $?
}
register_all_users() {
# register users from environment $EJABBERD_USERS with given
# password or random password written to stout. Use whitespace
# to seperate users.
#
# sample:
# - add a user with an given password:
# -e "EJABBERD_USERS=admin@example.com:adminSecret"
# - add a user with a random password:
# -e "EJABBERD_USERS=user@example.com"
# - set password for admin and use random for user1:
# -e "EJABBERD_USERS=admin@example.com:adminSecret user@example.com"
for user in ${EJABBERD_USERS} ; do
local jid=${user%%:*}
local password=${user#*:}
local username=${jid%%@*}
local domain=${jid#*@}
[[ "${password}" == "${jid}" ]] \
&& password=$(randpw)
register_user ${username} ${domain} ${password}
local retval=$?
[[ ${retval} -eq 0 ]] \
&& log "Password for user ${username}@${domain} is ${password}"
done
}
file_exist ${FIRST_START_DONE_FILE} \
&& exit 0
file_exist ${CLUSTER_NODE_FILE} \
&& exit 0
is_set ${EJABBERD_USERS} \
&& register_all_users
exit 0
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
set -e
# Write a first-start-done file
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
if [ ! -e "${FIRST_START_DONE_FILE}" ]; then
touch ${FIRST_START_DONE_FILE}
fi
exit 0
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
set -e
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
# Instead of having to mount a direction, specify the ssl certs
# via environment variables:
# `EJABBERD_SSLCERT_HOST` and `EJABBERD_SSLCERT_{domain_name}`.
# For example: `EJABBERD_SSLCERT_EXAMPLE_COM`.
write_file_from_env() {
log "Writing $1 to $2"
mkdir -p "$(dirname $2)"
log "${!1}" > $2
}
# Write the host certificate
is_set ${EJABBERD_SSLCERT_HOST} \
&& write_file_from_env "EJABBERD_SSLCERT_HOST" ${SSLCERTHOST}
# Write the domain certificates for each XMPP_DOMAIN
for xmpp_domain in ${XMPP_DOMAIN} ; do
var="EJABBERD_SSLCERT_$(echo $xmpp_domain | awk '{print toupper($0)}' | sed 's/\./_/g;s/-/_/g')"
if is_set ${!var} ; then
file_exist "${SSLCERTDIR}/${xmpp_domain}.pem" \
|| write_file_from_env "$var" "${SSLCERTDIR}/${xmpp_domain}.pem"
fi
done
exit 0
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
set -e
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
make_snakeoil_certificate() {
local domain=$1
local certfile=$2
openssl req -subj "/CN=${domain}" \
-new \
-newkey rsa:4096 \
-days 365 \
-nodes \
-x509 \
-keyout /tmp/selfsigned.key \
-out /tmp/selfsigned.crt
log "Writing ssl cert and private key to '${certfile}'..."
cat /tmp/selfsigned.crt /tmp/selfsigned.key > ${certfile}
rm /tmp/selfsigned.crt /tmp/selfsigned.key
}
make_host_snakeoil_certificate() {
local IFS=@
local domain='localhost'
local erlang_node=${ERLANG_NODE}
if is_true ${erlang_node} ; then
domain=${HOSTNAME}
elif is_set ${erlang_node} ; then
set ${erlang_node}
local nodehost=$2
if is_zero ${nodehost} ; then
domain=${HOSTNAME}
else
domain=${nodehost}
fi
fi
log "Generating snakeoil ssl cert for ${domain}..."
make_snakeoil_certificate ${domain} ${SSLCERTHOST}
}
make_domain_snakeoil_certificate() {
local domain=$1
local certfile=$2
log "Generating snakeoil ssl cert for ${domain}..."
make_snakeoil_certificate ${domain} ${certfile}
}
# generate host ssl cert if missing
file_exist ${SSLCERTHOST} \
|| make_host_snakeoil_certificate
# generate xmmp domain ssl certificates if missing
for xmpp_domain in ${XMPP_DOMAIN} ; do
domain_certfile="${SSLCERTDIR}/${xmpp_domain}.pem"
file_exist ${domain_certfile} \
|| make_domain_snakeoil_certificate ${xmpp_domain} ${domain_certfile}
done
exit 0
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
set -e
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
make_dhparam() {
local dhfile=$1
local bits=$2
log "Writing dh file to '${dhfile}'..."
openssl dhparam -out ${dhfile} ${bits}
}
if is_true ${EJABBERD_DHPARAM} ; then
file_exist ${SSLDHPARAM} \
|| make_dhparam ${SSLDHPARAM} 4096
fi
exit 0
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
set_erlang_cookie() {
chmod 600 ${ERLANGCOOKIEFILE}
log "Set erlang cookie to ${ERLANG_COOKIE}..."
echo ${ERLANG_COOKIE} > ${ERLANGCOOKIEFILE}
chmod 400 ${ERLANGCOOKIEFILE}
}
file_exist ${FIRST_START_DONE_FILE} \
&& exit 0
# set erlang cookie if ERLANG_COOKIE is set in environemt
is_set ${ERLANG_COOKIE} \
&& set_erlang_cookie
exit 0
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
set -e
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
make_config() {
local filename=$1
local template="${CONFIGTMPDIR}/${filename}.tpl"
local configfile="${CONFIGDIR}/${filename}"
file_exist $configfile \
&& return 1
if [ ! -e ${configfile} ]; then
log "Generating ${configfile} config file..."
cat $template \
| python -c "${PYTHON_JINJA2}" \
> $configfile
else
echo "File ${configfile} exists."
fi
}
# /opt/ejabberd/conf/ejabberd.yml
make_config "ejabberd.yml"
# /opt/ejabberd/conf/ejabberdctl.cfg
make_config "ejabberdctl.cfg"
exit 0
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
set -e
# Environment
export EJABBERD_HTTPS=${EJABBERD_HTTPS:-'true'}
export EJABBERD_STARTTLS=${EJABBERD_STARTTLS:-'true'}
export EJABBERD_S2S_SSL=${EJABBERD_S2S_SSL:-'true'}
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
# discover hostname
readonly nodename=$(get_nodename)
# set erlang node to node name from get_nodename
if [[ "$ERLANG_NODE" == "nodename" ]]; then
export ERLANG_NODE="ejabberd@${nodename}"
fi
run_scripts() {
local run_script=$1
local run_script_dir="${EJABBERD_HOME}/docker/${run_script}"
log "Run ${run_script} scripts..."
for script in ${run_script_dir}/*.sh ; do
if [ -f ${script} -a -x ${script} ] ; then
${script}
fi
done
}
_trap() {
run_scripts "stop"
log "Stopping ejabberd..."
$EJABBERDCTL stop
$EJABBERDCTL stopped
exit 0
}
# Catch signals and shutdown ejabberd
trap _trap SIGTERM SIGINT
# print logfiles to stdout
tail -F ${LOGDIR}/crash.log \
${LOGDIR}/error.log \
${LOGDIR}/erlang.log \
${LOGDIR}/ejabberd.log &
# start ejabberd
run_scripts "pre"
log "Starting ejabberd..."
$EJABBERDCTL start
$EJABBERDCTL started
log "Ejabberd started."
run_scripts "post"
# run forever
while true; do sleep 1; done
log "Ejabberd stopped."
exit 0
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
set -e
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
source "${EJABBERD_HOME}/docker/lib/config.sh"
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
source "${EJABBERD_HOME}/docker/lib/functions.sh"
leave_cluster() {
log "Leave cluster..."
rm ${CLUSTER_NODE_FILE}
NO_WARNINGS=true ${EJABBERDCTL} leave_cluster
}
file_exist ${CLUSTER_NODE_FILE} \
&& leave_cluster
exit 0
+5 -11
View File
@@ -3,22 +3,16 @@ Description=XMPP Server
After=network.target
[Service]
Type=forking
User=ejabberd
Group=ejabberd
LimitNOFILE=16000
LimitNOFILE=65536
Restart=on-failure
RestartSec=5
ExecStart=@ctlscriptpath@/ejabberdctl start
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
ExecStart=/bin/sh -c '@ctlscriptpath@/ejabberdctl start && @ctlscriptpath@/ejabberdctl started'
ExecStop=/bin/sh -c '@ctlscriptpath@/ejabberdctl stop && @ctlscriptpath@/ejabberdctl stopped'
PrivateDevices=true
ProtectHome=true
ProtectSystem=full
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
+51
View File
@@ -0,0 +1,51 @@
%%%----------------------------------------------------------------------
%%%
%%% 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.
%%%
%%%----------------------------------------------------------------------
-define(CT_XML,
{<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
-define(CT_PLAIN,
{<<"Content-Type">>, <<"text/plain">>}).
-define(CT_JSON,
{<<"Content-Type">>, <<"application/json">>}).
-define(AC_ALLOW_ORIGIN,
{<<"Access-Control-Allow-Origin">>, <<"*">>}).
-define(AC_ALLOW_METHODS,
{<<"Access-Control-Allow-Methods">>,
<<"GET, POST, OPTIONS">>}).
-define(AC_ALLOW_HEADERS,
{<<"Access-Control-Allow-Headers">>,
<<"Content-Type">>}).
-define(AC_MAX_AGE,
{<<"Access-Control-Max-Age">>, <<"86400">>}).
-define(OPTIONS_HEADER,
[?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
-define(HEADER(CType),
[CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
-define(PROCNAME, ejabberd_mod_bosh).
+4 -2
View File
@@ -39,7 +39,9 @@
-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>).
-define(S2STIMEOUT, 600000).
-define(COPYRIGHT, "Copyright (c) 2002-2016 ProcessOne").
-define(S2STIMEOUT, timer:minutes(10)).
%%-define(DBGFSM, true).
@@ -64,7 +66,7 @@
-define(TDICT, dict:dict()).
-define(TGB_TREE, gb_trees:tree()).
-define(TGB_SET, gb_set:set()).
-define(TGB_SET, gb_sets:set()).
-define(TQUEUE, queue:queue()).
-endif.
+4 -3
View File
@@ -46,12 +46,13 @@
%% to command, so that the command can perform additional check.
-record(ejabberd_commands,
{name :: atom(),
{name :: atom(),
tags = [] :: [atom()] | '_' | '$2',
desc = "" :: string() | '_' | '$3',
longdesc = "" :: string() | '_',
version = 0 :: integer(),
module :: atom() | '_',
version = 0 :: integer(),
weight = 1 :: integer(),
module :: atom() | '_',
function :: atom() | '_',
args = [] :: [aterm()] | '_' | '$1' | '$2',
policy = restricted :: open | restricted | admin | user,
-20
View File
@@ -1,20 +0,0 @@
-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{} ).
-4
View File
@@ -19,11 +19,7 @@
%%%----------------------------------------------------------------------
-include("ns.hrl").
-ifdef(NO_EXT_LIB).
-include("fxml.hrl").
-else.
-include_lib("fast_xml/include/fxml.hrl").
-endif.
-define(STANZA_ERROR(Code, Type, Condition),
#xmlel{name = <<"error">>,
+1 -1
View File
@@ -4,7 +4,7 @@
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1',
peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
packet = #xmlel{} :: xmlel() | '_',
packet = #xmlel{} :: xmlel() | message() | '_',
nick = <<"">> :: binary(),
type = chat :: chat | groupchat}).
+1 -2
View File
@@ -71,6 +71,7 @@
-type config() :: #config{}.
-type role() :: moderator | participant | visitor | none.
-type affiliation() :: admin | member | outcast | owner | none.
-record(user,
{
@@ -126,5 +127,3 @@
host = <<>> :: binary() | '_' | '$2'}).
-type muc_online_users() :: #muc_online_users{}.
-type muc_room_state() :: #state{}.
+2 -2
View File
@@ -1,7 +1,7 @@
-record(offline_msg,
{us = {<<"">>, <<"">>} :: {binary(), binary()},
timestamp = now() :: erlang:timestamp() | '_',
expire = now() :: erlang:timestamp() | never | '_',
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_',
expire = p1_time_compat:timestamp() :: erlang:timestamp() | never | '_',
from = #jid{} :: jid() | '_',
to = #jid{} :: jid() | '_',
packet = #xmlel{} :: xmlel() | '_'}).
+8 -3
View File
@@ -22,9 +22,11 @@
default = none :: none | binary(),
lists = [] :: [{binary(), [listitem()]}]}).
-record(listitem, {type = none :: none | jid | group | subscription,
value = none :: none | both | from | to | ljid() | binary(),
action = allow :: allow | deny,
-type privacy() :: #privacy{}.
-record(listitem, {type = none :: listitem_type(),
value = none :: listitem_value(),
action = allow :: listitem_action(),
order = 0 :: integer(),
match_all = false :: boolean(),
match_iq = false :: boolean(),
@@ -33,6 +35,9 @@
match_presence_out = false :: boolean()}).
-type listitem() :: #listitem{}.
-type listitem_type() :: none | jid | group | subscription.
-type listitem_value() :: none | both | from | to | jid:ljid() | binary().
-type listitem_action() :: allow | deny.
-record(userlist, {name = none :: none | binary(),
list = [] :: [listitem()],
+3 -3
View File
@@ -20,15 +20,15 @@
-record(roster,
{
usj = {<<>>, <<>>, {<<>>, <<>>, <<>>}} :: {binary(), binary(), ljid()} | '_',
usj = {<<>>, <<>>, {<<>>, <<>>, <<>>}} :: {binary(), binary(), jid:ljid()} | '_',
us = {<<>>, <<>>} :: {binary(), binary()} | '_',
jid = {<<>>, <<>>, <<>>} :: ljid(),
jid = {<<>>, <<>>, <<>>} :: jid:ljid(),
name = <<>> :: binary() | '_',
subscription = none :: subscription() | '_',
ask = none :: ask() | '_',
groups = [] :: [binary()] | '_',
askmessage = <<"">> :: binary() | '_',
xs = [] :: [xmlel()] | '_'
xs = [] :: [fxml:xmlel()] | '_'
}).
-record(roster_version,
-176
View File
@@ -1,176 +0,0 @@
%%%----------------------------------------------------------------------
%%%
%%% 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.
%%%
%%%----------------------------------------------------------------------
-define(NS_DISCO_ITEMS,
<<"http://jabber.org/protocol/disco#items">>).
-define(NS_DISCO_INFO,
<<"http://jabber.org/protocol/disco#info">>).
-define(NS_VCARD, <<"vcard-temp">>).
-define(NS_VCARD_UPDATE, <<"vcard-temp:x:update">>).
-define(NS_AUTH, <<"jabber:iq:auth">>).
-define(NS_AUTH_ERROR, <<"jabber:iq:auth:error">>).
-define(NS_REGISTER, <<"jabber:iq:register">>).
-define(NS_SEARCH, <<"jabber:iq:search">>).
-define(NS_ROSTER, <<"jabber:iq:roster">>).
-define(NS_ROSTER_VER,
<<"urn:xmpp:features:rosterver">>).
-define(NS_PRIVACY, <<"jabber:iq:privacy">>).
-define(NS_BLOCKING, <<"urn:xmpp:blocking">>).
-define(NS_PRIVATE, <<"jabber:iq:private">>).
-define(NS_VERSION, <<"jabber:iq:version">>).
-define(NS_TIME, <<"urn:xmpp:time">>).
-define(NS_LAST, <<"jabber:iq:last">>).
-define(NS_XDATA, <<"jabber:x:data">>).
-define(NS_IQDATA, <<"jabber:iq:data">>).
-define(NS_DELAY, <<"urn:xmpp:delay">>).
-define(NS_HINTS, <<"urn:xmpp:hints">>).
-define(NS_EXPIRE, <<"jabber:x:expire">>).
-define(NS_EVENT, <<"jabber:x:event">>).
-define(NS_CHATSTATES,
<<"http://jabber.org/protocol/chatstates">>).
-define(NS_XCONFERENCE, <<"jabber:x:conference">>).
-define(NS_STATS,
<<"http://jabber.org/protocol/stats">>).
-define(NS_MUC, <<"http://jabber.org/protocol/muc">>).
-define(NS_MUC_USER,
<<"http://jabber.org/protocol/muc#user">>).
-define(NS_MUC_ADMIN,
<<"http://jabber.org/protocol/muc#admin">>).
-define(NS_MUC_OWNER,
<<"http://jabber.org/protocol/muc#owner">>).
-define(NS_MUC_UNIQUE,
<<"http://jabber.org/protocol/muc#unique">>).
-define(NS_PUBSUB,
<<"http://jabber.org/protocol/pubsub">>).
-define(NS_PUBSUB_EVENT,
<<"http://jabber.org/protocol/pubsub#event">>).
-define(NS_PUBSUB_META_DATA,
<<"http://jabber.org/protocol/pubsub#meta-data">>).
-define(NS_PUBSUB_OWNER,
<<"http://jabber.org/protocol/pubsub#owner">>).
-define(NS_PUBSUB_NMI,
<<"http://jabber.org/protocol/pubsub#node-meta-info">>).
-define(NS_PUBSUB_ERRORS,
<<"http://jabber.org/protocol/pubsub#errors">>).
-define(NS_PUBSUB_NODE_CONFIG,
<<"http://jabber.org/protocol/pubsub#node_config">>).
-define(NS_PUBSUB_SUB_OPTIONS,
<<"http://jabber.org/protocol/pubsub#subscribe_options">>).
-define(NS_PUBSUB_SUBSCRIBE_OPTIONS,
<<"http://jabber.org/protocol/pubsub#subscribe_options">>).
-define(NS_PUBSUB_PUBLISH_OPTIONS,
<<"http://jabber.org/protocol/pubsub#publish_options">>).
-define(NS_PUBSUB_SUB_AUTH,
<<"http://jabber.org/protocol/pubsub#subscribe_authorization">>).
-define(NS_PUBSUB_GET_PENDING,
<<"http://jabber.org/protocol/pubsub#get-pending">>).
-define(NS_COMMANDS,
<<"http://jabber.org/protocol/commands">>).
-define(NS_BYTESTREAMS,
<<"http://jabber.org/protocol/bytestreams">>).
-define(NS_ADMIN,
<<"http://jabber.org/protocol/admin">>).
-define(NS_ADMIN_ANNOUNCE,
<<"http://jabber.org/protocol/admin#announce">>).
-define(NS_ADMIN_ANNOUNCE_ALL,
<<"http://jabber.org/protocol/admin#announce-all">>).
-define(NS_ADMIN_SET_MOTD,
<<"http://jabber.org/protocol/admin#set-motd">>).
-define(NS_ADMIN_EDIT_MOTD,
<<"http://jabber.org/protocol/admin#edit-motd">>).
-define(NS_ADMIN_DELETE_MOTD,
<<"http://jabber.org/protocol/admin#delete-motd">>).
-define(NS_ADMIN_ANNOUNCE_ALLHOSTS,
<<"http://jabber.org/protocol/admin#announce-allhosts">>).
-define(NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS,
<<"http://jabber.org/protocol/admin#announce-all-allhosts">>).
-define(NS_ADMIN_SET_MOTD_ALLHOSTS,
<<"http://jabber.org/protocol/admin#set-motd-allhosts">>).
-define(NS_ADMIN_EDIT_MOTD_ALLHOSTS,
<<"http://jabber.org/protocol/admin#edit-motd-allhosts">>).
-define(NS_ADMIN_DELETE_MOTD_ALLHOSTS,
<<"http://jabber.org/protocol/admin#delete-motd-allhosts">>).
-define(NS_SERVERINFO,
<<"http://jabber.org/network/serverinfo">>).
-define(NS_RSM, <<"http://jabber.org/protocol/rsm">>).
-define(NS_EJABBERD_CONFIG, <<"ejabberd:config">>).
-define(NS_STREAM,
<<"http://etherx.jabber.org/streams">>).
-define(NS_STANZAS,
<<"urn:ietf:params:xml:ns:xmpp-stanzas">>).
-define(NS_STREAMS,
<<"urn:ietf:params:xml:ns:xmpp-streams">>).
-define(NS_TLS, <<"urn:ietf:params:xml:ns:xmpp-tls">>).
-define(NS_SASL,
<<"urn:ietf:params:xml:ns:xmpp-sasl">>).
-define(NS_SESSION,
<<"urn:ietf:params:xml:ns:xmpp-session">>).
-define(NS_BIND,
<<"urn:ietf:params:xml:ns:xmpp-bind">>).
-define(NS_FEATURE_IQAUTH,
<<"http://jabber.org/features/iq-auth">>).
-define(NS_FEATURE_IQREGISTER,
<<"http://jabber.org/features/iq-register">>).
-define(NS_FEATURE_COMPRESS,
<<"http://jabber.org/features/compress">>).
-define(NS_FEATURE_MSGOFFLINE, <<"msgoffline">>).
-define(NS_FLEX_OFFLINE, <<"http://jabber.org/protocol/offline">>).
-define(NS_COMPRESS,
<<"http://jabber.org/protocol/compress">>).
-define(NS_CAPS, <<"http://jabber.org/protocol/caps">>).
-define(NS_SHIM, <<"http://jabber.org/protocol/shim">>).
-define(NS_ADDRESS,
<<"http://jabber.org/protocol/address">>).
-define(NS_OOB, <<"jabber:x:oob">>).
-define(NS_CAPTCHA, <<"urn:xmpp:captcha">>).
-define(NS_MEDIA, <<"urn:xmpp:media-element">>).
-define(NS_BOB, <<"urn:xmpp:bob">>).
-define(NS_MAM_TMP, <<"urn:xmpp:mam:tmp">>).
-define(NS_MAM_0, <<"urn:xmpp:mam:0">>).
-define(NS_MAM_1, <<"urn:xmpp:mam:1">>).
-define(NS_SID_0, <<"urn:xmpp:sid:0">>).
-define(NS_PING, <<"urn:xmpp:ping">>).
-define(NS_CARBONS_2, <<"urn:xmpp:carbons:2">>).
-define(NS_CARBONS_1, <<"urn:xmpp:carbons:1">>).
-define(NS_FORWARD, <<"urn:xmpp:forward:0">>).
-define(NS_CLIENT_STATE, <<"urn:xmpp:csi:0">>).
-define(NS_STREAM_MGMT_2, <<"urn:xmpp:sm:2">>).
-define(NS_STREAM_MGMT_3, <<"urn:xmpp:sm:3">>).
-define(NS_HTTP_UPLOAD, <<"urn:xmpp:http:upload">>).
-define(NS_HTTP_UPLOAD_OLD, <<"eu:siacs:conversations:http:upload">>).
-define(NS_THUMBS_1, <<"urn:xmpp:thumbs:1">>).
-define(NS_NICK, <<"http://jabber.org/protocol/nick">>).
-define(NS_MIX_0, <<"urn:xmpp:mix:0">>).
-define(NS_MIX_SERVICEINFO_0, <<"urn:xmpp:mix:0#serviceinfo">>).
-define(NS_MIX_NODES_MESSAGES, <<"urn:xmpp:mix:nodes:messages">>).
-define(NS_MIX_NODES_PRESENCE, <<"urn:xmpp:mix:nodes:presence">>).
-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">>).
+6
View File
@@ -176,3 +176,9 @@
creation ,% :: {erlang:timestamp(), jlib:ljid()},
payload % :: mod_pubsub:payload()
}).
-record(pubsub_orphan,
{
nodeid ,% :: mod_pubsub:nodeIdx(),
items = [] % :: list()
}).
+57 -23
View File
@@ -3,9 +3,9 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "16.08.0",
version: "16.12.0",
description: description,
elixir: "~> 1.2",
elixir: "~> 1.3",
elixirc_paths: ["lib"],
compile_path: ".",
compilers: [:asn1] ++ Mix.compilers,
@@ -17,7 +17,7 @@ defmodule Ejabberd.Mixfile do
deps: deps]
end
defp description do
def description do
"""
Robust, ubiquitous and massively scalable Jabber / XMPP Instant Messaging platform.
"""
@@ -27,16 +27,15 @@ defmodule Ejabberd.Mixfile do
[mod: {:ejabberd_app, []},
applications: [:ssl],
included_applications: [:lager, :mnesia, :p1_utils, :cache_tab,
:fast_tls, :stringprep, :fast_xml,
:stun, :fast_yaml, :ezlib, :iconv,
:esip, :jiffy, :p1_oauth2, :eredis,
:p1_mysql, :p1_pgsql, :sqlite3]]
:fast_tls, :stringprep, :fast_xml, :xmpp,
:stun, :fast_yaml, :esip, :jiffy, :p1_oauth2]
++ cond_apps]
end
defp erlc_options do
# Use our own includes + includes from all dependencies
includes = ["include"] ++ Path.wildcard(Path.join("..", "/*/include"))
[:debug_info] ++ Enum.map(includes, fn(path) -> {:i, path} end)
includes = ["include"] ++ deps_include(["fast_xml", "xmpp"])
[:debug_info, {:d, :ELIXIR_ENABLED}] ++ Enum.map(includes, fn(path) -> {:i, path} end)
end
defp deps do
@@ -47,26 +46,46 @@ defmodule Ejabberd.Mixfile do
{:fast_yaml, "~> 1.0"},
{:fast_tls, "~> 1.0"},
{:fast_xml, "~> 1.1"},
{:xmpp, "~> 1.1"},
{:stun, "~> 1.0"},
{:esip, "~> 1.0"},
{:jiffy, "~> 0.14.7"},
{:p1_oauth2, "~> 0.6.1"},
{:p1_mysql, "~> 1.0"},
{:p1_pgsql, "~> 1.1"},
{:sqlite3, "~> 1.1"},
{:ezlib, "~> 1.0"},
{:iconv, "~> 1.0"},
{:eredis, "~> 1.0"},
{: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}]
{:distillery, "~> 1.0"},
{:ex_doc, ">= 0.0.0", only: :dev}]
++ cond_deps
end
defp package do
defp deps_include(deps) do
Enum.map(deps, fn dep -> "deps/#{dep}/include" end)
end
defp cond_deps do
for {:true, dep} <- [{config(:mysql), {:p1_mysql, "~> 1.0"}},
{config(:pgsql), {:p1_pgsql, "~> 1.1"}},
{config(:sqlite), {:sqlite3, "~> 1.1"}},
{config(:riak), {:riakc, "~> 2.4"}},
{config(:redis), {:eredis, "~> 1.0"}},
{config(:zlib), {:ezlib, "~> 1.0"}},
{config(:iconv), {:iconv, "~> 1.0"}},
{config(:pam), {:p1_pam, "~> 1.0"}},
{config(:tools), {:luerl, github: "rvirding/luerl", tag: "v0.2"}},
{config(:tools), {:meck, "~> 0.8.4"}},
{config(:tools), {:moka, github: "processone/moka", tag: "1.0.5c"}}], do:
dep
end
defp cond_apps do
for {:true, app} <- [{config(:redis), :eredis},
{config(:mysql), :p1_mysql},
{config(:pgsql), :p1_pgsql},
{config(:sqlite), :sqlite3},
{config(:zlib), :ezlib},
{config(:iconv), :iconv}], do:
app
end
def package do
[# These are the default files included in the package
files: ["lib", "src", "priv", "mix.exs", "include", "README.md", "COPYING"],
maintainers: ["ProcessOne"],
@@ -76,6 +95,21 @@ defmodule Ejabberd.Mixfile do
"Source" => "https://github.com/processone/ejabberd",
"ProcessOne" => "http://www.process-one.net/"}]
end
def vars do
case :file.consult("vars.config") do
{:ok,config} -> config
_ -> [zlib: true, iconv: true]
end
end
defp config(key) do
case vars[key] do
nil -> false
value -> value
end
end
end
defmodule Mix.Tasks.Compile.Asn1 do
+14 -25
View File
@@ -1,30 +1,19 @@
%{"bbmustache": {:hex, :bbmustache, "1.0.4", "7ba94f971c5afd7b6617918a4bb74705e36cab36eb84b19b6a1b7ee06427aa38", [:rebar], []},
"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.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]}]},
%{"cache_tab": {:hex, :cache_tab, "1.0.5", "022da45aa10c2ccfdc804e69af36e3be1717c7579f3578767a29bd8ceb3b3c92", [:rebar3], [{:p1_utils, "1.0.6", [hex: :p1_utils, optional: false]}]},
"distillery": {:hex, :distillery, "1.0.0", "a866a72bf2a3a5f078f5a249017ed951acda88a760d200512f91f585d74db1ec", [:mix], []},
"earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []},
"esip": {:hex, :esip, "1.0.9", "256259792c07d2f888d56e89031b0d064ff795c395fdca11ee3aab912c56d9de", [:rebar3], [{:fast_tls, "1.0.8", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.6", [hex: :p1_utils, optional: false]}, {:stun, "1.0.8", [hex: :stun, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"ezlib": {:hex, :ezlib, "1.0.1", "add8b2770a1a70c174aaea082b4a8668c0c7fdb03ee6cc81c6c68d3a6c3d767d", [:rebar3], []},
"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], []},
"fast_tls": {:hex, :fast_tls, "1.0.8", "697dc84ab958aed36924d8d9df9a463103ddc92f53283bbd930c9015f05ecf0a", [:rebar3], [{:p1_utils, "1.0.6", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.18", "5cad4f35fa50070d38ef7cbb64975c6a8750e92b976f49e5bb88819f47451e69", [:rebar3], [{:p1_utils, "1.0.6", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.7", "c1fc7995c8422bb4dc2a3502f6432054d6c16fde86f259f50080bb1d09fc5e50", [:rebar3], [{:p1_utils, "1.0.6", [hex: :p1_utils, optional: false]}]},
"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], []},
"iconv": {:hex, :iconv, "1.0.3", "f5c159f7e0ad2a3b55c6b5528ce71d7926f206df2f9bf83201a77cf1bc91c6f0", [:rebar3], [{:p1_utils, "1.0.6", [hex: :p1_utils, optional: false]}]},
"jiffy": {:hex, :jiffy, "0.14.7", "9f33b893edd6041ceae03bc1e50b412e858cc80b46f3d7535a7a9940a79a1c37", [:make, :rebar], []},
"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.5", "3e698354fdc1fea5491d991457b0cb986c0a00a47d224feb841dc3ec82b9f721", [:rebar3], []},
"providers": {:hex, :providers, "1.6.0", "db0e2f9043ae60c0155205fcd238d68516331d0e5146155e33d1e79dc452964a", [:rebar3], [{:getopt, "0.8.2", [hex: :getopt, 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.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]}]}}
"p1_utils": {:hex, :p1_utils, "1.0.6", "ef0951ddf38e92b7e479af4b8dc950df76af8c1030432ef68b7fd7ad17c436fe", [:rebar3], []},
"stringprep": {:hex, :stringprep, "1.0.7", "f709c7ee3697ae9d2becbbbba1dcea47e0f583e313b4eb7d0ced2163c595ee12", [:rebar3], [{:p1_utils, "1.0.6", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.8", "cfe6df914b12227ca0920e5e3d5b1203331c6662d0948859357c84d2c87a0411", [:rebar3], [{:fast_tls, "1.0.8", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.6", [hex: :p1_utils, optional: false]}]},
"xmpp": {:hex, :xmpp, "1.1.4", "fa9c890a779c57699d870e2e0966e4b6a66e92de19d0263a54df26f1be6ae046", [:rebar3], [{:fast_xml, "1.1.18", [hex: :fast_xml, optional: false]}, {:stringprep, "1.0.7", [hex: :stringprep, optional: false]}]}}
+2 -3
View File
@@ -144,7 +144,7 @@
{"Import Users from Dir at ","Importovat uživatele z adresáře na "}.
{"Import Users From jabberd14 Spool Files","Importovat uživatele z jabberd14 spool souborů"}.
{"Improper message type","Nesprávný typ zprávy"}.
{"Incoming s2s Connections:",""}.
{"Incoming s2s Connections:","Příchozí s2s spojení:"}.
{"Incorrect password","Nesprávné heslo"}.
{"Invalid affiliation: ~s","Neplatné přiřazení: ~s"}.
{"Invalid role: ~s","Neplatná role: ~s"}.
@@ -333,7 +333,6 @@
{"September",". září"}.
{"Server ~b","Server ~b"}.
{"Server:","Server:"}.
{"Server","Server"}.
{"Set message of the day and send to online users","Nastavit zprávu dne a odeslat ji online uživatelům"}.
{"Set message of the day on all hosts and send to online users","Nastavit zprávu dne a odeslat ji online uživatelům"}.
{"Shared Roster Groups","Skupiny pro sdílený seznam kontaktů"}.
@@ -409,10 +408,10 @@
{"User JID","Jabber ID uživatele"}.
{"User Management","Správa uživatelů"}.
{"Username:","Uživatelské jméno:"}.
{"User ~s",""}.
{"Users are not allowed to register accounts so quickly","Je zakázáno registrovat účty v tak rychlém sledu"}.
{"Users Last Activity","Poslední aktivita uživatele"}.
{"Users","Uživatelé"}.
{"User ~s","Uživatel ~s"}.
{"User","Uživatel"}.
{"Validate","Ověřit"}.
{"vCard User Search","Hledání uživatelů podle vizitek"}.
+2 -6
View File
@@ -242,9 +242,7 @@ msgstr "Odchozí s2s spojení:"
#: ejabberd_web_admin.erl:1559
msgid "Incoming s2s Connections:"
msgstr ""
"Příchozí\n"
" s2s spojení:"
msgstr "Příchozí s2s spojení:"
#: ejabberd_web_admin.erl:1595 ejabberd_web_admin.erl:1794
#: ejabberd_web_admin.erl:1804 ejabberd_web_admin.erl:2214 mod_roster.erl:1429
@@ -258,9 +256,7 @@ msgstr "Změnit heslo"
#: ejabberd_web_admin.erl:1673
msgid "User ~s"
msgstr ""
"Uživatel\n"
" ~s"
msgstr "Uživatel ~s"
#: ejabberd_web_admin.erl:1684
msgid "Connected Resources:"
+11 -11
View File
@@ -31,7 +31,7 @@ msgstr "foi removido"
#: ejabberd_c2s.erl:2105
msgid "Your active privacy list has denied the routing of this stanza."
msgstr "Sua lista de privacidade ativa negou o roteamento deste."
msgstr "Sua lista de privacidade ativa negou o roteamento desta instância."
#: ejabberd_c2s.erl:2420
msgid "Too many unacked stanzas"
@@ -286,7 +286,7 @@ msgstr "Nós em execução"
#: ejabberd_web_admin.erl:1815 mod_configure.erl:526
msgid "Stopped Nodes"
msgstr "Nos parados"
msgstr "Nós parados"
#: ejabberd_web_admin.erl:1833 ejabberd_web_admin.erl:1858
msgid "Node ~p"
@@ -302,7 +302,7 @@ msgstr "Salvar cópia de segurança"
#: ejabberd_web_admin.erl:1845
msgid "Listened Ports"
msgstr "Portas escutadas"
msgstr "Portas abertas"
#: ejabberd_web_admin.erl:1848 ejabberd_web_admin.erl:2261
msgid "Update"
@@ -1013,7 +1013,7 @@ msgstr "Apelido"
#: mod_muc.erl:1050 mod_muc_room.erl:1104 mod_muc_room.erl:1843
msgid "That nickname is registered by another person"
msgstr "O nick já está registrado por outra pessoa"
msgstr "O apelido já está registrado por outra pessoa"
#: mod_muc.erl:1078
msgid "You must fill in field \"Nickname\" in the form"
@@ -1303,7 +1303,7 @@ msgstr "O Jabber ID ~s não es válido"
#: mod_muc_room.erl:2772
msgid "Nickname ~s does not exist in the room"
msgstr "O nick ~s não existe na sala"
msgstr "O apelido ~s não existe na sala"
#: mod_muc_room.erl:2795 mod_muc_room.erl:3175
msgid "Invalid affiliation: ~s"
@@ -1568,15 +1568,15 @@ msgstr "Entregar as notificações de evento"
#: mod_pubsub.erl:3749
msgid "Notify subscribers when the node configuration changes"
msgstr "Notificar subscritores quando cambia la configuração do nodo"
msgstr "Notificar assinantes a configuração do nó mudar"
#: mod_pubsub.erl:3751
msgid "Notify subscribers when the node is deleted"
msgstr "Notificar subscritores quando o nodo se elimine"
msgstr "Notificar assinantes quando o nó for eliminado se elimine"
#: mod_pubsub.erl:3753
msgid "Notify subscribers when items are removed from the node"
msgstr "Notificar subscritores quando os elementos se eliminem do nodo"
msgstr "Notificar assinantes quando itens forem eliminados do nó"
#: mod_pubsub.erl:3755
msgid "Persist items to storage"
@@ -1637,7 +1637,7 @@ msgstr "A verificação do CAPTCHA falhou"
#: mod_register.erl:253
msgid "You need a client that supports x:data and CAPTCHA to register"
msgstr ""
"Você precisa de um cliente com suporte de x:data para poder registrar o nick"
"Você precisa de um cliente com suporte de x:data para poder registrar o apelido"
#: mod_register.erl:259 mod_register.erl:320
msgid "Choose a username and password to register with this server"
@@ -1693,7 +1693,7 @@ msgid ""
"(Jabber IDentifier) will be of the form: username@server. Please read "
"carefully the instructions to fill correctly the fields."
msgstr ""
"Esta pagina aceita criações de novas contas Jabber neste servidor. A sua JID "
"Esta pagina aceita criações de novas contas Jabber neste servidor. O seu JID "
"(Identificador Jabber) será da seguinte forma: 'usuário@servidor'. Por "
"favor, leia cuidadosamente as instruções para preencher corretamente os "
"campos."
@@ -1732,7 +1732,7 @@ msgid ""
"Some Jabber clients can store your password in the computer, but you should "
"do this only in your personal computer for safety reasons."
msgstr ""
"Alguns clientes jabber podem salvar a sua senha no seu computador. Use "
"Alguns clientes jabber podem salvar a sua senha no seu computador. Use o "
"recurso somente se você considera este computador seguro o suficiente."
#: mod_register_web.erl:256
+26 -22
View File
@@ -1,4 +1,4 @@
%%%-------------------------------------------------------------------
%-------------------------------------------------------------------
%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2013-2016, Evgeniy Khramtsov
%%% @doc
@@ -8,21 +8,22 @@
%%%-------------------------------------------------------------------
{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_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.6"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.5"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.9"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.7"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.18"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.4"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.9"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.10"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.7"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.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"}}}},
{tag, "1.0.2"}}}},
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
{tag, "1.1.0"}}}},
{tag, "1.1.1"}}}},
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
{tag, "1.1.5"}}}},
{if_var_true, pam, {p1_pam, ".*", {git, "https://github.com/processone/epam",
@@ -30,12 +31,7 @@
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
{tag, "1.0.1"}}}},
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
"527722d12d0433b837cdb92a60900c2cb5df8942"}}},
%% Forces correct dependency for riakc and allow using newer meck version)
{if_var_true, riak, {hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang",
"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
{tag, "2.4.1"}}}},
%% Elixir support, needed to run tests
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
{tag, {if_version_above, "17", "v1.2.6", "v1.1.1"}}}}},
@@ -43,7 +39,7 @@
{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.2"}}}},
{tag, "1.0.3"}}}},
{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",
@@ -60,6 +56,7 @@
luerl,
stun,
fast_yaml,
xmpp,
p1_utils,
p1_mysql,
p1_pgsql,
@@ -67,15 +64,19 @@
ezlib,
iconv]}}.
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl"]}.
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl"]}.
{erl_opts, [nowarn_deprecated_function,
{i, "include"},
{i, "deps/fast_xml/include"},
{i, "deps/xmpp/include"},
{if_var_false, debug, no_debug_info},
{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_version_above, "18", {d, 'STRONG_RAND_BYTES'}},
{if_var_true, hipe, native},
{src_dirs, [asn1, src,
{if_var_true, tools, tools},
@@ -113,10 +114,13 @@
{if_var_false, iconv, "(\"iconv\":_/_)"},
{if_var_false, odbc, "(\"odbc\":_/_)"},
{if_var_false, sqlite, "(\"sqlite3\":_/_)"},
{if_var_false, elixir, "(\"Elixir.Logger.*\":_/_)"},
{if_var_false, elixir, "(\"Elixir.*\":_/_)"},
{if_var_false, redis, "(\"eredis\":_/_)"}]}.
{eunit_compile_opts, [{i, "tools"}]}.
{eunit_compile_opts, [{i, "tools"},
{i, "include"},
{i, "deps/fast_xml/include"},
{i, "deps/xmpp/include"}]}.
{if_version_above, "17", {cover_enabled, true}}.
{cover_export_enabled, true}.
+65 -5
View File
@@ -21,6 +21,14 @@ ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) ->
end,
ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end,
IsRebar3 = case application:get_key(rebar, vsn) of
{ok, VSN} ->
[VSN1 | _] = string:tokens(VSN, "-"),
[Maj, Min, Patch] = string:tokens(VSN1, "."),
(list_to_integer(Maj) >= 3);
undefined ->
lists:keymember(mix, 1, application:loaded_applications())
end,
Cfg = case file:consult(filename:join(filename:dirname(SCRIPT), "vars.config")) of
{ok, Terms} ->
Terms;
@@ -119,12 +127,64 @@ TestConfig = case file:read_file_info(TestConfigFile) of
"-userconfig ct_config_plain " ++ TestConfigFile ++ " ";
_ ->
""
end,
end,
ResolveDepPath = case IsRebar3 of
true ->
fun("deps/" ++ Rest) ->
Slash = string:str(Rest, "/"),
Dir = "_build/default/lib/" ++
string:sub_string(Rest, 1, Slash-1),
Dir ++ string:sub_string(Rest, Slash);
(Path) ->
Path
end;
_ ->
fun(P) ->
P
end
end,
CtIncludes = case lists:keyfind(eunit_compile_opts, 1, Conf1) of
false ->
[];
{_, EunitCompOpts} ->
[[" -include ", filename:join([Cwd, ResolveDepPath(IncPath)])]
|| {i, IncPath} <- EunitCompOpts]
end,
ProcessErlOpt = fun({i, Path}) ->
{i, ResolveDepPath(Path)};
(ErlOpt) ->
ErlOpt
end,
Conf1a = ModCfg(Conf1, [erl_opts],
fun(ErlOpts) -> lists:map(ProcessErlOpt, ErlOpts) end, []),
Conf2a = [{ct_extra_params, lists:flatten(["-ct_hooks cth_surefire ", TestConfig,
CtIncludes])} | Conf1a],
Conf2 = case IsRebar3 of
true ->
DepsFun = fun(DepsList) ->
lists:filtermap(fun({rebar_elixir_plugin, _, _}) ->
false;
({DepName,_, {git,_, _} = Git}) ->
{true, {DepName, Git}};
(Dep) ->
true
end, DepsList)
end,
RB1 = ModCfg(Conf2a, [deps], DepsFun, []),
ModCfg(RB1, [plugins], fun(V) -> V -- [deps_erl_opts,
rebar_elixir_compiler,
rebar_exunit] ++
[rebar3_hex] end, []);
false ->
Conf2a
end,
Conf2 = [{ct_extra_params, "-ct_hooks cth_surefire "
++ TestConfig
++ "-include "
++ filename:join([Cwd, "tools"])} | Conf1],
Conf3 = case lists:keytake(xref_exclusions, 1, Conf2) of
{value, {_, Items2}, Rest2} ->
+27 -27
View File
@@ -36,11 +36,12 @@
acl_rule_verify/1, access_matches/3,
transform_access_rules_config/1,
parse_ip_netmask/1,
access_rules_validator/1, shaper_rules_validator/1]).
access_rules_validator/1, shaper_rules_validator/1,
normalize_spec/1, resolve_access/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("jid.hrl").
-record(acl, {aclname, aclspec}).
-record(access, {name :: aclname(),
@@ -75,17 +76,11 @@
-export_type([acl/0]).
start() ->
case catch mnesia:table_info(acl, storage_type) of
disc_copies ->
mnesia:delete_table(acl);
_ ->
ok
end,
mnesia:create_table(acl,
ejabberd_mnesia:create(?MODULE, acl,
[{ram_copies, [node()]}, {type, bag},
{local_content, true},
{attributes, record_info(fields, acl)}]),
mnesia:create_table(access,
ejabberd_mnesia:create(?MODULE, access,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, access)}]),
@@ -347,7 +342,7 @@ acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
acl_rule_verify(_Spec) ->
false.
invalid_syntax(Msg, Data) ->
throw({invalid_syntax, iolist_to_binary(io_lib:format(Msg, Data))}).
throw({invalid_syntax, (str:format(Msg, Data))}).
acl_rules_verify([{acl, Name} | Rest], true) when is_atom(Name) ->
acl_rules_verify(Rest, true);
@@ -443,30 +438,35 @@ acl_rule_matches({node_glob, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
acl_rule_matches(_ACL, _Data, _Host) ->
false.
-spec access_matches(atom()|list(), any(), global|binary()) -> any().
access_matches(all, _Data, _Host) ->
allow;
access_matches(none, _Data, _Host) ->
deny;
access_matches(Name, Data, Host) when is_atom(Name) ->
GAccess = ets:lookup(access, {Name, global}),
resolve_access(all, _Host) ->
all;
resolve_access(none, _Host) ->
none;
resolve_access(Name, Host) when is_atom(Name) ->
GAccess = mnesia:dirty_read(access, {Name, global}),
LAccess =
if Host /= global -> ets:lookup(access, {Name, Host});
if Host /= global -> mnesia:dirty_read(access, {Name, Host});
true -> []
end,
case GAccess ++ LAccess of
[] ->
deny;
[];
AccessList ->
Rules = lists:flatmap(
lists:flatmap(
fun(#access{rules = Rs}) ->
Rs
end, AccessList),
access_rules_matches(Rules, Data, Host)
end, AccessList)
end;
access_matches(Rules, Data, Host) when is_list(Rules) ->
access_rules_matches(Rules, Data, Host).
resolve_access(Rules, _Host) when is_list(Rules) ->
Rules.
-spec access_matches(atom()|list(), any(), global|binary()) -> allow|deny.
access_matches(Rules, Data, Host) ->
case resolve_access(Rules, Host) of
all -> allow;
none -> deny;
RRules -> access_rules_matches(RRules, Data, Host)
end.
-spec access_rules_matches(list(), any(), global|binary()) -> any().
@@ -484,7 +484,7 @@ access_rules_matches([], _Data, _Host, Default) ->
Default.
get_aclspecs(ACL, Host) ->
ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
mnesia:dirty_read(acl, {ACL, Host}) ++ mnesia:dirty_read(acl, {ACL, global}).
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
@@ -542,7 +542,7 @@ parse_ip_netmask(S) ->
_ -> error
end;
[IPStr, MaskStr] ->
case catch jlib:binary_to_integer(MaskStr) of
case catch binary_to_integer(MaskStr) of
Mask when is_integer(Mask), Mask >= 0 ->
case inet_parse:address(binary_to_list(IPStr)) of
{ok, {_, _, _, _} = IP} when Mask =< 32 ->
-149
View File
@@ -1,149 +0,0 @@
%%%----------------------------------------------------------------------
%%% File : adhoc.erl
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
%%% Purpose : Provide helper functions for ad-hoc commands (XEP-0050)
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
%%%
%%%
%%% 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.
%%%
%%%----------------------------------------------------------------------
-module(adhoc).
-author('henoch@dtek.chalmers.se').
-export([
parse_request/1,
produce_response/2,
produce_response/1
]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
%% Parse an ad-hoc request. Return either an adhoc_request record or
%% an {error, ErrorType} tuple.
%%
-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...", []),
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
SessionID = fxml:get_tag_attr_s(<<"sessionid">>, SubEl),
Action = fxml:get_tag_attr_s(<<"action">>, SubEl),
XData = find_xdata_el(SubEl),
#xmlel{children = AllEls} = SubEl,
Others = case XData of
false -> AllEls;
_ -> lists:delete(XData, AllEls)
end,
#adhoc_request{
lang = Lang,
node = Node,
sessionid = SessionID,
action = Action,
xdata = XData,
others = Others
};
parse_request(#iq{lang = Lang}) ->
Text = <<"Failed to parse ad-hoc command request">>,
{error, ?ERRT_BAD_REQUEST(Lang, Text)}.
%% Borrowed from mod_vcard.erl
find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls).
find_xdata_el1([]) -> false;
find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
case fxml:get_tag_attr_s(<<"xmlns">>, El) of
?NS_XDATA -> El;
_ -> find_xdata_el1(Els)
end;
find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
%% Produce a <command/> node to use as response from an adhoc_response
%% record, filling in values for language, node and session id from
%% the request.
%%
-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.
produce_response(#adhoc_request{lang = Lang, node = Node, sessionid = SessionID},
Adhoc_Response) ->
produce_response(Adhoc_Response#adhoc_response{
lang = Lang, node = Node, sessionid = SessionID
}).
%%
-spec produce_response(Adhoc_Response::adhoc_response()) -> Xmlel::xmlel().
produce_response(
#adhoc_response{
%lang = _Lang,
node = Node,
sessionid = ProvidedSessionID,
status = Status,
defaultaction = DefaultAction,
actions = Actions,
notes = Notes,
elements = Elements
}) ->
SessionID = if is_binary(ProvidedSessionID),
ProvidedSessionID /= <<"">> -> ProvidedSessionID;
true -> jlib:now_to_utc_string(p1_time_compat:timestamp())
end,
case Actions of
[] ->
ActionsEls = [];
_ ->
case DefaultAction of
<<"">> -> ActionsElAttrs = [];
_ -> ActionsElAttrs = [{<<"execute">>, DefaultAction}]
end,
ActionsEls = [
#xmlel{
name = <<"actions">>,
attrs = ActionsElAttrs,
children = [
#xmlel{name = Action, attrs = [], children = []}
|| Action <- Actions]
}
]
end,
NotesEls = lists:map(fun({Type, Text}) ->
#xmlel{
name = <<"note">>,
attrs = [{<<"type">>, Type}],
children = [{xmlcdata, Text}]
}
end, Notes),
#xmlel{
name = <<"command">>,
attrs = [
{<<"xmlns">>, ?NS_COMMANDS},
{<<"sessionid">>, SessionID},
{<<"node">>, Node},
{<<"status">>, iolist_to_binary(atom_to_list(Status))}
],
children = ActionsEls ++ NotesEls ++ Elements
}.
+10 -30
View File
@@ -73,8 +73,8 @@
-callback mech_step(any(), binary()) -> {ok, props()} |
{ok, props(), binary()} |
{continue, binary(), any()} |
{error, binary()} |
{error, binary(), binary()}.
{error, atom()} |
{error, atom(), binary()}.
start() ->
ets:new(sasl_mechanism,
@@ -102,35 +102,11 @@ register_mechanism(Mechanism, Module, PasswordType) ->
true
end.
%%% TODO: use callbacks
%%-include("ejabberd.hrl").
%%-include("jlib.hrl").
%%check_authzid(_State, Props) ->
%% AuthzId = fxml:get_attr_s(authzid, Props),
%% case jid:from_string(AuthzId) of
%% error ->
%% {error, "invalid-authzid"};
%% JID ->
%% LUser = jid:nodeprep(fxml:get_attr_s(username, Props)),
%% {U, S, R} = jid:tolower(JID),
%% case R of
%% "" ->
%% {error, "invalid-authzid"};
%% _ ->
%% case {LUser, ?MYNAME} of
%% {U, S} ->
%% ok;
%% _ ->
%% {error, "invalid-authzid"}
%% end
%% end
%% end.
check_credentials(_State, Props) ->
User = proplists:get_value(authzid, Props, <<>>),
case jid:nodeprep(User) of
error -> {error, <<"not-authorized">>};
<<"">> -> {error, <<"not-authorized">>};
error -> {error, 'not-authorized'};
<<"">> -> {error, 'not-authorized'};
_LUser -> ok
end.
@@ -159,6 +135,8 @@ server_new(Service, ServerFQDN, UserRealm, _SecFlags,
check_password = CheckPassword,
check_password_digest = CheckPasswordDigest}.
server_start(State, Mech, undefined) ->
server_start(State, Mech, <<"">>);
server_start(State, Mech, ClientIn) ->
case lists:member(Mech,
listmech(State#sasl_state.myname))
@@ -174,11 +152,13 @@ server_start(State, Mech, ClientIn) ->
server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState},
ClientIn);
_ -> {error, <<"no-mechanism">>}
_ -> {error, 'no-mechanism'}
end;
false -> {error, <<"no-mechanism">>}
false -> {error, 'no-mechanism'}
end.
server_step(State, undefined) ->
server_step(State, <<"">>);
server_step(State, ClientIn) ->
Module = State#sasl_state.mech_mod,
MechState = State#sasl_state.mech_state,
+1 -1
View File
@@ -45,7 +45,7 @@ mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
mech_step(#state{server = Server} = S, ClientIn) ->
User = iolist_to_binary([randoms:get_string(),
jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]),
integer_to_binary(p1_time_compat:unique_integer([positive]))]),
case ejabberd_auth:is_user_exists(User, Server) of
true -> mech_step(S, ClientIn);
false -> {ok, [{username, User}, {authzid, User}, {auth_module, ejabberd_auth_anonymous}]}
+6 -6
View File
@@ -80,7 +80,7 @@ mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
mech_step(#state{step = 3, nonce = Nonce} = State,
ClientIn) ->
case parse(ClientIn) of
bad -> {error, <<"bad-protocol">>};
bad -> {error, 'bad-protocol'};
KeyVals ->
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
@@ -92,11 +92,11 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
"seems invalid: ~p (checking for Host "
"~p, FQDN ~p)",
[DigestURI, State#state.host, State#state.hostfqdn]),
{error, <<"not-authorized">>, UserName};
{error, 'not-authorized', UserName};
true ->
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
case (State#state.get_password)(UserName) of
{false, _} -> {error, <<"not-authorized">>, UserName};
{false, _} -> {error, 'not-authorized', UserName};
{Passwd, AuthModule} ->
case (State#state.check_password)(UserName, UserName, <<"">>,
proplists:get_value(<<"response">>, KeyVals, <<>>),
@@ -116,8 +116,8 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
State#state{step = 5, auth_module = AuthModule,
username = UserName,
authzid = AuthzId}};
false -> {error, <<"not-authorized">>, UserName};
{false, _} -> {error, <<"not-authorized">>, UserName}
false -> {error, 'not-authorized', UserName};
{false, _} -> {error, 'not-authorized', UserName}
end
end
end
@@ -134,7 +134,7 @@ mech_step(#state{step = 5, auth_module = AuthModule,
{auth_module, AuthModule}]};
mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
{error, <<"bad-protocol">>}.
{error, 'bad-protocol'}.
parse(S) -> parse1(binary_to_list(S), "", []).
+2 -2
View File
@@ -52,9 +52,9 @@ mech_step(State, ClientIn) ->
[{username, User}, {authzid, AuthzId},
{auth_module, ejabberd_oauth}]};
_ ->
{error, <<"not-authorized">>, User}
{error, 'not-authorized', User}
end;
_ -> {error, <<"bad-protocol">>}
_ -> {error, 'bad-protocol'}
end.
prepare(ClientIn) ->
+2 -2
View File
@@ -50,9 +50,9 @@ mech_step(State, ClientIn) ->
{ok,
[{username, User}, {authzid, AuthzId},
{auth_module, AuthModule}]};
_ -> {error, <<"not-authorized">>, User}
_ -> {error, 'not-authorized', User}
end;
_ -> {error, <<"bad-protocol">>}
_ -> {error, 'bad-protocol'}
end.
prepare(ClientIn) ->
+18 -20
View File
@@ -34,8 +34,6 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-behaviour(cyrsasl).
-record(state,
@@ -67,27 +65,27 @@ mech_step(#state{step = 2} = State, ClientIn) ->
case re:split(ClientIn, <<",">>, [{return, binary}]) of
[_CBind, _AuthorizationIdentity, _UserNameAttribute, _ClientNonceAttribute, ExtensionAttribute | _]
when ExtensionAttribute /= [] ->
{error, <<"protocol-error-extension-not-supported">>};
{error, 'protocol-error-extension-not-supported'};
[CBind, _AuthorizationIdentity, UserNameAttribute, ClientNonceAttribute | _]
when (CBind == <<"y">>) or (CBind == <<"n">>) ->
case parse_attribute(UserNameAttribute) of
{error, Reason} -> {error, Reason};
{_, EscapedUserName} ->
case unescape_username(EscapedUserName) of
error -> {error, <<"protocol-error-bad-username">>};
error -> {error, 'protocol-error-bad-username'};
UserName ->
case parse_attribute(ClientNonceAttribute) of
{$r, ClientNonce} ->
{Ret, _AuthModule} = (State#state.get_password)(UserName),
case {Ret, jid:resourceprep(Ret)} of
{false, _} -> {error, <<"not-authorized">>, UserName};
{_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, <<"not-authorized">>, UserName};
{false, _} -> {error, 'not-authorized', UserName};
{_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, 'not-authorized', UserName};
{Ret, _} ->
{StoredKey, ServerKey, Salt, IterationCount} =
if is_tuple(Ret) -> Ret;
true ->
TempSalt =
crypto:rand_bytes(?SALT_LENGTH),
randoms:bytes(?SALT_LENGTH),
SaltedPassword =
scram:salted_password(Ret,
TempSalt,
@@ -101,7 +99,7 @@ mech_step(#state{step = 2} = State, ClientIn) ->
str:substr(ClientIn,
str:str(ClientIn, <<"n=">>)),
ServerNonce =
jlib:encode_base64(crypto:rand_bytes(?NONCE_LENGTH)),
jlib:encode_base64(randoms:bytes(?NONCE_LENGTH)),
ServerFirstMessage =
iolist_to_binary(
["r=",
@@ -121,11 +119,11 @@ mech_step(#state{step = 2} = State, ClientIn) ->
server_nonce = ServerNonce,
username = UserName}}
end;
_Else -> {error, <<"not-supported">>}
_Else -> {error, 'not-supported'}
end
end
end;
_Else -> {error, <<"bad-protocol">>}
_Else -> {error, 'bad-protocol'}
end;
mech_step(#state{step = 4} = State, ClientIn) ->
case str:tokens(ClientIn, <<",">>) of
@@ -163,18 +161,18 @@ mech_step(#state{step = 4} = State, ClientIn) ->
{authzid, State#state.username}],
<<"v=",
(jlib:encode_base64(ServerSignature))/binary>>};
true -> {error, <<"bad-auth">>, State#state.username}
true -> {error, 'bad-auth', State#state.username}
end;
_Else -> {error, <<"bad-protocol">>}
_Else -> {error, 'bad-protocol'}
end;
{$r, _} -> {error, <<"bad-nonce">>};
_Else -> {error, <<"bad-protocol">>}
{$r, _} -> {error, 'bad-nonce'};
_Else -> {error, 'bad-protocol'}
end;
true -> {error, <<"bad-channel-binding">>}
true -> {error, 'bad-channel-binding'}
end;
_Else -> {error, <<"bad-protocol">>}
_Else -> {error, 'bad-protocol'}
end;
_Else -> {error, <<"bad-protocol">>}
_Else -> {error, 'bad-protocol'}
end.
parse_attribute(Attribute) ->
@@ -187,11 +185,11 @@ parse_attribute(Attribute) ->
if SecondChar == $= ->
String = str:substr(Attribute, 3),
{lists:nth(1, AttributeS), String};
true -> {error, <<"bad-format second char not equal sign">>}
true -> {error, 'bad-format-second-char-not-equal-sign'}
end;
_Else -> {error, <<"bad-format first char not a letter">>}
_Else -> {error, 'bad-format-first-char-not-a-letter'}
end;
true -> {error, <<"bad-format attribute too short">>}
true -> {error, 'bad-format-attribute-too-short'}
end.
unescape_username(<<"">>) -> <<"">>;
+1 -3
View File
@@ -105,8 +105,6 @@ start_app([], _Type, _StartFlag) ->
ok.
check_app_modules(App, StartFlag) ->
{A, B, C} = p1_time_compat:timestamp(),
random:seed(A, B, C),
sleep(5000),
case application:get_key(App, modules) of
{ok, Mods} ->
@@ -140,7 +138,7 @@ exit_or_halt(Reason, StartFlag) ->
end.
sleep(N) ->
timer:sleep(random:uniform(N)).
timer:sleep(randoms:uniform(N)).
get_module_file(App, Mod) ->
BaseName = atom_to_list(Mod),
+544
View File
@@ -0,0 +1,544 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_access_permissions.erl
%%% Author : Paweł Chmielowski <pawel@process-one.net>
%%% Purpose : Administrative functions and commands
%%% Created : 7 Sep 2016 by Paweł Chmielowski <pawel@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.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_access_permissions).
-author("pawel@process-one.net").
-include("ejabberd_commands.hrl").
-include("logger.hrl").
-behaviour(gen_server).
-behavior(ejabberd_config).
%% API
-export([start_link/0,
parse_api_permissions/1,
can_access/2,
invalidate/0,
opt_type/1,
show_current_definitions/0,
register_permission_addon/2,
unregister_permission_addon/1]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {
definitions = none,
fragments_generators = []
}).
%%%===================================================================
%%% API
%%%===================================================================
-spec can_access(atom(), map()) -> allow | deny.
can_access(Cmd, CallerInfo) ->
gen_server:call(?MODULE, {can_access, Cmd, CallerInfo}).
-spec invalidate() -> ok.
invalidate() ->
gen_server:cast(?MODULE, invalidate).
-spec register_permission_addon(atom(), fun()) -> ok.
register_permission_addon(Name, Fun) ->
gen_server:call(?MODULE, {register_config_fragment_generator, Name, Fun}).
-spec unregister_permission_addon(atom()) -> ok.
unregister_permission_addon(Name) ->
gen_server:call(?MODULE, {unregister_config_fragment_generator, Name}).
-spec show_current_definitions() -> any().
show_current_definitions() ->
gen_server:call(?MODULE, show_current_definitions).
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore.
init([]) ->
{ok, #state{}}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}.
handle_call({can_access, Cmd, CallerInfo}, _From, State) ->
CallerModule = maps:get(caller_module, CallerInfo, none),
Host = maps:get(caller_host, CallerInfo, global),
{State2, Defs0} = get_definitions(State),
Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0,
Res = lists:foldl(
fun({Name, _} = Def, none) ->
case matches_definition(Def, Cmd, CallerModule, Host, CallerInfo) of
true ->
?DEBUG("Command '~p' execution allowed by rule '~s' (CallerInfo=~p)", [Cmd, Name, CallerInfo]),
allow;
_ ->
none
end;
(_, Val) ->
Val
end, none, Defs),
Res2 = case Res of
allow -> allow;
_ ->
?DEBUG("Command '~p' execution denied (CallerInfo=~p)", [Cmd, CallerInfo]),
deny
end,
{reply, Res2, State2};
handle_call(show_current_definitions, _From, State) ->
{State2, Defs} = get_definitions(State),
{reply, Defs, State2};
handle_call({register_config_fragment_generator, Name, Fun}, _From, #state{fragments_generators = Gens} = State) ->
NGens = lists:keystore(Name, 1, Gens, {Name, Fun}),
{reply, ok, State#state{fragments_generators = NGens}};
handle_call({unregister_config_fragment_generator, Name}, _From, #state{fragments_generators = Gens} = State) ->
NGens = lists:keydelete(Name, 1, Gens),
{reply, ok, State#state{fragments_generators = NGens}};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}.
handle_cast(invalidate, State) ->
{noreply, State#state{definitions = none}};
handle_cast(_Request, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}.
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term().
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec get_definitions(#state{}) -> {#state{}, any()}.
get_definitions(#state{definitions = Defs, fragments_generators = Gens} = State) ->
DefaultOptions = [{<<"console commands">>,
{[ejabberd_ctl],
[{acl, all}],
{all, none}}},
{<<"admin access">>,
{[],
[{acl, admin}],
{all, [start, stop]}}}],
NDefs = case Defs of
none ->
ApiPerms = ejabberd_config:get_option(api_permissions, fun(A) -> A end, DefaultOptions),
AllCommands = ejabberd_commands:get_commands_definition(),
Frags = lists:foldl(
fun({_Name, Generator}, Acc) ->
Acc ++ Generator()
end, [], Gens),
lists:map(
fun({Name, {From, Who, {Add, Del}}}) ->
Cmds = filter_commands_with_permissions(AllCommands, Add, Del),
{Name, {From, Who, Cmds}}
end, ApiPerms ++ Frags);
V ->
V
end,
{State#state{definitions = NDefs}, NDefs}.
matches_definition({_Name, {From, Who, What}}, Cmd, Module, Host, CallerInfo) ->
case What == all orelse lists:member(Cmd, What) of
true ->
case From == [] orelse lists:member(Module, From) of
true ->
Scope = maps:get(oauth_scope, CallerInfo, none),
lists:any(
fun({access, Access}) when Scope == none ->
acl:access_matches(Access, CallerInfo, Host) == allow;
({acl, Acl}) when Scope == none ->
acl:acl_rule_matches(Acl, CallerInfo, Host);
({oauth, Scopes, List}) when Scope /= none ->
case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of
true ->
lists:any(
fun({access, Access}) ->
acl:access_matches(Access, CallerInfo, Host) == allow;
({acl, Acl} = Acl) ->
acl:acl_rule_matches(Acl, CallerInfo, Host)
end, List);
_ ->
false
end;
(_) ->
false
end, Who);
_ ->
false
end;
_ ->
false
end.
filter_commands_with_permissions(AllCommands, Add, Del) ->
CommandsAdd = filter_commands_with_patterns(AllCommands, Add, []),
CommandsDel = filter_commands_with_patterns(CommandsAdd, Del, []),
lists:map(fun(#ejabberd_commands{name = N}) -> N end,
CommandsAdd -- CommandsDel).
filter_commands_with_patterns([], _Patterns, Acc) ->
Acc;
filter_commands_with_patterns([C | CRest], Patterns, Acc) ->
case command_matches_patterns(C, Patterns) of
true ->
filter_commands_with_patterns(CRest, Patterns, [C | Acc]);
_ ->
filter_commands_with_patterns(CRest, Patterns, Acc)
end.
command_matches_patterns(_, all) ->
true;
command_matches_patterns(_, none) ->
false;
command_matches_patterns(_, []) ->
false;
command_matches_patterns(#ejabberd_commands{tags = Tags} = C, [{tag, Tag} | Tail]) ->
case lists:member(Tag, Tags) of
true ->
true;
_ ->
command_matches_patterns(C, Tail)
end;
command_matches_patterns(#ejabberd_commands{name = Name}, [Name | _Tail]) ->
true;
command_matches_patterns(C, [_ | Tail]) ->
command_matches_patterns(C, Tail).
%%%===================================================================
%%% Options parsing code
%%%===================================================================
parse_api_permissions(Data) when is_list(Data) ->
throw({replace_with, [parse_api_permission(Name, Args) || {Name, Args} <- Data]}).
parse_api_permission(Name, Args0) ->
Args = lists:flatten(Args0),
{From, Who, What} = case key_split(Args, [{from, []}, {who, none}, {what, []}]) of
{error, Msg} ->
report_error(<<"~s inside api_permission '~s' section">>, [Msg, Name]);
Val -> Val
end,
{Name, {parse_from(Name, From), parse_who(Name, Who, oauth), parse_what(Name, What)}}.
parse_from(_Name, Module) when is_atom(Module) ->
[Module];
parse_from(Name, Modules) when is_list(Modules) ->
lists:foreach(fun(Module) when is_atom(Module) ->
ok;
(Val) ->
report_error(<<"Invalid value '~p' used inside 'from' section for api_permission '~s'">>,
[Val, Name])
end, Modules),
Modules;
parse_from(Name, Val) ->
report_error(<<"Invalid value '~p' used inside 'from' section for api_permission '~s'">>,
[Val, Name]).
parse_who(Name, Atom, ParseOauth) when is_atom(Atom) ->
parse_who(Name, [Atom], ParseOauth);
parse_who(Name, Defs, ParseOauth) when is_list(Defs) ->
lists:map(
fun([{access, Val}]) ->
try acl:access_rules_validator(Val) of
Rule ->
{access, Rule}
catch
throw:{invalid_syntax, Msg} ->
report_error(<<"Invalid access rule: '~s' used inside 'who' section for api_permission '~s'">>,
[Msg, Name]);
throw:{replace_with, NVal} ->
{access, NVal};
error:_ ->
report_error(<<"Invalid access rule '~p' used inside 'who' section for api_permission '~s'">>,
[Val, Name])
end;
([{oauth, OauthList}]) when is_list(OauthList) ->
case ParseOauth of
oauth ->
Nested = parse_who(Name, lists:flatten(OauthList), scope),
{Scopes, Rest} = lists:partition(
fun({scope, _}) -> true;
(_) -> false
end, Nested),
case Scopes of
[] ->
report_error(<<"Oauth rule must contain at least one scope rule in 'who' section for api_permission '~s'">>,
[Name]);
_ ->
{oauth, lists:foldl(fun({scope, S}, A) -> S ++ A end, [], Scopes), Rest}
end;
scope ->
report_error(<<"Oauth rule can't be embeded inside other oauth rule in 'who' section for api_permission '~s'">>,
[Name])
end;
({scope, ScopeList}) ->
case ParseOauth of
oauth ->
report_error(<<"Scope can be included only inside oauth rule in 'who' section for api_permission '~s'">>,
[Name]);
scope ->
ScopeList2 = case ScopeList of
V when is_binary(V) -> [V];
V2 when is_list(V2) -> V2;
V3 ->
report_error(<<"Invalid value for scope '~p' in 'who' section for api_permission '~s'">>,
[V3, Name])
end,
{scope, ScopeList2}
end;
(Atom) when is_atom(Atom) ->
{acl, {acl, Atom}};
([Other]) ->
try acl:normalize_spec(Other) of
Rule2 ->
{acl, Rule2}
catch
_:_ ->
report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>,
[Other, Name])
end;
(Invalid) ->
report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>,
[Invalid, Name])
end, Defs);
parse_who(Name, Val, _ParseOauth) ->
report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>,
[Val, Name]).
parse_what(Name, Binary) when is_binary(Binary) ->
parse_what(Name, [Binary]);
parse_what(Name, Defs) when is_list(Defs) ->
{A, D} = lists:foldl(
fun(Def, {Add, Del}) ->
case parse_single_what(Def) of
{error, Err} ->
report_error(<<"~s used in value '~p' in 'what' section for api_permission '~s'">>,
[Err, Def, Name]);
all ->
{case Add of none -> none; _ -> all end, Del};
{neg, all} ->
{none, all};
{neg, Value} ->
{Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end};
Value ->
{case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del}
end
end, {[], []}, Defs),
case {A, D} of
{[], _} ->
{none, all};
{A2, []} ->
{A2, none};
V ->
V
end;
parse_what(Name, Val) ->
report_error(<<"Invalid value '~p' used inside 'what' section for api_permission '~s'">>,
[Val, Name]).
parse_single_what(<<"*">>) ->
all;
parse_single_what(<<"!*">>) ->
{neg, all};
parse_single_what(<<"!", Rest/binary>>) ->
case parse_single_what(Rest) of
{neg, _} ->
{error, <<"Double negation">>};
{error, _} = Err ->
Err;
V ->
{neg, V}
end;
parse_single_what(<<"[tag:", Rest/binary>>) ->
case binary:split(Rest, <<"]">>) of
[TagName, <<"">>] ->
case parse_single_what(TagName) of
{error, _} = Err ->
Err;
V when is_atom(V) ->
{tag, V};
_ ->
{error, <<"Invalid tag">>}
end;
_ ->
{error, <<"Invalid tag">>}
end;
parse_single_what(Binary) when is_binary(Binary) ->
case is_valid_command_name(Binary) of
true ->
binary_to_atom(Binary, latin1);
_ ->
{error, <<"Invalid value">>}
end;
parse_single_what(_) ->
{error, <<"Invalid value">>}.
is_valid_command_name(<<>>) ->
false;
is_valid_command_name(Val) ->
is_valid_command_name2(Val).
is_valid_command_name2(<<>>) ->
true;
is_valid_command_name2(<<K:8, Rest/binary>>) when K >= $a andalso K =< $z orelse K == $_ ->
is_valid_command_name2(Rest);
is_valid_command_name2(_) ->
false.
key_split(Args, Fields) ->
{_, Order1, Results1, Required1} = lists:foldl(
fun({Field, Default}, {Idx, Order, Results, Required}) ->
{Idx + 1, maps:put(Field, Idx, Order), [Default | Results], Required};
(Field, {Idx, Order, Results, Required}) ->
{Idx + 1, maps:put(Field, Idx, Order), [none | Results], maps:put(Field, 1, Required)}
end, {1, #{}, [], #{}}, Fields),
key_split(Args, list_to_tuple(Results1), Order1, Required1, #{}).
key_split([], _Results, _Order, Required, _Duplicates) when map_size(Required) > 0 ->
parse_error(<<"Missing fields '~s">>, [str:join(maps:keys(Required), <<", ">>)]);
key_split([], Results, _Order, _Required, _Duplicates) ->
Results;
key_split([{Arg, Value} | Rest], Results, Order, Required, Duplicates) ->
case maps:find(Arg, Order) of
{ok, Idx} ->
case maps:is_key(Arg, Duplicates) of
false ->
Results2 = setelement(Idx, Results, Value),
key_split(Rest, Results2, Order, maps:remove(Arg, Required), maps:put(Arg, 1, Duplicates));
true ->
parse_error(<<"Duplicate field '~s'">>, [Arg])
end;
_ ->
parse_error(<<"Unknown field '~s'">>, [Arg])
end.
report_error(Format, Args) ->
throw({invalid_syntax, (str:format(Format, Args))}).
parse_error(Format, Args) ->
{error, (str:format(Format, Args))}.
opt_type(api_permissions) ->
fun parse_api_permissions/1;
opt_type(_) ->
[api_permissions].
+84 -12
View File
@@ -76,6 +76,9 @@ get_commands_spec() ->
#ejabberd_commands{name = status, tags = [server],
desc = "Get status of the ejabberd server",
module = ?MODULE, function = status,
result_desc = "Result tuple",
result_example = {ok, <<"The node ejabberd@localhost is started with status: started"
"ejabberd X.X is running in that node">>},
args = [], result = {res, restuple}},
#ejabberd_commands{name = stop, tags = [server],
desc = "Stop ejabberd gracefully",
@@ -101,11 +104,15 @@ get_commands_spec() ->
"ejabberdctl stop_kindly 60 "
"\\\"The server will stop in one minute.\\\"",
module = ?MODULE, function = stop_kindly,
args_desc = ["Seconds to wait", "Announcement to send, with quotes"],
args_example = [60, <<"Server will stop now.">>],
args = [{delay, integer}, {announcement, string}],
result = {res, rescode}},
#ejabberd_commands{name = get_loglevel, tags = [logs, server],
desc = "Get the current loglevel",
module = ejabberd_logger, function = get,
result_desc = "Tuple with the log level number, its keyword and description",
result_example = {4, <<"info">>, <<"Info">>},
args = [],
result = {leveltuple, {tuple, [{levelnumber, integer},
{levelatom, atom},
@@ -114,6 +121,10 @@ get_commands_spec() ->
#ejabberd_commands{name = set_loglevel, tags = [logs, server],
desc = "Set the loglevel (0 to 5)",
module = ?MODULE, function = set_loglevel,
args_desc = ["Integer of the desired logging level, between 1 and 5"],
args_example = [5],
result_desc = "The type of logger module used",
result_example = lager,
args = [{loglevel, integer}],
result = {logger, atom}},
@@ -121,10 +132,12 @@ get_commands_spec() ->
desc = "List modified modules that can be updated",
module = ?MODULE, function = update_list,
args = [],
result_example = ["mod_configure", "mod_vcard"],
result = {modules, {list, {module, string}}}},
#ejabberd_commands{name = update, tags = [server],
desc = "Update the given module, or use the keyword: all",
module = ?MODULE, function = update,
args_example = ["mod_vcard"],
args = [{module, string}],
result = {res, restuple}},
@@ -132,21 +145,32 @@ get_commands_spec() ->
desc = "Register a user",
policy = admin,
module = ?MODULE, function = register,
args_desc = ["Username", "Local vhost served by ejabberd", "Password"],
args_example = [<<"bob">>, <<"example.com">>, <<"SomEPass44">>],
args = [{user, binary}, {host, binary}, {password, binary}],
result = {res, restuple}},
#ejabberd_commands{name = unregister, tags = [accounts],
desc = "Unregister a user",
policy = admin,
module = ?MODULE, function = unregister,
args_desc = ["Username", "Local vhost served by ejabberd"],
args_example = [<<"bob">>, <<"example.com">>],
args = [{user, binary}, {host, binary}],
result = {res, restuple}},
#ejabberd_commands{name = registered_users, tags = [accounts],
desc = "List all registered users in HOST",
module = ?MODULE, function = registered_users,
args_desc = ["Local vhost"],
args_example = [<<"example.com">>],
result_desc = "List of registered accounts usernames",
result_example = [<<"user1">>, <<"user2">>],
args = [{host, binary}],
result = {users, {list, {username, string}}}},
#ejabberd_commands{name = registered_vhosts, tags = [server],
desc = "List all registered vhosts in SERVER",
module = ?MODULE, function = registered_vhosts,
result_desc = "List of available vhosts",
result_example = [<<"example.com">>, <<"anon.example.com">>],
args = [],
result = {vhosts, {list, {vhost, string}}}},
#ejabberd_commands{name = reload_config, tags = [server],
@@ -158,59 +182,85 @@ get_commands_spec() ->
#ejabberd_commands{name = join_cluster, tags = [cluster],
desc = "Join this node into the cluster handled by Node",
module = ?MODULE, function = join_cluster,
args_desc = ["Nodename of the node to join"],
args_example = [<<"ejabberd1@machine7">>],
args = [{node, binary}],
result = {res, rescode}},
#ejabberd_commands{name = leave_cluster, tags = [cluster],
desc = "Remove node handled by Node from the cluster",
desc = "Remove and shutdown Node from the running cluster",
longdesc = "This command can be run from any running node of the cluster, "
"even the node to be removed.",
module = ?MODULE, function = leave_cluster,
args_desc = ["Nodename of the node to kick from the cluster"],
args_example = [<<"ejabberd1@machine8">>],
args = [{node, binary}],
result = {res, rescode}},
#ejabberd_commands{name = list_cluster, tags = [cluster],
desc = "List nodes that are part of the cluster handled by Node",
module = ?MODULE, function = list_cluster,
result_example = [ejabberd1@machine7, ejabberd1@machine8],
args = [],
result = {nodes, {list, {node, atom}}}},
#ejabberd_commands{name = import_file, tags = [mnesia],
desc = "Import user data from jabberd14 spool file",
module = ?MODULE, function = import_file,
args_desc = ["Full path to the jabberd14 spool file"],
args_example = ["/var/lib/ejabberd/jabberd14.spool"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = import_dir, tags = [mnesia],
desc = "Import users data from jabberd14 spool dir",
module = ?MODULE, function = import_dir,
args_desc = ["Full path to the jabberd14 spool directory"],
args_example = ["/var/lib/ejabberd/jabberd14/"],
args = [{file, string}],
result = {res, restuple}},
#ejabberd_commands{name = import_piefxis, tags = [mnesia],
desc = "Import users data from a PIEFXIS file (XEP-0227)",
module = ejabberd_piefxis, function = import_file,
args_desc = ["Full path to the PIEFXIS file"],
args_example = ["/var/lib/ejabberd/example.com.xml"],
args = [{file, string}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis, tags = [mnesia],
desc = "Export data of all users in the server to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_server,
args_desc = ["Full path to a directory"],
args_example = ["/var/lib/ejabberd/"],
args = [{dir, string}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis_host, tags = [mnesia],
desc = "Export data of users in a host to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_host,
args_desc = ["Full path to a directory", "Vhost to export"],
args_example = ["/var/lib/ejabberd/", "example.com"],
args = [{dir, string}, {host, string}], result = {res, rescode}},
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
desc = "Export all tables as SQL queries to a file",
desc = "Delete elements in Mnesia database for a given vhost",
module = ejd2sql, function = delete,
args_desc = ["Vhost which content will be deleted in Mnesia database"],
args_example = ["example.com"],
args = [{host, string}], result = {res, rescode}},
#ejabberd_commands{name = convert_to_scram, tags = [sql],
desc = "Convert the passwords in 'users' ODBC table to SCRAM",
module = ejabberd_auth_sql, function = convert_to_scram,
args_desc = ["Vhost which users' passwords will be scrammed"],
args_example = ["example.com"],
args = [{host, binary}], result = {res, rescode}},
#ejabberd_commands{name = import_prosody, tags = [mnesia, sql, riak],
desc = "Import data from Prosody",
module = prosody2ejabberd, function = from_dir,
args_desc = ["Full path to the Prosody data directory"],
args_example = ["/var/lib/prosody/datadump/"],
args = [{dir, string}], result = {res, rescode}},
#ejabberd_commands{name = convert_to_yaml, tags = [config],
desc = "Convert the input file from Erlang to YAML format",
module = ejabberd_config, function = convert_to_yaml,
args_desc = ["Full path to the original configuration file", "And full path to final file"],
args_example = ["/etc/ejabberd/ejabberd.cfg", "/etc/ejabberd/ejabberd.yml"],
args = [{in, string}, {out, string}],
result = {res, rescode}},
@@ -221,11 +271,15 @@ get_commands_spec() ->
#ejabberd_commands{name = delete_old_messages, tags = [purge],
desc = "Delete offline messages older than DAYS",
module = ?MODULE, function = delete_old_messages,
args_desc = ["Number of days"],
args_example = [31],
args = [{days, integer}], result = {res, rescode}},
#ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL files",
desc = "Export virtual host information from Mnesia tables to SQL file",
module = ejd2sql, function = export,
args_desc = ["Vhost", "Full path to the destination SQL file"],
args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"],
args = [{host, string}, {file, string}],
result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [mnesia],
@@ -233,36 +287,54 @@ get_commands_spec() ->
longdesc = "If you provide as nodename \"self\", this "
"node will be set as its own master.",
module = ?MODULE, function = set_master,
args_desc = ["Name of the erlang node that will be considered master of this node"],
args_example = ["ejabberd@machine7"],
args = [{nodename, string}], result = {res, restuple}},
#ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
desc = "Change the erlang node name in a backup file",
module = ?MODULE, function = mnesia_change_nodename,
args_desc = ["Name of the old erlang node", "Name of the new node",
"Path to old backup file", "Path to the new backup file"],
args_example = ["ejabberd@machine1", "ejabberd@machine2",
"/var/lib/ejabberd/old.backup", "/var/lib/ejabberd/new.backup"],
args = [{oldnodename, string}, {newnodename, string},
{oldbackup, string}, {newbackup, string}],
result = {res, restuple}},
#ejabberd_commands{name = backup, tags = [mnesia],
desc = "Store the database to backup file",
module = ?MODULE, function = backup_mnesia,
args_desc = ["Full path for the destination backup file"],
args_example = ["/var/lib/ejabberd/database.backup"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = restore, tags = [mnesia],
desc = "Restore the database from backup file",
module = ?MODULE, function = restore_mnesia,
args_desc = ["Full path to the backup file"],
args_example = ["/var/lib/ejabberd/database.backup"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = dump, tags = [mnesia],
desc = "Dump the database to text file",
desc = "Dump the database to a text file",
module = ?MODULE, function = dump_mnesia,
args_desc = ["Full path for the text file"],
args_example = ["/var/lib/ejabberd/database.txt"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = dump_table, tags = [mnesia],
desc = "Dump a table to text file",
desc = "Dump a table to a text file",
module = ?MODULE, function = dump_table,
args_desc = ["Full path for the text file", "Table name"],
args_example = ["/var/lib/ejabberd/table-muc-registered.txt", "muc_registered"],
args = [{file, string}, {table, string}], result = {res, restuple}},
#ejabberd_commands{name = load, tags = [mnesia],
desc = "Restore the database from text file",
desc = "Restore the database from a text file",
module = ?MODULE, function = load_mnesia,
args_desc = ["Full path to the text file"],
args_example = ["/var/lib/ejabberd/database.txt"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = install_fallback, tags = [mnesia],
desc = "Install the database from a fallback file",
module = ?MODULE, function = install_fallback_mnesia,
args_desc = ["Full path to the fallback file"],
args_example = ["/var/lib/ejabberd/database.fallback"],
args = [{file, string}], result = {res, restuple}}
].
@@ -302,8 +374,8 @@ set_loglevel(LogLevel) ->
%%%
stop_kindly(DelaySeconds, AnnouncementTextString) ->
Subject = list_to_binary(io_lib:format("Server stop in ~p seconds!", [DelaySeconds])),
WaitingDesc = list_to_binary(io_lib:format("Waiting ~p seconds", [DelaySeconds])),
Subject = (str:format("Server stop in ~p seconds!", [DelaySeconds])),
WaitingDesc = (str:format("Waiting ~p seconds", [DelaySeconds])),
AnnouncementText = list_to_binary(AnnouncementTextString),
Steps = [
{"Stopping ejabberd port listeners",
@@ -337,8 +409,7 @@ stop_kindly(DelaySeconds, AnnouncementTextString) ->
ok.
send_service_message_all_mucs(Subject, AnnouncementText) ->
Message = list_to_binary(
io_lib:format("~s~n~s", [Subject, AnnouncementText])),
Message = str:format("~s~n~s", [Subject, AnnouncementText]),
lists:foreach(
fun(ServerHost) ->
MUCHost = gen_mod:get_module_opt_host(
@@ -402,8 +473,9 @@ registered_vhosts() ->
reload_config() ->
ejabberd_config:reload_file(),
acl:start(),
shaper:start().
acl:load_from_config(),
shaper:load_from_config(),
ejabberd_access_permissions:invalidate().
%%%
%%% Cluster management
+2 -4
View File
@@ -43,7 +43,6 @@
start(normal, _Args) ->
ejabberd_logger:start(),
write_pid_file(),
jid:start(),
start_apps(),
start_elixir_application(),
ejabberd:check_app(ejabberd),
@@ -51,6 +50,7 @@ start(normal, _Args) ->
db_init(),
start(),
translate:start(),
ejabberd_access_permissions:start_link(),
ejabberd_ctl:init(),
ejabberd_commands:init(),
ejabberd_admin:start(),
@@ -76,7 +76,6 @@ 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;
@@ -223,8 +222,7 @@ start_apps() ->
ejabberd:start_app(ssl),
ejabberd:start_app(fast_yaml),
ejabberd:start_app(fast_tls),
ejabberd:start_app(fast_xml),
ejabberd:start_app(stringprep),
ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab).
opt_type(net_ticktime) ->
+15 -10
View File
@@ -36,8 +36,8 @@
check_password/6, check_password_with_authmodule/4,
check_password_with_authmodule/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2, export/1, import/1,
get_vh_registered_users_number/1, import/3,
get_vh_registered_users/2, export/1, import_info/0,
get_vh_registered_users_number/1, import/5, import_start/2,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, get_password_with_authmodule/2,
is_user_exists/2, is_user_exists_in_other_modules/3,
@@ -438,15 +438,20 @@ auth_modules(Server) ->
export(Server) ->
ejabberd_auth_mnesia:export(Server).
import(Server) ->
ejabberd_auth_mnesia:import(Server).
import_info() ->
[{<<"users">>, 3}].
import(Server, mnesia, Passwd) ->
ejabberd_auth_mnesia:import(Server, mnesia, Passwd);
import(Server, riak, Passwd) ->
ejabberd_auth_riak:import(Server, riak, Passwd);
import(_, _, _) ->
pass.
import_start(_LServer, mnesia) ->
ejabberd_auth_mnesia:init_db();
import_start(_LServer, _) ->
ok.
import(Server, {sql, _}, mnesia, <<"users">>, Fields) ->
ejabberd_auth_mnesia:import(Server, Fields);
import(Server, {sql, _}, riak, <<"users">>, Fields) ->
ejabberd_auth_riak:import(Server, Fields);
import(_LServer, {sql, _}, sql, <<"users">>, _) ->
ok.
opt_type(auth_method) ->
fun (V) when is_list(V) ->
+4 -3
View File
@@ -50,8 +50,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("jid.hrl").
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
@@ -60,7 +59,7 @@
start(Host) ->
%% TODO: Check cluster mode
mnesia:create_table(anonymous, [{ram_copies, [node()]},
ejabberd_mnesia:create(?MODULE, anonymous, [{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, anonymous)}]),
%% The hooks are needed to add / remove users from the anonymous tables
@@ -140,6 +139,7 @@ remove_connection(SID, LUser, LServer) ->
mnesia:transaction(F).
%% Register connection
-spec register_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
register_connection(SID,
#jid{luser = LUser, lserver = LServer}, Info) ->
AuthModule = proplists:get_value(auth_module, Info, undefined),
@@ -156,6 +156,7 @@ register_connection(SID,
end.
%% Remove an anonymous user from the anonymous users table
-spec unregister_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any().
unregister_connection(SID,
#jid{luser = LUser, lserver = LServer}, _) ->
purge_hook(anonymous_user_exist(LUser, LServer), LUser,
+16 -20
View File
@@ -36,12 +36,12 @@
-export([start/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users/2, init_db/0,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, export/1, import/1,
import/3, plain_password_required/0, opt_type/1]).
remove_user/3, store_type/0, export/1, import/2,
plain_password_required/0, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -59,17 +59,20 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
mnesia:create_table(passwd,
[{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
mnesia:create_table(reg_users_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]),
init_db(),
update_table(),
update_reg_users_counter_table(Host),
maybe_alert_password_scrammed_without_option(),
ok.
init_db() ->
ejabberd_mnesia:create(?MODULE, passwd,
[{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
ejabberd_mnesia:create(?MODULE, reg_users_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]).
update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Size = length(Set),
@@ -450,7 +453,7 @@ password_to_scram(Password) ->
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = crypto:rand_bytes(?SALT_LENGTH),
Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
@@ -493,16 +496,9 @@ export(_Server) ->
[]
end}].
import(LServer) ->
[{<<"select username, password from users;">>,
fun([LUser, Password]) ->
#passwd{us = {LUser, LServer}, password = Password}
end}].
import(_LServer, mnesia, #passwd{} = P) ->
mnesia:dirty_write(P);
import(_, _, _) ->
pass.
import(LServer, [LUser, Password, _TimeStamp]) ->
mnesia:dirty_write(
#passwd{us = {LUser, LServer}, password = Password}).
opt_type(auth_password_format) -> fun (V) -> V end;
opt_type(_) -> [auth_password_format].
+11 -7
View File
@@ -27,6 +27,8 @@
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
@@ -39,8 +41,8 @@
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, export/1, import/3,
plain_password_required/0]).
remove_user/3, store_type/0, export/1, import/2,
plain_password_required/0, opt_type/1]).
-export([passwd_schema/0]).
-include("ejabberd.hrl").
@@ -270,7 +272,7 @@ password_to_scram(Password) ->
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = crypto:rand_bytes(?SALT_LENGTH),
Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
@@ -301,7 +303,9 @@ export(_Server) ->
[]
end}].
import(LServer, riak, #passwd{} = Passwd) ->
ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]);
import(_, _, _) ->
pass.
import(LServer, [LUser, Password, _TimeStamp]) ->
Passwd = #passwd{us = {LUser, LServer}, password = Password},
ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]).
opt_type(auth_password_format) -> fun (V) -> V end;
opt_type(_) -> [auth_password_format].
+1 -1
View File
@@ -406,7 +406,7 @@ password_to_scram(Password) ->
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = crypto:rand_bytes(?SALT_LENGTH),
Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
File diff suppressed because it is too large Load Diff
+1108 -1540
View File
File diff suppressed because it is too large Load Diff
+72 -197
View File
@@ -41,31 +41,17 @@
-export([create_captcha/6, build_captcha_html/2,
check_captcha/2, process_reply/1, process/2,
is_feature_available/0, create_captcha_x/5,
create_captcha_x/6, opt_type/1]).
-include("jlib.hrl").
opt_type/1]).
-include("xmpp.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_http.hrl").
-define(VFIELD(Type, Var, Value),
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
children =
[#xmlel{name = <<"value">>, attrs = [],
children = [Value]}]}).
-define(CAPTCHA_TEXT(Lang),
translate:translate(Lang,
<<"Enter the text you see">>)).
-define(CAPTCHA_LIFETIME, 120000).
-define(LIMIT_PERIOD, 60*1000*1000).
-type error() :: efbig | enodata | limit | malformed_image | timeout.
-type image_error() :: efbig | enodata | limit | malformed_image | timeout.
-record(state, {limits = treap:empty() :: treap:treap()}).
@@ -79,188 +65,80 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
-spec captcha_text(undefined | binary()) -> binary().
captcha_text(Lang) ->
translate:translate(Lang, <<"Enter the text you see">>).
-spec mk_ocr_field(binary() | undefined, binary(), binary()) -> xdata_field().
mk_ocr_field(Lang, CID, Type) ->
URI = #media_uri{type = Type, uri = <<"cid:", CID/binary>>},
#xdata_field{var = <<"ocr">>,
type = 'text-single',
label = captcha_text(Lang),
required = true,
sub_els = [#media{uri = [URI]}]}.
mk_field(Type, Var, Value) ->
#xdata_field{type = Type, var = Var, values = [Value]}.
-spec create_captcha(binary(), jid(), jid(),
binary(), any(), any()) -> {error, error()} |
{ok, binary(), [xmlel()]}.
binary(), any(), any()) -> {error, image_error()} |
{ok, binary(), [text()], [xmlel()]}.
create_captcha(SID, From, To, Lang, Limiter, Args) ->
case create_image(Limiter) of
{ok, Type, Key, Image} ->
Id = <<(randoms:get_string())/binary>>,
B64Image = jlib:encode_base64((Image)),
JID = jid:to_string(From),
CID = <<"sha1+", (p1_sha:sha(Image))/binary,
"@bob.xmpp.org">>,
Data = #xmlel{name = <<"data">>,
attrs =
[{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
{<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
children = [{xmlcdata, B64Image}]},
Captcha = #xmlel{name = <<"captcha">>,
attrs = [{<<"xmlns">>, ?NS_CAPTCHA}],
children =
[#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>, ?NS_XDATA},
{<<"type">>, <<"form">>}],
children =
[?VFIELD(<<"hidden">>,
<<"FORM_TYPE">>,
{xmlcdata, ?NS_CAPTCHA}),
?VFIELD(<<"hidden">>, <<"from">>,
{xmlcdata,
jid:to_string(To)}),
?VFIELD(<<"hidden">>,
<<"challenge">>,
{xmlcdata, Id}),
?VFIELD(<<"hidden">>, <<"sid">>,
{xmlcdata, SID}),
#xmlel{name = <<"field">>,
attrs =
[{<<"var">>, <<"ocr">>},
{<<"label">>,
?CAPTCHA_TEXT(Lang)}],
children =
[#xmlel{name =
<<"required">>,
attrs = [],
children = []},
#xmlel{name =
<<"media">>,
attrs =
[{<<"xmlns">>,
?NS_MEDIA}],
children =
[#xmlel{name
=
<<"uri">>,
attrs
=
[{<<"type">>,
Type}],
children
=
[{xmlcdata,
<<"cid:",
CID/binary>>}]}]}]}]}]},
BodyString1 = translate:translate(Lang,
<<"Your messages to ~s are being blocked. "
"To unblock them, visit ~s">>),
BodyString = iolist_to_binary(io_lib:format(BodyString1,
[JID, get_url(Id)])),
Body = #xmlel{name = <<"body">>, attrs = [],
children = [{xmlcdata, BodyString}]},
OOB = #xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_OOB}],
children =
[#xmlel{name = <<"url">>, attrs = [],
children = [{xmlcdata, get_url(Id)}]}]},
CID = <<"sha1+", (p1_sha:sha(Image))/binary, "@bob.xmpp.org">>,
Data = #bob_data{cid = CID, 'max-age' = 0, type = Type,
data = Image},
Fs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA),
mk_field(hidden, <<"from">>, jid:to_string(To)),
mk_field(hidden, <<"challenge">>, Id),
mk_field(hidden, <<"sid">>, SID),
mk_ocr_field(Lang, CID, Type)],
X = #xdata{type = form, fields = Fs},
Captcha = #xcaptcha{xdata = X},
BodyString = {<<"Your messages to ~s are being blocked. "
"To unblock them, visit ~s">>, [JID, get_url(Id)]},
Body = xmpp:mk_text(BodyString, Lang),
OOB = #oob_x{url = get_url(Id)},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
{remove_id, Id}),
ets:insert(captcha,
#captcha{id = Id, pid = self(), key = Key, tref = Tref,
args = Args}),
{ok, Id, [Body, OOB, Captcha, Data]};
{ok, Id, Body, [OOB, Captcha, Data]};
Err -> Err
end.
-spec create_captcha_x(binary(), jid(), binary(),
any(), [xmlel()]) -> {ok, [xmlel()]} |
{error, error()}.
-spec create_captcha_x(binary(), jid(), binary(), any(), xdata()) ->
{ok, xdata()} | {error, image_error()}.
create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
-spec create_captcha_x(binary(), jid(), binary(),
any(), [xmlel()], [xmlel()]) -> {ok, [xmlel()]} |
{error, error()}.
create_captcha_x(SID, To, Lang, Limiter, HeadEls,
TailEls) ->
create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) ->
case create_image(Limiter) of
{ok, Type, Key, Image} ->
Id = <<(randoms:get_string())/binary>>,
B64Image = jlib:encode_base64((Image)),
CID = <<"sha1+", (p1_sha:sha(Image))/binary,
"@bob.xmpp.org">>,
Data = #xmlel{name = <<"data">>,
attrs =
[{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
{<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
children = [{xmlcdata, B64Image}]},
CID = <<"sha1+", (p1_sha:sha(Image))/binary, "@bob.xmpp.org">>,
Data = #bob_data{cid = CID, 'max-age' = 0, type = Type, data = Image},
HelpTxt = translate:translate(Lang,
<<"If you don't see the CAPTCHA image here, "
"visit the web page.">>),
Imageurl = get_url(<<Id/binary, "/image">>),
Captcha = #xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>, ?NS_XDATA},
{<<"type">>, <<"form">>}],
children =
[?VFIELD(<<"hidden">>, <<"FORM_TYPE">>,
{xmlcdata, ?NS_CAPTCHA})
| HeadEls]
++
[#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"fixed">>}],
children =
[#xmlel{name = <<"value">>,
attrs = [],
children =
[{xmlcdata,
HelpTxt}]}]},
#xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"hidden">>},
{<<"var">>, <<"captchahidden">>}],
children =
[#xmlel{name = <<"value">>,
attrs = [],
children =
[{xmlcdata,
<<"workaround-for-psi">>}]}]},
#xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"text-single">>},
{<<"label">>,
translate:translate(Lang,
<<"CAPTCHA web page">>)},
{<<"var">>, <<"url">>}],
children =
[#xmlel{name = <<"value">>,
attrs = [],
children =
[{xmlcdata,
Imageurl}]}]},
?VFIELD(<<"hidden">>, <<"from">>,
{xmlcdata, jid:to_string(To)}),
?VFIELD(<<"hidden">>, <<"challenge">>,
{xmlcdata, Id}),
?VFIELD(<<"hidden">>, <<"sid">>,
{xmlcdata, SID}),
#xmlel{name = <<"field">>,
attrs =
[{<<"var">>, <<"ocr">>},
{<<"label">>,
?CAPTCHA_TEXT(Lang)}],
children =
[#xmlel{name = <<"required">>,
attrs = [], children = []},
#xmlel{name = <<"media">>,
attrs =
[{<<"xmlns">>,
?NS_MEDIA}],
children =
[#xmlel{name =
<<"uri">>,
attrs =
[{<<"type">>,
Type}],
children =
[{xmlcdata,
<<"cid:",
CID/binary>>}]}]}]}]
++ TailEls},
NewFs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA)|Fs] ++
[#xdata_field{type = fixed, values = [HelpTxt]},
#xdata_field{type = hidden, var = <<"captchahidden">>,
values = [<<"workaround-for-psi">>]},
#xdata_field{type = 'text-single', var = <<"url">>,
label = translate:translate(
Lang, <<"CAPTCHA web page">>),
values = [Imageurl]},
mk_field(hidden, <<"from">>, jid:to_string(To)),
mk_field(hidden, <<"challenge">>, Id),
mk_field(hidden, <<"sid">>, SID),
mk_ocr_field(Lang, CID, Type)],
Captcha = X#xdata{type = form, fields = NewFs},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
{remove_id, Id}),
ets:insert(captcha,
@@ -281,7 +159,7 @@ build_captcha_html(Id, Lang) ->
attrs =
[{<<"src">>, get_url(<<Id/binary, "/image">>)}],
children = []},
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
TextEl = {xmlcdata, captcha_text(Lang)},
IdEl = #xmlel{name = <<"input">>,
attrs =
[{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>},
@@ -317,27 +195,24 @@ build_captcha_html(Id, Lang) ->
_ -> captcha_not_found
end.
-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
-spec process_reply(xmpp_element()) -> ok | {error, bad_match | not_found | malformed}.
process_reply(#xmlel{} = El) ->
case fxml:get_subtag(El, <<"x">>) of
false -> {error, malformed};
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
case catch {proplists:get_value(<<"challenge">>,
Fields),
proplists:get_value(<<"ocr">>, Fields)}
of
{[Id | _], [OCR | _]} ->
case check_captcha(Id, OCR) of
captcha_valid -> ok;
captcha_non_valid -> {error, bad_match};
captcha_not_found -> {error, not_found}
end;
_ -> {error, malformed}
end
process_reply(#xdata{} = X) ->
case {xmpp_util:get_xdata_values(<<"challenge">>, X),
xmpp_util:get_xdata_values(<<"ocr">>, X)} of
{[Id], [OCR]} ->
case check_captcha(Id, OCR) of
captcha_valid -> ok;
captcha_non_valid -> {error, bad_match};
captcha_not_found -> {error, not_found}
end;
_ ->
{error, malformed}
end;
process_reply(_) -> {error, malformed}.
process_reply(#xcaptcha{xdata = #xdata{} = X}) ->
process_reply(X);
process_reply(_) ->
{error, malformed}.
process(_Handlers,
#request{method = 'GET', lang = Lang,
@@ -515,7 +390,7 @@ get_url(Str) ->
end.
get_transfer_protocol(PortString) ->
PortNumber = jlib:binary_to_integer(PortString),
PortNumber = binary_to_integer(PortString),
PortListeners = get_port_listeners(PortNumber),
get_captcha_transfer_protocol(PortListeners).
+48 -11
View File
@@ -218,23 +218,26 @@
get_command_format/1,
get_command_format/2,
get_command_format/3,
get_command_policy_and_scope/1,
get_command_policy_and_scope/1,
get_command_definition/1,
get_command_definition/2,
get_tags_commands/0,
get_tags_commands/1,
get_exposed_commands/0,
get_exposed_commands/0,
register_commands/1,
unregister_commands/1,
expose_commands/1,
unregister_commands/1,
expose_commands/1,
execute_command/2,
execute_command/3,
execute_command/4,
execute_command/5,
execute_command/6,
opt_type/1,
get_commands_spec/0
]).
opt_type/1,
get_commands_spec/0,
get_commands_definition/0,
get_commands_definition/1,
execute_command2/3,
execute_command2/4]).
-include("ejabberd_commands.hrl").
-include("ejabberd.hrl").
@@ -274,13 +277,18 @@ get_commands_spec() ->
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result_example = ok}].
init() ->
mnesia:create_table(ejabberd_commands,
try mnesia:transform_table(ejabberd_commands, ignore,
record_info(fields, ejabberd_commands))
catch exit:{aborted, {no_exists, _}} -> ok
end,
ejabberd_mnesia:create(?MODULE, ejabberd_commands,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, ejabberd_commands)},
{type, bag}]),
mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
register_commands(get_commands_spec()).
register_commands(get_commands_spec()),
ejabberd_access_permissions:register_permission_addon(?MODULE, fun permission_addon/0).
-spec register_commands([ejabberd_commands()]) -> ok.
@@ -296,7 +304,9 @@ register_commands(Commands) ->
mnesia:dirty_write(Command)
%% ?DEBUG("This command is already defined:~n~p", [Command])
end,
Commands).
Commands),
ejabberd_access_permissions:invalidate(),
ok.
-spec unregister_commands([ejabberd_commands()]) -> ok.
@@ -306,7 +316,9 @@ unregister_commands(Commands) ->
fun(Command) ->
mnesia:dirty_delete_object(Command)
end,
Commands).
Commands),
ejabberd_access_permissions:invalidate(),
ok.
%% @doc Expose command through ejabberd ReST API.
%% Pass a list of command names or policy to expose.
@@ -427,6 +439,9 @@ get_command_definition(Name, Version) ->
_E -> throw({error, unknown_command})
end.
get_commands_definition() ->
get_commands_definition(?DEFAULT_VERSION).
-spec get_commands_definition(integer()) -> [ejabberd_commands()].
% @doc Returns all commands for a given API version
@@ -448,6 +463,18 @@ get_commands_definition(Version) ->
end,
lists:foldl(F, [], L).
execute_command2(Name, Arguments, CallerInfo) ->
execute_command2(Name, Arguments, CallerInfo, ?DEFAULT_VERSION).
execute_command2(Name, Arguments, CallerInfo, Version) ->
Command = get_command_definition(Name, Version),
case ejabberd_access_permissions:can_access(Name, CallerInfo) of
allow ->
do_execute_command(Command, Arguments);
_ ->
throw({error, access_rules_unauthorized})
end.
%% @spec (Name::atom(), Arguments) -> ResultTerm
%% where
%% Arguments = [any()]
@@ -811,6 +838,8 @@ is_admin(_Name, admin, _Extra) ->
true;
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
false;
is_admin(_Name, Map, _extra) when is_map(Map) ->
true;
is_admin(Name, Auth, Extra) ->
{ACLInfo, Server} = case Auth of
{U, S, _, _} ->
@@ -832,6 +861,14 @@ is_admin(Name, Auth, Extra) ->
deny -> false
end.
permission_addon() ->
[{<<"'commands' option compatibility shim">>,
{[],
[{access, ejabberd_config:get_option(commands_admin_access,
fun(V) -> V end,
none)}],
{get_exposed_commands(), []}}}].
opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
opt_type(commands) ->
fun(V) when is_list(V) -> V end;
+3 -3
View File
@@ -5,7 +5,7 @@
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
%%% 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
@@ -41,7 +41,7 @@
-define(SPAN(N, V), ?TAG_R(span, ??N, V)).
-define(STR(A), ?SPAN(str,[<<"\"">>, A, <<"\"">>])).
-define(NUM(A), ?SPAN(num,jlib:integer_to_binary(A))).
-define(NUM(A), ?SPAN(num,integer_to_binary(A))).
-define(FIELD(A), ?SPAN(field,A)).
-define(ID(A), ?SPAN(id,A)).
-define(OP(A), ?SPAN(op,A)).
@@ -171,7 +171,7 @@ xml_gen({Name, integer}, Int, Indent, HTMLOutput) ->
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1,
[?XML_L(integer, Indent, 2, ?ID(jlib:integer_to_binary(Int)))])])];
[?XML_L(integer, Indent, 2, ?ID(integer_to_binary(Int)))])])];
xml_gen({Name, string}, Str, Indent, HTMLOutput) ->
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
+27 -13
View File
@@ -29,16 +29,16 @@
-export([start/0, load_file/1, reload_file/0, read_file/1,
add_global_option/2, add_local_option/2,
get_global_option/2, get_local_option/2,
get_global_option/3, get_local_option/3,
get_option/2, get_option/3, add_option/2, 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,
is_elixir_enabled/0]).
get_global_option/3, get_local_option/3,
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,
is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1]).
-export([start/2]).
@@ -104,7 +104,7 @@ mnesia_init() ->
_ ->
ok
end,
mnesia:create_table(local_config,
ejabberd_mnesia:create(?MODULE, local_config,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, local_config)}]),
@@ -178,7 +178,8 @@ read_file(File, Opts) ->
-spec load_file(string()) -> ok.
load_file(File) ->
State = read_file(File),
State0 = read_file(File),
State = validate_opts(State0),
set_opts(State).
-spec reload_file() -> ok.
@@ -892,6 +893,19 @@ v_db(Mod, Type) ->
[] -> erlang:error(badarg)
end.
-spec v_dbs(module()) -> [atom()].
v_dbs(Mod) ->
lists:flatten(ets:match(module_db, {Mod, '$1'})).
-spec v_dbs_mods(module()) -> [module()].
v_dbs_mods(Mod) ->
lists:map(fun([M]) ->
binary_to_atom(<<(atom_to_binary(Mod, utf8))/binary, "_",
(atom_to_binary(M, utf8))/binary>>, utf8)
end, ets:match(module_db, {Mod, '$1'})).
-spec default_db(binary(), module()) -> atom().
default_db(Host, Module) ->
@@ -1286,7 +1300,7 @@ convert_table_to_binary(Tab, Fields, Type, DetectFun, ConvertFun) ->
?INFO_MSG("Converting '~s' table from strings to binaries.", [Tab]),
TmpTab = list_to_atom(atom_to_list(Tab) ++ "_tmp_table"),
catch mnesia:delete_table(TmpTab),
case mnesia:create_table(TmpTab,
case ejabberd_mnesia:create(?MODULE, TmpTab,
[{disc_only_copies, [node()]},
{type, Type},
{local_content, true},
+10 -5
View File
@@ -311,7 +311,7 @@ try_call_command(Args, Auth, AccessCommands, Version) ->
end.
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
call_command([CmdString | Args], Auth, AccessCommands, Version) ->
call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
CmdStringU = ejabberd_regexp:greplace(
list_to_binary(CmdString), <<"-">>, <<"_">>),
Command = list_to_atom(binary_to_list(CmdStringU)),
@@ -321,10 +321,15 @@ 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,
Auth, Command,
ArgsFormatted,
Version),
CI = case Auth of
{U, S, _, _} -> #{usr => {U, S, <<"">>}, caller_host => S};
_ -> #{}
end,
CI2 = CI#{caller_module => ?MODULE},
Result = ejabberd_commands:execute_command2(Command,
ArgsFormatted,
CI2,
Version),
format_result(Result, ResultFormat);
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
{NumCompa, TextCompa} =
+17
View File
@@ -42,6 +42,7 @@
change_shaper/2,
monitor/1,
get_sockmod/1,
get_transport/1,
get_peer_certificate/1,
get_verify_result/1,
close/1,
@@ -118,6 +119,9 @@ monitor(FsmRef) -> erlang:monitor(process, FsmRef).
get_sockmod(FsmRef) ->
gen_server:call(FsmRef, get_sockmod).
get_transport(FsmRef) ->
gen_server:call(FsmRef, get_transport).
get_peer_certificate(FsmRef) ->
gen_server:call(FsmRef, get_peer_certificate).
@@ -186,6 +190,19 @@ handle_call({change_shaper, Shaper}, _From, State) ->
handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_transport, _From, State) ->
Reply = case State#state.sockmod of
gen_tcp -> tcp;
fast_tls -> tls;
ezlib ->
case ezlib:get_sockmod(State#state.socket) of
tcp -> tcp_zlib;
tls -> tls_zlib
end;
ejabberd_http_bind -> http_bind;
ejabberd_http_ws -> websocket
end,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_peer_certificate, _From, State) ->
Reply = fast_tls:get_peer_certificate(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
+15 -12
View File
@@ -39,7 +39,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@@ -257,7 +257,7 @@ process_header(State, Data) ->
request_headers = add_header(Name, Auth, State)};
{ok,
{http_header, _, 'Content-Length' = Name, _, SLen}} ->
case catch jlib:binary_to_integer(SLen) of
case catch binary_to_integer(SLen) of
Len when is_integer(Len) ->
State#state{request_content_length = Len,
request_headers = add_header(Name, SLen, State)};
@@ -332,10 +332,10 @@ get_transfer_protocol(SockMod, HostPort) ->
case {SockMod, PortList} of
{gen_tcp, []} -> {Host, 80, http};
{gen_tcp, [Port]} ->
{Host, jlib:binary_to_integer(Port), http};
{Host, binary_to_integer(Port), http};
{fast_tls, []} -> {Host, 443, https};
{fast_tls, [Port]} ->
{Host, jlib:binary_to_integer(Port), https}
{Host, binary_to_integer(Port), https}
end.
%% XXX bard: search through request handlers looking for one that
@@ -396,7 +396,9 @@ extract_path_query(#state{request_method = Method,
socket = _Socket} = State)
when (Method =:= 'POST' orelse Method =:= 'PUT') andalso
is_integer(Len) ->
{NewState, Data} = recv_data(State, Len),
case recv_data(State, Len) of
error -> {State, false};
{NewState, Data} ->
?DEBUG("client data: ~p~n", [Data]),
case catch url_decode_q_split(Path) of
{'EXIT', _} -> {NewState, false};
@@ -408,6 +410,7 @@ extract_path_query(#state{request_method = Method,
LQ -> LQ
end,
{NewState, {LPath, LQuery, Data}}
end
end;
extract_path_query(State) ->
{State, false}.
@@ -525,7 +528,7 @@ recv_data(State, Len, Acc) ->
recv_data(State, Len - byte_size(Data), <<Acc/binary, Data/binary>>);
Err ->
?DEBUG("Cannot receive HTTP data: ~p", [Err]),
<<"">>
error
end;
_ ->
Trail = (State#state.trail),
@@ -547,12 +550,12 @@ make_xhtml_output(State, Status, Headers, XHTML) ->
of
{value, _} ->
[{<<"Content-Length">>,
iolist_to_binary(integer_to_list(byte_size(Data)))}
integer_to_binary(byte_size(Data))}
| Headers];
_ ->
[{<<"Content-Type">>, <<"text/html; charset=utf-8">>},
{<<"Content-Length">>,
iolist_to_binary(integer_to_list(byte_size(Data)))}
integer_to_binary(byte_size(Data))}
| Headers]
end,
HeadersOut = case {State#state.request_version,
@@ -574,7 +577,7 @@ make_xhtml_output(State, Status, Headers, XHTML) ->
end,
HeadersOut),
SL = [Version,
iolist_to_binary(integer_to_list(Status)), <<" ">>,
integer_to_binary(Status), <<" ">>,
code_to_phrase(Status), <<"\r\n">>],
Data2 = case State#state.request_method of
'HEAD' -> <<"">>;
@@ -592,12 +595,12 @@ make_text_output(State, Status, Reason, Headers, Text) ->
of
{value, _} ->
[{<<"Content-Length">>,
jlib:integer_to_binary(byte_size(Data))}
integer_to_binary(byte_size(Data))}
| Headers];
_ ->
[{<<"Content-Type">>, <<"text/html; charset=utf-8">>},
{<<"Content-Length">>,
jlib:integer_to_binary(byte_size(Data))}
integer_to_binary(byte_size(Data))}
| Headers]
end,
HeadersOut = case {State#state.request_version,
@@ -622,7 +625,7 @@ make_text_output(State, Status, Reason, Headers, Text) ->
_ -> Reason
end,
SL = [Version,
jlib:integer_to_binary(Status), <<" ">>,
integer_to_binary(Status), <<" ">>,
NewReason, <<"\r\n">>],
Data2 = case State#state.request_method of
'HEAD' -> <<"">>;
+10 -16
View File
@@ -62,7 +62,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@@ -125,8 +125,6 @@
%% Wait 100ms before continue processing, to allow the client provide more related stanzas.
-define(BOSH_VERSION, <<"1.8">>).
-define(NS_CLIENT, <<"jabber:client">>).
-define(NS_BOSH, <<"urn:xmpp:xbosh">>).
-define(NS_HTTP_BIND,
@@ -856,7 +854,7 @@ rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) ->
%% We did not miss any packet, we can process it immediately:
NewRid == OldRid + 1 ->
case catch
jlib:binary_to_integer(fxml:get_attr_s(<<"pause">>,
binary_to_integer(fxml:get_attr_s(<<"pause">>,
Attrs))
of
{'EXIT', _} -> {true, 0};
@@ -974,21 +972,17 @@ prepare_outpacket_response(#http_bind{id = Sid,
[{<<"xmlns">>, ?NS_HTTP_BIND},
{<<"sid">>, Sid},
{<<"wait">>,
iolist_to_binary(integer_to_list(Wait))},
integer_to_binary(Wait)},
{<<"requests">>,
iolist_to_binary(integer_to_list(Hold
+
1))},
integer_to_binary(Hold + 1)},
{<<"inactivity">>,
iolist_to_binary(integer_to_list(trunc(MaxInactivity
/
1000)))},
integer_to_binary(
trunc(MaxInactivity / 1000))},
{<<"maxpause">>,
iolist_to_binary(integer_to_list(MaxPause))},
integer_to_binary(MaxPause)},
{<<"polling">>,
iolist_to_binary(integer_to_list(trunc((?MIN_POLLING)
/
1000000)))},
integer_to_binary(
trunc((?MIN_POLLING) / 1000000))},
{<<"ver">>, ?BOSH_VERSION},
{<<"from">>, From},
{<<"secure">>, <<"true">>}]
@@ -1122,7 +1116,7 @@ parse_request(Data, PayloadSize, MaxStanzaSize) ->
if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request};
true ->
case catch
jlib:binary_to_integer(fxml:get_attr_s(<<"rid">>,
binary_to_integer(fxml:get_attr_s(<<"rid">>,
Attrs))
of
{'EXIT', _} -> {error, bad_request};
+1 -1
View File
@@ -39,7 +39,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
+67 -65
View File
@@ -46,7 +46,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-record(state, {}).
@@ -60,6 +60,8 @@
%% This value is used in SIP and Megaco for a transaction lifetime.
-define(IQ_TIMEOUT, 32000).
-type ping_timeout() :: non_neg_integer() | undefined.
%%====================================================================
%% API
%%====================================================================
@@ -71,37 +73,30 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
#iq{xmlns = XMLNS, lang = Lang} ->
Host = To#jid.lserver,
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function}] ->
ResIQ = Module:Function(From, To, IQ),
if ResIQ /= ignore ->
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
true -> ok
end;
[{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
[] ->
Txt = <<"No module is handling this query">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end;
reply ->
IQReply = jlib:iq_query_or_response_info(Packet),
process_iq_reply(From, To, IQReply);
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
ejabberd_router:route(To, From, Err),
ok
end.
-spec process_iq(jid(), jid(), iq()) -> any().
process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
when T == get; T == set ->
XMLNS = xmpp:get_ns(El),
Host = To#jid.lserver,
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function}] ->
gen_iq_handler:handle(Host, Module, Function, no_queue,
From, To, Packet);
[{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, Packet);
[] ->
Txt = <<"No module is handling this query">>,
Err = xmpp:err_service_unavailable(Txt, Lang),
ejabberd_router:route_error(To, From, Packet, Err)
end;
process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
Err = xmpp:err_bad_request(),
ejabberd_router:route_error(To, From, Packet, Err);
process_iq(From, To, #iq{type = T} = Packet) when T == result; T == error ->
process_iq_reply(From, To, Packet).
-spec process_iq_reply(jid(), jid(), iq()) -> any().
process_iq_reply(From, To, #iq{id = ID} = IQ) ->
case get_iq_callback(ID) of
{ok, undefined, Function} -> Function(IQ), ok;
@@ -110,6 +105,7 @@ process_iq_reply(From, To, #iq{id = ID} = IQ) ->
_ -> nothing
end.
-spec route(jid(), jid(), stanza()) -> any().
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@@ -118,26 +114,32 @@ route(From, To, Packet) ->
_ -> ok
end.
-spec route_iq(jid(), jid(), iq(), function()) -> any().
route_iq(From, To, IQ, F) ->
route_iq(From, To, IQ, F, undefined).
-spec route_iq(jid(), jid(), iq(), function(), ping_timeout()) -> any().
route_iq(From, To, #iq{type = Type} = IQ, F, Timeout)
when is_function(F) ->
Packet = if Type == set; Type == get ->
ID = randoms:get_string(),
Host = From#jid.lserver,
register_iq_response_handler(Host, ID, undefined, F, Timeout),
jlib:iq_to_xml(IQ#iq{id = ID});
IQ#iq{id = ID};
true ->
jlib:iq_to_xml(IQ)
IQ
end,
ejabberd_router:route(From, To, Packet).
-spec register_iq_response_handler(binary(), binary(), module(),
atom() | function()) -> any().
register_iq_response_handler(Host, ID, Module,
Function) ->
register_iq_response_handler(Host, ID, Module, Function,
undefined).
-spec register_iq_response_handler(binary(), binary(), module(),
atom() | function(), ping_timeout()) -> any().
register_iq_response_handler(_Host, ID, Module,
Function, Timeout0) ->
Timeout = case Timeout0 of
@@ -150,29 +152,40 @@ register_iq_response_handler(_Host, ID, Module,
function = Function,
timer = TRef}).
-spec register_iq_handler(binary(), binary(), module(), function()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_local !
{register_iq_handler, Host, XMLNS, Module, Fun}.
-spec register_iq_handler(binary(), binary(), module(), function(),
gen_iq_handler:opts()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
ejabberd_local !
{register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
-spec unregister_iq_response_handler(binary(), binary()) -> ok.
unregister_iq_response_handler(_Host, ID) ->
catch get_iq_callback(ID), ok.
-spec unregister_iq_handler(binary(), binary()) -> any().
unregister_iq_handler(Host, XMLNS) ->
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
-spec refresh_iq_handlers() -> any().
refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers.
-spec bounce_resource_packet(jid(), jid(), stanza()) -> stop.
bounce_resource_packet(_From, #jid{lresource = <<"">>}, #presence{}) ->
ok;
bounce_resource_packet(_From, #jid{lresource = <<"">>},
#message{type = headline}) ->
ok;
bounce_resource_packet(From, To, Packet) ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Lang = xmpp:get_lang(Packet),
Txt = <<"No available resource found">>,
Err = jlib:make_error_reply(Packet,
?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
ejabberd_router:route(To, From, Err),
Err = xmpp:err_item_not_found(Txt, Lang),
ejabberd_router:route_error(To, From, Packet, Err),
stop.
%%====================================================================
@@ -192,7 +205,7 @@ init([]) ->
?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]),
update_table(),
mnesia:create_table(iq_response,
ejabberd_mnesia:create(?MODULE, iq_response,
[{ram_copies, [node()]},
{attributes, record_info(fields, iq_response)}]),
mnesia:add_table_copy(iq_response, node(), ram_copies),
@@ -261,50 +274,36 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-spec do_route(jid(), jid(), stanza()) -> any().
do_route(From, To, Packet) ->
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
[From, To, Packet, 8]),
Type = xmpp:get_type(Packet),
if To#jid.luser /= <<"">> ->
ejabberd_sm:route(From, To, Packet);
To#jid.lresource == <<"">> ->
#xmlel{name = Name} = Packet,
case Name of
<<"iq">> -> process_iq(From, To, Packet);
<<"message">> ->
#xmlel{attrs = Attrs} = Packet,
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"headline">> -> ok;
<<"error">> -> ok;
_ ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
end;
<<"presence">> -> ok;
_ -> ok
end;
ejabberd_sm:route(From, To, Packet);
is_record(Packet, iq), To#jid.lresource == <<"">> ->
process_iq(From, To, Packet);
Type == result; Type == error ->
ok;
true ->
#xmlel{attrs = Attrs} = Packet,
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
ejabberd_hooks:run(local_send_to_resource_hook,
To#jid.lserver, [From, To, Packet])
end
ejabberd_hooks:run(local_send_to_resource_hook,
To#jid.lserver, [From, To, Packet])
end.
-spec update_table() -> ok.
update_table() ->
case catch mnesia:table_info(iq_response, attributes) of
[id, module, function] ->
mnesia:delete_table(iq_response);
mnesia:delete_table(iq_response),
ok;
[id, module, function, timer] ->
ok;
{'EXIT', _} ->
ok
end.
-spec get_iq_callback(binary()) -> {ok, module(), atom() | function()} | error.
get_iq_callback(ID) ->
case mnesia:dirty_read(iq_response, ID) of
[#iq_response{module = Module, timer = TRef,
@@ -316,9 +315,11 @@ get_iq_callback(ID) ->
error
end.
-spec process_iq_timeout(binary()) -> any().
process_iq_timeout(ID) ->
spawn(fun process_iq_timeout/0) ! ID.
-spec process_iq_timeout() -> any().
process_iq_timeout() ->
receive
ID ->
@@ -332,6 +333,7 @@ process_iq_timeout() ->
ok
end.
-spec cancel_timer(reference()) -> ok.
cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of
false ->
+169
View File
@@ -0,0 +1,169 @@
%%%----------------------------------------------------------------------
%%% File : mnesia_mnesia.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Handle configurable mnesia schema
%%% Created : 17 Nov 2016 by Christophe Romain <christophe.romain@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.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
%%% This module should be used everywhere ejabberd creates a mnesia table
%%% to make the schema customizable without code change
%%% Just apply this change in ejabberd modules
%%% s/ejabberd_mnesia:create(?MODULE, /ejabberd_mnesia:create(?MODULE, /
-module(ejabberd_mnesia).
-author('christophe.romain@process-one.net').
-export([create/3, reset/2, update/2]).
-define(STORAGE_TYPES, [disc_copies, disc_only_copies, ram_copies]).
-define(NEED_RESET, [local_content, type]).
create(Module, Name, TabDef) ->
Schema = schema(Module, Name, TabDef),
{attributes, Attrs} = lists:keyfind(attributes, 1, Schema),
case catch mnesia:table_info(Name, attributes) of
{'EXIT', _} ->
mnesia:create_table(Name, Schema);
Attrs ->
case need_reset(TabDef, Schema) of
true -> reset(Name, Schema);
false -> update(Name, Schema)
end;
OldAttrs ->
Fun = case lists:member({transform,1}, Module:module_info(exports)) of
true -> fun(Old) -> Module:transform(Old) end;
false -> fun(Old) -> transform(OldAttrs, Attrs, Old) end
end,
mnesia:transform_table(Name, Fun, Attrs)
end.
reset(Name, TabDef) ->
mnesia:delete_table(Name),
ejabberd_mnesia:create(?MODULE, Name, TabDef).
update(Name, TabDef) ->
Storage = mnesia:table_info(Name, storage_type),
NewStorage = lists:foldl(
fun({Key, _}, Acc) ->
case lists:member(Key, ?STORAGE_TYPES) of
true -> Key;
false -> Acc
end
end, Storage, TabDef),
R1 = if Storage=/=NewStorage ->
mnesia:change_table_copy_type(Name, node(), NewStorage);
true ->
{atomic, ok}
end,
Indexes = mnesia:table_info(Name, index),
NewIndexes = proplists:get_value(index, TabDef, []),
[mnesia:del_table_index(Name, Attr)
|| Attr <- Indexes--NewIndexes],
R2 = [mnesia:add_table_index(Name, Attr)
|| Attr <- NewIndexes--Indexes],
lists:foldl(
fun({atomic, ok}, Acc) -> Acc;
(Error, _Acc) -> Error
end, {atomic, ok}, [R1|R2]).
%
% utilities
%
schema(Module, Name, TabDef) ->
case parse(Module) of
{ok, CustomDefs} ->
case lists:keyfind(Name, 1, CustomDefs) of
{Name, CustomDef} -> merge(TabDef, CustomDef);
_ -> TabDef
end;
_ ->
TabDef
end.
merge(TabDef, CustomDef) ->
{CustomKeys, _} = lists:unzip(CustomDef),
CleanDef = lists:foldl(
fun(Elem, Acc) ->
case lists:member(Elem, ?STORAGE_TYPES) of
true ->
lists:foldl(
fun(Key, CleanAcc) ->
lists:keydelete(Key, 1, CleanAcc)
end, Acc, ?STORAGE_TYPES);
false ->
Acc
end
end, TabDef, CustomKeys),
lists:ukeymerge(1,
lists:ukeysort(1, CustomDef),
lists:ukeysort(1, CleanDef)).
parse(Module) ->
Path = case os:getenv("EJABBERD_SCHEMA_PATH") of
false ->
case code:priv_dir(ejabberd) of
{error, _} -> "schema"; % $SPOOL_DIR/schema
Priv -> filename:join(Priv, "schema")
end;
CustomDir ->
CustomDir
end,
File = filename:join(Path, atom_to_list(Module)++".mnesia"),
case file:consult(File) of
{ok, Terms} -> parse(Terms, []);
Error -> Error
end.
parse([], Acc) ->
{ok, lists:reverse(Acc)};
parse([{Name, Storage, TabDef}|Tail], Acc)
when is_atom(Name),
is_atom(Storage),
is_list(TabDef) ->
NewDef = case lists:member(Storage, ?STORAGE_TYPES) of
true -> [{Storage, [node()]} | TabDef];
false -> TabDef
end,
parse(Tail, [{Name, NewDef} | Acc]);
parse([Other|_], _) ->
{error, {invalid, Other}}.
need_reset(FromDef, ToDef) ->
ValuesF = [lists:keyfind(Key, 1, FromDef) || Key <- ?NEED_RESET],
ValuesT = [lists:keyfind(Key, 1, ToDef) || Key <- ?NEED_RESET],
lists:foldl(
fun({Val, Val}, Acc) -> Acc;
({_, false}, Acc) -> Acc;
({_, _}, _) -> true
end, false, lists:zip(ValuesF, ValuesT)).
transform(OldAttrs, Attrs, Old) ->
[Name|OldValues] = tuple_to_list(Old),
Before = lists:zip(OldAttrs, OldValues),
After = lists:foldl(
fun(Attr, Acc) ->
case lists:keyfind(Attr, 1, Before) of
false -> [{Attr, undefined}|Acc];
Value -> [Value|Acc]
end
end, [], lists:reverse(Attrs)),
{Attrs, NewRecord} = lists:unzip(After),
list_to_tuple([Name|NewRecord]).
+44 -19
View File
@@ -42,14 +42,16 @@
associate_access_code/3,
associate_access_token/3,
associate_refresh_token/3,
check_token/1,
check_token/4,
check_token/2,
scope_in_scope_list/2,
process/2,
opt_type/1]).
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -138,11 +140,11 @@ oauth_issue_token(Jid, TTLSeconds, ScopesString) ->
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),
{ok, AccessToken} = oauth2_response:access_token(Response),
{ok, VerifiedScope} = oauth2_response:scope(Response),
{AccessToken, VerifiedScope, integer_to_list(TTLSeconds) ++ " seconds"};
{error, Error} ->
{error, Error}
{error, Error} ->
{error, Error}
end;
error ->
{error, "Invalid JID: " ++ Jid}
@@ -210,12 +212,12 @@ authenticate_user({User, Server}, Ctx) ->
allow ->
case Ctx of
{password, Password} ->
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
true ->
{ok, {Ctx, {user, User, Server}}};
false ->
{error, badpass}
end;
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;
@@ -289,7 +291,7 @@ associate_access_token(AccessToken, Context, AppContext) ->
%% 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,
end,
{user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>),
Scope = proplists:get_value(<<"scope">>, Context, []),
R = #oauth_token{
@@ -305,12 +307,35 @@ associate_refresh_token(_RefreshToken, _Context, AppContext) ->
%put(?REFRESH_TOKEN_TABLE, RefreshToken, Context),
{ok, AppContext}.
scope_in_scope_list(Scope, ScopeList) ->
TokenScopeSet = oauth2_priv_set:new(Scope),
lists:any(fun(Scope2) ->
oauth2_priv_set:is_member(Scope2, TokenScopeSet) end,
ScopeList).
check_token(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 ->
{ok, US, TokenScope};
true ->
{false, expired}
end;
_ ->
{false, not_found}
end.
check_token(User, Server, ScopeList, Token) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case lookup(Token) of
{ok, #oauth_token{us = {LUser, LServer},
scope = TokenScope,
scope = TokenScope,
expire = Expire}} ->
{MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs,
@@ -330,7 +355,7 @@ check_token(User, Server, ScopeList, Token) ->
check_token(ScopeList, Token) ->
case lookup(Token) of
{ok, #oauth_token{us = US,
scope = TokenScope,
scope = TokenScope,
expire = Expire}} ->
{MegaSecs, Secs, _} = os:timestamp(),
TS = 1000000 * MegaSecs + Secs,
@@ -342,7 +367,7 @@ check_token(ScopeList, Token) ->
ScopeList) of
true -> {ok, user, US};
false -> {false, no_matching_scope}
end;
end;
true ->
{false, expired}
end;
@@ -446,7 +471,7 @@ process(_Handlers,
[{<<"href">>, <<"https://www.ejabberd.im">>},
{<<"title">>, <<"ejabberd XMPP server">>}],
<<"ejabberd">>),
?C(" is maintained by "),
?C(<<" is maintained by ">>),
?XAC(<<"a">>,
[{<<"href">>, <<"https://www.process-one.net">>},
{<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}],
@@ -469,7 +494,7 @@ process(_Handlers,
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
ExpiresIn = case TTL of
<<>> -> undefined;
_ -> jlib:binary_to_integer(TTL)
_ -> binary_to_integer(TTL)
end,
case oauth2:authorize_password({Username, Server},
ClientId,
@@ -531,7 +556,7 @@ process(_Handlers,
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
ExpiresIn = case TTL of
<<>> -> undefined;
_ -> jlib:binary_to_integer(TTL)
_ -> binary_to_integer(TTL)
end,
case oauth2:authorize_password({Username, Server},
Scope,
@@ -707,7 +732,7 @@ css() ->
text-decoration: underline;
}
.container > .section {
.container > .section {
background: #424A55;
}
+1 -1
View File
@@ -34,7 +34,7 @@
-include("ejabberd_oauth.hrl").
init() ->
mnesia:create_table(oauth_token,
ejabberd_mnesia:create(?MODULE, oauth_token,
[{disc_copies, [node()]},
{attributes,
record_info(fields, oauth_token)}]),
+98
View File
@@ -0,0 +1,98 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_oauth_rest.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : OAUTH2 REST backend
%%% Created : 26 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_rest).
-export([init/0,
store/1,
lookup/1,
clean/1,
opt_type/1]).
-include("ejabberd.hrl").
-include("ejabberd_oauth.hrl").
-include("logger.hrl").
-include("jid.hrl").
init() ->
rest:start(?MYNAME),
ok.
store(R) ->
Path = path(<<"store">>),
%% Retry 2 times, with a backoff of 500millisec
{User, Server} = R#oauth_token.us,
SJID = jid:to_string({User, Server, <<"">>}),
case rest:with_retry(
post,
[?MYNAME, Path, [],
{[{<<"token">>, R#oauth_token.token},
{<<"user">>, SJID},
{<<"scope">>, R#oauth_token.scope},
{<<"expire">>, R#oauth_token.expire}
]}], 2, 500) of
{ok, Code, _} when Code == 200 orelse Code == 201 ->
ok;
Err ->
?ERROR_MSG("failed to store oauth record ~p: ~p", [R, Err]),
{error, Err}
end.
lookup(Token) ->
Path = path(<<"lookup">>),
case rest:with_retry(post, [?MYNAME, Path, [],
{[{<<"token">>, Token}]}],
2, 500) of
{ok, 200, {Data}} ->
SJID = proplists:get_value(<<"user">>, Data, <<>>),
JID = jid:from_string(SJID),
US = {JID#jid.luser, JID#jid.lserver},
Scope = proplists:get_value(<<"scope">>, Data, []),
Expire = proplists:get_value(<<"expire">>, Data, 0),
#oauth_token{token = Token,
us = US,
scope = Scope,
expire = Expire};
{ok, 404, _Resp} ->
false;
Other ->
?ERROR_MSG("Unexpected response for oauth lookup: ~p", [Other]),
{error, rest_failed}
end.
clean(_TS) ->
ok.
path(Path) ->
Base = ejabberd_config:get_option(ext_api_path_oauth,
fun(X) -> iolist_to_binary(X) end,
<<"/oauth">>),
<<Base/binary, "/", Path/binary>>.
opt_type(ext_api_path_oauth) ->
fun (X) -> iolist_to_binary(X) end;
opt_type(_) -> [ext_api_path_oauth].
+1 -1
View File
@@ -36,7 +36,7 @@
-include("ejabberd_oauth.hrl").
-include("ejabberd.hrl").
-include("ejabberd_sql_pt.hrl").
-include("jlib.hrl").
-include("jid.hrl").
init() ->
ok.
+114 -142
View File
@@ -48,7 +48,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("mod_privacy.hrl").
-include("mod_roster.hrl").
@@ -196,7 +196,7 @@ format_scram_password({StoredKey, ServerKey, Salt, IterationCount}) ->
StoredKeyB64 = base64:encode(StoredKey),
ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt),
IterationCountBin = list_to_binary(integer_to_list(IterationCount)),
IterationCountBin = (integer_to_binary(IterationCount)),
<<"scram:", StoredKeyB64/binary, ",", ServerKeyB64/binary, ",", SaltB64/binary, ",", IterationCountBin/binary>>.
parse_scram_password(PassData) ->
@@ -206,34 +206,31 @@ parse_scram_password(PassData) ->
storedkey = StoredKeyB64,
serverkey = ServerKeyB64,
salt = SaltB64,
iterationcount = list_to_integer(binary_to_list(IterationCountBin))
iterationcount = (binary_to_integer(IterationCountBin))
}.
-spec get_vcard(binary(), binary()) -> [xmlel()].
get_vcard(User, Server) ->
JID = jid:make(User, Server, <<>>),
case mod_vcard:process_sm_iq(JID, JID, #iq{type = get}) of
#iq{type = result, sub_el = [_|_] = VCardEls} ->
VCardEls;
_ ->
[]
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case mod_vcard:get_vcard(LUser, LServer) of
error -> [];
Els -> Els
end.
-spec get_offline(binary(), binary()) -> [xmlel()].
get_offline(User, Server) ->
case mod_offline:get_offline_els(User, Server) of
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case mod_offline:get_offline_els(LUser, LServer) of
[] ->
[];
Els ->
NewEls = lists:map(
fun(#xmlel{attrs = Attrs} = El) ->
NewAttrs = lists:keystore(<<"xmlns">>, 1,
Attrs,
{<<"xmlns">>,
<<"jabber:client">>}),
El#xmlel{attrs = NewAttrs}
end, Els),
NewEls = lists:map(fun xmpp:encode/1, Els),
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
end.
-spec get_privacy(binary(), binary()) -> [xmlel()].
get_privacy(User, Server) ->
case mod_privacy:get_user_lists(User, Server) of
{ok, #privacy{default = Default,
@@ -241,25 +238,16 @@ get_privacy(User, Server) ->
XLists = lists:map(
fun({Name, Items}) ->
XItems = lists:map(
fun mod_privacy:item_to_xml/1, Items),
#xmlel{name = <<"list">>,
attrs = [{<<"name">>, Name}],
children = XItems}
fun mod_privacy:encode_list_item/1,
Items),
#privacy_list{name = Name, items = XItems}
end, Lists),
DefaultEl = case Default of
none ->
[];
_ ->
[#xmlel{name = <<"default">>,
attrs = [{<<"name">>, Default}]}]
end,
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
children = DefaultEl ++ XLists}];
[xmpp:encode(#privacy_query{default = Default, lists = XLists})];
_ ->
[]
end.
-spec get_roster(binary(), binary()) -> [xmlel()].
get_roster(User, Server) ->
JID = jid:make(User, Server, <<>>),
case mod_roster:get_roster(User, Server) of
@@ -272,18 +260,11 @@ get_roster(User, Server) ->
Status = if is_binary(Msg) -> (Msg);
true -> <<"">>
end,
[#xmlel{name = <<"presence">>,
attrs =
[{<<"from">>,
jid:to_string(R#roster.jid)},
{<<"to">>, jid:to_string(JID)},
{<<"xmlns">>, <<"jabber:client">>},
{<<"type">>, <<"subscribe">>}],
children =
[#xmlel{name = <<"status">>,
attrs = [],
children =
[{xmlcdata, Status}]}]}];
[xmpp:encode(
#presence{from = jid:make(R#roster.jid),
to = JID,
type = subscribe,
status = xmpp:mk_text(Status)})];
(_) ->
[]
end, Items),
@@ -291,21 +272,18 @@ get_roster(User, Server) ->
fun(#roster{ask = in, subscription = none}) ->
[];
(R) ->
[mod_roster:item_to_xml(R)]
[mod_roster:encode_item(R)]
end, Items),
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_ROSTER}],
children = Rs} | Subs];
[xmpp:encode(#roster_query{items = Rs}) | Subs];
_ ->
[]
end.
-spec get_private(binary(), binary()) -> [xmlel()].
get_private(User, Server) ->
case mod_private:get_data(User, Server) of
[_|_] = Els ->
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_PRIVATE}],
children = Els}];
[xmpp:encode(#private{xml_els = Els})];
_ ->
[]
end.
@@ -451,129 +429,124 @@ process_user_els([], State) ->
process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
State) ->
case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
{<<"query">>, ?NS_ROSTER} ->
process_roster(El, State);
{<<"query">>, ?NS_PRIVACY} ->
%% Make sure <list/> elements go before <active/> and <default/>
NewEls = lists:reverse(lists:keysort(#xmlel.name, Els)),
process_privacy_el(El#xmlel{children = NewEls}, State);
{<<"query">>, ?NS_PRIVATE} ->
process_private(El, State);
{<<"vCard">>, ?NS_VCARD} ->
process_vcard(El, State);
{<<"offline-messages">>, _} ->
process_offline_msgs(Els, State);
{<<"presence">>, <<"jabber:client">>} ->
process_presence(El, State);
_ ->
{ok, State}
try
case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
{<<"query">>, ?NS_ROSTER} ->
process_roster(xmpp:decode(El), State);
{<<"query">>, ?NS_PRIVACY} ->
%% Make sure <list/> elements go before <active/> and <default/>
process_privacy(xmpp:decode(El), State);
{<<"query">>, ?NS_PRIVATE} ->
process_private(xmpp:decode(El), State);
{<<"vCard">>, ?NS_VCARD} ->
process_vcard(El, State);
{<<"offline-messages">>, NS} ->
Msgs = [xmpp:decode(E, NS, [ignore_els]) || E <- Els],
process_offline_msgs(Msgs, State);
{<<"presence">>, ?NS_CLIENT} ->
process_presence(xmpp:decode(El, ?NS_CLIENT, [ignore_els]), State);
_ ->
{ok, State}
end
catch _:{xmpp_codec, Why} ->
ErrTxt = xmpp:format_error(Why),
stop("failed to decode XML '~s': ~s",
[fxml:element_to_binary(El), ErrTxt])
end.
process_privacy_el(#xmlel{children = [#xmlel{} = SubEl|SubEls]} = El, State) ->
case process_privacy(#xmlel{children = [SubEl]}, State) of
-spec process_offline_msgs([stanza()], state()) -> {ok, state()} | {error, _}.
process_offline_msgs([#message{} = Msg|Msgs], State) ->
case process_offline_msg(Msg, State) of
{ok, NewState} ->
process_privacy_el(El#xmlel{children = SubEls}, NewState);
process_offline_msgs(Msgs, NewState);
Err ->
Err
end;
process_privacy_el(#xmlel{children = [_|SubEls]} = El, State) ->
process_privacy_el(El#xmlel{children = SubEls}, State);
process_privacy_el(#xmlel{children = []}, State) ->
{ok, State}.
process_offline_msgs([#xmlel{} = El|Els], State) ->
case process_offline_msg(El, State) of
{ok, NewState} ->
process_offline_msgs(Els, NewState);
Err ->
Err
end;
process_offline_msgs([_|Els], State) ->
process_offline_msgs(Els, State);
process_offline_msgs([_|Msgs], State) ->
process_offline_msgs(Msgs, State);
process_offline_msgs([], State) ->
{ok, State}.
process_roster(El, State = #state{user = U, server = S}) ->
case mod_roster:set_items(U, S, El) of
-spec process_roster(roster_query(), state()) -> {ok, state()} | {error, _}.
process_roster(RosterQuery, State = #state{user = U, server = S}) ->
case mod_roster:set_items(U, S, RosterQuery) of
{atomic, _} ->
{ok, State};
Err ->
stop("Failed to write roster: ~p", [Err])
end.
process_privacy(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S, <<"">>),
case mod_privacy:process_iq_set(
[], JID, JID, #iq{type = set, sub_el = El}) of
{error, Error} = Err ->
#xmlel{children = Els} = El,
Name = case fxml:remove_cdata(Els) of
[#xmlel{name = N}] -> N;
_ -> undefined
end,
#xmlel{attrs = Attrs} = Error,
ErrorCode = case lists:keysearch(<<"code">>, 1, Attrs) of
{value, {_, V}} -> V;
false -> undefined
end,
if
ErrorCode == <<"404">>, Name == <<"default">> ->
{ok, State};
true ->
stop("Failed to write privacy: ~p", [Err])
-spec process_privacy(privacy_query(), state()) -> {ok, state()} | {error, _}.
process_privacy(#privacy_query{lists = Lists,
default = Default,
active = Active} = PrivacyQuery,
State = #state{user = U, server = S}) ->
JID = jid:make(U, S),
IQ = #iq{type = set, id = randoms:get_string(),
from = JID, to = JID, sub_els = [PrivacyQuery]},
Txt = <<"No module is handling this query">>,
Error = {error, xmpp:err_feature_not_implemented(Txt, ?MYLANG)},
case mod_privacy:process_iq_set(Error, IQ, #userlist{}) of
{error, #stanza_error{reason = Reason}} = Err ->
if Reason == 'item-not-found', Lists == [],
Active == undefined, Default /= undefined ->
%% Failed to set default list because there is no
%% list with such name. We shouldn't stop here.
{ok, State};
true ->
stop("Failed to write privacy: ~p", [Err])
end;
_ ->
{ok, State}
end.
process_private(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S, <<"">>),
case mod_private:process_sm_iq(
JID, JID, #iq{type = set, sub_el = El}) of
-spec process_private(private(), state()) -> {ok, state()} | {error, _}.
process_private(Private, State = #state{user = U, server = S}) ->
JID = jid:make(U, S),
IQ = #iq{type = set, id = randoms:get_string(),
from = JID, to = JID, sub_els = [Private]},
case mod_private:process_sm_iq(IQ) of
#iq{type = result} ->
{ok, State};
Err ->
stop("Failed to write private: ~p", [Err])
end.
-spec process_vcard(xmlel(), state()) -> {ok, state()} | {error, _}.
process_vcard(El, State = #state{user = U, server = S}) ->
JID = jid:make(U, S, <<"">>),
case mod_vcard:process_sm_iq(
JID, JID, #iq{type = set, sub_el = El}) of
JID = jid:make(U, S),
IQ = #iq{type = set, id = randoms:get_string(),
from = JID, to = JID, sub_els = [El]},
case mod_vcard:process_sm_iq(IQ) of
#iq{type = result} ->
{ok, State};
Err ->
stop("Failed to write vcard: ~p", [Err])
end.
process_offline_msg(El, State = #state{user = U, server = S}) ->
FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
case jid:from_string(FromS) of
#jid{} = From ->
To = jid:make(U, S, <<>>),
NewEl = jlib:replace_from_to(From, To, El),
case catch mod_offline:store_packet(From, To, NewEl) of
{'EXIT', _} = Err ->
stop("Failed to store offline message: ~p", [Err]);
_ ->
{ok, State}
end;
_ ->
stop("Invalid 'from' = ~s", [FromS])
-spec process_offline_msg(message(), state()) -> {ok, state()} | {error, _}.
process_offline_msg(#message{from = undefined}, _State) ->
stop("No 'from' attribute found", []);
process_offline_msg(Msg, State = #state{user = U, server = S}) ->
From = xmpp:get_from(Msg),
To = jid:make(U, S, <<>>),
NewMsg = xmpp:set_from_to(Msg, From, To),
case catch mod_offline:store_packet(From, To, NewMsg) of
{'EXIT', _} = Err ->
stop("Failed to store offline message: ~p", [Err]);
_ ->
{ok, State}
end.
process_presence(El, #state{user = U, server = S} = State) ->
FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
case jid:from_string(FromS) of
#jid{} = From ->
To = jid:make(U, S, <<>>),
NewEl = jlib:replace_from_to(From, To, El),
ejabberd_router:route(From, To, NewEl),
{ok, State};
_ ->
stop("Invalid 'from' = ~s", [FromS])
end.
-spec process_presence(presence(), state()) -> {ok, state()} | {error, _}.
process_presence(#presence{from = undefined}, _State) ->
stop("No 'from' attribute found", []);
process_presence(Pres, #state{user = U, server = S} = State) ->
From = xmpp:get_from(Pres),
To = jid:make(U, S, <<>>),
NewPres = xmpp:set_from_to(Pres, From, To),
ejabberd_router:route(From, To, NewPres),
{ok, State}.
stop(Fmt, Args) ->
?ERROR_MSG(Fmt, Args),
@@ -581,9 +554,8 @@ stop(Fmt, Args) ->
make_filename_template() ->
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
list_to_binary(
io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
[Year, Month, Day, Hour, Minute, Second])).
str:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
[Year, Month, Day, Hour, Minute, Second]).
make_main_basefilename(Dir, FnT) ->
Filename2 = <<FnT/binary, ".xml">>,
+3 -3
View File
@@ -428,7 +428,7 @@ map_key(Obj, _, _) ->
<<"b_", B/binary>> ->
B;
<<"i_", B/binary>> ->
list_to_integer(binary_to_list(B));
(binary_to_integer(B));
B ->
erlang:binary_to_term(B)
end].
@@ -483,7 +483,7 @@ encode_index_key(Idx, Key) ->
encode_key(Bin) when is_binary(Bin) ->
<<"b_", Bin/binary>>;
encode_key(Int) when is_integer(Int) ->
<<"i_", (list_to_binary(integer_to_list(Int)))/binary>>;
<<"i_", ((integer_to_binary(Int)))/binary>>;
encode_key(Term) ->
erlang:term_to_binary(Term).
@@ -519,7 +519,7 @@ log_error(_, _, _) ->
ok.
make_invalid_object(Val) ->
list_to_binary(io_lib:fwrite("Invalid object: ~p", [Val])).
(str:format("Invalid object: ~p", [Val])).
get_random_pid() ->
PoolPid = ejabberd_riak_sup:get_random_pid(),
+88 -45
View File
@@ -39,6 +39,7 @@
register_route/3,
register_routes/1,
host_of_route/1,
process_iq/3,
unregister_route/1,
unregister_routes/1,
dirty_get_all_routes/0,
@@ -53,7 +54,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
@@ -71,7 +72,7 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(jid(), jid(), xmlel()) -> ok.
-spec route(jid(), jid(), xmlel() | stanza()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -84,13 +85,21 @@ route(From, To, Packet) ->
%% Route the error packet only if the originating packet is not an error itself.
%% RFC3920 9.3.1
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok.
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok;
(jid(), jid(), stanza(), stanza_error()) -> ok.
route_error(From, To, ErrPacket, OrigPacket) ->
route_error(From, To, #xmlel{} = ErrPacket, #xmlel{} = OrigPacket) ->
#xmlel{attrs = Attrs} = OrigPacket,
case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
false -> route(From, To, ErrPacket);
true -> ok
end;
route_error(From, To, Packet, #stanza_error{} = Err) ->
Type = xmpp:get_type(Packet),
if Type == error; Type == result ->
ok;
true ->
ejabberd_router:route(From, To, xmpp:make_error(Packet, Err))
end.
-spec register_route(binary()) -> term().
@@ -236,6 +245,28 @@ host_of_route(Domain) ->
end
end.
-spec process_iq(jid(), jid(), iq() | xmlel()) -> any().
process_iq(From, To, #iq{} = IQ) ->
if To#jid.luser == <<"">> ->
ejabberd_local:process_iq(From, To, IQ);
true ->
ejabberd_sm:process_iq(From, To, IQ)
end;
process_iq(From, To, El) ->
try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
IQ -> process_iq(From, To, IQ)
catch _:{xmpp_codec, Why} ->
Type = xmpp:get_type(El),
if Type == <<"get">>; Type == <<"set">> ->
Txt = xmpp:format_error(Why),
Lang = xmpp:get_lang(El),
Err = xmpp:make_error(El, xmpp:err_bad_request(Txt, Lang)),
ejabberd_router:route(To, From, Err);
true ->
ok
end
end.
%%====================================================================
%% gen_server callbacks
%%====================================================================
@@ -249,7 +280,7 @@ host_of_route(Domain) ->
%%--------------------------------------------------------------------
init([]) ->
update_tables(),
mnesia:create_table(route,
ejabberd_mnesia:create(?MODULE, route,
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, route)}]),
@@ -347,6 +378,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-spec do_route(jid(), jid(), xmlel() | xmpp_element()) -> any().
do_route(OrigFrom, OrigTo, OrigPacket) ->
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~p~n",
@@ -357,69 +389,80 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
{From, To, Packet} ->
LDstDomain = To#jid.lserver,
case mnesia:dirty_read(route, LDstDomain) of
[] -> ejabberd_s2s:route(From, To, Packet);
[] ->
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
Pkt ->
ejabberd_s2s:route(From, To, Pkt)
catch _:{xmpp_codec, Why} ->
log_decoding_error(From, To, Packet, Why)
end;
[R] ->
Pid = R#route.pid,
if node(Pid) == node() ->
case R#route.local_hint of
{apply, Module, Function} ->
Module:Function(From, To, Packet);
_ -> Pid ! {route, From, To, Packet}
end;
is_pid(Pid) -> Pid ! {route, From, To, Packet};
true -> drop
end;
do_route(From, To, Packet, R);
Rs ->
Value = case
ejabberd_config:get_option({domain_balancing,
LDstDomain}, fun(D) when is_atom(D) -> D end)
of
undefined -> p1_time_compat:monotonic_time();
random -> p1_time_compat:monotonic_time();
source -> jid:tolower(From);
destination -> jid:tolower(To);
bare_source ->
jid:remove_resource(jid:tolower(From));
bare_destination ->
jid:remove_resource(jid:tolower(To))
end,
Value = get_domain_balancing(From, To, LDstDomain),
case get_component_number(LDstDomain) of
undefined ->
case [R || R <- Rs, node(R#route.pid) == node()] of
[] ->
R = lists:nth(erlang:phash(Value, length(Rs)), Rs),
Pid = R#route.pid,
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
true -> drop
end;
do_route(From, To, Packet, R);
LRs ->
R = lists:nth(erlang:phash(Value, length(LRs)),
LRs),
Pid = R#route.pid,
case R#route.local_hint of
{apply, Module, Function} ->
Module:Function(From, To, Packet);
_ -> Pid ! {route, From, To, Packet}
end
R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
do_route(From, To, Packet, R)
end;
_ ->
SRs = lists:ukeysort(#route.local_hint, Rs),
R = lists:nth(erlang:phash(Value, length(SRs)), SRs),
Pid = R#route.pid,
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
true -> drop
end
do_route(From, To, Packet, R)
end
end;
drop -> ok
end.
-spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any().
do_route(From, To, Packet, #route{local_hint = LocalHint,
pid = Pid}) when is_pid(Pid) ->
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
Pkt ->
case LocalHint of
{apply, Module, Function} when node(Pid) == node() ->
Module:Function(From, To, Pkt);
_ ->
Pid ! {route, From, To, Pkt}
end
catch error:{xmpp_codec, Why} ->
log_decoding_error(From, To, Packet, Why)
end;
do_route(_From, _To, _Packet, _Route) ->
drop.
-spec log_decoding_error(jid(), jid(), xmlel() | xmpp_element(), term()) -> ok.
log_decoding_error(From, To, Packet, Reason) ->
?ERROR_MSG("failed to decode xml element ~p when "
"routing from ~s to ~s: ~s",
[Packet, jid:to_string(From), jid:to_string(To),
xmpp:format_error(Reason)]).
-spec get_component_number(binary()) -> pos_integer() | undefined.
get_component_number(LDomain) ->
ejabberd_config:get_option(
{domain_balancing_component_number, LDomain},
fun(N) when is_integer(N), N > 1 -> N end,
undefined).
-spec get_domain_balancing(jid(), jid(), binary()) -> any().
get_domain_balancing(From, To, LDomain) ->
case ejabberd_config:get_option(
{domain_balancing, LDomain}, fun(D) when is_atom(D) -> D end) of
undefined -> p1_time_compat:monotonic_time();
random -> p1_time_compat:monotonic_time();
source -> jid:tolower(From);
destination -> jid:tolower(To);
bare_source -> jid:remove_resource(jid:tolower(From));
bare_destination -> jid:remove_resource(jid:tolower(To))
end.
-spec update_tables() -> ok.
update_tables() ->
try
mnesia:transform_table(route, ignore, record_info(fields, route))
+10 -4
View File
@@ -43,9 +43,10 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-record(route_multicast, {domain, pid}).
-record(route_multicast, {domain = <<"">> :: binary() | '_',
pid = self() :: pid()}).
-record(state, {}).
%%====================================================================
@@ -58,7 +59,7 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route_multicast(jid(), binary(), [jid()], stanza()) -> ok.
route_multicast(From, Domain, Destinations, Packet) ->
case catch do_route(From, Domain, Destinations, Packet) of
{'EXIT', Reason} ->
@@ -68,6 +69,7 @@ route_multicast(From, Domain, Destinations, Packet) ->
ok
end.
-spec register_route(binary()) -> any().
register_route(Domain) ->
case jid:nameprep(Domain) of
error ->
@@ -81,6 +83,7 @@ register_route(Domain) ->
mnesia:transaction(F)
end.
-spec unregister_route(binary()) -> any().
unregister_route(Domain) ->
case jid:nameprep(Domain) of
error ->
@@ -112,7 +115,7 @@ unregister_route(Domain) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
mnesia:create_table(route_multicast,
ejabberd_mnesia:create(?MODULE, route_multicast,
[{ram_copies, [node()]},
{type, bag},
{attributes,
@@ -206,6 +209,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%% From = #jid
%% Destinations = [#jid]
-spec do_route(jid(), binary(), [jid()], stanza()) -> any().
do_route(From, Domain, Destinations, Packet) ->
?DEBUG("route_multicast~n\tfrom ~s~n\tdomain ~s~n\tdestinations ~p~n\tpacket ~p~n",
@@ -226,6 +230,7 @@ do_route(From, Domain, Destinations, Packet) ->
Pid ! {route_trusted, From, Destinations, Packet}
end.
-spec pick_multicast_pid([#route_multicast{}]) -> pid().
pick_multicast_pid(Rs) ->
List = case [R || R <- Rs, node(R#route_multicast.pid) == node()] of
[] -> Rs;
@@ -233,5 +238,6 @@ pick_multicast_pid(Rs) ->
end,
(hd(List))#route_multicast.pid.
-spec do_route_normal(jid(), [jid()], stanza()) -> any().
do_route_normal(From, Destinations, Packet) ->
[ejabberd_router:route(From, To, Packet) || To <- Destinations].
+45 -37
View File
@@ -39,6 +39,7 @@
remove_connection/2, find_connection/2,
dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0,
stop_all_connections/0,
clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0,
external_host_overloaded/1, is_temporarly_blocked/1,
@@ -55,7 +56,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_commands.hrl").
@@ -89,7 +90,7 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
-spec route(jid(), jid(), xmlel()) -> ok.
-spec route(jid(), jid(), xmpp_element()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -222,6 +223,7 @@ check_peer_certificate(SockMod, Sock, Peer) ->
{error, <<"Cannot get peer certificate">>}
end.
-spec make_key({binary(), binary()}, binary()) -> binary().
make_key({From, To}, StreamID) ->
Secret = ejabberd_config:get_option(shared_key, fun(V) -> V end),
p1_sha:to_hexlist(
@@ -234,14 +236,14 @@ make_key({From, To}, StreamID) ->
init([]) ->
update_tables(),
mnesia:create_table(s2s,
ejabberd_mnesia:create(?MODULE, s2s,
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, s2s)}]),
mnesia:add_table_copy(s2s, node(), ram_copies),
mnesia:subscribe(system),
ejabberd_commands:register_commands(get_commands_spec()),
mnesia:create_table(temporarily_blocked,
ejabberd_mnesia:create(?MODULE, temporarily_blocked,
[{ram_copies, [node()]},
{attributes, record_info(fields, temporarily_blocked)}]),
{ok, #state{}}.
@@ -275,7 +277,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-spec clean_table_from_bad_node(node()) -> any().
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
@@ -289,6 +291,7 @@ clean_table_from_bad_node(Node) ->
end,
mnesia:async_dirty(F).
-spec do_route(jid(), jid(), stanza()) -> ok | false.
do_route(From, To, Packet) ->
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
@@ -296,28 +299,16 @@ do_route(From, To, Packet) ->
case find_connection(From, To) of
{atomic, Pid} when is_pid(Pid) ->
?DEBUG("sending to process ~p~n", [Pid]),
#xmlel{name = Name, attrs = Attrs, children = Els} =
Packet,
NewAttrs =
jlib:replace_from_to_attrs(jid:to_string(From),
jid:to_string(To), Attrs),
#jid{lserver = MyServer} = From,
ejabberd_hooks:run(s2s_send_packet, MyServer,
[From, To, Packet]),
send_element(Pid,
#xmlel{name = Name, attrs = NewAttrs, children = Els}),
send_element(Pid, xmpp:set_from_to(Packet, From, To)),
ok;
{aborted, _Reason} ->
case fxml:get_tag_attr_s(<<"type">>, Packet) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"No s2s connection found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end,
Lang = xmpp:get_lang(Packet),
Txt = <<"No s2s connection found">>,
Err = xmpp:err_service_unavailable(Txt, Lang),
ejabberd_router:route_error(To, From, Packet, Err),
false
end.
@@ -367,9 +358,11 @@ find_connection(From, To) ->
end
end.
-spec choose_connection(jid(), [#s2s{}]) -> pid().
choose_connection(From, Connections) ->
choose_pid(From, [C#s2s.pid || C <- Connections]).
-spec choose_pid(jid(), [pid()]) -> pid().
choose_pid(From, Pids) ->
Pids1 = case [P || P <- Pids, node(P) == node()] of
[] -> Pids;
@@ -417,22 +410,21 @@ new_connection(MyServer, Server, From, FromTo,
end,
TRes.
-spec max_s2s_connections_number({binary(), binary()}) -> integer().
max_s2s_connections_number({From, To}) ->
case acl:match_rule(From, max_s2s_connections,
jid:make(<<"">>, To, <<"">>))
of
case acl:match_rule(From, max_s2s_connections, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
end.
-spec max_s2s_connections_number_per_node({binary(), binary()}) -> integer().
max_s2s_connections_number_per_node({From, To}) ->
case acl:match_rule(From, max_s2s_connections_per_node,
jid:make(<<"">>, To, <<"">>))
of
case acl:match_rule(From, max_s2s_connections_per_node, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
end.
-spec needed_connections_number([#s2s{}], integer(), integer()) -> integer().
needed_connections_number(Ls, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode) ->
LocalLs = [L || L <- Ls, node(L#s2s.pid) == node()],
@@ -444,6 +436,7 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
%% Description: Return true if the destination must be considered as a
%% service.
%% --------------------------------------------------------------------
-spec is_service(jid(), jid()) -> boolean().
is_service(From, To) ->
LFromDomain = From#jid.lserver,
case ejabberd_config:get_option(
@@ -475,18 +468,24 @@ send_element(Pid, El) ->
get_commands_spec() ->
[#ejabberd_commands{
name = incoming_s2s_number,
tags = [stats, s2s],
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}},
policy = admin,
module = ?MODULE, function = incoming_s2s_number,
args = [], result = {s2s_incoming, integer}},
#ejabberd_commands{
name = outgoing_s2s_number,
tags = [stats, s2s],
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}}].
policy = admin,
module = ?MODULE, function = outgoing_s2s_number,
args = [], result = {s2s_outgoing, integer}},
#ejabberd_commands{name = stop_all_connections,
tags = [s2s],
desc = "Stop all outgoing and incoming connections",
policy = admin,
module = ?MODULE, function = stop_all_connections,
args = [], result = {res, rescode}}].
%% TODO Move those stats commands to ejabberd stats command ?
incoming_s2s_number() ->
@@ -502,6 +501,15 @@ supervisor_count(Supervisor) ->
length(Result)
end.
stop_all_connections() ->
lists:foreach(
fun({_Id, Pid, _Type, _Module}) ->
exit(Pid, kill)
end,
supervisor:which_children(ejabberd_s2s_in_sup) ++
supervisor:which_children(ejabberd_s2s_out_sup)),
mnesia:clear_table(s2s).
%%%----------------------------------------------------------------------
%%% Update Mnesia tables
@@ -547,7 +555,7 @@ allow_host1(MyHost, S2SHost) ->
s2s_access,
fun(A) -> A end,
all),
JID = jid:make(<<"">>, S2SHost, <<"">>),
JID = jid:make(S2SHost),
case acl:match_rule(MyHost, Rule, JID) of
deny -> false;
allow ->
+321 -352
View File
@@ -42,7 +42,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-define(DICT, dict).
@@ -62,40 +62,19 @@
connections = (?DICT):new() :: ?TDICT,
timer = make_ref() :: reference()}).
%-define(DBGFSM, true).
-type state_name() :: wait_for_stream | wait_for_feature_request | stream_established.
-type state() :: #state{}.
-type fsm_next() :: {next_state, state_name(), state()}.
-type fsm_stop() :: {stop, normal, state()}.
-type fsm_transition() :: fsm_stop() | fsm_next().
%%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
-define(STREAM_HEADER(Version),
<<"<?xml version='1.0'?><stream:stream "
"xmlns:stream='http://etherx.jabber.org/stream"
"s' xmlns='jabber:server' xmlns:db='jabber:ser"
"ver:dialback' id='",
(StateData#state.streamid)/binary, "'", Version/binary,
">">>).
-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_NAMESPACE_ERR,
fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
-define(INVALID_FROM_ERR,
fxml:element_to_binary(?SERR_INVALID_FROM)).
-define(INVALID_XML_ERR,
fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
start(SockData, Opts) ->
supervisor:start_child(ejabberd_s2s_in_sup,
[SockData, Opts]).
@@ -185,351 +164,294 @@ init([{SockMod, Socket}, Opts]) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
case {fxml:get_attr_s(<<"xmlns">>, Attrs),
fxml:get_attr_s(<<"xmlns:db">>, Attrs),
fxml:get_attr_s(<<"to">>, Attrs),
fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
of
{<<"jabber:server">>, _, Server, true}
when StateData#state.tls and
not StateData#state.authenticated ->
send_text(StateData,
?STREAM_HEADER(<<" version='1.0'">>)),
Auth = if StateData#state.tls_enabled ->
case jid:nameprep(fxml:get_attr_s(<<"from">>, Attrs)) of
From when From /= <<"">>, From /= error ->
{Result, Message} =
ejabberd_s2s:check_peer_certificate(StateData#state.sockmod,
StateData#state.socket,
From),
{Result, From, Message};
_ ->
{error, <<"(unknown)">>,
<<"Got no valid 'from' attribute">>}
end;
true ->
{no_verify, <<"(unknown)">>,
<<"TLS not (yet) enabled">>}
end,
StartTLS = if StateData#state.tls_enabled -> [];
not StateData#state.tls_enabled and
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
#stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM}
when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM ->
send_header(StateData, {1,0}),
send_element(StateData, xmpp:serr_invalid_namespace()),
{stop, normal, StateData};
#stream_start{to = #jid{lserver = Server},
from = From, version = {1,0}}
when StateData#state.tls and not StateData#state.authenticated ->
send_header(StateData, {1,0}),
Auth = if StateData#state.tls_enabled ->
case From of
#jid{} ->
{Result, Message} =
ejabberd_s2s:check_peer_certificate(
StateData#state.sockmod,
StateData#state.socket,
From#jid.lserver),
{Result, From#jid.lserver, Message};
undefined ->
{error, <<"(unknown)">>,
<<"Got no valid 'from' attribute">>}
end;
true ->
{no_verify, <<"(unknown)">>, <<"TLS not (yet) enabled">>}
end,
StartTLS = if StateData#state.tls_enabled -> [];
not StateData#state.tls_enabled and
not StateData#state.tls_required ->
[#xmlel{name = <<"starttls">>,
attrs = [{<<"xmlns">>, ?NS_TLS}],
children = []}];
not StateData#state.tls_enabled and
[#starttls{required = false}];
not StateData#state.tls_enabled and
StateData#state.tls_required ->
[#xmlel{name = <<"starttls">>,
attrs = [{<<"xmlns">>, ?NS_TLS}],
children =
[#xmlel{name = <<"required">>,
attrs = [], children = []}]}]
end,
case Auth of
{error, RemoteServer, CertError}
when StateData#state.tls_certverify ->
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
[StateData#state.server, RemoteServer, CertError]),
send_text(StateData,
<<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
CertError)))/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
{VerifyResult, RemoteServer, Msg} ->
{SASL, NewStateData} = case VerifyResult of
ok ->
{[#xmlel{name = <<"mechanisms">>,
attrs = [{<<"xmlns">>, ?NS_SASL}],
children =
[#xmlel{name = <<"mechanism">>,
attrs = [],
children =
[{xmlcdata,
<<"EXTERNAL">>}]}]}],
StateData#state{auth_domain = RemoteServer}};
error ->
?DEBUG("Won't accept certificate of ~s: ~s",
[RemoteServer, Msg]),
{[], StateData};
no_verify ->
{[], StateData}
end,
send_element(NewStateData,
#xmlel{name = <<"stream:features">>, attrs = [],
children =
SASL ++
StartTLS ++
ejabberd_hooks:run_fold(s2s_stream_features,
Server, [],
[Server])}),
{next_state, wait_for_feature_request,
NewStateData#state{server = Server}}
end;
{<<"jabber:server">>, _, Server, true}
when StateData#state.authenticated ->
send_text(StateData,
?STREAM_HEADER(<<" version='1.0'">>)),
send_element(StateData,
#xmlel{name = <<"stream:features">>, attrs = [],
children =
ejabberd_hooks:run_fold(s2s_stream_features,
Server, [],
[Server])}),
{next_state, stream_established, StateData};
{<<"jabber:server">>, <<"jabber:server:dialback">>,
_Server, _} when
(StateData#state.tls_required and StateData#state.tls_enabled)
or (not StateData#state.tls_required) ->
send_text(StateData, ?STREAM_HEADER(<<"">>)),
{next_state, stream_established, StateData};
_ ->
send_text(StateData, ?INVALID_NAMESPACE_ERR),
{stop, normal, StateData}
[#starttls{required = true}]
end,
case Auth of
{error, RemoteServer, CertError}
when StateData#state.tls_certverify ->
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
[StateData#state.server, RemoteServer, CertError]),
send_element(StateData,
xmpp:serr_policy_violation(CertError, ?MYLANG)),
{stop, normal, StateData};
{VerifyResult, RemoteServer, Msg} ->
{SASL, NewStateData} =
case VerifyResult of
ok ->
{[#sasl_mechanisms{list = [<<"EXTERNAL">>]}],
StateData#state{auth_domain = RemoteServer}};
error ->
?DEBUG("Won't accept certificate of ~s: ~s",
[RemoteServer, Msg]),
{[], StateData};
no_verify ->
{[], StateData}
end,
send_element(NewStateData,
#stream_features{
sub_els = SASL ++ StartTLS ++
ejabberd_hooks:run_fold(
s2s_stream_features, Server, [],
[Server])}),
{next_state, wait_for_feature_request,
NewStateData#state{server = Server}}
end;
#stream_start{to = #jid{lserver = Server},
version = {1,0}} when StateData#state.authenticated ->
send_header(StateData, {1,0}),
send_element(StateData,
#stream_features{
sub_els = ejabberd_hooks:run_fold(
s2s_stream_features, Server, [],
[Server])}),
{next_state, stream_established, StateData};
#stream_start{db_xmlns = ?NS_SERVER_DIALBACK}
when (StateData#state.tls_required and StateData#state.tls_enabled)
or (not StateData#state.tls_required) ->
send_header(StateData, undefined),
{next_state, stream_established, StateData};
#stream_start{} ->
send_header(StateData, {1,0}),
send_element(StateData, xmpp:serr_undefined_condition()),
{stop, normal, StateData};
_ ->
send_header(StateData, {1,0}),
send_element(StateData, xmpp:serr_invalid_xml()),
{stop, normal, StateData}
catch _:{xmpp_codec, Why} ->
Txt = xmpp:format_error(Why),
send_header(StateData, {1,0}),
send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
{stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_text(StateData,
<<(?STREAM_HEADER(<<"">>))/binary,
(?INVALID_XML_ERR)/binary, (?STREAM_TRAILER)/binary>>),
send_header(StateData, {1,0}),
send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_stream(timeout, StateData) ->
send_header(StateData, {1,0}),
send_element(StateData, xmpp:serr_connection_timeout()),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
wait_for_feature_request({xmlstreamelement, El},
StateData) ->
#xmlel{name = Name, attrs = Attrs} = El,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
SockMod =
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
{?NS_TLS, <<"starttls">>}
when TLS == true, TLSEnabled == false,
SockMod == gen_tcp ->
?DEBUG("starttls", []),
Socket = StateData#state.socket,
TLSOpts1 = case
ejabberd_config:get_option(
{domain_certfile, StateData#state.server},
fun iolist_to_binary/1) of
undefined -> StateData#state.tls_options;
CertFile ->
[{certfile, CertFile} | lists:keydelete(certfile, 1,
StateData#state.tls_options)]
end,
TLSOpts = case ejabberd_config:get_option(
{s2s_tls_compression, StateData#state.server},
fun(true) -> true;
(false) -> false
end, false) of
true -> lists:delete(compression_none, TLSOpts1);
false -> [compression_none | TLSOpts1]
end,
TLSSocket = (StateData#state.sockmod):starttls(Socket,
TLSOpts,
fxml:element_to_binary(#xmlel{name
=
<<"proceed">>,
attrs
=
[{<<"xmlns">>,
?NS_TLS}],
children
=
[]})),
{next_state, wait_for_stream,
StateData#state{socket = TLSSocket, streamid = new_id(),
tls_enabled = true, tls_options = TLSOpts}};
{?NS_SASL, <<"auth">>} when TLSEnabled ->
Mech = fxml:get_attr_s(<<"mechanism">>, Attrs),
case Mech of
<<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
AuthDomain = StateData#state.auth_domain,
AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>,
AuthDomain),
if AllowRemoteHost ->
(StateData#state.sockmod):reset_stream(StateData#state.socket),
send_element(StateData,
#xmlel{name = <<"success">>,
attrs = [{<<"xmlns">>, ?NS_SASL}],
children = []}),
?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
[AuthDomain, StateData#state.tls_enabled]),
change_shaper(StateData, <<"">>,
jid:make(<<"">>, AuthDomain, <<"">>)),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(),
authenticated = true}};
true ->
send_element(StateData,
#xmlel{name = <<"failure">>,
attrs = [{<<"xmlns">>, ?NS_SASL}],
children = []}),
send_text(StateData, ?STREAM_TRAILER),
{stop, normal, StateData}
end;
_ ->
send_element(StateData,
#xmlel{name = <<"failure">>,
attrs = [{<<"xmlns">>, ?NS_SASL}],
children =
[#xmlel{name = <<"invalid-mechanism">>,
attrs = [], children = []}]}),
{stop, normal, StateData}
end;
_ ->
stream_established({xmlstreamelement, El}, StateData)
wait_for_feature_request({xmlstreamelement, El}, StateData) ->
decode_element(El, wait_for_feature_request, StateData);
wait_for_feature_request(#starttls{},
#state{tls = true, tls_enabled = false} = StateData) ->
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
gen_tcp ->
?DEBUG("starttls", []),
Socket = StateData#state.socket,
TLSOpts1 = case
ejabberd_config:get_option(
{domain_certfile, StateData#state.server},
fun iolist_to_binary/1) of
undefined -> StateData#state.tls_options;
CertFile ->
lists:keystore(certfile, 1,
StateData#state.tls_options,
{certfile, CertFile})
end,
TLSOpts2 = case ejabberd_config:get_option(
{s2s_cafile, StateData#state.server},
fun iolist_to_binary/1) of
undefined -> TLSOpts1;
CAFile ->
lists:keystore(cafile, 1, TLSOpts1,
{cafile, CAFile})
end,
TLSOpts = case ejabberd_config:get_option(
{s2s_tls_compression, StateData#state.server},
fun(true) -> true;
(false) -> false
end, false) of
true -> lists:delete(compression_none, TLSOpts2);
false -> [compression_none | TLSOpts2]
end,
TLSSocket = (StateData#state.sockmod):starttls(
Socket, TLSOpts,
fxml:element_to_binary(
xmpp:encode(#starttls_proceed{}))),
{next_state, wait_for_stream,
StateData#state{socket = TLSSocket, streamid = new_id(),
tls_enabled = true, tls_options = TLSOpts}};
_ ->
send_element(StateData, #starttls_failure{}),
{stop, normal, StateData}
end;
wait_for_feature_request({xmlstreamend, _Name},
StateData) ->
send_text(StateData, ?STREAM_TRAILER),
wait_for_feature_request(#sasl_auth{mechanism = Mech},
#state{tls_enabled = true} = StateData) ->
case Mech of
<<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
AuthDomain = StateData#state.auth_domain,
AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>, AuthDomain),
if AllowRemoteHost ->
(StateData#state.sockmod):reset_stream(StateData#state.socket),
send_element(StateData, #sasl_success{}),
?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
[AuthDomain, StateData#state.tls_enabled]),
change_shaper(StateData, <<"">>, jid:make(AuthDomain)),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(),
authenticated = true}};
true ->
Txt = xmpp:mk_text(<<"Denied by ACL">>, ?MYLANG),
send_element(StateData,
#sasl_failure{reason = 'not-authorized',
text = Txt}),
{stop, normal, StateData}
end;
_ ->
send_element(StateData, #sasl_failure{reason = 'invalid-mechanism'}),
{stop, normal, StateData}
end;
wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_feature_request({xmlstreamerror, _},
StateData) ->
send_text(StateData,
<<(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
wait_for_feature_request({xmlstreamerror, _}, StateData) ->
send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
{stop, normal, StateData}.
{stop, normal, StateData};
wait_for_feature_request(_Pkt, #state{tls_required = TLSRequired,
tls_enabled = TLSEnabled} = StateData)
when TLSRequired and not TLSEnabled ->
Txt = <<"Use of STARTTLS required">>,
send_element(StateData, xmpp:serr_policy_violation(Txt, ?MYLANG)),
{stop, normal, StateData};
wait_for_feature_request(El, StateData) ->
stream_established({xmlstreamelement, El}, StateData).
stream_established({xmlstreamelement, El}, StateData) ->
cancel_timer(StateData#state.timer),
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
case is_key_packet(El) of
{key, To, From, Id, Key} ->
?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]),
LTo = jid:nameprep(To),
LFrom = jid:nameprep(From),
case {ejabberd_s2s:allow_host(LTo, LFrom),
lists:member(LTo,
ejabberd_router:dirty_get_all_domains())}
of
{true, true} ->
ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom),
ejabberd_s2s_out:start(LTo, LFrom,
{verify, self(), Key,
StateData#state.streamid}),
Conns = (?DICT):store({LFrom, LTo},
wait_for_verification,
StateData#state.connections),
change_shaper(StateData, LTo,
jid:make(<<"">>, LFrom, <<"">>)),
{next_state, stream_established,
StateData#state{connections = Conns, timer = Timer}};
{_, false} ->
send_text(StateData, ?HOST_UNKNOWN_ERR),
{stop, normal, StateData};
{false, _} ->
send_text(StateData, ?INVALID_FROM_ERR),
{stop, normal, StateData}
end;
{verify, To, From, Id, Key} ->
?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
LTo = jid:nameprep(To),
LFrom = jid:nameprep(From),
Type = case ejabberd_s2s:make_key({LTo, LFrom}, Id) of
Key -> <<"valid">>;
_ -> <<"invalid">>
end,
send_element(StateData,
#xmlel{name = <<"db:verify">>,
attrs =
[{<<"from">>, To}, {<<"to">>, From},
{<<"id">>, Id}, {<<"type">>, Type}],
children = []}),
{next_state, stream_established,
StateData#state{timer = Timer}};
_ ->
NewEl = jlib:remove_attr(<<"xmlns">>, El),
#xmlel{name = Name, attrs = Attrs} = NewEl,
From_s = fxml:get_attr_s(<<"from">>, Attrs),
From = jid:from_string(From_s),
To_s = fxml:get_attr_s(<<"to">>, Attrs),
To = jid:from_string(To_s),
if (To /= error) and (From /= error) ->
LFrom = From#jid.lserver,
LTo = To#jid.lserver,
if StateData#state.authenticated ->
case LFrom == StateData#state.auth_domain andalso
lists:member(LTo,
ejabberd_router:dirty_get_all_domains())
of
true ->
if (Name == <<"iq">>) or (Name == <<"message">>)
or (Name == <<"presence">>) ->
ejabberd_hooks:run(s2s_receive_packet, LTo,
[From, To, NewEl]),
ejabberd_router:route(From, To, NewEl);
true -> error
end;
false -> error
end;
true ->
case (?DICT):find({LFrom, LTo},
StateData#state.connections)
of
{ok, established} ->
if (Name == <<"iq">>) or (Name == <<"message">>)
or (Name == <<"presence">>) ->
ejabberd_hooks:run(s2s_receive_packet, LTo,
[From, To, NewEl]),
ejabberd_router:route(From, To, NewEl);
true -> error
end;
_ -> error
end
end;
true -> error
end,
ejabberd_hooks:run(s2s_loop_debug,
[{xmlstreamelement, El}]),
{next_state, stream_established,
StateData#state{timer = Timer}}
decode_element(El, stream_established, StateData#state{timer = Timer});
stream_established(#db_result{to = To, from = From, key = Key},
StateData) ->
?DEBUG("GET KEY: ~p", [{To, From, Key}]),
case {ejabberd_s2s:allow_host(To, From),
lists:member(To, ejabberd_router:dirty_get_all_domains())} of
{true, true} ->
ejabberd_s2s_out:terminate_if_waiting_delay(To, From),
ejabberd_s2s_out:start(To, From,
{verify, self(), Key,
StateData#state.streamid}),
Conns = (?DICT):store({From, To},
wait_for_verification,
StateData#state.connections),
change_shaper(StateData, To, jid:make(From)),
{next_state, stream_established,
StateData#state{connections = Conns}};
{_, false} ->
send_element(StateData, xmpp:serr_host_unknown()),
{stop, normal, StateData};
{false, _} ->
send_element(StateData, xmpp:serr_invalid_from()),
{stop, normal, StateData}
end;
stream_established(#db_verify{to = To, from = From, id = Id, key = Key},
StateData) ->
?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
Type = case ejabberd_s2s:make_key({To, From}, Id) of
Key -> valid;
_ -> invalid
end,
send_element(StateData,
#db_verify{from = To, to = From, id = Id, type = Type}),
{next_state, stream_established, StateData};
stream_established(Pkt, StateData) when ?is_stanza(Pkt) ->
From = xmpp:get_from(Pkt),
To = xmpp:get_to(Pkt),
if To /= undefined, From /= undefined ->
LFrom = From#jid.lserver,
LTo = To#jid.lserver,
if StateData#state.authenticated ->
case LFrom == StateData#state.auth_domain andalso
lists:member(LTo, ejabberd_router:dirty_get_all_domains()) of
true ->
ejabberd_hooks:run(s2s_receive_packet, LTo,
[From, To, Pkt]),
ejabberd_router:route(From, To, Pkt);
false ->
send_error(StateData, Pkt, xmpp:err_not_authorized())
end;
true ->
case (?DICT):find({LFrom, LTo}, StateData#state.connections) of
{ok, established} ->
ejabberd_hooks:run(s2s_receive_packet, LTo,
[From, To, Pkt]),
ejabberd_router:route(From, To, Pkt);
_ ->
send_error(StateData, Pkt, xmpp:err_not_authorized())
end
end;
true ->
send_error(StateData, Pkt, xmpp:err_jid_malformed())
end,
ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
{next_state, stream_established, StateData};
stream_established({valid, From, To}, StateData) ->
send_element(StateData,
#xmlel{name = <<"db:result">>,
attrs =
[{<<"from">>, To}, {<<"to">>, From},
{<<"type">>, <<"valid">>}],
children = []}),
#db_result{from = To, to = From, type = valid}),
?INFO_MSG("Accepted s2s dialback authentication for ~s (TLS=~p)",
[From, StateData#state.tls_enabled]),
LFrom = jid:nameprep(From),
LTo = jid:nameprep(To),
NSD = StateData#state{connections =
(?DICT):store({LFrom, LTo}, established,
(?DICT):store({From, To}, established,
StateData#state.connections)},
{next_state, stream_established, NSD};
stream_established({invalid, From, To}, StateData) ->
send_element(StateData,
#xmlel{name = <<"db:result">>,
attrs =
[{<<"from">>, To}, {<<"to">>, From},
{<<"type">>, <<"invalid">>}],
children = []}),
LFrom = jid:nameprep(From),
LTo = jid:nameprep(To),
#db_result{from = To, to = From, type = invalid}),
NSD = StateData#state{connections =
(?DICT):erase({LFrom, LTo},
(?DICT):erase({From, To},
StateData#state.connections)},
{next_state, stream_established, NSD};
stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData,
<<(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
stream_established(timeout, StateData) ->
send_element(StateData, xmpp:serr_connection_timeout()),
{stop, normal, StateData};
stream_established(closed, StateData) ->
{stop, normal, StateData}.
{stop, normal, StateData};
stream_established(Pkt, StateData) ->
ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
{next_state, stream_established, StateData}.
%%----------------------------------------------------------------------
%% Func: StateName/3
@@ -589,8 +511,14 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
handle_info({send_text, Text}, StateName, StateData) ->
send_text(StateData, Text),
{next_state, StateName, StateData};
handle_info({timeout, Timer, _}, _StateName,
handle_info({timeout, Timer, _}, StateName,
#state{timer = Timer} = StateData) ->
if StateName == wait_for_stream ->
send_header(StateData, undefined);
true ->
ok
end,
send_element(StateData, xmpp:serr_connection_timeout()),
{stop, normal, StateData};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
@@ -603,6 +531,7 @@ terminate(Reason, _StateName, StateData) ->
|| Host <- get_external_hosts(StateData)];
_ -> ok
end,
catch send_trailer(StateData),
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@@ -621,39 +550,55 @@ print_state(State) -> State.
%%% Internal functions
%%%----------------------------------------------------------------------
-spec send_text(state(), iodata()) -> ok.
send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket,
Text).
-spec send_element(state(), xmpp_element()) -> ok.
send_element(StateData, El) ->
send_text(StateData, fxml:element_to_binary(El)).
El1 = xmpp:encode(El, ?NS_SERVER),
send_text(StateData, fxml:element_to_binary(El1)).
-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
send_error(StateData, Stanza, Error) ->
Type = xmpp:get_type(Stanza),
if Type == error; Type == result;
Type == <<"error">>; Type == <<"result">> ->
ok;
true ->
send_element(StateData, xmpp:make_error(Stanza, Error))
end.
-spec send_trailer(state()) -> ok.
send_trailer(StateData) ->
send_text(StateData, <<"</stream:stream>">>).
-spec send_header(state(), undefined | {integer(), integer()}) -> ok.
send_header(StateData, Version) ->
Header = xmpp:encode(
#stream_start{xmlns = ?NS_SERVER,
stream_xmlns = ?NS_STREAM,
db_xmlns = ?NS_SERVER_DIALBACK,
id = StateData#state.streamid,
version = Version}),
send_text(StateData, fxml:element_to_header(Header)).
-spec change_shaper(state(), binary(), jid()) -> ok.
change_shaper(StateData, Host, JID) ->
Shaper = acl:match_rule(Host, StateData#state.shaper,
JID),
(StateData#state.sockmod):change_shaper(StateData#state.socket,
Shaper).
-spec new_id() -> binary().
new_id() -> randoms:get_string().
-spec cancel_timer(reference()) -> ok.
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
receive {timeout, Timer, _} -> ok after 0 -> ok end.
is_key_packet(#xmlel{name = Name, attrs = Attrs,
children = Els})
when Name == <<"db:result">> ->
{key, fxml:get_attr_s(<<"to">>, Attrs),
fxml:get_attr_s(<<"from">>, Attrs),
fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
is_key_packet(#xmlel{name = Name, attrs = Attrs,
children = Els})
when Name == <<"db:verify">> ->
{verify, fxml:get_attr_s(<<"to">>, Attrs),
fxml:get_attr_s(<<"from">>, Attrs),
fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
is_key_packet(_) -> false.
fsm_limit_opts(Opts) ->
case lists:keysearch(max_fsm_queue, 1, Opts) of
{value, {_, N}} when is_integer(N) -> [{max_queue, N}];
@@ -666,10 +611,34 @@ fsm_limit_opts(Opts) ->
end
end.
-spec decode_element(xmlel() | xmpp_element(), state_name(), state()) -> fsm_transition().
decode_element(#xmlel{} = El, StateName, StateData) ->
Opts = if StateName == stream_established ->
[ignore_els];
true ->
[]
end,
try xmpp:decode(El, ?NS_SERVER, Opts) of
Pkt -> ?MODULE:StateName(Pkt, StateData)
catch error:{xmpp_codec, Why} ->
case xmpp:is_stanza(El) of
true ->
Lang = xmpp:get_lang(El),
Txt = xmpp:format_error(Why),
send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
false ->
ok
end,
{next_state, StateName, StateData}
end;
decode_element(Pkt, StateName, StateData) ->
?MODULE:StateName(Pkt, StateData).
opt_type(domain_certfile) -> fun iolist_to_binary/1;
opt_type(max_fsm_queue) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(s2s_certfile) -> fun iolist_to_binary/1;
opt_type(s2s_cafile) -> fun iolist_to_binary/1;
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
opt_type(s2s_protocol_options) ->
@@ -691,6 +660,6 @@ opt_type(s2s_use_starttls) ->
(required_trusted) -> required_trusted
end;
opt_type(_) ->
[domain_certfile, max_fsm_queue, s2s_certfile,
[domain_certfile, max_fsm_queue, s2s_certfile, s2s_cafile,
s2s_ciphers, s2s_dhfile, s2s_protocol_options,
s2s_tls_compression, s2s_use_starttls].
+357 -575
View File
File diff suppressed because it is too large Load Diff
+174 -345
View File
@@ -36,7 +36,7 @@
-behaviour(?GEN_FSM).
%% External exports
-export([start/0, start/2, start_link/2, send_text/2,
-export([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,61 +44,35 @@
handle_event/3, handle_sync_event/4, code_change/4,
handle_info/3, terminate/3, print_state/1, opt_type/1]).
-include("ejabberd_service.hrl").
-include("mod_privacy.hrl").
-include("ejabberd.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
-export([get_delegated_ns/1]).
-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()}).
-type state_name() :: wait_for_stream | wait_for_handshake | stream_established.
-type state() :: #state{}.
-type fsm_next() :: {next_state, state_name(), state()}.
-type fsm_stop() :: {stop, normal, state()}.
-type fsm_transition() :: fsm_stop() | fsm_next().
%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
-define(STREAM_HEADER,
<<"<?xml version='1.0'?><stream:stream "
"xmlns:stream='http://etherx.jabber.org/stream"
"s' xmlns='jabber:component:accept' id='~s' "
"from='~s'>">>).
-define(STREAM_TRAILER, <<"</stream:stream>">>).
-define(INVALID_HEADER_ERR,
<<"<stream:stream xmlns:stream='http://etherx.ja"
"bber.org/streams'><stream:error>Invalid "
"Stream Header</stream:error></stream:stream>">>).
-define(INVALID_HANDSHAKE_ERR,
<<"<stream:error><not-authorized xmlns='urn:ietf"
":params:xml:ns:xmpp-streams'/><text "
"xmlns='urn:ietf:params:xml:ns:xmpp-streams' "
"xml:lang='en'>Invalid Handshake</text></strea"
"m:error></stream:stream>">>).
-define(INVALID_XML_ERR,
fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
-define(INVALID_NS_ERR,
fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
%%%----------------------------------------------------------------------
%%% 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,20 +83,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
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
?INFO_MSG("(~w) External service connected", [Socket]),
Access = case lists:keysearch(access, 1, Opts) of
@@ -135,30 +98,15 @@ init([{SockMod, Socket}, Opts]) ->
fun({H, Os}, D) ->
P = proplists:get_value(
password, Os,
p1_sha:sha(crypto:rand_bytes(20))),
p1_sha:sha(randoms:bytes(20))),
dict:store(H, P, D)
end, dict:new(), HOpts);
false ->
Pass = proplists:get_value(
password, Opts,
p1_sha:sha(crypto:rand_bytes(20))),
p1_sha:sha(randoms: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
@@ -172,223 +120,127 @@ 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, privilege_access = PrivAccess,
delegations = Delegations}}.
streamid = new_id(), host_opts = HostOpts,
access = Access, check_from = CheckFrom}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"jabber:component:accept">> ->
To = fxml:get_attr_s(<<"to">>, Attrs),
Host = jid:nameprep(To),
if Host == error ->
Header = io_lib:format(?STREAM_HEADER,
[<<"none">>, ?MYNAME]),
send_text(StateData,
<<(list_to_binary(Header))/binary,
(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
true ->
Header = io_lib:format(?STREAM_HEADER,
[StateData#state.streamid, fxml:crypt(To)]),
send_text(StateData, Header),
HostOpts = case dict:is_key(Host, StateData#state.host_opts) of
true ->
StateData#state.host_opts;
false ->
case dict:find(global, StateData#state.host_opts) of
{ok, GlobalPass} ->
dict:from_list([{Host, GlobalPass}]);
error ->
StateData#state.host_opts
end
end,
{next_state, wait_for_handshake,
StateData#state{host = Host, host_opts = HostOpts}}
end;
_ ->
send_text(StateData, ?INVALID_HEADER_ERR),
{stop, normal, StateData}
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
#stream_start{xmlns = NS_COMPONENT, stream_xmlns = NS_STREAM}
when NS_COMPONENT /= ?NS_COMPONENT; NS_STREAM /= ?NS_STREAM ->
send_header(StateData, ?MYNAME),
send_element(StateData, xmpp:serr_invalid_namespace()),
{stop, normal, StateData};
#stream_start{to = To} when is_record(To, jid) ->
Host = To#jid.lserver,
send_header(StateData, Host),
HostOpts = case dict:is_key(Host, StateData#state.host_opts) of
true ->
StateData#state.host_opts;
false ->
case dict:find(global, StateData#state.host_opts) of
{ok, GlobalPass} ->
dict:from_list([{Host, GlobalPass}]);
error ->
StateData#state.host_opts
end
end,
{next_state, wait_for_handshake,
StateData#state{host = Host, host_opts = HostOpts}};
#stream_start{} ->
send_header(StateData, ?MYNAME),
send_element(StateData, xmpp:serr_improper_addressing()),
{stop, normal, StateData};
_ ->
send_header(StateData, ?MYNAME),
send_element(StateData, xmpp:serr_invalid_xml()),
{stop, normal, StateData}
catch _:{xmpp_codec, Why} ->
Txt = xmpp:format_error(Why),
send_header(StateData, ?MYNAME),
send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
{stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
Header = io_lib:format(?STREAM_HEADER,
[<<"none">>, ?MYNAME]),
send_text(StateData,
<<(list_to_binary(Header))/binary, (?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
send_header(StateData, ?MYNAME),
send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData}.
wait_for_handshake({xmlstreamelement, El}, StateData) ->
#xmlel{name = Name, children = Els} = El,
case {Name, fxml:get_cdata(Els)} of
{<<"handshake">>, Digest} ->
case dict:find(StateData#state.host, StateData#state.host_opts) of
{ok, Password} ->
case p1_sha:sha(<<(StateData#state.streamid)/binary,
Password/binary>>) of
Digest ->
send_text(StateData, <<"<handshake/>">>),
lists:foreach(
fun (H) ->
ejabberd_router:register_route(H, ?MYNAME),
?INFO_MSG("Route registered for service ~p~n",
[H]),
ejabberd_hooks:run(component_connected,
[H])
end, dict:fetch_keys(StateData#state.host_opts)),
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}
end;
_ ->
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
{stop, normal, StateData}
end;
_ -> {next_state, wait_for_handshake, StateData}
decode_element(El, wait_for_handshake, StateData);
wait_for_handshake(#handshake{data = Digest}, StateData) ->
case dict:find(StateData#state.host, StateData#state.host_opts) of
{ok, Password} ->
case p1_sha:sha(<<(StateData#state.streamid)/binary,
Password/binary>>) of
Digest ->
send_element(StateData, #handshake{}),
lists:foreach(
fun (H) ->
ejabberd_router:register_route(H, ?MYNAME),
?INFO_MSG("Route registered for service ~p~n",
[H]),
ejabberd_hooks:run(component_connected, [H])
end, dict:fetch_keys(StateData#state.host_opts)),
{next_state, stream_established, StateData};
_ ->
send_element(StateData, xmpp:serr_not_authorized()),
{stop, normal, StateData}
end;
_ ->
send_element(StateData, xmpp:serr_not_authorized()),
{stop, normal, StateData}
end;
wait_for_handshake({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_handshake({xmlstreamerror, _}, StateData) ->
send_text(StateData,
<<(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
wait_for_handshake(closed, StateData) ->
{stop, normal, StateData}.
{stop, normal, StateData};
wait_for_handshake(_Pkt, StateData) ->
{next_state, wait_for_handshake, StateData}.
stream_established({xmlstreamelement, El}, StateData) ->
NewEl = jlib:remove_attr(<<"xmlns">>, El),
#xmlel{name = Name, attrs = Attrs} = NewEl,
From = fxml:get_attr_s(<<"from">>, Attrs),
FromJID = case StateData#state.check_from of
%% If the admin does not want to check the from field
%% when accept packets from any address.
%% In this case, the component can send packet of
%% behalf of the server users.
false -> jid:from_string(From);
%% The default is the standard behaviour in XEP-0114
_ ->
FromJID1 = jid:from_string(From),
case FromJID1 of
#jid{lserver = Server} ->
case dict:is_key(Server, StateData#state.host_opts) of
true -> FromJID1;
false -> error
end;
_ -> error
end
end,
To = fxml:get_attr_s(<<"to">>, Attrs),
ToJID = case To of
<<"">> -> error;
_ -> jid:from_string(To)
end,
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);
decode_element(El, stream_established, StateData);
stream_established(El, StateData) when ?is_stanza(El) ->
From = xmpp:get_from(El),
To = xmpp:get_to(El),
Lang = xmpp:get_lang(El),
if From == undefined orelse To == undefined ->
Txt = <<"Missing 'from' or 'to' attribute">>,
send_error(StateData, El, xmpp:err_jid_malformed(Txt, Lang));
true ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
Txt = <<"Incorrect stanza name or from/to JID">>,
Err = jlib:make_error_reply(NewEl, ?ERRT_BAD_REQUEST(Lang, Txt)),
send_element(StateData, Err),
error
case check_from(From, StateData) of
true ->
ejabberd_router:route(From, To, El);
false ->
Txt = <<"Improper domain part of 'from' attribute">>,
send_error(StateData, El, xmpp:err_not_allowed(Txt, Lang))
end
end,
{next_state, stream_established, StateData};
stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData,
<<(?INVALID_XML_ERR)/binary,
(?STREAM_TRAILER)/binary>>),
send_element(StateData, xmpp:serr_not_well_formed()),
{stop, normal, StateData};
stream_established(closed, StateData) ->
{stop, normal, StateData}.
{stop, normal, StateData};
stream_established(_Event, StateData) ->
{next_state, stream_established, StateData}.
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%state_name(Event, From, StateData) ->
% Reply = ok,
% {reply, Reply, state_name, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
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) ->
handle_sync_event(_Event, _From, StateName,
StateData) ->
Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({send_text, Text}, StateName, StateData) ->
send_text(StateData, Text),
{next_state, StateName, StateData};
@@ -397,64 +249,20 @@ handle_info({send_element, El}, StateName, StateData) ->
{next_state, StateName, StateData};
handle_info({route, From, To, Packet}, StateName,
StateData) ->
case acl:match_rule(global, StateData#state.access,
From)
of
case acl:match_rule(global, StateData#state.access, From) of
allow ->
#xmlel{name = Name, attrs = Attrs, children = Els} =
Packet,
Attrs2 =
jlib:replace_from_to_attrs(jid:to_string(From),
jid:to_string(To), Attrs),
Text = fxml:element_to_binary(#xmlel{name = Name,
attrs = Attrs2, children = Els}),
send_text(StateData, Text);
deny ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Denied by ACL">>,
Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
ejabberd_router:route_error(To, From, Err, Packet)
Pkt = xmpp:set_from_to(Packet, From, To),
send_element(StateData, Pkt);
deny ->
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang),
ejabberd_router:route_error(To, From, Packet, Err)
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}.
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, StateName, StateData) ->
?INFO_MSG("terminated: ~p", [Reason]),
case StateName of
@@ -462,30 +270,12 @@ terminate(Reason, StateName, StateData) ->
lists:foreach(fun (H) ->
ejabberd_router:unregister_route(H),
ejabberd_hooks:run(component_disconnected,
[StateData#state.host, Reason])
[H, Reason])
end,
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;
dict:fetch_keys(StateData#state.host_opts));
_ -> ok
end,
catch send_trailer(StateData),
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@@ -500,13 +290,68 @@ print_state(State) -> State.
%%% Internal functions
%%%----------------------------------------------------------------------
-spec send_text(state(), iodata()) -> ok.
send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket,
Text).
-spec send_element(state(), xmpp_element()) -> ok.
send_element(StateData, El) ->
send_text(StateData, fxml:element_to_binary(El)).
El1 = xmpp:encode(El, ?NS_COMPONENT),
send_text(StateData, fxml:element_to_binary(El1)).
-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
send_error(StateData, Stanza, Error) ->
Type = xmpp:get_type(Stanza),
if Type == error; Type == result;
Type == <<"error">>; Type == <<"result">> ->
ok;
true ->
send_element(StateData, xmpp:make_error(Stanza, Error))
end.
-spec send_header(state(), binary()) -> ok.
send_header(StateData, Host) ->
Header = xmpp:encode(
#stream_start{xmlns = ?NS_COMPONENT,
stream_xmlns = ?NS_STREAM,
from = jid:make(Host),
id = StateData#state.streamid}),
send_text(StateData, fxml:element_to_header(Header)).
-spec send_trailer(state()) -> ok.
send_trailer(StateData) ->
send_text(StateData, <<"</stream:stream>">>).
-spec decode_element(xmlel(), state_name(), state()) -> fsm_transition().
decode_element(#xmlel{} = El, StateName, StateData) ->
try xmpp:decode(El, ?NS_COMPONENT, [ignore_els]) of
Pkt -> ?MODULE:StateName(Pkt, StateData)
catch error:{xmpp_codec, Why} ->
case xmpp:is_stanza(El) of
true ->
Lang = xmpp:get_lang(El),
Txt = xmpp:format_error(Why),
send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
false ->
ok
end,
{next_state, StateName, StateData}
end.
-spec check_from(jid(), state()) -> boolean().
check_from(_From, #state{check_from = false}) ->
%% If the admin does not want to check the from field
%% when accept packets from any address.
%% In this case, the component can send packet of
%% behalf of the server users.
true;
check_from(From, StateData) ->
%% The default is the standard behaviour in XEP-0114
Server = From#jid.lserver,
dict:is_key(Server, StateData#state.host_opts).
-spec new_id() -> binary().
new_id() -> randoms:get_string().
transform_listen_option({hosts, Hosts, O}, Opts) ->
@@ -543,19 +388,3 @@ 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).
+182 -213
View File
@@ -78,7 +78,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_commands.hrl").
-include("mod_privacy.hrl").
@@ -98,10 +98,19 @@
%% default value for the maximum number of user connections
-define(MAX_USER_SESSIONS, infinity).
-type broadcast() :: {broadcast, broadcast_data()}.
-type broadcast_data() ::
{rebind, pid(), binary()} | %% ejabberd_c2s
{item, ljid(), mod_roster:subscription()} | %% mod_roster/mod_shared_roster
{exit, binary()} | %% mod_roster/mod_shared_roster
{privacy_list, mod_privacy:userlist(), binary()} | %% mod_privacy
{blocking, unblock_all | {block | unblock, [ljid()]}}. %% mod_blocking
%%====================================================================
%% API
%%====================================================================
-export_type([sid/0]).
-export_type([sid/0, info/0]).
start() ->
ChildSpec = {?MODULE, {?MODULE, start_link, []},
@@ -111,7 +120,7 @@ start() ->
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(jid(), jid(), xmlel() | broadcast()) -> ok.
-spec route(jid(), jid(), stanza() | broadcast()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
@@ -150,23 +159,22 @@ close_session(SID, User, Server, Resource) ->
ejabberd_hooks:run(sm_remove_connection_hook,
JID#jid.lserver, [SID, JID, Info]).
-spec check_in_subscription(any(), binary(), binary(),
any(), any(), any()) -> any().
-spec check_in_subscription(boolean(), binary(), binary(), jid(),
subscribe | subscribed | unsubscribe | unsubscribed,
binary()) -> boolean() | {stop, false}.
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
case ejabberd_auth:is_user_exists(User, Server) of
true -> Acc;
false -> {stop, false}
end.
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
-spec bounce_offline_message(jid(), jid(), message()) -> stop.
bounce_offline_message(From, To, Packet) ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Lang = xmpp:get_lang(Packet),
Txt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err),
Err = xmpp:err_service_unavailable(Txt, Lang),
ejabberd_router:route_error(To, From, Packet, Err),
stop.
-spec disconnect_removed_user(binary(), binary()) -> ok.
@@ -225,7 +233,7 @@ get_user_info(User, Server, Resource) ->
end.
-spec set_presence(sid(), binary(), binary(), binary(),
prio(), xmlel(), info()) -> ok.
prio(), presence(), info()) -> ok.
set_presence(SID, User, Server, Resource, Priority,
Presence, Info) ->
@@ -288,7 +296,7 @@ get_offline_info(Time, User, Server, Resource) ->
[#session{sid = {Time, _}, info = Info}] ->
case proplists:get_bool(offline, Info) of
true ->
Info;
Info;
false ->
none
end;
@@ -436,164 +444,105 @@ is_online(#session{info = Info}) ->
not proplists:get_bool(offline, Info).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec do_route(jid(), jid(), stanza() | broadcast()) -> any().
do_route(From, #jid{lresource = <<"">>} = To, {broadcast, _} = Packet) ->
?DEBUG("processing broadcast to bare JID: ~p", [Packet]),
lists:foreach(
fun(R) ->
do_route(From, jid:replace_resource(To, R), Packet)
end, get_user_resources(To#jid.user, To#jid.server));
do_route(From, To, {broadcast, _} = Packet) ->
case To#jid.lresource of
<<"">> ->
lists:foreach(fun(R) ->
do_route(From,
jid:replace_resource(To, R),
Packet)
end,
get_user_resources(To#jid.user, To#jid.server));
_ ->
{U, S, R} = jid:tolower(To),
Mod = get_sm_backend(S),
case online(Mod:get_sessions(U, S, R)) of
[] ->
?DEBUG("packet dropped~n", []);
Ss ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p~n", [Pid]),
Pid ! {route, From, To, Packet}
end
?DEBUG("processing broadcast to full JID: ~p", [Packet]),
{U, S, R} = jid:tolower(To),
Mod = get_sm_backend(S),
case online(Mod:get_sessions(U, S, R)) of
[] ->
?DEBUG("dropping broadcast to unavailable resourse: ~p", [Packet]);
Ss ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p: ~p", [Pid, Packet]),
Pid ! {route, From, To, Packet}
end;
do_route(From, To, #xmlel{} = Packet) ->
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
[From, To, Packet, 8]),
do_route(From, To, #presence{type = T, status = Status} = Packet)
when T == subscribe; T == subscribed; T == unsubscribe; T == unsubscribed ->
?DEBUG("processing subscription:~n~s", [xmpp:pp(Packet)]),
#jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To,
#xmlel{name = Name, attrs = Attrs} = Packet,
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case LResource of
<<"">> ->
case Name of
<<"presence">> ->
{Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs)
of
<<"subscribe">> ->
Reason = fxml:get_path_s(Packet,
[{elem,
<<"status">>},
cdata]),
{is_privacy_allow(From, To, Packet)
andalso
ejabberd_hooks:run_fold(roster_in_subscription,
LServer,
false,
[User, Server,
From,
subscribe,
Reason]),
true};
<<"subscribed">> ->
{is_privacy_allow(From, To, Packet)
andalso
ejabberd_hooks:run_fold(roster_in_subscription,
LServer,
false,
[User, Server,
From,
subscribed,
<<"">>]),
true};
<<"unsubscribe">> ->
{is_privacy_allow(From, To, Packet)
andalso
ejabberd_hooks:run_fold(roster_in_subscription,
LServer,
false,
[User, Server,
From,
unsubscribe,
<<"">>]),
true};
<<"unsubscribed">> ->
{is_privacy_allow(From, To, Packet)
andalso
ejabberd_hooks:run_fold(roster_in_subscription,
LServer,
false,
[User, Server,
From,
unsubscribed,
<<"">>]),
true};
_ -> {true, false}
end,
if Pass ->
PResources = get_user_present_resources(LUser, LServer),
lists:foreach(fun ({_, R}) ->
do_route(From,
jid:replace_resource(To,
R),
Packet)
end,
PResources);
true -> ok
end;
<<"message">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"headline">> -> route_message(From, To, Packet, headline);
<<"error">> -> ok;
<<"groupchat">> ->
ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err);
_ ->
route_message(From, To, Packet, normal)
end;
<<"iq">> -> process_iq(From, To, Packet);
_ -> ok
end;
_ ->
Mod = get_sm_backend(LServer),
case online(Mod:get_sessions(LUser, LServer, LResource)) of
[] ->
case Name of
<<"message">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"headline">> -> ok;
<<"error">> -> ok;
<<"groupchat">> ->
ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err);
_ ->
route_message(From, To, Packet, normal)
end;
<<"iq">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err)
end;
_ -> ?DEBUG("packet dropped~n", [])
end;
Ss ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p~n", [Pid]),
Pid ! {route, From, To, Packet}
end
luser = LUser, lserver = LServer} = To,
Reason = if T == subscribe -> xmpp:get_text(Status);
true -> <<"">>
end,
case is_privacy_allow(From, To, Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
LServer, false,
[User, Server, From, T, Reason]) of
true ->
Mod = get_sm_backend(LServer),
lists:foreach(
fun(#session{sid = SID, usr = {_, _, R},
priority = Prio}) when is_integer(Prio) ->
Pid = element(2, SID),
?DEBUG("sending to process ~p:~n~s",
[Pid, xmpp:pp(Packet)]),
Pid ! {route, From, jid:replace_resource(To, R), Packet};
(_) ->
ok
end, online(Mod:get_sessions(LUser, LServer)));
false ->
ok
end;
do_route(From, #jid{lresource = <<"">>} = To, #presence{} = Packet) ->
?DEBUG("processing presence to bare JID:~n~s", [xmpp:pp(Packet)]),
{LUser, LServer, _} = jid:tolower(To),
lists:foreach(
fun({_, R}) ->
do_route(From, jid:replace_resource(To, R), Packet)
end, get_user_present_resources(LUser, LServer));
do_route(From, #jid{lresource = <<"">>} = To, #message{type = T} = Packet) ->
?DEBUG("processing message to bare JID:~n~s", [xmpp:pp(Packet)]),
if T == chat; T == headline; T == normal; T == groupchat ->
route_message(From, To, Packet, T);
true ->
Lang = xmpp:get_lang(Packet),
ErrTxt = <<"User session not found">>,
Err = xmpp:err_service_unavailable(ErrTxt, Lang),
ejabberd_router:route_error(To, From, Packet, Err)
end;
do_route(From, #jid{lresource = <<"">>} = To, #iq{} = Packet) ->
?DEBUG("processing IQ to bare JID:~n~s", [xmpp:pp(Packet)]),
process_iq(From, To, Packet);
do_route(From, To, Packet) ->
?DEBUG("processing packet to full JID:~n~s", [xmpp:pp(Packet)]),
{LUser, LServer, LResource} = jid:tolower(To),
Mod = get_sm_backend(LServer),
case online(Mod:get_sessions(LUser, LServer, LResource)) of
[] ->
case Packet of
#message{type = T} when T == chat; T == normal;
T == headline; T == groupchat ->
route_message(From, To, Packet, T);
#presence{} ->
?DEBUG("dropping presence to unavalable resource:~n~s",
[xmpp:pp(Packet)]);
_ ->
Lang = xmpp:get_lang(Packet),
ErrTxt = <<"User session not found">>,
Err = xmpp:err_service_unavailable(ErrTxt, Lang),
ejabberd_router:route_error(To, From, Packet, Err)
end;
Ss ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p:~n~s", [Pid, xmpp:pp(Packet)]),
Pid ! {route, From, To, Packet}
end.
%% The default list applies to the user as a whole,
%% and is processed if there is no active list set
%% for the target session/resource to which a stanza is addressed,
%% or if there are no current sessions for the user.
-spec is_privacy_allow(jid(), jid(), stanza()) -> boolean().
is_privacy_allow(From, To, Packet) ->
User = To#jid.user,
Server = To#jid.server,
@@ -604,6 +553,7 @@ is_privacy_allow(From, To, Packet) ->
%% Check if privacy rules allow this delivery
%% Function copied from ejabberd_c2s.erl
-spec is_privacy_allow(jid(), jid(), stanza(), #userlist{}) -> boolean().
is_privacy_allow(From, To, Packet, PrivacyList) ->
User = To#jid.user,
Server = To#jid.server,
@@ -613,14 +563,15 @@ is_privacy_allow(From, To, Packet, PrivacyList) ->
[User, Server, PrivacyList, {From, To, Packet},
in]).
-spec route_message(jid(), jid(), message(), message_type()) -> any().
route_message(From, To, Packet, Type) ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
PrioRes = get_user_present_resources(LUser, LServer),
case catch lists:max(PrioRes) of
{Priority, _R}
when is_integer(Priority), Priority >= 0 ->
lists:foreach(fun ({P, R}) when P == Priority;
{MaxPrio, MaxRes}
when is_integer(MaxPrio), MaxPrio >= 0 ->
lists:foreach(fun ({P, R}) when P == MaxPrio;
(P >= 0) and (Type == headline) ->
LResource = jid:resourceprep(R),
Mod = get_sm_backend(LServer),
@@ -632,34 +583,44 @@ route_message(From, To, Packet, Type) ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p~n", [Pid]),
Pid ! {route, From, To, Packet}
LMaxRes = jid:resourceprep(MaxRes),
Packet1 = maybe_mark_as_copy(Packet,
LResource,
LMaxRes,
P, MaxPrio),
Pid ! {route, From, To, Packet1}
end;
%% Ignore other priority:
({_Prio, _Res}) -> ok
end,
PrioRes);
_ ->
case Type of
headline -> ok;
_ ->
case ejabberd_auth:is_user_exists(LUser, LServer) andalso
is_privacy_allow(From, To, Packet) of
true ->
ejabberd_hooks:run(offline_message_hook, LServer,
[From, To, Packet]);
false ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
end
end
case ejabberd_auth:is_user_exists(LUser, LServer) andalso
is_privacy_allow(From, To, Packet) of
true ->
ejabberd_hooks:run(offline_message_hook, LServer,
[From, To, Packet]);
false ->
Err = xmpp:err_service_unavailable(),
ejabberd_router:route_error(To, From, Packet, Err)
end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec maybe_mark_as_copy(message(), binary(), binary(), integer(), integer())
-> message().
maybe_mark_as_copy(Packet, R, R, P, P) ->
Packet;
maybe_mark_as_copy(Packet, _, _, P, P) ->
xmpp:put_meta(Packet, sm_copy, true);
maybe_mark_as_copy(Packet, _, _, _, _) ->
Packet.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec clean_session_list([#session{}]) -> [#session{}].
clean_session_list(Ss) ->
clean_session_list(lists:keysort(#session.usr, Ss), []).
-spec clean_session_list([#session{}], [#session{}]) -> [#session{}].
clean_session_list([], Res) -> Res;
clean_session_list([S], Res) -> [S | Res];
clean_session_list([S1, S2 | Rest], Res) ->
@@ -674,6 +635,7 @@ clean_session_list([S1, S2 | Rest], Res) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% On new session, check if some existing connections need to be replace
-spec check_for_sessions_to_replace(binary(), binary(), binary()) -> ok | replaced.
check_for_sessions_to_replace(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -681,6 +643,7 @@ check_for_sessions_to_replace(User, Server, Resource) ->
check_existing_resources(LUser, LServer, LResource),
check_max_sessions(LUser, LServer).
-spec check_existing_resources(binary(), binary(), binary()) -> ok.
check_existing_resources(LUser, LServer, LResource) ->
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer, LResource),
@@ -704,6 +667,7 @@ check_existing_resources(LUser, LServer, LResource) ->
is_existing_resource(LUser, LServer, LResource) ->
[] /= get_resource_sessions(LUser, LServer, LResource).
-spec get_resource_sessions(binary(), binary(), binary()) -> [sid()].
get_resource_sessions(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -711,18 +675,28 @@ get_resource_sessions(User, Server, Resource) ->
Mod = get_sm_backend(LServer),
[S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))].
-spec check_max_sessions(binary(), binary()) -> ok | replaced.
check_max_sessions(LUser, LServer) ->
Mod = get_sm_backend(LServer),
SIDs = [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer))],
Ss = Mod:get_sessions(LUser, LServer),
{OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
MaxSessions = get_max_user_sessions(LUser, LServer),
if length(SIDs) =< MaxSessions -> ok;
true -> {_, Pid} = lists:min(SIDs), Pid ! replaced
if length(OnlineSs) =< MaxSessions -> ok;
true ->
#session{sid = {_, Pid}} = lists:min(OnlineSs),
Pid ! replaced
end,
if length(OfflineSs) =< MaxSessions -> ok;
true ->
#session{sid = SID, usr = {_, _, R}} = lists:min(OfflineSs),
Mod:delete_session(LUser, LServer, R, SID)
end.
%% Get the user_max_session setting
%% This option defines the max number of time a given users are allowed to
%% log in
%% Defaults to infinity
-spec get_max_user_sessions(binary(), binary()) -> infinity | non_neg_integer().
get_max_user_sessions(LUser, Host) ->
case acl:match_rule(Host, max_user_sessions,
jid:make(LUser, Host, <<"">>))
@@ -734,36 +708,31 @@ get_max_user_sessions(LUser, Host) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
#iq{xmlns = XMLNS, lang = Lang} ->
Host = To#jid.lserver,
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function}] ->
ResIQ = Module:Function(From, To, IQ),
if ResIQ /= ignore ->
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
true -> ok
end;
[{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
[] ->
Txt = <<"No module is handling this query">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end;
reply -> ok;
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
ejabberd_router:route(To, From, Err),
ok
end.
-spec process_iq(jid(), jid(), iq()) -> any().
process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
when T == get; T == set ->
XMLNS = xmpp:get_ns(El),
Host = To#jid.lserver,
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function}] ->
gen_iq_handler:handle(Host, Module, Function, no_queue,
From, To, Packet);
[{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, Packet);
[] ->
Txt = <<"No module is handling this query">>,
Err = xmpp:err_service_unavailable(Txt, Lang),
ejabberd_router:route_error(To, From, Packet, Err)
end;
process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
Err = xmpp:err_bad_request(),
ejabberd_router:route_error(To, From, Packet, Err),
ok;
process_iq(_From, _To, #iq{}) ->
ok.
-spec force_update_presence({binary(), binary()}) -> any().
-spec force_update_presence({binary(), binary()}) -> ok.
force_update_presence({LUser, LServer}) ->
Mod = get_sm_backend(LServer),
+2 -3
View File
@@ -26,7 +26,6 @@
-include("ejabberd.hrl").
-include("ejabberd_sm.hrl").
-include("jlib.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-record(state, {}).
@@ -81,10 +80,10 @@ get_sessions(LUser, LServer, LResource) ->
%%%===================================================================
init([]) ->
update_tables(),
mnesia:create_table(session,
ejabberd_mnesia:create(?MODULE, session,
[{ram_copies, [node()]},
{attributes, record_info(fields, session)}]),
mnesia:create_table(session_counter,
ejabberd_mnesia:create(?MODULE, session_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, session_counter)}]),
mnesia:add_table_index(session, usr),
-1
View File
@@ -19,7 +19,6 @@
-include("ejabberd.hrl").
-include("ejabberd_sm.hrl").
-include("logger.hrl").
-include("jlib.hrl").
%%%===================================================================
%%% API
+2 -3
View File
@@ -24,7 +24,6 @@
-include("ejabberd.hrl").
-include("ejabberd_sm.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("ejabberd_sql_pt.hrl").
%%%===================================================================
@@ -148,7 +147,7 @@ timestamp_to_now(I) ->
{MSec, Sec, USec}.
dec_priority(Prio) ->
case catch jlib:binary_to_integer(Prio) of
case catch binary_to_integer(Prio) of
{'EXIT', _} ->
undefined;
Int ->
@@ -158,7 +157,7 @@ dec_priority(Prio) ->
enc_priority(undefined) ->
<<"">>;
enc_priority(Int) when is_integer(Int) ->
jlib:integer_to_binary(Int).
integer_to_binary(Int).
row_to_session(LServer, {USec, PidS, User, Resource, PrioS, InfoS}) ->
Now = timestamp_to_now(USec),
+27 -8
View File
@@ -31,6 +31,7 @@
-export([start/4,
connect/3,
connect/4,
connect/5,
starttls/2,
starttls/3,
compress/1,
@@ -41,6 +42,7 @@
change_shaper/2,
monitor/1,
get_sockmod/1,
get_transport/1,
get_peer_certificate/1,
get_verify_result/1,
close/1,
@@ -48,15 +50,16 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-type sockmod() :: ejabberd_http_bind |
ejabberd_bosh |
ejabberd_http_ws |
gen_tcp | fast_tls | ezlib.
-type receiver() :: pid () | atom().
-type socket() :: pid() | inet:socket() |
fast_tls:tls_socket() |
ezlib:zlib_socket() |
ezlib:zlib_socket() |
ejabberd_bosh:bind_socket() |
ejabberd_http_bind:bind_socket().
-record(socket_state, {sockmod = gen_tcp :: sockmod(),
@@ -65,7 +68,7 @@
-type socket_state() :: #socket_state{}.
-export_type([socket_state/0, sockmod/0]).
-export_type([socket/0, socket_state/0, sockmod/0]).
%%====================================================================
@@ -125,19 +128,21 @@ start(Module, SockMod, Socket, Opts) ->
end.
connect(Addr, Port, Opts) ->
connect(Addr, Port, Opts, infinity).
connect(Addr, Port, Opts, infinity, self()).
connect(Addr, Port, Opts, Timeout) ->
connect(Addr, Port, Opts, Timeout, self()).
connect(Addr, Port, Opts, Timeout, Owner) ->
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
{ok, Socket} ->
Receiver = ejabberd_receiver:start(Socket, gen_tcp,
none),
SocketData = #socket_state{sockmod = gen_tcp,
socket = Socket, receiver = Receiver},
Pid = self(),
case gen_tcp:controlling_process(Socket, Receiver) of
ok ->
ejabberd_receiver:become_controller(Receiver, Pid),
ejabberd_receiver:become_controller(Receiver, Owner),
{ok, SocketData};
{error, _Reason} = Error -> gen_tcp:close(Socket), Error
end;
@@ -188,7 +193,7 @@ send(SocketData, Data) ->
%% Can only be called when in c2s StateData#state.xml_socket is true
%% This function is used for HTTP bind
%% sockmod=ejabberd_http_ws|ejabberd_http_bind or any custom module
-spec send_xml(socket_state(), xmlel()) -> any().
-spec send_xml(socket_state(), fxml:xmlel()) -> any().
send_xml(SocketData, Data) ->
catch
@@ -215,6 +220,21 @@ monitor(SocketData)
get_sockmod(SocketData) ->
SocketData#socket_state.sockmod.
get_transport(#socket_state{sockmod = SockMod,
socket = Socket}) ->
case SockMod of
gen_tcp -> tcp;
fast_tls -> tls;
ezlib ->
case ezlib:get_sockmod(Socket) of
tcp -> tcp_zlib;
tls -> tls_zlib
end;
ejabberd_bosh -> http_bind;
ejabberd_http_bind -> http_bind;
ejabberd_http_ws -> websocket
end.
get_peer_certificate(SocketData) ->
fast_tls:get_peer_certificate(SocketData#socket_state.socket).
@@ -237,4 +257,3 @@ peername(#socket_state{sockmod = SockMod,
gen_tcp -> inet:peername(Socket);
_ -> SockMod:peername(Socket)
end.
+8 -8
View File
@@ -629,7 +629,7 @@ generic_sql_query_format(SQLQuery) ->
generic_escape() ->
#sql_escape{string = fun(X) -> <<"'", (escape(X))/binary, "'">> end,
integer = fun(X) -> integer_to_binary(X) end,
integer = fun(X) -> jlib:i2l(X) end,
boolean = fun(true) -> <<"1">>;
(false) -> <<"0">>
end
@@ -646,7 +646,7 @@ sqlite_sql_query_format(SQLQuery) ->
sqlite_escape() ->
#sql_escape{string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end,
integer = fun(X) -> integer_to_binary(X) end,
integer = fun(X) -> jlib:i2l(X) end,
boolean = fun(true) -> <<"1">>;
(false) -> <<"0">>
end
@@ -670,7 +670,7 @@ pgsql_prepare(SQLQuery, State) ->
pgsql_execute_escape() ->
#sql_escape{string = fun(X) -> X end,
integer = fun(X) -> [integer_to_binary(X)] end,
integer = fun(X) -> [jlib:i2l(X)] end,
boolean = fun(true) -> "1";
(false) -> "0"
end
@@ -768,7 +768,7 @@ sqlite_to_odbc(Host, {rowid, _}) ->
sqlite_to_odbc(_Host, [{columns, Columns}, {rows, TRows}]) ->
Rows = [lists:map(
fun(I) when is_integer(I) ->
jlib:integer_to_binary(I);
integer_to_binary(I);
(B) ->
B
end, tuple_to_list(Row)) || Row <- TRows],
@@ -813,11 +813,11 @@ pgsql_item_to_odbc({<<"FETCH", _/binary>>, Rows,
{selected, [element(1, Row) || Row <- Rows], Recs};
pgsql_item_to_odbc(<<"INSERT ", OIDN/binary>>) ->
[_OID, N] = str:tokens(OIDN, <<" ">>),
{updated, jlib:binary_to_integer(N)};
{updated, binary_to_integer(N)};
pgsql_item_to_odbc(<<"DELETE ", N/binary>>) ->
{updated, jlib:binary_to_integer(N)};
{updated, binary_to_integer(N)};
pgsql_item_to_odbc(<<"UPDATE ", N/binary>>) ->
{updated, jlib:binary_to_integer(N)};
{updated, binary_to_integer(N)};
pgsql_item_to_odbc({error, Error}) -> {error, Error};
pgsql_item_to_odbc(_) -> {updated, undefined}.
@@ -875,7 +875,7 @@ mysql_item_to_odbc(Columns, Recs) ->
to_odbc({selected, Columns, Recs}) ->
Rows = [lists:map(
fun(I) when is_integer(I) ->
jlib:integer_to_binary(I);
integer_to_binary(I);
(B) ->
B
end, Row) || Row <- Recs],
+19 -5
View File
@@ -49,7 +49,7 @@
-record(sql_pool, {host, pid}).
start_link(Host) ->
mnesia:create_table(sql_pool,
ejabberd_mnesia:create(?MODULE, sql_pool,
[{ram_copies, [node()]}, {type, bag},
{local_content, true},
{attributes, record_info(fields, sql_pool)}]),
@@ -61,10 +61,6 @@ start_link(Host) ->
?MODULE, [Host]).
init([Host]) ->
PoolSize = ejabberd_config:get_option(
{sql_pool_size, Host},
fun(I) when is_integer(I), I>0 -> I end,
?DEFAULT_POOL_SIZE),
StartInterval = ejabberd_config:get_option(
{sql_start_interval, Host},
fun(I) when is_integer(I), I>0 -> I end,
@@ -76,6 +72,7 @@ init([Host]) ->
(mssql) -> mssql;
(odbc) -> odbc
end, odbc),
PoolSize = get_pool_size(Type, Host),
case Type of
sqlite ->
check_sqlite_db(Host);
@@ -117,6 +114,23 @@ remove_pid(Host, Pid) ->
end,
mnesia:ets(F).
-spec get_pool_size(atom(), binary()) -> pos_integer().
get_pool_size(SQLType, Host) ->
PoolSize = ejabberd_config:get_option(
{sql_pool_size, Host},
fun(I) when is_integer(I), I>0 -> I end,
case SQLType of
sqlite -> 1;
_ -> ?DEFAULT_POOL_SIZE
end),
if PoolSize > 1 andalso SQLType == sqlite ->
?WARNING_MSG("it's not recommended to set sql_pool_size > 1 for "
"sqlite, because it may cause race conditions", []);
true ->
ok
end,
PoolSize.
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
+19 -25
View File
@@ -41,7 +41,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-record(state, {}).
@@ -61,23 +61,22 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts,
[]).
-spec process_command(jid(), jid(), stanza()) -> ok.
process_command(From, To, Packet) ->
case To of
#jid{luser = <<"">>, lresource = <<"watchdog">>} ->
#xmlel{name = Name} = Packet,
case Name of
<<"message">> ->
case Packet of
#message{body = Body} ->
LFrom =
jid:tolower(jid:remove_resource(From)),
case lists:member(LFrom, get_admin_jids()) of
true ->
Body = fxml:get_path_s(Packet,
[{elem, <<"body">>}, cdata]),
BodyText = xmpp:get_text(Body),
spawn(fun () ->
process_flag(priority, high),
process_command1(From, To, Body)
process_command1(From, To, BodyText)
end),
stop;
ok;
false -> ok
end;
_ -> ok
@@ -181,29 +180,24 @@ process_large_heap(Pid, Info) ->
Host = (?MYNAME),
JIDs = get_admin_jids(),
DetailedInfo = detailed_info(Pid),
Body = iolist_to_binary(
io_lib:format("(~w) The process ~w is consuming too "
"much memory:~n~p~n~s",
[node(), Pid, Info, DetailedInfo])),
Body = str:format("(~w) The process ~w is consuming too "
"much memory:~n~p~n~s",
[node(), Pid, Info, DetailedInfo]),
From = jid:make(<<"">>, Host, <<"watchdog">>),
Hint = [#xmlel{name = <<"no-permanent-store">>,
attrs = [{<<"xmlns">>, ?NS_HINTS}]}],
lists:foreach(fun (JID) ->
send_message(From, jid:make(JID), Body, Hint)
end, JIDs).
Hint = [#hint{type = 'no-permanent-store'}],
lists:foreach(
fun(JID) ->
send_message(From, jid:make(JID), Body, Hint)
end, JIDs).
send_message(From, To, Body) ->
send_message(From, To, Body, []).
send_message(From, To, Body, ExtraEls) ->
ejabberd_router:route(From, To,
#xmlel{name = <<"message">>,
attrs = [{<<"type">>, <<"chat">>}],
children =
[#xmlel{name = <<"body">>, attrs = [],
children =
[{xmlcdata, Body}]}
| ExtraEls]}).
#message{type = chat,
body = xmpp:mk_text(Body),
sub_els = ExtraEls}).
get_admin_jids() ->
ejabberd_config:get_option(
@@ -305,7 +299,7 @@ process_command2([<<"showlh">>, SNode], From, To) ->
process_command2([<<"setlh">>, SNode, NewValueString],
From, To) ->
Node = jlib:binary_to_atom(SNode),
NewValue = jlib:binary_to_integer(NewValueString),
NewValue = binary_to_integer(NewValueString),
remote_command(Node, [setlh, NewValue], From, To);
process_command2([<<"help">>], From, To) ->
send_message(To, From, help());
+1 -1
View File
@@ -34,7 +34,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
+65 -62
View File
@@ -38,7 +38,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
@@ -274,7 +274,7 @@ get_auth_account(HostOfRule, AccessRule, User, Server,
case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
true ->
case acl:any_rules_allowed(HostOfRule, AccessRule,
jid:make(User, Server, <<"">>))
jid:make(User, Server, <<"">>))
of
false -> {unauthorized, <<"unprivileged-account">>};
true -> {ok, {User, Server}}
@@ -747,7 +747,7 @@ process_admin(Host,
_ -> nothing
end,
ACLs = lists:keysort(2,
ets:select(acl,
mnesia:dirty_select(acl,
[{{acl, {'$1', Host}, '$2'}, [],
[{{acl, '$1', '$2'}}]}])),
{NumLines, ACLsP} = term_to_paragraph(ACLs, 80),
@@ -763,8 +763,8 @@ process_admin(Host,
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"acls">>,
(iolist_to_binary(integer_to_list(lists:max([16,
NumLines])))),
(integer_to_binary(lists:max([16,
NumLines]))),
<<"80">>, <<(iolist_to_binary(ACLsP))/binary, ".">>),
?BR,
?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
@@ -784,7 +784,7 @@ process_admin(Host,
_ -> nothing
end,
ACLs = lists:keysort(2,
ets:select(acl,
mnesia:dirty_select(acl,
[{{acl, {'$1', Host}, '$2'}, [],
[{{acl, '$1', '$2'}}]}])),
make_xhtml((?H1GL((?T(<<"Access Control Lists">>)),
@@ -849,7 +849,7 @@ process_admin(Host,
end;
_ -> nothing
end,
Access = ets:select(access,
Access = mnesia:dirty_select(access,
[{{access, {'$1', Host}, '$2'}, [],
[{{access, '$1', '$2'}}]}]),
{NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80),
@@ -865,8 +865,8 @@ process_admin(Host,
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
[?TEXTAREA(<<"access">>,
(iolist_to_binary(integer_to_list(lists:max([16,
NumLines])))),
(integer_to_binary(lists:max([16,
NumLines]))),
<<"80">>, <<(iolist_to_binary(AccessP))/binary, ".">>),
?BR,
?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
@@ -883,7 +883,7 @@ process_admin(Host,
end;
_ -> nothing
end,
AccessRules = ets:select(access,
AccessRules = mnesia:dirty_select(access,
[{{access, {'$1', Host}, '$2'}, [],
[{{access, '$1', '$2'}}]}]),
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
@@ -926,7 +926,7 @@ process_admin(Host,
Rs1 -> Rs1
end,
make_xhtml([?XC(<<"h1">>,
list_to_binary(io_lib:format(
(str:format(
?T(<<"~s access rule configuration">>),
[SName])))]
++
@@ -1052,17 +1052,21 @@ process_admin(Host,
process_admin(Host,
#request{lang = Lang, auth = {_, _Auth, AJID}} =
Request) ->
{Hook, Opts} = case Host of
global -> {webadmin_page_main, [Request]};
Host -> {webadmin_page_host, [Host, Request]}
end,
case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
Res = case Host of
global ->
ejabberd_hooks:run_fold(
webadmin_page_main, Host, [], [Request]);
_ ->
ejabberd_hooks:run_fold(
webadmin_page_host, Host, [], [Host, Request])
end,
case Res of
[] ->
setelement(1,
make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Lang,
AJID),
404);
Res -> make_xhtml(Res, Host, Lang, AJID)
_ -> make_xhtml(Res, Host, Lang, AJID)
end.
%%%==================================
@@ -1137,7 +1141,7 @@ acl_spec_select(ID, Opt) ->
%% @spec (T::any()) -> StringLine::string()
term_to_string(T) ->
StringParagraph =
iolist_to_binary(io_lib:format("~1000000p", [T])),
(str:format("~1000000p", [T])),
ejabberd_regexp:greplace(StringParagraph, <<"\\n ">>,
<<"">>).
@@ -1153,7 +1157,7 @@ term_to_paragraph(T, Cols) ->
term_to_id(T) -> jlib:encode_base64((term_to_binary(T))).
acl_parse_query(Host, Query) ->
ACLs = ets:select(acl,
ACLs = mnesia:dirty_select(acl,
[{{acl, {'$1', Host}, '$2'}, [],
[{{acl, '$1', '$2'}}]}]),
case lists:keysearch(<<"submit">>, 1, Query) of
@@ -1267,7 +1271,7 @@ access_rules_to_xhtml(AccessRules, Lang) ->
<<"Add New">>)])])]))]).
access_parse_query(Host, Query) ->
AccessRules = ets:select(access,
AccessRules = mnesia:dirty_select(access,
[{{access, {'$1', Host}, '$2'}, [],
[{{access, '$1', '$2'}}]}]),
case lists:keysearch(<<"addnew">>, 1, Query) of
@@ -1461,8 +1465,8 @@ list_users_in_diapason(Host, Diap, Lang, URLFunc) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
N1 = jlib:binary_to_integer(S1),
N2 = jlib:binary_to_integer(S2),
N1 = binary_to_integer(S1),
N2 = binary_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
[list_given_users(Host, Sub, <<"../../">>, Lang,
URLFunc)].
@@ -1502,7 +1506,7 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
{{Year, Month, Day},
{Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
(str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year,
Month,
Day,
@@ -1552,7 +1556,7 @@ su_to_list({Server, User}) ->
%%%% get_stats
get_stats(global, Lang) ->
OnlineUsers = mnesia:table_info(session, size),
OnlineUsers = ejabberd_sm:connected_users_number(),
RegisteredUsers = lists:foldl(fun (Host, Total) ->
ejabberd_auth:get_vh_registered_users_number(Host)
+ Total
@@ -1651,7 +1655,7 @@ user_info(User, Server, Query, Lang) ->
"://",
(jlib:ip_to_list(IP))/binary,
":",
(jlib:integer_to_binary(Port))/binary,
(integer_to_binary(Port))/binary,
"#",
(jlib:atom_to_binary(Node))/binary>>
end,
@@ -1679,14 +1683,14 @@ user_info(User, Server, Query, Lang) ->
Shift rem 1000000, 0},
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
(str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day,
Hour, Minute,
Second]))
end;
_ -> ?T(<<"Online">>)
end,
[?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"User ~s">>),
[?XC(<<"h1">>, (str:format(?T(<<"User ~s">>),
[us_to_list(US)])))]
++
case Res of
@@ -1769,9 +1773,7 @@ list_last_activity(Host, Lang, Integral, Period) ->
[?XAE(<<"li">>,
[{<<"style">>,
<<"width:",
(iolist_to_binary(integer_to_list(trunc(90 * V
/
Max))))/binary,
(integer_to_binary(trunc(90 * V / Max)))/binary,
"%;">>}],
[{xmlcdata, pretty_string_int(V)}])
|| V <- Hist ++ Tail])]
@@ -1846,7 +1848,7 @@ get_node(global, Node, [], Query, Lang) ->
Base = get_base_path(global, Node),
MenuItems2 = make_menu_items(global, Node, Base, Lang),
[?XC(<<"h1">>,
list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node])))]
(str:format(?T(<<"Node ~p">>), [Node])))]
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -1871,7 +1873,7 @@ get_node(global, Node, [], Query, Lang) ->
get_node(Host, Node, [], _Query, Lang) ->
Base = get_base_path(Host, Node),
MenuItems2 = make_menu_items(Host, Node, Base, Lang),
[?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node]))),
[?XC(<<"h1">>, (str:format(?T(<<"Node ~p">>), [Node]))),
?XE(<<"ul">>,
([?LI([?ACT(<<Base/binary, "modules/">>,
<<"Modules">>)])]
@@ -1930,7 +1932,7 @@ get_node(global, Node, [<<"db">>], Query, Lang) ->
end,
STables),
[?XC(<<"h1">>,
list_to_binary(io_lib:format(?T(<<"Database Tables at ~p">>),
(str:format(?T(<<"Database Tables at ~p">>),
[Node]))
)]
++
@@ -1966,9 +1968,9 @@ get_node(global, Node, [<<"backup">>], Query, Lang) ->
ok -> [?XREST(<<"Submitted">>)];
{error, Error} ->
[?XRES(<<(?T(<<"Error">>))/binary, ": ",
(list_to_binary(io_lib:format("~p", [Error])))/binary>>)]
((str:format("~p", [Error])))/binary>>)]
end,
[?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])))]
[?XC(<<"h1">>, (str:format(?T(<<"Backup of ~p">>), [Node])))]
++
ResS ++
[?XCT(<<"p">>,
@@ -2120,7 +2122,7 @@ get_node(global, Node, [<<"ports">>], Query, Lang) ->
{'EXIT', _Reason} -> error;
{is_added, ok} -> ok;
{is_added, {error, Reason}} ->
{error, iolist_to_binary(io_lib:format("~p", [Reason]))};
{error, (str:format("~p", [Reason]))};
_ -> nothing
end,
NewPorts = lists:sort(ejabberd_cluster:call(Node, ejabberd_config,
@@ -2157,7 +2159,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
end,
NewModules = lists:sort(ejabberd_cluster:call(Node, gen_mod,
loaded_modules_with_opts, [Host])),
H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])),
H1String = (str:format(?T(<<"Modules at ~p">>), [Node])),
(?H1GL(H1String, <<"modulesoverview">>,
<<"Modules Overview">>))
++
@@ -2173,12 +2175,12 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
get_node(global, Node, [<<"stats">>], _Query, Lang) ->
UpTime = ejabberd_cluster:call(Node, erlang, statistics,
[wall_clock]),
UpTimeS = list_to_binary(io_lib:format("~.3f",
UpTimeS = (str:format("~.3f",
[element(1, UpTime) / 1000])),
CPUTime = ejabberd_cluster:call(Node, erlang, statistics, [runtime]),
CPUTimeS = list_to_binary(io_lib:format("~.3f",
CPUTimeS = (str:format("~.3f",
[element(1, CPUTime) / 1000])),
OnlineUsers = mnesia:table_info(session, size),
OnlineUsers = ejabberd_sm:connected_users_number(),
TransactionsCommitted = ejabberd_cluster:call(Node, mnesia,
system_info, [transaction_commits]),
TransactionsAborted = ejabberd_cluster:call(Node, mnesia,
@@ -2188,7 +2190,7 @@ get_node(global, Node, [<<"stats">>], _Query, Lang) ->
TransactionsLogged = ejabberd_cluster:call(Node, mnesia, system_info,
[transaction_log_writes]),
[?XC(<<"h1">>,
list_to_binary(io_lib:format(?T(<<"Statistics of ~p">>), [Node]))),
(str:format(?T(<<"Statistics of ~p">>), [Node]))),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
@@ -2252,11 +2254,11 @@ get_node(global, Node, [<<"update">>], Query, Lang) ->
(BeamsLis ++ SelectButtons))
end,
FmtScript = (?XC(<<"pre">>,
list_to_binary(io_lib:format("~p", [Script])))),
(str:format("~p", [Script])))),
FmtLowLevelScript = (?XC(<<"pre">>,
list_to_binary(io_lib:format("~p", [LowLevelScript])))),
(str:format("~p", [LowLevelScript])))),
[?XC(<<"h1">>,
list_to_binary(io_lib:format(?T(<<"Update ~p">>), [Node])))]
(str:format(?T(<<"Update ~p">>), [Node])))]
++
case Res of
ok -> [?XREST(<<"Submitted">>)];
@@ -2276,16 +2278,17 @@ get_node(global, Node, [<<"update">>], Query, Lang) ->
?BR,
?INPUTT(<<"submit">>, <<"update">>, <<"Update">>)])];
get_node(Host, Node, NPath, Query, Lang) ->
{Hook, Opts} = case Host of
global ->
{webadmin_page_node, [Node, NPath, Query, Lang]};
Host ->
{webadmin_page_hostnode,
[Host, Node, NPath, Query, Lang]}
end,
case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
Res = case Host of
global ->
ejabberd_hooks:run_fold(webadmin_page_node, Host, [],
[Node, NPath, Query, Lang]);
_ ->
ejabberd_hooks:run_fold(webadmin_page_hostnode, Host, [],
[Host, Node, NPath, Query, Lang])
end,
case Res of
[] -> [?XC(<<"h1">>, <<"Not Found">>)];
Res -> Res
_ -> Res
end.
%%%==================================
@@ -2477,7 +2480,7 @@ node_ports_to_xhtml(Ports, Lang) ->
SModule, <<"15">>)]),
?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SSPort/binary>>,
(iolist_to_binary(integer_to_list(NumLines))),
(integer_to_binary(NumLines)),
<<"35">>, SOptsClean)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
@@ -2522,7 +2525,7 @@ make_netprot_html(NetProt) ->
get_port_data(PortIP, Opts) ->
{Port, IPT, IPS, _IPV, NetProt, OptsClean} =
ejabberd_listener:parse_listener_portip(PortIP, Opts),
SPort = jlib:integer_to_binary(Port),
SPort = integer_to_binary(Port),
SSPort = list_to_binary(
lists:map(fun (N) ->
io_lib:format("~.16b", [N])
@@ -2620,7 +2623,7 @@ node_modules_to_xhtml(Modules, Lang) ->
[?XC(<<"td">>, SModule),
?XAE(<<"td">>, direction(ltr),
[?TEXTAREA(<<"opts", SModule/binary>>,
(iolist_to_binary(integer_to_list(NumLines))),
(integer_to_binary(NumLines)),
<<"40">>, SOpts)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
@@ -2702,11 +2705,11 @@ node_update_parse_query(Node, Query) ->
{ok, _} -> ok;
{error, Error} ->
?ERROR_MSG("~p~n", [Error]),
{error, iolist_to_binary(io_lib:format("~p", [Error]))};
{error, (str:format("~p", [Error]))};
{badrpc, Error} ->
?ERROR_MSG("Bad RPC: ~p~n", [Error]),
{error,
<<"Bad RPC: ", (iolist_to_binary(io_lib:format("~p", [Error])))/binary>>}
<<"Bad RPC: ", ((str:format("~p", [Error])))/binary>>}
end;
_ -> nothing
end.
@@ -2771,7 +2774,7 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
element_to_list(X) when is_atom(X) ->
iolist_to_binary(atom_to_list(X));
element_to_list(X) when is_integer(X) ->
iolist_to_binary(integer_to_list(X)).
integer_to_binary(X).
list_to_element(Bin) ->
{ok, Tokens, _} = erl_scan:string(binary_to_list(Bin)),
@@ -2779,8 +2782,8 @@ list_to_element(Bin) ->
Element.
url_func({user_diapason, From, To}) ->
<<(iolist_to_binary(integer_to_list(From)))/binary, "-",
(iolist_to_binary(integer_to_list(To)))/binary, "/">>;
<<(integer_to_binary(From))/binary, "-",
(integer_to_binary(To))/binary, "/">>;
url_func({users_queue, Prefix, User, _Server}) ->
<<Prefix/binary, "user/", User/binary, "/queue/">>;
url_func({user, Prefix, User, _Server}) ->
@@ -2795,7 +2798,7 @@ cache_control_public() ->
%% Transform 1234567890 into "1,234,567,890"
pretty_string_int(Integer) when is_integer(Integer) ->
pretty_string_int(iolist_to_binary(integer_to_list(Integer)));
pretty_string_int(integer_to_binary(Integer));
pretty_string_int(String) when is_binary(String) ->
{_, Result} = lists:foldl(fun (NewNumber, {3, Result}) ->
{1, <<NewNumber, $,, Result/binary>>};
+1 -1
View File
@@ -47,7 +47,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
+64 -26
View File
@@ -42,12 +42,13 @@
-include("ejabberd_http.hrl").
-include("mod_roster.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-record(state,
{access_commands = [] :: list(),
auth = noauth :: noauth | {binary(), binary(), binary()},
get_auth = true :: boolean()}).
get_auth = true :: boolean(),
ip :: inet:ip_address()}).
%% Test:
@@ -195,7 +196,7 @@ socket_type() -> raw.
%% -----------------------------
%% HTTP interface
%% -----------------------------
process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
process(_, #request{method = 'POST', data = Data, opts = Opts, ip = {IP, _}}) ->
AccessCommandsOpts = gen_mod:get_opt(access_commands, Opts,
fun(L) when is_list(L) -> L end,
undefined),
@@ -206,7 +207,7 @@ process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
lists:flatmap(
fun({Ac, AcOpts}) ->
Commands = gen_mod:get_opt(
commands, AcOpts,
commands, lists:flatten(AcOpts),
fun(A) when is_atom(A) ->
A;
(L) when is_list(L) ->
@@ -215,19 +216,19 @@ process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
L),
L
end, all),
CommOpts = gen_mod:get_opt(
options, AcOpts,
fun(L) when is_list(L) -> L end,
[]),
[{Ac, Commands, CommOpts}];
%% CommOpts = gen_mod:get_opt(
%% options, AcOpts,
%% fun(L) when is_list(L) -> L end,
%% []),
[{<<"ejabberd_xmlrpc compatibility shim">>, {[?MODULE], [{access, Ac}], Commands}}];
(Wrong) ->
?WARNING_MSG("wrong options format for ~p: ~p",
[?MODULE, Wrong]),
[]
end, AccessCommandsOpts)
end, lists:flatten(AccessCommandsOpts))
end,
GetAuth = true,
State = #state{access_commands = AccessCommands, get_auth = GetAuth},
State = #state{access_commands = AccessCommands, get_auth = GetAuth, ip = IP},
case fxml_stream:parse_element(Data) of
{error, _} ->
{400, [],
@@ -258,21 +259,35 @@ process(_, _) ->
%% Access verification
%% -----------------------------
get_auth(AuthList) ->
Admin =
case lists:keysearch(admin, 1, AuthList) of
{value, {admin, true}} -> true;
_ -> false
end,
extract_auth(AuthList) ->
?DEBUG("AUTHLIST ~p", [AuthList]),
try get_attrs([user, server, token], AuthList) of
[U, S, T] -> {U, S, {oauth, T}, Admin}
[U0, S0, T] ->
U = jid:nodeprep(U0),
S = jid:nameprep(S0),
case ejabberd_oauth:check_token(T) of
{ok, {U, S}, Scope} ->
#{usr => {U, S, <<"">>}, oauth_scope => Scope, caller_server => S};
{false, Reason} ->
{error, Reason};
_ ->
{error, not_found}
end
catch
exit:{attribute_not_found, _Attr, _} ->
try get_attrs([user, server, password], AuthList) of
[U, S, P] -> {U, S, P, Admin}
[U0, S0, P] ->
U = jid:nodeprep(U0),
S = jid:nameprep(S0),
case ejabberd_auth:check_password(U, <<"">>, S, P) of
true ->
#{usr => {U, S, <<"">>}, caller_server => S};
false ->
{error, invalid_auth}
end
catch
exit:{attribute_not_found, Attr, _} ->
throw({error, missing_auth_arguments, Attr})
exit:{attribute_not_found, _Attr, _} ->
#{}
end
end.
@@ -300,12 +315,28 @@ get_auth(AuthList) ->
%% xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, echothis, [{struct, [{user, "badlop"}, {server, "localhost"}, {password, "79C1574A43BC995F2B145A299EF97277"}]}, 152]}).
%% {ok,{response,[152]}}
handler(#state{get_auth = true, auth = noauth} = State,
handler(#state{get_auth = true, auth = noauth, ip = IP} = State,
{call, Method,
[{struct, AuthList} | Arguments] = AllArgs}) ->
try get_auth(AuthList) of
try extract_auth(AuthList) of
{error, invalid_auth} ->
build_fault_response(-118,
"Invalid authentication data",
[]);
{error, not_found} ->
build_fault_response(-118,
"Invalid oauth token",
[]);
{error, expired} ->
build_fault_response(-118,
"Invalid oauth token",
[]);
{error, Value} ->
build_fault_response(-118,
"Invalid authentication data: ~p",
[Value]);
Auth ->
handler(State#state{get_auth = false, auth = Auth},
handler(State#state{get_auth = false, auth = Auth#{ip => IP, caller_module => ?MODULE}},
{call, Method, Arguments})
catch
{error, missing_auth_arguments, _Attr} ->
@@ -393,9 +424,14 @@ build_fault_response(Code, ParseString, ParseArgs) ->
do_command(AccessCommands, Auth, Command, AttrL, ArgsF,
ResultF) ->
ArgsFormatted = format_args(AttrL, ArgsF),
Auth2 = case AccessCommands of
V when is_list(V) ->
Auth#{extra_permissions => AccessCommands};
_ ->
Auth
end,
Result =
ejabberd_commands:execute_command(AccessCommands, Auth,
Command, ArgsFormatted),
ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth2),
ResultFormatted = format_result(Result, ResultF),
{command_result, ResultFormatted}.
@@ -489,6 +525,8 @@ process_unicode_codepoints(Str) ->
format_result({error, Error}, _) ->
throw({error, Error});
format_result({error, _Type, _Code, Error}, _) ->
throw({error, Error});
format_result(String, string) -> lists:flatten(String);
format_result(Atom, {Name, atom}) ->
{struct,
+163 -131
View File
@@ -30,12 +30,12 @@
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-export([export/2, export/3, import_file/2, import/2,
import/3, delete/1]).
-export([export/2, export/3, import/3, import/4, delete/1, import_info/1]).
-define(MAX_RECORDS_PER_TRANSACTION, 100).
-record(dump, {fd, cont = start}).
-record(sql_dump, {fd, type}).
%%%----------------------------------------------------------------------
%%% API
@@ -50,13 +50,14 @@
modules() ->
[ejabberd_auth,
mod_announce,
mod_caps,
mod_irc,
mod_last,
mod_muc,
mod_offline,
mod_privacy,
mod_private,
%% mod_pubsub,
mod_pubsub,
mod_roster,
mod_shared_roster,
mod_vcard,
@@ -100,49 +101,44 @@ delete(Server, Module) ->
delete(LServer, Table, ConvertFun)
end, Module:export(Server)).
import_file(Server, FileName) when is_binary(FileName) ->
import(Server, binary_to_list(FileName));
import_file(Server, FileName) ->
case disk_log:open([{name, make_ref()},
{file, FileName},
{mode, read_only}]) of
{ok, Fd} ->
LServer = jid:nameprep(Server),
Mods = [{Mod, gen_mod:db_type(LServer, Mod)}
|| Mod <- modules(), gen_mod:is_loaded(LServer, Mod)],
AuthMods = case lists:member(ejabberd_auth_mnesia,
ejabberd_auth:auth_modules(LServer)) of
true ->
[{ejabberd_auth, mnesia}];
false ->
[]
end,
import_dump(LServer, AuthMods ++ Mods, #dump{fd = Fd});
Err ->
exit(Err)
end.
import(Server, Output) ->
import(Server, Output, [{fast, true}]).
import(Server, Output, Opts) ->
LServer = jid:nameprep(iolist_to_binary(Server)),
Modules = modules(),
IO = prepare_output(Output, disk_log),
import(Server, Dir, ToType) ->
lists:foreach(
fun(Module) ->
import(LServer, IO, Opts, Module)
end, Modules),
close_output(Output, IO).
fun(Mod) ->
?INFO_MSG("importing ~p...", [Mod]),
import(Mod, Server, Dir, ToType)
end, modules()).
import(Server, Output, Opts, Module) ->
import(Mod, Server, Dir, ToType) ->
LServer = jid:nameprep(iolist_to_binary(Server)),
IO = prepare_output(Output, disk_log),
try Mod:import_start(LServer, ToType)
catch error:undef -> ok end,
lists:foreach(
fun({SelectQuery, ConvertFun}) ->
import(LServer, SelectQuery, IO, ConvertFun, Opts)
end, Module:import(Server)),
close_output(Output, IO).
fun({File, Tab, _Mod, FieldsNumber}) ->
FileName = filename:join([Dir, File]),
case open_sql_dump(FileName) of
{ok, #sql_dump{type = FromType} = Dump} ->
import_rows(LServer, {sql, FromType}, ToType,
Tab, Mod, Dump, FieldsNumber),
close_sql_dump(Dump);
{error, enoent} ->
ok;
eof ->
?INFO_MSG("It seems like SQL dump ~s is empty", [FileName]);
Err ->
?ERROR_MSG("Failed to open SQL dump ~s: ~s",
[FileName, format_error(Err)])
end
end, import_info(Mod)),
try Mod:import_stop(LServer, ToType)
catch error:undef -> ok end.
import_info(Mod) ->
Info = Mod:import_info(),
lists:map(
fun({Tab, FieldsNum}) ->
FileName = <<Tab/binary, ".txt">>,
{FileName, Tab, Mod, FieldsNum}
end, Info).
%%%----------------------------------------------------------------------
%%% Internal functions
@@ -200,79 +196,6 @@ delete(LServer, Table, ConvertFun) ->
end,
mnesia:transaction(F).
import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
F = case proplists:get_bool(fast, Opts) of
true ->
fun() ->
case ejabberd_sql:sql_query_t(SelectQuery) of
{selected, _, Rows} ->
lists:foldl(fun process_sql_row/2,
{IO, ConvertFun, undefined}, Rows);
Err ->
erlang:error(Err)
end
end;
false ->
fun() ->
ejabberd_sql:sql_query_t(
[iolist_to_binary(
[<<"declare c cursor for ">>, SelectQuery])]),
fetch(IO, ConvertFun, undefined)
end
end,
ejabberd_sql:sql_transaction(LServer, F).
fetch(IO, ConvertFun, PrevRow) ->
case ejabberd_sql:sql_query_t([<<"fetch c;">>]) of
{selected, _, [Row]} ->
process_sql_row(Row, {IO, ConvertFun, PrevRow}),
fetch(IO, ConvertFun, Row);
{selected, _, []} ->
ok;
Err ->
erlang:error(Err)
end.
process_sql_row(Row, {IO, ConvertFun, PrevRow}) when Row == PrevRow ->
%% Avoid calling ConvertFun with the same input
{IO, ConvertFun, Row};
process_sql_row(Row, {IO, ConvertFun, _PrevRow}) ->
case catch ConvertFun(Row) of
{'EXIT', _} = Err ->
?ERROR_MSG("failed to convert ~p: ~p", [Row, Err]);
Term ->
ok = disk_log:log(IO#dump.fd, Term)
end,
{IO, ConvertFun, Row}.
import_dump(LServer, Mods, #dump{fd = Fd, cont = Cont}) ->
case disk_log:chunk(Fd, Cont) of
{NewCont, Terms} ->
import_terms(LServer, Mods, Terms),
import_dump(LServer, Mods, #dump{fd = Fd, cont = NewCont});
eof ->
ok;
Err ->
exit(Err)
end.
import_terms(LServer, Mods, [Term|Terms]) ->
import_term(LServer, Mods, Term),
import_terms(LServer, Mods, Terms);
import_terms(_LServer, _Mods, []) ->
ok.
import_term(LServer, [{Mod, DBType}|Mods], Term) ->
case catch Mod:import(LServer, DBType, Term) of
pass -> import_term(LServer, Mods, Term);
ok -> ok;
Err ->
?ERROR_MSG("failed to import ~p for module ~p: ~p",
[Term, Mod, Err])
end;
import_term(_LServer, [], _Term) ->
ok.
prepare_output(FileName) ->
prepare_output(FileName, normal).
@@ -285,25 +208,11 @@ prepare_output(FileName, normal) when is_list(FileName) ->
Err ->
exit(Err)
end;
prepare_output(FileName, disk_log) when is_list(FileName) ->
case disk_log:open([{name, make_ref()},
{repair, truncate},
{file, FileName}]) of
{ok, Fd} ->
#dump{fd = Fd};
Err ->
exit(Err)
end;
prepare_output(Output, _Type) ->
Output.
close_output(FileName, Fd) when FileName /= Fd ->
case Fd of
#dump{} ->
disk_log:close(Fd#dump.fd);
_ ->
file:close(Fd)
end,
file:close(Fd),
ok;
close_output(_, _) ->
ok.
@@ -321,6 +230,129 @@ flatten1([H|T], Acc) ->
flatten1([], Acc) ->
Acc.
import_rows(LServer, FromType, ToType, Tab, Mod, Dump, FieldsNumber) ->
case read_row_from_sql_dump(Dump, FieldsNumber) of
{ok, Fields} ->
case catch Mod:import(LServer, FromType, ToType, Tab, Fields) of
ok ->
ok;
Err ->
?ERROR_MSG("Failed to import fields ~p for tab ~p: ~p",
[Fields, Tab, Err])
end,
import_rows(LServer, FromType, ToType,
Tab, Mod, Dump, FieldsNumber);
eof ->
ok;
Err ->
?ERROR_MSG("Failed to read row from SQL dump: ~s",
[format_error(Err)])
end.
open_sql_dump(FileName) ->
case file:open(FileName, [raw, read, binary, read_ahead]) of
{ok, Fd} ->
case file:read(Fd, 11) of
{ok, <<"PGCOPY\n", 16#ff, "\r\n", 0>>} ->
case skip_pgcopy_header(Fd) of
ok ->
{ok, #sql_dump{fd = Fd, type = pgsql}};
Err ->
Err
end;
{ok, _} ->
file:position(Fd, 0),
{ok, #sql_dump{fd = Fd, type = mysql}};
Err ->
Err
end;
Err ->
Err
end.
close_sql_dump(#sql_dump{fd = Fd}) ->
file:close(Fd).
read_row_from_sql_dump(#sql_dump{fd = Fd, type = pgsql}, _) ->
case file:read(Fd, 2) of
{ok, <<(-1):16/signed>>} ->
eof;
{ok, <<FieldsNum:16>>} ->
read_fields(Fd, FieldsNum, []);
{ok, _} ->
{error, eof};
eof ->
{error, eof};
{error, _} = Err ->
Err
end;
read_row_from_sql_dump(#sql_dump{fd = Fd, type = mysql}, FieldsNum) ->
read_lines(Fd, FieldsNum, <<"">>, []).
skip_pgcopy_header(Fd) ->
try
{ok, <<_:4/binary, ExtSize:32>>} = file:read(Fd, 8),
{ok, <<_:ExtSize/binary>>} = file:read(Fd, ExtSize),
ok
catch error:{badmatch, {error, _} = Err} ->
Err;
error:{badmatch, _} ->
{error, eof}
end.
read_fields(_Fd, 0, Acc) ->
{ok, lists:reverse(Acc)};
read_fields(Fd, N, Acc) ->
case file:read(Fd, 4) of
{ok, <<(-1):32/signed>>} ->
read_fields(Fd, N-1, [null|Acc]);
{ok, <<ValSize:32>>} ->
case file:read(Fd, ValSize) of
{ok, <<Val:ValSize/binary>>} ->
read_fields(Fd, N-1, [Val|Acc]);
{ok, _} ->
{error, eof};
Err ->
Err
end;
{ok, _} ->
{error, eof};
eof ->
{error, eof};
{error, _} = Err ->
Err
end.
read_lines(_Fd, 0, <<"">>, Acc) ->
{ok, lists:reverse(Acc)};
read_lines(Fd, N, Buf, Acc) ->
case file:read_line(Fd) of
{ok, Data} when size(Data) >= 2 ->
Size = size(Data) - 2,
case Data of
<<Val:Size/binary, 0, $\n>> ->
NewBuf = <<Buf/binary, Val/binary>>,
read_lines(Fd, N-1, <<"">>, [NewBuf|Acc]);
_ ->
NewBuf = <<Buf/binary, Data/binary>>,
read_lines(Fd, N, NewBuf, Acc)
end;
{ok, Data} ->
NewBuf = <<Buf/binary, Data/binary>>,
read_lines(Fd, N, NewBuf, Acc);
eof when Buf == <<"">>, Acc == [] ->
eof;
eof ->
{error, eof};
{error, _} = Err ->
Err
end.
format_error({error, eof}) ->
"unexpected end of file";
format_error({error, Posix}) ->
file:format_error(Posix).
format_queries(SQLs) ->
lists:map(
fun(#sql_query{} = SQL) ->

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