Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 405a0a21c1 | |||
| c39501a48d | |||
| c3543e002d | |||
| 2f596b0e10 | |||
| 054382f074 | |||
| 96d05dad8f | |||
| 1aca541639 | |||
| d0761039ff | |||
| fe1bf27ef3 | |||
| d222fed228 | |||
| 8fd888eb2b | |||
| 41386d718d | |||
| 26a040e2d5 | |||
| 4bd45bada7 | |||
| ad39da0b0a | |||
| 36ab9cc2ea | |||
| 6c943aa293 | |||
| 803270fc6b | |||
| e6f7233351 | |||
| 58a72bd395 | |||
| d2621130a3 | |||
| a8368278ec | |||
| 621f0e2b7c | |||
| 7a538bb88b | |||
| af0a493c66 | |||
| f304149615 | |||
| 3803a8de3c | |||
| 1edca899ff | |||
| c6afb9731b | |||
| 5ec2874a96 | |||
| 417284a921 | |||
| af2999a783 | |||
| 48ce34987d | |||
| e29f47893f | |||
| c770a54aac | |||
| 96a748d34f | |||
| 31592fe51f | |||
| f1afea223b | |||
| 1bfa1c613b | |||
| d5659735b3 | |||
| 23d9fb0592 | |||
| 8dd2044a27 | |||
| e13edff6ae | |||
| 8af85d913f | |||
| 20a510d877 | |||
| 8821cf8b27 | |||
| 4d19fb518f | |||
| e7217e6320 | |||
| 5b4f347da8 | |||
| 38666cfd58 | |||
| 877d0752e2 | |||
| 0ab08f4eeb | |||
| 4ee8af633b | |||
| bf9d6b5534 | |||
| 28dde294e5 | |||
| ffba664f2c | |||
| 50596dc4d3 | |||
| e63fe5c216 | |||
| 1fc58ace2f | |||
| c4b14d045a | |||
| 9c6ee60f1a | |||
| efc744092b | |||
| a0c8012c66 | |||
| b62aa3d2dc | |||
| 91e26fbf7a | |||
| c2ef55a075 | |||
| d969e917c6 | |||
| 9a5f0751be |
+1
-1
@@ -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/'
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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{} ).
|
||||
@@ -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,
|
||||
|
||||
@@ -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">>).
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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) ->
|
||||
|
||||
@@ -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
@@ -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
@@ -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},
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
@@ -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
@@ -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()) ->
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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().
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user