mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-24 17:29:28 +01:00
Merge branch 'master' of github.com:processone/ejabberd
This commit is contained in:
commit
309fd56fb4
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
.git
|
||||
.win32
|
||||
.examples
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -29,6 +29,7 @@
|
||||
/doc/version.tex
|
||||
/ebin/
|
||||
/ejabberd.init
|
||||
/ejabberd.service
|
||||
/ejabberdctl.example
|
||||
XmppAddr.hrl
|
||||
/rel/ejabberd/
|
||||
|
@ -3,6 +3,7 @@ language: erlang
|
||||
otp_release:
|
||||
- 17.5
|
||||
- 18.3
|
||||
- 19.1
|
||||
|
||||
services:
|
||||
- riak
|
||||
|
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@ -0,0 +1,25 @@
|
||||
FROM debian:jessie
|
||||
MAINTAINER Rafael Römhild <rafael@roemhild.de>
|
||||
|
||||
ENV XMPP_DOMAIN=localhost \
|
||||
EJABBERD_HOME=/opt/ejabberd \
|
||||
PATH=/opt/ejabberd/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
||||
LC_ALL=C.UTF-8 \
|
||||
LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US.UTF-8
|
||||
|
||||
# bootstrap
|
||||
COPY . /tmp/ejabberd
|
||||
RUN /tmp/ejabberd/docker/bootstrap.sh
|
||||
|
||||
# Continue as user
|
||||
USER ejabberd
|
||||
|
||||
# Set workdir to ejabberd root
|
||||
WORKDIR /opt/ejabberd
|
||||
|
||||
VOLUME ["/opt/ejabberd/conf", "/opt/ejabberd/database", "/opt/ejabberd/ssl", "/opt/ejabberd/backup", "/opt/ejabberd/upload", "/opt/ejabberd/modules"]
|
||||
|
||||
EXPOSE 4560 5222 5269 5280 5443
|
||||
|
||||
ENTRYPOINT ["/opt/ejabberd/docker/start.sh"]
|
32
Makefile.in
32
Makefile.in
@ -108,10 +108,6 @@ edoc:
|
||||
$(ERL) -noinput +B -eval \
|
||||
'case edoc:application(ejabberd, ".", []) of ok -> halt(0); error -> halt(1) end.'
|
||||
|
||||
spec:
|
||||
$(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \
|
||||
'case fxml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.'
|
||||
|
||||
JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1))
|
||||
|
||||
VERSIONED_DEP=$(if $(DEP_$(1)_VERSION),$(DEP_$(1)_VERSION),$(1))
|
||||
@ -177,15 +173,15 @@ install: all copy-files
|
||||
[ -f $(ETCDIR)/ejabberd.yml ] \
|
||||
&& $(INSTALL) -b -m 640 $(G_USER) ejabberd.yml.example $(ETCDIR)/ejabberd.yml-new \
|
||||
|| $(INSTALL) -b -m 640 $(G_USER) ejabberd.yml.example $(ETCDIR)/ejabberd.yml
|
||||
$(SED) -e "s*{{rootdir}}*@prefix@*" \
|
||||
-e "s*{{installuser}}*@INSTALLUSER@*" \
|
||||
-e "s*{{bindir}}*@bindir@*" \
|
||||
-e "s*{{libdir}}*@libdir@*" \
|
||||
-e "s*{{sysconfdir}}*@sysconfdir@*" \
|
||||
-e "s*{{localstatedir}}*@localstatedir@*" \
|
||||
-e "s*{{docdir}}*@docdir@*" \
|
||||
-e "s*{{erl}}*@ERL@*" \
|
||||
-e "s*{{epmd}}*@EPMD@*" ejabberdctl.template \
|
||||
$(SED) -e "s*{{rootdir}}*@prefix@*g" \
|
||||
-e "s*{{installuser}}*@INSTALLUSER@*g" \
|
||||
-e "s*{{bindir}}*@bindir@*g" \
|
||||
-e "s*{{libdir}}*@libdir@*g" \
|
||||
-e "s*{{sysconfdir}}*@sysconfdir@*g" \
|
||||
-e "s*{{localstatedir}}*@localstatedir@*g" \
|
||||
-e "s*{{docdir}}*@docdir@*g" \
|
||||
-e "s*{{erl}}*@ERL@*g" \
|
||||
-e "s*{{epmd}}*@EPMD@*g" ejabberdctl.template \
|
||||
> ejabberdctl.example
|
||||
[ -f $(ETCDIR)/ejabberdctl.cfg ] \
|
||||
&& $(INSTALL) -b -m 640 $(G_USER) ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg-new \
|
||||
@ -202,15 +198,15 @@ install: all copy-files
|
||||
[ -f deps/elixir/bin/mix ] && $(INSTALL) -m 550 $(G_USER) deps/elixir/bin/mix $(BINDIR)/mix || true
|
||||
#
|
||||
# Init script
|
||||
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*" \
|
||||
-e "s*@installuser@*$(INIT_USER)*" ejabberd.init.template \
|
||||
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*g" \
|
||||
-e "s*@installuser@*$(INIT_USER)*g" ejabberd.init.template \
|
||||
> ejabberd.init
|
||||
chmod 755 ejabberd.init
|
||||
#
|
||||
# Service script
|
||||
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*" ejabberd.service.template \
|
||||
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*g" ejabberd.service.template \
|
||||
> ejabberd.service
|
||||
chmod 755 ejabberd.service
|
||||
chmod 644 ejabberd.service
|
||||
#
|
||||
# Spool directory
|
||||
$(INSTALL) -d -m 750 $(O_USER) $(SPOOLDIR)
|
||||
@ -343,5 +339,5 @@ quicktest:
|
||||
$(REBAR) skip_deps=true ct suites=elixir
|
||||
|
||||
.PHONY: src edoc dialyzer Makefile TAGS clean clean-rel distclean rel \
|
||||
install uninstall uninstall-binary uninstall-all translations deps test spec \
|
||||
install uninstall uninstall-binary uninstall-all translations deps test \
|
||||
quicktest erlang_plt deps_plt ejabberd_plt
|
||||
|
@ -1,301 +0,0 @@
|
||||
-- LDAPv3 ASN.1 specification, taken from RFC 2251
|
||||
|
||||
-- Lightweight-Directory-Access-Protocol-V3 DEFINITIONS
|
||||
ELDAPv3 DEFINITIONS
|
||||
IMPLICIT TAGS ::=
|
||||
|
||||
BEGIN
|
||||
|
||||
LDAPMessage ::= SEQUENCE {
|
||||
messageID MessageID,
|
||||
protocolOp CHOICE {
|
||||
bindRequest BindRequest,
|
||||
bindResponse BindResponse,
|
||||
unbindRequest UnbindRequest,
|
||||
searchRequest SearchRequest,
|
||||
searchResEntry SearchResultEntry,
|
||||
searchResDone SearchResultDone,
|
||||
searchResRef SearchResultReference,
|
||||
modifyRequest ModifyRequest,
|
||||
modifyResponse ModifyResponse,
|
||||
addRequest AddRequest,
|
||||
addResponse AddResponse,
|
||||
delRequest DelRequest,
|
||||
delResponse DelResponse,
|
||||
modDNRequest ModifyDNRequest,
|
||||
modDNResponse ModifyDNResponse,
|
||||
compareRequest CompareRequest,
|
||||
compareResponse CompareResponse,
|
||||
abandonRequest AbandonRequest,
|
||||
extendedReq ExtendedRequest,
|
||||
extendedResp ExtendedResponse },
|
||||
controls [0] Controls OPTIONAL }
|
||||
|
||||
MessageID ::= INTEGER (0 .. maxInt)
|
||||
|
||||
maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
|
||||
|
||||
LDAPString ::= OCTET STRING
|
||||
|
||||
LDAPOID ::= OCTET STRING
|
||||
|
||||
LDAPDN ::= LDAPString
|
||||
|
||||
RelativeLDAPDN ::= LDAPString
|
||||
|
||||
AttributeType ::= LDAPString
|
||||
|
||||
AttributeDescription ::= LDAPString
|
||||
|
||||
|
||||
|
||||
|
||||
-- Wahl, et. al. Standards Track [Page 44]
|
||||
--
|
||||
-- RFC 2251 LDAPv3 December 1997
|
||||
|
||||
|
||||
AttributeDescriptionList ::= SEQUENCE OF
|
||||
AttributeDescription
|
||||
|
||||
AttributeValue ::= OCTET STRING
|
||||
|
||||
AttributeValueAssertion ::= SEQUENCE {
|
||||
attributeDesc AttributeDescription,
|
||||
assertionValue AssertionValue }
|
||||
|
||||
AssertionValue ::= OCTET STRING
|
||||
|
||||
Attribute ::= SEQUENCE {
|
||||
type AttributeDescription,
|
||||
vals SET OF AttributeValue }
|
||||
|
||||
MatchingRuleId ::= LDAPString
|
||||
|
||||
LDAPResult ::= SEQUENCE {
|
||||
resultCode ENUMERATED {
|
||||
success (0),
|
||||
operationsError (1),
|
||||
protocolError (2),
|
||||
timeLimitExceeded (3),
|
||||
sizeLimitExceeded (4),
|
||||
compareFalse (5),
|
||||
compareTrue (6),
|
||||
authMethodNotSupported (7),
|
||||
strongAuthRequired (8),
|
||||
-- 9 reserved --
|
||||
referral (10), -- new
|
||||
adminLimitExceeded (11), -- new
|
||||
unavailableCriticalExtension (12), -- new
|
||||
confidentialityRequired (13), -- new
|
||||
saslBindInProgress (14), -- new
|
||||
noSuchAttribute (16),
|
||||
undefinedAttributeType (17),
|
||||
inappropriateMatching (18),
|
||||
constraintViolation (19),
|
||||
attributeOrValueExists (20),
|
||||
invalidAttributeSyntax (21),
|
||||
-- 22-31 unused --
|
||||
noSuchObject (32),
|
||||
aliasProblem (33),
|
||||
invalidDNSyntax (34),
|
||||
-- 35 reserved for undefined isLeaf --
|
||||
aliasDereferencingProblem (36),
|
||||
-- 37-47 unused --
|
||||
inappropriateAuthentication (48),
|
||||
|
||||
-- Wahl, et. al. Standards Track [Page 45]
|
||||
--
|
||||
-- RFC 2251 LDAPv3 December 1997
|
||||
|
||||
|
||||
invalidCredentials (49),
|
||||
insufficientAccessRights (50),
|
||||
busy (51),
|
||||
unavailable (52),
|
||||
unwillingToPerform (53),
|
||||
loopDetect (54),
|
||||
-- 55-63 unused --
|
||||
namingViolation (64),
|
||||
objectClassViolation (65),
|
||||
notAllowedOnNonLeaf (66),
|
||||
notAllowedOnRDN (67),
|
||||
entryAlreadyExists (68),
|
||||
objectClassModsProhibited (69),
|
||||
-- 70 reserved for CLDAP --
|
||||
affectsMultipleDSAs (71), -- new
|
||||
-- 72-79 unused --
|
||||
other (80) },
|
||||
-- 81-90 reserved for APIs --
|
||||
matchedDN LDAPDN,
|
||||
errorMessage LDAPString,
|
||||
referral [3] Referral OPTIONAL }
|
||||
|
||||
Referral ::= SEQUENCE OF LDAPURL
|
||||
|
||||
LDAPURL ::= LDAPString -- limited to characters permitted in URLs
|
||||
|
||||
Controls ::= SEQUENCE OF Control
|
||||
|
||||
Control ::= SEQUENCE {
|
||||
controlType LDAPOID,
|
||||
criticality BOOLEAN DEFAULT FALSE,
|
||||
controlValue OCTET STRING OPTIONAL }
|
||||
|
||||
BindRequest ::= [APPLICATION 0] SEQUENCE {
|
||||
version INTEGER (1 .. 127),
|
||||
name LDAPDN,
|
||||
authentication AuthenticationChoice }
|
||||
|
||||
AuthenticationChoice ::= CHOICE {
|
||||
simple [0] OCTET STRING,
|
||||
-- 1 and 2 reserved
|
||||
sasl [3] SaslCredentials }
|
||||
|
||||
SaslCredentials ::= SEQUENCE {
|
||||
mechanism LDAPString,
|
||||
credentials OCTET STRING OPTIONAL }
|
||||
|
||||
BindResponse ::= [APPLICATION 1] SEQUENCE {
|
||||
|
||||
-- Wahl, et. al. Standards Track [Page 46]
|
||||
--
|
||||
-- RFC 2251 LDAPv3 December 1997
|
||||
|
||||
|
||||
COMPONENTS OF LDAPResult,
|
||||
serverSaslCreds [7] OCTET STRING OPTIONAL }
|
||||
|
||||
UnbindRequest ::= [APPLICATION 2] NULL
|
||||
|
||||
SearchRequest ::= [APPLICATION 3] SEQUENCE {
|
||||
baseObject LDAPDN,
|
||||
scope ENUMERATED {
|
||||
baseObject (0),
|
||||
singleLevel (1),
|
||||
wholeSubtree (2) },
|
||||
derefAliases ENUMERATED {
|
||||
neverDerefAliases (0),
|
||||
derefInSearching (1),
|
||||
derefFindingBaseObj (2),
|
||||
derefAlways (3) },
|
||||
sizeLimit INTEGER (0 .. maxInt),
|
||||
timeLimit INTEGER (0 .. maxInt),
|
||||
typesOnly BOOLEAN,
|
||||
filter Filter,
|
||||
attributes AttributeDescriptionList }
|
||||
|
||||
Filter ::= CHOICE {
|
||||
and [0] SET OF Filter,
|
||||
or [1] SET OF Filter,
|
||||
not [2] Filter,
|
||||
equalityMatch [3] AttributeValueAssertion,
|
||||
substrings [4] SubstringFilter,
|
||||
greaterOrEqual [5] AttributeValueAssertion,
|
||||
lessOrEqual [6] AttributeValueAssertion,
|
||||
present [7] AttributeDescription,
|
||||
approxMatch [8] AttributeValueAssertion,
|
||||
extensibleMatch [9] MatchingRuleAssertion }
|
||||
|
||||
SubstringFilter ::= SEQUENCE {
|
||||
type AttributeDescription,
|
||||
-- at least one must be present
|
||||
substrings SEQUENCE OF CHOICE {
|
||||
initial [0] LDAPString,
|
||||
any [1] LDAPString,
|
||||
final [2] LDAPString } }
|
||||
|
||||
MatchingRuleAssertion ::= SEQUENCE {
|
||||
matchingRule [1] MatchingRuleId OPTIONAL,
|
||||
type [2] AttributeDescription OPTIONAL,
|
||||
matchValue [3] AssertionValue,
|
||||
dnAttributes [4] BOOLEAN DEFAULT FALSE }
|
||||
|
||||
-- Wahl, et. al. Standards Track [Page 47]
|
||||
--
|
||||
-- RFC 2251 LDAPv3 December 1997
|
||||
|
||||
SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
|
||||
objectName LDAPDN,
|
||||
attributes PartialAttributeList }
|
||||
|
||||
PartialAttributeList ::= SEQUENCE OF SEQUENCE {
|
||||
type AttributeDescription,
|
||||
vals SET OF AttributeValue }
|
||||
|
||||
SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LDAPURL
|
||||
|
||||
SearchResultDone ::= [APPLICATION 5] LDAPResult
|
||||
|
||||
ModifyRequest ::= [APPLICATION 6] SEQUENCE {
|
||||
object LDAPDN,
|
||||
modification SEQUENCE OF SEQUENCE {
|
||||
operation ENUMERATED {
|
||||
add (0),
|
||||
delete (1),
|
||||
replace (2) },
|
||||
modification AttributeTypeAndValues } }
|
||||
|
||||
AttributeTypeAndValues ::= SEQUENCE {
|
||||
type AttributeDescription,
|
||||
vals SET OF AttributeValue }
|
||||
|
||||
ModifyResponse ::= [APPLICATION 7] LDAPResult
|
||||
|
||||
AddRequest ::= [APPLICATION 8] SEQUENCE {
|
||||
entry LDAPDN,
|
||||
attributes AttributeList }
|
||||
|
||||
AttributeList ::= SEQUENCE OF SEQUENCE {
|
||||
type AttributeDescription,
|
||||
vals SET OF AttributeValue }
|
||||
|
||||
AddResponse ::= [APPLICATION 9] LDAPResult
|
||||
|
||||
DelRequest ::= [APPLICATION 10] LDAPDN
|
||||
|
||||
DelResponse ::= [APPLICATION 11] LDAPResult
|
||||
|
||||
ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
|
||||
entry LDAPDN,
|
||||
newrdn RelativeLDAPDN,
|
||||
deleteoldrdn BOOLEAN,
|
||||
newSuperior [0] LDAPDN OPTIONAL }
|
||||
|
||||
ModifyDNResponse ::= [APPLICATION 13] LDAPResult
|
||||
|
||||
-- Wahl, et. al. Standards Track [Page 48]
|
||||
--
|
||||
-- RFC 2251 LDAPv3 December 1997
|
||||
|
||||
|
||||
CompareRequest ::= [APPLICATION 14] SEQUENCE {
|
||||
entry LDAPDN,
|
||||
ava AttributeValueAssertion }
|
||||
|
||||
CompareResponse ::= [APPLICATION 15] LDAPResult
|
||||
|
||||
AbandonRequest ::= [APPLICATION 16] MessageID
|
||||
|
||||
ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
|
||||
requestName [0] LDAPOID,
|
||||
requestValue [1] OCTET STRING OPTIONAL }
|
||||
|
||||
ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
|
||||
COMPONENTS OF LDAPResult,
|
||||
responseName [10] LDAPOID OPTIONAL,
|
||||
response [11] OCTET STRING OPTIONAL }
|
||||
|
||||
passwdModifyOID LDAPOID ::= "1.3.6.1.4.1.4203.1.11.1"
|
||||
|
||||
PasswdModifyRequestValue ::= SEQUENCE {
|
||||
userIdentity [0] OCTET STRING OPTIONAL,
|
||||
oldPasswd [1] OCTET STRING OPTIONAL,
|
||||
newPasswd [2] OCTET STRING OPTIONAL }
|
||||
|
||||
PasswdModifyResponseValue ::= SEQUENCE {
|
||||
genPasswd [0] OCTET STRING OPTIONAL }
|
||||
|
||||
END
|
||||
|
||||
|
360
docker/README.md
Normal file
360
docker/README.md
Normal file
@ -0,0 +1,360 @@
|
||||
ejabberd container
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Usage](#usage)
|
||||
- [Persistence](#persistence)
|
||||
- [SSL Certificates](#ssl-certificates)
|
||||
- [Base Image](#base-image)
|
||||
- [Ejabberd Configuration](#ejabberd-configuration)
|
||||
- [Served Hostnames](#served-hostnames)
|
||||
- [Authentication](#authentication)
|
||||
- [Admins](#admins)
|
||||
- [Users](#users)
|
||||
- [SSL](#ssl)
|
||||
- [Modules](#modules)
|
||||
- [Logging](#logging)
|
||||
- [Mount Configurations](#mount-configurations)
|
||||
- [Erlang Configuration](#erlang-configuration)
|
||||
- [Maintenance](#maintenance)
|
||||
- [Register Users](#register-users)
|
||||
- [Creating Backups](#creating-backups)
|
||||
- [Restoring Backups](#restoring-backups)
|
||||
- [Debug](#debug)
|
||||
- [Erlang Shell](#erlang-shell)
|
||||
- [System Shell](#system-shell)
|
||||
- [System Commands](#system-commands)
|
||||
- [Exposed Ports](#exposed-ports)
|
||||
|
||||
# Introduction
|
||||
|
||||
Dockerfile to build an [ejabberd](https://www.ejabberd.im/) container image.
|
||||
|
||||
Docker Tag Names are based on ejabberd versions in git [tags][]. The image tag `:latest` is based on the master branch.
|
||||
|
||||
[tags]: https://github.com/rroemhild/ejabberd/tags
|
||||
|
||||
# Quick Start
|
||||
|
||||
You can start of with the following container:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name "ejabberd" \
|
||||
-p 5222:5222 \
|
||||
-p 5269:5269 \
|
||||
-p 5280:5280 \
|
||||
-h 'xmpp.example.de' \
|
||||
-e "XMPP_DOMAIN=example.de" \
|
||||
-e "ERLANG_NODE=nodename" \
|
||||
-e "EJABBERD_ADMINS=admin@example.de admin2@example.de" \
|
||||
-e "EJABBERD_USERS=admin@example.de:password1234 admin2@example.de" \
|
||||
-e "TZ=Europe/Berlin" \
|
||||
rroemhild/ejabberd
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
## Persistence
|
||||
|
||||
For storage of the application data, you can mount volumes at
|
||||
|
||||
* `/opt/ejabberd/ssl`
|
||||
* `/opt/ejabberd/backup`
|
||||
* `/opt/ejabberd/upload`
|
||||
* `/opt/ejabberd/database`
|
||||
|
||||
or use a data container
|
||||
|
||||
```bash
|
||||
docker create --name ejabberd-data rroemhild/ejabberd-data
|
||||
docker run -d --name ejabberd --volumes-from processone-data rroemhild/ejabberd
|
||||
```
|
||||
|
||||
## SSL Certificates
|
||||
|
||||
TLS is enabled by default and the run script will auto-generate two snake-oil certificates during boot if you don't provide your SSL certificates.
|
||||
|
||||
To use your own certificates, there are two options.
|
||||
|
||||
1. Mount the volume `/opt/ejabberd/ssl` to a local directory with the `.pem` files:
|
||||
|
||||
* /tmp/ssl/host.pem (SERVER_HOSTNAME)
|
||||
* /tmp/ssl/xmpp_domain.pem (XMPP_DOMAIN)
|
||||
|
||||
Make sure that the certificate and private key are in one `.pem` file. If one file is missing it will be auto-generated. I.e. you can provide your certificate for your **XMMP_DOMAIN** and use a snake-oil certificate for the `SERVER_HOSTNAME`.
|
||||
|
||||
2. Specify the certificates via environment variables: **EJABBERD_SSLCERT_HOST** and **EJABBERD_SSLCERT_EXAMPLE_COM**. For the
|
||||
domain certificates, make sure you match the domain names given in **XMPP_DOMAIN** and replace dots and dashes with underscore.
|
||||
|
||||
## Base Image
|
||||
|
||||
Build your own ejabberd container image and add your config templates, certificates or [extend](#cluster-example) it for your needs.
|
||||
|
||||
```
|
||||
FROM rroemhild/ejabberd
|
||||
ADD ./ejabberd.yml.tpl /opt/ejabberd/conf/ejabberd.yml.tpl
|
||||
ADD ./ejabberdctl.cfg.tpl /opt/ejabberd/conf/ejabberdctl.cfg.tpl
|
||||
ADD ./example.com.pem /opt/ejabberd/ssl/example.com.pem
|
||||
```
|
||||
|
||||
If you need root privileges switch to `USER root` and go back to `USER ejabberd` when you're done.
|
||||
|
||||
# Ejabberd Configuration
|
||||
|
||||
You can additionally provide extra runtime configuration in a downstream image by replacing the config template `ejabberd.yml.tpl` with one based on this image's template and include extra interpolation of environment variables. The template is parsed by Jinja2 with the runtime environment (equivalent to Python's `os.environ` available as `env`).
|
||||
|
||||
## Served Hostnames
|
||||
|
||||
By default the container will serve the XMPP domain `localhost`. In order to serve a different domain at runtime, provide the **XMPP_DOMAIN** variable with a domain name. You can add more domains separated with whitespace.
|
||||
|
||||
```
|
||||
XMPP_DOMAIN=example.ninja xyz.io test.com
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Authentication methods can be set with the **EJABBERD_AUTH_METHOD** environment variable. The default authentication mode is `internal`.
|
||||
|
||||
Supported authentication methods:
|
||||
|
||||
* anonymous
|
||||
* internal
|
||||
* external
|
||||
* ldap
|
||||
|
||||
Internal and anonymous authentication example:
|
||||
|
||||
```
|
||||
EJABBERD_AUTH_METHOD=internal anonymous
|
||||
```
|
||||
|
||||
[External authentication](http://docs.ejabberd.im/admin/guide/configuration/#external-script) example:
|
||||
```
|
||||
EJABBERD_AUTH_METHOD=external
|
||||
EJABBERD_EXTAUTH_PROGRAM="/opt/ejabberd/scripts/authenticate-user.sh"
|
||||
EJABBERD_EXTAUTH_INSTANCES=3
|
||||
EJABBERD_EXTAUTH_CACHE=600
|
||||
```
|
||||
**EJABBERD_EXTAUTH_INSTANCES** must be an integer with a minimum value of 1. **EJABBERD_EXTAUTH_CACHE** can be set to "false" or an integer value representing cache time in seconds. Note that caching should not be enabled if internal auth is also enabled.
|
||||
|
||||
### MySQL Authentication
|
||||
|
||||
Set `EJABBERD_AUTH_METHOD=external` and `EJABBERD_EXTAUTH_PROGRAM=/opt/ejabberd/scripts/lib/auth_mysql.py` to enable MySQL authentication. Use the following environment variables to configure the database connection and the layout of the database. Password changing, registration, and unregistration are optional features and are enabled only if the respective queries are provided.
|
||||
|
||||
- **AUTH_MYSQL_HOST**: The MySQL host
|
||||
- **AUTH_MYSQL_USER**: Username to connect to the MySQL host
|
||||
- **AUTH_MYSQL_PASSWORD**: Password to connect to the MySQL host
|
||||
- **AUTH_MYSQL_DATABASE**: Database name where to find the user information
|
||||
- **AUTH_MYSQL_HASHALG**: Format of the password in the database. Default is cleartext. Options are `crypt`, `md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`. `crypt` is recommended, as it is salted. When setting the password, `crypt` uses SHA-512 (prefix `$6$`).
|
||||
- **AUTH_MYSQL_QUERY_GETPASS**: Get the password for a user. Use the placeholders `%(user)s`, `%(host)s`. Example: `SELECT password FROM users WHERE username = CONCAT(%(user)s, '@', %(host)s)`
|
||||
- **AUTH_MYSQL_QUERY_SETPASS**: Update the password for a user. Leave empty to disable. Placeholder `%(password)s` contains the hashed password. Example: `UPDATE users SET password = %(password)s WHERE username = CONCAT(%(user)s, '@', %(host)s)`
|
||||
- **AUTH_MYSQL_QUERY_REGISTER**: Register a new user. Leave empty to disable. Example: `INSERT INTO users ( username, password ) VALUES ( CONCAT(%(user)s, '@', %(host)s), %(password)s )`
|
||||
- **AUTH_MYSQL_QUERY_UNREGISTER**: Removes a user. Leave empty to disable. Example: `DELETE FROM users WHERE username = CONCAT(%(user)s, '@', %(host)s)`
|
||||
|
||||
Note that the MySQL authentication script writes a debug log into the file `/var/log/ejabberd/extauth.log`. To get its content, execute the following command:
|
||||
|
||||
```bash
|
||||
docker exec -ti ejabberd tail -n50 -f /var/log/ejabberd/extauth.log
|
||||
```
|
||||
|
||||
To find out more about the mysql authentication script, check out the [ejabberd-auth-mysql](https://github.com/rankenstein/ejabberd-auth-mysql) repository.
|
||||
|
||||
### LDAP Auth
|
||||
|
||||
Full documentation http://docs.ejabberd.im/admin/guide/configuration/#ldap.
|
||||
|
||||
Connection
|
||||
|
||||
- **EJABBERD_LDAP_SERVERS**: List of IP addresses or DNS names of your LDAP servers. This option is required.
|
||||
- **EJABBERD_LDAP_ENCRYPT**: The value `tls` enables encryption by using LDAP over SSL. The default value is: `none`.
|
||||
- **EJABBERD_LDAP_TLS_VERIFY**: `false|soft|hard` This option specifies whether to verify LDAP server certificate or not when TLS is enabled. The default is `false` which means no checks are performed.
|
||||
- **EJABBERD_LDAP_TLS_CACERTFILE**: Path to file containing PEM encoded CA certificates.
|
||||
- **EJABBERD_LDAP_TLS_DEPTH**: Specifies the maximum verification depth when TLS verification is enabled. The default value is 1.
|
||||
- **EJABBERD_LDAP_PORT**: The default port is `389` if encryption is disabled; and `636` if encryption is enabled.
|
||||
- **EJABBERD_LDAP_ROOTDN**: Bind DN. The default value is "" which means ‘anonymous connection’.
|
||||
- **EJABBERD_LDAP_PASSWORD**: Bind password. The default value is "".
|
||||
- **EJABBERD_LDAP_DEREF_ALIASES**: `never|always|finding|searching`
|
||||
Whether or not to dereference aliases. The default is `never`.
|
||||
|
||||
Authentication
|
||||
|
||||
- **EJABBERD_LDAP_BASE**: LDAP base directory which stores users accounts. This option is required.
|
||||
- **EJABBERD_LDAP_UIDS**: `ldap_uidattr:ldap_uidattr_format` The default attributes are `uid:%u`.
|
||||
- **EJABBERD_LDAP_FILTER**: RFC 4515 LDAP filter. The default Filter value is undefined.
|
||||
- **EJABBERD_LDAP_DN_FILTER**: `{ Filter: FilterAttrs }` This filter is applied on the results returned by the main filter. By default ldap_dn_filter is undefined.
|
||||
|
||||
## Admins
|
||||
|
||||
Set one or more admin user (seperated by whitespace) with the **EJABBERD_ADMINS** environment variable. You can register admin users with the **EJABBERD_USERS** environment variable during container startup, use you favorite XMPP client or the `ejabberdctl` command line utility.
|
||||
|
||||
```
|
||||
EJABBERD_ADMINS=admin@example.ninja
|
||||
```
|
||||
|
||||
## Users
|
||||
|
||||
Automatically register users during container startup. Uses random password if you don't provide a password for the user. Format is `JID:PASSWORD`. Register more users separated with whitespace.
|
||||
|
||||
Register the admin user from **EJABBERD_ADMINS** with a give password:
|
||||
|
||||
```
|
||||
EJABBERD_USERS=admin@example.ninja:password1234
|
||||
```
|
||||
|
||||
Or without a random password printed to stdout (check container logs):
|
||||
|
||||
```
|
||||
EJABBERD_USERS=admin@example.ninja
|
||||
```
|
||||
|
||||
Register more than one user:
|
||||
|
||||
```
|
||||
EJABBERD_USERS=admin@example.ninja:password1234 user1@test.com user1@xyz.io
|
||||
```
|
||||
|
||||
## SSL
|
||||
|
||||
- **EJABBERD_SSLCERT_HOST**: SSL Certificate for the hostname.
|
||||
- **EJABBERD_SSLCERT_EXAMPLE_COM**: SSL Certificates for XMPP domains.
|
||||
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Default: `true`.
|
||||
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Default: `true`.
|
||||
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Default: `true`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Default: `false`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Default: `true`.
|
||||
- **EJABBERD_CIPHERS**: Cipher suite. Default: `HIGH:!aNULL:!3DES`.
|
||||
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Default: `false`.
|
||||
|
||||
## Modules
|
||||
|
||||
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Default: `false`.
|
||||
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Default: `false`.
|
||||
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Default: `true`.
|
||||
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Default: `true`.
|
||||
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Default: `true`.
|
||||
|
||||
## Logging
|
||||
|
||||
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Default: `4` (Info).
|
||||
|
||||
```
|
||||
loglevel: Verbosity of log files generated by ejabberd.
|
||||
0: No ejabberd log at all (not recommended)
|
||||
1: Critical
|
||||
2: Error
|
||||
3: Warning
|
||||
4: Info
|
||||
5: Debug
|
||||
```
|
||||
|
||||
## Mount Configurations
|
||||
|
||||
If you prefer to use your own configuration files and avoid passing docker environment variables (```-e```), you can do so by mounting a host directory.
|
||||
Pass in an additional ```-v``` to the ```docker run``` command, like so:
|
||||
```
|
||||
docker run -d \
|
||||
--name "ejabberd" \
|
||||
-p 5222:5222 \
|
||||
-p 5269:5269 \
|
||||
-p 5280:5280 \
|
||||
-h 'xmpp.example.de' \
|
||||
-v /<host_path>/conf:/opt/ejabberd/conf \
|
||||
rroemhild/ejabberd
|
||||
```
|
||||
|
||||
Your ```/<host_path>/conf``` folder should look like so:
|
||||
|
||||
```
|
||||
/<host_path>/conf/
|
||||
├── ejabberdctl.cfg
|
||||
├── ejabberd.yml
|
||||
└── inetrc
|
||||
```
|
||||
|
||||
Example configuration files can be downloaded from the ejabberd [github](https://github.com/rroemhild/ejabberd) page.
|
||||
|
||||
When these files exist in ```/opt/ejabberd/conf```, the run script will ignore the configuration templates.
|
||||
|
||||
## Erlang Configuration
|
||||
|
||||
With the following environment variables you can configure options that are passed by ejabberdctl to the erlang runtime system when starting ejabberd.
|
||||
|
||||
- **POLL**: Set to `false` to disable Kernel polling. Default: `true`.
|
||||
- **SMP**: SMP support `enable`, `auto`, `disable`. Default: `auto`.
|
||||
- **ERL_MAX_PORTS**: Maximum number of simultaneously open Erlang ports. Default: `32000`.
|
||||
- **FIREWALL_WINDOW**: Range of allowed ports to pass through a firewall. Default: `not defined`.
|
||||
- **INET_DIST_INTERFACE**: IP address where this Erlang node listens other nodes. Default: `0.0.0.0`.
|
||||
- **ERL_EPMD_ADDRESS**: IP addresses where epmd listens for connections. Default: `0.0.0.0`.
|
||||
- **ERL_PROCESSES**: Maximum number of Erlang processes. Default: `250000`.
|
||||
- **ERL_MAX_ETS_TABLES**: Maximum number of Erlang processes. Default: `1400`.
|
||||
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd. Default: `-noshell`
|
||||
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `nodename` lets erlang add the hostname. Default: `ejabberd@localhost`.
|
||||
- **EJABBERD_CONFIG_PATH**: ejabberd configuration file. Default: `/opt/ejabberd/conf/ejabberd.yml`.
|
||||
- **CONTRIB_MODULES_PATH**: contributed ejabberd modules path. Default: `/opt/ejabberd/modules`.
|
||||
- **CONTRIB_MODULES_CONF_DIR**: configuration directory for contributed modules. Default: `/opt/ejabberd/modules/conf`.
|
||||
- **ERLANG_COOKIE**: Set erlang cookie. Default is to auto-generated cookie.
|
||||
|
||||
# Maintenance
|
||||
|
||||
The `ejabberdctl` command is in the search path and can be run by:
|
||||
|
||||
```bash
|
||||
docker exec CONTAINER ejabberdctl help
|
||||
```
|
||||
|
||||
## Register Users
|
||||
|
||||
```bash
|
||||
docker exec CONTAINER ejabberdctl register user XMPP_DOMAIN PASSWORD
|
||||
```
|
||||
|
||||
## Creating Backups
|
||||
|
||||
Create a backupfile with ejabberdctl and copy the file from the container to localhost
|
||||
|
||||
```bash
|
||||
docker exec CONTAINER ejabberdctl backup /opt/ejabberd/backup/ejabberd.backup
|
||||
docker cp CONTAINER:/opt/ejabberd/backup/ejabberd.backup /tmp/ejabberd.backup
|
||||
```
|
||||
|
||||
## Restoring Backups
|
||||
|
||||
Copy the backupfile from localhost to the running container and restore with ejabberdctl
|
||||
|
||||
```bash
|
||||
docker cp /tmp/ejabberd.backup CONTAINER:/opt/ejabberd/backup/ejabberd.backup
|
||||
docker exec CONTAINER ejabberdctl restore /opt/ejabberd/backup/ejabberd.backup
|
||||
```
|
||||
|
||||
# Debug
|
||||
|
||||
## Erlang Shell
|
||||
|
||||
Set `-i` and `-t` option and append `live` to get an interactive erlang shell:
|
||||
|
||||
```bash
|
||||
docker run -i -t -P rroemhild/ejabberd live
|
||||
```
|
||||
|
||||
You can terminate the erlang shell with `q().`.
|
||||
|
||||
## System Shell
|
||||
|
||||
```bash
|
||||
docker run -i -t rroemhild/ejabberd shell
|
||||
```
|
||||
|
||||
## System Commands
|
||||
|
||||
```bash
|
||||
docker run -i -t rroemhild/ejabberd env
|
||||
```
|
||||
|
||||
# Exposed Ports
|
||||
|
||||
* 4560 (XMLRPC)
|
||||
* 5222 (Client 2 Server)
|
||||
* 5269 (Server 2 Server)
|
||||
* 5280 (HTTP admin/websocket/http-bind)
|
||||
* 5443 (HTTP Upload)
|
75
docker/bootstrap.sh
Executable file
75
docker/bootstrap.sh
Executable file
@ -0,0 +1,75 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
export DEBIAN_FRONTEND="noninteractive"
|
||||
|
||||
readonly buildDeps='
|
||||
git-core
|
||||
build-essential
|
||||
automake
|
||||
libssl-dev
|
||||
zlib1g-dev
|
||||
libexpat-dev
|
||||
libyaml-dev
|
||||
libsqlite3-dev
|
||||
erlang-src erlang-dev'
|
||||
|
||||
readonly requiredAptPackages='
|
||||
locales
|
||||
ldnsutils
|
||||
python2.7
|
||||
python-jinja2
|
||||
ca-certificates
|
||||
libyaml-0-2
|
||||
erlang-base erlang-snmp erlang-ssl erlang-ssh erlang-webtool
|
||||
erlang-tools erlang-xmerl erlang-corba erlang-diameter erlang-eldap
|
||||
erlang-eunit erlang-ic erlang-odbc erlang-os-mon
|
||||
erlang-parsetools erlang-percept erlang-typer
|
||||
python-mysqldb
|
||||
imagemagick'
|
||||
|
||||
apt-key adv \
|
||||
--keyserver keys.gnupg.net \
|
||||
--recv-keys 434975BD900CCBE4F7EE1B1ED208507CA14F4FCA
|
||||
|
||||
apt-get update
|
||||
apt-get install -y $buildDeps $requiredAptPackages --no-install-recommends
|
||||
dpkg-reconfigure locales && locale-gen C.UTF-8
|
||||
/usr/sbin/update-locale LANG=C.UTF-8
|
||||
echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen
|
||||
locale-gen
|
||||
|
||||
# add ejabberd user
|
||||
useradd --home $EJABBERD_HOME -M --system ejabberd
|
||||
mkdir $EJABBERD_HOME
|
||||
|
||||
cd /tmp/ejabberd
|
||||
chmod +x ./autogen.sh
|
||||
./autogen.sh
|
||||
./configure --enable-user=ejabberd \
|
||||
--enable-all \
|
||||
--disable-tools \
|
||||
--disable-pam
|
||||
|
||||
make debug=$EJABBERD_DEBUG_MODE
|
||||
make install
|
||||
|
||||
cd $EJABBERD_HOME
|
||||
mkdir -p logs ssl backup upload module_source modules/conf
|
||||
mv /tmp/ejabberd/docker $EJABBERD_HOME
|
||||
|
||||
# Move config to homedir
|
||||
mv /etc/ejabberd conf
|
||||
ln -s $EJABBERD_HOME/conf /etc/ejabberd
|
||||
|
||||
# rename original configs
|
||||
mv conf/ejabberd.yml conf/ejabberd.yml.orig
|
||||
mv conf/ejabberdctl.cfg conf/ejabberdctl.cfg.orig
|
||||
|
||||
# clean up
|
||||
rm -rf /tmp/ejabberd
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
apt-get purge -y --auto-remove $buildDeps
|
||||
|
||||
# change owner for ejabberd home
|
||||
chown -R ejabberd $EJABBERD_HOME
|
434
docker/conf/ejabberd.yml.tpl
Normal file
434
docker/conf/ejabberd.yml.tpl
Normal file
@ -0,0 +1,434 @@
|
||||
###
|
||||
### ejabberd configuration file
|
||||
###
|
||||
###
|
||||
|
||||
### The parameters used in this configuration file are explained in more detail
|
||||
### in the ejabberd Installation and Operation Guide.
|
||||
### Please consult the Guide in case of doubts, it is included with
|
||||
### your copy of ejabberd, and is also available online at
|
||||
### http://www.process-one.net/en/ejabberd/docs/
|
||||
|
||||
### =======
|
||||
### LOGGING
|
||||
|
||||
loglevel: {{ env['EJABBERD_LOGLEVEL'] or 4 }}
|
||||
log_rotate_size: 10485760
|
||||
log_rotate_count: 0
|
||||
log_rate_limit: 100
|
||||
|
||||
## watchdog_admins:
|
||||
## - "bob@example.com"
|
||||
|
||||
### ================
|
||||
### SERVED HOSTNAMES
|
||||
|
||||
hosts:
|
||||
{%- for xmpp_domain in env['XMPP_DOMAIN'].split() %}
|
||||
- "{{ xmpp_domain }}"
|
||||
{%- endfor %}
|
||||
|
||||
##
|
||||
## route_subdomains: Delegate subdomains to other XMPP servers.
|
||||
## For example, if this ejabberd serves example.org and you want
|
||||
## to allow communication with an XMPP server called im.example.org.
|
||||
##
|
||||
## route_subdomains: s2s
|
||||
|
||||
### ===============
|
||||
### LISTENING PORTS
|
||||
|
||||
listen:
|
||||
-
|
||||
port: 5222
|
||||
module: ejabberd_c2s
|
||||
{%- if env['EJABBERD_STARTTLS'] == "true" %}
|
||||
starttls_required: true
|
||||
{%- endif %}
|
||||
protocol_options:
|
||||
- "no_sslv3"
|
||||
{%- if env.get('EJABBERD_PROTOCOL_OPTIONS_TLSV1', "false") == "false" %}
|
||||
- "no_tlsv1"
|
||||
{%- endif %}
|
||||
{%- if env.get('EJABBERD_PROTOCOL_OPTIONS_TLSV1_1', "true") == "false" %}
|
||||
- "no_tlsv1_1"
|
||||
{%- endif %}
|
||||
max_stanza_size: 65536
|
||||
shaper: c2s_shaper
|
||||
access: c2s
|
||||
ciphers: "{{ env.get('EJABBERD_CIPHERS', 'HIGH:!aNULL:!3DES') }}"
|
||||
{%- if env.get('EJABBERD_DHPARAM', false) == "true" %}
|
||||
dhfile: "/opt/ejabberd/ssl/dh.pem"
|
||||
{%- endif %}
|
||||
-
|
||||
port: 5269
|
||||
module: ejabberd_s2s_in
|
||||
-
|
||||
port: 4560
|
||||
module: ejabberd_xmlrpc
|
||||
access_commands:
|
||||
configure:
|
||||
all: []
|
||||
|
||||
-
|
||||
port: 5280
|
||||
module: ejabberd_http
|
||||
request_handlers:
|
||||
"/websocket": ejabberd_http_ws
|
||||
## "/pub/archive": mod_http_fileserver
|
||||
web_admin: true
|
||||
http_bind: true
|
||||
## register: true
|
||||
captcha: true
|
||||
{%- if env['EJABBERD_HTTPS'] == "true" %}
|
||||
tls: true
|
||||
certfile: "/opt/ejabberd/ssl/host.pem"
|
||||
{% endif %}
|
||||
-
|
||||
port: 5443
|
||||
module: ejabberd_http
|
||||
request_handlers:
|
||||
"": mod_http_upload
|
||||
{%- if env['EJABBERD_HTTPS'] == "true" %}
|
||||
tls: true
|
||||
certfile: "/opt/ejabberd/ssl/host.pem"
|
||||
{% endif %}
|
||||
|
||||
|
||||
### SERVER TO SERVER
|
||||
### ================
|
||||
|
||||
{%- if env['EJABBERD_S2S_SSL'] == "true" %}
|
||||
s2s_use_starttls: required
|
||||
s2s_certfile: "/opt/ejabberd/ssl/host.pem"
|
||||
s2s_protocol_options:
|
||||
- "no_sslv3"
|
||||
{%- if env.get('EJABBERD_PROTOCOL_OPTIONS_TLSV1', "false") == "false" %}
|
||||
- "no_tlsv1"
|
||||
{%- endif %}
|
||||
{%- if env.get('EJABBERD_PROTOCOL_OPTIONS_TLSV1_1', "true") == "false" %}
|
||||
- "no_tlsv1_1"
|
||||
{%- endif %}
|
||||
s2s_ciphers: "{{ env.get('EJABBERD_CIPHERS', 'HIGH:!aNULL:!3DES') }}"
|
||||
{%- if env.get('EJABBERD_DHPARAM', false) == "true" %}
|
||||
s2s_dhfile: "/opt/ejabberd/ssl/dh.pem"
|
||||
{%- endif %}
|
||||
{% endif %}
|
||||
|
||||
### ==============
|
||||
### AUTHENTICATION
|
||||
|
||||
auth_method:
|
||||
{%- for auth_method in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
|
||||
- {{ auth_method }}
|
||||
{%- endfor %}
|
||||
|
||||
{%- if 'anonymous' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
|
||||
anonymous_protocol: login_anon
|
||||
allow_multiple_connections: true
|
||||
{%- endif %}
|
||||
|
||||
|
||||
## LDAP authentication
|
||||
|
||||
{%- if 'ldap' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
|
||||
|
||||
ldap_servers:
|
||||
{%- for ldap_server in env.get('EJABBERD_LDAP_SERVERS', 'internal').split() %}
|
||||
- "{{ ldap_server }}"
|
||||
{%- endfor %}
|
||||
|
||||
ldap_encrypt: {{ env.get('EJABBERD_LDAP_ENCRYPT', 'none') }}
|
||||
ldap_tls_verify: {{ env.get('EJABBERD_LDAP_TLS_VERIFY', 'false') }}
|
||||
|
||||
{%- if env['EJABBERD_LDAP_TLS_CACERTFILE'] %}
|
||||
ldap_tls_cacertfile: "{{ env['EJABBERD_LDAP_TLS_CACERTFILE'] }}"
|
||||
{%- endif %}
|
||||
|
||||
ldap_tls_depth: {{ env.get('EJABBERD_LDAP_TLS_DEPTH', 1) }}
|
||||
|
||||
{%- if env['EJABBERD_LDAP_PORT'] %}
|
||||
ldap_port: {{ env['EJABBERD_LDAP_PORT'] }}
|
||||
{%- endif %}
|
||||
|
||||
{%- if env['EJABBERD_LDAP_ROOTDN'] %}
|
||||
ldap_rootdn: "{{ env['EJABBERD_LDAP_ROOTDN'] }}"
|
||||
{%- endif %}
|
||||
|
||||
{%- if env['EJABBERD_LDAP_PASSWORD'] %}
|
||||
ldap_password: "{{ env['EJABBERD_LDAP_PASSWORD'] }}"
|
||||
{%- endif %}
|
||||
|
||||
ldap_deref_aliases: {{ env.get('EJABBERD_LDAP_DEREF_ALIASES', 'never') }}
|
||||
ldap_base: "{{ env['EJABBERD_LDAP_BASE'] }}"
|
||||
|
||||
{%- if env['EJABBERD_LDAP_UIDS'] %}
|
||||
ldap_uids:
|
||||
{%- for ldap_uid in env['EJABBERD_LDAP_UIDS'].split() %}
|
||||
"{{ ldap_uid.split(':')[0] }}": "{{ ldap_uid.split(':')[1] }}"
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
|
||||
{%- if env['EJABBERD_LDAP_FILTER'] %}
|
||||
ldap_filter: "{{ env['EJABBERD_LDAP_FILTER'] }}"
|
||||
{%- endif %}
|
||||
|
||||
{%- if env['EJABBERD_LDAP_DN_FILTER'] %}
|
||||
ldap_dn_filter:
|
||||
{%- for dn_filter in env['EJABBERD_LDAP_DN_FILTER'].split() %}
|
||||
"{{ dn_filter.split(':')[0] }}": ["{{ dn_filter.split(':')[1] }}"]
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
|
||||
{%- endif %}
|
||||
|
||||
{%- if 'external' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
|
||||
{%- if env['EJABBERD_EXTAUTH_PROGRAM'] %}
|
||||
extauth_program: "{{ env['EJABBERD_EXTAUTH_PROGRAM'] }}"
|
||||
{%- endif %}
|
||||
{%- if env['EJABBERD_EXTAUTH_INSTANCES'] %}
|
||||
extauth_instances: {{ env['EJABBERD_EXTAUTH_INSTANCES'] }}
|
||||
{%- endif %}
|
||||
{%- if 'internal' in env.get('EJABBERD_AUTH_METHOD').split() %}
|
||||
extauth_cache: false
|
||||
{%- elif env['EJABBERD_EXTAUTH_CACHE'] %}
|
||||
extauth_cache: {{ env['EJABBERD_EXTAUTH_CACHE'] }}
|
||||
{%- endif %}
|
||||
{% endif %}
|
||||
|
||||
### ===============
|
||||
### TRAFFIC SHAPERS
|
||||
|
||||
shaper:
|
||||
normal: 1000
|
||||
fast: 50000
|
||||
max_fsm_queue: 1000
|
||||
|
||||
### ====================
|
||||
### ACCESS CONTROL LISTS
|
||||
|
||||
acl:
|
||||
admin:
|
||||
user:
|
||||
{%- if env['EJABBERD_ADMINS'] %}
|
||||
{%- for admin in env['EJABBERD_ADMINS'].split() %}
|
||||
- "{{ admin.split('@')[0] }}": "{{ admin.split('@')[1] }}"
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
- "admin": "{{ env['XMPP_DOMAIN'].split()[0] }}"
|
||||
{%- endif %}
|
||||
local:
|
||||
user_regexp: ""
|
||||
|
||||
### ============
|
||||
### ACCESS RULES
|
||||
|
||||
access:
|
||||
## Maximum number of simultaneous sessions allowed for a single user:
|
||||
max_user_sessions:
|
||||
all: 10
|
||||
## Maximum number of offline messages that users can have:
|
||||
max_user_offline_messages:
|
||||
admin: 5000
|
||||
all: 100
|
||||
## This rule allows access only for local users:
|
||||
local:
|
||||
local: allow
|
||||
## Only non-blocked users can use c2s connections:
|
||||
c2s:
|
||||
blocked: deny
|
||||
all: allow
|
||||
## For C2S connections, all users except admins use the "normal" shaper
|
||||
c2s_shaper:
|
||||
admin: none
|
||||
all: normal
|
||||
## All S2S connections use the "fast" shaper
|
||||
s2s_shaper:
|
||||
all: fast
|
||||
## Only admins can send announcement messages:
|
||||
announce:
|
||||
admin: allow
|
||||
## Only admins can use the configuration interface:
|
||||
configure:
|
||||
admin: allow
|
||||
## Admins of this server are also admins of the MUC service:
|
||||
muc_admin:
|
||||
admin: allow
|
||||
## Only accounts of the local ejabberd server, or only admins can create rooms, depending on environment variable:
|
||||
muc_create:
|
||||
{%- if env['EJABBERD_MUC_CREATE_ADMIN_ONLY'] == "true" %}
|
||||
admin: allow
|
||||
{% else %}
|
||||
local: allow
|
||||
{% endif %}
|
||||
## All users are allowed to use the MUC service:
|
||||
muc:
|
||||
all: allow
|
||||
## Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||
pubsub_createnode:
|
||||
local: allow
|
||||
## In-band registration allows registration of any possible username.
|
||||
register:
|
||||
{%- if env['EJABBERD_REGISTER_ADMIN_ONLY'] == "true" %}
|
||||
all: deny
|
||||
admin: allow
|
||||
{% else %}
|
||||
all: allow
|
||||
{% endif %}
|
||||
## Only allow to register from localhost
|
||||
trusted_network:
|
||||
loopback: allow
|
||||
soft_upload_quota:
|
||||
all: 400 # MiB
|
||||
hard_upload_quota:
|
||||
all: 500 # MiB
|
||||
|
||||
|
||||
language: "en"
|
||||
|
||||
### =======
|
||||
### MODULES
|
||||
|
||||
modules:
|
||||
mod_adhoc: {}
|
||||
{%- if env['EJABBERD_MOD_ADMIN_EXTRA'] == "true" %}
|
||||
mod_admin_extra: {}
|
||||
{% endif %}
|
||||
mod_announce: # recommends mod_adhoc
|
||||
access: announce
|
||||
mod_blocking: {} # requires mod_privacy
|
||||
mod_caps: {}
|
||||
mod_carboncopy: {}
|
||||
mod_client_state:
|
||||
drop_chat_states: true
|
||||
queue_presence: false
|
||||
mod_configure: {} # requires mod_adhoc
|
||||
mod_disco: {}
|
||||
## mod_echo: {}
|
||||
mod_irc: {}
|
||||
mod_http_bind: {}
|
||||
## mod_http_fileserver:
|
||||
## docroot: "/var/www"
|
||||
## accesslog: "/var/log/ejabberd/access.log"
|
||||
mod_last: {}
|
||||
mod_muc:
|
||||
host: "conference.@HOST@"
|
||||
access: muc
|
||||
access_create: muc_create
|
||||
access_persistent: muc_create
|
||||
access_admin: muc_admin
|
||||
history_size: 50
|
||||
default_room_options:
|
||||
persistent: true
|
||||
{%- if env['EJABBERD_MOD_MUC_ADMIN'] == "true" %}
|
||||
mod_muc_admin: {}
|
||||
{% endif %}
|
||||
## mod_muc_log: {}
|
||||
## mod_multicast: {}
|
||||
mod_offline:
|
||||
access_max_user_messages: max_user_offline_messages
|
||||
mod_ping: {}
|
||||
## mod_pres_counter:
|
||||
## count: 5
|
||||
## interval: 60
|
||||
mod_privacy: {}
|
||||
mod_private: {}
|
||||
## mod_proxy65: {}
|
||||
mod_pubsub:
|
||||
access_createnode: pubsub_createnode
|
||||
## reduces resource comsumption, but XEP incompliant
|
||||
ignore_pep_from_offline: true
|
||||
## XEP compliant, but increases resource comsumption
|
||||
## ignore_pep_from_offline: false
|
||||
last_item_cache: false
|
||||
plugins:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep" # pep requires mod_caps
|
||||
mod_register:
|
||||
##
|
||||
## Protect In-Band account registrations with CAPTCHA.
|
||||
##
|
||||
## captcha_protected: true
|
||||
|
||||
##
|
||||
## Set the minimum informational entropy for passwords.
|
||||
##
|
||||
## password_strength: 32
|
||||
|
||||
##
|
||||
## After successful registration, the user receives
|
||||
## a message with this subject and body.
|
||||
##
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
body: |-
|
||||
Hi.
|
||||
Welcome to this XMPP server.
|
||||
|
||||
##
|
||||
## Only clients in the server machine can register accounts
|
||||
##
|
||||
{%- if env['EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY'] == "true" %}
|
||||
ip_access: trusted_network
|
||||
{% endif %}
|
||||
|
||||
access: register
|
||||
mod_roster: {}
|
||||
mod_shared_roster: {}
|
||||
mod_stats: {}
|
||||
mod_time: {}
|
||||
mod_vcard: {}
|
||||
{% if env.get('EJABBERD_MOD_VERSION', true) == "true" %}
|
||||
mod_version: {}
|
||||
{% endif %}
|
||||
mod_http_upload:
|
||||
docroot: "/opt/ejabberd/upload"
|
||||
{%- if env['EJABBERD_HTTPS'] == "true" %}
|
||||
put_url: "https://@HOST@:5443"
|
||||
{%- else %}
|
||||
put_url: "http://@HOST@:5443"
|
||||
{% endif %}
|
||||
mod_http_upload_quota:
|
||||
max_days: 10
|
||||
|
||||
### ============
|
||||
### HOST CONFIG
|
||||
|
||||
host_config:
|
||||
{%- for xmpp_domain in env['XMPP_DOMAIN'].split() %}
|
||||
"{{ xmpp_domain }}":
|
||||
domain_certfile: "/opt/ejabberd/ssl/{{ xmpp_domain }}.pem"
|
||||
{%- endfor %}
|
||||
|
||||
{%- if env['EJABBERD_CONFIGURE_ODBC'] == "true" %}
|
||||
### ====================
|
||||
### ODBC DATABASE CONFIG
|
||||
odbc_type: {{ env['EJABBERD_ODBC_TYPE'] }}
|
||||
odbc_server: {{ env['EJABBERD_ODBC_SERVER'] }}
|
||||
odbc_database: {{ env['EJABBERD_ODBC_DATABASE'] }}
|
||||
odbc_username: {{ env['EJABBERD_ODBC_USERNAME'] }}
|
||||
odbc_password: {{ env['EJABBERD_ODBC_PASSWORD'] }}
|
||||
odbc_pool_size: {{ env['EJABBERD_ODBC_POOL_SIZE'] }}
|
||||
{% endif %}
|
||||
|
||||
{%- if env['EJABBERD_DEFAULT_DB'] is defined %}
|
||||
default_db: {{ env['EJABBERD_DEFAULT_DB'] }}
|
||||
{% endif %}
|
||||
|
||||
### =====================
|
||||
### SESSION MANAGEMENT DB
|
||||
sm_db_type: {{ env['EJABBERD_SESSION_DB'] or "mnesia" }}
|
||||
|
||||
{%- if env['EJABBERD_CONFIGURE_REDIS'] == "true" %}
|
||||
### ====================
|
||||
### REDIS DATABASE CONFIG
|
||||
redis_server: {{ env['EJABBERD_REDIS_SERVER'] or "localhost" }}
|
||||
redis_port: {{ env['EJABBERD_REDIS_PORT'] or 6379 }}
|
||||
{%- if env['EJABBERD_REDIS_PASSWORD'] is defined %}
|
||||
redis_password: {{ env['EJABBERD_REDIS_PASSWORD'] }}
|
||||
{% endif %}
|
||||
redis_db: {{ env['EJABBERD_REDIS_DB'] or 0}}
|
||||
redis_reconnect_timeout: {{ env['EJABBERD_REDIS_RECONNECT_TIMEOUT'] or 1 }}
|
||||
redis_connect_timeout: {{ env['EJABBERD_REDIS_CONNECT_TIMEOUT'] or 1 }}
|
||||
{% endif %}
|
199
docker/conf/ejabberdctl.cfg.tpl
Normal file
199
docker/conf/ejabberdctl.cfg.tpl
Normal file
@ -0,0 +1,199 @@
|
||||
#
|
||||
# In this file you can configure options that are passed by ejabberdctl
|
||||
# to the erlang runtime system when starting ejabberd
|
||||
#
|
||||
|
||||
#' POLL: Kernel polling ([true|false])
|
||||
#
|
||||
# The kernel polling option requires support in the kernel.
|
||||
# Additionally, you need to enable this feature while compiling Erlang.
|
||||
#
|
||||
# Default: true
|
||||
#
|
||||
POLL={{ env['POLL'] or 'true' }}
|
||||
|
||||
#.
|
||||
#' SMP: SMP support ([enable|auto|disable])
|
||||
#
|
||||
# Explanation in Erlang/OTP documentation:
|
||||
# enable: starts the Erlang runtime system with SMP support enabled.
|
||||
# This may fail if no runtime system with SMP support is available.
|
||||
# auto: starts the Erlang runtime system with SMP support enabled if it
|
||||
# is available and more than one logical processor are detected.
|
||||
# disable: starts a runtime system without SMP support.
|
||||
#
|
||||
# Default: auto
|
||||
#
|
||||
SMP={{ env['SMP'] or 'auto' }}
|
||||
|
||||
#.
|
||||
#' ERL_MAX_PORTS: Maximum number of simultaneously open Erlang ports
|
||||
#
|
||||
# ejabberd consumes two or three ports for every connection, either
|
||||
# from a client or from another Jabber server. So take this into
|
||||
# account when setting this limit.
|
||||
#
|
||||
# Default: 32000
|
||||
# Maximum: 268435456
|
||||
#
|
||||
ERL_MAX_PORTS={{ env['ERL_MAX_PORTS'] or '32000' }}
|
||||
|
||||
#.
|
||||
#' FIREWALL_WINDOW: Range of allowed ports to pass through a firewall
|
||||
#
|
||||
# If Ejabberd is configured to run in cluster, and a firewall is blocking ports,
|
||||
# it's possible to make Erlang use a defined range of port (instead of dynamic
|
||||
# ports) for node communication.
|
||||
#
|
||||
# Default: not defined
|
||||
# Example: 4200-4210
|
||||
#
|
||||
{%- if env['FIREWALL_WINDOW'] %}
|
||||
FIREWALL_WINDOW={{ env['FIREWALL_WINDOW'] }}
|
||||
{%- endif %}
|
||||
|
||||
#.
|
||||
#' INET_DIST_INTERFACE: IP address where this Erlang node listens other nodes
|
||||
#
|
||||
# This communication is used by ejabberdctl command line tool,
|
||||
# and in a cluster of several ejabberd nodes.
|
||||
#
|
||||
# Default: 0.0.0.0
|
||||
#
|
||||
{%- if env['INET_DIST_INTERFACE'] %}
|
||||
INET_DIST_INTERFACE={{ env['INET_DIST_INTERFACE'] }}
|
||||
{%- endif %}
|
||||
|
||||
#.
|
||||
#' ERL_EPMD_ADDRESS: IP addresses where epmd listens for connections
|
||||
#
|
||||
# IMPORTANT: This option works only in Erlang/OTP R14B03 and newer.
|
||||
#
|
||||
# This environment variable may be set to a comma-separated
|
||||
# list of IP addresses, in which case the epmd daemon
|
||||
# will listen only on the specified address(es) and on the
|
||||
# loopback address (which is implicitly added to the list if it
|
||||
# has not been specified). The default behaviour is to listen on
|
||||
# all available IP addresses.
|
||||
#
|
||||
# Default: 0.0.0.0
|
||||
#
|
||||
{%- if env['ERL_EPMD_ADDRESS'] %}
|
||||
ERL_EPMD_ADDRESS={{ env['ERL_EPMD_ADDRESS'] }}
|
||||
{%- endif %}
|
||||
|
||||
#.
|
||||
#' ERL_PROCESSES: Maximum number of Erlang processes
|
||||
#
|
||||
# Erlang consumes a lot of lightweight processes. If there is a lot of activity
|
||||
# on ejabberd so that the maximum number of processes is reached, people will
|
||||
# experience greater latency times. As these processes are implemented in
|
||||
# Erlang, and therefore not related to the operating system processes, you do
|
||||
# not have to worry about allowing a huge number of them.
|
||||
#
|
||||
# Default: 250000
|
||||
# Maximum: 268435456
|
||||
#
|
||||
ERL_PROCESSES={{ env['ERL_PROCESSES'] or '250000' }}
|
||||
|
||||
#.
|
||||
#' ERL_MAX_ETS_TABLES: Maximum number of ETS and Mnesia tables
|
||||
#
|
||||
# The number of concurrent ETS and Mnesia tables is limited. When the limit is
|
||||
# reached, errors will appear in the logs:
|
||||
# ** Too many db tables **
|
||||
# You can safely increase this limit when starting ejabberd. It impacts memory
|
||||
# consumption but the difference will be quite small.
|
||||
#
|
||||
# Default: 1400
|
||||
#
|
||||
ERL_MAX_ETS_TABLES={{ env['ERL_MAX_ETS_TABLES'] or '1400' }}
|
||||
|
||||
#.
|
||||
#' ERL_OPTIONS: Additional Erlang options
|
||||
#
|
||||
# The next variable allows to specify additional options passed to erlang while
|
||||
# starting ejabberd. Some useful options are -noshell, -detached, -heart. When
|
||||
# ejabberd is started from an init.d script options -noshell and -detached are
|
||||
# added implicitly. See erl(1) for more info.
|
||||
#
|
||||
# It might be useful to add "-pa /usr/local/lib/ejabberd/ebin" if you
|
||||
# want to add local modules in this path.
|
||||
#
|
||||
# Default: ""
|
||||
#
|
||||
ERL_OPTIONS="{{ env['ERL_OPTIONS'] or '-noshell' }}"
|
||||
|
||||
#.
|
||||
#' ERLANG_NODE: Erlang node name
|
||||
#
|
||||
# The next variable allows to explicitly specify erlang node for ejabberd
|
||||
# It can be given in different formats:
|
||||
# ERLANG_NODE=ejabberd
|
||||
# Lets erlang add hostname to the node (ejabberd uses short name in this case)
|
||||
# ERLANG_NODE=ejabberd@hostname
|
||||
# Erlang uses node name as is (so make sure that hostname is a real
|
||||
# machine hostname or you'll not be able to control ejabberd)
|
||||
# ERLANG_NODE=ejabberd@hostname.domainname
|
||||
# The same as previous, but erlang will use long hostname
|
||||
# (see erl (1) manual for details)
|
||||
#
|
||||
# Default: ejabberd@localhost
|
||||
#
|
||||
ERLANG_NODE={{ env['ERLANG_NODE'] or 'ejabberd@localhost' }}
|
||||
|
||||
#.
|
||||
#' EJABBERD_PID_PATH: ejabberd PID file
|
||||
#
|
||||
# Indicate the full path to the ejabberd Process identifier (PID) file.
|
||||
# If this variable is defined, ejabberd writes the PID file when starts,
|
||||
# and deletes it when stops.
|
||||
# Remember to create the directory and grant write permission to ejabberd.
|
||||
#
|
||||
# Default: don't write PID file
|
||||
#
|
||||
#EJABBERD_PID_PATH=/var/run/ejabberd/ejabberd.pid
|
||||
|
||||
#.
|
||||
#' EJABBERD_CONFIG_PATH: ejabberd configuration file
|
||||
#
|
||||
# Specify the full path to the ejabberd configuration file. If the file name has
|
||||
# yml or yaml extension, it is parsed as a YAML file; otherwise, Erlang syntax is
|
||||
# expected.
|
||||
#
|
||||
# Default: $ETC_DIR/ejabberd.yml
|
||||
#
|
||||
EJABBERD_CONFIG_PATH={{ env['EJABBERD_CONFIG_PATH'] or '/opt/ejabberd/conf/ejabberd.yml' }}
|
||||
|
||||
#.
|
||||
#' CONTRIB_MODULES_PATH: contributed ejabberd modules path
|
||||
#
|
||||
# Specify the full path to the contributed ejabberd modules. If the path is not
|
||||
# defined, ejabberd will use ~/.ejabberd-modules in home of user running ejabberd.
|
||||
#
|
||||
# Default: $HOME/.ejabberd-modules
|
||||
#
|
||||
CONTRIB_MODULES_PATH={{ env['CONTRIB_MODULES_PATH'] or '/opt/ejabberd/modules' }}
|
||||
|
||||
#.
|
||||
#' CONTRIB_MODULES_CONF_DIR: configuration directory for contributed modules
|
||||
#
|
||||
# Specify the full path to the configuration directory for contributed ejabberd
|
||||
# modules. In order to configure a module named mod_foo, a mod_foo.yml file can
|
||||
# be created in this directory. This file will then be used instead of the
|
||||
# default configuration file provided with the module.
|
||||
#
|
||||
# Default: $CONTRIB_MODULES_PATH/conf
|
||||
#
|
||||
CONTRIB_MODULES_CONF_DIR={{ env['CONTRIB_MODULES_CONF_DIR'] or '/opt/ejabberd/modules/conf' }}
|
||||
|
||||
#.
|
||||
#' EJABBERD_BYPASS_WARNINGS: Bypass LIVE warning
|
||||
#
|
||||
# Default: don't bypass the warning
|
||||
#
|
||||
EJABBERD_BYPASS_WARNINGS=true
|
||||
|
||||
#.
|
||||
#'
|
||||
# vim: foldmarker=#',#. foldmethod=marker:
|
22
docker/lib/base_config.sh
Normal file
22
docker/lib/base_config.sh
Normal file
@ -0,0 +1,22 @@
|
||||
readonly HOSTIP=$(hostname -i)
|
||||
readonly HOSTNAME=$(hostname -f)
|
||||
readonly DOMAINNAME=$(hostname -d)
|
||||
|
||||
readonly DOCKER_LIB="${EJABBERD_HOME}/docker/lib"
|
||||
readonly ERLANGCOOKIEFILE="${EJABBERD_HOME}/.erlang.cookie"
|
||||
readonly EJABBERDCTL="/sbin/ejabberdctl"
|
||||
readonly CONFIGDIR="${EJABBERD_HOME}/conf"
|
||||
readonly CONFIGTMPDIR="${EJABBERD_HOME}/docker/conf"
|
||||
readonly SSLCERTDIR="${EJABBERD_HOME}/ssl"
|
||||
readonly SSLCERTHOST="${SSLCERTDIR}/host.pem"
|
||||
readonly LOGDIR="/var/log/ejabberd"
|
||||
readonly FIRST_START_DONE_FILE="${EJABBERD_HOME}/first-start-done"
|
||||
readonly CLUSTER_NODE_FILE="${EJABBERD_HOME}/cluster-done"
|
||||
|
||||
readonly PYTHON_JINJA2="import os;
|
||||
import sys;
|
||||
import jinja2;
|
||||
sys.stdout.write(
|
||||
jinja2.Template
|
||||
(sys.stdin.read()
|
||||
).render(env=os.environ))"
|
72
docker/lib/base_functions.sh
Normal file
72
docker/lib/base_functions.sh
Normal file
@ -0,0 +1,72 @@
|
||||
is_set() {
|
||||
local var=$1
|
||||
|
||||
[[ -n $var ]]
|
||||
}
|
||||
|
||||
|
||||
is_zero() {
|
||||
local var=$1
|
||||
|
||||
[[ -z $var ]]
|
||||
}
|
||||
|
||||
|
||||
file_exist() {
|
||||
local file=$1
|
||||
|
||||
[[ -e $file ]]
|
||||
}
|
||||
|
||||
|
||||
is_true() {
|
||||
local var=${1,,}
|
||||
local choices=("yes" "1" "y" "true")
|
||||
for ((i=0;i < ${#choices[@]};i++)) {
|
||||
[[ "${choices[i]}" == $var ]] && return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
log() {
|
||||
local message=$1
|
||||
echo $message
|
||||
}
|
||||
|
||||
|
||||
# overwrite this function to get hostname from other sources
|
||||
# like dns or etcd
|
||||
get_nodename() {
|
||||
log ${HOSTNAME}
|
||||
}
|
||||
|
||||
|
||||
join_cluster() {
|
||||
local cluster_node=$1
|
||||
|
||||
is_zero ${cluster_node} \
|
||||
&& exit 0
|
||||
|
||||
log "Join cluster..."
|
||||
|
||||
local erlang_node_name=${ERLANG_NODE%@*}
|
||||
local erlang_cluster_node="${erlang_node_name}@${cluster_node}"
|
||||
|
||||
response=$(${EJABBERDCTL} ping ${erlang_cluster_node})
|
||||
while [ "$response" != "pong" ]; do
|
||||
log "Waiting for ${erlang_cluster_node}..."
|
||||
sleep 2
|
||||
response=$(${EJABBERDCTL} ping ${erlang_cluster_node})
|
||||
done
|
||||
|
||||
log "Join cluster at ${erlang_cluster_node}... "
|
||||
NO_WARNINGS=true ${EJABBERDCTL} join_cluster $erlang_cluster_node
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
touch ${CLUSTER_NODE_FILE}
|
||||
else
|
||||
log "cloud not join cluster"
|
||||
exit 1
|
||||
fi
|
||||
}
|
1
docker/lib/config.sh
Normal file
1
docker/lib/config.sh
Normal file
@ -0,0 +1 @@
|
||||
# Overridable file
|
1
docker/lib/functions.sh
Normal file
1
docker/lib/functions.sh
Normal file
@ -0,0 +1 @@
|
||||
# Overridable file
|
24
docker/post/10_ejabberd_modules_update_specs.sh
Executable file
24
docker/post/10_ejabberd_modules_update_specs.sh
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Updates the known modules as to be found in https://github.com/processone/ejabberd-contrib
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
|
||||
run_modules_update_specs() {
|
||||
log "Updating module specs... "
|
||||
${EJABBERDCTL} modules_update_specs
|
||||
}
|
||||
|
||||
|
||||
is_true ${EJABBERD_SKIP_MODULES_UPDATE} \
|
||||
&& exit 0
|
||||
|
||||
run_modules_update_specs
|
||||
|
||||
|
||||
exit 0
|
144
docker/post/11_ejabberd_install_modules.sh
Executable file
144
docker/post/11_ejabberd_install_modules.sh
Executable file
@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Installs modules as defined in environment variables
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
|
||||
install_module_from_source() {
|
||||
local module_name=$1
|
||||
local module_source_path=${EJABBERD_HOME}/module_source/${module_name}
|
||||
local module_install_folder=${EJABBERD_HOME}/.ejabberd-modules/sources/${module_name}
|
||||
|
||||
log "Analyzing module ${module_name} for installation"
|
||||
# Make sure that the module exists in the source folder before attempting a copy
|
||||
|
||||
if [ ! -d ${module_source_path} ]; then
|
||||
log "Error: Module ${module_name} not found in ${EJABBERD_HOME}/module_source"
|
||||
log "Please use a shared volume to populate your module in ${EJABBERD_HOME}/module_source"
|
||||
return 1;
|
||||
fi
|
||||
|
||||
# Check to see if the module is already installed
|
||||
local install_count=$(${EJABBERDCTL} modules_installed | grep -ce "^${module_name}[[:space:]]")
|
||||
if [ $install_count -gt 0 ]; then
|
||||
log "Error: Module already installed: ${module_name}"
|
||||
return 1;
|
||||
fi
|
||||
|
||||
# Copy the module into the shared folder
|
||||
log "Copying module to ejabberd folder ${module_install_folder}"
|
||||
mkdir -p ${module_install_folder}
|
||||
cp -R ${module_source_path} ${module_install_folder}
|
||||
|
||||
# Run the ejabberdctl module_check on the module
|
||||
log "Running module_check on ${module_name}"
|
||||
${EJABBERDCTL} module_check ${module_name}
|
||||
if [ $? -ne 0 ]; then
|
||||
log "Module check failed for ${module_name}"
|
||||
return 1;
|
||||
fi
|
||||
log "Module check succeeded for ${module_name}"
|
||||
|
||||
# Install the module
|
||||
log "Running module_install on ${module_name}"
|
||||
${EJABBERDCTL} module_install ${module_name}
|
||||
if [ $? -ne 0 ]; then
|
||||
log "Module installation failed for ${module_name}"
|
||||
return 1;
|
||||
fi
|
||||
log "Module installation succeeded for ${module_name}"
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
install_module_from_ejabberd_contrib() {
|
||||
local module_name=$1
|
||||
|
||||
# Check to see if the module is already installed
|
||||
local install_count=$(${EJABBERDCTL} modules_installed | grep -ce "^${module_name}[[:space:]]")
|
||||
if [ $install_count -gt 0 ]; then
|
||||
log "Error: Module already installed: ejabberd_contrib ${module_name}"
|
||||
return 1;
|
||||
fi
|
||||
|
||||
# Install the module
|
||||
log "Running module_install on ejabberd_contrib ${module_name}"
|
||||
${EJABBERDCTL} module_install ${module_name}
|
||||
if [ $? -ne 0 ]; then
|
||||
log "Module installation failed for ejabberd_contrib ${module_name}"
|
||||
return 1;
|
||||
fi
|
||||
log "Module installation succeeded for ejabberd_contrib ${module_name}"
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enable_custom_auth_module_override() {
|
||||
module_name=$1;
|
||||
# When using custom authentication modules, the module name must be
|
||||
# in the following pattern: ejabberd_auth_foo, where foo is the
|
||||
# value you will use for your auth_method yml configuration.
|
||||
required_prefix="ejabberd_auth_"
|
||||
|
||||
if [[ "${module_name}" != "${required_prefix}"* ]]; then
|
||||
log "Error: module_name must begin with ${required_prefix}"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
log "Checking custom auth module: ${module_name}"
|
||||
# Make sure the auth module is installed
|
||||
local install_count=$(${EJABBERDCTL} modules_installed | grep -ce "^${module_name}[[:space:]]")
|
||||
if [ $install_count -eq 0 ]; then
|
||||
log "Error: custom auth_module not installed: ${module_name}"
|
||||
return 1;
|
||||
fi
|
||||
|
||||
custom_auth_method=${module_name#$required_prefix}
|
||||
echo -e "\nauth_method: [${custom_auth_method}]" >> ${CONFIGFILE}
|
||||
log "Custom auth module ${module_name} configuration complete."
|
||||
}
|
||||
|
||||
file_exist ${FIRST_START_DONE_FILE} \
|
||||
&& exit 0
|
||||
|
||||
is_restart_needed=0;
|
||||
|
||||
if [ -n "${EJABBERD_SOURCE_MODULES}" ]; then
|
||||
for module_name in ${EJABBERD_SOURCE_MODULES} ; do
|
||||
install_module_from_source ${module_name}
|
||||
done
|
||||
is_restart_needed=1;
|
||||
fi
|
||||
|
||||
# Check the EJABBERD_CONTRIB_MODULES variable for any ejabberd_contrib modules
|
||||
if [ -n "${EJABBERD_CONTRIB_MODULES}" ]; then
|
||||
for module_name in ${EJABBERD_CONTRIB_MODULES} ; do
|
||||
install_module_from_ejabberd_contrib ${module_name}
|
||||
done
|
||||
is_restart_needed=1;
|
||||
fi
|
||||
|
||||
# If a custom module was defined for handling auth, we need to override
|
||||
# the pre-defined auth methods in the config.
|
||||
if [ -n "${EJABBERD_CUSTOM_AUTH_MODULE_OVERRIDE}" ]; then
|
||||
enable_custom_auth_module_override "${EJABBERD_CUSTOM_AUTH_MODULE_OVERRIDE}"
|
||||
is_restart_needed=1;
|
||||
fi
|
||||
|
||||
# If any modules were installed, restart the server, if the option is enabled
|
||||
if [ ${is_restart_needed} -eq 1 ]; then
|
||||
if is_true ${EJABBERD_RESTART_AFTER_MODULE_INSTALL} ; then
|
||||
log "Restarting ejabberd after successful module installation(s)"
|
||||
${EJABBERDCTL} restart
|
||||
child=$!
|
||||
${EJABBERDCTL} "started"
|
||||
wait $child
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
72
docker/post/20_ejabberd_register_users.sh
Executable file
72
docker/post/20_ejabberd_register_users.sh
Executable file
@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
# Do not exit if users already registered
|
||||
set +e
|
||||
|
||||
randpw() {
|
||||
< /dev/urandom tr -dc A-Z-a-z-0-9 | head -c ${1:-16};
|
||||
echo;
|
||||
}
|
||||
|
||||
|
||||
register_user() {
|
||||
local user=$1
|
||||
local domain=$2
|
||||
local password=$3
|
||||
|
||||
${EJABBERDCTL} register ${user} ${domain} ${password}
|
||||
return $?
|
||||
}
|
||||
|
||||
|
||||
register_all_users() {
|
||||
# register users from environment $EJABBERD_USERS with given
|
||||
# password or random password written to stout. Use whitespace
|
||||
# to seperate users.
|
||||
#
|
||||
# sample:
|
||||
# - add a user with an given password:
|
||||
# -e "EJABBERD_USERS=admin@example.com:adminSecret"
|
||||
# - add a user with a random password:
|
||||
# -e "EJABBERD_USERS=user@example.com"
|
||||
# - set password for admin and use random for user1:
|
||||
# -e "EJABBERD_USERS=admin@example.com:adminSecret user@example.com"
|
||||
|
||||
for user in ${EJABBERD_USERS} ; do
|
||||
local jid=${user%%:*}
|
||||
local password=${user#*:}
|
||||
|
||||
local username=${jid%%@*}
|
||||
local domain=${jid#*@}
|
||||
|
||||
[[ "${password}" == "${jid}" ]] \
|
||||
&& password=$(randpw)
|
||||
|
||||
register_user ${username} ${domain} ${password}
|
||||
local retval=$?
|
||||
|
||||
[[ ${retval} -eq 0 ]] \
|
||||
&& log "Password for user ${username}@${domain} is ${password}"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
file_exist ${FIRST_START_DONE_FILE} \
|
||||
&& exit 0
|
||||
|
||||
|
||||
file_exist ${CLUSTER_NODE_FILE} \
|
||||
&& exit 0
|
||||
|
||||
|
||||
is_set ${EJABBERD_USERS} \
|
||||
&& register_all_users
|
||||
|
||||
|
||||
exit 0
|
17
docker/post/99_first_start_done.sh
Executable file
17
docker/post/99_first_start_done.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Write a first-start-done file
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
|
||||
if [ ! -e "${FIRST_START_DONE_FILE}" ]; then
|
||||
touch ${FIRST_START_DONE_FILE}
|
||||
fi
|
||||
|
||||
|
||||
exit 0
|
34
docker/pre/01_write_certifiates_from_env.sh
Executable file
34
docker/pre/01_write_certifiates_from_env.sh
Executable file
@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
# Instead of having to mount a direction, specify the ssl certs
|
||||
# via environment variables:
|
||||
# `EJABBERD_SSLCERT_HOST` and `EJABBERD_SSLCERT_{domain_name}`.
|
||||
# For example: `EJABBERD_SSLCERT_EXAMPLE_COM`.
|
||||
|
||||
write_file_from_env() {
|
||||
log "Writing $1 to $2"
|
||||
mkdir -p "$(dirname $2)"
|
||||
log "${!1}" > $2
|
||||
}
|
||||
|
||||
# Write the host certificate
|
||||
is_set ${EJABBERD_SSLCERT_HOST} \
|
||||
&& write_file_from_env "EJABBERD_SSLCERT_HOST" ${SSLCERTHOST}
|
||||
|
||||
# Write the domain certificates for each XMPP_DOMAIN
|
||||
for xmpp_domain in ${XMPP_DOMAIN} ; do
|
||||
var="EJABBERD_SSLCERT_$(echo $xmpp_domain | awk '{print toupper($0)}' | sed 's/\./_/g;s/-/_/g')"
|
||||
if is_set ${!var} ; then
|
||||
file_exist "${SSLCERTDIR}/${xmpp_domain}.pem" \
|
||||
|| write_file_from_env "$var" "${SSLCERTDIR}/${xmpp_domain}.pem"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
exit 0
|
75
docker/pre/02_make_snakeoil_certificates.sh
Executable file
75
docker/pre/02_make_snakeoil_certificates.sh
Executable file
@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
|
||||
make_snakeoil_certificate() {
|
||||
local domain=$1
|
||||
local certfile=$2
|
||||
|
||||
openssl req -subj "/CN=${domain}" \
|
||||
-new \
|
||||
-newkey rsa:4096 \
|
||||
-days 365 \
|
||||
-nodes \
|
||||
-x509 \
|
||||
-keyout /tmp/selfsigned.key \
|
||||
-out /tmp/selfsigned.crt
|
||||
|
||||
log "Writing ssl cert and private key to '${certfile}'..."
|
||||
cat /tmp/selfsigned.crt /tmp/selfsigned.key > ${certfile}
|
||||
rm /tmp/selfsigned.crt /tmp/selfsigned.key
|
||||
}
|
||||
|
||||
|
||||
make_host_snakeoil_certificate() {
|
||||
local IFS=@
|
||||
local domain='localhost'
|
||||
local erlang_node=${ERLANG_NODE}
|
||||
|
||||
if is_true ${erlang_node} ; then
|
||||
domain=${HOSTNAME}
|
||||
elif is_set ${erlang_node} ; then
|
||||
set ${erlang_node}
|
||||
local nodehost=$2
|
||||
if is_zero ${nodehost} ; then
|
||||
domain=${HOSTNAME}
|
||||
else
|
||||
domain=${nodehost}
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Generating snakeoil ssl cert for ${domain}..."
|
||||
|
||||
make_snakeoil_certificate ${domain} ${SSLCERTHOST}
|
||||
}
|
||||
|
||||
|
||||
make_domain_snakeoil_certificate() {
|
||||
local domain=$1
|
||||
local certfile=$2
|
||||
|
||||
log "Generating snakeoil ssl cert for ${domain}..."
|
||||
|
||||
make_snakeoil_certificate ${domain} ${certfile}
|
||||
}
|
||||
|
||||
|
||||
# generate host ssl cert if missing
|
||||
file_exist ${SSLCERTHOST} \
|
||||
|| make_host_snakeoil_certificate
|
||||
|
||||
|
||||
# generate xmmp domain ssl certificates if missing
|
||||
for xmpp_domain in ${XMPP_DOMAIN} ; do
|
||||
domain_certfile="${SSLCERTDIR}/${xmpp_domain}.pem"
|
||||
file_exist ${domain_certfile} \
|
||||
|| make_domain_snakeoil_certificate ${xmpp_domain} ${domain_certfile}
|
||||
done
|
||||
|
||||
|
||||
exit 0
|
22
docker/pre/03_make_dhparam.sh
Executable file
22
docker/pre/03_make_dhparam.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
make_dhparam() {
|
||||
local dhfile=$1
|
||||
local bits=$2
|
||||
|
||||
log "Writing dh file to '${dhfile}'..."
|
||||
openssl dhparam -out ${dhfile} ${bits}
|
||||
}
|
||||
|
||||
if is_true ${EJABBERD_DHPARAM} ; then
|
||||
file_exist ${SSLDHPARAM} \
|
||||
|| make_dhparam ${SSLDHPARAM} 4096
|
||||
fi
|
||||
|
||||
exit 0
|
26
docker/pre/10_erlang_cookie.sh
Executable file
26
docker/pre/10_erlang_cookie.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
|
||||
set_erlang_cookie() {
|
||||
chmod 600 ${ERLANGCOOKIEFILE}
|
||||
log "Set erlang cookie to ${ERLANG_COOKIE}..."
|
||||
echo ${ERLANG_COOKIE} > ${ERLANGCOOKIEFILE}
|
||||
chmod 400 ${ERLANGCOOKIEFILE}
|
||||
}
|
||||
|
||||
|
||||
file_exist ${FIRST_START_DONE_FILE} \
|
||||
&& exit 0
|
||||
|
||||
|
||||
# set erlang cookie if ERLANG_COOKIE is set in environemt
|
||||
is_set ${ERLANG_COOKIE} \
|
||||
&& set_erlang_cookie
|
||||
|
||||
|
||||
exit 0
|
36
docker/pre/20_ejabberd_config.sh
Executable file
36
docker/pre/20_ejabberd_config.sh
Executable file
@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
|
||||
make_config() {
|
||||
local filename=$1
|
||||
local template="${CONFIGTMPDIR}/${filename}.tpl"
|
||||
local configfile="${CONFIGDIR}/${filename}"
|
||||
|
||||
file_exist $configfile \
|
||||
&& return 1
|
||||
|
||||
if [ ! -e ${configfile} ]; then
|
||||
log "Generating ${configfile} config file..."
|
||||
cat $template \
|
||||
| python -c "${PYTHON_JINJA2}" \
|
||||
> $configfile
|
||||
else
|
||||
echo "File ${configfile} exists."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# /opt/ejabberd/conf/ejabberd.yml
|
||||
make_config "ejabberd.yml"
|
||||
|
||||
# /opt/ejabberd/conf/ejabberdctl.cfg
|
||||
make_config "ejabberdctl.cfg"
|
||||
|
||||
|
||||
exit 0
|
69
docker/start.sh
Executable file
69
docker/start.sh
Executable file
@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Environment
|
||||
export EJABBERD_HTTPS=${EJABBERD_HTTPS:-'true'}
|
||||
export EJABBERD_STARTTLS=${EJABBERD_STARTTLS:-'true'}
|
||||
export EJABBERD_S2S_SSL=${EJABBERD_S2S_SSL:-'true'}
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
|
||||
# discover hostname
|
||||
readonly nodename=$(get_nodename)
|
||||
|
||||
# set erlang node to node name from get_nodename
|
||||
if [[ "$ERLANG_NODE" == "nodename" ]]; then
|
||||
export ERLANG_NODE="ejabberd@${nodename}"
|
||||
fi
|
||||
|
||||
|
||||
run_scripts() {
|
||||
local run_script=$1
|
||||
local run_script_dir="${EJABBERD_HOME}/docker/${run_script}"
|
||||
|
||||
log "Run ${run_script} scripts..."
|
||||
for script in ${run_script_dir}/*.sh ; do
|
||||
if [ -f ${script} -a -x ${script} ] ; then
|
||||
${script}
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
_trap() {
|
||||
run_scripts "stop"
|
||||
log "Stopping ejabberd..."
|
||||
$EJABBERDCTL stop
|
||||
$EJABBERDCTL stopped
|
||||
exit 0
|
||||
}
|
||||
|
||||
|
||||
# Catch signals and shutdown ejabberd
|
||||
trap _trap SIGTERM SIGINT
|
||||
|
||||
# print logfiles to stdout
|
||||
tail -F ${LOGDIR}/crash.log \
|
||||
${LOGDIR}/error.log \
|
||||
${LOGDIR}/erlang.log \
|
||||
${LOGDIR}/ejabberd.log &
|
||||
|
||||
# start ejabberd
|
||||
run_scripts "pre"
|
||||
log "Starting ejabberd..."
|
||||
$EJABBERDCTL start
|
||||
$EJABBERDCTL started
|
||||
log "Ejabberd started."
|
||||
run_scripts "post"
|
||||
|
||||
# run forever
|
||||
while true; do sleep 1; done
|
||||
|
||||
log "Ejabberd stopped."
|
||||
|
||||
|
||||
exit 0
|
21
docker/stop/10_leave_cluster.sh
Executable file
21
docker/stop/10_leave_cluster.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source "${EJABBERD_HOME}/docker/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/docker/lib/functions.sh"
|
||||
|
||||
|
||||
leave_cluster() {
|
||||
log "Leave cluster..."
|
||||
rm ${CLUSTER_NODE_FILE}
|
||||
NO_WARNINGS=true ${EJABBERDCTL} leave_cluster
|
||||
}
|
||||
|
||||
|
||||
file_exist ${CLUSTER_NODE_FILE} \
|
||||
&& leave_cluster
|
||||
|
||||
|
||||
exit 0
|
@ -3,22 +3,16 @@ Description=XMPP Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
User=ejabberd
|
||||
Group=ejabberd
|
||||
LimitNOFILE=16000
|
||||
LimitNOFILE=65536
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
ExecStart=@ctlscriptpath@/ejabberdctl start
|
||||
ExecStop=@ctlscriptpath@/ejabberdctl stop
|
||||
ExecReload=@ctlscriptpath@/ejabberdctl reload_config
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
# The CAP_DAC_OVERRIDE capability is required for pam authentication to work
|
||||
CapabilityBoundingSet=CAP_DAC_OVERRIDE
|
||||
PrivateTmp=true
|
||||
ExecStart=/bin/sh -c '@ctlscriptpath@/ejabberdctl start && @ctlscriptpath@/ejabberdctl started'
|
||||
ExecStop=/bin/sh -c '@ctlscriptpath@/ejabberdctl stop && @ctlscriptpath@/ejabberdctl stopped'
|
||||
PrivateDevices=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=full
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
51
include/bosh.hrl
Normal file
51
include/bosh.hrl
Normal file
@ -0,0 +1,51 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(CT_XML,
|
||||
{<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
|
||||
|
||||
-define(CT_PLAIN,
|
||||
{<<"Content-Type">>, <<"text/plain">>}).
|
||||
|
||||
-define(CT_JSON,
|
||||
{<<"Content-Type">>, <<"application/json">>}).
|
||||
|
||||
-define(AC_ALLOW_ORIGIN,
|
||||
{<<"Access-Control-Allow-Origin">>, <<"*">>}).
|
||||
|
||||
-define(AC_ALLOW_METHODS,
|
||||
{<<"Access-Control-Allow-Methods">>,
|
||||
<<"GET, POST, OPTIONS">>}).
|
||||
|
||||
-define(AC_ALLOW_HEADERS,
|
||||
{<<"Access-Control-Allow-Headers">>,
|
||||
<<"Content-Type">>}).
|
||||
|
||||
-define(AC_MAX_AGE,
|
||||
{<<"Access-Control-Max-Age">>, <<"86400">>}).
|
||||
|
||||
-define(OPTIONS_HEADER,
|
||||
[?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
|
||||
?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
|
||||
|
||||
-define(HEADER(CType),
|
||||
[CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_bosh).
|
@ -39,7 +39,9 @@
|
||||
|
||||
-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>).
|
||||
|
||||
-define(S2STIMEOUT, 600000).
|
||||
-define(COPYRIGHT, "Copyright (c) 2002-2016 ProcessOne").
|
||||
|
||||
-define(S2STIMEOUT, timer:minutes(10)).
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
|
||||
@ -64,7 +66,7 @@
|
||||
|
||||
-define(TDICT, dict:dict()).
|
||||
-define(TGB_TREE, gb_trees:tree()).
|
||||
-define(TGB_SET, gb_set:set()).
|
||||
-define(TGB_SET, gb_sets:set()).
|
||||
-define(TQUEUE, queue:queue()).
|
||||
|
||||
-endif.
|
||||
|
@ -46,12 +46,13 @@
|
||||
%% to command, so that the command can perform additional check.
|
||||
|
||||
-record(ejabberd_commands,
|
||||
{name :: atom(),
|
||||
{name :: atom(),
|
||||
tags = [] :: [atom()] | '_' | '$2',
|
||||
desc = "" :: string() | '_' | '$3',
|
||||
longdesc = "" :: string() | '_',
|
||||
version = 0 :: integer(),
|
||||
module :: atom() | '_',
|
||||
version = 0 :: integer(),
|
||||
weight = 1 :: integer(),
|
||||
module :: atom() | '_',
|
||||
function :: atom() | '_',
|
||||
args = [] :: [aterm()] | '_' | '$1' | '$2',
|
||||
policy = restricted :: open | restricted | admin | user,
|
||||
|
@ -1,20 +0,0 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-type filter_attr() :: {binary(), [binary()]}.
|
||||
|
||||
-record(state,
|
||||
{socket :: ejabberd_socket:socket_state(),
|
||||
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
|
||||
streamid = <<"">> :: binary(),
|
||||
host_opts = dict:new() :: ?TDICT,
|
||||
host = <<"">> :: binary(),
|
||||
access :: atom(),
|
||||
check_from = true :: boolean(),
|
||||
server_hosts = ?MYHOSTS :: [binary()],
|
||||
privilege_access :: [attr()],
|
||||
delegations :: [filter_attr()],
|
||||
last_pres = dict:new() :: ?TDICT}).
|
||||
|
||||
-type(state() :: #state{} ).
|
@ -19,11 +19,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-include("ns.hrl").
|
||||
-ifdef(NO_EXT_LIB).
|
||||
-include("fxml.hrl").
|
||||
-else.
|
||||
-include_lib("fast_xml/include/fxml.hrl").
|
||||
-endif.
|
||||
|
||||
-define(STANZA_ERROR(Code, Type, Condition),
|
||||
#xmlel{name = <<"error">>,
|
||||
|
@ -4,7 +4,7 @@
|
||||
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1',
|
||||
peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
|
||||
bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
|
||||
packet = #xmlel{} :: xmlel() | '_',
|
||||
packet = #xmlel{} :: xmlel() | message() | '_',
|
||||
nick = <<"">> :: binary(),
|
||||
type = chat :: chat | groupchat}).
|
||||
|
||||
|
@ -71,6 +71,7 @@
|
||||
-type config() :: #config{}.
|
||||
|
||||
-type role() :: moderator | participant | visitor | none.
|
||||
-type affiliation() :: admin | member | outcast | owner | none.
|
||||
|
||||
-record(user,
|
||||
{
|
||||
@ -126,5 +127,3 @@
|
||||
host = <<>> :: binary() | '_' | '$2'}).
|
||||
|
||||
-type muc_online_users() :: #muc_online_users{}.
|
||||
|
||||
-type muc_room_state() :: #state{}.
|
||||
|
@ -1,7 +1,7 @@
|
||||
-record(offline_msg,
|
||||
{us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
timestamp = now() :: erlang:timestamp() | '_',
|
||||
expire = now() :: erlang:timestamp() | never | '_',
|
||||
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_',
|
||||
expire = p1_time_compat:timestamp() :: erlang:timestamp() | never | '_',
|
||||
from = #jid{} :: jid() | '_',
|
||||
to = #jid{} :: jid() | '_',
|
||||
packet = #xmlel{} :: xmlel() | '_'}).
|
||||
|
@ -22,9 +22,11 @@
|
||||
default = none :: none | binary(),
|
||||
lists = [] :: [{binary(), [listitem()]}]}).
|
||||
|
||||
-record(listitem, {type = none :: none | jid | group | subscription,
|
||||
value = none :: none | both | from | to | ljid() | binary(),
|
||||
action = allow :: allow | deny,
|
||||
-type privacy() :: #privacy{}.
|
||||
|
||||
-record(listitem, {type = none :: listitem_type(),
|
||||
value = none :: listitem_value(),
|
||||
action = allow :: listitem_action(),
|
||||
order = 0 :: integer(),
|
||||
match_all = false :: boolean(),
|
||||
match_iq = false :: boolean(),
|
||||
@ -33,6 +35,9 @@
|
||||
match_presence_out = false :: boolean()}).
|
||||
|
||||
-type listitem() :: #listitem{}.
|
||||
-type listitem_type() :: none | jid | group | subscription.
|
||||
-type listitem_value() :: none | both | from | to | jid:ljid() | binary().
|
||||
-type listitem_action() :: allow | deny.
|
||||
|
||||
-record(userlist, {name = none :: none | binary(),
|
||||
list = [] :: [listitem()],
|
||||
|
@ -20,15 +20,15 @@
|
||||
|
||||
-record(roster,
|
||||
{
|
||||
usj = {<<>>, <<>>, {<<>>, <<>>, <<>>}} :: {binary(), binary(), ljid()} | '_',
|
||||
usj = {<<>>, <<>>, {<<>>, <<>>, <<>>}} :: {binary(), binary(), jid:ljid()} | '_',
|
||||
us = {<<>>, <<>>} :: {binary(), binary()} | '_',
|
||||
jid = {<<>>, <<>>, <<>>} :: ljid(),
|
||||
jid = {<<>>, <<>>, <<>>} :: jid:ljid(),
|
||||
name = <<>> :: binary() | '_',
|
||||
subscription = none :: subscription() | '_',
|
||||
ask = none :: ask() | '_',
|
||||
groups = [] :: [binary()] | '_',
|
||||
askmessage = <<"">> :: binary() | '_',
|
||||
xs = [] :: [xmlel()] | '_'
|
||||
xs = [] :: [fxml:xmlel()] | '_'
|
||||
}).
|
||||
|
||||
-record(roster_version,
|
||||
|
176
include/ns.hrl
176
include/ns.hrl
@ -1,176 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(NS_DISCO_ITEMS,
|
||||
<<"http://jabber.org/protocol/disco#items">>).
|
||||
-define(NS_DISCO_INFO,
|
||||
<<"http://jabber.org/protocol/disco#info">>).
|
||||
-define(NS_VCARD, <<"vcard-temp">>).
|
||||
-define(NS_VCARD_UPDATE, <<"vcard-temp:x:update">>).
|
||||
-define(NS_AUTH, <<"jabber:iq:auth">>).
|
||||
-define(NS_AUTH_ERROR, <<"jabber:iq:auth:error">>).
|
||||
-define(NS_REGISTER, <<"jabber:iq:register">>).
|
||||
-define(NS_SEARCH, <<"jabber:iq:search">>).
|
||||
-define(NS_ROSTER, <<"jabber:iq:roster">>).
|
||||
-define(NS_ROSTER_VER,
|
||||
<<"urn:xmpp:features:rosterver">>).
|
||||
-define(NS_PRIVACY, <<"jabber:iq:privacy">>).
|
||||
-define(NS_BLOCKING, <<"urn:xmpp:blocking">>).
|
||||
-define(NS_PRIVATE, <<"jabber:iq:private">>).
|
||||
-define(NS_VERSION, <<"jabber:iq:version">>).
|
||||
-define(NS_TIME, <<"urn:xmpp:time">>).
|
||||
-define(NS_LAST, <<"jabber:iq:last">>).
|
||||
-define(NS_XDATA, <<"jabber:x:data">>).
|
||||
-define(NS_IQDATA, <<"jabber:iq:data">>).
|
||||
-define(NS_DELAY, <<"urn:xmpp:delay">>).
|
||||
-define(NS_HINTS, <<"urn:xmpp:hints">>).
|
||||
-define(NS_EXPIRE, <<"jabber:x:expire">>).
|
||||
-define(NS_EVENT, <<"jabber:x:event">>).
|
||||
-define(NS_CHATSTATES,
|
||||
<<"http://jabber.org/protocol/chatstates">>).
|
||||
-define(NS_XCONFERENCE, <<"jabber:x:conference">>).
|
||||
-define(NS_STATS,
|
||||
<<"http://jabber.org/protocol/stats">>).
|
||||
-define(NS_MUC, <<"http://jabber.org/protocol/muc">>).
|
||||
-define(NS_MUC_USER,
|
||||
<<"http://jabber.org/protocol/muc#user">>).
|
||||
-define(NS_MUC_ADMIN,
|
||||
<<"http://jabber.org/protocol/muc#admin">>).
|
||||
-define(NS_MUC_OWNER,
|
||||
<<"http://jabber.org/protocol/muc#owner">>).
|
||||
-define(NS_MUC_UNIQUE,
|
||||
<<"http://jabber.org/protocol/muc#unique">>).
|
||||
-define(NS_PUBSUB,
|
||||
<<"http://jabber.org/protocol/pubsub">>).
|
||||
-define(NS_PUBSUB_EVENT,
|
||||
<<"http://jabber.org/protocol/pubsub#event">>).
|
||||
-define(NS_PUBSUB_META_DATA,
|
||||
<<"http://jabber.org/protocol/pubsub#meta-data">>).
|
||||
-define(NS_PUBSUB_OWNER,
|
||||
<<"http://jabber.org/protocol/pubsub#owner">>).
|
||||
-define(NS_PUBSUB_NMI,
|
||||
<<"http://jabber.org/protocol/pubsub#node-meta-info">>).
|
||||
-define(NS_PUBSUB_ERRORS,
|
||||
<<"http://jabber.org/protocol/pubsub#errors">>).
|
||||
-define(NS_PUBSUB_NODE_CONFIG,
|
||||
<<"http://jabber.org/protocol/pubsub#node_config">>).
|
||||
-define(NS_PUBSUB_SUB_OPTIONS,
|
||||
<<"http://jabber.org/protocol/pubsub#subscribe_options">>).
|
||||
-define(NS_PUBSUB_SUBSCRIBE_OPTIONS,
|
||||
<<"http://jabber.org/protocol/pubsub#subscribe_options">>).
|
||||
-define(NS_PUBSUB_PUBLISH_OPTIONS,
|
||||
<<"http://jabber.org/protocol/pubsub#publish_options">>).
|
||||
-define(NS_PUBSUB_SUB_AUTH,
|
||||
<<"http://jabber.org/protocol/pubsub#subscribe_authorization">>).
|
||||
-define(NS_PUBSUB_GET_PENDING,
|
||||
<<"http://jabber.org/protocol/pubsub#get-pending">>).
|
||||
-define(NS_COMMANDS,
|
||||
<<"http://jabber.org/protocol/commands">>).
|
||||
-define(NS_BYTESTREAMS,
|
||||
<<"http://jabber.org/protocol/bytestreams">>).
|
||||
-define(NS_ADMIN,
|
||||
<<"http://jabber.org/protocol/admin">>).
|
||||
-define(NS_ADMIN_ANNOUNCE,
|
||||
<<"http://jabber.org/protocol/admin#announce">>).
|
||||
-define(NS_ADMIN_ANNOUNCE_ALL,
|
||||
<<"http://jabber.org/protocol/admin#announce-all">>).
|
||||
-define(NS_ADMIN_SET_MOTD,
|
||||
<<"http://jabber.org/protocol/admin#set-motd">>).
|
||||
-define(NS_ADMIN_EDIT_MOTD,
|
||||
<<"http://jabber.org/protocol/admin#edit-motd">>).
|
||||
-define(NS_ADMIN_DELETE_MOTD,
|
||||
<<"http://jabber.org/protocol/admin#delete-motd">>).
|
||||
-define(NS_ADMIN_ANNOUNCE_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#announce-allhosts">>).
|
||||
-define(NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#announce-all-allhosts">>).
|
||||
-define(NS_ADMIN_SET_MOTD_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#set-motd-allhosts">>).
|
||||
-define(NS_ADMIN_EDIT_MOTD_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#edit-motd-allhosts">>).
|
||||
-define(NS_ADMIN_DELETE_MOTD_ALLHOSTS,
|
||||
<<"http://jabber.org/protocol/admin#delete-motd-allhosts">>).
|
||||
-define(NS_SERVERINFO,
|
||||
<<"http://jabber.org/network/serverinfo">>).
|
||||
-define(NS_RSM, <<"http://jabber.org/protocol/rsm">>).
|
||||
-define(NS_EJABBERD_CONFIG, <<"ejabberd:config">>).
|
||||
-define(NS_STREAM,
|
||||
<<"http://etherx.jabber.org/streams">>).
|
||||
-define(NS_STANZAS,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-stanzas">>).
|
||||
-define(NS_STREAMS,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-streams">>).
|
||||
-define(NS_TLS, <<"urn:ietf:params:xml:ns:xmpp-tls">>).
|
||||
-define(NS_SASL,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-sasl">>).
|
||||
-define(NS_SESSION,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-session">>).
|
||||
-define(NS_BIND,
|
||||
<<"urn:ietf:params:xml:ns:xmpp-bind">>).
|
||||
-define(NS_FEATURE_IQAUTH,
|
||||
<<"http://jabber.org/features/iq-auth">>).
|
||||
-define(NS_FEATURE_IQREGISTER,
|
||||
<<"http://jabber.org/features/iq-register">>).
|
||||
-define(NS_FEATURE_COMPRESS,
|
||||
<<"http://jabber.org/features/compress">>).
|
||||
-define(NS_FEATURE_MSGOFFLINE, <<"msgoffline">>).
|
||||
-define(NS_FLEX_OFFLINE, <<"http://jabber.org/protocol/offline">>).
|
||||
-define(NS_COMPRESS,
|
||||
<<"http://jabber.org/protocol/compress">>).
|
||||
-define(NS_CAPS, <<"http://jabber.org/protocol/caps">>).
|
||||
-define(NS_SHIM, <<"http://jabber.org/protocol/shim">>).
|
||||
-define(NS_ADDRESS,
|
||||
<<"http://jabber.org/protocol/address">>).
|
||||
-define(NS_OOB, <<"jabber:x:oob">>).
|
||||
-define(NS_CAPTCHA, <<"urn:xmpp:captcha">>).
|
||||
-define(NS_MEDIA, <<"urn:xmpp:media-element">>).
|
||||
-define(NS_BOB, <<"urn:xmpp:bob">>).
|
||||
-define(NS_MAM_TMP, <<"urn:xmpp:mam:tmp">>).
|
||||
-define(NS_MAM_0, <<"urn:xmpp:mam:0">>).
|
||||
-define(NS_MAM_1, <<"urn:xmpp:mam:1">>).
|
||||
-define(NS_SID_0, <<"urn:xmpp:sid:0">>).
|
||||
-define(NS_PING, <<"urn:xmpp:ping">>).
|
||||
-define(NS_CARBONS_2, <<"urn:xmpp:carbons:2">>).
|
||||
-define(NS_CARBONS_1, <<"urn:xmpp:carbons:1">>).
|
||||
-define(NS_FORWARD, <<"urn:xmpp:forward:0">>).
|
||||
-define(NS_CLIENT_STATE, <<"urn:xmpp:csi:0">>).
|
||||
-define(NS_STREAM_MGMT_2, <<"urn:xmpp:sm:2">>).
|
||||
-define(NS_STREAM_MGMT_3, <<"urn:xmpp:sm:3">>).
|
||||
-define(NS_HTTP_UPLOAD, <<"urn:xmpp:http:upload">>).
|
||||
-define(NS_HTTP_UPLOAD_OLD, <<"eu:siacs:conversations:http:upload">>).
|
||||
-define(NS_THUMBS_1, <<"urn:xmpp:thumbs:1">>).
|
||||
-define(NS_NICK, <<"http://jabber.org/protocol/nick">>).
|
||||
-define(NS_MIX_0, <<"urn:xmpp:mix:0">>).
|
||||
-define(NS_MIX_SERVICEINFO_0, <<"urn:xmpp:mix:0#serviceinfo">>).
|
||||
-define(NS_MIX_NODES_MESSAGES, <<"urn:xmpp:mix:nodes:messages">>).
|
||||
-define(NS_MIX_NODES_PRESENCE, <<"urn:xmpp:mix:nodes:presence">>).
|
||||
-define(NS_MIX_NODES_PARTICIPANTS, <<"urn:xmpp:mix:nodes:participants">>).
|
||||
-define(NS_MIX_NODES_SUBJECT, <<"urn:xmpp:mix:nodes:subject">>).
|
||||
-define(NS_MIX_NODES_CONFIG, <<"urn:xmpp:mix:nodes:config">>).
|
||||
-define(NS_PRIVILEGE, <<"urn:xmpp:privilege:1">>).
|
||||
-define(NS_DELEGATION, <<"urn:xmpp:delegation:1">>).
|
||||
-define(NS_MUCSUB, <<"urn:xmpp:mucsub:0">>).
|
||||
-define(NS_MUCSUB_NODES_PRESENCE, <<"urn:xmpp:mucsub:nodes:presence">>).
|
||||
-define(NS_MUCSUB_NODES_MESSAGES, <<"urn:xmpp:mucsub:nodes:messages">>).
|
||||
-define(NS_MUCSUB_NODES_PARTICIPANTS, <<"urn:xmpp:mucsub:nodes:participants">>).
|
||||
-define(NS_MUCSUB_NODES_AFFILIATIONS, <<"urn:xmpp:mucsub:nodes:affiliations">>).
|
||||
-define(NS_MUCSUB_NODES_SUBJECT, <<"urn:xmpp:mucsub:nodes:subject">>).
|
||||
-define(NS_MUCSUB_NODES_CONFIG, <<"urn:xmpp:mucsub:nodes:config">>).
|
||||
-define(NS_MUCSUB_NODES_SYSTEM, <<"urn:xmpp:mucsub:nodes:system">>).
|
11
mix.exs
11
mix.exs
@ -27,25 +27,26 @@ defmodule Ejabberd.Mixfile do
|
||||
[mod: {:ejabberd_app, []},
|
||||
applications: [:ssl],
|
||||
included_applications: [:lager, :mnesia, :p1_utils, :cache_tab,
|
||||
:fast_tls, :stringprep, :fast_xml,
|
||||
:fast_tls, :stringprep, :fast_xml, :xmpp,
|
||||
:stun, :fast_yaml, :esip, :jiffy, :p1_oauth2]
|
||||
++ cond_apps]
|
||||
end
|
||||
|
||||
defp erlc_options do
|
||||
# Use our own includes + includes from all dependencies
|
||||
includes = ["include"] ++ Path.wildcard(Path.join("..", "/*/include"))
|
||||
[:debug_info] ++ Enum.map(includes, fn(path) -> {:i, path} end)
|
||||
includes = ["include"] ++ Path.wildcard("deps/*/include")
|
||||
[:debug_info, {:d, :ELIXIR_ENABLED}] ++ Enum.map(includes, fn(path) -> {:i, path} end)
|
||||
end
|
||||
|
||||
defp deps do
|
||||
[{:lager, "~> 3.2"},
|
||||
{:p1_utils, "~> 1.0"},
|
||||
{:cache_tab, "~> 1.0"},
|
||||
{:stringprep, "~> 1.0"},
|
||||
{:stringprep, "~> 1.0", override: true}, # override cause of :xmpp
|
||||
{:fast_yaml, "~> 1.0"},
|
||||
{:fast_tls, "~> 1.0"},
|
||||
{:fast_xml, "~> 1.1"},
|
||||
{:fast_xml, "~> 1.1", override: true}, # override cause of :xmpp
|
||||
{:xmpp, github: "processone/xmpp", tag: "1.1.1"},
|
||||
{:stun, "~> 1.0"},
|
||||
{:esip, "~> 1.0"},
|
||||
{:jiffy, "~> 0.14.7"},
|
||||
|
7
mix.lock
7
mix.lock
@ -1,7 +1,7 @@
|
||||
%{"bbmustache": {:hex, :bbmustache, "1.0.4", "7ba94f971c5afd7b6617918a4bb74705e36cab36eb84b19b6a1b7ee06427aa38", [:rebar], []},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.4", "3fd2b1ab40c36e7830a4e09e836c6b0fa89191cd4e5fd471873e4eb42f5cd37c", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
|
||||
"cf": {:hex, :cf, "0.2.1", "69d0b1349fd4d7d4dc55b7f407d29d7a840bf9a1ef5af529f1ebe0ce153fc2ab", [:rebar3], []},
|
||||
"earmark": {:hex, :earmark, "1.0.2", "a0b0904d74ecc14da8bd2e6e0248e1a409a2bc91aade75fcf428125603de3853", [:mix], []},
|
||||
"earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []},
|
||||
"erlware_commons": {:hex, :erlware_commons, "0.21.0", "a04433071ad7d112edefc75ac77719dd3e6753e697ac09428fc83d7564b80b15", [:rebar3], [{:cf, "0.2.1", [hex: :cf, optional: false]}]},
|
||||
"esip": {:hex, :esip, "1.0.8", "69885a6c07964aabc6c077fe1372aa810a848bd3d9a415b160dabdce9c7a79b5", [:rebar3], [{:fast_tls, "1.0.7", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}, {:stun, "1.0.7", [hex: :stun, optional: false]}]},
|
||||
"ex_doc": {:hex, :ex_doc, "0.14.3", "e61cec6cf9731d7d23d254266ab06ac1decbb7651c3d1568402ec535d387b6f7", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
|
||||
@ -13,11 +13,12 @@
|
||||
"getopt": {:hex, :getopt, "0.8.2", "b17556db683000ba50370b16c0619df1337e7af7ecbf7d64fbf8d1d6bce3109b", [:rebar], []},
|
||||
"goldrush": {:hex, :goldrush, "0.1.8", "2024ba375ceea47e27ea70e14d2c483b2d8610101b4e852ef7f89163cdb6e649", [:rebar3], []},
|
||||
"iconv": {:hex, :iconv, "1.0.2", "a0792f06ab4b5ea1b5bb49789405739f1281a91c44cf3879cb70e4d777666217", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
|
||||
"jiffy": {:hex, :jiffy, "0.14.7", "9f33b893edd6041ceae03bc1e50b412e858cc80b46f3d7535a7a9940a79a1c37", [:rebar, :make], []},
|
||||
"jiffy": {:hex, :jiffy, "0.14.7", "9f33b893edd6041ceae03bc1e50b412e858cc80b46f3d7535a7a9940a79a1c37", [:make, :rebar], []},
|
||||
"lager": {:hex, :lager, "3.2.1", "eef4e18b39e4195d37606d9088ea05bf1b745986cf8ec84f01d332456fe88d17", [:rebar3], [{:goldrush, "0.1.8", [hex: :goldrush, optional: false]}]},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.5", "3e698354fdc1fea5491d991457b0cb986c0a00a47d224feb841dc3ec82b9f721", [:rebar3], []},
|
||||
"providers": {:hex, :providers, "1.6.0", "db0e2f9043ae60c0155205fcd238d68516331d0e5146155e33d1e79dc452964a", [:rebar3], [{:getopt, "0.8.2", [hex: :getopt, optional: false]}]},
|
||||
"relx": {:hex, :relx, "3.21.1", "f989dc520730efd9075e9f4debcb8ba1d7d1e86b018b0bcf45a2eb80270b4ad6", [:rebar3], [{:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:erlware_commons, "0.21.0", [hex: :erlware_commons, optional: false]}, {:getopt, "0.8.2", [hex: :getopt, optional: false]}, {:providers, "1.6.0", [hex: :providers, optional: false]}]},
|
||||
"stringprep": {:hex, :stringprep, "1.0.6", "1cf1c439eb038aa590da5456e019f86afbfbfeb5a2d37b6e5f873041624c6701", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
|
||||
"stun": {:hex, :stun, "1.0.7", "904dc6f26a3c30c54881c4c3003699f2a4968067ee6b3aecdf9895aad02df75e", [:rebar3], [{:fast_tls, "1.0.7", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]}}
|
||||
"stun": {:hex, :stun, "1.0.7", "904dc6f26a3c30c54881c4c3003699f2a4968067ee6b3aecdf9895aad02df75e", [:rebar3], [{:fast_tls, "1.0.7", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]},
|
||||
"xmpp": {:git, "https://github.com/processone/xmpp.git", "758c3a865563e019e46f8f6e96857a4161a833dd", [tag: "1.1.1"]}}
|
||||
|
@ -31,7 +31,7 @@ msgstr "foi removido"
|
||||
|
||||
#: ejabberd_c2s.erl:2105
|
||||
msgid "Your active privacy list has denied the routing of this stanza."
|
||||
msgstr "Sua lista de privacidade ativa negou o roteamento deste."
|
||||
msgstr "Sua lista de privacidade ativa negou o roteamento desta instância."
|
||||
|
||||
#: ejabberd_c2s.erl:2420
|
||||
msgid "Too many unacked stanzas"
|
||||
@ -286,7 +286,7 @@ msgstr "Nós em execução"
|
||||
|
||||
#: ejabberd_web_admin.erl:1815 mod_configure.erl:526
|
||||
msgid "Stopped Nodes"
|
||||
msgstr "Nos parados"
|
||||
msgstr "Nós parados"
|
||||
|
||||
#: ejabberd_web_admin.erl:1833 ejabberd_web_admin.erl:1858
|
||||
msgid "Node ~p"
|
||||
@ -302,7 +302,7 @@ msgstr "Salvar cópia de segurança"
|
||||
|
||||
#: ejabberd_web_admin.erl:1845
|
||||
msgid "Listened Ports"
|
||||
msgstr "Portas escutadas"
|
||||
msgstr "Portas abertas"
|
||||
|
||||
#: ejabberd_web_admin.erl:1848 ejabberd_web_admin.erl:2261
|
||||
msgid "Update"
|
||||
@ -1013,7 +1013,7 @@ msgstr "Apelido"
|
||||
|
||||
#: mod_muc.erl:1050 mod_muc_room.erl:1104 mod_muc_room.erl:1843
|
||||
msgid "That nickname is registered by another person"
|
||||
msgstr "O nick já está registrado por outra pessoa"
|
||||
msgstr "O apelido já está registrado por outra pessoa"
|
||||
|
||||
#: mod_muc.erl:1078
|
||||
msgid "You must fill in field \"Nickname\" in the form"
|
||||
@ -1303,7 +1303,7 @@ msgstr "O Jabber ID ~s não es válido"
|
||||
|
||||
#: mod_muc_room.erl:2772
|
||||
msgid "Nickname ~s does not exist in the room"
|
||||
msgstr "O nick ~s não existe na sala"
|
||||
msgstr "O apelido ~s não existe na sala"
|
||||
|
||||
#: mod_muc_room.erl:2795 mod_muc_room.erl:3175
|
||||
msgid "Invalid affiliation: ~s"
|
||||
@ -1568,15 +1568,15 @@ msgstr "Entregar as notificações de evento"
|
||||
|
||||
#: mod_pubsub.erl:3749
|
||||
msgid "Notify subscribers when the node configuration changes"
|
||||
msgstr "Notificar subscritores quando cambia la configuração do nodo"
|
||||
msgstr "Notificar assinantes a configuração do nó mudar"
|
||||
|
||||
#: mod_pubsub.erl:3751
|
||||
msgid "Notify subscribers when the node is deleted"
|
||||
msgstr "Notificar subscritores quando o nodo se elimine"
|
||||
msgstr "Notificar assinantes quando o nó for eliminado se elimine"
|
||||
|
||||
#: mod_pubsub.erl:3753
|
||||
msgid "Notify subscribers when items are removed from the node"
|
||||
msgstr "Notificar subscritores quando os elementos se eliminem do nodo"
|
||||
msgstr "Notificar assinantes quando itens forem eliminados do nó"
|
||||
|
||||
#: mod_pubsub.erl:3755
|
||||
msgid "Persist items to storage"
|
||||
@ -1637,7 +1637,7 @@ msgstr "A verificação do CAPTCHA falhou"
|
||||
#: mod_register.erl:253
|
||||
msgid "You need a client that supports x:data and CAPTCHA to register"
|
||||
msgstr ""
|
||||
"Você precisa de um cliente com suporte de x:data para poder registrar o nick"
|
||||
"Você precisa de um cliente com suporte de x:data para poder registrar o apelido"
|
||||
|
||||
#: mod_register.erl:259 mod_register.erl:320
|
||||
msgid "Choose a username and password to register with this server"
|
||||
@ -1693,7 +1693,7 @@ msgid ""
|
||||
"(Jabber IDentifier) will be of the form: username@server. Please read "
|
||||
"carefully the instructions to fill correctly the fields."
|
||||
msgstr ""
|
||||
"Esta pagina aceita criações de novas contas Jabber neste servidor. A sua JID "
|
||||
"Esta pagina aceita criações de novas contas Jabber neste servidor. O seu JID "
|
||||
"(Identificador Jabber) será da seguinte forma: 'usuário@servidor'. Por "
|
||||
"favor, leia cuidadosamente as instruções para preencher corretamente os "
|
||||
"campos."
|
||||
@ -1732,7 +1732,7 @@ msgid ""
|
||||
"Some Jabber clients can store your password in the computer, but you should "
|
||||
"do this only in your personal computer for safety reasons."
|
||||
msgstr ""
|
||||
"Alguns clientes jabber podem salvar a sua senha no seu computador. Use "
|
||||
"Alguns clientes jabber podem salvar a sua senha no seu computador. Use o "
|
||||
"recurso somente se você considera este computador seguro o suficiente."
|
||||
|
||||
#: mod_register_web.erl:256
|
||||
|
40
rebar.config
40
rebar.config
@ -1,4 +1,4 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%-------------------------------------------------------------------
|
||||
%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2013-2016, Evgeniy Khramtsov
|
||||
%%% @doc
|
||||
@ -8,21 +8,22 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.2.1"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.5"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.4"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.7"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.6"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.15"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.7"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.8"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.6"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.7"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.6"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.5"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.8"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.7"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.18"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.2"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.8"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.9"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.7"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
|
||||
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
|
||||
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
||||
{tag, "1.0.1"}}}},
|
||||
{tag, "1.0.2"}}}},
|
||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||
{tag, "1.1.0"}}}},
|
||||
{tag, "1.1.1"}}}},
|
||||
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
|
||||
{tag, "1.1.5"}}}},
|
||||
{if_var_true, pam, {p1_pam, ".*", {git, "https://github.com/processone/epam",
|
||||
@ -38,7 +39,7 @@
|
||||
{if_var_true, elixir, {rebar_elixir_plugin, ".*",
|
||||
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}},
|
||||
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
|
||||
{tag, "1.0.2"}}}},
|
||||
{tag, "1.0.3"}}}},
|
||||
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
|
||||
{tag, "0.8.4"}}}},
|
||||
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
|
||||
@ -55,6 +56,7 @@
|
||||
luerl,
|
||||
stun,
|
||||
fast_yaml,
|
||||
xmpp,
|
||||
p1_utils,
|
||||
p1_mysql,
|
||||
p1_pgsql,
|
||||
@ -62,9 +64,12 @@
|
||||
ezlib,
|
||||
iconv]}}.
|
||||
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl"]}.
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl"]}.
|
||||
|
||||
{erl_opts, [nowarn_deprecated_function,
|
||||
{i, "include"},
|
||||
{i, "deps/fast_xml/include"},
|
||||
{i, "deps/xmpp/include"},
|
||||
{if_var_false, debug, no_debug_info},
|
||||
{if_var_true, debug, debug_info},
|
||||
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
|
||||
@ -109,10 +114,13 @@
|
||||
{if_var_false, iconv, "(\"iconv\":_/_)"},
|
||||
{if_var_false, odbc, "(\"odbc\":_/_)"},
|
||||
{if_var_false, sqlite, "(\"sqlite3\":_/_)"},
|
||||
{if_var_false, elixir, "(\"Elixir.Logger.*\":_/_)"},
|
||||
{if_var_false, elixir, "(\"Elixir.*\":_/_)"},
|
||||
{if_var_false, redis, "(\"eredis\":_/_)"}]}.
|
||||
|
||||
{eunit_compile_opts, [{i, "tools"}]}.
|
||||
{eunit_compile_opts, [{i, "tools"},
|
||||
{i, "include"},
|
||||
{i, "deps/fast_xml/include"},
|
||||
{i, "deps/xmpp/include"}]}.
|
||||
|
||||
{if_version_above, "17", {cover_enabled, true}}.
|
||||
{cover_export_enabled, true}.
|
||||
|
@ -21,6 +21,14 @@ ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) ->
|
||||
end,
|
||||
ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end,
|
||||
|
||||
IsRebar3 = case application:get_key(rebar, vsn) of
|
||||
{ok, VSN} ->
|
||||
[VSN1 | _] = string:tokens(VSN, "-"),
|
||||
[Maj, Min, Patch] = string:tokens(VSN1, "."),
|
||||
(list_to_integer(Maj) >= 3);
|
||||
undefined ->
|
||||
lists:keymember(mix, 1, application:loaded_applications())
|
||||
end,
|
||||
Cfg = case file:consult(filename:join(filename:dirname(SCRIPT), "vars.config")) of
|
||||
{ok, Terms} ->
|
||||
Terms;
|
||||
@ -119,12 +127,64 @@ TestConfig = case file:read_file_info(TestConfigFile) of
|
||||
"-userconfig ct_config_plain " ++ TestConfigFile ++ " ";
|
||||
_ ->
|
||||
""
|
||||
end,
|
||||
end,
|
||||
|
||||
ResolveDepPath = case IsRebar3 of
|
||||
true ->
|
||||
fun("deps/" ++ Rest) ->
|
||||
Slash = string:str(Rest, "/"),
|
||||
Dir = "_build/default/lib/" ++
|
||||
string:sub_string(Rest, 1, Slash-1),
|
||||
Dir ++ string:sub_string(Rest, Slash);
|
||||
(Path) ->
|
||||
Path
|
||||
end;
|
||||
_ ->
|
||||
fun(P) ->
|
||||
P
|
||||
end
|
||||
end,
|
||||
|
||||
CtIncludes = case lists:keyfind(eunit_compile_opts, 1, Conf1) of
|
||||
false ->
|
||||
[];
|
||||
{_, EunitCompOpts} ->
|
||||
[[" -include ", filename:join([Cwd, ResolveDepPath(IncPath)])]
|
||||
|| {i, IncPath} <- EunitCompOpts]
|
||||
end,
|
||||
|
||||
ProcessErlOpt = fun({i, Path}) ->
|
||||
{i, ResolveDepPath(Path)};
|
||||
(ErlOpt) ->
|
||||
ErlOpt
|
||||
end,
|
||||
|
||||
Conf1a = ModCfg(Conf1, [erl_opts],
|
||||
fun(ErlOpts) -> lists:map(ProcessErlOpt, ErlOpts) end, []),
|
||||
|
||||
Conf2a = [{ct_extra_params, lists:flatten(["-ct_hooks cth_surefire ", TestConfig,
|
||||
CtIncludes])} | Conf1a],
|
||||
|
||||
Conf2 = case IsRebar3 of
|
||||
true ->
|
||||
DepsFun = fun(DepsList) ->
|
||||
lists:filtermap(fun({rebar_elixir_plugin, _, _}) ->
|
||||
false;
|
||||
({DepName,_, {git,_, _} = Git}) ->
|
||||
{true, {DepName, Git}};
|
||||
(Dep) ->
|
||||
true
|
||||
end, DepsList)
|
||||
end,
|
||||
RB1 = ModCfg(Conf2a, [deps], DepsFun, []),
|
||||
ModCfg(RB1, [plugins], fun(V) -> V -- [deps_erl_opts,
|
||||
rebar_elixir_compiler,
|
||||
rebar_exunit] ++
|
||||
[rebar3_hex] end, []);
|
||||
false ->
|
||||
Conf2a
|
||||
end,
|
||||
|
||||
Conf2 = [{ct_extra_params, "-ct_hooks cth_surefire "
|
||||
++ TestConfig
|
||||
++ "-include "
|
||||
++ filename:join([Cwd, "tools"])} | Conf1],
|
||||
|
||||
Conf3 = case lists:keytake(xref_exclusions, 1, Conf2) of
|
||||
{value, {_, Items2}, Rest2} ->
|
||||
|
14
src/acl.erl
14
src/acl.erl
@ -41,7 +41,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("jid.hrl").
|
||||
|
||||
-record(acl, {aclname, aclspec}).
|
||||
-record(access, {name :: aclname(),
|
||||
@ -76,11 +76,11 @@
|
||||
-export_type([acl/0]).
|
||||
|
||||
start() ->
|
||||
mnesia:create_table(acl,
|
||||
ejabberd_mnesia:create(?MODULE, acl,
|
||||
[{ram_copies, [node()]}, {type, bag},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, acl)}]),
|
||||
mnesia:create_table(access,
|
||||
ejabberd_mnesia:create(?MODULE, access,
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, access)}]),
|
||||
@ -342,7 +342,7 @@ acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
|
||||
acl_rule_verify(_Spec) ->
|
||||
false.
|
||||
invalid_syntax(Msg, Data) ->
|
||||
throw({invalid_syntax, iolist_to_binary(io_lib:format(Msg, Data))}).
|
||||
throw({invalid_syntax, (str:format(Msg, Data))}).
|
||||
|
||||
acl_rules_verify([{acl, Name} | Rest], true) when is_atom(Name) ->
|
||||
acl_rules_verify(Rest, true);
|
||||
@ -446,8 +446,8 @@ resolve_access(Name, Host) when is_atom(Name) ->
|
||||
GAccess = mnesia:dirty_read(access, {Name, global}),
|
||||
LAccess =
|
||||
if Host /= global -> mnesia:dirty_read(access, {Name, Host});
|
||||
true -> []
|
||||
end,
|
||||
true -> []
|
||||
end,
|
||||
case GAccess ++ LAccess of
|
||||
[] ->
|
||||
[];
|
||||
@ -542,7 +542,7 @@ parse_ip_netmask(S) ->
|
||||
_ -> error
|
||||
end;
|
||||
[IPStr, MaskStr] ->
|
||||
case catch jlib:binary_to_integer(MaskStr) of
|
||||
case catch binary_to_integer(MaskStr) of
|
||||
Mask when is_integer(Mask), Mask >= 0 ->
|
||||
case inet_parse:address(binary_to_list(IPStr)) of
|
||||
{ok, {_, _, _, _} = IP} when Mask =< 32 ->
|
||||
|
157
src/adhoc.erl
157
src/adhoc.erl
@ -1,157 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : adhoc.erl
|
||||
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Purpose : Provide helper functions for ad-hoc commands (XEP-0050)
|
||||
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(adhoc).
|
||||
|
||||
-author('henoch@dtek.chalmers.se').
|
||||
|
||||
-export([
|
||||
parse_request/1,
|
||||
produce_response/2,
|
||||
produce_response/1
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("adhoc.hrl").
|
||||
|
||||
%% Parse an ad-hoc request. Return either an adhoc_request record or
|
||||
%% an {error, ErrorType} tuple.
|
||||
%%
|
||||
-spec parse_request(IQ :: iq_request()) -> adhoc_response() | {error, _}.
|
||||
|
||||
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
|
||||
?DEBUG("entering parse_request...", []),
|
||||
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
|
||||
SessionID = fxml:get_tag_attr_s(<<"sessionid">>, SubEl),
|
||||
Action = fxml:get_tag_attr_s(<<"action">>, SubEl),
|
||||
XData = find_xdata_el(SubEl),
|
||||
#xmlel{children = AllEls} = SubEl,
|
||||
Others = case XData of
|
||||
false -> AllEls;
|
||||
_ -> lists:delete(XData, AllEls)
|
||||
end,
|
||||
#adhoc_request{
|
||||
lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID,
|
||||
action = Action,
|
||||
xdata = XData,
|
||||
others = Others
|
||||
};
|
||||
parse_request(#iq{lang = Lang}) ->
|
||||
Text = <<"Failed to parse ad-hoc command request">>,
|
||||
{error, ?ERRT_BAD_REQUEST(Lang, Text)}.
|
||||
|
||||
%% Borrowed from mod_vcard.erl
|
||||
find_xdata_el(#xmlel{children = SubEls}) ->
|
||||
find_xdata_el1(SubEls).
|
||||
|
||||
find_xdata_el1([]) -> false;
|
||||
find_xdata_el1([El | Els]) when is_record(El, xmlel) ->
|
||||
case fxml:get_tag_attr_s(<<"xmlns">>, El) of
|
||||
?NS_XDATA -> El;
|
||||
_ -> find_xdata_el1(Els)
|
||||
end;
|
||||
find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
|
||||
|
||||
%% Produce a <command/> node to use as response from an adhoc_response
|
||||
%% record, filling in values for language, node and session id from
|
||||
%% the request.
|
||||
%%
|
||||
-spec produce_response(Adhoc_Request :: adhoc_request(),
|
||||
Adhoc_Response :: adhoc_response()) ->
|
||||
Xmlel::xmlel().
|
||||
|
||||
%% Produce a <command/> node to use as response from an adhoc_response
|
||||
%% record.
|
||||
produce_response(#adhoc_request{lang = Lang, node = Node, sessionid = SessionID},
|
||||
Adhoc_Response) ->
|
||||
produce_response(Adhoc_Response#adhoc_response{
|
||||
lang = Lang, node = Node, sessionid = SessionID
|
||||
}).
|
||||
|
||||
%%
|
||||
-spec produce_response(Adhoc_Response::adhoc_response()) -> Xmlel::xmlel().
|
||||
|
||||
produce_response(
|
||||
#adhoc_response{
|
||||
%lang = _Lang,
|
||||
node = Node,
|
||||
sessionid = ProvidedSessionID,
|
||||
status = Status,
|
||||
defaultaction = DefaultAction,
|
||||
actions = Actions,
|
||||
notes = Notes,
|
||||
elements = Elements
|
||||
}) ->
|
||||
SessionID = if is_binary(ProvidedSessionID),
|
||||
ProvidedSessionID /= <<"">> -> ProvidedSessionID;
|
||||
true -> jlib:now_to_utc_string(p1_time_compat:timestamp())
|
||||
end,
|
||||
case {Actions, Status} of
|
||||
{[], completed} ->
|
||||
ActionsEls = [];
|
||||
{[], _} ->
|
||||
ActionsEls = [
|
||||
#xmlel{
|
||||
name = <<"actions">>,
|
||||
attrs = [{<<"execute">>, <<"complete">>}],
|
||||
children = [#xmlel{name = <<"complete">>}]
|
||||
}
|
||||
];
|
||||
_ ->
|
||||
case DefaultAction of
|
||||
<<"">> -> ActionsElAttrs = [];
|
||||
_ -> ActionsElAttrs = [{<<"execute">>, DefaultAction}]
|
||||
end,
|
||||
ActionsEls = [
|
||||
#xmlel{
|
||||
name = <<"actions">>,
|
||||
attrs = ActionsElAttrs,
|
||||
children = [
|
||||
#xmlel{name = Action, attrs = [], children = []}
|
||||
|| Action <- Actions]
|
||||
}
|
||||
]
|
||||
end,
|
||||
NotesEls = lists:map(fun({Type, Text}) ->
|
||||
#xmlel{
|
||||
name = <<"note">>,
|
||||
attrs = [{<<"type">>, Type}],
|
||||
children = [{xmlcdata, Text}]
|
||||
}
|
||||
end, Notes),
|
||||
#xmlel{
|
||||
name = <<"command">>,
|
||||
attrs = [
|
||||
{<<"xmlns">>, ?NS_COMMANDS},
|
||||
{<<"sessionid">>, SessionID},
|
||||
{<<"node">>, Node},
|
||||
{<<"status">>, iolist_to_binary(atom_to_list(Status))}
|
||||
],
|
||||
children = ActionsEls ++ NotesEls ++ Elements
|
||||
}.
|
@ -73,8 +73,8 @@
|
||||
-callback mech_step(any(), binary()) -> {ok, props()} |
|
||||
{ok, props(), binary()} |
|
||||
{continue, binary(), any()} |
|
||||
{error, binary()} |
|
||||
{error, binary(), binary()}.
|
||||
{error, atom()} |
|
||||
{error, atom(), binary()}.
|
||||
|
||||
start() ->
|
||||
ets:new(sasl_mechanism,
|
||||
@ -102,35 +102,11 @@ register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
true
|
||||
end.
|
||||
|
||||
%%% TODO: use callbacks
|
||||
%%-include("ejabberd.hrl").
|
||||
%%-include("jlib.hrl").
|
||||
%%check_authzid(_State, Props) ->
|
||||
%% AuthzId = fxml:get_attr_s(authzid, Props),
|
||||
%% case jid:from_string(AuthzId) of
|
||||
%% error ->
|
||||
%% {error, "invalid-authzid"};
|
||||
%% JID ->
|
||||
%% LUser = jid:nodeprep(fxml:get_attr_s(username, Props)),
|
||||
%% {U, S, R} = jid:tolower(JID),
|
||||
%% case R of
|
||||
%% "" ->
|
||||
%% {error, "invalid-authzid"};
|
||||
%% _ ->
|
||||
%% case {LUser, ?MYNAME} of
|
||||
%% {U, S} ->
|
||||
%% ok;
|
||||
%% _ ->
|
||||
%% {error, "invalid-authzid"}
|
||||
%% end
|
||||
%% end
|
||||
%% end.
|
||||
|
||||
check_credentials(_State, Props) ->
|
||||
User = proplists:get_value(authzid, Props, <<>>),
|
||||
case jid:nodeprep(User) of
|
||||
error -> {error, <<"not-authorized">>};
|
||||
<<"">> -> {error, <<"not-authorized">>};
|
||||
error -> {error, 'not-authorized'};
|
||||
<<"">> -> {error, 'not-authorized'};
|
||||
_LUser -> ok
|
||||
end.
|
||||
|
||||
@ -159,6 +135,8 @@ server_new(Service, ServerFQDN, UserRealm, _SecFlags,
|
||||
check_password = CheckPassword,
|
||||
check_password_digest = CheckPasswordDigest}.
|
||||
|
||||
server_start(State, Mech, undefined) ->
|
||||
server_start(State, Mech, <<"">>);
|
||||
server_start(State, Mech, ClientIn) ->
|
||||
case lists:member(Mech,
|
||||
listmech(State#sasl_state.myname))
|
||||
@ -174,11 +152,13 @@ server_start(State, Mech, ClientIn) ->
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ -> {error, <<"no-mechanism">>}
|
||||
_ -> {error, 'no-mechanism'}
|
||||
end;
|
||||
false -> {error, <<"no-mechanism">>}
|
||||
false -> {error, 'no-mechanism'}
|
||||
end.
|
||||
|
||||
server_step(State, undefined) ->
|
||||
server_step(State, <<"">>);
|
||||
server_step(State, ClientIn) ->
|
||||
Module = State#sasl_state.mech_mod,
|
||||
MechState = State#sasl_state.mech_state,
|
||||
|
@ -45,7 +45,7 @@ mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
|
||||
|
||||
mech_step(#state{server = Server} = S, ClientIn) ->
|
||||
User = iolist_to_binary([randoms:get_string(),
|
||||
jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]),
|
||||
integer_to_binary(p1_time_compat:unique_integer([positive]))]),
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true -> mech_step(S, ClientIn);
|
||||
false -> {ok, [{username, User}, {authzid, User}, {auth_module, ejabberd_auth_anonymous}]}
|
||||
|
@ -80,7 +80,7 @@ mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
mech_step(#state{step = 3, nonce = Nonce} = State,
|
||||
ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
bad -> {error, <<"bad-protocol">>};
|
||||
bad -> {error, 'bad-protocol'};
|
||||
KeyVals ->
|
||||
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
|
||||
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
|
||||
@ -92,11 +92,11 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
|
||||
"seems invalid: ~p (checking for Host "
|
||||
"~p, FQDN ~p)",
|
||||
[DigestURI, State#state.host, State#state.hostfqdn]),
|
||||
{error, <<"not-authorized">>, UserName};
|
||||
{error, 'not-authorized', UserName};
|
||||
true ->
|
||||
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName};
|
||||
{false, _} -> {error, 'not-authorized', UserName};
|
||||
{Passwd, AuthModule} ->
|
||||
case (State#state.check_password)(UserName, UserName, <<"">>,
|
||||
proplists:get_value(<<"response">>, KeyVals, <<>>),
|
||||
@ -116,8 +116,8 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
|
||||
State#state{step = 5, auth_module = AuthModule,
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
false -> {error, <<"not-authorized">>, UserName};
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName}
|
||||
false -> {error, 'not-authorized', UserName};
|
||||
{false, _} -> {error, 'not-authorized', UserName}
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -134,7 +134,7 @@ mech_step(#state{step = 5, auth_module = AuthModule,
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(A, B) ->
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
|
||||
{error, <<"bad-protocol">>}.
|
||||
{error, 'bad-protocol'}.
|
||||
|
||||
parse(S) -> parse1(binary_to_list(S), "", []).
|
||||
|
||||
|
@ -52,9 +52,9 @@ mech_step(State, ClientIn) ->
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, ejabberd_oauth}]};
|
||||
_ ->
|
||||
{error, <<"not-authorized">>, User}
|
||||
{error, 'not-authorized', User}
|
||||
end;
|
||||
_ -> {error, <<"bad-protocol">>}
|
||||
_ -> {error, 'bad-protocol'}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
|
@ -50,9 +50,9 @@ mech_step(State, ClientIn) ->
|
||||
{ok,
|
||||
[{username, User}, {authzid, AuthzId},
|
||||
{auth_module, AuthModule}]};
|
||||
_ -> {error, <<"not-authorized">>, User}
|
||||
_ -> {error, 'not-authorized', User}
|
||||
end;
|
||||
_ -> {error, <<"bad-protocol">>}
|
||||
_ -> {error, 'bad-protocol'}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
|
@ -34,8 +34,6 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state,
|
||||
@ -67,21 +65,21 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
case re:split(ClientIn, <<",">>, [{return, binary}]) of
|
||||
[_CBind, _AuthorizationIdentity, _UserNameAttribute, _ClientNonceAttribute, ExtensionAttribute | _]
|
||||
when ExtensionAttribute /= [] ->
|
||||
{error, <<"protocol-error-extension-not-supported">>};
|
||||
{error, 'protocol-error-extension-not-supported'};
|
||||
[CBind, _AuthorizationIdentity, UserNameAttribute, ClientNonceAttribute | _]
|
||||
when (CBind == <<"y">>) or (CBind == <<"n">>) ->
|
||||
case parse_attribute(UserNameAttribute) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
{_, EscapedUserName} ->
|
||||
case unescape_username(EscapedUserName) of
|
||||
error -> {error, <<"protocol-error-bad-username">>};
|
||||
error -> {error, 'protocol-error-bad-username'};
|
||||
UserName ->
|
||||
case parse_attribute(ClientNonceAttribute) of
|
||||
{$r, ClientNonce} ->
|
||||
{Ret, _AuthModule} = (State#state.get_password)(UserName),
|
||||
case {Ret, jid:resourceprep(Ret)} of
|
||||
{false, _} -> {error, <<"not-authorized">>, UserName};
|
||||
{_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, <<"not-authorized">>, UserName};
|
||||
{false, _} -> {error, 'not-authorized', UserName};
|
||||
{_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, 'not-authorized', UserName};
|
||||
{Ret, _} ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} =
|
||||
if is_tuple(Ret) -> Ret;
|
||||
@ -121,11 +119,11 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
server_nonce = ServerNonce,
|
||||
username = UserName}}
|
||||
end;
|
||||
_Else -> {error, <<"not-supported">>}
|
||||
_Else -> {error, 'not-supported'}
|
||||
end
|
||||
end
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end;
|
||||
mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
case str:tokens(ClientIn, <<",">>) of
|
||||
@ -163,18 +161,18 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
{authzid, State#state.username}],
|
||||
<<"v=",
|
||||
(jlib:encode_base64(ServerSignature))/binary>>};
|
||||
true -> {error, <<"bad-auth">>, State#state.username}
|
||||
true -> {error, 'bad-auth', State#state.username}
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end;
|
||||
{$r, _} -> {error, <<"bad-nonce">>};
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
{$r, _} -> {error, 'bad-nonce'};
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end;
|
||||
true -> {error, <<"bad-channel-binding">>}
|
||||
true -> {error, 'bad-channel-binding'}
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end;
|
||||
_Else -> {error, <<"bad-protocol">>}
|
||||
_Else -> {error, 'bad-protocol'}
|
||||
end.
|
||||
|
||||
parse_attribute(Attribute) ->
|
||||
@ -187,11 +185,11 @@ parse_attribute(Attribute) ->
|
||||
if SecondChar == $= ->
|
||||
String = str:substr(Attribute, 3),
|
||||
{lists:nth(1, AttributeS), String};
|
||||
true -> {error, <<"bad-format second char not equal sign">>}
|
||||
true -> {error, 'bad-format-second-char-not-equal-sign'}
|
||||
end;
|
||||
_Else -> {error, <<"bad-format first char not a letter">>}
|
||||
_Else -> {error, 'bad-format-first-char-not-a-letter'}
|
||||
end;
|
||||
true -> {error, <<"bad-format attribute too short">>}
|
||||
true -> {error, 'bad-format-attribute-too-short'}
|
||||
end.
|
||||
|
||||
unescape_username(<<"">>) -> <<"">>;
|
||||
|
@ -532,10 +532,10 @@ key_split([{Arg, Value} | Rest], Results, Order, Required, Duplicates) ->
|
||||
end.
|
||||
|
||||
report_error(Format, Args) ->
|
||||
throw({invalid_syntax, iolist_to_binary(io_lib:format(Format, Args))}).
|
||||
throw({invalid_syntax, (str:format(Format, Args))}).
|
||||
|
||||
parse_error(Format, Args) ->
|
||||
{error, iolist_to_binary(io_lib:format(Format, Args))}.
|
||||
{error, (str:format(Format, Args))}.
|
||||
|
||||
opt_type(api_permissions) ->
|
||||
fun parse_api_permissions/1;
|
||||
|
@ -303,8 +303,8 @@ set_loglevel(LogLevel) ->
|
||||
%%%
|
||||
|
||||
stop_kindly(DelaySeconds, AnnouncementTextString) ->
|
||||
Subject = list_to_binary(io_lib:format("Server stop in ~p seconds!", [DelaySeconds])),
|
||||
WaitingDesc = list_to_binary(io_lib:format("Waiting ~p seconds", [DelaySeconds])),
|
||||
Subject = (str:format("Server stop in ~p seconds!", [DelaySeconds])),
|
||||
WaitingDesc = (str:format("Waiting ~p seconds", [DelaySeconds])),
|
||||
AnnouncementText = list_to_binary(AnnouncementTextString),
|
||||
Steps = [
|
||||
{"Stopping ejabberd port listeners",
|
||||
@ -338,8 +338,7 @@ stop_kindly(DelaySeconds, AnnouncementTextString) ->
|
||||
ok.
|
||||
|
||||
send_service_message_all_mucs(Subject, AnnouncementText) ->
|
||||
Message = list_to_binary(
|
||||
io_lib:format("~s~n~s", [Subject, AnnouncementText])),
|
||||
Message = str:format("~s~n~s", [Subject, AnnouncementText]),
|
||||
lists:foreach(
|
||||
fun(ServerHost) ->
|
||||
MUCHost = gen_mod:get_module_opt_host(
|
||||
|
@ -43,7 +43,6 @@
|
||||
start(normal, _Args) ->
|
||||
ejabberd_logger:start(),
|
||||
write_pid_file(),
|
||||
jid:start(),
|
||||
start_apps(),
|
||||
start_elixir_application(),
|
||||
ejabberd:check_app(ejabberd),
|
||||
@ -77,7 +76,6 @@ start(normal, _Args) ->
|
||||
ejabberd_oauth:start(),
|
||||
gen_mod:start_modules(),
|
||||
ejabberd_listener:start_listeners(),
|
||||
ejabberd_service:start(),
|
||||
register_elixir_config_hooks(),
|
||||
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
||||
Sup;
|
||||
@ -224,9 +222,7 @@ start_apps() ->
|
||||
ejabberd:start_app(ssl),
|
||||
ejabberd:start_app(fast_yaml),
|
||||
ejabberd:start_app(fast_tls),
|
||||
ejabberd:start_app(fast_xml),
|
||||
ejabberd:start_app(stringprep),
|
||||
http_p1:start(),
|
||||
ejabberd:start_app(xmpp),
|
||||
ejabberd:start_app(cache_tab).
|
||||
|
||||
opt_type(net_ticktime) ->
|
||||
|
@ -36,8 +36,8 @@
|
||||
check_password/6, check_password_with_authmodule/4,
|
||||
check_password_with_authmodule/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2, export/1, import/1,
|
||||
get_vh_registered_users_number/1, import/3,
|
||||
get_vh_registered_users/2, export/1, import_info/0,
|
||||
get_vh_registered_users_number/1, import/5, import_start/2,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, get_password_with_authmodule/2,
|
||||
is_user_exists/2, is_user_exists_in_other_modules/3,
|
||||
@ -438,15 +438,20 @@ auth_modules(Server) ->
|
||||
export(Server) ->
|
||||
ejabberd_auth_mnesia:export(Server).
|
||||
|
||||
import(Server) ->
|
||||
ejabberd_auth_mnesia:import(Server).
|
||||
import_info() ->
|
||||
[{<<"users">>, 3}].
|
||||
|
||||
import(Server, mnesia, Passwd) ->
|
||||
ejabberd_auth_mnesia:import(Server, mnesia, Passwd);
|
||||
import(Server, riak, Passwd) ->
|
||||
ejabberd_auth_riak:import(Server, riak, Passwd);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
import_start(_LServer, mnesia) ->
|
||||
ejabberd_auth_mnesia:init_db();
|
||||
import_start(_LServer, _) ->
|
||||
ok.
|
||||
|
||||
import(Server, {sql, _}, mnesia, <<"users">>, Fields) ->
|
||||
ejabberd_auth_mnesia:import(Server, Fields);
|
||||
import(Server, {sql, _}, riak, <<"users">>, Fields) ->
|
||||
ejabberd_auth_riak:import(Server, Fields);
|
||||
import(_LServer, {sql, _}, sql, <<"users">>, _) ->
|
||||
ok.
|
||||
|
||||
opt_type(auth_method) ->
|
||||
fun (V) when is_list(V) ->
|
||||
|
@ -50,8 +50,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("jid.hrl").
|
||||
|
||||
%% Create the anonymous table if at least one virtual host has anonymous features enabled
|
||||
%% Register to login / logout events
|
||||
@ -60,7 +59,7 @@
|
||||
|
||||
start(Host) ->
|
||||
%% TODO: Check cluster mode
|
||||
mnesia:create_table(anonymous, [{ram_copies, [node()]},
|
||||
ejabberd_mnesia:create(?MODULE, anonymous, [{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, anonymous)}]),
|
||||
%% The hooks are needed to add / remove users from the anonymous tables
|
||||
@ -140,6 +139,7 @@ remove_connection(SID, LUser, LServer) ->
|
||||
mnesia:transaction(F).
|
||||
|
||||
%% Register connection
|
||||
-spec register_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
|
||||
register_connection(SID,
|
||||
#jid{luser = LUser, lserver = LServer}, Info) ->
|
||||
AuthModule = proplists:get_value(auth_module, Info, undefined),
|
||||
@ -156,6 +156,7 @@ register_connection(SID,
|
||||
end.
|
||||
|
||||
%% Remove an anonymous user from the anonymous users table
|
||||
-spec unregister_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any().
|
||||
unregister_connection(SID,
|
||||
#jid{luser = LUser, lserver = LServer}, _) ->
|
||||
purge_hook(anonymous_user_exist(LUser, LServer), LUser,
|
||||
|
@ -36,12 +36,12 @@
|
||||
-export([start/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users/2, init_db/0,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, export/1, import/1,
|
||||
import/3, plain_password_required/0, opt_type/1]).
|
||||
remove_user/3, store_type/0, export/1, import/2,
|
||||
plain_password_required/0, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@ -59,17 +59,20 @@
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
mnesia:create_table(passwd,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
mnesia:create_table(reg_users_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, reg_users_counter)}]),
|
||||
init_db(),
|
||||
update_table(),
|
||||
update_reg_users_counter_table(Host),
|
||||
maybe_alert_password_scrammed_without_option(),
|
||||
ok.
|
||||
|
||||
init_db() ->
|
||||
ejabberd_mnesia:create(?MODULE, passwd,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
ejabberd_mnesia:create(?MODULE, reg_users_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, reg_users_counter)}]).
|
||||
|
||||
update_reg_users_counter_table(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
Size = length(Set),
|
||||
@ -493,16 +496,9 @@ export(_Server) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select username, password from users;">>,
|
||||
fun([LUser, Password]) ->
|
||||
#passwd{us = {LUser, LServer}, password = Password}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #passwd{} = P) ->
|
||||
mnesia:dirty_write(P);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
import(LServer, [LUser, Password, _TimeStamp]) ->
|
||||
mnesia:dirty_write(
|
||||
#passwd{us = {LUser, LServer}, password = Password}).
|
||||
|
||||
opt_type(auth_password_format) -> fun (V) -> V end;
|
||||
opt_type(_) -> [auth_password_format].
|
||||
|
@ -27,6 +27,8 @@
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
@ -39,8 +41,8 @@
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, export/1, import/3,
|
||||
plain_password_required/0]).
|
||||
remove_user/3, store_type/0, export/1, import/2,
|
||||
plain_password_required/0, opt_type/1]).
|
||||
-export([passwd_schema/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@ -301,7 +303,9 @@ export(_Server) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer, riak, #passwd{} = Passwd) ->
|
||||
ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
import(LServer, [LUser, Password, _TimeStamp]) ->
|
||||
Passwd = #passwd{us = {LUser, LServer}, password = Password},
|
||||
ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]).
|
||||
|
||||
opt_type(auth_password_format) -> fun (V) -> V end;
|
||||
opt_type(_) -> [auth_password_format].
|
||||
|
1095
src/ejabberd_bosh.erl
Normal file
1095
src/ejabberd_bosh.erl
Normal file
File diff suppressed because it is too large
Load Diff
2649
src/ejabberd_c2s.erl
2649
src/ejabberd_c2s.erl
File diff suppressed because it is too large
Load Diff
@ -41,31 +41,17 @@
|
||||
-export([create_captcha/6, build_captcha_html/2,
|
||||
check_captcha/2, process_reply/1, process/2,
|
||||
is_feature_available/0, create_captcha_x/5,
|
||||
create_captcha_x/6, opt_type/1]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
opt_type/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
-define(VFIELD(Type, Var, Value),
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>, attrs = [],
|
||||
children = [Value]}]}).
|
||||
|
||||
-define(CAPTCHA_TEXT(Lang),
|
||||
translate:translate(Lang,
|
||||
<<"Enter the text you see">>)).
|
||||
|
||||
-define(CAPTCHA_LIFETIME, 120000).
|
||||
|
||||
-define(LIMIT_PERIOD, 60*1000*1000).
|
||||
|
||||
-type error() :: efbig | enodata | limit | malformed_image | timeout.
|
||||
-type image_error() :: efbig | enodata | limit | malformed_image | timeout.
|
||||
|
||||
-record(state, {limits = treap:empty() :: treap:treap()}).
|
||||
|
||||
@ -79,188 +65,80 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
|
||||
[]).
|
||||
|
||||
-spec captcha_text(undefined | binary()) -> binary().
|
||||
captcha_text(Lang) ->
|
||||
translate:translate(Lang, <<"Enter the text you see">>).
|
||||
|
||||
-spec mk_ocr_field(binary() | undefined, binary(), binary()) -> xdata_field().
|
||||
mk_ocr_field(Lang, CID, Type) ->
|
||||
URI = #media_uri{type = Type, uri = <<"cid:", CID/binary>>},
|
||||
#xdata_field{var = <<"ocr">>,
|
||||
type = 'text-single',
|
||||
label = captcha_text(Lang),
|
||||
required = true,
|
||||
sub_els = [#media{uri = [URI]}]}.
|
||||
|
||||
mk_field(Type, Var, Value) ->
|
||||
#xdata_field{type = Type, var = Var, values = [Value]}.
|
||||
|
||||
-spec create_captcha(binary(), jid(), jid(),
|
||||
binary(), any(), any()) -> {error, error()} |
|
||||
{ok, binary(), [xmlel()]}.
|
||||
binary(), any(), any()) -> {error, image_error()} |
|
||||
{ok, binary(), [text()], [xmlel()]}.
|
||||
|
||||
create_captcha(SID, From, To, Lang, Limiter, Args) ->
|
||||
case create_image(Limiter) of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = <<(randoms:get_string())/binary>>,
|
||||
B64Image = jlib:encode_base64((Image)),
|
||||
JID = jid:to_string(From),
|
||||
CID = <<"sha1+", (p1_sha:sha(Image))/binary,
|
||||
"@bob.xmpp.org">>,
|
||||
Data = #xmlel{name = <<"data">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
|
||||
{<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
|
||||
children = [{xmlcdata, B64Image}]},
|
||||
Captcha = #xmlel{name = <<"captcha">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CAPTCHA}],
|
||||
children =
|
||||
[#xmlel{name = <<"x">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_XDATA},
|
||||
{<<"type">>, <<"form">>}],
|
||||
children =
|
||||
[?VFIELD(<<"hidden">>,
|
||||
<<"FORM_TYPE">>,
|
||||
{xmlcdata, ?NS_CAPTCHA}),
|
||||
?VFIELD(<<"hidden">>, <<"from">>,
|
||||
{xmlcdata,
|
||||
jid:to_string(To)}),
|
||||
?VFIELD(<<"hidden">>,
|
||||
<<"challenge">>,
|
||||
{xmlcdata, Id}),
|
||||
?VFIELD(<<"hidden">>, <<"sid">>,
|
||||
{xmlcdata, SID}),
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"var">>, <<"ocr">>},
|
||||
{<<"label">>,
|
||||
?CAPTCHA_TEXT(Lang)}],
|
||||
children =
|
||||
[#xmlel{name =
|
||||
<<"required">>,
|
||||
attrs = [],
|
||||
children = []},
|
||||
#xmlel{name =
|
||||
<<"media">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_MEDIA}],
|
||||
children =
|
||||
[#xmlel{name
|
||||
=
|
||||
<<"uri">>,
|
||||
attrs
|
||||
=
|
||||
[{<<"type">>,
|
||||
Type}],
|
||||
children
|
||||
=
|
||||
[{xmlcdata,
|
||||
<<"cid:",
|
||||
CID/binary>>}]}]}]}]}]},
|
||||
BodyString1 = translate:translate(Lang,
|
||||
<<"Your messages to ~s are being blocked. "
|
||||
"To unblock them, visit ~s">>),
|
||||
BodyString = iolist_to_binary(io_lib:format(BodyString1,
|
||||
[JID, get_url(Id)])),
|
||||
Body = #xmlel{name = <<"body">>, attrs = [],
|
||||
children = [{xmlcdata, BodyString}]},
|
||||
OOB = #xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_OOB}],
|
||||
children =
|
||||
[#xmlel{name = <<"url">>, attrs = [],
|
||||
children = [{xmlcdata, get_url(Id)}]}]},
|
||||
CID = <<"sha1+", (p1_sha:sha(Image))/binary, "@bob.xmpp.org">>,
|
||||
Data = #bob_data{cid = CID, 'max-age' = 0, type = Type,
|
||||
data = Image},
|
||||
Fs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA),
|
||||
mk_field(hidden, <<"from">>, jid:to_string(To)),
|
||||
mk_field(hidden, <<"challenge">>, Id),
|
||||
mk_field(hidden, <<"sid">>, SID),
|
||||
mk_ocr_field(Lang, CID, Type)],
|
||||
X = #xdata{type = form, fields = Fs},
|
||||
Captcha = #xcaptcha{xdata = X},
|
||||
BodyString = {<<"Your messages to ~s are being blocked. "
|
||||
"To unblock them, visit ~s">>, [JID, get_url(Id)]},
|
||||
Body = xmpp:mk_text(BodyString, Lang),
|
||||
OOB = #oob_x{url = get_url(Id)},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
|
||||
{remove_id, Id}),
|
||||
ets:insert(captcha,
|
||||
#captcha{id = Id, pid = self(), key = Key, tref = Tref,
|
||||
args = Args}),
|
||||
{ok, Id, [Body, OOB, Captcha, Data]};
|
||||
{ok, Id, Body, [OOB, Captcha, Data]};
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
-spec create_captcha_x(binary(), jid(), binary(),
|
||||
any(), [xmlel()]) -> {ok, [xmlel()]} |
|
||||
{error, error()}.
|
||||
-spec create_captcha_x(binary(), jid(), binary(), any(), xdata()) ->
|
||||
{ok, xdata()} | {error, image_error()}.
|
||||
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls) ->
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls, []).
|
||||
|
||||
-spec create_captcha_x(binary(), jid(), binary(),
|
||||
any(), [xmlel()], [xmlel()]) -> {ok, [xmlel()]} |
|
||||
{error, error()}.
|
||||
|
||||
create_captcha_x(SID, To, Lang, Limiter, HeadEls,
|
||||
TailEls) ->
|
||||
create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) ->
|
||||
case create_image(Limiter) of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = <<(randoms:get_string())/binary>>,
|
||||
B64Image = jlib:encode_base64((Image)),
|
||||
CID = <<"sha1+", (p1_sha:sha(Image))/binary,
|
||||
"@bob.xmpp.org">>,
|
||||
Data = #xmlel{name = <<"data">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_BOB}, {<<"cid">>, CID},
|
||||
{<<"max-age">>, <<"0">>}, {<<"type">>, Type}],
|
||||
children = [{xmlcdata, B64Image}]},
|
||||
CID = <<"sha1+", (p1_sha:sha(Image))/binary, "@bob.xmpp.org">>,
|
||||
Data = #bob_data{cid = CID, 'max-age' = 0, type = Type, data = Image},
|
||||
HelpTxt = translate:translate(Lang,
|
||||
<<"If you don't see the CAPTCHA image here, "
|
||||
"visit the web page.">>),
|
||||
Imageurl = get_url(<<Id/binary, "/image">>),
|
||||
Captcha = #xmlel{name = <<"x">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_XDATA},
|
||||
{<<"type">>, <<"form">>}],
|
||||
children =
|
||||
[?VFIELD(<<"hidden">>, <<"FORM_TYPE">>,
|
||||
{xmlcdata, ?NS_CAPTCHA})
|
||||
| HeadEls]
|
||||
++
|
||||
[#xmlel{name = <<"field">>,
|
||||
attrs = [{<<"type">>, <<"fixed">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
HelpTxt}]}]},
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"hidden">>},
|
||||
{<<"var">>, <<"captchahidden">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<"workaround-for-psi">>}]}]},
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"text-single">>},
|
||||
{<<"label">>,
|
||||
translate:translate(Lang,
|
||||
<<"CAPTCHA web page">>)},
|
||||
{<<"var">>, <<"url">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"value">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
Imageurl}]}]},
|
||||
?VFIELD(<<"hidden">>, <<"from">>,
|
||||
{xmlcdata, jid:to_string(To)}),
|
||||
?VFIELD(<<"hidden">>, <<"challenge">>,
|
||||
{xmlcdata, Id}),
|
||||
?VFIELD(<<"hidden">>, <<"sid">>,
|
||||
{xmlcdata, SID}),
|
||||
#xmlel{name = <<"field">>,
|
||||
attrs =
|
||||
[{<<"var">>, <<"ocr">>},
|
||||
{<<"label">>,
|
||||
?CAPTCHA_TEXT(Lang)}],
|
||||
children =
|
||||
[#xmlel{name = <<"required">>,
|
||||
attrs = [], children = []},
|
||||
#xmlel{name = <<"media">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>,
|
||||
?NS_MEDIA}],
|
||||
children =
|
||||
[#xmlel{name =
|
||||
<<"uri">>,
|
||||
attrs =
|
||||
[{<<"type">>,
|
||||
Type}],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<"cid:",
|
||||
CID/binary>>}]}]}]}]
|
||||
++ TailEls},
|
||||
NewFs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA)|Fs] ++
|
||||
[#xdata_field{type = fixed, values = [HelpTxt]},
|
||||
#xdata_field{type = hidden, var = <<"captchahidden">>,
|
||||
values = [<<"workaround-for-psi">>]},
|
||||
#xdata_field{type = 'text-single', var = <<"url">>,
|
||||
label = translate:translate(
|
||||
Lang, <<"CAPTCHA web page">>),
|
||||
values = [Imageurl]},
|
||||
mk_field(hidden, <<"from">>, jid:to_string(To)),
|
||||
mk_field(hidden, <<"challenge">>, Id),
|
||||
mk_field(hidden, <<"sid">>, SID),
|
||||
mk_ocr_field(Lang, CID, Type)],
|
||||
Captcha = X#xdata{type = form, fields = NewFs},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE,
|
||||
{remove_id, Id}),
|
||||
ets:insert(captcha,
|
||||
@ -281,7 +159,7 @@ build_captcha_html(Id, Lang) ->
|
||||
attrs =
|
||||
[{<<"src">>, get_url(<<Id/binary, "/image">>)}],
|
||||
children = []},
|
||||
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
|
||||
TextEl = {xmlcdata, captcha_text(Lang)},
|
||||
IdEl = #xmlel{name = <<"input">>,
|
||||
attrs =
|
||||
[{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>},
|
||||
@ -317,27 +195,24 @@ build_captcha_html(Id, Lang) ->
|
||||
_ -> captcha_not_found
|
||||
end.
|
||||
|
||||
-spec process_reply(xmlel()) -> ok | {error, bad_match | not_found | malformed}.
|
||||
-spec process_reply(xmpp_element()) -> ok | {error, bad_match | not_found | malformed}.
|
||||
|
||||
process_reply(#xmlel{} = El) ->
|
||||
case fxml:get_subtag(El, <<"x">>) of
|
||||
false -> {error, malformed};
|
||||
Xdata ->
|
||||
Fields = jlib:parse_xdata_submit(Xdata),
|
||||
case catch {proplists:get_value(<<"challenge">>,
|
||||
Fields),
|
||||
proplists:get_value(<<"ocr">>, Fields)}
|
||||
of
|
||||
{[Id | _], [OCR | _]} ->
|
||||
case check_captcha(Id, OCR) of
|
||||
captcha_valid -> ok;
|
||||
captcha_non_valid -> {error, bad_match};
|
||||
captcha_not_found -> {error, not_found}
|
||||
end;
|
||||
_ -> {error, malformed}
|
||||
end
|
||||
process_reply(#xdata{} = X) ->
|
||||
case {xmpp_util:get_xdata_values(<<"challenge">>, X),
|
||||
xmpp_util:get_xdata_values(<<"ocr">>, X)} of
|
||||
{[Id], [OCR]} ->
|
||||
case check_captcha(Id, OCR) of
|
||||
captcha_valid -> ok;
|
||||
captcha_non_valid -> {error, bad_match};
|
||||
captcha_not_found -> {error, not_found}
|
||||
end;
|
||||
_ ->
|
||||
{error, malformed}
|
||||
end;
|
||||
process_reply(_) -> {error, malformed}.
|
||||
process_reply(#xcaptcha{xdata = #xdata{} = X}) ->
|
||||
process_reply(X);
|
||||
process_reply(_) ->
|
||||
{error, malformed}.
|
||||
|
||||
process(_Handlers,
|
||||
#request{method = 'GET', lang = Lang,
|
||||
@ -515,7 +390,7 @@ get_url(Str) ->
|
||||
end.
|
||||
|
||||
get_transfer_protocol(PortString) ->
|
||||
PortNumber = jlib:binary_to_integer(PortString),
|
||||
PortNumber = binary_to_integer(PortString),
|
||||
PortListeners = get_port_listeners(PortNumber),
|
||||
get_captcha_transfer_protocol(PortListeners).
|
||||
|
||||
|
@ -277,7 +277,11 @@ get_commands_spec() ->
|
||||
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
|
||||
result_example = ok}].
|
||||
init() ->
|
||||
mnesia:create_table(ejabberd_commands,
|
||||
try mnesia:transform_table(ejabberd_commands, ignore,
|
||||
record_info(fields, ejabberd_commands))
|
||||
catch exit:{aborted, {no_exists, _}} -> ok
|
||||
end,
|
||||
ejabberd_mnesia:create(?MODULE, ejabberd_commands,
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, ejabberd_commands)},
|
||||
|
@ -5,7 +5,7 @@
|
||||
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
@ -41,7 +41,7 @@
|
||||
-define(SPAN(N, V), ?TAG_R(span, ??N, V)).
|
||||
|
||||
-define(STR(A), ?SPAN(str,[<<"\"">>, A, <<"\"">>])).
|
||||
-define(NUM(A), ?SPAN(num,jlib:integer_to_binary(A))).
|
||||
-define(NUM(A), ?SPAN(num,integer_to_binary(A))).
|
||||
-define(FIELD(A), ?SPAN(field,A)).
|
||||
-define(ID(A), ?SPAN(id,A)).
|
||||
-define(OP(A), ?SPAN(op,A)).
|
||||
@ -171,7 +171,7 @@ xml_gen({Name, integer}, Int, Indent, HTMLOutput) ->
|
||||
[?XML(member, Indent,
|
||||
[?XML_L(name, Indent, 1, ?ID_A(Name)),
|
||||
?XML(value, Indent, 1,
|
||||
[?XML_L(integer, Indent, 2, ?ID(jlib:integer_to_binary(Int)))])])];
|
||||
[?XML_L(integer, Indent, 2, ?ID(integer_to_binary(Int)))])])];
|
||||
xml_gen({Name, string}, Str, Indent, HTMLOutput) ->
|
||||
[?XML(member, Indent,
|
||||
[?XML_L(name, Indent, 1, ?ID_A(Name)),
|
||||
|
@ -104,7 +104,7 @@ mnesia_init() ->
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
mnesia:create_table(local_config,
|
||||
ejabberd_mnesia:create(?MODULE, local_config,
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, local_config)}]),
|
||||
@ -1300,7 +1300,7 @@ convert_table_to_binary(Tab, Fields, Type, DetectFun, ConvertFun) ->
|
||||
?INFO_MSG("Converting '~s' table from strings to binaries.", [Tab]),
|
||||
TmpTab = list_to_atom(atom_to_list(Tab) ++ "_tmp_table"),
|
||||
catch mnesia:delete_table(TmpTab),
|
||||
case mnesia:create_table(TmpTab,
|
||||
case ejabberd_mnesia:create(?MODULE, TmpTab,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, Type},
|
||||
{local_content, true},
|
||||
|
@ -311,7 +311,7 @@ try_call_command(Args, Auth, AccessCommands, Version) ->
|
||||
end.
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
|
||||
call_command([CmdString | Args], Auth, AccessCommands, Version) ->
|
||||
call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
|
||||
CmdStringU = ejabberd_regexp:greplace(
|
||||
list_to_binary(CmdString), <<"-">>, <<"_">>),
|
||||
Command = list_to_atom(binary_to_list(CmdStringU)),
|
||||
|
@ -42,6 +42,7 @@
|
||||
change_shaper/2,
|
||||
monitor/1,
|
||||
get_sockmod/1,
|
||||
get_transport/1,
|
||||
get_peer_certificate/1,
|
||||
get_verify_result/1,
|
||||
close/1,
|
||||
@ -118,6 +119,9 @@ monitor(FsmRef) -> erlang:monitor(process, FsmRef).
|
||||
get_sockmod(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_sockmod).
|
||||
|
||||
get_transport(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_transport).
|
||||
|
||||
get_peer_certificate(FsmRef) ->
|
||||
gen_server:call(FsmRef, get_peer_certificate).
|
||||
|
||||
@ -186,6 +190,19 @@ handle_call({change_shaper, Shaper}, _From, State) ->
|
||||
handle_call(get_sockmod, _From, State) ->
|
||||
Reply = State#state.sockmod,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_call(get_transport, _From, State) ->
|
||||
Reply = case State#state.sockmod of
|
||||
gen_tcp -> tcp;
|
||||
fast_tls -> tls;
|
||||
ezlib ->
|
||||
case ezlib:get_sockmod(State#state.socket) of
|
||||
tcp -> tcp_zlib;
|
||||
tls -> tls_zlib
|
||||
end;
|
||||
ejabberd_http_bind -> http_bind;
|
||||
ejabberd_http_ws -> websocket
|
||||
end,
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
handle_call(get_peer_certificate, _From, State) ->
|
||||
Reply = fast_tls:get_peer_certificate(State#state.socket),
|
||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||
|
@ -39,7 +39,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
@ -257,7 +257,7 @@ process_header(State, Data) ->
|
||||
request_headers = add_header(Name, Auth, State)};
|
||||
{ok,
|
||||
{http_header, _, 'Content-Length' = Name, _, SLen}} ->
|
||||
case catch jlib:binary_to_integer(SLen) of
|
||||
case catch binary_to_integer(SLen) of
|
||||
Len when is_integer(Len) ->
|
||||
State#state{request_content_length = Len,
|
||||
request_headers = add_header(Name, SLen, State)};
|
||||
@ -332,10 +332,10 @@ get_transfer_protocol(SockMod, HostPort) ->
|
||||
case {SockMod, PortList} of
|
||||
{gen_tcp, []} -> {Host, 80, http};
|
||||
{gen_tcp, [Port]} ->
|
||||
{Host, jlib:binary_to_integer(Port), http};
|
||||
{Host, binary_to_integer(Port), http};
|
||||
{fast_tls, []} -> {Host, 443, https};
|
||||
{fast_tls, [Port]} ->
|
||||
{Host, jlib:binary_to_integer(Port), https}
|
||||
{Host, binary_to_integer(Port), https}
|
||||
end.
|
||||
|
||||
%% XXX bard: search through request handlers looking for one that
|
||||
@ -399,17 +399,17 @@ extract_path_query(#state{request_method = Method,
|
||||
case recv_data(State, Len) of
|
||||
error -> {State, false};
|
||||
{NewState, Data} ->
|
||||
?DEBUG("client data: ~p~n", [Data]),
|
||||
case catch url_decode_q_split(Path) of
|
||||
{'EXIT', _} -> {NewState, false};
|
||||
{NPath, _Query} ->
|
||||
LPath = normalize_path([NPE
|
||||
|| NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
|
||||
LQuery = case catch parse_urlencoded(Data) of
|
||||
{'EXIT', _Reason} -> [];
|
||||
LQ -> LQ
|
||||
end,
|
||||
{NewState, {LPath, LQuery, Data}}
|
||||
?DEBUG("client data: ~p~n", [Data]),
|
||||
case catch url_decode_q_split(Path) of
|
||||
{'EXIT', _} -> {NewState, false};
|
||||
{NPath, _Query} ->
|
||||
LPath = normalize_path([NPE
|
||||
|| NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
|
||||
LQuery = case catch parse_urlencoded(Data) of
|
||||
{'EXIT', _Reason} -> [];
|
||||
LQ -> LQ
|
||||
end,
|
||||
{NewState, {LPath, LQuery, Data}}
|
||||
end
|
||||
end;
|
||||
extract_path_query(State) ->
|
||||
@ -550,12 +550,12 @@ make_xhtml_output(State, Status, Headers, XHTML) ->
|
||||
of
|
||||
{value, _} ->
|
||||
[{<<"Content-Length">>,
|
||||
iolist_to_binary(integer_to_list(byte_size(Data)))}
|
||||
integer_to_binary(byte_size(Data))}
|
||||
| Headers];
|
||||
_ ->
|
||||
[{<<"Content-Type">>, <<"text/html; charset=utf-8">>},
|
||||
{<<"Content-Length">>,
|
||||
iolist_to_binary(integer_to_list(byte_size(Data)))}
|
||||
integer_to_binary(byte_size(Data))}
|
||||
| Headers]
|
||||
end,
|
||||
HeadersOut = case {State#state.request_version,
|
||||
@ -577,7 +577,7 @@ make_xhtml_output(State, Status, Headers, XHTML) ->
|
||||
end,
|
||||
HeadersOut),
|
||||
SL = [Version,
|
||||
iolist_to_binary(integer_to_list(Status)), <<" ">>,
|
||||
integer_to_binary(Status), <<" ">>,
|
||||
code_to_phrase(Status), <<"\r\n">>],
|
||||
Data2 = case State#state.request_method of
|
||||
'HEAD' -> <<"">>;
|
||||
@ -595,12 +595,12 @@ make_text_output(State, Status, Reason, Headers, Text) ->
|
||||
of
|
||||
{value, _} ->
|
||||
[{<<"Content-Length">>,
|
||||
jlib:integer_to_binary(byte_size(Data))}
|
||||
integer_to_binary(byte_size(Data))}
|
||||
| Headers];
|
||||
_ ->
|
||||
[{<<"Content-Type">>, <<"text/html; charset=utf-8">>},
|
||||
{<<"Content-Length">>,
|
||||
jlib:integer_to_binary(byte_size(Data))}
|
||||
integer_to_binary(byte_size(Data))}
|
||||
| Headers]
|
||||
end,
|
||||
HeadersOut = case {State#state.request_version,
|
||||
@ -625,7 +625,7 @@ make_text_output(State, Status, Reason, Headers, Text) ->
|
||||
_ -> Reason
|
||||
end,
|
||||
SL = [Version,
|
||||
jlib:integer_to_binary(Status), <<" ">>,
|
||||
integer_to_binary(Status), <<" ">>,
|
||||
NewReason, <<"\r\n">>],
|
||||
Data2 = case State#state.request_method of
|
||||
'HEAD' -> <<"">>;
|
||||
|
@ -62,7 +62,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
@ -125,8 +125,6 @@
|
||||
%% Wait 100ms before continue processing, to allow the client provide more related stanzas.
|
||||
-define(BOSH_VERSION, <<"1.8">>).
|
||||
|
||||
-define(NS_CLIENT, <<"jabber:client">>).
|
||||
|
||||
-define(NS_BOSH, <<"urn:xmpp:xbosh">>).
|
||||
|
||||
-define(NS_HTTP_BIND,
|
||||
@ -856,7 +854,7 @@ rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) ->
|
||||
%% We did not miss any packet, we can process it immediately:
|
||||
NewRid == OldRid + 1 ->
|
||||
case catch
|
||||
jlib:binary_to_integer(fxml:get_attr_s(<<"pause">>,
|
||||
binary_to_integer(fxml:get_attr_s(<<"pause">>,
|
||||
Attrs))
|
||||
of
|
||||
{'EXIT', _} -> {true, 0};
|
||||
@ -974,21 +972,17 @@ prepare_outpacket_response(#http_bind{id = Sid,
|
||||
[{<<"xmlns">>, ?NS_HTTP_BIND},
|
||||
{<<"sid">>, Sid},
|
||||
{<<"wait">>,
|
||||
iolist_to_binary(integer_to_list(Wait))},
|
||||
integer_to_binary(Wait)},
|
||||
{<<"requests">>,
|
||||
iolist_to_binary(integer_to_list(Hold
|
||||
+
|
||||
1))},
|
||||
integer_to_binary(Hold + 1)},
|
||||
{<<"inactivity">>,
|
||||
iolist_to_binary(integer_to_list(trunc(MaxInactivity
|
||||
/
|
||||
1000)))},
|
||||
integer_to_binary(
|
||||
trunc(MaxInactivity / 1000))},
|
||||
{<<"maxpause">>,
|
||||
iolist_to_binary(integer_to_list(MaxPause))},
|
||||
integer_to_binary(MaxPause)},
|
||||
{<<"polling">>,
|
||||
iolist_to_binary(integer_to_list(trunc((?MIN_POLLING)
|
||||
/
|
||||
1000000)))},
|
||||
integer_to_binary(
|
||||
trunc((?MIN_POLLING) / 1000000))},
|
||||
{<<"ver">>, ?BOSH_VERSION},
|
||||
{<<"from">>, From},
|
||||
{<<"secure">>, <<"true">>}]
|
||||
@ -1122,7 +1116,7 @@ parse_request(Data, PayloadSize, MaxStanzaSize) ->
|
||||
if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request};
|
||||
true ->
|
||||
case catch
|
||||
jlib:binary_to_integer(fxml:get_attr_s(<<"rid">>,
|
||||
binary_to_integer(fxml:get_attr_s(<<"rid">>,
|
||||
Attrs))
|
||||
of
|
||||
{'EXIT', _} -> {error, bad_request};
|
||||
|
@ -39,7 +39,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
@ -60,6 +60,8 @@
|
||||
%% This value is used in SIP and Megaco for a transaction lifetime.
|
||||
-define(IQ_TIMEOUT, 32000).
|
||||
|
||||
-type ping_timeout() :: non_neg_integer() | undefined.
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@ -71,37 +73,30 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
|
||||
[]).
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = XMLNS, lang = Lang} ->
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
ResIQ = Module:Function(From, To, IQ),
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
true -> ok
|
||||
end;
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply ->
|
||||
IQReply = jlib:iq_query_or_response_info(Packet),
|
||||
process_iq_reply(From, To, IQReply);
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
ok
|
||||
end.
|
||||
-spec process_iq(jid(), jid(), iq()) -> any().
|
||||
process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
|
||||
when T == get; T == set ->
|
||||
XMLNS = xmpp:get_ns(El),
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, no_queue,
|
||||
From, To, Packet);
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, Packet);
|
||||
[] ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = xmpp:err_service_unavailable(Txt, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end;
|
||||
process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
|
||||
Err = xmpp:err_bad_request(),
|
||||
ejabberd_router:route_error(To, From, Packet, Err);
|
||||
process_iq(From, To, #iq{type = T} = Packet) when T == result; T == error ->
|
||||
process_iq_reply(From, To, Packet).
|
||||
|
||||
-spec process_iq_reply(jid(), jid(), iq()) -> any().
|
||||
process_iq_reply(From, To, #iq{id = ID} = IQ) ->
|
||||
case get_iq_callback(ID) of
|
||||
{ok, undefined, Function} -> Function(IQ), ok;
|
||||
@ -110,6 +105,7 @@ process_iq_reply(From, To, #iq{id = ID} = IQ) ->
|
||||
_ -> nothing
|
||||
end.
|
||||
|
||||
-spec route(jid(), jid(), stanza()) -> any().
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
@ -118,26 +114,32 @@ route(From, To, Packet) ->
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
-spec route_iq(jid(), jid(), iq(), function()) -> any().
|
||||
route_iq(From, To, IQ, F) ->
|
||||
route_iq(From, To, IQ, F, undefined).
|
||||
|
||||
-spec route_iq(jid(), jid(), iq(), function(), ping_timeout()) -> any().
|
||||
route_iq(From, To, #iq{type = Type} = IQ, F, Timeout)
|
||||
when is_function(F) ->
|
||||
Packet = if Type == set; Type == get ->
|
||||
ID = randoms:get_string(),
|
||||
Host = From#jid.lserver,
|
||||
register_iq_response_handler(Host, ID, undefined, F, Timeout),
|
||||
jlib:iq_to_xml(IQ#iq{id = ID});
|
||||
IQ#iq{id = ID};
|
||||
true ->
|
||||
jlib:iq_to_xml(IQ)
|
||||
IQ
|
||||
end,
|
||||
ejabberd_router:route(From, To, Packet).
|
||||
|
||||
-spec register_iq_response_handler(binary(), binary(), module(),
|
||||
atom() | function()) -> any().
|
||||
register_iq_response_handler(Host, ID, Module,
|
||||
Function) ->
|
||||
register_iq_response_handler(Host, ID, Module, Function,
|
||||
undefined).
|
||||
|
||||
-spec register_iq_response_handler(binary(), binary(), module(),
|
||||
atom() | function(), ping_timeout()) -> any().
|
||||
register_iq_response_handler(_Host, ID, Module,
|
||||
Function, Timeout0) ->
|
||||
Timeout = case Timeout0 of
|
||||
@ -150,29 +152,40 @@ register_iq_response_handler(_Host, ID, Module,
|
||||
function = Function,
|
||||
timer = TRef}).
|
||||
|
||||
-spec register_iq_handler(binary(), binary(), module(), function()) -> any().
|
||||
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||
ejabberd_local !
|
||||
{register_iq_handler, Host, XMLNS, Module, Fun}.
|
||||
|
||||
-spec register_iq_handler(binary(), binary(), module(), function(),
|
||||
gen_iq_handler:opts()) -> any().
|
||||
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
|
||||
ejabberd_local !
|
||||
{register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
|
||||
|
||||
-spec unregister_iq_response_handler(binary(), binary()) -> ok.
|
||||
unregister_iq_response_handler(_Host, ID) ->
|
||||
catch get_iq_callback(ID), ok.
|
||||
|
||||
-spec unregister_iq_handler(binary(), binary()) -> any().
|
||||
unregister_iq_handler(Host, XMLNS) ->
|
||||
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
|
||||
|
||||
-spec refresh_iq_handlers() -> any().
|
||||
refresh_iq_handlers() ->
|
||||
ejabberd_local ! refresh_iq_handlers.
|
||||
|
||||
-spec bounce_resource_packet(jid(), jid(), stanza()) -> stop.
|
||||
bounce_resource_packet(_From, #jid{lresource = <<"">>}, #presence{}) ->
|
||||
ok;
|
||||
bounce_resource_packet(_From, #jid{lresource = <<"">>},
|
||||
#message{type = headline}) ->
|
||||
ok;
|
||||
bounce_resource_packet(From, To, Packet) ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Txt = <<"No available resource found">>,
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERRT_ITEM_NOT_FOUND(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
Err = xmpp:err_item_not_found(Txt, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err),
|
||||
stop.
|
||||
|
||||
%%====================================================================
|
||||
@ -192,7 +205,7 @@ init([]) ->
|
||||
?MYHOSTS),
|
||||
catch ets:new(?IQTABLE, [named_table, public]),
|
||||
update_table(),
|
||||
mnesia:create_table(iq_response,
|
||||
ejabberd_mnesia:create(?MODULE, iq_response,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, iq_response)}]),
|
||||
mnesia:add_table_copy(iq_response, node(), ram_copies),
|
||||
@ -261,50 +274,36 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
-spec do_route(jid(), jid(), stanza()) -> any().
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket "
|
||||
"~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
Type = xmpp:get_type(Packet),
|
||||
if To#jid.luser /= <<"">> ->
|
||||
ejabberd_sm:route(From, To, Packet);
|
||||
To#jid.lresource == <<"">> ->
|
||||
#xmlel{name = Name} = Packet,
|
||||
case Name of
|
||||
<<"iq">> -> process_iq(From, To, Packet);
|
||||
<<"message">> ->
|
||||
#xmlel{attrs = Attrs} = Packet,
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"headline">> -> ok;
|
||||
<<"error">> -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
<<"presence">> -> ok;
|
||||
_ -> ok
|
||||
end;
|
||||
ejabberd_sm:route(From, To, Packet);
|
||||
is_record(Packet, iq), To#jid.lresource == <<"">> ->
|
||||
process_iq(From, To, Packet);
|
||||
Type == result; Type == error ->
|
||||
ok;
|
||||
true ->
|
||||
#xmlel{attrs = Attrs} = Packet,
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
ejabberd_hooks:run(local_send_to_resource_hook,
|
||||
To#jid.lserver, [From, To, Packet])
|
||||
end
|
||||
ejabberd_hooks:run(local_send_to_resource_hook,
|
||||
To#jid.lserver, [From, To, Packet])
|
||||
end.
|
||||
|
||||
-spec update_table() -> ok.
|
||||
update_table() ->
|
||||
case catch mnesia:table_info(iq_response, attributes) of
|
||||
[id, module, function] ->
|
||||
mnesia:delete_table(iq_response);
|
||||
mnesia:delete_table(iq_response),
|
||||
ok;
|
||||
[id, module, function, timer] ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec get_iq_callback(binary()) -> {ok, module(), atom() | function()} | error.
|
||||
get_iq_callback(ID) ->
|
||||
case mnesia:dirty_read(iq_response, ID) of
|
||||
[#iq_response{module = Module, timer = TRef,
|
||||
@ -316,9 +315,11 @@ get_iq_callback(ID) ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec process_iq_timeout(binary()) -> any().
|
||||
process_iq_timeout(ID) ->
|
||||
spawn(fun process_iq_timeout/0) ! ID.
|
||||
|
||||
-spec process_iq_timeout() -> any().
|
||||
process_iq_timeout() ->
|
||||
receive
|
||||
ID ->
|
||||
@ -332,6 +333,7 @@ process_iq_timeout() ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec cancel_timer(reference()) -> ok.
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
|
169
src/ejabberd_mnesia.erl
Normal file
169
src/ejabberd_mnesia.erl
Normal file
@ -0,0 +1,169 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mnesia_mnesia.erl
|
||||
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% Purpose : Handle configurable mnesia schema
|
||||
%%% Created : 17 Nov 2016 by Christophe Romain <christophe.romain@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% This module should be used everywhere ejabberd creates a mnesia table
|
||||
%%% to make the schema customizable without code change
|
||||
%%% Just apply this change in ejabberd modules
|
||||
%%% s/ejabberd_mnesia:create(?MODULE, /ejabberd_mnesia:create(?MODULE, /
|
||||
|
||||
-module(ejabberd_mnesia).
|
||||
-author('christophe.romain@process-one.net').
|
||||
-export([create/3, reset/2, update/2]).
|
||||
|
||||
-define(STORAGE_TYPES, [disc_copies, disc_only_copies, ram_copies]).
|
||||
-define(NEED_RESET, [local_content, type]).
|
||||
|
||||
create(Module, Name, TabDef) ->
|
||||
Schema = schema(Module, Name, TabDef),
|
||||
{attributes, Attrs} = lists:keyfind(attributes, 1, Schema),
|
||||
case catch mnesia:table_info(Name, attributes) of
|
||||
{'EXIT', _} ->
|
||||
mnesia:create_table(Name, Schema);
|
||||
Attrs ->
|
||||
case need_reset(TabDef, Schema) of
|
||||
true -> reset(Name, Schema);
|
||||
false -> update(Name, Schema)
|
||||
end;
|
||||
OldAttrs ->
|
||||
Fun = case lists:member({transform,1}, Module:module_info(exports)) of
|
||||
true -> fun(Old) -> Module:transform(Old) end;
|
||||
false -> fun(Old) -> transform(OldAttrs, Attrs, Old) end
|
||||
end,
|
||||
mnesia:transform_table(Name, Fun, Attrs)
|
||||
end.
|
||||
|
||||
reset(Name, TabDef) ->
|
||||
mnesia:delete_table(Name),
|
||||
ejabberd_mnesia:create(?MODULE, Name, TabDef).
|
||||
|
||||
update(Name, TabDef) ->
|
||||
Storage = mnesia:table_info(Name, storage_type),
|
||||
NewStorage = lists:foldl(
|
||||
fun({Key, _}, Acc) ->
|
||||
case lists:member(Key, ?STORAGE_TYPES) of
|
||||
true -> Key;
|
||||
false -> Acc
|
||||
end
|
||||
end, Storage, TabDef),
|
||||
R1 = if Storage=/=NewStorage ->
|
||||
mnesia:change_table_copy_type(Name, node(), NewStorage);
|
||||
true ->
|
||||
{atomic, ok}
|
||||
end,
|
||||
Indexes = mnesia:table_info(Name, index),
|
||||
NewIndexes = proplists:get_value(index, TabDef, []),
|
||||
[mnesia:del_table_index(Name, Attr)
|
||||
|| Attr <- Indexes--NewIndexes],
|
||||
R2 = [mnesia:add_table_index(Name, Attr)
|
||||
|| Attr <- NewIndexes--Indexes],
|
||||
lists:foldl(
|
||||
fun({atomic, ok}, Acc) -> Acc;
|
||||
(Error, _Acc) -> Error
|
||||
end, {atomic, ok}, [R1|R2]).
|
||||
|
||||
%
|
||||
% utilities
|
||||
%
|
||||
|
||||
schema(Module, Name, TabDef) ->
|
||||
case parse(Module) of
|
||||
{ok, CustomDefs} ->
|
||||
case lists:keyfind(Name, 1, CustomDefs) of
|
||||
{Name, CustomDef} -> merge(TabDef, CustomDef);
|
||||
_ -> TabDef
|
||||
end;
|
||||
_ ->
|
||||
TabDef
|
||||
end.
|
||||
|
||||
merge(TabDef, CustomDef) ->
|
||||
{CustomKeys, _} = lists:unzip(CustomDef),
|
||||
CleanDef = lists:foldl(
|
||||
fun(Elem, Acc) ->
|
||||
case lists:member(Elem, ?STORAGE_TYPES) of
|
||||
true ->
|
||||
lists:foldl(
|
||||
fun(Key, CleanAcc) ->
|
||||
lists:keydelete(Key, 1, CleanAcc)
|
||||
end, Acc, ?STORAGE_TYPES);
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
end, TabDef, CustomKeys),
|
||||
lists:ukeymerge(1,
|
||||
lists:ukeysort(1, CustomDef),
|
||||
lists:ukeysort(1, CleanDef)).
|
||||
|
||||
parse(Module) ->
|
||||
Path = case os:getenv("EJABBERD_SCHEMA_PATH") of
|
||||
false ->
|
||||
case code:priv_dir(ejabberd) of
|
||||
{error, _} -> "schema"; % $SPOOL_DIR/schema
|
||||
Priv -> filename:join(Priv, "schema")
|
||||
end;
|
||||
CustomDir ->
|
||||
CustomDir
|
||||
end,
|
||||
File = filename:join(Path, atom_to_list(Module)++".mnesia"),
|
||||
case file:consult(File) of
|
||||
{ok, Terms} -> parse(Terms, []);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
parse([], Acc) ->
|
||||
{ok, lists:reverse(Acc)};
|
||||
parse([{Name, Storage, TabDef}|Tail], Acc)
|
||||
when is_atom(Name),
|
||||
is_atom(Storage),
|
||||
is_list(TabDef) ->
|
||||
NewDef = case lists:member(Storage, ?STORAGE_TYPES) of
|
||||
true -> [{Storage, [node()]} | TabDef];
|
||||
false -> TabDef
|
||||
end,
|
||||
parse(Tail, [{Name, NewDef} | Acc]);
|
||||
parse([Other|_], _) ->
|
||||
{error, {invalid, Other}}.
|
||||
|
||||
need_reset(FromDef, ToDef) ->
|
||||
ValuesF = [lists:keyfind(Key, 1, FromDef) || Key <- ?NEED_RESET],
|
||||
ValuesT = [lists:keyfind(Key, 1, ToDef) || Key <- ?NEED_RESET],
|
||||
lists:foldl(
|
||||
fun({Val, Val}, Acc) -> Acc;
|
||||
({_, false}, Acc) -> Acc;
|
||||
({_, _}, _) -> true
|
||||
end, false, lists:zip(ValuesF, ValuesT)).
|
||||
|
||||
transform(OldAttrs, Attrs, Old) ->
|
||||
[Name|OldValues] = tuple_to_list(Old),
|
||||
Before = lists:zip(OldAttrs, OldValues),
|
||||
After = lists:foldl(
|
||||
fun(Attr, Acc) ->
|
||||
case lists:keyfind(Attr, 1, Before) of
|
||||
false -> [{Attr, undefined}|Acc];
|
||||
Value -> [Value|Acc]
|
||||
end
|
||||
end, [], lists:reverse(Attrs)),
|
||||
{Attrs, NewRecord} = lists:unzip(After),
|
||||
list_to_tuple([Name|NewRecord]).
|
@ -51,7 +51,7 @@
|
||||
|
||||
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@ -140,11 +140,11 @@ oauth_issue_token(Jid, TTLSeconds, ScopesString) ->
|
||||
case oauth2:authorize_password({Username, Server}, Scopes, admin_generated) of
|
||||
{ok, {_Ctx,Authorization}} ->
|
||||
{ok, {_AppCtx2, Response}} = oauth2:issue_token(Authorization, [{expiry_time, TTLSeconds}]),
|
||||
{ok, AccessToken} = oauth2_response:access_token(Response),
|
||||
{ok, VerifiedScope} = oauth2_response:scope(Response),
|
||||
{ok, AccessToken} = oauth2_response:access_token(Response),
|
||||
{ok, VerifiedScope} = oauth2_response:scope(Response),
|
||||
{AccessToken, VerifiedScope, integer_to_list(TTLSeconds) ++ " seconds"};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end;
|
||||
error ->
|
||||
{error, "Invalid JID: " ++ Jid}
|
||||
@ -212,12 +212,12 @@ authenticate_user({User, Server}, Ctx) ->
|
||||
allow ->
|
||||
case Ctx of
|
||||
{password, Password} ->
|
||||
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
|
||||
true ->
|
||||
{ok, {Ctx, {user, User, Server}}};
|
||||
false ->
|
||||
{error, badpass}
|
||||
end;
|
||||
case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
|
||||
true ->
|
||||
{ok, {Ctx, {user, User, Server}}};
|
||||
false ->
|
||||
{error, badpass}
|
||||
end;
|
||||
admin_generated ->
|
||||
{ok, {Ctx, {user, User, Server}}}
|
||||
end;
|
||||
@ -291,7 +291,7 @@ associate_access_token(AccessToken, Context, AppContext) ->
|
||||
%% It always pass the global configured value. Here we use the app context to pass the per-case
|
||||
%% ttl if we want to override it.
|
||||
seconds_since_epoch(ExpiresIn)
|
||||
end,
|
||||
end,
|
||||
{user, User, Server} = proplists:get_value(<<"resource_owner">>, Context, <<"">>),
|
||||
Scope = proplists:get_value(<<"scope">>, Context, []),
|
||||
R = #oauth_token{
|
||||
@ -335,7 +335,7 @@ check_token(User, Server, ScopeList, Token) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
case lookup(Token) of
|
||||
{ok, #oauth_token{us = {LUser, LServer},
|
||||
scope = TokenScope,
|
||||
scope = TokenScope,
|
||||
expire = Expire}} ->
|
||||
{MegaSecs, Secs, _} = os:timestamp(),
|
||||
TS = 1000000 * MegaSecs + Secs,
|
||||
@ -355,7 +355,7 @@ check_token(User, Server, ScopeList, Token) ->
|
||||
check_token(ScopeList, Token) ->
|
||||
case lookup(Token) of
|
||||
{ok, #oauth_token{us = US,
|
||||
scope = TokenScope,
|
||||
scope = TokenScope,
|
||||
expire = Expire}} ->
|
||||
{MegaSecs, Secs, _} = os:timestamp(),
|
||||
TS = 1000000 * MegaSecs + Secs,
|
||||
@ -367,7 +367,7 @@ check_token(ScopeList, Token) ->
|
||||
ScopeList) of
|
||||
true -> {ok, user, US};
|
||||
false -> {false, no_matching_scope}
|
||||
end;
|
||||
end;
|
||||
true ->
|
||||
{false, expired}
|
||||
end;
|
||||
@ -471,7 +471,7 @@ process(_Handlers,
|
||||
[{<<"href">>, <<"https://www.ejabberd.im">>},
|
||||
{<<"title">>, <<"ejabberd XMPP server">>}],
|
||||
<<"ejabberd">>),
|
||||
?C(" is maintained by "),
|
||||
?C(<<" is maintained by ">>),
|
||||
?XAC(<<"a">>,
|
||||
[{<<"href">>, <<"https://www.process-one.net">>},
|
||||
{<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}],
|
||||
@ -494,7 +494,7 @@ process(_Handlers,
|
||||
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
|
||||
ExpiresIn = case TTL of
|
||||
<<>> -> undefined;
|
||||
_ -> jlib:binary_to_integer(TTL)
|
||||
_ -> binary_to_integer(TTL)
|
||||
end,
|
||||
case oauth2:authorize_password({Username, Server},
|
||||
ClientId,
|
||||
@ -556,7 +556,7 @@ process(_Handlers,
|
||||
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
|
||||
ExpiresIn = case TTL of
|
||||
<<>> -> undefined;
|
||||
_ -> jlib:binary_to_integer(TTL)
|
||||
_ -> binary_to_integer(TTL)
|
||||
end,
|
||||
case oauth2:authorize_password({Username, Server},
|
||||
Scope,
|
||||
@ -732,7 +732,7 @@ css() ->
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.container > .section {
|
||||
.container > .section {
|
||||
background: #424A55;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@
|
||||
-include("ejabberd_oauth.hrl").
|
||||
|
||||
init() ->
|
||||
mnesia:create_table(oauth_token,
|
||||
ejabberd_mnesia:create(?MODULE, oauth_token,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, oauth_token)}]),
|
||||
|
@ -35,7 +35,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_oauth.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("jid.hrl").
|
||||
|
||||
init() ->
|
||||
rest:start(?MYNAME),
|
||||
|
@ -36,7 +36,7 @@
|
||||
-include("ejabberd_oauth.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("jid.hrl").
|
||||
|
||||
init() ->
|
||||
ok.
|
||||
|
@ -48,7 +48,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
@ -196,7 +196,7 @@ format_scram_password({StoredKey, ServerKey, Salt, IterationCount}) ->
|
||||
StoredKeyB64 = base64:encode(StoredKey),
|
||||
ServerKeyB64 = base64:encode(ServerKey),
|
||||
SaltB64 = base64:encode(Salt),
|
||||
IterationCountBin = list_to_binary(integer_to_list(IterationCount)),
|
||||
IterationCountBin = (integer_to_binary(IterationCount)),
|
||||
<<"scram:", StoredKeyB64/binary, ",", ServerKeyB64/binary, ",", SaltB64/binary, ",", IterationCountBin/binary>>.
|
||||
|
||||
parse_scram_password(PassData) ->
|
||||
@ -206,34 +206,31 @@ parse_scram_password(PassData) ->
|
||||
storedkey = StoredKeyB64,
|
||||
serverkey = ServerKeyB64,
|
||||
salt = SaltB64,
|
||||
iterationcount = list_to_integer(binary_to_list(IterationCountBin))
|
||||
iterationcount = (binary_to_integer(IterationCountBin))
|
||||
}.
|
||||
|
||||
-spec get_vcard(binary(), binary()) -> [xmlel()].
|
||||
get_vcard(User, Server) ->
|
||||
JID = jid:make(User, Server, <<>>),
|
||||
case mod_vcard:process_sm_iq(JID, JID, #iq{type = get}) of
|
||||
#iq{type = result, sub_el = [_|_] = VCardEls} ->
|
||||
VCardEls;
|
||||
_ ->
|
||||
[]
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case mod_vcard:get_vcard(LUser, LServer) of
|
||||
error -> [];
|
||||
Els -> Els
|
||||
end.
|
||||
|
||||
-spec get_offline(binary(), binary()) -> [xmlel()].
|
||||
get_offline(User, Server) ->
|
||||
case mod_offline:get_offline_els(User, Server) of
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case mod_offline:get_offline_els(LUser, LServer) of
|
||||
[] ->
|
||||
[];
|
||||
Els ->
|
||||
NewEls = lists:map(
|
||||
fun(#xmlel{attrs = Attrs} = El) ->
|
||||
NewAttrs = lists:keystore(<<"xmlns">>, 1,
|
||||
Attrs,
|
||||
{<<"xmlns">>,
|
||||
<<"jabber:client">>}),
|
||||
El#xmlel{attrs = NewAttrs}
|
||||
end, Els),
|
||||
NewEls = lists:map(fun xmpp:encode/1, Els),
|
||||
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
|
||||
end.
|
||||
|
||||
-spec get_privacy(binary(), binary()) -> [xmlel()].
|
||||
get_privacy(User, Server) ->
|
||||
case mod_privacy:get_user_lists(User, Server) of
|
||||
{ok, #privacy{default = Default,
|
||||
@ -241,25 +238,16 @@ get_privacy(User, Server) ->
|
||||
XLists = lists:map(
|
||||
fun({Name, Items}) ->
|
||||
XItems = lists:map(
|
||||
fun mod_privacy:item_to_xml/1, Items),
|
||||
#xmlel{name = <<"list">>,
|
||||
attrs = [{<<"name">>, Name}],
|
||||
children = XItems}
|
||||
fun mod_privacy:encode_list_item/1,
|
||||
Items),
|
||||
#privacy_list{name = Name, items = XItems}
|
||||
end, Lists),
|
||||
DefaultEl = case Default of
|
||||
none ->
|
||||
[];
|
||||
_ ->
|
||||
[#xmlel{name = <<"default">>,
|
||||
attrs = [{<<"name">>, Default}]}]
|
||||
end,
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
|
||||
children = DefaultEl ++ XLists}];
|
||||
[xmpp:encode(#privacy_query{default = Default, lists = XLists})];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec get_roster(binary(), binary()) -> [xmlel()].
|
||||
get_roster(User, Server) ->
|
||||
JID = jid:make(User, Server, <<>>),
|
||||
case mod_roster:get_roster(User, Server) of
|
||||
@ -272,18 +260,11 @@ get_roster(User, Server) ->
|
||||
Status = if is_binary(Msg) -> (Msg);
|
||||
true -> <<"">>
|
||||
end,
|
||||
[#xmlel{name = <<"presence">>,
|
||||
attrs =
|
||||
[{<<"from">>,
|
||||
jid:to_string(R#roster.jid)},
|
||||
{<<"to">>, jid:to_string(JID)},
|
||||
{<<"xmlns">>, <<"jabber:client">>},
|
||||
{<<"type">>, <<"subscribe">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"status">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata, Status}]}]}];
|
||||
[xmpp:encode(
|
||||
#presence{from = jid:make(R#roster.jid),
|
||||
to = JID,
|
||||
type = subscribe,
|
||||
status = xmpp:mk_text(Status)})];
|
||||
(_) ->
|
||||
[]
|
||||
end, Items),
|
||||
@ -291,21 +272,18 @@ get_roster(User, Server) ->
|
||||
fun(#roster{ask = in, subscription = none}) ->
|
||||
[];
|
||||
(R) ->
|
||||
[mod_roster:item_to_xml(R)]
|
||||
[mod_roster:encode_item(R)]
|
||||
end, Items),
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_ROSTER}],
|
||||
children = Rs} | Subs];
|
||||
[xmpp:encode(#roster_query{items = Rs}) | Subs];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec get_private(binary(), binary()) -> [xmlel()].
|
||||
get_private(User, Server) ->
|
||||
case mod_private:get_data(User, Server) of
|
||||
[_|_] = Els ->
|
||||
[#xmlel{name = <<"query">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PRIVATE}],
|
||||
children = Els}];
|
||||
[xmpp:encode(#private{xml_els = Els})];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
@ -451,129 +429,124 @@ process_user_els([], State) ->
|
||||
|
||||
process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El,
|
||||
State) ->
|
||||
case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
|
||||
{<<"query">>, ?NS_ROSTER} ->
|
||||
process_roster(El, State);
|
||||
{<<"query">>, ?NS_PRIVACY} ->
|
||||
%% Make sure <list/> elements go before <active/> and <default/>
|
||||
NewEls = lists:reverse(lists:keysort(#xmlel.name, Els)),
|
||||
process_privacy_el(El#xmlel{children = NewEls}, State);
|
||||
{<<"query">>, ?NS_PRIVATE} ->
|
||||
process_private(El, State);
|
||||
{<<"vCard">>, ?NS_VCARD} ->
|
||||
process_vcard(El, State);
|
||||
{<<"offline-messages">>, _} ->
|
||||
process_offline_msgs(Els, State);
|
||||
{<<"presence">>, <<"jabber:client">>} ->
|
||||
process_presence(El, State);
|
||||
_ ->
|
||||
{ok, State}
|
||||
try
|
||||
case {Name, fxml:get_attr_s(<<"xmlns">>, Attrs)} of
|
||||
{<<"query">>, ?NS_ROSTER} ->
|
||||
process_roster(xmpp:decode(El), State);
|
||||
{<<"query">>, ?NS_PRIVACY} ->
|
||||
%% Make sure <list/> elements go before <active/> and <default/>
|
||||
process_privacy(xmpp:decode(El), State);
|
||||
{<<"query">>, ?NS_PRIVATE} ->
|
||||
process_private(xmpp:decode(El), State);
|
||||
{<<"vCard">>, ?NS_VCARD} ->
|
||||
process_vcard(El, State);
|
||||
{<<"offline-messages">>, NS} ->
|
||||
Msgs = [xmpp:decode(E, NS, [ignore_els]) || E <- Els],
|
||||
process_offline_msgs(Msgs, State);
|
||||
{<<"presence">>, ?NS_CLIENT} ->
|
||||
process_presence(xmpp:decode(El, ?NS_CLIENT, [ignore_els]), State);
|
||||
_ ->
|
||||
{ok, State}
|
||||
end
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
ErrTxt = xmpp:format_error(Why),
|
||||
stop("failed to decode XML '~s': ~s",
|
||||
[fxml:element_to_binary(El), ErrTxt])
|
||||
end.
|
||||
|
||||
process_privacy_el(#xmlel{children = [#xmlel{} = SubEl|SubEls]} = El, State) ->
|
||||
case process_privacy(#xmlel{children = [SubEl]}, State) of
|
||||
-spec process_offline_msgs([stanza()], state()) -> {ok, state()} | {error, _}.
|
||||
process_offline_msgs([#message{} = Msg|Msgs], State) ->
|
||||
case process_offline_msg(Msg, State) of
|
||||
{ok, NewState} ->
|
||||
process_privacy_el(El#xmlel{children = SubEls}, NewState);
|
||||
process_offline_msgs(Msgs, NewState);
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
process_privacy_el(#xmlel{children = [_|SubEls]} = El, State) ->
|
||||
process_privacy_el(El#xmlel{children = SubEls}, State);
|
||||
process_privacy_el(#xmlel{children = []}, State) ->
|
||||
{ok, State}.
|
||||
|
||||
process_offline_msgs([#xmlel{} = El|Els], State) ->
|
||||
case process_offline_msg(El, State) of
|
||||
{ok, NewState} ->
|
||||
process_offline_msgs(Els, NewState);
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
process_offline_msgs([_|Els], State) ->
|
||||
process_offline_msgs(Els, State);
|
||||
process_offline_msgs([_|Msgs], State) ->
|
||||
process_offline_msgs(Msgs, State);
|
||||
process_offline_msgs([], State) ->
|
||||
{ok, State}.
|
||||
|
||||
process_roster(El, State = #state{user = U, server = S}) ->
|
||||
case mod_roster:set_items(U, S, El) of
|
||||
-spec process_roster(roster_query(), state()) -> {ok, state()} | {error, _}.
|
||||
process_roster(RosterQuery, State = #state{user = U, server = S}) ->
|
||||
case mod_roster:set_items(U, S, RosterQuery) of
|
||||
{atomic, _} ->
|
||||
{ok, State};
|
||||
Err ->
|
||||
stop("Failed to write roster: ~p", [Err])
|
||||
end.
|
||||
|
||||
process_privacy(El, State = #state{user = U, server = S}) ->
|
||||
JID = jid:make(U, S, <<"">>),
|
||||
case mod_privacy:process_iq_set(
|
||||
[], JID, JID, #iq{type = set, sub_el = El}) of
|
||||
{error, Error} = Err ->
|
||||
#xmlel{children = Els} = El,
|
||||
Name = case fxml:remove_cdata(Els) of
|
||||
[#xmlel{name = N}] -> N;
|
||||
_ -> undefined
|
||||
end,
|
||||
#xmlel{attrs = Attrs} = Error,
|
||||
ErrorCode = case lists:keysearch(<<"code">>, 1, Attrs) of
|
||||
{value, {_, V}} -> V;
|
||||
false -> undefined
|
||||
end,
|
||||
if
|
||||
ErrorCode == <<"404">>, Name == <<"default">> ->
|
||||
{ok, State};
|
||||
true ->
|
||||
stop("Failed to write privacy: ~p", [Err])
|
||||
-spec process_privacy(privacy_query(), state()) -> {ok, state()} | {error, _}.
|
||||
process_privacy(#privacy_query{lists = Lists,
|
||||
default = Default,
|
||||
active = Active} = PrivacyQuery,
|
||||
State = #state{user = U, server = S}) ->
|
||||
JID = jid:make(U, S),
|
||||
IQ = #iq{type = set, id = randoms:get_string(),
|
||||
from = JID, to = JID, sub_els = [PrivacyQuery]},
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Error = {error, xmpp:err_feature_not_implemented(Txt, ?MYLANG)},
|
||||
case mod_privacy:process_iq_set(Error, IQ, #userlist{}) of
|
||||
{error, #stanza_error{reason = Reason}} = Err ->
|
||||
if Reason == 'item-not-found', Lists == [],
|
||||
Active == undefined, Default /= undefined ->
|
||||
%% Failed to set default list because there is no
|
||||
%% list with such name. We shouldn't stop here.
|
||||
{ok, State};
|
||||
true ->
|
||||
stop("Failed to write privacy: ~p", [Err])
|
||||
end;
|
||||
_ ->
|
||||
{ok, State}
|
||||
end.
|
||||
|
||||
process_private(El, State = #state{user = U, server = S}) ->
|
||||
JID = jid:make(U, S, <<"">>),
|
||||
case mod_private:process_sm_iq(
|
||||
JID, JID, #iq{type = set, sub_el = El}) of
|
||||
-spec process_private(private(), state()) -> {ok, state()} | {error, _}.
|
||||
process_private(Private, State = #state{user = U, server = S}) ->
|
||||
JID = jid:make(U, S),
|
||||
IQ = #iq{type = set, id = randoms:get_string(),
|
||||
from = JID, to = JID, sub_els = [Private]},
|
||||
case mod_private:process_sm_iq(IQ) of
|
||||
#iq{type = result} ->
|
||||
{ok, State};
|
||||
Err ->
|
||||
stop("Failed to write private: ~p", [Err])
|
||||
end.
|
||||
|
||||
-spec process_vcard(xmlel(), state()) -> {ok, state()} | {error, _}.
|
||||
process_vcard(El, State = #state{user = U, server = S}) ->
|
||||
JID = jid:make(U, S, <<"">>),
|
||||
case mod_vcard:process_sm_iq(
|
||||
JID, JID, #iq{type = set, sub_el = El}) of
|
||||
JID = jid:make(U, S),
|
||||
IQ = #iq{type = set, id = randoms:get_string(),
|
||||
from = JID, to = JID, sub_els = [El]},
|
||||
case mod_vcard:process_sm_iq(IQ) of
|
||||
#iq{type = result} ->
|
||||
{ok, State};
|
||||
Err ->
|
||||
stop("Failed to write vcard: ~p", [Err])
|
||||
end.
|
||||
|
||||
process_offline_msg(El, State = #state{user = U, server = S}) ->
|
||||
FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
|
||||
case jid:from_string(FromS) of
|
||||
#jid{} = From ->
|
||||
To = jid:make(U, S, <<>>),
|
||||
NewEl = jlib:replace_from_to(From, To, El),
|
||||
case catch mod_offline:store_packet(From, To, NewEl) of
|
||||
{'EXIT', _} = Err ->
|
||||
stop("Failed to store offline message: ~p", [Err]);
|
||||
_ ->
|
||||
{ok, State}
|
||||
end;
|
||||
_ ->
|
||||
stop("Invalid 'from' = ~s", [FromS])
|
||||
-spec process_offline_msg(message(), state()) -> {ok, state()} | {error, _}.
|
||||
process_offline_msg(#message{from = undefined}, _State) ->
|
||||
stop("No 'from' attribute found", []);
|
||||
process_offline_msg(Msg, State = #state{user = U, server = S}) ->
|
||||
From = xmpp:get_from(Msg),
|
||||
To = jid:make(U, S, <<>>),
|
||||
NewMsg = xmpp:set_from_to(Msg, From, To),
|
||||
case catch mod_offline:store_packet(From, To, NewMsg) of
|
||||
{'EXIT', _} = Err ->
|
||||
stop("Failed to store offline message: ~p", [Err]);
|
||||
_ ->
|
||||
{ok, State}
|
||||
end.
|
||||
|
||||
process_presence(El, #state{user = U, server = S} = State) ->
|
||||
FromS = fxml:get_attr_s(<<"from">>, El#xmlel.attrs),
|
||||
case jid:from_string(FromS) of
|
||||
#jid{} = From ->
|
||||
To = jid:make(U, S, <<>>),
|
||||
NewEl = jlib:replace_from_to(From, To, El),
|
||||
ejabberd_router:route(From, To, NewEl),
|
||||
{ok, State};
|
||||
_ ->
|
||||
stop("Invalid 'from' = ~s", [FromS])
|
||||
end.
|
||||
-spec process_presence(presence(), state()) -> {ok, state()} | {error, _}.
|
||||
process_presence(#presence{from = undefined}, _State) ->
|
||||
stop("No 'from' attribute found", []);
|
||||
process_presence(Pres, #state{user = U, server = S} = State) ->
|
||||
From = xmpp:get_from(Pres),
|
||||
To = jid:make(U, S, <<>>),
|
||||
NewPres = xmpp:set_from_to(Pres, From, To),
|
||||
ejabberd_router:route(From, To, NewPres),
|
||||
{ok, State}.
|
||||
|
||||
stop(Fmt, Args) ->
|
||||
?ERROR_MSG(Fmt, Args),
|
||||
@ -581,9 +554,8 @@ stop(Fmt, Args) ->
|
||||
|
||||
make_filename_template() ->
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
|
||||
list_to_binary(
|
||||
io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
|
||||
[Year, Month, Day, Hour, Minute, Second])).
|
||||
str:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
|
||||
[Year, Month, Day, Hour, Minute, Second]).
|
||||
|
||||
make_main_basefilename(Dir, FnT) ->
|
||||
Filename2 = <<FnT/binary, ".xml">>,
|
||||
|
@ -428,7 +428,7 @@ map_key(Obj, _, _) ->
|
||||
<<"b_", B/binary>> ->
|
||||
B;
|
||||
<<"i_", B/binary>> ->
|
||||
list_to_integer(binary_to_list(B));
|
||||
(binary_to_integer(B));
|
||||
B ->
|
||||
erlang:binary_to_term(B)
|
||||
end].
|
||||
@ -483,7 +483,7 @@ encode_index_key(Idx, Key) ->
|
||||
encode_key(Bin) when is_binary(Bin) ->
|
||||
<<"b_", Bin/binary>>;
|
||||
encode_key(Int) when is_integer(Int) ->
|
||||
<<"i_", (list_to_binary(integer_to_list(Int)))/binary>>;
|
||||
<<"i_", ((integer_to_binary(Int)))/binary>>;
|
||||
encode_key(Term) ->
|
||||
erlang:term_to_binary(Term).
|
||||
|
||||
@ -519,7 +519,7 @@ log_error(_, _, _) ->
|
||||
ok.
|
||||
|
||||
make_invalid_object(Val) ->
|
||||
list_to_binary(io_lib:fwrite("Invalid object: ~p", [Val])).
|
||||
(str:format("Invalid object: ~p", [Val])).
|
||||
|
||||
get_random_pid() ->
|
||||
PoolPid = ejabberd_riak_sup:get_random_pid(),
|
||||
|
@ -39,6 +39,7 @@
|
||||
register_route/3,
|
||||
register_routes/1,
|
||||
host_of_route/1,
|
||||
process_iq/3,
|
||||
unregister_route/1,
|
||||
unregister_routes/1,
|
||||
dirty_get_all_routes/0,
|
||||
@ -53,7 +54,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
|
||||
|
||||
@ -71,7 +72,7 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec route(jid(), jid(), xmlel()) -> ok.
|
||||
-spec route(jid(), jid(), xmlel() | stanza()) -> ok.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
@ -84,13 +85,21 @@ route(From, To, Packet) ->
|
||||
|
||||
%% Route the error packet only if the originating packet is not an error itself.
|
||||
%% RFC3920 9.3.1
|
||||
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok.
|
||||
-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok;
|
||||
(jid(), jid(), stanza(), stanza_error()) -> ok.
|
||||
|
||||
route_error(From, To, ErrPacket, OrigPacket) ->
|
||||
route_error(From, To, #xmlel{} = ErrPacket, #xmlel{} = OrigPacket) ->
|
||||
#xmlel{attrs = Attrs} = OrigPacket,
|
||||
case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
false -> route(From, To, ErrPacket);
|
||||
true -> ok
|
||||
end;
|
||||
route_error(From, To, Packet, #stanza_error{} = Err) ->
|
||||
Type = xmpp:get_type(Packet),
|
||||
if Type == error; Type == result ->
|
||||
ok;
|
||||
true ->
|
||||
ejabberd_router:route(From, To, xmpp:make_error(Packet, Err))
|
||||
end.
|
||||
|
||||
-spec register_route(binary()) -> term().
|
||||
@ -236,6 +245,28 @@ host_of_route(Domain) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec process_iq(jid(), jid(), iq() | xmlel()) -> any().
|
||||
process_iq(From, To, #iq{} = IQ) ->
|
||||
if To#jid.luser == <<"">> ->
|
||||
ejabberd_local:process_iq(From, To, IQ);
|
||||
true ->
|
||||
ejabberd_sm:process_iq(From, To, IQ)
|
||||
end;
|
||||
process_iq(From, To, El) ->
|
||||
try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of
|
||||
IQ -> process_iq(From, To, IQ)
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
Type = xmpp:get_type(El),
|
||||
if Type == <<"get">>; Type == <<"set">> ->
|
||||
Txt = xmpp:format_error(Why),
|
||||
Lang = xmpp:get_lang(El),
|
||||
Err = xmpp:make_error(El, xmpp:err_bad_request(Txt, Lang)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
@ -249,7 +280,7 @@ host_of_route(Domain) ->
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
update_tables(),
|
||||
mnesia:create_table(route,
|
||||
ejabberd_mnesia:create(?MODULE, route,
|
||||
[{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, route)}]),
|
||||
@ -347,6 +378,7 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
-spec do_route(jid(), jid(), xmlel() | xmpp_element()) -> any().
|
||||
do_route(OrigFrom, OrigTo, OrigPacket) ->
|
||||
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket "
|
||||
"~p~n",
|
||||
@ -357,69 +389,80 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
|
||||
{From, To, Packet} ->
|
||||
LDstDomain = To#jid.lserver,
|
||||
case mnesia:dirty_read(route, LDstDomain) of
|
||||
[] -> ejabberd_s2s:route(From, To, Packet);
|
||||
[] ->
|
||||
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
|
||||
Pkt ->
|
||||
ejabberd_s2s:route(From, To, Pkt)
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
log_decoding_error(From, To, Packet, Why)
|
||||
end;
|
||||
[R] ->
|
||||
Pid = R#route.pid,
|
||||
if node(Pid) == node() ->
|
||||
case R#route.local_hint of
|
||||
{apply, Module, Function} ->
|
||||
Module:Function(From, To, Packet);
|
||||
_ -> Pid ! {route, From, To, Packet}
|
||||
end;
|
||||
is_pid(Pid) -> Pid ! {route, From, To, Packet};
|
||||
true -> drop
|
||||
end;
|
||||
do_route(From, To, Packet, R);
|
||||
Rs ->
|
||||
Value = case
|
||||
ejabberd_config:get_option({domain_balancing,
|
||||
LDstDomain}, fun(D) when is_atom(D) -> D end)
|
||||
of
|
||||
undefined -> p1_time_compat:monotonic_time();
|
||||
random -> p1_time_compat:monotonic_time();
|
||||
source -> jid:tolower(From);
|
||||
destination -> jid:tolower(To);
|
||||
bare_source ->
|
||||
jid:remove_resource(jid:tolower(From));
|
||||
bare_destination ->
|
||||
jid:remove_resource(jid:tolower(To))
|
||||
end,
|
||||
Value = get_domain_balancing(From, To, LDstDomain),
|
||||
case get_component_number(LDstDomain) of
|
||||
undefined ->
|
||||
case [R || R <- Rs, node(R#route.pid) == node()] of
|
||||
[] ->
|
||||
R = lists:nth(erlang:phash(Value, length(Rs)), Rs),
|
||||
Pid = R#route.pid,
|
||||
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
|
||||
true -> drop
|
||||
end;
|
||||
do_route(From, To, Packet, R);
|
||||
LRs ->
|
||||
R = lists:nth(erlang:phash(Value, length(LRs)),
|
||||
LRs),
|
||||
Pid = R#route.pid,
|
||||
case R#route.local_hint of
|
||||
{apply, Module, Function} ->
|
||||
Module:Function(From, To, Packet);
|
||||
_ -> Pid ! {route, From, To, Packet}
|
||||
end
|
||||
R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
|
||||
do_route(From, To, Packet, R)
|
||||
end;
|
||||
_ ->
|
||||
SRs = lists:ukeysort(#route.local_hint, Rs),
|
||||
R = lists:nth(erlang:phash(Value, length(SRs)), SRs),
|
||||
Pid = R#route.pid,
|
||||
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
|
||||
true -> drop
|
||||
end
|
||||
do_route(From, To, Packet, R)
|
||||
end
|
||||
end;
|
||||
drop -> ok
|
||||
end.
|
||||
|
||||
-spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any().
|
||||
do_route(From, To, Packet, #route{local_hint = LocalHint,
|
||||
pid = Pid}) when is_pid(Pid) ->
|
||||
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
|
||||
Pkt ->
|
||||
case LocalHint of
|
||||
{apply, Module, Function} when node(Pid) == node() ->
|
||||
Module:Function(From, To, Pkt);
|
||||
_ ->
|
||||
Pid ! {route, From, To, Pkt}
|
||||
end
|
||||
catch error:{xmpp_codec, Why} ->
|
||||
log_decoding_error(From, To, Packet, Why)
|
||||
end;
|
||||
do_route(_From, _To, _Packet, _Route) ->
|
||||
drop.
|
||||
|
||||
-spec log_decoding_error(jid(), jid(), xmlel() | xmpp_element(), term()) -> ok.
|
||||
log_decoding_error(From, To, Packet, Reason) ->
|
||||
?ERROR_MSG("failed to decode xml element ~p when "
|
||||
"routing from ~s to ~s: ~s",
|
||||
[Packet, jid:to_string(From), jid:to_string(To),
|
||||
xmpp:format_error(Reason)]).
|
||||
|
||||
-spec get_component_number(binary()) -> pos_integer() | undefined.
|
||||
get_component_number(LDomain) ->
|
||||
ejabberd_config:get_option(
|
||||
{domain_balancing_component_number, LDomain},
|
||||
fun(N) when is_integer(N), N > 1 -> N end,
|
||||
undefined).
|
||||
|
||||
-spec get_domain_balancing(jid(), jid(), binary()) -> any().
|
||||
get_domain_balancing(From, To, LDomain) ->
|
||||
case ejabberd_config:get_option(
|
||||
{domain_balancing, LDomain}, fun(D) when is_atom(D) -> D end) of
|
||||
undefined -> p1_time_compat:monotonic_time();
|
||||
random -> p1_time_compat:monotonic_time();
|
||||
source -> jid:tolower(From);
|
||||
destination -> jid:tolower(To);
|
||||
bare_source -> jid:remove_resource(jid:tolower(From));
|
||||
bare_destination -> jid:remove_resource(jid:tolower(To))
|
||||
end.
|
||||
|
||||
-spec update_tables() -> ok.
|
||||
update_tables() ->
|
||||
try
|
||||
mnesia:transform_table(route, ignore, record_info(fields, route))
|
||||
|
@ -43,9 +43,10 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(route_multicast, {domain, pid}).
|
||||
-record(route_multicast, {domain = <<"">> :: binary() | '_',
|
||||
pid = self() :: pid()}).
|
||||
-record(state, {}).
|
||||
|
||||
%%====================================================================
|
||||
@ -58,7 +59,7 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
|
||||
-spec route_multicast(jid(), binary(), [jid()], stanza()) -> ok.
|
||||
route_multicast(From, Domain, Destinations, Packet) ->
|
||||
case catch do_route(From, Domain, Destinations, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
@ -68,6 +69,7 @@ route_multicast(From, Domain, Destinations, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec register_route(binary()) -> any().
|
||||
register_route(Domain) ->
|
||||
case jid:nameprep(Domain) of
|
||||
error ->
|
||||
@ -81,6 +83,7 @@ register_route(Domain) ->
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
-spec unregister_route(binary()) -> any().
|
||||
unregister_route(Domain) ->
|
||||
case jid:nameprep(Domain) of
|
||||
error ->
|
||||
@ -112,7 +115,7 @@ unregister_route(Domain) ->
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
mnesia:create_table(route_multicast,
|
||||
ejabberd_mnesia:create(?MODULE, route_multicast,
|
||||
[{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes,
|
||||
@ -206,6 +209,7 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% From = #jid
|
||||
%% Destinations = [#jid]
|
||||
-spec do_route(jid(), binary(), [jid()], stanza()) -> any().
|
||||
do_route(From, Domain, Destinations, Packet) ->
|
||||
|
||||
?DEBUG("route_multicast~n\tfrom ~s~n\tdomain ~s~n\tdestinations ~p~n\tpacket ~p~n",
|
||||
@ -226,6 +230,7 @@ do_route(From, Domain, Destinations, Packet) ->
|
||||
Pid ! {route_trusted, From, Destinations, Packet}
|
||||
end.
|
||||
|
||||
-spec pick_multicast_pid([#route_multicast{}]) -> pid().
|
||||
pick_multicast_pid(Rs) ->
|
||||
List = case [R || R <- Rs, node(R#route_multicast.pid) == node()] of
|
||||
[] -> Rs;
|
||||
@ -233,5 +238,6 @@ pick_multicast_pid(Rs) ->
|
||||
end,
|
||||
(hd(List))#route_multicast.pid.
|
||||
|
||||
-spec do_route_normal(jid(), [jid()], stanza()) -> any().
|
||||
do_route_normal(From, Destinations, Packet) ->
|
||||
[ejabberd_router:route(From, To, Packet) || To <- Destinations].
|
||||
|
@ -39,6 +39,7 @@
|
||||
remove_connection/2, find_connection/2,
|
||||
dirty_get_connections/0, allow_host/2,
|
||||
incoming_s2s_number/0, outgoing_s2s_number/0,
|
||||
stop_all_connections/0,
|
||||
clean_temporarily_blocked_table/0,
|
||||
list_temporarily_blocked_hosts/0,
|
||||
external_host_overloaded/1, is_temporarly_blocked/1,
|
||||
@ -55,7 +56,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
|
||||
@ -89,7 +90,7 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
|
||||
[]).
|
||||
|
||||
-spec route(jid(), jid(), xmlel()) -> ok.
|
||||
-spec route(jid(), jid(), xmpp_element()) -> ok.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
@ -222,6 +223,7 @@ check_peer_certificate(SockMod, Sock, Peer) ->
|
||||
{error, <<"Cannot get peer certificate">>}
|
||||
end.
|
||||
|
||||
-spec make_key({binary(), binary()}, binary()) -> binary().
|
||||
make_key({From, To}, StreamID) ->
|
||||
Secret = ejabberd_config:get_option(shared_key, fun(V) -> V end),
|
||||
p1_sha:to_hexlist(
|
||||
@ -234,14 +236,14 @@ make_key({From, To}, StreamID) ->
|
||||
|
||||
init([]) ->
|
||||
update_tables(),
|
||||
mnesia:create_table(s2s,
|
||||
ejabberd_mnesia:create(?MODULE, s2s,
|
||||
[{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, s2s)}]),
|
||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
ejabberd_commands:register_commands(get_commands_spec()),
|
||||
mnesia:create_table(temporarily_blocked,
|
||||
ejabberd_mnesia:create(?MODULE, temporarily_blocked,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, temporarily_blocked)}]),
|
||||
{ok, #state{}}.
|
||||
@ -275,7 +277,7 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec clean_table_from_bad_node(node()) -> any().
|
||||
clean_table_from_bad_node(Node) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
@ -289,6 +291,7 @@ clean_table_from_bad_node(Node) ->
|
||||
end,
|
||||
mnesia:async_dirty(F).
|
||||
|
||||
-spec do_route(jid(), jid(), stanza()) -> ok | false.
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket "
|
||||
"~P~n",
|
||||
@ -296,28 +299,16 @@ do_route(From, To, Packet) ->
|
||||
case find_connection(From, To) of
|
||||
{atomic, Pid} when is_pid(Pid) ->
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
#xmlel{name = Name, attrs = Attrs, children = Els} =
|
||||
Packet,
|
||||
NewAttrs =
|
||||
jlib:replace_from_to_attrs(jid:to_string(From),
|
||||
jid:to_string(To), Attrs),
|
||||
#jid{lserver = MyServer} = From,
|
||||
ejabberd_hooks:run(s2s_send_packet, MyServer,
|
||||
[From, To, Packet]),
|
||||
send_element(Pid,
|
||||
#xmlel{name = Name, attrs = NewAttrs, children = Els}),
|
||||
send_element(Pid, xmpp:set_from_to(Packet, From, To)),
|
||||
ok;
|
||||
{aborted, _Reason} ->
|
||||
case fxml:get_tag_attr_s(<<"type">>, Packet) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"No s2s connection found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end,
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Txt = <<"No s2s connection found">>,
|
||||
Err = xmpp:err_service_unavailable(Txt, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err),
|
||||
false
|
||||
end.
|
||||
|
||||
@ -367,9 +358,11 @@ find_connection(From, To) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec choose_connection(jid(), [#s2s{}]) -> pid().
|
||||
choose_connection(From, Connections) ->
|
||||
choose_pid(From, [C#s2s.pid || C <- Connections]).
|
||||
|
||||
-spec choose_pid(jid(), [pid()]) -> pid().
|
||||
choose_pid(From, Pids) ->
|
||||
Pids1 = case [P || P <- Pids, node(P) == node()] of
|
||||
[] -> Pids;
|
||||
@ -417,22 +410,21 @@ new_connection(MyServer, Server, From, FromTo,
|
||||
end,
|
||||
TRes.
|
||||
|
||||
-spec max_s2s_connections_number({binary(), binary()}) -> integer().
|
||||
max_s2s_connections_number({From, To}) ->
|
||||
case acl:match_rule(From, max_s2s_connections,
|
||||
jid:make(<<"">>, To, <<"">>))
|
||||
of
|
||||
case acl:match_rule(From, max_s2s_connections, jid:make(To)) of
|
||||
Max when is_integer(Max) -> Max;
|
||||
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
|
||||
end.
|
||||
|
||||
-spec max_s2s_connections_number_per_node({binary(), binary()}) -> integer().
|
||||
max_s2s_connections_number_per_node({From, To}) ->
|
||||
case acl:match_rule(From, max_s2s_connections_per_node,
|
||||
jid:make(<<"">>, To, <<"">>))
|
||||
of
|
||||
case acl:match_rule(From, max_s2s_connections_per_node, jid:make(To)) of
|
||||
Max when is_integer(Max) -> Max;
|
||||
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
|
||||
end.
|
||||
|
||||
-spec needed_connections_number([#s2s{}], integer(), integer()) -> integer().
|
||||
needed_connections_number(Ls, MaxS2SConnectionsNumber,
|
||||
MaxS2SConnectionsNumberPerNode) ->
|
||||
LocalLs = [L || L <- Ls, node(L#s2s.pid) == node()],
|
||||
@ -444,6 +436,7 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
|
||||
%% Description: Return true if the destination must be considered as a
|
||||
%% service.
|
||||
%% --------------------------------------------------------------------
|
||||
-spec is_service(jid(), jid()) -> boolean().
|
||||
is_service(From, To) ->
|
||||
LFromDomain = From#jid.lserver,
|
||||
case ejabberd_config:get_option(
|
||||
@ -475,18 +468,24 @@ send_element(Pid, El) ->
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{
|
||||
name = incoming_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of incoming s2s connections on the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [], result = {s2s_incoming, integer}},
|
||||
policy = admin,
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [], result = {s2s_incoming, integer}},
|
||||
#ejabberd_commands{
|
||||
name = outgoing_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
tags = [stats, s2s],
|
||||
desc = "Number of outgoing s2s connections on the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [], result = {s2s_outgoing, integer}}].
|
||||
policy = admin,
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [], result = {s2s_outgoing, integer}},
|
||||
#ejabberd_commands{name = stop_all_connections,
|
||||
tags = [s2s],
|
||||
desc = "Stop all outgoing and incoming connections",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = stop_all_connections,
|
||||
args = [], result = {res, rescode}}].
|
||||
|
||||
%% TODO Move those stats commands to ejabberd stats command ?
|
||||
incoming_s2s_number() ->
|
||||
@ -502,6 +501,15 @@ supervisor_count(Supervisor) ->
|
||||
length(Result)
|
||||
end.
|
||||
|
||||
stop_all_connections() ->
|
||||
lists:foreach(
|
||||
fun({_Id, Pid, _Type, _Module}) ->
|
||||
exit(Pid, kill)
|
||||
end,
|
||||
supervisor:which_children(ejabberd_s2s_in_sup) ++
|
||||
supervisor:which_children(ejabberd_s2s_out_sup)),
|
||||
mnesia:clear_table(s2s).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Update Mnesia tables
|
||||
|
||||
@ -547,7 +555,7 @@ allow_host1(MyHost, S2SHost) ->
|
||||
s2s_access,
|
||||
fun(A) -> A end,
|
||||
all),
|
||||
JID = jid:make(<<"">>, S2SHost, <<"">>),
|
||||
JID = jid:make(S2SHost),
|
||||
case acl:match_rule(MyHost, Rule, JID) of
|
||||
deny -> false;
|
||||
allow ->
|
||||
|
@ -42,7 +42,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-define(DICT, dict).
|
||||
|
||||
@ -62,40 +62,19 @@
|
||||
connections = (?DICT):new() :: ?TDICT,
|
||||
timer = make_ref() :: reference()}).
|
||||
|
||||
%-define(DBGFSM, true).
|
||||
-type state_name() :: wait_for_stream | wait_for_feature_request | stream_established.
|
||||
-type state() :: #state{}.
|
||||
-type fsm_next() :: {next_state, state_name(), state()}.
|
||||
-type fsm_stop() :: {stop, normal, state()}.
|
||||
-type fsm_transition() :: fsm_stop() | fsm_next().
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
-ifdef(DBGFSM).
|
||||
|
||||
-define(FSMOPTS, [{debug, [trace]}]).
|
||||
|
||||
-else.
|
||||
|
||||
-define(FSMOPTS, []).
|
||||
|
||||
-endif.
|
||||
|
||||
-define(STREAM_HEADER(Version),
|
||||
<<"<?xml version='1.0'?><stream:stream "
|
||||
"xmlns:stream='http://etherx.jabber.org/stream"
|
||||
"s' xmlns='jabber:server' xmlns:db='jabber:ser"
|
||||
"ver:dialback' id='",
|
||||
(StateData#state.streamid)/binary, "'", Version/binary,
|
||||
">">>).
|
||||
|
||||
-define(STREAM_TRAILER, <<"</stream:stream>">>).
|
||||
|
||||
-define(INVALID_NAMESPACE_ERR,
|
||||
fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
|
||||
|
||||
-define(HOST_UNKNOWN_ERR,
|
||||
fxml:element_to_binary(?SERR_HOST_UNKNOWN)).
|
||||
|
||||
-define(INVALID_FROM_ERR,
|
||||
fxml:element_to_binary(?SERR_INVALID_FROM)).
|
||||
|
||||
-define(INVALID_XML_ERR,
|
||||
fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
|
||||
|
||||
start(SockData, Opts) ->
|
||||
supervisor:start_child(ejabberd_s2s_in_sup,
|
||||
[SockData, Opts]).
|
||||
@ -185,351 +164,294 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
wait_for_stream({xmlstreamstart, _Name, Attrs},
|
||||
StateData) ->
|
||||
case {fxml:get_attr_s(<<"xmlns">>, Attrs),
|
||||
fxml:get_attr_s(<<"xmlns:db">>, Attrs),
|
||||
fxml:get_attr_s(<<"to">>, Attrs),
|
||||
fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
|
||||
of
|
||||
{<<"jabber:server">>, _, Server, true}
|
||||
when StateData#state.tls and
|
||||
not StateData#state.authenticated ->
|
||||
send_text(StateData,
|
||||
?STREAM_HEADER(<<" version='1.0'">>)),
|
||||
Auth = if StateData#state.tls_enabled ->
|
||||
case jid:nameprep(fxml:get_attr_s(<<"from">>, Attrs)) of
|
||||
From when From /= <<"">>, From /= error ->
|
||||
{Result, Message} =
|
||||
ejabberd_s2s:check_peer_certificate(StateData#state.sockmod,
|
||||
StateData#state.socket,
|
||||
From),
|
||||
{Result, From, Message};
|
||||
_ ->
|
||||
{error, <<"(unknown)">>,
|
||||
<<"Got no valid 'from' attribute">>}
|
||||
end;
|
||||
true ->
|
||||
{no_verify, <<"(unknown)">>,
|
||||
<<"TLS not (yet) enabled">>}
|
||||
end,
|
||||
StartTLS = if StateData#state.tls_enabled -> [];
|
||||
not StateData#state.tls_enabled and
|
||||
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
|
||||
try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
|
||||
#stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM}
|
||||
when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM ->
|
||||
send_header(StateData, {1,0}),
|
||||
send_element(StateData, xmpp:serr_invalid_namespace()),
|
||||
{stop, normal, StateData};
|
||||
#stream_start{to = #jid{lserver = Server},
|
||||
from = From, version = {1,0}}
|
||||
when StateData#state.tls and not StateData#state.authenticated ->
|
||||
send_header(StateData, {1,0}),
|
||||
Auth = if StateData#state.tls_enabled ->
|
||||
case From of
|
||||
#jid{} ->
|
||||
{Result, Message} =
|
||||
ejabberd_s2s:check_peer_certificate(
|
||||
StateData#state.sockmod,
|
||||
StateData#state.socket,
|
||||
From#jid.lserver),
|
||||
{Result, From#jid.lserver, Message};
|
||||
undefined ->
|
||||
{error, <<"(unknown)">>,
|
||||
<<"Got no valid 'from' attribute">>}
|
||||
end;
|
||||
true ->
|
||||
{no_verify, <<"(unknown)">>, <<"TLS not (yet) enabled">>}
|
||||
end,
|
||||
StartTLS = if StateData#state.tls_enabled -> [];
|
||||
not StateData#state.tls_enabled and
|
||||
not StateData#state.tls_required ->
|
||||
[#xmlel{name = <<"starttls">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_TLS}],
|
||||
children = []}];
|
||||
not StateData#state.tls_enabled and
|
||||
[#starttls{required = false}];
|
||||
not StateData#state.tls_enabled and
|
||||
StateData#state.tls_required ->
|
||||
[#xmlel{name = <<"starttls">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_TLS}],
|
||||
children =
|
||||
[#xmlel{name = <<"required">>,
|
||||
attrs = [], children = []}]}]
|
||||
end,
|
||||
case Auth of
|
||||
{error, RemoteServer, CertError}
|
||||
when StateData#state.tls_certverify ->
|
||||
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
|
||||
[StateData#state.server, RemoteServer, CertError]),
|
||||
send_text(StateData,
|
||||
<<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
|
||||
CertError)))/binary,
|
||||
(?STREAM_TRAILER)/binary>>),
|
||||
{stop, normal, StateData};
|
||||
{VerifyResult, RemoteServer, Msg} ->
|
||||
{SASL, NewStateData} = case VerifyResult of
|
||||
ok ->
|
||||
{[#xmlel{name = <<"mechanisms">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
children =
|
||||
[#xmlel{name = <<"mechanism">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata,
|
||||
<<"EXTERNAL">>}]}]}],
|
||||
StateData#state{auth_domain = RemoteServer}};
|
||||
error ->
|
||||
?DEBUG("Won't accept certificate of ~s: ~s",
|
||||
[RemoteServer, Msg]),
|
||||
{[], StateData};
|
||||
no_verify ->
|
||||
{[], StateData}
|
||||
end,
|
||||
send_element(NewStateData,
|
||||
#xmlel{name = <<"stream:features">>, attrs = [],
|
||||
children =
|
||||
SASL ++
|
||||
StartTLS ++
|
||||
ejabberd_hooks:run_fold(s2s_stream_features,
|
||||
Server, [],
|
||||
[Server])}),
|
||||
{next_state, wait_for_feature_request,
|
||||
NewStateData#state{server = Server}}
|
||||
end;
|
||||
{<<"jabber:server">>, _, Server, true}
|
||||
when StateData#state.authenticated ->
|
||||
send_text(StateData,
|
||||
?STREAM_HEADER(<<" version='1.0'">>)),
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"stream:features">>, attrs = [],
|
||||
children =
|
||||
ejabberd_hooks:run_fold(s2s_stream_features,
|
||||
Server, [],
|
||||
[Server])}),
|
||||
{next_state, stream_established, StateData};
|
||||
{<<"jabber:server">>, <<"jabber:server:dialback">>,
|
||||
_Server, _} when
|
||||
(StateData#state.tls_required and StateData#state.tls_enabled)
|
||||
or (not StateData#state.tls_required) ->
|
||||
send_text(StateData, ?STREAM_HEADER(<<"">>)),
|
||||
{next_state, stream_established, StateData};
|
||||
_ ->
|
||||
send_text(StateData, ?INVALID_NAMESPACE_ERR),
|
||||
{stop, normal, StateData}
|
||||
[#starttls{required = true}]
|
||||
end,
|
||||
case Auth of
|
||||
{error, RemoteServer, CertError}
|
||||
when StateData#state.tls_certverify ->
|
||||
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
|
||||
[StateData#state.server, RemoteServer, CertError]),
|
||||
send_element(StateData,
|
||||
xmpp:serr_policy_violation(CertError, ?MYLANG)),
|
||||
{stop, normal, StateData};
|
||||
{VerifyResult, RemoteServer, Msg} ->
|
||||
{SASL, NewStateData} =
|
||||
case VerifyResult of
|
||||
ok ->
|
||||
{[#sasl_mechanisms{list = [<<"EXTERNAL">>]}],
|
||||
StateData#state{auth_domain = RemoteServer}};
|
||||
error ->
|
||||
?DEBUG("Won't accept certificate of ~s: ~s",
|
||||
[RemoteServer, Msg]),
|
||||
{[], StateData};
|
||||
no_verify ->
|
||||
{[], StateData}
|
||||
end,
|
||||
send_element(NewStateData,
|
||||
#stream_features{
|
||||
sub_els = SASL ++ StartTLS ++
|
||||
ejabberd_hooks:run_fold(
|
||||
s2s_stream_features, Server, [],
|
||||
[Server])}),
|
||||
{next_state, wait_for_feature_request,
|
||||
NewStateData#state{server = Server}}
|
||||
end;
|
||||
#stream_start{to = #jid{lserver = Server},
|
||||
version = {1,0}} when StateData#state.authenticated ->
|
||||
send_header(StateData, {1,0}),
|
||||
send_element(StateData,
|
||||
#stream_features{
|
||||
sub_els = ejabberd_hooks:run_fold(
|
||||
s2s_stream_features, Server, [],
|
||||
[Server])}),
|
||||
{next_state, stream_established, StateData};
|
||||
#stream_start{db_xmlns = ?NS_SERVER_DIALBACK}
|
||||
when (StateData#state.tls_required and StateData#state.tls_enabled)
|
||||
or (not StateData#state.tls_required) ->
|
||||
send_header(StateData, undefined),
|
||||
{next_state, stream_established, StateData};
|
||||
#stream_start{} ->
|
||||
send_header(StateData, {1,0}),
|
||||
send_element(StateData, xmpp:serr_undefined_condition()),
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_header(StateData, {1,0}),
|
||||
send_element(StateData, xmpp:serr_invalid_xml()),
|
||||
{stop, normal, StateData}
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
Txt = xmpp:format_error(Why),
|
||||
send_header(StateData, {1,0}),
|
||||
send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
wait_for_stream({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
<<(?STREAM_HEADER(<<"">>))/binary,
|
||||
(?INVALID_XML_ERR)/binary, (?STREAM_TRAILER)/binary>>),
|
||||
send_header(StateData, {1,0}),
|
||||
send_element(StateData, xmpp:serr_not_well_formed()),
|
||||
{stop, normal, StateData};
|
||||
wait_for_stream(timeout, StateData) ->
|
||||
send_header(StateData, {1,0}),
|
||||
send_element(StateData, xmpp:serr_connection_timeout()),
|
||||
{stop, normal, StateData};
|
||||
wait_for_stream(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
wait_for_feature_request({xmlstreamelement, El},
|
||||
StateData) ->
|
||||
#xmlel{name = Name, attrs = Attrs} = El,
|
||||
TLS = StateData#state.tls,
|
||||
TLSEnabled = StateData#state.tls_enabled,
|
||||
SockMod =
|
||||
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
|
||||
case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of
|
||||
{?NS_TLS, <<"starttls">>}
|
||||
when TLS == true, TLSEnabled == false,
|
||||
SockMod == gen_tcp ->
|
||||
?DEBUG("starttls", []),
|
||||
Socket = StateData#state.socket,
|
||||
TLSOpts1 = case
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, StateData#state.server},
|
||||
fun iolist_to_binary/1) of
|
||||
undefined -> StateData#state.tls_options;
|
||||
CertFile ->
|
||||
[{certfile, CertFile} | lists:keydelete(certfile, 1,
|
||||
StateData#state.tls_options)]
|
||||
end,
|
||||
TLSOpts = case ejabberd_config:get_option(
|
||||
{s2s_tls_compression, StateData#state.server},
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, false) of
|
||||
true -> lists:delete(compression_none, TLSOpts1);
|
||||
false -> [compression_none | TLSOpts1]
|
||||
end,
|
||||
TLSSocket = (StateData#state.sockmod):starttls(Socket,
|
||||
TLSOpts,
|
||||
fxml:element_to_binary(#xmlel{name
|
||||
=
|
||||
<<"proceed">>,
|
||||
attrs
|
||||
=
|
||||
[{<<"xmlns">>,
|
||||
?NS_TLS}],
|
||||
children
|
||||
=
|
||||
[]})),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{socket = TLSSocket, streamid = new_id(),
|
||||
tls_enabled = true, tls_options = TLSOpts}};
|
||||
{?NS_SASL, <<"auth">>} when TLSEnabled ->
|
||||
Mech = fxml:get_attr_s(<<"mechanism">>, Attrs),
|
||||
case Mech of
|
||||
<<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
|
||||
AuthDomain = StateData#state.auth_domain,
|
||||
AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>,
|
||||
AuthDomain),
|
||||
if AllowRemoteHost ->
|
||||
(StateData#state.sockmod):reset_stream(StateData#state.socket),
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"success">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
children = []}),
|
||||
?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
|
||||
[AuthDomain, StateData#state.tls_enabled]),
|
||||
change_shaper(StateData, <<"">>,
|
||||
jid:make(<<"">>, AuthDomain, <<"">>)),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{streamid = new_id(),
|
||||
authenticated = true}};
|
||||
true ->
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"failure">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
children = []}),
|
||||
send_text(StateData, ?STREAM_TRAILER),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"failure">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
children =
|
||||
[#xmlel{name = <<"invalid-mechanism">>,
|
||||
attrs = [], children = []}]}),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
stream_established({xmlstreamelement, El}, StateData)
|
||||
wait_for_feature_request({xmlstreamelement, El}, StateData) ->
|
||||
decode_element(El, wait_for_feature_request, StateData);
|
||||
wait_for_feature_request(#starttls{},
|
||||
#state{tls = true, tls_enabled = false} = StateData) ->
|
||||
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
|
||||
gen_tcp ->
|
||||
?DEBUG("starttls", []),
|
||||
Socket = StateData#state.socket,
|
||||
TLSOpts1 = case
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, StateData#state.server},
|
||||
fun iolist_to_binary/1) of
|
||||
undefined -> StateData#state.tls_options;
|
||||
CertFile ->
|
||||
lists:keystore(certfile, 1,
|
||||
StateData#state.tls_options,
|
||||
{certfile, CertFile})
|
||||
end,
|
||||
TLSOpts2 = case ejabberd_config:get_option(
|
||||
{s2s_cafile, StateData#state.server},
|
||||
fun iolist_to_binary/1) of
|
||||
undefined -> TLSOpts1;
|
||||
CAFile ->
|
||||
lists:keystore(cafile, 1, TLSOpts1,
|
||||
{cafile, CAFile})
|
||||
end,
|
||||
TLSOpts = case ejabberd_config:get_option(
|
||||
{s2s_tls_compression, StateData#state.server},
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, false) of
|
||||
true -> lists:delete(compression_none, TLSOpts2);
|
||||
false -> [compression_none | TLSOpts2]
|
||||
end,
|
||||
TLSSocket = (StateData#state.sockmod):starttls(
|
||||
Socket, TLSOpts,
|
||||
fxml:element_to_binary(
|
||||
xmpp:encode(#starttls_proceed{}))),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{socket = TLSSocket, streamid = new_id(),
|
||||
tls_enabled = true, tls_options = TLSOpts}};
|
||||
_ ->
|
||||
send_element(StateData, #starttls_failure{}),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
wait_for_feature_request({xmlstreamend, _Name},
|
||||
StateData) ->
|
||||
send_text(StateData, ?STREAM_TRAILER),
|
||||
wait_for_feature_request(#sasl_auth{mechanism = Mech},
|
||||
#state{tls_enabled = true} = StateData) ->
|
||||
case Mech of
|
||||
<<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
|
||||
AuthDomain = StateData#state.auth_domain,
|
||||
AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>, AuthDomain),
|
||||
if AllowRemoteHost ->
|
||||
(StateData#state.sockmod):reset_stream(StateData#state.socket),
|
||||
send_element(StateData, #sasl_success{}),
|
||||
?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
|
||||
[AuthDomain, StateData#state.tls_enabled]),
|
||||
change_shaper(StateData, <<"">>, jid:make(AuthDomain)),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{streamid = new_id(),
|
||||
authenticated = true}};
|
||||
true ->
|
||||
Txt = xmpp:mk_text(<<"Denied by ACL">>, ?MYLANG),
|
||||
send_element(StateData,
|
||||
#sasl_failure{reason = 'not-authorized',
|
||||
text = Txt}),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
send_element(StateData, #sasl_failure{reason = 'invalid-mechanism'}),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
wait_for_feature_request({xmlstreamerror, _},
|
||||
StateData) ->
|
||||
send_text(StateData,
|
||||
<<(?INVALID_XML_ERR)/binary,
|
||||
(?STREAM_TRAILER)/binary>>),
|
||||
wait_for_feature_request({xmlstreamerror, _}, StateData) ->
|
||||
send_element(StateData, xmpp:serr_not_well_formed()),
|
||||
{stop, normal, StateData};
|
||||
wait_for_feature_request(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
{stop, normal, StateData};
|
||||
wait_for_feature_request(_Pkt, #state{tls_required = TLSRequired,
|
||||
tls_enabled = TLSEnabled} = StateData)
|
||||
when TLSRequired and not TLSEnabled ->
|
||||
Txt = <<"Use of STARTTLS required">>,
|
||||
send_element(StateData, xmpp:serr_policy_violation(Txt, ?MYLANG)),
|
||||
{stop, normal, StateData};
|
||||
wait_for_feature_request(El, StateData) ->
|
||||
stream_established({xmlstreamelement, El}, StateData).
|
||||
|
||||
stream_established({xmlstreamelement, El}, StateData) ->
|
||||
cancel_timer(StateData#state.timer),
|
||||
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
|
||||
case is_key_packet(El) of
|
||||
{key, To, From, Id, Key} ->
|
||||
?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]),
|
||||
LTo = jid:nameprep(To),
|
||||
LFrom = jid:nameprep(From),
|
||||
case {ejabberd_s2s:allow_host(LTo, LFrom),
|
||||
lists:member(LTo,
|
||||
ejabberd_router:dirty_get_all_domains())}
|
||||
of
|
||||
{true, true} ->
|
||||
ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom),
|
||||
ejabberd_s2s_out:start(LTo, LFrom,
|
||||
{verify, self(), Key,
|
||||
StateData#state.streamid}),
|
||||
Conns = (?DICT):store({LFrom, LTo},
|
||||
wait_for_verification,
|
||||
StateData#state.connections),
|
||||
change_shaper(StateData, LTo,
|
||||
jid:make(<<"">>, LFrom, <<"">>)),
|
||||
{next_state, stream_established,
|
||||
StateData#state{connections = Conns, timer = Timer}};
|
||||
{_, false} ->
|
||||
send_text(StateData, ?HOST_UNKNOWN_ERR),
|
||||
{stop, normal, StateData};
|
||||
{false, _} ->
|
||||
send_text(StateData, ?INVALID_FROM_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
{verify, To, From, Id, Key} ->
|
||||
?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
|
||||
LTo = jid:nameprep(To),
|
||||
LFrom = jid:nameprep(From),
|
||||
Type = case ejabberd_s2s:make_key({LTo, LFrom}, Id) of
|
||||
Key -> <<"valid">>;
|
||||
_ -> <<"invalid">>
|
||||
end,
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"db:verify">>,
|
||||
attrs =
|
||||
[{<<"from">>, To}, {<<"to">>, From},
|
||||
{<<"id">>, Id}, {<<"type">>, Type}],
|
||||
children = []}),
|
||||
{next_state, stream_established,
|
||||
StateData#state{timer = Timer}};
|
||||
_ ->
|
||||
NewEl = jlib:remove_attr(<<"xmlns">>, El),
|
||||
#xmlel{name = Name, attrs = Attrs} = NewEl,
|
||||
From_s = fxml:get_attr_s(<<"from">>, Attrs),
|
||||
From = jid:from_string(From_s),
|
||||
To_s = fxml:get_attr_s(<<"to">>, Attrs),
|
||||
To = jid:from_string(To_s),
|
||||
if (To /= error) and (From /= error) ->
|
||||
LFrom = From#jid.lserver,
|
||||
LTo = To#jid.lserver,
|
||||
if StateData#state.authenticated ->
|
||||
case LFrom == StateData#state.auth_domain andalso
|
||||
lists:member(LTo,
|
||||
ejabberd_router:dirty_get_all_domains())
|
||||
of
|
||||
true ->
|
||||
if (Name == <<"iq">>) or (Name == <<"message">>)
|
||||
or (Name == <<"presence">>) ->
|
||||
ejabberd_hooks:run(s2s_receive_packet, LTo,
|
||||
[From, To, NewEl]),
|
||||
ejabberd_router:route(From, To, NewEl);
|
||||
true -> error
|
||||
end;
|
||||
false -> error
|
||||
end;
|
||||
true ->
|
||||
case (?DICT):find({LFrom, LTo},
|
||||
StateData#state.connections)
|
||||
of
|
||||
{ok, established} ->
|
||||
if (Name == <<"iq">>) or (Name == <<"message">>)
|
||||
or (Name == <<"presence">>) ->
|
||||
ejabberd_hooks:run(s2s_receive_packet, LTo,
|
||||
[From, To, NewEl]),
|
||||
ejabberd_router:route(From, To, NewEl);
|
||||
true -> error
|
||||
end;
|
||||
_ -> error
|
||||
end
|
||||
end;
|
||||
true -> error
|
||||
end,
|
||||
ejabberd_hooks:run(s2s_loop_debug,
|
||||
[{xmlstreamelement, El}]),
|
||||
{next_state, stream_established,
|
||||
StateData#state{timer = Timer}}
|
||||
decode_element(El, stream_established, StateData#state{timer = Timer});
|
||||
stream_established(#db_result{to = To, from = From, key = Key},
|
||||
StateData) ->
|
||||
?DEBUG("GET KEY: ~p", [{To, From, Key}]),
|
||||
case {ejabberd_s2s:allow_host(To, From),
|
||||
lists:member(To, ejabberd_router:dirty_get_all_domains())} of
|
||||
{true, true} ->
|
||||
ejabberd_s2s_out:terminate_if_waiting_delay(To, From),
|
||||
ejabberd_s2s_out:start(To, From,
|
||||
{verify, self(), Key,
|
||||
StateData#state.streamid}),
|
||||
Conns = (?DICT):store({From, To},
|
||||
wait_for_verification,
|
||||
StateData#state.connections),
|
||||
change_shaper(StateData, To, jid:make(From)),
|
||||
{next_state, stream_established,
|
||||
StateData#state{connections = Conns}};
|
||||
{_, false} ->
|
||||
send_element(StateData, xmpp:serr_host_unknown()),
|
||||
{stop, normal, StateData};
|
||||
{false, _} ->
|
||||
send_element(StateData, xmpp:serr_invalid_from()),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
stream_established(#db_verify{to = To, from = From, id = Id, key = Key},
|
||||
StateData) ->
|
||||
?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
|
||||
Type = case ejabberd_s2s:make_key({To, From}, Id) of
|
||||
Key -> valid;
|
||||
_ -> invalid
|
||||
end,
|
||||
send_element(StateData,
|
||||
#db_verify{from = To, to = From, id = Id, type = Type}),
|
||||
{next_state, stream_established, StateData};
|
||||
stream_established(Pkt, StateData) when ?is_stanza(Pkt) ->
|
||||
From = xmpp:get_from(Pkt),
|
||||
To = xmpp:get_to(Pkt),
|
||||
if To /= undefined, From /= undefined ->
|
||||
LFrom = From#jid.lserver,
|
||||
LTo = To#jid.lserver,
|
||||
if StateData#state.authenticated ->
|
||||
case LFrom == StateData#state.auth_domain andalso
|
||||
lists:member(LTo, ejabberd_router:dirty_get_all_domains()) of
|
||||
true ->
|
||||
ejabberd_hooks:run(s2s_receive_packet, LTo,
|
||||
[From, To, Pkt]),
|
||||
ejabberd_router:route(From, To, Pkt);
|
||||
false ->
|
||||
send_error(StateData, Pkt, xmpp:err_not_authorized())
|
||||
end;
|
||||
true ->
|
||||
case (?DICT):find({LFrom, LTo}, StateData#state.connections) of
|
||||
{ok, established} ->
|
||||
ejabberd_hooks:run(s2s_receive_packet, LTo,
|
||||
[From, To, Pkt]),
|
||||
ejabberd_router:route(From, To, Pkt);
|
||||
_ ->
|
||||
send_error(StateData, Pkt, xmpp:err_not_authorized())
|
||||
end
|
||||
end;
|
||||
true ->
|
||||
send_error(StateData, Pkt, xmpp:err_jid_malformed())
|
||||
end,
|
||||
ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
|
||||
{next_state, stream_established, StateData};
|
||||
stream_established({valid, From, To}, StateData) ->
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"db:result">>,
|
||||
attrs =
|
||||
[{<<"from">>, To}, {<<"to">>, From},
|
||||
{<<"type">>, <<"valid">>}],
|
||||
children = []}),
|
||||
#db_result{from = To, to = From, type = valid}),
|
||||
?INFO_MSG("Accepted s2s dialback authentication for ~s (TLS=~p)",
|
||||
[From, StateData#state.tls_enabled]),
|
||||
LFrom = jid:nameprep(From),
|
||||
LTo = jid:nameprep(To),
|
||||
NSD = StateData#state{connections =
|
||||
(?DICT):store({LFrom, LTo}, established,
|
||||
(?DICT):store({From, To}, established,
|
||||
StateData#state.connections)},
|
||||
{next_state, stream_established, NSD};
|
||||
stream_established({invalid, From, To}, StateData) ->
|
||||
send_element(StateData,
|
||||
#xmlel{name = <<"db:result">>,
|
||||
attrs =
|
||||
[{<<"from">>, To}, {<<"to">>, From},
|
||||
{<<"type">>, <<"invalid">>}],
|
||||
children = []}),
|
||||
LFrom = jid:nameprep(From),
|
||||
LTo = jid:nameprep(To),
|
||||
#db_result{from = To, to = From, type = invalid}),
|
||||
NSD = StateData#state{connections =
|
||||
(?DICT):erase({LFrom, LTo},
|
||||
(?DICT):erase({From, To},
|
||||
StateData#state.connections)},
|
||||
{next_state, stream_established, NSD};
|
||||
stream_established({xmlstreamend, _Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
stream_established({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
<<(?INVALID_XML_ERR)/binary,
|
||||
(?STREAM_TRAILER)/binary>>),
|
||||
send_element(StateData, xmpp:serr_not_well_formed()),
|
||||
{stop, normal, StateData};
|
||||
stream_established(timeout, StateData) ->
|
||||
send_element(StateData, xmpp:serr_connection_timeout()),
|
||||
{stop, normal, StateData};
|
||||
stream_established(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
{stop, normal, StateData};
|
||||
stream_established(Pkt, StateData) ->
|
||||
ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
|
||||
{next_state, stream_established, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: StateName/3
|
||||
@ -589,8 +511,14 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
|
||||
handle_info({send_text, Text}, StateName, StateData) ->
|
||||
send_text(StateData, Text),
|
||||
{next_state, StateName, StateData};
|
||||
handle_info({timeout, Timer, _}, _StateName,
|
||||
handle_info({timeout, Timer, _}, StateName,
|
||||
#state{timer = Timer} = StateData) ->
|
||||
if StateName == wait_for_stream ->
|
||||
send_header(StateData, undefined);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
send_element(StateData, xmpp:serr_connection_timeout()),
|
||||
{stop, normal, StateData};
|
||||
handle_info(_, StateName, StateData) ->
|
||||
{next_state, StateName, StateData}.
|
||||
@ -603,6 +531,7 @@ terminate(Reason, _StateName, StateData) ->
|
||||
|| Host <- get_external_hosts(StateData)];
|
||||
_ -> ok
|
||||
end,
|
||||
catch send_trailer(StateData),
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
@ -621,39 +550,55 @@ print_state(State) -> State.
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-spec send_text(state(), iodata()) -> ok.
|
||||
send_text(StateData, Text) ->
|
||||
(StateData#state.sockmod):send(StateData#state.socket,
|
||||
Text).
|
||||
|
||||
-spec send_element(state(), xmpp_element()) -> ok.
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, fxml:element_to_binary(El)).
|
||||
El1 = xmpp:encode(El, ?NS_SERVER),
|
||||
send_text(StateData, fxml:element_to_binary(El1)).
|
||||
|
||||
-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
|
||||
send_error(StateData, Stanza, Error) ->
|
||||
Type = xmpp:get_type(Stanza),
|
||||
if Type == error; Type == result;
|
||||
Type == <<"error">>; Type == <<"result">> ->
|
||||
ok;
|
||||
true ->
|
||||
send_element(StateData, xmpp:make_error(Stanza, Error))
|
||||
end.
|
||||
|
||||
-spec send_trailer(state()) -> ok.
|
||||
send_trailer(StateData) ->
|
||||
send_text(StateData, <<"</stream:stream>">>).
|
||||
|
||||
-spec send_header(state(), undefined | {integer(), integer()}) -> ok.
|
||||
send_header(StateData, Version) ->
|
||||
Header = xmpp:encode(
|
||||
#stream_start{xmlns = ?NS_SERVER,
|
||||
stream_xmlns = ?NS_STREAM,
|
||||
db_xmlns = ?NS_SERVER_DIALBACK,
|
||||
id = StateData#state.streamid,
|
||||
version = Version}),
|
||||
send_text(StateData, fxml:element_to_header(Header)).
|
||||
|
||||
-spec change_shaper(state(), binary(), jid()) -> ok.
|
||||
change_shaper(StateData, Host, JID) ->
|
||||
Shaper = acl:match_rule(Host, StateData#state.shaper,
|
||||
JID),
|
||||
(StateData#state.sockmod):change_shaper(StateData#state.socket,
|
||||
Shaper).
|
||||
|
||||
-spec new_id() -> binary().
|
||||
new_id() -> randoms:get_string().
|
||||
|
||||
-spec cancel_timer(reference()) -> ok.
|
||||
cancel_timer(Timer) ->
|
||||
erlang:cancel_timer(Timer),
|
||||
receive {timeout, Timer, _} -> ok after 0 -> ok end.
|
||||
|
||||
is_key_packet(#xmlel{name = Name, attrs = Attrs,
|
||||
children = Els})
|
||||
when Name == <<"db:result">> ->
|
||||
{key, fxml:get_attr_s(<<"to">>, Attrs),
|
||||
fxml:get_attr_s(<<"from">>, Attrs),
|
||||
fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
|
||||
is_key_packet(#xmlel{name = Name, attrs = Attrs,
|
||||
children = Els})
|
||||
when Name == <<"db:verify">> ->
|
||||
{verify, fxml:get_attr_s(<<"to">>, Attrs),
|
||||
fxml:get_attr_s(<<"from">>, Attrs),
|
||||
fxml:get_attr_s(<<"id">>, Attrs), fxml:get_cdata(Els)};
|
||||
is_key_packet(_) -> false.
|
||||
|
||||
fsm_limit_opts(Opts) ->
|
||||
case lists:keysearch(max_fsm_queue, 1, Opts) of
|
||||
{value, {_, N}} when is_integer(N) -> [{max_queue, N}];
|
||||
@ -666,10 +611,34 @@ fsm_limit_opts(Opts) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec decode_element(xmlel() | xmpp_element(), state_name(), state()) -> fsm_transition().
|
||||
decode_element(#xmlel{} = El, StateName, StateData) ->
|
||||
Opts = if StateName == stream_established ->
|
||||
[ignore_els];
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
try xmpp:decode(El, ?NS_SERVER, Opts) of
|
||||
Pkt -> ?MODULE:StateName(Pkt, StateData)
|
||||
catch error:{xmpp_codec, Why} ->
|
||||
case xmpp:is_stanza(El) of
|
||||
true ->
|
||||
Lang = xmpp:get_lang(El),
|
||||
Txt = xmpp:format_error(Why),
|
||||
send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
{next_state, StateName, StateData}
|
||||
end;
|
||||
decode_element(Pkt, StateName, StateData) ->
|
||||
?MODULE:StateName(Pkt, StateData).
|
||||
|
||||
opt_type(domain_certfile) -> fun iolist_to_binary/1;
|
||||
opt_type(max_fsm_queue) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(s2s_certfile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_cafile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_protocol_options) ->
|
||||
@ -691,6 +660,6 @@ opt_type(s2s_use_starttls) ->
|
||||
(required_trusted) -> required_trusted
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[domain_certfile, max_fsm_queue, s2s_certfile,
|
||||
[domain_certfile, max_fsm_queue, s2s_certfile, s2s_cafile,
|
||||
s2s_ciphers, s2s_dhfile, s2s_protocol_options,
|
||||
s2s_tls_compression, s2s_use_starttls].
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -36,7 +36,7 @@
|
||||
-behaviour(?GEN_FSM).
|
||||
|
||||
%% External exports
|
||||
-export([start/0, start/2, start_link/2, send_text/2,
|
||||
-export([start/2, start_link/2, send_text/2,
|
||||
send_element/2, socket_type/0, transform_listen_option/2]).
|
||||
|
||||
-export([init/1, wait_for_stream/2,
|
||||
@ -44,61 +44,35 @@
|
||||
handle_event/3, handle_sync_event/4, code_change/4,
|
||||
handle_info/3, terminate/3, print_state/1, opt_type/1]).
|
||||
|
||||
-include("ejabberd_service.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-export([get_delegated_ns/1]).
|
||||
-record(state,
|
||||
{socket :: ejabberd_socket:socket_state(),
|
||||
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
|
||||
streamid = <<"">> :: binary(),
|
||||
host_opts = dict:new() :: ?TDICT,
|
||||
host = <<"">> :: binary(),
|
||||
access :: atom(),
|
||||
check_from = true :: boolean()}).
|
||||
|
||||
-type state_name() :: wait_for_stream | wait_for_handshake | stream_established.
|
||||
-type state() :: #state{}.
|
||||
-type fsm_next() :: {next_state, state_name(), state()}.
|
||||
-type fsm_stop() :: {stop, normal, state()}.
|
||||
-type fsm_transition() :: fsm_stop() | fsm_next().
|
||||
|
||||
%-define(DBGFSM, true).
|
||||
|
||||
-ifdef(DBGFSM).
|
||||
|
||||
-define(FSMOPTS, [{debug, [trace]}]).
|
||||
|
||||
-else.
|
||||
|
||||
-define(FSMOPTS, []).
|
||||
|
||||
-endif.
|
||||
|
||||
-define(STREAM_HEADER,
|
||||
<<"<?xml version='1.0'?><stream:stream "
|
||||
"xmlns:stream='http://etherx.jabber.org/stream"
|
||||
"s' xmlns='jabber:component:accept' id='~s' "
|
||||
"from='~s'>">>).
|
||||
|
||||
-define(STREAM_TRAILER, <<"</stream:stream>">>).
|
||||
|
||||
-define(INVALID_HEADER_ERR,
|
||||
<<"<stream:stream xmlns:stream='http://etherx.ja"
|
||||
"bber.org/streams'><stream:error>Invalid "
|
||||
"Stream Header</stream:error></stream:stream>">>).
|
||||
|
||||
-define(INVALID_HANDSHAKE_ERR,
|
||||
<<"<stream:error><not-authorized xmlns='urn:ietf"
|
||||
":params:xml:ns:xmpp-streams'/><text "
|
||||
"xmlns='urn:ietf:params:xml:ns:xmpp-streams' "
|
||||
"xml:lang='en'>Invalid Handshake</text></strea"
|
||||
"m:error></stream:stream>">>).
|
||||
|
||||
-define(INVALID_XML_ERR,
|
||||
fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)).
|
||||
|
||||
-define(INVALID_NS_ERR,
|
||||
fxml:element_to_binary(?SERR_INVALID_NAMESPACE)).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% for xep-0355
|
||||
%% table contans records like {namespace, fitering attributes, pid(),
|
||||
%% host, disco info for general case, bare jid disco info }
|
||||
|
||||
start() ->
|
||||
ets:new(delegated_namespaces, [named_table, public]),
|
||||
ets:new(hooks_tmp, [named_table, public]).
|
||||
|
||||
start(SockData, Opts) ->
|
||||
supervisor:start_child(ejabberd_service_sup,
|
||||
[SockData, Opts]).
|
||||
@ -109,20 +83,9 @@ start_link(SockData, Opts) ->
|
||||
|
||||
socket_type() -> xml_stream.
|
||||
|
||||
get_delegated_ns(FsmRef) ->
|
||||
(?GEN_FSM):sync_send_all_state_event(FsmRef, {get_delegated_ns}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_fsm
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: init/1
|
||||
%% Returns: {ok, StateName, StateData} |
|
||||
%% {ok, StateName, StateData, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, StopReason}
|
||||
%%----------------------------------------------------------------------
|
||||
init([{SockMod, Socket}, Opts]) ->
|
||||
?INFO_MSG("(~w) External service connected", [Socket]),
|
||||
Access = case lists:keysearch(access, 1, Opts) of
|
||||
@ -144,21 +107,6 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
p1_sha:sha(randoms:bytes(20))),
|
||||
dict:from_list([{global, Pass}])
|
||||
end,
|
||||
%% privilege access to entities data
|
||||
PrivAccess = case lists:keysearch(privilege_access, 1, Opts) of
|
||||
{value, {_, PrivAcc}} -> PrivAcc;
|
||||
_ -> []
|
||||
end,
|
||||
Delegations = case lists:keyfind(delegations, 1, Opts) of
|
||||
{delegations, Del} ->
|
||||
lists:foldl(
|
||||
fun({Ns, FiltAttr}, D) when Ns /= ?NS_DELEGATION ->
|
||||
Attr = proplists:get_value(filtering, FiltAttr, []),
|
||||
D ++ [{Ns, Attr}];
|
||||
(_Deleg, D) -> D
|
||||
end, [], Del);
|
||||
false -> []
|
||||
end,
|
||||
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
|
||||
{value, {_, S}} -> S;
|
||||
_ -> none
|
||||
@ -172,223 +120,127 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
SockMod:change_shaper(Socket, Shaper),
|
||||
{ok, wait_for_stream,
|
||||
#state{socket = Socket, sockmod = SockMod,
|
||||
streamid = new_id(), host_opts = HostOpts, access = Access,
|
||||
check_from = CheckFrom, privilege_access = PrivAccess,
|
||||
delegations = Delegations}}.
|
||||
streamid = new_id(), host_opts = HostOpts,
|
||||
access = Access, check_from = CheckFrom}}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: StateName/2
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
wait_for_stream({xmlstreamstart, _Name, Attrs},
|
||||
StateData) ->
|
||||
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
<<"jabber:component:accept">> ->
|
||||
To = fxml:get_attr_s(<<"to">>, Attrs),
|
||||
Host = jid:nameprep(To),
|
||||
if Host == error ->
|
||||
Header = io_lib:format(?STREAM_HEADER,
|
||||
[<<"none">>, ?MYNAME]),
|
||||
send_text(StateData,
|
||||
<<(list_to_binary(Header))/binary,
|
||||
(?INVALID_XML_ERR)/binary,
|
||||
(?STREAM_TRAILER)/binary>>),
|
||||
{stop, normal, StateData};
|
||||
true ->
|
||||
Header = io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.streamid, fxml:crypt(To)]),
|
||||
send_text(StateData, Header),
|
||||
HostOpts = case dict:is_key(Host, StateData#state.host_opts) of
|
||||
true ->
|
||||
StateData#state.host_opts;
|
||||
false ->
|
||||
case dict:find(global, StateData#state.host_opts) of
|
||||
{ok, GlobalPass} ->
|
||||
dict:from_list([{Host, GlobalPass}]);
|
||||
error ->
|
||||
StateData#state.host_opts
|
||||
end
|
||||
end,
|
||||
{next_state, wait_for_handshake,
|
||||
StateData#state{host = Host, host_opts = HostOpts}}
|
||||
end;
|
||||
_ ->
|
||||
send_text(StateData, ?INVALID_HEADER_ERR),
|
||||
{stop, normal, StateData}
|
||||
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
|
||||
try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
|
||||
#stream_start{xmlns = NS_COMPONENT, stream_xmlns = NS_STREAM}
|
||||
when NS_COMPONENT /= ?NS_COMPONENT; NS_STREAM /= ?NS_STREAM ->
|
||||
send_header(StateData, ?MYNAME),
|
||||
send_element(StateData, xmpp:serr_invalid_namespace()),
|
||||
{stop, normal, StateData};
|
||||
#stream_start{to = To} when is_record(To, jid) ->
|
||||
Host = To#jid.lserver,
|
||||
send_header(StateData, Host),
|
||||
HostOpts = case dict:is_key(Host, StateData#state.host_opts) of
|
||||
true ->
|
||||
StateData#state.host_opts;
|
||||
false ->
|
||||
case dict:find(global, StateData#state.host_opts) of
|
||||
{ok, GlobalPass} ->
|
||||
dict:from_list([{Host, GlobalPass}]);
|
||||
error ->
|
||||
StateData#state.host_opts
|
||||
end
|
||||
end,
|
||||
{next_state, wait_for_handshake,
|
||||
StateData#state{host = Host, host_opts = HostOpts}};
|
||||
#stream_start{} ->
|
||||
send_header(StateData, ?MYNAME),
|
||||
send_element(StateData, xmpp:serr_improper_addressing()),
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME),
|
||||
send_element(StateData, xmpp:serr_invalid_xml()),
|
||||
{stop, normal, StateData}
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
Txt = xmpp:format_error(Why),
|
||||
send_header(StateData, ?MYNAME),
|
||||
send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
wait_for_stream({xmlstreamerror, _}, StateData) ->
|
||||
Header = io_lib:format(?STREAM_HEADER,
|
||||
[<<"none">>, ?MYNAME]),
|
||||
send_text(StateData,
|
||||
<<(list_to_binary(Header))/binary, (?INVALID_XML_ERR)/binary,
|
||||
(?STREAM_TRAILER)/binary>>),
|
||||
send_header(StateData, ?MYNAME),
|
||||
send_element(StateData, xmpp:serr_not_well_formed()),
|
||||
{stop, normal, StateData};
|
||||
wait_for_stream(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
wait_for_handshake({xmlstreamelement, El}, StateData) ->
|
||||
#xmlel{name = Name, children = Els} = El,
|
||||
case {Name, fxml:get_cdata(Els)} of
|
||||
{<<"handshake">>, Digest} ->
|
||||
case dict:find(StateData#state.host, StateData#state.host_opts) of
|
||||
{ok, Password} ->
|
||||
case p1_sha:sha(<<(StateData#state.streamid)/binary,
|
||||
Password/binary>>) of
|
||||
Digest ->
|
||||
send_text(StateData, <<"<handshake/>">>),
|
||||
lists:foreach(
|
||||
fun (H) ->
|
||||
ejabberd_router:register_route(H, ?MYNAME),
|
||||
?INFO_MSG("Route registered for service ~p~n",
|
||||
[H]),
|
||||
ejabberd_hooks:run(component_connected,
|
||||
[H])
|
||||
end, dict:fetch_keys(StateData#state.host_opts)),
|
||||
|
||||
mod_privilege:advertise_permissions(StateData),
|
||||
DelegatedNs = mod_delegation:advertise_delegations(StateData),
|
||||
|
||||
RosterAccess = proplists:get_value(roster,
|
||||
StateData#state.privilege_access),
|
||||
|
||||
case proplists:get_value(presence,
|
||||
StateData#state.privilege_access) of
|
||||
<<"managed_entity">> ->
|
||||
mod_privilege:initial_presences(StateData),
|
||||
Fun = mod_privilege:process_presence(self()),
|
||||
add_hooks(user_send_packet, Fun);
|
||||
<<"roster">> when (RosterAccess == <<"both">>) or
|
||||
(RosterAccess == <<"get">>) ->
|
||||
mod_privilege:initial_presences(StateData),
|
||||
Fun = mod_privilege:process_presence(self()),
|
||||
add_hooks(user_send_packet, Fun),
|
||||
Fun2 = mod_privilege:process_roster_presence(self()),
|
||||
add_hooks(s2s_receive_packet, Fun2);
|
||||
_ -> ok
|
||||
end,
|
||||
{next_state, stream_established,
|
||||
StateData#state{delegations = DelegatedNs}};
|
||||
_ ->
|
||||
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ -> {next_state, wait_for_handshake, StateData}
|
||||
decode_element(El, wait_for_handshake, StateData);
|
||||
wait_for_handshake(#handshake{data = Digest}, StateData) ->
|
||||
case dict:find(StateData#state.host, StateData#state.host_opts) of
|
||||
{ok, Password} ->
|
||||
case p1_sha:sha(<<(StateData#state.streamid)/binary,
|
||||
Password/binary>>) of
|
||||
Digest ->
|
||||
send_element(StateData, #handshake{}),
|
||||
lists:foreach(
|
||||
fun (H) ->
|
||||
ejabberd_router:register_route(H, ?MYNAME),
|
||||
?INFO_MSG("Route registered for service ~p~n",
|
||||
[H]),
|
||||
ejabberd_hooks:run(component_connected, [H])
|
||||
end, dict:fetch_keys(StateData#state.host_opts)),
|
||||
{next_state, stream_established, StateData};
|
||||
_ ->
|
||||
send_element(StateData, xmpp:serr_not_authorized()),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
send_element(StateData, xmpp:serr_not_authorized()),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
wait_for_handshake({xmlstreamend, _Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
wait_for_handshake({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
<<(?INVALID_XML_ERR)/binary,
|
||||
(?STREAM_TRAILER)/binary>>),
|
||||
send_element(StateData, xmpp:serr_not_well_formed()),
|
||||
{stop, normal, StateData};
|
||||
wait_for_handshake(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
{stop, normal, StateData};
|
||||
wait_for_handshake(_Pkt, StateData) ->
|
||||
{next_state, wait_for_handshake, StateData}.
|
||||
|
||||
stream_established({xmlstreamelement, El}, StateData) ->
|
||||
NewEl = jlib:remove_attr(<<"xmlns">>, El),
|
||||
#xmlel{name = Name, attrs = Attrs} = NewEl,
|
||||
From = fxml:get_attr_s(<<"from">>, Attrs),
|
||||
FromJID = case StateData#state.check_from of
|
||||
%% If the admin does not want to check the from field
|
||||
%% when accept packets from any address.
|
||||
%% In this case, the component can send packet of
|
||||
%% behalf of the server users.
|
||||
false -> jid:from_string(From);
|
||||
%% The default is the standard behaviour in XEP-0114
|
||||
_ ->
|
||||
FromJID1 = jid:from_string(From),
|
||||
case FromJID1 of
|
||||
#jid{lserver = Server} ->
|
||||
case dict:is_key(Server, StateData#state.host_opts) of
|
||||
true -> FromJID1;
|
||||
false -> error
|
||||
end;
|
||||
_ -> error
|
||||
end
|
||||
end,
|
||||
To = fxml:get_attr_s(<<"to">>, Attrs),
|
||||
ToJID = case To of
|
||||
<<"">> -> error;
|
||||
_ -> jid:from_string(To)
|
||||
end,
|
||||
if (Name == <<"iq">>) and (ToJID /= error) and (FromJID /= error) ->
|
||||
mod_privilege:process_iq(StateData, FromJID, ToJID, NewEl);
|
||||
(Name == <<"presence">>) and (ToJID /= error) and (FromJID /= error) ->
|
||||
ejabberd_router:route(FromJID, ToJID, NewEl);
|
||||
(Name == <<"message">>) and (ToJID /= error) and (FromJID /= error) ->
|
||||
mod_privilege:process_message(StateData, FromJID, ToJID, NewEl);
|
||||
decode_element(El, stream_established, StateData);
|
||||
stream_established(El, StateData) when ?is_stanza(El) ->
|
||||
From = xmpp:get_from(El),
|
||||
To = xmpp:get_to(El),
|
||||
Lang = xmpp:get_lang(El),
|
||||
if From == undefined orelse To == undefined ->
|
||||
Txt = <<"Missing 'from' or 'to' attribute">>,
|
||||
send_error(StateData, El, xmpp:err_jid_malformed(Txt, Lang));
|
||||
true ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
|
||||
Txt = <<"Incorrect stanza name or from/to JID">>,
|
||||
Err = jlib:make_error_reply(NewEl, ?ERRT_BAD_REQUEST(Lang, Txt)),
|
||||
send_element(StateData, Err),
|
||||
error
|
||||
case check_from(From, StateData) of
|
||||
true ->
|
||||
ejabberd_router:route(From, To, El);
|
||||
false ->
|
||||
Txt = <<"Improper domain part of 'from' attribute">>,
|
||||
send_error(StateData, El, xmpp:err_not_allowed(Txt, Lang))
|
||||
end
|
||||
end,
|
||||
{next_state, stream_established, StateData};
|
||||
stream_established({xmlstreamend, _Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
stream_established({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
<<(?INVALID_XML_ERR)/binary,
|
||||
(?STREAM_TRAILER)/binary>>),
|
||||
send_element(StateData, xmpp:serr_not_well_formed()),
|
||||
{stop, normal, StateData};
|
||||
stream_established(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
{stop, normal, StateData};
|
||||
stream_established(_Event, StateData) ->
|
||||
{next_state, stream_established, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: StateName/3
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {reply, Reply, NextStateName, NextStateData} |
|
||||
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData} |
|
||||
%% {stop, Reason, Reply, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
%state_name(Event, From, StateData) ->
|
||||
% Reply = ok,
|
||||
% {reply, Reply, state_name, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_event/3
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_event(_Event, StateName, StateData) ->
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_sync_event/4
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {reply, Reply, NextStateName, NextStateData} |
|
||||
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData} |
|
||||
%% {stop, Reason, Reply, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_sync_event({get_delegated_ns}, _From, StateName, StateData) ->
|
||||
Reply = {StateData#state.host, StateData#state.delegations},
|
||||
{reply, Reply, StateName, StateData};
|
||||
|
||||
handle_sync_event(_Event, _From, StateName, StateData) ->
|
||||
handle_sync_event(_Event, _From, StateName,
|
||||
StateData) ->
|
||||
Reply = ok, {reply, Reply, StateName, StateData}.
|
||||
|
||||
code_change(_OldVsn, StateName, StateData, _Extra) ->
|
||||
{ok, StateName, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_info/3
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_info({send_text, Text}, StateName, StateData) ->
|
||||
send_text(StateData, Text),
|
||||
{next_state, StateName, StateData};
|
||||
@ -397,64 +249,20 @@ handle_info({send_element, El}, StateName, StateData) ->
|
||||
{next_state, StateName, StateData};
|
||||
handle_info({route, From, To, Packet}, StateName,
|
||||
StateData) ->
|
||||
case acl:match_rule(global, StateData#state.access,
|
||||
From)
|
||||
of
|
||||
case acl:match_rule(global, StateData#state.access, From) of
|
||||
allow ->
|
||||
#xmlel{name = Name, attrs = Attrs, children = Els} =
|
||||
Packet,
|
||||
Attrs2 =
|
||||
jlib:replace_from_to_attrs(jid:to_string(From),
|
||||
jid:to_string(To), Attrs),
|
||||
Text = fxml:element_to_binary(#xmlel{name = Name,
|
||||
attrs = Attrs2, children = Els}),
|
||||
send_text(StateData, Text);
|
||||
deny ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
|
||||
ejabberd_router:route_error(To, From, Err, Packet)
|
||||
Pkt = xmpp:set_from_to(Packet, From, To),
|
||||
send_element(StateData, Pkt);
|
||||
deny ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end,
|
||||
{next_state, StateName, StateData};
|
||||
|
||||
handle_info({user_presence, Packet, From},
|
||||
stream_established, StateData) ->
|
||||
To = jid:from_string(StateData#state.host),
|
||||
PacketNew = jlib:replace_from_to(From, To, Packet),
|
||||
send_element(StateData, PacketNew),
|
||||
{next_state, stream_established, StateData};
|
||||
|
||||
handle_info({roster_presence, Packet, From},
|
||||
stream_established, StateData) ->
|
||||
%% check that current presence stanza is equivalent to last
|
||||
PresenceNew = jlib:remove_attr(<<"to">>, Packet),
|
||||
Dict = StateData#state.last_pres,
|
||||
LastPresence =
|
||||
try dict:fetch(From, Dict)
|
||||
catch _:_ ->
|
||||
undefined
|
||||
end,
|
||||
case mod_privilege:compare_presences(LastPresence, PresenceNew) of
|
||||
false ->
|
||||
#xmlel{attrs = Attrs} = PresenceNew,
|
||||
Presence = PresenceNew#xmlel{attrs = [{<<"to">>, StateData#state.host} | Attrs]},
|
||||
send_element(StateData, Presence),
|
||||
DictNew = dict:store(From, PresenceNew, Dict),
|
||||
StateDataNew = StateData#state{last_pres = DictNew},
|
||||
{next_state, stream_established, StateDataNew};
|
||||
_ ->
|
||||
{next_state, stream_established, StateData}
|
||||
end;
|
||||
|
||||
handle_info(Info, StateName, StateData) ->
|
||||
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: terminate/3
|
||||
%% Purpose: Shutdown the fsm
|
||||
%% Returns: any
|
||||
%%----------------------------------------------------------------------
|
||||
terminate(Reason, StateName, StateData) ->
|
||||
?INFO_MSG("terminated: ~p", [Reason]),
|
||||
case StateName of
|
||||
@ -462,30 +270,12 @@ terminate(Reason, StateName, StateData) ->
|
||||
lists:foreach(fun (H) ->
|
||||
ejabberd_router:unregister_route(H),
|
||||
ejabberd_hooks:run(component_disconnected,
|
||||
[StateData#state.host, Reason])
|
||||
[H, Reason])
|
||||
end,
|
||||
dict:fetch_keys(StateData#state.host_opts)),
|
||||
|
||||
lists:foreach(fun({Ns, _FilterAttr}) ->
|
||||
ets:delete(delegated_namespaces, Ns),
|
||||
remove_iq_handlers(Ns)
|
||||
end, StateData#state.delegations),
|
||||
|
||||
RosterAccess = proplists:get_value(roster, StateData#state.privilege_access),
|
||||
case proplists:get_value(presence, StateData#state.privilege_access) of
|
||||
<<"managed_entity">> ->
|
||||
Fun = mod_privilege:process_presence(self()),
|
||||
remove_hooks(user_send_packet, Fun);
|
||||
<<"roster">> when (RosterAccess == <<"both">>) or
|
||||
(RosterAccess == <<"get">>) ->
|
||||
Fun = mod_privilege:process_presence(self()),
|
||||
remove_hooks(user_send_packet, Fun),
|
||||
Fun2 = mod_privilege:process_roster_presence(self()),
|
||||
remove_hooks(s2s_receive_packet, Fun2);
|
||||
_ -> ok
|
||||
end;
|
||||
dict:fetch_keys(StateData#state.host_opts));
|
||||
_ -> ok
|
||||
end,
|
||||
catch send_trailer(StateData),
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
@ -500,13 +290,68 @@ print_state(State) -> State.
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-spec send_text(state(), iodata()) -> ok.
|
||||
send_text(StateData, Text) ->
|
||||
(StateData#state.sockmod):send(StateData#state.socket,
|
||||
Text).
|
||||
|
||||
-spec send_element(state(), xmpp_element()) -> ok.
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, fxml:element_to_binary(El)).
|
||||
El1 = xmpp:encode(El, ?NS_COMPONENT),
|
||||
send_text(StateData, fxml:element_to_binary(El1)).
|
||||
|
||||
-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
|
||||
send_error(StateData, Stanza, Error) ->
|
||||
Type = xmpp:get_type(Stanza),
|
||||
if Type == error; Type == result;
|
||||
Type == <<"error">>; Type == <<"result">> ->
|
||||
ok;
|
||||
true ->
|
||||
send_element(StateData, xmpp:make_error(Stanza, Error))
|
||||
end.
|
||||
|
||||
-spec send_header(state(), binary()) -> ok.
|
||||
send_header(StateData, Host) ->
|
||||
Header = xmpp:encode(
|
||||
#stream_start{xmlns = ?NS_COMPONENT,
|
||||
stream_xmlns = ?NS_STREAM,
|
||||
from = jid:make(Host),
|
||||
id = StateData#state.streamid}),
|
||||
send_text(StateData, fxml:element_to_header(Header)).
|
||||
|
||||
-spec send_trailer(state()) -> ok.
|
||||
send_trailer(StateData) ->
|
||||
send_text(StateData, <<"</stream:stream>">>).
|
||||
|
||||
-spec decode_element(xmlel(), state_name(), state()) -> fsm_transition().
|
||||
decode_element(#xmlel{} = El, StateName, StateData) ->
|
||||
try xmpp:decode(El, ?NS_COMPONENT, [ignore_els]) of
|
||||
Pkt -> ?MODULE:StateName(Pkt, StateData)
|
||||
catch error:{xmpp_codec, Why} ->
|
||||
case xmpp:is_stanza(El) of
|
||||
true ->
|
||||
Lang = xmpp:get_lang(El),
|
||||
Txt = xmpp:format_error(Why),
|
||||
send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
{next_state, StateName, StateData}
|
||||
end.
|
||||
|
||||
-spec check_from(jid(), state()) -> boolean().
|
||||
check_from(_From, #state{check_from = false}) ->
|
||||
%% If the admin does not want to check the from field
|
||||
%% when accept packets from any address.
|
||||
%% In this case, the component can send packet of
|
||||
%% behalf of the server users.
|
||||
true;
|
||||
check_from(From, StateData) ->
|
||||
%% The default is the standard behaviour in XEP-0114
|
||||
Server = From#jid.lserver,
|
||||
dict:is_key(Server, StateData#state.host_opts).
|
||||
|
||||
-spec new_id() -> binary().
|
||||
new_id() -> randoms:get_string().
|
||||
|
||||
transform_listen_option({hosts, Hosts, O}, Opts) ->
|
||||
@ -543,19 +388,3 @@ fsm_limit_opts(Opts) ->
|
||||
opt_type(max_fsm_queue) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(_) -> [max_fsm_queue].
|
||||
|
||||
remove_iq_handlers(Ns) ->
|
||||
lists:foreach(fun(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, Ns),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, Ns)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
add_hooks(Hook, Fun) ->
|
||||
lists:foreach(fun(Host) ->
|
||||
ejabberd_hooks:add(Hook, Host,Fun, 100)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
remove_hooks(Hook, Fun) ->
|
||||
lists:foreach(fun(Host) ->
|
||||
ejabberd_hooks:delete(Hook, Host, Fun, 100)
|
||||
end, ?MYHOSTS).
|
||||
|
@ -78,7 +78,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
@ -98,10 +98,19 @@
|
||||
%% default value for the maximum number of user connections
|
||||
-define(MAX_USER_SESSIONS, infinity).
|
||||
|
||||
-type broadcast() :: {broadcast, broadcast_data()}.
|
||||
|
||||
-type broadcast_data() ::
|
||||
{rebind, pid(), binary()} | %% ejabberd_c2s
|
||||
{item, ljid(), mod_roster:subscription()} | %% mod_roster/mod_shared_roster
|
||||
{exit, binary()} | %% mod_roster/mod_shared_roster
|
||||
{privacy_list, mod_privacy:userlist(), binary()} | %% mod_privacy
|
||||
{blocking, unblock_all | {block | unblock, [ljid()]}}. %% mod_blocking
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
-export_type([sid/0]).
|
||||
-export_type([sid/0, info/0]).
|
||||
|
||||
start() ->
|
||||
ChildSpec = {?MODULE, {?MODULE, start_link, []},
|
||||
@ -111,7 +120,7 @@ start() ->
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec route(jid(), jid(), xmlel() | broadcast()) -> ok.
|
||||
-spec route(jid(), jid(), stanza() | broadcast()) -> ok.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
@ -150,23 +159,22 @@ close_session(SID, User, Server, Resource) ->
|
||||
ejabberd_hooks:run(sm_remove_connection_hook,
|
||||
JID#jid.lserver, [SID, JID, Info]).
|
||||
|
||||
-spec check_in_subscription(any(), binary(), binary(),
|
||||
any(), any(), any()) -> any().
|
||||
|
||||
-spec check_in_subscription(boolean(), binary(), binary(), jid(),
|
||||
subscribe | subscribed | unsubscribe | unsubscribed,
|
||||
binary()) -> boolean() | {stop, false}.
|
||||
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true -> Acc;
|
||||
false -> {stop, false}
|
||||
end.
|
||||
|
||||
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
|
||||
-spec bounce_offline_message(jid(), jid(), message()) -> stop.
|
||||
|
||||
bounce_offline_message(From, To, Packet) ->
|
||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Txt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
Err = xmpp:err_service_unavailable(Txt, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err),
|
||||
stop.
|
||||
|
||||
-spec disconnect_removed_user(binary(), binary()) -> ok.
|
||||
@ -225,7 +233,7 @@ get_user_info(User, Server, Resource) ->
|
||||
end.
|
||||
|
||||
-spec set_presence(sid(), binary(), binary(), binary(),
|
||||
prio(), xmlel(), info()) -> ok.
|
||||
prio(), presence(), info()) -> ok.
|
||||
|
||||
set_presence(SID, User, Server, Resource, Priority,
|
||||
Presence, Info) ->
|
||||
@ -288,7 +296,7 @@ get_offline_info(Time, User, Server, Resource) ->
|
||||
[#session{sid = {Time, _}, info = Info}] ->
|
||||
case proplists:get_bool(offline, Info) of
|
||||
true ->
|
||||
Info;
|
||||
Info;
|
||||
false ->
|
||||
none
|
||||
end;
|
||||
@ -436,164 +444,105 @@ is_online(#session{info = Info}) ->
|
||||
not proplists:get_bool(offline, Info).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
-spec do_route(jid(), jid(), stanza() | broadcast()) -> any().
|
||||
do_route(From, #jid{lresource = <<"">>} = To, {broadcast, _} = Packet) ->
|
||||
?DEBUG("processing broadcast to bare JID: ~p", [Packet]),
|
||||
lists:foreach(
|
||||
fun(R) ->
|
||||
do_route(From, jid:replace_resource(To, R), Packet)
|
||||
end, get_user_resources(To#jid.user, To#jid.server));
|
||||
do_route(From, To, {broadcast, _} = Packet) ->
|
||||
case To#jid.lresource of
|
||||
<<"">> ->
|
||||
lists:foreach(fun(R) ->
|
||||
do_route(From,
|
||||
jid:replace_resource(To, R),
|
||||
Packet)
|
||||
end,
|
||||
get_user_resources(To#jid.user, To#jid.server));
|
||||
_ ->
|
||||
{U, S, R} = jid:tolower(To),
|
||||
Mod = get_sm_backend(S),
|
||||
case online(Mod:get_sessions(U, S, R)) of
|
||||
[] ->
|
||||
?DEBUG("packet dropped~n", []);
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end
|
||||
?DEBUG("processing broadcast to full JID: ~p", [Packet]),
|
||||
{U, S, R} = jid:tolower(To),
|
||||
Mod = get_sm_backend(S),
|
||||
case online(Mod:get_sessions(U, S, R)) of
|
||||
[] ->
|
||||
?DEBUG("dropping broadcast to unavailable resourse: ~p", [Packet]);
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p: ~p", [Pid, Packet]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end;
|
||||
do_route(From, To, #xmlel{} = Packet) ->
|
||||
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket "
|
||||
"~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
do_route(From, To, #presence{type = T, status = Status} = Packet)
|
||||
when T == subscribe; T == subscribed; T == unsubscribe; T == unsubscribed ->
|
||||
?DEBUG("processing subscription:~n~s", [xmpp:pp(Packet)]),
|
||||
#jid{user = User, server = Server,
|
||||
luser = LUser, lserver = LServer, lresource = LResource} = To,
|
||||
#xmlel{name = Name, attrs = Attrs} = Packet,
|
||||
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
|
||||
case LResource of
|
||||
<<"">> ->
|
||||
case Name of
|
||||
<<"presence">> ->
|
||||
{Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs)
|
||||
of
|
||||
<<"subscribe">> ->
|
||||
Reason = fxml:get_path_s(Packet,
|
||||
[{elem,
|
||||
<<"status">>},
|
||||
cdata]),
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server,
|
||||
From,
|
||||
subscribe,
|
||||
Reason]),
|
||||
true};
|
||||
<<"subscribed">> ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server,
|
||||
From,
|
||||
subscribed,
|
||||
<<"">>]),
|
||||
true};
|
||||
<<"unsubscribe">> ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server,
|
||||
From,
|
||||
unsubscribe,
|
||||
<<"">>]),
|
||||
true};
|
||||
<<"unsubscribed">> ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server,
|
||||
From,
|
||||
unsubscribed,
|
||||
<<"">>]),
|
||||
true};
|
||||
_ -> {true, false}
|
||||
end,
|
||||
if Pass ->
|
||||
PResources = get_user_present_resources(LUser, LServer),
|
||||
lists:foreach(fun ({_, R}) ->
|
||||
do_route(From,
|
||||
jid:replace_resource(To,
|
||||
R),
|
||||
Packet)
|
||||
end,
|
||||
PResources);
|
||||
true -> ok
|
||||
end;
|
||||
<<"message">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"chat">> -> route_message(From, To, Packet, chat);
|
||||
<<"headline">> -> route_message(From, To, Packet, headline);
|
||||
<<"error">> -> ok;
|
||||
<<"groupchat">> ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
route_message(From, To, Packet, normal)
|
||||
end;
|
||||
<<"iq">> -> process_iq(From, To, Packet);
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
case Name of
|
||||
<<"message">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"chat">> -> route_message(From, To, Packet, chat);
|
||||
<<"headline">> -> ok;
|
||||
<<"error">> -> ok;
|
||||
<<"groupchat">> ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
route_message(From, To, Packet, normal)
|
||||
end;
|
||||
<<"iq">> ->
|
||||
case fxml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> -> ok;
|
||||
<<"result">> -> ok;
|
||||
_ ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
_ -> ?DEBUG("packet dropped~n", [])
|
||||
end;
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end
|
||||
luser = LUser, lserver = LServer} = To,
|
||||
Reason = if T == subscribe -> xmpp:get_text(Status);
|
||||
true -> <<"">>
|
||||
end,
|
||||
case is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer, false,
|
||||
[User, Server, From, T, Reason]) of
|
||||
true ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
lists:foreach(
|
||||
fun(#session{sid = SID, usr = {_, _, R},
|
||||
priority = Prio}) when is_integer(Prio) ->
|
||||
Pid = element(2, SID),
|
||||
?DEBUG("sending to process ~p:~n~s",
|
||||
[Pid, xmpp:pp(Packet)]),
|
||||
Pid ! {route, From, jid:replace_resource(To, R), Packet};
|
||||
(_) ->
|
||||
ok
|
||||
end, online(Mod:get_sessions(LUser, LServer)));
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
do_route(From, #jid{lresource = <<"">>} = To, #presence{} = Packet) ->
|
||||
?DEBUG("processing presence to bare JID:~n~s", [xmpp:pp(Packet)]),
|
||||
{LUser, LServer, _} = jid:tolower(To),
|
||||
lists:foreach(
|
||||
fun({_, R}) ->
|
||||
do_route(From, jid:replace_resource(To, R), Packet)
|
||||
end, get_user_present_resources(LUser, LServer));
|
||||
do_route(From, #jid{lresource = <<"">>} = To, #message{type = T} = Packet) ->
|
||||
?DEBUG("processing message to bare JID:~n~s", [xmpp:pp(Packet)]),
|
||||
if T == chat; T == headline; T == normal; T == groupchat ->
|
||||
route_message(From, To, Packet, T);
|
||||
true ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:err_service_unavailable(ErrTxt, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end;
|
||||
do_route(From, #jid{lresource = <<"">>} = To, #iq{} = Packet) ->
|
||||
?DEBUG("processing IQ to bare JID:~n~s", [xmpp:pp(Packet)]),
|
||||
process_iq(From, To, Packet);
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("processing packet to full JID:~n~s", [xmpp:pp(Packet)]),
|
||||
{LUser, LServer, LResource} = jid:tolower(To),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
case Packet of
|
||||
#message{type = T} when T == chat; T == normal;
|
||||
T == headline; T == groupchat ->
|
||||
route_message(From, To, Packet, T);
|
||||
#presence{} ->
|
||||
?DEBUG("dropping presence to unavalable resource:~n~s",
|
||||
[xmpp:pp(Packet)]);
|
||||
_ ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:err_service_unavailable(ErrTxt, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end;
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p:~n~s", [Pid, xmpp:pp(Packet)]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end.
|
||||
|
||||
%% The default list applies to the user as a whole,
|
||||
%% and is processed if there is no active list set
|
||||
%% for the target session/resource to which a stanza is addressed,
|
||||
%% or if there are no current sessions for the user.
|
||||
-spec is_privacy_allow(jid(), jid(), stanza()) -> boolean().
|
||||
is_privacy_allow(From, To, Packet) ->
|
||||
User = To#jid.user,
|
||||
Server = To#jid.server,
|
||||
@ -604,6 +553,7 @@ is_privacy_allow(From, To, Packet) ->
|
||||
|
||||
%% Check if privacy rules allow this delivery
|
||||
%% Function copied from ejabberd_c2s.erl
|
||||
-spec is_privacy_allow(jid(), jid(), stanza(), #userlist{}) -> boolean().
|
||||
is_privacy_allow(From, To, Packet, PrivacyList) ->
|
||||
User = To#jid.user,
|
||||
Server = To#jid.server,
|
||||
@ -613,14 +563,15 @@ is_privacy_allow(From, To, Packet, PrivacyList) ->
|
||||
[User, Server, PrivacyList, {From, To, Packet},
|
||||
in]).
|
||||
|
||||
-spec route_message(jid(), jid(), message(), message_type()) -> any().
|
||||
route_message(From, To, Packet, Type) ->
|
||||
LUser = To#jid.luser,
|
||||
LServer = To#jid.lserver,
|
||||
PrioRes = get_user_present_resources(LUser, LServer),
|
||||
case catch lists:max(PrioRes) of
|
||||
{Priority, _R}
|
||||
when is_integer(Priority), Priority >= 0 ->
|
||||
lists:foreach(fun ({P, R}) when P == Priority;
|
||||
{MaxPrio, MaxRes}
|
||||
when is_integer(MaxPrio), MaxPrio >= 0 ->
|
||||
lists:foreach(fun ({P, R}) when P == MaxPrio;
|
||||
(P >= 0) and (Type == headline) ->
|
||||
LResource = jid:resourceprep(R),
|
||||
Mod = get_sm_backend(LServer),
|
||||
@ -632,34 +583,44 @@ route_message(From, To, Packet, Type) ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
LMaxRes = jid:resourceprep(MaxRes),
|
||||
Packet1 = maybe_mark_as_copy(Packet,
|
||||
LResource,
|
||||
LMaxRes,
|
||||
P, MaxPrio),
|
||||
Pid ! {route, From, To, Packet1}
|
||||
end;
|
||||
%% Ignore other priority:
|
||||
({_Prio, _Res}) -> ok
|
||||
end,
|
||||
PrioRes);
|
||||
_ ->
|
||||
case Type of
|
||||
headline -> ok;
|
||||
_ ->
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) andalso
|
||||
is_privacy_allow(From, To, Packet) of
|
||||
true ->
|
||||
ejabberd_hooks:run(offline_message_hook, LServer,
|
||||
[From, To, Packet]);
|
||||
false ->
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) andalso
|
||||
is_privacy_allow(From, To, Packet) of
|
||||
true ->
|
||||
ejabberd_hooks:run(offline_message_hook, LServer,
|
||||
[From, To, Packet]);
|
||||
false ->
|
||||
Err = xmpp:err_service_unavailable(),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
-spec maybe_mark_as_copy(message(), binary(), binary(), integer(), integer())
|
||||
-> message().
|
||||
maybe_mark_as_copy(Packet, R, R, P, P) ->
|
||||
Packet;
|
||||
maybe_mark_as_copy(Packet, _, _, P, P) ->
|
||||
xmpp:put_meta(Packet, sm_copy, true);
|
||||
maybe_mark_as_copy(Packet, _, _, _, _) ->
|
||||
Packet.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
-spec clean_session_list([#session{}]) -> [#session{}].
|
||||
clean_session_list(Ss) ->
|
||||
clean_session_list(lists:keysort(#session.usr, Ss), []).
|
||||
|
||||
-spec clean_session_list([#session{}], [#session{}]) -> [#session{}].
|
||||
clean_session_list([], Res) -> Res;
|
||||
clean_session_list([S], Res) -> [S | Res];
|
||||
clean_session_list([S1, S2 | Rest], Res) ->
|
||||
@ -674,6 +635,7 @@ clean_session_list([S1, S2 | Rest], Res) ->
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%% On new session, check if some existing connections need to be replace
|
||||
-spec check_for_sessions_to_replace(binary(), binary(), binary()) -> ok | replaced.
|
||||
check_for_sessions_to_replace(User, Server, Resource) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
@ -681,6 +643,7 @@ check_for_sessions_to_replace(User, Server, Resource) ->
|
||||
check_existing_resources(LUser, LServer, LResource),
|
||||
check_max_sessions(LUser, LServer).
|
||||
|
||||
-spec check_existing_resources(binary(), binary(), binary()) -> ok.
|
||||
check_existing_resources(LUser, LServer, LResource) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer, LResource),
|
||||
@ -704,6 +667,7 @@ check_existing_resources(LUser, LServer, LResource) ->
|
||||
is_existing_resource(LUser, LServer, LResource) ->
|
||||
[] /= get_resource_sessions(LUser, LServer, LResource).
|
||||
|
||||
-spec get_resource_sessions(binary(), binary(), binary()) -> [sid()].
|
||||
get_resource_sessions(User, Server, Resource) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
@ -711,6 +675,7 @@ get_resource_sessions(User, Server, Resource) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
[S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))].
|
||||
|
||||
-spec check_max_sessions(binary(), binary()) -> ok | replaced.
|
||||
check_max_sessions(LUser, LServer) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer),
|
||||
@ -731,6 +696,7 @@ check_max_sessions(LUser, LServer) ->
|
||||
%% This option defines the max number of time a given users are allowed to
|
||||
%% log in
|
||||
%% Defaults to infinity
|
||||
-spec get_max_user_sessions(binary(), binary()) -> infinity | non_neg_integer().
|
||||
get_max_user_sessions(LUser, Host) ->
|
||||
case acl:match_rule(Host, max_user_sessions,
|
||||
jid:make(LUser, Host, <<"">>))
|
||||
@ -742,36 +708,31 @@ get_max_user_sessions(LUser, Host) ->
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = XMLNS, lang = Lang} ->
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
ResIQ = Module:Function(From, To, IQ),
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
true -> ok
|
||||
end;
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply -> ok;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
ok
|
||||
end.
|
||||
-spec process_iq(jid(), jid(), iq()) -> any().
|
||||
process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
|
||||
when T == get; T == set ->
|
||||
XMLNS = xmpp:get_ns(El),
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
|
||||
[{_, Module, Function}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, no_queue,
|
||||
From, To, Packet);
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
From, To, Packet);
|
||||
[] ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
Err = xmpp:err_service_unavailable(Txt, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end;
|
||||
process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
|
||||
Err = xmpp:err_bad_request(),
|
||||
ejabberd_router:route_error(To, From, Packet, Err),
|
||||
ok;
|
||||
process_iq(_From, _To, #iq{}) ->
|
||||
ok.
|
||||
|
||||
-spec force_update_presence({binary(), binary()}) -> any().
|
||||
-spec force_update_presence({binary(), binary()}) -> ok.
|
||||
|
||||
force_update_presence({LUser, LServer}) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
|
@ -26,7 +26,6 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sm.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
@ -81,10 +80,10 @@ get_sessions(LUser, LServer, LResource) ->
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
update_tables(),
|
||||
mnesia:create_table(session,
|
||||
ejabberd_mnesia:create(?MODULE, session,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, session)}]),
|
||||
mnesia:create_table(session_counter,
|
||||
ejabberd_mnesia:create(?MODULE, session_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, session_counter)}]),
|
||||
mnesia:add_table_index(session, usr),
|
||||
|
@ -19,7 +19,6 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sm.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
|
@ -24,7 +24,6 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sm.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
@ -148,7 +147,7 @@ timestamp_to_now(I) ->
|
||||
{MSec, Sec, USec}.
|
||||
|
||||
dec_priority(Prio) ->
|
||||
case catch jlib:binary_to_integer(Prio) of
|
||||
case catch binary_to_integer(Prio) of
|
||||
{'EXIT', _} ->
|
||||
undefined;
|
||||
Int ->
|
||||
@ -158,7 +157,7 @@ dec_priority(Prio) ->
|
||||
enc_priority(undefined) ->
|
||||
<<"">>;
|
||||
enc_priority(Int) when is_integer(Int) ->
|
||||
jlib:integer_to_binary(Int).
|
||||
integer_to_binary(Int).
|
||||
|
||||
row_to_session(LServer, {USec, PidS, User, Resource, PrioS, InfoS}) ->
|
||||
Now = timestamp_to_now(USec),
|
||||
|
@ -31,6 +31,7 @@
|
||||
-export([start/4,
|
||||
connect/3,
|
||||
connect/4,
|
||||
connect/5,
|
||||
starttls/2,
|
||||
starttls/3,
|
||||
compress/1,
|
||||
@ -41,6 +42,7 @@
|
||||
change_shaper/2,
|
||||
monitor/1,
|
||||
get_sockmod/1,
|
||||
get_transport/1,
|
||||
get_peer_certificate/1,
|
||||
get_verify_result/1,
|
||||
close/1,
|
||||
@ -48,15 +50,16 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-type sockmod() :: ejabberd_http_bind |
|
||||
ejabberd_bosh |
|
||||
ejabberd_http_ws |
|
||||
gen_tcp | fast_tls | ezlib.
|
||||
-type receiver() :: pid () | atom().
|
||||
-type socket() :: pid() | inet:socket() |
|
||||
fast_tls:tls_socket() |
|
||||
ezlib:zlib_socket() |
|
||||
ezlib:zlib_socket() |
|
||||
ejabberd_bosh:bind_socket() |
|
||||
ejabberd_http_bind:bind_socket().
|
||||
|
||||
-record(socket_state, {sockmod = gen_tcp :: sockmod(),
|
||||
@ -65,7 +68,7 @@
|
||||
|
||||
-type socket_state() :: #socket_state{}.
|
||||
|
||||
-export_type([socket_state/0, sockmod/0]).
|
||||
-export_type([socket/0, socket_state/0, sockmod/0]).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
@ -125,19 +128,21 @@ start(Module, SockMod, Socket, Opts) ->
|
||||
end.
|
||||
|
||||
connect(Addr, Port, Opts) ->
|
||||
connect(Addr, Port, Opts, infinity).
|
||||
connect(Addr, Port, Opts, infinity, self()).
|
||||
|
||||
connect(Addr, Port, Opts, Timeout) ->
|
||||
connect(Addr, Port, Opts, Timeout, self()).
|
||||
|
||||
connect(Addr, Port, Opts, Timeout, Owner) ->
|
||||
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
|
||||
{ok, Socket} ->
|
||||
Receiver = ejabberd_receiver:start(Socket, gen_tcp,
|
||||
none),
|
||||
SocketData = #socket_state{sockmod = gen_tcp,
|
||||
socket = Socket, receiver = Receiver},
|
||||
Pid = self(),
|
||||
case gen_tcp:controlling_process(Socket, Receiver) of
|
||||
ok ->
|
||||
ejabberd_receiver:become_controller(Receiver, Pid),
|
||||
ejabberd_receiver:become_controller(Receiver, Owner),
|
||||
{ok, SocketData};
|
||||
{error, _Reason} = Error -> gen_tcp:close(Socket), Error
|
||||
end;
|
||||
@ -188,7 +193,7 @@ send(SocketData, Data) ->
|
||||
%% Can only be called when in c2s StateData#state.xml_socket is true
|
||||
%% This function is used for HTTP bind
|
||||
%% sockmod=ejabberd_http_ws|ejabberd_http_bind or any custom module
|
||||
-spec send_xml(socket_state(), xmlel()) -> any().
|
||||
-spec send_xml(socket_state(), fxml:xmlel()) -> any().
|
||||
|
||||
send_xml(SocketData, Data) ->
|
||||
catch
|
||||
@ -215,6 +220,21 @@ monitor(SocketData)
|
||||
get_sockmod(SocketData) ->
|
||||
SocketData#socket_state.sockmod.
|
||||
|
||||
get_transport(#socket_state{sockmod = SockMod,
|
||||
socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp -> tcp;
|
||||
fast_tls -> tls;
|
||||
ezlib ->
|
||||
case ezlib:get_sockmod(Socket) of
|
||||
tcp -> tcp_zlib;
|
||||
tls -> tls_zlib
|
||||
end;
|
||||
ejabberd_bosh -> http_bind;
|
||||
ejabberd_http_bind -> http_bind;
|
||||
ejabberd_http_ws -> websocket
|
||||
end.
|
||||
|
||||
get_peer_certificate(SocketData) ->
|
||||
fast_tls:get_peer_certificate(SocketData#socket_state.socket).
|
||||
|
||||
@ -237,4 +257,3 @@ peername(#socket_state{sockmod = SockMod,
|
||||
gen_tcp -> inet:peername(Socket);
|
||||
_ -> SockMod:peername(Socket)
|
||||
end.
|
||||
|
||||
|
@ -768,7 +768,7 @@ sqlite_to_odbc(Host, {rowid, _}) ->
|
||||
sqlite_to_odbc(_Host, [{columns, Columns}, {rows, TRows}]) ->
|
||||
Rows = [lists:map(
|
||||
fun(I) when is_integer(I) ->
|
||||
jlib:integer_to_binary(I);
|
||||
integer_to_binary(I);
|
||||
(B) ->
|
||||
B
|
||||
end, tuple_to_list(Row)) || Row <- TRows],
|
||||
@ -813,11 +813,11 @@ pgsql_item_to_odbc({<<"FETCH", _/binary>>, Rows,
|
||||
{selected, [element(1, Row) || Row <- Rows], Recs};
|
||||
pgsql_item_to_odbc(<<"INSERT ", OIDN/binary>>) ->
|
||||
[_OID, N] = str:tokens(OIDN, <<" ">>),
|
||||
{updated, jlib:binary_to_integer(N)};
|
||||
{updated, binary_to_integer(N)};
|
||||
pgsql_item_to_odbc(<<"DELETE ", N/binary>>) ->
|
||||
{updated, jlib:binary_to_integer(N)};
|
||||
{updated, binary_to_integer(N)};
|
||||
pgsql_item_to_odbc(<<"UPDATE ", N/binary>>) ->
|
||||
{updated, jlib:binary_to_integer(N)};
|
||||
{updated, binary_to_integer(N)};
|
||||
pgsql_item_to_odbc({error, Error}) -> {error, Error};
|
||||
pgsql_item_to_odbc(_) -> {updated, undefined}.
|
||||
|
||||
@ -875,7 +875,7 @@ mysql_item_to_odbc(Columns, Recs) ->
|
||||
to_odbc({selected, Columns, Recs}) ->
|
||||
Rows = [lists:map(
|
||||
fun(I) when is_integer(I) ->
|
||||
jlib:integer_to_binary(I);
|
||||
integer_to_binary(I);
|
||||
(B) ->
|
||||
B
|
||||
end, Row) || Row <- Recs],
|
||||
|
@ -49,7 +49,7 @@
|
||||
-record(sql_pool, {host, pid}).
|
||||
|
||||
start_link(Host) ->
|
||||
mnesia:create_table(sql_pool,
|
||||
ejabberd_mnesia:create(?MODULE, sql_pool,
|
||||
[{ram_copies, [node()]}, {type, bag},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, sql_pool)}]),
|
||||
@ -61,10 +61,6 @@ start_link(Host) ->
|
||||
?MODULE, [Host]).
|
||||
|
||||
init([Host]) ->
|
||||
PoolSize = ejabberd_config:get_option(
|
||||
{sql_pool_size, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?DEFAULT_POOL_SIZE),
|
||||
StartInterval = ejabberd_config:get_option(
|
||||
{sql_start_interval, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
@ -76,6 +72,7 @@ init([Host]) ->
|
||||
(mssql) -> mssql;
|
||||
(odbc) -> odbc
|
||||
end, odbc),
|
||||
PoolSize = get_pool_size(Type, Host),
|
||||
case Type of
|
||||
sqlite ->
|
||||
check_sqlite_db(Host);
|
||||
@ -117,6 +114,23 @@ remove_pid(Host, Pid) ->
|
||||
end,
|
||||
mnesia:ets(F).
|
||||
|
||||
-spec get_pool_size(atom(), binary()) -> pos_integer().
|
||||
get_pool_size(SQLType, Host) ->
|
||||
PoolSize = ejabberd_config:get_option(
|
||||
{sql_pool_size, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
case SQLType of
|
||||
sqlite -> 1;
|
||||
_ -> ?DEFAULT_POOL_SIZE
|
||||
end),
|
||||
if PoolSize > 1 andalso SQLType == sqlite ->
|
||||
?WARNING_MSG("it's not recommended to set sql_pool_size > 1 for "
|
||||
"sqlite, because it may cause race conditions", []);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
PoolSize.
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
|
@ -41,7 +41,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
@ -61,23 +61,22 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts,
|
||||
[]).
|
||||
|
||||
-spec process_command(jid(), jid(), stanza()) -> ok.
|
||||
process_command(From, To, Packet) ->
|
||||
case To of
|
||||
#jid{luser = <<"">>, lresource = <<"watchdog">>} ->
|
||||
#xmlel{name = Name} = Packet,
|
||||
case Name of
|
||||
<<"message">> ->
|
||||
case Packet of
|
||||
#message{body = Body} ->
|
||||
LFrom =
|
||||
jid:tolower(jid:remove_resource(From)),
|
||||
case lists:member(LFrom, get_admin_jids()) of
|
||||
true ->
|
||||
Body = fxml:get_path_s(Packet,
|
||||
[{elem, <<"body">>}, cdata]),
|
||||
BodyText = xmpp:get_text(Body),
|
||||
spawn(fun () ->
|
||||
process_flag(priority, high),
|
||||
process_command1(From, To, Body)
|
||||
process_command1(From, To, BodyText)
|
||||
end),
|
||||
stop;
|
||||
ok;
|
||||
false -> ok
|
||||
end;
|
||||
_ -> ok
|
||||
@ -181,29 +180,24 @@ process_large_heap(Pid, Info) ->
|
||||
Host = (?MYNAME),
|
||||
JIDs = get_admin_jids(),
|
||||
DetailedInfo = detailed_info(Pid),
|
||||
Body = iolist_to_binary(
|
||||
io_lib:format("(~w) The process ~w is consuming too "
|
||||
"much memory:~n~p~n~s",
|
||||
[node(), Pid, Info, DetailedInfo])),
|
||||
Body = str:format("(~w) The process ~w is consuming too "
|
||||
"much memory:~n~p~n~s",
|
||||
[node(), Pid, Info, DetailedInfo]),
|
||||
From = jid:make(<<"">>, Host, <<"watchdog">>),
|
||||
Hint = [#xmlel{name = <<"no-permanent-store">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_HINTS}]}],
|
||||
lists:foreach(fun (JID) ->
|
||||
send_message(From, jid:make(JID), Body, Hint)
|
||||
end, JIDs).
|
||||
Hint = [#hint{type = 'no-permanent-store'}],
|
||||
lists:foreach(
|
||||
fun(JID) ->
|
||||
send_message(From, jid:make(JID), Body, Hint)
|
||||
end, JIDs).
|
||||
|
||||
send_message(From, To, Body) ->
|
||||
send_message(From, To, Body, []).
|
||||
|
||||
send_message(From, To, Body, ExtraEls) ->
|
||||
ejabberd_router:route(From, To,
|
||||
#xmlel{name = <<"message">>,
|
||||
attrs = [{<<"type">>, <<"chat">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"body">>, attrs = [],
|
||||
children =
|
||||
[{xmlcdata, Body}]}
|
||||
| ExtraEls]}).
|
||||
#message{type = chat,
|
||||
body = xmpp:mk_text(Body),
|
||||
sub_els = ExtraEls}).
|
||||
|
||||
get_admin_jids() ->
|
||||
ejabberd_config:get_option(
|
||||
@ -305,7 +299,7 @@ process_command2([<<"showlh">>, SNode], From, To) ->
|
||||
process_command2([<<"setlh">>, SNode, NewValueString],
|
||||
From, To) ->
|
||||
Node = jlib:binary_to_atom(SNode),
|
||||
NewValue = jlib:binary_to_integer(NewValueString),
|
||||
NewValue = binary_to_integer(NewValueString),
|
||||
remote_command(Node, [setlh, NewValue], From, To);
|
||||
process_command2([<<"help">>], From, To) ->
|
||||
send_message(To, From, help());
|
||||
|
@ -34,7 +34,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
|
@ -38,7 +38,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
@ -274,7 +274,7 @@ get_auth_account(HostOfRule, AccessRule, User, Server,
|
||||
case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
|
||||
true ->
|
||||
case acl:any_rules_allowed(HostOfRule, AccessRule,
|
||||
jid:make(User, Server, <<"">>))
|
||||
jid:make(User, Server, <<"">>))
|
||||
of
|
||||
false -> {unauthorized, <<"unprivileged-account">>};
|
||||
true -> {ok, {User, Server}}
|
||||
@ -763,8 +763,8 @@ process_admin(Host,
|
||||
[?XAE(<<"form">>,
|
||||
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
|
||||
[?TEXTAREA(<<"acls">>,
|
||||
(iolist_to_binary(integer_to_list(lists:max([16,
|
||||
NumLines])))),
|
||||
(integer_to_binary(lists:max([16,
|
||||
NumLines]))),
|
||||
<<"80">>, <<(iolist_to_binary(ACLsP))/binary, ".">>),
|
||||
?BR,
|
||||
?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
|
||||
@ -865,8 +865,8 @@ process_admin(Host,
|
||||
[?XAE(<<"form">>,
|
||||
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr),
|
||||
[?TEXTAREA(<<"access">>,
|
||||
(iolist_to_binary(integer_to_list(lists:max([16,
|
||||
NumLines])))),
|
||||
(integer_to_binary(lists:max([16,
|
||||
NumLines]))),
|
||||
<<"80">>, <<(iolist_to_binary(AccessP))/binary, ".">>),
|
||||
?BR,
|
||||
?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])],
|
||||
@ -926,7 +926,7 @@ process_admin(Host,
|
||||
Rs1 -> Rs1
|
||||
end,
|
||||
make_xhtml([?XC(<<"h1">>,
|
||||
list_to_binary(io_lib:format(
|
||||
(str:format(
|
||||
?T(<<"~s access rule configuration">>),
|
||||
[SName])))]
|
||||
++
|
||||
@ -1052,17 +1052,21 @@ process_admin(Host,
|
||||
process_admin(Host,
|
||||
#request{lang = Lang, auth = {_, _Auth, AJID}} =
|
||||
Request) ->
|
||||
{Hook, Opts} = case Host of
|
||||
global -> {webadmin_page_main, [Request]};
|
||||
Host -> {webadmin_page_host, [Host, Request]}
|
||||
end,
|
||||
case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
|
||||
Res = case Host of
|
||||
global ->
|
||||
ejabberd_hooks:run_fold(
|
||||
webadmin_page_main, Host, [], [Request]);
|
||||
_ ->
|
||||
ejabberd_hooks:run_fold(
|
||||
webadmin_page_host, Host, [], [Host, Request])
|
||||
end,
|
||||
case Res of
|
||||
[] ->
|
||||
setelement(1,
|
||||
make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Lang,
|
||||
AJID),
|
||||
404);
|
||||
Res -> make_xhtml(Res, Host, Lang, AJID)
|
||||
_ -> make_xhtml(Res, Host, Lang, AJID)
|
||||
end.
|
||||
|
||||
%%%==================================
|
||||
@ -1137,7 +1141,7 @@ acl_spec_select(ID, Opt) ->
|
||||
%% @spec (T::any()) -> StringLine::string()
|
||||
term_to_string(T) ->
|
||||
StringParagraph =
|
||||
iolist_to_binary(io_lib:format("~1000000p", [T])),
|
||||
(str:format("~1000000p", [T])),
|
||||
ejabberd_regexp:greplace(StringParagraph, <<"\\n ">>,
|
||||
<<"">>).
|
||||
|
||||
@ -1461,8 +1465,8 @@ list_users_in_diapason(Host, Diap, Lang, URLFunc) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
|
||||
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
|
||||
N1 = jlib:binary_to_integer(S1),
|
||||
N2 = jlib:binary_to_integer(S2),
|
||||
N1 = binary_to_integer(S1),
|
||||
N2 = binary_to_integer(S2),
|
||||
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
|
||||
[list_given_users(Host, Sub, <<"../../">>, Lang,
|
||||
URLFunc)].
|
||||
@ -1502,7 +1506,7 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
|
||||
{{Year, Month, Day},
|
||||
{Hour, Minute, Second}} =
|
||||
calendar:now_to_local_time(TimeStamp),
|
||||
iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
(str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
[Year,
|
||||
Month,
|
||||
Day,
|
||||
@ -1651,7 +1655,7 @@ user_info(User, Server, Query, Lang) ->
|
||||
"://",
|
||||
(jlib:ip_to_list(IP))/binary,
|
||||
":",
|
||||
(jlib:integer_to_binary(Port))/binary,
|
||||
(integer_to_binary(Port))/binary,
|
||||
"#",
|
||||
(jlib:atom_to_binary(Node))/binary>>
|
||||
end,
|
||||
@ -1679,14 +1683,14 @@ user_info(User, Server, Query, Lang) ->
|
||||
Shift rem 1000000, 0},
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
||||
calendar:now_to_local_time(TimeStamp),
|
||||
iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
(str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
[Year, Month, Day,
|
||||
Hour, Minute,
|
||||
Second]))
|
||||
end;
|
||||
_ -> ?T(<<"Online">>)
|
||||
end,
|
||||
[?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"User ~s">>),
|
||||
[?XC(<<"h1">>, (str:format(?T(<<"User ~s">>),
|
||||
[us_to_list(US)])))]
|
||||
++
|
||||
case Res of
|
||||
@ -1769,9 +1773,7 @@ list_last_activity(Host, Lang, Integral, Period) ->
|
||||
[?XAE(<<"li">>,
|
||||
[{<<"style">>,
|
||||
<<"width:",
|
||||
(iolist_to_binary(integer_to_list(trunc(90 * V
|
||||
/
|
||||
Max))))/binary,
|
||||
(integer_to_binary(trunc(90 * V / Max)))/binary,
|
||||
"%;">>}],
|
||||
[{xmlcdata, pretty_string_int(V)}])
|
||||
|| V <- Hist ++ Tail])]
|
||||
@ -1846,7 +1848,7 @@ get_node(global, Node, [], Query, Lang) ->
|
||||
Base = get_base_path(global, Node),
|
||||
MenuItems2 = make_menu_items(global, Node, Base, Lang),
|
||||
[?XC(<<"h1">>,
|
||||
list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node])))]
|
||||
(str:format(?T(<<"Node ~p">>), [Node])))]
|
||||
++
|
||||
case Res of
|
||||
ok -> [?XREST(<<"Submitted">>)];
|
||||
@ -1871,7 +1873,7 @@ get_node(global, Node, [], Query, Lang) ->
|
||||
get_node(Host, Node, [], _Query, Lang) ->
|
||||
Base = get_base_path(Host, Node),
|
||||
MenuItems2 = make_menu_items(Host, Node, Base, Lang),
|
||||
[?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node]))),
|
||||
[?XC(<<"h1">>, (str:format(?T(<<"Node ~p">>), [Node]))),
|
||||
?XE(<<"ul">>,
|
||||
([?LI([?ACT(<<Base/binary, "modules/">>,
|
||||
<<"Modules">>)])]
|
||||
@ -1930,7 +1932,7 @@ get_node(global, Node, [<<"db">>], Query, Lang) ->
|
||||
end,
|
||||
STables),
|
||||
[?XC(<<"h1">>,
|
||||
list_to_binary(io_lib:format(?T(<<"Database Tables at ~p">>),
|
||||
(str:format(?T(<<"Database Tables at ~p">>),
|
||||
[Node]))
|
||||
)]
|
||||
++
|
||||
@ -1966,9 +1968,9 @@ get_node(global, Node, [<<"backup">>], Query, Lang) ->
|
||||
ok -> [?XREST(<<"Submitted">>)];
|
||||
{error, Error} ->
|
||||
[?XRES(<<(?T(<<"Error">>))/binary, ": ",
|
||||
(list_to_binary(io_lib:format("~p", [Error])))/binary>>)]
|
||||
((str:format("~p", [Error])))/binary>>)]
|
||||
end,
|
||||
[?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])))]
|
||||
[?XC(<<"h1">>, (str:format(?T(<<"Backup of ~p">>), [Node])))]
|
||||
++
|
||||
ResS ++
|
||||
[?XCT(<<"p">>,
|
||||
@ -2120,7 +2122,7 @@ get_node(global, Node, [<<"ports">>], Query, Lang) ->
|
||||
{'EXIT', _Reason} -> error;
|
||||
{is_added, ok} -> ok;
|
||||
{is_added, {error, Reason}} ->
|
||||
{error, iolist_to_binary(io_lib:format("~p", [Reason]))};
|
||||
{error, (str:format("~p", [Reason]))};
|
||||
_ -> nothing
|
||||
end,
|
||||
NewPorts = lists:sort(ejabberd_cluster:call(Node, ejabberd_config,
|
||||
@ -2157,7 +2159,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
|
||||
end,
|
||||
NewModules = lists:sort(ejabberd_cluster:call(Node, gen_mod,
|
||||
loaded_modules_with_opts, [Host])),
|
||||
H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])),
|
||||
H1String = (str:format(?T(<<"Modules at ~p">>), [Node])),
|
||||
(?H1GL(H1String, <<"modulesoverview">>,
|
||||
<<"Modules Overview">>))
|
||||
++
|
||||
@ -2173,10 +2175,10 @@ get_node(Host, Node, [<<"modules">>], Query, Lang)
|
||||
get_node(global, Node, [<<"stats">>], _Query, Lang) ->
|
||||
UpTime = ejabberd_cluster:call(Node, erlang, statistics,
|
||||
[wall_clock]),
|
||||
UpTimeS = list_to_binary(io_lib:format("~.3f",
|
||||
UpTimeS = (str:format("~.3f",
|
||||
[element(1, UpTime) / 1000])),
|
||||
CPUTime = ejabberd_cluster:call(Node, erlang, statistics, [runtime]),
|
||||
CPUTimeS = list_to_binary(io_lib:format("~.3f",
|
||||
CPUTimeS = (str:format("~.3f",
|
||||
[element(1, CPUTime) / 1000])),
|
||||
OnlineUsers = ejabberd_sm:connected_users_number(),
|
||||
TransactionsCommitted = ejabberd_cluster:call(Node, mnesia,
|
||||
@ -2188,7 +2190,7 @@ get_node(global, Node, [<<"stats">>], _Query, Lang) ->
|
||||
TransactionsLogged = ejabberd_cluster:call(Node, mnesia, system_info,
|
||||
[transaction_log_writes]),
|
||||
[?XC(<<"h1">>,
|
||||
list_to_binary(io_lib:format(?T(<<"Statistics of ~p">>), [Node]))),
|
||||
(str:format(?T(<<"Statistics of ~p">>), [Node]))),
|
||||
?XAE(<<"table">>, [],
|
||||
[?XE(<<"tbody">>,
|
||||
[?XE(<<"tr">>,
|
||||
@ -2252,11 +2254,11 @@ get_node(global, Node, [<<"update">>], Query, Lang) ->
|
||||
(BeamsLis ++ SelectButtons))
|
||||
end,
|
||||
FmtScript = (?XC(<<"pre">>,
|
||||
list_to_binary(io_lib:format("~p", [Script])))),
|
||||
(str:format("~p", [Script])))),
|
||||
FmtLowLevelScript = (?XC(<<"pre">>,
|
||||
list_to_binary(io_lib:format("~p", [LowLevelScript])))),
|
||||
(str:format("~p", [LowLevelScript])))),
|
||||
[?XC(<<"h1">>,
|
||||
list_to_binary(io_lib:format(?T(<<"Update ~p">>), [Node])))]
|
||||
(str:format(?T(<<"Update ~p">>), [Node])))]
|
||||
++
|
||||
case Res of
|
||||
ok -> [?XREST(<<"Submitted">>)];
|
||||
@ -2276,16 +2278,17 @@ get_node(global, Node, [<<"update">>], Query, Lang) ->
|
||||
?BR,
|
||||
?INPUTT(<<"submit">>, <<"update">>, <<"Update">>)])];
|
||||
get_node(Host, Node, NPath, Query, Lang) ->
|
||||
{Hook, Opts} = case Host of
|
||||
global ->
|
||||
{webadmin_page_node, [Node, NPath, Query, Lang]};
|
||||
Host ->
|
||||
{webadmin_page_hostnode,
|
||||
[Host, Node, NPath, Query, Lang]}
|
||||
end,
|
||||
case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
|
||||
Res = case Host of
|
||||
global ->
|
||||
ejabberd_hooks:run_fold(webadmin_page_node, Host, [],
|
||||
[Node, NPath, Query, Lang]);
|
||||
_ ->
|
||||
ejabberd_hooks:run_fold(webadmin_page_hostnode, Host, [],
|
||||
[Host, Node, NPath, Query, Lang])
|
||||
end,
|
||||
case Res of
|
||||
[] -> [?XC(<<"h1">>, <<"Not Found">>)];
|
||||
Res -> Res
|
||||
_ -> Res
|
||||
end.
|
||||
|
||||
%%%==================================
|
||||
@ -2477,7 +2480,7 @@ node_ports_to_xhtml(Ports, Lang) ->
|
||||
SModule, <<"15">>)]),
|
||||
?XAE(<<"td">>, direction(ltr),
|
||||
[?TEXTAREA(<<"opts", SSPort/binary>>,
|
||||
(iolist_to_binary(integer_to_list(NumLines))),
|
||||
(integer_to_binary(NumLines)),
|
||||
<<"35">>, SOptsClean)]),
|
||||
?XE(<<"td">>,
|
||||
[?INPUTT(<<"submit">>,
|
||||
@ -2522,7 +2525,7 @@ make_netprot_html(NetProt) ->
|
||||
get_port_data(PortIP, Opts) ->
|
||||
{Port, IPT, IPS, _IPV, NetProt, OptsClean} =
|
||||
ejabberd_listener:parse_listener_portip(PortIP, Opts),
|
||||
SPort = jlib:integer_to_binary(Port),
|
||||
SPort = integer_to_binary(Port),
|
||||
SSPort = list_to_binary(
|
||||
lists:map(fun (N) ->
|
||||
io_lib:format("~.16b", [N])
|
||||
@ -2620,7 +2623,7 @@ node_modules_to_xhtml(Modules, Lang) ->
|
||||
[?XC(<<"td">>, SModule),
|
||||
?XAE(<<"td">>, direction(ltr),
|
||||
[?TEXTAREA(<<"opts", SModule/binary>>,
|
||||
(iolist_to_binary(integer_to_list(NumLines))),
|
||||
(integer_to_binary(NumLines)),
|
||||
<<"40">>, SOpts)]),
|
||||
?XE(<<"td">>,
|
||||
[?INPUTT(<<"submit">>,
|
||||
@ -2702,11 +2705,11 @@ node_update_parse_query(Node, Query) ->
|
||||
{ok, _} -> ok;
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("~p~n", [Error]),
|
||||
{error, iolist_to_binary(io_lib:format("~p", [Error]))};
|
||||
{error, (str:format("~p", [Error]))};
|
||||
{badrpc, Error} ->
|
||||
?ERROR_MSG("Bad RPC: ~p~n", [Error]),
|
||||
{error,
|
||||
<<"Bad RPC: ", (iolist_to_binary(io_lib:format("~p", [Error])))/binary>>}
|
||||
<<"Bad RPC: ", ((str:format("~p", [Error])))/binary>>}
|
||||
end;
|
||||
_ -> nothing
|
||||
end.
|
||||
@ -2771,7 +2774,7 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
|
||||
element_to_list(X) when is_atom(X) ->
|
||||
iolist_to_binary(atom_to_list(X));
|
||||
element_to_list(X) when is_integer(X) ->
|
||||
iolist_to_binary(integer_to_list(X)).
|
||||
integer_to_binary(X).
|
||||
|
||||
list_to_element(Bin) ->
|
||||
{ok, Tokens, _} = erl_scan:string(binary_to_list(Bin)),
|
||||
@ -2779,8 +2782,8 @@ list_to_element(Bin) ->
|
||||
Element.
|
||||
|
||||
url_func({user_diapason, From, To}) ->
|
||||
<<(iolist_to_binary(integer_to_list(From)))/binary, "-",
|
||||
(iolist_to_binary(integer_to_list(To)))/binary, "/">>;
|
||||
<<(integer_to_binary(From))/binary, "-",
|
||||
(integer_to_binary(To))/binary, "/">>;
|
||||
url_func({users_queue, Prefix, User, _Server}) ->
|
||||
<<Prefix/binary, "user/", User/binary, "/queue/">>;
|
||||
url_func({user, Prefix, User, _Server}) ->
|
||||
@ -2795,7 +2798,7 @@ cache_control_public() ->
|
||||
|
||||
%% Transform 1234567890 into "1,234,567,890"
|
||||
pretty_string_int(Integer) when is_integer(Integer) ->
|
||||
pretty_string_int(iolist_to_binary(integer_to_list(Integer)));
|
||||
pretty_string_int(integer_to_binary(Integer));
|
||||
pretty_string_int(String) when is_binary(String) ->
|
||||
{_, Result} = lists:foldl(fun (NewNumber, {3, Result}) ->
|
||||
{1, <<NewNumber, $,, Result/binary>>};
|
||||
|
@ -47,7 +47,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
|
@ -42,7 +42,7 @@
|
||||
-include("ejabberd_http.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state,
|
||||
{access_commands = [] :: list(),
|
||||
@ -216,10 +216,10 @@ process(_, #request{method = 'POST', data = Data, opts = Opts, ip = {IP, _}}) ->
|
||||
L),
|
||||
L
|
||||
end, all),
|
||||
CommOpts = gen_mod:get_opt(
|
||||
options, AcOpts,
|
||||
fun(L) when is_list(L) -> L end,
|
||||
[]),
|
||||
%% CommOpts = gen_mod:get_opt(
|
||||
%% options, AcOpts,
|
||||
%% fun(L) when is_list(L) -> L end,
|
||||
%% []),
|
||||
[{<<"ejabberd_xmlrpc compatibility shim">>, {[?MODULE], [{access, Ac}], Commands}}];
|
||||
(Wrong) ->
|
||||
?WARNING_MSG("wrong options format for ~p: ~p",
|
||||
|
294
src/ejd2sql.erl
294
src/ejd2sql.erl
@ -30,12 +30,12 @@
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-export([export/2, export/3, import_file/2, import/2,
|
||||
import/3, delete/1]).
|
||||
-export([export/2, export/3, import/3, import/4, delete/1, import_info/1]).
|
||||
|
||||
|
||||
-define(MAX_RECORDS_PER_TRANSACTION, 100).
|
||||
|
||||
-record(dump, {fd, cont = start}).
|
||||
-record(sql_dump, {fd, type}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
@ -50,13 +50,14 @@
|
||||
modules() ->
|
||||
[ejabberd_auth,
|
||||
mod_announce,
|
||||
mod_caps,
|
||||
mod_irc,
|
||||
mod_last,
|
||||
mod_muc,
|
||||
mod_offline,
|
||||
mod_privacy,
|
||||
mod_private,
|
||||
%% mod_pubsub,
|
||||
mod_pubsub,
|
||||
mod_roster,
|
||||
mod_shared_roster,
|
||||
mod_vcard,
|
||||
@ -100,49 +101,44 @@ delete(Server, Module) ->
|
||||
delete(LServer, Table, ConvertFun)
|
||||
end, Module:export(Server)).
|
||||
|
||||
import_file(Server, FileName) when is_binary(FileName) ->
|
||||
import(Server, binary_to_list(FileName));
|
||||
import_file(Server, FileName) ->
|
||||
case disk_log:open([{name, make_ref()},
|
||||
{file, FileName},
|
||||
{mode, read_only}]) of
|
||||
{ok, Fd} ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Mods = [{Mod, gen_mod:db_type(LServer, Mod)}
|
||||
|| Mod <- modules(), gen_mod:is_loaded(LServer, Mod)],
|
||||
AuthMods = case lists:member(ejabberd_auth_mnesia,
|
||||
ejabberd_auth:auth_modules(LServer)) of
|
||||
true ->
|
||||
[{ejabberd_auth, mnesia}];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
import_dump(LServer, AuthMods ++ Mods, #dump{fd = Fd});
|
||||
Err ->
|
||||
exit(Err)
|
||||
end.
|
||||
|
||||
import(Server, Output) ->
|
||||
import(Server, Output, [{fast, true}]).
|
||||
|
||||
import(Server, Output, Opts) ->
|
||||
LServer = jid:nameprep(iolist_to_binary(Server)),
|
||||
Modules = modules(),
|
||||
IO = prepare_output(Output, disk_log),
|
||||
import(Server, Dir, ToType) ->
|
||||
lists:foreach(
|
||||
fun(Module) ->
|
||||
import(LServer, IO, Opts, Module)
|
||||
end, Modules),
|
||||
close_output(Output, IO).
|
||||
fun(Mod) ->
|
||||
?INFO_MSG("importing ~p...", [Mod]),
|
||||
import(Mod, Server, Dir, ToType)
|
||||
end, modules()).
|
||||
|
||||
import(Server, Output, Opts, Module) ->
|
||||
import(Mod, Server, Dir, ToType) ->
|
||||
LServer = jid:nameprep(iolist_to_binary(Server)),
|
||||
IO = prepare_output(Output, disk_log),
|
||||
try Mod:import_start(LServer, ToType)
|
||||
catch error:undef -> ok end,
|
||||
lists:foreach(
|
||||
fun({SelectQuery, ConvertFun}) ->
|
||||
import(LServer, SelectQuery, IO, ConvertFun, Opts)
|
||||
end, Module:import(Server)),
|
||||
close_output(Output, IO).
|
||||
fun({File, Tab, _Mod, FieldsNumber}) ->
|
||||
FileName = filename:join([Dir, File]),
|
||||
case open_sql_dump(FileName) of
|
||||
{ok, #sql_dump{type = FromType} = Dump} ->
|
||||
import_rows(LServer, {sql, FromType}, ToType,
|
||||
Tab, Mod, Dump, FieldsNumber),
|
||||
close_sql_dump(Dump);
|
||||
{error, enoent} ->
|
||||
ok;
|
||||
eof ->
|
||||
?INFO_MSG("It seems like SQL dump ~s is empty", [FileName]);
|
||||
Err ->
|
||||
?ERROR_MSG("Failed to open SQL dump ~s: ~s",
|
||||
[FileName, format_error(Err)])
|
||||
end
|
||||
end, import_info(Mod)),
|
||||
try Mod:import_stop(LServer, ToType)
|
||||
catch error:undef -> ok end.
|
||||
|
||||
import_info(Mod) ->
|
||||
Info = Mod:import_info(),
|
||||
lists:map(
|
||||
fun({Tab, FieldsNum}) ->
|
||||
FileName = <<Tab/binary, ".txt">>,
|
||||
{FileName, Tab, Mod, FieldsNum}
|
||||
end, Info).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
@ -200,79 +196,6 @@ delete(LServer, Table, ConvertFun) ->
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
|
||||
F = case proplists:get_bool(fast, Opts) of
|
||||
true ->
|
||||
fun() ->
|
||||
case ejabberd_sql:sql_query_t(SelectQuery) of
|
||||
{selected, _, Rows} ->
|
||||
lists:foldl(fun process_sql_row/2,
|
||||
{IO, ConvertFun, undefined}, Rows);
|
||||
Err ->
|
||||
erlang:error(Err)
|
||||
end
|
||||
end;
|
||||
false ->
|
||||
fun() ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
[iolist_to_binary(
|
||||
[<<"declare c cursor for ">>, SelectQuery])]),
|
||||
fetch(IO, ConvertFun, undefined)
|
||||
end
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
fetch(IO, ConvertFun, PrevRow) ->
|
||||
case ejabberd_sql:sql_query_t([<<"fetch c;">>]) of
|
||||
{selected, _, [Row]} ->
|
||||
process_sql_row(Row, {IO, ConvertFun, PrevRow}),
|
||||
fetch(IO, ConvertFun, Row);
|
||||
{selected, _, []} ->
|
||||
ok;
|
||||
Err ->
|
||||
erlang:error(Err)
|
||||
end.
|
||||
|
||||
process_sql_row(Row, {IO, ConvertFun, PrevRow}) when Row == PrevRow ->
|
||||
%% Avoid calling ConvertFun with the same input
|
||||
{IO, ConvertFun, Row};
|
||||
process_sql_row(Row, {IO, ConvertFun, _PrevRow}) ->
|
||||
case catch ConvertFun(Row) of
|
||||
{'EXIT', _} = Err ->
|
||||
?ERROR_MSG("failed to convert ~p: ~p", [Row, Err]);
|
||||
Term ->
|
||||
ok = disk_log:log(IO#dump.fd, Term)
|
||||
end,
|
||||
{IO, ConvertFun, Row}.
|
||||
|
||||
import_dump(LServer, Mods, #dump{fd = Fd, cont = Cont}) ->
|
||||
case disk_log:chunk(Fd, Cont) of
|
||||
{NewCont, Terms} ->
|
||||
import_terms(LServer, Mods, Terms),
|
||||
import_dump(LServer, Mods, #dump{fd = Fd, cont = NewCont});
|
||||
eof ->
|
||||
ok;
|
||||
Err ->
|
||||
exit(Err)
|
||||
end.
|
||||
|
||||
import_terms(LServer, Mods, [Term|Terms]) ->
|
||||
import_term(LServer, Mods, Term),
|
||||
import_terms(LServer, Mods, Terms);
|
||||
import_terms(_LServer, _Mods, []) ->
|
||||
ok.
|
||||
|
||||
import_term(LServer, [{Mod, DBType}|Mods], Term) ->
|
||||
case catch Mod:import(LServer, DBType, Term) of
|
||||
pass -> import_term(LServer, Mods, Term);
|
||||
ok -> ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to import ~p for module ~p: ~p",
|
||||
[Term, Mod, Err])
|
||||
end;
|
||||
import_term(_LServer, [], _Term) ->
|
||||
ok.
|
||||
|
||||
prepare_output(FileName) ->
|
||||
prepare_output(FileName, normal).
|
||||
|
||||
@ -285,25 +208,11 @@ prepare_output(FileName, normal) when is_list(FileName) ->
|
||||
Err ->
|
||||
exit(Err)
|
||||
end;
|
||||
prepare_output(FileName, disk_log) when is_list(FileName) ->
|
||||
case disk_log:open([{name, make_ref()},
|
||||
{repair, truncate},
|
||||
{file, FileName}]) of
|
||||
{ok, Fd} ->
|
||||
#dump{fd = Fd};
|
||||
Err ->
|
||||
exit(Err)
|
||||
end;
|
||||
prepare_output(Output, _Type) ->
|
||||
Output.
|
||||
|
||||
close_output(FileName, Fd) when FileName /= Fd ->
|
||||
case Fd of
|
||||
#dump{} ->
|
||||
disk_log:close(Fd#dump.fd);
|
||||
_ ->
|
||||
file:close(Fd)
|
||||
end,
|
||||
file:close(Fd),
|
||||
ok;
|
||||
close_output(_, _) ->
|
||||
ok.
|
||||
@ -321,6 +230,129 @@ flatten1([H|T], Acc) ->
|
||||
flatten1([], Acc) ->
|
||||
Acc.
|
||||
|
||||
import_rows(LServer, FromType, ToType, Tab, Mod, Dump, FieldsNumber) ->
|
||||
case read_row_from_sql_dump(Dump, FieldsNumber) of
|
||||
{ok, Fields} ->
|
||||
case catch Mod:import(LServer, FromType, ToType, Tab, Fields) of
|
||||
ok ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("Failed to import fields ~p for tab ~p: ~p",
|
||||
[Fields, Tab, Err])
|
||||
end,
|
||||
import_rows(LServer, FromType, ToType,
|
||||
Tab, Mod, Dump, FieldsNumber);
|
||||
eof ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("Failed to read row from SQL dump: ~s",
|
||||
[format_error(Err)])
|
||||
end.
|
||||
|
||||
open_sql_dump(FileName) ->
|
||||
case file:open(FileName, [raw, read, binary, read_ahead]) of
|
||||
{ok, Fd} ->
|
||||
case file:read(Fd, 11) of
|
||||
{ok, <<"PGCOPY\n", 16#ff, "\r\n", 0>>} ->
|
||||
case skip_pgcopy_header(Fd) of
|
||||
ok ->
|
||||
{ok, #sql_dump{fd = Fd, type = pgsql}};
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
{ok, _} ->
|
||||
file:position(Fd, 0),
|
||||
{ok, #sql_dump{fd = Fd, type = mysql}};
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
close_sql_dump(#sql_dump{fd = Fd}) ->
|
||||
file:close(Fd).
|
||||
|
||||
read_row_from_sql_dump(#sql_dump{fd = Fd, type = pgsql}, _) ->
|
||||
case file:read(Fd, 2) of
|
||||
{ok, <<(-1):16/signed>>} ->
|
||||
eof;
|
||||
{ok, <<FieldsNum:16>>} ->
|
||||
read_fields(Fd, FieldsNum, []);
|
||||
{ok, _} ->
|
||||
{error, eof};
|
||||
eof ->
|
||||
{error, eof};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
read_row_from_sql_dump(#sql_dump{fd = Fd, type = mysql}, FieldsNum) ->
|
||||
read_lines(Fd, FieldsNum, <<"">>, []).
|
||||
|
||||
skip_pgcopy_header(Fd) ->
|
||||
try
|
||||
{ok, <<_:4/binary, ExtSize:32>>} = file:read(Fd, 8),
|
||||
{ok, <<_:ExtSize/binary>>} = file:read(Fd, ExtSize),
|
||||
ok
|
||||
catch error:{badmatch, {error, _} = Err} ->
|
||||
Err;
|
||||
error:{badmatch, _} ->
|
||||
{error, eof}
|
||||
end.
|
||||
|
||||
read_fields(_Fd, 0, Acc) ->
|
||||
{ok, lists:reverse(Acc)};
|
||||
read_fields(Fd, N, Acc) ->
|
||||
case file:read(Fd, 4) of
|
||||
{ok, <<(-1):32/signed>>} ->
|
||||
read_fields(Fd, N-1, [null|Acc]);
|
||||
{ok, <<ValSize:32>>} ->
|
||||
case file:read(Fd, ValSize) of
|
||||
{ok, <<Val:ValSize/binary>>} ->
|
||||
read_fields(Fd, N-1, [Val|Acc]);
|
||||
{ok, _} ->
|
||||
{error, eof};
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
{ok, _} ->
|
||||
{error, eof};
|
||||
eof ->
|
||||
{error, eof};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
read_lines(_Fd, 0, <<"">>, Acc) ->
|
||||
{ok, lists:reverse(Acc)};
|
||||
read_lines(Fd, N, Buf, Acc) ->
|
||||
case file:read_line(Fd) of
|
||||
{ok, Data} when size(Data) >= 2 ->
|
||||
Size = size(Data) - 2,
|
||||
case Data of
|
||||
<<Val:Size/binary, 0, $\n>> ->
|
||||
NewBuf = <<Buf/binary, Val/binary>>,
|
||||
read_lines(Fd, N-1, <<"">>, [NewBuf|Acc]);
|
||||
_ ->
|
||||
NewBuf = <<Buf/binary, Data/binary>>,
|
||||
read_lines(Fd, N, NewBuf, Acc)
|
||||
end;
|
||||
{ok, Data} ->
|
||||
NewBuf = <<Buf/binary, Data/binary>>,
|
||||
read_lines(Fd, N, NewBuf, Acc);
|
||||
eof when Buf == <<"">>, Acc == [] ->
|
||||
eof;
|
||||
eof ->
|
||||
{error, eof};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
format_error({error, eof}) ->
|
||||
"unexpected end of file";
|
||||
format_error({error, Posix}) ->
|
||||
file:format_error(Posix).
|
||||
|
||||
format_queries(SQLs) ->
|
||||
lists:map(
|
||||
fun(#sql_query{} = SQL) ->
|
||||
|
@ -45,7 +45,7 @@
|
||||
start() ->
|
||||
[code:add_patha(module_ebin_dir(Module))
|
||||
|| {Module, _} <- installed()],
|
||||
application:start(inets),
|
||||
p1_http:start(),
|
||||
ejabberd_commands:register_commands(get_commands_spec()).
|
||||
|
||||
stop() ->
|
||||
@ -170,7 +170,10 @@ install(Package) when is_binary(Package) ->
|
||||
ok ->
|
||||
code:add_patha(module_ebin_dir(Module)),
|
||||
ejabberd_config:reload_file(),
|
||||
ok;
|
||||
case erlang:function_exported(Module, post_install, 0) of
|
||||
true -> Module:post_install();
|
||||
_ -> ok
|
||||
end;
|
||||
Error ->
|
||||
delete_path(module_lib_dir(Module)),
|
||||
Error
|
||||
@ -183,6 +186,10 @@ uninstall(Package) when is_binary(Package) ->
|
||||
case installed(Package) of
|
||||
true ->
|
||||
Module = jlib:binary_to_atom(Package),
|
||||
case erlang:function_exported(Module, pre_uninstall, 0) of
|
||||
true -> Module:pre_uninstall();
|
||||
_ -> ok
|
||||
end,
|
||||
[catch gen_mod:stop_module(Host, Module)
|
||||
|| Host <- ejabberd_config:get_myhosts()],
|
||||
code:purge(Module),
|
||||
@ -271,10 +278,10 @@ geturl(Url, Hdrs, UsrOpts) ->
|
||||
[U, Pass] -> [{proxy_user, U}, {proxy_password, Pass}];
|
||||
_ -> []
|
||||
end,
|
||||
case httpc:request(get, {Url, Hdrs}, Host++User++UsrOpts++[{version, "HTTP/1.0"}], []) of
|
||||
{ok, {{_, 200, _}, Headers, Response}} ->
|
||||
case p1_http:request(get, Url, Hdrs, [], Host++User++UsrOpts++[{version, "HTTP/1.0"}]) of
|
||||
{ok, 200, Headers, Response} ->
|
||||
{ok, Headers, Response};
|
||||
{ok, {{_, Code, _}, _Headers, Response}} ->
|
||||
{ok, Code, _Headers, Response} ->
|
||||
{error, {Code, Response}};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
@ -520,11 +527,8 @@ compile(_Module, _Spec, DestDir) ->
|
||||
filelib:ensure_dir(filename:join(Ebin, ".")),
|
||||
EjabBin = filename:dirname(code:which(ejabberd)),
|
||||
EjabInc = filename:join(filename:dirname(EjabBin), "include"),
|
||||
XmlHrl = filename:join(EjabInc, "fxml.hrl"),
|
||||
ExtLib = [{d, 'NO_EXT_LIB'} || filelib:is_file(XmlHrl)],
|
||||
Options = [{outdir, Ebin}, {i, "include"}, {i, EjabInc},
|
||||
verbose, report_errors, report_warnings]
|
||||
++ ExtLib,
|
||||
verbose, report_errors, report_warnings],
|
||||
[file:copy(App, Ebin) || App <- filelib:wildcard("src/*.app")],
|
||||
|
||||
%% Compile erlang files
|
||||
|
@ -40,13 +40,14 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {host, module, function}).
|
||||
|
||||
-type component() :: ejabberd_sm | ejabberd_local.
|
||||
-type type() :: no_queue | one_queue | pos_integer() | parallel.
|
||||
-type opts() :: no_queue | {one_queue, pid()} | {queues, [pid()]} | parallel.
|
||||
-export_type([opts/0]).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
@ -59,6 +60,8 @@ start_link(Host, Module, Function) ->
|
||||
gen_server:start_link(?MODULE, [Host, Module, Function],
|
||||
[]).
|
||||
|
||||
-spec add_iq_handler(module(), binary(), binary(), module(), atom(), type()) -> any().
|
||||
|
||||
add_iq_handler(Component, Host, NS, Module, Function,
|
||||
Type) ->
|
||||
case Type of
|
||||
@ -124,14 +127,47 @@ handle(Host, Module, Function, Opts, From, To, IQ) ->
|
||||
|
||||
-spec process_iq(binary(), atom(), atom(), jid(), jid(), iq()) -> any().
|
||||
|
||||
process_iq(_Host, Module, Function, From, To, IQ) ->
|
||||
case catch Module:Function(From, To, IQ) of
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
|
||||
ResIQ ->
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
|
||||
true -> ok
|
||||
end
|
||||
process_iq(_Host, Module, Function, From, To, IQ0) ->
|
||||
IQ = xmpp:set_from_to(IQ0, From, To),
|
||||
try
|
||||
ResIQ = case erlang:function_exported(Module, Function, 1) of
|
||||
true ->
|
||||
process_iq(Module, Function, IQ);
|
||||
false ->
|
||||
process_iq(Module, Function, From, To,
|
||||
jlib:iq_query_info(xmpp:encode(IQ)))
|
||||
end,
|
||||
if ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From, ResIQ);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
catch E:R ->
|
||||
?ERROR_MSG("failed to process iq:~n~s~nReason = ~p",
|
||||
[xmpp:pp(IQ), {E, {R, erlang:get_stacktrace()}}]),
|
||||
Txt = <<"Module failed to handle the query">>,
|
||||
Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang),
|
||||
ejabberd_router:route_error(To, From, IQ, Err)
|
||||
end.
|
||||
|
||||
-spec process_iq(module(), atom(), iq()) -> ignore | iq().
|
||||
process_iq(Module, Function, #iq{lang = Lang, sub_els = [El]} = IQ) ->
|
||||
try
|
||||
Pkt = case erlang:function_exported(Module, decode_iq_subel, 1) of
|
||||
true -> Module:decode_iq_subel(El);
|
||||
false -> xmpp:decode(El)
|
||||
end,
|
||||
Module:Function(IQ#iq{sub_els = [Pkt]})
|
||||
catch error:{xmpp_codec, Why} ->
|
||||
Txt = xmpp:format_error(Why),
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec process_iq(module(), atom(), jid(), jid(), term()) -> iq().
|
||||
process_iq(Module, Function, From, To, IQ) ->
|
||||
case Module:Function(From, To, IQ) of
|
||||
ignore -> ignore;
|
||||
ResIQ -> xmpp:decode(jlib:iq_to_xml(ResIQ), ?NS_CLIENT, [ignore_els])
|
||||
end.
|
||||
|
||||
-spec check_type(type()) -> type().
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
-module(gen_pubsub_node).
|
||||
|
||||
-include("jlib.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-type(host() :: mod_pubsub:host()).
|
||||
-type(nodeId() :: mod_pubsub:nodeId()).
|
||||
@ -175,20 +175,13 @@
|
||||
ok |
|
||||
{error, xmlel()}.
|
||||
|
||||
-callback get_items(NodeIdx :: nodeIdx(),
|
||||
JID :: jid(),
|
||||
AccessModel :: accessModel(),
|
||||
Presence_Subscription :: boolean(),
|
||||
RosterGroup :: boolean(),
|
||||
SubId :: subId(),
|
||||
RSM :: none | rsm_in()) ->
|
||||
{result, {[pubsubItem()], none | rsm_out()}} |
|
||||
{error, xmlel()}.
|
||||
-callback get_items(nodeIdx(), jid(), accessModel(),
|
||||
boolean(), boolean(), binary(),
|
||||
undefined | rsm_set()) ->
|
||||
{result, {[pubsubItem()], undefined | rsm_set()}} | {error, stanza_error()}.
|
||||
|
||||
-callback get_items(NodeIdx :: nodeIdx(),
|
||||
From :: jid(),
|
||||
RSM :: none | rsm_in()) ->
|
||||
{result, {[pubsubItem()], none | rsm_out()}}.
|
||||
-callback get_items(nodeIdx(), jid(), undefined | rsm_set()) ->
|
||||
{result, {[pubsubItem()], undefined | rsm_set()}}.
|
||||
|
||||
-callback get_item(NodeIdx :: nodeIdx(),
|
||||
ItemId :: itemId(),
|
||||
|
@ -25,7 +25,6 @@
|
||||
|
||||
-module(gen_pubsub_nodetree).
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-type(host() :: mod_pubsub:host()).
|
||||
-type(nodeId() :: mod_pubsub:nodeId()).
|
||||
@ -42,25 +41,25 @@
|
||||
-callback options() -> nodeOptions().
|
||||
|
||||
-callback set_node(PubsubNode :: pubsubNode()) ->
|
||||
ok | {result, NodeIdx::nodeIdx()} | {error, xmlel()}.
|
||||
ok | {result, NodeIdx::nodeIdx()} | {error, fxml:xmlel()}.
|
||||
|
||||
-callback get_node(Host :: host(),
|
||||
NodeId :: nodeId(),
|
||||
From :: jid()) ->
|
||||
From :: jid:jid()) ->
|
||||
pubsubNode() |
|
||||
{error, xmlel()}.
|
||||
{error, fxml:xmlel()}.
|
||||
|
||||
-callback get_node(Host :: host(),
|
||||
NodeId :: nodeId()) ->
|
||||
pubsubNode() |
|
||||
{error, xmlel()}.
|
||||
{error, fxml:xmlel()}.
|
||||
|
||||
-callback get_node(NodeIdx :: nodeIdx()) ->
|
||||
pubsubNode() |
|
||||
{error, xmlel()}.
|
||||
{error, fxml:xmlel()}.
|
||||
|
||||
-callback get_nodes(Host :: host(),
|
||||
From :: jid())->
|
||||
From :: jid:jid())->
|
||||
[pubsubNode()].
|
||||
|
||||
-callback get_nodes(Host :: host())->
|
||||
@ -68,33 +67,33 @@
|
||||
|
||||
-callback get_parentnodes(Host :: host(),
|
||||
NodeId :: nodeId(),
|
||||
From :: jid()) ->
|
||||
From :: jid:jid()) ->
|
||||
[pubsubNode()] |
|
||||
{error, xmlel()}.
|
||||
{error, fxml:xmlel()}.
|
||||
|
||||
-callback get_parentnodes_tree(Host :: host(),
|
||||
NodeId :: nodeId(),
|
||||
From :: jid()) ->
|
||||
From :: jid:jid()) ->
|
||||
[{0, [pubsubNode(),...]}].
|
||||
|
||||
-callback get_subnodes(Host :: host(),
|
||||
NodeId :: nodeId(),
|
||||
From :: jid()) ->
|
||||
From :: jid:jid()) ->
|
||||
[pubsubNode()].
|
||||
|
||||
-callback get_subnodes_tree(Host :: host(),
|
||||
NodeId :: nodeId(),
|
||||
From :: jid()) ->
|
||||
From :: jid:jid()) ->
|
||||
[pubsubNode()].
|
||||
|
||||
-callback create_node(Host :: host(),
|
||||
NodeId :: nodeId(),
|
||||
Type :: binary(),
|
||||
Owner :: jid(),
|
||||
Owner :: jid:jid(),
|
||||
Options :: nodeOptions(),
|
||||
Parents :: [nodeId()]) ->
|
||||
{ok, NodeIdx::nodeIdx()} |
|
||||
{error, xmlel()} |
|
||||
{error, fxml:xmlel()} |
|
||||
{error, {virtual, {host(), nodeId()}}}.
|
||||
|
||||
-callback delete_node(Host :: host(),
|
||||
|
358
src/http_p1.erl
358
src/http_p1.erl
@ -1,358 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : http_p1.erl
|
||||
%%% Author : Emilio Bustos <ebustos@process-one.net>
|
||||
%%% Purpose : Provide a common API for inets / lhttpc / ibrowse
|
||||
%%% Created : 29 Jul 2010 by Emilio Bustos <ebustos@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(http_p1).
|
||||
|
||||
-author('ebustos@process-one.net').
|
||||
|
||||
-export([start/0, stop/0, get/1, get/2, post/2, post/3,
|
||||
request/3, request/4, request/5,
|
||||
get_pool_size/0, set_pool_size/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(USE_INETS, 1).
|
||||
% -define(USE_LHTTPC, 1).
|
||||
% -define(USE_IBROWSE, 1).
|
||||
% inets used as default if none specified
|
||||
|
||||
-ifdef(USE_IBROWSE).
|
||||
|
||||
start() ->
|
||||
ejabberd:start_app(ibrowse).
|
||||
|
||||
stop() ->
|
||||
application:stop(ibrowse).
|
||||
|
||||
request(Method, URL, Hdrs, Body, Opts) ->
|
||||
TimeOut = proplists:get_value(timeout, Opts, infinity),
|
||||
Options = [{inactivity_timeout, TimeOut}
|
||||
| proplists:delete(timeout, Opts)],
|
||||
case ibrowse:send_req(URL, Hdrs, Method, Body, Options)
|
||||
of
|
||||
{ok, Status, Headers, Response} ->
|
||||
{ok, jlib:binary_to_integer(Status), Headers,
|
||||
Response};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
get_pool_size() ->
|
||||
application:get_env(ibrowse, default_max_sessions, 10).
|
||||
|
||||
set_pool_size(Size) ->
|
||||
application:set_env(ibrowse, default_max_sessions, Size).
|
||||
|
||||
-else.
|
||||
|
||||
-ifdef(USE_LHTTPC).
|
||||
|
||||
start() ->
|
||||
ejabberd:start_app(lhttpc).
|
||||
|
||||
stop() ->
|
||||
application:stop(lhttpc).
|
||||
|
||||
request(Method, URL, Hdrs, Body, Opts) ->
|
||||
{[TO, SO], Rest} = proplists:split(Opts, [timeout, socket_options]),
|
||||
TimeOut = proplists:get_value(timeout, TO, infinity),
|
||||
SockOpt = proplists:get_value(socket_options, SO, []),
|
||||
Options = [{connect_options, SockOpt} | Rest],
|
||||
Result = lhttpc:request(URL, Method, Hdrs, Body, TimeOut, Options),
|
||||
?DEBUG("HTTP request -> response:~n"
|
||||
"** Method = ~p~n"
|
||||
"** URI = ~s~n"
|
||||
"** Body = ~s~n"
|
||||
"** Hdrs = ~p~n"
|
||||
"** Timeout = ~p~n"
|
||||
"** Options = ~p~n"
|
||||
"** Response = ~p",
|
||||
[Method, URL, Body, Hdrs, TimeOut, Options, Result]),
|
||||
case Result of
|
||||
{ok, {{Status, _Reason}, Headers, Response}} ->
|
||||
{ok, Status, Headers, (Response)};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
get_pool_size() ->
|
||||
Opts = proplists:get_value(lhttpc_manager, lhttpc_manager:list_pools()),
|
||||
proplists:get_value(max_pool_size,Opts).
|
||||
|
||||
set_pool_size(Size) ->
|
||||
lhttpc_manager:set_max_pool_size(lhttpc_manager, Size).
|
||||
|
||||
-else.
|
||||
|
||||
start() ->
|
||||
ejabberd:start_app(inets).
|
||||
|
||||
stop() ->
|
||||
application:stop(inets).
|
||||
|
||||
to_list(Str) when is_binary(Str) ->
|
||||
binary_to_list(Str);
|
||||
to_list(Str) ->
|
||||
Str.
|
||||
|
||||
request(Method, URLRaw, HdrsRaw, Body, Opts) ->
|
||||
Hdrs = lists:map(fun({N, V}) ->
|
||||
{to_list(N), to_list(V)}
|
||||
end, HdrsRaw),
|
||||
URL = to_list(URLRaw),
|
||||
|
||||
Request = case Method of
|
||||
get -> {URL, Hdrs};
|
||||
head -> {URL, Hdrs};
|
||||
delete -> {URL, Hdrs};
|
||||
_ -> % post, etc.
|
||||
{URL, Hdrs,
|
||||
to_list(proplists:get_value(<<"content-type">>, HdrsRaw, [])),
|
||||
Body}
|
||||
end,
|
||||
Options = case proplists:get_value(timeout, Opts,
|
||||
infinity)
|
||||
of
|
||||
infinity -> proplists:delete(timeout, Opts);
|
||||
_ -> Opts
|
||||
end,
|
||||
case httpc:request(Method, Request, Options, []) of
|
||||
{ok, {{_, Status, _}, Headers, Response}} ->
|
||||
{ok, Status, Headers, Response};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
get_pool_size() ->
|
||||
{ok, Size} = httpc:get_option(max_sessions),
|
||||
Size.
|
||||
|
||||
set_pool_size(Size) ->
|
||||
httpc:set_option(max_sessions, Size).
|
||||
|
||||
-endif.
|
||||
|
||||
-endif.
|
||||
|
||||
-type({header,
|
||||
{type, 63, tuple,
|
||||
[{type, 63, union,
|
||||
[{type, 63, string, []}, {type, 63, atom, []}]},
|
||||
{type, 63, string, []}]},
|
||||
[]}).
|
||||
|
||||
-type({headers,
|
||||
{type, 64, list, [{type, 64, header, []}]}, []}).
|
||||
|
||||
-type({option,
|
||||
{type, 67, union,
|
||||
[{type, 67, tuple,
|
||||
[{atom, 67, connect_timeout}, {type, 67, timeout, []}]},
|
||||
{type, 68, tuple,
|
||||
[{atom, 68, timeout}, {type, 68, timeout, []}]},
|
||||
{type, 70, tuple,
|
||||
[{atom, 70, send_retry},
|
||||
{type, 70, non_neg_integer, []}]},
|
||||
{type, 71, tuple,
|
||||
[{atom, 71, partial_upload},
|
||||
{type, 71, union,
|
||||
[{type, 71, non_neg_integer, []},
|
||||
{atom, 71, infinity}]}]},
|
||||
{type, 72, tuple,
|
||||
[{atom, 72, partial_download}, {type, 72, pid, []},
|
||||
{type, 72, union,
|
||||
[{type, 72, non_neg_integer, []},
|
||||
{atom, 72, infinity}]}]}]},
|
||||
[]}).
|
||||
|
||||
-type({options,
|
||||
{type, 74, list, [{type, 74, option, []}]}, []}).
|
||||
|
||||
-type({result,
|
||||
{type, 76, union,
|
||||
[{type, 76, tuple,
|
||||
[{atom, 76, ok},
|
||||
{type, 76, tuple,
|
||||
[{type, 76, tuple,
|
||||
[{type, 76, pos_integer, []}, {type, 76, string, []}]},
|
||||
{type, 76, headers, []}, {type, 76, string, []}]}]},
|
||||
{type, 77, tuple,
|
||||
[{atom, 77, error}, {type, 77, atom, []}]}]},
|
||||
[]}).
|
||||
|
||||
%% @spec (URL) -> Result
|
||||
%% URL = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a GET request.
|
||||
%% Would be the same as calling `request(get, URL, [])',
|
||||
%% that is {@link request/3} with an empty header list.
|
||||
%% @end
|
||||
%% @see request/3
|
||||
-spec get(string()) -> result().
|
||||
get(URL) -> request(get, URL, []).
|
||||
|
||||
%% @spec (URL, Hdrs) -> Result
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a GET request.
|
||||
%% Would be the same as calling `request(get, URL, Hdrs)'.
|
||||
%% @end
|
||||
%% @see request/3
|
||||
-spec get(string(), headers()) -> result().
|
||||
get(URL, Hdrs) -> request(get, URL, Hdrs).
|
||||
|
||||
%% @spec (URL, RequestBody) -> Result
|
||||
%% URL = string()
|
||||
%% RequestBody = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a POST request with form data.
|
||||
%% Would be the same as calling
|
||||
%% `request(post, URL, [{"content-type", "x-www-form-urlencoded"}], Body)'.
|
||||
%% @end
|
||||
%% @see request/4
|
||||
-spec post(string(), string()) -> result().
|
||||
post(URL, Body) ->
|
||||
request(post, URL,
|
||||
[{<<"content-type">>, <<"x-www-form-urlencoded">>}],
|
||||
Body).
|
||||
|
||||
%% @spec (URL, Hdrs, RequestBody) -> Result
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% RequestBody = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a POST request.
|
||||
%% Would be the same as calling
|
||||
%% `request(post, URL, Hdrs, Body)'.
|
||||
%% @end
|
||||
%% @see request/4
|
||||
-spec post(string(), headers(), string()) -> result().
|
||||
post(URL, Hdrs, Body) ->
|
||||
NewHdrs = case [X
|
||||
|| {X, _} <- Hdrs,
|
||||
str:to_lower(X) == <<"content-type">>]
|
||||
of
|
||||
[] ->
|
||||
[{<<"content-type">>, <<"x-www-form-urlencoded">>}
|
||||
| Hdrs];
|
||||
_ -> Hdrs
|
||||
end,
|
||||
request(post, URL, NewHdrs, Body).
|
||||
|
||||
%% @spec (Method, URL, Hdrs) -> Result
|
||||
%% Method = atom()
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a request without a body.
|
||||
%% Would be the same as calling `request(Method, URL, Hdrs, [], [])',
|
||||
%% that is {@link request/5} with an empty body.
|
||||
%% @end
|
||||
%% @see request/5
|
||||
-spec request(atom(), string(), headers()) -> result().
|
||||
request(Method, URL, Hdrs) ->
|
||||
request(Method, URL, Hdrs, [], []).
|
||||
|
||||
%% @spec (Method, URL, Hdrs, RequestBody) -> Result
|
||||
%% Method = atom()
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% RequestBody = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a request with a body.
|
||||
%% Would be the same as calling
|
||||
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
|
||||
%% with no options.
|
||||
%% @end
|
||||
%% @see request/5
|
||||
-spec request(atom(), string(), headers(), string()) -> result().
|
||||
request(Method, URL, Hdrs, Body) ->
|
||||
request(Method, URL, Hdrs, Body, []).
|
||||
|
||||
%% @spec (Method, URL, Hdrs, RequestBody, Options) -> Result
|
||||
%% Method = atom()
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% RequestBody = string()
|
||||
%% Options = [Option]
|
||||
%% Option = {timeout, Milliseconds | infinity} |
|
||||
%% {connect_timeout, Milliseconds | infinity} |
|
||||
%% {socket_options, [term()]} |
|
||||
|
||||
%% Milliseconds = integer()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a request with a body.
|
||||
%% Would be the same as calling
|
||||
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
|
||||
%% with no options.
|
||||
%% @end
|
||||
%% @see request/5
|
||||
-spec request(atom(), string(), headers(), string(), options()) -> result().
|
||||
|
||||
% ibrowse {response_format, response_format()} |
|
||||
% Options - [option()]
|
||||
% Option - {sync, boolean()} | {stream, StreamTo} | {body_format, body_format()} | {full_result,
|
||||
% boolean()} | {headers_as_is, boolean()}
|
||||
%body_format() = string() | binary()
|
||||
% The body_format option is only valid for the synchronous request and the default is string.
|
||||
% When making an asynchronous request the body will always be received as a binary.
|
||||
% lhttpc: always binary
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user