mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-20 16:15:59 +01:00
Support for Elixir configuration file #1208
Contribution for Google Summer of code 2016 by Gabriel Gatu
This commit is contained in:
parent
e6f7233351
commit
803270fc6b
169
config/ejabberd.exs
Normal file
169
config/ejabberd.exs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
defmodule Ejabberd.ConfigFile do
|
||||||
|
use Ejabberd.Config
|
||||||
|
|
||||||
|
def start do
|
||||||
|
[loglevel: 4,
|
||||||
|
log_rotate_size: 10485760,
|
||||||
|
log_rotate_date: "",
|
||||||
|
log_rotate_count: 1,
|
||||||
|
log_rate_limit: 100,
|
||||||
|
auth_method: :internal,
|
||||||
|
max_fsm_queue: 1000,
|
||||||
|
language: "en",
|
||||||
|
allow_contrib_modules: true,
|
||||||
|
hosts: ["localhost"],
|
||||||
|
shaper: shaper,
|
||||||
|
acl: acl,
|
||||||
|
access: access]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp shaper do
|
||||||
|
[normal: 1000,
|
||||||
|
fast: 50000,
|
||||||
|
max_fsm_queue: 1000]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp acl do
|
||||||
|
[local:
|
||||||
|
[user_regexp: "", loopback: [ip: "127.0.0.0/8"]]]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp access do
|
||||||
|
[max_user_sessions: [all: 10],
|
||||||
|
max_user_offline_messages: [admin: 5000, all: 100],
|
||||||
|
local: [local: :allow],
|
||||||
|
c2s: [blocked: :deny, all: :allow],
|
||||||
|
c2s_shaper: [admin: :none, all: :normal],
|
||||||
|
s2s_shaper: [all: :fast],
|
||||||
|
announce: [admin: :allow],
|
||||||
|
configure: [admin: :allow],
|
||||||
|
muc_admin: [admin: :allow],
|
||||||
|
muc_create: [local: :allow],
|
||||||
|
muc: [all: :allow],
|
||||||
|
pubsub_createnode: [local: :allow],
|
||||||
|
register: [all: :allow],
|
||||||
|
trusted_network: [loopback: :allow]]
|
||||||
|
end
|
||||||
|
|
||||||
|
listen :ejabberd_c2s do
|
||||||
|
@opts [
|
||||||
|
port: 5222,
|
||||||
|
max_stanza_size: 65536,
|
||||||
|
shaper: :c2s_shaper,
|
||||||
|
access: :c2s]
|
||||||
|
end
|
||||||
|
|
||||||
|
listen :ejabberd_s2s_in do
|
||||||
|
@opts [port: 5269]
|
||||||
|
end
|
||||||
|
|
||||||
|
listen :ejabberd_http do
|
||||||
|
@opts [
|
||||||
|
port: 5280,
|
||||||
|
web_admin: true,
|
||||||
|
http_poll: true,
|
||||||
|
http_bind: true,
|
||||||
|
captcha: true]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_adhoc do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_announce do
|
||||||
|
@opts [access: :announce]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_blocking do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_caps do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_carboncopy do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_client_state do
|
||||||
|
@opts [
|
||||||
|
drop_chat_states: true,
|
||||||
|
queue_presence: false]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_configure do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_disco do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_irc do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_http_bind do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_last do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_muc do
|
||||||
|
@opts [
|
||||||
|
access: :muc,
|
||||||
|
access_create: :muc_create,
|
||||||
|
access_persistent: :muc_create,
|
||||||
|
access_admin: :muc_admin]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_offline do
|
||||||
|
@opts [access_max_user_messages: :max_user_offline_messages]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_ping do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_privacy do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_private do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_pubsub do
|
||||||
|
@opts [
|
||||||
|
access_createnode: :pubsub_createnode,
|
||||||
|
ignore_pep_from_offline: true,
|
||||||
|
last_item_cache: true,
|
||||||
|
plugins: ["flat", "hometree", "pep"]]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_register do
|
||||||
|
@opts [welcome_message: [
|
||||||
|
subject: "Welcome!",
|
||||||
|
body: "Hi.\nWelcome to this XMPP Server",
|
||||||
|
ip_access: :trusted_network,
|
||||||
|
access: :register]]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_roster do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_shared_roster do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_stats do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_time do
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_version do
|
||||||
|
end
|
||||||
|
|
||||||
|
# Example of how to define a hook, called when the event
|
||||||
|
# specified is triggered.
|
||||||
|
#
|
||||||
|
# @event: Name of the event
|
||||||
|
# @opts: Params are optional. Available: :host and :priority.
|
||||||
|
# If missing, defaults are used. (host: :global | priority: 50)
|
||||||
|
# @callback Could be an anonymous function or a callback from a module,
|
||||||
|
# use the &ModuleName.function/arity format for that.
|
||||||
|
hook :register_user, [host: "localhost"], fn(user, server) ->
|
||||||
|
info("User registered: #{user} on #{server}")
|
||||||
|
end
|
||||||
|
end
|
667
config/ejabberd.yml
Normal file
667
config/ejabberd.yml
Normal file
@ -0,0 +1,667 @@
|
|||||||
|
###
|
||||||
|
### ejabberd configuration file
|
||||||
|
###
|
||||||
|
###
|
||||||
|
|
||||||
|
### The parameters used in this configuration file are explained in more detail
|
||||||
|
### in the ejabberd Installation and Operation Guide.
|
||||||
|
### Please consult the Guide in case of doubts, it is included with
|
||||||
|
### your copy of ejabberd, and is also available online at
|
||||||
|
### http://www.process-one.net/en/ejabberd/docs/
|
||||||
|
|
||||||
|
### The configuration file is written in YAML.
|
||||||
|
### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
|
||||||
|
### However, ejabberd treats different literals as different types:
|
||||||
|
###
|
||||||
|
### - unquoted or single-quoted strings. They are called "atoms".
|
||||||
|
### Example: dog, 'Jupiter', '3.14159', YELLOW
|
||||||
|
###
|
||||||
|
### - numeric literals. Example: 3, -45.0, .0
|
||||||
|
###
|
||||||
|
### - quoted or folded strings.
|
||||||
|
### Examples of quoted string: "Lizzard", "orange".
|
||||||
|
### Example of folded string:
|
||||||
|
### > Art thou not Romeo,
|
||||||
|
### and a Montague?
|
||||||
|
|
||||||
|
### =======
|
||||||
|
### LOGGING
|
||||||
|
|
||||||
|
##
|
||||||
|
## loglevel: Verbosity of log files generated by ejabberd.
|
||||||
|
## 0: No ejabberd log at all (not recommended)
|
||||||
|
## 1: Critical
|
||||||
|
## 2: Error
|
||||||
|
## 3: Warning
|
||||||
|
## 4: Info
|
||||||
|
## 5: Debug
|
||||||
|
##
|
||||||
|
loglevel: 4
|
||||||
|
|
||||||
|
##
|
||||||
|
## rotation: Describe how to rotate logs. Either size and/or date can trigger
|
||||||
|
## log rotation. Setting count to N keeps N rotated logs. Setting count to 0
|
||||||
|
## does not disable rotation, it instead rotates the file and keeps no previous
|
||||||
|
## versions around. Setting size to X rotate log when it reaches X bytes.
|
||||||
|
## To disable rotation set the size to 0 and the date to ""
|
||||||
|
## Date syntax is taken from the syntax newsyslog uses in newsyslog.conf.
|
||||||
|
## Some examples:
|
||||||
|
## $D0 rotate every night at midnight
|
||||||
|
## $D23 rotate every day at 23:00 hr
|
||||||
|
## $W0D23 rotate every week on Sunday at 23:00 hr
|
||||||
|
## $W5D16 rotate every week on Friday at 16:00 hr
|
||||||
|
## $M1D0 rotate on the first day of every month at midnight
|
||||||
|
## $M5D6 rotate on every 5th day of the month at 6:00 hr
|
||||||
|
##
|
||||||
|
log_rotate_size: 10485760
|
||||||
|
log_rotate_date: ""
|
||||||
|
log_rotate_count: 1
|
||||||
|
|
||||||
|
##
|
||||||
|
## overload protection: If you want to limit the number of messages per second
|
||||||
|
## allowed from error_logger, which is a good idea if you want to avoid a flood
|
||||||
|
## of messages when system is overloaded, you can set a limit.
|
||||||
|
## 100 is ejabberd's default.
|
||||||
|
log_rate_limit: 100
|
||||||
|
|
||||||
|
##
|
||||||
|
## watchdog_admins: Only useful for developers: if an ejabberd process
|
||||||
|
## consumes a lot of memory, send live notifications to these XMPP
|
||||||
|
## accounts.
|
||||||
|
##
|
||||||
|
## watchdog_admins:
|
||||||
|
## - "bob@example.com"
|
||||||
|
|
||||||
|
|
||||||
|
### ================
|
||||||
|
### SERVED HOSTNAMES
|
||||||
|
|
||||||
|
##
|
||||||
|
## hosts: Domains served by ejabberd.
|
||||||
|
## You can define one or several, for example:
|
||||||
|
## hosts:
|
||||||
|
## - "example.net"
|
||||||
|
## - "example.com"
|
||||||
|
## - "example.org"
|
||||||
|
##
|
||||||
|
hosts:
|
||||||
|
- "localhost"
|
||||||
|
|
||||||
|
##
|
||||||
|
## route_subdomains: Delegate subdomains to other XMPP servers.
|
||||||
|
## For example, if this ejabberd serves example.org and you want
|
||||||
|
## to allow communication with an XMPP server called im.example.org.
|
||||||
|
##
|
||||||
|
## route_subdomains: s2s
|
||||||
|
|
||||||
|
### ===============
|
||||||
|
### LISTENING PORTS
|
||||||
|
|
||||||
|
##
|
||||||
|
## listen: The ports ejabberd will listen on, which service each is handled
|
||||||
|
## by and what options to start it with.
|
||||||
|
##
|
||||||
|
listen:
|
||||||
|
-
|
||||||
|
port: 5222
|
||||||
|
module: ejabberd_c2s
|
||||||
|
##
|
||||||
|
## If TLS is compiled in and you installed a SSL
|
||||||
|
## certificate, specify the full path to the
|
||||||
|
## file and uncomment these lines:
|
||||||
|
##
|
||||||
|
## certfile: "/path/to/ssl.pem"
|
||||||
|
## starttls: true
|
||||||
|
##
|
||||||
|
## To enforce TLS encryption for client connections,
|
||||||
|
## use this instead of the "starttls" option:
|
||||||
|
##
|
||||||
|
## starttls_required: true
|
||||||
|
##
|
||||||
|
## Custom OpenSSL options
|
||||||
|
##
|
||||||
|
## protocol_options:
|
||||||
|
## - "no_sslv3"
|
||||||
|
## - "no_tlsv1"
|
||||||
|
max_stanza_size: 65536
|
||||||
|
shaper: c2s_shaper
|
||||||
|
access: c2s
|
||||||
|
-
|
||||||
|
port: 5269
|
||||||
|
module: ejabberd_s2s_in
|
||||||
|
##
|
||||||
|
## ejabberd_service: Interact with external components (transports, ...)
|
||||||
|
##
|
||||||
|
## -
|
||||||
|
## port: 8888
|
||||||
|
## module: ejabberd_service
|
||||||
|
## access: all
|
||||||
|
## shaper_rule: fast
|
||||||
|
## ip: "127.0.0.1"
|
||||||
|
## hosts:
|
||||||
|
## "icq.example.org":
|
||||||
|
## password: "secret"
|
||||||
|
## "sms.example.org":
|
||||||
|
## password: "secret"
|
||||||
|
|
||||||
|
##
|
||||||
|
## ejabberd_stun: Handles STUN Binding requests
|
||||||
|
##
|
||||||
|
## -
|
||||||
|
## port: 3478
|
||||||
|
## transport: udp
|
||||||
|
## module: ejabberd_stun
|
||||||
|
|
||||||
|
##
|
||||||
|
## To handle XML-RPC requests that provide admin credentials:
|
||||||
|
##
|
||||||
|
## -
|
||||||
|
## port: 4560
|
||||||
|
## module: ejabberd_xmlrpc
|
||||||
|
-
|
||||||
|
port: 5280
|
||||||
|
module: ejabberd_http
|
||||||
|
## request_handlers:
|
||||||
|
## "/pub/archive": mod_http_fileserver
|
||||||
|
web_admin: true
|
||||||
|
http_poll: true
|
||||||
|
http_bind: true
|
||||||
|
## register: true
|
||||||
|
captcha: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
|
||||||
|
## Allowed values are: false optional required required_trusted
|
||||||
|
## You must specify a certificate file.
|
||||||
|
##
|
||||||
|
## s2s_use_starttls: optional
|
||||||
|
|
||||||
|
##
|
||||||
|
## s2s_certfile: Specify a certificate file.
|
||||||
|
##
|
||||||
|
## s2s_certfile: "/path/to/ssl.pem"
|
||||||
|
|
||||||
|
## Custom OpenSSL options
|
||||||
|
##
|
||||||
|
## s2s_protocol_options:
|
||||||
|
## - "no_sslv3"
|
||||||
|
## - "no_tlsv1"
|
||||||
|
|
||||||
|
##
|
||||||
|
## domain_certfile: Specify a different certificate for each served hostname.
|
||||||
|
##
|
||||||
|
## host_config:
|
||||||
|
## "example.org":
|
||||||
|
## domain_certfile: "/path/to/example_org.pem"
|
||||||
|
## "example.com":
|
||||||
|
## domain_certfile: "/path/to/example_com.pem"
|
||||||
|
|
||||||
|
##
|
||||||
|
## S2S whitelist or blacklist
|
||||||
|
##
|
||||||
|
## Default s2s policy for undefined hosts.
|
||||||
|
##
|
||||||
|
## s2s_access: s2s
|
||||||
|
|
||||||
|
##
|
||||||
|
## Outgoing S2S options
|
||||||
|
##
|
||||||
|
## Preferred address families (which to try first) and connect timeout
|
||||||
|
## in milliseconds.
|
||||||
|
##
|
||||||
|
## outgoing_s2s_families:
|
||||||
|
## - ipv4
|
||||||
|
## - ipv6
|
||||||
|
## outgoing_s2s_timeout: 10000
|
||||||
|
|
||||||
|
### ==============
|
||||||
|
### AUTHENTICATION
|
||||||
|
|
||||||
|
##
|
||||||
|
## auth_method: Method used to authenticate the users.
|
||||||
|
## The default method is the internal.
|
||||||
|
## If you want to use a different method,
|
||||||
|
## comment this line and enable the correct ones.
|
||||||
|
##
|
||||||
|
auth_method: internal
|
||||||
|
|
||||||
|
##
|
||||||
|
## Store the plain passwords or hashed for SCRAM:
|
||||||
|
## auth_password_format: plain
|
||||||
|
## auth_password_format: scram
|
||||||
|
##
|
||||||
|
## Define the FQDN if ejabberd doesn't detect it:
|
||||||
|
## fqdn: "server3.example.com"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Authentication using external script
|
||||||
|
## Make sure the script is executable by ejabberd.
|
||||||
|
##
|
||||||
|
## auth_method: external
|
||||||
|
## extauth_program: "/path/to/authentication/script"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Authentication using ODBC
|
||||||
|
## Remember to setup a database in the next section.
|
||||||
|
##
|
||||||
|
## auth_method: odbc
|
||||||
|
|
||||||
|
##
|
||||||
|
## Authentication using PAM
|
||||||
|
##
|
||||||
|
## auth_method: pam
|
||||||
|
## pam_service: "pamservicename"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Authentication using LDAP
|
||||||
|
##
|
||||||
|
## auth_method: ldap
|
||||||
|
##
|
||||||
|
## List of LDAP servers:
|
||||||
|
## ldap_servers:
|
||||||
|
## - "localhost"
|
||||||
|
##
|
||||||
|
## Encryption of connection to LDAP servers:
|
||||||
|
## ldap_encrypt: none
|
||||||
|
## ldap_encrypt: tls
|
||||||
|
##
|
||||||
|
## Port to connect to on LDAP servers:
|
||||||
|
## ldap_port: 389
|
||||||
|
## ldap_port: 636
|
||||||
|
##
|
||||||
|
## LDAP manager:
|
||||||
|
## ldap_rootdn: "dc=example,dc=com"
|
||||||
|
##
|
||||||
|
## Password of LDAP manager:
|
||||||
|
## ldap_password: "******"
|
||||||
|
##
|
||||||
|
## Search base of LDAP directory:
|
||||||
|
## ldap_base: "dc=example,dc=com"
|
||||||
|
##
|
||||||
|
## LDAP attribute that holds user ID:
|
||||||
|
## ldap_uids:
|
||||||
|
## - "mail": "%u@mail.example.org"
|
||||||
|
##
|
||||||
|
## LDAP filter:
|
||||||
|
## ldap_filter: "(objectClass=shadowAccount)"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Anonymous login support:
|
||||||
|
## auth_method: anonymous
|
||||||
|
## anonymous_protocol: sasl_anon | login_anon | both
|
||||||
|
## allow_multiple_connections: true | false
|
||||||
|
##
|
||||||
|
## host_config:
|
||||||
|
## "public.example.org":
|
||||||
|
## auth_method: anonymous
|
||||||
|
## allow_multiple_connections: false
|
||||||
|
## anonymous_protocol: sasl_anon
|
||||||
|
##
|
||||||
|
## To use both anonymous and internal authentication:
|
||||||
|
##
|
||||||
|
## host_config:
|
||||||
|
## "public.example.org":
|
||||||
|
## auth_method:
|
||||||
|
## - internal
|
||||||
|
## - anonymous
|
||||||
|
|
||||||
|
### ==============
|
||||||
|
### DATABASE SETUP
|
||||||
|
|
||||||
|
## ejabberd by default uses the internal Mnesia database,
|
||||||
|
## so you do not necessarily need this section.
|
||||||
|
## This section provides configuration examples in case
|
||||||
|
## you want to use other database backends.
|
||||||
|
## Please consult the ejabberd Guide for details on database creation.
|
||||||
|
|
||||||
|
##
|
||||||
|
## MySQL server:
|
||||||
|
##
|
||||||
|
## odbc_type: mysql
|
||||||
|
## odbc_server: "server"
|
||||||
|
## odbc_database: "database"
|
||||||
|
## odbc_username: "username"
|
||||||
|
## odbc_password: "password"
|
||||||
|
##
|
||||||
|
## If you want to specify the port:
|
||||||
|
## odbc_port: 1234
|
||||||
|
|
||||||
|
##
|
||||||
|
## PostgreSQL server:
|
||||||
|
##
|
||||||
|
## odbc_type: pgsql
|
||||||
|
## odbc_server: "server"
|
||||||
|
## odbc_database: "database"
|
||||||
|
## odbc_username: "username"
|
||||||
|
## odbc_password: "password"
|
||||||
|
##
|
||||||
|
## If you want to specify the port:
|
||||||
|
## odbc_port: 1234
|
||||||
|
##
|
||||||
|
## If you use PostgreSQL, have a large database, and need a
|
||||||
|
## faster but inexact replacement for "select count(*) from users"
|
||||||
|
##
|
||||||
|
## pgsql_users_number_estimate: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## ODBC compatible or MSSQL server:
|
||||||
|
##
|
||||||
|
## odbc_type: odbc
|
||||||
|
## odbc_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Number of connections to open to the database for each virtual host
|
||||||
|
##
|
||||||
|
## odbc_pool_size: 10
|
||||||
|
|
||||||
|
##
|
||||||
|
## Interval to make a dummy SQL request to keep the connections to the
|
||||||
|
## database alive. Specify in seconds: for example 28800 means 8 hours
|
||||||
|
##
|
||||||
|
## odbc_keepalive_interval: undefined
|
||||||
|
|
||||||
|
### ===============
|
||||||
|
### TRAFFIC SHAPERS
|
||||||
|
|
||||||
|
shaper:
|
||||||
|
##
|
||||||
|
## The "normal" shaper limits traffic speed to 1000 B/s
|
||||||
|
##
|
||||||
|
normal: 1000
|
||||||
|
|
||||||
|
##
|
||||||
|
## The "fast" shaper limits traffic speed to 50000 B/s
|
||||||
|
##
|
||||||
|
fast: 50000
|
||||||
|
|
||||||
|
##
|
||||||
|
## This option specifies the maximum number of elements in the queue
|
||||||
|
## of the FSM. Refer to the documentation for details.
|
||||||
|
##
|
||||||
|
max_fsm_queue: 1000
|
||||||
|
|
||||||
|
###. ====================
|
||||||
|
###' ACCESS CONTROL LISTS
|
||||||
|
acl:
|
||||||
|
##
|
||||||
|
## The 'admin' ACL grants administrative privileges to XMPP accounts.
|
||||||
|
## You can put here as many accounts as you want.
|
||||||
|
##
|
||||||
|
## admin:
|
||||||
|
## user:
|
||||||
|
## - "aleksey": "localhost"
|
||||||
|
## - "ermine": "example.org"
|
||||||
|
##
|
||||||
|
## Blocked users
|
||||||
|
##
|
||||||
|
## blocked:
|
||||||
|
## user:
|
||||||
|
## - "baduser": "example.org"
|
||||||
|
## - "test"
|
||||||
|
|
||||||
|
## Local users: don't modify this.
|
||||||
|
##
|
||||||
|
local:
|
||||||
|
user_regexp: ""
|
||||||
|
|
||||||
|
##
|
||||||
|
## More examples of ACLs
|
||||||
|
##
|
||||||
|
## jabberorg:
|
||||||
|
## server:
|
||||||
|
## - "jabber.org"
|
||||||
|
## aleksey:
|
||||||
|
## user:
|
||||||
|
## - "aleksey": "jabber.ru"
|
||||||
|
## test:
|
||||||
|
## user_regexp: "^test"
|
||||||
|
## user_glob: "test*"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Loopback network
|
||||||
|
##
|
||||||
|
loopback:
|
||||||
|
ip:
|
||||||
|
- "127.0.0.0/8"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Bad XMPP servers
|
||||||
|
##
|
||||||
|
## bad_servers:
|
||||||
|
## server:
|
||||||
|
## - "xmpp.zombie.org"
|
||||||
|
## - "xmpp.spam.com"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Define specific ACLs in a virtual host.
|
||||||
|
##
|
||||||
|
## host_config:
|
||||||
|
## "localhost":
|
||||||
|
## acl:
|
||||||
|
## admin:
|
||||||
|
## user:
|
||||||
|
## - "bob-local": "localhost"
|
||||||
|
|
||||||
|
### ============
|
||||||
|
### ACCESS RULES
|
||||||
|
access:
|
||||||
|
## Maximum number of simultaneous sessions allowed for a single user:
|
||||||
|
max_user_sessions:
|
||||||
|
all: 10
|
||||||
|
## Maximum number of offline messages that users can have:
|
||||||
|
max_user_offline_messages:
|
||||||
|
admin: 5000
|
||||||
|
all: 100
|
||||||
|
## This rule allows access only for local users:
|
||||||
|
local:
|
||||||
|
local: allow
|
||||||
|
## Only non-blocked users can use c2s connections:
|
||||||
|
c2s:
|
||||||
|
blocked: deny
|
||||||
|
all: allow
|
||||||
|
## For C2S connections, all users except admins use the "normal" shaper
|
||||||
|
c2s_shaper:
|
||||||
|
admin: none
|
||||||
|
all: normal
|
||||||
|
## All S2S connections use the "fast" shaper
|
||||||
|
s2s_shaper:
|
||||||
|
all: fast
|
||||||
|
## Only admins can send announcement messages:
|
||||||
|
announce:
|
||||||
|
admin: allow
|
||||||
|
## Only admins can use the configuration interface:
|
||||||
|
configure:
|
||||||
|
admin: allow
|
||||||
|
## Admins of this server are also admins of the MUC service:
|
||||||
|
muc_admin:
|
||||||
|
admin: allow
|
||||||
|
## Only accounts of the local ejabberd server can create rooms:
|
||||||
|
muc_create:
|
||||||
|
local: allow
|
||||||
|
## All users are allowed to use the MUC service:
|
||||||
|
muc:
|
||||||
|
all: allow
|
||||||
|
## Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||||
|
pubsub_createnode:
|
||||||
|
local: allow
|
||||||
|
## In-band registration allows registration of any possible username.
|
||||||
|
## To disable in-band registration, replace 'allow' with 'deny'.
|
||||||
|
register:
|
||||||
|
all: allow
|
||||||
|
## Only allow to register from localhost
|
||||||
|
trusted_network:
|
||||||
|
loopback: allow
|
||||||
|
## Do not establish S2S connections with bad servers
|
||||||
|
## s2s:
|
||||||
|
## bad_servers: deny
|
||||||
|
## all: allow
|
||||||
|
|
||||||
|
## By default the frequency of account registrations from the same IP
|
||||||
|
## is limited to 1 account every 10 minutes. To disable, specify: infinity
|
||||||
|
## registration_timeout: 600
|
||||||
|
|
||||||
|
##
|
||||||
|
## Define specific Access Rules in a virtual host.
|
||||||
|
##
|
||||||
|
## host_config:
|
||||||
|
## "localhost":
|
||||||
|
## access:
|
||||||
|
## c2s:
|
||||||
|
## admin: allow
|
||||||
|
## all: deny
|
||||||
|
## register:
|
||||||
|
## all: deny
|
||||||
|
|
||||||
|
### ================
|
||||||
|
### DEFAULT LANGUAGE
|
||||||
|
|
||||||
|
##
|
||||||
|
## language: Default language used for server messages.
|
||||||
|
##
|
||||||
|
language: "en"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Set a different default language in a virtual host.
|
||||||
|
##
|
||||||
|
## host_config:
|
||||||
|
## "localhost":
|
||||||
|
## language: "ru"
|
||||||
|
|
||||||
|
### =======
|
||||||
|
### CAPTCHA
|
||||||
|
|
||||||
|
##
|
||||||
|
## Full path to a script that generates the image.
|
||||||
|
##
|
||||||
|
## captcha_cmd: "/lib/ejabberd/priv/bin/captcha.sh"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Host for the URL and port where ejabberd listens for CAPTCHA requests.
|
||||||
|
##
|
||||||
|
## captcha_host: "example.org:5280"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
|
||||||
|
##
|
||||||
|
## captcha_limit: 5
|
||||||
|
|
||||||
|
### =======
|
||||||
|
### MODULES
|
||||||
|
|
||||||
|
##
|
||||||
|
## Modules enabled in all ejabberd virtual hosts.
|
||||||
|
##
|
||||||
|
modules:
|
||||||
|
mod_adhoc: {}
|
||||||
|
## mod_admin_extra: {}
|
||||||
|
mod_announce: # recommends mod_adhoc
|
||||||
|
access: announce
|
||||||
|
mod_blocking: {} # requires mod_privacy
|
||||||
|
mod_caps: {}
|
||||||
|
mod_carboncopy: {}
|
||||||
|
mod_client_state:
|
||||||
|
drop_chat_states: true
|
||||||
|
queue_presence: false
|
||||||
|
mod_configure: {} # requires mod_adhoc
|
||||||
|
mod_disco: {}
|
||||||
|
## mod_echo: {}
|
||||||
|
mod_irc: {}
|
||||||
|
mod_http_bind: {}
|
||||||
|
## mod_http_fileserver:
|
||||||
|
## docroot: "/var/www"
|
||||||
|
## accesslog: "/var/log/ejabberd/access.log"
|
||||||
|
mod_last: {}
|
||||||
|
mod_muc:
|
||||||
|
## host: "conference.@HOST@"
|
||||||
|
access: muc
|
||||||
|
access_create: muc_create
|
||||||
|
access_persistent: muc_create
|
||||||
|
access_admin: muc_admin
|
||||||
|
## mod_muc_log: {}
|
||||||
|
mod_offline:
|
||||||
|
access_max_user_messages: max_user_offline_messages
|
||||||
|
mod_ping: {}
|
||||||
|
## mod_pres_counter:
|
||||||
|
## count: 5
|
||||||
|
## interval: 60
|
||||||
|
mod_privacy: {}
|
||||||
|
mod_private: {}
|
||||||
|
## mod_proxy65: {}
|
||||||
|
mod_pubsub:
|
||||||
|
access_createnode: pubsub_createnode
|
||||||
|
## reduces resource comsumption, but XEP incompliant
|
||||||
|
ignore_pep_from_offline: true
|
||||||
|
## XEP compliant, but increases resource comsumption
|
||||||
|
## ignore_pep_from_offline: false
|
||||||
|
last_item_cache: false
|
||||||
|
plugins:
|
||||||
|
- "flat"
|
||||||
|
- "hometree"
|
||||||
|
- "pep" # pep requires mod_caps
|
||||||
|
mod_register:
|
||||||
|
##
|
||||||
|
## Protect In-Band account registrations with CAPTCHA.
|
||||||
|
##
|
||||||
|
## captcha_protected: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## Set the minimum informational entropy for passwords.
|
||||||
|
##
|
||||||
|
## password_strength: 32
|
||||||
|
|
||||||
|
##
|
||||||
|
## After successful registration, the user receives
|
||||||
|
## a message with this subject and body.
|
||||||
|
##
|
||||||
|
welcome_message:
|
||||||
|
subject: "Welcome!"
|
||||||
|
body: |-
|
||||||
|
Hi.
|
||||||
|
Welcome to this XMPP server.
|
||||||
|
|
||||||
|
##
|
||||||
|
## When a user registers, send a notification to
|
||||||
|
## these XMPP accounts.
|
||||||
|
##
|
||||||
|
## registration_watchers:
|
||||||
|
## - "admin1@example.org"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Only clients in the server machine can register accounts
|
||||||
|
##
|
||||||
|
ip_access: trusted_network
|
||||||
|
|
||||||
|
##
|
||||||
|
## Local c2s or remote s2s users cannot register accounts
|
||||||
|
##
|
||||||
|
## access_from: deny
|
||||||
|
|
||||||
|
access: register
|
||||||
|
mod_roster: {}
|
||||||
|
mod_shared_roster: {}
|
||||||
|
mod_stats: {}
|
||||||
|
mod_time: {}
|
||||||
|
mod_vcard: {}
|
||||||
|
mod_version: {}
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable modules with custom options in a specific virtual host
|
||||||
|
##
|
||||||
|
## host_config:
|
||||||
|
## "localhost":
|
||||||
|
## modules:
|
||||||
|
## mod_echo:
|
||||||
|
## host: "mirror.localhost"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable modules management via ejabberdctl for installation and
|
||||||
|
## uninstallation of public/private contributed modules
|
||||||
|
## (enabled by default)
|
||||||
|
##
|
||||||
|
|
||||||
|
allow_contrib_modules: true
|
||||||
|
|
||||||
|
### Local Variables:
|
||||||
|
### mode: yaml
|
||||||
|
### End:
|
||||||
|
### vim: set filetype=yaml tabstop=8
|
119
lib/ejabberd/config/attr.ex
Normal file
119
lib/ejabberd/config/attr.ex
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
defmodule Ejabberd.Config.Attr do
|
||||||
|
@moduledoc """
|
||||||
|
Module used to work with the attributes parsed from
|
||||||
|
an elixir block (do...end).
|
||||||
|
|
||||||
|
Contains functions for extracting attrs from a block
|
||||||
|
and validation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@type attr :: {atom(), any()}
|
||||||
|
|
||||||
|
@attr_supported [
|
||||||
|
active:
|
||||||
|
[type: :boolean, default: true],
|
||||||
|
git:
|
||||||
|
[type: :string, default: ""],
|
||||||
|
name:
|
||||||
|
[type: :string, default: ""],
|
||||||
|
opts:
|
||||||
|
[type: :list, default: []],
|
||||||
|
dependency:
|
||||||
|
[type: :list, default: []]
|
||||||
|
]
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Takes a block with annotations and extracts the list
|
||||||
|
of attributes.
|
||||||
|
"""
|
||||||
|
@spec extract_attrs_from_block_with_defaults(any()) :: [attr]
|
||||||
|
def extract_attrs_from_block_with_defaults(block) do
|
||||||
|
block
|
||||||
|
|> extract_attrs_from_block
|
||||||
|
|> put_into_list_if_not_already
|
||||||
|
|> insert_default_attrs_if_missing
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Takes an attribute or a list of attrs and validate them.
|
||||||
|
|
||||||
|
Returns a {:ok, attr} or {:error, attr, cause} for each of the attributes.
|
||||||
|
"""
|
||||||
|
@spec validate([attr]) :: [{:ok, attr}] | [{:error, attr, atom()}]
|
||||||
|
def validate(attrs) when is_list(attrs), do: Enum.map(attrs, &valid_attr?/1)
|
||||||
|
def validate(attr), do: validate([attr]) |> List.first
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the type of an attribute, given its name.
|
||||||
|
"""
|
||||||
|
@spec get_type_for_attr(atom()) :: atom()
|
||||||
|
def get_type_for_attr(attr_name) do
|
||||||
|
@attr_supported
|
||||||
|
|> Keyword.get(attr_name)
|
||||||
|
|> Keyword.get(:type)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the default value for an attribute, given its name.
|
||||||
|
"""
|
||||||
|
@spec get_default_for_attr(atom()) :: any()
|
||||||
|
def get_default_for_attr(attr_name) do
|
||||||
|
@attr_supported
|
||||||
|
|> Keyword.get(attr_name)
|
||||||
|
|> Keyword.get(:default)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Private API
|
||||||
|
|
||||||
|
# Given an elixir block (do...end) returns a list with the annotations
|
||||||
|
# or a single annotation.
|
||||||
|
@spec extract_attrs_from_block(any()) :: [attr] | attr
|
||||||
|
defp extract_attrs_from_block({:__block__, [], attrs}), do: Enum.map(attrs, &extract_attrs_from_block/1)
|
||||||
|
defp extract_attrs_from_block({:@, _, [attrs]}), do: extract_attrs_from_block(attrs)
|
||||||
|
defp extract_attrs_from_block({attr_name, _, [value]}), do: {attr_name, value}
|
||||||
|
defp extract_attrs_from_block(nil), do: []
|
||||||
|
|
||||||
|
# In case extract_attrs_from_block returns a single attribute,
|
||||||
|
# then put it into a list. (Ensures attrs are always into a list).
|
||||||
|
@spec put_into_list_if_not_already([attr] | attr) :: [attr]
|
||||||
|
defp put_into_list_if_not_already(attrs) when is_list(attrs), do: attrs
|
||||||
|
defp put_into_list_if_not_already(attr), do: [attr]
|
||||||
|
|
||||||
|
# Given a list of attributes, it inserts the missing attribute with their
|
||||||
|
# default value.
|
||||||
|
@spec insert_default_attrs_if_missing([attr]) :: [attr]
|
||||||
|
defp insert_default_attrs_if_missing(attrs) do
|
||||||
|
Enum.reduce @attr_supported, attrs, fn({attr_name, _}, acc) ->
|
||||||
|
case Keyword.has_key?(acc, attr_name) do
|
||||||
|
true -> acc
|
||||||
|
false -> Keyword.put(acc, attr_name, get_default_for_attr(attr_name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Given an attribute, validates it and return a tuple with
|
||||||
|
# {:ok, attr} or {:error, attr, cause}
|
||||||
|
@spec valid_attr?(attr) :: {:ok, attr} | {:error, attr, atom()}
|
||||||
|
defp valid_attr?({attr_name, param} = attr) do
|
||||||
|
case Keyword.get(@attr_supported, attr_name) do
|
||||||
|
nil -> {:error, attr, :attr_not_supported}
|
||||||
|
[{:type, param_type} | _] -> case is_of_type?(param, param_type) do
|
||||||
|
true -> {:ok, attr}
|
||||||
|
false -> {:error, attr, :type_not_supported}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Given an attribute value and a type, it returns a true
|
||||||
|
# if the value its of the type specified, false otherwise.
|
||||||
|
|
||||||
|
# Usefoul for checking if an attr value respects the type
|
||||||
|
# specified for the annotation.
|
||||||
|
@spec is_of_type?(any(), atom()) :: boolean()
|
||||||
|
defp is_of_type?(param, type) when type == :boolean and is_boolean(param), do: true
|
||||||
|
defp is_of_type?(param, type) when type == :string and is_bitstring(param), do: true
|
||||||
|
defp is_of_type?(param, type) when type == :list and is_list(param), do: true
|
||||||
|
defp is_of_type?(param, type) when type == :atom and is_atom(param), do: true
|
||||||
|
defp is_of_type?(_param, type) when type == :any, do: true
|
||||||
|
defp is_of_type?(_, _), do: false
|
||||||
|
end
|
145
lib/ejabberd/config/config.ex
Normal file
145
lib/ejabberd/config/config.ex
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
defmodule Ejabberd.Config do
|
||||||
|
@moduledoc """
|
||||||
|
Base module for configuration file.
|
||||||
|
|
||||||
|
Imports macros for the config DSL and contains functions
|
||||||
|
for working/starting the configuration parsed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Ejabberd.Config.EjabberdModule
|
||||||
|
alias Ejabberd.Config.Attr
|
||||||
|
alias Ejabberd.Config.EjabberdLogger
|
||||||
|
|
||||||
|
defmacro __using__(_opts) do
|
||||||
|
quote do
|
||||||
|
import Ejabberd.Config, only: :macros
|
||||||
|
import Ejabberd.Logger
|
||||||
|
|
||||||
|
@before_compile Ejabberd.Config
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate the modules parsed and log validation errors at compile time.
|
||||||
|
# Could be also possible to interrupt the compilation&execution by throwing
|
||||||
|
# an exception if necessary.
|
||||||
|
def __before_compile__(_env) do
|
||||||
|
get_modules_parsed_in_order
|
||||||
|
|> EjabberdModule.validate
|
||||||
|
|> EjabberdLogger.log_errors
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Given the path of the config file, it evaluates it.
|
||||||
|
"""
|
||||||
|
def init(file_path, force \\ false) do
|
||||||
|
init_already_executed = Ejabberd.Config.Store.get(:module_name) != []
|
||||||
|
|
||||||
|
case force do
|
||||||
|
true ->
|
||||||
|
Ejabberd.Config.Store.stop
|
||||||
|
Ejabberd.Config.Store.start_link
|
||||||
|
do_init(file_path)
|
||||||
|
false ->
|
||||||
|
if not init_already_executed, do: do_init(file_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns a list with all the opts, formatted for ejabberd.
|
||||||
|
"""
|
||||||
|
def get_ejabberd_opts do
|
||||||
|
get_general_opts
|
||||||
|
|> Dict.put(:modules, get_modules_parsed_in_order())
|
||||||
|
|> Dict.put(:listeners, get_listeners_parsed_in_order())
|
||||||
|
|> Ejabberd.Config.OptsFormatter.format_opts_for_ejabberd
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Register the hooks defined inside the elixir config file.
|
||||||
|
"""
|
||||||
|
def start_hooks do
|
||||||
|
get_hooks_parsed_in_order()
|
||||||
|
|> Enum.each(&Ejabberd.Config.EjabberdHook.start/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
###
|
||||||
|
### MACROS
|
||||||
|
###
|
||||||
|
|
||||||
|
defmacro listen(module, do: block) do
|
||||||
|
attrs = Attr.extract_attrs_from_block_with_defaults(block)
|
||||||
|
|
||||||
|
quote do
|
||||||
|
Ejabberd.Config.Store.put(:listeners, %EjabberdModule{
|
||||||
|
module: unquote(module),
|
||||||
|
attrs: unquote(attrs)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defmacro module(module, do: block) do
|
||||||
|
attrs = Attr.extract_attrs_from_block_with_defaults(block)
|
||||||
|
|
||||||
|
quote do
|
||||||
|
Ejabberd.Config.Store.put(:modules, %EjabberdModule{
|
||||||
|
module: unquote(module),
|
||||||
|
attrs: unquote(attrs)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defmacro hook(hook_name, opts, fun) do
|
||||||
|
quote do
|
||||||
|
Ejabberd.Config.Store.put(:hooks, %Ejabberd.Config.EjabberdHook{
|
||||||
|
hook: unquote(hook_name),
|
||||||
|
opts: unquote(opts),
|
||||||
|
fun: unquote(fun)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Private API
|
||||||
|
|
||||||
|
defp do_init(file_path) do
|
||||||
|
# File evaluation
|
||||||
|
Code.eval_file(file_path) |> extract_and_store_module_name()
|
||||||
|
|
||||||
|
# Getting start/0 config
|
||||||
|
Ejabberd.Config.Store.get(:module_name)
|
||||||
|
|> case do
|
||||||
|
nil -> IO.puts "[ ERR ] Configuration module not found."
|
||||||
|
[module] -> call_start_func_and_store_data(module)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fetching git modules and install them
|
||||||
|
get_modules_parsed_in_order()
|
||||||
|
|> EjabberdModule.fetch_git_repos
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the modules from the store
|
||||||
|
defp get_modules_parsed_in_order,
|
||||||
|
do: Ejabberd.Config.Store.get(:modules) |> Enum.reverse
|
||||||
|
|
||||||
|
# Returns the listeners from the store
|
||||||
|
defp get_listeners_parsed_in_order,
|
||||||
|
do: Ejabberd.Config.Store.get(:listeners) |> Enum.reverse
|
||||||
|
|
||||||
|
defp get_hooks_parsed_in_order,
|
||||||
|
do: Ejabberd.Config.Store.get(:hooks) |> Enum.reverse
|
||||||
|
|
||||||
|
# Returns the general config options
|
||||||
|
defp get_general_opts,
|
||||||
|
do: Ejabberd.Config.Store.get(:general) |> List.first
|
||||||
|
|
||||||
|
# Gets the general ejabberd options calling
|
||||||
|
# the start/0 function and stores them.
|
||||||
|
defp call_start_func_and_store_data(module) do
|
||||||
|
opts = apply(module, :start, [])
|
||||||
|
Ejabberd.Config.Store.put(:general, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Stores the configuration module name
|
||||||
|
defp extract_and_store_module_name({{:module, mod, _bytes, _}, _}) do
|
||||||
|
Ejabberd.Config.Store.put(:module_name, mod)
|
||||||
|
end
|
||||||
|
end
|
23
lib/ejabberd/config/ejabberd_hook.ex
Normal file
23
lib/ejabberd/config/ejabberd_hook.ex
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
defmodule Ejabberd.Config.EjabberdHook do
|
||||||
|
@moduledoc """
|
||||||
|
Module containing functions for manipulating
|
||||||
|
ejabberd hooks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
defstruct hook: nil, opts: [], fun: nil
|
||||||
|
|
||||||
|
alias Ejabberd.Config.EjabberdHook
|
||||||
|
|
||||||
|
@type t :: %EjabberdHook{}
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Register a hook to ejabberd.
|
||||||
|
"""
|
||||||
|
@spec start(EjabberdHook.t) :: none
|
||||||
|
def start(%EjabberdHook{hook: hook, opts: opts, fun: fun}) do
|
||||||
|
host = Keyword.get(opts, :host, :global)
|
||||||
|
priority = Keyword.get(opts, :priority, 50)
|
||||||
|
|
||||||
|
:ejabberd_hooks.add(hook, host, fun, priority)
|
||||||
|
end
|
||||||
|
end
|
70
lib/ejabberd/config/ejabberd_module.ex
Normal file
70
lib/ejabberd/config/ejabberd_module.ex
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
defmodule Ejabberd.Config.EjabberdModule do
|
||||||
|
@moduledoc """
|
||||||
|
Module representing a module block in the configuration file.
|
||||||
|
It offers functions for validation and for starting the modules.
|
||||||
|
|
||||||
|
Warning: The name is EjabberdModule to not collide with
|
||||||
|
the already existing Elixir.Module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@type t :: %{module: atom, attrs: [Attr.t]}
|
||||||
|
|
||||||
|
defstruct [:module, :attrs]
|
||||||
|
|
||||||
|
alias Ejabberd.Config.EjabberdModule
|
||||||
|
alias Ejabberd.Config.Attr
|
||||||
|
alias Ejabberd.Config.Validation
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Given a list of modules / single module
|
||||||
|
it runs different validators on them.
|
||||||
|
|
||||||
|
For each module, returns a {:ok, mod} or {:error, mod, errors}
|
||||||
|
"""
|
||||||
|
def validate(modules) do
|
||||||
|
Validation.validate(modules)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Given a list of modules, it takes only the ones with
|
||||||
|
a git attribute and tries to fetch the repo,
|
||||||
|
then, it install them through :ext_mod.install/1
|
||||||
|
"""
|
||||||
|
@spec fetch_git_repos([EjabberdModule.t]) :: none()
|
||||||
|
def fetch_git_repos(modules) do
|
||||||
|
modules
|
||||||
|
|> Enum.filter(&is_git_module?/1)
|
||||||
|
|> Enum.each(&fetch_and_install_git_module/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Private API
|
||||||
|
|
||||||
|
defp is_git_module?(%EjabberdModule{attrs: attrs}) do
|
||||||
|
case Keyword.get(attrs, :git) do
|
||||||
|
"" -> false
|
||||||
|
repo -> String.match?(repo, ~r/((git|ssh|http(s)?)|(git@[\w\.]+))(:(\/\/)?)([\w\.@\:\/\-~]+)(\.git)(\/)?/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_and_install_git_module(%EjabberdModule{attrs: attrs}) do
|
||||||
|
repo = Keyword.get(attrs, :git)
|
||||||
|
mod_name = case Keyword.get(attrs, :name) do
|
||||||
|
"" -> infer_mod_name_from_git_url(repo)
|
||||||
|
name -> name
|
||||||
|
end
|
||||||
|
|
||||||
|
path = "#{:ext_mod.modules_dir()}/sources/ejabberd-contrib\/#{mod_name}"
|
||||||
|
fetch_and_store_repo_source_if_not_exists(path, repo)
|
||||||
|
:ext_mod.install(mod_name) # Have to check if overwrites an already present mod
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_and_store_repo_source_if_not_exists(path, repo) do
|
||||||
|
unless File.exists?(path) do
|
||||||
|
IO.puts "[info] Fetching: #{repo}"
|
||||||
|
:os.cmd('git clone #{repo} #{path}')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp infer_mod_name_from_git_url(repo),
|
||||||
|
do: String.split(repo, "/") |> List.last |> String.replace(".git", "")
|
||||||
|
end
|
32
lib/ejabberd/config/logger/ejabberd_logger.ex
Normal file
32
lib/ejabberd/config/logger/ejabberd_logger.ex
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
defmodule Ejabberd.Config.EjabberdLogger do
|
||||||
|
@moduledoc """
|
||||||
|
Module used to log validation errors given validated modules
|
||||||
|
given validated modules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Ejabberd.Config.EjabberdModule
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Given a list of modules validated, in the form of {:ok, mod} or
|
||||||
|
{:error, mod, errors}, it logs to the user the errors found.
|
||||||
|
"""
|
||||||
|
@spec log_errors([EjabberdModule.t]) :: [EjabberdModule.t]
|
||||||
|
def log_errors(modules_validated) when is_list(modules_validated) do
|
||||||
|
Enum.each modules_validated, &do_log_errors/1
|
||||||
|
modules_validated
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_log_errors({:ok, _mod}), do: nil
|
||||||
|
defp do_log_errors({:error, _mod, errors}), do: Enum.each errors, &do_log_errors/1
|
||||||
|
defp do_log_errors({:attribute, errors}), do: Enum.each errors, &log_attribute_error/1
|
||||||
|
defp do_log_errors({:dependency, errors}), do: Enum.each errors, &log_dependency_error/1
|
||||||
|
|
||||||
|
defp log_attribute_error({{attr_name, val}, :attr_not_supported}), do:
|
||||||
|
IO.puts "[ WARN ] Annotation @#{attr_name} is not supported."
|
||||||
|
|
||||||
|
defp log_attribute_error({{attr_name, val}, :type_not_supported}), do:
|
||||||
|
IO.puts "[ WARN ] Annotation @#{attr_name} with value #{inspect val} is not supported (type mismatch)."
|
||||||
|
|
||||||
|
defp log_dependency_error({module, :not_found}), do:
|
||||||
|
IO.puts "[ WARN ] Module #{inspect module} was not found, but is required as a dependency."
|
||||||
|
end
|
46
lib/ejabberd/config/opts_formatter.ex
Normal file
46
lib/ejabberd/config/opts_formatter.ex
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
defmodule Ejabberd.Config.OptsFormatter do
|
||||||
|
@moduledoc """
|
||||||
|
Module for formatting options parsed into the format
|
||||||
|
ejabberd uses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Ejabberd.Config.EjabberdModule
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Takes a keyword list with keys corresponding to
|
||||||
|
the keys requested by the ejabberd config (ex: modules: mods)
|
||||||
|
and formats them to be correctly evaluated by ejabberd.
|
||||||
|
|
||||||
|
Look at how Config.get_ejabberd_opts/0 is constructed for
|
||||||
|
more informations.
|
||||||
|
"""
|
||||||
|
@spec format_opts_for_ejabberd([{atom(), any()}]) :: list()
|
||||||
|
def format_opts_for_ejabberd(opts) do
|
||||||
|
opts
|
||||||
|
|> format_attrs_for_ejabberd
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_attrs_for_ejabberd(opts) when is_list(opts),
|
||||||
|
do: Enum.map opts, &format_attrs_for_ejabberd/1
|
||||||
|
|
||||||
|
defp format_attrs_for_ejabberd({:listeners, mods}),
|
||||||
|
do: {:listen, format_listeners_for_ejabberd(mods)}
|
||||||
|
|
||||||
|
defp format_attrs_for_ejabberd({:modules, mods}),
|
||||||
|
do: {:modules, format_mods_for_ejabberd(mods)}
|
||||||
|
|
||||||
|
defp format_attrs_for_ejabberd({key, opts}) when is_atom(key),
|
||||||
|
do: {key, opts}
|
||||||
|
|
||||||
|
defp format_mods_for_ejabberd(mods) do
|
||||||
|
Enum.map mods, fn %EjabberdModule{module: mod, attrs: attrs} ->
|
||||||
|
{mod, attrs[:opts]}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_listeners_for_ejabberd(mods) do
|
||||||
|
Enum.map mods, fn %EjabberdModule{module: mod, attrs: attrs} ->
|
||||||
|
Keyword.put(attrs[:opts], :module, mod)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
55
lib/ejabberd/config/store.ex
Normal file
55
lib/ejabberd/config/store.ex
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
defmodule Ejabberd.Config.Store do
|
||||||
|
@moduledoc """
|
||||||
|
Module used for storing the modules parsed from
|
||||||
|
the configuration file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
- Store.put(:modules, mod1)
|
||||||
|
- Store.put(:modules, mod2)
|
||||||
|
|
||||||
|
- Store.get(:modules) :: [mod1, mod2]
|
||||||
|
|
||||||
|
Be carefoul: when retrieving data you get them
|
||||||
|
in the order inserted into the store, which normally
|
||||||
|
is the reversed order of how the modules are specified
|
||||||
|
inside the configuration file. To resolve this just use
|
||||||
|
a Enum.reverse/1.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@name __MODULE__
|
||||||
|
|
||||||
|
def start_link do
|
||||||
|
Agent.start_link(fn -> %{} end, name: @name)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Stores a value based on the key. If the key already exists,
|
||||||
|
then it inserts the new element, maintaining all the others.
|
||||||
|
It uses a list for this.
|
||||||
|
"""
|
||||||
|
@spec put(atom, any) :: :ok
|
||||||
|
def put(key, val) do
|
||||||
|
Agent.update @name, &Map.update(&1, key, [val], fn coll ->
|
||||||
|
[val | coll]
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a value based on the key passed.
|
||||||
|
Returns always a list.
|
||||||
|
"""
|
||||||
|
@spec get(atom) :: [any]
|
||||||
|
def get(key) do
|
||||||
|
Agent.get @name, &Map.get(&1, key, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Stops the store.
|
||||||
|
It uses Agent.stop underneath, so be aware that exit
|
||||||
|
could be called.
|
||||||
|
"""
|
||||||
|
@spec stop() :: :ok
|
||||||
|
def stop do
|
||||||
|
Agent.stop @name
|
||||||
|
end
|
||||||
|
end
|
40
lib/ejabberd/config/validator/validation.ex
Normal file
40
lib/ejabberd/config/validator/validation.ex
Normal file
@ -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
|
28
lib/ejabberd/config/validator/validator_attrs.ex
Normal file
28
lib/ejabberd/config/validator/validator_attrs.ex
Normal file
@ -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
|
30
lib/ejabberd/config/validator/validator_dependencies.ex
Normal file
30
lib/ejabberd/config/validator/validator_dependencies.ex
Normal file
@ -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
|
30
lib/ejabberd/config/validator/validator_utility.ex
Normal file
30
lib/ejabberd/config/validator/validator_utility.ex
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
defmodule Ejabberd.Config.ValidatorUtility do
|
||||||
|
@moduledoc """
|
||||||
|
Module used as a base validator for validation modules.
|
||||||
|
Imports utility functions for working with validation structures.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Ejabberd.Config.EjabberdModule
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Inserts an error inside the errors collection, for the given key.
|
||||||
|
If the key doesn't exists then it creates an empty collection
|
||||||
|
and inserts the value passed.
|
||||||
|
"""
|
||||||
|
@spec put_error(map, atom, any) :: map
|
||||||
|
def put_error(errors, key, val) do
|
||||||
|
Map.update errors, key, [val], fn coll ->
|
||||||
|
[val | coll]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Given a list of modules it extracts and returns a list
|
||||||
|
of the module names (which are Elixir.Module).
|
||||||
|
"""
|
||||||
|
@spec extract_module_names(EjabberdModule.t) :: [atom]
|
||||||
|
def extract_module_names(modules) when is_list(modules) do
|
||||||
|
modules
|
||||||
|
|> Enum.map(&Map.get(&1, :module))
|
||||||
|
end
|
||||||
|
end
|
18
lib/ejabberd/config_util.ex
Normal file
18
lib/ejabberd/config_util.ex
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
defmodule Ejabberd.ConfigUtil do
|
||||||
|
@moduledoc """
|
||||||
|
Module containing utility functions for
|
||||||
|
the config file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns true when the config file is based on elixir.
|
||||||
|
"""
|
||||||
|
@spec is_elixir_config(list) :: boolean
|
||||||
|
def is_elixir_config(filename) when is_list(filename) do
|
||||||
|
is_elixir_config(to_string(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_elixir_config(filename) do
|
||||||
|
String.ends_with?(filename, "exs")
|
||||||
|
end
|
||||||
|
end
|
19
lib/ejabberd/module.ex
Normal file
19
lib/ejabberd/module.ex
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
defmodule Ejabberd.Module do
|
||||||
|
|
||||||
|
defmacro __using__(opts) do
|
||||||
|
logger_enabled = Keyword.get(opts, :logger, true)
|
||||||
|
|
||||||
|
quote do
|
||||||
|
@behaviour :gen_mod
|
||||||
|
import Ejabberd.Module
|
||||||
|
|
||||||
|
unquote(if logger_enabled do
|
||||||
|
quote do: import Ejabberd.Logger
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# gen_mod callbacks
|
||||||
|
def depends(_host, _opts), do: []
|
||||||
|
def mod_opt_type(_), do: []
|
||||||
|
end
|
94
lib/mix/tasks/deps.tree.ex
Normal file
94
lib/mix/tasks/deps.tree.ex
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
defmodule Mix.Tasks.Ejabberd.Deps.Tree do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
alias Ejabberd.Config.EjabberdModule
|
||||||
|
|
||||||
|
@shortdoc "Lists all ejabberd modules and their dependencies"
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Lists all ejabberd modules and their dependencies.
|
||||||
|
|
||||||
|
The project must have ejabberd as a dependency.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(_argv) do
|
||||||
|
# First we need to start manually the store to be available
|
||||||
|
# during the compilation of the config file.
|
||||||
|
Ejabberd.Config.Store.start_link
|
||||||
|
Ejabberd.Config.init(:ejabberd_config.get_ejabberd_config_path())
|
||||||
|
|
||||||
|
Mix.shell.info "ejabberd modules"
|
||||||
|
|
||||||
|
Ejabberd.Config.Store.get(:modules)
|
||||||
|
|> Enum.reverse # Because of how mods are stored inside the store
|
||||||
|
|> format_mods
|
||||||
|
|> Mix.shell.info
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_mods(mods) when is_list(mods) do
|
||||||
|
deps_tree = build_dependency_tree(mods)
|
||||||
|
mods_used_as_dependency = get_mods_used_as_dependency(deps_tree)
|
||||||
|
|
||||||
|
keep_only_mods_not_used_as_dep(deps_tree, mods_used_as_dependency)
|
||||||
|
|> format_mods_into_string
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_dependency_tree(mods) do
|
||||||
|
Enum.map mods, fn %EjabberdModule{module: mod, attrs: attrs} ->
|
||||||
|
deps = attrs[:dependency]
|
||||||
|
build_dependency_tree(mods, mod, deps)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_dependency_tree(mods, mod, []), do: %{module: mod, dependency: []}
|
||||||
|
defp build_dependency_tree(mods, mod, deps) when is_list(deps) do
|
||||||
|
dependencies = Enum.map deps, fn dep ->
|
||||||
|
dep_deps = get_dependencies_of_mod(mods, dep)
|
||||||
|
build_dependency_tree(mods, dep, dep_deps)
|
||||||
|
end
|
||||||
|
|
||||||
|
%{module: mod, dependency: dependencies}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_mods_used_as_dependency(mods) when is_list(mods) do
|
||||||
|
Enum.reduce mods, [], fn(mod, acc) ->
|
||||||
|
case mod do
|
||||||
|
%{dependency: []} -> acc
|
||||||
|
%{dependency: deps} -> get_mod_names(deps) ++ acc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_mod_names([]), do: []
|
||||||
|
defp get_mod_names(mods) when is_list(mods), do: Enum.map(mods, &get_mod_names/1) |> List.flatten
|
||||||
|
defp get_mod_names(%{module: mod, dependency: deps}), do: [mod | get_mod_names(deps)]
|
||||||
|
|
||||||
|
defp keep_only_mods_not_used_as_dep(mods, mods_used_as_dep) do
|
||||||
|
Enum.filter mods, fn %{module: mod} ->
|
||||||
|
not mod in mods_used_as_dep
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_dependencies_of_mod(deps, mod_name) do
|
||||||
|
Enum.find(deps, &(Map.get(&1, :module) == mod_name))
|
||||||
|
|> Map.get(:attrs)
|
||||||
|
|> Keyword.get(:dependency)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_mods_into_string(mods), do: format_mods_into_string(mods, 0)
|
||||||
|
defp format_mods_into_string([], _indentation), do: ""
|
||||||
|
defp format_mods_into_string(mods, indentation) when is_list(mods) do
|
||||||
|
Enum.reduce mods, "", fn(mod, acc) ->
|
||||||
|
acc <> format_mods_into_string(mod, indentation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_mods_into_string(%{module: mod, dependency: deps}, 0) do
|
||||||
|
"\n├── #{mod}" <> format_mods_into_string(deps, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_mods_into_string(%{module: mod, dependency: deps}, indentation) do
|
||||||
|
spaces = Enum.reduce 0..indentation, "", fn(_, acc) -> " " <> acc end
|
||||||
|
"\n│#{spaces}└── #{mod}" <> format_mods_into_string(deps, indentation + 4)
|
||||||
|
end
|
||||||
|
end
|
@ -1,16 +1,15 @@
|
|||||||
defmodule ModPresenceDemo do
|
defmodule ModPresenceDemo do
|
||||||
import Ejabberd.Logger # this allow using info, error, etc for logging
|
use Ejabberd.Module
|
||||||
@behaviour :gen_mod
|
|
||||||
|
|
||||||
def start(host, _opts) do
|
def start(host, _opts) do
|
||||||
info('Starting ejabberd module Presence Demo')
|
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
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def stop(host) do
|
def stop(host) do
|
||||||
info('Stopping ejabberd module Presence Demo')
|
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
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -18,9 +17,4 @@ defmodule ModPresenceDemo do
|
|||||||
info('Receive presence for #{user}')
|
info('Receive presence for #{user}')
|
||||||
:none
|
:none
|
||||||
end
|
end
|
||||||
|
|
||||||
# gen_mod callbacks
|
|
||||||
def depends(_host, _opts), do: []
|
|
||||||
def mod_opt_type(_), do: []
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -56,6 +56,7 @@ start(normal, _Args) ->
|
|||||||
ejabberd_admin:start(),
|
ejabberd_admin:start(),
|
||||||
gen_mod:start(),
|
gen_mod:start(),
|
||||||
ext_mod:start(),
|
ext_mod:start(),
|
||||||
|
setup_if_elixir_conf_used(),
|
||||||
ejabberd_config:start(),
|
ejabberd_config:start(),
|
||||||
set_settings_from_config(),
|
set_settings_from_config(),
|
||||||
acl:start(),
|
acl:start(),
|
||||||
@ -76,6 +77,7 @@ start(normal, _Args) ->
|
|||||||
gen_mod:start_modules(),
|
gen_mod:start_modules(),
|
||||||
ejabberd_listener:start_listeners(),
|
ejabberd_listener:start_listeners(),
|
||||||
ejabberd_service:start(),
|
ejabberd_service:start(),
|
||||||
|
register_elixir_config_hooks(),
|
||||||
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
||||||
Sup;
|
Sup;
|
||||||
start(_, _) ->
|
start(_, _) ->
|
||||||
@ -240,6 +242,18 @@ opt_type(modules) ->
|
|||||||
end;
|
end;
|
||||||
opt_type(_) -> [cluster_nodes, loglevel, modules, net_ticktime].
|
opt_type(_) -> [cluster_nodes, loglevel, modules, net_ticktime].
|
||||||
|
|
||||||
|
setup_if_elixir_conf_used() ->
|
||||||
|
case ejabberd_config:is_using_elixir_config() of
|
||||||
|
true -> 'Elixir.Ejabberd.Config.Store':start_link();
|
||||||
|
false -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
register_elixir_config_hooks() ->
|
||||||
|
case ejabberd_config:is_using_elixir_config() of
|
||||||
|
true -> 'Elixir.Ejabberd.Config':start_hooks();
|
||||||
|
false -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
start_elixir_application() ->
|
start_elixir_application() ->
|
||||||
case application:ensure_started(elixir) of
|
case application:ensure_started(elixir) of
|
||||||
ok -> ok;
|
ok -> ok;
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
get_option/2, get_option/3, add_option/2, has_option/1,
|
get_option/2, get_option/3, add_option/2, has_option/1,
|
||||||
get_vh_by_auth_method/1, is_file_readable/1,
|
get_vh_by_auth_method/1, is_file_readable/1,
|
||||||
get_version/0, get_myhosts/0, get_mylang/0,
|
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,
|
prepare_opt_val/4, convert_table_to_binary/5,
|
||||||
transform_options/1, collect_options/1, default_db/2,
|
transform_options/1, collect_options/1, default_db/2,
|
||||||
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
|
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
|
||||||
@ -147,7 +148,13 @@ read_file(File) ->
|
|||||||
{include_modules_configs, true}]).
|
{include_modules_configs, true}]).
|
||||||
|
|
||||||
read_file(File, Opts) ->
|
read_file(File, Opts) ->
|
||||||
Terms1 = get_plain_terms_file(File, Opts),
|
Terms1 = 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,
|
||||||
Terms_macros = case proplists:get_bool(replace_macros, Opts) of
|
Terms_macros = case proplists:get_bool(replace_macros, Opts) of
|
||||||
true -> replace_macros(Terms1);
|
true -> replace_macros(Terms1);
|
||||||
false -> Terms1
|
false -> Terms1
|
||||||
@ -1042,6 +1049,10 @@ replace_modules(Modules) ->
|
|||||||
%% Elixir module naming
|
%% Elixir module naming
|
||||||
%% ====================
|
%% ====================
|
||||||
|
|
||||||
|
is_using_elixir_config() ->
|
||||||
|
Config = get_ejabberd_config_path(),
|
||||||
|
'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config).
|
||||||
|
|
||||||
%% If module name start with uppercase letter, this is an Elixir module:
|
%% If module name start with uppercase letter, this is an Elixir module:
|
||||||
is_elixir_module(Module) ->
|
is_elixir_module(Module) ->
|
||||||
case atom_to_list(Module) of
|
case atom_to_list(Module) of
|
||||||
|
@ -145,9 +145,14 @@ init({SockMod, Socket}, Opts) ->
|
|||||||
DefinedHandlers = gen_mod:get_opt(
|
DefinedHandlers = gen_mod:get_opt(
|
||||||
request_handlers, Opts,
|
request_handlers, Opts,
|
||||||
fun(Hs) ->
|
fun(Hs) ->
|
||||||
|
Hs1 = lists:map(fun
|
||||||
|
({Mod, Path}) when is_atom(Mod) -> {Path, Mod};
|
||||||
|
({Path, Mod}) -> {Path, Mod}
|
||||||
|
end, Hs),
|
||||||
|
|
||||||
[{str:tokens(
|
[{str:tokens(
|
||||||
iolist_to_binary(Path), <<"/">>),
|
iolist_to_binary(Path), <<"/">>),
|
||||||
Mod} || {Path, Mod} <- Hs]
|
Mod} || {Path, Mod} <- Hs1]
|
||||||
end, []),
|
end, []),
|
||||||
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
||||||
Admin ++ Bind ++ XMLRPC,
|
Admin ++ Bind ++ XMLRPC,
|
||||||
|
87
test/elixir-config/attr_test.exs
Normal file
87
test/elixir-config/attr_test.exs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
defmodule Ejabberd.Config.AttrTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias Ejabberd.Config.Attr
|
||||||
|
|
||||||
|
test "extract attrs from single line block" do
|
||||||
|
block = quote do
|
||||||
|
@active false
|
||||||
|
end
|
||||||
|
|
||||||
|
block_res = Attr.extract_attrs_from_block_with_defaults(block)
|
||||||
|
assert {:active, false} in block_res
|
||||||
|
end
|
||||||
|
|
||||||
|
test "extract attrs from multi line block" do
|
||||||
|
block = quote do
|
||||||
|
@active false
|
||||||
|
@opts [http: true]
|
||||||
|
end
|
||||||
|
|
||||||
|
block_res = Attr.extract_attrs_from_block_with_defaults(block)
|
||||||
|
assert {:active, false} in block_res
|
||||||
|
assert {:opts, [http: true]} in block_res
|
||||||
|
end
|
||||||
|
|
||||||
|
test "inserts correctly defaults attr when missing in block" do
|
||||||
|
block = quote do
|
||||||
|
@active false
|
||||||
|
@opts [http: true]
|
||||||
|
end
|
||||||
|
|
||||||
|
block_res = Attr.extract_attrs_from_block_with_defaults(block)
|
||||||
|
|
||||||
|
assert {:active, false} in block_res
|
||||||
|
assert {:git, ""} in block_res
|
||||||
|
assert {:name, ""} in block_res
|
||||||
|
assert {:opts, [http: true]} in block_res
|
||||||
|
assert {:dependency, []} in block_res
|
||||||
|
end
|
||||||
|
|
||||||
|
test "inserts all defaults attr when passed an empty block" do
|
||||||
|
block = quote do
|
||||||
|
end
|
||||||
|
|
||||||
|
block_res = Attr.extract_attrs_from_block_with_defaults(block)
|
||||||
|
|
||||||
|
assert {:active, true} in block_res
|
||||||
|
assert {:git, ""} in block_res
|
||||||
|
assert {:name, ""} in block_res
|
||||||
|
assert {:opts, []} in block_res
|
||||||
|
assert {:dependency, []} in block_res
|
||||||
|
end
|
||||||
|
|
||||||
|
test "validates attrs and returns errors, if any" do
|
||||||
|
block = quote do
|
||||||
|
@not_supported_attr true
|
||||||
|
@active "false"
|
||||||
|
@opts [http: true]
|
||||||
|
end
|
||||||
|
|
||||||
|
block_res =
|
||||||
|
block
|
||||||
|
|> Attr.extract_attrs_from_block_with_defaults
|
||||||
|
|> Attr.validate
|
||||||
|
|
||||||
|
assert {:ok, {:opts, [http: true]}} in block_res
|
||||||
|
assert {:ok, {:git, ""}} in block_res
|
||||||
|
assert {:error, {:not_supported_attr, true}, :attr_not_supported} in block_res
|
||||||
|
assert {:error, {:active, "false"}, :type_not_supported} in block_res
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns the correct type for an attribute" do
|
||||||
|
assert :boolean == Attr.get_type_for_attr(:active)
|
||||||
|
assert :string == Attr.get_type_for_attr(:git)
|
||||||
|
assert :string == Attr.get_type_for_attr(:name)
|
||||||
|
assert :list == Attr.get_type_for_attr(:opts)
|
||||||
|
assert :list == Attr.get_type_for_attr(:dependency)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns the correct default for an attribute" do
|
||||||
|
assert true == Attr.get_default_for_attr(:active)
|
||||||
|
assert "" == Attr.get_default_for_attr(:git)
|
||||||
|
assert "" == Attr.get_default_for_attr(:name)
|
||||||
|
assert [] == Attr.get_default_for_attr(:opts)
|
||||||
|
assert [] == Attr.get_default_for_attr(:dependency)
|
||||||
|
end
|
||||||
|
end
|
65
test/elixir-config/config_test.exs
Normal file
65
test/elixir-config/config_test.exs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
defmodule Ejabberd.ConfigTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
alias Ejabberd.Config
|
||||||
|
alias Ejabberd.Config.Store
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
pid = Process.whereis(Ejabberd.Config.Store)
|
||||||
|
unless pid != nil and Process.alive?(pid) do
|
||||||
|
Store.start_link
|
||||||
|
|
||||||
|
File.cd("test/elixir-config/shared")
|
||||||
|
config_file_path = File.cwd! <> "/ejabberd.exs"
|
||||||
|
Config.init(config_file_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "extracts successfully the module name from config file" do
|
||||||
|
assert [Ejabberd.ConfigFile] == Store.get(:module_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "extracts successfully general opts from config file" do
|
||||||
|
[general] = Store.get(:general)
|
||||||
|
shaper = [normal: 1000, fast: 50000, max_fsm_queue: 1000]
|
||||||
|
assert [loglevel: 4, language: "en", hosts: ["localhost"], shaper: shaper] == general
|
||||||
|
end
|
||||||
|
|
||||||
|
test "extracts successfully listeners from config file" do
|
||||||
|
[listen] = Store.get(:listeners)
|
||||||
|
assert :ejabberd_c2s == listen.module
|
||||||
|
assert [port: 5222, max_stanza_size: 65536, shaper: :c2s_shaper, access: :c2s] == listen.attrs[:opts]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "extracts successfully modules from config file" do
|
||||||
|
[module] = Store.get(:modules)
|
||||||
|
assert :mod_adhoc == module.module
|
||||||
|
assert [] == module.attrs[:opts]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "extracts successfully hooks from config file" do
|
||||||
|
[register_hook] = Store.get(:hooks)
|
||||||
|
|
||||||
|
assert :register_user == register_hook.hook
|
||||||
|
assert [host: "localhost"] == register_hook.opts
|
||||||
|
assert is_function(register_hook.fun)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: When enalbed, this test causes the evaluation of a different config file, so
|
||||||
|
# the other tests, that uses the store, are compromised because the data is different.
|
||||||
|
# So, until a good way is found, this test should remain disabed.
|
||||||
|
#
|
||||||
|
# test "init/2 with force:true re-initializes the config store with new data" do
|
||||||
|
# config_file_path = File.cwd! <> "/ejabberd_different_from_default.exs"
|
||||||
|
# Config.init(config_file_path, true)
|
||||||
|
#
|
||||||
|
# assert [Ejabberd.ConfigFile] == Store.get(:module_name)
|
||||||
|
# assert [[loglevel: 4, language: "en", hosts: ["localhost"]]] == Store.get(:general)
|
||||||
|
# assert [] == Store.get(:modules)
|
||||||
|
# assert [] == Store.get(:listeners)
|
||||||
|
#
|
||||||
|
# Store.stop
|
||||||
|
# end
|
||||||
|
end
|
49
test/elixir-config/ejabberd_logger.exs
Normal file
49
test/elixir-config/ejabberd_logger.exs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
defmodule Ejabberd.Config.EjabberdLoggerTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
import ExUnit.CaptureIO
|
||||||
|
|
||||||
|
alias Ejabberd.Config
|
||||||
|
alias Ejabberd.Config.Store
|
||||||
|
alias Ejabberd.Config.Validation
|
||||||
|
alias Ejabberd.Config.EjabberdLogger
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
pid = Process.whereis(Ejabberd.Config.Store)
|
||||||
|
unless pid != nil and Process.alive?(pid) do
|
||||||
|
Store.start_link
|
||||||
|
|
||||||
|
File.cd("test/elixir-config/shared")
|
||||||
|
config_file_path = File.cwd! <> "/ejabberd_for_validation.exs"
|
||||||
|
Config.init(config_file_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "outputs correctly when attr is not supported" do
|
||||||
|
error_msg = "[ WARN ] Annotation @attr_not_supported is not supported.\n"
|
||||||
|
|
||||||
|
[_mod_irc, _mod_configure, mod_time] = Store.get(:modules)
|
||||||
|
fun = fn ->
|
||||||
|
mod_time
|
||||||
|
|> Validation.validate
|
||||||
|
|> EjabberdLogger.log_errors
|
||||||
|
end
|
||||||
|
|
||||||
|
assert capture_io(fun) == error_msg
|
||||||
|
end
|
||||||
|
|
||||||
|
test "outputs correctly when dependency is not found" do
|
||||||
|
error_msg = "[ WARN ] Module :mod_adhoc was not found, but is required as a dependency.\n"
|
||||||
|
|
||||||
|
[_mod_irc, mod_configure, _mod_time] = Store.get(:modules)
|
||||||
|
fun = fn ->
|
||||||
|
mod_configure
|
||||||
|
|> Validation.validate
|
||||||
|
|> EjabberdLogger.log_errors
|
||||||
|
end
|
||||||
|
|
||||||
|
assert capture_io(fun) == error_msg
|
||||||
|
end
|
||||||
|
end
|
31
test/elixir-config/shared/ejabberd.exs
Normal file
31
test/elixir-config/shared/ejabberd.exs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
defmodule Ejabberd.ConfigFile do
|
||||||
|
use Ejabberd.Config
|
||||||
|
|
||||||
|
def start do
|
||||||
|
[loglevel: 4,
|
||||||
|
language: "en",
|
||||||
|
hosts: ["localhost"],
|
||||||
|
shaper: shaper]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp shaper do
|
||||||
|
[normal: 1000,
|
||||||
|
fast: 50000,
|
||||||
|
max_fsm_queue: 1000]
|
||||||
|
end
|
||||||
|
|
||||||
|
listen :ejabberd_c2s do
|
||||||
|
@opts [
|
||||||
|
port: 5222,
|
||||||
|
max_stanza_size: 65536,
|
||||||
|
shaper: :c2s_shaper,
|
||||||
|
access: :c2s]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_adhoc do
|
||||||
|
end
|
||||||
|
|
||||||
|
hook :register_user, [host: "localhost"], fn(user, server) ->
|
||||||
|
info("User registered: #{user} on #{server}")
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,9 @@
|
|||||||
|
defmodule Ejabberd.ConfigFile do
|
||||||
|
use Ejabberd.Config
|
||||||
|
|
||||||
|
def start do
|
||||||
|
[loglevel: 4,
|
||||||
|
language: "en",
|
||||||
|
hosts: ["localhost"]]
|
||||||
|
end
|
||||||
|
end
|
20
test/elixir-config/shared/ejabberd_for_validation.exs
Normal file
20
test/elixir-config/shared/ejabberd_for_validation.exs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
defmodule Ejabberd.ConfigFile do
|
||||||
|
use Ejabberd.Config
|
||||||
|
|
||||||
|
def start do
|
||||||
|
[loglevel: 4,
|
||||||
|
language: "en",
|
||||||
|
hosts: ["localhost"]]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_time do
|
||||||
|
@attr_not_supported true
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_configure do
|
||||||
|
@dependency [:mod_adhoc]
|
||||||
|
end
|
||||||
|
|
||||||
|
module :mod_irc do
|
||||||
|
end
|
||||||
|
end
|
32
test/elixir-config/validation_test.exs
Normal file
32
test/elixir-config/validation_test.exs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
defmodule Ejabberd.Config.ValidationTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
alias Ejabberd.Config
|
||||||
|
alias Ejabberd.Config.Store
|
||||||
|
alias Ejabberd.Config.Validation
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
pid = Process.whereis(Ejabberd.Config.Store)
|
||||||
|
unless pid != nil and Process.alive?(pid) do
|
||||||
|
Store.start_link
|
||||||
|
|
||||||
|
File.cd("test/elixir-config/shared")
|
||||||
|
config_file_path = File.cwd! <> "/ejabberd_for_validation.exs"
|
||||||
|
Config.init(config_file_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "validates correctly the modules" do
|
||||||
|
[mod_irc, mod_configure, mod_time] = Store.get(:modules)
|
||||||
|
|
||||||
|
[{:error, _mod, errors}] = Validation.validate(mod_configure)
|
||||||
|
assert %{dependency: [mod_adhoc: :not_found]} == errors
|
||||||
|
|
||||||
|
[{:error, _mod, errors}] = Validation.validate(mod_time)
|
||||||
|
assert %{attribute: [{{:attr_not_supported, true}, :attr_not_supported}]} == errors
|
||||||
|
|
||||||
|
[{:ok, _mod}] = Validation.validate(mod_irc)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user