Compare commits

...

68 Commits

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

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

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

Signed-off-by: Peter Lemenkov <lemenkov@gmail.com>
2016-08-17 18:32:09 +03:00
Badlop 8af85d913f Erlang R17 has a Time limit in erlang:send_after (#1246) 2016-08-16 18:32:06 +02:00
Holger Weiss 20a510d877 mod_mam: Add 'store_mam_message' hook
The new 'store_mam_message' hook is invoked whenever a MAM message is
stored.
2016-08-15 23:28:36 +02:00
Holger Weiss 8821cf8b27 mod_offline: Add 'store_offline_message' hook
The new 'store_offline_message' hook is invoked whenever an offline
message is stored.
2016-08-15 22:30:08 +02:00
Holger Weiss 4d19fb518f ejabberd_c2s: Add XEP-0198 resumption hooks
The new 'c2s_session_pending' and 'c2s_session_resumed' hooks are
invoked when a stream management session is pending and resumed,
respectively.
2016-08-15 21:49:58 +02:00
Holger Weiss e7217e6320 Add functions to get/set some c2s state elements 2016-08-15 20:24:43 +02:00
Badlop 5b4f347da8 Support sql backend in mod_shared_roster commands (#1244) 2016-08-15 15:53:35 +02:00
Evgeny Khramtsov 38666cfd58 Merge pull request #1245 from hamano/clean_redis_table
fix clean redis table
2016-08-15 15:12:20 +04:00
HAMANO Tsukasa 877d0752e2 fix clean redis table 2016-08-15 17:51:06 +09:00
Badlop 0ab08f4eeb Don't delete ejabberd_commands table, it's problematic in cluster (#1210) 2016-08-14 20:35:25 +02:00
Holger Weiss 4ee8af633b Store announcements for offline users
Add a <store/> hint to announcements (unless they are explicitly sent to
online users).  Without that hint, announcements weren't delivered to
offline users, since they are sent as messages of type "headline".
2016-08-13 00:07:27 +02:00
Holger Weiss bf9d6b5534 Honor <store/> hint for any non-"error" message
XEP-0334 says: "A message containing the <store/> hint that is not of
type 'error' SHOULD be stored by the entity."
2016-08-12 21:13:10 +02:00
Holger Weiss 28dde294e5 mod_mam: Don't store messages of type "headline"
XEP-0313 says: "a server SHOULD include in a user archive all of the
messages a user sends or receives of type 'normal' or 'chat' that
contain a <body> element."
2016-08-12 20:38:17 +02:00
Evgeniy Khramtsov ffba664f2c Add a requirement for full JID in subscribe_room command 2016-08-11 17:13:20 +03:00
Paweł Chmielowski 50596dc4d3 Provide proper args_desc in oauth_issue_token command 2016-08-10 11:26:04 +02:00
Paweł Chmielowski e63fe5c216 Fix result type of subscribe_room command 2016-08-10 11:22:19 +02:00
Evgeniy Khramtsov 1fc58ace2f Add commands for MUC subscriptions management 2016-08-09 13:36:43 +03:00
Mickael Remond c4b14d045a Update to released version 2016-08-07 18:31:55 +02:00
Mickael Remond 9c6ee60f1a Update moka dependency 2016-08-07 18:28:14 +02:00
Mickael Remond efc744092b We do not force yet access rules check on register command 2016-08-07 18:27:16 +02:00
Mickael Remond a0c8012c66 Do not force command line to pass a credentials 2016-08-07 18:24:08 +02:00
Holger Weiss b62aa3d2dc mod_client_state: Let other modules filter stanzas
Don't stop execution of the 'csi_filter_stanza' hook if mod_client_state
won't queue the stanza.
2016-08-06 13:36:27 +02:00
Holger Weiss 91e26fbf7a Add user's JID to CSI hook arguments
Add the JID of the CSI user to the arguments of the 'csi_filter_stanza'
and 'csi_flush_queue' hooks.
2016-08-05 23:47:18 +02:00
Holger Weiss c2ef55a075 Cosmetic change: Set CSI state 'active' on resume
The CSI state is always set to 'active' when a stream management session
is resumed; so there's no need to apply the CSI state of the old c2s
process, first.
2016-08-05 20:09:52 +02:00
Paweł Chmielowski d969e917c6 Use newer samerlib/moka 2016-08-05 13:03:22 +02:00
Holger Weiss 9a5f0751be mod_mam: Simplify "assume_mam_usage" option
The "assume_mam_usage" option now takes a boolean value.  Setting it to
"true" has the same effect as "if_enabled" had before.  The "on_request"
behavior is no longer offered, as it made the option (and its
documentation) overly complex.
2016-08-05 01:57:01 +02:00
62 changed files with 3981 additions and 490 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ use Mix.Config
config :ejabberd,
file: "config/ejabberd.yml",
log_path: 'log/ejabberd.log'
# Customize Mnesia directory:
config :mnesia,
dir: 'mnesiadb/'
+169
View File
@@ -0,0 +1,169 @@
defmodule Ejabberd.ConfigFile do
use Ejabberd.Config
def start do
[loglevel: 4,
log_rotate_size: 10485760,
log_rotate_date: "",
log_rotate_count: 1,
log_rate_limit: 100,
auth_method: :internal,
max_fsm_queue: 1000,
language: "en",
allow_contrib_modules: true,
hosts: ["localhost"],
shaper: shaper,
acl: acl,
access: access]
end
defp shaper do
[normal: 1000,
fast: 50000,
max_fsm_queue: 1000]
end
defp acl do
[local:
[user_regexp: "", loopback: [ip: "127.0.0.0/8"]]]
end
defp access do
[max_user_sessions: [all: 10],
max_user_offline_messages: [admin: 5000, all: 100],
local: [local: :allow],
c2s: [blocked: :deny, all: :allow],
c2s_shaper: [admin: :none, all: :normal],
s2s_shaper: [all: :fast],
announce: [admin: :allow],
configure: [admin: :allow],
muc_admin: [admin: :allow],
muc_create: [local: :allow],
muc: [all: :allow],
pubsub_createnode: [local: :allow],
register: [all: :allow],
trusted_network: [loopback: :allow]]
end
listen :ejabberd_c2s do
@opts [
port: 5222,
max_stanza_size: 65536,
shaper: :c2s_shaper,
access: :c2s]
end
listen :ejabberd_s2s_in do
@opts [port: 5269]
end
listen :ejabberd_http do
@opts [
port: 5280,
web_admin: true,
http_poll: true,
http_bind: true,
captcha: true]
end
module :mod_adhoc do
end
module :mod_announce do
@opts [access: :announce]
end
module :mod_blocking do
end
module :mod_caps do
end
module :mod_carboncopy do
end
module :mod_client_state do
@opts [
drop_chat_states: true,
queue_presence: false]
end
module :mod_configure do
end
module :mod_disco do
end
module :mod_irc do
end
module :mod_http_bind do
end
module :mod_last do
end
module :mod_muc do
@opts [
access: :muc,
access_create: :muc_create,
access_persistent: :muc_create,
access_admin: :muc_admin]
end
module :mod_offline do
@opts [access_max_user_messages: :max_user_offline_messages]
end
module :mod_ping do
end
module :mod_privacy do
end
module :mod_private do
end
module :mod_pubsub do
@opts [
access_createnode: :pubsub_createnode,
ignore_pep_from_offline: true,
last_item_cache: true,
plugins: ["flat", "hometree", "pep"]]
end
module :mod_register do
@opts [welcome_message: [
subject: "Welcome!",
body: "Hi.\nWelcome to this XMPP Server",
ip_access: :trusted_network,
access: :register]]
end
module :mod_roster do
end
module :mod_shared_roster do
end
module :mod_stats do
end
module :mod_time do
end
module :mod_version do
end
# Example of how to define a hook, called when the event
# specified is triggered.
#
# @event: Name of the event
# @opts: Params are optional. Available: :host and :priority.
# If missing, defaults are used. (host: :global | priority: 50)
# @callback Could be an anonymous function or a callback from a module,
# use the &ModuleName.function/arity format for that.
hook :register_user, [host: "localhost"], fn(user, server) ->
info("User registered: #{user} on #{server}")
end
end
+667
View File
@@ -0,0 +1,667 @@
###
### ejabberd configuration file
###
###
### The parameters used in this configuration file are explained in more detail
### in the ejabberd Installation and Operation Guide.
### Please consult the Guide in case of doubts, it is included with
### your copy of ejabberd, and is also available online at
### http://www.process-one.net/en/ejabberd/docs/
### The configuration file is written in YAML.
### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
### However, ejabberd treats different literals as different types:
###
### - unquoted or single-quoted strings. They are called "atoms".
### Example: dog, 'Jupiter', '3.14159', YELLOW
###
### - numeric literals. Example: 3, -45.0, .0
###
### - quoted or folded strings.
### Examples of quoted string: "Lizzard", "orange".
### Example of folded string:
### > Art thou not Romeo,
### and a Montague?
### =======
### LOGGING
##
## loglevel: Verbosity of log files generated by ejabberd.
## 0: No ejabberd log at all (not recommended)
## 1: Critical
## 2: Error
## 3: Warning
## 4: Info
## 5: Debug
##
loglevel: 4
##
## rotation: Describe how to rotate logs. Either size and/or date can trigger
## log rotation. Setting count to N keeps N rotated logs. Setting count to 0
## does not disable rotation, it instead rotates the file and keeps no previous
## versions around. Setting size to X rotate log when it reaches X bytes.
## To disable rotation set the size to 0 and the date to ""
## Date syntax is taken from the syntax newsyslog uses in newsyslog.conf.
## Some examples:
## $D0 rotate every night at midnight
## $D23 rotate every day at 23:00 hr
## $W0D23 rotate every week on Sunday at 23:00 hr
## $W5D16 rotate every week on Friday at 16:00 hr
## $M1D0 rotate on the first day of every month at midnight
## $M5D6 rotate on every 5th day of the month at 6:00 hr
##
log_rotate_size: 10485760
log_rotate_date: ""
log_rotate_count: 1
##
## overload protection: If you want to limit the number of messages per second
## allowed from error_logger, which is a good idea if you want to avoid a flood
## of messages when system is overloaded, you can set a limit.
## 100 is ejabberd's default.
log_rate_limit: 100
##
## watchdog_admins: Only useful for developers: if an ejabberd process
## consumes a lot of memory, send live notifications to these XMPP
## accounts.
##
## watchdog_admins:
## - "bob@example.com"
### ================
### SERVED HOSTNAMES
##
## hosts: Domains served by ejabberd.
## You can define one or several, for example:
## hosts:
## - "example.net"
## - "example.com"
## - "example.org"
##
hosts:
- "localhost"
##
## route_subdomains: Delegate subdomains to other XMPP servers.
## For example, if this ejabberd serves example.org and you want
## to allow communication with an XMPP server called im.example.org.
##
## route_subdomains: s2s
### ===============
### LISTENING PORTS
##
## listen: The ports ejabberd will listen on, which service each is handled
## by and what options to start it with.
##
listen:
-
port: 5222
module: ejabberd_c2s
##
## If TLS is compiled in and you installed a SSL
## certificate, specify the full path to the
## file and uncomment these lines:
##
## certfile: "/path/to/ssl.pem"
## starttls: true
##
## To enforce TLS encryption for client connections,
## use this instead of the "starttls" option:
##
## starttls_required: true
##
## Custom OpenSSL options
##
## protocol_options:
## - "no_sslv3"
## - "no_tlsv1"
max_stanza_size: 65536
shaper: c2s_shaper
access: c2s
-
port: 5269
module: ejabberd_s2s_in
##
## ejabberd_service: Interact with external components (transports, ...)
##
## -
## port: 8888
## module: ejabberd_service
## access: all
## shaper_rule: fast
## ip: "127.0.0.1"
## hosts:
## "icq.example.org":
## password: "secret"
## "sms.example.org":
## password: "secret"
##
## ejabberd_stun: Handles STUN Binding requests
##
## -
## port: 3478
## transport: udp
## module: ejabberd_stun
##
## To handle XML-RPC requests that provide admin credentials:
##
## -
## port: 4560
## module: ejabberd_xmlrpc
-
port: 5280
module: ejabberd_http
## request_handlers:
## "/pub/archive": mod_http_fileserver
web_admin: true
http_poll: true
http_bind: true
## register: true
captcha: true
##
## s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
## Allowed values are: false optional required required_trusted
## You must specify a certificate file.
##
## s2s_use_starttls: optional
##
## s2s_certfile: Specify a certificate file.
##
## s2s_certfile: "/path/to/ssl.pem"
## Custom OpenSSL options
##
## s2s_protocol_options:
## - "no_sslv3"
## - "no_tlsv1"
##
## domain_certfile: Specify a different certificate for each served hostname.
##
## host_config:
## "example.org":
## domain_certfile: "/path/to/example_org.pem"
## "example.com":
## domain_certfile: "/path/to/example_com.pem"
##
## S2S whitelist or blacklist
##
## Default s2s policy for undefined hosts.
##
## s2s_access: s2s
##
## Outgoing S2S options
##
## Preferred address families (which to try first) and connect timeout
## in milliseconds.
##
## outgoing_s2s_families:
## - ipv4
## - ipv6
## outgoing_s2s_timeout: 10000
### ==============
### AUTHENTICATION
##
## auth_method: Method used to authenticate the users.
## The default method is the internal.
## If you want to use a different method,
## comment this line and enable the correct ones.
##
auth_method: internal
##
## Store the plain passwords or hashed for SCRAM:
## auth_password_format: plain
## auth_password_format: scram
##
## Define the FQDN if ejabberd doesn't detect it:
## fqdn: "server3.example.com"
##
## Authentication using external script
## Make sure the script is executable by ejabberd.
##
## auth_method: external
## extauth_program: "/path/to/authentication/script"
##
## Authentication using ODBC
## Remember to setup a database in the next section.
##
## auth_method: odbc
##
## Authentication using PAM
##
## auth_method: pam
## pam_service: "pamservicename"
##
## Authentication using LDAP
##
## auth_method: ldap
##
## List of LDAP servers:
## ldap_servers:
## - "localhost"
##
## Encryption of connection to LDAP servers:
## ldap_encrypt: none
## ldap_encrypt: tls
##
## Port to connect to on LDAP servers:
## ldap_port: 389
## ldap_port: 636
##
## LDAP manager:
## ldap_rootdn: "dc=example,dc=com"
##
## Password of LDAP manager:
## ldap_password: "******"
##
## Search base of LDAP directory:
## ldap_base: "dc=example,dc=com"
##
## LDAP attribute that holds user ID:
## ldap_uids:
## - "mail": "%u@mail.example.org"
##
## LDAP filter:
## ldap_filter: "(objectClass=shadowAccount)"
##
## Anonymous login support:
## auth_method: anonymous
## anonymous_protocol: sasl_anon | login_anon | both
## allow_multiple_connections: true | false
##
## host_config:
## "public.example.org":
## auth_method: anonymous
## allow_multiple_connections: false
## anonymous_protocol: sasl_anon
##
## To use both anonymous and internal authentication:
##
## host_config:
## "public.example.org":
## auth_method:
## - internal
## - anonymous
### ==============
### DATABASE SETUP
## ejabberd by default uses the internal Mnesia database,
## so you do not necessarily need this section.
## This section provides configuration examples in case
## you want to use other database backends.
## Please consult the ejabberd Guide for details on database creation.
##
## MySQL server:
##
## odbc_type: mysql
## odbc_server: "server"
## odbc_database: "database"
## odbc_username: "username"
## odbc_password: "password"
##
## If you want to specify the port:
## odbc_port: 1234
##
## PostgreSQL server:
##
## odbc_type: pgsql
## odbc_server: "server"
## odbc_database: "database"
## odbc_username: "username"
## odbc_password: "password"
##
## If you want to specify the port:
## odbc_port: 1234
##
## If you use PostgreSQL, have a large database, and need a
## faster but inexact replacement for "select count(*) from users"
##
## pgsql_users_number_estimate: true
##
## ODBC compatible or MSSQL server:
##
## odbc_type: odbc
## odbc_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
##
## Number of connections to open to the database for each virtual host
##
## odbc_pool_size: 10
##
## Interval to make a dummy SQL request to keep the connections to the
## database alive. Specify in seconds: for example 28800 means 8 hours
##
## odbc_keepalive_interval: undefined
### ===============
### TRAFFIC SHAPERS
shaper:
##
## The "normal" shaper limits traffic speed to 1000 B/s
##
normal: 1000
##
## The "fast" shaper limits traffic speed to 50000 B/s
##
fast: 50000
##
## This option specifies the maximum number of elements in the queue
## of the FSM. Refer to the documentation for details.
##
max_fsm_queue: 1000
###. ====================
###' ACCESS CONTROL LISTS
acl:
##
## The 'admin' ACL grants administrative privileges to XMPP accounts.
## You can put here as many accounts as you want.
##
## admin:
## user:
## - "aleksey": "localhost"
## - "ermine": "example.org"
##
## Blocked users
##
## blocked:
## user:
## - "baduser": "example.org"
## - "test"
## Local users: don't modify this.
##
local:
user_regexp: ""
##
## More examples of ACLs
##
## jabberorg:
## server:
## - "jabber.org"
## aleksey:
## user:
## - "aleksey": "jabber.ru"
## test:
## user_regexp: "^test"
## user_glob: "test*"
##
## Loopback network
##
loopback:
ip:
- "127.0.0.0/8"
##
## Bad XMPP servers
##
## bad_servers:
## server:
## - "xmpp.zombie.org"
## - "xmpp.spam.com"
##
## Define specific ACLs in a virtual host.
##
## host_config:
## "localhost":
## acl:
## admin:
## user:
## - "bob-local": "localhost"
### ============
### ACCESS RULES
access:
## Maximum number of simultaneous sessions allowed for a single user:
max_user_sessions:
all: 10
## Maximum number of offline messages that users can have:
max_user_offline_messages:
admin: 5000
all: 100
## This rule allows access only for local users:
local:
local: allow
## Only non-blocked users can use c2s connections:
c2s:
blocked: deny
all: allow
## For C2S connections, all users except admins use the "normal" shaper
c2s_shaper:
admin: none
all: normal
## All S2S connections use the "fast" shaper
s2s_shaper:
all: fast
## Only admins can send announcement messages:
announce:
admin: allow
## Only admins can use the configuration interface:
configure:
admin: allow
## Admins of this server are also admins of the MUC service:
muc_admin:
admin: allow
## Only accounts of the local ejabberd server can create rooms:
muc_create:
local: allow
## All users are allowed to use the MUC service:
muc:
all: allow
## Only accounts on the local ejabberd server can create Pubsub nodes:
pubsub_createnode:
local: allow
## In-band registration allows registration of any possible username.
## To disable in-band registration, replace 'allow' with 'deny'.
register:
all: allow
## Only allow to register from localhost
trusted_network:
loopback: allow
## Do not establish S2S connections with bad servers
## s2s:
## bad_servers: deny
## all: allow
## By default the frequency of account registrations from the same IP
## is limited to 1 account every 10 minutes. To disable, specify: infinity
## registration_timeout: 600
##
## Define specific Access Rules in a virtual host.
##
## host_config:
## "localhost":
## access:
## c2s:
## admin: allow
## all: deny
## register:
## all: deny
### ================
### DEFAULT LANGUAGE
##
## language: Default language used for server messages.
##
language: "en"
##
## Set a different default language in a virtual host.
##
## host_config:
## "localhost":
## language: "ru"
### =======
### CAPTCHA
##
## Full path to a script that generates the image.
##
## captcha_cmd: "/lib/ejabberd/priv/bin/captcha.sh"
##
## Host for the URL and port where ejabberd listens for CAPTCHA requests.
##
## captcha_host: "example.org:5280"
##
## Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
##
## captcha_limit: 5
### =======
### MODULES
##
## Modules enabled in all ejabberd virtual hosts.
##
modules:
mod_adhoc: {}
## mod_admin_extra: {}
mod_announce: # recommends mod_adhoc
access: announce
mod_blocking: {} # requires mod_privacy
mod_caps: {}
mod_carboncopy: {}
mod_client_state:
drop_chat_states: true
queue_presence: false
mod_configure: {} # requires mod_adhoc
mod_disco: {}
## mod_echo: {}
mod_irc: {}
mod_http_bind: {}
## mod_http_fileserver:
## docroot: "/var/www"
## accesslog: "/var/log/ejabberd/access.log"
mod_last: {}
mod_muc:
## host: "conference.@HOST@"
access: muc
access_create: muc_create
access_persistent: muc_create
access_admin: muc_admin
## mod_muc_log: {}
mod_offline:
access_max_user_messages: max_user_offline_messages
mod_ping: {}
## mod_pres_counter:
## count: 5
## interval: 60
mod_privacy: {}
mod_private: {}
## mod_proxy65: {}
mod_pubsub:
access_createnode: pubsub_createnode
## reduces resource comsumption, but XEP incompliant
ignore_pep_from_offline: true
## XEP compliant, but increases resource comsumption
## ignore_pep_from_offline: false
last_item_cache: false
plugins:
- "flat"
- "hometree"
- "pep" # pep requires mod_caps
mod_register:
##
## Protect In-Band account registrations with CAPTCHA.
##
## captcha_protected: true
##
## Set the minimum informational entropy for passwords.
##
## password_strength: 32
##
## After successful registration, the user receives
## a message with this subject and body.
##
welcome_message:
subject: "Welcome!"
body: |-
Hi.
Welcome to this XMPP server.
##
## When a user registers, send a notification to
## these XMPP accounts.
##
## registration_watchers:
## - "admin1@example.org"
##
## Only clients in the server machine can register accounts
##
ip_access: trusted_network
##
## Local c2s or remote s2s users cannot register accounts
##
## access_from: deny
access: register
mod_roster: {}
mod_shared_roster: {}
mod_stats: {}
mod_time: {}
mod_vcard: {}
mod_version: {}
##
## Enable modules with custom options in a specific virtual host
##
## host_config:
## "localhost":
## modules:
## mod_echo:
## host: "mirror.localhost"
##
## Enable modules management via ejabberdctl for installation and
## uninstallation of public/private contributed modules
## (enabled by default)
##
allow_contrib_modules: true
### Local Variables:
### mode: yaml
### End:
### vim: set filetype=yaml tabstop=8
+10
View File
@@ -147,6 +147,15 @@ listen:
## access: all
## shaper_rule: fast
## ip: "127.0.0.1"
## privilege_access:
## roster: "both"
## message: "outgoing"
## presence: "roster"
## delegations:
## "urn:xmpp:mam:1":
## filtering: ["node"]
## "http://jabber.org/protocol/pubsub":
## filtering: []
## hosts:
## "icq.example.org":
## password: "secret"
@@ -580,6 +589,7 @@ modules:
mod_carboncopy: {}
mod_client_state: {}
mod_configure: {} # requires mod_adhoc
##mod_delegation: {} # for xep0356
mod_disco: {}
## mod_echo: {}
mod_irc: {}
+20
View File
@@ -0,0 +1,20 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-type filter_attr() :: {binary(), [binary()]}.
-record(state,
{socket :: ejabberd_socket:socket_state(),
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
streamid = <<"">> :: binary(),
host_opts = dict:new() :: ?TDICT,
host = <<"">> :: binary(),
access :: atom(),
check_from = true :: boolean(),
server_hosts = ?MYHOSTS :: [binary()],
privilege_access :: [attr()],
delegations :: [filter_attr()],
last_pres = dict:new() :: ?TDICT}).
-type(state() :: #state{} ).
+8 -2
View File
@@ -77,11 +77,15 @@
jid :: jid(),
nick :: binary(),
role :: role(),
is_subscriber = false :: boolean(),
subscriptions = [] :: [binary()],
%%is_subscriber = false :: boolean(),
%%subscriptions = [] :: [binary()],
last_presence :: xmlel()
}).
-record(subscriber, {jid :: jid(),
nick = <<>> :: binary(),
nodes = [] :: [binary()]}).
-record(activity,
{
message_time = 0 :: integer(),
@@ -101,6 +105,8 @@
jid = #jid{} :: jid(),
config = #config{} :: config(),
users = (?DICT):new() :: ?TDICT,
subscribers = (?DICT):new() :: ?TDICT,
subscriber_nicks = (?DICT):new() :: ?TDICT,
last_voice_request_time = treap:empty() :: treap:treap(),
robots = (?DICT):new() :: ?TDICT,
nicks = (?DICT):new() :: ?TDICT,
+2
View File
@@ -164,6 +164,8 @@
-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">>).
+119
View File
@@ -0,0 +1,119 @@
defmodule Ejabberd.Config.Attr do
@moduledoc """
Module used to work with the attributes parsed from
an elixir block (do...end).
Contains functions for extracting attrs from a block
and validation.
"""
@type attr :: {atom(), any()}
@attr_supported [
active:
[type: :boolean, default: true],
git:
[type: :string, default: ""],
name:
[type: :string, default: ""],
opts:
[type: :list, default: []],
dependency:
[type: :list, default: []]
]
@doc """
Takes a block with annotations and extracts the list
of attributes.
"""
@spec extract_attrs_from_block_with_defaults(any()) :: [attr]
def extract_attrs_from_block_with_defaults(block) do
block
|> extract_attrs_from_block
|> put_into_list_if_not_already
|> insert_default_attrs_if_missing
end
@doc """
Takes an attribute or a list of attrs and validate them.
Returns a {:ok, attr} or {:error, attr, cause} for each of the attributes.
"""
@spec validate([attr]) :: [{:ok, attr}] | [{:error, attr, atom()}]
def validate(attrs) when is_list(attrs), do: Enum.map(attrs, &valid_attr?/1)
def validate(attr), do: validate([attr]) |> List.first
@doc """
Returns the type of an attribute, given its name.
"""
@spec get_type_for_attr(atom()) :: atom()
def get_type_for_attr(attr_name) do
@attr_supported
|> Keyword.get(attr_name)
|> Keyword.get(:type)
end
@doc """
Returns the default value for an attribute, given its name.
"""
@spec get_default_for_attr(atom()) :: any()
def get_default_for_attr(attr_name) do
@attr_supported
|> Keyword.get(attr_name)
|> Keyword.get(:default)
end
# Private API
# Given an elixir block (do...end) returns a list with the annotations
# or a single annotation.
@spec extract_attrs_from_block(any()) :: [attr] | attr
defp extract_attrs_from_block({:__block__, [], attrs}), do: Enum.map(attrs, &extract_attrs_from_block/1)
defp extract_attrs_from_block({:@, _, [attrs]}), do: extract_attrs_from_block(attrs)
defp extract_attrs_from_block({attr_name, _, [value]}), do: {attr_name, value}
defp extract_attrs_from_block(nil), do: []
# In case extract_attrs_from_block returns a single attribute,
# then put it into a list. (Ensures attrs are always into a list).
@spec put_into_list_if_not_already([attr] | attr) :: [attr]
defp put_into_list_if_not_already(attrs) when is_list(attrs), do: attrs
defp put_into_list_if_not_already(attr), do: [attr]
# Given a list of attributes, it inserts the missing attribute with their
# default value.
@spec insert_default_attrs_if_missing([attr]) :: [attr]
defp insert_default_attrs_if_missing(attrs) do
Enum.reduce @attr_supported, attrs, fn({attr_name, _}, acc) ->
case Keyword.has_key?(acc, attr_name) do
true -> acc
false -> Keyword.put(acc, attr_name, get_default_for_attr(attr_name))
end
end
end
# Given an attribute, validates it and return a tuple with
# {:ok, attr} or {:error, attr, cause}
@spec valid_attr?(attr) :: {:ok, attr} | {:error, attr, atom()}
defp valid_attr?({attr_name, param} = attr) do
case Keyword.get(@attr_supported, attr_name) do
nil -> {:error, attr, :attr_not_supported}
[{:type, param_type} | _] -> case is_of_type?(param, param_type) do
true -> {:ok, attr}
false -> {:error, attr, :type_not_supported}
end
end
end
# Given an attribute value and a type, it returns a true
# if the value its of the type specified, false otherwise.
# Usefoul for checking if an attr value respects the type
# specified for the annotation.
@spec is_of_type?(any(), atom()) :: boolean()
defp is_of_type?(param, type) when type == :boolean and is_boolean(param), do: true
defp is_of_type?(param, type) when type == :string and is_bitstring(param), do: true
defp is_of_type?(param, type) when type == :list and is_list(param), do: true
defp is_of_type?(param, type) when type == :atom and is_atom(param), do: true
defp is_of_type?(_param, type) when type == :any, do: true
defp is_of_type?(_, _), do: false
end
+145
View File
@@ -0,0 +1,145 @@
defmodule Ejabberd.Config do
@moduledoc """
Base module for configuration file.
Imports macros for the config DSL and contains functions
for working/starting the configuration parsed.
"""
alias Ejabberd.Config.EjabberdModule
alias Ejabberd.Config.Attr
alias Ejabberd.Config.EjabberdLogger
defmacro __using__(_opts) do
quote do
import Ejabberd.Config, only: :macros
import Ejabberd.Logger
@before_compile Ejabberd.Config
end
end
# Validate the modules parsed and log validation errors at compile time.
# Could be also possible to interrupt the compilation&execution by throwing
# an exception if necessary.
def __before_compile__(_env) do
get_modules_parsed_in_order
|> EjabberdModule.validate
|> EjabberdLogger.log_errors
end
@doc """
Given the path of the config file, it evaluates it.
"""
def init(file_path, force \\ false) do
init_already_executed = Ejabberd.Config.Store.get(:module_name) != []
case force do
true ->
Ejabberd.Config.Store.stop
Ejabberd.Config.Store.start_link
do_init(file_path)
false ->
if not init_already_executed, do: do_init(file_path)
end
end
@doc """
Returns a list with all the opts, formatted for ejabberd.
"""
def get_ejabberd_opts do
get_general_opts
|> Dict.put(:modules, get_modules_parsed_in_order())
|> Dict.put(:listeners, get_listeners_parsed_in_order())
|> Ejabberd.Config.OptsFormatter.format_opts_for_ejabberd
end
@doc """
Register the hooks defined inside the elixir config file.
"""
def start_hooks do
get_hooks_parsed_in_order()
|> Enum.each(&Ejabberd.Config.EjabberdHook.start/1)
end
###
### MACROS
###
defmacro listen(module, do: block) do
attrs = Attr.extract_attrs_from_block_with_defaults(block)
quote do
Ejabberd.Config.Store.put(:listeners, %EjabberdModule{
module: unquote(module),
attrs: unquote(attrs)
})
end
end
defmacro module(module, do: block) do
attrs = Attr.extract_attrs_from_block_with_defaults(block)
quote do
Ejabberd.Config.Store.put(:modules, %EjabberdModule{
module: unquote(module),
attrs: unquote(attrs)
})
end
end
defmacro hook(hook_name, opts, fun) do
quote do
Ejabberd.Config.Store.put(:hooks, %Ejabberd.Config.EjabberdHook{
hook: unquote(hook_name),
opts: unquote(opts),
fun: unquote(fun)
})
end
end
# Private API
defp do_init(file_path) do
# File evaluation
Code.eval_file(file_path) |> extract_and_store_module_name()
# Getting start/0 config
Ejabberd.Config.Store.get(:module_name)
|> case do
nil -> IO.puts "[ ERR ] Configuration module not found."
[module] -> call_start_func_and_store_data(module)
end
# Fetching git modules and install them
get_modules_parsed_in_order()
|> EjabberdModule.fetch_git_repos
end
# Returns the modules from the store
defp get_modules_parsed_in_order,
do: Ejabberd.Config.Store.get(:modules) |> Enum.reverse
# Returns the listeners from the store
defp get_listeners_parsed_in_order,
do: Ejabberd.Config.Store.get(:listeners) |> Enum.reverse
defp get_hooks_parsed_in_order,
do: Ejabberd.Config.Store.get(:hooks) |> Enum.reverse
# Returns the general config options
defp get_general_opts,
do: Ejabberd.Config.Store.get(:general) |> List.first
# Gets the general ejabberd options calling
# the start/0 function and stores them.
defp call_start_func_and_store_data(module) do
opts = apply(module, :start, [])
Ejabberd.Config.Store.put(:general, opts)
end
# Stores the configuration module name
defp extract_and_store_module_name({{:module, mod, _bytes, _}, _}) do
Ejabberd.Config.Store.put(:module_name, mod)
end
end
+23
View File
@@ -0,0 +1,23 @@
defmodule Ejabberd.Config.EjabberdHook do
@moduledoc """
Module containing functions for manipulating
ejabberd hooks.
"""
defstruct hook: nil, opts: [], fun: nil
alias Ejabberd.Config.EjabberdHook
@type t :: %EjabberdHook{}
@doc """
Register a hook to ejabberd.
"""
@spec start(EjabberdHook.t) :: none
def start(%EjabberdHook{hook: hook, opts: opts, fun: fun}) do
host = Keyword.get(opts, :host, :global)
priority = Keyword.get(opts, :priority, 50)
:ejabberd_hooks.add(hook, host, fun, priority)
end
end
+70
View File
@@ -0,0 +1,70 @@
defmodule Ejabberd.Config.EjabberdModule do
@moduledoc """
Module representing a module block in the configuration file.
It offers functions for validation and for starting the modules.
Warning: The name is EjabberdModule to not collide with
the already existing Elixir.Module.
"""
@type t :: %{module: atom, attrs: [Attr.t]}
defstruct [:module, :attrs]
alias Ejabberd.Config.EjabberdModule
alias Ejabberd.Config.Attr
alias Ejabberd.Config.Validation
@doc """
Given a list of modules / single module
it runs different validators on them.
For each module, returns a {:ok, mod} or {:error, mod, errors}
"""
def validate(modules) do
Validation.validate(modules)
end
@doc """
Given a list of modules, it takes only the ones with
a git attribute and tries to fetch the repo,
then, it install them through :ext_mod.install/1
"""
@spec fetch_git_repos([EjabberdModule.t]) :: none()
def fetch_git_repos(modules) do
modules
|> Enum.filter(&is_git_module?/1)
|> Enum.each(&fetch_and_install_git_module/1)
end
# Private API
defp is_git_module?(%EjabberdModule{attrs: attrs}) do
case Keyword.get(attrs, :git) do
"" -> false
repo -> String.match?(repo, ~r/((git|ssh|http(s)?)|(git@[\w\.]+))(:(\/\/)?)([\w\.@\:\/\-~]+)(\.git)(\/)?/)
end
end
defp fetch_and_install_git_module(%EjabberdModule{attrs: attrs}) do
repo = Keyword.get(attrs, :git)
mod_name = case Keyword.get(attrs, :name) do
"" -> infer_mod_name_from_git_url(repo)
name -> name
end
path = "#{:ext_mod.modules_dir()}/sources/ejabberd-contrib\/#{mod_name}"
fetch_and_store_repo_source_if_not_exists(path, repo)
:ext_mod.install(mod_name) # Have to check if overwrites an already present mod
end
defp fetch_and_store_repo_source_if_not_exists(path, repo) do
unless File.exists?(path) do
IO.puts "[info] Fetching: #{repo}"
:os.cmd('git clone #{repo} #{path}')
end
end
defp infer_mod_name_from_git_url(repo),
do: String.split(repo, "/") |> List.last |> String.replace(".git", "")
end
@@ -0,0 +1,32 @@
defmodule Ejabberd.Config.EjabberdLogger do
@moduledoc """
Module used to log validation errors given validated modules
given validated modules.
"""
alias Ejabberd.Config.EjabberdModule
@doc """
Given a list of modules validated, in the form of {:ok, mod} or
{:error, mod, errors}, it logs to the user the errors found.
"""
@spec log_errors([EjabberdModule.t]) :: [EjabberdModule.t]
def log_errors(modules_validated) when is_list(modules_validated) do
Enum.each modules_validated, &do_log_errors/1
modules_validated
end
defp do_log_errors({:ok, _mod}), do: nil
defp do_log_errors({:error, _mod, errors}), do: Enum.each errors, &do_log_errors/1
defp do_log_errors({:attribute, errors}), do: Enum.each errors, &log_attribute_error/1
defp do_log_errors({:dependency, errors}), do: Enum.each errors, &log_dependency_error/1
defp log_attribute_error({{attr_name, val}, :attr_not_supported}), do:
IO.puts "[ WARN ] Annotation @#{attr_name} is not supported."
defp log_attribute_error({{attr_name, val}, :type_not_supported}), do:
IO.puts "[ WARN ] Annotation @#{attr_name} with value #{inspect val} is not supported (type mismatch)."
defp log_dependency_error({module, :not_found}), do:
IO.puts "[ WARN ] Module #{inspect module} was not found, but is required as a dependency."
end
+46
View File
@@ -0,0 +1,46 @@
defmodule Ejabberd.Config.OptsFormatter do
@moduledoc """
Module for formatting options parsed into the format
ejabberd uses.
"""
alias Ejabberd.Config.EjabberdModule
@doc """
Takes a keyword list with keys corresponding to
the keys requested by the ejabberd config (ex: modules: mods)
and formats them to be correctly evaluated by ejabberd.
Look at how Config.get_ejabberd_opts/0 is constructed for
more informations.
"""
@spec format_opts_for_ejabberd([{atom(), any()}]) :: list()
def format_opts_for_ejabberd(opts) do
opts
|> format_attrs_for_ejabberd
end
defp format_attrs_for_ejabberd(opts) when is_list(opts),
do: Enum.map opts, &format_attrs_for_ejabberd/1
defp format_attrs_for_ejabberd({:listeners, mods}),
do: {:listen, format_listeners_for_ejabberd(mods)}
defp format_attrs_for_ejabberd({:modules, mods}),
do: {:modules, format_mods_for_ejabberd(mods)}
defp format_attrs_for_ejabberd({key, opts}) when is_atom(key),
do: {key, opts}
defp format_mods_for_ejabberd(mods) do
Enum.map mods, fn %EjabberdModule{module: mod, attrs: attrs} ->
{mod, attrs[:opts]}
end
end
defp format_listeners_for_ejabberd(mods) do
Enum.map mods, fn %EjabberdModule{module: mod, attrs: attrs} ->
Keyword.put(attrs[:opts], :module, mod)
end
end
end
+55
View File
@@ -0,0 +1,55 @@
defmodule Ejabberd.Config.Store do
@moduledoc """
Module used for storing the modules parsed from
the configuration file.
Example:
- Store.put(:modules, mod1)
- Store.put(:modules, mod2)
- Store.get(:modules) :: [mod1, mod2]
Be carefoul: when retrieving data you get them
in the order inserted into the store, which normally
is the reversed order of how the modules are specified
inside the configuration file. To resolve this just use
a Enum.reverse/1.
"""
@name __MODULE__
def start_link do
Agent.start_link(fn -> %{} end, name: @name)
end
@doc """
Stores a value based on the key. If the key already exists,
then it inserts the new element, maintaining all the others.
It uses a list for this.
"""
@spec put(atom, any) :: :ok
def put(key, val) do
Agent.update @name, &Map.update(&1, key, [val], fn coll ->
[val | coll]
end)
end
@doc """
Gets a value based on the key passed.
Returns always a list.
"""
@spec get(atom) :: [any]
def get(key) do
Agent.get @name, &Map.get(&1, key, [])
end
@doc """
Stops the store.
It uses Agent.stop underneath, so be aware that exit
could be called.
"""
@spec stop() :: :ok
def stop do
Agent.stop @name
end
end
@@ -0,0 +1,40 @@
defmodule Ejabberd.Config.Validation do
@moduledoc """
Module used to validate a list of modules.
"""
@type mod_validation :: {[EjabberdModule.t], EjabberdModule.t, map}
@type mod_validation_result :: {:ok, EjabberdModule.t} | {:error, EjabberdModule.t, map}
alias Ejabberd.Config.EjabberdModule
alias Ejabberd.Config.Attr
alias Ejabberd.Config.Validator
alias Ejabberd.Config.ValidatorUtility
@doc """
Given a module or a list of modules it runs validators on them
and returns {:ok, mod} or {:error, mod, errors}, for each
of them.
"""
@spec validate([EjabberdModule.t] | EjabberdModule.t) :: [mod_validation_result]
def validate(modules) when is_list(modules), do: Enum.map(modules, &do_validate(modules, &1))
def validate(module), do: validate([module])
# Private API
@spec do_validate([EjabberdModule.t], EjabberdModule.t) :: mod_validation_result
defp do_validate(modules, mod) do
{modules, mod, %{}}
|> Validator.Attrs.validate
|> Validator.Dependencies.validate
|> resolve_validation_result
end
@spec resolve_validation_result(mod_validation) :: mod_validation_result
defp resolve_validation_result({_modules, mod, errors}) do
case errors do
err when err == %{} -> {:ok, mod}
err -> {:error, mod, err}
end
end
end
@@ -0,0 +1,28 @@
defmodule Ejabberd.Config.Validator.Attrs do
@moduledoc """
Validator module used to validate attributes.
"""
# TODO: Duplicated from validator.ex !!!
@type mod_validation :: {[EjabberdModule.t], EjabberdModule.t, map}
import Ejabberd.Config.ValidatorUtility
alias Ejabberd.Config.Attr
@doc """
Given a module (with the form used for validation)
it runs Attr.validate/1 on each attribute and
returns the validation tuple with the errors updated, if found.
"""
@spec validate(mod_validation) :: mod_validation
def validate({modules, mod, errors}) do
errors = Enum.reduce mod.attrs, errors, fn(attr, err) ->
case Attr.validate(attr) do
{:ok, attr} -> err
{:error, attr, cause} -> put_error(err, :attribute, {attr, cause})
end
end
{modules, mod, errors}
end
end
@@ -0,0 +1,30 @@
defmodule Ejabberd.Config.Validator.Dependencies do
@moduledoc """
Validator module used to validate dependencies specified
with the @dependency annotation.
"""
# TODO: Duplicated from validator.ex !!!
@type mod_validation :: {[EjabberdModule.t], EjabberdModule.t, map}
import Ejabberd.Config.ValidatorUtility
@doc """
Given a module (with the form used for validation)
it checks if the @dependency annotation is respected and
returns the validation tuple with the errors updated, if found.
"""
@spec validate(mod_validation) :: mod_validation
def validate({modules, mod, errors}) do
module_names = extract_module_names(modules)
dependencies = mod.attrs[:dependency]
errors = Enum.reduce dependencies, errors, fn(req_module, err) ->
case req_module in module_names do
true -> err
false -> put_error(err, :dependency, {req_module, :not_found})
end
end
{modules, mod, errors}
end
end
@@ -0,0 +1,30 @@
defmodule Ejabberd.Config.ValidatorUtility do
@moduledoc """
Module used as a base validator for validation modules.
Imports utility functions for working with validation structures.
"""
alias Ejabberd.Config.EjabberdModule
@doc """
Inserts an error inside the errors collection, for the given key.
If the key doesn't exists then it creates an empty collection
and inserts the value passed.
"""
@spec put_error(map, atom, any) :: map
def put_error(errors, key, val) do
Map.update errors, key, [val], fn coll ->
[val | coll]
end
end
@doc """
Given a list of modules it extracts and returns a list
of the module names (which are Elixir.Module).
"""
@spec extract_module_names(EjabberdModule.t) :: [atom]
def extract_module_names(modules) when is_list(modules) do
modules
|> Enum.map(&Map.get(&1, :module))
end
end
+18
View File
@@ -0,0 +1,18 @@
defmodule Ejabberd.ConfigUtil do
@moduledoc """
Module containing utility functions for
the config file.
"""
@doc """
Returns true when the config file is based on elixir.
"""
@spec is_elixir_config(list) :: boolean
def is_elixir_config(filename) when is_list(filename) do
is_elixir_config(to_string(filename))
end
def is_elixir_config(filename) do
String.ends_with?(filename, "exs")
end
end
+19
View File
@@ -0,0 +1,19 @@
defmodule Ejabberd.Module do
defmacro __using__(opts) do
logger_enabled = Keyword.get(opts, :logger, true)
quote do
@behaviour :gen_mod
import Ejabberd.Module
unquote(if logger_enabled do
quote do: import Ejabberd.Logger
end)
end
end
# gen_mod callbacks
def depends(_host, _opts), do: []
def mod_opt_type(_), do: []
end
+94
View File
@@ -0,0 +1,94 @@
defmodule Mix.Tasks.Ejabberd.Deps.Tree do
use Mix.Task
alias Ejabberd.Config.EjabberdModule
@shortdoc "Lists all ejabberd modules and their dependencies"
@moduledoc """
Lists all ejabberd modules and their dependencies.
The project must have ejabberd as a dependency.
"""
def run(_argv) do
# First we need to start manually the store to be available
# during the compilation of the config file.
Ejabberd.Config.Store.start_link
Ejabberd.Config.init(:ejabberd_config.get_ejabberd_config_path())
Mix.shell.info "ejabberd modules"
Ejabberd.Config.Store.get(:modules)
|> Enum.reverse # Because of how mods are stored inside the store
|> format_mods
|> Mix.shell.info
end
defp format_mods(mods) when is_list(mods) do
deps_tree = build_dependency_tree(mods)
mods_used_as_dependency = get_mods_used_as_dependency(deps_tree)
keep_only_mods_not_used_as_dep(deps_tree, mods_used_as_dependency)
|> format_mods_into_string
end
defp build_dependency_tree(mods) do
Enum.map mods, fn %EjabberdModule{module: mod, attrs: attrs} ->
deps = attrs[:dependency]
build_dependency_tree(mods, mod, deps)
end
end
defp build_dependency_tree(mods, mod, []), do: %{module: mod, dependency: []}
defp build_dependency_tree(mods, mod, deps) when is_list(deps) do
dependencies = Enum.map deps, fn dep ->
dep_deps = get_dependencies_of_mod(mods, dep)
build_dependency_tree(mods, dep, dep_deps)
end
%{module: mod, dependency: dependencies}
end
defp get_mods_used_as_dependency(mods) when is_list(mods) do
Enum.reduce mods, [], fn(mod, acc) ->
case mod do
%{dependency: []} -> acc
%{dependency: deps} -> get_mod_names(deps) ++ acc
end
end
end
defp get_mod_names([]), do: []
defp get_mod_names(mods) when is_list(mods), do: Enum.map(mods, &get_mod_names/1) |> List.flatten
defp get_mod_names(%{module: mod, dependency: deps}), do: [mod | get_mod_names(deps)]
defp keep_only_mods_not_used_as_dep(mods, mods_used_as_dep) do
Enum.filter mods, fn %{module: mod} ->
not mod in mods_used_as_dep
end
end
defp get_dependencies_of_mod(deps, mod_name) do
Enum.find(deps, &(Map.get(&1, :module) == mod_name))
|> Map.get(:attrs)
|> Keyword.get(:dependency)
end
defp format_mods_into_string(mods), do: format_mods_into_string(mods, 0)
defp format_mods_into_string([], _indentation), do: ""
defp format_mods_into_string(mods, indentation) when is_list(mods) do
Enum.reduce mods, "", fn(mod, acc) ->
acc <> format_mods_into_string(mod, indentation)
end
end
defp format_mods_into_string(%{module: mod, dependency: deps}, 0) do
"\n├── #{mod}" <> format_mods_into_string(deps, 2)
end
defp format_mods_into_string(%{module: mod, dependency: deps}, indentation) do
spaces = Enum.reduce 0..indentation, "", fn(_, acc) -> " " <> acc end
"\n#{spaces}└── #{mod}" <> format_mods_into_string(deps, indentation + 4)
end
end
+3 -9
View File
@@ -1,16 +1,15 @@
defmodule ModPresenceDemo do
import Ejabberd.Logger # this allow using info, error, etc for logging
@behaviour :gen_mod
use Ejabberd.Module
def start(host, _opts) do
info('Starting ejabberd module Presence Demo')
Ejabberd.Hooks.add(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
Ejabberd.Hooks.add(:set_presence_hook, host, __MODULE__, :on_presence, 50)
:ok
end
def stop(host) do
info('Stopping ejabberd module Presence Demo')
Ejabberd.Hooks.delete(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
Ejabberd.Hooks.delete(:set_presence_hook, host, __MODULE__, :on_presence, 50)
:ok
end
@@ -18,9 +17,4 @@ defmodule ModPresenceDemo do
info('Receive presence for #{user}')
:none
end
# gen_mod callbacks
def depends(_host, _opts), do: []
def mod_opt_type(_), do: []
end
+6 -6
View File
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "16.06.0",
version: "16.08.0",
description: description,
elixir: "~> 1.2",
elixirc_paths: ["lib"],
@@ -29,7 +29,7 @@ defmodule Ejabberd.Mixfile do
included_applications: [:lager, :mnesia, :p1_utils, :cache_tab,
:fast_tls, :stringprep, :fast_xml,
:stun, :fast_yaml, :ezlib, :iconv,
:esip, :jiffy, :p1_oauth2, :p1_xmlrpc, :eredis,
:esip, :jiffy, :p1_oauth2, :eredis,
:p1_mysql, :p1_pgsql, :sqlite3]]
end
@@ -40,7 +40,7 @@ defmodule Ejabberd.Mixfile do
end
defp deps do
[{:lager, "~> 3.0.0"},
[{:lager, "~> 3.2"},
{:p1_utils, "~> 1.0"},
{:cache_tab, "~> 1.0"},
{:stringprep, "~> 1.0"},
@@ -51,7 +51,6 @@ defmodule Ejabberd.Mixfile do
{:esip, "~> 1.0"},
{:jiffy, "~> 0.14.7"},
{:p1_oauth2, "~> 0.6.1"},
{:p1_xmlrpc, "~> 1.15"},
{:p1_mysql, "~> 1.0"},
{:p1_pgsql, "~> 1.1"},
{:sqlite3, "~> 1.1"},
@@ -61,9 +60,10 @@ defmodule Ejabberd.Mixfile do
{: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.19.0", only: :dev},
{: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.5b", only: :test}]
{:moka, github: "processone/moka", tag: "1.0.5c", only: :test}]
end
defp package do
+17 -16
View File
@@ -1,29 +1,30 @@
%{"bbmustache": {:hex, :bbmustache, "1.0.4", "7ba94f971c5afd7b6617918a4bb74705e36cab36eb84b19b6a1b7ee06427aa38", [:rebar], []},
"cache_tab": {:hex, :cache_tab, "1.0.3", "0e3c40dde2fe2a6a4db241d7583cea0cc1bcf29e546a0a22f15b75366b2f336e", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
"cache_tab": {:hex, :cache_tab, "1.0.4", "3fd2b1ab40c36e7830a4e09e836c6b0fa89191cd4e5fd471873e4eb42f5cd37c", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"cf": {:hex, :cf, "0.2.1", "69d0b1349fd4d7d4dc55b7f407d29d7a840bf9a1ef5af529f1ebe0ce153fc2ab", [:rebar3], []},
"earmark": {:hex, :earmark, "1.0.1", "2c2cd903bfdc3de3f189bd9a8d4569a075b88a8981ded9a0d95672f6e2b63141", [:mix], []},
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
"erlware_commons": {:hex, :erlware_commons, "0.19.0", "7b43caf2c91950c5f60dc20451e3c3afba44d3d4f7f27bcdc52469285a5a3e70", [:rebar3], [{:cf, "0.2.1", [hex: :cf, optional: false]}]},
"esip": {:hex, :esip, "1.0.7", "f75f6a5cac6814e506f0ff96141fbe276dee3261fca1471c8edfdde25b74f877", [:rebar3], [{:fast_tls, "1.0.6", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}, {:stun, "1.0.6", [hex: :stun, optional: false]}]},
"erlware_commons": {:hex, :erlware_commons, "0.21.0", "a04433071ad7d112edefc75ac77719dd3e6753e697ac09428fc83d7564b80b15", [:rebar3], [{:cf, "0.2.1", [hex: :cf, optional: false]}]},
"esip": {:hex, :esip, "1.0.8", "69885a6c07964aabc6c077fe1372aa810a848bd3d9a415b160dabdce9c7a79b5", [:rebar3], [{:fast_tls, "1.0.7", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}, {:stun, "1.0.7", [hex: :stun, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.13.0", "aa2f8fe4c6136a2f7cfc0a7e06805f82530e91df00e2bff4b4362002b43ada65", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"exrm": {:hex, :exrm, "1.0.8", "5aa8990cdfe300282828b02cefdc339e235f7916388ce99f9a1f926a9271a45d", [:mix], [{:relx, "~> 3.5", [hex: :relx, optional: false]}]},
"ezlib": {:hex, :ezlib, "1.0.1", "add8b2770a1a70c174aaea082b4a8668c0c7fdb03ee6cc81c6c68d3a6c3d767d", [:rebar3], []},
"fast_tls": {:hex, :fast_tls, "1.0.6", "750a74aabb05056f0f222910f0955883649e6c5d67df6ca504ff676160d22b89", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.14", "23d4de66e645bca1d8a557444e83062cc42f235d7a7e2d4072d525bac3986f04", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.5", "a67772c75abb84181c6c9899e1f988b08ac214ea0d764ff1f9889bb7e27f74d4", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
"fast_tls": {:hex, :fast_tls, "1.0.7", "9b72ecfcdcad195ab072c196fab8334f49d8fea76bf1a51f536d69e7527d902a", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.15", "6d23eb7f874e1357cf80a48d75a7bd0c8f6318029dc4b70122e9f54911f57f83", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.6", "3fe6feb7935ae8028b337e53e1db29e73ad3bca8041108f6a8f73b7175ece75c", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"getopt": {:hex, :getopt, "0.8.2", "b17556db683000ba50370b16c0619df1337e7af7ecbf7d64fbf8d1d6bce3109b", [:rebar], []},
"goldrush": {:hex, :goldrush, "0.1.7", "349a351d17c71c2fdaa18a6c2697562abe136fec945f147b381f0cf313160228", [:rebar3], []},
"iconv": {:hex, :iconv, "1.0.1", "dbb8700070577e7a021a095cc5ead221069a0c4034bfadca2516c1f1109ee7fd", [:rebar3], [{:p1_utils, "1.0.4", [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], []},
"lager": {:hex, :lager, "3.0.2", "25dc81bc3659b62f5ab9bd073e97ddd894fc4c242019fccef96f3889d7366c97", [:rebar3], [{:goldrush, "0.1.7", [hex: :goldrush, optional: false]}]},
"lager": {:hex, :lager, "3.2.1", "eef4e18b39e4195d37606d9088ea05bf1b745986cf8ec84f01d332456fe88d17", [:rebar3], [{:goldrush, "0.1.8", [hex: :goldrush, optional: false]}]},
"meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:rebar, :make], []},
"moka": {:git, "https://github.com/processone/moka.git", "768efea96443c57125e6247dbebee687f17be149", [tag: "1.0.5b"]},
"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.4", "7face65db102b5d1ebe7ad3c7517c5ee8cfbe174c6658e3affbb00eb66e06787", [:rebar3], []},
"p1_xmlrpc": {:hex, :p1_xmlrpc, "1.15.1", "a382b62dc21bb372281c2488f99294d84f2b4020ed0908a1c4ad710ace3cf35a", [:rebar3], []},
"p1_utils": {:hex, :p1_utils, "1.0.5", "3e698354fdc1fea5491d991457b0cb986c0a00a47d224feb841dc3ec82b9f721", [:rebar3], []},
"providers": {:hex, :providers, "1.6.0", "db0e2f9043ae60c0155205fcd238d68516331d0e5146155e33d1e79dc452964a", [:rebar3], [{:getopt, "0.8.2", [hex: :getopt, optional: false]}]},
"relx": {:hex, :relx, "3.19.0", "286dd5244b4786f56aac75d5c8e2d1fb4cfd306810d4ec8548f3ae1b3aadb8f7", [:rebar3], [{:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:erlware_commons, "0.19.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", "9158f65d18ec63f8b409543b6fb46dd5fce46160", [tag: "0.8.0b"]},
"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.5", "f29395275c35af5051b29bf875b44ac632dc4d0287880f0e143b536c61fd0ed5", [:rebar3], [{:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.6", "1ca9dea574e09f60971bd8de9cb7e34f327cbf435462cf56aa30f05c1ee2f231", [:rebar3], [{:fast_tls, "1.0.6", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.4", [hex: :p1_utils, optional: false]}]}}
"stringprep": {:hex, :stringprep, "1.0.6", "1cf1c439eb038aa590da5456e019f86afbfbfeb5a2d37b6e5f873041624c6701", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.7", "904dc6f26a3c30c54881c4c3003699f2a4968067ee6b3aecdf9895aad02df75e", [:rebar3], [{:fast_tls, "1.0.7", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]}}
+10 -10
View File
@@ -9,16 +9,15 @@
{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.3"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.6"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.5"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.14"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.6"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.7"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.5"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.4"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.7"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.6"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.15"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.7"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.8"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.6"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.7"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
{p1_xmlrpc, ".*", {git, "https://github.com/processone/p1_xmlrpc", {tag, "1.15.1"}}},
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
{tag, "1.0.1"}}}},
@@ -44,11 +43,11 @@
{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.1"}}}},
{tag, "1.0.2"}}}},
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
{tag, "0.8.4"}}}},
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
{tag, "1.0.5b"}}}},
{tag, "1.0.5c"}}}},
{if_var_true, redis, {eredis, ".*", {git, "https://github.com/wooga/eredis",
{tag, "v1.0.8"}}}}]}.
@@ -75,6 +74,7 @@
{if_var_true, debug, debug_info},
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
{if_var_match, db_type, mssql, {d, 'mssql'}},
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
{if_var_true, hipe, native},
{src_dirs, [asn1, src,
+3 -1
View File
@@ -262,6 +262,7 @@ normalize_spec(Spec) ->
{server, S} -> {server, nameprep(S)};
{resource, R} -> {resource, resourceprep(R)};
{server_regexp, SR} -> {server_regexp, b(SR)};
{resource_regexp, R} -> {resource_regexp, b(R)};
{server_glob, S} -> {server_glob, b(S)};
{resource_glob, R} -> {resource_glob, b(R)};
{ip, {Net, Mask}} -> {ip, {Net, Mask}};
@@ -686,7 +687,8 @@ transform_options({acl, Name, Type}, Opts) ->
{server_regexp, SR} -> {server_regexp, [b(SR)]};
{server_glob, S} -> {server_glob, [b(S)]};
{ip, S} -> {ip, [b(S)]};
{resource_glob, R} -> {resource_glob, [b(R)]}
{resource_glob, R} -> {resource_glob, [b(R)]};
{resource_regexp, R} -> {resource_regexp, [b(R)]}
end,
[{acl, [{Name, [T]}]}|Opts];
transform_options({access, Name, Rules}, Opts) ->
-1
View File
@@ -131,7 +131,6 @@ get_commands_spec() ->
#ejabberd_commands{name = register, tags = [accounts],
desc = "Register a user",
policy = admin,
access = [{mod_register, access, configure}],
module = ?MODULE, function = register,
args = [{user, binary}, {host, binary}, {password, binary}],
result = {res, restuple}},
+24 -4
View File
@@ -56,6 +56,7 @@ start(normal, _Args) ->
ejabberd_admin:start(),
gen_mod:start(),
ext_mod:start(),
setup_if_elixir_conf_used(),
ejabberd_config:start(),
set_settings_from_config(),
acl:start(),
@@ -75,6 +76,8 @@ start(normal, _Args) ->
ejabberd_oauth:start(),
gen_mod:start_modules(),
ejabberd_listener:start_listeners(),
ejabberd_service:start(),
register_elixir_config_hooks(),
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
Sup;
start(_, _) ->
@@ -239,8 +242,25 @@ opt_type(modules) ->
end;
opt_type(_) -> [cluster_nodes, loglevel, modules, net_ticktime].
start_elixir_application() ->
case application:ensure_started(elixir) of
ok -> ok;
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
setup_if_elixir_conf_used() ->
case ejabberd_config:is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config.Store':start_link();
false -> ok
end.
register_elixir_config_hooks() ->
case ejabberd_config:is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config':start_hooks();
false -> ok
end.
start_elixir_application() ->
case ejabberd_config:is_elixir_enabled() of
true ->
case application:ensure_started(elixir) of
ok -> ok;
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
end;
_ ->
ok
end.
+139 -32
View File
@@ -32,6 +32,7 @@
-protocol({xep, 78, '2.5'}).
-protocol({xep, 138, '2.0'}).
-protocol({xep, 198, '1.3'}).
-protocol({xep, 356, '7.1'}).
-update_info({update, 0}).
@@ -48,10 +49,16 @@
send_element/2,
socket_type/0,
get_presence/1,
get_last_presence/1,
get_aux_field/2,
set_aux_field/3,
del_aux_field/2,
get_subscription/2,
get_queued_stanzas/1,
get_csi_state/1,
set_csi_state/2,
get_resume_timeout/1,
set_resume_timeout/2,
send_filtered/5,
broadcast/4,
get_subscribed/1,
@@ -111,9 +118,12 @@
mgmt_pending_since,
mgmt_timeout,
mgmt_max_timeout,
mgmt_ack_timeout,
mgmt_ack_timer,
mgmt_resend,
mgmt_stanzas_in = 0,
mgmt_stanzas_out = 0,
mgmt_stanzas_req = 0,
ask_offline = true,
lang = <<"">>}).
@@ -212,6 +222,9 @@ socket_type() -> xml_stream.
get_presence(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef,
{get_presence}, 1000).
get_last_presence(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef,
{get_last_presence}, 1000).
get_aux_field(Key, #state{aux_fields = Opts}) ->
case lists:keysearch(Key, 1, Opts) of
@@ -244,6 +257,27 @@ get_subscription(LFrom, StateData) ->
true -> none
end.
get_queued_stanzas(#state{mgmt_queue = Queue} = StateData) ->
lists:map(fun({_N, Time, El}) ->
add_resent_delay_info(StateData, El, Time)
end, queue:to_list(Queue)).
get_csi_state(#state{csi_state = CsiState}) ->
CsiState.
set_csi_state(#state{} = StateData, CsiState) ->
StateData#state{csi_state = CsiState};
set_csi_state(FsmRef, CsiState) ->
FsmRef ! {set_csi_state, CsiState}.
get_resume_timeout(#state{mgmt_timeout = Timeout}) ->
Timeout.
set_resume_timeout(#state{} = StateData, Timeout) ->
StateData#state{mgmt_timeout = Timeout};
set_resume_timeout(FsmRef, Timeout) ->
FsmRef ! {set_resume_timeout, Timeout}.
send_filtered(FsmRef, Feature, From, To, Packet) ->
FsmRef ! {send_filtered, Feature, From, To, Packet}.
@@ -303,13 +337,18 @@ init([{SockMod, Socket}, Opts]) ->
_ -> 1000
end,
ResumeTimeout = case proplists:get_value(resume_timeout, Opts) of
Timeout when is_integer(Timeout), Timeout >= 0 -> Timeout;
RTimeo when is_integer(RTimeo), RTimeo >= 0 -> RTimeo;
_ -> 300
end,
MaxResumeTimeout = case proplists:get_value(max_resume_timeout, Opts) of
Max when is_integer(Max), Max >= ResumeTimeout -> Max;
_ -> ResumeTimeout
end,
AckTimeout = case proplists:get_value(ack_timeout, Opts) of
ATimeo when is_integer(ATimeo), ATimeo > 0 -> ATimeo * 1000;
infinity -> undefined;
_ -> 60000
end,
ResendOnTimeout = case proplists:get_value(resend_on_timeout, Opts) of
Resend when is_boolean(Resend) -> Resend;
if_offline -> if_offline;
@@ -333,6 +372,7 @@ init([{SockMod, Socket}, Opts]) ->
mgmt_max_queue = MaxAckQueue,
mgmt_timeout = ResumeTimeout,
mgmt_max_timeout = MaxResumeTimeout,
mgmt_ack_timeout = AckTimeout,
mgmt_resend = ResendOnTimeout},
{ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}.
@@ -1306,6 +1346,15 @@ handle_sync_event({get_presence}, _From, StateName,
Resource = StateData#state.resource,
Reply = {User, Resource, Show, Status},
fsm_reply(Reply, StateName, StateData);
handle_sync_event({get_last_presence}, _From, StateName,
StateData) ->
User = StateData#state.user,
Server = StateData#state.server,
PresLast = StateData#state.pres_last,
Resource = StateData#state.resource,
Reply = {User, Server, Resource, PresLast},
fsm_reply(Reply, StateName, StateData);
handle_sync_event(get_subscribed, _From, StateName,
StateData) ->
Subscribed = (?SETS):to_list(StateData#state.pres_f),
@@ -1743,8 +1792,17 @@ handle_info({broadcast, Type, From, Packet}, StateName, StateData) ->
From, jid:make(USR), Packet)
end, lists:usort(Recipients)),
fsm_next_state(StateName, StateData);
handle_info({set_csi_state, CsiState}, StateName, StateData) ->
fsm_next_state(StateName, StateData#state{csi_state = CsiState});
handle_info({set_resume_timeout, Timeout}, StateName, StateData) ->
fsm_next_state(StateName, StateData#state{mgmt_timeout = Timeout});
handle_info(dont_ask_offline, StateName, StateData) ->
fsm_next_state(StateName, StateData#state{ask_offline = false});
handle_info(close, StateName, StateData) ->
?DEBUG("Timeout waiting for stream management acknowledgement of ~s",
[jid:to_string(StateData#state.jid)]),
close(self()),
fsm_next_state(StateName, StateData#state{mgmt_ack_timer = undefined});
handle_info({_Ref, {resume, OldStateData}}, StateName, StateData) ->
%% This happens if the resume_session/1 request timed out; the new session
%% now receives the late response.
@@ -1880,8 +1938,8 @@ send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive ->
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == pending ->
mgmt_queue_add(StateData, Stanza);
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == active ->
NewStateData = send_stanza_and_ack_req(StateData, Stanza),
mgmt_queue_add(NewStateData, Stanza);
NewStateData = mgmt_queue_add(StateData, Stanza),
mgmt_send_stanza(NewStateData, Stanza);
send_stanza(StateData, Stanza) ->
send_element(StateData, Stanza),
StateData.
@@ -2465,13 +2523,25 @@ fsm_next_state(session_established, StateData) ->
?C2S_HIBERNATE_TIMEOUT};
fsm_next_state(wait_for_resume, #state{mgmt_timeout = 0} = StateData) ->
{stop, normal, StateData};
fsm_next_state(wait_for_resume, #state{mgmt_pending_since = undefined} =
StateData) ->
fsm_next_state(wait_for_resume, #state{mgmt_pending_since = undefined,
sid = SID, jid = JID, ip = IP,
conn = Conn, auth_module = AuthModule,
server = Host} = StateData) ->
case StateData of
#state{mgmt_ack_timer = undefined} ->
ok;
#state{mgmt_ack_timer = Timer} ->
erlang:cancel_timer(Timer)
end,
?INFO_MSG("Waiting for resumption of stream for ~s",
[jid:to_string(StateData#state.jid)]),
[jid:to_string(JID)]),
Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthModule}],
NewStateData = ejabberd_hooks:run_fold(c2s_session_pending, Host, StateData,
[SID, JID, Info]),
{next_state, wait_for_resume,
StateData#state{mgmt_state = pending, mgmt_pending_since = os:timestamp()},
StateData#state.mgmt_timeout};
NewStateData#state{mgmt_state = pending,
mgmt_pending_since = os:timestamp()},
NewStateData#state.mgmt_timeout};
fsm_next_state(wait_for_resume, StateData) ->
Diff = timer:now_diff(os:timestamp(), StateData#state.mgmt_pending_since),
Timeout = max(StateData#state.mgmt_timeout - Diff div 1000, 1),
@@ -2743,7 +2813,8 @@ handle_r(StateData) ->
handle_a(StateData, Attrs) ->
case catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs)) of
H when is_integer(H), H >= 0 ->
check_h_attribute(StateData, H);
NewStateData = check_h_attribute(StateData, H),
maybe_renew_ack_request(NewStateData);
_ ->
?DEBUG("Ignoring invalid ACK element from ~s",
[jid:to_string(StateData#state.jid)]),
@@ -2760,8 +2831,8 @@ handle_resume(StateData, Attrs) ->
of
{{value, PrevID}, H} when is_integer(H), H >= 0 ->
case inherit_session_state(StateData, PrevID) of
{ok, InheritedState} ->
{ok, InheritedState, H};
{ok, InheritedState, Info} ->
{ok, InheritedState, Info, H};
{error, Err, InH} ->
{error, ?MGMT_ITEM_NOT_FOUND_H(Xmlns, InH), Err};
{error, Err} ->
@@ -2780,7 +2851,7 @@ handle_resume(StateData, Attrs) ->
<<"Invalid XMLNS">>}
end,
case R of
{ok, ResumedState, NumHandled} ->
{ok, ResumedState, ResumedInfo, NumHandled} ->
NewState = check_h_attribute(ResumedState, NumHandled),
AttrXmlns = NewState#state.mgmt_xmlns,
AttrId = make_resume_id(NewState),
@@ -2800,11 +2871,16 @@ handle_resume(StateData, Attrs) ->
#xmlel{name = <<"r">>,
attrs = [{<<"xmlns">>, AttrXmlns}],
children = []}),
FlushedState = csi_flush_queue(NewState),
NewStateData = FlushedState#state{csi_state = active},
NewState1 = csi_flush_queue(NewState),
NewState2 = ejabberd_hooks:run_fold(c2s_session_resumed,
StateData#state.server,
NewState1,
[NewState1#state.sid,
NewState1#state.jid,
ResumedInfo]),
?INFO_MSG("Resumed session for ~s",
[jid:to_string(NewStateData#state.jid)]),
{ok, NewStateData};
[jid:to_string(NewState2#state.jid)]),
{ok, NewState2};
{error, El, Msg} ->
send_element(StateData, El),
?INFO_MSG("Cannot resume session for ~s@~s: ~s",
@@ -2837,18 +2913,47 @@ update_num_stanzas_in(#state{mgmt_state = MgmtState} = StateData, El)
update_num_stanzas_in(StateData, _El) ->
StateData.
send_stanza_and_ack_req(StateData, Stanza) ->
AckReq = #xmlel{name = <<"r">>,
attrs = [{<<"xmlns">>, StateData#state.mgmt_xmlns}],
children = []},
case send_element(StateData, Stanza) == ok andalso
send_element(StateData, AckReq) == ok of
true ->
StateData;
false ->
mgmt_send_stanza(StateData, Stanza) ->
case send_element(StateData, Stanza) of
ok ->
maybe_request_ack(StateData);
_ ->
StateData#state{mgmt_state = pending}
end.
maybe_request_ack(#state{mgmt_ack_timer = undefined} = StateData) ->
request_ack(StateData);
maybe_request_ack(StateData) ->
StateData.
request_ack(#state{mgmt_xmlns = Xmlns,
mgmt_ack_timeout = AckTimeout} = StateData) ->
AckReq = #xmlel{name = <<"r">>, attrs = [{<<"xmlns">>, Xmlns}]},
case {send_element(StateData, AckReq), AckTimeout} of
{ok, undefined} ->
ok;
{ok, Timeout} ->
Timer = erlang:send_after(Timeout, self(), close),
StateData#state{mgmt_ack_timer = Timer,
mgmt_stanzas_req = StateData#state.mgmt_stanzas_out};
_ ->
StateData#state{mgmt_state = pending}
end.
maybe_renew_ack_request(#state{mgmt_ack_timer = undefined} = StateData) ->
StateData;
maybe_renew_ack_request(#state{mgmt_ack_timer = Timer,
mgmt_queue = Queue,
mgmt_stanzas_out = NumStanzasOut,
mgmt_stanzas_req = NumStanzasReq} = StateData) ->
erlang:cancel_timer(Timer),
case NumStanzasReq < NumStanzasOut andalso not queue:is_empty(Queue) of
true ->
request_ack(StateData#state{mgmt_ack_timer = undefined});
false ->
StateData#state{mgmt_ack_timer = undefined}
end.
mgmt_queue_add(StateData, El) ->
NewNum = case StateData#state.mgmt_stanzas_out of
4294967295 ->
@@ -3048,13 +3153,13 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
pres_timestamp = OldStateData#state.pres_timestamp,
privacy_list = OldStateData#state.privacy_list,
aux_fields = OldStateData#state.aux_fields,
csi_state = OldStateData#state.csi_state,
mgmt_xmlns = OldStateData#state.mgmt_xmlns,
mgmt_queue = OldStateData#state.mgmt_queue,
mgmt_timeout = OldStateData#state.mgmt_timeout,
mgmt_stanzas_in = OldStateData#state.mgmt_stanzas_in,
mgmt_stanzas_out = OldStateData#state.mgmt_stanzas_out,
mgmt_state = active}};
mgmt_state = active,
csi_state = active}, Info};
{error, Msg} ->
{error, Msg};
_ ->
@@ -3081,20 +3186,22 @@ add_resent_delay_info(#state{server = From}, El, Time) ->
%%% XEP-0352
%%%----------------------------------------------------------------------
csi_filter_stanza(#state{csi_state = CsiState, server = Server} = StateData,
Stanza) ->
csi_filter_stanza(#state{csi_state = CsiState, jid = JID, server = Server} =
StateData, Stanza) ->
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_filter_stanza, Server,
{StateData, [Stanza]},
[Server, Stanza]),
[Server, JID, Stanza]),
StateData2 = lists:foldl(fun(CurStanza, AccState) ->
send_stanza(AccState, CurStanza)
end, StateData1#state{csi_state = active},
Stanzas),
StateData2#state{csi_state = CsiState}.
csi_flush_queue(#state{csi_state = CsiState, server = Server} = StateData) ->
csi_flush_queue(#state{csi_state = CsiState, jid = JID, server = Server} =
StateData) ->
{StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_flush_queue, Server,
{StateData, []}, [Server]),
{StateData, []},
[Server, JID]),
StateData2 = lists:foldl(fun(CurStanza, AccState) ->
send_stanza(AccState, CurStanza)
end, StateData1#state{csi_state = active},
+3 -4
View File
@@ -274,7 +274,6 @@ get_commands_spec() ->
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result_example = ok}].
init() ->
mnesia:delete_table(ejabberd_commands),
mnesia:create_table(ejabberd_commands,
[{ram_copies, [node()]},
{local_content, true},
@@ -537,6 +536,9 @@ execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) ->
execute_check_policy(
_Auth, _JID, #ejabberd_commands{policy = open} = Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_policy(
noauth, _JID, Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_policy(
_Auth, _JID, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
do_execute_command(Command, Arguments);
@@ -546,9 +548,6 @@ execute_check_policy(
execute_check_policy(
admin, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
execute_check_access(JID, Command, Arguments);
execute_check_policy(
noauth, _JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_policy(
{User, Server, _, _}, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
execute_check_access(JID, Command, [User, Server | Arguments]).
+35 -3
View File
@@ -33,10 +33,12 @@
get_option/2, get_option/3, add_option/2, has_option/1,
get_vh_by_auth_method/1, is_file_readable/1,
get_version/0, get_myhosts/0, get_mylang/0,
get_ejabberd_config_path/0, is_using_elixir_config/0,
prepare_opt_val/4, convert_table_to_binary/5,
transform_options/1, collect_options/1, default_db/2,
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
env_binary_to_list/2, opt_type/1, may_hide_data/1]).
env_binary_to_list/2, opt_type/1, may_hide_data/1,
is_elixir_enabled/0]).
-export([start/2]).
@@ -147,7 +149,18 @@ read_file(File) ->
{include_modules_configs, true}]).
read_file(File, Opts) ->
Terms1 = get_plain_terms_file(File, Opts),
Terms1 = case is_elixir_enabled() of
true ->
case 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(File) of
true ->
'Elixir.Ejabberd.Config':init(File),
'Elixir.Ejabberd.Config':get_ejabberd_opts();
false ->
get_plain_terms_file(File, Opts)
end;
false ->
get_plain_terms_file(File, Opts)
end,
Terms_macros = case proplists:get_bool(replace_macros, Opts) of
true -> replace_macros(Terms1);
false -> Terms1
@@ -318,7 +331,9 @@ get_absolute_path(File) ->
File;
relative ->
{ok, Dir} = file:get_cwd(),
filename:absname_join(Dir, File)
filename:absname_join(Dir, File);
volumerelative ->
filename:absname(File)
end.
@@ -1040,6 +1055,23 @@ replace_modules(Modules) ->
%% Elixir module naming
%% ====================
-ifdef(ELIXIR_ENABLED).
is_elixir_enabled() ->
true.
-else.
is_elixir_enabled() ->
false.
-endif.
is_using_elixir_config() ->
case is_elixir_enabled() of
true ->
Config = get_ejabberd_config_path(),
'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config);
false ->
false
end.
%% If module name start with uppercase letter, this is an Elixir module:
is_elixir_module(Module) ->
case atom_to_list(Module) of
+6 -1
View File
@@ -145,9 +145,14 @@ init({SockMod, Socket}, Opts) ->
DefinedHandlers = gen_mod:get_opt(
request_handlers, Opts,
fun(Hs) ->
Hs1 = lists:map(fun
({Mod, Path}) when is_atom(Mod) -> {Path, Mod};
({Path, Mod}) -> {Path, Mod}
end, Hs),
[{str:tokens(
iolist_to_binary(Path), <<"/">>),
Mod} || {Path, Mod} <- Hs]
Mod} || {Path, Mod} <- Hs1]
end, []),
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
Admin ++ Bind ++ XMLRPC,
+2 -1
View File
@@ -338,8 +338,9 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
init([Sid, Key, IP, HOpts]) ->
?DEBUG("started: ~p", [{Sid, Key, IP}]),
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
SOpts = lists:filtermap(fun({stream_managment, _}) -> true;
SOpts = lists:filtermap(fun({stream_management, _}) -> true;
({max_ack_queue, _}) -> true;
({ack_timeout, _}) -> true;
({resume_timeout, _}) -> true;
({max_resume_timeout, _}) -> true;
({resend_on_timeout, _}) -> true;
+2 -1
View File
@@ -112,8 +112,9 @@ socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) ->
%%% Internal
init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
SOpts = lists:filtermap(fun({stream_managment, _}) -> true;
SOpts = lists:filtermap(fun({stream_management, _}) -> true;
({max_ack_queue, _}) -> true;
({ack_timeout, _}) -> true;
({resume_timeout, _}) -> true;
({max_resume_timeout, _}) -> true;
({resend_on_timeout, _}) -> true;
+4 -2
View File
@@ -66,7 +66,7 @@
%% * Using the command line and oauth_issue_token command, the token is generated in behalf of ejabberd' sysadmin
%% (as it has access to ejabberd command line).
-define(EXPIRE, 31536000).
-define(EXPIRE, 4294967).
start() ->
DBMod = get_db_backend(),
@@ -102,7 +102,9 @@ get_commands_spec() ->
args = [{jid, string},{ttl, integer}, {scopes, string}],
policy = restricted,
args_example = ["user@server.com", "connected_users_number;muc_online_rooms"],
args_desc = ["List of scopes to allow, separated by ';'"],
args_desc = ["Jid for which issue token",
"Time to live of generated token in seconds",
"List of scopes to allow, separated by ';'"],
result = {result, {tuple, [{token, string}, {scopes, string}, {expires_in, string}]}}
},
#ejabberd_commands{name = oauth_list_tokens, tags = [oauth],
+136 -25
View File
@@ -36,7 +36,7 @@
-behaviour(?GEN_FSM).
%% External exports
-export([start/2, start_link/2, send_text/2,
-export([start/0, start/2, start_link/2, send_text/2,
send_element/2, socket_type/0, transform_listen_option/2]).
-export([init/1, wait_for_stream/2,
@@ -44,19 +44,10 @@
handle_event/3, handle_sync_event/4, code_change/4,
handle_info/3, terminate/3, print_state/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_service.hrl").
-include("mod_privacy.hrl").
-include("jlib.hrl").
-record(state,
{socket :: ejabberd_socket:socket_state(),
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
streamid = <<"">> :: binary(),
host_opts = dict:new() :: ?TDICT,
host = <<"">> :: binary(),
access :: atom(),
check_from = true :: boolean()}).
-export([get_delegated_ns/1]).
%-define(DBGFSM, true).
@@ -99,6 +90,15 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
%% for xep-0355
%% table contans records like {namespace, fitering attributes, pid(),
%% host, disco info for general case, bare jid disco info }
start() ->
ets:new(delegated_namespaces, [named_table, public]),
ets:new(hooks_tmp, [named_table, public]).
start(SockData, Opts) ->
supervisor:start_child(ejabberd_service_sup,
[SockData, Opts]).
@@ -109,6 +109,9 @@ start_link(SockData, Opts) ->
socket_type() -> xml_stream.
get_delegated_ns(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef, {get_delegated_ns}).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
@@ -141,6 +144,21 @@ init([{SockMod, Socket}, Opts]) ->
p1_sha:sha(crypto:rand_bytes(20))),
dict:from_list([{global, Pass}])
end,
%% privilege access to entities data
PrivAccess = case lists:keysearch(privilege_access, 1, Opts) of
{value, {_, PrivAcc}} -> PrivAcc;
_ -> []
end,
Delegations = case lists:keyfind(delegations, 1, Opts) of
{delegations, Del} ->
lists:foldl(
fun({Ns, FiltAttr}, D) when Ns /= ?NS_DELEGATION ->
Attr = proplists:get_value(filtering, FiltAttr, []),
D ++ [{Ns, Attr}];
(_Deleg, D) -> D
end, [], Del);
false -> []
end,
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
{value, {_, S}} -> S;
_ -> none
@@ -154,8 +172,9 @@ init([{SockMod, Socket}, Opts]) ->
SockMod:change_shaper(Socket, Shaper),
{ok, wait_for_stream,
#state{socket = Socket, sockmod = SockMod,
streamid = new_id(), host_opts = HostOpts,
access = Access, check_from = CheckFrom}}.
streamid = new_id(), host_opts = HostOpts, access = Access,
check_from = CheckFrom, privilege_access = PrivAccess,
delegations = Delegations}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@@ -227,8 +246,31 @@ wait_for_handshake({xmlstreamelement, El}, StateData) ->
[H]),
ejabberd_hooks:run(component_connected,
[H])
end, dict:fetch_keys(StateData#state.host_opts)),
{next_state, stream_established, StateData};
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}
@@ -276,11 +318,12 @@ stream_established({xmlstreamelement, El}, StateData) ->
<<"">> -> error;
_ -> jid:from_string(To)
end,
if ((Name == <<"iq">>) or (Name == <<"message">>) or
(Name == <<"presence">>))
and (ToJID /= error)
and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl);
if (Name == <<"iq">>) and (ToJID /= error) and (FromJID /= error) ->
mod_privilege:process_iq(StateData, FromJID, ToJID, NewEl);
(Name == <<"presence">>) and (ToJID /= error) and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl);
(Name == <<"message">>) and (ToJID /= error) and (FromJID /= error) ->
mod_privilege:process_message(StateData, FromJID, ToJID, NewEl);
true ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
Txt = <<"Incorrect stanza name or from/to JID">>,
@@ -330,8 +373,11 @@ handle_event(_Event, StateName, StateData) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event(_Event, _From, StateName,
StateData) ->
handle_sync_event({get_delegated_ns}, _From, StateName, StateData) ->
Reply = {StateData#state.host, StateData#state.delegations},
{reply, Reply, StateName, StateData};
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
@@ -370,6 +416,36 @@ handle_info({route, From, To, Packet}, StateName,
ejabberd_router:route_error(To, From, Err, Packet)
end,
{next_state, StateName, StateData};
handle_info({user_presence, Packet, From},
stream_established, StateData) ->
To = jid:from_string(StateData#state.host),
PacketNew = jlib:replace_from_to(From, To, Packet),
send_element(StateData, PacketNew),
{next_state, stream_established, StateData};
handle_info({roster_presence, Packet, From},
stream_established, StateData) ->
%% check that current presence stanza is equivalent to last
PresenceNew = jlib:remove_attr(<<"to">>, Packet),
Dict = StateData#state.last_pres,
LastPresence =
try dict:fetch(From, Dict)
catch _:_ ->
undefined
end,
case mod_privilege:compare_presences(LastPresence, PresenceNew) of
false ->
#xmlel{attrs = Attrs} = PresenceNew,
Presence = PresenceNew#xmlel{attrs = [{<<"to">>, StateData#state.host} | Attrs]},
send_element(StateData, Presence),
DictNew = dict:store(From, PresenceNew, Dict),
StateDataNew = StateData#state{last_pres = DictNew},
{next_state, stream_established, StateDataNew};
_ ->
{next_state, stream_established, StateData}
end;
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{next_state, StateName, StateData}.
@@ -388,7 +464,26 @@ terminate(Reason, StateName, StateData) ->
ejabberd_hooks:run(component_disconnected,
[StateData#state.host, Reason])
end,
dict:fetch_keys(StateData#state.host_opts));
dict:fetch_keys(StateData#state.host_opts)),
lists:foreach(fun({Ns, _FilterAttr}) ->
ets:delete(delegated_namespaces, Ns),
remove_iq_handlers(Ns)
end, StateData#state.delegations),
RosterAccess = proplists:get_value(roster, StateData#state.privilege_access),
case proplists:get_value(presence, StateData#state.privilege_access) of
<<"managed_entity">> ->
Fun = mod_privilege:process_presence(self()),
remove_hooks(user_send_packet, Fun);
<<"roster">> when (RosterAccess == <<"both">>) or
(RosterAccess == <<"get">>) ->
Fun = mod_privilege:process_presence(self()),
remove_hooks(user_send_packet, Fun),
Fun2 = mod_privilege:process_roster_presence(self()),
remove_hooks(s2s_receive_packet, Fun2);
_ -> ok
end;
_ -> ok
end,
(StateData#state.sockmod):close(StateData#state.socket),
@@ -448,3 +543,19 @@ fsm_limit_opts(Opts) ->
opt_type(max_fsm_queue) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(_) -> [max_fsm_queue].
remove_iq_handlers(Ns) ->
lists:foreach(fun(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, Ns),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, Ns)
end, ?MYHOSTS).
add_hooks(Hook, Fun) ->
lists:foreach(fun(Host) ->
ejabberd_hooks:add(Hook, Host,Fun, 100)
end, ?MYHOSTS).
remove_hooks(Hook, Fun) ->
lists:foreach(fun(Host) ->
ejabberd_hooks:delete(Hook, Host, Fun, 100)
end, ?MYHOSTS).
+5 -2
View File
@@ -145,7 +145,10 @@ clean_table() ->
{_, SID} = binary_to_term(USSIDKey),
node(element(2, SID)) == node()
end, Vals),
Q1 = ["HDEL", ServKey | Vals1],
Q1 = case Vals1 of
[] -> [];
_ -> ["HDEL", ServKey | Vals1]
end,
Q2 = lists:map(
fun(USSIDKey) ->
{US, SID} = binary_to_term(USSIDKey),
@@ -153,7 +156,7 @@ clean_table() ->
SIDKey = sid_to_key(SID),
["HDEL", USKey, SIDKey]
end, Vals1),
Res = ejabberd_redis:qp([Q1|Q2]),
Res = ejabberd_redis:qp(lists:delete([], [Q1|Q2])),
case lists:filter(
fun({ok, _}) -> false;
(_) -> true
+7 -2
View File
@@ -371,15 +371,20 @@ iq_type_to_string(error) -> <<"error">>.
-spec iq_to_xml(IQ :: iq()) -> xmlel().
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
Children =
if
is_list(SubEl) -> SubEl;
true -> [SubEl]
end,
if ID /= <<"">> ->
#xmlel{name = <<"iq">>,
attrs =
[{<<"id">>, ID}, {<<"type">>, iq_type_to_string(Type)}],
children = SubEl};
children = Children};
true ->
#xmlel{name = <<"iq">>,
attrs = [{<<"type">>, iq_type_to_string(Type)}],
children = SubEl}
children = Children}
end.
-spec parse_xdata_submit(El :: xmlel()) ->
+4 -4
View File
@@ -1315,11 +1315,11 @@ srg_create(Group, Host, Name, Description, Display) ->
Opts = [{name, Name},
{displayed_groups, DisplayList},
{description, Description}],
{atomic, ok} = mod_shared_roster:create_group(Host, Group, Opts),
{atomic, _} = mod_shared_roster:create_group(Host, Group, Opts),
ok.
srg_delete(Group, Host) ->
{atomic, ok} = mod_shared_roster:delete_group(Host, Group),
{atomic, _} = mod_shared_roster:delete_group(Host, Group),
ok.
srg_list(Host) ->
@@ -1342,11 +1342,11 @@ srg_get_members(Group, Host) ->
|| {MUser, MServer} <- Members].
srg_user_add(User, Host, Group, GroupHost) ->
{atomic, ok} = mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group),
{atomic, _} = mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group),
ok.
srg_user_del(User, Host, Group, GroupHost) ->
{atomic, ok} = mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group),
{atomic, _} = mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group),
ok.
+9 -3
View File
@@ -696,7 +696,7 @@ announce_all(From, To, Packet) ->
lists:foreach(
fun({User, Server}) ->
Dest = jid:make(User, Server, <<>>),
ejabberd_router:route(Local, Dest, Packet)
ejabberd_router:route(Local, Dest, add_store_hint(Packet))
end, ejabberd_auth:get_vh_registered_users(Host))
end.
@@ -713,7 +713,7 @@ announce_all_hosts_all(From, To, Packet) ->
lists:foreach(
fun({User, Server}) ->
Dest = jid:make(User, Server, <<>>),
ejabberd_router:route(Local, Dest, Packet)
ejabberd_router:route(Local, Dest, add_store_hint(Packet))
end, ejabberd_auth:dirty_get_registered_users())
end.
@@ -899,7 +899,7 @@ send_announcement_to_all(Host, SubjectS, BodyS) ->
lists:foreach(
fun({U, S, R}) ->
Dest = jid:make(U, S, R),
ejabberd_router:route(Local, Dest, Packet)
ejabberd_router:route(Local, Dest, add_store_hint(Packet))
end, Sessions).
-spec get_access(global | binary()) -> atom().
@@ -909,6 +909,12 @@ get_access(Host) ->
fun(A) -> A end,
none).
-spec add_store_hint(xmlel()) -> xmlel().
add_store_hint(El) ->
Hint = #xmlel{name = <<"store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]},
fxml:append_subtags(El, [Hint]).
%%-------------------------------------------------------------------------
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
+26 -25
View File
@@ -34,8 +34,8 @@
-export([start/2, stop/1, mod_opt_type/1, depends/2]).
%% ejabberd_hooks callbacks.
-export([filter_presence/3, filter_chat_states/3, filter_pep/3, filter_other/3,
flush_queue/2, add_stream_feature/2]).
-export([filter_presence/4, filter_chat_states/4, filter_pep/4, filter_other/4,
flush_queue/3, add_stream_feature/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -151,69 +151,70 @@ depends(_Host, _Opts) ->
%% ejabberd_hooks callbacks.
%%--------------------------------------------------------------------
-spec filter_presence({term(), [xmlel()]}, binary(), xmlel())
-spec filter_presence({term(), [xmlel()]}, binary(), jid(), xmlel())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
filter_presence({C2SState, _OutStanzas} = Acc, Host,
filter_presence({C2SState, _OutStanzas} = Acc, Host, To,
#xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) ->
case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} when Type /= <<"unavailable">> ->
Acc;
_ ->
?DEBUG("Got availability presence stanza", []),
?DEBUG("Got availability presence stanza for ~s",
[jid:to_string(To)]),
queue_add(presence, Stanza, Host, C2SState)
end;
filter_presence(Acc, _Host, _Stanza) -> Acc.
filter_presence(Acc, _Host, _To, _Stanza) -> Acc.
-spec filter_chat_states({term(), [xmlel()]}, binary(), xmlel())
-spec filter_chat_states({term(), [xmlel()]}, binary(), jid(), xmlel())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
filter_chat_states({C2SState, _OutStanzas} = Acc, Host,
filter_chat_states({C2SState, _OutStanzas} = Acc, Host, To,
#xmlel{name = <<"message">>} = Stanza) ->
case jlib:is_standalone_chat_state(Stanza) of
true ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
To = fxml:get_tag_attr_s(<<"to">>, Stanza),
case {jid:from_string(From), jid:from_string(To)} of
case {jid:from_string(From), To} of
{#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
%% Don't queue (carbon copies of) chat states from other
%% resources, as they might be used to sync the state of
%% conversations across clients.
Acc;
_ ->
?DEBUG("Got standalone chat state notification", []),
?DEBUG("Got standalone chat state notification for ~s",
[jid:to_string(To)]),
queue_add(chatstate, Stanza, Host, C2SState)
end;
false ->
Acc
end;
filter_chat_states(Acc, _Host, _Stanza) -> Acc.
filter_chat_states(Acc, _Host, _To, _Stanza) -> Acc.
-spec filter_pep({term(), [xmlel()]}, binary(), xmlel())
-spec filter_pep({term(), [xmlel()]}, binary(), jid(), xmlel())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}.
filter_pep({C2SState, _OutStanzas} = Acc, Host,
filter_pep({C2SState, _OutStanzas} = Acc, Host, To,
#xmlel{name = <<"message">>} = Stanza) ->
case get_pep_node(Stanza) of
{value, Node} ->
?DEBUG("Got PEP notification", []),
?DEBUG("Got PEP notification for ~s", [jid:to_string(To)]),
queue_add({pep, Node}, Stanza, Host, C2SState);
false ->
Acc
end;
filter_pep(Acc, _Host, _Stanza) -> Acc.
filter_pep(Acc, _Host, _To, _Stanza) -> Acc.
-spec filter_other({term(), [xmlel()]}, binary(), xmlel())
-> {stop, {term(), [xmlel()]}}.
-spec filter_other({term(), [xmlel()]}, binary(), jid(), xmlel())
-> {term(), [xmlel()]}.
filter_other({C2SState, _OutStanzas}, Host, Stanza) ->
?DEBUG("Won't add stanza to CSI queue", []),
filter_other({C2SState, _OutStanzas}, Host, To, Stanza) ->
?DEBUG("Won't add stanza for ~s to CSI queue", [jid:to_string(To)]),
queue_take(Stanza, Host, C2SState).
-spec flush_queue({term(), [xmlel()]}, binary()) -> {term(), [xmlel()]}.
-spec flush_queue({term(), [xmlel()]}, binary(), jid()) -> {term(), [xmlel()]}.
flush_queue({C2SState, _OutStanzas}, Host) ->
?DEBUG("Going to flush CSI queue", []),
flush_queue({C2SState, _OutStanzas}, Host, JID) ->
?DEBUG("Going to flush CSI queue of ~s", [jid:to_string(JID)]),
Queue = get_queue(C2SState),
NewState = set_queue([], C2SState),
{NewState, get_stanzas(Queue, Host)}.
@@ -249,7 +250,7 @@ queue_add(Type, Stanza, Host, C2SState) ->
{stop, {NewState, []}}
end.
-spec queue_take(xmlel(), binary(), term()) -> {stop, {term(), [xmlel()]}}.
-spec queue_take(xmlel(), binary(), term()) -> {term(), [xmlel()]}.
queue_take(Stanza, Host, C2SState) ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza),
@@ -259,7 +260,7 @@ queue_take(Stanza, Host, C2SState) ->
U == LUser andalso S == LServer
end, get_queue(C2SState)),
NewState = set_queue(Rest, C2SState),
{stop, {NewState, get_stanzas(Selected, Host) ++ [Stanza]}}.
{NewState, get_stanzas(Selected, Host) ++ [Stanza]}.
-spec set_queue(csi_queue(), term()) -> term().
+538
View File
@@ -0,0 +1,538 @@
%%%--------------------------------------------------------------------------------------
%%% File : mod_delegation.erl
%%% Author : Anna Mukharram <amuhar3@gmail.com>
%%% Purpose : This module is an implementation for XEP-0355: Namespace Delegation
%%%--------------------------------------------------------------------------------------
-module(mod_delegation).
-author('amuhar3@gmail.com').
-behaviour(gen_mod).
-protocol({xep, 0355, '0.3'}).
-export([start/2, stop/1, depends/2, mod_opt_type/1]).
-export([advertise_delegations/1, process_iq/3,
disco_local_features/5, disco_sm_features/5,
disco_local_identity/5, disco_sm_identity/5, disco_info/5, clean/0]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("ejabberd_service.hrl").
-define(CLEAN_INTERVAL, timer:minutes(10)).
%%%--------------------------------------------------------------------------------------
%%% API
%%%--------------------------------------------------------------------------------------
start(Host, _Opts) ->
mod_disco:register_feature(Host, ?NS_DELEGATION),
%% start timer for hooks_tmp table cleaning
timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_local_features, 500), %% This hook should be the last
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
disco_local_identity, 500),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
disco_sm_identity, 500),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
disco_sm_features, 500),
ejabberd_hooks:add(disco_info, Host, ?MODULE,
disco_info, 500).
stop(Host) ->
mod_disco:unregister_feature(Host, ?NS_DELEGATION),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
disco_local_features, 500),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE,
disco_local_identity, 500),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
disco_sm_identity, 500),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
disco_sm_features, 500),
ejabberd_hooks:delete(disco_info, Host, ?MODULE,
disco_info, 500).
depends(_Host, _Opts) -> [].
mod_opt_type(_Opt) -> [].
%%%--------------------------------------------------------------------------------------
%%% 4.2 Functions to advertise service of delegated namespaces
%%%--------------------------------------------------------------------------------------
attribute_tag(Attrs) ->
lists:map(fun(Attr) ->
#xmlel{name = <<"attribute">>, attrs = [{<<"name">> , Attr}]}
end, Attrs).
delegations(From, To, Delegations) ->
{Elem0, DelegatedNs} =
lists:foldl(fun({Ns, FiltAttr}, {Acc, AccNs}) ->
case ets:insert_new(delegated_namespaces,
{Ns, FiltAttr, self(), To, {}, {}}) of
true ->
Attrs =
if
FiltAttr == [] ->
?DEBUG("namespace ~s is delegated to ~s with"
" no filtering attributes ~n",[Ns, To]),
[];
true ->
?DEBUG("namespace ~s is delegated to ~s with"
" ~p filtering attributes ~n",[Ns, To, FiltAttr]),
attribute_tag(FiltAttr)
end,
add_iq_handlers(Ns),
{[#xmlel{name = <<"delegated">>,
attrs = [{<<"namespace">>, Ns}],
children = Attrs}| Acc], [{Ns, FiltAttr}|AccNs]};
false -> {Acc, AccNs}
end
end, {[], []}, Delegations),
case Elem0 of
[] -> {ignore, DelegatedNs};
_ ->
Elem1 = #xmlel{name = <<"delegation">>,
attrs = [{<<"xmlns">>, ?NS_DELEGATION}],
children = Elem0},
Id = randoms:get_string(),
{#xmlel{name = <<"message">>,
attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}],
children = [Elem1]}, DelegatedNs}
end.
add_iq_handlers(Ns) ->
lists:foreach(fun(Host) ->
IQDisc =
gen_mod:get_module_opt(Host, ?MODULE, iqdisc,
fun gen_iq_handler:check_type/1, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
Ns, ?MODULE,
process_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
Ns, ?MODULE,
process_iq, IQDisc)
end, ?MYHOSTS).
advertise_delegations(#state{delegations = []}) -> [];
advertise_delegations(StateData) ->
{Delegated, DelegatedNs} =
delegations(?MYNAME, StateData#state.host, StateData#state.delegations),
if
Delegated /= ignore ->
ejabberd_service:send_element(StateData, Delegated),
% server asks available features for delegated namespaces
disco_info(StateData#state{delegations = DelegatedNs});
true -> ok
end,
DelegatedNs.
%%%--------------------------------------------------------------------------------------
%%% Delegated namespaces hook
%%%--------------------------------------------------------------------------------------
check_filter_attr([], _Children) -> true;
check_filter_attr(_FilterAttr, []) -> false;
check_filter_attr(FilterAttr, [#xmlel{} = Stanza|_]) ->
Attrs = proplists:get_keys(Stanza#xmlel.attrs),
lists:all(fun(Attr) ->
lists:member(Attr, Attrs)
end, FilterAttr);
check_filter_attr(_FilterAttr, _Children) -> false.
-spec get_client_server([attr()]) -> {jid(), jid()}.
get_client_server(Attrs) ->
Client = fxml:get_attr_s(<<"from">>, Attrs),
ClientJID = jid:from_string(Client),
ServerJID = jid:from_string(ClientJID#jid.lserver),
{ClientJID, ServerJID}.
decapsulate_result(#xmlel{children = []}) -> ok;
decapsulate_result(#xmlel{children = Children}) ->
decapsulate_result0(Children).
decapsulate_result0([]) -> ok;
decapsulate_result0([#xmlel{name = <<"delegation">>,
attrs = [{<<"xmlns">>, ?NS_DELEGATION}]} = Packet]) ->
decapsulate_result1(Packet#xmlel.children);
decapsulate_result0(_Children) -> ok.
decapsulate_result1([]) -> ok;
decapsulate_result1([#xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}]} = Packet]) ->
decapsulate_result2(Packet#xmlel.children);
decapsulate_result1(_Children) -> ok.
decapsulate_result2([]) -> ok;
decapsulate_result2([#xmlel{name = <<"iq">>, attrs = Attrs} = Packet]) ->
Ns = fxml:get_attr_s(<<"xmlns">>, Attrs),
if
Ns /= <<"jabber:client">> ->
ok;
true -> Packet
end;
decapsulate_result2(_Children) -> ok.
-spec check_iq(xmlel(), xmlel()) -> xmlel() | ignore.
check_iq(#xmlel{attrs = Attrs} = Packet,
#xmlel{attrs = AttrsOrigin} = OriginPacket) ->
% Id attribute of OriginPacket Must be equil to Packet Id attribute
Id1 = fxml:get_attr_s(<<"id">>, Attrs),
Id2 = fxml:get_attr_s(<<"id">>, AttrsOrigin),
% From attribute of OriginPacket Must be equil to Packet To attribute
From = fxml:get_attr_s(<<"from">>, AttrsOrigin),
To = fxml:get_attr_s(<<"to">>, Attrs),
% Type attribute Must be error or result
Type = fxml:get_attr_s(<<"type">>, Attrs),
if
((Type == <<"result">>) or (Type == <<"error">>)),
Id1 == Id2, To == From ->
NewPacket = jlib:remove_attr(<<"xmlns">>, Packet),
%% We can send the decapsulated stanza from Server to Client (To)
NewPacket;
true ->
%% service-unavailable
Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
Err
end;
check_iq(_Packet, _OriginPacket) -> ignore.
-spec manage_service_result(atom(), atom(), binary(), xmlel()) -> ok.
manage_service_result(HookRes, HookErr, Service, OriginPacket) ->
fun(Packet) ->
{ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
Server = ClientJID#jid.lserver,
ets:delete(hooks_tmp, {HookRes, Server}),
ets:delete(hooks_tmp, {HookErr, Server}),
% Check Packet "from" attribute
% It Must be equil to current service host
From = fxml:get_attr_s(<<"from">> , Packet#xmlel.attrs),
if
From == Service ->
% decapsulate iq result
ResultIQ = decapsulate_result(Packet),
ServResponse = check_iq(ResultIQ, OriginPacket),
if
ServResponse /= ignore ->
ejabberd_router:route(ServerJID, ClientJID, ServResponse);
true -> ok
end;
true ->
% service unavailable
Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(ServerJID, ClientJID, Err)
end
end.
-spec manage_service_error(atom(), atom(), xmlel()) -> ok.
manage_service_error(HookRes, HookErr, OriginPacket) ->
fun(_Packet) ->
{ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
Server = ClientJID#jid.lserver,
ets:delete(hooks_tmp, {HookRes, Server}),
ets:delete(hooks_tmp, {HookErr, Server}),
Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(ServerJID, ClientJID, Err)
end.
-spec forward_iq(binary(), binary(), xmlel()) -> ok.
forward_iq(Server, Service, Packet) ->
Elem0 = #xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}], children = [Packet]},
Elem1 = #xmlel{name = <<"delegation">>,
attrs = [{<<"xmlns">>, ?NS_DELEGATION}], children = [Elem0]},
Id = randoms:get_string(),
Elem2 = #xmlel{name = <<"iq">>,
attrs = [{<<"from">>, Server}, {<<"to">>, Service},
{<<"type">>, <<"set">>}, {<<"id">>, Id}],
children = [Elem1]},
HookRes = {iq, result, Id},
HookErr = {iq, error, Id},
FunRes = manage_service_result(HookRes, HookErr, Service, Packet),
FunErr = manage_service_error(HookRes, HookErr, Packet),
Timestamp = p1_time_compat:system_time(seconds),
ets:insert(hooks_tmp, {{HookRes, Server}, FunRes, Timestamp}),
ets:insert(hooks_tmp, {{HookErr, Server}, FunErr, Timestamp}),
From = jid:make(<<"">>, Server, <<"">>),
To = jid:make(<<"">>, Service, <<"">>),
ejabberd_router:route(From, To, Elem2).
process_iq(From, #jid{lresource = <<"">>} = To,
#iq{type = Type, xmlns = XMLNS} = IQ) ->
%% check if stanza directed to server
%% or directed to the bare JID of the sender
case ((Type == get) or (Type == set)) of
true ->
Packet = jlib:iq_to_xml(IQ),
#xmlel{name = <<"iq">>, attrs = Attrs, children = Children} = Packet,
AttrsNew = [{<<"xmlns">>, <<"jabber:client">>} | Attrs],
AttrsNew2 = jlib:replace_from_to_attrs(jid:to_string(From),
jid:to_string(To), AttrsNew),
case ets:lookup(delegated_namespaces, XMLNS) of
[{XMLNS, FiltAttr, _Pid, ServiceHost, _, _}] ->
case check_filter_attr(FiltAttr, Children) of
true ->
forward_iq(From#jid.server, ServiceHost,
Packet#xmlel{attrs = AttrsNew2});
_ -> ok
end;
[] -> ok
end,
ignore;
_ ->
ignore
end;
process_iq(_From, _To, _IQ) -> ignore.
%%%--------------------------------------------------------------------------------------
%%% 7. Discovering Support
%%%--------------------------------------------------------------------------------------
decapsulate_features(#xmlel{attrs = Attrs} = Packet, Node) ->
case fxml:get_attr_s(<<"node">>, Attrs) of
Node ->
PREFIX = << ?NS_DELEGATION/binary, "::" >>,
Size = byte_size(PREFIX),
BARE_PREFIX = << ?NS_DELEGATION/binary, ":bare:" >>,
SizeBare = byte_size(BARE_PREFIX),
Features = [Feat || #xmlel{attrs = [{<<"var">>, Feat}]} <-
fxml:get_subtags(Packet, <<"feature">>)],
Identity = [I || I <- fxml:get_subtags(Packet, <<"identity">>)],
Exten = [I || I <- fxml:get_subtags_with_xmlns(Packet, <<"x">>, ?NS_XDATA)],
case Node of
<< PREFIX:Size/binary, NS/binary >> ->
ets:update_element(delegated_namespaces, NS,
{5, {Features, Identity, Exten}});
<< BARE_PREFIX:SizeBare/binary, NS/binary >> ->
ets:update_element(delegated_namespaces, NS,
{6, {Features, Identity, Exten}});
_ -> ok
end;
_ -> ok
end;
decapsulate_features(_Packet, _Node) -> ok.
-spec disco_result(atom(), atom(), binary()) -> ok.
disco_result(HookRes, HookErr, Node) ->
fun(Packet) ->
Tag = fxml:get_subtag_with_xmlns(Packet, <<"query">>, ?NS_DISCO_INFO),
decapsulate_features(Tag, Node),
ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
ets:delete(hooks_tmp, {HookErr, ?MYNAME})
end.
-spec disco_error(atom(), atom()) -> ok.
disco_error(HookRes, HookErr) ->
fun(_Packet) ->
ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
ets:delete(hooks_tmp, {HookErr, ?MYNAME})
end.
-spec disco_info(state()) -> ok.
disco_info(StateData) ->
disco_info(StateData, <<"::">>),
disco_info(StateData, <<":bare:">>).
-spec disco_info(state(), binary()) -> ok.
disco_info(StateData, Sep) ->
lists:foreach(fun({Ns, _FilterAttr}) ->
Id = randoms:get_string(),
Node = << ?NS_DELEGATION/binary, Sep/binary, Ns/binary >>,
HookRes = {iq, result, Id},
HookErr = {iq, error, Id},
FunRes = disco_result(HookRes, HookErr, Node),
FunErr = disco_error(HookRes, HookErr),
Timestamp = p1_time_compat:system_time(seconds),
ets:insert(hooks_tmp, {{HookRes, ?MYNAME}, FunRes, Timestamp}),
ets:insert(hooks_tmp, {{HookErr, ?MYNAME}, FunErr, Timestamp}),
Tag = #xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_INFO},
{<<"node">>, Node}],
children = []},
DiscoReq = #xmlel{name = <<"iq">>,
attrs = [{<<"type">>, <<"get">>}, {<<"id">>, Id},
{<<"from">>, ?MYNAME},
{<<"to">>, StateData#state.host }],
children = [Tag]},
ejabberd_service:send_element(StateData, DiscoReq)
end, StateData#state.delegations).
disco_features(Acc, Bare) ->
Fun = fun(Feat) ->
ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
A or str:prefix(Ns, Feat)
end, false, delegated_namespaces)
end,
% delete feature namespace which is delegated to service
Features = lists:filter(fun ({{Feature, _Host}}) ->
not Fun(Feature);
(Feature) when is_binary(Feature) ->
not Fun(Feature)
end, Acc),
% add service features
FeaturesList =
ets:foldl(fun({_, _, _, _, {Feats, _, _}, {FeatsBare, _, _}}, A) ->
if
Bare -> A ++ FeatsBare;
true -> A ++ Feats
end;
(_, A) -> A
end, Features, delegated_namespaces),
{result, FeaturesList}.
disco_identity(Acc, Bare) ->
% filter delegated identites
Fun = fun(Ident) ->
ets:foldl(fun({_, _, _, _, {_ , I, _}, {_ , IBare, _}}, A) ->
Identity =
if
Bare -> IBare;
true -> I
end,
(fxml:get_attr_s(<<"category">> , Ident) ==
fxml:get_attr_s(<<"category">>, Identity)) and
(fxml:get_attr_s(<<"type">> , Ident) ==
fxml:get_attr_s(<<"type">>, Identity)) or A;
(_, A) -> A
end, false, delegated_namespaces)
end,
Identities =
lists:filter(fun (#xmlel{attrs = Attrs}) ->
not Fun(Attrs)
end, Acc),
% add service features
ets:foldl(fun({_, _, _, _, {_, I, _}, {_, IBare, _}}, A) ->
if
Bare -> A ++ IBare;
true -> A ++ I
end;
(_, A) -> A
end, Identities, delegated_namespaces).
%% xmlns from value element
-spec get_field_value([xmlel()]) -> binary().
get_field_value([]) -> <<"">>;
get_field_value([Elem| Elems]) ->
case (fxml:get_attr_s(<<"var">>, Elem#xmlel.attrs) == <<"FORM_TYPE">>) and
(fxml:get_attr_s(<<"type">>, Elem#xmlel.attrs) == <<"hidden">>) of
true ->
Ns = fxml:get_subtag_cdata(Elem, <<"value">>),
if
Ns /= <<"">> -> Ns;
true -> get_field_value(Elems)
end;
_ -> get_field_value(Elems)
end.
get_info(Acc, Bare) ->
Fun = fun(Feat) ->
ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
(A or str:prefix(Ns, Feat))
end, false, delegated_namespaces)
end,
Exten = lists:filter(fun(Xmlel) ->
Tags = fxml:get_subtags(Xmlel, <<"field">>),
case get_field_value(Tags) of
<<"">> -> true;
Value -> not Fun(Value)
end
end, Acc),
ets:foldl(fun({_, _, _, _, {_, _, Ext}, {_, _, ExtBare}}, A) ->
if
Bare -> A ++ ExtBare;
true -> A ++ Ext
end;
(_, A) -> A
end, Exten, delegated_namespaces).
%% 7.2.1 General Case
disco_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
Acc;
disco_local_features(Acc, _From, _To, <<>>, _Lang) ->
FeatsOld = case Acc of
{result, I} -> I;
_ -> []
end,
disco_features(FeatsOld, false);
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
disco_local_identity(Acc, _From, _To, <<>>, _Lang) ->
disco_identity(Acc, false);
disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
%% 7.2.2 Rediction Of Bare JID Disco Info
disco_sm_features({error, ?ERR_ITEM_NOT_FOUND}, _From,
#jid{lresource = <<"">>}, <<>>, _Lang) ->
disco_features([], true);
disco_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
Acc;
disco_sm_features(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
FeatsOld = case Acc of
{result, I} -> I;
_ -> []
end,
disco_features(FeatsOld, true);
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
disco_sm_identity(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
disco_identity(Acc, true);
disco_sm_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
disco_info(Acc, #jid{}, #jid{lresource = <<"">>}, <<>>, _Lang) ->
get_info(Acc, true);
disco_info(Acc, _Host, _Mod, <<>>, _Lang) ->
get_info(Acc, false);
disco_info(Acc, _Host, _Mod, _Node, _Lang) ->
Acc.
%% clean hooks_tmp table
clean() ->
?DEBUG("cleaning ~p ETS table~n", [hooks_tmp]),
Now = p1_time_compat:system_time(seconds),
catch ets:select_delete(hooks_tmp,
ets:fun2ms(fun({_, _, Timestamp}) ->
Now - 300 >= Timestamp
end)),
%% start timer for table cleaning
timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []).
+23 -2
View File
@@ -101,7 +101,7 @@
-define(AC_ALLOW_HEADERS,
{<<"Access-Control-Allow-Headers">>,
<<"Content-Type">>}).
<<"Content-Type, Authorization, X-Admin">>}).
-define(AC_MAX_AGE,
{<<"Access-Control-Max-Age">>, <<"86400">>}).
@@ -259,8 +259,10 @@ process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
badrequest_response()
end;
process([], #request{method = 'OPTIONS', data = <<>>}) ->
process([_Call], #request{method = 'OPTIONS', data = <<>>}) ->
{200, ?OPTIONS_HEADER, []};
process(_, #request{method = 'OPTIONS'}) ->
{400, ?OPTIONS_HEADER, []};
process(_Path, Request) ->
?DEBUG("Bad Request: no handler ~p", [Request]),
json_error(400, 40, <<"Missing command name.">>).
@@ -385,6 +387,20 @@ format_args(Args, ArgsFormat) ->
L when is_list(L) -> exit({additional_unused_args, L})
end.
format_arg({Elements},
{list, {_ElementDefName, {tuple, [{_Tuple1N, Tuple1S}, {_Tuple2N, Tuple2S}]} = Tuple}})
when is_list(Elements) andalso
(Tuple1S == binary orelse Tuple1S == string) ->
lists:map(fun({F1, F2}) ->
{format_arg(F1, Tuple1S), format_arg(F2, Tuple2S)};
({Val}) when is_list(Val) ->
format_arg({Val}, Tuple)
end, Elements);
format_arg(Elements,
{list, {_ElementDefName, {list, _} = ElementDefFormat}})
when is_list(Elements) ->
[{format_arg(Element, ElementDefFormat)}
|| Element <- Elements];
format_arg(Elements,
{list, {_ElementDefName, ElementDefFormat}})
when is_list(Elements) ->
@@ -492,6 +508,11 @@ format_result({Code, Text}, {Name, restuple}) ->
{[{<<"res">>, Code == true orelse Code == ok},
{<<"text">>, iolist_to_binary(Text)}]}};
format_result(Code, {Name, restuple}) ->
{jlib:atom_to_binary(Name),
{[{<<"res">>, Code == true orelse Code == ok},
{<<"text">>, <<"">>}]}};
format_result(Els, {Name, {list, {_, {tuple, [{_, atom}, _]}} = Fmt}}) ->
{jlib:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
+54 -70
View File
@@ -111,15 +111,12 @@ start(Host, Opts) ->
ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
remove_user, 50),
case gen_mod:get_opt(assume_mam_usage, Opts,
fun(if_enabled) -> if_enabled;
(on_request) -> on_request;
(never) -> never
end, never) of
never ->
ok;
_ ->
fun(B) when is_boolean(B) -> B end, false) of
true ->
ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
message_is_archived, 50)
message_is_archived, 50);
false ->
ok
end,
ejabberd_commands:register_commands(get_commands_spec()),
ok.
@@ -164,15 +161,12 @@ stop(Host) ->
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage,
fun(if_enabled) -> if_enabled;
(on_request) -> on_request;
(never) -> never
end, never) of
never ->
ok;
_ ->
fun(B) when is_boolean(B) -> B end, false) of
true ->
ejabberd_hooks:delete(message_is_archived, Host, ?MODULE,
message_is_archived, 50)
message_is_archived, 50);
false ->
ok
end,
ejabberd_commands:unregister_commands(get_commands_spec()),
ok.
@@ -381,32 +375,13 @@ message_is_archived(true, _C2SState, _Peer, _JID, _Pkt) ->
true;
message_is_archived(false, C2SState, Peer,
#jid{luser = LUser, lserver = LServer}, Pkt) ->
Res = case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage,
fun(if_enabled) -> if_enabled;
(on_request) -> on_request;
(never) -> never
end, never) of
if_enabled ->
case get_prefs(LUser, LServer) of
#archive_prefs{} = P ->
{ok, P};
error ->
error
end;
on_request ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
cache_tab:lookup(archive_prefs, {LUser, LServer},
fun() ->
Mod:get_prefs(LUser, LServer)
end);
never ->
error
end,
case Res of
{ok, Prefs} ->
case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage,
fun(B) when is_boolean(B) -> B end, false) of
true ->
should_archive(strip_my_archived_tag(Pkt, LServer), LServer)
andalso should_archive_peer(C2SState, Prefs, Peer);
error ->
andalso should_archive_peer(C2SState, get_prefs(LUser, LServer),
Peer);
false ->
false
end.
@@ -580,29 +555,29 @@ parse_query_v0_2(Query) ->
end, Query#xmlel.children).
should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
<<"error">> ->
case is_resent(Pkt, LServer) of
true ->
false;
<<"groupchat">> ->
false;
_ ->
case is_resent(Pkt, LServer) of
true ->
false ->
case {check_store_hint(Pkt),
fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs)} of
{_Hint, <<"error">>} ->
false;
false ->
case check_store_hint(Pkt) of
store ->
true;
no_store ->
{store, _Type} ->
true;
{no_store, _Type} ->
false;
{none, <<"groupchat">>} ->
false;
{none, <<"headline">>} ->
false;
{none, _Type} ->
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
%% Empty body
false;
none ->
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
%% Empty body
false;
_ ->
true
end
_ ->
true
end
end
end;
@@ -743,8 +718,14 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
case should_archive_peer(C2SState, Prefs, Peer) of
true ->
US = {LUser, LServer},
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir);
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
[LUser, LServer, Peer, chat, Dir]) of
drop ->
pass;
NewPkt ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(NewPkt, LServer, US, chat, Peer, <<"">>, Dir)
end;
false ->
pass
end.
@@ -752,10 +733,16 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
case should_archive_muc(Pkt) of
true ->
LServer = MUCState#state.server_host,
{U, S, _} = jid:tolower(RoomJID),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv);
LServer = MUCState#state.server_host,
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
[U, S, Peer, groupchat, recv]) of
drop ->
pass;
NewPkt ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(NewPkt, LServer, {U, S}, groupchat, Peer, Nick, recv)
end;
false ->
pass
end.
@@ -1088,10 +1075,7 @@ get_commands_spec() ->
result = {res, rescode}}].
mod_opt_type(assume_mam_usage) ->
fun(if_enabled) -> if_enabled;
(on_request) -> on_request;
(never) -> never
end;
fun (B) when is_boolean(B) -> B end;
mod_opt_type(cache_life_time) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(cache_size) ->
+21 -5
View File
@@ -488,9 +488,8 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
_ ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
Type = fxml:get_attr_s(<<"type">>, Attrs),
case {Name, Type} of
{<<"presence">>, <<"">>} ->
case is_create_request(Packet) of
true ->
case check_user_can_create_room(ServerHost,
AccessCreate, From, Room) and
check_create_roomid(ServerHost, Room) of
@@ -508,7 +507,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
ejabberd_router:route(To, From, Err)
end;
_ ->
false ->
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
ErrText = <<"Conference room does not exist">>,
Err = jlib:make_error_reply(Packet,
@@ -523,6 +522,22 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
end
end.
-spec is_create_request(xmlel()) -> boolean().
is_create_request(#xmlel{name = <<"presence">>} = Packet) ->
<<"">> == fxml:get_tag_attr_s(<<"type">>, Packet);
is_create_request(#xmlel{name = <<"iq">>} = Packet) ->
case jlib:iq_query_info(Packet) of
#iq{type = set, xmlns = ?NS_MUCSUB,
sub_el = #xmlel{name = <<"subscribe">>}} ->
true;
#iq{type = get, xmlns = ?NS_MUC_OWNER, sub_el = SubEl} ->
[] == fxml:remove_cdata(SubEl#xmlel.children);
_ ->
false
end;
is_create_request(_) ->
false.
check_user_can_create_room(ServerHost, AccessCreate,
From, _RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of
@@ -705,10 +720,11 @@ get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
get_subscribed_rooms(ServerHost, Host, From) ->
Rooms = get_rooms(ServerHost, Host),
BareFrom = jid:remove_resource(From),
lists:flatmap(
fun(#muc_room{name_host = {Name, _}, opts = Opts}) ->
Subscribers = proplists:get_value(subscribers, Opts, []),
case lists:keymember(From, 1, Subscribers) of
case lists:keymember(BareFrom, 1, Subscribers) of
true -> [jid:make(Name, Host, <<>>)];
false -> []
end;
+123 -14
View File
@@ -13,6 +13,7 @@
-export([start/2, stop/1, depends/2, muc_online_rooms/1,
muc_unregister_nick/1, create_room/3, destroy_room/2,
create_room_with_opts/4,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
get_user_rooms/2, get_room_occupants/2,
@@ -20,6 +21,7 @@
change_room_option/4, get_room_options/2,
set_room_affiliation/4, get_room_affiliations/2,
web_menu_main/2, web_page_main/2, web_menu_host/3,
subscribe_room/4, unsubscribe_room/2, get_subscribers/2,
web_page_host/3, mod_opt_type/1, get_commands_spec/0]).
-include("ejabberd.hrl").
@@ -87,6 +89,18 @@ get_commands_spec() ->
module = ?MODULE, function = create_rooms_file,
args = [{file, string}],
result = {res, rescode}},
#ejabberd_commands{name = create_room_with_opts, tags = [muc_room],
desc = "Create a MUC room name@service in host with given options",
module = ?MODULE, function = create_room_with_opts,
args = [{name, binary}, {service, binary},
{host, binary},
{options, {list,
{option, {tuple,
[{name, binary},
{value, binary}
]}}
}}],
result = {res, rescode}},
#ejabberd_commands{name = destroy_rooms_file, tags = [muc],
desc = "Destroy the rooms indicated in file",
longdesc = "Provide one room JID per line.",
@@ -151,7 +165,22 @@ get_commands_spec() ->
{value, string}
]}}
}}},
#ejabberd_commands{name = subscribe_room, tags = [muc_room],
desc = "Subscribe to a MUC conference",
module = ?MODULE, function = subscribe_room,
args = [{user, binary}, {nick, binary}, {room, binary},
{nodes, binary}],
result = {nodes, {list, {node, string}}}},
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room],
desc = "Unsubscribe from a MUC conference",
module = ?MODULE, function = unsubscribe_room,
args = [{user, binary}, {room, binary}],
result = {res, rescode}},
#ejabberd_commands{name = get_subscribers, tags = [muc_room],
desc = "List subscribers of a MUC conference",
module = ?MODULE, function = get_subscribers,
args = [{name, binary}, {service, binary}],
result = {subscribers, {list, {jid, string}}}},
#ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
desc = "Change an affiliation in a MUC room",
module = ?MODULE, function = set_room_affiliation,
@@ -400,15 +429,23 @@ prepare_room_info(Room_info) ->
%% ok | error
%% @doc Create a room immediately with the default options.
create_room(Name1, Host1, ServerHost) ->
create_room_with_opts(Name1, Host1, ServerHost, []).
create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) ->
Name = jid:nodeprep(Name1),
Host = jid:nodeprep(Host1),
%% Get the default room options from the muc configuration
DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc,
default_room_options, fun(X) -> X end, []),
%% Change default room options as required
FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts],
RoomOpts = lists:ukeymerge(1,
lists:keysort(1, FormattedRoomOpts),
lists:keysort(1, DefRoomOpts)),
%% Store the room on the server, it is not started yet though at this point
mod_muc:store_room(ServerHost, Host, Name, DefRoomOpts),
mod_muc:store_room(ServerHost, Host, Name, RoomOpts),
%% Get all remaining mod_muc parameters that might be utilized
Access = gen_mod:get_module_opt(ServerHost, mod_muc, access, fun(X) -> X end, all),
@@ -429,7 +466,7 @@ create_room(Name1, Host1, ServerHost) ->
Name,
HistorySize,
RoomShaper,
DefRoomOpts),
RoomOpts),
{atomic, ok} = register_room(Host, Name, Pid),
ok;
_ ->
@@ -748,12 +785,20 @@ send_direct_invitation(FromJid, UserJid, XmlEl) ->
%% the option to change (for example title or max_users),
%% and the value to assign to the new option.
%% For example:
%% change_room_option("testroom", "conference.localhost", "title", "Test Room")
change_room_option(Name, Service, Option, Value) when is_atom(Option) ->
Pid = get_room_pid(Name, Service),
{ok, _} = change_room_option(Pid, Option, Value),
ok;
%% change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)
change_room_option(Name, Service, OptionString, ValueString) ->
case get_room_pid(Name, Service) of
room_not_found ->
room_not_found;
Pid ->
{Option, Value} = format_room_option(OptionString, ValueString),
Config = get_room_config(Pid),
Config2 = change_option(Option, Value, Config),
{ok, _} = gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
ok
end.
format_room_option(OptionString, ValueString) ->
Option = jlib:binary_to_atom(OptionString),
Value = case Option of
title -> ValueString;
@@ -764,12 +809,7 @@ change_room_option(Name, Service, OptionString, ValueString) ->
max_users -> jlib:binary_to_integer(ValueString);
_ -> jlib:binary_to_atom(ValueString)
end,
change_room_option(Name, Service, Option, Value).
change_room_option(Pid, Option, Value) ->
Config = get_room_config(Pid),
Config2 = change_option(Option, Value, Config),
gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}).
{Option, Value}.
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
get_room_pid(Name, Service) ->
@@ -789,6 +829,7 @@ change_option(Option, Value, Config) ->
allow_private_messages -> Config#config{allow_private_messages = Value};
allow_private_messages_from_visitors -> Config#config{allow_private_messages_from_visitors = Value};
allow_query_users -> Config#config{allow_query_users = Value};
allow_subscription -> Config#config{allow_subscription = Value};
allow_user_invites -> Config#config{allow_user_invites = Value};
allow_visitor_nickchange -> Config#config{allow_visitor_nickchange = Value};
allow_visitor_status -> Config#config{allow_visitor_status = Value};
@@ -884,6 +925,74 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
error
end.
%%%
%%% MUC Subscription
%%%
subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
throw({error, "Nickname must be set"});
subscribe_room(User, Nick, Room, Nodes) ->
NodeList = re:split(Nodes, "\\h*,\\h*"),
case jid:from_string(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
case jid:from_string(User) of
error ->
throw({error, "Malformed user JID"});
#jid{lresource = <<"">>} ->
throw({error, "User's JID should have a resource"});
UserJID ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
case gen_fsm:sync_send_all_state_event(
Pid,
{muc_subscribe, UserJID, Nick, NodeList}) of
{ok, SubscribedNodes} ->
SubscribedNodes;
{error, Reason} ->
throw({error, binary_to_list(Reason)})
end;
_ ->
throw({error, "The room does not exist"})
end
end;
_ ->
throw({error, "Malformed room JID"})
end.
unsubscribe_room(User, Room) ->
case jid:from_string(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
case jid:from_string(User) of
error ->
throw({error, "Malformed user JID"});
UserJID ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
case gen_fsm:sync_send_all_state_event(
Pid,
{muc_unsubscribe, UserJID}) of
ok ->
ok;
{error, Reason} ->
throw({error, binary_to_list(Reason)})
end;
_ ->
throw({error, "The room does not exist"})
end
end;
_ ->
throw({error, "Malformed room JID"})
end.
get_subscribers(Name, Host) ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
{ok, JIDList} = gen_fsm:sync_send_all_state_event(Pid, get_subscribers),
[jid:to_string(jid:remove_resource(J)) || J <- JIDList];
_ ->
throw({error, "The room does not exist"})
end.
make_opts(StateData) ->
Config = StateData#state.config,
[
+294 -203
View File
@@ -139,7 +139,8 @@ normal_state({route, From, <<"">>,
StateData) ->
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
is_user_allowed_message_nonparticipant(From, StateData)
is_subscriber(From, StateData) orelse
is_user_allowed_message_nonparticipant(From, StateData)
of
true ->
case fxml:get_attr_s(<<"type">>, Attrs) of
@@ -252,20 +253,13 @@ normal_state({route, From, <<"">>,
Err = jlib:make_error_reply(Packet, Error),
ejabberd_router:route(StateData#state.jid, From, Err),
{next_state, normal_state, StateData};
IJID ->
IJIDs ->
Config = StateData#state.config,
case Config#config.members_only of
true ->
case get_affiliation(IJID, StateData) of
none ->
NSD = set_affiliation(IJID, member,
StateData),
send_affiliation(IJID, member,
StateData),
store_room(NSD),
{next_state, normal_state, NSD};
_ -> {next_state, normal_state, StateData}
end;
NSD = process_invitees(IJIDs, StateData),
store_room(NSD),
{next_state, normal_state, NSD};
false -> {next_state, normal_state, StateData}
end
end;
@@ -527,7 +521,8 @@ normal_state({route, From, ToNick,
continue_delivery ->
case
{(StateData#state.config)#config.allow_private_messages,
is_user_online(From, StateData)}
is_user_online(From, StateData) orelse
is_subscriber(From, StateData)}
of
{true, true} ->
case Type of
@@ -562,9 +557,7 @@ normal_state({route, From, ToNick,
PmFromVisitors == anyone;
(PmFromVisitors == moderators) and
DstIsModerator ->
{ok, #user{nick = FromNick}} =
(?DICT):find(jid:tolower(From),
StateData#state.users),
{FromNick, _} = get_participant_data(From, StateData),
FromNickJID =
jid:replace_resource(StateData#state.jid,
FromNick),
@@ -678,7 +671,7 @@ handle_event({service_message, Msg}, _StateName,
children = [{xmlcdata, Msg}]}]},
send_wrapped_multiple(
StateData#state.jid,
StateData#state.users,
get_users_and_subscribers(StateData),
MessagePkt,
?NS_MUCSUB_NODES_MESSAGES,
StateData),
@@ -749,6 +742,64 @@ handle_sync_event({change_state, NewStateData}, _From,
handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) ->
NSD = process_item_change(Item, StateData, UJID),
{reply, {ok, NSD}, StateName, NSD};
handle_sync_event(get_subscribers, _From, StateName, StateData) ->
JIDs = lists:map(fun jid:make/1,
?DICT:fetch_keys(StateData#state.subscribers)),
{reply, {ok, JIDs}, StateName, StateData};
handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
StateName, StateData) ->
SubEl = #xmlel{name = <<"subscribe">>,
attrs = [{<<"xmlns">>, ?NS_MUCSUB}, {<<"nick">>, Nick}],
children = [#xmlel{name = <<"event">>,
attrs = [{<<"node">>, Node}]}
|| Node <- Nodes]},
IQ = #iq{type = set, id = randoms:get_string(),
xmlns = ?NS_MUCSUB, sub_el = SubEl},
Packet = jlib:iq_to_xml(IQ#iq{sub_el = [SubEl]}),
Config = StateData#state.config,
CaptchaRequired = Config#config.captcha_protected,
PasswordProtected = Config#config.password_protected,
TmpConfig = Config#config{captcha_protected = false,
password_protected = false},
TmpState = StateData#state{config = TmpConfig},
case process_iq_mucsub(From, Packet, IQ, TmpState) of
{result, _, NewState} ->
NewConfig = (NewState#state.config)#config{
captcha_protected = CaptchaRequired,
password_protected = PasswordProtected},
{reply, {ok, get_subscription_nodes(Packet)}, StateName,
NewState#state{config = NewConfig}};
{ignore, NewState} ->
NewConfig = (NewState#state.config)#config{
captcha_protected = CaptchaRequired,
password_protected = PasswordProtected},
{reply, {error, <<"Requrest is ignored">>},
NewState#state{config = NewConfig}};
{error, Err, NewState} ->
NewConfig = (NewState#state.config)#config{
captcha_protected = CaptchaRequired,
password_protected = PasswordProtected},
{reply, {error, get_error_text(Err)}, StateName,
NewState#state{config = NewConfig}};
{error, Err} ->
{reply, {error, get_error_text(Err)}, StateName, StateData}
end;
handle_sync_event({muc_unsubscribe, From}, _From, StateName, StateData) ->
SubEl = #xmlel{name = <<"unsubscribe">>,
attrs = [{<<"xmlns">>, ?NS_MUCSUB}]},
IQ = #iq{type = set, id = randoms:get_string(),
xmlns = ?NS_MUCSUB, sub_el = SubEl},
Packet = jlib:iq_to_xml(IQ),
case process_iq_mucsub(From, Packet, IQ, StateData) of
{result, _, NewState} ->
{reply, ok, StateName, NewState};
{ignore, NewState} ->
{reply, {error, <<"Requrest is ignored">>}, NewState};
{error, Err, NewState} ->
{reply, {error, get_error_text(Err)}, StateName, NewState};
{error, Err} ->
{reply, {error, get_error_text(Err)}, StateName, StateData}
end;
handle_sync_event(_Event, _From, StateName,
StateData) ->
Reply = ok, {reply, Reply, StateName, StateData}.
@@ -874,7 +925,7 @@ terminate(Reason, _StateName, StateData) ->
end,
tab_remove_online_user(LJID, StateData)
end,
[], StateData#state.users),
[], get_users_and_subscribers(StateData)),
add_to_log(room_existence, stopped, StateData),
mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(),
StateData#state.server_host),
@@ -891,11 +942,12 @@ process_groupchat_message(From,
#xmlel{name = <<"message">>, attrs = Attrs} = Packet,
StateData) ->
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
IsSubscriber = is_subscriber(From, StateData),
case is_user_online(From, StateData) orelse IsSubscriber orelse
is_user_allowed_message_nonparticipant(From, StateData)
of
true ->
{FromNick, Role, IsSubscriber} = get_participant_data(From, StateData),
{FromNick, Role} = get_participant_data(From, StateData),
if (Role == moderator) or (Role == participant) or IsSubscriber or
((StateData#state.config)#config.moderated == false) ->
Subject = check_subject(Packet),
@@ -938,7 +990,7 @@ process_groupchat_message(From,
end,
send_wrapped_multiple(
jid:replace_resource(StateData#state.jid, FromNick),
StateData#state.users,
get_users_and_subscribers(StateData),
NewPacket, Node, NewStateData1),
NewStateData2 = case has_body_or_subject(NewPacket) of
true ->
@@ -1006,9 +1058,16 @@ get_participant_data(From, StateData) ->
case (?DICT):find(jid:tolower(From),
StateData#state.users)
of
{ok, #user{nick = FromNick, role = Role, is_subscriber = IsSubscriber}} ->
{FromNick, Role, IsSubscriber};
error -> {<<"">>, moderator, false}
{ok, #user{nick = FromNick, role = Role}} ->
{FromNick, Role};
error ->
case ?DICT:find(jid:tolower(jid:remove_resource(From)),
StateData#state.subscribers) of
{ok, #subscriber{nick = FromNick}} ->
{FromNick, none};
error ->
{<<"">>, moderator}
end
end.
process_presence(From, Nick,
@@ -1016,7 +1075,6 @@ process_presence(From, Nick,
StateData) ->
Type0 = fxml:get_attr_s(<<"type">>, Attrs0),
IsOnline = is_user_online(From, StateData),
IsSubscriber = is_subscriber(From, StateData),
if Type0 == <<"">>;
IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) ->
case ejabberd_hooks:run_fold(muc_filter_presence,
@@ -1053,7 +1111,7 @@ process_presence(From, Nick,
Status_el ->
fxml:get_tag_cdata(Status_el)
end,
remove_online_user(From, NewState, IsSubscriber, Reason);
remove_online_user(From, NewState, Reason);
<<"error">> ->
ErrorText = <<"It is not allowed to send error messages to the"
" room. The participant (~s) has sent an error "
@@ -1110,27 +1168,15 @@ process_presence(From, Nick,
From, Err),
StateData;
_ ->
case is_initial_presence(From, StateData) of
true ->
subscriber_becomes_available(
From, Nick, Packet, StateData);
false ->
change_nick(From, Nick, StateData)
end
change_nick(From, Nick, StateData)
end;
_NotNickChange ->
case is_initial_presence(From, StateData) of
true ->
subscriber_becomes_available(
From, Nick, Packet, StateData);
false ->
Stanza = maybe_strip_status_from_presence(
From, Packet, StateData),
NewState = add_user_presence(From, Stanza,
StateData),
send_new_presence(From, NewState, StateData),
NewState
end
Stanza = maybe_strip_status_from_presence(
From, Packet, StateData),
NewState = add_user_presence(From, Stanza,
StateData),
send_new_presence(From, NewState, StateData),
NewState
end
end
end,
@@ -1148,21 +1194,10 @@ maybe_strip_status_from_presence(From, Packet, StateData) ->
_Allowed -> Packet
end.
subscriber_becomes_available(From, Nick, Packet, StateData) ->
Stanza = maybe_strip_status_from_presence(From, Packet, StateData),
State1 = add_user_presence(From, Stanza, StateData),
Aff = get_affiliation(From, State1),
Role = get_default_role(Aff, State1),
State2 = set_role(From, Role, State1),
State3 = set_nick(From, Nick, State2),
send_existing_presences(From, State3),
send_initial_presence(From, State3, StateData),
State3.
close_room_if_temporary_and_empty(StateData1) ->
case not (StateData1#state.config)#config.persistent
andalso (?DICT):to_list(StateData1#state.users) == []
of
andalso (?DICT):size(StateData1#state.users) == 0
andalso (?DICT):size(StateData1#state.subscribers) == 0 of
true ->
?INFO_MSG("Destroyed MUC room ~s because it's temporary "
"and empty",
@@ -1172,18 +1207,39 @@ close_room_if_temporary_and_empty(StateData1) ->
_ -> {next_state, normal_state, StateData1}
end.
get_users_and_subscribers(StateData) ->
OnlineSubscribers = ?DICT:fold(
fun(LJID, _, Acc) ->
LBareJID = jid:remove_resource(LJID),
case is_subscriber(LBareJID, StateData) of
true ->
?SETS:add_element(LBareJID, Acc);
false ->
Acc
end
end, ?SETS:new(), StateData#state.users),
?DICT:fold(
fun(LBareJID, #subscriber{nick = Nick}, Acc) ->
case ?SETS:is_element(LBareJID, OnlineSubscribers) of
false ->
?DICT:store(LBareJID,
#user{jid = jid:make(LBareJID),
nick = Nick,
role = none,
last_presence = undefined},
Acc);
true ->
Acc
end
end, StateData#state.users, StateData#state.subscribers).
is_user_online(JID, StateData) ->
LJID = jid:tolower(JID),
(?DICT):is_key(LJID, StateData#state.users).
is_subscriber(JID, StateData) ->
LJID = jid:tolower(JID),
case (?DICT):find(LJID, StateData#state.users) of
{ok, #user{is_subscriber = IsSubscriber}} ->
IsSubscriber;
_ ->
false
end.
LJID = jid:tolower(jid:remove_resource(JID)),
(?DICT):is_key(LJID, StateData#state.subscribers).
%% Check if the user is occupant of the room, or at least is an admin or owner.
is_occupant_or_admin(JID, StateData) ->
@@ -1346,13 +1402,20 @@ get_error_condition2(Packet) ->
<- EEls],
{condition, Condition}.
get_error_text(Error) ->
case fxml:get_subtag_with_xmlns(Error, <<"text">>, ?NS_STANZAS) of
#xmlel{} = Tag ->
fxml:get_tag_cdata(Tag);
false ->
<<"">>
end.
make_reason(Packet, From, StateData, Reason1) ->
{ok, #user{nick = FromNick}} = (?DICT):find(jid:tolower(From), StateData#state.users),
Condition = get_error_condition(Packet),
iolist_to_binary(io_lib:format(Reason1, [FromNick, Condition])).
expulse_participant(Packet, From, StateData, Reason1) ->
IsSubscriber = is_subscriber(From, StateData),
Reason2 = make_reason(Packet, From, StateData, Reason1),
NewState = add_user_presence_un(From,
#xmlel{name = <<"presence">>,
@@ -1367,7 +1430,7 @@ expulse_participant(Packet, From, StateData, Reason1) ->
Reason2}]}]},
StateData),
send_new_presence(From, NewState, StateData),
remove_online_user(From, NewState, IsSubscriber).
remove_online_user(From, NewState).
set_affiliation(JID, Affiliation, StateData) ->
set_affiliation(JID, Affiliation, StateData, <<"">>).
@@ -1647,8 +1710,7 @@ prepare_room_queue(StateData) ->
{empty, _} -> StateData
end.
update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
is_subscriber = IsSubscriber} = User, StateData) ->
update_online_user(JID, #user{nick = Nick} = User, StateData) ->
LJID = jid:tolower(JID),
Nicks1 = case (?DICT):find(LJID, StateData#state.users) of
{ok, #user{nick = OldNick}} ->
@@ -1667,9 +1729,7 @@ update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
[LJID], Nicks1),
Users = (?DICT):update(LJID,
fun(U) ->
U#user{nick = Nick,
subscriptions = Nodes,
is_subscriber = IsSubscriber}
U#user{nick = Nick}
end, User, StateData#state.users),
NewStateData = StateData#state{users = Users, nicks = Nicks},
case {?DICT:find(LJID, StateData#state.users),
@@ -1681,32 +1741,29 @@ update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
end,
NewStateData.
add_online_user(JID, Nick, Role, IsSubscriber, Nodes, StateData) ->
set_subscriber(JID, Nick, Nodes, StateData) ->
BareJID = jid:remove_resource(JID),
LBareJID = jid:tolower(BareJID),
Subscribers = ?DICT:store(LBareJID,
#subscriber{jid = BareJID,
nick = Nick,
nodes = Nodes},
StateData#state.subscribers),
Nicks = ?DICT:store(Nick, [LBareJID], StateData#state.subscriber_nicks),
NewStateData = StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks},
store_room(NewStateData),
NewStateData.
add_online_user(JID, Nick, Role, StateData) ->
tab_add_online_user(JID, StateData),
User = #user{jid = JID, nick = Nick, role = Role,
is_subscriber = IsSubscriber, subscriptions = Nodes},
StateData1 = update_online_user(JID, User, StateData),
if IsSubscriber ->
store_room(StateData1);
true ->
ok
end,
StateData1.
User = #user{jid = JID, nick = Nick, role = Role},
update_online_user(JID, User, StateData).
remove_online_user(JID, StateData, IsSubscriber) ->
remove_online_user(JID, StateData, IsSubscriber, <<"">>).
remove_online_user(JID, StateData) ->
remove_online_user(JID, StateData, <<"">>).
remove_online_user(JID, StateData, _IsSubscriber = true, _Reason) ->
LJID = jid:tolower(JID),
Users = case (?DICT):find(LJID, StateData#state.users) of
{ok, U} ->
(?DICT):store(LJID, U#user{last_presence = undefined},
StateData#state.users);
error ->
StateData#state.users
end,
StateData#state{users = Users};
remove_online_user(JID, StateData, _IsSubscriber, Reason) ->
remove_online_user(JID, StateData, Reason) ->
LJID = jid:tolower(JID),
{ok, #user{nick = Nick}} = (?DICT):find(LJID,
StateData#state.users),
@@ -1779,10 +1836,13 @@ add_user_presence_un(JID, Presence, StateData) ->
%% Find and return a list of the full JIDs of the users of Nick.
%% Return jid record.
find_jids_by_nick(Nick, StateData) ->
case (?DICT):find(Nick, StateData#state.nicks) of
{ok, [User]} -> [jid:make(User)];
{ok, Users} -> [jid:make(LJID) || LJID <- Users];
error -> false
Nicks = ?DICT:merge(fun(_, Val, _) -> Val end,
StateData#state.nicks,
StateData#state.subscriber_nicks),
case (?DICT):find(Nick, Nicks) of
{ok, [User]} -> [jid:make(User)];
{ok, Users} -> [jid:make(LJID) || LJID <- Users];
error -> false
end.
%% Find and return the full JID of the user of Nick with
@@ -1849,7 +1909,14 @@ is_nick_change(JID, Nick, StateData) ->
end.
nick_collision(User, Nick, StateData) ->
UserOfNick = find_jid_by_nick(Nick, StateData),
UserOfNick = case find_jid_by_nick(Nick, StateData) of
false ->
case ?DICT:find(Nick, StateData#state.subscriber_nicks) of
{ok, [J]} -> J;
error -> false
end;
J -> J
end,
(UserOfNick /= false andalso
jid:remove_resource(jid:tolower(UserOfNick))
/= jid:remove_resource(jid:tolower(User))).
@@ -1961,8 +2028,7 @@ add_new_user(From, Nick,
NewState = add_user_presence(
From, Packet,
add_online_user(From, Nick, Role,
IsSubscribeRequest,
Nodes, StateData)),
StateData)),
send_existing_presences(From, NewState),
send_initial_presence(From, NewState, StateData),
Shift = count_stanza_shift(Nick, Els, NewState),
@@ -1972,9 +2038,7 @@ add_new_user(From, Nick,
end,
NewState;
true ->
add_online_user(From, Nick, none,
IsSubscribeRequest,
Nodes, StateData)
set_subscriber(From, Nick, Nodes, StateData)
end,
ResultState =
case NewStateData#state.just_created of
@@ -2214,15 +2278,6 @@ presence_broadcast_allowed(JID, StateData) ->
Role = get_role(JID, StateData),
lists:member(Role, (StateData#state.config)#config.presence_broadcast).
is_initial_presence(From, StateData) ->
LJID = jid:tolower(From),
case (?DICT):find(LJID, StateData#state.users) of
{ok, #user{last_presence = Pres}} when Pres /= undefined ->
false;
_ ->
true
end.
send_initial_presence(NJID, StateData, OldStateData) ->
send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
@@ -2316,7 +2371,7 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
true ->
[{LNJID, UserInfo}];
false ->
(?DICT):to_list(StateData#state.users)
(?DICT):to_list(get_users_and_subscribers(StateData))
end,
lists:foreach(
fun({LUJID, Info}) ->
@@ -2372,7 +2427,7 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node1, StateData),
Type = fxml:get_tag_attr_s(<<"type">>, Packet),
IsSubscriber = Info#user.is_subscriber,
IsSubscriber = is_subscriber(Info#user.jid, StateData),
IsOccupant = Info#user.last_presence /= undefined,
if (IsSubscriber and not IsOccupant) and
(IsInitialPresence or (Type == <<"unavailable">>)) ->
@@ -2596,19 +2651,20 @@ send_nick_changing(JID, OldNick, StateData,
(_) ->
ok
end,
(?DICT):to_list(StateData#state.users)).
?DICT:to_list(get_users_and_subscribers(StateData))).
maybe_send_affiliation(JID, Affiliation, StateData) ->
LJID = jid:tolower(JID),
Users = get_users_and_subscribers(StateData),
IsOccupant = case LJID of
{LUser, LServer, <<"">>} ->
not (?DICT):is_empty(
(?DICT):filter(fun({U, S, _}, _) ->
U == LUser andalso
S == LServer
end, StateData#state.users));
end, Users));
{_LUser, _LServer, _LResource} ->
(?DICT):is_key(LJID, StateData#state.users)
(?DICT):is_key(LJID, Users)
end,
case IsOccupant of
true ->
@@ -2629,19 +2685,19 @@ send_affiliation(LJID, Affiliation, StateData) ->
children =
[#xmlel{name = <<"item">>,
attrs = ItemAttrs}]}]},
Users = get_users_and_subscribers(StateData),
Recipients = case (StateData#state.config)#config.anonymous of
true ->
(?DICT):filter(fun(_, #user{role = moderator}) ->
true;
(_, _) ->
false
end, StateData#state.users);
end, Users);
false ->
StateData#state.users
Users
end,
send_multiple(StateData#state.jid,
StateData#state.server_host,
Recipients, Message).
send_wrapped_multiple(StateData#state.jid, Recipients, Message,
?NS_MUCSUB_NODES_AFFILIATIONS, StateData).
status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
Status = case IsInitialPresence of
@@ -3348,7 +3404,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
StateData#state.jid, Nick),
send_wrapped(RoomJIDNick, Info#user.jid, Packet,
?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
IsSubscriber = Info#user.is_subscriber,
IsSubscriber = is_subscriber(Info#user.jid, StateData),
IsOccupant = Info#user.last_presence /= undefined,
if (IsSubscriber and not IsOccupant) ->
send_wrapped(RoomJIDNick, Info#user.jid, Packet,
@@ -3357,7 +3413,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
ok
end
end,
(?DICT):to_list(StateData#state.users)).
(?DICT):to_list(get_users_and_subscribers(StateData))).
get_actor_nick(<<"">>, _StateData) ->
<<"">>;
@@ -4187,7 +4243,7 @@ send_config_change_info(New, #state{config = Old} = StateData) ->
attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
children = StatusEls}]},
send_wrapped_multiple(StateData#state.jid,
StateData#state.users,
get_users_and_subscribers(StateData),
Message,
?NS_MUCSUB_NODES_CONFIG,
StateData).
@@ -4203,7 +4259,7 @@ remove_nonmembers(StateData) ->
_ -> SD
end
end,
StateData, (?DICT):to_list(StateData#state.users)).
StateData, (?DICT):to_list(get_users_and_subscribers(StateData))).
set_opts([], StateData) -> StateData;
set_opts([{Opt, Val} | Opts], StateData) ->
@@ -4324,14 +4380,17 @@ set_opts([{Opt, Val} | Opts], StateData) ->
StateData#state{config =
(StateData#state.config)#config{allow_subscription = Val}};
subscribers ->
lists:foldl(
fun({JID, Nick, Nodes}, State) ->
User = #user{jid = JID, nick = Nick,
subscriptions = Nodes,
is_subscriber = true,
role = none},
update_online_user(JID, User, State)
end, StateData, Val);
Subscribers = lists:foldl(
fun({JID, Nick, Nodes}, Acc) ->
BareJID = jid:remove_resource(JID),
?DICT:store(
jid:tolower(BareJID),
#subscriber{jid = BareJID,
nick = Nick,
nodes = Nodes},
Acc)
end, ?DICT:new(), Val),
StateData#state{subscribers = Subscribers};
affiliations ->
StateData#state{affiliations = (?DICT):from_list(Val)};
subject -> StateData#state{subject = Val};
@@ -4346,12 +4405,11 @@ set_opts([{Opt, Val} | Opts], StateData) ->
make_opts(StateData) ->
Config = StateData#state.config,
Subscribers = (?DICT):fold(
fun(_LJID, #user{is_subscriber = true} = User, Acc) ->
[{User#user.jid, User#user.nick,
User#user.subscriptions}|Acc];
(_, _, Acc) ->
Acc
end, [], StateData#state.users),
fun(_LJID, Sub, Acc) ->
[{Sub#subscriber.jid,
Sub#subscriber.nick,
Sub#subscriber.nodes}|Acc]
end, [], StateData#state.subscribers),
[?MAKE_CONFIG_OPT(title), ?MAKE_CONFIG_OPT(description),
?MAKE_CONFIG_OPT(allow_change_subj),
?MAKE_CONFIG_OPT(allow_query_users),
@@ -4407,7 +4465,7 @@ destroy_room(DEl, StateData) ->
Info#user.jid, Packet,
?NS_MUCSUB_NODES_CONFIG, StateData)
end,
(?DICT):to_list(StateData#state.users)),
(?DICT):to_list(get_users_and_subscribers(StateData))),
case (StateData#state.config)#config.persistent of
true ->
mod_muc:forget_room(StateData#state.server_host,
@@ -4567,9 +4625,9 @@ process_iq_mucsub(From, Packet,
Err = ?ERRT_BAD_REQUEST(Lang, <<"Missing 'nick' attribute">>),
{error, Err};
Nick when Config#config.allow_subscription ->
LJID = jid:tolower(From),
case (?DICT):find(LJID, StateData#state.users) of
{ok, #user{role = Role, nick = Nick1}} when Nick1 /= Nick ->
LBareJID = jid:tolower(jid:remove_resource(From)),
case (?DICT):find(LBareJID, StateData#state.subscribers) of
{ok, #subscriber{nick = Nick1}} when Nick1 /= Nick ->
Nodes = get_subscription_nodes(Packet),
case {nick_collision(From, Nick, StateData),
mod_muc:can_use_nick(StateData#state.server_host,
@@ -4582,14 +4640,12 @@ process_iq_mucsub(From, Packet,
ErrText = <<"That nickname is registered by another person">>,
{error, ?ERRT_CONFLICT(Lang, ErrText)};
_ ->
NewStateData = add_online_user(
From, Nick, Role, true, Nodes, StateData),
NewStateData = set_subscriber(From, Nick, Nodes, StateData),
{result, subscription_nodes_to_events(Nodes), NewStateData}
end;
{ok, #user{role = Role}} ->
{ok, #subscriber{}} ->
Nodes = get_subscription_nodes(Packet),
NewStateData = add_online_user(
From, Nick, Role, true, Nodes, StateData),
NewStateData = set_subscriber(From, Nick, Nodes, StateData),
{result, subscription_nodes_to_events(Nodes), NewStateData};
error ->
add_new_user(From, Nick, Packet, StateData)
@@ -4602,41 +4658,44 @@ process_iq_mucsub(From, _Packet,
#iq{type = set,
sub_el = #xmlel{name = <<"unsubscribe">>}},
StateData) ->
LJID = jid:tolower(From),
case ?DICT:find(LJID, StateData#state.users) of
{ok, #user{is_subscriber = true} = User} ->
NewStateData = remove_subscription(From, User, StateData),
LBareJID = jid:tolower(jid:remove_resource(From)),
case ?DICT:find(LBareJID, StateData#state.subscribers) of
{ok, #subscriber{nick = Nick}} ->
Nicks = ?DICT:erase(Nick, StateData#state.subscriber_nicks),
Subscribers = ?DICT:erase(LBareJID, StateData#state.subscribers),
NewStateData = StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks},
store_room(NewStateData),
{result, [], NewStateData};
_ ->
error ->
{result, [], StateData}
end;
process_iq_mucsub(_From, _Packet, #iq{type = set, lang = Lang}, _StateData) ->
Txt = <<"Unrecognized subscription command">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)};
process_iq_mucsub(_From, _Packet, #iq{type = get, lang = Lang}, _StateData) ->
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}.
remove_subscription(JID, #user{is_subscriber = true} = User, StateData) ->
case User#user.last_presence of
undefined ->
remove_online_user(JID, StateData, false);
_ ->
LJID = jid:tolower(JID),
Users = ?DICT:store(LJID, User#user{is_subscriber = false},
StateData#state.users),
StateData#state{users = Users}
process_iq_mucsub(From, _Packet,
#iq{type = get, lang = Lang,
sub_el = #xmlel{name = <<"subscriptions">>}},
StateData) ->
FAffiliation = get_affiliation(From, StateData),
FRole = get_role(From, StateData),
if FRole == moderator; FAffiliation == owner; FAffiliation == admin ->
Subs = dict:fold(
fun(_, #subscriber{jid = J}, Acc) ->
SJID = jid:to_string(J),
[#xmlel{name = <<"subscription">>,
attrs = [{<<"jid">>, SJID}]}|Acc]
end, [], StateData#state.subscribers),
{result, Subs, StateData};
true ->
Txt = <<"Moderator privileges required">>,
{error, ?ERRT_FORBIDDEN(Lang, Txt)}
end;
remove_subscription(_JID, #user{}, StateData) ->
StateData.
process_iq_mucsub(_From, _Packet, #iq{lang = Lang}, _StateData) ->
Txt = <<"Unrecognized subscription command">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}.
remove_subscriptions(StateData) ->
if not (StateData#state.config)#config.allow_subscription ->
dict:fold(
fun(_LJID, User, State) ->
remove_subscription(User#user.jid, User, State)
end, StateData, StateData#state.users);
StateData#state{subscribers = ?DICT:new(),
subscriber_nicks = ?DICT:new()};
true ->
StateData
end.
@@ -4869,18 +4928,35 @@ is_invitation(Els) ->
end,
false, Els).
process_invitees(Invetees, StateDataIni) ->
lists:foldl(
fun(IJID, StateData) ->
case get_affiliation(IJID, StateData) of
none ->
NSD = set_affiliation(IJID, member,
StateData),
send_affiliation(IJID, member,
StateData),
NSD;
_ -> StateData
end
end,
StateDataIni,
Invetees).
check_invitation(From, Packet, Lang, StateData) ->
FAffiliation = get_affiliation(From, StateData),
CanInvite =
(StateData#state.config)#config.allow_user_invites
orelse
FAffiliation == admin orelse FAffiliation == owner,
InviteEl = case fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) of
InviteEls = case fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) of
false ->
Txt1 = <<"No 'x' element found">>,
throw({error, ?ERRT_BAD_REQUEST(Lang, Txt1)});
XEl ->
case fxml:get_subtag(XEl, <<"invite">>) of
case fxml:get_subtags(XEl, <<"invite">>) of
false ->
Txt2 = <<"No 'invite' element found">>,
throw({error, ?ERRT_BAD_REQUEST(Lang, Txt2)});
@@ -4888,20 +4964,17 @@ check_invitation(From, Packet, Lang, StateData) ->
InviteEl1
end
end,
JID = case
jid:from_string(fxml:get_tag_attr_s(<<"to">>,
InviteEl))
of
error ->
Txt = <<"Incorrect value of 'to' attribute">>,
throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)});
JID1 -> JID1
end,
case CanInvite of
false ->
Txt3 = <<"Invitations are not allowed in this conference">>,
throw({error, ?ERRT_NOT_ALLOWED(Lang, Txt3)});
true ->
process_invitations(From, InviteEls, Lang, StateData)
end.
process_invitations(From, InviteEls, Lang, StateData) ->
lists:map(
fun(InviteEl) ->
Reason = fxml:get_path_s(InviteEl,
[{elem, <<"reason">>}, cdata]),
ContinueEl = case fxml:get_path_s(InviteEl,
@@ -4970,9 +5043,19 @@ check_invitation(From, Packet, Lang, StateData) ->
<<"">>})}],
children = [{xmlcdata, Reason}]},
Body]},
JID = case
jid:from_string(fxml:get_tag_attr_s(<<"to">>,
InviteEl))
of
error ->
Txt = <<"Incorrect value of 'to' attribute">>,
throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)});
JID1 -> JID1
end,
ejabberd_router:route(StateData#state.jid, JID, Msg),
JID
end.
end,
InviteEls).
%% Handle a message sent to the room by a non-participant.
%% If it is a decline, send to the inviter.
@@ -5097,18 +5180,26 @@ store_room(StateData) ->
send_wrapped(From, To, Packet, Node, State) ->
LTo = jid:tolower(To),
case ?DICT:find(LTo, State#state.users) of
{ok, #user{is_subscriber = true,
subscriptions = Nodes,
last_presence = undefined}} ->
case lists:member(Node, Nodes) of
true ->
NewPacket = wrap(From, To, Packet, Node),
ejabberd_router:route(State#state.jid, To, NewPacket);
false ->
LBareTo = jid:tolower(jid:remove_resource(To)),
IsOffline = case ?DICT:find(LTo, State#state.users) of
{ok, #user{last_presence = undefined}} -> true;
error -> true;
_ -> false
end,
if IsOffline ->
case ?DICT:find(LBareTo, State#state.subscribers) of
{ok, #subscriber{nodes = Nodes, jid = JID}} ->
case lists:member(Node, Nodes) of
true ->
NewPacket = wrap(From, JID, Packet, Node),
ejabberd_router:route(State#state.jid, JID, NewPacket);
false ->
ok
end;
_ ->
ok
end;
_ ->
true ->
ejabberd_router:route(From, To, Packet)
end.
@@ -5129,9 +5220,9 @@ wrap(From, To, Packet, Node) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Multicast
send_multiple(From, Server, Users, Packet) ->
JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
%% send_multiple(From, Server, Users, Packet) ->
%% JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
%% ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
send_wrapped_multiple(From, Users, Packet, Node, State) ->
lists:foreach(
+44 -35
View File
@@ -437,35 +437,36 @@ remove_msg_by_node(To, Seq) ->
end.
need_to_store(LServer, Packet) ->
Type = fxml:get_tag_attr_s(<<"type">>, Packet),
if (Type /= <<"error">>) and (Type /= <<"groupchat">>)
and (Type /= <<"headline">>) ->
case has_offline_tag(Packet) of
false ->
case check_store_hint(Packet) of
store ->
case has_offline_tag(Packet) of
false ->
case {check_store_hint(Packet),
fxml:get_tag_attr_s(<<"type">>, Packet)} of
{_Hint, <<"error">>} ->
false;
{store, _Type} ->
true;
{no_store, _Type} ->
false;
{none, <<"groupchat">>} ->
false;
{none, <<"headline">>} ->
false;
{none, _Type} ->
case gen_mod:get_module_opt(
LServer, ?MODULE, store_empty_body,
fun(V) when is_boolean(V) -> V;
(unless_chat_state) -> unless_chat_state
end,
unless_chat_state) of
true ->
true;
no_store ->
false;
none ->
case gen_mod:get_module_opt(
LServer, ?MODULE, store_empty_body,
fun(V) when is_boolean(V) -> V;
(unless_chat_state) -> unless_chat_state
end,
unless_chat_state) of
false ->
fxml:get_subtag(Packet, <<"body">>) /= false;
unless_chat_state ->
not jlib:is_standalone_chat_state(Packet);
true ->
true
end
end;
true ->
false
false ->
fxml:get_subtag(Packet, <<"body">>) /= false;
unless_chat_state ->
not jlib:is_standalone_chat_state(Packet)
end
end;
true ->
true ->
false
end.
@@ -475,14 +476,22 @@ store_packet(From, To, Packet) ->
case check_event(From, To, Packet) of
true ->
#jid{luser = LUser, lserver = LServer} = To,
TimeStamp = p1_time_compat:timestamp(),
#xmlel{children = Els} = Packet,
Expire = find_x_expire(TimeStamp, Els),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
#offline_msg{us = {LUser, LServer},
timestamp = TimeStamp, expire = Expire,
from = From, to = To, packet = Packet},
stop;
case ejabberd_hooks:run_fold(store_offline_message, LServer,
Packet, [From, To]) of
drop ->
ok;
NewPacket ->
TimeStamp = p1_time_compat:timestamp(),
#xmlel{children = Els} = NewPacket,
Expire = find_x_expire(TimeStamp, Els),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
#offline_msg{us = {LUser, LServer},
timestamp = TimeStamp,
expire = Expire,
from = From, to = To,
packet = NewPacket},
stop
end;
_ -> ok
end;
false -> ok
+363
View File
@@ -0,0 +1,363 @@
%%%--------------------------------------------------------------------------------------
%%% File : mod_privilege.erl
%%% Author : Anna Mukharram <amuhar3@gmail.com>
%%% Purpose : This module is an implementation for XEP-0356: Privileged Entity
%%%--------------------------------------------------------------------------------------
-module(mod_privilege).
-author('amuhar3@gmail.com').
-protocol({xep, 0356, '0.2.1'}).
-export([advertise_permissions/1, initial_presences/1, process_presence/1,
process_roster_presence/1, compare_presences/2,
process_message/4, process_iq/4]).
-include("ejabberd_service.hrl").
-include("mod_privacy.hrl").
%%%--------------------------------------------------------------------------------------
%%% Functions to advertise services of allowed permission
%%%--------------------------------------------------------------------------------------
-spec permissions(binary(), binary(), list()) -> xmlel().
permissions(From, To, PrivAccess) ->
Perms = lists:map(fun({Access, Type}) ->
?DEBUG("Advertise service ~s of allowed permission: ~s = ~s~n",
[To, Access, Type]),
#xmlel{name = <<"perm">>,
attrs = [{<<"access">>,
atom_to_binary(Access,latin1)},
{<<"type">>, Type}]}
end, PrivAccess),
Stanza = #xmlel{name = <<"privilege">>,
attrs = [{<<"xmlns">> ,?NS_PRIVILEGE}],
children = Perms},
Id = randoms:get_string(),
#xmlel{name = <<"message">>,
attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}],
children = [Stanza]}.
advertise_permissions(#state{privilege_access = []}) -> ok;
advertise_permissions(StateData) ->
Stanza =
permissions(?MYNAME, StateData#state.host, StateData#state.privilege_access),
ejabberd_service:send_element(StateData, Stanza).
%%%--------------------------------------------------------------------------------------
%%% Process presences
%%%--------------------------------------------------------------------------------------
initial_presences(StateData) ->
Pids = ejabberd_sm:get_all_pids(),
lists:foreach(
fun(Pid) ->
{User, Server, Resource, PresenceLast} = ejabberd_c2s:get_last_presence(Pid),
From = #jid{user = User, server = Server, resource = Resource},
To = jid:from_string(StateData#state.host),
PacketNew = jlib:replace_from_to(From, To, PresenceLast),
ejabberd_service:send_element(StateData, PacketNew)
end, Pids).
%% hook user_send_packet(Packet, C2SState, From, To) -> Packet
%% for Managed Entity Presence
process_presence(Pid) ->
fun(#xmlel{name = <<"presence">>} = Packet, _C2SState, From, _To) ->
case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of
T when (T == <<"">>) or (T == <<"unavailable">>) ->
Pid ! {user_presence, Packet, From};
_ -> ok
end,
Packet;
(Packet, _C2SState, _From, _To) ->
Packet
end.
%% s2s_receive_packet(From, To, Packet) -> ok
%% for Roster Presence
%% From subscription "from" or "both"
process_roster_presence(Pid) ->
fun(From, To, #xmlel{name = <<"presence">>} = Packet) ->
case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of
T when (T == <<"">>) or (T == <<"unavailable">>) ->
Server = To#jid.server,
User = To#jid.user,
PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
Server, #userlist{}, [User, Server]),
case privacy_check_packet(Server, User, PrivList, From, To, Packet, in) of
allow ->
Pid ! {roster_presence, Packet, From};
_ -> ok
end,
ok;
_ -> ok
end;
(_From, _To, _Packet) -> ok
end.
%%%--------------------------------------------------------------------------------------
%%% Manage Roster
%%%--------------------------------------------------------------------------------------
process_iq(StateData, FromJID, ToJID, Packet) ->
IQ = jlib:iq_query_or_response_info(Packet),
case IQ of
#iq{xmlns = ?NS_ROSTER} ->
case (ToJID#jid.luser /= <<"">>) and
(FromJID#jid.luser == <<"">>) and
lists:member(ToJID#jid.lserver, ?MYHOSTS) of
true ->
AccessType =
proplists:get_value(roster, StateData#state.privilege_access, none),
case IQ#iq.type of
get when (AccessType == <<"both">>) or (AccessType == <<"get">>) ->
RosterIQ = roster_management(ToJID, FromJID, IQ),
ejabberd_service:send_element(StateData, RosterIQ);
set when (AccessType == <<"both">>) or (AccessType == <<"set">>) ->
%% check if user ToJID exist
#jid{lserver = Server, luser = User} = ToJID,
case ejabberd_auth:is_user_exists(User,Server) of
true ->
ResIQ = roster_management(ToJID, FromJID, IQ),
ejabberd_service:send_element(StateData, ResIQ);
_ -> ok
end;
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end;
#iq{type = Type, id = Id} when (Type == error) or (Type == result) -> % for XEP-0355
Hook = {iq, Type, Id},
Host = ToJID#jid.lserver,
case (ToJID#jid.luser == <<"">>) and
(FromJID#jid.luser == <<"">>) and
lists:member(ToJID#jid.lserver, ?MYHOSTS) of
true ->
case ets:lookup(hooks_tmp, {Hook, Host}) of
[{_, Function, _Timestamp}] ->
catch apply(Function, [Packet]);
[] ->
ejabberd_router:route(FromJID, ToJID, Packet)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end.
roster_management(FromJID, ToJID, IQ) ->
ResIQ = mod_roster:process_iq(FromJID, FromJID, IQ),
ResXml = jlib:iq_to_xml(ResIQ),
jlib:replace_from_to(FromJID, ToJID, ResXml).
%%%--------------------------------------------------------------------------------------
%%% Message permission
%%%--------------------------------------------------------------------------------------
process_message(StateData, FromJID, ToJID, #xmlel{children = Children} = Packet) ->
%% if presence was send from service to server,
case lists:member(ToJID#jid.lserver, ?MYHOSTS) and
(ToJID#jid.luser == <<"">>) and
(FromJID#jid.luser == <<"">>) of %% service
true ->
%% if stanza contains privilege element
case Children of
[#xmlel{name = <<"privilege">>,
attrs = [{<<"xmlns">>, ?NS_PRIVILEGE}],
children = [#xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
children = Children2}]}] ->
%% 1 case : privilege service send subscription message
%% on behalf of the client
%% 2 case : privilege service send message on behalf
%% of the client
case Children2 of
%% it isn't case of 0356 extension
[#xmlel{name = <<"presence">>} = Child] ->
forward_subscribe(StateData, Child, Packet);
[#xmlel{name = <<"message">>} = Child] -> %% xep-0356
forward_message(StateData, Child, Packet);
_ ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"invalid forwarded element">>,
Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_service:send_element(StateData, Err)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end;
_ ->
ejabberd_router:route(FromJID, ToJID, Packet)
end.
forward_subscribe(StateData, Presence, Packet) ->
PrivAccess = StateData#state.privilege_access,
T = proplists:get_value(roster, PrivAccess, none),
Type = fxml:get_attr_s(<<"type">>, Presence#xmlel.attrs),
if
((T == <<"both">>) or (T == <<"set">>)) and (Type == <<"subscribe">>) ->
From = fxml:get_attr_s(<<"from">>, Presence#xmlel.attrs),
FromJ = jid:from_string(From),
To = fxml:get_attr_s(<<"to">>, Presence#xmlel.attrs),
ToJ = case To of
<<"">> -> error;
_ -> jid:from_string(To)
end,
if
(ToJ /= error) and (FromJ /= error) ->
Server = FromJ#jid.lserver,
User = FromJ#jid.luser,
case (FromJ#jid.lresource == <<"">>) and
lists:member(Server, ?MYHOSTS) of
true ->
if
(Server /= ToJ#jid.lserver) or
(User /= ToJ#jid.luser) ->
%% 0356 server MUST NOT allow the privileged entity
%% to do anything that the managed entity could not do
try_roster_subscribe(Server,User, FromJ, ToJ, Presence);
true -> %% we don't want presence sent to self
ok
end;
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end;
true ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Incorrect stanza from/to JID">>,
Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_service:send_element(StateData, Err)
end;
true ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end.
forward_message(StateData, Message, Packet) ->
PrivAccess = StateData#state.privilege_access,
T = proplists:get_value(message, PrivAccess, none),
if
(T == <<"outgoing">>) ->
From = fxml:get_attr_s(<<"from">>, Message#xmlel.attrs),
FromJ = jid:from_string(From),
To = fxml:get_attr_s(<<"to">>, Message#xmlel.attrs),
ToJ = case To of
<<"">> -> FromJ;
_ -> jid:from_string(To)
end,
if
(ToJ /= error) and (FromJ /= error) ->
Server = FromJ#jid.server,
User = FromJ#jid.user,
case (FromJ#jid.lresource == <<"">>) and
lists:member(Server, ?MYHOSTS) of
true ->
%% there are no restriction on to attribute
PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
Server, #userlist{},
[User, Server]),
check_privacy_route(Server, User, PrivList,
FromJ, ToJ, Message);
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end;
true ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"Incorrect stanza from/to JID">>,
Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_service:send_element(StateData, Err)
end;
true ->
Err = jlib:make_error_reply(Packet,?ERR_FORBIDDEN),
ejabberd_service:send_element(StateData, Err)
end.
%%%--------------------------------------------------------------------------------------
%%% helper functions
%%%--------------------------------------------------------------------------------------
compare_presences(undefined, _Presence) -> false;
compare_presences(#xmlel{attrs = Attrs, children = Child},
#xmlel{attrs = Attrs2, children = Child2}) ->
Id1 = fxml:get_attr_s(<<"id">>, Attrs),
Id2 = fxml:get_attr_s(<<"id">>, Attrs2),
if
(Id1 /= Id2) ->
false;
(Id1 /= <<"">>) and (Id1 == Id2) ->
true;
true ->
case not compare_attrs(Attrs, Attrs2) of
true -> false;
_ ->
compare_elements(Child, Child2)
end
end.
compare_elements([],[]) -> true;
compare_elements(Tags1, Tags2) when length(Tags1) == length(Tags2) ->
compare_tags(Tags1,Tags2);
compare_elements(_Tags1, _Tags2) -> false.
compare_tags([],[]) -> true;
compare_tags([{xmlcdata, CData}|Tags1], [{xmlcdata, CData}|Tags2]) ->
compare_tags(Tags1, Tags2);
compare_tags([{xmlcdata, _CData1}|_Tags1], [{xmlcdata, _CData2}|_Tags2]) ->
false;
compare_tags([#xmlel{} = Stanza1|Tags1], [#xmlel{} = Stanza2|Tags2]) ->
case (Stanza1#xmlel.name == Stanza2#xmlel.name) and
compare_attrs(Stanza1#xmlel.attrs, Stanza2#xmlel.attrs) and
compare_tags(Stanza1#xmlel.children, Stanza2#xmlel.children) of
true ->
compare_tags(Tags1,Tags2);
false ->
false
end.
%% attr() :: {Name, Value}
-spec compare_attrs([attr()], [attr()]) -> boolean().
compare_attrs([],[]) -> true;
compare_attrs(Attrs1, Attrs2) when length(Attrs1) == length(Attrs2) ->
lists:foldl(fun(Attr,Acc) -> lists:member(Attr, Attrs2) and Acc end, true, Attrs1);
compare_attrs(_Attrs1, _Attrs2) -> false.
%% Check if privacy rules allow this delivery
%% from ejabberd_c2s.erl
privacy_check_packet(Server, User, PrivList, From, To, Packet , Dir) ->
ejabberd_hooks:run_fold(privacy_check_packet,
Server, allow, [User, Server, PrivList,
{From, To, Packet}, Dir]).
check_privacy_route(Server, User, PrivList, From, To, Packet) ->
case privacy_check_packet(Server, User, PrivList, From, To, Packet, out) of
allow ->
ejabberd_router:route(From, To, Packet);
_ -> ok %% who should receive error : service or user?
end.
try_roster_subscribe(Server,User, From, To, Packet) ->
Access =
gen_mod:get_module_opt(Server, mod_roster, access,
fun(A) when is_atom(A) -> A end, all),
case acl:match_rule(Server, Access, From) of
deny ->
ok;
allow ->
ejabberd_hooks:run(roster_out_subscription, Server,
[User, Server, To, subscribe]),
PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
Server,
#userlist{},
[User, Server]),
check_privacy_route(Server, User, PrivList, From, To, Packet)
end.
-2
View File
@@ -363,8 +363,6 @@ depends(ServerHost, Opts) ->
%% The default plugin module is implicit.
%% <p>The Erlang code for the plugin is located in a module called
%% <em>node_plugin</em>. The 'node_' prefix is mandatory.</p>
%% <p>The modules are initialized in alphetical order and the list is checked
%% and sorted to ensure that each module is initialized only once.</p>
%% <p>See {@link node_hometree:init/1} for an example implementation.</p>
init_plugins(Host, ServerHost, Opts) ->
TreePlugin = tree(Host, gen_mod:get_opt(nodetree, Opts,
+3 -2
View File
@@ -946,8 +946,9 @@ select_affiliation_subscriptions(Nidx, JID, JID) ->
select_affiliation_subscriptions(Nidx, JID);
select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
{result, Affiliation} = get_affiliation(Nidx, GenKey),
{result, Subscriptions} = get_subscriptions(Nidx, SubKey),
{Affiliation, Subscriptions}.
{result, BareJidSubs} = get_subscriptions(Nidx, GenKey),
{result, FullJidSubs} = get_subscriptions(Nidx, SubKey),
{Affiliation, BareJidSubs++FullJidSubs}.
update_affiliation(Nidx, JID, Affiliation) ->
J = encode_jid(JID),
+2 -1
View File
@@ -38,6 +38,7 @@
%%% plugins:
%%% - "flat"
%%% - "pep" # Requires mod_caps.
%%% - "mb"
%%% pep_mapping:
%%% "urn:xmpp:microblog:0": "mb"
%%% </pre></p>
@@ -154,7 +155,7 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) ->
node_pep:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
node_pep:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
node_pep:get_states(Nidx).
+151
View File
@@ -0,0 +1,151 @@
%%%----------------------------------------------------------------------
%%% File : node_mb_sql.erl
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Purpose : PEP microblogging (XEP-0277) plugin with SQL backend
%%% Created : 6 Sep 2016 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
%%% ejabberd, Copyright (C) 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(node_mb_sql).
-behaviour(gen_pubsub_node).
-author('holger@zedat.fu-berlin.de').
-include("pubsub.hrl").
-include("jlib.hrl").
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
get_subscriptions/2, set_subscriptions/4,
get_pending_nodes/2, get_states/1, get_state/2,
set_state/1, get_items/7, get_items/3, get_item/7,
get_item/2, set_item/1, get_item_name/3, node_to_path/1,
path_to_node/1]).
init(Host, ServerHost, Opts) ->
node_pep_sql:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_pep_sql:terminate(Host, ServerHost), ok.
options() ->
[{sql, true}, {rsm, true} | node_mb:options()].
features() ->
[<<"rsm">> | node_mb:features()].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_pep_sql:create_node_permission(Host, ServerHost, Node, ParentNode,
Owner, Access).
create_node(Nidx, Owner) ->
node_pep_sql:create_node(Nidx, Owner).
delete_node(Removed) ->
node_pep_sql:delete_node(Removed).
subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options) ->
node_pep_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
node_pep_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_pep_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_pep_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_pep_sql:delete_item(Nidx, Publisher, PublishModel, ItemId).
purge_node(Nidx, Owner) ->
node_pep_sql:purge_node(Nidx, Owner).
get_entity_affiliations(Host, Owner) ->
node_pep_sql:get_entity_affiliations(Host, Owner).
get_node_affiliations(Nidx) ->
node_pep_sql:get_node_affiliations(Nidx).
get_affiliation(Nidx, Owner) ->
node_pep_sql:get_affiliation(Nidx, Owner).
set_affiliation(Nidx, Owner, Affiliation) ->
node_pep_sql:set_affiliation(Nidx, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_pep_sql:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Nidx) ->
node_pep_sql:get_node_subscriptions(Nidx).
get_subscriptions(Nidx, Owner) ->
node_pep_sql:get_subscriptions(Nidx, Owner).
set_subscriptions(Nidx, Owner, Subscription, SubId) ->
node_pep_sql:set_subscriptions(Nidx, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_pep_sql:get_pending_nodes(Host, Owner).
get_states(Nidx) ->
node_pep_sql:get_states(Nidx).
get_state(Nidx, JID) ->
node_pep_sql:get_state(Nidx, JID).
set_state(State) ->
node_pep_sql:set_state(State).
get_items(Nidx, From, RSM) ->
node_pep_sql:get_items(Nidx, From, RSM).
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId,
RSM) ->
node_pep_sql:get_items(Nidx, JID, AccessModel, PresenceSubscription,
RosterGroup, SubId, RSM).
get_item(Nidx, ItemId) ->
node_pep_sql:get_item(Nidx, ItemId).
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup,
SubId) ->
node_pep_sql:get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription,
RosterGroup, SubId).
set_item(Item) ->
node_pep_sql:set_item(Item).
get_item_name(Host, Node, Id) ->
node_pep_sql:get_item_name(Host, Node, Id).
node_to_path(Node) ->
node_pep_sql:node_to_path(Node).
path_to_node(Path) ->
node_pep_sql:path_to_node(Path).
+1
View File
@@ -58,6 +58,7 @@ defmodule EjabberdCommandsMockTest do
setup do
:meck.unload
:meck.new(@module, [:non_strict])
:mnesia.delete_table(:ejabberd_commands)
:ejabberd_commands.init
end
+4 -1
View File
@@ -26,7 +26,10 @@ defmodule EjabberdOauthMock do
:mnesia.start
:mnesia.create_table(:oauth_token,
[ram_copies: [node],
attributes: [:oauth_token, :us, :scope, :expire]])
attributes: [:oauth_token, :us, :scope, :expire]])
:application.start(:cache_tab)
:cache_tab.new(:oauth_token,
[{:max_size, 1000}, {:life_time, 3600}])
end
def get_token(user, domain, command, expiration \\ 3600) do
+87
View File
@@ -0,0 +1,87 @@
defmodule Ejabberd.Config.AttrTest do
use ExUnit.Case, async: true
alias Ejabberd.Config.Attr
test "extract attrs from single line block" do
block = quote do
@active false
end
block_res = Attr.extract_attrs_from_block_with_defaults(block)
assert {:active, false} in block_res
end
test "extract attrs from multi line block" do
block = quote do
@active false
@opts [http: true]
end
block_res = Attr.extract_attrs_from_block_with_defaults(block)
assert {:active, false} in block_res
assert {:opts, [http: true]} in block_res
end
test "inserts correctly defaults attr when missing in block" do
block = quote do
@active false
@opts [http: true]
end
block_res = Attr.extract_attrs_from_block_with_defaults(block)
assert {:active, false} in block_res
assert {:git, ""} in block_res
assert {:name, ""} in block_res
assert {:opts, [http: true]} in block_res
assert {:dependency, []} in block_res
end
test "inserts all defaults attr when passed an empty block" do
block = quote do
end
block_res = Attr.extract_attrs_from_block_with_defaults(block)
assert {:active, true} in block_res
assert {:git, ""} in block_res
assert {:name, ""} in block_res
assert {:opts, []} in block_res
assert {:dependency, []} in block_res
end
test "validates attrs and returns errors, if any" do
block = quote do
@not_supported_attr true
@active "false"
@opts [http: true]
end
block_res =
block
|> Attr.extract_attrs_from_block_with_defaults
|> Attr.validate
assert {:ok, {:opts, [http: true]}} in block_res
assert {:ok, {:git, ""}} in block_res
assert {:error, {:not_supported_attr, true}, :attr_not_supported} in block_res
assert {:error, {:active, "false"}, :type_not_supported} in block_res
end
test "returns the correct type for an attribute" do
assert :boolean == Attr.get_type_for_attr(:active)
assert :string == Attr.get_type_for_attr(:git)
assert :string == Attr.get_type_for_attr(:name)
assert :list == Attr.get_type_for_attr(:opts)
assert :list == Attr.get_type_for_attr(:dependency)
end
test "returns the correct default for an attribute" do
assert true == Attr.get_default_for_attr(:active)
assert "" == Attr.get_default_for_attr(:git)
assert "" == Attr.get_default_for_attr(:name)
assert [] == Attr.get_default_for_attr(:opts)
assert [] == Attr.get_default_for_attr(:dependency)
end
end
+65
View File
@@ -0,0 +1,65 @@
defmodule Ejabberd.ConfigTest do
use ExUnit.Case
alias Ejabberd.Config
alias Ejabberd.Config.Store
setup_all do
pid = Process.whereis(Ejabberd.Config.Store)
unless pid != nil and Process.alive?(pid) do
Store.start_link
File.cd("test/elixir-config/shared")
config_file_path = File.cwd! <> "/ejabberd.exs"
Config.init(config_file_path)
end
{:ok, %{}}
end
test "extracts successfully the module name from config file" do
assert [Ejabberd.ConfigFile] == Store.get(:module_name)
end
test "extracts successfully general opts from config file" do
[general] = Store.get(:general)
shaper = [normal: 1000, fast: 50000, max_fsm_queue: 1000]
assert [loglevel: 4, language: "en", hosts: ["localhost"], shaper: shaper] == general
end
test "extracts successfully listeners from config file" do
[listen] = Store.get(:listeners)
assert :ejabberd_c2s == listen.module
assert [port: 5222, max_stanza_size: 65536, shaper: :c2s_shaper, access: :c2s] == listen.attrs[:opts]
end
test "extracts successfully modules from config file" do
[module] = Store.get(:modules)
assert :mod_adhoc == module.module
assert [] == module.attrs[:opts]
end
test "extracts successfully hooks from config file" do
[register_hook] = Store.get(:hooks)
assert :register_user == register_hook.hook
assert [host: "localhost"] == register_hook.opts
assert is_function(register_hook.fun)
end
# TODO: When enalbed, this test causes the evaluation of a different config file, so
# the other tests, that uses the store, are compromised because the data is different.
# So, until a good way is found, this test should remain disabed.
#
# test "init/2 with force:true re-initializes the config store with new data" do
# config_file_path = File.cwd! <> "/ejabberd_different_from_default.exs"
# Config.init(config_file_path, true)
#
# assert [Ejabberd.ConfigFile] == Store.get(:module_name)
# assert [[loglevel: 4, language: "en", hosts: ["localhost"]]] == Store.get(:general)
# assert [] == Store.get(:modules)
# assert [] == Store.get(:listeners)
#
# Store.stop
# end
end
+49
View File
@@ -0,0 +1,49 @@
defmodule Ejabberd.Config.EjabberdLoggerTest do
use ExUnit.Case
import ExUnit.CaptureIO
alias Ejabberd.Config
alias Ejabberd.Config.Store
alias Ejabberd.Config.Validation
alias Ejabberd.Config.EjabberdLogger
setup_all do
pid = Process.whereis(Ejabberd.Config.Store)
unless pid != nil and Process.alive?(pid) do
Store.start_link
File.cd("test/elixir-config/shared")
config_file_path = File.cwd! <> "/ejabberd_for_validation.exs"
Config.init(config_file_path)
end
{:ok, %{}}
end
test "outputs correctly when attr is not supported" do
error_msg = "[ WARN ] Annotation @attr_not_supported is not supported.\n"
[_mod_irc, _mod_configure, mod_time] = Store.get(:modules)
fun = fn ->
mod_time
|> Validation.validate
|> EjabberdLogger.log_errors
end
assert capture_io(fun) == error_msg
end
test "outputs correctly when dependency is not found" do
error_msg = "[ WARN ] Module :mod_adhoc was not found, but is required as a dependency.\n"
[_mod_irc, mod_configure, _mod_time] = Store.get(:modules)
fun = fn ->
mod_configure
|> Validation.validate
|> EjabberdLogger.log_errors
end
assert capture_io(fun) == error_msg
end
end
+31
View File
@@ -0,0 +1,31 @@
defmodule Ejabberd.ConfigFile do
use Ejabberd.Config
def start do
[loglevel: 4,
language: "en",
hosts: ["localhost"],
shaper: shaper]
end
defp shaper do
[normal: 1000,
fast: 50000,
max_fsm_queue: 1000]
end
listen :ejabberd_c2s do
@opts [
port: 5222,
max_stanza_size: 65536,
shaper: :c2s_shaper,
access: :c2s]
end
module :mod_adhoc do
end
hook :register_user, [host: "localhost"], fn(user, server) ->
info("User registered: #{user} on #{server}")
end
end
@@ -0,0 +1,9 @@
defmodule Ejabberd.ConfigFile do
use Ejabberd.Config
def start do
[loglevel: 4,
language: "en",
hosts: ["localhost"]]
end
end
@@ -0,0 +1,20 @@
defmodule Ejabberd.ConfigFile do
use Ejabberd.Config
def start do
[loglevel: 4,
language: "en",
hosts: ["localhost"]]
end
module :mod_time do
@attr_not_supported true
end
module :mod_configure do
@dependency [:mod_adhoc]
end
module :mod_irc do
end
end
+32
View File
@@ -0,0 +1,32 @@
defmodule Ejabberd.Config.ValidationTest do
use ExUnit.Case
alias Ejabberd.Config
alias Ejabberd.Config.Store
alias Ejabberd.Config.Validation
setup_all do
pid = Process.whereis(Ejabberd.Config.Store)
unless pid != nil and Process.alive?(pid) do
Store.start_link
File.cd("test/elixir-config/shared")
config_file_path = File.cwd! <> "/ejabberd_for_validation.exs"
Config.init(config_file_path)
end
{:ok, %{}}
end
test "validates correctly the modules" do
[mod_irc, mod_configure, mod_time] = Store.get(:modules)
[{:error, _mod, errors}] = Validation.validate(mod_configure)
assert %{dependency: [mod_adhoc: :not_found]} == errors
[{:error, _mod, errors}] = Validation.validate(mod_time)
assert %{attribute: [{{:attr_not_supported, true}, :attr_not_supported}]} == errors
[{:ok, _mod}] = Validation.validate(mod_irc)
end
end