Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b72ed7afa4 | |||
| c109d3eff0 | |||
| 0d743da595 | |||
| ef57067edc | |||
| e054c2800b | |||
| 49f1b4a691 | |||
| 8b61c7fe4b | |||
| 9bac2fa185 | |||
| c3f62c037d | |||
| 4ddee2d89b | |||
| 054426072e | |||
| f28200b6de | |||
| 8c16400332 | |||
| 0394baaa7a | |||
| 1b3a6dd54a | |||
| 6d1ea222c0 | |||
| 08f3d066b1 | |||
| ddca2e8b4a | |||
| a2b2a27bb6 | |||
| d60d72d7bf | |||
| 7ff5f2d3fa | |||
| 1866b56e3b | |||
| e96bfbdbfa | |||
| 3cc964fbcc | |||
| 29f6c43ae3 | |||
| 03de853e4f | |||
| fb367469d4 | |||
| d2cdfa66f9 | |||
| d5c1174385 | |||
| dd888f90ec | |||
| bb9593dd12 | |||
| de385591d0 | |||
| 78dae4036e | |||
| dafea66c0f | |||
| c851f9608a | |||
| 3367c5b120 | |||
| d4579d2a20 | |||
| 410ac9b966 | |||
| adf0d7de91 | |||
| 8c03427c25 | |||
| c156eabb24 | |||
| 1d6cbd2561 | |||
| a7a1e7be94 | |||
| 8673d2926d | |||
| 2d246f61dd | |||
| 9de2ca4568 | |||
| b545301f63 | |||
| 145c0116bf | |||
| 79c511a441 | |||
| 90b22da880 | |||
| 57936bfa4e | |||
| 5c931d7004 | |||
| 88d0b71d58 | |||
| b416527e4f | |||
| 8c8c480477 | |||
| 66132353df | |||
| b30a9f2f75 | |||
| 0cbd41fbdc | |||
| 5b055d7eec | |||
| 01a1f929b4 | |||
| f0f3ec211e | |||
| fd76bc9242 | |||
| 6cd70947be | |||
| 8cefe58a89 | |||
| c3361bab95 | |||
| f3f3b1586e | |||
| 0bf93eefcb | |||
| 10e01b7bfc | |||
| 8f0e066135 | |||
| dfd96b6037 | |||
| 5b373470ac | |||
| 0146189b65 | |||
| 4c4c82897c | |||
| 5509e648ad | |||
| c9ba0e83d2 | |||
| b56c012407 | |||
| 9b48dc9cc3 | |||
| efb4fd0d10 | |||
| 83e2462853 | |||
| cdfd0cce7b | |||
| 2d45832a39 | |||
| 1af2cf37ea | |||
| ca022b6d1f | |||
| e54f1a8485 | |||
| 1be2112634 | |||
| 6dc452e7f5 | |||
| 57a3512dcc | |||
| 1de69174ef | |||
| 26b9d25f32 | |||
| 8ad6afd652 | |||
| 7fed5a3eb6 | |||
| b199b68380 | |||
| e433a63105 | |||
| 68c9328a9c | |||
| 6601f182c4 | |||
| 326db5535c | |||
| 2539be1a04 | |||
| 4e9930597d | |||
| 2dfb5a6a5c | |||
| 8faa6afa67 | |||
| 12e537c43f | |||
| 4394ec38b6 | |||
| 420e05fa0d | |||
| e2fb154fe9 | |||
| e9f219a0ac | |||
| 711c5c0d54 | |||
| f9ed34db4d | |||
| 9a895058e7 | |||
| e76a57e144 | |||
| 6fc6bdefc2 | |||
| 96e35a3248 | |||
| 48be8e7b1e | |||
| f40f3a9da7 | |||
| f81b49fe44 | |||
| 395d2e86bc | |||
| 5b3af9d4cd | |||
| 7e5d766a02 | |||
| c5dd1bdd9d | |||
| d03432a956 | |||
| 4b747c2c78 | |||
| 3a566e3cdf | |||
| b915469f5e | |||
| 8b9166d067 | |||
| dc6861eb73 | |||
| 90a4aafec0 | |||
| 4c06f13d18 | |||
| 8c796ed027 | |||
| 68d12017cc | |||
| 491993d401 | |||
| a981bf9a59 | |||
| 920e4512b6 | |||
| fbdcc44fd9 | |||
| b2b29269ec | |||
| 11811e5f48 | |||
| b7f62a4fa7 | |||
| 0bb14d16c7 | |||
| 59f5a098b5 | |||
| ed1ee6061e | |||
| 50b645aa92 | |||
| 52f2a7de4b | |||
| bce8922e5d | |||
| 86236431b9 | |||
| c0d4d31b5b | |||
| 295bec8551 | |||
| b341a3cef3 | |||
| fface33d54 | |||
| fbf6ba2738 | |||
| 38ec3f66c7 | |||
| 56dc625f9a | |||
| 7c5ee93c88 | |||
| 77163c43d2 | |||
| d1d02e2f26 | |||
| 6b8bc811ac | |||
| b662ec2a78 | |||
| 8ca035496e | |||
| a463f5a25a | |||
| dce4e4de6d | |||
| 9b70177fd5 | |||
| 1fbb36c34a | |||
| 46abf7cfab | |||
| 62cb398734 | |||
| dff940b89e | |||
| 66591b1c0d | |||
| b094ce8ea5 | |||
| 9c82c2f6d0 | |||
| 0a40ab93c8 |
+18
-12
@@ -1,15 +1,21 @@
|
||||
> What version of ejabberd are you using?
|
||||
Environment
|
||||
-----------
|
||||
- ejabberd version: 18.06
|
||||
- Erlang version: `erl +V`
|
||||
- OS: Linux (Debian)
|
||||
- Installed from: source | distro package | official deb/rpm | official binary installer | other
|
||||
|
||||
Configuration (only if needed): grep -Ev '^$|^\s*#' ejabberd.yml
|
||||
---------------------------------------------------------------------------
|
||||
```yaml
|
||||
loglevel: 4
|
||||
...
|
||||
```
|
||||
|
||||
Errors from error.log/crash.log
|
||||
-------------------------------
|
||||
No errors
|
||||
|
||||
> What operating system (version) are you using?
|
||||
|
||||
|
||||
|
||||
> How did you install ejabberd (source, package, distribution)?
|
||||
|
||||
|
||||
|
||||
> What did not work as expected? Are there error messages in the log? What
|
||||
> was the unexpected behavior? What was the expected result?
|
||||
|
||||
Bug description
|
||||
---------------
|
||||
Nothing works, plz halp :(
|
||||
|
||||
@@ -58,6 +58,9 @@ JSDIR = $(PRIVDIR)/js
|
||||
# /usr/lib/ejabberd/priv/sql
|
||||
SQLDIR = $(PRIVDIR)/sql
|
||||
|
||||
# /usr/lib/ejabberd/priv/lua
|
||||
LUADIR = $(PRIVDIR)/lua
|
||||
|
||||
# /var/lib/ejabberd/
|
||||
SPOOLDIR = $(DESTDIR)@localstatedir@/lib/ejabberd
|
||||
|
||||
@@ -283,6 +286,8 @@ uninstall-binary:
|
||||
rm -fr $(JSDIR)
|
||||
rm -f $(SQLDIR)/*.sql
|
||||
rm -fr $(SQLDIR)
|
||||
rm -fr $(LUADIR)/*.lua
|
||||
rm -fr $(LUADIR)
|
||||
rm -fr $(PRIVDIR)
|
||||
rm -fr $(EJABBERDDIR)
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
XmppAddr { iso(1) identified-organization(3)
|
||||
dod(6) internet(1) security(5) mechanisms(5) pkix(7)
|
||||
id-on(8) id-on-xmppAddr(5) }
|
||||
|
||||
DEFINITIONS EXPLICIT TAGS ::=
|
||||
BEGIN
|
||||
|
||||
id-on-xmppAddr OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
|
||||
dod(6) internet(1) security(5) mechanisms(5) pkix(7)
|
||||
id-on(8) 5 }
|
||||
|
||||
XmppAddr ::= UTF8String
|
||||
|
||||
END
|
||||
+75
-730
@@ -1,15 +1,16 @@
|
||||
###
|
||||
###' ejabberd configuration file
|
||||
### ejabberd configuration file
|
||||
###
|
||||
### The parameters used in this configuration file are explained at
|
||||
###
|
||||
### https://docs.ejabberd.im/admin/configuration
|
||||
###
|
||||
|
||||
### 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.
|
||||
### *******************************************************
|
||||
### ******* !!! WARNING !!! *******
|
||||
### ******* YAML IS INDENTATION SENSITIVE *******
|
||||
### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY *******
|
||||
### *******************************************************
|
||||
### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
|
||||
### However, ejabberd treats different literals as different types:
|
||||
###
|
||||
@@ -23,590 +24,77 @@
|
||||
### 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
|
||||
|
||||
###. ===============
|
||||
###' NODE PARAMETERS
|
||||
|
||||
##
|
||||
## net_ticktime: Specifies net_kernel tick time in seconds. This options must have
|
||||
## identical value on all nodes, and in most cases shouldn't be changed at all from
|
||||
## default value.
|
||||
##
|
||||
## net_ticktime: 60
|
||||
|
||||
###. ================
|
||||
###' 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
|
||||
loglevel: 4
|
||||
log_rotate_size: 10485760
|
||||
log_rotate_date: ""
|
||||
log_rotate_count: 1
|
||||
log_rate_limit: 100
|
||||
|
||||
###. ============
|
||||
###' Certificates
|
||||
certfiles:
|
||||
- "/etc/letsencrypt/live/*/*.pem"
|
||||
|
||||
## List all available PEM files containing certificates for your domains,
|
||||
## chains of certificates or certificate keys. Full chains will be built
|
||||
## automatically by ejabberd.
|
||||
##
|
||||
## certfiles:
|
||||
## - "/etc/letsencrypt/live/example.org/*.pem"
|
||||
## - "/etc/letsencrypt/live/example.com/*.pem"
|
||||
##
|
||||
## If your system provides only a single CA file (CentOS/FreeBSD):
|
||||
## ca_file: "/etc/ssl/certs/ca-bundle.pem"
|
||||
|
||||
###. =================
|
||||
###' TLS configuration
|
||||
|
||||
## Note that the following configuration is the default
|
||||
## configuration of the TLS driver, so you don't need to
|
||||
## uncomment it.
|
||||
##
|
||||
## define_macro:
|
||||
## 'TLS_CIPHERS': "HIGH:!aNULL:!eNULL:!3DES:@STRENGTH"
|
||||
## 'TLS_OPTIONS':
|
||||
## - "no_sslv3"
|
||||
## - "cipher_server_preference"
|
||||
## - "no_compression"
|
||||
## 'DH_FILE': "/path/to/dhparams.pem" # generated with: openssl dhparam -out dhparams.pem 2048
|
||||
##
|
||||
## c2s_dhfile: 'DH_FILE'
|
||||
## s2s_dhfile: 'DH_FILE'
|
||||
## c2s_ciphers: 'TLS_CIPHERS'
|
||||
## s2s_ciphers: 'TLS_CIPHERS'
|
||||
## c2s_protocol_options: 'TLS_OPTIONS'
|
||||
## s2s_protocol_options: 'TLS_OPTIONS'
|
||||
|
||||
###. ===============
|
||||
###' 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
|
||||
ip: "::"
|
||||
module: ejabberd_c2s
|
||||
##
|
||||
## If TLS is compiled in and you installed a SSL
|
||||
## certificate, uncomment this line:
|
||||
##
|
||||
## starttls: true
|
||||
##
|
||||
## To enforce TLS encryption for client connections,
|
||||
## use this instead of the "starttls" option:
|
||||
##
|
||||
## starttls_required: true
|
||||
##
|
||||
## Stream compression
|
||||
##
|
||||
## zlib: true
|
||||
##
|
||||
max_stanza_size: 65536
|
||||
max_stanza_size: 262144
|
||||
shaper: c2s_shaper
|
||||
access: c2s
|
||||
##
|
||||
## Direct-TLS for C2S (XEP-0368). A good practice is to forward
|
||||
## traffic from port 443 to this port, possibly multiplexing it
|
||||
## with HTTP using e.g. sslh [https://wiki.xmpp.org/web/Tech_pages/XEP-0368],
|
||||
## so modern clients can bypass restrictive firewalls (in airports, hotels, etc.).
|
||||
##
|
||||
## -
|
||||
## port: 5223
|
||||
## ip: "::"
|
||||
## module: ejabberd_c2s
|
||||
## tls: true
|
||||
## max_stanza_size: 65536
|
||||
## shaper: c2s_shaper
|
||||
## access: c2s
|
||||
starttls_required: true
|
||||
-
|
||||
port: 5269
|
||||
ip: "::"
|
||||
module: ejabberd_s2s_in
|
||||
max_stanza_size: 524288
|
||||
-
|
||||
port: 5280
|
||||
port: 5443
|
||||
ip: "::"
|
||||
module: ejabberd_http
|
||||
request_handlers:
|
||||
"/ws": ejabberd_http_ws
|
||||
"/bosh": mod_bosh
|
||||
"/api": mod_http_api
|
||||
## "/pub/archive": mod_http_fileserver
|
||||
"/bosh": mod_bosh
|
||||
"/upload": mod_http_upload
|
||||
"/ws": ejabberd_http_ws
|
||||
web_admin: true
|
||||
## register: true
|
||||
captcha: true
|
||||
tls: true
|
||||
|
||||
##
|
||||
## ejabberd_service: Interact with external components (transports, ...)
|
||||
##
|
||||
## -
|
||||
## port: 8888
|
||||
## ip: "::"
|
||||
## module: ejabberd_service
|
||||
## 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"
|
||||
## "sms.example.org":
|
||||
## password: "secret"
|
||||
s2s_use_starttls: optional
|
||||
|
||||
##
|
||||
## ejabberd_stun: Handles STUN Binding requests
|
||||
##
|
||||
## -
|
||||
## port: 3478
|
||||
## transport: udp
|
||||
## module: ejabberd_stun
|
||||
|
||||
##
|
||||
## To handle XML-RPC requests that provide admin credentials:
|
||||
##
|
||||
## -
|
||||
## port: 4560
|
||||
## ip: "::"
|
||||
## module: ejabberd_xmlrpc
|
||||
## maxsessions: 10
|
||||
## timeout: 5000
|
||||
## access_commands:
|
||||
## admin:
|
||||
## commands: all
|
||||
## options: []
|
||||
|
||||
##
|
||||
## To enable secure http upload
|
||||
##
|
||||
## -
|
||||
## port: 5444
|
||||
## ip: "::"
|
||||
## module: ejabberd_http
|
||||
## request_handlers:
|
||||
## "": mod_http_upload
|
||||
## tls: true
|
||||
## protocol_options: 'TLS_OPTIONS'
|
||||
## dhfile: 'DH_FILE'
|
||||
## ciphers: 'TLS_CIPHERS'
|
||||
|
||||
## Disabling digest-md5 SASL authentication. digest-md5 requires plain-text
|
||||
## password storage (see auth_password_format option).
|
||||
## disable_sasl_mechanisms: "digest-md5"
|
||||
|
||||
###. ==================
|
||||
###' S2S GLOBAL OPTIONS
|
||||
|
||||
##
|
||||
## s2s_use_starttls: Enable STARTTLS for S2S connections.
|
||||
## Allowed values are: false, optional or required
|
||||
## You must specify 'certfiles' option
|
||||
##
|
||||
## s2s_use_starttls: optional
|
||||
|
||||
##
|
||||
## 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 seconds.
|
||||
##
|
||||
## outgoing_s2s_families:
|
||||
## - ipv4
|
||||
## - ipv6
|
||||
## outgoing_s2s_timeout: 190
|
||||
|
||||
###. ==============
|
||||
###' 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 SQL
|
||||
## Remember to setup a database in the next section.
|
||||
##
|
||||
## auth_method: sql
|
||||
|
||||
##
|
||||
## 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:
|
||||
##
|
||||
## sql_type: mysql
|
||||
## sql_server: "server"
|
||||
## sql_database: "database"
|
||||
## sql_username: "username"
|
||||
## sql_password: "password"
|
||||
##
|
||||
## If you want to specify the port:
|
||||
## sql_port: 1234
|
||||
|
||||
##
|
||||
## PostgreSQL server:
|
||||
##
|
||||
## sql_type: pgsql
|
||||
## sql_server: "server"
|
||||
## sql_database: "database"
|
||||
## sql_username: "username"
|
||||
## sql_password: "password"
|
||||
##
|
||||
## If you want to specify the port:
|
||||
## sql_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
|
||||
|
||||
##
|
||||
## SQLite:
|
||||
##
|
||||
## sql_type: sqlite
|
||||
## sql_database: "/path/to/database.db"
|
||||
|
||||
##
|
||||
## ODBC compatible or MSSQL server:
|
||||
##
|
||||
## sql_type: odbc
|
||||
## sql_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
|
||||
|
||||
##
|
||||
## Number of connections to open to the database for each virtual host
|
||||
##
|
||||
## sql_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
|
||||
##
|
||||
## sql_keepalive_interval: undefined
|
||||
|
||||
##
|
||||
## Use the new SQL schema
|
||||
##
|
||||
## new_sql_schema: true
|
||||
|
||||
##
|
||||
## A database can also can be used to store information from several
|
||||
## modules. To enable storage to the database, just make sure it is
|
||||
## setup above and set default_db: sql if you want to use SQL for
|
||||
## all modules.
|
||||
##
|
||||
## default_db: sql
|
||||
|
||||
###. ===============
|
||||
###' 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: 10000
|
||||
|
||||
###. ====================
|
||||
###' 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"
|
||||
- "::1/128"
|
||||
- "::FFFF:127.0.0.1/128"
|
||||
|
||||
##
|
||||
## 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"
|
||||
|
||||
###. ============
|
||||
###' SHAPER RULES
|
||||
|
||||
shaper_rules:
|
||||
## Maximum number of simultaneous sessions allowed for a single user:
|
||||
max_user_sessions: 10
|
||||
## Maximum number of offline messages that users can have:
|
||||
max_user_offline_messages:
|
||||
- 5000: admin
|
||||
- 100
|
||||
## For C2S connections, all users except admins use the "normal" shaper
|
||||
c2s_shaper:
|
||||
- none: admin
|
||||
- normal
|
||||
## All S2S connections use the "fast" shaper
|
||||
s2s_shaper: fast
|
||||
|
||||
###. ============
|
||||
###' ACCESS RULES
|
||||
access_rules:
|
||||
## This rule allows access only for local users:
|
||||
local:
|
||||
- allow: local
|
||||
## Only non-blocked users can use c2s connections:
|
||||
c2s:
|
||||
- deny: blocked
|
||||
- allow
|
||||
## Only admins can send announcement messages:
|
||||
announce:
|
||||
- allow: admin
|
||||
## Only admins can use the configuration interface:
|
||||
configure:
|
||||
- allow: admin
|
||||
## Only accounts of the local ejabberd server can create rooms:
|
||||
muc_create:
|
||||
- allow: local
|
||||
## Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||
pubsub_createnode:
|
||||
- allow: local
|
||||
## In-band registration allows registration of any possible username.
|
||||
## To disable in-band registration, replace 'allow' with 'deny'.
|
||||
register:
|
||||
- allow
|
||||
## Only allow to register from localhost
|
||||
trusted_network:
|
||||
- allow: loopback
|
||||
## Do not establish S2S connections with bad servers
|
||||
## If you enable this you also have to uncomment "s2s_access: s2s"
|
||||
## s2s:
|
||||
## - deny:
|
||||
## - ip: "XXX.XXX.XXX.XXX/32"
|
||||
## - deny:
|
||||
## - ip: "XXX.XXX.XXX.XXX/32"
|
||||
## - allow
|
||||
|
||||
## ===============
|
||||
## API PERMISSIONS
|
||||
## ===============
|
||||
##
|
||||
## This section allows you to define who and using what method
|
||||
## can execute commands offered by ejabberd.
|
||||
##
|
||||
## By default "console commands" section allow executing all commands
|
||||
## issued using ejabberdctl command, and "admin access" section allows
|
||||
## users in admin acl that connect from 127.0.0.1 to execute all
|
||||
## commands except start and stop with any available access method
|
||||
## (ejabberdctl, http-api, xmlrpc depending what is enabled on server).
|
||||
##
|
||||
## If you remove "console commands" there will be one added by
|
||||
## default allowing executing all commands, but if you just change
|
||||
## permissions in it, version from config file will be used instead
|
||||
## of default one.
|
||||
##
|
||||
api_permissions:
|
||||
"console commands":
|
||||
from:
|
||||
@@ -636,154 +124,68 @@ api_permissions:
|
||||
- "status"
|
||||
- "connected_users_number"
|
||||
|
||||
## 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:
|
||||
## - allow: admin
|
||||
## - deny
|
||||
## register:
|
||||
## - deny
|
||||
shaper:
|
||||
normal: 1000
|
||||
fast: 50000
|
||||
|
||||
###. ================
|
||||
###' DEFAULT LANGUAGE
|
||||
shaper_rules:
|
||||
max_user_sessions: 10
|
||||
max_user_offline_messages:
|
||||
- 5000: admin
|
||||
- 100
|
||||
c2s_shaper:
|
||||
- none: admin
|
||||
- normal
|
||||
s2s_shaper: fast
|
||||
|
||||
##
|
||||
## 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
|
||||
|
||||
###. ====
|
||||
###' ACME
|
||||
##
|
||||
## In order to use the acme certificate acquiring through "Let's Encrypt"
|
||||
## an http listener has to be configured to listen to port 80 so that
|
||||
## the authorization challenges posed by "Let's Encrypt" can be solved.
|
||||
##
|
||||
## A simple way of doing this would be to add the following in the listening
|
||||
## section and to configure port forwarding from 80 to 5280 either via NAT
|
||||
## (for ipv4 only) or using frontends such as haproxy/nginx/sslh/etc.
|
||||
## -
|
||||
## port: 5280
|
||||
## ip: "::"
|
||||
## module: ejabberd_http
|
||||
|
||||
acme:
|
||||
|
||||
## A contact mail that the ACME Certificate Authority can contact in case of
|
||||
## an authorization issue, such as a server-initiated certificate revocation.
|
||||
## It is not mandatory to provide an email address but it is highly suggested.
|
||||
contact: "mailto:example-admin@example.com"
|
||||
|
||||
|
||||
## The ACME Certificate Authority URL.
|
||||
## This could either be:
|
||||
## - https://acme-v01.api.letsencrypt.org - (Default) for the production CA
|
||||
## - https://acme-staging.api.letsencrypt.org - for the staging CA
|
||||
## - http://localhost:4000 - for a local version of the CA
|
||||
ca_url: "https://acme-v01.api.letsencrypt.org"
|
||||
|
||||
###. =======
|
||||
###' MODULES
|
||||
|
||||
##
|
||||
## Modules enabled in all ejabberd virtual hosts.
|
||||
##
|
||||
modules:
|
||||
mod_adhoc: {}
|
||||
mod_admin_extra: {}
|
||||
mod_announce: # recommends mod_adhoc
|
||||
mod_announce:
|
||||
access: announce
|
||||
mod_blocking: {} # requires mod_privacy
|
||||
mod_avatar: {}
|
||||
mod_blocking: {}
|
||||
mod_bosh: {}
|
||||
mod_caps: {}
|
||||
mod_carboncopy: {}
|
||||
mod_client_state: {}
|
||||
mod_configure: {} # requires mod_adhoc
|
||||
## mod_delegation: {} # for xep0356
|
||||
mod_configure: {}
|
||||
mod_disco: {}
|
||||
mod_echo: {}
|
||||
mod_bosh: {}
|
||||
## mod_http_fileserver:
|
||||
## docroot: "/var/www"
|
||||
## accesslog: "/var/log/ejabberd/access.log"
|
||||
## mod_http_upload:
|
||||
## # docroot: "@HOME@/upload"
|
||||
## put_url: "https://@HOST@:5444"
|
||||
## thumbnail: false # otherwise needs ejabberd to be compiled with libgd support
|
||||
## mod_http_upload_quota:
|
||||
## max_days: 30
|
||||
mod_fail2ban: {}
|
||||
mod_http_api: {}
|
||||
mod_http_upload:
|
||||
put_url: "https://@HOST@:5443/upload"
|
||||
mod_last: {}
|
||||
## XEP-0313: Message Archive Management
|
||||
## You might want to setup a SQL backend for MAM because the mnesia database is
|
||||
## limited to 2GB which might be exceeded on large servers
|
||||
## mod_mam: {} # for xep0313, mnesia is limited to 2GB, better use an SQL backend
|
||||
mod_mam:
|
||||
## Mnesia is limited to 2GB, better to use an SQL backend
|
||||
## For small servers SQLite is a good fit and is very easy
|
||||
## to configure. Uncomment this when you have SQL configured:
|
||||
## db_type: sql
|
||||
assume_mam_usage: true
|
||||
default: always
|
||||
mod_muc:
|
||||
## host: "conference.@HOST@"
|
||||
access:
|
||||
- allow
|
||||
access_admin:
|
||||
- allow: admin
|
||||
access_create: muc_create
|
||||
access_persistent: muc_create
|
||||
default_room_options:
|
||||
mam: true
|
||||
mod_muc_admin: {}
|
||||
## mod_muc_log: {}
|
||||
## mod_multicast: {}
|
||||
mod_offline:
|
||||
access_max_user_messages: max_user_offline_messages
|
||||
mod_ping: {}
|
||||
## mod_pres_counter:
|
||||
## count: 5
|
||||
## interval: 60
|
||||
mod_privacy: {}
|
||||
mod_private: {}
|
||||
## mod_proxy65: {}
|
||||
mod_pubsub:
|
||||
access_createnode: pubsub_createnode
|
||||
## reduces resource comsumption, but XEP incompliant
|
||||
ignore_pep_from_offline: true
|
||||
## XEP compliant, but increases resource comsumption
|
||||
## ignore_pep_from_offline: false
|
||||
last_item_cache: false
|
||||
plugins:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep" # pep requires mod_caps
|
||||
- "pep"
|
||||
force_node_config:
|
||||
## Avoid using OMEMO by default because it
|
||||
## introduces a lot of hard-to-track problems.
|
||||
## Comment out the following lines to enable OMEMO support
|
||||
## See https://github.com/processone/ejabberd/issues/2425
|
||||
"eu.siacs.conversations.axolotl.*":
|
||||
access_model: whitelist
|
||||
## Avoid buggy clients to make their bookmarks public
|
||||
@@ -791,82 +193,25 @@ modules:
|
||||
access_model: whitelist
|
||||
mod_push: {}
|
||||
mod_push_keepalive: {}
|
||||
## 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:
|
||||
search: false
|
||||
mod_vcard_xupdate: {}
|
||||
mod_avatar: {}
|
||||
mod_version: {}
|
||||
mod_stream_mgmt: {}
|
||||
## Non-SASL Authentication (XEP-0078) is now disabled by default
|
||||
## because it's obsoleted and is used mostly by abandoned
|
||||
## client software
|
||||
## mod_legacy_auth: {}
|
||||
## The module for S2S dialback (XEP-0220). Please note that you cannot
|
||||
## rely solely on dialback if you want to federate with other servers,
|
||||
## because a lot of servers have dialback disabled and instead rely on
|
||||
## PKIX authentication. Make sure you have proper certificates installed
|
||||
## and check your accessibility at https://check.messaging.one/
|
||||
mod_register:
|
||||
## Only accept registration requests from the "trusted"
|
||||
## network (see access_rules section above).
|
||||
## Think twice before enabling registration from any
|
||||
## address. See the Jabber SPAM Manifesto for details:
|
||||
## https://github.com/ge0rg/jabber-spam-fighting-manifesto
|
||||
ip_access: trusted_network
|
||||
mod_roster:
|
||||
versioning: true
|
||||
mod_s2s_dialback: {}
|
||||
mod_http_api: {}
|
||||
mod_fail2ban: {}
|
||||
mod_shared_roster: {}
|
||||
mod_stream_mgmt:
|
||||
resend_on_timeout: if_offline
|
||||
mod_vcard: {}
|
||||
mod_vcard_xupdate: {}
|
||||
mod_version:
|
||||
show_os: false
|
||||
|
||||
##
|
||||
## 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 foldmarker=###',###. foldmethod=marker:
|
||||
### vim: set filetype=yaml tabstop=8
|
||||
|
||||
+5
-5
@@ -129,13 +129,13 @@
|
||||
id ,% :: mod_pubsub:nodeIdx(),
|
||||
parents = [] ,% :: [mod_pubsub:nodeId(),...],
|
||||
type = <<"flat">>,% :: binary(),
|
||||
owners = [] ,% :: [jlib:ljid(),...],
|
||||
owners = [] ,% :: [jid:ljid(),...],
|
||||
options = [] % :: mod_pubsub:nodeOptions()
|
||||
}).
|
||||
|
||||
-record(pubsub_state,
|
||||
{
|
||||
stateid ,% :: {jlib:ljid(), mod_pubsub:nodeIdx()},
|
||||
stateid ,% :: {jid:ljid(), mod_pubsub:nodeIdx()},
|
||||
nodeidx ,% :: mod_pubsub:nodeIdx(),
|
||||
items = [] ,% :: [mod_pubsub:itemId(),...],
|
||||
affiliation = 'none',% :: mod_pubsub:affiliation(),
|
||||
@@ -146,8 +146,8 @@
|
||||
{
|
||||
itemid ,% :: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()},
|
||||
nodeidx ,% :: mod_pubsub:nodeIdx(),
|
||||
creation = {unknown, unknown},% :: {erlang:timestamp(), jlib:ljid()},
|
||||
modification = {unknown, unknown},% :: {erlang:timestamp(), jlib:ljid()},
|
||||
creation = {unknown, unknown},% :: {erlang:timestamp(), jid:ljid()},
|
||||
modification = {unknown, unknown},% :: {erlang:timestamp(), jid:ljid()},
|
||||
payload = [] % :: mod_pubsub:payload()
|
||||
}).
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
{
|
||||
nodeid ,% :: {binary(), mod_pubsub:nodeIdx()},
|
||||
itemid ,% :: mod_pubsub:itemId(),
|
||||
creation ,% :: {erlang:timestamp(), jlib:ljid()},
|
||||
creation ,% :: {erlang:timestamp(), jid:ljid()},
|
||||
payload % :: mod_pubsub:payload()
|
||||
}).
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(scram, {storedkey = <<"">> :: binary(),
|
||||
serverkey = <<"">> :: binary(),
|
||||
salt = <<"">> :: binary(),
|
||||
iterationcount = 0 :: integer()}).
|
||||
|
||||
-type scram() :: #scram{}.
|
||||
|
||||
-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
|
||||
@@ -1,35 +1,35 @@
|
||||
%{
|
||||
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.14", "e68d24789ff596a7cb4f08780f72a725f3f18e93dee486559b261df904234871", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.16", "0223820e5071d3252b9921db9dcc74a09ec8a660120271fdb47c3c40b6b52bee", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"distillery": {:hex, :distillery, "1.5.3", "b2f4fc34ec71ab4f1202a796f9290e068883b042319aa8c9aa45377ecac8597a", [:mix], [], "hexpm"},
|
||||
"earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"},
|
||||
"eimp": {:hex, :eimp, "1.0.6", "087fc92daf7b03bac4aada8ea6063d9034d4b9088be24e050ff73323c8444a04", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"eimp": {:hex, :eimp, "1.0.9", "daf0d2904be3ef5e4128d946e158113cdb4d52555023746d29b83ce86b531f3c", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"epam": {:hex, :epam, "1.0.4", "2a5e40cbf9b2cf41df515782894c3b33c81b8ad32fff2fc847c3f725071dfaed", [:rebar3], [], "hexpm"},
|
||||
"eredis": {:hex, :eredis, "1.1.0", "8d8d74496f35216679b97726b75fb1c8715e99dd7f3ef9f9824a2264c3e0aac0", [:rebar3], [], "hexpm"},
|
||||
"esip": {:hex, :esip, "1.0.24", "c14efd0817012721dad3d1c36816e4d113adc86d185503f8c08f7faa11082018", [:rebar3], [{:fast_tls, "1.0.23", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.23", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"esip": {:hex, :esip, "1.0.26", "b50c92f8ac3e8e8ba901f0a6cc7e0e47fdc832b0f3044eddb6032ca26845cf97", [:rebar3], [{:fast_tls, "1.0.25", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.25", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ezlib": {:hex, :ezlib, "1.0.4", "2434e4bb549cb060d5ac02261ba48fbe1a69b2ae4e1bf7485a3b27b3f3ec618d", [:rebar3], [], "hexpm"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.23", "5fd44b84b60f1caeb82270aae842599997e2ef70a628c954e494a6613cc6f026", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.31", "7d85dd50533737adbaa95472c962ff8ad3d434ced1dbd04b83465edc2a4363b6", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.15", "55a8ff117e2bb44fda5ca96c53473799f864c865dc63a3b598cb626001207cab", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.25", "cbf875fe709d6fd03d3266c920bfe15f4d22736535d73421300cebf9d86bd851", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.34", "d76fc639d3607a44c4f0fb2dfdee1067b6c37b02b39706d8f067cb77eb2f6016", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.17", "e945ef64e0cb7c311c7b42804dbe32a24e13a2afc0ffe249b7e0f9f9ac08e176", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"},
|
||||
"hamcrest": {:hex, :basho_hamcrest, "0.4.1", "fb7b2c92d252a1e9db936750b86089addaebeb8f87967fb4bbdda61e8863338e", [:make, :mix, :rebar3], [], "hexpm"},
|
||||
"iconv": {:hex, :iconv, "1.0.8", "772a19153e9546b9f17206931ece321220feccfd0b3f5a5509dc92108326c2f9", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"iconv": {:hex, :iconv, "1.0.10", "fc7de75c0a1fbc1e4ed0c68637ae7464a5dd9eee81710fff26321922d144ecea", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"jiffy": {:hex, :jiffy, "0.14.13", "225a9a35e26417832c611526567194b4d3adc4f0dfa5f2f7008f4684076f2a01", [:rebar3], [], "hexpm"},
|
||||
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"luerl": {:hex, :luerl, "0.3.1", "5412807630aac1aaf59ffe5a1bc09259c447b4faeb1d3fe2d4ef41b87676cb04", [:rebar3], [], "hexpm"},
|
||||
"meck": {:hex, :meck, "0.8.10", "455aaef8403be46752272206613e7a15467c014d40994b22fb54cde4d1ff7075", [:rebar3], [], "hexpm"},
|
||||
"moka": {:git, "https://github.com/processone/moka.git", "3eed3a6dd7dedb70a6cd18f86c7561a18626eb3b", [tag: "1.0.5c"]},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.6", "1fb48a907a7fe214a78be15a08f8ebfae2db424c4d9886891a298a395cc3afce", [:rebar3], [], "hexpm"},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.7", "9fbadf8fa283ae8657faa4f6bbe13f2e3b9da0cdcfbc699cfc120d0905282056", [:rebar3], [], "hexpm"},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.3", "fbd91ba86bd7f03d2a4f6e62affa86bab9930abfd6b473d61eefb148f246cd46", [:rebar3], [], "hexpm"},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.6", "631004602b06ca6f55d759001f180185685c7097e583f3b0f7dd9b8e05ba5db1", [:rebar3], [], "hexpm"},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.12", "4304223a2b78d8031e980ae18d6b6d83f764cf2a91c779b2df7cbdfde73acbf8", [:rebar3], [], "hexpm"},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.13", "176577cafb54a8c2fdc0a7fc62b9a21ab0f66588f4062792cd9e65f3e500bfdb", [:rebar3], [], "hexpm"},
|
||||
"riak_pb": {:hex, :riak_pb, "2.3.2", "48ffbf66dbb3f136ab9a7134bac4e496754baa5ef58c4f50a61326736d996390", [:make, :mix, :rebar3], [{:hamcrest, "~> 0.4.1", [hex: :basho_hamcrest, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"riakc": {:hex, :riakc, "2.5.3", "6132d9e687a0dfd314b2b24c4594302ca8b55568a5d733c491d8fb6cd4004763", [:make, :mix, :rebar3], [{:riak_pb, "~> 2.3", [hex: :riak_pb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"samerlib": {:git, "https://github.com/processone/samerlib", "fbbba035b1548ac4e681df00d61bf609645333a0", [tag: "0.8.0c"]},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.6", "4ea71af0b45908b5f02c9b09e4c87177039ef404f20accb35049cd8924cc417c", [:rebar3], [], "hexpm"},
|
||||
"stringprep": {:hex, :stringprep, "1.0.12", "3364897b9a376b2fb5e429944fd34ca0b562b44c9e5acf4e0299564371a6fbef", [:rebar3], [{:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"stun": {:hex, :stun, "1.0.23", "79982f6a26dc65b58bb24082e8bbfba83a5228ddd6407753e9c3092fb45ba916", [:rebar3], [{:fast_tls, "1.0.23", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"xmpp": {:hex, :xmpp, "1.2.1", "9c94ab556559dc6c3bf4963f756a02e9771dd220f5891d57fc30b76c3d71cb06", [:rebar3], [{:fast_xml, "1.1.31", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.12", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.12", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"stringprep": {:hex, :stringprep, "1.0.14", "230a2d1c576bba168749d653fd6681166d02431ef07161a918444f3edb478ad0", [:rebar3], [{:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"stun": {:hex, :stun, "1.0.25", "e324c94c28d636578db79eb26979cd07140f0dabcdc0d5b197650ba0bc44a31a", [:rebar3], [{:fast_tls, "1.0.25", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"xmpp": {:hex, :xmpp, "1.2.5", "98ae86706013e51349e962b67c30293d14672603b5c7d782b2c79b52ceaa659f", [:rebar3], [{:ezlib, "1.0.4", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.0.25", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.34", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.13", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.14", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
redis.replicate_commands()
|
||||
local cursor = redis.call('GET', KEYS[3]) or 0
|
||||
local scan_result = redis.call('HSCAN', KEYS[1], cursor, 'COUNT', ARGV[1])
|
||||
local newcursor = scan_result[1]
|
||||
local cursor = redis.call('SET', KEYS[3], newcursor)
|
||||
redis.call('EXPIRE', KEYS[3], 30)
|
||||
for key,value in ipairs(scan_result[2]) do
|
||||
local uskey, sidkey = string.match(value, '(.*)||(.*)')
|
||||
if uskey and sidkey then
|
||||
redis.call('HDEL', uskey, sidkey)
|
||||
redis.call('HDEL', KEYS[1], value)
|
||||
else
|
||||
redis.call('HDEL', KEYS[2], value)
|
||||
end
|
||||
end
|
||||
return newcursor
|
||||
+123
-121
@@ -152,7 +152,7 @@ msgstr "Agosto"
|
||||
|
||||
#: mod_pubsub:1842
|
||||
msgid "Automatic node creation is not enabled"
|
||||
msgstr ""
|
||||
msgstr "Criação automatizada de nós está desabilitada"
|
||||
|
||||
#: ejabberd_web_admin:1867 mod_configure:148 mod_configure:617
|
||||
msgid "Backup"
|
||||
@@ -185,11 +185,11 @@ msgstr "Aniversário"
|
||||
|
||||
#: mod_legacy_auth:102
|
||||
msgid "Both the username and the resource are required"
|
||||
msgstr ""
|
||||
msgstr "Nome de usuário e recurso são necessários"
|
||||
|
||||
#: mod_proxy65_service:226
|
||||
msgid "Bytestream already activated"
|
||||
msgstr ""
|
||||
msgstr "Bytestream já foi ativado"
|
||||
|
||||
#: ejabberd_captcha:135
|
||||
msgid "CAPTCHA web page"
|
||||
@@ -201,11 +201,11 @@ msgstr "Tempo de CPU"
|
||||
|
||||
#: mod_privacy:334
|
||||
msgid "Cannot remove active list"
|
||||
msgstr ""
|
||||
msgstr "Não é possível remover uma lista ativa"
|
||||
|
||||
#: mod_privacy:341
|
||||
msgid "Cannot remove default list"
|
||||
msgstr ""
|
||||
msgstr "Não é possível remover uma lista padrão"
|
||||
|
||||
#: ejabberd_web_admin:1679 mod_register_web:194 mod_register_web:353
|
||||
#: mod_register_web:361 mod_register_web:386
|
||||
@@ -383,7 +383,7 @@ msgstr "Exportar para arquivo texto"
|
||||
|
||||
#: mod_roster:180
|
||||
msgid "Duplicated groups are not allowed by RFC6121"
|
||||
msgstr ""
|
||||
msgstr "Grupos duplicados não são permitidos pela RFC6121"
|
||||
|
||||
#: mod_configure:1776
|
||||
msgid "Edit Properties"
|
||||
@@ -413,7 +413,7 @@ msgstr "Permitir criação de logs"
|
||||
|
||||
#: mod_push:252
|
||||
msgid "Enabling push without 'node' attribute is not supported"
|
||||
msgstr ""
|
||||
msgstr "Abilitar push sem o atributo 'node' não é suportado"
|
||||
|
||||
#: mod_irc:834
|
||||
msgid "Encoding for server ~b"
|
||||
@@ -503,15 +503,15 @@ msgstr ""
|
||||
|
||||
#: mod_delegation:275
|
||||
msgid "External component failure"
|
||||
msgstr ""
|
||||
msgstr "Falha de componente externo"
|
||||
|
||||
#: mod_delegation:283
|
||||
msgid "External component timeout"
|
||||
msgstr ""
|
||||
msgstr "Tempo esgotado à espera de componente externo"
|
||||
|
||||
#: mod_proxy65_service:218
|
||||
msgid "Failed to activate bytestream"
|
||||
msgstr ""
|
||||
msgstr "Falha ao ativar bytestream"
|
||||
|
||||
#: mod_muc_room:910
|
||||
msgid "Failed to extract JID from your voice request approval"
|
||||
@@ -519,19 +519,19 @@ msgstr "Não foi possível extrair o JID (Jabber ID) da requisição de voz"
|
||||
|
||||
#: mod_delegation:257
|
||||
msgid "Failed to map delegated namespace to external component"
|
||||
msgstr ""
|
||||
msgstr "Falha ao mapear namespace delegado ao componente externo"
|
||||
|
||||
#: mod_http_upload:602
|
||||
msgid "Failed to parse HTTP response"
|
||||
msgstr ""
|
||||
msgstr "Falha ao analisar resposta HTTP"
|
||||
|
||||
#: mod_irc:426
|
||||
msgid "Failed to parse chanserv"
|
||||
msgstr ""
|
||||
msgstr "Falha ao analisar chanserv"
|
||||
|
||||
#: mod_muc_room:3297
|
||||
msgid "Failed to process option '~s'"
|
||||
msgstr ""
|
||||
msgstr "Falha ao processar opção '~s'"
|
||||
|
||||
#: mod_vcard_ldap:332 mod_vcard_ldap:345 mod_vcard_mnesia:103
|
||||
#: mod_vcard_mnesia:117 mod_vcard_sql:158 mod_vcard_sql:172
|
||||
@@ -544,7 +544,7 @@ msgstr "Fevereiro"
|
||||
|
||||
#: mod_http_upload:555
|
||||
msgid "File larger than ~w bytes"
|
||||
msgstr ""
|
||||
msgstr "Arquivo maior que ~w bytes"
|
||||
|
||||
#: mod_vcard:437
|
||||
msgid ""
|
||||
@@ -610,7 +610,7 @@ msgstr "Máquina"
|
||||
|
||||
#: mod_s2s_dialback:325
|
||||
msgid "Host unknown"
|
||||
msgstr ""
|
||||
msgstr "Máquina desconhecida"
|
||||
|
||||
#: ejabberd_web_admin:2463
|
||||
msgid "IP"
|
||||
@@ -699,15 +699,15 @@ msgstr "Importar dados dos usuários de um diretório-fila jabberd14:"
|
||||
|
||||
#: xmpp_stream_in:983
|
||||
msgid "Improper 'from' attribute"
|
||||
msgstr ""
|
||||
msgstr "Atributo 'from' incorreto"
|
||||
|
||||
#: xmpp_stream_in:477
|
||||
msgid "Improper 'to' attribute"
|
||||
msgstr ""
|
||||
msgstr "Atributo 'to' incorreto"
|
||||
|
||||
#: ejabberd_service:189
|
||||
msgid "Improper domain part of 'from' attribute"
|
||||
msgstr ""
|
||||
msgstr "Atributo 'from' contém domínio incorreto"
|
||||
|
||||
#: mod_muc_room:260
|
||||
msgid "Improper message type"
|
||||
@@ -715,11 +715,11 @@ msgstr "Tipo de mensagem incorreto"
|
||||
|
||||
#: ejabberd_web_admin:1586
|
||||
msgid "Incoming s2s Connections:"
|
||||
msgstr "Conexões que entram de s2s"
|
||||
msgstr "Conexões s2s de Entrada:"
|
||||
|
||||
#: mod_muc_room:3669 mod_register:187
|
||||
msgid "Incorrect CAPTCHA submit"
|
||||
msgstr ""
|
||||
msgstr "CAPTCHA submetido incorretamente"
|
||||
|
||||
#: mod_muc:772 mod_muc_room:3052 mod_pubsub:1194 mod_register:183 mod_vcard:256
|
||||
#, fuzzy
|
||||
@@ -732,41 +732,41 @@ msgstr "Senha incorreta"
|
||||
|
||||
#: mod_irc:585
|
||||
msgid "Incorrect value in data form"
|
||||
msgstr ""
|
||||
msgstr "Valor incorreto em formulário de dados"
|
||||
|
||||
#: mod_adhoc:276
|
||||
msgid "Incorrect value of 'action' attribute"
|
||||
msgstr ""
|
||||
msgstr "Valor incorreto do atributo 'action'"
|
||||
|
||||
#: mod_configure:1806
|
||||
msgid "Incorrect value of 'action' in data form"
|
||||
msgstr ""
|
||||
msgstr "Valor incorreto de 'action' no formulário de dados"
|
||||
|
||||
#: mod_configure:1335 mod_configure:1367 mod_configure:1399 mod_configure:1419
|
||||
#: mod_configure:1439
|
||||
msgid "Incorrect value of 'path' in data form"
|
||||
msgstr ""
|
||||
msgstr "Valor incorreto de 'path' no formulário de dados"
|
||||
|
||||
#: mod_irc:331
|
||||
msgid "Incorrect value of 'type' attribute"
|
||||
msgstr ""
|
||||
msgstr "Valor incorreto do atributo 'type'"
|
||||
|
||||
#: mod_privilege:100
|
||||
msgid "Insufficient privilege"
|
||||
msgstr ""
|
||||
msgstr "Privilégio insuficiente"
|
||||
|
||||
#: mod_privilege:286
|
||||
msgid "Invalid 'from' attribute in forwarded message"
|
||||
msgstr ""
|
||||
msgstr "Atributo 'from' inválido na mensagem reenviada"
|
||||
|
||||
#: mod_privilege:300
|
||||
msgid "Invalid <forwarded/> element"
|
||||
msgstr ""
|
||||
msgstr "Elemento <forwarded/> inválido"
|
||||
|
||||
#: mod_muc_room:3926
|
||||
#, fuzzy
|
||||
msgid "Invitations are not allowed in this conference"
|
||||
msgstr "Requisições de voz estão desabilitadas nesta conferência"
|
||||
msgstr "Convites estão desabilitados nesta sala de conferência"
|
||||
|
||||
#: mod_muc_room:233 mod_muc_room:375 mod_muc_room:1046
|
||||
msgid ""
|
||||
@@ -786,11 +786,11 @@ msgstr "Não é permitido enviar mensagens privadas do tipo \"groupchat\""
|
||||
|
||||
#: mod_muc_room:244
|
||||
msgid "It is not allowed to send private messages to the conference"
|
||||
msgstr "Impedir o envio de mensagens privadas para a sala"
|
||||
msgstr "Não é permitido enviar mensagens privadas para a sala de conferência"
|
||||
|
||||
#: mod_register_web:181 mod_register_web:189
|
||||
msgid "Jabber Account Registration"
|
||||
msgstr "Registros de Contas Jabber"
|
||||
msgstr "Registro de Contas Jabber"
|
||||
|
||||
#: mod_configure:1122 mod_configure:1139 mod_configure:1149 mod_configure:1159
|
||||
#: mod_configure:1169 mod_configure:1183 mod_configure:1192 mod_configure:1600
|
||||
@@ -805,11 +805,11 @@ msgstr "Janeiro"
|
||||
|
||||
#: mod_irc:665
|
||||
msgid "Join IRC channel"
|
||||
msgstr "Juntar-se ao canal IRC"
|
||||
msgstr "Entrar no canal IRC"
|
||||
|
||||
#: mod_irc:689
|
||||
msgid "Join the IRC channel here."
|
||||
msgstr "Aqui! Juntar-se ao canal IRC."
|
||||
msgstr "Entre no canal IRC aqui."
|
||||
|
||||
#: mod_irc:690
|
||||
msgid "Join the IRC channel in this Jabber ID: ~s"
|
||||
@@ -890,7 +890,7 @@ msgstr "Tornar sala pública possível de ser encontrada"
|
||||
#: mod_register:317
|
||||
#, fuzzy
|
||||
msgid "Malformed username"
|
||||
msgstr "Usuário IRC"
|
||||
msgstr "Nome de usuário inválido"
|
||||
|
||||
#: mod_muc_log:475
|
||||
msgid "March"
|
||||
@@ -910,7 +910,7 @@ msgstr "Membros:"
|
||||
|
||||
#: mod_muc_room:1833
|
||||
msgid "Membership is required to enter this room"
|
||||
msgstr "Necessitas ser membro desta sala para poder entrar"
|
||||
msgstr "É necessário ser membro desta sala para poder entrar"
|
||||
|
||||
#: mod_register_web:262
|
||||
msgid ""
|
||||
@@ -918,9 +918,9 @@ msgid ""
|
||||
"Jabber there isn't an automated way to recover your password if you forget "
|
||||
"it."
|
||||
msgstr ""
|
||||
"Memorize a sua senha, ou escreva-a em um papel e guarde-o em um lugar "
|
||||
"seguro. Jabber não é uma maneira automatizada para recuperar a sua senha, se "
|
||||
"você a esquecer eventualmente."
|
||||
"Memorize a sua senha, ou anote-a em um papel, guardado em um local seguro. "
|
||||
"No Jabber, não há uma maneira automatizada de recuperar a sua senha, "
|
||||
"caso a esqueça."
|
||||
|
||||
#: ejabberd_web_admin:1954
|
||||
msgid "Memory"
|
||||
@@ -932,7 +932,7 @@ msgstr "Corpo da mensagem"
|
||||
|
||||
#: mod_privilege:291
|
||||
msgid "Message not found in forwarded payload"
|
||||
msgstr ""
|
||||
msgstr "Mensagem não encontrada em conteúdo encaminhado"
|
||||
|
||||
#: mod_vcard_ldap:331 mod_vcard_ldap:344 mod_vcard_mnesia:102
|
||||
#: mod_vcard_mnesia:116 mod_vcard_sql:157 mod_vcard_sql:171
|
||||
@@ -941,15 +941,15 @@ msgstr "Nome do meio"
|
||||
|
||||
#: mod_irc:704
|
||||
msgid "Missing 'channel' or 'server' in the data form"
|
||||
msgstr ""
|
||||
msgstr "Faltando 'channel' ou 'server' no formulário de dados"
|
||||
|
||||
#: xmpp_stream_in:990
|
||||
msgid "Missing 'from' attribute"
|
||||
msgstr ""
|
||||
msgstr "Faltando atributo 'from'"
|
||||
|
||||
#: xmpp_stream_in:472 xmpp_stream_in:993
|
||||
msgid "Missing 'to' attribute"
|
||||
msgstr ""
|
||||
msgstr "Faltando atributo 'to'"
|
||||
|
||||
#: mod_muc_room:2536 mod_muc_room:3721 mod_muc_room:3765 mod_muc_room:3798
|
||||
msgid "Moderator privileges required"
|
||||
@@ -965,7 +965,7 @@ msgstr "Módulo"
|
||||
|
||||
#: gen_iq_handler:153
|
||||
msgid "Module failed to handle the query"
|
||||
msgstr ""
|
||||
msgstr "Módulo falhou ao processar a consulta"
|
||||
|
||||
#: ejabberd_web_admin:1885 mod_configure:582 mod_configure:595
|
||||
msgid "Modules"
|
||||
@@ -989,7 +989,7 @@ msgstr "Multicast"
|
||||
|
||||
#: mod_roster:195
|
||||
msgid "Multiple <item/> elements are not allowed by RFC6121"
|
||||
msgstr ""
|
||||
msgstr "Vários elementos <item/> não são permitidos pela RFC6121"
|
||||
|
||||
#: ejabberd_web_admin:1951 mod_vcard_mnesia:101 mod_vcard_mnesia:115
|
||||
#: mod_vcard_sql:156 mod_vcard_sql:170
|
||||
@@ -1002,11 +1002,11 @@ msgstr "Nome:"
|
||||
|
||||
#: mod_muc_room:2696
|
||||
msgid "Neither 'jid' nor 'nick' attribute found"
|
||||
msgstr ""
|
||||
msgstr "Nem o atributo 'jid' nem 'nick' foram encontrados"
|
||||
|
||||
#: mod_muc_room:2518 mod_muc_room:2701
|
||||
msgid "Neither 'role' nor 'affiliation' attribute found"
|
||||
msgstr ""
|
||||
msgstr "Nem o atributo 'role' nem 'affiliation' foram encontrados"
|
||||
|
||||
#: ejabberd_web_admin:1506 ejabberd_web_admin:1687 mod_configure:1629
|
||||
msgid "Never"
|
||||
@@ -1031,41 +1031,41 @@ msgstr "O apelido ~s não existe na sala"
|
||||
|
||||
#: mod_configure:1496
|
||||
msgid "No 'access' found in data form"
|
||||
msgstr ""
|
||||
msgstr "'access' não foi encontrado em formulário de dados"
|
||||
|
||||
#: mod_configure:1455
|
||||
msgid "No 'acls' found in data form"
|
||||
msgstr ""
|
||||
msgstr "'acls' não foi encontrado em formulário de dados"
|
||||
|
||||
#: mod_muc_room:3075
|
||||
msgid "No 'affiliation' attribute found"
|
||||
msgstr ""
|
||||
msgstr "Atributo 'affiliation' não foi encontrado"
|
||||
|
||||
#: mod_muc_room:2505
|
||||
#, fuzzy
|
||||
msgid "No 'item' element found"
|
||||
msgstr "Nó não encontrado"
|
||||
msgstr "Elemento 'item' não foi encontrado"
|
||||
|
||||
#: mod_configure:1280
|
||||
msgid "No 'modules' found in data form"
|
||||
msgstr ""
|
||||
msgstr "'modules' não foi encontrado em formulário de dados"
|
||||
|
||||
#: mod_configure:1799
|
||||
msgid "No 'password' found in data form"
|
||||
msgstr ""
|
||||
msgstr "'password' não foi encontrado em formulário de dados"
|
||||
|
||||
#: mod_register:148
|
||||
msgid "No 'password' found in this query"
|
||||
msgstr ""
|
||||
msgstr "'password' não foi encontrado nesta consulta"
|
||||
|
||||
#: mod_configure:1319 mod_configure:1350 mod_configure:1382 mod_configure:1413
|
||||
#: mod_configure:1433
|
||||
msgid "No 'path' found in data form"
|
||||
msgstr ""
|
||||
msgstr "'path' não foi encontrado em formulário de dados"
|
||||
|
||||
#: mod_muc_room:3922
|
||||
msgid "No 'to' attribute found in the invitation"
|
||||
msgstr ""
|
||||
msgstr "Atributo 'to' não foi encontrado no convite"
|
||||
|
||||
#: ejabberd_web_admin:1767
|
||||
msgid "No Data"
|
||||
@@ -1073,7 +1073,7 @@ msgstr "Nenhum dado"
|
||||
|
||||
#: ejabberd_local:181
|
||||
msgid "No available resource found"
|
||||
msgstr ""
|
||||
msgstr "Nenhum recurso disponível foi encontrado"
|
||||
|
||||
#: mod_announce:575
|
||||
msgid "No body provided for announce message"
|
||||
@@ -1082,45 +1082,45 @@ msgstr "Nenhum corpo de texto fornecido para anunciar mensagem"
|
||||
#: mod_irc:335 mod_pubsub:1183 mod_pubsub:3289
|
||||
#, fuzzy
|
||||
msgid "No data form found"
|
||||
msgstr "Nó não encontrado"
|
||||
msgstr "Formulário de dados não foi encontrado"
|
||||
|
||||
#: mod_disco:224 mod_vcard:282
|
||||
msgid "No features available"
|
||||
msgstr ""
|
||||
msgstr "Nenhuma funcionalidade disponível"
|
||||
|
||||
#: mod_adhoc:239
|
||||
msgid "No hook has processed this command"
|
||||
msgstr ""
|
||||
msgstr "Nenhum hook processou este comando"
|
||||
|
||||
#: mod_last:218
|
||||
msgid "No info about last activity found"
|
||||
msgstr ""
|
||||
msgstr "Não foi encontrada informação sobre última atividade"
|
||||
|
||||
#: mod_blocking:99
|
||||
msgid "No items found in this query"
|
||||
msgstr ""
|
||||
msgstr "Nenhum item encontrado nesta consulta"
|
||||
|
||||
#: ejabberd_local:90 ejabberd_sm:863 mod_blocking:92 mod_blocking:110
|
||||
#: mod_http_upload:513 mod_muc:482 mod_muc:534 mod_muc:556 mod_muc:580
|
||||
#: mod_offline:303 mod_privacy:168 mod_privacy:285 mod_roster:207
|
||||
msgid "No module is handling this query"
|
||||
msgstr ""
|
||||
msgstr "Nenhum módulo está processando esta consulta"
|
||||
|
||||
#: mod_pubsub:1541
|
||||
msgid "No node specified"
|
||||
msgstr ""
|
||||
msgstr "Nenhum nó especificado"
|
||||
|
||||
#: mod_pubsub:1426
|
||||
msgid "No pending subscriptions found"
|
||||
msgstr ""
|
||||
msgstr "Não foram encontradas subscrições"
|
||||
|
||||
#: mod_privacy:201 mod_privacy:295 mod_privacy:311 mod_privacy:344
|
||||
msgid "No privacy list with this name found"
|
||||
msgstr ""
|
||||
msgstr "Nenhuma lista de privacidade encontrada com este nome"
|
||||
|
||||
#: mod_private:96
|
||||
msgid "No private data found in this query"
|
||||
msgstr ""
|
||||
msgstr "Nenhum dado privado encontrado nesta consulta"
|
||||
|
||||
#: mod_configure:869 mod_configure:908 mod_configure:1222 mod_configure:1254
|
||||
#: mod_configure:1275 mod_configure:1314 mod_configure:1345 mod_configure:1377
|
||||
@@ -1131,15 +1131,15 @@ msgstr "Nó não encontrado"
|
||||
|
||||
#: mod_disco:252 mod_vcard:265
|
||||
msgid "No services available"
|
||||
msgstr ""
|
||||
msgstr "Não há serviços disponíveis"
|
||||
|
||||
#: mod_stats:101
|
||||
msgid "No statistics found for this item"
|
||||
msgstr ""
|
||||
msgstr "Não foram encontradas estatísticas para este item"
|
||||
|
||||
#: nodetree_dag:72 nodetree_tree:181 nodetree_tree_sql:255
|
||||
msgid "Node already exists"
|
||||
msgstr ""
|
||||
msgstr "Nó já existe"
|
||||
|
||||
#: nodetree_tree_sql:99
|
||||
#, fuzzy
|
||||
@@ -1159,7 +1159,7 @@ msgstr "Nó ~p"
|
||||
|
||||
#: mod_vcard:385
|
||||
msgid "Nodeprep has failed"
|
||||
msgstr ""
|
||||
msgstr "Processo de identificação de nó falhou (nodeprep)"
|
||||
|
||||
#: ejabberd_web_admin:1837
|
||||
msgid "Nodes"
|
||||
@@ -1176,7 +1176,7 @@ msgstr "Não encontrado"
|
||||
|
||||
#: mod_disco:296 mod_disco:370 mod_last:159
|
||||
msgid "Not subscribed"
|
||||
msgstr ""
|
||||
msgstr "Não subscrito"
|
||||
|
||||
#: mod_muc_log:483
|
||||
msgid "November"
|
||||
@@ -1227,11 +1227,11 @@ msgstr "Usuários online"
|
||||
|
||||
#: mod_carboncopy:141
|
||||
msgid "Only <enable/> or <disable/> tags are allowed"
|
||||
msgstr ""
|
||||
msgstr "Apenas tags <enable/> ou <disable/> são permitidas"
|
||||
|
||||
#: mod_privacy:154
|
||||
msgid "Only <list/> element is allowed in this query"
|
||||
msgstr ""
|
||||
msgstr "Apenas elemento <list/> é permitido nesta consulta"
|
||||
|
||||
#: mod_mam:379
|
||||
msgid "Only members may query archives of this room"
|
||||
@@ -1255,11 +1255,11 @@ msgstr "Somente moderadores podem aprovar requisições de voz"
|
||||
|
||||
#: mod_muc_room:424 mod_muc_room:792 mod_muc_room:3989
|
||||
msgid "Only occupants are allowed to send messages to the conference"
|
||||
msgstr "Somente os ocupantes podem enviar mensagens à sala"
|
||||
msgstr "Somente os ocupantes podem enviar mensagens à sala de conferência"
|
||||
|
||||
#: mod_muc_room:457
|
||||
msgid "Only occupants are allowed to send queries to the conference"
|
||||
msgstr "Somente os ocupantes podem enviar consultas à sala"
|
||||
msgstr "Somente os ocupantes podem enviar consultas à sala de conferência"
|
||||
|
||||
#: mod_muc:422
|
||||
msgid "Only service administrators are allowed to send service messages"
|
||||
@@ -1282,11 +1282,11 @@ msgstr "Departamento/Unidade"
|
||||
|
||||
#: mod_configure:508
|
||||
msgid "Outgoing s2s Connections"
|
||||
msgstr "Conexões que partam de s2s"
|
||||
msgstr "Conexões s2s de Saída"
|
||||
|
||||
#: ejabberd_web_admin:1583
|
||||
msgid "Outgoing s2s Connections:"
|
||||
msgstr "Conexões que partem de s2s"
|
||||
msgstr "Conexões s2s de Saída"
|
||||
|
||||
#: mod_muc_room:3023 mod_muc_room:3067 mod_muc_room:3697 mod_pubsub:1302
|
||||
#: mod_pubsub:1394 mod_pubsub:1553 mod_pubsub:2118 mod_pubsub:2184
|
||||
@@ -1300,11 +1300,11 @@ msgstr "Pacote"
|
||||
|
||||
#: mod_irc:578
|
||||
msgid "Parse error"
|
||||
msgstr ""
|
||||
msgstr "Erro de análise de dados"
|
||||
|
||||
#: mod_configure:1299 mod_configure:1468 mod_configure:1513
|
||||
msgid "Parse failed"
|
||||
msgstr ""
|
||||
msgstr "Análise de dados falhou"
|
||||
|
||||
#: ejabberd_oauth:431 ejabberd_web_admin:1435 mod_configure:1126
|
||||
#: mod_configure:1173 mod_configure:1602 mod_configure:1781 mod_muc_log:1036
|
||||
@@ -1384,7 +1384,7 @@ msgstr "Porta ~b"
|
||||
|
||||
#: mod_roster:173
|
||||
msgid "Possessing 'ask' attribute is not allowed by RFC6121"
|
||||
msgstr ""
|
||||
msgstr "Possuir atributo 'ask' não é permitido pela RFC6121"
|
||||
|
||||
#: ejabberd_web_admin:2464
|
||||
msgid "Protocol"
|
||||
@@ -1400,16 +1400,16 @@ msgstr "Publicação de Tópico"
|
||||
|
||||
#: node_dag:81
|
||||
msgid "Publishing items to collection node is not allowed"
|
||||
msgstr ""
|
||||
msgstr "Publicar items em um nó de coleção não é permitido"
|
||||
|
||||
#: mod_muc_room:462
|
||||
msgid "Queries to the conference members are not allowed in this room"
|
||||
msgstr "Nesta sala não se permite consultas aos membros da sala"
|
||||
msgstr "Nesta sala de conferência, consultas aos membros não são permitidas"
|
||||
|
||||
#: mod_blocking:85 mod_disco:325 mod_disco:393 mod_offline:270 mod_privacy:146
|
||||
#: mod_private:118 mod_roster:163 mod_sic:90
|
||||
msgid "Query to another users is forbidden"
|
||||
msgstr ""
|
||||
msgstr "Consultar a outro usuário é proibido"
|
||||
|
||||
#: mod_configure:889
|
||||
msgid "RAM and disc copy"
|
||||
@@ -1540,7 +1540,7 @@ msgstr "Lista de contatos"
|
||||
|
||||
#: mod_roster:334
|
||||
msgid "Roster module has failed"
|
||||
msgstr ""
|
||||
msgstr "O módulo Roster falhou"
|
||||
|
||||
#: mod_roster:968
|
||||
msgid "Roster of "
|
||||
@@ -1556,7 +1556,7 @@ msgstr "Nós em execução"
|
||||
|
||||
#: xmpp_stream_in:541 xmpp_stream_in:549
|
||||
msgid "SASL negotiation is not allowed in this state"
|
||||
msgstr ""
|
||||
msgstr "Negociação SASL não é permitida neste estado"
|
||||
|
||||
#: mod_muc_log:468
|
||||
msgid "Saturday"
|
||||
@@ -1564,12 +1564,12 @@ msgstr "Sábado"
|
||||
|
||||
#: mod_irc:582
|
||||
msgid "Scan error"
|
||||
msgstr ""
|
||||
msgstr "Erro de escaneamento"
|
||||
|
||||
#: mod_configure:1303 mod_configure:1472 mod_configure:1517
|
||||
#, fuzzy
|
||||
msgid "Scan failed"
|
||||
msgstr "O CAPTCHA é inválido."
|
||||
msgstr "O escaneamento falhou"
|
||||
|
||||
#: ejabberd_web_admin:2282
|
||||
msgid "Script check"
|
||||
@@ -1605,11 +1605,11 @@ msgstr "Setembro"
|
||||
|
||||
#: mod_irc_connection:648
|
||||
msgid "Server Connect Failed"
|
||||
msgstr ""
|
||||
msgstr "Conexão ao servidor falhou"
|
||||
|
||||
#: ejabberd_s2s:369
|
||||
msgid "Server connections to local subdomains are forbidden"
|
||||
msgstr ""
|
||||
msgstr "Conexões de servidor a subdomínios locais estão proibidas"
|
||||
|
||||
#: mod_irc:842
|
||||
msgid "Server ~b"
|
||||
@@ -1724,7 +1724,7 @@ msgstr "Subscrição"
|
||||
|
||||
#: mod_muc_room:3708
|
||||
msgid "Subscriptions are not allowed"
|
||||
msgstr ""
|
||||
msgstr "Subscrições não estão permitidas"
|
||||
|
||||
#: mod_muc_log:469
|
||||
msgid "Sunday"
|
||||
@@ -1748,15 +1748,15 @@ msgstr "A verificação do CAPTCHA falhou"
|
||||
|
||||
#: mod_muc_room:302
|
||||
msgid "The feature requested is not supported by the conference"
|
||||
msgstr ""
|
||||
msgstr "A funcionalidade solicitada não é suportada pela sala de conferência"
|
||||
|
||||
#: mod_register:308 mod_register:366
|
||||
msgid "The password contains unacceptable characters"
|
||||
msgstr ""
|
||||
msgstr "A senha contém caracteres proibidos"
|
||||
|
||||
#: mod_register:311 mod_register:370
|
||||
msgid "The password is too weak"
|
||||
msgstr "Senha considerada fraca'"
|
||||
msgstr "Senha considerada muito fraca"
|
||||
|
||||
#: mod_register_web:141
|
||||
msgid "The password of your Jabber account was successfully changed."
|
||||
@@ -1764,17 +1764,19 @@ msgstr "A senha da sua conta Jabber foi mudada com sucesso."
|
||||
|
||||
#: mod_register:160 mod_vcard:219
|
||||
msgid "The query is only allowed from local users"
|
||||
msgstr ""
|
||||
msgstr "Esta consulta só é permitida a partir de usuários locais"
|
||||
|
||||
#: mod_roster:203
|
||||
msgid "The query must not contain <item/> elements"
|
||||
msgstr ""
|
||||
msgstr "A consulta não pode conter elementos <item/>"
|
||||
|
||||
#: mod_privacy:280
|
||||
msgid ""
|
||||
"The stanza MUST contain only one <active/> element, one <default/> element, "
|
||||
"or one <list/> element"
|
||||
msgstr ""
|
||||
"A instância DEVE conter apenas um elemento <active/>, um elemento <default/>, "
|
||||
"ou um elemento <list/>"
|
||||
|
||||
#: mod_register_web:146
|
||||
msgid "There was an error changing the password: "
|
||||
@@ -1830,7 +1832,7 @@ msgstr "Para"
|
||||
|
||||
#: mod_register:215
|
||||
msgid "To register, visit ~s"
|
||||
msgstr ""
|
||||
msgstr "Para registrar, visite ~s"
|
||||
|
||||
#: mod_configure:709
|
||||
msgid "To ~s"
|
||||
@@ -1838,19 +1840,19 @@ msgstr "Para ~s"
|
||||
|
||||
#: ejabberd_oauth:439
|
||||
msgid "Token TTL"
|
||||
msgstr ""
|
||||
msgstr "Token TTL"
|
||||
|
||||
#: xmpp_stream_in:463
|
||||
msgid "Too long value of 'xml:lang' attribute"
|
||||
msgstr ""
|
||||
msgstr "Valor do atributo 'xml:lang' é demasiado longo"
|
||||
|
||||
#: mod_muc_room:2541 mod_muc_room:3081
|
||||
msgid "Too many <item/> elements"
|
||||
msgstr ""
|
||||
msgstr "Número excessivo de elementos <item/>"
|
||||
|
||||
#: mod_privacy:164
|
||||
msgid "Too many <list/> elements"
|
||||
msgstr ""
|
||||
msgstr "Número excessivo de elementos <list/>"
|
||||
|
||||
#: mod_muc_room:1924 mod_register:240
|
||||
msgid "Too many CAPTCHA requests"
|
||||
@@ -1859,20 +1861,20 @@ msgstr "Número excessivo de requisições para o CAPTCHA"
|
||||
#: mod_proxy65_service:223
|
||||
#, fuzzy
|
||||
msgid "Too many active bytestreams"
|
||||
msgstr "número excessivo de instâncias sem confirmação"
|
||||
msgstr "Número excessivo de bytestreams ativos"
|
||||
|
||||
#: mod_stream_mgmt:205
|
||||
msgid "Too many unacked stanzas"
|
||||
msgstr "número excessivo de instâncias sem confirmação"
|
||||
msgstr "Número excessivo de instâncias sem confirmação"
|
||||
|
||||
#: mod_muc_room:1802
|
||||
#, fuzzy
|
||||
msgid "Too many users in this conference"
|
||||
msgstr "Requisições de voz estão desabilitadas nesta conferência"
|
||||
msgstr "Número excessivo de usuários nesta sala de conferência"
|
||||
|
||||
#: mod_register:355
|
||||
msgid "Too many users registered"
|
||||
msgstr ""
|
||||
msgstr "Número excessivo de usuários registrados"
|
||||
|
||||
#: mod_muc_admin:368
|
||||
msgid "Total rooms"
|
||||
@@ -1908,7 +1910,7 @@ msgstr "Impossível gerar um CAPTCHA"
|
||||
|
||||
#: ejabberd_service:120
|
||||
msgid "Unable to register route on existing local domain"
|
||||
msgstr ""
|
||||
msgstr "Não foi possível registrar rota no domínio local existente"
|
||||
|
||||
#: ejabberd_web_admin:209 ejabberd_web_admin:221 ejabberd_web_admin:241
|
||||
#: ejabberd_web_admin:253
|
||||
@@ -1917,7 +1919,7 @@ msgstr "Não Autorizado"
|
||||
|
||||
#: mod_announce:485 mod_configure:830 mod_configure:1758
|
||||
msgid "Unexpected action"
|
||||
msgstr ""
|
||||
msgstr "Ação inesperada"
|
||||
|
||||
#: mod_register_web:488
|
||||
msgid "Unregister"
|
||||
@@ -1929,11 +1931,11 @@ msgstr "Deletar conta Jabber"
|
||||
|
||||
#: mod_mam:526
|
||||
msgid "Unsupported <index/> element"
|
||||
msgstr ""
|
||||
msgstr "Elemento <index/> não suportado"
|
||||
|
||||
#: mod_mix:119
|
||||
msgid "Unsupported MIX query"
|
||||
msgstr ""
|
||||
msgstr "Consula MIX não suportada"
|
||||
|
||||
#: ejabberd_web_admin:1872 ejabberd_web_admin:2285
|
||||
msgid "Update"
|
||||
@@ -1979,7 +1981,7 @@ msgstr "Usuário"
|
||||
|
||||
#: ejabberd_oauth:428
|
||||
msgid "User (jid)"
|
||||
msgstr ""
|
||||
msgstr "Usuário (jid)"
|
||||
|
||||
#: mod_configure:308 mod_configure:505
|
||||
msgid "User Management"
|
||||
@@ -1987,11 +1989,11 @@ msgstr "Gerenciamento de Usuários"
|
||||
|
||||
#: mod_register:345
|
||||
msgid "User already exists"
|
||||
msgstr ""
|
||||
msgstr "Usuário já existe"
|
||||
|
||||
#: mod_echo:138
|
||||
msgid "User part of JID in 'from' is empty"
|
||||
msgstr ""
|
||||
msgstr "Parte do usuário do JID em 'from' está vazia"
|
||||
|
||||
#: ejabberd_sm:193 ejabberd_sm:662 ejabberd_sm:687 mod_sic:106
|
||||
#, fuzzy
|
||||
@@ -2000,7 +2002,7 @@ msgstr "Nó não encontrado"
|
||||
|
||||
#: mod_stream_mgmt:561 mod_stream_mgmt:583
|
||||
msgid "User session terminated"
|
||||
msgstr ""
|
||||
msgstr "Sessão de usuário terminada"
|
||||
|
||||
#: ejabberd_web_admin:1700
|
||||
msgid "User ~s"
|
||||
@@ -2029,7 +2031,7 @@ msgstr "Validar"
|
||||
#: mod_carboncopy:144 mod_irc:345 mod_muc_room:3662 mod_muc_room:3802
|
||||
#: mod_pubsub:895 mod_push:249
|
||||
msgid "Value 'get' of 'type' attribute is not allowed"
|
||||
msgstr ""
|
||||
msgstr "Valor 'get' não permitido para atributo 'type'"
|
||||
|
||||
#: mod_disco:159 mod_disco:175 mod_disco:279 mod_disco:346 mod_irc:270
|
||||
#: mod_irc:285 mod_irc:339 mod_last:118 mod_last:140 mod_muc:479 mod_muc:504
|
||||
@@ -2038,20 +2040,20 @@ msgstr ""
|
||||
#: mod_pubsub:821 mod_pubsub:839 mod_pubsub:877 mod_sic:81 mod_sic:93
|
||||
#: mod_stats:55 mod_time:62 mod_vcard:198 mod_vcard:236 mod_version:62
|
||||
msgid "Value 'set' of 'type' attribute is not allowed"
|
||||
msgstr ""
|
||||
msgstr "Valor 'set' não permitido para atributo 'type'"
|
||||
|
||||
#: pubsub_subscription:237 pubsub_subscription_sql:202
|
||||
msgid "Value of '~s' should be boolean"
|
||||
msgstr ""
|
||||
msgstr "Value de '~s' deveria ser um booleano"
|
||||
|
||||
#: pubsub_subscription:215 pubsub_subscription_sql:180
|
||||
msgid "Value of '~s' should be datetime string"
|
||||
msgstr ""
|
||||
msgstr "Valor de '~s' deveria ser data e hora"
|
||||
|
||||
#: pubsub_subscription:209 pubsub_subscription:227 pubsub_subscription_sql:174
|
||||
#: pubsub_subscription_sql:192
|
||||
msgid "Value of '~s' should be integer"
|
||||
msgstr ""
|
||||
msgstr "Valor de '~s' deveria ser um inteiro"
|
||||
|
||||
#: ejabberd_web_admin:950
|
||||
msgid "Virtual Hosts"
|
||||
@@ -2071,7 +2073,7 @@ msgstr "Requisição de voz"
|
||||
|
||||
#: mod_muc_room:885
|
||||
msgid "Voice requests are disabled in this conference"
|
||||
msgstr "Requisições de voz estão desabilitadas nesta conferência"
|
||||
msgstr "Requisições de voz estão desabilitadas nesta sala de conferência"
|
||||
|
||||
#: mod_muc_log:465
|
||||
msgid "Wednesday"
|
||||
@@ -2087,7 +2089,7 @@ msgstr "Você foi banido desta sala"
|
||||
|
||||
#: mod_muc_room:1811
|
||||
msgid "You have joined too many conferences"
|
||||
msgstr ""
|
||||
msgstr "Você entrou em um número excessivo de salas de conferência"
|
||||
|
||||
#: mod_muc:777
|
||||
msgid "You must fill in field \"Nickname\" in the form"
|
||||
@@ -2379,7 +2381,7 @@ msgstr "~s's Fila de Mensagens Offline"
|
||||
#~ msgstr "Preencha campos para buscar usuários Jabber que concordem"
|
||||
|
||||
#~ msgid "Outgoing s2s Servers:"
|
||||
#~ msgstr "Servidores que partem de s2s"
|
||||
#~ msgstr "Servidores s2s de Saída"
|
||||
|
||||
#~ msgid "Delete"
|
||||
#~ msgstr "Eliminar"
|
||||
|
||||
+14
-16
@@ -19,22 +19,22 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager",
|
||||
{tag, {if_version_above, "17", "3.4.2", "3.2.1"}}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.12"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.14"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.23"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.12"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.31"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.2.1"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.15"}}},
|
||||
{tag, {if_version_above, "17", "3.6.5", "3.2.1"}}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.13"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.16"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.25"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.14"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.34"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.2.5"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.17"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}},
|
||||
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.8.4"}}},
|
||||
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.6"}}},
|
||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.23"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.24"}}}},
|
||||
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.8"}}},
|
||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.25"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.26"}}}},
|
||||
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
||||
{tag, "1.0.6"}}}},
|
||||
{tag, "1.0.7"}}}},
|
||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||
{tag, "1.1.6"}}}},
|
||||
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
|
||||
@@ -52,13 +52,11 @@
|
||||
{if_not_rebar3, {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.8"}}}},
|
||||
{tag, "1.0.10"}}}},
|
||||
{if_var_true, tools, {luerl, ".*", {git, "https://github.com/rvirding/luerl",
|
||||
{tag, "v0.3"}}}},
|
||||
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
|
||||
{tag, "0.8.4"}}}},
|
||||
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka",
|
||||
{tag, "1.0.5d"}}}},
|
||||
{if_var_true, redis, {eredis, ".*", {git, "https://github.com/wooga/eredis",
|
||||
{tag, "v1.0.8"}}}}]}.
|
||||
|
||||
@@ -102,7 +100,7 @@
|
||||
{if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
|
||||
{if_var_true, new_sql_schema, {d, 'NEW_SQL_SCHEMA'}},
|
||||
{if_var_true, hipe, native},
|
||||
{src_dirs, [asn1, src,
|
||||
{src_dirs, [src,
|
||||
{if_var_true, tools, tools},
|
||||
{if_var_true, elixir, include}]}]}.
|
||||
|
||||
|
||||
+4
-4
@@ -134,9 +134,9 @@ CREATE TABLE [dbo].[muc_online_users] (
|
||||
node text NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE CLUSTERED INDEX [muc_online_users_i] ON [muc_online_users] (username, server, resource, name, host)
|
||||
CREATE UNIQUE INDEX [muc_online_users_i] ON [muc_online_users] (username, server, resource, name, host)
|
||||
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
|
||||
CREATE UNIQUE CLUSTERED INDEX [muc_online_users_us] ON [muc_online_users] (username, server);
|
||||
CREATE UNIQUE CLUSTERED INDEX [muc_online_users_us] ON [muc_online_users] (username, server)
|
||||
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
|
||||
|
||||
CREATE TABLE [dbo].[muc_room_subscribers] (
|
||||
@@ -529,7 +529,7 @@ CREATE TABLE [dbo].[bosh] (
|
||||
(
|
||||
[sid] ASC
|
||||
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||
) TEXTIMAGE_ON [PRIMARY];
|
||||
);
|
||||
|
||||
CREATE TABLE [dbo].[carboncopy] (
|
||||
[username] [varchar] (255) NOT NULL,
|
||||
@@ -555,5 +555,5 @@ CREATE TABLE [dbo].[push_session] (
|
||||
CREATE UNIQUE CLUSTERED INDEX [i_push_usn] ON [push_session] (username, service, node)
|
||||
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
|
||||
|
||||
CREATE UNIQUE CLUSTERED INDEX [i_push_ut] ON [push_session] (username, timestamp)
|
||||
CREATE UNIQUE INDEX [i_push_ut] ON [push_session] (username, timestamp)
|
||||
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
|
||||
|
||||
+3
-3
@@ -90,7 +90,7 @@ CREATE INDEX i_sr_user_sh_grp ON sr_user(server_host(191), grp);
|
||||
CREATE TABLE spool (
|
||||
username varchar(191) NOT NULL,
|
||||
server_host text NOT NULL,
|
||||
xml BLOB NOT NULL,
|
||||
xml mediumtext NOT NULL,
|
||||
seq BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
@@ -104,8 +104,8 @@ CREATE TABLE archive (
|
||||
timestamp BIGINT UNSIGNED NOT NULL,
|
||||
peer varchar(191) NOT NULL,
|
||||
bare_peer varchar(191) NOT NULL,
|
||||
xml text NOT NULL,
|
||||
txt text,
|
||||
xml mediumtext NOT NULL,
|
||||
txt mediumtext,
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
|
||||
kind varchar(10),
|
||||
nick varchar(191),
|
||||
|
||||
+3
-3
@@ -80,7 +80,7 @@ CREATE INDEX i_sr_user_grp ON sr_user(grp);
|
||||
|
||||
CREATE TABLE spool (
|
||||
username varchar(191) NOT NULL,
|
||||
xml BLOB NOT NULL,
|
||||
xml mediumtext NOT NULL,
|
||||
seq BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
@@ -93,8 +93,8 @@ CREATE TABLE archive (
|
||||
timestamp BIGINT UNSIGNED NOT NULL,
|
||||
peer varchar(191) NOT NULL,
|
||||
bare_peer varchar(191) NOT NULL,
|
||||
xml text NOT NULL,
|
||||
txt text,
|
||||
xml mediumtext NOT NULL,
|
||||
txt mediumtext,
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
|
||||
kind varchar(10),
|
||||
nick varchar(191),
|
||||
|
||||
+1
-1
@@ -312,7 +312,7 @@ normalize_spec(Spec) ->
|
||||
{ok, Net, Mask} ->
|
||||
{ip, {Net, Mask}};
|
||||
error ->
|
||||
?INFO_MSG("Invalid network address: ~p", [S]),
|
||||
?WARNING_MSG("Invalid network address: ~p", [S]),
|
||||
none
|
||||
end;
|
||||
BadVal ->
|
||||
|
||||
-229
@@ -1,229 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Cyrus SASL-like library
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 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(cyrsasl).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start_link/0, register_mechanism/3, listmech/1,
|
||||
server_new/7, server_start/3, server_step/2,
|
||||
get_mech/1, format_error/2]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
-record(sasl_mechanism,
|
||||
{mechanism = <<"">> :: mechanism() | '$1',
|
||||
module :: atom(),
|
||||
password_type = plain :: password_type() | '$2'}).
|
||||
|
||||
-type(mechanism() :: binary()).
|
||||
-type(mechanisms() :: [mechanism(),...]).
|
||||
-type(password_type() :: plain | digest | scram).
|
||||
-type sasl_property() :: {username, binary()} |
|
||||
{authzid, binary()} |
|
||||
{mechanism, binary()} |
|
||||
{auth_module, atom()}.
|
||||
-type sasl_return() :: {ok, [sasl_property()]} |
|
||||
{ok, [sasl_property()], binary()} |
|
||||
{continue, binary(), sasl_state()} |
|
||||
{error, atom(), binary()}.
|
||||
|
||||
-type(sasl_mechanism() :: #sasl_mechanism{}).
|
||||
-type error_reason() :: cyrsasl_digest:error_reason() |
|
||||
cyrsasl_oauth:error_reason() |
|
||||
cyrsasl_plain:error_reason() |
|
||||
cyrsasl_scram:error_reason() |
|
||||
unsupported_mechanism | nodeprep_failed |
|
||||
empty_username | aborted.
|
||||
-record(sasl_state,
|
||||
{
|
||||
service,
|
||||
myname,
|
||||
realm,
|
||||
get_password,
|
||||
check_password,
|
||||
check_password_digest,
|
||||
mech_name = <<"">>,
|
||||
mech_mod,
|
||||
mech_state
|
||||
}).
|
||||
-type sasl_state() :: #sasl_state{}.
|
||||
-export_type([mechanism/0, mechanisms/0, sasl_mechanism/0, error_reason/0,
|
||||
sasl_state/0, sasl_return/0, sasl_property/0]).
|
||||
|
||||
-callback start(list()) -> any().
|
||||
-callback stop() -> any().
|
||||
-callback mech_new(binary(), fun(), fun(), fun()) -> any().
|
||||
-callback mech_step(any(), binary()) -> sasl_return().
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
init([]) ->
|
||||
ets:new(sasl_mechanism,
|
||||
[named_table, public,
|
||||
{keypos, #sasl_mechanism.mechanism}]),
|
||||
cyrsasl_plain:start([]),
|
||||
cyrsasl_digest:start([]),
|
||||
cyrsasl_scram:start([]),
|
||||
cyrsasl_anonymous:start([]),
|
||||
cyrsasl_oauth:start([]),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
cyrsasl_plain:stop(),
|
||||
cyrsasl_digest:stop(),
|
||||
cyrsasl_scram:stop(),
|
||||
cyrsasl_anonymous:stop(),
|
||||
cyrsasl_oauth:stop().
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
-spec format_error(mechanism() | sasl_state(), error_reason()) -> {atom(), binary()}.
|
||||
format_error(_, unsupported_mechanism) ->
|
||||
{'invalid-mechanism', <<"Unsupported mechanism">>};
|
||||
format_error(_, nodeprep_failed) ->
|
||||
{'bad-protocol', <<"Nodeprep failed">>};
|
||||
format_error(_, empty_username) ->
|
||||
{'bad-protocol', <<"Empty username">>};
|
||||
format_error(_, aborted) ->
|
||||
{'aborted', <<"Aborted">>};
|
||||
format_error(#sasl_state{mech_mod = Mod}, Reason) ->
|
||||
Mod:format_error(Reason);
|
||||
format_error(Mech, Reason) ->
|
||||
case ets:lookup(sasl_mechanism, Mech) of
|
||||
[#sasl_mechanism{module = Mod}] ->
|
||||
Mod:format_error(Reason);
|
||||
[] ->
|
||||
{'invalid-mechanism', <<"Unsupported mechanism">>}
|
||||
end.
|
||||
|
||||
-spec register_mechanism(Mechanim :: mechanism(), Module :: module(),
|
||||
PasswordType :: password_type()) -> any().
|
||||
|
||||
register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism, module = Module,
|
||||
password_type = PasswordType}).
|
||||
|
||||
check_credentials(_State, Props) ->
|
||||
User = proplists:get_value(authzid, Props, <<>>),
|
||||
case jid:nodeprep(User) of
|
||||
error -> {error, nodeprep_failed};
|
||||
<<"">> -> {error, empty_username};
|
||||
_LUser -> ok
|
||||
end.
|
||||
|
||||
-spec listmech(Host ::binary()) -> Mechanisms::mechanisms().
|
||||
|
||||
listmech(Host) ->
|
||||
ets:select(sasl_mechanism,
|
||||
[{#sasl_mechanism{mechanism = '$1',
|
||||
password_type = '$2', _ = '_'},
|
||||
case catch ejabberd_auth:store_type(Host) of
|
||||
external -> [{'==', '$2', plain}];
|
||||
scram -> [{'/=', '$2', digest}];
|
||||
{'EXIT', {undef, [{Module, store_type, []} | _]}} ->
|
||||
?WARNING_MSG("~p doesn't implement the function store_type/0",
|
||||
[Module]),
|
||||
[];
|
||||
_Else -> []
|
||||
end,
|
||||
['$1']}]).
|
||||
|
||||
-spec server_new(binary(), binary(), binary(), term(),
|
||||
fun(), fun(), fun()) -> sasl_state().
|
||||
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
||||
GetPassword, CheckPassword, CheckPasswordDigest) ->
|
||||
#sasl_state{service = Service, myname = ServerFQDN,
|
||||
realm = UserRealm, get_password = GetPassword,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest = CheckPasswordDigest}.
|
||||
|
||||
-spec server_start(sasl_state(), mechanism(), binary()) -> sasl_return().
|
||||
server_start(State, Mech, ClientIn) ->
|
||||
case lists:member(Mech,
|
||||
listmech(State#sasl_state.myname))
|
||||
of
|
||||
true ->
|
||||
case ets:lookup(sasl_mechanism, Mech) of
|
||||
[#sasl_mechanism{module = Module}] ->
|
||||
{ok, MechState} =
|
||||
Module:mech_new(State#sasl_state.myname,
|
||||
State#sasl_state.get_password,
|
||||
State#sasl_state.check_password,
|
||||
State#sasl_state.check_password_digest),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_name = Mech,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ -> {error, unsupported_mechanism, <<"">>}
|
||||
end;
|
||||
false -> {error, unsupported_mechanism, <<"">>}
|
||||
end.
|
||||
|
||||
-spec server_step(sasl_state(), binary()) -> sasl_return().
|
||||
server_step(State, ClientIn) ->
|
||||
Module = State#sasl_state.mech_mod,
|
||||
MechState = State#sasl_state.mech_state,
|
||||
case Module:mech_step(MechState, ClientIn) of
|
||||
{ok, Props} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok -> {ok, Props};
|
||||
{error, Error} -> {error, Error, <<"">>}
|
||||
end;
|
||||
{ok, Props, ServerOut} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok -> {ok, Props, ServerOut};
|
||||
{error, Error} -> {error, Error, <<"">>}
|
||||
end;
|
||||
{continue, ServerOut, NewMechState} ->
|
||||
{continue, ServerOut, State#sasl_state{mech_state = NewMechState}};
|
||||
{error, Error, Username} ->
|
||||
{error, Error, Username};
|
||||
{error, Error} ->
|
||||
{error, Error, <<"">>}
|
||||
end.
|
||||
|
||||
-spec get_mech(sasl_state()) -> binary().
|
||||
get_mech(#sasl_state{mech_name = Mech}) ->
|
||||
Mech.
|
||||
@@ -1,50 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_anonymous.erl
|
||||
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Purpose : ANONYMOUS SASL mechanism
|
||||
%%% See http://www.ietf.org/internet-drafts/draft-ietf-sasl-anon-05.txt
|
||||
%%% Created : 23 Aug 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 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(cyrsasl_anonymous).
|
||||
|
||||
-protocol({xep, 175, '1.2'}).
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {server = <<"">> :: binary()}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"ANONYMOUS">>, ?MODULE, plain).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{server = Host}}.
|
||||
|
||||
mech_step(#state{}, _ClientIn) ->
|
||||
User = iolist_to_binary([randoms:get_string(),
|
||||
integer_to_binary(p1_time_compat:unique_integer([positive]))]),
|
||||
{ok, [{username, User},
|
||||
{authzid, User},
|
||||
{auth_module, ejabberd_auth_anonymous}]}.
|
||||
@@ -1,270 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_digest.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : DIGEST-MD5 SASL mechanism
|
||||
%%% Created : 11 Mar 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 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(cyrsasl_digest).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2,
|
||||
parse/1, format_error/1, opt_type/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-type get_password_fun() :: fun((binary()) -> {false, any()} |
|
||||
{binary(), atom()}).
|
||||
-type check_password_fun() :: fun((binary(), binary(), binary(), binary(),
|
||||
fun((binary()) -> binary())) ->
|
||||
{boolean(), any()} |
|
||||
false).
|
||||
-type error_reason() :: parser_failed | invalid_digest_uri |
|
||||
not_authorized | unexpected_response.
|
||||
-export_type([error_reason/0]).
|
||||
|
||||
-record(state, {step = 1 :: 1 | 3 | 5,
|
||||
nonce = <<"">> :: binary(),
|
||||
username = <<"">> :: binary(),
|
||||
authzid = <<"">> :: binary(),
|
||||
get_password :: get_password_fun(),
|
||||
check_password :: check_password_fun(),
|
||||
auth_module :: atom(),
|
||||
host = <<"">> :: binary(),
|
||||
hostfqdn = [] :: [binary()]}).
|
||||
|
||||
start(_Opts) ->
|
||||
Fqdn = get_local_fqdn(),
|
||||
?DEBUG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
|
||||
[Fqdn]),
|
||||
cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
|
||||
digest).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
-spec format_error(error_reason()) -> {atom(), binary()}.
|
||||
format_error(parser_failed) ->
|
||||
{'bad-protocol', <<"Response decoding failed">>};
|
||||
format_error(invalid_digest_uri) ->
|
||||
{'bad-protocol', <<"Invalid digest URI">>};
|
||||
format_error(not_authorized) ->
|
||||
{'not-authorized', <<"Invalid username or password">>};
|
||||
format_error(unexpected_response) ->
|
||||
{'bad-protocol', <<"Unexpected response">>}.
|
||||
|
||||
mech_new(Host, GetPassword, _CheckPassword,
|
||||
CheckPasswordDigest) ->
|
||||
{ok,
|
||||
#state{step = 1, nonce = randoms:get_string(),
|
||||
host = Host, hostfqdn = get_local_fqdn(),
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPasswordDigest}}.
|
||||
|
||||
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
{continue,
|
||||
<<"nonce=\"", Nonce/binary,
|
||||
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess">>,
|
||||
State#state{step = 3}};
|
||||
mech_step(#state{step = 3, nonce = Nonce} = State,
|
||||
ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
bad -> {error, parser_failed};
|
||||
KeyVals ->
|
||||
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
|
||||
case is_digesturi_valid(DigestURI, State#state.host,
|
||||
State#state.hostfqdn)
|
||||
of
|
||||
false ->
|
||||
?DEBUG("User login not authorized because digest-uri "
|
||||
"seems invalid: ~p (checking for Host "
|
||||
"~p, FQDN ~p)",
|
||||
[DigestURI, State#state.host, State#state.hostfqdn]),
|
||||
{error, invalid_digest_uri, UserName};
|
||||
true ->
|
||||
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} -> {error, not_authorized, UserName};
|
||||
{Passwd, AuthModule} ->
|
||||
case (State#state.check_password)(UserName, UserName, <<"">>,
|
||||
proplists:get_value(<<"response">>, KeyVals, <<>>),
|
||||
fun (PW) ->
|
||||
response(KeyVals,
|
||||
UserName,
|
||||
PW,
|
||||
Nonce,
|
||||
AuthzId,
|
||||
<<"AUTHENTICATE">>)
|
||||
end)
|
||||
of
|
||||
{true, _} ->
|
||||
RspAuth = response(KeyVals, UserName, Passwd, Nonce,
|
||||
AuthzId, <<"">>),
|
||||
{continue, <<"rspauth=", RspAuth/binary>>,
|
||||
State#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
false -> {error, not_authorized, UserName};
|
||||
{false, _} -> {error, not_authorized, UserName}
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
mech_step(#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName, authzid = AuthzId},
|
||||
<<"">>) ->
|
||||
{ok,
|
||||
[{username, UserName}, {authzid, case AuthzId of
|
||||
<<"">> -> UserName;
|
||||
_ -> AuthzId
|
||||
end
|
||||
},
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(A, B) ->
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
|
||||
{error, unexpected_response}.
|
||||
|
||||
parse(S) -> parse1(binary_to_list(S), "", []).
|
||||
|
||||
parse1([$= | Cs], S, Ts) ->
|
||||
parse2(Cs, lists:reverse(S), "", Ts);
|
||||
parse1([$, | Cs], [], Ts) -> parse1(Cs, [], Ts);
|
||||
parse1([$\s | Cs], [], Ts) -> parse1(Cs, [], Ts);
|
||||
parse1([C | Cs], S, Ts) -> parse1(Cs, [C | S], Ts);
|
||||
parse1([], [], T) -> lists:reverse(T);
|
||||
parse1([], _S, _T) -> bad.
|
||||
|
||||
parse2([$" | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, Val, Ts);
|
||||
parse2([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse2([], _, _, _) -> bad.
|
||||
|
||||
parse3([$" | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse3([$\\, C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([C | Cs], Key, Val, Ts) ->
|
||||
parse3(Cs, Key, [C | Val], Ts);
|
||||
parse3([], _, _, _) -> bad.
|
||||
|
||||
parse4([$, | Cs], Key, Val, Ts) ->
|
||||
parse1(Cs, "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]);
|
||||
parse4([$\s | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse4([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse4([], Key, Val, Ts) ->
|
||||
%% @doc Check if the digest-uri is valid.
|
||||
%% RFC-2831 allows to provide the IP address in Host,
|
||||
%% however ejabberd doesn't allow that.
|
||||
%% If the service (for example jabber.example.org)
|
||||
%% is provided by several hosts (being one of them server3.example.org),
|
||||
%% then acceptable digest-uris would be:
|
||||
%% xmpp/server3.example.org/jabber.example.org, xmpp/server3.example.org and
|
||||
%% xmpp/jabber.example.org
|
||||
%% The last version is not actually allowed by the RFC, but implemented by popular clients
|
||||
parse1([], "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]).
|
||||
|
||||
is_digesturi_valid(DigestURICase, JabberDomain,
|
||||
JabberFQDN) ->
|
||||
DigestURI = stringprep:tolower(DigestURICase),
|
||||
case catch str:tokens(DigestURI, <<"/">>) of
|
||||
[<<"xmpp">>, Host] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(Host == JabberDomain) or IsHostFqdn;
|
||||
[<<"xmpp">>, Host, ServName] ->
|
||||
IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
|
||||
(ServName == JabberDomain) and IsHostFqdn;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_host_fqdn(_Host, []) ->
|
||||
false;
|
||||
is_host_fqdn(Host, [Fqdn | _FqdnTail]) when Host == Fqdn ->
|
||||
true;
|
||||
is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
|
||||
is_host_fqdn(Host, FqdnTail).
|
||||
|
||||
get_local_fqdn() ->
|
||||
case ejabberd_config:get_option(fqdn) of
|
||||
undefined ->
|
||||
{ok, Hostname} = inet:gethostname(),
|
||||
{ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname),
|
||||
[list_to_binary(Fqdn)];
|
||||
Fqdn ->
|
||||
Fqdn
|
||||
end.
|
||||
|
||||
hex(S) ->
|
||||
str:to_hexlist(S).
|
||||
|
||||
proplists_get_bin_value(Key, Pairs, Default) ->
|
||||
case proplists:get_value(Key, Pairs, Default) of
|
||||
L when is_list(L) ->
|
||||
list_to_binary(L);
|
||||
L2 ->
|
||||
L2
|
||||
end.
|
||||
|
||||
response(KeyVals, User, Passwd, Nonce, AuthzId,
|
||||
A2Prefix) ->
|
||||
Realm = proplists_get_bin_value(<<"realm">>, KeyVals, <<>>),
|
||||
CNonce = proplists_get_bin_value(<<"cnonce">>, KeyVals, <<>>),
|
||||
DigestURI = proplists_get_bin_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
NC = proplists_get_bin_value(<<"nc">>, KeyVals, <<>>),
|
||||
QOP = proplists_get_bin_value(<<"qop">>, KeyVals, <<>>),
|
||||
MD5Hash = erlang:md5(<<User/binary, ":", Realm/binary, ":",
|
||||
Passwd/binary>>),
|
||||
A1 = case AuthzId of
|
||||
<<"">> ->
|
||||
<<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary>>;
|
||||
_ ->
|
||||
<<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary, ":",
|
||||
AuthzId/binary>>
|
||||
end,
|
||||
A2 = case QOP of
|
||||
<<"auth">> ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary>>;
|
||||
_ ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary,
|
||||
":00000000000000000000000000000000">>
|
||||
end,
|
||||
T = <<(hex((erlang:md5(A1))))/binary, ":", Nonce/binary,
|
||||
":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
|
||||
":", (hex((erlang:md5(A2))))/binary>>,
|
||||
hex((erlang:md5(T))).
|
||||
|
||||
-spec opt_type(fqdn) -> fun((binary() | [binary()]) -> [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(fqdn) ->
|
||||
fun(FQDN) when is_binary(FQDN) ->
|
||||
[FQDN];
|
||||
(FQDNs) when is_list(FQDNs) ->
|
||||
[iolist_to_binary(FQDN) || FQDN <- FQDNs]
|
||||
end;
|
||||
opt_type(_) -> [fqdn].
|
||||
@@ -1,104 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_oauth.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : X-OAUTH2 SASL mechanism
|
||||
%%% Created : 17 Sep 2015 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 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(cyrsasl_oauth).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1, format_error/1]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {host}).
|
||||
-type error_reason() :: parser_failed | not_authorized.
|
||||
-export_type([error_reason/0]).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"X-OAUTH2">>, ?MODULE, plain).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
-spec format_error(error_reason()) -> {atom(), binary()}.
|
||||
format_error(parser_failed) ->
|
||||
{'bad-protocol', <<"Response decoding failed">>};
|
||||
format_error(not_authorized) ->
|
||||
{'not-authorized', <<"Invalid token">>}.
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{host = Host}}.
|
||||
|
||||
mech_step(State, ClientIn) ->
|
||||
case prepare(ClientIn) of
|
||||
[AuthzId, User, Token] ->
|
||||
case ejabberd_oauth:check_token(
|
||||
User, State#state.host, [<<"sasl_auth">>], Token) of
|
||||
true ->
|
||||
{ok,
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, ejabberd_oauth}]};
|
||||
_ ->
|
||||
{error, not_authorized, User}
|
||||
end;
|
||||
_ -> {error, parser_failed}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
[<<"">>, UserMaybeDomain, Token] ->
|
||||
case parse_domain(UserMaybeDomain) of
|
||||
%% <NUL>login@domain<NUL>pwd
|
||||
[User, _Domain] -> [User, User, Token];
|
||||
%% <NUL>login<NUL>pwd
|
||||
[User] -> [User, User, Token]
|
||||
end;
|
||||
%% login@domain<NUL>login<NUL>pwd
|
||||
[AuthzId, User, Token] ->
|
||||
case parse_domain(AuthzId) of
|
||||
%% login@domain<NUL>login<NUL>pwd
|
||||
[AuthzUser, _Domain] -> [AuthzUser, User, Token];
|
||||
%% login<NUL>login<NUL>pwd
|
||||
[AuthzUser] -> [AuthzUser, User, Token]
|
||||
end;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
parse(S) -> parse1(binary_to_list(S), "", []).
|
||||
|
||||
parse1([0 | Cs], S, T) ->
|
||||
parse1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
|
||||
parse1([C | Cs], S, T) -> parse1(Cs, [C | S], T);
|
||||
%parse1([], [], T) ->
|
||||
% lists:reverse(T);
|
||||
parse1([], S, T) ->
|
||||
lists:reverse([list_to_binary(lists:reverse(S)) | T]).
|
||||
|
||||
parse_domain(S) -> parse_domain1(binary_to_list(S), "", []).
|
||||
|
||||
parse_domain1([$@ | Cs], S, T) ->
|
||||
parse_domain1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
|
||||
parse_domain1([C | Cs], S, T) ->
|
||||
parse_domain1(Cs, [C | S], T);
|
||||
parse_domain1([], S, T) ->
|
||||
lists:reverse([list_to_binary(lists:reverse(S)) | T]).
|
||||
@@ -1,94 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_plain.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : PLAIN SASL mechanism
|
||||
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 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(cyrsasl_plain).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1, format_error/1]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {check_password}).
|
||||
-type error_reason() :: parser_failed | not_authorized.
|
||||
-export_type([error_reason/0]).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"PLAIN">>, ?MODULE, plain).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
-spec format_error(error_reason()) -> {atom(), binary()}.
|
||||
format_error(parser_failed) ->
|
||||
{'bad-protocol', <<"Response decoding failed">>};
|
||||
format_error(not_authorized) ->
|
||||
{'not-authorized', <<"Invalid username or password">>}.
|
||||
|
||||
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
|
||||
{ok, #state{check_password = CheckPassword}}.
|
||||
|
||||
mech_step(State, ClientIn) ->
|
||||
case prepare(ClientIn) of
|
||||
[AuthzId, User, Password] ->
|
||||
case (State#state.check_password)(User, AuthzId, Password) of
|
||||
{true, AuthModule} ->
|
||||
{ok,
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
_ -> {error, not_authorized, User}
|
||||
end;
|
||||
_ -> {error, parser_failed}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
[<<"">>, UserMaybeDomain, Password] ->
|
||||
case parse_domain(UserMaybeDomain) of
|
||||
%% <NUL>login@domain<NUL>pwd
|
||||
[User, _Domain] -> [User, User, Password];
|
||||
%% <NUL>login<NUL>pwd
|
||||
[User] -> [User, User, Password]
|
||||
end;
|
||||
[AuthzId, User, Password] ->
|
||||
case parse_domain(AuthzId) of
|
||||
%% login@domain<NUL>login<NUL>pwd
|
||||
[AuthzUser, _Domain] -> [AuthzUser, User, Password];
|
||||
%% login<NUL>login<NUL>pwd
|
||||
[AuthzUser] -> [AuthzUser, User, Password]
|
||||
end;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
parse(S) ->
|
||||
binary:split(S, <<0>>, [global]).
|
||||
|
||||
parse_domain(S) -> parse_domain1(binary_to_list(S), "", []).
|
||||
|
||||
parse_domain1([$@ | Cs], S, T) ->
|
||||
parse_domain1(Cs, "", [list_to_binary(lists:reverse(S)) | T]);
|
||||
parse_domain1([C | Cs], S, T) ->
|
||||
parse_domain1(Cs, [C | S], T);
|
||||
parse_domain1([], S, T) ->
|
||||
lists:reverse([list_to_binary(lists:reverse(S)) | T]).
|
||||
@@ -1,249 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_scram.erl
|
||||
%%% Author : Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%% Purpose : SASL SCRAM authentication
|
||||
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 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(cyrsasl_scram).
|
||||
|
||||
-author('stephen.roettger@googlemail.com').
|
||||
|
||||
-protocol({rfc, 5802}).
|
||||
|
||||
-export([start/1, stop/0, mech_new/4, mech_step/2, format_error/1]).
|
||||
|
||||
-include("scram.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state,
|
||||
{step = 2 :: 2 | 4,
|
||||
stored_key = <<"">> :: binary(),
|
||||
server_key = <<"">> :: binary(),
|
||||
username = <<"">> :: binary(),
|
||||
auth_module :: module(),
|
||||
get_password :: fun((binary()) ->
|
||||
{false | ejabberd_auth:password(), module()}),
|
||||
auth_message = <<"">> :: binary(),
|
||||
client_nonce = <<"">> :: binary(),
|
||||
server_nonce = <<"">> :: binary()}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
-define(NONCE_LENGTH, 16).
|
||||
|
||||
-type error_reason() :: unsupported_extension | bad_username |
|
||||
not_authorized | saslprep_failed |
|
||||
parser_failed | bad_attribute |
|
||||
nonce_mismatch | bad_channel_binding.
|
||||
|
||||
-export_type([error_reason/0]).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism(<<"SCRAM-SHA-1">>, ?MODULE,
|
||||
scram).
|
||||
|
||||
stop() -> ok.
|
||||
|
||||
-spec format_error(error_reason()) -> {atom(), binary()}.
|
||||
format_error(unsupported_extension) ->
|
||||
{'bad-protocol', <<"Unsupported extension">>};
|
||||
format_error(bad_username) ->
|
||||
{'invalid-authzid', <<"Malformed username">>};
|
||||
format_error(not_authorized) ->
|
||||
{'not-authorized', <<"Invalid username or password">>};
|
||||
format_error(saslprep_failed) ->
|
||||
{'not-authorized', <<"SASLprep failed">>};
|
||||
format_error(parser_failed) ->
|
||||
{'bad-protocol', <<"Response decoding failed">>};
|
||||
format_error(bad_attribute) ->
|
||||
{'bad-protocol', <<"Malformed or unexpected attribute">>};
|
||||
format_error(nonce_mismatch) ->
|
||||
{'bad-protocol', <<"Nonce mismatch">>};
|
||||
format_error(bad_channel_binding) ->
|
||||
{'bad-protocol', <<"Invalid channel binding">>}.
|
||||
|
||||
mech_new(_Host, GetPassword, _CheckPassword,
|
||||
_CheckPasswordDigest) ->
|
||||
{ok, #state{step = 2, get_password = GetPassword}}.
|
||||
|
||||
mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
case re:split(ClientIn, <<",">>, [{return, binary}]) of
|
||||
[_CBind, _AuthorizationIdentity, _UserNameAttribute, _ClientNonceAttribute, ExtensionAttribute | _]
|
||||
when ExtensionAttribute /= <<"">> ->
|
||||
{error, unsupported_extension};
|
||||
[CBind, _AuthorizationIdentity, UserNameAttribute, ClientNonceAttribute | _]
|
||||
when (CBind == <<"y">>) or (CBind == <<"n">>) ->
|
||||
case parse_attribute(UserNameAttribute) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
{_, EscapedUserName} ->
|
||||
case unescape_username(EscapedUserName) of
|
||||
error -> {error, bad_username};
|
||||
UserName ->
|
||||
case parse_attribute(ClientNonceAttribute) of
|
||||
{$r, ClientNonce} ->
|
||||
{Pass, AuthModule} = (State#state.get_password)(UserName),
|
||||
LPass = if is_binary(Pass) -> jid:resourceprep(Pass);
|
||||
true -> Pass
|
||||
end,
|
||||
if Pass == false ->
|
||||
{error, not_authorized, UserName};
|
||||
LPass == error ->
|
||||
{error, saslprep_failed, UserName};
|
||||
true ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} =
|
||||
if is_record(Pass, scram) ->
|
||||
{base64:decode(Pass#scram.storedkey),
|
||||
base64:decode(Pass#scram.serverkey),
|
||||
base64:decode(Pass#scram.salt),
|
||||
Pass#scram.iterationcount};
|
||||
true ->
|
||||
TempSalt =
|
||||
randoms:bytes(?SALT_LENGTH),
|
||||
SaltedPassword =
|
||||
scram:salted_password(Pass,
|
||||
TempSalt,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT),
|
||||
{scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
scram:server_key(SaltedPassword),
|
||||
TempSalt,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT}
|
||||
end,
|
||||
ClientFirstMessageBare =
|
||||
str:substr(ClientIn,
|
||||
str:str(ClientIn, <<"n=">>)),
|
||||
ServerNonce =
|
||||
base64:encode(randoms:bytes(?NONCE_LENGTH)),
|
||||
ServerFirstMessage =
|
||||
iolist_to_binary(
|
||||
["r=",
|
||||
ClientNonce,
|
||||
ServerNonce,
|
||||
",", "s=",
|
||||
base64:encode(Salt),
|
||||
",", "i=",
|
||||
integer_to_list(IterationCount)]),
|
||||
{continue, ServerFirstMessage,
|
||||
State#state{step = 4, stored_key = StoredKey,
|
||||
server_key = ServerKey,
|
||||
auth_module = AuthModule,
|
||||
auth_message =
|
||||
<<ClientFirstMessageBare/binary,
|
||||
",", ServerFirstMessage/binary>>,
|
||||
client_nonce = ClientNonce,
|
||||
server_nonce = ServerNonce,
|
||||
username = UserName}}
|
||||
end;
|
||||
_ -> {error, bad_attribute}
|
||||
end
|
||||
end
|
||||
end;
|
||||
_Else -> {error, parser_failed}
|
||||
end;
|
||||
mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
case str:tokens(ClientIn, <<",">>) of
|
||||
[GS2ChannelBindingAttribute, NonceAttribute,
|
||||
ClientProofAttribute] ->
|
||||
case parse_attribute(GS2ChannelBindingAttribute) of
|
||||
{$c, CVal} ->
|
||||
ChannelBindingSupport = try binary:first(base64:decode(CVal))
|
||||
catch _:badarg -> 0
|
||||
end,
|
||||
if (ChannelBindingSupport == $n)
|
||||
or (ChannelBindingSupport == $y) ->
|
||||
Nonce = <<(State#state.client_nonce)/binary,
|
||||
(State#state.server_nonce)/binary>>,
|
||||
case parse_attribute(NonceAttribute) of
|
||||
{$r, CompareNonce} when CompareNonce == Nonce ->
|
||||
case parse_attribute(ClientProofAttribute) of
|
||||
{$p, ClientProofB64} ->
|
||||
ClientProof = try base64:decode(ClientProofB64)
|
||||
catch _:badarg -> <<>>
|
||||
end,
|
||||
AuthMessage = iolist_to_binary(
|
||||
[State#state.auth_message,
|
||||
",",
|
||||
str:substr(ClientIn, 1,
|
||||
str:str(ClientIn, <<",p=">>)
|
||||
- 1)]),
|
||||
ClientSignature =
|
||||
scram:client_signature(State#state.stored_key,
|
||||
AuthMessage),
|
||||
ClientKey = scram:client_key(ClientProof,
|
||||
ClientSignature),
|
||||
CompareStoredKey = scram:stored_key(ClientKey),
|
||||
if CompareStoredKey == State#state.stored_key ->
|
||||
ServerSignature =
|
||||
scram:server_signature(State#state.server_key,
|
||||
AuthMessage),
|
||||
{ok, [{username, State#state.username},
|
||||
{auth_module, State#state.auth_module},
|
||||
{authzid, State#state.username}],
|
||||
<<"v=",
|
||||
(base64:encode(ServerSignature))/binary>>};
|
||||
true -> {error, not_authorized, State#state.username}
|
||||
end;
|
||||
_ -> {error, bad_attribute}
|
||||
end;
|
||||
{$r, _} -> {error, nonce_mismatch};
|
||||
_ -> {error, bad_attribute}
|
||||
end;
|
||||
true -> {error, bad_channel_binding}
|
||||
end;
|
||||
_ -> {error, bad_attribute}
|
||||
end;
|
||||
_ -> {error, parser_failed}
|
||||
end.
|
||||
|
||||
parse_attribute(<<Name, $=, Val/binary>>) when Val /= <<>> ->
|
||||
case is_alpha(Name) of
|
||||
true -> {Name, Val};
|
||||
false -> {error, bad_attribute}
|
||||
end;
|
||||
parse_attribute(_) ->
|
||||
{error, bad_attribute}.
|
||||
|
||||
unescape_username(<<"">>) -> <<"">>;
|
||||
unescape_username(EscapedUsername) ->
|
||||
Pos = str:str(EscapedUsername, <<"=">>),
|
||||
if Pos == 0 -> EscapedUsername;
|
||||
true ->
|
||||
Start = str:substr(EscapedUsername, 1, Pos - 1),
|
||||
End = str:substr(EscapedUsername, Pos),
|
||||
EndLen = byte_size(End),
|
||||
if EndLen < 3 -> error;
|
||||
true ->
|
||||
case str:substr(End, 1, 3) of
|
||||
<<"=2C">> ->
|
||||
<<Start/binary, ",",
|
||||
(unescape_username(str:substr(End, 4)))/binary>>;
|
||||
<<"=3D">> ->
|
||||
<<Start/binary, "=",
|
||||
(unescape_username(str:substr(End, 4)))/binary>>;
|
||||
_Else -> error
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
is_alpha(Char) when Char >= $a, Char =< $z -> true;
|
||||
is_alpha(Char) when Char >= $A, Char =< $Z -> true;
|
||||
is_alpha(_) -> false.
|
||||
+1
-1
@@ -143,7 +143,7 @@ exit_or_halt(Reason, StartFlag) ->
|
||||
end.
|
||||
|
||||
sleep(N) ->
|
||||
timer:sleep(randoms:uniform(N)).
|
||||
timer:sleep(p1_rand:uniform(N)).
|
||||
|
||||
get_module_file(App, Mod) ->
|
||||
BaseName = atom_to_list(Mod),
|
||||
|
||||
+19
-14
@@ -3,7 +3,8 @@
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
%% ejabberdctl commands
|
||||
-export([get_certificates/1,
|
||||
-export([get_commands_spec/0,
|
||||
get_certificates/1,
|
||||
renew_certificates/0,
|
||||
list_certificates/1,
|
||||
revoke_certificate/1]).
|
||||
@@ -119,7 +120,7 @@ get_commands_spec() ->
|
||||
args = [],
|
||||
result = {certificates, string}},
|
||||
#ejabberd_commands{name = list_certificates, tags = [acme],
|
||||
desc = "Lists all curently handled certificates and "
|
||||
desc = "Lists all currently handled certificates and "
|
||||
"their respective domains in {plain|verbose} format",
|
||||
module = ?MODULE, function = list_certificates,
|
||||
args_desc = ["Whether to print the whole certificate "
|
||||
@@ -150,7 +151,8 @@ get_certificates(Domains) ->
|
||||
throw:Throw ->
|
||||
Throw;
|
||||
E:R ->
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
|
||||
{error, get_certificates}
|
||||
end;
|
||||
false ->
|
||||
@@ -242,7 +244,8 @@ get_certificate(CAUrl, DomainName, PrivateKey) ->
|
||||
throw:Throw ->
|
||||
Throw;
|
||||
E:R ->
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
|
||||
{error, DomainName, get_certificate}
|
||||
end.
|
||||
|
||||
@@ -381,7 +384,8 @@ renew_certificates() ->
|
||||
throw:Throw ->
|
||||
Throw;
|
||||
E:R ->
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
|
||||
{error, get_certificates}
|
||||
end.
|
||||
|
||||
@@ -446,7 +450,8 @@ list_certificates(Verbose) ->
|
||||
throw:Throw ->
|
||||
Throw;
|
||||
E:R ->
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
|
||||
{error, list_certificates}
|
||||
end;
|
||||
false ->
|
||||
@@ -488,7 +493,8 @@ format_certificate(DataCert, Verbose) ->
|
||||
end
|
||||
catch
|
||||
E:R ->
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
|
||||
fail_format_certificate(DomainName)
|
||||
end.
|
||||
|
||||
@@ -613,7 +619,8 @@ revoke_certificates(DomainOrFile) ->
|
||||
throw:Throw ->
|
||||
Throw;
|
||||
E:R ->
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
|
||||
{error, revoke_certificate}
|
||||
end.
|
||||
|
||||
@@ -1117,7 +1124,8 @@ save_certificate({ok, DomainName, Cert}) ->
|
||||
throw:Throw ->
|
||||
Throw;
|
||||
E:R ->
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]),
|
||||
{error, DomainName, saving}
|
||||
end.
|
||||
|
||||
@@ -1214,15 +1222,12 @@ generate_key() ->
|
||||
%% Option Parsing Code
|
||||
%%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
-spec opt_type(acme) -> fun((acme_config()) -> (acme_config()));
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(acme) ->
|
||||
fun(L) ->
|
||||
lists:map(
|
||||
fun({ca_url, URL}) ->
|
||||
URL1 = binary_to_list(URL),
|
||||
{ok, _} = http_uri:parse(URL1),
|
||||
{ca_url, URL1};
|
||||
{ca_url, misc:try_url(URL)};
|
||||
({contact, Contact}) ->
|
||||
[<<_, _/binary>>, <<_, _/binary>>] =
|
||||
binary:split(Contact, <<":">>),
|
||||
|
||||
@@ -275,6 +275,8 @@ get_commands_spec() ->
|
||||
|
||||
#ejabberd_commands{name = import_prosody, tags = [mnesia, sql, riak],
|
||||
desc = "Import data from Prosody",
|
||||
longdesc = "Note: this method requires ejabberd compiled with optional tools support "
|
||||
"and package must provide optional luerl dependency.",
|
||||
module = prosody2ejabberd, function = from_dir,
|
||||
args_desc = ["Full path to the Prosody data directory"],
|
||||
args_example = ["/var/lib/prosody/datadump/"],
|
||||
|
||||
+6
-16
@@ -139,7 +139,7 @@ handle_cast(config_reloaded, #state{host_modules = HostModules} = State) ->
|
||||
NewModules = auth_modules(Host),
|
||||
start(Host, NewModules -- OldModules),
|
||||
stop(Host, OldModules -- NewModules),
|
||||
reload(Host, lists_intersection(OldModules, NewModules)),
|
||||
reload(Host, misc:intersection(OldModules, NewModules)),
|
||||
maps:put(Host, NewModules, Acc)
|
||||
end, HostModules, ejabberd_config:get_myhosts()),
|
||||
init_cache(NewHostModules),
|
||||
@@ -245,7 +245,9 @@ check_password_with_authmodule(User, AuthzId, Server, Password, Digest, DigestGe
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, false, auth_modules(LServer))
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec set_password(binary(), binary(), password()) -> ok | {error, atom()}.
|
||||
@@ -693,7 +695,7 @@ password_to_scram(Password) ->
|
||||
password_to_scram(#scram{} = Password, _IterationCount) ->
|
||||
Password;
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = randoms:bytes(?SALT_LENGTH),
|
||||
Salt = p1_rand:bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
@@ -834,12 +836,6 @@ validate_credentials(User, Server, Password) ->
|
||||
end
|
||||
end.
|
||||
|
||||
lists_intersection(L1, L2) ->
|
||||
lists:filter(
|
||||
fun(E) ->
|
||||
lists:member(E, L2)
|
||||
end, L1).
|
||||
|
||||
import_info() ->
|
||||
[{<<"users">>, 3}].
|
||||
|
||||
@@ -855,13 +851,7 @@ import(Server, {sql, _}, riak, <<"users">>, Fields) ->
|
||||
import(_LServer, {sql, _}, sql, <<"users">>, _) ->
|
||||
ok.
|
||||
|
||||
-spec opt_type(auth_method) -> fun((atom() | [atom()]) -> [atom()]);
|
||||
(auth_password_format) -> fun((plain | scram) -> plain | scram);
|
||||
(auth_use_cache) -> fun((boolean()) -> boolean());
|
||||
(auth_cache_missed) -> fun((boolean()) -> boolean());
|
||||
(auth_cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(auth_cache_size) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(auth_method) ->
|
||||
fun (V) when is_list(V) ->
|
||||
lists:map(fun(M) -> ejabberd_config:v_db(?MODULE, M) end, V);
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
use_cache/1,
|
||||
allow_anonymous/1,
|
||||
is_sasl_anonymous_enabled/1,
|
||||
is_login_anonymous_enabled/1,
|
||||
@@ -60,6 +61,9 @@ stop(Host) ->
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
|
||||
?MODULE, unregister_connection, 100).
|
||||
|
||||
use_cache(_) ->
|
||||
false.
|
||||
|
||||
%% Return true if anonymous is allowed for host or false otherwise
|
||||
allow_anonymous(Host) ->
|
||||
lists:member(?MODULE, ejabberd_auth:auth_modules(Host)).
|
||||
@@ -173,10 +177,7 @@ plain_password_required(_) ->
|
||||
store_type(_) ->
|
||||
external.
|
||||
|
||||
-spec opt_type(allow_multiple_connection) -> fun((boolean()) -> boolean());
|
||||
(anonymous_protocol) -> fun((sasl_anon | login_anon | both) ->
|
||||
sasl_anon | login_anon | both);
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(allow_multiple_connections) ->
|
||||
fun (V) when is_boolean(V) -> V end;
|
||||
opt_type(anonymous_protocol) ->
|
||||
|
||||
@@ -118,9 +118,11 @@ opt_type(extauth_instances) ->
|
||||
fun (V) when is_integer(V), V > 0 -> V end;
|
||||
opt_type(extauth_program) ->
|
||||
fun (V) -> binary_to_list(iolist_to_binary(V)) end;
|
||||
opt_type(extauth_pool_name) ->
|
||||
fun (V) -> iolist_to_binary(V) end;
|
||||
opt_type(extauth_pool_size) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
opt_type(_) ->
|
||||
[extauth_program, extauth_pool_size,
|
||||
[extauth_program, extauth_pool_size, extauth_pool_name,
|
||||
%% Deprecated:
|
||||
extauth_cache, extauth_instances].
|
||||
|
||||
@@ -362,10 +362,7 @@ parse_options(Host) ->
|
||||
sfilter = SearchFilter, lfilter = LocalFilter,
|
||||
dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
|
||||
|
||||
-spec opt_type(ldap_dn_filter) -> fun(([{binary(), binary()}]) ->
|
||||
[{binary(), binary()}]);
|
||||
(ldap_local_filter) -> fun((any()) -> any());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(ldap_dn_filter) ->
|
||||
fun ([{DNF, DNFA}]) ->
|
||||
NewDNFA = case DNFA of
|
||||
|
||||
@@ -82,9 +82,7 @@ get_pam_service(Host) ->
|
||||
get_pam_userinfotype(Host) ->
|
||||
ejabberd_config:get_option({pam_userinfotype, Host}, username).
|
||||
|
||||
-spec opt_type(pam_service) -> fun((binary()) -> binary());
|
||||
(pam_userinfotype) -> fun((username | jid) -> username | jid);
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(pam_service) -> fun iolist_to_binary/1;
|
||||
opt_type(pam_userinfotype) ->
|
||||
fun (username) -> username;
|
||||
|
||||
@@ -324,8 +324,7 @@ export(_Server) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
-spec opt_type(pgsql_users_number_estimate) -> fun((boolean()) -> boolean());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(pgsql_users_number_estimate) ->
|
||||
fun (V) when is_boolean(V) -> V end;
|
||||
opt_type(_) -> [pgsql_users_number_estimate].
|
||||
|
||||
+71
-101
@@ -23,20 +23,18 @@
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_bosh).
|
||||
|
||||
-behaviour(xmpp_socket).
|
||||
-behaviour(p1_fsm).
|
||||
-protocol({xep, 124, '1.11'}).
|
||||
-protocol({xep, 206, '1.4'}).
|
||||
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% API
|
||||
-export([start/2, start/3, start_link/3]).
|
||||
|
||||
-export([send_xml/2, setopts/2, controlling_process/2,
|
||||
migrate/3, become_controller/2,
|
||||
reset_stream/1, change_shaper/2, monitor/1, close/1,
|
||||
reset_stream/1, change_shaper/2, close/1,
|
||||
sockname/1, peername/1, process_request/3, send/2,
|
||||
change_controller/2]).
|
||||
get_transport/1, get_owner/1]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1, wait_for_session/2, wait_for_session/3,
|
||||
@@ -88,7 +86,7 @@
|
||||
sid = <<"">> :: binary(),
|
||||
el_ibuf :: p1_queue:queue(),
|
||||
el_obuf :: p1_queue:queue(),
|
||||
shaper_state = none :: shaper:shaper(),
|
||||
shaper_state = none :: ejabberd_shaper:shaper(),
|
||||
c2s_pid :: pid() | undefined,
|
||||
xmpp_ver = <<"">> :: binary(),
|
||||
inactivity_timer :: reference() | undefined,
|
||||
@@ -167,22 +165,12 @@ setopts({http_bind, FsmRef, _IP}, Opts) ->
|
||||
|
||||
controlling_process(_Socket, _Pid) -> ok.
|
||||
|
||||
become_controller(FsmRef, C2SPid) ->
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{become_controller, C2SPid}).
|
||||
|
||||
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
|
||||
become_controller(FsmRef, C2SPid).
|
||||
|
||||
reset_stream({http_bind, _FsmRef, _IP} = Socket) ->
|
||||
Socket.
|
||||
|
||||
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
|
||||
p1_fsm:send_all_state_event(FsmRef, {change_shaper, Shaper}).
|
||||
|
||||
monitor({http_bind, FsmRef, _IP}) ->
|
||||
erlang:monitor(process, FsmRef).
|
||||
|
||||
close({http_bind, FsmRef, _IP}) ->
|
||||
catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
close).
|
||||
@@ -191,10 +179,11 @@ sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
|
||||
|
||||
peername({http_bind, _FsmRef, IP}) -> {ok, IP}.
|
||||
|
||||
migrate(FsmRef, Node, After) when node(FsmRef) == node() ->
|
||||
catch erlang:send_after(After, FsmRef, {migrate, Node});
|
||||
migrate(_FsmRef, _Node, _After) ->
|
||||
ok.
|
||||
get_transport(_Socket) ->
|
||||
http_bind.
|
||||
|
||||
get_owner({http_bind, FsmRef, _IP}) ->
|
||||
FsmRef.
|
||||
|
||||
process_request(Data, IP, Type) ->
|
||||
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
|
||||
@@ -281,7 +270,7 @@ init([#body{attrs = Attrs}, IP, SID]) ->
|
||||
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
|
||||
Opts2 = [{xml_socket, true} | Opts1],
|
||||
Shaper = none,
|
||||
ShaperState = shaper:new(Shaper),
|
||||
ShaperState = ejabberd_shaper:new(Shaper),
|
||||
Socket = make_socket(self(), IP),
|
||||
XMPPVer = get_attr('xmpp:version', Attrs),
|
||||
XMPPDomain = get_attr(to, Attrs),
|
||||
@@ -295,30 +284,26 @@ init([#body{attrs = Attrs}, IP, SID]) ->
|
||||
buf_new(XMPPDomain)),
|
||||
Opts2}
|
||||
end,
|
||||
xmpp_socket:start(ejabberd_c2s, ?MODULE, Socket,
|
||||
[{receiver, self()}|Opts]),
|
||||
Inactivity = gen_mod:get_module_opt(XMPPDomain,
|
||||
mod_bosh, max_inactivity),
|
||||
MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat),
|
||||
ShapedReceivers = buf_new(XMPPDomain, ?MAX_SHAPED_REQUESTS_QUEUE_LEN),
|
||||
State = #state{host = XMPPDomain, sid = SID, ip = IP,
|
||||
xmpp_ver = XMPPVer, el_ibuf = InBuf,
|
||||
max_concat = MaxConcat, el_obuf = buf_new(XMPPDomain),
|
||||
inactivity_timeout = Inactivity,
|
||||
shaped_receivers = ShapedReceivers,
|
||||
shaper_state = ShaperState},
|
||||
NewState = restart_inactivity_timer(State),
|
||||
mod_bosh:open_session(SID, self()),
|
||||
{ok, wait_for_session, NewState};
|
||||
init([StateName, State]) ->
|
||||
mod_bosh:open_session(State#state.sid, self()),
|
||||
case State#state.c2s_pid of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
NewSocket = make_socket(self(), State#state.ip),
|
||||
C2SPid ! {change_socket, NewSocket},
|
||||
NewState = restart_inactivity_timer(State),
|
||||
{ok, StateName, NewState};
|
||||
_ -> {stop, normal}
|
||||
case ejabberd_c2s:start({?MODULE, Socket}, [{receiver, self()}|Opts]) of
|
||||
{ok, C2SPid} ->
|
||||
ejabberd_c2s:accept(C2SPid),
|
||||
Inactivity = gen_mod:get_module_opt(XMPPDomain,
|
||||
mod_bosh, max_inactivity),
|
||||
MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat),
|
||||
ShapedReceivers = buf_new(XMPPDomain, ?MAX_SHAPED_REQUESTS_QUEUE_LEN),
|
||||
State = #state{host = XMPPDomain, sid = SID, ip = IP,
|
||||
xmpp_ver = XMPPVer, el_ibuf = InBuf,
|
||||
max_concat = MaxConcat, el_obuf = buf_new(XMPPDomain),
|
||||
inactivity_timeout = Inactivity,
|
||||
shaped_receivers = ShapedReceivers,
|
||||
shaper_state = ShaperState},
|
||||
NewState = restart_inactivity_timer(State),
|
||||
mod_bosh:open_session(SID, self()),
|
||||
{ok, wait_for_session, NewState};
|
||||
{error, Reason} ->
|
||||
{stop, Reason};
|
||||
ignore ->
|
||||
ignore
|
||||
end.
|
||||
|
||||
wait_for_session(_Event, State) ->
|
||||
@@ -355,7 +340,7 @@ wait_for_session(#body{attrs = Attrs} = Req, From,
|
||||
{'xmlns:stream', ?NS_STREAM}, {from, State#state.host}
|
||||
| Polling]},
|
||||
{ShaperState, _} =
|
||||
shaper:update(State#state.shaper_state, Req#body.size),
|
||||
ejabberd_shaper:update(State#state.shaper_state, Req#body.size),
|
||||
State1 = State#state{wait_timeout = Wait,
|
||||
prev_rid = RID, prev_key = NewKey,
|
||||
prev_poll = PollTime, shaper_state = ShaperState,
|
||||
@@ -365,15 +350,22 @@ wait_for_session(#body{attrs = Attrs} = Req, From,
|
||||
{State3, RespEls} = get_response_els(State2),
|
||||
State4 = stop_inactivity_timer(State3),
|
||||
case RespEls of
|
||||
[] ->
|
||||
State5 = restart_wait_timer(State4),
|
||||
Receivers = gb_trees:insert(RID, {From, Resp},
|
||||
State5#state.receivers),
|
||||
{next_state, active,
|
||||
State5#state{receivers = Receivers}};
|
||||
_ ->
|
||||
reply_next_state(State4, Resp#body{els = RespEls}, RID,
|
||||
From)
|
||||
[{xmlstreamstart, _, _} = El1] ->
|
||||
OutBuf = buf_in([El1], State4#state.el_obuf),
|
||||
State5 = restart_wait_timer(State4),
|
||||
Receivers = gb_trees:insert(RID, {From, Resp},
|
||||
State5#state.receivers),
|
||||
{next_state, active,
|
||||
State5#state{receivers = Receivers, el_obuf = OutBuf}};
|
||||
[] ->
|
||||
State5 = restart_wait_timer(State4),
|
||||
Receivers = gb_trees:insert(RID, {From, Resp},
|
||||
State5#state.receivers),
|
||||
{next_state, active,
|
||||
State5#state{receivers = Receivers}};
|
||||
_ ->
|
||||
reply_next_state(State4, Resp#body{els = RespEls}, RID,
|
||||
From)
|
||||
end;
|
||||
wait_for_session(_Event, _From, State) ->
|
||||
?ERROR_MSG("unexpected sync event in 'wait_for_session': ~p",
|
||||
@@ -393,7 +385,7 @@ active(#body{attrs = Attrs, size = Size} = Req, From,
|
||||
"~p~n** State: ~p",
|
||||
[Req, From, State]),
|
||||
{ShaperState, Pause} =
|
||||
shaper:update(State#state.shaper_state, Size),
|
||||
ejabberd_shaper:update(State#state.shaper_state, Size),
|
||||
State1 = State#state{shaper_state = ShaperState},
|
||||
if Pause > 0 ->
|
||||
TRef = start_shaper_timer(Pause),
|
||||
@@ -404,7 +396,7 @@ active(#body{attrs = Attrs, size = Size} = Req, From,
|
||||
{next_state, active,
|
||||
State2#state{shaped_receivers = Q}}
|
||||
catch error:full ->
|
||||
cancel_timer(TRef),
|
||||
misc:cancel_timer(TRef),
|
||||
RID = get_attr(rid, Attrs),
|
||||
reply_stop(State1,
|
||||
#body{http_reason = <<"Too many requests">>,
|
||||
@@ -518,15 +510,13 @@ active1(#body{attrs = Attrs} = Req, From, State) ->
|
||||
end
|
||||
end.
|
||||
|
||||
handle_event({become_controller, C2SPid}, StateName,
|
||||
handle_event({activate, C2SPid}, StateName,
|
||||
State) ->
|
||||
State1 = route_els(State#state{c2s_pid = C2SPid}),
|
||||
{next_state, StateName, State1};
|
||||
handle_event({change_shaper, Shaper}, StateName,
|
||||
State) ->
|
||||
NewShaperState = shaper:new(Shaper),
|
||||
{next_state, StateName,
|
||||
State#state{shaper_state = NewShaperState}};
|
||||
{next_state, StateName, State#state{shaper_state = Shaper}};
|
||||
handle_event(_Event, StateName, State) ->
|
||||
?ERROR_MSG("unexpected event in '~s': ~p",
|
||||
[StateName, _Event]),
|
||||
@@ -554,7 +544,7 @@ handle_sync_event({send_xml, El}, _From, StateName,
|
||||
State2 = case p1_queue:out(State1#state.shaped_receivers)
|
||||
of
|
||||
{{value, {TRef, From, Body}}, Q} ->
|
||||
cancel_timer(TRef),
|
||||
misc:cancel_timer(TRef),
|
||||
p1_fsm:send_event(self(), {Body, From}),
|
||||
State1#state{shaped_receivers = Q};
|
||||
_ -> State1
|
||||
@@ -574,7 +564,8 @@ handle_sync_event(_Event, _From, StateName, State) ->
|
||||
|
||||
handle_info({timeout, TRef, wait_timeout}, StateName,
|
||||
#state{wait_timer = TRef} = State) ->
|
||||
{next_state, StateName, drop_holding_receiver(State)};
|
||||
State2 = State#state{wait_timer = undefined},
|
||||
{next_state, StateName, drop_holding_receiver(State2)};
|
||||
handle_info({timeout, TRef, inactive}, _StateName,
|
||||
#state{inactivity_timer = TRef} = State) ->
|
||||
{stop, normal, State};
|
||||
@@ -592,24 +583,11 @@ handle_info({timeout, TRef, shaper_timeout}, StateName,
|
||||
{stop, normal, State};
|
||||
_ -> {next_state, StateName, State}
|
||||
end;
|
||||
handle_info({migrate, Node}, StateName, State) ->
|
||||
if Node /= node() ->
|
||||
NewState = bounce_receivers(State, migrated),
|
||||
{migrate, NewState,
|
||||
{Node, ?MODULE, start, [StateName, NewState]}, 0};
|
||||
true -> {next_state, StateName, State}
|
||||
end;
|
||||
handle_info(_Info, StateName, State) ->
|
||||
?ERROR_MSG("unexpected info:~n** Msg: ~p~n** StateName: ~p",
|
||||
[_Info, StateName]),
|
||||
{next_state, StateName, State}.
|
||||
|
||||
terminate({migrated, ClonePid}, _StateName, State) ->
|
||||
?INFO_MSG("Migrating session \"~s\" (c2s_pid = "
|
||||
"~p) to ~p on node ~p",
|
||||
[State#state.sid, State#state.c2s_pid, ClonePid,
|
||||
node(ClonePid)]),
|
||||
mod_bosh:close_session(State#state.sid);
|
||||
terminate(_Reason, _StateName, State) ->
|
||||
mod_bosh:close_session(State#state.sid),
|
||||
case State#state.c2s_pid of
|
||||
@@ -693,7 +671,8 @@ drop_holding_receiver(State, RID) ->
|
||||
State1#state.receivers),
|
||||
State2 = State1#state{receivers = Receivers},
|
||||
do_reply(State2, From, Body, RID);
|
||||
none -> State
|
||||
none ->
|
||||
restart_inactivity_timer(State)
|
||||
end.
|
||||
|
||||
do_reply(State, From, Body, RID) ->
|
||||
@@ -711,7 +690,7 @@ do_reply(State, From, Body, RID) ->
|
||||
Responses2 = gb_trees:insert(RID, Body, Responses1),
|
||||
State#state{responses = Responses2}.
|
||||
|
||||
bounce_receivers(State, Reason) ->
|
||||
bounce_receivers(State, _Reason) ->
|
||||
Receivers = gb_trees:to_list(State#state.receivers),
|
||||
ShapedReceivers = lists:map(fun ({_, From,
|
||||
#body{attrs = Attrs} = Body}) ->
|
||||
@@ -719,18 +698,13 @@ bounce_receivers(State, Reason) ->
|
||||
{RID, {From, Body}}
|
||||
end,
|
||||
p1_queue:to_list(State#state.shaped_receivers)),
|
||||
lists:foldl(fun ({RID, {From, Body}}, AccState) ->
|
||||
NewBody = if Reason == closed ->
|
||||
#body{http_reason =
|
||||
<<"Session closed">>,
|
||||
attrs =
|
||||
[{type, <<"terminate">>},
|
||||
{condition,
|
||||
<<"other-request">>}]};
|
||||
Reason == migrated ->
|
||||
Body#body{http_reason =
|
||||
<<"Session migrated">>}
|
||||
end,
|
||||
lists:foldl(fun ({RID, {From, _Body}}, AccState) ->
|
||||
NewBody = #body{http_reason =
|
||||
<<"Session closed">>,
|
||||
attrs =
|
||||
[{type, <<"terminate">>},
|
||||
{condition,
|
||||
<<"other-request">>}]},
|
||||
do_reply(AccState, From, NewBody, RID)
|
||||
end,
|
||||
State, Receivers ++ ShapedReceivers).
|
||||
@@ -984,7 +958,7 @@ http_error(Status, Reason, Type) ->
|
||||
end,
|
||||
{Status, Reason, ?HEADER(CType), <<"">>}.
|
||||
|
||||
make_sid() -> str:sha(randoms:get_string()).
|
||||
make_sid() -> str:sha(p1_rand:get_string()).
|
||||
|
||||
-compile({no_auto_import, [{min, 2}]}).
|
||||
|
||||
@@ -1037,12 +1011,8 @@ buf_out(Buf, I, Els) ->
|
||||
{empty, _} -> buf_out(Buf, 0, Els)
|
||||
end.
|
||||
|
||||
cancel_timer(TRef) when is_reference(TRef) ->
|
||||
p1_fsm:cancel_timer(TRef);
|
||||
cancel_timer(_) -> false.
|
||||
|
||||
restart_timer(TRef, Timeout, Msg) ->
|
||||
cancel_timer(TRef),
|
||||
misc:cancel_timer(TRef),
|
||||
erlang:start_timer(timer:seconds(Timeout), self(), Msg).
|
||||
|
||||
restart_inactivity_timer(#state{inactivity_timeout =
|
||||
@@ -1059,7 +1029,7 @@ restart_inactivity_timer(#state{inactivity_timer =
|
||||
|
||||
stop_inactivity_timer(#state{inactivity_timer = TRef} =
|
||||
State) ->
|
||||
cancel_timer(TRef),
|
||||
misc:cancel_timer(TRef),
|
||||
State#state{inactivity_timer = undefined}.
|
||||
|
||||
restart_wait_timer(#state{wait_timer = TRef,
|
||||
@@ -1069,13 +1039,13 @@ restart_wait_timer(#state{wait_timer = TRef,
|
||||
State#state{wait_timer = NewTRef}.
|
||||
|
||||
stop_wait_timer(#state{wait_timer = TRef} = State) ->
|
||||
cancel_timer(TRef), State#state{wait_timer = undefined}.
|
||||
misc:cancel_timer(TRef), State#state{wait_timer = undefined}.
|
||||
|
||||
start_shaper_timer(Timeout) ->
|
||||
erlang:start_timer(Timeout, self(), shaper_timeout).
|
||||
|
||||
make_random_jid(Host) ->
|
||||
User = randoms:get_string(),
|
||||
jid:make(User, Host, randoms:get_string()).
|
||||
User = p1_rand:get_string(),
|
||||
jid:make(User, Host, p1_rand:get_string()).
|
||||
|
||||
make_socket(Pid, IP) -> {http_bind, Pid, IP}.
|
||||
|
||||
+87
-110
@@ -22,20 +22,20 @@
|
||||
-module(ejabberd_c2s).
|
||||
-behaviour(xmpp_stream_in).
|
||||
-behaviour(ejabberd_config).
|
||||
-behaviour(xmpp_socket).
|
||||
-behaviour(ejabberd_listener).
|
||||
|
||||
-protocol({rfc, 6121}).
|
||||
|
||||
%% xmpp_socket callbacks
|
||||
-export([start/2, start_link/2, socket_type/0]).
|
||||
%% ejabberd_listener callbacks
|
||||
-export([start/2, start_link/2, accept/1, listen_opt_type/1, listen_options/0]).
|
||||
%% ejabberd_config callbacks
|
||||
-export([opt_type/1, listen_opt_type/1, transform_listen_option/2]).
|
||||
-export([opt_type/1, transform_listen_option/2]).
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
|
||||
-export([tls_options/1, tls_required/1, tls_enabled/1,
|
||||
compress_methods/1, bind/2, sasl_mechanisms/2,
|
||||
get_password_fun/1, check_password_fun/1, check_password_digest_fun/1,
|
||||
get_password_fun/2, check_password_fun/2, check_password_digest_fun/2,
|
||||
unauthenticated_stream_features/1, authenticated_stream_features/1,
|
||||
handle_stream_start/2, handle_stream_end/2,
|
||||
handle_unauthenticated_packet/2, handle_authenticated_packet/2,
|
||||
@@ -61,26 +61,18 @@
|
||||
-export_type([state/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% xmpp_socket API
|
||||
%%% ejabberd_listener API
|
||||
%%%===================================================================
|
||||
start(SockData, Opts) ->
|
||||
case proplists:get_value(supervisor, Opts, true) of
|
||||
true ->
|
||||
case supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]) of
|
||||
{ok, undefined} -> ignore;
|
||||
Res -> Res
|
||||
end;
|
||||
_ ->
|
||||
xmpp_stream_in:start(?MODULE, [SockData, Opts],
|
||||
ejabberd_config:fsm_limit_opts(Opts))
|
||||
end.
|
||||
xmpp_stream_in:start(?MODULE, [SockData, Opts],
|
||||
ejabberd_config:fsm_limit_opts(Opts)).
|
||||
|
||||
start_link(SockData, Opts) ->
|
||||
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
|
||||
ejabberd_config:fsm_limit_opts(Opts)).
|
||||
|
||||
socket_type() ->
|
||||
xml_stream.
|
||||
accept(Ref) ->
|
||||
xmpp_stream_in:accept(Ref).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Common API
|
||||
@@ -104,7 +96,7 @@ get_presence(Ref) ->
|
||||
set_presence(Ref, Pres) ->
|
||||
call(Ref, {set_presence, Pres}, 1000).
|
||||
|
||||
-spec resend_presence(pid()) -> ok.
|
||||
-spec resend_presence(pid()) -> boolean().
|
||||
resend_presence(Pid) ->
|
||||
resend_presence(Pid, undefined).
|
||||
|
||||
@@ -272,7 +264,6 @@ process_terminated(#{sid := SID, socket := Socket,
|
||||
State1 = case maps:is_key(pres_last, State) of
|
||||
true ->
|
||||
Pres = #presence{type = unavailable,
|
||||
status = xmpp:mk_text(Status),
|
||||
from = JID,
|
||||
to = jid:remove_resource(JID)},
|
||||
ejabberd_sm:close_session_unset_presence(SID, U, S, R,
|
||||
@@ -339,9 +330,6 @@ tls_options(#{lserver := LServer, tls_options := DefaultOpts,
|
||||
tls_required(#{tls_required := TLSRequired}) ->
|
||||
TLSRequired.
|
||||
|
||||
tls_verify(#{tls_verify := TLSVerify}) ->
|
||||
TLSVerify.
|
||||
|
||||
tls_enabled(#{tls_enabled := TLSEnabled,
|
||||
tls_required := TLSRequired,
|
||||
tls_verify := TLSVerify}) ->
|
||||
@@ -358,25 +346,41 @@ unauthenticated_stream_features(#{lserver := LServer}) ->
|
||||
authenticated_stream_features(#{lserver := LServer}) ->
|
||||
ejabberd_hooks:run_fold(c2s_post_auth_features, LServer, [], [LServer]).
|
||||
|
||||
sasl_mechanisms(Mechs, #{lserver := LServer}) ->
|
||||
sasl_mechanisms(Mechs, #{lserver := LServer} = State) ->
|
||||
Type = ejabberd_auth:store_type(LServer),
|
||||
Mechs1 = ejabberd_config:get_option({disable_sasl_mechanisms, LServer}, []),
|
||||
Mechs2 = case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer) of
|
||||
true -> Mechs1;
|
||||
false -> [<<"ANONYMOUS">>|Mechs1]
|
||||
end,
|
||||
Mechs -- Mechs2.
|
||||
%% I re-created it from cyrsasl ets magic, but I think it's wrong
|
||||
%% TODO: need to check before 18.09 release
|
||||
lists:filter(
|
||||
fun(<<"ANONYMOUS">>) ->
|
||||
ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer);
|
||||
(<<"DIGEST-MD5">>) -> Type == plain;
|
||||
(<<"SCRAM-SHA-1">>) -> Type /= external;
|
||||
(<<"PLAIN">>) -> true;
|
||||
(<<"X-OAUTH2">>) -> true;
|
||||
(<<"EXTERNAL">>) -> maps:get(tls_verify, State, false);
|
||||
(_) -> false
|
||||
end, Mechs -- Mechs1).
|
||||
|
||||
get_password_fun(#{lserver := LServer}) ->
|
||||
get_password_fun(_Mech, #{lserver := LServer}) ->
|
||||
fun(U) ->
|
||||
ejabberd_auth:get_password_with_authmodule(U, LServer)
|
||||
end.
|
||||
|
||||
check_password_fun(#{lserver := LServer}) ->
|
||||
check_password_fun(<<"X-OAUTH2">>, #{lserver := LServer}) ->
|
||||
fun(User, _AuthzId, Token) ->
|
||||
case ejabberd_oauth:check_token(
|
||||
User, LServer, [<<"sasl_auth">>], Token) of
|
||||
true -> {true, ejabberd_oauth};
|
||||
_ -> {false, ejabberd_oauth}
|
||||
end
|
||||
end;
|
||||
check_password_fun(_Mech, #{lserver := LServer}) ->
|
||||
fun(U, AuthzId, P) ->
|
||||
ejabberd_auth:check_password_with_authmodule(U, AuthzId, LServer, P)
|
||||
end.
|
||||
|
||||
check_password_digest_fun(#{lserver := LServer}) ->
|
||||
check_password_digest_fun(_Mech, #{lserver := LServer}) ->
|
||||
fun(U, AuthzId, P, D, DG) ->
|
||||
ejabberd_auth:check_password_with_authmodule(U, AuthzId, LServer, P, D, DG)
|
||||
end.
|
||||
@@ -404,8 +408,8 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
|
||||
{ok, State2};
|
||||
deny ->
|
||||
ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
|
||||
?INFO_MSG("(~s) Forbidden c2s session for ~s",
|
||||
[xmpp_socket:pp(Socket), jid:encode(JID)]),
|
||||
?WARNING_MSG("(~s) Forbidden c2s session for ~s",
|
||||
[xmpp_socket:pp(Socket), jid:encode(JID)]),
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
{error, xmpp:err_not_allowed(Txt, Lang), State}
|
||||
end
|
||||
@@ -440,12 +444,12 @@ handle_auth_success(User, Mech, AuthModule,
|
||||
handle_auth_failure(User, Mech, Reason,
|
||||
#{socket := Socket,
|
||||
ip := IP, lserver := LServer} = State) ->
|
||||
?INFO_MSG("(~s) Failed c2s ~s authentication ~sfrom ~s: ~s",
|
||||
[xmpp_socket:pp(Socket), Mech,
|
||||
if User /= <<"">> -> ["for ", User, "@", LServer, " "];
|
||||
true -> ""
|
||||
end,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
|
||||
?WARNING_MSG("(~s) Failed c2s ~s authentication ~sfrom ~s: ~s",
|
||||
[xmpp_socket:pp(Socket), Mech,
|
||||
if User /= <<"">> -> ["for ", User, "@", LServer, " "];
|
||||
true -> ""
|
||||
end,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
|
||||
ejabberd_hooks:run_fold(c2s_auth_result, LServer, State, [false, User]).
|
||||
|
||||
handle_unbinded_packet(Pkt, #{lserver := LServer} = State) ->
|
||||
@@ -881,7 +885,7 @@ bounce_message_queue() ->
|
||||
-spec new_uniq_id() -> binary().
|
||||
new_uniq_id() ->
|
||||
iolist_to_binary(
|
||||
[randoms:get_string(),
|
||||
[p1_rand:get_string(),
|
||||
integer_to_binary(p1_time_compat:unique_integer([positive]))]).
|
||||
|
||||
-spec get_conn_type(state()) -> c2s | c2s_tls | c2s_compressed | websocket |
|
||||
@@ -920,7 +924,7 @@ change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer,
|
||||
Shaper = acl:access_matches(ShaperName,
|
||||
#{usr => jid:split(JID), ip => IP},
|
||||
LServer),
|
||||
xmpp_stream_in:change_shaper(State, Shaper).
|
||||
xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)).
|
||||
|
||||
-spec format_reason(state(), term()) -> binary().
|
||||
format_reason(#{stop_reason := Reason}, _) ->
|
||||
@@ -934,7 +938,7 @@ format_reason(_, {shutdown, _}) ->
|
||||
format_reason(_, _) ->
|
||||
<<"internal server error">>.
|
||||
|
||||
-spec get_certfile(binary()) -> file:filename_all().
|
||||
-spec get_certfile(binary()) -> file:filename_all() | undefined.
|
||||
get_certfile(LServer) ->
|
||||
case ejabberd_pkix:get_certfile(LServer) of
|
||||
{ok, CertFile} ->
|
||||
@@ -948,15 +952,7 @@ get_certfile(LServer) ->
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-type resource_conflict() :: setresource | closeold | closenew | acceptnew.
|
||||
-spec opt_type(c2s_ciphers) -> fun((binary()) -> binary());
|
||||
(c2s_dhfile) -> fun((binary()) -> binary());
|
||||
(c2s_cafile) -> fun((binary()) -> binary());
|
||||
(c2s_protocol_options) -> fun(([binary()]) -> binary());
|
||||
(c2s_tls_compression) -> fun((boolean()) -> boolean());
|
||||
(resource_conflict) -> fun((resource_conflict()) -> resource_conflict());
|
||||
(disable_sasl_mechanisms) -> fun((binary() | [binary()]) -> [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(c2s_ciphers) -> fun iolist_to_binary/1;
|
||||
opt_type(c2s_dhfile) -> fun misc:try_read_file/1;
|
||||
opt_type(c2s_cafile) -> fun misc:try_read_file/1;
|
||||
@@ -982,73 +978,54 @@ opt_type(_) ->
|
||||
c2s_protocol_options, c2s_tls_compression, resource_conflict,
|
||||
disable_sasl_mechanisms].
|
||||
|
||||
-spec listen_opt_type(access) -> fun((any()) -> any());
|
||||
(shaper) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(cafile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(tls) -> fun((boolean()) -> boolean());
|
||||
(starttls) -> fun((boolean()) -> boolean());
|
||||
(tls_verify) -> fun((boolean()) -> boolean());
|
||||
(zlib) -> fun((boolean()) -> boolean());
|
||||
(supervisor) -> fun((boolean()) -> boolean());
|
||||
(max_stanza_size) -> fun((timeout()) -> timeout());
|
||||
(max_fsm_queue) -> fun((timeout()) -> timeout());
|
||||
(stream_management) -> fun((boolean()) -> boolean());
|
||||
(inet) -> fun((boolean()) -> boolean());
|
||||
(inet6) -> fun((boolean()) -> boolean());
|
||||
(backlog) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(certfile = Opt) ->
|
||||
fun(S) ->
|
||||
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
|
||||
"'certfiles' global option instead", [Opt, ?MODULE]),
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
ok = ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) -> opt_type(c2s_ciphers);
|
||||
listen_opt_type(dhfile) -> opt_type(c2s_dhfile);
|
||||
listen_opt_type(cafile) -> opt_type(c2s_cafile);
|
||||
listen_opt_type(protocol_options) -> opt_type(c2s_protocol_options);
|
||||
listen_opt_type(tls_compression) -> opt_type(c2s_tls_compression);
|
||||
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(starttls) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(starttls_required) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(tls_verify) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(zlib) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(max_stanza_size) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
listen_opt_type(zlib) ->
|
||||
fun(true) ->
|
||||
ejabberd:start_app(ezlib),
|
||||
true;
|
||||
(false) ->
|
||||
false
|
||||
end;
|
||||
listen_opt_type(max_fsm_queue) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(stream_management) ->
|
||||
?ERROR_MSG("listening option 'stream_management' is ignored: "
|
||||
"use mod_stream_mgmt module", []),
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(backlog) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(accept_interval) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
fun(B) when is_boolean(B) ->
|
||||
?ERROR_MSG("Listening option 'stream_management' is ignored: "
|
||||
"use mod_stream_mgmt module", []),
|
||||
B
|
||||
end;
|
||||
listen_opt_type(O) ->
|
||||
StreamOpts = mod_stream_mgmt:mod_options(ejabberd_config:get_myname()),
|
||||
case lists:keyfind(O, 1, StreamOpts) of
|
||||
false ->
|
||||
[access, shaper, certfile, ciphers, dhfile, cafile,
|
||||
protocol_options, tls, tls_compression, starttls,
|
||||
starttls_required, tls_verify, zlib, max_fsm_queue,
|
||||
backlog, inet, inet6, accept_interval];
|
||||
_ ->
|
||||
?ERROR_MSG("Listening option '~s' is ignored: use '~s' "
|
||||
"option from mod_stream_mgmt module", [O, O]),
|
||||
mod_stream_mgmt:mod_opt_type(O)
|
||||
MgmtOpts = mod_stream_mgmt:mod_options(ejabberd_config:get_myname()),
|
||||
case lists:keymember(O, 1, MgmtOpts) of
|
||||
true ->
|
||||
fun(V) ->
|
||||
?ERROR_MSG("Listening option '~s' is ignored: use '~s' "
|
||||
"option from mod_stream_mgmt module", [O, O]),
|
||||
(mod_stream_mgmt:mod_opt_type(O))(V)
|
||||
end
|
||||
end.
|
||||
|
||||
listen_options() ->
|
||||
[{access, all},
|
||||
{shaper, none},
|
||||
{certfile, undefined},
|
||||
{ciphers, undefined},
|
||||
{dhfile, undefined},
|
||||
{cafile, undefined},
|
||||
{protocol_options, undefined},
|
||||
{tls, false},
|
||||
{tls_compression, false},
|
||||
{starttls, false},
|
||||
{starttls_required, false},
|
||||
{tls_verify, false},
|
||||
{zlib, false},
|
||||
{max_stanza_size, infinity},
|
||||
{max_fsm_queue, 5000}|
|
||||
mod_stream_mgmt:mod_options(ejabberd_config:get_myname())].
|
||||
|
||||
@@ -89,7 +89,7 @@ mk_field(Type, Var, Value) ->
|
||||
create_captcha(SID, From, To, Lang, Limiter, Args) ->
|
||||
case create_image(Limiter) of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = <<(randoms:get_string())/binary>>,
|
||||
Id = <<(p1_rand:get_string())/binary>>,
|
||||
JID = jid:encode(From),
|
||||
CID = <<"sha1+", (str:sha(Image))/binary, "@bob.xmpp.org">>,
|
||||
Data = #bob_data{cid = CID, 'max-age' = 0, type = Type,
|
||||
@@ -120,7 +120,7 @@ create_captcha(SID, From, To, Lang, Limiter, Args) ->
|
||||
create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) ->
|
||||
case create_image(Limiter) of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = <<(randoms:get_string())/binary>>,
|
||||
Id = <<(p1_rand:get_string())/binary>>,
|
||||
CID = <<"sha1+", (str:sha(Image))/binary, "@bob.xmpp.org">>,
|
||||
Data = #bob_data{cid = CID, 'max-age' = 0, type = Type, data = Image},
|
||||
HelpTxt = translate:translate(Lang,
|
||||
@@ -230,7 +230,6 @@ process_iq(#iq{type = get, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_iq(#iq{lang = Lang} = IQ) ->
|
||||
?INFO_MSG("IQ = ~p", [IQ]),
|
||||
Txt = <<"No module is handling this query">>,
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
|
||||
|
||||
@@ -376,7 +375,7 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
create_image() -> create_image(undefined).
|
||||
|
||||
create_image(Limiter) ->
|
||||
Key = str:substr(randoms:get_string(), 1, 6),
|
||||
Key = str:substr(p1_rand:get_string(), 1, 6),
|
||||
create_image(Limiter, Key).
|
||||
|
||||
create_image(Limiter, Key) ->
|
||||
@@ -517,11 +516,7 @@ recv_data(Port, TRef, Buf) ->
|
||||
end.
|
||||
|
||||
return(Port, TRef, Result) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive {timeout, TRef, _} -> ok after 0 -> ok end;
|
||||
_ -> ok
|
||||
end,
|
||||
misc:cancel_timer(TRef),
|
||||
catch port_close(Port),
|
||||
Result.
|
||||
|
||||
@@ -561,7 +556,7 @@ check_captcha(Id, ProvidedKey) ->
|
||||
case ets:lookup(captcha, Id) of
|
||||
[#captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}] ->
|
||||
ets:delete(captcha, Id),
|
||||
erlang:cancel_timer(Tref),
|
||||
misc:cancel_timer(Tref),
|
||||
if ValidKey == ProvidedKey ->
|
||||
callback(captcha_succeed, Pid, Args),
|
||||
captcha_valid;
|
||||
@@ -595,10 +590,7 @@ callback(_, _, _) ->
|
||||
now_priority() ->
|
||||
-p1_time_compat:system_time(micro_seconds).
|
||||
|
||||
-spec opt_type(captcha_cmd) -> fun((binary()) -> binary());
|
||||
(captcha_host) -> fun((binary()) -> binary());
|
||||
(captcha_limit) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(captcha_cmd) ->
|
||||
fun (FileName) ->
|
||||
F = iolist_to_binary(FileName), if F /= <<"">> -> F end
|
||||
|
||||
@@ -619,9 +619,7 @@ permission_addon() ->
|
||||
[{access, ejabberd_config:get_option(commands_admin_access, none)}],
|
||||
{get_exposed_commands(), []}}}].
|
||||
|
||||
-spec opt_type(commands_admin_access) -> fun((any()) -> any());
|
||||
(commands) -> fun((list()) -> list());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
|
||||
opt_type(commands) ->
|
||||
fun(V) when is_list(V) -> V end;
|
||||
|
||||
@@ -459,7 +459,8 @@ generate_md_output(File, RegExp, Languages) ->
|
||||
end, Cmds2),
|
||||
Cmds4 = [maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3],
|
||||
Langs = binary:split(Languages, <<",">>, [global]),
|
||||
Header = <<"---\ntitle: Administration API reference\nbodyclass: nocomment\n---">>,
|
||||
Header = <<"---\ntitle: Administration API reference\ntoc: true\nmenu: Administration API\norder: 40\n"
|
||||
"// Autogenerated with 'ejabberdctl gen_markdown_doc_for_commands'\n---">>,
|
||||
Out = lists:map(fun(C) -> gen_doc(C, false, Langs) end, Cmds4),
|
||||
{ok, Fh} = file:open(File, [write]),
|
||||
io:format(Fh, "~s~s", [Header, Out]),
|
||||
|
||||
+66
-30
@@ -34,7 +34,7 @@
|
||||
prepare_opt_val/4, transform_options/1, collect_options/1,
|
||||
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
|
||||
env_binary_to_list/2, opt_type/1, may_hide_data/1,
|
||||
is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1,
|
||||
is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1, v_host/1, v_hosts/1,
|
||||
default_db/1, default_db/2, default_ram_db/1, default_ram_db/2,
|
||||
default_queue_type/1, queue_dir/0, fsm_limit_opts/1,
|
||||
use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1,
|
||||
@@ -57,9 +57,10 @@
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
-include_lib("kernel/include/inet.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-callback opt_type(atom()) -> function() | [atom()].
|
||||
-callback opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
-type bad_option() :: invalid_option | unknown_option.
|
||||
|
||||
-spec start() -> ok | {error, bad_option()}.
|
||||
@@ -75,7 +76,7 @@ start() ->
|
||||
UnixTime = p1_time_compat:system_time(seconds),
|
||||
SharedKey = case erlang:get_cookie() of
|
||||
nocookie ->
|
||||
str:sha(randoms:get_string());
|
||||
str:sha(p1_rand:get_string());
|
||||
Cookie ->
|
||||
str:sha(misc:atom_to_binary(Cookie))
|
||||
end,
|
||||
@@ -115,7 +116,7 @@ start(Hosts, Opts) ->
|
||||
UnixTime = p1_time_compat:system_time(seconds),
|
||||
SharedKey = case erlang:get_cookie() of
|
||||
nocookie ->
|
||||
str:sha(randoms:get_string());
|
||||
str:sha(p1_rand:get_string());
|
||||
Cookie ->
|
||||
str:sha(misc:atom_to_binary(Cookie))
|
||||
end,
|
||||
@@ -782,8 +783,24 @@ set_opts(State) ->
|
||||
fun(#local_config{key = Key, value = Val}) ->
|
||||
{Key, Val}
|
||||
end, Opts)),
|
||||
set_fqdn(),
|
||||
set_log_level().
|
||||
|
||||
set_fqdn() ->
|
||||
FQDNs = case get_option(fqdn, []) of
|
||||
[] ->
|
||||
{ok, Hostname} = inet:gethostname(),
|
||||
case inet:gethostbyname(Hostname) of
|
||||
{ok, #hostent{h_name = FQDN}} ->
|
||||
[iolist_to_binary(FQDN)];
|
||||
{error, _} ->
|
||||
[]
|
||||
end;
|
||||
Domains ->
|
||||
Domains
|
||||
end,
|
||||
xmpp:set_config([{fqdn, FQDNs}]).
|
||||
|
||||
set_log_level() ->
|
||||
Level = get_option(loglevel, 4),
|
||||
ejabberd_logger:set(Level).
|
||||
@@ -954,6 +971,33 @@ v_dbs_mods(Mod) ->
|
||||
(atom_to_binary(M, utf8))/binary>>, utf8)
|
||||
end, v_dbs(Mod)).
|
||||
|
||||
-spec v_host(binary()) -> binary().
|
||||
v_host(Host) ->
|
||||
hd(v_hosts([Host])).
|
||||
|
||||
-spec v_hosts([binary()]) -> [binary()].
|
||||
v_hosts(Hosts) ->
|
||||
ServerHosts = get_myhosts(),
|
||||
lists:foldr(
|
||||
fun(Host, Acc) ->
|
||||
case lists:member(Host, ServerHosts) of
|
||||
true ->
|
||||
?ERROR_MSG("Failed to reuse route ~s because it's "
|
||||
"already registered on a virtual host",
|
||||
[Host]),
|
||||
erlang:error(badarg);
|
||||
false ->
|
||||
case lists:member(Host, Acc) of
|
||||
true ->
|
||||
?ERROR_MSG("Host ~s is defined multiple times",
|
||||
[Host]),
|
||||
erlang:error(badarg);
|
||||
false ->
|
||||
[Host|Acc]
|
||||
end
|
||||
end
|
||||
end, [], Hosts).
|
||||
|
||||
-spec default_db(module()) -> atom().
|
||||
default_db(Module) ->
|
||||
default_db(global, Module).
|
||||
@@ -1034,14 +1078,15 @@ validate_opts(#state{opts = Opts} = State, ModOpts) ->
|
||||
NewVal ->
|
||||
In#local_config{value = NewVal}
|
||||
catch {invalid_syntax, Error} ->
|
||||
?ERROR_MSG("Invalid value '~p' for "
|
||||
"option '~s': ~s",
|
||||
[Val, Opt, Error]),
|
||||
?ERROR_MSG("Invalid value for "
|
||||
"option '~s' (~s): ~s",
|
||||
[Opt, Error,
|
||||
misc:format_val({yaml, Val})]),
|
||||
erlang:error(invalid_option);
|
||||
_:_ ->
|
||||
?ERROR_MSG("Invalid value '~p' for "
|
||||
"option '~s'",
|
||||
[Val, Opt]),
|
||||
_:R when R /= undef ->
|
||||
?ERROR_MSG("Invalid value for "
|
||||
"option '~s': ~s",
|
||||
[Opt, misc:format_val({yaml, Val})]),
|
||||
erlang:error(invalid_option)
|
||||
end;
|
||||
_ ->
|
||||
@@ -1085,7 +1130,7 @@ get_version() ->
|
||||
-spec get_myhosts() -> [binary()].
|
||||
|
||||
get_myhosts() ->
|
||||
get_option(hosts).
|
||||
get_option(hosts, [<<"localhost">>]).
|
||||
|
||||
-spec get_myname() -> binary().
|
||||
|
||||
@@ -1245,7 +1290,7 @@ transform_terms(Terms) ->
|
||||
ejabberd_s2s,
|
||||
ejabberd_listener,
|
||||
ejabberd_sql_sup,
|
||||
shaper,
|
||||
ejabberd_shaper,
|
||||
ejabberd_s2s_out,
|
||||
acl,
|
||||
ejabberd_config],
|
||||
@@ -1394,22 +1439,7 @@ emit_deprecation_warning(Module, NewModule) ->
|
||||
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
|
||||
MegaSecs * 1000000 + Secs.
|
||||
|
||||
-spec opt_type(hide_sensitive_log_data) -> fun((boolean()) -> boolean());
|
||||
(hosts) -> fun(([binary()]) -> [binary()]);
|
||||
(language) -> fun((binary()) -> binary());
|
||||
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
|
||||
(default_db) -> fun((atom()) -> atom());
|
||||
(default_ram_db) -> fun((atom()) -> atom());
|
||||
(loglevel) -> fun((0..5) -> 0..5);
|
||||
(queue_dir) -> fun((binary()) -> binary());
|
||||
(queue_type) -> fun((ram | file) -> ram | file);
|
||||
(use_cache) -> fun((boolean()) -> boolean());
|
||||
(cache_size) -> fun((timeout()) -> timeout());
|
||||
(cache_missed) -> fun((boolean()) -> boolean());
|
||||
(cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(shared_key) -> fun((binary()) -> binary());
|
||||
(node_start) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(hide_sensitive_log_data) ->
|
||||
fun (H) when is_boolean(H) -> H end;
|
||||
opt_type(hosts) ->
|
||||
@@ -1452,10 +1482,16 @@ opt_type(node_start) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
opt_type(validate_stream) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(fqdn) ->
|
||||
fun(Domain) when is_binary(Domain) ->
|
||||
[Domain];
|
||||
(Domains) ->
|
||||
[iolist_to_binary(Domain) || Domain <- Domains]
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[hide_sensitive_log_data, hosts, language, max_fsm_queue,
|
||||
default_db, default_ram_db, queue_type, queue_dir, loglevel,
|
||||
use_cache, cache_size, cache_missed, cache_life_time,
|
||||
use_cache, cache_size, cache_missed, cache_life_time, fqdn,
|
||||
shared_key, node_start, validate_stream, negotiation_timeout].
|
||||
|
||||
-spec may_hide_data(any()) -> any().
|
||||
|
||||
@@ -168,15 +168,15 @@ process(["status"], _Version) ->
|
||||
{InternalStatus, ProvidedStatus} = init:get_status(),
|
||||
print("The node ~p is ~p with status: ~p~n",
|
||||
[node(), InternalStatus, ProvidedStatus]),
|
||||
case lists:keysearch(ejabberd, 1, application:which_applications()) of
|
||||
case lists:keymember(ejabberd, 1, application:which_applications()) of
|
||||
false ->
|
||||
EjabberdLogPath = ejabberd_logger:get_log_path(),
|
||||
print("ejabberd is not running in that node~n"
|
||||
"Check for error messages: ~s~n"
|
||||
"or other files in that directory.~n", [EjabberdLogPath]),
|
||||
?STATUS_ERROR;
|
||||
{value, {_, _, Version}} ->
|
||||
print("ejabberd ~s is running in that node~n", [Version]),
|
||||
true ->
|
||||
print("ejabberd ~s is running in that node~n", [ejabberd_config:get_version()]),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
@@ -874,8 +874,7 @@ print(Format, Args) ->
|
||||
%% ["aaaa bbb ccc"].
|
||||
|
||||
|
||||
-spec opt_type(ejabberdctl_access_commands) -> fun((list()) -> list());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(ejabberdctl_access_commands) ->
|
||||
fun (V) when is_list(V) -> V end;
|
||||
opt_type(_) -> [ejabberdctl_access_commands].
|
||||
|
||||
@@ -380,11 +380,12 @@ safe_apply(Hook, Module, Function, Args) ->
|
||||
apply(Module, Function, Args)
|
||||
end
|
||||
catch E:R when E /= exit; R /= normal ->
|
||||
St = get_stacktrace(),
|
||||
?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n"
|
||||
"** Reason = ~p~n"
|
||||
"** Arguments = ~p",
|
||||
[Hook, Module, Function, length(Args),
|
||||
{E, R, get_stacktrace()}, Args]),
|
||||
{E, R, St}, Args]),
|
||||
'EXIT'
|
||||
end.
|
||||
|
||||
|
||||
+55
-74
@@ -24,15 +24,16 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_http).
|
||||
|
||||
-behaviour(ejabberd_listener).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%% External exports
|
||||
-export([start/2, start_link/2, become_controller/1,
|
||||
socket_type/0, receive_headers/1, recv_file/2,
|
||||
transform_listen_option/2, listen_opt_type/1]).
|
||||
-export([start/2, start_link/2,
|
||||
accept/1, receive_headers/1, recv_file/2,
|
||||
transform_listen_option/2, listen_opt_type/1,
|
||||
listen_options/0]).
|
||||
|
||||
-export([init/2, opt_type/1]).
|
||||
|
||||
@@ -101,6 +102,7 @@ init({SockMod, Socket}, Opts) ->
|
||||
TLSEnabled = proplists:get_bool(tls, Opts),
|
||||
TLSOpts1 = lists:filter(fun ({ciphers, _}) -> true;
|
||||
({dhfile, _}) -> true;
|
||||
({cafile, _}) -> true;
|
||||
({protocol_options, _}) -> true;
|
||||
(_) -> false
|
||||
end,
|
||||
@@ -164,12 +166,9 @@ init({SockMod, Socket}, Opts) ->
|
||||
{error, _} -> State
|
||||
end.
|
||||
|
||||
become_controller(_Pid) ->
|
||||
accept(_Pid) ->
|
||||
ok.
|
||||
|
||||
socket_type() ->
|
||||
raw.
|
||||
|
||||
send_text(_State, none) ->
|
||||
ok;
|
||||
send_text(State, Text) ->
|
||||
@@ -202,8 +201,8 @@ send_file(State, Fd, Size, FileName) ->
|
||||
end
|
||||
catch _:{case_clause, {error, Why}} ->
|
||||
if Why /= closed ->
|
||||
?INFO_MSG("Failed to read ~s: ~s",
|
||||
[FileName, file_format_error(Why)]),
|
||||
?WARNING_MSG("Failed to read ~s: ~s",
|
||||
[FileName, file_format_error(Why)]),
|
||||
exit(normal);
|
||||
true ->
|
||||
ok
|
||||
@@ -598,35 +597,37 @@ recv_file(#request{length = Len, data = Trail,
|
||||
sockmod = SockMod, socket = Socket}, Path) ->
|
||||
case file:open(Path, [write, exclusive, raw]) of
|
||||
{ok, Fd} ->
|
||||
case file:write(Fd, Trail) of
|
||||
ok ->
|
||||
NewLen = max(0, Len - byte_size(Trail)),
|
||||
case do_recv_file(NewLen, SockMod, Socket, Fd) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, _} = Err ->
|
||||
file:delete(Path),
|
||||
Err
|
||||
end;
|
||||
{error, _} = Err ->
|
||||
file:delete(Path),
|
||||
Err
|
||||
end;
|
||||
Res = case file:write(Fd, Trail) of
|
||||
ok ->
|
||||
NewLen = max(0, Len - byte_size(Trail)),
|
||||
do_recv_file(NewLen, SockMod, Socket, Fd);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end,
|
||||
file:close(Fd),
|
||||
case Res of
|
||||
ok -> ok;
|
||||
{error, _} -> file:delete(Path)
|
||||
end,
|
||||
Res;
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
do_recv_file(0, _SockMod, _Socket, Fd) ->
|
||||
file:close(Fd);
|
||||
do_recv_file(0, _SockMod, _Socket, _Fd) ->
|
||||
ok;
|
||||
do_recv_file(Len, SockMod, Socket, Fd) ->
|
||||
ChunkLen = min(Len, ?RECV_BUF),
|
||||
try
|
||||
{ok, Data} = SockMod:recv(Socket, ChunkLen, timer:seconds(30)),
|
||||
ok = file:write(Fd, Data),
|
||||
do_recv_file(Len-size(Data), SockMod, Socket, Fd)
|
||||
catch _:{badmatch, {error, _} = Err} ->
|
||||
file:close(Fd),
|
||||
Err
|
||||
case SockMod:recv(Socket, ChunkLen, timer:seconds(30)) of
|
||||
{ok, Data} ->
|
||||
case file:write(Fd, Data) of
|
||||
ok ->
|
||||
do_recv_file(Len-size(Data), SockMod, Socket, Fd);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
{error, _} ->
|
||||
{error, closed}
|
||||
end.
|
||||
|
||||
make_headers(State, Status, Reason, Headers, Data) ->
|
||||
@@ -947,8 +948,7 @@ transform_listen_option({request_handlers, Hs}, Opts) ->
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-spec opt_type(trusted_proxies) -> fun((all | [binary()]) -> all | [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(trusted_proxies) ->
|
||||
fun (all) -> all;
|
||||
(TPs) -> lists:filtermap(
|
||||
@@ -961,40 +961,13 @@ opt_type(trusted_proxies) ->
|
||||
end;
|
||||
opt_type(_) -> [trusted_proxies].
|
||||
|
||||
-spec listen_opt_type(tls) -> fun((boolean()) -> boolean());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(captcha) -> fun((boolean()) -> boolean());
|
||||
(register) -> fun((boolean()) -> boolean());
|
||||
(web_admin) -> fun((boolean()) -> boolean());
|
||||
(http_bind) -> fun((boolean()) -> boolean());
|
||||
(xmlrpc) -> fun((boolean()) -> boolean());
|
||||
(request_handlers) -> fun(([{binary(), atom()}]) ->
|
||||
[{binary(), atom()}]);
|
||||
(default_host) -> fun((binary()) -> binary());
|
||||
(custom_headers) -> fun(([{binary(), binary()}]) ->
|
||||
[{binary(), binary()}]);
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(tls) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(certfile = Opt) ->
|
||||
fun(S) ->
|
||||
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
|
||||
"'certfiles' global option instead", [Opt, ?MODULE]),
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
ok = ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) ->
|
||||
fun iolist_to_binary/1;
|
||||
listen_opt_type(dhfile) ->
|
||||
fun misc:try_read_file/1;
|
||||
listen_opt_type(protocol_options) ->
|
||||
fun(Options) -> str:join(Options, <<"|">>) end;
|
||||
listen_opt_type(tls_compression) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(captcha) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(register) ->
|
||||
@@ -1021,15 +994,23 @@ listen_opt_type(request_handlers) ->
|
||||
end} || {Path, Mod} <- Hs2]
|
||||
end;
|
||||
listen_opt_type(default_host) ->
|
||||
fun(A) -> A end;
|
||||
fun iolist_to_binary/1;
|
||||
listen_opt_type(custom_headers) ->
|
||||
fun expand_custom_headers/1;
|
||||
listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(backlog) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(accept_interval) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
listen_opt_type(_) ->
|
||||
%% TODO
|
||||
fun(A) -> A end.
|
||||
fun expand_custom_headers/1.
|
||||
|
||||
listen_options() ->
|
||||
[{certfile, undefined},
|
||||
{ciphers, undefined},
|
||||
{dhfile, undefined},
|
||||
{cafile, undefined},
|
||||
{protocol_options, undefined},
|
||||
{tls, false},
|
||||
{tls_compression, false},
|
||||
{captcha, false},
|
||||
{register, false},
|
||||
{web_admin, false},
|
||||
{http_bind, false},
|
||||
{xmlrpc, false},
|
||||
{request_handlers, []},
|
||||
{default_host, undefined},
|
||||
{custom_headers, []}].
|
||||
|
||||
+47
-57
@@ -23,19 +23,17 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
-module(ejabberd_http_ws).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('ecestari@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
-behaviour(xmpp_socket).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
-export([start/1, start_link/1, init/1, handle_event/3,
|
||||
handle_sync_event/4, code_change/4, handle_info/3,
|
||||
terminate/3, send_xml/2, setopts/2, sockname/1,
|
||||
peername/1, controlling_process/2, become_controller/2,
|
||||
monitor/1, reset_stream/1, close/1, change_shaper/2,
|
||||
socket_handoff/3, opt_type/1]).
|
||||
peername/1, controlling_process/2, get_owner/1,
|
||||
reset_stream/1, close/1, change_shaper/2,
|
||||
socket_handoff/3, get_transport/1, opt_type/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
@@ -54,8 +52,8 @@
|
||||
timeout = ?WEBSOCKET_TIMEOUT :: non_neg_integer(),
|
||||
timer = make_ref() :: reference(),
|
||||
input = [] :: list(),
|
||||
waiting_input = false :: false | pid(),
|
||||
last_receiver = self() :: pid(),
|
||||
active = false :: boolean(),
|
||||
c2s_pid :: pid(),
|
||||
ws :: {#ws{}, pid()},
|
||||
rfc_compilant = undefined :: boolean() | undefined}).
|
||||
|
||||
@@ -104,15 +102,9 @@ peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
|
||||
|
||||
controlling_process(_Socket, _Pid) -> ok.
|
||||
|
||||
become_controller(FsmRef, C2SPid) ->
|
||||
p1_fsm:send_all_state_event(FsmRef, {activate, C2SPid}).
|
||||
|
||||
close({http_ws, FsmRef, _IP}) ->
|
||||
catch p1_fsm:sync_send_all_state_event(FsmRef, close).
|
||||
|
||||
monitor({http_ws, FsmRef, _IP}) ->
|
||||
erlang:monitor(process, FsmRef).
|
||||
|
||||
reset_stream({http_ws, _FsmRef, _IP} = Socket) ->
|
||||
Socket.
|
||||
|
||||
@@ -120,6 +112,12 @@ change_shaper({http_ws, _FsmRef, _IP}, _Shaper) ->
|
||||
%% TODO???
|
||||
ok.
|
||||
|
||||
get_transport(_Socket) ->
|
||||
websocket.
|
||||
|
||||
get_owner({http_ws, FsmRef, _IP}) ->
|
||||
FsmRef.
|
||||
|
||||
socket_handoff(LocalPath, Request, Opts) ->
|
||||
ejabberd_websocket:socket_handoff(LocalPath, Request, Opts, ?MODULE, fun get_human_html_xmlel/0).
|
||||
|
||||
@@ -145,31 +143,34 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
|
||||
Socket = {http_ws, self(), IP},
|
||||
?DEBUG("Client connected through websocket ~p",
|
||||
[Socket]),
|
||||
xmpp_socket:start(ejabberd_c2s, ?MODULE, Socket,
|
||||
[{receiver, self()}|Opts]),
|
||||
Timer = erlang:start_timer(WSTimeout, self(), []),
|
||||
{ok, loop,
|
||||
#state{socket = Socket, timeout = WSTimeout,
|
||||
timer = Timer, ws = WS,
|
||||
ping_interval = PingInterval}}.
|
||||
|
||||
handle_event({activate, From}, StateName, StateData) ->
|
||||
case StateData#state.input of
|
||||
[] ->
|
||||
{next_state, StateName,
|
||||
StateData#state{waiting_input = From}};
|
||||
Input ->
|
||||
Receiver = From,
|
||||
lists:foreach(fun(I) when is_binary(I)->
|
||||
Receiver ! {tcp, StateData#state.socket, I};
|
||||
(I2) ->
|
||||
Receiver ! {tcp, StateData#state.socket, [I2]}
|
||||
end, Input),
|
||||
{next_state, StateName,
|
||||
StateData#state{input = [], waiting_input = false,
|
||||
last_receiver = Receiver}}
|
||||
case ejabberd_c2s:start({?MODULE, Socket}, [{receiver, self()}|Opts]) of
|
||||
{ok, C2SPid} ->
|
||||
ejabberd_c2s:accept(C2SPid),
|
||||
Timer = erlang:start_timer(WSTimeout, self(), []),
|
||||
{ok, loop,
|
||||
#state{socket = Socket, timeout = WSTimeout,
|
||||
timer = Timer, ws = WS, c2s_pid = C2SPid,
|
||||
ping_interval = PingInterval}};
|
||||
{error, Reason} ->
|
||||
{stop, Reason};
|
||||
ignore ->
|
||||
ignore
|
||||
end.
|
||||
|
||||
handle_event({activate, From}, StateName, State) ->
|
||||
State1 = case State#state.input of
|
||||
[] -> State#state{active = true};
|
||||
Input ->
|
||||
lists:foreach(
|
||||
fun(I) when is_binary(I)->
|
||||
From ! {tcp, State#state.socket, I};
|
||||
(I2) ->
|
||||
From ! {tcp, State#state.socket, [I2]}
|
||||
end, Input),
|
||||
State#state{active = false, input = []}
|
||||
end,
|
||||
{next_state, StateName, State1#state{c2s_pid = From}}.
|
||||
|
||||
handle_sync_event({send_xml, Packet}, _From, StateName,
|
||||
#state{ws = {_, WsPid}, rfc_compilant = R} = StateData) ->
|
||||
Packet2 = case {case R of undefined -> true; V -> V end, Packet} of
|
||||
@@ -233,14 +234,13 @@ handle_info(closed, _StateName, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
handle_info({received, Packet}, StateName, StateDataI) ->
|
||||
{StateData, Parsed} = parse(StateDataI, Packet),
|
||||
SD = case StateData#state.waiting_input of
|
||||
SD = case StateData#state.active of
|
||||
false ->
|
||||
Input = StateData#state.input ++ if is_binary(Parsed) -> [Parsed]; true -> Parsed end,
|
||||
StateData#state{input = Input};
|
||||
Receiver ->
|
||||
Receiver ! {tcp, StateData#state.socket, Parsed},
|
||||
setup_timers(StateData#state{waiting_input = false,
|
||||
last_receiver = Receiver})
|
||||
true ->
|
||||
StateData#state.c2s_pid ! {tcp, StateData#state.socket, Parsed},
|
||||
setup_timers(StateData#state{active = false})
|
||||
end,
|
||||
{next_state, StateName, SD};
|
||||
handle_info(PingPong, StateName, StateData) when PingPong == ping orelse
|
||||
@@ -256,7 +256,7 @@ handle_info({timeout, Timer, _}, StateName,
|
||||
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
|
||||
case StateData#state.pong_expected of
|
||||
false ->
|
||||
cancel_timer(StateData#state.ping_timer),
|
||||
misc:cancel_timer(StateData#state.ping_timer),
|
||||
PingTimer = erlang:start_timer(StateData#state.ping_interval,
|
||||
self(), []),
|
||||
WsPid ! {ping, <<>>},
|
||||
@@ -273,19 +273,13 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
|
||||
{ok, StateName, StateData}.
|
||||
|
||||
terminate(_Reason, _StateName, StateData) ->
|
||||
case StateData#state.waiting_input of
|
||||
false -> ok;
|
||||
Receiver ->
|
||||
?DEBUG("C2S Pid : ~p", [Receiver]),
|
||||
Receiver ! {tcp_closed, StateData#state.socket}
|
||||
end,
|
||||
ok.
|
||||
StateData#state.c2s_pid ! {tcp_closed, StateData#state.socket}.
|
||||
|
||||
setup_timers(StateData) ->
|
||||
cancel_timer(StateData#state.timer),
|
||||
misc:cancel_timer(StateData#state.timer),
|
||||
Timer = erlang:start_timer(StateData#state.timeout,
|
||||
self(), []),
|
||||
cancel_timer(StateData#state.ping_timer),
|
||||
misc:cancel_timer(StateData#state.ping_timer),
|
||||
PingTimer = case StateData#state.ping_interval of
|
||||
0 -> StateData#state.ping_timer;
|
||||
V -> erlang:start_timer(V, self(), [])
|
||||
@@ -293,10 +287,6 @@ setup_timers(StateData) ->
|
||||
StateData#state{timer = Timer, ping_timer = PingTimer,
|
||||
pong_expected = false}.
|
||||
|
||||
cancel_timer(Timer) ->
|
||||
erlang:cancel_timer(Timer),
|
||||
receive {timeout, Timer, _} -> ok after 0 -> ok end.
|
||||
|
||||
get_human_html_xmlel() ->
|
||||
Heading = <<"ejabberd ", (misc:atom_to_binary(?MODULE))/binary>>,
|
||||
#xmlel{name = <<"html">>,
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_idna.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Support for IDNA (RFC3490)
|
||||
%%% Created : 10 Apr 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_idna).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([domain_utf8_to_ascii/1,
|
||||
domain_ucs2_to_ascii/1,
|
||||
utf8_to_ucs2/1]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-endif.
|
||||
|
||||
-spec domain_utf8_to_ascii(binary()) -> false | binary().
|
||||
|
||||
domain_utf8_to_ascii(Domain) ->
|
||||
domain_ucs2_to_ascii(utf8_to_ucs2(Domain)).
|
||||
|
||||
utf8_to_ucs2(S) ->
|
||||
utf8_to_ucs2(binary_to_list(S), "").
|
||||
|
||||
utf8_to_ucs2([], R) -> lists:reverse(R);
|
||||
utf8_to_ucs2([C | S], R) when C < 128 ->
|
||||
utf8_to_ucs2(S, [C | R]);
|
||||
utf8_to_ucs2([C1, C2 | S], R) when C1 < 224 ->
|
||||
utf8_to_ucs2(S, [C1 band 31 bsl 6 bor C2 band 63 | R]);
|
||||
utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 240 ->
|
||||
utf8_to_ucs2(S,
|
||||
[C1 band 15 bsl 12 bor (C2 band 63 bsl 6) bor C3 band 63
|
||||
| R]).
|
||||
|
||||
-spec domain_ucs2_to_ascii(list()) -> false | binary().
|
||||
|
||||
domain_ucs2_to_ascii(Domain) ->
|
||||
case catch domain_ucs2_to_ascii1(Domain) of
|
||||
{'EXIT', _Reason} -> false;
|
||||
Res -> iolist_to_binary(Res)
|
||||
end.
|
||||
|
||||
domain_ucs2_to_ascii1(Domain) ->
|
||||
Parts = string:tokens(Domain,
|
||||
[46, 12290, 65294, 65377]),
|
||||
ASCIIParts = lists:map(fun (P) -> to_ascii(P) end,
|
||||
Parts),
|
||||
string:strip(lists:flatmap(fun (P) -> [$. | P] end,
|
||||
ASCIIParts),
|
||||
left, $.).
|
||||
|
||||
%% Domain names are already nameprep'ed in ejabberd, so we skiping this step
|
||||
to_ascii(Name) ->
|
||||
false = lists:any(fun (C)
|
||||
when (0 =< C) and (C =< 44) or
|
||||
(46 =< C) and (C =< 47)
|
||||
or (58 =< C) and (C =< 64)
|
||||
or (91 =< C) and (C =< 96)
|
||||
or (123 =< C) and (C =< 127) ->
|
||||
true;
|
||||
(_) -> false
|
||||
end,
|
||||
Name),
|
||||
case Name of
|
||||
[H | _] when H /= $- -> true = lists:last(Name) /= $-
|
||||
end,
|
||||
ASCIIName = case lists:any(fun (C) -> C > 127 end, Name)
|
||||
of
|
||||
true ->
|
||||
true = case Name of
|
||||
"xn--" ++ _ -> false;
|
||||
_ -> true
|
||||
end,
|
||||
"xn--" ++ punycode_encode(Name);
|
||||
false -> Name
|
||||
end,
|
||||
L = length(ASCIIName),
|
||||
true = (1 =< L) and (L =< 63),
|
||||
ASCIIName.
|
||||
|
||||
%%% PUNYCODE (RFC3492)
|
||||
|
||||
-define(BASE, 36).
|
||||
|
||||
-define(TMIN, 1).
|
||||
|
||||
-define(TMAX, 26).
|
||||
|
||||
-define(SKEW, 38).
|
||||
|
||||
-define(DAMP, 700).
|
||||
|
||||
-define(INITIAL_BIAS, 72).
|
||||
|
||||
-define(INITIAL_N, 128).
|
||||
|
||||
punycode_encode(Input) ->
|
||||
N = (?INITIAL_N),
|
||||
Delta = 0,
|
||||
Bias = (?INITIAL_BIAS),
|
||||
Basic = lists:filter(fun (C) -> C =< 127 end, Input),
|
||||
NonBasic = lists:filter(fun (C) -> C > 127 end, Input),
|
||||
L = length(Input),
|
||||
B = length(Basic),
|
||||
SNonBasic = lists:usort(NonBasic),
|
||||
Output1 = if B > 0 -> Basic ++ "-";
|
||||
true -> ""
|
||||
end,
|
||||
Output2 = punycode_encode1(Input, SNonBasic, B, B, L, N,
|
||||
Delta, Bias, ""),
|
||||
Output1 ++ Output2.
|
||||
|
||||
punycode_encode1(Input, [M | SNonBasic], B, H, L, N,
|
||||
Delta, Bias, Out)
|
||||
when H < L ->
|
||||
Delta1 = Delta + (M - N) * (H + 1),
|
||||
% let n = m
|
||||
{NewDelta, NewBias, NewH, NewOut} = lists:foldl(fun (C,
|
||||
{ADelta, ABias, AH,
|
||||
AOut}) ->
|
||||
if C < M ->
|
||||
{ADelta + 1,
|
||||
ABias, AH,
|
||||
AOut};
|
||||
C == M ->
|
||||
NewOut =
|
||||
punycode_encode_delta(ADelta,
|
||||
ABias,
|
||||
AOut),
|
||||
NewBias =
|
||||
adapt(ADelta,
|
||||
H +
|
||||
1,
|
||||
H
|
||||
==
|
||||
B),
|
||||
{0, NewBias,
|
||||
AH + 1,
|
||||
NewOut};
|
||||
true ->
|
||||
{ADelta,
|
||||
ABias, AH,
|
||||
AOut}
|
||||
end
|
||||
end,
|
||||
{Delta1, Bias, H, Out},
|
||||
Input),
|
||||
punycode_encode1(Input, SNonBasic, B, NewH, L, M + 1,
|
||||
NewDelta + 1, NewBias, NewOut);
|
||||
punycode_encode1(_Input, _SNonBasic, _B, _H, _L, _N,
|
||||
_Delta, _Bias, Out) ->
|
||||
lists:reverse(Out).
|
||||
|
||||
punycode_encode_delta(Delta, Bias, Out) ->
|
||||
punycode_encode_delta(Delta, Bias, Out, ?BASE).
|
||||
|
||||
punycode_encode_delta(Delta, Bias, Out, K) ->
|
||||
T = if K =< Bias -> ?TMIN;
|
||||
K >= Bias + (?TMAX) -> ?TMAX;
|
||||
true -> K - Bias
|
||||
end,
|
||||
if Delta < T -> [codepoint(Delta) | Out];
|
||||
true ->
|
||||
C = T + (Delta - T) rem ((?BASE) - T),
|
||||
punycode_encode_delta((Delta - T) div ((?BASE) - T),
|
||||
Bias, [codepoint(C) | Out], K + (?BASE))
|
||||
end.
|
||||
|
||||
adapt(Delta, NumPoints, FirstTime) ->
|
||||
Delta1 = if FirstTime -> Delta div (?DAMP);
|
||||
true -> Delta div 2
|
||||
end,
|
||||
Delta2 = Delta1 + Delta1 div NumPoints,
|
||||
adapt1(Delta2, 0).
|
||||
|
||||
adapt1(Delta, K) ->
|
||||
if Delta > ((?BASE) - (?TMIN)) * (?TMAX) div 2 ->
|
||||
adapt1(Delta div ((?BASE) - (?TMIN)), K + (?BASE));
|
||||
true ->
|
||||
K +
|
||||
((?BASE) - (?TMIN) + 1) * Delta div (Delta + (?SKEW))
|
||||
end.
|
||||
|
||||
codepoint(C) ->
|
||||
if (0 =< C) and (C =< 25) -> C + 97;
|
||||
(26 =< C) and (C =< 35) -> C + 22
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Unit tests
|
||||
%%%===================================================================
|
||||
-ifdef(TEST).
|
||||
|
||||
acsii_test() ->
|
||||
?assertEqual(<<"test.org">>, domain_utf8_to_ascii(<<"test.org">>)).
|
||||
|
||||
utf8_test() ->
|
||||
?assertEqual(
|
||||
<<"xn--d1acufc.xn--p1ai">>,
|
||||
domain_utf8_to_ascii(
|
||||
<<208,180,208,190,208,188,208,181,208,189,46,209,128,209,132>>)).
|
||||
|
||||
-endif.
|
||||
+1
-1
@@ -49,7 +49,7 @@ start_link() ->
|
||||
-spec route(iq(), atom() | pid(), term(), non_neg_integer()) -> ok.
|
||||
route(#iq{type = T} = IQ, Proc, Ctx, Timeout) when T == set; T == get ->
|
||||
Expire = current_time() + Timeout,
|
||||
Rnd = randoms:get_string(),
|
||||
Rnd = p1_rand:get_string(),
|
||||
ID = encode_id(Expire, Rnd),
|
||||
ets:insert(?MODULE, {{Expire, Rnd}, Proc, Ctx}),
|
||||
gen_server:cast(?MODULE, {restart_timer, Expire}),
|
||||
|
||||
+627
-524
File diff suppressed because it is too large
Load Diff
@@ -71,8 +71,9 @@ start_link() ->
|
||||
route(Packet) ->
|
||||
try do_route(Packet)
|
||||
catch E:R ->
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("failed to route packet:~n~s~nReason = ~p",
|
||||
[xmpp:pp(Packet), {E, {R, erlang:get_stacktrace()}}])
|
||||
[xmpp:pp(Packet), {E, {R, St}}])
|
||||
end.
|
||||
|
||||
-spec route_iq(iq(), function()) -> ok.
|
||||
|
||||
+16
-1
@@ -143,10 +143,14 @@ do_start(Level) ->
|
||||
LogRotateSize = get_integer_env(log_rotate_size, 10*1024*1024),
|
||||
LogRotateCount = get_integer_env(log_rotate_count, 1),
|
||||
LogRateLimit = get_integer_env(log_rate_limit, 100),
|
||||
ConsoleLevel = case get_lager_version() >= "3.6.0" of
|
||||
true -> [{level, Level}];
|
||||
false -> Level
|
||||
end,
|
||||
application:set_env(lager, error_logger_hwm, LogRateLimit),
|
||||
application:set_env(
|
||||
lager, handlers,
|
||||
[{lager_console_backend, Level},
|
||||
[{lager_console_backend, ConsoleLevel},
|
||||
{lager_file_backend, [{file, ConsoleLog}, {level, Level}, {date, LogRotateDate},
|
||||
{count, LogRotateCount}, {size, LogRotateSize}]},
|
||||
{lager_file_backend, [{file, ErrorLog}, {level, error}, {date, LogRotateDate},
|
||||
@@ -214,6 +218,10 @@ set(LogLevel) when is_integer(LogLevel) ->
|
||||
ok
|
||||
end, gen_event:which_handlers(lager_event))
|
||||
end,
|
||||
case LogLevel of
|
||||
5 -> xmpp:set_config([{debug, true}]);
|
||||
_ -> ok
|
||||
end,
|
||||
{module, lager};
|
||||
set({_LogLevel, _}) ->
|
||||
error_logger:error_msg("custom loglevels are not supported for 'lager'"),
|
||||
@@ -248,3 +256,10 @@ get_lager_handlers() ->
|
||||
Result ->
|
||||
Result
|
||||
end.
|
||||
|
||||
get_lager_version() ->
|
||||
Apps = application:loaded_applications(),
|
||||
case lists:keyfind(lager, 1, Apps) of
|
||||
{_, _, Vsn} -> Vsn;
|
||||
false -> "0.0.0"
|
||||
end.
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
config_reloaded/0,
|
||||
opt_type/1]).
|
||||
|
||||
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1]).
|
||||
-export([get_commands_spec/0,
|
||||
oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
|
||||
@@ -645,14 +646,7 @@ logo() ->
|
||||
<<>>
|
||||
end.
|
||||
|
||||
-spec opt_type(oauth_expire) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(oauth_access) -> fun((any()) -> any());
|
||||
(oauth_db_type) -> fun((atom()) -> atom());
|
||||
(oauth_cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(oauth_cache_size) -> fun((timeout()) -> timeout());
|
||||
(oauth_use_cache) -> fun((boolean()) -> boolean());
|
||||
(oauth_cache_misse) -> fun((boolean()) -> boolean());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(oauth_expire) ->
|
||||
fun(I) when is_integer(I), I >= 0 -> I end;
|
||||
opt_type(oauth_access) ->
|
||||
|
||||
@@ -92,8 +92,7 @@ path(Path) ->
|
||||
<<Base/binary, "/", Path/binary>>.
|
||||
|
||||
|
||||
-spec opt_type(ext_api_path_oauth) -> fun((binary()) -> binary());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(ext_api_path_oauth) ->
|
||||
fun (X) -> iolist_to_binary(X) end;
|
||||
opt_type(_) -> [ext_api_path_oauth].
|
||||
|
||||
+35
-18
@@ -435,7 +435,7 @@ process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
|
||||
{<<"query">>, ?NS_PRIVATE} ->
|
||||
process_private(xmpp:decode(El), State);
|
||||
{<<"vCard">>, ?NS_VCARD} ->
|
||||
process_vcard(El, State);
|
||||
process_vcard(xmpp:decode(El), State);
|
||||
{<<"offline-messages">>, NS} ->
|
||||
Msgs = [xmpp:decode(E, NS, [ignore_els]) || E <- Els],
|
||||
process_offline_msgs(Msgs, State);
|
||||
@@ -475,30 +475,47 @@ process_roster(RosterQuery, State = #state{user = U, server = S}) ->
|
||||
-spec process_privacy(privacy_query(), state()) -> {ok, state()} | {error, _}.
|
||||
process_privacy(#privacy_query{lists = Lists,
|
||||
default = Default,
|
||||
active = Active} = PrivacyQuery,
|
||||
active = Active},
|
||||
State = #state{user = U, server = S}) ->
|
||||
JID = jid:make(U, S),
|
||||
IQ = #iq{type = set, id = randoms:get_string(),
|
||||
from = JID, to = JID, sub_els = [PrivacyQuery]},
|
||||
case mod_privacy:process_iq(IQ) of
|
||||
#iq{type = error} = ResIQ ->
|
||||
#stanza_error{reason = Reason} = xmpp:get_error(ResIQ),
|
||||
if Reason == 'item-not-found', Lists == [],
|
||||
Active == undefined, Default /= undefined ->
|
||||
if Lists /= undefined ->
|
||||
process_privacy2(JID, #privacy_query{lists = Lists});
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if Active /= undefined ->
|
||||
process_privacy2(JID, #privacy_query{active = Active});
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if Default /= undefined ->
|
||||
process_privacy2(JID, #privacy_query{default = Default});
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ok, State}.
|
||||
|
||||
process_privacy2(JID, PQ) ->
|
||||
case mod_privacy:process_iq(#iq{type = set, id = p1_rand:get_string(),
|
||||
from = JID, to = JID,
|
||||
sub_els = [PQ]}) of
|
||||
#iq{type = error} = ResIQ ->
|
||||
#stanza_error{reason = Reason} = xmpp:get_error(ResIQ),
|
||||
if Reason /= 'item-not-found' ->
|
||||
%% Failed to set default list because there is no
|
||||
%% list with such name. We shouldn't stop here.
|
||||
{ok, State};
|
||||
true ->
|
||||
stop("Failed to write privacy: ~p", [Reason])
|
||||
end;
|
||||
_ ->
|
||||
{ok, State}
|
||||
end.
|
||||
stop("Failed to write default privacy: ~p", [Reason]);
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec process_private(private(), state()) -> {ok, state()} | {error, _}.
|
||||
process_private(Private, State = #state{user = U, server = S}) ->
|
||||
JID = jid:make(U, S),
|
||||
IQ = #iq{type = set, id = randoms:get_string(),
|
||||
IQ = #iq{type = set, id = p1_rand:get_string(),
|
||||
from = JID, to = JID, sub_els = [Private]},
|
||||
case mod_private:process_sm_iq(IQ) of
|
||||
#iq{type = result} ->
|
||||
@@ -510,7 +527,7 @@ process_private(Private, State = #state{user = U, server = S}) ->
|
||||
-spec process_vcard(xmlel(), state()) -> {ok, state()} | {error, _}.
|
||||
process_vcard(El, State = #state{user = U, server = S}) ->
|
||||
JID = jid:make(U, S),
|
||||
IQ = #iq{type = set, id = randoms:get_string(),
|
||||
IQ = #iq{type = set, id = p1_rand:get_string(),
|
||||
from = JID, to = JID, sub_els = [El]},
|
||||
case mod_vcard:process_sm_iq(IQ) of
|
||||
#iq{type = result} ->
|
||||
|
||||
+10
-5
@@ -60,12 +60,17 @@
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec add_certfile(filename:filename())
|
||||
-spec add_certfile(file:filename())
|
||||
-> ok | {error, cert_error() | file:posix()}.
|
||||
add_certfile(Path) ->
|
||||
gen_server:call(?MODULE, {add_certfile, prep_path(Path)}).
|
||||
try gen_server:call(?MODULE, {add_certfile, prep_path(Path)})
|
||||
catch exit:{noproc, {gen_server, call, _}} ->
|
||||
%% This hack will be removed after moving
|
||||
%% the code into a separate repo
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec try_certfile(filename:filename()) -> binary().
|
||||
-spec try_certfile(file:filename()) -> binary().
|
||||
try_certfile(Path0) ->
|
||||
Path = prep_path(Path0),
|
||||
case load_certfile(Path) of
|
||||
@@ -133,7 +138,7 @@ get_certfile(Domain) ->
|
||||
|
||||
-spec get_certfile_no_default(binary()) -> {ok, binary()} | error.
|
||||
get_certfile_no_default(Domain) ->
|
||||
case ejabberd_idna:domain_utf8_to_ascii(Domain) of
|
||||
case xmpp_idna:domain_utf8_to_ascii(Domain) of
|
||||
false ->
|
||||
error;
|
||||
ASCIIDomain ->
|
||||
@@ -885,7 +890,7 @@ get_cert_path(G, [Root|_] = Acc) ->
|
||||
end, Es)
|
||||
end.
|
||||
|
||||
-spec prep_path(filename:filename()) -> binary().
|
||||
-spec prep_path(file:filename()) -> binary().
|
||||
prep_path(Path0) ->
|
||||
case filename:pathtype(Path0) of
|
||||
relative ->
|
||||
|
||||
@@ -71,7 +71,7 @@ get_spec(Host) ->
|
||||
|
||||
-spec config_reloaded() -> ok.
|
||||
config_reloaded() ->
|
||||
lists:foreach(fun start_host/1, ejabberd_config:get_myhosts()).
|
||||
lists:foreach(fun reload_host/1, ejabberd_config:get_myhosts()).
|
||||
|
||||
-spec start_host(binary()) -> ok.
|
||||
start_host(Host) ->
|
||||
@@ -96,6 +96,10 @@ stop_host(Host) ->
|
||||
supervisor:delete_child(?MODULE, SupName),
|
||||
ok.
|
||||
|
||||
-spec reload_host(binary()) -> ok.
|
||||
reload_host(Host) ->
|
||||
ejabberd_sql_sup:reload(Host).
|
||||
|
||||
%% Returns {true, App} if we have configured sql for the given host
|
||||
needs_sql(Host) ->
|
||||
LHost = jid:nameprep(Host),
|
||||
@@ -108,9 +112,7 @@ needs_sql(Host) ->
|
||||
undefined -> false
|
||||
end.
|
||||
|
||||
-type sql_type() :: mysql | pgsql | sqlite | mssql | odbc.
|
||||
-spec opt_type(sql_type) -> fun((sql_type()) -> sql_type());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(sql_type) ->
|
||||
fun (mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
|
||||
+41
-4
@@ -33,10 +33,10 @@
|
||||
%% API
|
||||
-export([start_link/1, get_proc/1, get_connection/1, q/1, qp/1, format_error/1]).
|
||||
%% Commands
|
||||
-export([multi/1, get/1, set/2, del/1,
|
||||
-export([multi/1, get/1, set/2, del/1, info/1,
|
||||
sadd/2, srem/2, smembers/1, sismember/2, scard/1,
|
||||
hget/2, hset/3, hdel/2, hlen/1, hgetall/1, hkeys/1,
|
||||
subscribe/1, publish/2]).
|
||||
subscribe/1, publish/2, script_load/1, evalsha/3]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
@@ -61,6 +61,9 @@
|
||||
-type redis_reply() :: binary() | [binary()].
|
||||
-type redis_command() :: [binary()].
|
||||
-type redis_pipeline() :: [redis_command()].
|
||||
-type redis_info() :: server | clients | memory | persistence |
|
||||
stats | replication | cpu | commandstats |
|
||||
cluster | keyspace | default | all.
|
||||
-type state() :: #state{}.
|
||||
|
||||
-export_type([error_reason/0]).
|
||||
@@ -316,6 +319,40 @@ publish(Channel, Data) ->
|
||||
tr_enq(Cmd, Stack)
|
||||
end.
|
||||
|
||||
-spec script_load(iodata()) -> {ok, binary()} | redis_error().
|
||||
script_load(Data) ->
|
||||
case erlang:get(?TR_STACK) of
|
||||
undefined ->
|
||||
q([<<"SCRIPT">>, <<"LOAD">>, Data]);
|
||||
_ ->
|
||||
erlang:error(transaction_unsupported)
|
||||
end.
|
||||
|
||||
-spec evalsha(binary(), [iodata()], [iodata()]) -> {ok, binary()} | redis_error().
|
||||
evalsha(SHA, Keys, Args) ->
|
||||
case erlang:get(?TR_STACK) of
|
||||
undefined ->
|
||||
q([<<"EVALSHA">>, SHA, length(Keys)|Keys ++ Args]);
|
||||
_ ->
|
||||
erlang:error(transaction_unsupported)
|
||||
end.
|
||||
|
||||
-spec info(redis_info()) -> {ok, [{atom(), binary()}]} | redis_error().
|
||||
info(Type) ->
|
||||
case erlang:get(?TR_STACK) of
|
||||
undefined ->
|
||||
case q([<<"INFO">>, misc:atom_to_binary(Type)]) of
|
||||
{ok, Info} ->
|
||||
Lines = binary:split(Info, <<"\r\n">>, [global]),
|
||||
KVs = [binary:split(Line, <<":">>) || Line <- Lines],
|
||||
{ok, [{misc:binary_to_atom(K), V} || [K, V] <- KVs]};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
_ ->
|
||||
erlang:error(transaction_unsupported)
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
@@ -437,7 +474,7 @@ connect(#state{num = Num}) ->
|
||||
erlang:error(Why)
|
||||
end
|
||||
catch _:Reason ->
|
||||
Timeout = randoms:uniform(
|
||||
Timeout = p1_rand:uniform(
|
||||
min(10, ejabberd_redis_sup:get_pool_size())),
|
||||
?ERROR_MSG("Redis connection #~p at ~s:~p has failed: ~p; "
|
||||
"reconnecting in ~p seconds",
|
||||
@@ -502,7 +539,7 @@ log_error(Cmd, Reason) ->
|
||||
|
||||
-spec get_rnd_id() -> pos_integer().
|
||||
get_rnd_id() ->
|
||||
randoms:round_robin(ejabberd_redis_sup:get_pool_size() - 1) + 2.
|
||||
p1_rand:round_robin(ejabberd_redis_sup:get_pool_size() - 1) + 2.
|
||||
|
||||
-spec get_result([{error, atom() | binary()} | {ok, iodata()}]) ->
|
||||
{ok, [redis_reply()]} | {error, binary()}.
|
||||
|
||||
@@ -126,14 +126,7 @@ get_pool_size() ->
|
||||
iolist_to_list(IOList) ->
|
||||
binary_to_list(iolist_to_binary(IOList)).
|
||||
|
||||
-spec opt_type(redis_connect_timeout) -> fun((pos_integer()) -> pos_integer());
|
||||
(redis_db) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(redis_password) -> fun((binary()) -> binary());
|
||||
(redis_port) -> fun((0..65535) -> 0..65535);
|
||||
(redis_server) -> fun((binary()) -> binary());
|
||||
(redis_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(redis_queue_type) -> fun((ram | file) -> ram | file);
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(redis_connect_timeout) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(redis_db) ->
|
||||
|
||||
@@ -162,7 +162,7 @@ get_pids() ->
|
||||
[ejabberd_riak:get_proc(I) || I <- lists:seq(1, get_pool_size())].
|
||||
|
||||
get_random_pid() ->
|
||||
I = randoms:round_robin(get_pool_size()) + 1,
|
||||
I = p1_rand:round_robin(get_pool_size()) + 1,
|
||||
ejabberd_riak:get_proc(I).
|
||||
|
||||
transform_options(Opts) ->
|
||||
@@ -173,14 +173,7 @@ transform_options({riak_server, {S, P}}, Opts) ->
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-spec opt_type(riak_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(riak_port) -> fun((0..65535) -> 0..65535);
|
||||
(riak_server) -> fun((binary()) -> binary());
|
||||
(riak_start_interval) -> fun((pos_integer()) -> pos_integer());
|
||||
(riak_cacertfile) -> fun((binary()) -> binary());
|
||||
(riak_username) -> fun((binary()) -> binary());
|
||||
(riak_password) -> fun((binary()) -> binary());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(riak_pool_size) ->
|
||||
fun (N) when is_integer(N), N >= 1 -> N end;
|
||||
opt_type(riak_port) ->
|
||||
|
||||
+4
-12
@@ -91,8 +91,9 @@ start_link() ->
|
||||
route(Packet) ->
|
||||
try do_route(Packet)
|
||||
catch E:R ->
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("failed to route packet:~n~s~nReason = ~p",
|
||||
[xmpp:pp(Packet), {E, {R, erlang:get_stacktrace()}}])
|
||||
[xmpp:pp(Packet), {E, {R, St}}])
|
||||
end.
|
||||
|
||||
-spec route(jid(), jid(), xmlel() | stanza()) -> ok.
|
||||
@@ -479,7 +480,7 @@ cache_opts() ->
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec clean_cache(node()) -> ok.
|
||||
-spec clean_cache(node()) -> non_neg_integer().
|
||||
clean_cache(Node) ->
|
||||
ets_cache:filter(
|
||||
?ROUTES_CACHE,
|
||||
@@ -498,16 +499,7 @@ clean_cache(Node) ->
|
||||
clean_cache() ->
|
||||
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
|
||||
|
||||
-type domain_balancing() :: random | source | destination |
|
||||
bare_source | bare_destination.
|
||||
-spec opt_type(domain_balancing) -> fun((domain_balancing()) -> domain_balancing());
|
||||
(domain_balancing_component_number) -> fun((pos_integer()) -> pos_integer());
|
||||
(router_db_type) -> fun((atom()) -> atom());
|
||||
(router_use_cache) -> fun((boolean()) -> boolean());
|
||||
(router_cache_missed) -> fun((boolean()) -> boolean());
|
||||
(router_cache_size) -> fun((timeout()) -> timeout());
|
||||
(router_cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(domain_balancing) ->
|
||||
fun (random) -> random;
|
||||
(source) -> source;
|
||||
|
||||
@@ -122,10 +122,11 @@ row_to_route(Domain, {ServerHost, NodeS, PidS, LocalHintS} = Row) ->
|
||||
catch _:{bad_node, _} ->
|
||||
[];
|
||||
E:R ->
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("failed to decode row from 'route' table:~n"
|
||||
"Row = ~p~n"
|
||||
"Domain = ~s~n"
|
||||
"Reason = ~p",
|
||||
[Row, Domain, {E, {R, erlang:get_stacktrace()}}]),
|
||||
[Row, Domain, {E, {R, St}}]),
|
||||
[]
|
||||
end.
|
||||
|
||||
+9
-15
@@ -95,8 +95,9 @@ start_link() ->
|
||||
route(Packet) ->
|
||||
try do_route(Packet)
|
||||
catch E:R ->
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("failed to route packet:~n~s~nReason = ~p",
|
||||
[xmpp:pp(Packet), {E, {R, erlang:get_stacktrace()}}])
|
||||
[xmpp:pp(Packet), {E, {R, St}}])
|
||||
end.
|
||||
|
||||
clean_temporarily_blocked_table() ->
|
||||
@@ -707,19 +708,7 @@ get_s2s_state(S2sPid) ->
|
||||
end,
|
||||
[{s2s_pid, S2sPid} | Infos].
|
||||
|
||||
-type use_starttls() :: boolean() | optional | required | required_trusted.
|
||||
-spec opt_type(route_subdomains) -> fun((s2s | local) -> s2s | local);
|
||||
(s2s_access) -> fun((any()) -> any());
|
||||
(s2s_ciphers) -> fun((binary()) -> binary());
|
||||
(s2s_dhfile) -> fun((binary()) -> binary());
|
||||
(s2s_cafile) -> fun((binary()) -> binary());
|
||||
(s2s_protocol_options) -> fun(([binary()]) -> binary());
|
||||
(s2s_tls_compression) -> fun((boolean()) -> boolean());
|
||||
(s2s_use_starttls) -> fun((use_starttls()) -> use_starttls());
|
||||
(s2s_zlib) -> fun((boolean()) -> boolean());
|
||||
(s2s_timeout) -> fun((timeout()) -> timeout());
|
||||
(s2s_queue_type) -> fun((ram | file) -> ram | file);
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(route_subdomains) ->
|
||||
fun (s2s) -> s2s;
|
||||
(local) -> local
|
||||
@@ -749,7 +738,12 @@ opt_type(s2s_use_starttls) ->
|
||||
required_trusted
|
||||
end;
|
||||
opt_type(s2s_zlib) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
fun(true) ->
|
||||
ejabberd:start_app(ezlib),
|
||||
true;
|
||||
(false) ->
|
||||
false
|
||||
end;
|
||||
opt_type(s2s_timeout) ->
|
||||
fun(I) when is_integer(I), I >= 0 -> timer:seconds(I);
|
||||
(infinity) -> infinity;
|
||||
|
||||
+34
-68
@@ -21,17 +21,14 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_s2s_in).
|
||||
-behaviour(xmpp_stream_in).
|
||||
-behaviour(xmpp_socket).
|
||||
-behaviour(ejabberd_listener).
|
||||
|
||||
%% xmpp_socket callbacks
|
||||
-export([start/2, start_link/2, socket_type/0]).
|
||||
%% ejabberd_listener callbacks
|
||||
-export([listen_opt_type/1]).
|
||||
-export([start/2, start_link/2, accept/1, listen_opt_type/1, listen_options/0]).
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
|
||||
compress_methods/1,
|
||||
-export([tls_options/1, tls_required/1, tls_enabled/1, compress_methods/1,
|
||||
unauthenticated_stream_features/1, authenticated_stream_features/1,
|
||||
handle_stream_start/2, handle_stream_end/2,
|
||||
handle_stream_established/1, handle_auth_success/4,
|
||||
@@ -54,16 +51,8 @@
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start(SockData, Opts) ->
|
||||
case proplists:get_value(supervisor, Opts, true) of
|
||||
true ->
|
||||
case supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]) of
|
||||
{ok, undefined} -> ignore;
|
||||
Res -> Res
|
||||
end;
|
||||
_ ->
|
||||
xmpp_stream_in:start(?MODULE, [SockData, Opts],
|
||||
ejabberd_config:fsm_limit_opts(Opts))
|
||||
end.
|
||||
xmpp_stream_in:start(?MODULE, [SockData, Opts],
|
||||
ejabberd_config:fsm_limit_opts(Opts)).
|
||||
|
||||
start_link(SockData, Opts) ->
|
||||
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
|
||||
@@ -78,8 +67,8 @@ close(Ref, Reason) ->
|
||||
stop(Ref) ->
|
||||
xmpp_stream_in:stop(Ref).
|
||||
|
||||
socket_type() ->
|
||||
xml_stream.
|
||||
accept(Ref) ->
|
||||
xmpp_stream_in:accept(Ref).
|
||||
|
||||
-spec send(pid(), xmpp_element()) -> ok;
|
||||
(state(), xmpp_element()) -> state().
|
||||
@@ -132,7 +121,15 @@ reject_unauthenticated_packet(State, _Pkt) ->
|
||||
Err = xmpp:serr_not_authorized(),
|
||||
send(State, Err).
|
||||
|
||||
process_closed(State, _Reason) ->
|
||||
process_closed(#{server := LServer} = State, Reason) ->
|
||||
RServer = case State of
|
||||
#{remote_server := Name} ->
|
||||
Name;
|
||||
#{ip := IP} ->
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP))
|
||||
end,
|
||||
?INFO_MSG("Closing inbound s2s connection ~s -> ~s: ~s",
|
||||
[RServer, LServer, xmpp_stream_out:format_error(Reason)]),
|
||||
stop(State).
|
||||
|
||||
%%%===================================================================
|
||||
@@ -144,9 +141,6 @@ tls_options(#{tls_options := TLSOpts, server_host := LServer}) ->
|
||||
tls_required(#{server_host := LServer}) ->
|
||||
ejabberd_s2s:tls_required(LServer).
|
||||
|
||||
tls_verify(#{server_host := LServer}) ->
|
||||
ejabberd_s2s:tls_verify(LServer).
|
||||
|
||||
tls_enabled(#{server_host := LServer}) ->
|
||||
ejabberd_s2s:tls_enabled(LServer).
|
||||
|
||||
@@ -201,9 +195,9 @@ handle_auth_failure(RServer, Mech, Reason,
|
||||
#{socket := Socket, ip := IP,
|
||||
server_host := ServerHost,
|
||||
lserver := LServer} = State) ->
|
||||
?INFO_MSG("(~s) Failed inbound s2s ~s authentication ~s -> ~s (~s): ~s",
|
||||
[xmpp_socket:pp(Socket), Mech, RServer, LServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
|
||||
?WARNING_MSG("(~s) Failed inbound s2s ~s authentication ~s -> ~s (~s): ~s",
|
||||
[xmpp_socket:pp(Socket), Mech, RServer, LServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
|
||||
ejabberd_hooks:run_fold(s2s_in_auth_result,
|
||||
ServerHost, State, [false, RServer]).
|
||||
|
||||
@@ -344,52 +338,24 @@ set_idle_timeout(State) ->
|
||||
change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
|
||||
RServer) ->
|
||||
Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
|
||||
xmpp_stream_in:change_shaper(State, Shaper).
|
||||
xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)).
|
||||
|
||||
-spec listen_opt_type(shaper) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(cafile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(tls) -> fun((boolean()) -> boolean());
|
||||
(supervisor) -> fun((boolean()) -> boolean());
|
||||
(max_stanza_type) -> fun((timeout()) -> timeout());
|
||||
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
|
||||
(inet) -> fun((boolean()) -> boolean());
|
||||
(inet6) -> fun((boolean()) -> boolean());
|
||||
(backlog) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(certfile = Opt) ->
|
||||
fun(S) ->
|
||||
?WARNING_MSG("Listening option '~s' for ~s is deprecated, use "
|
||||
"'certfiles' global option instead", [Opt, ?MODULE]),
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
ok = ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) -> ejabberd_s2s:opt_type(s2s_ciphers);
|
||||
listen_opt_type(dhfile) -> ejabberd_s2s:opt_type(s2s_dhfile);
|
||||
listen_opt_type(cafile) -> ejabberd_s2s:opt_type(s2s_cafile);
|
||||
listen_opt_type(protocol_options) -> ejabberd_s2s:opt_type(s2s_protocol_options);
|
||||
listen_opt_type(tls_compression) -> ejabberd_s2s:opt_type(s2s_tls_compression);
|
||||
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(max_stanza_size) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
listen_opt_type(max_fsm_queue) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(backlog) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(accept_interval) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
listen_opt_type(_) ->
|
||||
[shaper, certfile, ciphers, dhfile, cafile, protocol_options,
|
||||
tls_compression, tls, max_fsm_queue, backlog, inet, inet6,
|
||||
accept_interval].
|
||||
end.
|
||||
|
||||
listen_options() ->
|
||||
[{shaper, none},
|
||||
{certfile, undefined},
|
||||
{ciphers, undefined},
|
||||
{dhfile, undefined},
|
||||
{cafile, undefined},
|
||||
{protocol_options, undefined},
|
||||
{tls, false},
|
||||
{tls_compression, false},
|
||||
{max_stanza_size, infinity},
|
||||
{max_fsm_queue, 5000}].
|
||||
|
||||
+12
-18
@@ -138,9 +138,9 @@ host_down(Host) ->
|
||||
process_auth_result(#{server := LServer, remote_server := RServer} = State,
|
||||
{false, Reason}) ->
|
||||
Delay = get_delay(),
|
||||
?INFO_MSG("Failed to establish outbound s2s connection ~s -> ~s: "
|
||||
"authentication failed; bouncing for ~p seconds",
|
||||
[LServer, RServer, Delay]),
|
||||
?WARNING_MSG("Failed to establish outbound s2s connection ~s -> ~s: "
|
||||
"authentication failed; bouncing for ~p seconds",
|
||||
[LServer, RServer, Delay]),
|
||||
State1 = State#{on_route => bounce, stop_reason => Reason},
|
||||
State2 = close(State1),
|
||||
State3 = bounce_queue(State2),
|
||||
@@ -157,9 +157,9 @@ process_closed(#{server := LServer, remote_server := RServer,
|
||||
process_closed(#{server := LServer, remote_server := RServer} = State,
|
||||
Reason) ->
|
||||
Delay = get_delay(),
|
||||
?INFO_MSG("Failed to establish outbound s2s connection ~s -> ~s: ~s; "
|
||||
"bouncing for ~p seconds",
|
||||
[LServer, RServer, format_error(Reason), Delay]),
|
||||
?WARNING_MSG("Failed to establish outbound s2s connection ~s -> ~s: ~s; "
|
||||
"bouncing for ~p seconds",
|
||||
[LServer, RServer, format_error(Reason), Delay]),
|
||||
State1 = State#{on_route => bounce},
|
||||
State2 = bounce_queue(State1),
|
||||
xmpp_stream_out:set_timeout(State2, timer:seconds(Delay)).
|
||||
@@ -223,10 +223,10 @@ handle_auth_failure(Mech, Reason,
|
||||
remote_server := RServer,
|
||||
server_host := ServerHost,
|
||||
server := LServer} = State) ->
|
||||
?INFO_MSG("(~s) Failed outbound s2s ~s authentication ~s -> ~s (~s): ~s",
|
||||
[xmpp_socket:pp(Socket), Mech, LServer, RServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP)),
|
||||
xmpp_stream_out:format_error(Reason)]),
|
||||
?WARNING_MSG("(~s) Failed outbound s2s ~s authentication ~s -> ~s (~s): ~s",
|
||||
[xmpp_socket:pp(Socket), Mech, LServer, RServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP)),
|
||||
xmpp_stream_out:format_error(Reason)]),
|
||||
ejabberd_hooks:run_fold(s2s_out_auth_result, ServerHost, State, [{false, Reason}]).
|
||||
|
||||
handle_packet(Pkt, #{server_host := ServerHost} = State) ->
|
||||
@@ -375,7 +375,7 @@ mk_bounce_error(_Lang, _State) ->
|
||||
-spec get_delay() -> non_neg_integer().
|
||||
get_delay() ->
|
||||
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
|
||||
randoms:uniform(MaxDelay).
|
||||
p1_rand:uniform(MaxDelay).
|
||||
|
||||
-spec set_idle_timeout(state()) -> state().
|
||||
set_idle_timeout(#{on_route := send, server := LServer} = State) ->
|
||||
@@ -443,13 +443,7 @@ maybe_report_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
|
||||
maybe_report_huge_timeout(_, _) ->
|
||||
ok.
|
||||
|
||||
-spec opt_type(outgoing_s2s_families) -> fun(([ipv4|ipv6]) -> [inet|inet6]);
|
||||
(outgoing_s2s_port) -> fun((0..65535) -> 0..65535);
|
||||
(outgoing_s2s_timeout) -> fun((timeout()) -> timeout());
|
||||
(s2s_dns_retries) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(s2s_dns_timeout) -> fun((timeout()) -> timeout());
|
||||
(s2s_max_retry_delay) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(outgoing_s2s_families) ->
|
||||
fun(Families) ->
|
||||
lists:map(
|
||||
|
||||
+43
-69
@@ -21,20 +21,19 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_service).
|
||||
-behaviour(xmpp_stream_in).
|
||||
-behaviour(xmpp_socket).
|
||||
-behaviour(ejabberd_listener).
|
||||
|
||||
-protocol({xep, 114, '1.6'}).
|
||||
|
||||
%% xmpp_socket callbacks
|
||||
-export([start/2, start_link/2, socket_type/0, close/1, close/2]).
|
||||
%% ejabberd_listener callbacks
|
||||
-export([listen_opt_type/1, transform_listen_option/2]).
|
||||
-export([start/2, start_link/2, accept/1]).
|
||||
-export([listen_opt_type/1, listen_options/0, transform_listen_option/2]).
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_info/2, terminate/2, code_change/3]).
|
||||
-export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4,
|
||||
handle_authenticated_packet/2, get_password_fun/1, tls_options/1]).
|
||||
%% API
|
||||
-export([send/2]).
|
||||
-export([send/2, close/1, close/2]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -53,8 +52,8 @@ start_link(SockData, Opts) ->
|
||||
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
|
||||
ejabberd_config:fsm_limit_opts(Opts)).
|
||||
|
||||
socket_type() ->
|
||||
xml_stream.
|
||||
accept(Ref) ->
|
||||
xmpp_stream_in:accept(Ref).
|
||||
|
||||
-spec send(pid(), xmpp_element()) -> ok;
|
||||
(state(), xmpp_element()) -> state().
|
||||
@@ -79,7 +78,8 @@ tls_options(#{tls_options := TLSOptions}) ->
|
||||
|
||||
init([State, Opts]) ->
|
||||
Access = proplists:get_value(access, Opts, all),
|
||||
Shaper = proplists:get_value(shaper_rule, Opts, none),
|
||||
Shaper = proplists:get_value(shaper, Opts,
|
||||
proplists:get_value(shaper_rule, Opts, none)),
|
||||
GlobalPassword = proplists:get_value(password, Opts, random_password()),
|
||||
HostOpts = proplists:get_value(hosts, Opts, [{global, GlobalPassword}]),
|
||||
HostOpts1 = lists:map(
|
||||
@@ -101,7 +101,7 @@ init([State, Opts]) ->
|
||||
end,
|
||||
GlobalRoutes = proplists:get_value(global_routes, Opts, true),
|
||||
Timeout = ejabberd_config:negotiation_timeout(),
|
||||
State1 = xmpp_stream_in:change_shaper(State, Shaper),
|
||||
State1 = xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)),
|
||||
State2 = xmpp_stream_in:set_timeout(State1, Timeout),
|
||||
State3 = State2#{access => Access,
|
||||
xmlns => ?NS_COMPONENT,
|
||||
@@ -146,10 +146,10 @@ get_password_fun(#{remote_server := RemoteServer,
|
||||
{ok, Password} ->
|
||||
{Password, undefined};
|
||||
error ->
|
||||
?INFO_MSG("(~s) Domain ~s is unconfigured for "
|
||||
"external component from ~s",
|
||||
[xmpp_socket:pp(Socket), RemoteServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
|
||||
?WARNING_MSG("(~s) Domain ~s is unconfigured for "
|
||||
"external component from ~s",
|
||||
[xmpp_socket:pp(Socket), RemoteServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
|
||||
{false, undefined}
|
||||
end
|
||||
end.
|
||||
@@ -177,11 +177,11 @@ handle_auth_success(_, Mech, _,
|
||||
handle_auth_failure(_, Mech, Reason,
|
||||
#{remote_server := RemoteServer,
|
||||
socket := Socket, ip := IP} = State) ->
|
||||
?INFO_MSG("(~s) Failed external component ~s authentication "
|
||||
"for ~s from ~s: ~s",
|
||||
[xmpp_socket:pp(Socket), Mech, RemoteServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP)),
|
||||
Reason]),
|
||||
?WARNING_MSG("(~s) Failed external component ~s authentication "
|
||||
"for ~s from ~s: ~s",
|
||||
[xmpp_socket:pp(Socket), Mech, RemoteServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP)),
|
||||
Reason]),
|
||||
State.
|
||||
|
||||
handle_authenticated_packet(Pkt0, #{ip := {IP, _}, lang := Lang} = State)
|
||||
@@ -261,7 +261,7 @@ check_from(From, #{host_opts := HostOpts}) ->
|
||||
dict:is_key(Server, HostOpts).
|
||||
|
||||
random_password() ->
|
||||
str:sha(randoms:bytes(20)).
|
||||
str:sha(p1_rand:bytes(20)).
|
||||
|
||||
transform_listen_option({hosts, Hosts, O}, Opts) ->
|
||||
case lists:keyfind(hosts, 1, Opts) of
|
||||
@@ -281,39 +281,12 @@ transform_listen_option({host, Host, Os}, Opts) ->
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-spec listen_opt_type(access) -> fun((any()) -> any());
|
||||
(shaper_rule) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(cafile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(tls) -> fun((boolean()) -> boolean());
|
||||
(check_from) -> fun((boolean()) -> boolean());
|
||||
(password) -> fun((boolean()) -> boolean());
|
||||
(hosts) -> fun(([{binary(), [{password, binary()}]}]) ->
|
||||
[{binary(), binary() | undefined}]);
|
||||
(max_stanza_type) -> fun((timeout()) -> timeout());
|
||||
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
|
||||
(inet) -> fun((boolean()) -> boolean());
|
||||
(inet6) -> fun((boolean()) -> boolean());
|
||||
(backlog) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
listen_opt_type(shaper_rule) -> fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
listen_opt_type(shaper_rule) ->
|
||||
fun(V) ->
|
||||
?WARNING_MSG("Listening option 'shaper_rule' of module ~s "
|
||||
"is renamed to 'shaper'", [?MODULE]),
|
||||
acl:shaper_rules_validator(V)
|
||||
end;
|
||||
listen_opt_type(ciphers) -> fun iolist_to_binary/1;
|
||||
listen_opt_type(dhfile) -> fun misc:try_read_file/1;
|
||||
listen_opt_type(cafile) -> fun misc:try_read_file/1;
|
||||
listen_opt_type(protocol_options) ->
|
||||
fun(Options) -> str:join(Options, <<"|">>) end;
|
||||
listen_opt_type(tls_compression) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(check_from) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(password) -> fun iolist_to_binary/1;
|
||||
listen_opt_type(hosts) ->
|
||||
@@ -328,21 +301,22 @@ listen_opt_type(hosts) ->
|
||||
end, HostOpts)
|
||||
end;
|
||||
listen_opt_type(global_routes) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(max_stanza_size) ->
|
||||
fun(I) when is_integer(I) -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
listen_opt_type(max_fsm_queue) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(backlog) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(accept_interval) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
listen_opt_type(_) ->
|
||||
[access, shaper_rule, certfile, ciphers, dhfile, cafile, tls,
|
||||
protocol_options, tls_compression, password, hosts, check_from,
|
||||
max_fsm_queue, global_routes, backlog, inet, inet6, accept_interval].
|
||||
fun(B) when is_boolean(B) -> B end.
|
||||
|
||||
listen_options() ->
|
||||
[{access, all},
|
||||
{shaper, none},
|
||||
{shaper_rule, none},
|
||||
{certfile, undefined},
|
||||
{ciphers, undefined},
|
||||
{dhfile, undefined},
|
||||
{cafile, undefined},
|
||||
{protocol_options, undefined},
|
||||
{tls, false},
|
||||
{tls_compression, false},
|
||||
{max_stanza_size, infinity},
|
||||
{max_fsm_queue, 5000},
|
||||
{password, undefined},
|
||||
{hosts, []},
|
||||
{check_from, true},
|
||||
{global_routes, true}].
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : shaper.erl
|
||||
%%% File : ejabberd_shaper.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Functions to control connections traffic
|
||||
%%% Created : 9 Feb 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
@@ -23,7 +23,7 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(shaper).
|
||||
-module(ejabberd_shaper).
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(ejabberd_config).
|
||||
@@ -39,19 +39,13 @@
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
-record(maxrate, {maxrate = 0 :: integer(),
|
||||
burst_size = 0 :: integer(),
|
||||
acquired_credit = 0 :: integer(),
|
||||
lasttime = 0 :: integer()}).
|
||||
|
||||
-record(shaper, {name :: {atom(), global},
|
||||
maxrate :: integer(),
|
||||
burst_size :: integer()}).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
-type shaper() :: none | #maxrate{}.
|
||||
|
||||
-type shaper() :: none | p1_shaper:state().
|
||||
-export_type([shaper/0]).
|
||||
|
||||
-spec start_link() -> {ok, pid()} | {error, any()}.
|
||||
@@ -84,7 +78,6 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
-spec load_from_config() -> ok | {error, any()}.
|
||||
|
||||
load_from_config() ->
|
||||
Shapers = ejabberd_config:get_option(shaper, []),
|
||||
case mnesia:transaction(
|
||||
@@ -105,7 +98,6 @@ load_from_config() ->
|
||||
end.
|
||||
|
||||
-spec get_max_rate(atom()) -> none | non_neg_integer().
|
||||
|
||||
get_max_rate(none) ->
|
||||
none;
|
||||
get_max_rate(Name) ->
|
||||
@@ -122,29 +114,18 @@ new(none) ->
|
||||
new(Name) ->
|
||||
case ets:lookup(shaper, {Name, global}) of
|
||||
[#shaper{maxrate = R, burst_size = B}] ->
|
||||
|
||||
#maxrate{maxrate = R, burst_size = B,
|
||||
acquired_credit = B,
|
||||
lasttime = p1_time_compat:system_time(micro_seconds)};
|
||||
p1_shaper:new(R, B);
|
||||
[] ->
|
||||
none
|
||||
end.
|
||||
|
||||
-spec update(shaper(), integer()) -> {shaper(), integer()}.
|
||||
|
||||
update(none, _Size) -> {none, 0};
|
||||
update(#maxrate{maxrate = MR, burst_size = BS,
|
||||
acquired_credit = AC, lasttime = L} = State, Size) ->
|
||||
Now = p1_time_compat:system_time(micro_seconds),
|
||||
AC2 = min(BS, AC + (MR*(Now - L) div 1000000) - Size),
|
||||
|
||||
Pause = if AC2 >= 0 -> 0;
|
||||
true -> -1000*AC2 div MR
|
||||
end,
|
||||
?DEBUG("MaxRate=~p, BurstSize=~p, AcquiredCredit=~p, Size=~p, NewAcquiredCredit=~p, Pause=~p",
|
||||
[MR, BS, AC, Size, AC2, Pause]),
|
||||
{State#maxrate{acquired_credit = AC2, lasttime = Now},
|
||||
Pause}.
|
||||
update(Shaper, Size) ->
|
||||
Result = p1_shaper:update(Shaper, Size),
|
||||
?DEBUG("Shaper update:~n~s =>~n~s",
|
||||
[p1_shaper:pp(Shaper), p1_shaper:pp(Result)]),
|
||||
Result.
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
@@ -166,7 +147,6 @@ transform_options({shaper, List}, Opts) when is_list(List) ->
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt | Opts].
|
||||
|
||||
-spec opt_type(shaper) -> fun((any()) -> any());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(shaper) -> fun(V) -> V end;
|
||||
opt_type(_) -> [shaper].
|
||||
+23
-27
@@ -24,25 +24,28 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_sip).
|
||||
-behaviour(ejabberd_listener).
|
||||
|
||||
-ifndef(SIP).
|
||||
-include("logger.hrl").
|
||||
-export([socket_type/0, start/2, listen_opt_type/1]).
|
||||
log_error() ->
|
||||
?CRITICAL_MSG("ejabberd is not compiled with SIP support", []).
|
||||
socket_type() ->
|
||||
log_error(),
|
||||
raw.
|
||||
listen_opt_type(_) ->
|
||||
log_error(),
|
||||
[].
|
||||
-export([accept/1, start/2, start_link/2, listen_options/0]).
|
||||
fail() ->
|
||||
?CRITICAL_MSG("Listening module ~s is not available: "
|
||||
"ejabberd is not compiled with SIP support",
|
||||
[?MODULE]),
|
||||
erlang:error(sip_not_compiled).
|
||||
accept(_) ->
|
||||
fail().
|
||||
listen_options() ->
|
||||
fail().
|
||||
start(_, _) ->
|
||||
log_error(),
|
||||
{error, sip_not_compiled}.
|
||||
fail().
|
||||
start_link(_, _) ->
|
||||
fail().
|
||||
-else.
|
||||
%% API
|
||||
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
|
||||
socket_type/0, listen_opt_type/1]).
|
||||
start_link/2, accept/1, listen_options/0]).
|
||||
|
||||
|
||||
%%%===================================================================
|
||||
@@ -62,8 +65,11 @@ udp_recv(Sock, Addr, Port, Data, Opts) ->
|
||||
start(Opaque, Opts) ->
|
||||
esip_socket:start(Opaque, Opts).
|
||||
|
||||
socket_type() ->
|
||||
raw.
|
||||
start_link({gen_tcp, Sock}, Opts) ->
|
||||
esip_socket:start_link(Sock, Opts).
|
||||
|
||||
accept(_) ->
|
||||
ok.
|
||||
|
||||
set_certfile(Opts) ->
|
||||
case lists:keymember(certfile, 1, Opts) of
|
||||
@@ -83,19 +89,9 @@ set_certfile(Opts) ->
|
||||
end
|
||||
end.
|
||||
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
%% We cannot deprecate the option for now:
|
||||
%% I think SIP clients are too stupid to set SNI
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(tls) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(accept_interval) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
listen_opt_type(_) ->
|
||||
[tls, certfile, accept_interval].
|
||||
listen_options() ->
|
||||
[{tls, false},
|
||||
{certfile, undefined}].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
|
||||
+20
-17
@@ -142,9 +142,10 @@ route(Packet) ->
|
||||
Packet1 ->
|
||||
try do_route(Packet1), ok
|
||||
catch E:R ->
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("failed to route packet:~n~s~nReason = ~p",
|
||||
[xmpp:pp(Packet1),
|
||||
{E, {R, erlang:get_stacktrace()}}])
|
||||
{E, {R, St}}])
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -426,15 +427,22 @@ config_reloaded() ->
|
||||
init([]) ->
|
||||
process_flag(trap_exit, true),
|
||||
init_cache(),
|
||||
lists:foreach(fun(Mod) -> Mod:init() end, get_sm_backends()),
|
||||
clean_cache(),
|
||||
gen_iq_handler:start(?MODULE),
|
||||
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
|
||||
ejabberd_hooks:add(host_down, ?MODULE, host_down, 60),
|
||||
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
|
||||
lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()),
|
||||
ejabberd_commands:register_commands(get_commands_spec()),
|
||||
{ok, #state{}}.
|
||||
case lists:foldl(
|
||||
fun(Mod, ok) -> Mod:init();
|
||||
(_, Err) -> Err
|
||||
end, ok, get_sm_backends()) of
|
||||
ok ->
|
||||
clean_cache(),
|
||||
gen_iq_handler:start(?MODULE),
|
||||
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
|
||||
ejabberd_hooks:add(host_down, ?MODULE, host_down, 60),
|
||||
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
|
||||
lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()),
|
||||
ejabberd_commands:register_commands(get_commands_spec()),
|
||||
{ok, #state{}};
|
||||
{error, Why} ->
|
||||
{stop, Why}
|
||||
end.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok, {reply, Reply, State}.
|
||||
@@ -890,7 +898,7 @@ cache_opts() ->
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec clean_cache(node()) -> ok.
|
||||
-spec clean_cache(node()) -> non_neg_integer().
|
||||
clean_cache(Node) ->
|
||||
ets_cache:filter(
|
||||
?SM_CACHE,
|
||||
@@ -1004,12 +1012,7 @@ kick_user(User, Server, Resource) ->
|
||||
make_sid() ->
|
||||
{p1_time_compat:unique_timestamp(), self()}.
|
||||
|
||||
-spec opt_type(sm_db_type) -> fun((atom()) -> atom());
|
||||
(sm_use_cache) -> fun((boolean()) -> boolean());
|
||||
(sm_cache_missed) -> fun((boolean()) -> boolean());
|
||||
(sm_cache_size) -> fun((timeout()) -> timeout());
|
||||
(sm_cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(O) when O == sm_use_cache; O == sm_cache_missed ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
|
||||
+79
-28
@@ -27,12 +27,11 @@
|
||||
-define(GEN_SERVER, p1_server).
|
||||
-endif.
|
||||
-behaviour(?GEN_SERVER).
|
||||
|
||||
-behaviour(ejabberd_sm).
|
||||
|
||||
-export([init/0, set_session/1, delete_session/1,
|
||||
get_sessions/0, get_sessions/1, get_sessions/2,
|
||||
cache_nodes/1]).
|
||||
cache_nodes/1, clean_table/1, clean_table/0]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_cast/2, handle_call/3, handle_info/2,
|
||||
terminate/2, code_change/3, start_link/0]).
|
||||
@@ -41,6 +40,7 @@
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(SM_KEY, <<"ejabberd:sm">>).
|
||||
-define(MIN_REDIS_VERSION, <<"3.2.0">>).
|
||||
-record(state, {}).
|
||||
|
||||
%%%===================================================================
|
||||
@@ -70,10 +70,14 @@ set_session(Session) ->
|
||||
SIDKey = sid_to_key(Session#session.sid),
|
||||
ServKey = server_to_key(element(2, Session#session.us)),
|
||||
USSIDKey = us_sid_to_key(Session#session.us, Session#session.sid),
|
||||
NodeHostKey = node_host_to_key(node(), element(2, Session#session.us)),
|
||||
case ejabberd_redis:multi(
|
||||
fun() ->
|
||||
ejabberd_redis:hset(USKey, SIDKey, T),
|
||||
ejabberd_redis:hset(ServKey, USSIDKey, T),
|
||||
ejabberd_redis:hset(NodeHostKey,
|
||||
<<USKey/binary, "||", SIDKey/binary>>,
|
||||
USSIDKey),
|
||||
ejabberd_redis:publish(
|
||||
?SM_KEY, term_to_binary({delete, Session#session.us}))
|
||||
end) of
|
||||
@@ -89,10 +93,13 @@ delete_session(#session{sid = SID} = Session) ->
|
||||
SIDKey = sid_to_key(SID),
|
||||
ServKey = server_to_key(element(2, Session#session.us)),
|
||||
USSIDKey = us_sid_to_key(Session#session.us, SID),
|
||||
NodeHostKey = node_host_to_key(node(), element(2, Session#session.us)),
|
||||
case ejabberd_redis:multi(
|
||||
fun() ->
|
||||
ejabberd_redis:hdel(USKey, [SIDKey]),
|
||||
ejabberd_redis:hdel(ServKey, [USSIDKey]),
|
||||
ejabberd_redis:hdel(NodeHostKey,
|
||||
[<<USKey/binary, "||", SIDKey/binary>>]),
|
||||
ejabberd_redis:publish(
|
||||
?SM_KEY,
|
||||
term_to_binary({delete, Session#session.us}))
|
||||
@@ -136,8 +143,10 @@ get_sessions(LUser, LServer) ->
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
ejabberd_redis:subscribe([?SM_KEY]),
|
||||
clean_table(),
|
||||
{ok, #state{}}.
|
||||
case clean_table() of
|
||||
ok -> {ok, #state{}};
|
||||
{error, Why} -> {stop, Why}
|
||||
end.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
@@ -168,10 +177,10 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
us_to_key({LUser, LServer}) ->
|
||||
<<"ejabberd:sm:", LUser/binary, "@", LServer/binary>>.
|
||||
<<(?SM_KEY)/binary, ":", LUser/binary, "@", LServer/binary>>.
|
||||
|
||||
server_to_key(LServer) ->
|
||||
<<"ejabberd:sm:", LServer/binary>>.
|
||||
<<(?SM_KEY)/binary, ":", LServer/binary>>.
|
||||
|
||||
us_sid_to_key(US, SID) ->
|
||||
term_to_binary({US, SID}).
|
||||
@@ -179,33 +188,75 @@ us_sid_to_key(US, SID) ->
|
||||
sid_to_key(SID) ->
|
||||
term_to_binary(SID).
|
||||
|
||||
node_session_deletion_cursor(Node, Host) ->
|
||||
NodeName = node_host_to_key(Node, Host),
|
||||
<<NodeName/binary, ":deletioncursor">>.
|
||||
|
||||
node_host_to_key(Node, Host) when is_atom(Node) ->
|
||||
NodeBin = atom_to_binary(node(), utf8),
|
||||
node_host_to_key(NodeBin, Host);
|
||||
node_host_to_key(NodeBin, Host) ->
|
||||
HostKey = server_to_key(Host),
|
||||
<<HostKey/binary, ":node:", NodeBin/binary>>.
|
||||
|
||||
decode_session_list(Vals) ->
|
||||
[binary_to_term(Val) || {_, Val} <- Vals].
|
||||
|
||||
clean_table() ->
|
||||
?DEBUG("Cleaning Redis SM table...", []),
|
||||
clean_table(node()).
|
||||
|
||||
clean_table(Node) when is_atom(Node) ->
|
||||
clean_table(atom_to_binary(Node, utf8));
|
||||
clean_table(Node) ->
|
||||
?DEBUG("Cleaning Redis SM table... ", []),
|
||||
try
|
||||
lists:foreach(
|
||||
fun(LServer) ->
|
||||
ServKey = server_to_key(LServer),
|
||||
{ok, Vals} = ejabberd_redis:hkeys(ServKey),
|
||||
{ok, _} =
|
||||
ejabberd_redis:multi(
|
||||
fun() ->
|
||||
lists:foreach(
|
||||
fun(USSIDKey) ->
|
||||
{US, SID} = binary_to_term(USSIDKey),
|
||||
if node(element(2, SID)) == node() ->
|
||||
USKey = us_to_key(US),
|
||||
SIDKey = sid_to_key(SID),
|
||||
ejabberd_redis:hdel(ServKey, [USSIDKey]),
|
||||
ejabberd_redis:hdel(USKey, [SIDKey]);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, Vals)
|
||||
end)
|
||||
fun(Host) ->
|
||||
ok = clean_node_sessions(Node, Host)
|
||||
end, ejabberd_sm:get_vh_by_backend(?MODULE))
|
||||
catch _:{badmatch, {error, _}} ->
|
||||
?ERROR_MSG("failed to clean redis c2s sessions", [])
|
||||
catch _:{badmatch, {error, _} = Err} ->
|
||||
?ERROR_MSG("Failed to clean Redis SM table", []),
|
||||
Err
|
||||
end.
|
||||
|
||||
clean_node_sessions(Node, Host) ->
|
||||
case load_script() of
|
||||
{ok, SHA} ->
|
||||
clean_node_sessions(Node, Host, SHA);
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
clean_node_sessions(Node, Host, SHA) ->
|
||||
Keys = [node_host_to_key(Node, Host),
|
||||
server_to_key(Host),
|
||||
node_session_deletion_cursor(Node, Host)],
|
||||
case ejabberd_redis:evalsha(SHA, Keys, [1000]) of
|
||||
{ok, <<"0">>} ->
|
||||
ok;
|
||||
{ok, _Cursor} ->
|
||||
clean_node_sessions(Node, Host, SHA);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
load_script() ->
|
||||
case misc:read_lua("redis_sm.lua") of
|
||||
{ok, Data} ->
|
||||
case ejabberd_redis:info(server) of
|
||||
{ok, Info} ->
|
||||
case proplists:get_value(redis_version, Info) of
|
||||
V when V >= ?MIN_REDIS_VERSION ->
|
||||
ejabberd_redis:script_load(Data);
|
||||
V ->
|
||||
?CRITICAL_MSG("Unsupported Redis version: ~s. "
|
||||
"The version must be ~s or above",
|
||||
[V, ?MIN_REDIS_VERSION]),
|
||||
{error, unsupported_redis_version}
|
||||
end;
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
@@ -55,7 +55,7 @@ init() ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to clean 'sm' table: ~p", [Err]),
|
||||
Err
|
||||
{error, db_failure}
|
||||
end;
|
||||
(_, Err) ->
|
||||
Err
|
||||
|
||||
+72
-45
@@ -136,7 +136,7 @@ start_link(Host, StartInterval) ->
|
||||
-spec sql_query(binary(), sql_query()) -> sql_query_result().
|
||||
|
||||
sql_query(Host, Query) ->
|
||||
check_error(sql_call(Host, {sql_query, Query}), Query).
|
||||
sql_call(Host, {sql_query, Query}).
|
||||
|
||||
%% SQL transaction based on a list of queries
|
||||
%% This function automatically
|
||||
@@ -172,10 +172,16 @@ sql_call(Host, Msg) ->
|
||||
end.
|
||||
|
||||
keep_alive(Host, PID) ->
|
||||
sync_send_event(PID,
|
||||
case sync_send_event(PID,
|
||||
{sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
|
||||
p1_time_compat:monotonic_time(milli_seconds)},
|
||||
query_timeout(Host)).
|
||||
query_timeout(Host)) of
|
||||
{selected,[<<"1">>],[[<<"1">>]]} ->
|
||||
ok;
|
||||
_Err ->
|
||||
?ERROR_MSG("keep alive query failed, closing connection: ~p", [_Err]),
|
||||
sync_send_event(PID, force_timeout, query_timeout(Host))
|
||||
end.
|
||||
|
||||
sync_send_event(Pid, Msg, Timeout) ->
|
||||
try p1_fsm:sync_send_event(Pid, Msg, Timeout)
|
||||
@@ -335,10 +341,10 @@ connecting(connect, #state{host = Host} = State) ->
|
||||
State2 = get_db_version(State1),
|
||||
{next_state, session_established, State2};
|
||||
{error, Reason} ->
|
||||
?INFO_MSG("~p connection failed:~n** Reason: ~p~n** "
|
||||
"Retry after: ~p seconds",
|
||||
[State#state.db_type, Reason,
|
||||
State#state.start_interval div 1000]),
|
||||
?WARNING_MSG("~p connection failed:~n** Reason: ~p~n** "
|
||||
"Retry after: ~p seconds",
|
||||
[State#state.db_type, Reason,
|
||||
State#state.start_interval div 1000]),
|
||||
p1_fsm:send_event_after(State#state.start_interval,
|
||||
connect),
|
||||
{next_state, connecting, State}
|
||||
@@ -389,6 +395,8 @@ session_established(Request, {Who, _Ref}, State) ->
|
||||
session_established({sql_cmd, Command, From, Timestamp},
|
||||
State) ->
|
||||
run_sql_cmd(Command, From, State, Timestamp);
|
||||
session_established(force_timeout, State) ->
|
||||
{stop, timeout, State};
|
||||
session_established(Event, State) ->
|
||||
?WARNING_MSG("unexpected event in 'session_established': ~p",
|
||||
[Event]),
|
||||
@@ -580,18 +588,19 @@ sql_query_internal(#sql_query{} = Query) ->
|
||||
sqlite ->
|
||||
sqlite_sql_query(Query)
|
||||
end
|
||||
catch
|
||||
Class:Reason ->
|
||||
catch exit:{timeout, _} ->
|
||||
{error, <<"timed out">>};
|
||||
exit:{killed, _} ->
|
||||
{error, <<"killed">>};
|
||||
exit:{normal, _} ->
|
||||
{error, <<"terminated unexpectedly">>};
|
||||
Class:Reason ->
|
||||
ST = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("Internal error while processing SQL query: ~p",
|
||||
[{Class, Reason, ST}]),
|
||||
{error, <<"internal error">>}
|
||||
end,
|
||||
case Res of
|
||||
{error, <<"No SQL-driver information available.">>} ->
|
||||
{updated, 0};
|
||||
_Else -> Res
|
||||
end;
|
||||
check_error(Res, Query);
|
||||
sql_query_internal(F) when is_function(F) ->
|
||||
case catch execute_fun(F) of
|
||||
{'EXIT', Reason} -> {error, Reason};
|
||||
@@ -616,17 +625,12 @@ sql_query_internal(Query) ->
|
||||
[Query], self(),
|
||||
[{timeout, QueryTimeout - 1000},
|
||||
{result_type, binary}])),
|
||||
%% ?INFO_MSG("MySQL, Received result~n~p~n", [R]),
|
||||
R;
|
||||
sqlite ->
|
||||
Host = State#state.host,
|
||||
sqlite_to_odbc(Host, sqlite3:sql_exec(sqlite_db(Host), Query))
|
||||
end,
|
||||
case Res of
|
||||
{error, <<"No SQL-driver information available.">>} ->
|
||||
{updated, 0};
|
||||
_Else -> Res
|
||||
end.
|
||||
check_error(Res, Query).
|
||||
|
||||
select_sql_query(Queries, State) ->
|
||||
select_sql_query(
|
||||
@@ -745,14 +749,23 @@ sql_query_to_iolist(SQLQuery) ->
|
||||
generic_sql_query_format(SQLQuery).
|
||||
|
||||
%% Generate the OTP callback return tuple depending on the driver result.
|
||||
abort_on_driver_error({error, <<"query timed out">>} =
|
||||
Reply,
|
||||
abort_on_driver_error({error,
|
||||
<<"query timed out">>} = Reply,
|
||||
From) ->
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, timeout, get(?STATE_KEY)};
|
||||
abort_on_driver_error({error,
|
||||
<<"Failed sending data on socket", _/binary>>} =
|
||||
Reply,
|
||||
<<"Failed sending data on socket", _/binary>>} = Reply,
|
||||
From) ->
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, closed, get(?STATE_KEY)};
|
||||
abort_on_driver_error({error,
|
||||
<<"SQL connection failed">>} = Reply,
|
||||
From) ->
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, timeout, get(?STATE_KEY)};
|
||||
abort_on_driver_error({error,
|
||||
<<"Communication link failure">>} = Reply,
|
||||
From) ->
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, closed, get(?STATE_KEY)};
|
||||
@@ -768,6 +781,7 @@ odbc_connect(SQLServer, Timeout) ->
|
||||
ejabberd:start_app(odbc),
|
||||
odbc:connect(binary_to_list(SQLServer),
|
||||
[{scrollable_cursors, off},
|
||||
{extended_errors, on},
|
||||
{tuple_row, off},
|
||||
{timeout, Timeout},
|
||||
{binary_strings, on}]).
|
||||
@@ -1029,6 +1043,7 @@ init_mssql(Host) ->
|
||||
FreeTDS = io_lib:fwrite("[~s]~n"
|
||||
"\thost = ~s~n"
|
||||
"\tport = ~p~n"
|
||||
"\tclient charset = UTF-8~n"
|
||||
"\ttds version = 7.1~n",
|
||||
[Host, Server, Port]),
|
||||
ODBCINST = io_lib:fwrite("[freetds]~n"
|
||||
@@ -1093,37 +1108,49 @@ query_timeout(LServer) ->
|
||||
timer:seconds(
|
||||
ejabberd_config:get_option({sql_query_timeout, LServer}, 60)).
|
||||
|
||||
%% ***IMPORTANT*** This error format requires extended_errors turned on.
|
||||
extended_error({"08S01", _, Reason}) ->
|
||||
% TCP Provider: The specified network name is no longer available
|
||||
?DEBUG("ODBC Link Failure: ~s", [Reason]),
|
||||
<<"Communication link failure">>;
|
||||
extended_error({"08001", _, Reason}) ->
|
||||
% Login timeout expired
|
||||
?DEBUG("ODBC Connect Timeout: ~s", [Reason]),
|
||||
<<"SQL connection failed">>;
|
||||
extended_error({"IMC01", _, Reason}) ->
|
||||
% The connection is broken and recovery is not possible
|
||||
?DEBUG("ODBC Link Failure: ~s", [Reason]),
|
||||
<<"Communication link failure">>;
|
||||
extended_error({"IMC06", _, Reason}) ->
|
||||
% The connection is broken and recovery is not possible
|
||||
?DEBUG("ODBC Link Failure: ~s", [Reason]),
|
||||
<<"Communication link failure">>;
|
||||
extended_error({Code, _, Reason}) ->
|
||||
?DEBUG("ODBC Error ~s: ~s", [Code, Reason]),
|
||||
iolist_to_binary(Reason);
|
||||
extended_error(Error) ->
|
||||
Error.
|
||||
|
||||
check_error({error, Why} = Err, _Query) when Why == killed ->
|
||||
Err;
|
||||
check_error({error, Why} = Err, #sql_query{} = Query) ->
|
||||
check_error({error, Why}, #sql_query{} = Query) ->
|
||||
Err = extended_error(Why),
|
||||
?ERROR_MSG("SQL query '~s' at ~p failed: ~p",
|
||||
[Query#sql_query.hash, Query#sql_query.loc, Why]),
|
||||
Err;
|
||||
check_error({error, Why} = Err, Query) ->
|
||||
[Query#sql_query.hash, Query#sql_query.loc, Err]),
|
||||
{error, Err};
|
||||
check_error({error, Why}, Query) ->
|
||||
Err = extended_error(Why),
|
||||
case catch iolist_to_binary(Query) of
|
||||
SQuery when is_binary(SQuery) ->
|
||||
?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Why]);
|
||||
?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Err]);
|
||||
_ ->
|
||||
?ERROR_MSG("SQL query ~p failed: ~p", [Query, Why])
|
||||
?ERROR_MSG("SQL query ~p failed: ~p", [Query, Err])
|
||||
end,
|
||||
Err;
|
||||
{error, Err};
|
||||
check_error(Result, _Query) ->
|
||||
Result.
|
||||
|
||||
-spec opt_type(sql_database) -> fun((binary()) -> binary());
|
||||
(sql_keepalive_interval) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_password) -> fun((binary()) -> binary());
|
||||
(sql_port) -> fun((0..65535) -> 0..65535);
|
||||
(sql_server) -> fun((binary()) -> binary());
|
||||
(sql_username) -> fun((binary()) -> binary());
|
||||
(sql_ssl) -> fun((boolean()) -> boolean());
|
||||
(sql_ssl_verify) -> fun((boolean()) -> boolean());
|
||||
(sql_ssl_certfile) -> fun((boolean()) -> boolean());
|
||||
(sql_ssl_cafile) -> fun((boolean()) -> boolean());
|
||||
(sql_query_timeout) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_connect_timeout) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_queue_type) -> fun((ram | file) -> ram | file);
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(sql_database) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_keepalive_interval) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
|
||||
+46
-25
@@ -31,21 +31,19 @@
|
||||
|
||||
-export([start_link/1, init/1, add_pid/2, remove_pid/2,
|
||||
get_pids/1, get_random_pid/1, transform_options/1,
|
||||
opt_type/1]).
|
||||
reload/1, opt_type/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-define(PGSQL_PORT, 5432).
|
||||
|
||||
-define(MYSQL_PORT, 3306).
|
||||
|
||||
-define(DEFAULT_POOL_SIZE, 10).
|
||||
|
||||
-define(DEFAULT_SQL_START_INTERVAL, 30).
|
||||
|
||||
-define(CONNECT_TIMEOUT, 500).
|
||||
|
||||
-record(sql_pool, {host, pid}).
|
||||
-record(sql_pool, {host :: binary(),
|
||||
pid :: pid()}).
|
||||
|
||||
start_link(Host) ->
|
||||
ejabberd_mnesia:create(?MODULE, sql_pool,
|
||||
@@ -59,9 +57,6 @@ start_link(Host) ->
|
||||
?MODULE, [Host]).
|
||||
|
||||
init([Host]) ->
|
||||
StartInterval = ejabberd_config:get_option(
|
||||
{sql_start_interval, Host},
|
||||
?DEFAULT_SQL_START_INTERVAL),
|
||||
Type = ejabberd_config:get_option({sql_type, Host}, odbc),
|
||||
PoolSize = get_pool_size(Type, Host),
|
||||
case Type of
|
||||
@@ -72,16 +67,37 @@ init([Host]) ->
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{ok, {{one_for_one, PoolSize * 10, 1},
|
||||
[child_spec(I, Host) || I <- lists:seq(1, PoolSize)]}}.
|
||||
|
||||
{ok,
|
||||
{{one_for_one, PoolSize * 10, 1},
|
||||
lists:map(fun (I) ->
|
||||
{I,
|
||||
{ejabberd_sql, start_link,
|
||||
[Host, StartInterval * 1000]},
|
||||
transient, 2000, worker, [?MODULE]}
|
||||
end,
|
||||
lists:seq(1, PoolSize))}}.
|
||||
reload(Host) ->
|
||||
Type = ejabberd_config:get_option({sql_type, Host}, odbc),
|
||||
NewPoolSize = get_pool_size(Type, Host),
|
||||
OldPoolSize = ets:select_count(
|
||||
sql_pool,
|
||||
ets:fun2ms(
|
||||
fun(#sql_pool{host = H}) when H == Host ->
|
||||
true
|
||||
end)),
|
||||
reload(Host, NewPoolSize, OldPoolSize).
|
||||
|
||||
reload(Host, NewPoolSize, OldPoolSize) ->
|
||||
Sup = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
if NewPoolSize == OldPoolSize ->
|
||||
ok;
|
||||
NewPoolSize > OldPoolSize ->
|
||||
lists:foreach(
|
||||
fun(I) ->
|
||||
Spec = child_spec(I, Host),
|
||||
supervisor:start_child(Sup, Spec)
|
||||
end, lists:seq(OldPoolSize+1, NewPoolSize));
|
||||
OldPoolSize > NewPoolSize ->
|
||||
lists:foreach(
|
||||
fun(I) ->
|
||||
supervisor:terminate_child(Sup, I),
|
||||
supervisor:delete_child(Sup, I)
|
||||
end, lists:seq(NewPoolSize+1, OldPoolSize))
|
||||
end.
|
||||
|
||||
get_pids(Host) ->
|
||||
Rs = mnesia:dirty_read(sql_pool, Host),
|
||||
@@ -91,7 +107,7 @@ get_random_pid(Host) ->
|
||||
case get_pids(Host) of
|
||||
[] -> none;
|
||||
Pids ->
|
||||
I = randoms:round_robin(length(Pids)) + 1,
|
||||
I = p1_rand:round_robin(length(Pids)) + 1,
|
||||
lists:nth(I, Pids)
|
||||
end.
|
||||
|
||||
@@ -123,6 +139,13 @@ get_pool_size(SQLType, Host) ->
|
||||
end,
|
||||
PoolSize.
|
||||
|
||||
child_spec(I, Host) ->
|
||||
StartInterval = ejabberd_config:get_option(
|
||||
{sql_start_interval, Host},
|
||||
?DEFAULT_SQL_START_INTERVAL),
|
||||
{I, {ejabberd_sql, start_link, [Host, timer:seconds(StartInterval)]},
|
||||
transient, 2000, worker, [?MODULE]}.
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
@@ -167,7 +190,7 @@ check_sqlite_db(Host) ->
|
||||
ok
|
||||
end;
|
||||
{error, Reason} ->
|
||||
?INFO_MSG("Failed open sqlite database, reason ~p", [Reason])
|
||||
?WARNING_MSG("Failed open sqlite database, reason ~p", [Reason])
|
||||
end.
|
||||
|
||||
create_sqlite_tables(DB) ->
|
||||
@@ -180,8 +203,8 @@ create_sqlite_tables(DB) ->
|
||||
[ok = sqlite3:sql_exec(DB, Q) || Q <- Qs],
|
||||
ok = sqlite3:sql_exec(DB, "commit");
|
||||
{error, Reason} ->
|
||||
?INFO_MSG("Failed to read SQLite schema file: ~s",
|
||||
[file:format_error(Reason)])
|
||||
?WARNING_MSG("Failed to read SQLite schema file: ~s",
|
||||
[file:format_error(Reason)])
|
||||
end.
|
||||
|
||||
read_lines(Fd, File, Acc) ->
|
||||
@@ -212,9 +235,7 @@ read_lines(Fd, File, Acc) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec opt_type(sql_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_start_interval) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(sql_pool_size) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(sql_start_interval) ->
|
||||
|
||||
+36
-38
@@ -24,27 +24,29 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_stun).
|
||||
|
||||
-behaviour(ejabberd_listener).
|
||||
-protocol({rfc, 5766}).
|
||||
-protocol({xep, 176, '1.0'}).
|
||||
|
||||
-ifndef(STUN).
|
||||
-include("logger.hrl").
|
||||
-export([socket_type/0, start/2, listen_opt_type/1]).
|
||||
log_error() ->
|
||||
?CRITICAL_MSG("ejabberd is not compiled with STUN/TURN support", []).
|
||||
socket_type() ->
|
||||
log_error(),
|
||||
raw.
|
||||
listen_opt_type(_) ->
|
||||
log_error(),
|
||||
[].
|
||||
-export([accept/1, start/2, start_link/2, listen_options/0]).
|
||||
fail() ->
|
||||
?CRITICAL_MSG("Listening module ~s is not available: "
|
||||
"ejabberd is not compiled with STUN/TURN support",
|
||||
[?MODULE]),
|
||||
erlang:error(stun_not_compiled).
|
||||
accept(_) ->
|
||||
fail().
|
||||
listen_options() ->
|
||||
fail().
|
||||
start(_, _) ->
|
||||
log_error(),
|
||||
{error, sip_not_compiled}.
|
||||
fail().
|
||||
start_link(_, _) ->
|
||||
fail().
|
||||
-else.
|
||||
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
|
||||
socket_type/0, listen_opt_type/1]).
|
||||
start_link/2, accept/1, listen_opt_type/1, listen_options/0]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
@@ -65,8 +67,11 @@ udp_recv(Socket, Addr, Port, Packet, Opts) ->
|
||||
start(Opaque, Opts) ->
|
||||
stun:start(Opaque, Opts).
|
||||
|
||||
socket_type() ->
|
||||
raw.
|
||||
start_link({gen_tcp, Sock}, Opts) ->
|
||||
stun:start_link(Sock, Opts).
|
||||
|
||||
accept(_Pid) ->
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
@@ -107,7 +112,7 @@ prepare_turn_opts(Opts, _UseTurn = true) ->
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
MaxRate = shaper:get_max_rate(Shaper),
|
||||
MaxRate = ejabberd_shaper:get_max_rate(Shaper),
|
||||
Opts1 = Realm ++ [{auth_fun, AuthFun},{shaper, MaxRate} |
|
||||
lists:keydelete(shaper, 1, Opts)],
|
||||
set_certfile(Opts1).
|
||||
@@ -138,27 +143,16 @@ listen_opt_type(turn_ip) ->
|
||||
{ok, Addr} = inet_parse:ipv4_address(binary_to_list(S)),
|
||||
Addr
|
||||
end;
|
||||
listen_opt_type(shaper) ->
|
||||
fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(auth_type) ->
|
||||
fun(anonymous) -> anonymous;
|
||||
(user) -> user
|
||||
end;
|
||||
listen_opt_type(auth_realm) ->
|
||||
fun iolist_to_binary/1;
|
||||
listen_opt_type(tls) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
%% We cannot deprecate the option for now:
|
||||
%% I think STUN/TURN clients are too stupid to set SNI
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(turn_min_port) ->
|
||||
fun(P) when is_integer(P), P > 0, P =< 65535 -> P end;
|
||||
fun(P) when is_integer(P), P > 1024, P < 65536 -> P end;
|
||||
listen_opt_type(turn_max_port) ->
|
||||
fun(P) when is_integer(P), P > 0, P =< 65535 -> P end;
|
||||
fun(P) when is_integer(P), P > 1024, P < 65536 -> P end;
|
||||
listen_opt_type(turn_max_allocations) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
@@ -170,13 +164,17 @@ listen_opt_type(turn_max_permissions) ->
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
listen_opt_type(server_name) ->
|
||||
fun iolist_to_binary/1;
|
||||
listen_opt_type(backlog) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(accept_interval) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
listen_opt_type(_) ->
|
||||
[shaper, auth_type, auth_realm, tls, certfile, turn_min_port,
|
||||
turn_max_port, turn_max_allocations, turn_max_permissions,
|
||||
server_name, backlog, accept_interval].
|
||||
fun iolist_to_binary/1.
|
||||
|
||||
listen_options() ->
|
||||
[{shaper, none},
|
||||
{auth_type, user},
|
||||
{auth_realm, undefined},
|
||||
{tls, false},
|
||||
{certfile, undefined},
|
||||
{turn_min_port, 49152},
|
||||
{turn_max_port, 65535},
|
||||
{turn_max_allocations, 10},
|
||||
{turn_max_permissions, 10},
|
||||
{server_name, <<"ejabberd">>}].
|
||||
-endif.
|
||||
|
||||
@@ -39,7 +39,6 @@ init([]) ->
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
[worker(ejabberd_hooks),
|
||||
worker(ejabberd_cluster),
|
||||
worker(cyrsasl),
|
||||
worker(translate),
|
||||
worker(ejabberd_access_permissions),
|
||||
worker(ejabberd_ctl),
|
||||
@@ -53,7 +52,7 @@ init([]) ->
|
||||
simple_supervisor(ejabberd_s2s_out),
|
||||
simple_supervisor(ejabberd_service),
|
||||
worker(acl),
|
||||
worker(shaper),
|
||||
worker(ejabberd_shaper),
|
||||
supervisor(ejabberd_backend_sup),
|
||||
supervisor(ejabberd_rdbms),
|
||||
supervisor(ejabberd_riak_sup),
|
||||
|
||||
@@ -88,7 +88,7 @@ handle_event({set_alarm, {system_memory_high_watermark, _}}, State) ->
|
||||
handle_overload(State),
|
||||
{ok, restart_timer(State)};
|
||||
handle_event({clear_alarm, system_memory_high_watermark}, State) ->
|
||||
cancel_timer(State#state.tref),
|
||||
misc:cancel_timer(State#state.tref),
|
||||
{ok, State#state{tref = undefined}};
|
||||
handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
|
||||
case proc_stat(Pid, get_app_pids()) of
|
||||
@@ -220,23 +220,10 @@ proc_stat(Pid, AppPids) ->
|
||||
|
||||
-spec restart_timer(#state{}) -> #state{}.
|
||||
restart_timer(State) ->
|
||||
cancel_timer(State#state.tref),
|
||||
misc:cancel_timer(State#state.tref),
|
||||
TRef = erlang:start_timer(?CHECK_INTERVAL, self(), handle_overload),
|
||||
State#state{tref = TRef}.
|
||||
|
||||
-spec cancel_timer(reference()) -> ok.
|
||||
cancel_timer(undefined) ->
|
||||
ok;
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive {timeout, TRef, _} -> ok
|
||||
after 0 -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec format_apps(dict:dict()) -> io:data().
|
||||
format_apps(Apps) ->
|
||||
AppList = lists:reverse(lists:keysort(2, dict:to_list(Apps))),
|
||||
|
||||
@@ -166,7 +166,7 @@ process([<<"doc">>, LocalFile], _Request) ->
|
||||
"documentation with the environment variable "
|
||||
"EJABBERD_DOC_PATH. Check the ejabberd "
|
||||
"Guide for more information.">>,
|
||||
?INFO_MSG("Problem '~p' accessing the local Guide file ~s", [Error, Help]),
|
||||
?WARNING_MSG("Problem '~p' accessing the local Guide file ~s", [Error, Help]),
|
||||
case Error of
|
||||
eacces -> {403, [], <<"Forbidden", Help/binary>>};
|
||||
enoent -> {307, [{<<"Location">>, <<"http://docs.ejabberd.im/admin/guide/configuration/">>}], <<"Not found", Help/binary>>};
|
||||
@@ -1873,7 +1873,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
|
||||
Modules, Query)
|
||||
of
|
||||
submitted -> ok;
|
||||
{'EXIT', Reason} -> ?INFO_MSG("~p~n", [Reason]), error;
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p~n", [Reason]), error;
|
||||
_ -> nothing
|
||||
end,
|
||||
NewModules = lists:sort(ejabberd_cluster:call(Node, gen_mod,
|
||||
@@ -2242,8 +2242,9 @@ make_netprot_html(NetProt) ->
|
||||
[<<"tcp">>, <<"udp">>]))).
|
||||
|
||||
get_port_data(PortIP, Opts) ->
|
||||
{Port, IPT, IPS, _IPV, NetProt, OptsClean} =
|
||||
{Port, IPT, _IPV, NetProt, OptsClean} =
|
||||
ejabberd_listener:parse_listener_portip(PortIP, Opts),
|
||||
IPS = misc:ip_to_list(IPT),
|
||||
SPort = integer_to_binary(Port),
|
||||
SSPort = list_to_binary(
|
||||
lists:map(fun (N) ->
|
||||
@@ -2694,8 +2695,7 @@ make_menu_item(item, 3, URI, Name, Lang) ->
|
||||
%%%==================================
|
||||
|
||||
|
||||
-spec opt_type(access_readonly) -> fun((any()) -> any());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(access_readonly) -> fun acl:access_rules_validator/1;
|
||||
opt_type(_) -> [access_readonly].
|
||||
|
||||
|
||||
+12
-17
@@ -31,11 +31,12 @@
|
||||
%%% TODO: commands strings should be strings without ~n
|
||||
|
||||
-module(ejabberd_xmlrpc).
|
||||
-behaviour(ejabberd_listener).
|
||||
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-export([start/2, handler/2, process/2, socket_type/0,
|
||||
transform_listen_option/2, listen_opt_type/1]).
|
||||
-export([start/2, start_link/2, handler/2, process/2, accept/1,
|
||||
transform_listen_option/2, listen_opt_type/1, listen_options/0]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
@@ -190,7 +191,11 @@
|
||||
start({gen_tcp = _SockMod, Socket}, Opts) ->
|
||||
ejabberd_http:start({gen_tcp, Socket}, [{xmlrpc, true}|Opts]).
|
||||
|
||||
socket_type() -> raw.
|
||||
start_link({gen_tcp = _SockMod, Socket}, Opts) ->
|
||||
ejabberd_http:start_link({gen_tcp, Socket}, [{xmlrpc, true}|Opts]).
|
||||
|
||||
accept(Pid) ->
|
||||
ejabberd_http:accept(Pid).
|
||||
|
||||
%% -----------------------------
|
||||
%% HTTP interface
|
||||
@@ -575,17 +580,7 @@ listen_opt_type(access_commands) ->
|
||||
{<<"ejabberd_xmlrpc compatibility shim">>,
|
||||
{[?MODULE], [{access, Ac}], Commands}}
|
||||
end, lists:flatten(Opts))
|
||||
end;
|
||||
listen_opt_type(maxsessions) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(timeout) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(inet) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(inet6) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(backlog) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(accept_interval) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
listen_opt_type(_) ->
|
||||
[access_commands, maxsessions, timeout, backlog, inet, inet6,
|
||||
accept_interval].
|
||||
end.
|
||||
|
||||
listen_options() ->
|
||||
[{access_commands, []}].
|
||||
|
||||
@@ -216,6 +216,10 @@ prepare_output(FileName, normal) when is_list(FileName) ->
|
||||
case file:open(FileName, [write, raw]) of
|
||||
{ok, Fd} ->
|
||||
Fd;
|
||||
{error, eacces} ->
|
||||
exit({"Not enough permission to the file or path", FileName});
|
||||
{error, enoent} ->
|
||||
exit({"Path does not exist", FileName});
|
||||
Err ->
|
||||
exit(Err)
|
||||
end;
|
||||
|
||||
+11
-16
@@ -659,7 +659,7 @@ handle_info({Tag, _Socket, Data}, connecting, S)
|
||||
{next_state, connecting, S};
|
||||
handle_info({Tag, _Socket, Data}, wait_bind_response, S)
|
||||
when Tag == tcp; Tag == ssl ->
|
||||
cancel_timer(S#eldap.bind_timer),
|
||||
misc:cancel_timer(S#eldap.bind_timer),
|
||||
case catch recvd_wait_bind_response(Data, S) of
|
||||
bound -> dequeue_commands(S);
|
||||
{fail_bind, Reason} ->
|
||||
@@ -847,14 +847,14 @@ recvd_packet(Pkt, S) ->
|
||||
if Reason == success; Reason == sizeLimitExceeded ->
|
||||
{Res, Ref} = polish(Result_so_far),
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
{reply,
|
||||
#eldap_search_result{entries = Res,
|
||||
referrals = Ref},
|
||||
From, S#eldap{dict = New_dict}};
|
||||
true ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
{reply, {error, Reason}, From,
|
||||
S#eldap{dict = New_dict}}
|
||||
end;
|
||||
@@ -863,37 +863,37 @@ recvd_packet(Pkt, S) ->
|
||||
{ok, S#eldap{dict = New_dict}};
|
||||
{addRequest, {addResponse, Result}} ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
Reply = check_reply(Result, From),
|
||||
{reply, Reply, From, S#eldap{dict = New_dict}};
|
||||
{delRequest, {delResponse, Result}} ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
Reply = check_reply(Result, From),
|
||||
{reply, Reply, From, S#eldap{dict = New_dict}};
|
||||
{modifyRequest, {modifyResponse, Result}} ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
Reply = check_reply(Result, From),
|
||||
{reply, Reply, From, S#eldap{dict = New_dict}};
|
||||
{modDNRequest, {modDNResponse, Result}} ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
Reply = check_reply(Result, From),
|
||||
{reply, Reply, From, S#eldap{dict = New_dict}};
|
||||
{bindRequest, {bindResponse, Result}} ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
Reply = check_bind_reply(Result, From),
|
||||
{reply, Reply, From, S#eldap{dict = New_dict}};
|
||||
{extendedReq, {extendedResp, Result}} ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
Reply = check_extended_reply(Result, From),
|
||||
{reply, Reply, From, S#eldap{dict = New_dict}};
|
||||
{OtherName, OtherResult} ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
{reply,
|
||||
{error, {invalid_result, OtherName, OtherResult}},
|
||||
From, S#eldap{dict = New_dict}}
|
||||
@@ -968,16 +968,11 @@ check_id(_, _) -> throw({error, wrong_bind_id}).
|
||||
%% General Helpers
|
||||
%%-----------------------------------------------------------------------
|
||||
|
||||
cancel_timer(Timer) ->
|
||||
erlang:cancel_timer(Timer),
|
||||
receive {timeout, Timer, _} -> ok after 0 -> ok end.
|
||||
|
||||
|
||||
close_and_retry(S, Timeout) ->
|
||||
catch (S#eldap.sockmod):close(S#eldap.fd),
|
||||
Queue = dict:fold(fun (_Id,
|
||||
[{Timer, Command, From, _Name} | _], Q) ->
|
||||
cancel_timer(Timer),
|
||||
misc:cancel_timer(Timer),
|
||||
queue:in_r({Command, From}, Q);
|
||||
(_, _, Q) -> Q
|
||||
end,
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ start_link(Name, Hosts, Backups, Port, Rootdn, Passwd,
|
||||
of
|
||||
{ok, Pid} -> pg2:join(PoolName, Pid);
|
||||
Err ->
|
||||
?INFO_MSG("Err = ~p", [Err]),
|
||||
?ERROR_MSG("Err = ~p", [Err]),
|
||||
error
|
||||
end
|
||||
end,
|
||||
|
||||
+1
-18
@@ -333,24 +333,7 @@ collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
|
||||
collect_parts_bit([],Acc,Uacc) ->
|
||||
list_to_binary([Uacc|lists:reverse(Acc)]).
|
||||
|
||||
-type deref_aliases() :: never | searching | finding | always.
|
||||
-type uids() :: binary() | {binary()} | {binary(), binary()}.
|
||||
-spec opt_type(deref_aliases) -> fun((deref_aliases()) -> deref_aliases());
|
||||
(ldap_backups) -> fun(([binary()]) -> [binary()]);
|
||||
(ldap_base) -> fun((binary()) -> binary());
|
||||
(ldap_deref_aliases) -> fun((deref_aliases()) -> deref_aliases());
|
||||
(ldap_encrypt) -> fun((tls | starttls | none) -> tls | starttls | none);
|
||||
(ldap_password) -> fun((binary()) -> binary());
|
||||
(ldap_port) -> fun((0..65535) -> 0..65535);
|
||||
(ldap_rootdn) -> fun((binary()) -> binary());
|
||||
(ldap_servers) -> fun(([binary()]) -> [binary()]);
|
||||
(ldap_tls_certfile) -> fun((binary()) -> string());
|
||||
(ldap_tls_cacertfile) -> fun((binary()) -> string());
|
||||
(ldap_tls_depth) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(ldap_tls_verify) -> fun((hard | soft | false) -> hard | soft | false);
|
||||
(ldap_filter) -> fun((binary()) -> binary());
|
||||
(ldap_uids) -> fun((uids()) -> uids());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(deref_aliases) ->
|
||||
fun(unspecified) -> unspecified;
|
||||
(never) -> never;
|
||||
|
||||
+1
-2
@@ -680,8 +680,7 @@ format({Key, Val}) when is_binary(Val) ->
|
||||
format({Key, Val}) -> % TODO: improve Yaml parsing
|
||||
{Key, Val}.
|
||||
|
||||
-spec opt_type(allow_contrib_modules) -> fun((boolean()) -> boolean());
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(allow_contrib_modules) ->
|
||||
fun (false) -> false;
|
||||
(no) -> false;
|
||||
|
||||
+7
-2
@@ -82,7 +82,12 @@ prog_name(Host) ->
|
||||
|
||||
-spec pool_name(binary()) -> atom().
|
||||
pool_name(Host) ->
|
||||
list_to_atom("extauth_pool_" ++ binary_to_list(Host)).
|
||||
case ejabberd_config:get_option({extauth_pool_name, Host}) of
|
||||
undefined ->
|
||||
list_to_atom("extauth_pool_" ++ binary_to_list(Host));
|
||||
Name ->
|
||||
list_to_atom("extauth_pool_" ++ binary_to_list(Name))
|
||||
end.
|
||||
|
||||
-spec worker_name(atom(), integer()) -> atom().
|
||||
worker_name(Pool, N) ->
|
||||
@@ -186,7 +191,7 @@ call_port(Server, Args, Timeout) ->
|
||||
StartTime = p1_time_compat:monotonic_time(milli_seconds),
|
||||
Pool = pool_name(Server),
|
||||
PoolSize = pool_size(Server),
|
||||
I = randoms:round_robin(PoolSize),
|
||||
I = p1_rand:round_robin(PoolSize),
|
||||
Cmd = str:join(Args, <<":">>),
|
||||
do_call(Cmd, I, I + PoolSize, Pool, PoolSize,
|
||||
StartTime + Timeout, StartTime).
|
||||
|
||||
@@ -114,8 +114,9 @@ process_iq(_Host, Module, Function, IQ) ->
|
||||
ignore ->
|
||||
ok
|
||||
catch E:R ->
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("failed to process iq:~n~s~nReason = ~p",
|
||||
[xmpp:pp(IQ), {E, {R, erlang:get_stacktrace()}}]),
|
||||
[xmpp:pp(IQ), {E, {R, St}}]),
|
||||
Txt = <<"Module failed to handle the query">>,
|
||||
Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang),
|
||||
ejabberd_router:route_error(IQ, Err)
|
||||
@@ -153,8 +154,7 @@ transform_module_options(Opts) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
-spec opt_type(iqdisc) -> fun((any()) -> no_queue);
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(iqdisc) -> fun check_type/1;
|
||||
opt_type(_) -> [iqdisc].
|
||||
|
||||
|
||||
+16
-11
@@ -67,7 +67,7 @@
|
||||
-type opts() :: [{atom(), any()}].
|
||||
-type db_type() :: atom().
|
||||
|
||||
-callback start(binary(), opts()) -> ok | {ok, pid()}.
|
||||
-callback start(binary(), opts()) -> ok | {ok, pid()} | {error, term()}.
|
||||
-callback stop(binary()) -> any().
|
||||
-callback reload(binary(), opts(), opts()) -> ok | {ok, pid()}.
|
||||
-callback mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
@@ -546,12 +546,14 @@ validate_opts(Host, Module, Opts0) ->
|
||||
[Module, Opt]),
|
||||
module_error(ErrTxt);
|
||||
_:{invalid_option, Opt, Val} ->
|
||||
ErrTxt = io_lib:format("Invalid value '~p' for option '~s' of "
|
||||
"module '~s'", [Val, Opt, Module]),
|
||||
ErrTxt = io_lib:format("Invalid value for option '~s' of "
|
||||
"module ~s: ~s",
|
||||
[Opt, Module, misc:format_val({yaml, Val})]),
|
||||
module_error(ErrTxt);
|
||||
_:{invalid_option, Opt, Val, Reason} ->
|
||||
ErrTxt = io_lib:format("Invalid value '~p' for option '~s' of "
|
||||
"module '~s': ~s", [Val, Opt, Module, Reason]),
|
||||
ErrTxt = io_lib:format("Invalid value for option '~s' of "
|
||||
"module ~s (~s): ~s",
|
||||
[Opt, Module, Reason, misc:format_val({yaml, Val})]),
|
||||
module_error(ErrTxt);
|
||||
_:{unknown_option, Opt, []} ->
|
||||
ErrTxt = io_lib:format("Unknown option '~s' of module '~s': "
|
||||
@@ -618,7 +620,7 @@ validate_opt(Opt, Val, VFun) ->
|
||||
NewVal -> [{Opt, NewVal}]
|
||||
catch {invalid_syntax, Error} ->
|
||||
err_invalid_option(Opt, Val, Error);
|
||||
_:_ ->
|
||||
_:R when R /= undef ->
|
||||
err_invalid_option(Opt, Val)
|
||||
end.
|
||||
|
||||
@@ -728,10 +730,14 @@ format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) ->
|
||||
"it doesn't export ~s/~B callback: "
|
||||
"is it really an ejabberd module?",
|
||||
[Fun, Module, Fun, Arity]);
|
||||
{error, {bad_return, Module, {error, _} = Err}} ->
|
||||
io_lib:format("Failed to ~s module ~s: ~s",
|
||||
[Fun, Module, misc:format_val(Err)]);
|
||||
{error, {bad_return, Module, Ret}} ->
|
||||
io_lib:format("Module ~s returned unexpected value from "
|
||||
"~s/~B: ~p; this is either not an ejabberd "
|
||||
"module or it implements ejabbed API incorrectly",
|
||||
io_lib:format("Module ~s returned unexpected value from ~s/~B:~n"
|
||||
"** Error: ~p~n"
|
||||
"** Hint: this is either not an ejabberd module "
|
||||
"or it implements ejabbed API incorrectly",
|
||||
[Module, Fun, Arity, Ret]);
|
||||
_ ->
|
||||
io_lib:format("Internal error of module ~s has "
|
||||
@@ -925,8 +931,7 @@ is_opt_list(L) when is_list(L) ->
|
||||
is_opt_list(_) ->
|
||||
false.
|
||||
|
||||
-spec opt_type(modules) -> fun(([{atom(), list()}]) -> [{atom(), list()}]);
|
||||
(atom()) -> [atom()].
|
||||
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
|
||||
opt_type(modules) ->
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
|
||||
+75
-2
@@ -34,8 +34,9 @@
|
||||
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
|
||||
now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
|
||||
compile_exprs/2, join_atoms/2, try_read_file/1, get_descr/2,
|
||||
css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0,
|
||||
read_css/1, read_img/1, read_js/1]).
|
||||
css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0, lua_dir/0,
|
||||
read_css/1, read_img/1, read_js/1, read_lua/1, try_url/1,
|
||||
intersection/2, format_val/1, cancel_timer/1]).
|
||||
|
||||
%% Deprecated functions
|
||||
-export([decode_base64/1, encode_base64/1]).
|
||||
@@ -219,6 +220,29 @@ try_read_file(Path) ->
|
||||
erlang:error(badarg)
|
||||
end.
|
||||
|
||||
%% @doc Checks if the URL is valid HTTP(S) URL and converts its name to binary.
|
||||
%% Fails with `badarg` otherwise. The function is intended for usage
|
||||
%% in configuration validators only.
|
||||
-spec try_url(binary() | string()) -> binary().
|
||||
try_url(URL0) ->
|
||||
URL = case URL0 of
|
||||
V when is_binary(V) -> binary_to_list(V);
|
||||
_ -> URL0
|
||||
end,
|
||||
case http_uri:parse(URL) of
|
||||
{ok, {Scheme, _, _, _, _, _}} when Scheme /= http, Scheme /= https ->
|
||||
?ERROR_MSG("Unsupported URI scheme: ~s", [URL]),
|
||||
erlang:error(badarg);
|
||||
{ok, {_, _, Host, _, _, _}} when Host == ""; Host == <<"">> ->
|
||||
?ERROR_MSG("Invalid URL: ~s", [URL]),
|
||||
erlang:error(badarg);
|
||||
{ok, _} ->
|
||||
iolist_to_binary(URL);
|
||||
{error, _} ->
|
||||
?ERROR_MSG("Invalid URL: ~s", [URL]),
|
||||
erlang:error(badarg)
|
||||
end.
|
||||
|
||||
-spec css_dir() -> file:filename().
|
||||
css_dir() ->
|
||||
get_dir("css").
|
||||
@@ -239,6 +263,10 @@ msgs_dir() ->
|
||||
sql_dir() ->
|
||||
get_dir("sql").
|
||||
|
||||
-spec lua_dir() -> file:filename().
|
||||
lua_dir() ->
|
||||
get_dir("lua").
|
||||
|
||||
-spec read_css(file:filename()) -> {ok, binary()} | {error, file:posix()}.
|
||||
read_css(File) ->
|
||||
read_file(filename:join(css_dir(), File)).
|
||||
@@ -251,12 +279,57 @@ read_img(File) ->
|
||||
read_js(File) ->
|
||||
read_file(filename:join(js_dir(), File)).
|
||||
|
||||
-spec read_lua(file:filename()) -> {ok, binary()} | {error, file:posix()}.
|
||||
read_lua(File) ->
|
||||
read_file(filename:join(lua_dir(), File)).
|
||||
|
||||
-spec get_descr(binary(), binary()) -> binary().
|
||||
get_descr(Lang, Text) ->
|
||||
Desc = translate:translate(Lang, Text),
|
||||
Copyright = ejabberd_config:get_copyright(),
|
||||
<<Desc/binary, $\n, Copyright/binary>>.
|
||||
|
||||
-spec intersection(list(), list()) -> list().
|
||||
intersection(L1, L2) ->
|
||||
lists:filter(
|
||||
fun(E) ->
|
||||
lists:member(E, L2)
|
||||
end, L1).
|
||||
|
||||
-spec format_val(any()) -> iodata().
|
||||
format_val({yaml, S}) when is_integer(S); is_binary(S); is_atom(S) ->
|
||||
format_val(S);
|
||||
format_val({yaml, YAML}) ->
|
||||
S = try fast_yaml:encode(YAML)
|
||||
catch _:_ -> YAML
|
||||
end,
|
||||
format_val(S);
|
||||
format_val(I) when is_integer(I) ->
|
||||
integer_to_list(I);
|
||||
format_val(B) when is_atom(B) ->
|
||||
erlang:atom_to_binary(B, utf8);
|
||||
format_val(Term) ->
|
||||
S = try iolist_to_binary(Term)
|
||||
catch _:_ -> list_to_binary(io_lib:format("~p", [Term]))
|
||||
end,
|
||||
case binary:match(S, <<"\n">>) of
|
||||
nomatch -> S;
|
||||
_ -> [io_lib:nl(), S]
|
||||
end.
|
||||
|
||||
-spec cancel_timer(reference()) -> ok.
|
||||
cancel_timer(TRef) when is_reference(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive {timeout, TRef, _} -> ok
|
||||
after 0 -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
cancel_timer(_) ->
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
||||
+24
-74
@@ -40,7 +40,7 @@
|
||||
restart_module/2,
|
||||
|
||||
% Sessions
|
||||
num_active_users/2, num_resources/2, resource_num/3,
|
||||
num_resources/2, resource_num/3,
|
||||
kick_session/4, status_num/2, status_num/1,
|
||||
status_list/2, status_list/1, connected_users_info/0,
|
||||
connected_users_vhost/1, set_presence/7,
|
||||
@@ -165,16 +165,6 @@ get_commands_spec() ->
|
||||
" - 0: code reloaded, module restarted\n"
|
||||
" - 1: error: module not loaded\n"
|
||||
" - 2: code not reloaded, but module restarted"},
|
||||
#ejabberd_commands{name = num_active_users, tags = [accounts, stats],
|
||||
desc = "Get number of users active in the last days (only Mnesia)",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = num_active_users,
|
||||
args = [{host, binary}, {days, integer}],
|
||||
args_example = [<<"myserver.com">>, 3],
|
||||
args_desc = ["Name of host to check", "Number of days to calculate sum"],
|
||||
result = {users, integer},
|
||||
result_example = 123,
|
||||
result_desc = "Number of users active on given server in last n days"},
|
||||
#ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
|
||||
desc = "Delete users that didn't log in last days, or that never logged",
|
||||
longdesc = "To protect admin accounts, configure this for example:\n"
|
||||
@@ -850,62 +840,6 @@ get_hash(AccountPass, Method) ->
|
||||
|| X <- binary_to_list(
|
||||
crypto:hash(binary_to_atom(Method, latin1), AccountPass))]).
|
||||
|
||||
num_active_users(Host, Days) ->
|
||||
DB_Type = gen_mod:get_module_opt(Host, mod_last, db_type),
|
||||
list_last_activity(Host, true, Days, DB_Type).
|
||||
|
||||
%% Code based on ejabberd/src/web/ejabberd_web_admin.erl
|
||||
list_last_activity(Host, Integral, Days, mnesia) ->
|
||||
TimeStamp = p1_time_compat:system_time(seconds),
|
||||
TS = TimeStamp - Days * 86400,
|
||||
case catch mnesia:dirty_select(
|
||||
last_activity, [{{last_activity, {'_', Host}, '$1', '_'},
|
||||
[{'>', '$1', TS}],
|
||||
[{'trunc', {'/',
|
||||
{'-', TimeStamp, '$1'},
|
||||
86400}}]}]) of
|
||||
{'EXIT', _Reason} ->
|
||||
[];
|
||||
Vals ->
|
||||
Hist = histogram(Vals, Integral),
|
||||
if
|
||||
Hist == [] ->
|
||||
0;
|
||||
true ->
|
||||
Left = Days - length(Hist),
|
||||
Tail = if
|
||||
Integral ->
|
||||
lists:duplicate(Left, lists:last(Hist));
|
||||
true ->
|
||||
lists:duplicate(Left, 0)
|
||||
end,
|
||||
lists:nth(Days, Hist ++ Tail)
|
||||
end
|
||||
end;
|
||||
list_last_activity(_Host, _Integral, _Days, DB_Type) ->
|
||||
throw({error, iolist_to_binary(io_lib:format("Unsupported backend: ~p",
|
||||
[DB_Type]))}).
|
||||
|
||||
histogram(Values, Integral) ->
|
||||
histogram(lists:sort(Values), Integral, 0, 0, []).
|
||||
histogram([H | T], Integral, Current, Count, Hist) when Current == H ->
|
||||
histogram(T, Integral, Current, Count + 1, Hist);
|
||||
histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H ->
|
||||
if
|
||||
Integral ->
|
||||
histogram(Values, Integral, Current + 1, Count, [Count | Hist]);
|
||||
true ->
|
||||
histogram(Values, Integral, Current + 1, 0, [Count | Hist])
|
||||
end;
|
||||
histogram([], _Integral, _Current, Count, Hist) ->
|
||||
if
|
||||
Count > 0 ->
|
||||
lists:reverse([Count | Hist]);
|
||||
true ->
|
||||
lists:reverse(Hist)
|
||||
end.
|
||||
|
||||
|
||||
delete_old_users(Days) ->
|
||||
%% Get the list of registered users
|
||||
Users = ejabberd_auth:get_users(),
|
||||
@@ -974,7 +908,7 @@ build_random_password(Reason) ->
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time(),
|
||||
Date = str:format("~4..0B~2..0B~2..0BT~2..0B:~2..0B:~2..0B",
|
||||
[Year, Month, Day, Hour, Minute, Second]),
|
||||
RandomString = randoms:get_string(),
|
||||
RandomString = p1_rand:get_string(),
|
||||
<<"BANNED_ACCOUNT--", Date/binary, "--", RandomString/binary, "--", Reason/binary>>.
|
||||
|
||||
set_password_auth(User, Server, Password) ->
|
||||
@@ -1018,7 +952,7 @@ status_num(Status) ->
|
||||
status_num(<<"all">>, Status).
|
||||
status_list(Host, Status) ->
|
||||
Res = get_status_list(Host, Status),
|
||||
[{U, S, R, P, St} || {U, S, R, P, St} <- Res].
|
||||
[{U, S, R, num_prio(P), St} || {U, S, R, P, St} <- Res].
|
||||
status_list(Status) ->
|
||||
status_list(<<"all">>, Status).
|
||||
|
||||
@@ -1046,7 +980,7 @@ get_status_list(Host, Status_required) ->
|
||||
_ ->
|
||||
fun(A, B) -> A == B end
|
||||
end,
|
||||
[{User, Server, Resource, Priority, stringize(Status_text)}
|
||||
[{User, Server, Resource, num_prio(Priority), stringize(Status_text)}
|
||||
|| {{User, Resource, Status, Status_text}, Server, Priority} <- Sessions4,
|
||||
apply(Fstatus, [Status, Status_required])].
|
||||
|
||||
@@ -1069,6 +1003,13 @@ stringize(String) ->
|
||||
ejabberd_regexp:greplace(String, <<"\n">>, <<"\\n">>).
|
||||
|
||||
get_presence(Pid) ->
|
||||
try get_presence2(Pid) of
|
||||
{_, _, _, _} = Res ->
|
||||
Res
|
||||
catch
|
||||
_:_ -> {<<"">>, <<"">>, <<"offline">>, <<"">>}
|
||||
end.
|
||||
get_presence2(Pid) ->
|
||||
Pres = #presence{from = From} = ejabberd_c2s:get_presence(Pid),
|
||||
Show = case Pres of
|
||||
#presence{type = unavailable} -> <<"unavailable">>;
|
||||
@@ -1127,7 +1068,7 @@ user_session_info(User, Host, Resource) ->
|
||||
NodeS = atom_to_list(node(Pid)),
|
||||
Uptime = CurrentSec - calendar:datetime_to_gregorian_seconds(
|
||||
calendar:now_to_local_time(Now)),
|
||||
{atom_to_list(Conn), IPS, Port, Priority, NodeS, Uptime, Status, Resource, StatusText}.
|
||||
{atom_to_list(Conn), IPS, Port, num_prio(Priority), NodeS, Uptime, Status, Resource, StatusText}.
|
||||
|
||||
|
||||
%%%
|
||||
@@ -1509,13 +1450,17 @@ srg_user_del(User, Host, Group, GroupHost) ->
|
||||
send_message(Type, From, To, Subject, Body) ->
|
||||
FromJID = jid:decode(From),
|
||||
ToJID = jid:decode(To),
|
||||
Packet = build_packet(Type, Subject, Body),
|
||||
Packet = build_packet(Type, Subject, Body, FromJID, ToJID),
|
||||
State1 = #{jid => FromJID},
|
||||
ejabberd_hooks:run_fold(user_send_packet, FromJID#jid.lserver, {Packet, State1}, []),
|
||||
ejabberd_router:route(xmpp:set_from_to(Packet, FromJID, ToJID)).
|
||||
|
||||
build_packet(Type, Subject, Body) ->
|
||||
build_packet(Type, Subject, Body, FromJID, ToJID) ->
|
||||
#message{type = misc:binary_to_atom(Type),
|
||||
body = xmpp:mk_text(Body),
|
||||
id = randoms:get_string(),
|
||||
from = FromJID,
|
||||
to = ToJID,
|
||||
id = p1_rand:get_string(),
|
||||
subject = xmpp:mk_text(Subject)}.
|
||||
|
||||
send_stanza(FromString, ToString, Stanza) ->
|
||||
@@ -1736,4 +1681,9 @@ is_glob_match(String, <<"!", Glob/binary>>) ->
|
||||
is_glob_match(String, Glob) ->
|
||||
is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)).
|
||||
|
||||
num_prio(Priority) when is_integer(Priority) ->
|
||||
Priority;
|
||||
num_prio(_) ->
|
||||
-1.
|
||||
|
||||
mod_options(_) -> [].
|
||||
|
||||
+1
-1
@@ -359,7 +359,7 @@ set_vcard_avatar(JID, VCardPhoto, Meta) ->
|
||||
ok;
|
||||
{ok, VCard} ->
|
||||
VCard1 = VCard#vcard_temp{photo = VCardPhoto},
|
||||
IQ = #iq{from = JID, to = JID, id = randoms:get_string(),
|
||||
IQ = #iq{from = JID, to = JID, id = p1_rand:get_string(),
|
||||
type = set, sub_els = [VCard1], meta = Meta},
|
||||
LServer = JID#jid.lserver,
|
||||
ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []),
|
||||
|
||||
@@ -241,7 +241,7 @@ broadcast_event(#jid{luser = LUser, lserver = LServer} = From, Event) ->
|
||||
fun(R) ->
|
||||
To = jid:replace_resource(From, R),
|
||||
IQ = #iq{type = set, from = BFrom, to = To,
|
||||
id = <<"push", (randoms:get_string())/binary>>,
|
||||
id = <<"push", (p1_rand:get_string())/binary>>,
|
||||
sub_els = [Event]},
|
||||
ejabberd_router:route(IQ)
|
||||
end, ejabberd_sm:get_user_resources(LUser, LServer)).
|
||||
|
||||
+1
-1
@@ -253,7 +253,7 @@ cache_opts() ->
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec clean_cache(node()) -> ok.
|
||||
-spec clean_cache(node()) -> non_neg_integer().
|
||||
clean_cache(Node) ->
|
||||
ets_cache:filter(
|
||||
?BOSH_CACHE,
|
||||
|
||||
@@ -345,7 +345,7 @@ cache_nodes(Mod, Host) ->
|
||||
false -> ejabberd_cluster:get_nodes()
|
||||
end.
|
||||
|
||||
-spec clean_cache(node()) -> ok.
|
||||
-spec clean_cache(node()) -> non_neg_integer().
|
||||
clean_cache(Node) ->
|
||||
ets_cache:filter(
|
||||
?CARBONCOPY_CACHE,
|
||||
|
||||
+1
-1
@@ -196,7 +196,7 @@ get_local_features(Acc, _From, To, <<"">>, _Lang) ->
|
||||
end,
|
||||
{result, lists:usort(
|
||||
lists:flatten(
|
||||
[<<"iq">>, <<"presence">>,
|
||||
[?NS_FEATURE_IQ, ?NS_FEATURE_PRESENCE,
|
||||
?NS_DISCO_INFO, ?NS_DISCO_ITEMS, Feats,
|
||||
ejabberd_local:get_features(To#jid.lserver)]))};
|
||||
get_local_features(Acc, _From, _To, _Node, Lang) ->
|
||||
|
||||
+5
-6
@@ -60,9 +60,8 @@ reload(Host, NewOpts, OldOpts) ->
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun(L) -> lists:map(fun iolist_to_binary/1, L) end.
|
||||
mod_opt_type(host) -> fun ejabberd_config:v_host/1;
|
||||
mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1.
|
||||
|
||||
mod_options(_Host) ->
|
||||
[{host, <<"echo.@HOST@">>}, {hosts, []}].
|
||||
@@ -188,12 +187,12 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
do_client_version(disabled, _From, _To) -> ok;
|
||||
do_client_version(enabled, From, To) ->
|
||||
Random_resource = randoms:get_string(),
|
||||
Random_resource = p1_rand:get_string(),
|
||||
From2 = From#jid{resource = Random_resource,
|
||||
lresource = Random_resource},
|
||||
ID = randoms:get_string(),
|
||||
ID = p1_rand:get_string(),
|
||||
Packet = #iq{from = From2, to = To, type = get,
|
||||
id = randoms:get_string(),
|
||||
id = p1_rand:get_string(),
|
||||
sub_els = [#version{}]},
|
||||
ejabberd_router:route(Packet),
|
||||
receive
|
||||
|
||||
@@ -168,8 +168,8 @@ log_and_disconnect(#{ip := {Addr, _}, lang := Lang} = State, Attempts, UnbanTS)
|
||||
"from this IP address (~s). The address "
|
||||
"will be unblocked at ~s UTC">>,
|
||||
Args = [Attempts, IP, UnbanDate],
|
||||
?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
|
||||
[IP, io_lib:fwrite(Format, Args)]),
|
||||
?WARNING_MSG("Connection attempt from blacklisted IP ~s: ~s",
|
||||
[IP, io_lib:fwrite(Format, Args)]),
|
||||
Err = xmpp:serr_policy_violation({Format, Args}, Lang),
|
||||
{stop, ejabberd_c2s:send(State, Err)}.
|
||||
|
||||
|
||||
@@ -193,7 +193,8 @@ process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) ->
|
||||
?DEBUG("Bad Request: ~p", [_Err]),
|
||||
badrequest_response(<<"Invalid JSON input">>);
|
||||
_:_Error ->
|
||||
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
St = erlang:get_stacktrace(),
|
||||
?DEBUG("Bad Request: ~p ~p", [_Error, St]),
|
||||
badrequest_response()
|
||||
end;
|
||||
process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) ->
|
||||
@@ -210,9 +211,9 @@ process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) ->
|
||||
throw:{error, unknown_command} ->
|
||||
json_format({404, 44, <<"Command not found.">>});
|
||||
_:_Error ->
|
||||
|
||||
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
badrequest_response()
|
||||
St = erlang:get_stacktrace(),
|
||||
?DEBUG("Bad Request: ~p ~p", [_Error, St]),
|
||||
badrequest_response()
|
||||
end;
|
||||
process([_Call], #request{method = 'OPTIONS', data = <<>>}) ->
|
||||
{200, ?OPTIONS_HEADER, []};
|
||||
@@ -314,7 +315,8 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
throw:Msg when is_list(Msg); is_binary(Msg) ->
|
||||
{400, iolist_to_binary(Msg)};
|
||||
_Error ->
|
||||
?ERROR_MSG("REST API Error: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("REST API Error: ~p ~p", [_Error, St]),
|
||||
{500, <<"internal_error">>}
|
||||
end;
|
||||
{error, Msg} ->
|
||||
|
||||
+126
-92
@@ -29,10 +29,8 @@
|
||||
-protocol({xep, 363, '0.1'}).
|
||||
|
||||
-define(SERVICE_REQUEST_TIMEOUT, 5000). % 5 seconds.
|
||||
-define(SLOT_TIMEOUT, 18000000). % 5 hours.
|
||||
-define(URL_ENC(URL), binary_to_list(misc:url_encode(URL))).
|
||||
-define(ADDR_TO_STR(IP), ejabberd_config:may_hide_data(misc:ip_to_list(IP))).
|
||||
-define(STR_TO_INT(Str, B), binary_to_integer(iolist_to_binary(Str), B)).
|
||||
-define(CALL_TIMEOUT, 60000). % 1 minute.
|
||||
-define(SLOT_TIMEOUT, timer:hours(5)).
|
||||
-define(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>).
|
||||
-define(CONTENT_TYPES,
|
||||
[{<<".avi">>, <<"video/avi">>},
|
||||
@@ -109,7 +107,8 @@
|
||||
service_url :: binary() | undefined,
|
||||
thumbnail :: boolean(),
|
||||
custom_headers :: [{binary(), binary()}],
|
||||
slots = #{} :: map()}).
|
||||
slots = #{} :: map(),
|
||||
external_secret :: binary()}).
|
||||
|
||||
-record(media_info,
|
||||
{type :: atom(),
|
||||
@@ -123,7 +122,7 @@
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_mod/supervisor callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec start(binary(), gen_mod:opts()) -> {ok, pid()}.
|
||||
-spec start(binary(), gen_mod:opts()) -> {ok, pid()} | {error, already_started}.
|
||||
start(ServerHost, Opts) ->
|
||||
case gen_mod:get_opt(rm_on_unregister, Opts) of
|
||||
true ->
|
||||
@@ -133,7 +132,14 @@ start(ServerHost, Opts) ->
|
||||
ok
|
||||
end,
|
||||
Proc = get_proc_name(ServerHost, ?MODULE),
|
||||
gen_mod:start_child(?MODULE, ServerHost, Opts, Proc).
|
||||
case whereis(Proc) of
|
||||
undefined ->
|
||||
gen_mod:start_child(?MODULE, ServerHost, Opts, Proc);
|
||||
_Pid ->
|
||||
?ERROR_MSG("Multiple virtual hosts can't use a single 'put_url' "
|
||||
"without the @HOST@ keyword", []),
|
||||
{error, already_started}
|
||||
end.
|
||||
|
||||
-spec stop(binary()) -> ok | {error, any()}.
|
||||
stop(ServerHost) ->
|
||||
@@ -149,9 +155,9 @@ stop(ServerHost) ->
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
mod_opt_type(host) ->
|
||||
fun iolist_to_binary/1;
|
||||
fun ejabberd_config:v_host/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
fun ejabberd_config:v_hosts/1;
|
||||
mod_opt_type(name) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(access) ->
|
||||
@@ -168,27 +174,26 @@ mod_opt_type(jid_in_url) ->
|
||||
end;
|
||||
mod_opt_type(file_mode) ->
|
||||
fun(undefined) -> undefined;
|
||||
(Mode) -> ?STR_TO_INT(Mode, 8)
|
||||
(Mode) -> binary_to_integer(iolist_to_binary(Mode), 8)
|
||||
end;
|
||||
mod_opt_type(dir_mode) ->
|
||||
fun(undefined) -> undefined;
|
||||
(Mode) -> ?STR_TO_INT(Mode, 8)
|
||||
(Mode) -> binary_to_integer(iolist_to_binary(Mode), 8)
|
||||
end;
|
||||
mod_opt_type(docroot) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(put_url) ->
|
||||
fun(<<"http://", _/binary>> = URL) -> URL;
|
||||
(<<"https://", _/binary>> = URL) -> URL
|
||||
end;
|
||||
fun misc:try_url/1;
|
||||
mod_opt_type(get_url) ->
|
||||
fun(<<"http://", _/binary>> = URL) -> URL;
|
||||
(<<"https://", _/binary>> = URL) -> URL;
|
||||
(undefined) -> undefined
|
||||
fun(undefined) -> undefined;
|
||||
(URL) -> misc:try_url(URL)
|
||||
end;
|
||||
mod_opt_type(service_url) ->
|
||||
fun(<<"http://", _/binary>> = URL) -> URL;
|
||||
(<<"https://", _/binary>> = URL) -> URL;
|
||||
(undefined) -> undefined
|
||||
fun(undefined) -> undefined;
|
||||
(URL) ->
|
||||
?WARNING_MSG("option 'service_url' is deprecated, consider unsing "
|
||||
"the 'external_secret' interface instead", []),
|
||||
misc:try_url(URL)
|
||||
end;
|
||||
mod_opt_type(custom_headers) ->
|
||||
fun(Headers) ->
|
||||
@@ -211,7 +216,9 @@ mod_opt_type(thumbnail) ->
|
||||
end;
|
||||
(false) ->
|
||||
false
|
||||
end.
|
||||
end;
|
||||
mod_opt_type(external_secret) ->
|
||||
fun iolist_to_binary/1.
|
||||
|
||||
-spec mod_options(binary()) -> [{atom(), any()}].
|
||||
mod_options(_Host) ->
|
||||
@@ -225,9 +232,10 @@ mod_options(_Host) ->
|
||||
{file_mode, undefined},
|
||||
{dir_mode, undefined},
|
||||
{docroot, <<"@HOME@/upload">>},
|
||||
{put_url, <<"http://@HOST@:5444">>},
|
||||
{put_url, <<"https://@HOST@:5443/upload">>},
|
||||
{get_url, undefined},
|
||||
{service_url, undefined},
|
||||
{external_secret, <<"">>},
|
||||
{custom_headers, []},
|
||||
{rm_on_unregister, true},
|
||||
{thumbnail, false}].
|
||||
@@ -258,6 +266,7 @@ init([ServerHost, Opts]) ->
|
||||
end,
|
||||
ServiceURL = gen_mod:get_opt(service_url, Opts),
|
||||
Thumbnail = gen_mod:get_opt(thumbnail, Opts),
|
||||
ExternalSecret = gen_mod:get_opt(external_secret, Opts),
|
||||
CustomHeaders = gen_mod:get_opt(custom_headers, Opts),
|
||||
DocRoot1 = expand_home(str:strip(DocRoot, right, $/)),
|
||||
DocRoot2 = expand_host(DocRoot1, ServerHost),
|
||||
@@ -280,6 +289,7 @@ init([ServerHost, Opts]) ->
|
||||
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
|
||||
get_url = expand_host(str:strip(GetURL, right, $/), ServerHost),
|
||||
service_url = ServiceURL,
|
||||
external_secret = ExternalSecret,
|
||||
custom_headers = CustomHeaders}}.
|
||||
|
||||
-spec handle_call(_, {pid(), _}, state())
|
||||
@@ -295,14 +305,14 @@ handle_call({use_slot, Slot, Size}, _From,
|
||||
custom_headers = CustomHeaders,
|
||||
docroot = DocRoot} = State) ->
|
||||
case get_slot(Slot, State) of
|
||||
{ok, {Size, Timer}} ->
|
||||
timer:cancel(Timer),
|
||||
{ok, {Size, TRef}} ->
|
||||
misc:cancel_timer(TRef),
|
||||
NewState = del_slot(Slot, State),
|
||||
Path = str:join([DocRoot | Slot], <<$/>>),
|
||||
{reply,
|
||||
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders},
|
||||
NewState};
|
||||
{ok, {_WrongSize, _Timer}} ->
|
||||
{ok, {_WrongSize, _TRef}} ->
|
||||
{reply, {error, size_mismatch}, State};
|
||||
error ->
|
||||
{reply, {error, invalid_slot}, State}
|
||||
@@ -344,7 +354,7 @@ handle_info({route, #iq{lang = Lang} = Packet}, State) ->
|
||||
ejabberd_router:route_error(Packet, Err),
|
||||
{noreply, State}
|
||||
end;
|
||||
handle_info({slot_timed_out, Slot}, State) ->
|
||||
handle_info({timeout, _TRef, Slot}, State) ->
|
||||
NewState = del_slot(Slot, State),
|
||||
{noreply, NewState};
|
||||
handle_info(Info, State) ->
|
||||
@@ -372,50 +382,54 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP})
|
||||
Method == 'GET' orelse
|
||||
Method == 'HEAD' ->
|
||||
?DEBUG("Rejecting ~s request from ~s for ~s: Too few path components",
|
||||
[Method, ?ADDR_TO_STR(IP), Host]),
|
||||
[Method, encode_addr(IP), Host]),
|
||||
http_response(404);
|
||||
process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
|
||||
length = Length} = Request) ->
|
||||
{Proc, Slot} = parse_http_request(Request),
|
||||
case catch gen_server:call(Proc, {use_slot, Slot, Length}) of
|
||||
case catch gen_server:call(Proc, {use_slot, Slot, Length}, ?CALL_TIMEOUT) of
|
||||
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders} ->
|
||||
?DEBUG("Storing file from ~s for ~s: ~s",
|
||||
[?ADDR_TO_STR(IP), Host, Path]),
|
||||
[encode_addr(IP), Host, Path]),
|
||||
case store_file(Path, Request, FileMode, DirMode,
|
||||
GetPrefix, Slot, Thumbnail) of
|
||||
ok ->
|
||||
http_response(201, CustomHeaders);
|
||||
{ok, Headers, OutData} ->
|
||||
http_response(201, Headers ++ CustomHeaders, OutData);
|
||||
{error, closed} ->
|
||||
?DEBUG("Cannot store file ~s from ~s for ~s: connection closed",
|
||||
[Path, encode_addr(IP), Host]),
|
||||
http_response(404);
|
||||
{error, Error} ->
|
||||
?INFO_MSG("Cannot store file ~s from ~s for ~s: ~s",
|
||||
[Path, ?ADDR_TO_STR(IP), Host, format_error(Error)]),
|
||||
?ERROR_MSG("Cannot store file ~s from ~s for ~s: ~s",
|
||||
[Path, encode_addr(IP), Host, format_error(Error)]),
|
||||
http_response(500)
|
||||
end;
|
||||
{error, size_mismatch} ->
|
||||
?INFO_MSG("Rejecting file from ~s for ~s: Unexpected size (~B)",
|
||||
[?ADDR_TO_STR(IP), Host, Length]),
|
||||
?WARNING_MSG("Rejecting file ~s from ~s for ~s: Unexpected size (~B)",
|
||||
[lists:last(Slot), encode_addr(IP), Host, Length]),
|
||||
http_response(413);
|
||||
{error, invalid_slot} ->
|
||||
?INFO_MSG("Rejecting file from ~s for ~s: Invalid slot",
|
||||
[?ADDR_TO_STR(IP), Host]),
|
||||
?WARNING_MSG("Rejecting file ~s from ~s for ~s: Invalid slot",
|
||||
[lists:last(Slot), encode_addr(IP), Host]),
|
||||
http_response(403);
|
||||
Error ->
|
||||
?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p",
|
||||
[?ADDR_TO_STR(IP), Host, Error]),
|
||||
[encode_addr(IP), Host, Error]),
|
||||
http_response(500)
|
||||
end;
|
||||
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
|
||||
when Method == 'GET';
|
||||
Method == 'HEAD' ->
|
||||
{Proc, [_UserDir, _RandDir, FileName] = Slot} = parse_http_request(Request),
|
||||
case catch gen_server:call(Proc, get_conf) of
|
||||
case catch gen_server:call(Proc, get_conf, ?CALL_TIMEOUT) of
|
||||
{ok, DocRoot, CustomHeaders} ->
|
||||
Path = str:join([DocRoot | Slot], <<$/>>),
|
||||
case file:open(Path, [read]) of
|
||||
{ok, Fd} ->
|
||||
file:close(Fd),
|
||||
?INFO_MSG("Serving ~s to ~s", [Path, ?ADDR_TO_STR(IP)]),
|
||||
?INFO_MSG("Serving ~s to ~s", [Path, encode_addr(IP)]),
|
||||
ContentType = guess_content_type(FileName),
|
||||
Headers1 = case ContentType of
|
||||
<<"image/", _SubType/binary>> -> [];
|
||||
@@ -429,43 +443,44 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
|
||||
Headers3 = Headers2 ++ CustomHeaders,
|
||||
http_response(200, Headers3, {file, Path});
|
||||
{error, eacces} ->
|
||||
?INFO_MSG("Cannot serve ~s to ~s: Permission denied",
|
||||
[Path, ?ADDR_TO_STR(IP)]),
|
||||
?WARNING_MSG("Cannot serve ~s to ~s: Permission denied",
|
||||
[Path, encode_addr(IP)]),
|
||||
http_response(403);
|
||||
{error, enoent} ->
|
||||
?INFO_MSG("Cannot serve ~s to ~s: No such file",
|
||||
[Path, ?ADDR_TO_STR(IP)]),
|
||||
?WARNING_MSG("Cannot serve ~s to ~s: No such file",
|
||||
[Path, encode_addr(IP)]),
|
||||
http_response(404);
|
||||
{error, eisdir} ->
|
||||
?INFO_MSG("Cannot serve ~s to ~s: Is a directory",
|
||||
[Path, ?ADDR_TO_STR(IP)]),
|
||||
?WARNING_MSG("Cannot serve ~s to ~s: Is a directory",
|
||||
[Path, encode_addr(IP)]),
|
||||
http_response(404);
|
||||
{error, Error} ->
|
||||
?INFO_MSG("Cannot serve ~s to ~s: ~s",
|
||||
[Path, ?ADDR_TO_STR(IP), format_error(Error)]),
|
||||
?WARNING_MSG("Cannot serve ~s to ~s: ~s",
|
||||
[Path, encode_addr(IP), format_error(Error)]),
|
||||
http_response(500)
|
||||
end;
|
||||
Error ->
|
||||
?ERROR_MSG("Cannot handle ~s request from ~s for ~s: ~p",
|
||||
[Method, ?ADDR_TO_STR(IP), Host, Error]),
|
||||
[Method, encode_addr(IP), Host, Error]),
|
||||
http_response(500)
|
||||
end;
|
||||
process(_LocalPath, #request{method = 'OPTIONS', host = Host,
|
||||
ip = IP} = Request) ->
|
||||
?DEBUG("Responding to OPTIONS request from ~s for ~s",
|
||||
[?ADDR_TO_STR(IP), Host]),
|
||||
[encode_addr(IP), Host]),
|
||||
{Proc, _Slot} = parse_http_request(Request),
|
||||
case catch gen_server:call(Proc, get_conf) of
|
||||
case catch gen_server:call(Proc, get_conf, ?CALL_TIMEOUT) of
|
||||
{ok, _DocRoot, CustomHeaders} ->
|
||||
http_response(200, CustomHeaders);
|
||||
AllowHeader = {<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>},
|
||||
http_response(200, [AllowHeader | CustomHeaders]);
|
||||
Error ->
|
||||
?ERROR_MSG("Cannot handle OPTIONS request from ~s for ~s: ~p",
|
||||
[?ADDR_TO_STR(IP), Host, Error]),
|
||||
[encode_addr(IP), Host, Error]),
|
||||
http_response(500)
|
||||
end;
|
||||
process(_LocalPath, #request{method = Method, host = Host, ip = IP}) ->
|
||||
?DEBUG("Rejecting ~s request from ~s for ~s",
|
||||
[Method, ?ADDR_TO_STR(IP), Host]),
|
||||
[Method, encode_addr(IP), Host]),
|
||||
http_response(405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -532,14 +547,12 @@ process_slot_request(#iq{lang = Lang, from = From} = IQ,
|
||||
case create_slot(State, From, File, Size, ContentType, XMLNS,
|
||||
Lang) of
|
||||
{ok, Slot} ->
|
||||
{ok, Timer} = timer:send_after(?SLOT_TIMEOUT,
|
||||
{slot_timed_out,
|
||||
Slot}),
|
||||
NewState = add_slot(Slot, Size, Timer, State),
|
||||
NewSlot = mk_slot(Slot, State, XMLNS),
|
||||
Query = make_query_string(Slot, Size, State),
|
||||
NewState = add_slot(Slot, Size, State),
|
||||
NewSlot = mk_slot(Slot, State, XMLNS, Query),
|
||||
{xmpp:make_iq_result(IQ, NewSlot), NewState};
|
||||
{ok, PutURL, GetURL} ->
|
||||
Slot = mk_slot(PutURL, GetURL, XMLNS),
|
||||
Slot = mk_slot(PutURL, GetURL, XMLNS, <<"">>),
|
||||
xmpp:make_iq_result(IQ, Slot);
|
||||
{error, Error} ->
|
||||
xmpp:make_error(IQ, Error)
|
||||
@@ -559,7 +572,7 @@ create_slot(#state{service_url = undefined, max_size = MaxSize},
|
||||
when MaxSize /= infinity,
|
||||
Size > MaxSize ->
|
||||
Text = {<<"File larger than ~w bytes">>, [MaxSize]},
|
||||
?INFO_MSG("Rejecting file ~s from ~s (too large: ~B bytes)",
|
||||
?WARNING_MSG("Rejecting file ~s from ~s (too large: ~B bytes)",
|
||||
[File, jid:encode(JID), Size]),
|
||||
Error = xmpp:err_not_acceptable(Text, Lang),
|
||||
Els = xmpp:get_els(Error),
|
||||
@@ -576,12 +589,12 @@ create_slot(#state{service_url = undefined,
|
||||
UserStr = make_user_string(JID, JIDinURL),
|
||||
UserDir = <<DocRoot/binary, $/, UserStr/binary>>,
|
||||
case ejabberd_hooks:run_fold(http_upload_slot_request, ServerHost, allow,
|
||||
[JID, UserDir, Size, Lang]) of
|
||||
[ServerHost, JID, UserDir, Size, Lang]) of
|
||||
allow ->
|
||||
RandStr = randoms:get_alphanum_string(SecretLength),
|
||||
RandStr = p1_rand:get_alphanum_string(SecretLength),
|
||||
FileStr = make_file_string(File),
|
||||
?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)",
|
||||
[jid:encode(JID), File]),
|
||||
?INFO_MSG("Got HTTP upload slot for ~s (file: ~s, size: ~B)",
|
||||
[jid:encode(JID), File, Size]),
|
||||
{ok, [UserStr, RandStr, FileStr]};
|
||||
deny ->
|
||||
{error, xmpp:err_service_unavailable()};
|
||||
@@ -594,18 +607,20 @@ create_slot(#state{service_url = ServiceURL},
|
||||
Options = [{body_format, binary}, {full_result, false}],
|
||||
HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}],
|
||||
SizeStr = integer_to_binary(Size),
|
||||
GetRequest = binary_to_list(ServiceURL) ++
|
||||
"?jid=" ++ ?URL_ENC(jid:encode({U, S, <<"">>})) ++
|
||||
"&name=" ++ ?URL_ENC(File) ++
|
||||
"&size=" ++ ?URL_ENC(SizeStr) ++
|
||||
"&content_type=" ++ ?URL_ENC(ContentType),
|
||||
case httpc:request(get, {GetRequest, []}, HttpOptions, Options) of
|
||||
JidStr = jid:encode({U, S, <<"">>}),
|
||||
GetRequest = <<ServiceURL/binary,
|
||||
"?jid=", (misc:url_encode(JidStr))/binary,
|
||||
"&name=", (misc:url_encode(File))/binary,
|
||||
"&size=", (misc:url_encode(SizeStr))/binary,
|
||||
"&content_type=", (misc:url_encode(ContentType))/binary>>,
|
||||
case httpc:request(get, {binary_to_list(GetRequest), []},
|
||||
HttpOptions, Options) of
|
||||
{ok, {Code, Body}} when Code >= 200, Code =< 299 ->
|
||||
case binary:split(Body, <<$\n>>, [global, trim]) of
|
||||
[<<"http", _/binary>> = PutURL,
|
||||
<<"http", _/binary>> = GetURL] ->
|
||||
?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)",
|
||||
[jid:encode(JID), File]),
|
||||
?INFO_MSG("Got HTTP upload slot for ~s (file: ~s, size: ~B)",
|
||||
[jid:encode(JID), File, Size]),
|
||||
{ok, PutURL, GetURL};
|
||||
Lines ->
|
||||
?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p",
|
||||
@@ -614,15 +629,15 @@ create_slot(#state{service_url = ServiceURL},
|
||||
{error, xmpp:err_service_unavailable(Txt, Lang)}
|
||||
end;
|
||||
{ok, {402, _Body}} ->
|
||||
?INFO_MSG("Got status code 402 for ~s from <~s>",
|
||||
?WARNING_MSG("Got status code 402 for ~s from <~s>",
|
||||
[jid:encode(JID), ServiceURL]),
|
||||
{error, xmpp:err_resource_constraint()};
|
||||
{ok, {403, _Body}} ->
|
||||
?INFO_MSG("Got status code 403 for ~s from <~s>",
|
||||
?WARNING_MSG("Got status code 403 for ~s from <~s>",
|
||||
[jid:encode(JID), ServiceURL]),
|
||||
{error, xmpp:err_not_allowed()};
|
||||
{ok, {413, _Body}} ->
|
||||
?INFO_MSG("Got status code 413 for ~s from <~s>",
|
||||
?WARNING_MSG("Got status code 413 for ~s from <~s>",
|
||||
[jid:encode(JID), ServiceURL]),
|
||||
{error, xmpp:err_not_acceptable()};
|
||||
{ok, {Code, _Body}} ->
|
||||
@@ -635,12 +650,15 @@ create_slot(#state{service_url = ServiceURL},
|
||||
{error, xmpp:err_service_unavailable()}
|
||||
end.
|
||||
|
||||
-spec add_slot(slot(), pos_integer(), timer:tref(), state()) -> state().
|
||||
add_slot(Slot, Size, Timer, #state{slots = Slots} = State) ->
|
||||
NewSlots = maps:put(Slot, {Size, Timer}, Slots),
|
||||
State#state{slots = NewSlots}.
|
||||
-spec add_slot(slot(), pos_integer(), state()) -> state().
|
||||
add_slot(Slot, Size, #state{external_secret = <<>>, slots = Slots} = State) ->
|
||||
TRef = erlang:start_timer(?SLOT_TIMEOUT, self(), Slot),
|
||||
NewSlots = maps:put(Slot, {Size, TRef}, Slots),
|
||||
State#state{slots = NewSlots};
|
||||
add_slot(_Slot, _Size, State) ->
|
||||
State.
|
||||
|
||||
-spec get_slot(slot(), state()) -> {ok, {pos_integer(), timer:tref()}} | error.
|
||||
-spec get_slot(slot(), state()) -> {ok, {pos_integer(), reference()}} | error.
|
||||
get_slot(Slot, #state{slots = Slots}) ->
|
||||
maps:find(Slot, Slots).
|
||||
|
||||
@@ -649,20 +667,21 @@ del_slot(Slot, #state{slots = Slots} = State) ->
|
||||
NewSlots = maps:remove(Slot, Slots),
|
||||
State#state{slots = NewSlots}.
|
||||
|
||||
-spec mk_slot(slot(), state(), binary()) -> upload_slot();
|
||||
(binary(), binary(), binary()) -> upload_slot().
|
||||
mk_slot(Slot, #state{put_url = PutPrefix, get_url = GetPrefix}, XMLNS) ->
|
||||
-spec mk_slot(slot(), state(), binary(), binary()) -> upload_slot();
|
||||
(binary(), binary(), binary(), binary()) -> upload_slot().
|
||||
mk_slot(Slot, #state{put_url = PutPrefix, get_url = GetPrefix}, XMLNS, Query) ->
|
||||
PutURL = str:join([PutPrefix | Slot], <<$/>>),
|
||||
GetURL = str:join([GetPrefix | Slot], <<$/>>),
|
||||
mk_slot(PutURL, GetURL, XMLNS);
|
||||
mk_slot(PutURL, GetURL, ?NS_HTTP_UPLOAD_0) ->
|
||||
#upload_slot_0{get = misc:url_encode(GetURL),
|
||||
put = misc:url_encode(PutURL),
|
||||
xmlns = ?NS_HTTP_UPLOAD_0};
|
||||
mk_slot(PutURL, GetURL, XMLNS) ->
|
||||
#upload_slot{get = misc:url_encode(GetURL),
|
||||
put = misc:url_encode(PutURL),
|
||||
xmlns = XMLNS}.
|
||||
mk_slot(PutURL, GetURL, XMLNS, Query);
|
||||
mk_slot(PutURL, GetURL, XMLNS, Query) ->
|
||||
PutURL1 = <<(misc:url_encode(PutURL))/binary, Query/binary>>,
|
||||
GetURL1 = misc:url_encode(GetURL),
|
||||
case XMLNS of
|
||||
?NS_HTTP_UPLOAD_0 ->
|
||||
#upload_slot_0{get = GetURL1, put = PutURL1, xmlns = XMLNS};
|
||||
_ ->
|
||||
#upload_slot{get = GetURL1, put = PutURL1, xmlns = XMLNS}
|
||||
end.
|
||||
|
||||
-spec make_user_string(jid(), sha1 | node) -> binary().
|
||||
make_user_string(#jid{luser = U, lserver = S}, sha1) ->
|
||||
@@ -674,6 +693,16 @@ make_user_string(#jid{luser = U}, node) ->
|
||||
make_file_string(File) ->
|
||||
replace_special_chars(File).
|
||||
|
||||
-spec make_query_string(slot(), non_neg_integer(), state()) -> binary().
|
||||
make_query_string(Slot, Size, #state{external_secret = Key}) when Key /= <<>> ->
|
||||
UrlPath = str:join(Slot, <<$/>>),
|
||||
SizeStr = integer_to_binary(Size),
|
||||
Data = <<UrlPath/binary, " ", SizeStr/binary>>,
|
||||
HMAC = str:to_hexlist(crypto:hmac(sha256, Key, Data)),
|
||||
<<"?v=", HMAC/binary>>;
|
||||
make_query_string(_Slot, _Size, _State) ->
|
||||
<<>>.
|
||||
|
||||
-spec replace_special_chars(binary()) -> binary().
|
||||
replace_special_chars(S) ->
|
||||
re:replace(S, <<"[^\\p{Xan}_.-]">>, <<$_>>,
|
||||
@@ -683,6 +712,11 @@ replace_special_chars(S) ->
|
||||
yield_content_type(<<"">>) -> ?DEFAULT_CONTENT_TYPE;
|
||||
yield_content_type(Type) -> Type.
|
||||
|
||||
-spec encode_addr(inet:ip_address() | {inet:ip_address(), inet:port_number()} |
|
||||
undefined) -> binary().
|
||||
encode_addr(IP) ->
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP)).
|
||||
|
||||
-spec iq_disco_info(binary(), binary(), binary(), [xdata()]) -> disco_info().
|
||||
iq_disco_info(Host, Lang, Name, AddInfo) ->
|
||||
Form = case gen_mod:get_module_opt(Host, ?MODULE, max_size) of
|
||||
@@ -868,7 +902,7 @@ convert(Path, #media_info{type = T, width = W, height = H} = Info) ->
|
||||
true ->
|
||||
Dir = filename:dirname(Path),
|
||||
Ext = atom_to_binary(T, latin1),
|
||||
FileName = <<(randoms:get_string())/binary, $., Ext/binary>>,
|
||||
FileName = <<(p1_rand:get_string())/binary, $., Ext/binary>>,
|
||||
OutPath = filename:join(Dir, FileName),
|
||||
{W1, H1} = if W > H -> {300, round(H*300/W)};
|
||||
H > W -> {round(W*300/H), 300};
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
code_change/3]).
|
||||
|
||||
%% ejabberd_hooks callback.
|
||||
-export([handle_slot_request/5]).
|
||||
-export([handle_slot_request/6]).
|
||||
|
||||
-include("jid.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -229,14 +229,13 @@ code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% ejabberd_hooks callback.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec handle_slot_request(allow | deny, jid(), binary(),
|
||||
-spec handle_slot_request(allow | deny, binary(), jid(), binary(),
|
||||
non_neg_integer(), binary()) -> allow | deny.
|
||||
handle_slot_request(allow, #jid{lserver = ServerHost} = JID, Path, Size,
|
||||
_Lang) ->
|
||||
handle_slot_request(allow, ServerHost, JID, Path, Size, _Lang) ->
|
||||
Proc = mod_http_upload:get_proc_name(ServerHost, ?MODULE),
|
||||
gen_server:cast(Proc, {handle_slot_request, JID, Path, Size}),
|
||||
allow;
|
||||
handle_slot_request(Acc, _JID, _Path, _Size, _Lang) -> Acc.
|
||||
handle_slot_request(Acc, _ServerHost, _JID, _Path, _Size, _Lang) -> Acc.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
|
||||
+16
-1
@@ -77,6 +77,17 @@
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start(Host, Opts) ->
|
||||
case gen_mod:get_opt(db_type, Opts) of
|
||||
mnesia ->
|
||||
?WARNING_MSG("Mnesia backend for ~s is not recommended: "
|
||||
"it's limited to 2GB and often gets corrupted "
|
||||
"when reaching this limit. SQL backend is "
|
||||
"recommended. Namely, for small servers SQLite "
|
||||
"is a preferred choice because it's very easy "
|
||||
"to configure.", [?MODULE]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
init_cache(Mod, Host, Opts),
|
||||
@@ -358,7 +369,9 @@ user_send_packet(Acc) ->
|
||||
-> {stanza(), c2s_state()}.
|
||||
user_send_packet_strip_tag({#message{} = Pkt, #{jid := JID} = C2SState}) ->
|
||||
LServer = JID#jid.lserver,
|
||||
{strip_my_stanza_id(Pkt, LServer), C2SState};
|
||||
Pkt1 = xmpp:del_meta(Pkt, stanza_id),
|
||||
Pkt2 = strip_my_stanza_id(Pkt1, LServer),
|
||||
{Pkt2, C2SState};
|
||||
user_send_packet_strip_tag(Acc) ->
|
||||
Acc.
|
||||
|
||||
@@ -406,6 +419,8 @@ get_stanza_id(#message{meta = #{stanza_id := ID}}) ->
|
||||
-spec init_stanza_id(stanza(), binary()) -> stanza().
|
||||
init_stanza_id(#message{meta = #{stanza_id := _ID}} = Pkt, _LServer) ->
|
||||
Pkt;
|
||||
init_stanza_id(#message{meta = #{from_offline := true}} = Pkt, _LServer) ->
|
||||
Pkt;
|
||||
init_stanza_id(Pkt, LServer) ->
|
||||
ID = p1_time_compat:system_time(micro_seconds),
|
||||
Pkt1 = strip_my_stanza_id(Pkt, LServer),
|
||||
|
||||
+2
-3
@@ -315,9 +315,8 @@ is_not_subscribed({error, StanzaError}) ->
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_pubsub, hard}].
|
||||
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end.
|
||||
mod_opt_type(host) -> fun ejabberd_config:v_host/1;
|
||||
mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1.
|
||||
|
||||
mod_options(_Host) ->
|
||||
[{host, <<"mix.@HOST@">>},
|
||||
|
||||
+21
-12
@@ -84,7 +84,7 @@
|
||||
max_rooms_discoitems = 100 :: non_neg_integer(),
|
||||
queue_type = ram :: ram | file,
|
||||
default_room_opts = [] :: list(),
|
||||
room_shaper = none :: shaper:shaper()}).
|
||||
room_shaper = none :: ejabberd_shaper:shaper()}).
|
||||
|
||||
-type muc_room_opts() :: [{atom(), any()}].
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
@@ -106,7 +106,7 @@
|
||||
-callback unregister_online_user(binary(), ljid(), binary(), binary()) -> any().
|
||||
-callback count_online_rooms_by_user(binary(), binary(), binary()) -> non_neg_integer().
|
||||
-callback get_online_rooms_by_user(binary(), binary(), binary()) -> [{binary(), binary()}].
|
||||
-callback get_subscribed_rooms(binary(), binary(), jid()) -> [ljid()] | [].
|
||||
-callback get_subscribed_rooms(binary(), binary(), jid()) -> [{ljid(), [binary()]}] | [].
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
@@ -295,6 +295,15 @@ handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{hosts = OldHosts}) ->
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_iq_handlers(OldHost)
|
||||
end, OldHosts -- NewHosts),
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
lists:foreach(
|
||||
fun({_, _, Pid}) when node(Pid) == node() ->
|
||||
Pid ! config_reloaded;
|
||||
(_) ->
|
||||
ok
|
||||
end, get_online_rooms(ServerHost, Host))
|
||||
end, misc:intersection(NewHosts, OldHosts)),
|
||||
{noreply, NewState};
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
@@ -568,7 +577,7 @@ process_muc_unique(#iq{type = set, lang = Lang} = IQ) ->
|
||||
process_muc_unique(#iq{from = From, type = get,
|
||||
sub_els = [#muc_unique{}]} = IQ) ->
|
||||
Name = str:sha(term_to_binary([From, p1_time_compat:timestamp(),
|
||||
randoms:get_string()])),
|
||||
p1_rand:get_string()])),
|
||||
xmpp:make_iq_result(IQ, #muc_unique{name = Name}).
|
||||
|
||||
-spec process_mucsub(iq()) -> iq().
|
||||
@@ -579,8 +588,8 @@ process_mucsub(#iq{type = get, from = From, to = To,
|
||||
sub_els = [#muc_subscriptions{}]} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
ServerHost = ejabberd_router:host_of_route(Host),
|
||||
RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
|
||||
xmpp:make_iq_result(IQ, #muc_subscriptions{list = RoomJIDs});
|
||||
Subs = get_subscribed_rooms(ServerHost, Host, From),
|
||||
xmpp:make_iq_result(IQ, #muc_subscriptions{list = Subs});
|
||||
process_mucsub(#iq{lang = Lang} = IQ) ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
|
||||
@@ -710,14 +719,15 @@ get_subscribed_rooms(ServerHost, Host, From) ->
|
||||
lists:flatmap(
|
||||
fun({Name, _, Pid}) ->
|
||||
case p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
|
||||
true -> [jid:make(Name, Host)];
|
||||
{true, Nodes} ->
|
||||
[#muc_subscription{jid = jid:make(Name, Host), events = Nodes}];
|
||||
false -> []
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, Rooms);
|
||||
V ->
|
||||
V
|
||||
[#muc_subscription{jid = Jid, events = Nodes} || {Jid, Nodes} <- V]
|
||||
end.
|
||||
|
||||
get_nick(ServerHost, Host, From) ->
|
||||
@@ -880,10 +890,9 @@ mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(history_size) ->
|
||||
fun (I) when is_integer(I), I >= 0 -> I end;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(host) -> fun ejabberd_config:v_host/1;
|
||||
mod_opt_type(name) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1;
|
||||
mod_opt_type(max_room_desc) ->
|
||||
fun (infinity) -> infinity;
|
||||
(I) when is_integer(I), I > 0 -> I
|
||||
@@ -994,7 +1003,7 @@ mod_options(Host) ->
|
||||
{max_room_id, infinity},
|
||||
{max_room_name, infinity},
|
||||
{max_rooms_discoitems, 100},
|
||||
{max_user_conferences, 10},
|
||||
{max_user_conferences, 100},
|
||||
{max_users, 200},
|
||||
{max_users_admin_threshold, 5},
|
||||
{max_users_presence, 1000},
|
||||
@@ -1014,7 +1023,7 @@ mod_options(Host) ->
|
||||
{allow_visitor_status,true},
|
||||
{anonymous,true},
|
||||
{captcha_protected,false},
|
||||
{lang, ejabberd_config:get_mylang()},
|
||||
{lang,<<>>},
|
||||
{logging,false},
|
||||
{members_by_default,true},
|
||||
{members_only,false},
|
||||
|
||||
@@ -361,7 +361,7 @@ build_summary_room(Name, Host, Pid) ->
|
||||
{<<Name/binary, "@", Host/binary>>,
|
||||
misc:atom_to_binary(Public),
|
||||
Participants
|
||||
}.
|
||||
}.
|
||||
|
||||
muc_register_nick(Nick, FromBinary, ServerHost) ->
|
||||
Host = find_host(ServerHost),
|
||||
@@ -424,9 +424,7 @@ web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
|
||||
Res = [?XCT(<<"h1">>, <<"Multi-User Chat">>),
|
||||
?XCT(<<"h3">>, <<"Statistics">>),
|
||||
?XAE(<<"table">>, [],
|
||||
[?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, OnlineRoomsNumber),
|
||||
?TDTD(<<"Permanent rooms">>, mnesia:table_info(muc_room, size)),
|
||||
?TDTD(<<"Registered nicknames">>, mnesia:table_info(muc_registered, size))
|
||||
[?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, OnlineRoomsNumber)
|
||||
])
|
||||
]),
|
||||
?XE(<<"ul">>, [?LI([?ACT(<<"rooms">>, <<"List of rooms">>)])])
|
||||
@@ -952,6 +950,13 @@ format_room_option(OptionString, ValueString) ->
|
||||
subject_author ->ValueString;
|
||||
presence_broadcast ->misc:expr_to_term(ValueString);
|
||||
max_users -> binary_to_integer(ValueString);
|
||||
voice_request_min_interval -> binary_to_integer(ValueString);
|
||||
vcard -> ValueString;
|
||||
vcard_xupdate when ValueString /= <<"undefined">>,
|
||||
ValueString /= <<"external">> ->
|
||||
ValueString;
|
||||
lang -> ValueString;
|
||||
pubsub -> ValueString;
|
||||
_ -> misc:binary_to_atom(ValueString)
|
||||
end,
|
||||
{Option, Value}.
|
||||
|
||||
+11
-7
@@ -599,20 +599,20 @@ put_header(F, Room, Date, CSSFile, Lang, Hour_offset,
|
||||
fw(F, <<"<br/><a class=\"ts\">GMT~s</a><br/>">>,
|
||||
[Time_offset_str]).
|
||||
|
||||
put_header_css(F, false) ->
|
||||
put_header_css(F, {file, Path}) ->
|
||||
fw(F, <<"<style type=\"text/css\">">>),
|
||||
fw(F, <<"<!--">>),
|
||||
case misc:read_css("muc.css") of
|
||||
case file:read_file(Path) of
|
||||
{ok, Data} -> fw(F, Data);
|
||||
{error, _} -> ok
|
||||
end,
|
||||
fw(F, <<"//-->">>),
|
||||
fw(F, <<"</style>">>);
|
||||
put_header_css(F, CSSFile) ->
|
||||
put_header_css(F, {url, URL}) ->
|
||||
fw(F,
|
||||
<<"<link rel=\"stylesheet\" type=\"text/css\" "
|
||||
"href=\"~s\" media=\"all\">">>,
|
||||
[CSSFile]).
|
||||
[URL]).
|
||||
|
||||
put_header_script(F) ->
|
||||
fw(F, <<"<script type=\"text/javascript\">">>),
|
||||
@@ -931,8 +931,12 @@ has_no_permanent_store_hint(Packet) ->
|
||||
mod_opt_type(access_log) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(cssfile) ->
|
||||
fun(false) -> false;
|
||||
(File) -> misc:try_read_file(File)
|
||||
fun(S) ->
|
||||
case str:to_lower(S) of
|
||||
<<"http:/", _/binary>> -> {url, misc:try_url(S)};
|
||||
<<"https:/", _/binary>> -> {url, misc:try_url(S)};
|
||||
_ -> {file, misc:try_read_file(S)}
|
||||
end
|
||||
end;
|
||||
mod_opt_type(dirname) ->
|
||||
fun (room_jid) -> room_jid;
|
||||
@@ -969,7 +973,7 @@ mod_opt_type(top_link) ->
|
||||
|
||||
mod_options(_) ->
|
||||
[{access_log, muc_admin},
|
||||
{cssfile, false},
|
||||
{cssfile, filename:join(misc:css_dir(), "muc.css")},
|
||||
{dirname, room_jid},
|
||||
{dirtype, subdirs},
|
||||
{file_format, html},
|
||||
|
||||
@@ -263,12 +263,13 @@ unregister_online_user(_ServerHost, {U, S, R}, Room, Host) ->
|
||||
#muc_online_users{us = {U, S}, resource = R,
|
||||
room = Room, host = Host}).
|
||||
|
||||
count_online_rooms_by_user(_ServerHost, U, S) ->
|
||||
count_online_rooms_by_user(ServerHost, U, S) ->
|
||||
MucHost = gen_mod:get_module_opt_host(ServerHost, mod_muc, <<"conference.@HOST@">>),
|
||||
ets:select_count(
|
||||
muc_online_users,
|
||||
ets:fun2ms(
|
||||
fun(#muc_online_users{us = {U1, S1}}) ->
|
||||
U == U1 andalso S == S1
|
||||
fun(#muc_online_users{us = {U1, S1}, host = Host}) ->
|
||||
U == U1 andalso S == S1 andalso MucHost == Host
|
||||
end)).
|
||||
|
||||
get_online_rooms_by_user(ServerHost, U, S) ->
|
||||
@@ -339,6 +340,8 @@ handle_cast(_Msg, State) ->
|
||||
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
||||
clean_table_from_bad_node(Node),
|
||||
{noreply, State};
|
||||
handle_info({mnesia_system_event, {mnesia_up, _Node}}, State) ->
|
||||
{noreply, State};
|
||||
handle_info(Info, State) ->
|
||||
?ERROR_MSG("unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
+109
-42
@@ -122,7 +122,7 @@ start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueT
|
||||
init([Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Creator, _Nick, DefRoomOpts, QueueType]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Shaper = shaper:new(RoomShaper),
|
||||
Shaper = ejabberd_shaper:new(RoomShaper),
|
||||
RoomQueue = room_queue_new(ServerHost, Shaper, QueueType),
|
||||
State = set_affiliation(Creator, owner,
|
||||
#state{host = Host, server_host = ServerHost,
|
||||
@@ -141,7 +141,7 @@ init([Host, ServerHost, Access, Room, HistorySize,
|
||||
{ok, normal_state, State1};
|
||||
init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Shaper = shaper:new(RoomShaper),
|
||||
Shaper = ejabberd_shaper:new(RoomShaper),
|
||||
RoomQueue = room_queue_new(ServerHost, Shaper, QueueType),
|
||||
State = set_opts(Opts, #state{host = Host,
|
||||
server_host = ServerHost,
|
||||
@@ -169,7 +169,7 @@ normal_state({route, <<"">>,
|
||||
* 1000000),
|
||||
Size = element_size(Packet),
|
||||
{MessageShaper, MessageShaperInterval} =
|
||||
shaper:update(Activity#activity.message_shaper, Size),
|
||||
ejabberd_shaper:update(Activity#activity.message_shaper, Size),
|
||||
if Activity#activity.message /= undefined ->
|
||||
ErrText = <<"Traffic rate limit is exceeded">>,
|
||||
Err = xmpp:err_resource_constraint(ErrText, Lang),
|
||||
@@ -178,7 +178,7 @@ normal_state({route, <<"">>,
|
||||
Now >= Activity#activity.message_time + MinMessageInterval,
|
||||
MessageShaperInterval == 0 ->
|
||||
{RoomShaper, RoomShaperInterval} =
|
||||
shaper:update(StateData#state.room_shaper, Size),
|
||||
ejabberd_shaper:update(StateData#state.room_shaper, Size),
|
||||
RoomQueueEmpty = case StateData#state.room_queue of
|
||||
undefined -> true;
|
||||
RQ -> p1_queue:is_empty(RQ)
|
||||
@@ -546,7 +546,7 @@ handle_sync_event(get_subscribers, _From, StateName, StateData) ->
|
||||
{reply, {ok, JIDs}, StateName, StateData};
|
||||
handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
|
||||
StateName, StateData) ->
|
||||
IQ = #iq{type = set, id = randoms:get_string(),
|
||||
IQ = #iq{type = set, id = p1_rand:get_string(),
|
||||
from = From, sub_els = [#muc_subscribe{nick = Nick,
|
||||
events = Nodes}]},
|
||||
Config = StateData#state.config,
|
||||
@@ -572,7 +572,7 @@ handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
|
||||
{reply, {error, get_error_text(Err)}, StateName, StateData}
|
||||
end;
|
||||
handle_sync_event({muc_unsubscribe, From}, _From, StateName, StateData) ->
|
||||
IQ = #iq{type = set, id = randoms:get_string(),
|
||||
IQ = #iq{type = set, id = p1_rand:get_string(),
|
||||
from = From, sub_els = [#muc_unsubscribe{}]},
|
||||
case process_iq_mucsub(From, IQ, StateData) of
|
||||
{result, _, NewState} ->
|
||||
@@ -583,7 +583,12 @@ handle_sync_event({muc_unsubscribe, From}, _From, StateName, StateData) ->
|
||||
{reply, {error, get_error_text(Err)}, StateName, StateData}
|
||||
end;
|
||||
handle_sync_event({is_subscribed, From}, _From, StateName, StateData) ->
|
||||
IsSubs = ?DICT:is_key(jid:split(From), StateData#state.subscribers),
|
||||
IsSubs = case (?DICT):find(jid:split(From), StateData#state.subscribers) of
|
||||
{ok, #subscriber{nodes = Nodes}} ->
|
||||
{true, Nodes};
|
||||
error ->
|
||||
false
|
||||
end,
|
||||
{reply, IsSubs, StateName, StateData};
|
||||
handle_sync_event(_Event, _From, StateName,
|
||||
StateData) ->
|
||||
@@ -678,6 +683,19 @@ handle_info({iq_reply, timeout, IQ}, StateName, StateData) ->
|
||||
Err = xmpp:err_recipient_unavailable(Txt, IQ#iq.lang),
|
||||
ejabberd_router:route_error(IQ, Err),
|
||||
{next_state, StateName, StateData};
|
||||
handle_info(config_reloaded, StateName, StateData) ->
|
||||
Max = gen_mod:get_module_opt(StateData#state.server_host,
|
||||
mod_muc, history_size),
|
||||
History1 = StateData#state.history,
|
||||
Q1 = History1#lqueue.queue,
|
||||
Q2 = case p1_queue:len(Q1) of
|
||||
Len when Len > Max ->
|
||||
lqueue_cut(Q1, Len-Max);
|
||||
_ ->
|
||||
Q1
|
||||
end,
|
||||
History2 = History1#lqueue{queue = Q2, max = Max},
|
||||
{next_state, StateName, StateData#state{history = History2}};
|
||||
handle_info(_Info, StateName, StateData) ->
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
@@ -766,7 +784,8 @@ process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData
|
||||
drop ->
|
||||
{next_state, normal_state, StateData};
|
||||
NewPacket1 ->
|
||||
NewPacket = xmpp:remove_subtag(NewPacket1, #nick{}),
|
||||
NewPacket = xmpp:put_meta(xmpp:remove_subtag(NewPacket1, #nick{}),
|
||||
muc_sender_real_jid, From),
|
||||
Node = if Subject == [] -> ?NS_MUCSUB_NODES_MESSAGES;
|
||||
true -> ?NS_MUCSUB_NODES_SUBJECT
|
||||
end,
|
||||
@@ -841,7 +860,7 @@ process_normal_message(From, #message{lang = Lang} = Pkt, StateData) ->
|
||||
{ok, [#muc_invite{}|_] = Invitations} ->
|
||||
lists:foldl(
|
||||
fun(Invitation, AccState) ->
|
||||
process_invitation(From, Invitation, Lang, AccState)
|
||||
process_invitation(From, Pkt, Invitation, Lang, AccState)
|
||||
end, StateData, Invitations);
|
||||
{ok, [{role, participant}]} ->
|
||||
process_voice_request(From, Pkt, StateData);
|
||||
@@ -854,9 +873,9 @@ process_normal_message(From, #message{lang = Lang} = Pkt, StateData) ->
|
||||
StateData
|
||||
end.
|
||||
|
||||
-spec process_invitation(jid(), muc_invite(), binary(), state()) -> state().
|
||||
process_invitation(From, Invitation, Lang, StateData) ->
|
||||
IJID = route_invitation(From, Invitation, Lang, StateData),
|
||||
-spec process_invitation(jid(), message(), muc_invite(), binary(), state()) -> state().
|
||||
process_invitation(From, Pkt, Invitation, Lang, StateData) ->
|
||||
IJID = route_invitation(From, Pkt, Invitation, Lang, StateData),
|
||||
Config = StateData#state.config,
|
||||
case Config#config.members_only of
|
||||
true ->
|
||||
@@ -1503,7 +1522,7 @@ get_max_users_admin_threshold(StateData) ->
|
||||
gen_mod:get_module_opt(StateData#state.server_host,
|
||||
mod_muc, max_users_admin_threshold).
|
||||
|
||||
-spec room_queue_new(binary(), shaper:shaper(), _) -> p1_queue:queue().
|
||||
-spec room_queue_new(binary(), ejabberd_shaper:shaper(), _) -> p1_queue:queue().
|
||||
room_queue_new(ServerHost, Shaper, QueueType) ->
|
||||
HaveRoomShaper = Shaper /= none,
|
||||
HaveMessageShaper = gen_mod:get_module_opt(
|
||||
@@ -1533,10 +1552,10 @@ get_user_activity(JID, StateData) ->
|
||||
{ok, _P, A} -> A;
|
||||
error ->
|
||||
MessageShaper =
|
||||
shaper:new(gen_mod:get_module_opt(StateData#state.server_host,
|
||||
ejabberd_shaper:new(gen_mod:get_module_opt(StateData#state.server_host,
|
||||
mod_muc, user_message_shaper)),
|
||||
PresenceShaper =
|
||||
shaper:new(gen_mod:get_module_opt(StateData#state.server_host,
|
||||
ejabberd_shaper:new(gen_mod:get_module_opt(StateData#state.server_host,
|
||||
mod_muc, user_presence_shaper)),
|
||||
#activity{message_shaper = MessageShaper,
|
||||
presence_shaper = PresenceShaper}
|
||||
@@ -1575,10 +1594,10 @@ store_user_activity(JID, UserActivity, StateData) ->
|
||||
of
|
||||
true ->
|
||||
{_, MessageShaperInterval} =
|
||||
shaper:update(UserActivity#activity.message_shaper,
|
||||
ejabberd_shaper:update(UserActivity#activity.message_shaper,
|
||||
100000),
|
||||
{_, PresenceShaperInterval} =
|
||||
shaper:update(UserActivity#activity.presence_shaper,
|
||||
ejabberd_shaper:update(UserActivity#activity.presence_shaper,
|
||||
100000),
|
||||
Delay = lists:max([MessageShaperInterval,
|
||||
PresenceShaperInterval,
|
||||
@@ -1620,7 +1639,7 @@ prepare_room_queue(StateData) ->
|
||||
Packet = Activity#activity.message,
|
||||
Size = element_size(Packet),
|
||||
{RoomShaper, RoomShaperInterval} =
|
||||
shaper:update(StateData#state.room_shaper, Size),
|
||||
ejabberd_shaper:update(StateData#state.room_shaper, Size),
|
||||
erlang:send_after(RoomShaperInterval, self(),
|
||||
process_room_queue),
|
||||
StateData#state{room_shaper = RoomShaper};
|
||||
@@ -1629,7 +1648,7 @@ prepare_room_queue(StateData) ->
|
||||
{_Nick, Packet} = Activity#activity.presence,
|
||||
Size = element_size(Packet),
|
||||
{RoomShaper, RoomShaperInterval} =
|
||||
shaper:update(StateData#state.room_shaper, Size),
|
||||
ejabberd_shaper:update(StateData#state.room_shaper, Size),
|
||||
erlang:send_after(RoomShaperInterval, self(),
|
||||
process_room_queue),
|
||||
StateData#state{room_shaper = RoomShaper};
|
||||
@@ -2152,7 +2171,9 @@ send_initial_presences_and_messages(From, Nick, Presence, NewState, OldState) ->
|
||||
send_self_presence(JID, State) ->
|
||||
AvatarHash = (State#state.config)#config.vcard_xupdate,
|
||||
DiscoInfo = make_disco_info(JID, State),
|
||||
DiscoHash = mod_caps:compute_disco_hash(DiscoInfo, sha),
|
||||
Extras = iq_disco_info_extras(<<"en">>, State, true),
|
||||
DiscoInfo1 = DiscoInfo#disco_info{xdata = [Extras]},
|
||||
DiscoHash = mod_caps:compute_disco_hash(DiscoInfo1, sha),
|
||||
Els1 = [#caps{hash = <<"sha-1">>,
|
||||
node = ejabberd_config:get_uri(),
|
||||
version = DiscoHash}],
|
||||
@@ -2162,7 +2183,7 @@ send_self_presence(JID, State) ->
|
||||
Els1
|
||||
end,
|
||||
ejabberd_router:route(#presence{from = State#state.jid, to = JID,
|
||||
id = randoms:get_string(),
|
||||
id = p1_rand:get_string(),
|
||||
sub_els = Els2}).
|
||||
|
||||
-spec send_initial_presence(jid(), state(), state()) -> ok.
|
||||
@@ -2465,7 +2486,7 @@ send_affiliation(JID, Affiliation, StateData) ->
|
||||
Item = #muc_item{jid = JID,
|
||||
affiliation = Affiliation,
|
||||
role = none},
|
||||
Message = #message{id = randoms:get_string(),
|
||||
Message = #message{id = p1_rand:get_string(),
|
||||
sub_els = [#muc_user{items = [Item]}]},
|
||||
Users = get_users_and_subscribers(StateData),
|
||||
Recipients = case (StateData#state.config)#config.anonymous of
|
||||
@@ -2794,9 +2815,9 @@ process_item_change(Item, SD, UJID) ->
|
||||
undefined ->
|
||||
<<"">>
|
||||
end,
|
||||
St = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("failed to set item ~p~s: ~p",
|
||||
[Item, FromSuffix,
|
||||
{E, {R, erlang:get_stacktrace()}}]),
|
||||
[Item, FromSuffix, {E, {R, St}}]),
|
||||
{error, xmpp:err_internal_server_error()}
|
||||
end.
|
||||
|
||||
@@ -2841,8 +2862,13 @@ find_changed_items(UJID, UAffiliation, URole,
|
||||
TAffiliation = get_affiliation(JID, StateData),
|
||||
TRole = get_role(JID, StateData),
|
||||
ServiceAf = get_service_affiliation(JID, StateData),
|
||||
UIsSubscriber = is_subscriber(UJID, StateData),
|
||||
URole1 = case {URole, UIsSubscriber} of
|
||||
{none, true} -> subscriber;
|
||||
{UR, _} -> UR
|
||||
end,
|
||||
CanChangeRA = case can_change_ra(UAffiliation,
|
||||
URole,
|
||||
URole1,
|
||||
TAffiliation,
|
||||
TRole, RoleOrAff, RoleOrAffValue,
|
||||
ServiceAf) of
|
||||
@@ -2962,9 +2988,19 @@ can_change_ra(_FAffiliation, _FRole, _TAffiliation,
|
||||
can_change_ra(_FAffiliation, moderator, _TAffiliation,
|
||||
visitor, role, none, _ServiceAf) ->
|
||||
true;
|
||||
can_change_ra(FAffiliation, subscriber, _TAffiliation,
|
||||
visitor, role, none, _ServiceAf)
|
||||
when (FAffiliation == owner) or
|
||||
(FAffiliation == admin) ->
|
||||
true;
|
||||
can_change_ra(_FAffiliation, moderator, _TAffiliation,
|
||||
visitor, role, participant, _ServiceAf) ->
|
||||
true;
|
||||
can_change_ra(FAffiliation, subscriber, _TAffiliation,
|
||||
visitor, role, participant, _ServiceAf)
|
||||
when (FAffiliation == owner) or
|
||||
(FAffiliation == admin) ->
|
||||
true;
|
||||
can_change_ra(FAffiliation, _FRole, _TAffiliation,
|
||||
visitor, role, moderator, _ServiceAf)
|
||||
when (FAffiliation == owner) or
|
||||
@@ -2973,9 +3009,19 @@ can_change_ra(FAffiliation, _FRole, _TAffiliation,
|
||||
can_change_ra(_FAffiliation, moderator, _TAffiliation,
|
||||
participant, role, none, _ServiceAf) ->
|
||||
true;
|
||||
can_change_ra(FAffiliation, subscriber, _TAffiliation,
|
||||
participant, role, none, _ServiceAf)
|
||||
when (FAffiliation == owner) or
|
||||
(FAffiliation == admin) ->
|
||||
true;
|
||||
can_change_ra(_FAffiliation, moderator, _TAffiliation,
|
||||
participant, role, visitor, _ServiceAf) ->
|
||||
true;
|
||||
can_change_ra(FAffiliation, subscriber, _TAffiliation,
|
||||
participant, role, visitor, _ServiceAf)
|
||||
when (FAffiliation == owner) or
|
||||
(FAffiliation == admin) ->
|
||||
true;
|
||||
can_change_ra(FAffiliation, _FRole, _TAffiliation,
|
||||
participant, role, moderator, _ServiceAf)
|
||||
when (FAffiliation == owner) or
|
||||
@@ -3005,6 +3051,24 @@ can_change_ra(_FAffiliation, _FRole, admin, moderator,
|
||||
can_change_ra(admin, _FRole, _TAffiliation, moderator,
|
||||
role, participant, _ServiceAf) ->
|
||||
true;
|
||||
can_change_ra(owner, moderator, TAffiliation,
|
||||
moderator, role, none, _ServiceAf)
|
||||
when TAffiliation /= owner ->
|
||||
true;
|
||||
can_change_ra(owner, subscriber, TAffiliation,
|
||||
moderator, role, none, _ServiceAf)
|
||||
when TAffiliation /= owner ->
|
||||
true;
|
||||
can_change_ra(admin, moderator, TAffiliation,
|
||||
moderator, role, none, _ServiceAf)
|
||||
when (TAffiliation /= owner) and
|
||||
(TAffiliation /= admin) ->
|
||||
true;
|
||||
can_change_ra(admin, subscriber, TAffiliation,
|
||||
moderator, role, none, _ServiceAf)
|
||||
when (TAffiliation /= owner) and
|
||||
(TAffiliation /= admin) ->
|
||||
true;
|
||||
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
|
||||
_TRole, role, _Value, _ServiceAf) ->
|
||||
false.
|
||||
@@ -3479,7 +3543,7 @@ send_config_change_info(New, #state{config = Old} = StateData) ->
|
||||
send_self_presence(JID, StateData#state{config = New})
|
||||
end, ?DICT:to_list(StateData#state.users)),
|
||||
Message = #message{type = groupchat,
|
||||
id = randoms:get_string(),
|
||||
id = p1_rand:get_string(),
|
||||
sub_els = [#muc_user{status_codes = Codes}]},
|
||||
send_wrapped_multiple(StateData#state.jid,
|
||||
get_users_and_subscribers(StateData),
|
||||
@@ -3834,10 +3898,11 @@ process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
||||
try
|
||||
true = mod_caps:is_valid_node(Node),
|
||||
DiscoInfo = make_disco_info(From, StateData),
|
||||
Hash = mod_caps:compute_disco_hash(DiscoInfo, sha),
|
||||
Node = <<(ejabberd_config:get_uri())/binary, $#, Hash/binary>>,
|
||||
Extras = iq_disco_info_extras(Lang, StateData, true),
|
||||
{result, DiscoInfo#disco_info{node = Node, xdata = [Extras]}}
|
||||
DiscoInfo1 = DiscoInfo#disco_info{xdata = [Extras]},
|
||||
Hash = mod_caps:compute_disco_hash(DiscoInfo1, sha),
|
||||
Node = <<(ejabberd_config:get_uri())/binary, $#, Hash/binary>>,
|
||||
{result, DiscoInfo1#disco_info{node = Node}}
|
||||
catch _:{badmatch, _} ->
|
||||
Txt = <<"Invalid node name">>,
|
||||
{error, xmpp:err_item_not_found(Txt, Lang)}
|
||||
@@ -4033,11 +4098,11 @@ process_iq_mucsub(From, #iq{type = get, lang = Lang,
|
||||
FAffiliation = get_affiliation(From, StateData),
|
||||
FRole = get_role(From, StateData),
|
||||
if FRole == moderator; FAffiliation == owner; FAffiliation == admin ->
|
||||
JIDs = dict:fold(
|
||||
fun(_, #subscriber{jid = J}, Acc) ->
|
||||
[J|Acc]
|
||||
Subs = dict:fold(
|
||||
fun(_, #subscriber{jid = J, nodes = Nodes}, Acc) ->
|
||||
[#muc_subscription{jid = J, events = Nodes}|Acc]
|
||||
end, [], StateData#state.subscribers),
|
||||
{result, #muc_subscriptions{list = JIDs}, StateData};
|
||||
{result, #muc_subscriptions{list = Subs}, StateData};
|
||||
true ->
|
||||
Txt = <<"Moderator privileges required">>,
|
||||
{error, xmpp:err_forbidden(Txt, Lang)}
|
||||
@@ -4170,8 +4235,8 @@ check_invitation(From, Invitations, Lang, StateData) ->
|
||||
{error, xmpp:err_not_allowed(Txt, Lang)}
|
||||
end.
|
||||
|
||||
-spec route_invitation(jid(), muc_invite(), binary(), state()) -> jid().
|
||||
route_invitation(From, Invitation, Lang, StateData) ->
|
||||
-spec route_invitation(jid(), message(), muc_invite(), binary(), state()) -> jid().
|
||||
route_invitation(From, Pkt, Invitation, Lang, StateData) ->
|
||||
#muc_invite{to = JID, reason = Reason} = Invitation,
|
||||
Invite = Invitation#muc_invite{to = undefined, from = From},
|
||||
Password = case (StateData#state.config)#config.password_protected of
|
||||
@@ -4210,10 +4275,12 @@ route_invitation(From, Invitation, Lang, StateData) ->
|
||||
type = normal,
|
||||
body = xmpp:mk_text(Body),
|
||||
sub_els = [XUser, XConference]},
|
||||
ejabberd_hooks:run(muc_invite, StateData#state.server_host,
|
||||
[StateData#state.jid, StateData#state.config,
|
||||
From, JID, Reason]),
|
||||
ejabberd_router:route(Msg),
|
||||
Msg2 = ejabberd_hooks:run_fold(muc_invite,
|
||||
StateData#state.server_host,
|
||||
Msg,
|
||||
[StateData#state.jid, StateData#state.config,
|
||||
From, JID, Reason, Pkt]),
|
||||
ejabberd_router:route(Msg2),
|
||||
JID.
|
||||
|
||||
%% Handle a message sent to the room by a non-participant.
|
||||
@@ -4324,7 +4391,7 @@ send_subscriptions_change_notifications(From, Nick, Type, State) ->
|
||||
items = #ps_items{
|
||||
node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
|
||||
items = [#ps_item{
|
||||
id = randoms:get_string(),
|
||||
id = p1_rand:get_string(),
|
||||
sub_els = [Payload]}]}}]},
|
||||
ejabberd_router:route(xmpp:set_from_to(Packet, From, JID));
|
||||
false ->
|
||||
@@ -4367,7 +4434,7 @@ send_wrapped(From, To, Packet, Node, State) ->
|
||||
true ->
|
||||
ejabberd_router:route(
|
||||
#presence{from = State#state.jid, to = To,
|
||||
id = randoms:get_string(),
|
||||
id = p1_rand:get_string(),
|
||||
type = unavailable});
|
||||
false ->
|
||||
ok
|
||||
@@ -4389,7 +4456,7 @@ wrap(From, To, Packet, Node) ->
|
||||
items = #ps_items{
|
||||
node = Node,
|
||||
items = [#ps_item{
|
||||
id = randoms:get_string(),
|
||||
id = p1_rand:get_string(),
|
||||
sub_els = [El]}]}}]}.
|
||||
|
||||
%% -spec send_multiple(jid(), binary(), [#user{}], stanza()) -> ok.
|
||||
|
||||
+2
-2
@@ -411,10 +411,10 @@ get_subscribed_rooms(LServer, Host, Jid) ->
|
||||
JidS = jid:encode(Jid),
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(room)s from muc_room_subscribers where jid=%(JidS)s"
|
||||
?SQL("select @(room)s, @(nodes)s from muc_room_subscribers where jid=%(JidS)s"
|
||||
" and host=%(Host)s")) of
|
||||
{selected, Subs} ->
|
||||
[jid:make(Room, Host, <<>>) || {Room} <- Subs];
|
||||
[{jid:make(Room, Host, <<>>), ejabberd_sql:decode_term(Nodes)} || {Room, Nodes} <- Subs];
|
||||
_Error ->
|
||||
[]
|
||||
end.
|
||||
|
||||
+9
-10
@@ -51,7 +51,7 @@
|
||||
ts :: integer()}).
|
||||
|
||||
-record(dest, {jid_string :: binary() | none,
|
||||
jid_jid :: xmpp:jid(),
|
||||
jid_jid :: jid(),
|
||||
type :: to | cc | bcc,
|
||||
address :: address()}).
|
||||
|
||||
@@ -536,7 +536,7 @@ decide_action_groups(Groups) ->
|
||||
%%% Route packet
|
||||
%%%-------------------------
|
||||
|
||||
-spec route_packet(jid(), #dest{}, xmpp:stanza(), [addresses()], [addresses()]) -> 'ok'.
|
||||
-spec route_packet(jid(), #dest{}, stanza(), [addresses()], [addresses()]) -> 'ok'.
|
||||
route_packet(From, ToDest, Packet, Others, Addresses) ->
|
||||
Dests = case ToDest#dest.type of
|
||||
bcc -> [];
|
||||
@@ -545,7 +545,7 @@ route_packet(From, ToDest, Packet, Others, Addresses) ->
|
||||
route_packet2(From, ToDest#dest.jid_string, Dests,
|
||||
Packet, {Others, Addresses}).
|
||||
|
||||
-spec route_packet_multicast(jid(), binary(), xmpp:stanza(), [#dest{}], [address()], #limits{}) -> 'ok'.
|
||||
-spec route_packet_multicast(jid(), binary(), stanza(), [#dest{}], [address()], #limits{}) -> 'ok'.
|
||||
route_packet_multicast(From, ToS, Packet, Dests,
|
||||
Addresses, Limits) ->
|
||||
Type_of_stanza = type_of_stanza(Packet),
|
||||
@@ -557,7 +557,7 @@ route_packet_multicast(From, ToS, Packet, Dests,
|
||||
Addresses)
|
||||
end, Fragmented_dests).
|
||||
|
||||
-spec route_packet2(jid(), binary(), [#dest{}], xmpp:stanza(), {[address()], [address()]} | [address()]) -> 'ok'.
|
||||
-spec route_packet2(jid(), binary(), [#dest{}], stanza(), {[address()], [address()]} | [address()]) -> 'ok'.
|
||||
route_packet2(From, ToS, Dests, Packet, Addresses) ->
|
||||
Els = case append_dests(Dests, Addresses) of
|
||||
[] ->
|
||||
@@ -715,7 +715,7 @@ process_discoinfo_result2(From, FromS, LServiceS,
|
||||
false ->
|
||||
case ST of
|
||||
{wait_for_info, _ID} ->
|
||||
Random = randoms:get_string(),
|
||||
Random = p1_rand:get_string(),
|
||||
ID = <<RServer/binary, $/, Random/binary>>,
|
||||
send_query_items(FromS, LServiceS, ID),
|
||||
add_response(RServer, Response, {wait_for_items, ID});
|
||||
@@ -784,7 +784,7 @@ process_discoitems_result(From, LServiceS, ID, #disco_items{items = Items}) ->
|
||||
[] ->
|
||||
add_response(RServer, not_supported, cached);
|
||||
_ ->
|
||||
Random = randoms:get_string(),
|
||||
Random = p1_rand:get_string(),
|
||||
ID2 = <<RServer/binary, $/, Random/binary>>,
|
||||
[send_query_info(Item, LServiceS, ID2) || Item <- List],
|
||||
add_response(RServer, Response,
|
||||
@@ -859,7 +859,7 @@ search_server_on_cache(RServer, _LServerS, LServiceS, Maxmins) ->
|
||||
end.
|
||||
|
||||
query_info(RServer, LServiceS, Response) ->
|
||||
Random = randoms:get_string(),
|
||||
Random = p1_rand:get_string(),
|
||||
ID = <<RServer/binary, $/, Random/binary>>,
|
||||
send_query_info(RServer, LServiceS, ID),
|
||||
add_response(RServer, Response, {wait_for_info, ID}).
|
||||
@@ -1083,9 +1083,8 @@ depends(_Host, _Opts) ->
|
||||
|
||||
mod_opt_type(access) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun(L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(host) -> fun ejabberd_config:v_host/1;
|
||||
mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1;
|
||||
mod_opt_type(name) -> fun iolist_to_binary/1;
|
||||
mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) ->
|
||||
fun(L) ->
|
||||
|
||||
+16
-23
@@ -123,21 +123,22 @@ handle_cast({stop_ping, JID}, State) ->
|
||||
Timers = del_timer(JID, State#state.timers),
|
||||
{noreply, State#state{timers = Timers}};
|
||||
handle_cast({iq_reply, timeout, JID}, State) ->
|
||||
Timers = del_timer(JID, State#state.timers),
|
||||
ejabberd_hooks:run(user_ping_timeout, State#state.host,
|
||||
[JID]),
|
||||
case State#state.timeout_action of
|
||||
kill ->
|
||||
#jid{user = User, server = Server,
|
||||
resource = Resource} =
|
||||
JID,
|
||||
case ejabberd_sm:get_session_pid(User, Server, Resource)
|
||||
of
|
||||
Pid when is_pid(Pid) -> ejabberd_c2s:close(Pid, ping_timeout);
|
||||
_ -> ok
|
||||
end;
|
||||
_ -> ok
|
||||
end,
|
||||
Timers = case State#state.timeout_action of
|
||||
kill ->
|
||||
#jid{user = User, server = Server,
|
||||
resource = Resource} =
|
||||
JID,
|
||||
case ejabberd_sm:get_session_pid(User, Server, Resource)
|
||||
of
|
||||
Pid when is_pid(Pid) -> ejabberd_c2s:close(Pid, ping_timeout);
|
||||
_ -> ok
|
||||
end,
|
||||
del_timer(JID, State#state.timers);
|
||||
_ ->
|
||||
State#state.timers
|
||||
end,
|
||||
{noreply, State#state{timers = Timers}};
|
||||
handle_cast({iq_reply, #iq{}, _JID}, State) ->
|
||||
{noreply, State};
|
||||
@@ -228,7 +229,7 @@ add_timer(JID, Interval, Timers) ->
|
||||
LJID = jid:tolower(JID),
|
||||
NewTimers = case maps:find(LJID, Timers) of
|
||||
{ok, OldTRef} ->
|
||||
cancel_timer(OldTRef),
|
||||
misc:cancel_timer(OldTRef),
|
||||
maps:remove(LJID, Timers);
|
||||
_ -> Timers
|
||||
end,
|
||||
@@ -241,19 +242,11 @@ del_timer(JID, Timers) ->
|
||||
LJID = jid:tolower(JID),
|
||||
case maps:find(LJID, Timers) of
|
||||
{ok, TRef} ->
|
||||
cancel_timer(TRef),
|
||||
misc:cancel_timer(TRef),
|
||||
maps:remove(LJID, Timers);
|
||||
_ -> Timers
|
||||
end.
|
||||
|
||||
-spec cancel_timer(reference()) -> ok.
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive {timeout, TRef, _} -> ok after 0 -> ok end;
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user