diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..94b549649 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +.win32 +.examples diff --git a/.gitignore b/.gitignore index bf5fe2863..38d6d77f8 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ /doc/version.tex /ebin/ /ejabberd.init +/ejabberd.service /ejabberdctl.example XmppAddr.hrl /rel/ejabberd/ diff --git a/.travis.yml b/.travis.yml index d30afbb18..aa9acef82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ language: erlang otp_release: - 17.5 - 18.3 + - 19.1 services: - riak diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..37763a734 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM debian:jessie +MAINTAINER Rafael Römhild + +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"] diff --git a/Makefile.in b/Makefile.in index 18c611e96..5ff3efe0b 100644 --- a/Makefile.in +++ b/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 diff --git a/asn1/ELDAPv3.asn1~ b/asn1/ELDAPv3.asn1~ deleted file mode 100644 index 1fec35cd8..000000000 --- a/asn1/ELDAPv3.asn1~ +++ /dev/null @@ -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 - - diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..f518b488c --- /dev/null +++ b/docker/README.md @@ -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 //conf:/opt/ejabberd/conf \ + rroemhild/ejabberd +``` + +Your ```//conf``` folder should look like so: + +``` +//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) diff --git a/docker/bootstrap.sh b/docker/bootstrap.sh new file mode 100755 index 000000000..cedab5f4f --- /dev/null +++ b/docker/bootstrap.sh @@ -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 diff --git a/docker/conf/ejabberd.yml.tpl b/docker/conf/ejabberd.yml.tpl new file mode 100644 index 000000000..dd8b58821 --- /dev/null +++ b/docker/conf/ejabberd.yml.tpl @@ -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 %} diff --git a/docker/conf/ejabberdctl.cfg.tpl b/docker/conf/ejabberdctl.cfg.tpl new file mode 100644 index 000000000..98b4608c2 --- /dev/null +++ b/docker/conf/ejabberdctl.cfg.tpl @@ -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: diff --git a/docker/lib/base_config.sh b/docker/lib/base_config.sh new file mode 100644 index 000000000..803c1db47 --- /dev/null +++ b/docker/lib/base_config.sh @@ -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))" diff --git a/docker/lib/base_functions.sh b/docker/lib/base_functions.sh new file mode 100644 index 000000000..d7bf97266 --- /dev/null +++ b/docker/lib/base_functions.sh @@ -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 +} diff --git a/docker/lib/config.sh b/docker/lib/config.sh new file mode 100644 index 000000000..6b9cbbb12 --- /dev/null +++ b/docker/lib/config.sh @@ -0,0 +1 @@ +# Overridable file diff --git a/docker/lib/functions.sh b/docker/lib/functions.sh new file mode 100644 index 000000000..6b9cbbb12 --- /dev/null +++ b/docker/lib/functions.sh @@ -0,0 +1 @@ +# Overridable file diff --git a/docker/post/10_ejabberd_modules_update_specs.sh b/docker/post/10_ejabberd_modules_update_specs.sh new file mode 100755 index 000000000..9e916016a --- /dev/null +++ b/docker/post/10_ejabberd_modules_update_specs.sh @@ -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 diff --git a/docker/post/11_ejabberd_install_modules.sh b/docker/post/11_ejabberd_install_modules.sh new file mode 100755 index 000000000..2dd4f3922 --- /dev/null +++ b/docker/post/11_ejabberd_install_modules.sh @@ -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 diff --git a/docker/post/20_ejabberd_register_users.sh b/docker/post/20_ejabberd_register_users.sh new file mode 100755 index 000000000..9dc910eeb --- /dev/null +++ b/docker/post/20_ejabberd_register_users.sh @@ -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 diff --git a/docker/post/99_first_start_done.sh b/docker/post/99_first_start_done.sh new file mode 100755 index 000000000..394531cf6 --- /dev/null +++ b/docker/post/99_first_start_done.sh @@ -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 diff --git a/docker/pre/01_write_certifiates_from_env.sh b/docker/pre/01_write_certifiates_from_env.sh new file mode 100755 index 000000000..a42c2e306 --- /dev/null +++ b/docker/pre/01_write_certifiates_from_env.sh @@ -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 diff --git a/docker/pre/02_make_snakeoil_certificates.sh b/docker/pre/02_make_snakeoil_certificates.sh new file mode 100755 index 000000000..d8eeec937 --- /dev/null +++ b/docker/pre/02_make_snakeoil_certificates.sh @@ -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 diff --git a/docker/pre/03_make_dhparam.sh b/docker/pre/03_make_dhparam.sh new file mode 100755 index 000000000..d897b2789 --- /dev/null +++ b/docker/pre/03_make_dhparam.sh @@ -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 diff --git a/docker/pre/10_erlang_cookie.sh b/docker/pre/10_erlang_cookie.sh new file mode 100755 index 000000000..2c08a64fb --- /dev/null +++ b/docker/pre/10_erlang_cookie.sh @@ -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 diff --git a/docker/pre/20_ejabberd_config.sh b/docker/pre/20_ejabberd_config.sh new file mode 100755 index 000000000..230a1981b --- /dev/null +++ b/docker/pre/20_ejabberd_config.sh @@ -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 diff --git a/docker/start.sh b/docker/start.sh new file mode 100755 index 000000000..64a971f66 --- /dev/null +++ b/docker/start.sh @@ -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 diff --git a/docker/stop/10_leave_cluster.sh b/docker/stop/10_leave_cluster.sh new file mode 100755 index 000000000..f6fc97fa7 --- /dev/null +++ b/docker/stop/10_leave_cluster.sh @@ -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 diff --git a/ejabberd.service.template b/ejabberd.service.template index 49ba14737..a70d2254d 100644 --- a/ejabberd.service.template +++ b/ejabberd.service.template @@ -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 diff --git a/include/bosh.hrl b/include/bosh.hrl new file mode 100644 index 000000000..9cdfd98d8 --- /dev/null +++ b/include/bosh.hrl @@ -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). diff --git a/include/ejabberd.hrl b/include/ejabberd.hrl index 6316d7813..391089a0e 100644 --- a/include/ejabberd.hrl +++ b/include/ejabberd.hrl @@ -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. diff --git a/include/ejabberd_commands.hrl b/include/ejabberd_commands.hrl index c5c34b743..199be890e 100644 --- a/include/ejabberd_commands.hrl +++ b/include/ejabberd_commands.hrl @@ -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, diff --git a/include/ejabberd_service.hrl b/include/ejabberd_service.hrl deleted file mode 100644 index 7cd3b6943..000000000 --- a/include/ejabberd_service.hrl +++ /dev/null @@ -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{} ). diff --git a/include/jlib.hrl b/include/jlib.hrl index 50a031334..5a3c1634e 100644 --- a/include/jlib.hrl +++ b/include/jlib.hrl @@ -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">>, diff --git a/include/mod_mam.hrl b/include/mod_mam.hrl index 463db4cff..a2b92fca5 100644 --- a/include/mod_mam.hrl +++ b/include/mod_mam.hrl @@ -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}). diff --git a/include/mod_muc_room.hrl b/include/mod_muc_room.hrl index 551da7285..dd414a8d8 100644 --- a/include/mod_muc_room.hrl +++ b/include/mod_muc_room.hrl @@ -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{}. diff --git a/include/mod_offline.hrl b/include/mod_offline.hrl index c4c70604a..cc644c4c2 100644 --- a/include/mod_offline.hrl +++ b/include/mod_offline.hrl @@ -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() | '_'}). diff --git a/include/mod_privacy.hrl b/include/mod_privacy.hrl index 8fe5abcca..dbd19a081 100644 --- a/include/mod_privacy.hrl +++ b/include/mod_privacy.hrl @@ -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()], diff --git a/include/mod_roster.hrl b/include/mod_roster.hrl index b05114e3e..818508703 100644 --- a/include/mod_roster.hrl +++ b/include/mod_roster.hrl @@ -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, diff --git a/include/ns.hrl b/include/ns.hrl deleted file mode 100644 index 3dbc765b0..000000000 --- a/include/ns.hrl +++ /dev/null @@ -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">>). diff --git a/mix.exs b/mix.exs index c77f2abb4..b9ff8794e 100644 --- a/mix.exs +++ b/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"}, diff --git a/mix.lock b/mix.lock index e515fd346..695a514de 100644 --- a/mix.lock +++ b/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"]}} diff --git a/priv/msgs/pt-br.po b/priv/msgs/pt-br.po index 6d2ae1c57..e211315f3 100644 --- a/priv/msgs/pt-br.po +++ b/priv/msgs/pt-br.po @@ -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 diff --git a/rebar.config b/rebar.config index 27439109b..80eb25a93 100644 --- a/rebar.config +++ b/rebar.config @@ -1,4 +1,4 @@ -%%%------------------------------------------------------------------- +%------------------------------------------------------------------- %%% @author Evgeniy Khramtsov %%% @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}. diff --git a/rebar.config.script b/rebar.config.script index ccafba7ec..34e0c328e 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -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} -> diff --git a/src/acl.erl b/src/acl.erl index 1476081dd..e00aaa5d3 100644 --- a/src/acl.erl +++ b/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 -> diff --git a/src/adhoc.erl b/src/adhoc.erl deleted file mode 100644 index 23ffd8dd8..000000000 --- a/src/adhoc.erl +++ /dev/null @@ -1,157 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : adhoc.erl -%%% Author : Magnus Henoch -%%% Purpose : Provide helper functions for ad-hoc commands (XEP-0050) -%%% Created : 31 Oct 2005 by Magnus Henoch -%%% -%%% -%%% 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 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 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 - }. diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl index a101e1380..4b0f5a26b 100644 --- a/src/cyrsasl.erl +++ b/src/cyrsasl.erl @@ -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, diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl index 802e1cd7b..15980afc5 100644 --- a/src/cyrsasl_anonymous.erl +++ b/src/cyrsasl_anonymous.erl @@ -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}]} diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index 0d408fc48..150aa854c 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -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), "", []). diff --git a/src/cyrsasl_oauth.erl b/src/cyrsasl_oauth.erl index 80ba315ed..21dedc6db 100644 --- a/src/cyrsasl_oauth.erl +++ b/src/cyrsasl_oauth.erl @@ -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) -> diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl index 82d68f87f..8e9b32b99 100644 --- a/src/cyrsasl_plain.erl +++ b/src/cyrsasl_plain.erl @@ -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) -> diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl index 1c464e121..1e2a5c681 100644 --- a/src/cyrsasl_scram.erl +++ b/src/cyrsasl_scram.erl @@ -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(<<"">>) -> <<"">>; diff --git a/src/ejabberd_access_permissions.erl b/src/ejabberd_access_permissions.erl index 7ce75aa9c..60ad68a29 100644 --- a/src/ejabberd_access_permissions.erl +++ b/src/ejabberd_access_permissions.erl @@ -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; diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 412e2bbd0..765d23810 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -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( diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index e4087142b..e4333c816 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -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) -> diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 6b7f537c0..74c8009c2 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -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) -> diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl index 5a5b395bf..e0c4d471f 100644 --- a/src/ejabberd_auth_anonymous.erl +++ b/src/ejabberd_auth_anonymous.erl @@ -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, diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl index f36c9fbc7..eac19f024 100644 --- a/src/ejabberd_auth_mnesia.erl +++ b/src/ejabberd_auth_mnesia.erl @@ -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]. diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl index 05add262e..51571c4e2 100644 --- a/src/ejabberd_auth_riak.erl +++ b/src/ejabberd_auth_riak.erl @@ -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]. diff --git a/src/ejabberd_bosh.erl b/src/ejabberd_bosh.erl new file mode 100644 index 000000000..b94184167 --- /dev/null +++ b/src/ejabberd_bosh.erl @@ -0,0 +1,1095 @@ +%%%------------------------------------------------------------------- +%%% File : ejabberd_bosh.erl +%%% Author : Evgeniy Khramtsov +%%% Purpose : Manage BOSH sockets +%%% Created : 20 Jul 2011 by Evgeniy Khramtsov +%%% +%%% +%%% 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(ejabberd_bosh). + +-protocol({xep, 124, '1.11'}). +-protocol({xep, 206, '1.4'}). + +-define(GEN_FSM, p1_fsm). + +-behaviour(?GEN_FSM). + +%% API +-export([start/2, start/3, start_link/3]). + +-export([send_xml/2, setopts/2, controlling_process/2, + migrate/3, custom_receiver/1, become_controller/2, + reset_stream/1, change_shaper/2, monitor/1, close/1, + sockname/1, peername/1, process_request/3, send/2, + change_controller/2]). + +%% gen_fsm callbacks +-export([init/1, wait_for_session/2, wait_for_session/3, + active/2, active/3, handle_event/3, print_state/1, + handle_sync_event/4, handle_info/3, terminate/3, + code_change/4]). + +-include("ejabberd.hrl"). +-include("logger.hrl"). + +-include("jlib.hrl"). + +-include("ejabberd_http.hrl"). + +-include("bosh.hrl"). + +%%-define(DBGFSM, true). +-ifdef(DBGFSM). + +-define(FSMOPTS, [{debug, [trace]}]). + +-else. + +-define(FSMOPTS, []). + +-endif. + +-define(BOSH_VERSION, <<"1.11">>). + +-define(NS_BOSH, <<"urn:xmpp:xbosh">>). + +-define(NS_HTTP_BIND, + <<"http://jabber.org/protocol/httpbind">>). + +-define(DEFAULT_MAXPAUSE, 120). + +-define(DEFAULT_WAIT, 300). + +-define(DEFAULT_HOLD, 1). + +-define(DEFAULT_POLLING, 2). + +-define(DEFAULT_INACTIVITY, 30). + +-define(MAX_SHAPED_REQUESTS_QUEUE_LEN, 1000). + +-define(SEND_TIMEOUT, 15000). + +-type bosh_socket() :: {http_bind, pid(), + {inet:ip_address(), + inet:port_number()}}. + +-export_type([bosh_socket/0]). + +-record(state, + {host = <<"">> :: binary(), + sid = <<"">> :: binary(), + el_ibuf = buf_new() :: ?TQUEUE, + el_obuf = buf_new() :: ?TQUEUE, + shaper_state = none :: shaper:shaper(), + c2s_pid :: pid(), + xmpp_ver = <<"">> :: binary(), + inactivity_timer :: reference(), + wait_timer :: reference(), + wait_timeout = ?DEFAULT_WAIT :: timeout(), + inactivity_timeout = ?DEFAULT_INACTIVITY :: timeout(), + prev_rid = 0 :: non_neg_integer(), + prev_key = <<"">> :: binary(), + prev_poll :: erlang:timestamp(), + max_concat = unlimited :: unlimited | non_neg_integer(), + responses = gb_trees:empty() :: ?TGB_TREE, + receivers = gb_trees:empty() :: ?TGB_TREE, + shaped_receivers = queue:new() :: ?TQUEUE, + ip :: inet:ip_address(), + max_requests = 1 :: non_neg_integer()}). + +-record(body, + {http_reason = <<"">> :: binary(), + attrs = [] :: [{any(), any()}], + els = [] :: [fxml_stream:xml_stream_el()], + size = 0 :: non_neg_integer()}). + +start(#body{attrs = Attrs} = Body, IP, SID) -> + XMPPDomain = get_attr(to, Attrs), + SupervisorProc = gen_mod:get_module_proc(XMPPDomain, ?PROCNAME), + case catch supervisor:start_child(SupervisorProc, + [Body, IP, SID]) + of + {ok, Pid} -> {ok, Pid}; + {'EXIT', {noproc, _}} -> + check_bosh_module(XMPPDomain), + {error, module_not_loaded}; + Err -> + ?ERROR_MSG("Failed to start BOSH session: ~p", [Err]), + {error, Err} + end. + +start(StateName, State) -> + (?GEN_FSM):start_link(?MODULE, [StateName, State], + ?FSMOPTS). + +start_link(Body, IP, SID) -> + (?GEN_FSM):start_link(?MODULE, [Body, IP, SID], + ?FSMOPTS). + +send({http_bind, FsmRef, IP}, Packet) -> + send_xml({http_bind, FsmRef, IP}, Packet). + +send_xml({http_bind, FsmRef, _IP}, Packet) -> + case catch (?GEN_FSM):sync_send_all_state_event(FsmRef, + {send_xml, Packet}, + ?SEND_TIMEOUT) + of + {'EXIT', {timeout, _}} -> {error, timeout}; + {'EXIT', _} -> {error, einval}; + Res -> Res + end. + +setopts({http_bind, FsmRef, _IP}, Opts) -> + case lists:member({active, once}, Opts) of + true -> + (?GEN_FSM):send_all_state_event(FsmRef, + {activate, self()}); + _ -> + case lists:member({active, false}, Opts) of + true -> + case catch (?GEN_FSM):sync_send_all_state_event(FsmRef, + deactivate_socket) + of + {'EXIT', _} -> {error, einval}; + Res -> Res + end; + _ -> ok + end + end. + +controlling_process(_Socket, _Pid) -> ok. + +custom_receiver({http_bind, FsmRef, _IP}) -> + {receiver, ?MODULE, FsmRef}. + +become_controller(FsmRef, C2SPid) -> + (?GEN_FSM):send_all_state_event(FsmRef, + {become_controller, C2SPid}). + +change_controller({http_bind, FsmRef, _IP}, C2SPid) -> + become_controller(FsmRef, C2SPid). + +reset_stream({http_bind, _FsmRef, _IP}) -> ok. + +change_shaper({http_bind, FsmRef, _IP}, Shaper) -> + (?GEN_FSM):send_all_state_event(FsmRef, + {change_shaper, Shaper}). + +monitor({http_bind, FsmRef, _IP}) -> + erlang:monitor(process, FsmRef). + +close({http_bind, FsmRef, _IP}) -> + catch (?GEN_FSM):sync_send_all_state_event(FsmRef, + close). + +sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}. + +peername({http_bind, _FsmRef, IP}) -> {ok, IP}. + +migrate(FsmRef, Node, After) when node(FsmRef) == node() -> + catch erlang:send_after(After, FsmRef, {migrate, Node}); +migrate(_FsmRef, _Node, _After) -> + ok. + +process_request(Data, IP, Type) -> + Opts1 = ejabberd_c2s_config:get_c2s_limits(), + Opts = case Type of + xml -> + [{xml_socket, true} | Opts1]; + json -> + Opts1 + end, + MaxStanzaSize = case lists:keysearch(max_stanza_size, 1, + Opts) + of + {value, {_, Size}} -> Size; + _ -> infinity + end, + PayloadSize = iolist_size(Data), + if PayloadSize > MaxStanzaSize -> + http_error(403, <<"Request Too Large">>, Type); + true -> + case decode_body(Data, PayloadSize, Type) of + {ok, #body{attrs = Attrs} = Body} -> + SID = get_attr(sid, Attrs), + To = get_attr(to, Attrs), + if SID == <<"">>, To == <<"">> -> + bosh_response_with_msg(#body{http_reason = + <<"Missing 'to' attribute">>, + attrs = + [{type, <<"terminate">>}, + {condition, + <<"improper-addressing">>}]}, + Type, Body); + SID == <<"">> -> + case start(Body, IP, make_sid()) of + {ok, Pid} -> process_request(Pid, Body, IP, Type); + _Err -> + bosh_response_with_msg(#body{http_reason = + <<"Failed to start BOSH session">>, + attrs = + [{type, <<"terminate">>}, + {condition, + <<"internal-server-error">>}]}, + Type, Body) + end; + true -> + case mod_bosh:find_session(SID) of + {ok, Pid} -> process_request(Pid, Body, IP, Type); + error -> + bosh_response_with_msg(#body{http_reason = + <<"Session ID mismatch">>, + attrs = + [{type, <<"terminate">>}, + {condition, + <<"item-not-found">>}]}, + Type, Body) + end + end; + {error, Reason} -> http_error(400, Reason, Type) + end + end. + +process_request(Pid, Req, _IP, Type) -> + case catch (?GEN_FSM):sync_send_event(Pid, Req, + infinity) + of + #body{} = Resp -> bosh_response(Resp, Type); + {'EXIT', {Reason, _}} + when Reason == noproc; Reason == normal -> + bosh_response(#body{http_reason = + <<"BOSH session not found">>, + attrs = + [{type, <<"terminate">>}, + {condition, <<"item-not-found">>}]}, + Type); + {'EXIT', _} -> + bosh_response(#body{http_reason = + <<"Unexpected error">>, + attrs = + [{type, <<"terminate">>}, + {condition, <<"internal-server-error">>}]}, + Type) + end. + +init([#body{attrs = Attrs}, IP, SID]) -> + Opts1 = ejabberd_c2s_config:get_c2s_limits(), + Opts2 = [{xml_socket, true} | Opts1], + Shaper = none, + ShaperState = shaper:new(Shaper), + Socket = make_socket(self(), IP), + XMPPVer = get_attr('xmpp:version', Attrs), + XMPPDomain = get_attr(to, Attrs), + {InBuf, Opts} = case gen_mod:get_module_opt( + XMPPDomain, + mod_bosh, prebind, + fun(B) when is_boolean(B) -> B end, + false) of + true -> + JID = make_random_jid(XMPPDomain), + {buf_new(), [{jid, JID} | Opts2]}; + false -> + {buf_in([make_xmlstreamstart(XMPPDomain, XMPPVer)], + buf_new()), + Opts2} + end, + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, + Opts), + Inactivity = gen_mod:get_module_opt(XMPPDomain, + mod_bosh, max_inactivity, + fun(I) when is_integer(I), I>0 -> I end, + ?DEFAULT_INACTIVITY), + MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat, + fun(unlimited) -> unlimited; + (N) when is_integer(N), N>0 -> N + end, unlimited), + State = #state{host = XMPPDomain, sid = SID, ip = IP, + xmpp_ver = XMPPVer, el_ibuf = InBuf, + max_concat = MaxConcat, el_obuf = buf_new(), + inactivity_timeout = Inactivity, + shaper_state = ShaperState}, + NewState = restart_inactivity_timer(State), + mod_bosh:open_session(SID, self()), + {ok, wait_for_session, NewState}; +init([StateName, State]) -> + mod_bosh:open_session(State#state.sid, self()), + case State#state.c2s_pid of + C2SPid when is_pid(C2SPid) -> + NewSocket = make_socket(self(), State#state.ip), + C2SPid ! {change_socket, NewSocket}, + NewState = restart_inactivity_timer(State), + {ok, StateName, NewState}; + _ -> {stop, normal} + end. + +wait_for_session(_Event, State) -> + ?ERROR_MSG("unexpected event in 'wait_for_session': ~p", + [_Event]), + {next_state, wait_for_session, State}. + +wait_for_session(#body{attrs = Attrs} = Req, From, + State) -> + RID = get_attr(rid, Attrs), + ?DEBUG("got request:~n** RequestID: ~p~n** Request: " + "~p~n** From: ~p~n** State: ~p", + [RID, Req, From, State]), + Wait = min(get_attr(wait, Attrs, undefined), + ?DEFAULT_WAIT), + Hold = min(get_attr(hold, Attrs, undefined), + ?DEFAULT_HOLD), + NewKey = get_attr(newkey, Attrs), + Type = get_attr(type, Attrs), + Requests = Hold + 1, + {PollTime, Polling} = if Wait == 0, Hold == 0 -> + {p1_time_compat:timestamp(), [{polling, ?DEFAULT_POLLING}]}; + true -> {undefined, []} + end, + MaxPause = gen_mod:get_module_opt(State#state.host, + mod_bosh, max_pause, + fun(I) when is_integer(I), I>0 -> I end, + ?DEFAULT_MAXPAUSE), + Resp = #body{attrs = + [{sid, State#state.sid}, {wait, Wait}, + {ver, ?BOSH_VERSION}, {polling, ?DEFAULT_POLLING}, + {inactivity, State#state.inactivity_timeout}, + {hold, Hold}, {'xmpp:restartlogic', true}, + {requests, Requests}, {secure, true}, + {maxpause, MaxPause}, {'xmlns:xmpp', ?NS_BOSH}, + {'xmlns:stream', ?NS_STREAM}, {from, State#state.host} + | Polling]}, + {ShaperState, _} = + shaper:update(State#state.shaper_state, Req#body.size), + State1 = State#state{wait_timeout = Wait, + prev_rid = RID, prev_key = NewKey, + prev_poll = PollTime, shaper_state = ShaperState, + max_requests = Requests}, + Els = maybe_add_xmlstreamend(Req#body.els, Type), + State2 = route_els(State1, Els), + {State3, RespEls} = get_response_els(State2), + State4 = stop_inactivity_timer(State3), + case RespEls of + [] -> + State5 = restart_wait_timer(State4), + Receivers = gb_trees:insert(RID, {From, Resp}, + State5#state.receivers), + {next_state, active, + State5#state{receivers = Receivers}}; + _ -> + reply_next_state(State4, Resp#body{els = RespEls}, RID, + From) + end; +wait_for_session(_Event, _From, State) -> + ?ERROR_MSG("unexpected sync event in 'wait_for_session': ~p", + [_Event]), + {reply, {error, badarg}, wait_for_session, State}. + +active({#body{} = Body, From}, State) -> + active1(Body, From, State); +active(_Event, State) -> + ?ERROR_MSG("unexpected event in 'active': ~p", + [_Event]), + {next_state, active, State}. + +active(#body{attrs = Attrs, size = Size} = Req, From, + State) -> + ?DEBUG("got request:~n** Request: ~p~n** From: " + "~p~n** State: ~p", + [Req, From, State]), + {ShaperState, Pause} = + shaper:update(State#state.shaper_state, Size), + State1 = State#state{shaper_state = ShaperState}, + if Pause > 0 -> + QLen = queue:len(State1#state.shaped_receivers), + if QLen < (?MAX_SHAPED_REQUESTS_QUEUE_LEN) -> + TRef = start_shaper_timer(Pause), + Q = queue:in({TRef, From, Req}, + State1#state.shaped_receivers), + State2 = stop_inactivity_timer(State1), + {next_state, active, + State2#state{shaped_receivers = Q}}; + true -> + RID = get_attr(rid, Attrs), + reply_stop(State1, + #body{http_reason = <<"Too many requests">>, + attrs = + [{<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"policy-violation">>}]}, + From, RID) + end; + true -> active1(Req, From, State1) + end; +active(_Event, _From, State) -> + ?ERROR_MSG("unexpected sync event in 'active': ~p", + [_Event]), + {reply, {error, badarg}, active, State}. + +active1(#body{attrs = Attrs} = Req, From, State) -> + RID = get_attr(rid, Attrs), + Key = get_attr(key, Attrs), + IsValidKey = is_valid_key(State#state.prev_key, Key), + IsOveractivity = is_overactivity(State#state.prev_poll), + Type = get_attr(type, Attrs), + if RID > + State#state.prev_rid + State#state.max_requests -> + reply_stop(State, + #body{http_reason = <<"Request ID is out of range">>, + attrs = + [{<<"type">>, <<"terminate">>}, + {<<"condition">>, <<"item-not-found">>}]}, + From, RID); + RID > State#state.prev_rid + 1 -> + State1 = restart_inactivity_timer(State), + Receivers = gb_trees:insert(RID, {From, Req}, + State1#state.receivers), + {next_state, active, + State1#state{receivers = Receivers}}; + RID =< State#state.prev_rid -> + %% TODO: do we need to check 'key' here? It seems so... + case gb_trees:lookup(RID, State#state.responses) of + {value, PrevBody} -> + {next_state, active, + do_reply(State, From, PrevBody, RID)}; + none -> + State1 = drop_holding_receiver(State), + State2 = stop_inactivity_timer(State1), + State3 = restart_wait_timer(State2), + Receivers = gb_trees:insert(RID, {From, Req}, + State3#state.receivers), + {next_state, active, State3#state{receivers = Receivers}} + end; + not IsValidKey -> + reply_stop(State, + #body{http_reason = <<"Session key mismatch">>, + attrs = + [{<<"type">>, <<"terminate">>}, + {<<"condition">>, <<"item-not-found">>}]}, + From, RID); + IsOveractivity -> + reply_stop(State, + #body{http_reason = <<"Too many requests">>, + attrs = + [{<<"type">>, <<"terminate">>}, + {<<"condition">>, <<"policy-violation">>}]}, + From, RID); + true -> + State1 = stop_inactivity_timer(State), + State2 = stop_wait_timer(State1), + Els = case get_attr('xmpp:restart', Attrs, false) of + true -> + XMPPDomain = get_attr(to, Attrs, State#state.host), + XMPPVer = get_attr('xmpp:version', Attrs, + State#state.xmpp_ver), + [make_xmlstreamstart(XMPPDomain, XMPPVer)]; + false -> Req#body.els + end, + State3 = route_els(State2, + maybe_add_xmlstreamend(Els, Type)), + {State4, RespEls} = get_response_els(State3), + NewKey = get_attr(newkey, Attrs, Key), + Pause = get_attr(pause, Attrs, undefined), + NewPoll = case State#state.prev_poll of + undefined -> undefined; + _ -> p1_time_compat:timestamp() + end, + State5 = State4#state{prev_poll = NewPoll, + prev_key = NewKey}, + if Type == <<"terminate">> -> + reply_stop(State5, + #body{http_reason = <<"Session close">>, + attrs = [{<<"type">>, <<"terminate">>}], + els = RespEls}, + From, RID); + Pause /= undefined -> + State6 = drop_holding_receiver(State5), + State7 = restart_inactivity_timer(State6, Pause), + InBuf = buf_in(RespEls, State7#state.el_ibuf), + {next_state, active, + State7#state{prev_rid = RID, el_ibuf = InBuf}}; + RespEls == [] -> + State6 = drop_holding_receiver(State5), + State7 = stop_inactivity_timer(State6), + State8 = restart_wait_timer(State7), + Receivers = gb_trees:insert(RID, {From, #body{}}, + State8#state.receivers), + {next_state, active, + State8#state{prev_rid = RID, receivers = Receivers}}; + true -> + State6 = drop_holding_receiver(State5), + reply_next_state(State6#state{prev_rid = RID}, + #body{els = RespEls}, RID, From) + end + end. + +handle_event({become_controller, C2SPid}, StateName, + State) -> + State1 = route_els(State#state{c2s_pid = C2SPid}), + {next_state, StateName, State1}; +handle_event({change_shaper, Shaper}, StateName, + State) -> + NewShaperState = shaper:new(Shaper), + {next_state, StateName, + State#state{shaper_state = NewShaperState}}; +handle_event(_Event, StateName, State) -> + ?ERROR_MSG("unexpected event in '~s': ~p", + [StateName, _Event]), + {next_state, StateName, State}. + +handle_sync_event({send_xml, + {xmlstreamstart, _, _} = El}, + _From, StateName, State) + when State#state.xmpp_ver >= <<"1.0">> -> + OutBuf = buf_in([El], State#state.el_obuf), + {reply, ok, StateName, State#state{el_obuf = OutBuf}}; +handle_sync_event({send_xml, El}, _From, StateName, + State) -> + OutBuf = buf_in([El], State#state.el_obuf), + State1 = State#state{el_obuf = OutBuf}, + case gb_trees:lookup(State1#state.prev_rid, + State1#state.receivers) + of + {value, {From, Body}} -> + {State2, Els} = get_response_els(State1), + {reply, ok, StateName, + reply(State2, Body#body{els = Els}, + State2#state.prev_rid, From)}; + none -> + State2 = case queue:out(State1#state.shaped_receivers) + of + {{value, {TRef, From, Body}}, Q} -> + cancel_timer(TRef), + (?GEN_FSM):send_event(self(), {Body, From}), + State1#state{shaped_receivers = Q}; + _ -> State1 + end, + {reply, ok, StateName, State2} + end; +handle_sync_event(close, _From, _StateName, State) -> + {stop, normal, State}; +handle_sync_event(deactivate_socket, _From, StateName, + StateData) -> + {reply, ok, StateName, + StateData#state{c2s_pid = undefined}}; +handle_sync_event(_Event, _From, StateName, State) -> + ?ERROR_MSG("unexpected sync event in '~s': ~p", + [StateName, _Event]), + {reply, {error, badarg}, StateName, State}. + +handle_info({timeout, TRef, wait_timeout}, StateName, + #state{wait_timer = TRef} = State) -> + {next_state, StateName, drop_holding_receiver(State)}; +handle_info({timeout, TRef, inactive}, _StateName, + #state{inactivity_timer = TRef} = State) -> + {stop, normal, State}; +handle_info({timeout, TRef, shaper_timeout}, StateName, + State) -> + case queue:out(State#state.shaped_receivers) of + {{value, {TRef, From, Req}}, Q} -> + (?GEN_FSM):send_event(self(), {Req, From}), + {next_state, StateName, + State#state{shaped_receivers = Q}}; + {{value, _}, _} -> + ?ERROR_MSG("shaper_timeout mismatch:~n** TRef: ~p~n** " + "State: ~p", + [TRef, State]), + {stop, normal, State}; + _ -> {next_state, StateName, State} + end; +handle_info({migrate, Node}, StateName, State) -> + if Node /= node() -> + NewState = bounce_receivers(State, migrated), + {migrate, NewState, + {Node, ?MODULE, start, [StateName, NewState]}, 0}; + true -> {next_state, StateName, State} + end; +handle_info(_Info, StateName, State) -> + ?ERROR_MSG("unexpected info:~n** Msg: ~p~n** StateName: ~p", + [_Info, StateName]), + {next_state, StateName, State}. + +terminate({migrated, ClonePid}, _StateName, State) -> + ?INFO_MSG("Migrating session \"~s\" (c2s_pid = " + "~p) to ~p on node ~p", + [State#state.sid, State#state.c2s_pid, ClonePid, + node(ClonePid)]), + mod_bosh:close_session(State#state.sid); +terminate(_Reason, _StateName, State) -> + mod_bosh:close_session(State#state.sid), + case State#state.c2s_pid of + C2SPid when is_pid(C2SPid) -> + (?GEN_FSM):send_event(C2SPid, closed); + _ -> ok + end, + bounce_receivers(State, closed), + bounce_els_from_obuf(State). + +code_change(_OldVsn, StateName, State, _Extra) -> + {ok, StateName, State}. + +print_state(State) -> State. + +route_els(#state{el_ibuf = Buf} = State) -> + route_els(State#state{el_ibuf = buf_new()}, + buf_to_list(Buf)). + +route_els(State, Els) -> + case State#state.c2s_pid of + C2SPid when is_pid(C2SPid) -> + lists:foreach(fun (El) -> + (?GEN_FSM):send_event(C2SPid, El) + end, + Els), + State; + _ -> + InBuf = buf_in(Els, State#state.el_ibuf), + State#state{el_ibuf = InBuf} + end. + +get_response_els(#state{el_obuf = OutBuf, + max_concat = MaxConcat} = + State) -> + {Els, NewOutBuf} = buf_out(OutBuf, MaxConcat), + {State#state{el_obuf = NewOutBuf}, Els}. + +reply(State, Body, RID, From) -> + State1 = restart_inactivity_timer(State), + Receivers = gb_trees:delete_any(RID, + State1#state.receivers), + State2 = do_reply(State1, From, Body, RID), + case catch gb_trees:take_smallest(Receivers) of + {NextRID, {From1, Req}, Receivers1} + when NextRID == RID + 1 -> + (?GEN_FSM):send_event(self(), {Req, From1}), + State2#state{receivers = Receivers1}; + _ -> State2#state{receivers = Receivers} + end. + +reply_next_state(State, Body, RID, From) -> + State1 = restart_inactivity_timer(State), + Receivers = gb_trees:delete_any(RID, + State1#state.receivers), + State2 = do_reply(State1, From, Body, RID), + case catch gb_trees:take_smallest(Receivers) of + {NextRID, {From1, Req}, Receivers1} + when NextRID == RID + 1 -> + active(Req, From1, + State2#state{receivers = Receivers1}); + _ -> + {next_state, active, + State2#state{receivers = Receivers}} + end. + +reply_stop(State, Body, From, RID) -> + {stop, normal, do_reply(State, From, Body, RID)}. + +drop_holding_receiver(State) -> + RID = State#state.prev_rid, + case gb_trees:lookup(RID, State#state.receivers) of + {value, {From, Body}} -> + State1 = restart_inactivity_timer(State), + Receivers = gb_trees:delete_any(RID, + State1#state.receivers), + State2 = State1#state{receivers = Receivers}, + do_reply(State2, From, Body, RID); + none -> State + end. + +do_reply(State, From, Body, RID) -> + ?DEBUG("send reply:~n** RequestID: ~p~n** Reply: " + "~p~n** To: ~p~n** State: ~p", + [RID, Body, From, State]), + (?GEN_FSM):reply(From, Body), + Responses = gb_trees:delete_any(RID, + State#state.responses), + Responses1 = case gb_trees:size(Responses) of + N when N < State#state.max_requests; N == 0 -> + Responses; + _ -> element(3, gb_trees:take_smallest(Responses)) + end, + Responses2 = gb_trees:insert(RID, Body, Responses1), + State#state{responses = Responses2}. + +bounce_receivers(State, Reason) -> + Receivers = gb_trees:to_list(State#state.receivers), + ShapedReceivers = lists:map(fun ({_, From, + #body{attrs = Attrs} = Body}) -> + RID = get_attr(rid, Attrs), + {RID, {From, Body}} + end, + queue:to_list(State#state.shaped_receivers)), + lists:foldl(fun ({RID, {From, Body}}, AccState) -> + NewBody = if Reason == closed -> + #body{http_reason = + <<"Session closed">>, + attrs = + [{type, <<"terminate">>}, + {condition, + <<"other-request">>}]}; + Reason == migrated -> + Body#body{http_reason = + <<"Session migrated">>} + end, + do_reply(AccState, From, NewBody, RID) + end, + State, Receivers ++ ShapedReceivers). + +bounce_els_from_obuf(State) -> + lists:foreach(fun ({xmlstreamelement, El}) -> + case El of + #xmlel{name = Name, attrs = Attrs} + when Name == <<"presence">>; + Name == <<"message">>; + Name == <<"iq">> -> + FromS = fxml:get_attr_s(<<"from">>, Attrs), + ToS = fxml:get_attr_s(<<"to">>, Attrs), + case {jid:from_string(FromS), + jid:from_string(ToS)} + of + {#jid{} = From, #jid{} = To} -> + ejabberd_router:route(From, To, El); + _ -> ok + end; + _ -> ok + end; + (_) -> ok + end, + buf_to_list(State#state.el_obuf)). + +is_valid_key(<<"">>, <<"">>) -> true; +is_valid_key(PrevKey, Key) -> + p1_sha:sha(Key) == PrevKey. + +is_overactivity(undefined) -> false; +is_overactivity(PrevPoll) -> + PollPeriod = timer:now_diff(p1_time_compat:timestamp(), PrevPoll) div + 1000000, + if PollPeriod < (?DEFAULT_POLLING) -> true; + true -> false + end. + +make_xmlstreamstart(XMPPDomain, Version) -> + VersionEl = case Version of + <<"">> -> []; + _ -> [{<<"version">>, Version}] + end, + {xmlstreamstart, <<"stream:stream">>, + [{<<"to">>, XMPPDomain}, {<<"xmlns">>, ?NS_CLIENT}, + {<<"xmlns:xmpp">>, ?NS_BOSH}, + {<<"xmlns:stream">>, ?NS_STREAM} + | VersionEl]}. + +maybe_add_xmlstreamend(Els, <<"terminate">>) -> + Els ++ [{xmlstreamend, <<"stream:stream">>}]; +maybe_add_xmlstreamend(Els, _) -> Els. + +encode_body(#body{attrs = Attrs, els = Els}, Type) -> + Attrs1 = lists:map(fun ({K, V}) when is_atom(K) -> + AmK = iolist_to_binary(atom_to_list(K)), + case V of + true -> {AmK, <<"true">>}; + false -> {AmK, <<"false">>}; + I when is_integer(I), I >= 0 -> + {AmK, integer_to_binary(I)}; + _ -> {AmK, V} + end; + ({K, V}) -> {K, V} + end, + Attrs), + Attrs2 = [{<<"xmlns">>, ?NS_HTTP_BIND} | Attrs1], + {Attrs3, XMLs} = lists:foldr(fun ({xmlstreamraw, XML}, + {AttrsAcc, XMLBuf}) -> + {AttrsAcc, [XML | XMLBuf]}; + ({xmlstreamelement, + #xmlel{name = <<"stream:error">>} = El}, + {AttrsAcc, XMLBuf}) -> + {[{<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"remote-stream-error">>}, + {<<"xmlns:stream">>, ?NS_STREAM} + | AttrsAcc], + [encode_element(El, Type) | XMLBuf]}; + ({xmlstreamelement, + #xmlel{name = <<"stream:features">>} = + El}, + {AttrsAcc, XMLBuf}) -> + {lists:keystore(<<"xmlns:stream">>, 1, + AttrsAcc, + {<<"xmlns:stream">>, + ?NS_STREAM}), + [encode_element(El, Type) | XMLBuf]}; + ({xmlstreamelement, + #xmlel{name = Name, attrs = EAttrs} = El}, + {AttrsAcc, XMLBuf}) + when Name == <<"message">>; + Name == <<"presence">>; + Name == <<"iq">> -> + NewAttrs = lists:keystore( + <<"xmlns">>, 1, EAttrs, + {<<"xmlns">>, ?NS_CLIENT}), + NewEl = El#xmlel{attrs = NewAttrs}, + {AttrsAcc, + [encode_element(NewEl, Type) | XMLBuf]}; + ({xmlstreamelement, El}, + {AttrsAcc, XMLBuf}) -> + {AttrsAcc, + [encode_element(El, Type) | XMLBuf]}; + ({xmlstreamend, _}, {AttrsAcc, XMLBuf}) -> + {[{<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"remote-stream-error">>} + | AttrsAcc], + XMLBuf}; + ({xmlstreamstart, <<"stream:stream">>, + SAttrs}, + {AttrsAcc, XMLBuf}) -> + StreamID = fxml:get_attr_s(<<"id">>, + SAttrs), + NewAttrs = case + fxml:get_attr_s(<<"version">>, + SAttrs) + of + <<"">> -> + [{<<"authid">>, + StreamID} + | AttrsAcc]; + V -> + lists:keystore(<<"xmlns:xmpp">>, + 1, + [{<<"xmpp:version">>, + V}, + {<<"authid">>, + StreamID} + | AttrsAcc], + {<<"xmlns:xmpp">>, + ?NS_BOSH}) + end, + {NewAttrs, XMLBuf}; + ({xmlstreamerror, _}, + {AttrsAcc, XMLBuf}) -> + {[{<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"remote-stream-error">>} + | AttrsAcc], + XMLBuf}; + (_, Acc) -> Acc + end, + {Attrs2, []}, Els), + case XMLs of + [] when Type == xml -> + [<<">, attrs_to_list(Attrs3), <<"/>">>]; + _ when Type == xml -> + [<<">, attrs_to_list(Attrs3), $>, XMLs, + <<"">>] + end. + +encode_element(El, xml) -> + fxml:element_to_binary(El); +encode_element(El, json) -> + El. + +decode_body(Data, Size, Type) -> + case decode(Data, Type) of + #xmlel{name = <<"body">>, attrs = Attrs, + children = Els} -> + case attrs_to_body_attrs(Attrs) of + {error, _} = Err -> Err; + BodyAttrs -> + case get_attr(rid, BodyAttrs) of + <<"">> -> {error, <<"Missing \"rid\" attribute">>}; + _ -> + Els1 = lists:flatmap(fun (#xmlel{} = El) -> + [{xmlstreamelement, El}]; + (_) -> [] + end, + Els), + {ok, #body{attrs = BodyAttrs, size = Size, els = Els1}} + end + end; + #xmlel{} -> {error, <<"Unexpected payload">>}; + _ when Type == xml -> + {error, <<"XML is not well-formed">>}; + _ when Type == json -> + {error, <<"JSON is not well-formed">>} + end. + +decode(Data, xml) -> + fxml_stream:parse_element(Data). + +attrs_to_body_attrs(Attrs) -> + lists:foldl(fun (_, {error, Reason}) -> {error, Reason}; + ({Attr, Val}, Acc) -> + try case Attr of + <<"ver">> -> [{ver, Val} | Acc]; + <<"xmpp:version">> -> + [{'xmpp:version', Val} | Acc]; + <<"type">> -> [{type, Val} | Acc]; + <<"key">> -> [{key, Val} | Acc]; + <<"newkey">> -> [{newkey, Val} | Acc]; + <<"xmlns">> -> Val = (?NS_HTTP_BIND), Acc; + <<"secure">> -> [{secure, to_bool(Val)} | Acc]; + <<"xmpp:restart">> -> + [{'xmpp:restart', to_bool(Val)} | Acc]; + <<"to">> -> + [{to, jid:nameprep(Val)} | Acc]; + <<"wait">> -> [{wait, to_int(Val, 0)} | Acc]; + <<"ack">> -> [{ack, to_int(Val, 0)} | Acc]; + <<"sid">> -> [{sid, Val} | Acc]; + <<"hold">> -> [{hold, to_int(Val, 0)} | Acc]; + <<"rid">> -> [{rid, to_int(Val, 0)} | Acc]; + <<"pause">> -> [{pause, to_int(Val, 0)} | Acc]; + _ -> [{Attr, Val} | Acc] + end + catch + _:_ -> + {error, + <<"Invalid \"", Attr/binary, "\" attribute">>} + end + end, + [], Attrs). + +to_int(S, Min) -> + case binary_to_integer(S) of + I when I >= Min -> I; + _ -> erlang:error(badarg) + end. + +to_bool(<<"true">>) -> true; +to_bool(<<"1">>) -> true; +to_bool(<<"false">>) -> false; +to_bool(<<"0">>) -> false. + +attrs_to_list(Attrs) -> [attr_to_list(A) || A <- Attrs]. + +attr_to_list({Name, Value}) -> + [$\s, Name, $=, $', fxml:crypt(Value), $']. + +bosh_response(Body, Type) -> + CType = case Type of + xml -> ?CT_XML; + json -> ?CT_JSON + end, + {200, Body#body.http_reason, ?HEADER(CType), + encode_body(Body, Type)}. + +bosh_response_with_msg(Body, Type, RcvBody) -> + ?DEBUG("send error reply:~p~n** Receiced body: ~p", + [Body, RcvBody]), + bosh_response(Body, Type). + +http_error(Status, Reason, Type) -> + CType = case Type of + xml -> ?CT_XML; + json -> ?CT_JSON + end, + {Status, Reason, ?HEADER(CType), <<"">>}. + +make_sid() -> p1_sha:sha(randoms:get_string()). + +-compile({no_auto_import, [{min, 2}]}). + +min(undefined, B) -> B; +min(A, B) -> erlang:min(A, B). + +check_bosh_module(XmppDomain) -> + case gen_mod:is_loaded(XmppDomain, mod_bosh) of + true -> ok; + false -> + ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) " + "in host ~p, but the module mod_bosh " + "is not started in that host. Configure " + "your BOSH client to connect to the correct " + "host, or add your desired host to the " + "configuration, or check your 'modules' " + "section in your ejabberd configuration " + "file.", + [XmppDomain]) + end. + +get_attr(Attr, Attrs) -> get_attr(Attr, Attrs, <<"">>). + +get_attr(Attr, Attrs, Default) -> + case lists:keysearch(Attr, 1, Attrs) of + {value, {_, Val}} -> Val; + _ -> Default + end. + +buf_new() -> queue:new(). + +buf_in(Xs, Buf) -> + lists:foldl(fun (X, Acc) -> queue:in(X, Acc) end, Buf, + Xs). + +buf_out(Buf, Num) when is_integer(Num), Num > 0 -> + buf_out(Buf, Num, []); +buf_out(Buf, _) -> {queue:to_list(Buf), buf_new()}. + +buf_out(Buf, 0, Els) -> {lists:reverse(Els), Buf}; +buf_out(Buf, I, Els) -> + case queue:out(Buf) of + {{value, El}, NewBuf} -> + buf_out(NewBuf, I - 1, [El | Els]); + {empty, _} -> buf_out(Buf, 0, Els) + end. + +buf_to_list(Buf) -> queue:to_list(Buf). + +cancel_timer(TRef) when is_reference(TRef) -> + (?GEN_FSM):cancel_timer(TRef); +cancel_timer(_) -> false. + +restart_timer(TRef, Timeout, Msg) -> + cancel_timer(TRef), + erlang:start_timer(timer:seconds(Timeout), self(), Msg). + +restart_inactivity_timer(#state{inactivity_timeout = + Timeout} = + State) -> + restart_inactivity_timer(State, Timeout). + +restart_inactivity_timer(#state{inactivity_timer = + TRef} = + State, + Timeout) -> + NewTRef = restart_timer(TRef, Timeout, inactive), + State#state{inactivity_timer = NewTRef}. + +stop_inactivity_timer(#state{inactivity_timer = TRef} = + State) -> + cancel_timer(TRef), + State#state{inactivity_timer = undefined}. + +restart_wait_timer(#state{wait_timer = TRef, + wait_timeout = Timeout} = + State) -> + NewTRef = restart_timer(TRef, Timeout, wait_timeout), + State#state{wait_timer = NewTRef}. + +stop_wait_timer(#state{wait_timer = TRef} = State) -> + cancel_timer(TRef), State#state{wait_timer = undefined}. + +start_shaper_timer(Timeout) -> + erlang:start_timer(Timeout, self(), shaper_timeout). + +make_random_jid(Host) -> + User = randoms:get_string(), + jid:make(User, Host, randoms:get_string()). + +make_socket(Pid, IP) -> {http_bind, Pid, IP}. diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 6068c85ef..6d84d8d93 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -74,7 +74,8 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). +%%-include("legacy.hrl"). -include("mod_privacy.hrl"). @@ -127,6 +128,17 @@ ask_offline = true, lang = <<"">>}). +-type state_name() :: wait_for_stream | wait_for_auth | + wait_for_feature_request | wait_for_bind | + wait_for_sasl_response | wait_for_resume | + session_established. +-type state() :: #state{}. +-type fsm_stop() :: {stop, normal, state()}. +-type fsm_next() :: {next_state, state_name(), state(), non_neg_integer()}. +-type fsm_reply() :: {reply, any(), state_name(), state(), non_neg_integer()}. +-type fsm_transition() :: fsm_stop() | fsm_next(). +-export_type([state/0]). + %-define(DBGFSM, true). -ifdef(DBGFSM). @@ -153,55 +165,13 @@ -define(STREAM_TRAILER, <<"">>). --define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE). - --define(INVALID_XML_ERR, ?SERR_XML_NOT_WELL_FORMED). - --define(HOST_UNKNOWN_ERR, ?SERR_HOST_UNKNOWN). - --define(POLICY_VIOLATION_ERR(Lang, Text), - ?SERRT_POLICY_VIOLATION(Lang, Text)). - --define(INVALID_FROM, ?SERR_INVALID_FROM). - %% XEP-0198: --define(IS_STREAM_MGMT_TAG(Name), - (Name == <<"enable">>) or - (Name == <<"resume">>) or - (Name == <<"a">>) or - (Name == <<"r">>)). - --define(IS_SUPPORTED_MGMT_XMLNS(Xmlns), - (Xmlns == ?NS_STREAM_MGMT_2) or - (Xmlns == ?NS_STREAM_MGMT_3)). - --define(MGMT_FAILED(Condition, Attrs), - #xmlel{name = <<"failed">>, - attrs = Attrs, - children = [#xmlel{name = Condition, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], - children = []}]}). - --define(MGMT_BAD_REQUEST(Xmlns), - ?MGMT_FAILED(<<"bad-request">>, [{<<"xmlns">>, Xmlns}])). - --define(MGMT_SERVICE_UNAVAILABLE(Xmlns), - ?MGMT_FAILED(<<"service-unavailable">>, [{<<"xmlns">>, Xmlns}])). - --define(MGMT_UNEXPECTED_REQUEST(Xmlns), - ?MGMT_FAILED(<<"unexpected-request">>, [{<<"xmlns">>, Xmlns}])). - --define(MGMT_UNSUPPORTED_VERSION(Xmlns), - ?MGMT_FAILED(<<"unsupported-version">>, [{<<"xmlns">>, Xmlns}])). - --define(MGMT_ITEM_NOT_FOUND(Xmlns), - ?MGMT_FAILED(<<"item-not-found">>, [{<<"xmlns">>, Xmlns}])). - --define(MGMT_ITEM_NOT_FOUND_H(Xmlns, NumStanzasIn), - ?MGMT_FAILED(<<"item-not-found">>, - [{<<"xmlns">>, Xmlns}, - {<<"h">>, jlib:integer_to_binary(NumStanzasIn)}])). +-define(IS_STREAM_MGMT_PACKET(Pkt), + is_record(Pkt, sm_enable) or + is_record(Pkt, sm_resume) or + is_record(Pkt, sm_a) or + is_record(Pkt, sm_r)). %%%---------------------------------------------------------------------- %%% API @@ -226,21 +196,25 @@ get_last_presence(FsmRef) -> (?GEN_FSM):sync_send_all_state_event(FsmRef, {get_last_presence}, 1000). +-spec get_aux_field(any(), state()) -> {ok, any()} | error. get_aux_field(Key, #state{aux_fields = Opts}) -> - case lists:keysearch(Key, 1, Opts) of - {value, {_, Val}} -> {ok, Val}; - _ -> error + case lists:keyfind(Key, 1, Opts) of + {_, Val} -> {ok, Val}; + false -> error end. +-spec set_aux_field(any(), any(), state()) -> state(). set_aux_field(Key, Val, #state{aux_fields = Opts} = State) -> Opts1 = lists:keydelete(Key, 1, Opts), State#state{aux_fields = [{Key, Val} | Opts1]}. +-spec del_aux_field(any(), state()) -> state(). del_aux_field(Key, #state{aux_fields = Opts} = State) -> Opts1 = lists:keydelete(Key, 1, Opts), State#state{aux_fields = Opts1}. +-spec get_subscription(jid() | ljid(), state()) -> both | from | to | none. get_subscription(From = #jid{}, StateData) -> get_subscription(jid:tolower(From), StateData); get_subscription(LFrom, StateData) -> @@ -278,14 +252,19 @@ set_resume_timeout(#state{} = StateData, Timeout) -> set_resume_timeout(FsmRef, Timeout) -> FsmRef ! {set_resume_timeout, Timeout}. +-spec send_filtered(pid(), binary(), jid(), jid(), stanza()) -> any(). send_filtered(FsmRef, Feature, From, To, Packet) -> FsmRef ! {send_filtered, Feature, From, To, Packet}. +-spec broadcast(pid(), any(), jid(), stanza()) -> any(). broadcast(FsmRef, Type, From, Packet) -> FsmRef ! {broadcast, Type, From, Packet}. +-spec stop(pid()) -> any(). stop(FsmRef) -> (?GEN_FSM):send_event(FsmRef, stop). +-spec close(pid()) -> any(). +%% What is the difference between stop and close??? close(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed). %%%---------------------------------------------------------------------- @@ -376,47 +355,53 @@ init([{SockMod, Socket}, Opts]) -> mgmt_resend = ResendOnTimeout}, {ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}. +-spec get_subscribed(pid()) -> [ljid()]. %% Return list of all available resources of contacts, get_subscribed(FsmRef) -> (?GEN_FSM):sync_send_all_state_event(FsmRef, get_subscribed, 1000). -wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> - DefaultLang = ?MYLANG, - case fxml:get_attr_s(<<"xmlns:stream">>, Attrs) of - ?NS_STREAM -> - Server = - case StateData#state.server of - <<"">> -> - jid:nameprep(fxml:get_attr_s(<<"to">>, Attrs)); - S -> S - end, - Lang = case fxml:get_attr_s(<<"xml:lang">>, Attrs) of - Lang1 when byte_size(Lang1) =< 35 -> - %% As stated in BCP47, 4.4.1: - %% Protocols or specifications that - %% specify limited buffer sizes for - %% language tags MUST allow for - %% language tags of at least 35 characters. - Lang1; - _ -> - %% Do not store long language tag to - %% avoid possible DoS/flood attacks - <<"">> - end, - StreamVersion = case fxml:get_attr_s(<<"version">>, Attrs) of - <<"1.0">> -> - <<"1.0">>; - _ -> - <<"">> - end, +wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> + try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of + #stream_start{xmlns = NS_CLIENT, stream_xmlns = NS_STREAM, + version = Version, lang = Lang} + when NS_CLIENT /= ?NS_CLIENT; NS_STREAM /= ?NS_STREAM -> + send_header(StateData, ?MYNAME, Version, Lang), + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData}; + #stream_start{lang = Lang, version = Version} when byte_size(Lang) > 35 -> + %% As stated in BCP47, 4.4.1: + %% Protocols or specifications that specify limited buffer sizes for + %% language tags MUST allow for language tags of at least 35 characters. + %% Do not store long language tag to avoid possible DoS/flood attacks + send_header(StateData, ?MYNAME, Version, ?MYLANG), + Txt = <<"Too long value of 'xml:lang' attribute">>, + send_element(StateData, + xmpp:serr_policy_violation(Txt, ?MYLANG)), + {stop, normal, StateData}; + #stream_start{to = undefined, lang = Lang, version = Version} -> + Txt = <<"Missing 'to' attribute">>, + send_header(StateData, ?MYNAME, Version, Lang), + send_element(StateData, + xmpp:serr_improper_addressing(Txt, Lang)), + {stop, normal, StateData}; + #stream_start{to = #jid{lserver = To}, lang = Lang, + version = Version} -> + Server = case StateData#state.server of + <<"">> -> To; + S -> S + end, + StreamVersion = case Version of + {1,0} -> {1,0}; + _ -> undefined + end, IsBlacklistedIP = is_ip_blacklisted(StateData#state.ip, Lang), case lists:member(Server, ?MYHOSTS) of true when IsBlacklistedIP == false -> change_shaper(StateData, jid:make(<<"">>, Server, <<"">>)), case StreamVersion of - <<"1.0">> -> - send_header(StateData, Server, <<"1.0">>, DefaultLang), + {1,0} -> + send_header(StateData, Server, {1,0}, ?MYLANG), case StateData#state.authenticated of false -> TLS = StateData#state.tls, @@ -439,15 +424,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> Mechs = case TLSEnabled or not TLSRequired of true -> - Ms = lists:map(fun (S) -> - #xmlel{name = <<"mechanism">>, - attrs = [], - children = [{xmlcdata, S}]} - end, - cyrsasl:listmech(Server)), - [#xmlel{name = <<"mechanisms">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = Ms}]; + [#sasl_mechanisms{list = cyrsasl:listmech(Server)}]; false -> [] end, @@ -457,11 +434,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> CompressFeature = case Zlib andalso ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of true -> - [#xmlel{name = <<"compression">>, - attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}], - children = [#xmlel{name = <<"method">>, - attrs = [], - children = [{xmlcdata, <<"zlib">>}]}]}]; + [#compression{methods = [<<"zlib">>]}]; _ -> [] end, @@ -470,18 +443,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> (TLSEnabled == false) andalso (SockMod == gen_tcp) of true -> - case TLSRequired of - true -> - [#xmlel{name = <<"starttls">>, - attrs = [{<<"xmlns">>, ?NS_TLS}], - children = [#xmlel{name = <<"required">>, - attrs = [], - children = []}]}]; - _ -> - [#xmlel{name = <<"starttls">>, - attrs = [{<<"xmlns">>, ?NS_TLS}], - children = []}] - end; + [#starttls{required = TLSRequired}]; false -> [] end, @@ -489,9 +451,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features, Server, StreamFeatures1, [Server]), send_element(StateData, - #xmlel{name = <<"stream:features">>, - attrs = [], - children = StreamFeatures}), + #stream_features{sub_els = StreamFeatures}), fsm_next_state(wait_for_feature_request, StateData#state{server = Server, sasl_state = SASLState, @@ -506,12 +466,8 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> StreamManagementFeature = case stream_mgmt_enabled(StateData) of true -> - [#xmlel{name = <<"sm">>, - attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_2}], - children = []}, - #xmlel{name = <<"sm">>, - attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_3}], - children = []}]; + [#feature_sm{xmlns = ?NS_STREAM_MGMT_2}, + #feature_sm{xmlns = ?NS_STREAM_MGMT_3}]; false -> [] end, @@ -523,21 +479,12 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> case Zlib andalso ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of true -> - [#xmlel{name = <<"compression">>, - attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}], - children = [#xmlel{name = <<"method">>, - attrs = [], - children = [{xmlcdata, <<"zlib">>}]}]}]; + [#compression{methods = [<<"zlib">>]}]; _ -> [] end, - StreamFeatures1 = [#xmlel{name = <<"bind">>, - attrs = [{<<"xmlns">>, ?NS_BIND}], - children = []}, - #xmlel{name = <<"session">>, - attrs = [{<<"xmlns">>, ?NS_SESSION}], - children = - [#xmlel{name = <<"optional">>}]}] + StreamFeatures1 = + [#bind{}, #xmpp_session{optional = true}] ++ RosterVersioningFeature ++ StreamManagementFeature ++ @@ -547,27 +494,23 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features, Server, StreamFeatures1, [Server]), send_element(StateData, - #xmlel{name = <<"stream:features">>, - attrs = [], - children = StreamFeatures}), + #stream_features{sub_els = StreamFeatures}), fsm_next_state(wait_for_bind, StateData#state{server = Server, lang = Lang}); _ -> - send_element(StateData, - #xmlel{name = <<"stream:features">>, - attrs = [], - children = []}), + send_element(StateData, #stream_features{}), fsm_next_state(session_established, StateData#state{server = Server, lang = Lang}) end end; _ -> - send_header(StateData, Server, <<"">>, DefaultLang), + send_header(StateData, Server, StreamVersion, ?MYLANG), if not StateData#state.tls_enabled and StateData#state.tls_required -> - send_element(StateData, - ?POLICY_VIOLATION_ERR(Lang, - <<"Use of STARTTLS required">>)), + send_element( + StateData, + xmpp:serr_policy_violation( + <<"Use of STARTTLS required">>, Lang)), {stop, normal, StateData}; true -> fsm_next_state(wait_for_auth, @@ -580,184 +523,152 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> {true, LogReason, ReasonT} = IsBlacklistedIP, ?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s", [jlib:ip_to_list(IP), LogReason]), - send_header(StateData, Server, StreamVersion, DefaultLang), - send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)), + send_header(StateData, Server, StreamVersion, ?MYLANG), + send_element(StateData, xmpp:serr_policy_violation(ReasonT, Lang)), {stop, normal, StateData}; _ -> - send_header(StateData, ?MYNAME, StreamVersion, DefaultLang), - send_element(StateData, ?HOST_UNKNOWN_ERR), + send_header(StateData, ?MYNAME, StreamVersion, ?MYLANG), + send_element(StateData, xmpp:serr_host_unknown()), {stop, normal, StateData} end; _ -> - send_header(StateData, ?MYNAME, <<"">>, DefaultLang), - send_element(StateData, ?INVALID_NS_ERR), + send_header(StateData, ?MYNAME, {1,0}, ?MYLANG), + send_element(StateData, xmpp:serr_invalid_xml()), + {stop, normal, StateData} + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + send_header(StateData, ?MYNAME, {1,0}, ?MYLANG), + send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)), {stop, normal, StateData} end; wait_for_stream(timeout, StateData) -> {stop, normal, StateData}; wait_for_stream({xmlstreamelement, _}, StateData) -> - send_element(StateData, ?INVALID_XML_ERR), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_stream({xmlstreamend, _}, StateData) -> - send_element(StateData, ?INVALID_XML_ERR), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_stream({xmlstreamerror, _}, StateData) -> - send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>), - send_element(StateData, ?INVALID_XML_ERR), + send_header(StateData, ?MYNAME, {1,0}, <<"">>), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_stream(closed, StateData) -> {stop, normal, StateData}; wait_for_stream(stop, StateData) -> {stop, normal, StateData}. -wait_for_auth({xmlstreamelement, #xmlel{name = Name} = El}, StateData) - when ?IS_STREAM_MGMT_TAG(Name) -> - fsm_next_state(wait_for_auth, dispatch_stream_mgmt(El, StateData)); -wait_for_auth({xmlstreamelement, El}, StateData) -> - case is_auth_packet(El) of - {auth, _ID, get, {U, _, _, _}} -> - #xmlel{name = Name, attrs = Attrs} = jlib:make_result_iq_reply(El), - case U of - <<"">> -> UCdata = []; - _ -> UCdata = [{xmlcdata, U}] - end, - Res = case - ejabberd_auth:plain_password_required(StateData#state.server) - of - false -> - #xmlel{name = Name, attrs = Attrs, - children = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_AUTH}], - children = - [#xmlel{name = <<"username">>, - attrs = [], - children = UCdata}, - #xmlel{name = <<"password">>, - attrs = [], children = []}, - #xmlel{name = <<"digest">>, - attrs = [], children = []}, - #xmlel{name = <<"resource">>, - attrs = [], - children = []}]}]}; - true -> - #xmlel{name = Name, attrs = Attrs, - children = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_AUTH}], - children = - [#xmlel{name = <<"username">>, - attrs = [], - children = UCdata}, - #xmlel{name = <<"password">>, - attrs = [], children = []}, - #xmlel{name = <<"resource">>, - attrs = [], - children = []}]}]} - end, - send_element(StateData, Res), - fsm_next_state(wait_for_auth, StateData); - {auth, _ID, set, {_U, _P, _D, <<"">>}} -> - Lang = StateData#state.lang, - Txt = <<"No resource provided">>, - Err = jlib:make_error_reply(El, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)), +wait_for_auth({xmlstreamelement, #xmlel{} = El}, StateData) -> + decode_element(El, wait_for_auth, StateData); +wait_for_auth(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) -> + fsm_next_state(wait_for_auth, dispatch_stream_mgmt(Pkt, StateData)); +wait_for_auth(#iq{type = get, sub_els = [#legacy_auth{}]} = IQ, StateData) -> + Auth = #legacy_auth{username = <<>>, password = <<>>, resource = <<>>}, + Res = case ejabberd_auth:plain_password_required(StateData#state.server) of + false -> + xmpp:make_iq_result(IQ, Auth#legacy_auth{digest = <<>>}); + true -> + xmpp:make_iq_result(IQ, Auth) + end, + send_element(StateData, Res), + fsm_next_state(wait_for_auth, StateData); +wait_for_auth(#iq{type = set, sub_els = [#legacy_auth{resource = <<"">>}]} = IQ, + StateData) -> + Lang = StateData#state.lang, + Txt = <<"No resource provided">>, + Err = xmpp:make_error(IQ, xmpp:err_not_acceptable(Txt, Lang)), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData); +wait_for_auth(#iq{type = set, sub_els = [#legacy_auth{username = U, + password = P0, + digest = D0, + resource = R}]} = IQ, + StateData) when is_binary(U), is_binary(R) -> + JID = jid:make(U, StateData#state.server, R), + case (JID /= error) andalso + acl:access_matches(StateData#state.access, + #{usr => jid:split(JID), ip => StateData#state.ip}, + StateData#state.server) == allow of + true -> + DGen = fun (PW) -> + p1_sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>) + end, + P = if is_binary(P0) -> P0; true -> <<>> end, + D = if is_binary(D0) -> D0; true -> <<>> end, + case ejabberd_auth:check_password_with_authmodule( + U, U, StateData#state.server, P, D, DGen) of + {true, AuthModule} -> + ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p from ~s", + [StateData#state.socket, + jid:to_string(JID), AuthModule, + ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [true, U, StateData#state.server, + StateData#state.ip]), + Conn = get_conn_type(StateData), + Info = [{ip, StateData#state.ip}, {conn, Conn}, + {auth_module, AuthModule}], + Res = xmpp:make_iq_result(IQ), + send_element(StateData, Res), + ejabberd_sm:open_session(StateData#state.sid, U, + StateData#state.server, R, + Info), + change_shaper(StateData, JID), + {Fs, Ts} = ejabberd_hooks:run_fold( + roster_get_subscription_lists, + StateData#state.server, + {[], []}, + [U, StateData#state.server]), + LJID = jid:tolower(jid:remove_resource(JID)), + Fs1 = [LJID | Fs], + Ts1 = [LJID | Ts], + PrivList = ejabberd_hooks:run_fold(privacy_get_user_list, + StateData#state.server, + #userlist{}, + [U, StateData#state.server]), + NewStateData = StateData#state{ + user = U, + resource = R, + jid = JID, + conn = Conn, + auth_module = AuthModule, + pres_f = (?SETS):from_list(Fs1), + pres_t = (?SETS):from_list(Ts1), + privacy_list = PrivList}, + fsm_next_state(session_established, NewStateData); + _ -> + ?INFO_MSG("(~w) Failed legacy authentication for ~s from ~s", + [StateData#state.socket, + jid:to_string(JID), + ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [false, U, StateData#state.server, + StateData#state.ip]), + Lang = StateData#state.lang, + Txt = <<"Legacy authentication failed">>, + Err = xmpp:make_error(IQ, xmpp:err_not_authorized(Txt, Lang)), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData) + end; + false when JID == error -> + ?INFO_MSG("(~w) Forbidden legacy authentication " + "for username '~s' with resource '~s'", + [StateData#state.socket, U, R]), + Err = xmpp:make_error(IQ, xmpp:err_jid_malformed()), send_element(StateData, Err), fsm_next_state(wait_for_auth, StateData); - {auth, _ID, set, {U, P, D, R}} -> - JID = jid:make(U, StateData#state.server, R), - case JID /= error andalso - acl:access_matches(StateData#state.access, - #{usr => jid:split(JID), ip => StateData#state.ip}, - StateData#state.server) == allow - of - true -> - DGen = fun (PW) -> - p1_sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>) - end, - case ejabberd_auth:check_password_with_authmodule(U, U, - StateData#state.server, - P, D, DGen) - of - {true, AuthModule} -> - ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p from ~s", - [StateData#state.socket, - jid:to_string(JID), AuthModule, - ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), - ejabberd_hooks:run(c2s_auth_result, StateData#state.server, - [true, U, StateData#state.server, - StateData#state.ip]), - Conn = get_conn_type(StateData), - Info = [{ip, StateData#state.ip}, {conn, Conn}, - {auth_module, AuthModule}], - Res = jlib:make_result_iq_reply( - El#xmlel{children = []}), - send_element(StateData, Res), - ejabberd_sm:open_session(StateData#state.sid, U, - StateData#state.server, R, - Info), - change_shaper(StateData, JID), - {Fs, Ts} = - ejabberd_hooks:run_fold(roster_get_subscription_lists, - StateData#state.server, - {[], []}, - [U, StateData#state.server]), - LJID = jid:tolower(jid:remove_resource(JID)), - Fs1 = [LJID | Fs], - Ts1 = [LJID | Ts], - PrivList = ejabberd_hooks:run_fold(privacy_get_user_list, - StateData#state.server, - #userlist{}, - [U, StateData#state.server]), - NewStateData = StateData#state{user = U, - resource = R, - jid = JID, - conn = Conn, - auth_module = AuthModule, - pres_f = (?SETS):from_list(Fs1), - pres_t = (?SETS):from_list(Ts1), - privacy_list = PrivList}, - fsm_next_state(session_established, NewStateData); - _ -> - ?INFO_MSG("(~w) Failed legacy authentication for ~s from ~s", - [StateData#state.socket, - jid:to_string(JID), - ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), - ejabberd_hooks:run(c2s_auth_result, StateData#state.server, - [false, U, StateData#state.server, - StateData#state.ip]), - Lang = StateData#state.lang, - Txt = <<"Legacy authentication failed">>, - Err = jlib:make_error_reply( - El, ?ERRT_NOT_AUTHORIZED(Lang, Txt)), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData) - end; - _ -> - if JID == error -> - ?INFO_MSG("(~w) Forbidden legacy authentication " - "for username '~s' with resource '~s'", - [StateData#state.socket, U, R]), - Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData); - true -> - ?INFO_MSG("(~w) Forbidden legacy authentication " - "for ~s from ~s", - [StateData#state.socket, - jid:to_string(JID), - ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), - ejabberd_hooks:run(c2s_auth_result, StateData#state.server, - [false, U, StateData#state.server, - StateData#state.ip]), - Lang = StateData#state.lang, - Txt = <<"Legacy authentication forbidden">>, - Err = jlib:make_error_reply(El, ?ERRT_NOT_ALLOWED(Lang, Txt)), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData) - end - end; - _ -> - process_unauthenticated_stanza(StateData, El), + false -> + ?INFO_MSG("(~w) Forbidden legacy authentication for ~s from ~s", + [StateData#state.socket, + jid:to_string(JID), + ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [false, U, StateData#state.server, + StateData#state.ip]), + Lang = StateData#state.lang, + Txt = <<"Legacy authentication forbidden">>, + Err = xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)), + send_element(StateData, Err), fsm_next_state(wait_for_auth, StateData) end; wait_for_auth(timeout, StateData) -> @@ -765,127 +676,97 @@ wait_for_auth(timeout, StateData) -> wait_for_auth({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; wait_for_auth({xmlstreamerror, _}, StateData) -> - send_element(StateData, ?INVALID_XML_ERR), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_auth(closed, StateData) -> {stop, normal, StateData}; wait_for_auth(stop, StateData) -> - {stop, normal, StateData}. + {stop, normal, StateData}; +wait_for_auth(Pkt, StateData) -> + process_unauthenticated_stanza(StateData, Pkt), + fsm_next_state(wait_for_auth, StateData). -wait_for_feature_request({xmlstreamelement, #xmlel{name = Name} = El}, - StateData) - when ?IS_STREAM_MGMT_TAG(Name) -> +wait_for_feature_request({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_feature_request, StateData); +wait_for_feature_request(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) -> fsm_next_state(wait_for_feature_request, - dispatch_stream_mgmt(El, StateData)); -wait_for_feature_request({xmlstreamelement, El}, - StateData) -> - #xmlel{name = Name, attrs = Attrs, children = Els} = El, + dispatch_stream_mgmt(Pkt, StateData)); +wait_for_feature_request(#sasl_auth{mechanism = Mech, + text = ClientIn}, + #state{tls_enabled = TLSEnabled, + tls_required = TLSRequired} = StateData) + when TLSEnabled or not TLSRequired -> + case cyrsasl:server_start(StateData#state.sasl_state, Mech, ClientIn) of + {ok, Props} -> + (StateData#state.sockmod):reset_stream(StateData#state.socket), + U = identity(Props), + AuthModule = proplists:get_value(auth_module, Props, undefined), + ?INFO_MSG("(~w) Accepted authentication for ~s by ~p from ~s", + [StateData#state.socket, U, AuthModule, + ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [true, U, StateData#state.server, + StateData#state.ip]), + send_element(StateData, #sasl_success{}), + fsm_next_state(wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + sasl_state = undefined, + user = U}); + {continue, ServerOut, NewSASLState} -> + send_element(StateData, #sasl_challenge{text = ServerOut}), + fsm_next_state(wait_for_sasl_response, + StateData#state{sasl_state = NewSASLState}); + {error, Error, Username} -> + ?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s", + [StateData#state.socket, + Username, StateData#state.server, + ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [false, Username, StateData#state.server, + StateData#state.ip]), + send_element(StateData, #sasl_failure{reason = Error}), + fsm_next_state(wait_for_feature_request, StateData); + {error, Error} -> + send_element(StateData, #sasl_failure{reason = Error}), + fsm_next_state(wait_for_feature_request, StateData) + end; +wait_for_feature_request(#starttls{}, + #state{tls = true, tls_enabled = false} = StateData) -> + case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of + gen_tcp -> + TLSOpts = 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, + Socket = StateData#state.socket, + BProceed = fxml:element_to_binary(xmpp:encode(#starttls_proceed{})), + TLSSocket = (StateData#state.sockmod):starttls(Socket, TLSOpts, BProceed), + fsm_next_state(wait_for_stream, + StateData#state{socket = TLSSocket, + streamid = new_id(), + tls_enabled = true}); + _ -> + Lang = StateData#state.lang, + Txt = <<"Unsupported TLS transport">>, + send_element(StateData, xmpp:serr_policy_violation(Txt, Lang)), + {stop, normal, StateData} + end; +wait_for_feature_request(#compress{} = Comp, StateData) -> Zlib = StateData#state.zlib, - TLS = StateData#state.tls, - TLSEnabled = StateData#state.tls_enabled, - TLSRequired = StateData#state.tls_required, - SockMod = - (StateData#state.sockmod):get_sockmod(StateData#state.socket), - case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of - {?NS_SASL, <<"auth">>} - when TLSEnabled or not TLSRequired -> - Mech = fxml:get_attr_s(<<"mechanism">>, Attrs), - ClientIn = jlib:decode_base64(fxml:get_cdata(Els)), - case cyrsasl:server_start(StateData#state.sasl_state, - Mech, ClientIn) - of - {ok, Props} -> - (StateData#state.sockmod):reset_stream(StateData#state.socket), - U = identity(Props), - AuthModule = proplists:get_value(auth_module, Props, undefined), - ?INFO_MSG("(~w) Accepted authentication for ~s " - "by ~p from ~s", - [StateData#state.socket, U, AuthModule, - ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), - ejabberd_hooks:run(c2s_auth_result, StateData#state.server, - [true, U, StateData#state.server, - StateData#state.ip]), - send_element(StateData, - #xmlel{name = <<"success">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = []}), - fsm_next_state(wait_for_stream, - StateData#state{streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - sasl_state = undefined, - user = U}); - {continue, ServerOut, NewSASLState} -> - send_element(StateData, - #xmlel{name = <<"challenge">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = - [{xmlcdata, - jlib:encode_base64(ServerOut)}]}), - fsm_next_state(wait_for_sasl_response, - StateData#state{sasl_state = NewSASLState}); - {error, Error, Username} -> - ?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s", - [StateData#state.socket, - Username, StateData#state.server, - ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), - ejabberd_hooks:run(c2s_auth_result, StateData#state.server, - [false, Username, StateData#state.server, - StateData#state.ip]), - send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = - [#xmlel{name = Error, attrs = [], - children = []}]}), - fsm_next_state(wait_for_feature_request, StateData); - {error, Error} -> - send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = - [#xmlel{name = Error, attrs = [], - children = []}]}), - fsm_next_state(wait_for_feature_request, StateData) - end; - {?NS_TLS, <<"starttls">>} - when TLS == true, TLSEnabled == false, - SockMod == gen_tcp -> - TLSOpts = 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, - Socket = StateData#state.socket, - BProceed = fxml:element_to_binary(#xmlel{name = <<"proceed">>, - attrs = [{<<"xmlns">>, ?NS_TLS}]}), - TLSSocket = (StateData#state.sockmod):starttls(Socket, - TLSOpts, - BProceed), - fsm_next_state(wait_for_stream, - StateData#state{socket = TLSSocket, - streamid = new_id(), - tls_enabled = true}); - {?NS_COMPRESS, <<"compress">>} - when Zlib == true, - (SockMod == gen_tcp) or (SockMod == fast_tls) -> - process_compression_request(El, wait_for_feature_request, StateData); - _ -> - if TLSRequired and not TLSEnabled -> - Lang = StateData#state.lang, - send_element(StateData, - ?POLICY_VIOLATION_ERR(Lang, - <<"Use of STARTTLS required">>)), - {stop, normal, StateData}; - true -> - process_unauthenticated_stanza(StateData, El), - fsm_next_state(wait_for_feature_request, StateData) - end + SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket), + if Zlib == true, (SockMod == gen_tcp) or (SockMod == fast_tls) -> + process_compression_request(Comp, wait_for_feature_request, StateData); + true -> + send_element(StateData, #compress_failure{reason = 'setup-failed'}), + fsm_next_state(wait_for_feature_request, StateData) end; wait_for_feature_request(timeout, StateData) -> {stop, normal, StateData}; @@ -894,106 +775,82 @@ wait_for_feature_request({xmlstreamend, _Name}, {stop, normal, StateData}; wait_for_feature_request({xmlstreamerror, _}, StateData) -> - send_element(StateData, ?INVALID_XML_ERR), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_feature_request(closed, StateData) -> {stop, normal, StateData}; wait_for_feature_request(stop, 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 -> + Lang = StateData#state.lang, + Txt = <<"Use of STARTTLS required">>, + send_element(StateData, xmpp:serr_policy_violation(Txt, Lang)), + {stop, normal, StateData}; +wait_for_feature_request(Pkt, StateData) -> + process_unauthenticated_stanza(StateData, Pkt), + fsm_next_state(wait_for_feature_request, StateData). -wait_for_sasl_response({xmlstreamelement, #xmlel{name = Name} = El}, StateData) - when ?IS_STREAM_MGMT_TAG(Name) -> - fsm_next_state(wait_for_sasl_response, dispatch_stream_mgmt(El, StateData)); -wait_for_sasl_response({xmlstreamelement, El}, - StateData) -> - #xmlel{name = Name, attrs = Attrs, children = Els} = El, - case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of - {?NS_SASL, <<"response">>} -> - ClientIn = jlib:decode_base64(fxml:get_cdata(Els)), - case cyrsasl:server_step(StateData#state.sasl_state, - ClientIn) - of - {ok, Props} -> - catch - (StateData#state.sockmod):reset_stream(StateData#state.socket), - U = identity(Props), - AuthModule = proplists:get_value(auth_module, Props, <<>>), - ?INFO_MSG("(~w) Accepted authentication for ~s " - "by ~p from ~s", - [StateData#state.socket, U, AuthModule, - ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), - ejabberd_hooks:run(c2s_auth_result, StateData#state.server, - [true, U, StateData#state.server, - StateData#state.ip]), - send_element(StateData, - #xmlel{name = <<"success">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = []}), - fsm_next_state(wait_for_stream, - StateData#state{streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - sasl_state = undefined, - user = U}); - {ok, Props, ServerOut} -> - (StateData#state.sockmod):reset_stream(StateData#state.socket), - U = identity(Props), - AuthModule = proplists:get_value(auth_module, Props, undefined), - ?INFO_MSG("(~w) Accepted authentication for ~s " - "by ~p from ~s", - [StateData#state.socket, U, AuthModule, - ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), - ejabberd_hooks:run(c2s_auth_result, StateData#state.server, - [true, U, StateData#state.server, - StateData#state.ip]), - send_element(StateData, - #xmlel{name = <<"success">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = - [{xmlcdata, - jlib:encode_base64(ServerOut)}]}), - fsm_next_state(wait_for_stream, - StateData#state{streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - sasl_state = undefined, - user = U}); - {continue, ServerOut, NewSASLState} -> - send_element(StateData, - #xmlel{name = <<"challenge">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = - [{xmlcdata, - jlib:encode_base64(ServerOut)}]}), - fsm_next_state(wait_for_sasl_response, - StateData#state{sasl_state = NewSASLState}); - {error, Error, Username} -> - ?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s", - [StateData#state.socket, - Username, StateData#state.server, - ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), - ejabberd_hooks:run(c2s_auth_result, StateData#state.server, - [false, Username, StateData#state.server, - StateData#state.ip]), - send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = - [#xmlel{name = Error, attrs = [], - children = []}]}), - fsm_next_state(wait_for_feature_request, StateData); - {error, Error} -> - send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_SASL}], - children = - [#xmlel{name = Error, attrs = [], - children = []}]}), - fsm_next_state(wait_for_feature_request, StateData) - end; - _ -> - process_unauthenticated_stanza(StateData, El), - fsm_next_state(wait_for_feature_request, StateData) +wait_for_sasl_response({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_sasl_response, StateData); +wait_for_sasl_response(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) -> + fsm_next_state(wait_for_sasl_response, + dispatch_stream_mgmt(Pkt, StateData)); +wait_for_sasl_response(#sasl_response{text = ClientIn}, StateData) -> + case cyrsasl:server_step(StateData#state.sasl_state, ClientIn) of + {ok, Props} -> + catch (StateData#state.sockmod):reset_stream(StateData#state.socket), + U = identity(Props), + AuthModule = proplists:get_value(auth_module, Props, <<>>), + ?INFO_MSG("(~w) Accepted authentication for ~s by ~p from ~s", + [StateData#state.socket, U, AuthModule, + ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [true, U, StateData#state.server, + StateData#state.ip]), + send_element(StateData, #sasl_success{}), + fsm_next_state(wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + sasl_state = undefined, + user = U}); + {ok, Props, ServerOut} -> + (StateData#state.sockmod):reset_stream(StateData#state.socket), + U = identity(Props), + AuthModule = proplists:get_value(auth_module, Props, undefined), + ?INFO_MSG("(~w) Accepted authentication for ~s by ~p from ~s", + [StateData#state.socket, U, AuthModule, + ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [true, U, StateData#state.server, + StateData#state.ip]), + send_element(StateData, #sasl_success{text = ServerOut}), + fsm_next_state(wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + sasl_state = undefined, + user = U}); + {continue, ServerOut, NewSASLState} -> + send_element(StateData, #sasl_challenge{text = ServerOut}), + fsm_next_state(wait_for_sasl_response, + StateData#state{sasl_state = NewSASLState}); + {error, Error, Username} -> + ?INFO_MSG("(~w) Failed authentication for ~s@~s from ~s", + [StateData#state.socket, + Username, StateData#state.server, + ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]), + ejabberd_hooks:run(c2s_auth_result, StateData#state.server, + [false, Username, StateData#state.server, + StateData#state.ip]), + send_element(StateData, #sasl_failure{reason = Error}), + fsm_next_state(wait_for_feature_request, StateData); + {error, Error} -> + send_element(StateData, #sasl_failure{reason = Error}), + fsm_next_state(wait_for_feature_request, StateData) end; wait_for_sasl_response(timeout, StateData) -> {stop, normal, StateData}; @@ -1002,13 +859,18 @@ wait_for_sasl_response({xmlstreamend, _Name}, {stop, normal, StateData}; wait_for_sasl_response({xmlstreamerror, _}, StateData) -> - send_element(StateData, ?INVALID_XML_ERR), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_sasl_response(closed, StateData) -> {stop, normal, StateData}; wait_for_sasl_response(stop, StateData) -> - {stop, normal, StateData}. + {stop, normal, StateData}; +wait_for_sasl_response(Pkt, StateData) -> + process_unauthenticated_stanza(StateData, Pkt), + fsm_next_state(wait_for_feature_request, StateData). +-spec resource_conflict_action(binary(), binary(), binary()) -> + {accept_resource, binary()} | closenew. resource_conflict_action(U, S, R) -> OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of true -> @@ -1038,108 +900,115 @@ resource_conflict_action(U, S, R) -> {accept_resource, Rnew} end. -wait_for_bind({xmlstreamelement, #xmlel{name = Name, attrs = Attrs} = El}, - StateData) - when ?IS_STREAM_MGMT_TAG(Name) -> - case Name of - <<"resume">> -> - case handle_resume(StateData, Attrs) of - {ok, ResumedState} -> - fsm_next_state(session_established, ResumedState); - error -> - fsm_next_state(wait_for_bind, StateData) - end; - _ -> - fsm_next_state(wait_for_bind, dispatch_stream_mgmt(El, StateData)) - end; -wait_for_bind({xmlstreamelement, El}, StateData) -> - case jlib:iq_query_info(El) of - #iq{type = set, lang = Lang, xmlns = ?NS_BIND, sub_el = SubEl} = - IQ -> - U = StateData#state.user, - R1 = fxml:get_path_s(SubEl, - [{elem, <<"resource">>}, cdata]), - R = case jid:resourceprep(R1) of - error -> error; - <<"">> -> new_uniq_id(); - Resource -> Resource - end, - case R of - error -> - Txt = <<"Malformed resource">>, - Err = jlib:make_error_reply(El, ?ERRT_BAD_REQUEST(Lang, Txt)), - send_element(StateData, Err), - fsm_next_state(wait_for_bind, StateData); - _ -> - case resource_conflict_action(U, StateData#state.server, - R) - of - closenew -> - Err = jlib:make_error_reply(El, - ?STANZA_ERROR(<<"409">>, - <<"modify">>, - <<"conflict">>)), +-spec decode_element(xmlel(), state_name(), state()) -> fsm_transition(). +decode_element(#xmlel{} = El, StateName, StateData) -> + try case xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of + #iq{sub_els = [_], type = T} = Pkt when T == set; T == get -> + NewPkt = xmpp:decode_els( + Pkt, ?NS_CLIENT, + fun(SubEl) when StateName == session_established -> + case xmpp:get_ns(SubEl) of + ?NS_PRIVACY -> true; + ?NS_BLOCKING -> true; + _ -> false + end; + (SubEl) -> + xmpp:is_known_tag(SubEl) + end), + ?MODULE:StateName(NewPkt, StateData); + Pkt -> + ?MODULE:StateName(Pkt, StateData) + end + catch error:{xmpp_codec, Why} -> + NS = xmpp:get_ns(El), + fsm_next_state( + StateName, + 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 when NS == ?NS_STREAM_MGMT_2; NS == ?NS_STREAM_MGMT_3 -> + Err = #sm_failed{reason = 'bad-request', xmlns = NS}, send_element(StateData, Err), - fsm_next_state(wait_for_bind, StateData); - {accept_resource, R2} -> - JID = jid:make(U, StateData#state.server, R2), - StateData2 = - StateData#state{resource = R2, jid = JID}, - case open_session(StateData2) of - {ok, StateData3} -> - Res = - IQ#iq{ - type = result, - sub_el = - [#xmlel{name = <<"bind">>, - attrs = [{<<"xmlns">>, ?NS_BIND}], - children = - [#xmlel{name = <<"jid">>, - attrs = [], - children = - [{xmlcdata, - jid:to_string(JID)}]}]}]}, - try - send_element(StateData3, jlib:iq_to_xml(Res)) - catch exit:normal -> - close(self()) - end, - fsm_next_state_pack( - session_established, - StateData3); - {error, Error} -> - Err = jlib:make_error_reply(El, Error), - send_element(StateData, Err), - fsm_next_state(wait_for_bind, StateData) - end - end - end; - _ -> - #xmlel{name = Name, attrs = Attrs, children = _Els} = El, - Zlib = StateData#state.zlib, - SockMod = - (StateData#state.sockmod):get_sockmod(StateData#state.socket), - case {fxml:get_attr_s(<<"xmlns">>, Attrs), Name} of - {?NS_COMPRESS, <<"compress">>} - when Zlib == true, - (SockMod == gen_tcp) or (SockMod == fast_tls) -> - process_compression_request(El, wait_for_bind, StateData); - _ -> + StateData; + false -> + StateData + end) + end. + +wait_for_bind({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_bind, StateData); +wait_for_bind(#sm_resume{} = Pkt, StateData) -> + case handle_resume(StateData, Pkt) of + {ok, ResumedState} -> + fsm_next_state(session_established, ResumedState); + error -> + fsm_next_state(wait_for_bind, StateData) + end; +wait_for_bind(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) -> + fsm_next_state(wait_for_bind, dispatch_stream_mgmt(Pkt, StateData)); +wait_for_bind(#iq{type = set, + sub_els = [#bind{resource = R0}]} = IQ, StateData) -> + U = StateData#state.user, + R = case R0 of + <<>> -> new_uniq_id(); + _ -> R0 + end, + case resource_conflict_action(U, StateData#state.server, R) of + closenew -> + Err = xmpp:make_error(IQ, xmpp:err_conflict()), + send_element(StateData, Err), + fsm_next_state(wait_for_bind, StateData); + {accept_resource, R2} -> + JID = jid:make(U, StateData#state.server, R2), + StateData2 = StateData#state{resource = R2, jid = JID}, + case open_session(StateData2) of + {ok, StateData3} -> + Res = xmpp:make_iq_result(IQ, #bind{jid = JID}), + try + send_element(StateData3, Res) + catch + exit:normal -> close(self()) + end, + fsm_next_state_pack(session_established,StateData3); + {error, Error} -> + Err = xmpp:make_error(IQ, Error), + send_element(StateData, Err), fsm_next_state(wait_for_bind, StateData) end end; +wait_for_bind(#compress{} = Comp, StateData) -> + Zlib = StateData#state.zlib, + SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket), + if Zlib == true, (SockMod == gen_tcp) or (SockMod == fast_tls) -> + process_compression_request(Comp, wait_for_bind, StateData); + true -> + send_element(StateData, #compress_failure{reason = 'setup-failed'}), + fsm_next_state(wait_for_bind, StateData) + end; wait_for_bind(timeout, StateData) -> {stop, normal, StateData}; wait_for_bind({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; wait_for_bind({xmlstreamerror, _}, StateData) -> - send_element(StateData, ?INVALID_XML_ERR), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; wait_for_bind(closed, StateData) -> {stop, normal, StateData}; wait_for_bind(stop, StateData) -> - {stop, normal, StateData}. + {stop, normal, StateData}; +wait_for_bind(Pkt, StateData) -> + fsm_next_state( + wait_for_bind, + case xmpp:is_stanza(Pkt) of + true -> + send_error(StateData, Pkt, xmpp:err_not_acceptable()); + false -> + StateData + end). +-spec open_session(state()) -> {ok, state()} | {error, stanza_error()}. open_session(StateData) -> U = StateData#state.user, R = StateData#state.resource, @@ -1185,33 +1054,18 @@ open_session(StateData) -> ?INFO_MSG("(~w) Forbidden session for ~s", [StateData#state.socket, jid:to_string(JID)]), Txt = <<"Denied by ACL">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)} + {error, xmpp:err_not_allowed(Txt, Lang)} end. -session_established({xmlstreamelement, #xmlel{name = Name} = El}, StateData) - when ?IS_STREAM_MGMT_TAG(Name) -> - fsm_next_state(session_established, dispatch_stream_mgmt(El, StateData)); -session_established({xmlstreamelement, - #xmlel{name = <<"active">>, - attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}}, - StateData) -> +session_established({xmlstreamelement, El}, StateData) -> + decode_element(El, session_established, StateData); +session_established(Pkt, StateData) when ?IS_STREAM_MGMT_PACKET(Pkt) -> + fsm_next_state(session_established, dispatch_stream_mgmt(Pkt, StateData)); +session_established(#csi{type = active}, StateData) -> NewStateData = csi_flush_queue(StateData), fsm_next_state(session_established, NewStateData#state{csi_state = active}); -session_established({xmlstreamelement, - #xmlel{name = <<"inactive">>, - attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}}, - StateData) -> +session_established(#csi{type = inactive}, StateData) -> fsm_next_state(session_established, StateData#state{csi_state = inactive}); -session_established({xmlstreamelement, El}, - StateData) -> - FromJID = StateData#state.jid, - case check_from(El, FromJID) of - 'invalid-from' -> - send_element(StateData, ?INVALID_FROM), - {stop, normal, StateData}; - _NewEl -> - session_established2(El, StateData) - end; %% We hibernate the process to reduce memory consumption after a %% configurable activity timeout session_established(timeout, StateData) -> @@ -1225,10 +1079,10 @@ session_established({xmlstreamerror, <<"XML stanza is too big">> = E}, StateData) -> send_element(StateData, - ?POLICY_VIOLATION_ERR((StateData#state.lang), E)), + xmpp:serr_policy_violation(E, StateData#state.lang)), {stop, normal, StateData}; session_established({xmlstreamerror, _}, StateData) -> - send_element(StateData, ?INVALID_XML_ERR), + send_element(StateData, xmpp:serr_not_well_formed()), {stop, normal, StateData}; session_established(closed, #state{mgmt_state = active} = StateData) -> catch (StateData#state.sockmod):close(StateData#state.socket), @@ -1236,91 +1090,78 @@ session_established(closed, #state{mgmt_state = active} = StateData) -> session_established(closed, StateData) -> {stop, normal, StateData}; session_established(stop, StateData) -> - {stop, normal, StateData}. + {stop, normal, StateData}; +session_established(Pkt, StateData) when ?is_stanza(Pkt) -> + FromJID = StateData#state.jid, + case check_from(Pkt, FromJID) of + 'invalid-from' -> + send_element(StateData, xmpp:serr_invalid_from()), + {stop, normal, StateData}; + _ -> + NewStateData = update_num_stanzas_in(StateData, Pkt), + session_established2(Pkt, NewStateData) + end; +session_established(_Pkt, StateData) -> + fsm_next_state(session_established, StateData). +-spec session_established2(xmpp_element(), state()) -> fsm_next(). %% Process packets sent by user (coming from user on c2s XMPP connection) -session_established2(El, StateData) -> - #xmlel{name = Name, attrs = Attrs} = El, - NewStateData = update_num_stanzas_in(StateData, El), - User = NewStateData#state.user, - Server = NewStateData#state.server, - FromJID = NewStateData#state.jid, - To = fxml:get_attr_s(<<"to">>, Attrs), - ToJID = case To of - <<"">> -> jid:make(User, Server, <<"">>); - _ -> jid:from_string(To) +session_established2(Pkt, StateData) -> + User = StateData#state.user, + Server = StateData#state.server, + FromJID = StateData#state.jid, + ToJID = case xmpp:get_to(Pkt) of + undefined -> jid:make(User, Server, <<"">>); + J -> J end, - NewEl1 = jlib:remove_attr(<<"xmlns">>, El), - NewEl = case fxml:get_attr_s(<<"xml:lang">>, Attrs) of - <<"">> -> - case NewStateData#state.lang of - <<"">> -> NewEl1; - Lang -> - fxml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1) - end; - _ -> NewEl1 - end, - NewState = case ToJID of - error -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> NewStateData; - <<"result">> -> NewStateData; - _ -> - Err = jlib:make_error_reply(NewEl, - ?ERR_JID_MALFORMED), - send_packet(NewStateData, Err) - end; - _ -> - case Name of - <<"presence">> -> - PresenceEl0 = - ejabberd_hooks:run_fold(c2s_update_presence, - Server, NewEl, - [User, Server]), - PresenceEl = - ejabberd_hooks:run_fold( - user_send_packet, Server, PresenceEl0, - [NewStateData, FromJID, ToJID]), - case ToJID of - #jid{user = User, server = Server, - resource = <<"">>} -> - ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)", - [FromJID, PresenceEl, NewStateData]), - presence_update(FromJID, PresenceEl, - NewStateData); - _ -> - presence_track(FromJID, ToJID, PresenceEl, - NewStateData) - end; - <<"iq">> -> - case jlib:iq_query_info(NewEl) of - #iq{xmlns = Xmlns} = IQ - when Xmlns == (?NS_PRIVACY); - Xmlns == (?NS_BLOCKING) -> - process_privacy_iq(FromJID, ToJID, IQ, - NewStateData); - #iq{xmlns = ?NS_SESSION} -> - Res = jlib:make_result_iq_reply( - NewEl#xmlel{children = []}), - send_stanza(NewStateData, Res); - _ -> - NewEl0 = ejabberd_hooks:run_fold( - user_send_packet, Server, NewEl, - [NewStateData, FromJID, ToJID]), - check_privacy_route(FromJID, NewStateData, - FromJID, ToJID, NewEl0) - end; - <<"message">> -> - NewEl0 = ejabberd_hooks:run_fold( - user_send_packet, Server, NewEl, - [NewStateData, FromJID, ToJID]), - check_privacy_route(FromJID, NewStateData, FromJID, - ToJID, NewEl0); - _ -> NewStateData - end - end, + Lang = case xmpp:get_lang(Pkt) of + <<"">> -> StateData#state.lang; + L -> L + end, + NewPkt = xmpp:set_lang(Pkt, Lang), + NewState = + case NewPkt of + #presence{} -> + Presence0 = ejabberd_hooks:run_fold( + c2s_update_presence, Server, NewPkt, + [User, Server]), + Presence = ejabberd_hooks:run_fold( + user_send_packet, Server, Presence0, + [StateData, FromJID, ToJID]), + case ToJID of + #jid{user = User, server = Server, resource = <<"">>} -> + ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)", + [FromJID, Presence, StateData]), + presence_update(FromJID, Presence, + StateData); + _ -> + presence_track(FromJID, ToJID, Presence, + StateData) + end; + #iq{type = T, sub_els = [El]} when T == set; T == get -> + NS = xmpp:get_ns(El), + if NS == ?NS_BLOCKING; NS == ?NS_PRIVACY -> + IQ = xmpp:set_from_to(Pkt, FromJID, ToJID), + process_privacy_iq(IQ, StateData); + NS == ?NS_SESSION -> + Res = xmpp:make_iq_result(Pkt), + send_stanza(StateData, Res); + true -> + NewPkt0 = ejabberd_hooks:run_fold( + user_send_packet, Server, NewPkt, + [StateData, FromJID, ToJID]), + check_privacy_route(FromJID, StateData, FromJID, + ToJID, NewPkt0) + end; + _ -> + NewPkt0 = ejabberd_hooks:run_fold( + user_send_packet, Server, NewPkt, + [StateData, FromJID, ToJID]), + check_privacy_route(FromJID, StateData, FromJID, + ToJID, NewPkt0) + end, ejabberd_hooks:run(c2s_loop_debug, - [{xmlstreamelement, El}]), + [{xmlstreamelement, Pkt}]), fsm_next_state(session_established, NewState). wait_for_resume({xmlstreamelement, _El} = Event, StateData) -> @@ -1384,14 +1225,14 @@ handle_info({send_text, Text}, StateName, StateData) -> fsm_next_state(StateName, StateData); handle_info(replaced, StateName, StateData) -> Lang = StateData#state.lang, - Xmlelement = ?SERRT_CONFLICT(Lang, <<"Replaced by new connection">>), - handle_info({kick, replaced, Xmlelement}, StateName, StateData); + Pkt = xmpp:serr_conflict(<<"Replaced by new connection">>, Lang), + handle_info({kick, replaced, Pkt}, StateName, StateData); handle_info(kick, StateName, StateData) -> Lang = StateData#state.lang, - Xmlelement = ?SERRT_POLICY_VIOLATION(Lang, <<"has been kicked">>), - handle_info({kick, kicked_by_admin, Xmlelement}, StateName, StateData); -handle_info({kick, Reason, Xmlelement}, _StateName, StateData) -> - send_element(StateData, Xmlelement), + Pkt = xmpp:serr_policy_violation(<<"has been kicked">>, Lang), + handle_info({kick, kicked_by_admin, Pkt}, StateName, StateData); +handle_info({kick, Reason, Pkt}, _StateName, StateData) -> + send_element(StateData, Pkt), {stop, normal, StateData#state{authenticated = Reason}}; handle_info({route, _From, _To, {broadcast, Data}}, @@ -1403,7 +1244,7 @@ handle_info({route, _From, _To, {broadcast, Data}}, roster_change(IJID, ISubscription, StateData)); {exit, Reason} -> Lang = StateData#state.lang, - send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)), + send_element(StateData, xmpp:serr_conflict(Reason, Lang)), {stop, normal, StateData}; {privacy_list, PrivList, PrivListName} -> case ejabberd_hooks:run_fold(privacy_updated_list, @@ -1414,24 +1255,15 @@ handle_info({route, _From, _To, {broadcast, Data}}, false -> fsm_next_state(StateName, StateData); NewPL -> - PrivPushIQ = #iq{type = set, - id = <<"push", - (randoms:get_string())/binary>>, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, - ?NS_PRIVACY}], - children = - [#xmlel{name = <<"list">>, - attrs = [{<<"name">>, - PrivListName}], - children = []}]}]}, - PrivPushEl = jlib:replace_from_to( - jid:remove_resource(StateData#state.jid), - StateData#state.jid, - jlib:iq_to_xml(PrivPushIQ)), - NewState = send_stanza( - StateData, PrivPushEl), + PrivPushIQ = + #iq{type = set, + from = jid:remove_resource(StateData#state.jid), + to = StateData#state.jid, + id = <<"push", (randoms:get_string())/binary>>, + sub_els = [#privacy_query{ + lists = [#privacy_list{ + name = PrivListName}]}]}, + NewState = send_stanza(StateData, PrivPushIQ), fsm_next_state(StateName, NewState#state{privacy_list = NewPL}) end; @@ -1442,272 +1274,146 @@ handle_info({route, _From, _To, {broadcast, Data}}, fsm_next_state(StateName, StateData) end; %% Process Packets that are to be send to the user -handle_info({route, From, To, - #xmlel{name = Name, attrs = Attrs, children = Els} = Packet}, - StateName, StateData) -> - {Pass, NewAttrs, NewState} = case Name of - <<"presence">> -> - State = - ejabberd_hooks:run_fold(c2s_presence_in, - StateData#state.server, - StateData, - [{From, To, - Packet}]), - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"probe">> -> - LFrom = jid:tolower(From), - LBFrom = - jid:remove_resource(LFrom), - NewStateData = case - (?SETS):is_element(LFrom, - State#state.pres_a) - orelse - (?SETS):is_element(LBFrom, - State#state.pres_a) - of - true -> State; - false -> - case - (?SETS):is_element(LFrom, - State#state.pres_f) - of - true -> - A = - (?SETS):add_element(LFrom, - State#state.pres_a), - State#state{pres_a - = - A}; - false -> - case - (?SETS):is_element(LBFrom, - State#state.pres_f) - of - true -> - A = - (?SETS):add_element(LBFrom, - State#state.pres_a), - State#state{pres_a - = - A}; - false -> - State - end - end - end, - process_presence_probe(From, To, - NewStateData), - {false, Attrs, NewStateData}; - <<"error">> -> - NewA = - remove_element(jid:tolower(From), - State#state.pres_a), - {true, Attrs, - State#state{pres_a = NewA}}; - <<"subscribe">> -> - SRes = is_privacy_allow(State, - From, To, - Packet, - in), - {SRes, Attrs, State}; - <<"subscribed">> -> - SRes = is_privacy_allow(State, - From, To, - Packet, - in), - {SRes, Attrs, State}; - <<"unsubscribe">> -> - SRes = is_privacy_allow(State, - From, To, - Packet, - in), - {SRes, Attrs, State}; - <<"unsubscribed">> -> - SRes = is_privacy_allow(State, - From, To, - Packet, - in), - {SRes, Attrs, State}; - _ -> - case privacy_check_packet(State, - From, To, - Packet, - in) - of - allow -> - LFrom = - jid:tolower(From), - LBFrom = - jid:remove_resource(LFrom), - case - (?SETS):is_element(LFrom, - State#state.pres_a) - orelse - (?SETS):is_element(LBFrom, - State#state.pres_a) - of - true -> - {true, Attrs, State}; - false -> - case - (?SETS):is_element(LFrom, - State#state.pres_f) - of - true -> - A = - (?SETS):add_element(LFrom, - State#state.pres_a), - {true, Attrs, - State#state{pres_a - = - A}}; - false -> - case - (?SETS):is_element(LBFrom, - State#state.pres_f) - of - true -> - A = - (?SETS):add_element(LBFrom, - State#state.pres_a), - {true, - Attrs, - State#state{pres_a - = - A}}; - false -> - {true, - Attrs, - State} - end - end - end; - deny -> {false, Attrs, State} - end - end; - <<"iq">> -> - IQ = jlib:iq_query_info(Packet), - case IQ of - #iq{xmlns = ?NS_LAST} -> - LFrom = jid:tolower(From), - LBFrom = - jid:remove_resource(LFrom), - HasFromSub = - ((?SETS):is_element(LFrom, - StateData#state.pres_f) - orelse - (?SETS):is_element(LBFrom, - StateData#state.pres_f)) - andalso - is_privacy_allow(StateData, - To, From, - #xmlel{name - = - <<"presence">>, - attrs - = - [], - children - = - []}, - out), - case HasFromSub of - true -> - case - privacy_check_packet(StateData, - From, - To, - Packet, - in) - of - allow -> - {true, Attrs, - StateData}; - deny -> - Err = - jlib:make_error_reply(Packet, - ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, - From, - Err), - {false, Attrs, - StateData} - end; - _ -> - Err = - jlib:make_error_reply(Packet, - ?ERR_FORBIDDEN), - ejabberd_router:route(To, - From, - Err), - {false, Attrs, StateData} - end; - IQ - when is_record(IQ, iq) or - (IQ == reply) -> - case - privacy_check_packet(StateData, - From, To, - Packet, in) - of - allow -> - {true, Attrs, StateData}; - deny when is_record(IQ, iq) -> - Err = - jlib:make_error_reply(Packet, - ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, - From, - Err), - {false, Attrs, StateData}; - deny when IQ == reply -> - {false, Attrs, StateData} - end; - IQ - when (IQ == invalid) or - (IQ == not_iq) -> - {false, Attrs, StateData} - end; - <<"message">> -> - case privacy_check_packet(StateData, - From, To, - Packet, in) - of - allow -> - {true, Attrs, StateData}; - deny -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - <<"groupchat">> -> ok; - <<"headline">> -> ok; - _ -> - case fxml:get_subtag_with_xmlns(Packet, - <<"x">>, - ?NS_MUC_USER) - of - false -> - Err = - jlib:make_error_reply(Packet, - ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, - Err); - _ -> ok - end - end, - {false, Attrs, StateData} - end; - _ -> {true, Attrs, StateData} - end, +handle_info({route, From, To, Packet}, StateName, StateData) when ?is_stanza(Packet) -> + {Pass, NewState} = + case Packet of + #presence{type = T} -> + State = ejabberd_hooks:run_fold(c2s_presence_in, + StateData#state.server, + StateData, + [{From, To, Packet}]), + case T of + probe -> + LFrom = jid:tolower(From), + LBFrom = jid:remove_resource(LFrom), + NewStateData = + case (?SETS):is_element(LFrom, State#state.pres_a) + orelse (?SETS):is_element(LBFrom, State#state.pres_a) of + true -> State; + false -> + case (?SETS):is_element(LFrom, State#state.pres_f) of + true -> + A = (?SETS):add_element(LFrom, State#state.pres_a), + State#state{pres_a = A}; + false -> + case (?SETS):is_element(LBFrom, State#state.pres_f) of + true -> + A = (?SETS):add_element(LBFrom, State#state.pres_a), + State#state{pres_a = A}; + false -> + State + end + end + end, + process_presence_probe(From, To, NewStateData), + {false, NewStateData}; + error -> + NewA = ?SETS:del_element(jid:tolower(From), State#state.pres_a), + {true, State#state{pres_a = NewA}}; + subscribe -> + SRes = is_privacy_allow(State, From, To, Packet, in), + {SRes, State}; + subscribed -> + SRes = is_privacy_allow(State, From, To, Packet, in), + {SRes, State}; + unsubscribe -> + SRes = is_privacy_allow(State, From, To, Packet, in), + {SRes, State}; + unsubscribed -> + SRes = is_privacy_allow(State, From, To, Packet, in), + {SRes, State}; + _ -> + case privacy_check_packet(State, From, To, Packet, in) of + allow -> + LFrom = jid:tolower(From), + LBFrom = jid:remove_resource(LFrom), + case (?SETS):is_element(LFrom, State#state.pres_a) + orelse (?SETS):is_element(LBFrom, State#state.pres_a) of + true -> + {true, State}; + false -> + case (?SETS):is_element(LFrom, State#state.pres_f) of + true -> + A = (?SETS):add_element(LFrom, State#state.pres_a), + {true, State#state{pres_a = A}}; + false -> + case (?SETS):is_element(LBFrom, + State#state.pres_f) of + true -> + A = (?SETS):add_element( + LBFrom, + State#state.pres_a), + {true, State#state{pres_a = A}}; + false -> + {true, State} + end + end + end; + deny -> {false, State} + end + end; + #iq{type = T} -> + case xmpp:has_subtag(Packet, #last{}) of + true when T == get; T == set -> + LFrom = jid:tolower(From), + LBFrom = jid:remove_resource(LFrom), + HasFromSub = ((?SETS):is_element(LFrom, StateData#state.pres_f) + orelse (?SETS):is_element(LBFrom, StateData#state.pres_f)) + andalso is_privacy_allow(StateData, To, From, #presence{}, out), + case HasFromSub of + true -> + case privacy_check_packet( + StateData, From, To, Packet, in) of + allow -> + {true, StateData}; + deny -> + ejabberd_router:route_error( + To, From, Packet, + xmpp:err_service_unavailable()), + {false, StateData} + end; + _ -> + ejabberd_router:route_error( + To, From, Packet, xmpp:err_forbidden()), + {false, StateData} + end; + _ -> + case privacy_check_packet(StateData, From, To, Packet, in) of + allow -> + {true, StateData}; + deny -> + ejabberd_router:route_error( + To, From, Packet, xmpp:err_service_unavailable()), + {false, StateData} + end + end; + #message{type = T} -> + case privacy_check_packet(StateData, From, To, Packet, in) of + allow -> + {true, StateData}; + deny -> + case T of + groupchat -> ok; + headline -> ok; + _ -> + case xmpp:has_subtag(Packet, #muc_user{}) of + true -> + ok; + false -> + ejabberd_router:route_error( + To, From, Packet, xmpp:err_service_unavailable()) + end + end, + {false, StateData} + end + end, if Pass -> - Attrs2 = - jlib:replace_from_to_attrs(jid:to_string(From), - jid:to_string(To), NewAttrs), - FixedPacket0 = #xmlel{name = Name, attrs = Attrs2, children = Els}, + FixedPacket0 = xmpp:set_from_to(Packet, From, To), FixedPacket = ejabberd_hooks:run_fold( - user_receive_packet, - NewState#state.server, - FixedPacket0, - [NewState, NewState#state.jid, From, To]), + user_receive_packet, + NewState#state.server, + FixedPacket0, + [NewState, NewState#state.jid, From, To]), SentStateData = send_packet(NewState, FixedPacket), ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]), fsm_next_state(StateName, SentStateData); @@ -1727,11 +1433,11 @@ handle_info({'DOWN', Monitor, _Type, _Object, _Info}, handle_info(system_shutdown, StateName, StateData) -> case StateName of wait_for_stream -> - send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>), - send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), + send_header(StateData, ?MYNAME, {1,0}, <<"en">>), + send_element(StateData, xmpp:serr_system_shutdown()), ok; _ -> - send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), + send_element(StateData, xmpp:serr_system_shutdown()), ok end, {stop, normal, StateData}; @@ -1742,17 +1448,17 @@ handle_info({route_xmlstreamelement, El}, _StateName, StateData) -> handle_info({force_update_presence, LUser, LServer}, StateName, #state{jid = #jid{luser = LUser, lserver = LServer}} = StateData) -> NewStateData = case StateData#state.pres_last of - #xmlel{name = <<"presence">>} -> - PresenceEl = - ejabberd_hooks:run_fold(c2s_update_presence, - LServer, - StateData#state.pres_last, - [LUser, LServer]), - StateData2 = StateData#state{pres_last = PresenceEl}, - presence_update(StateData2#state.jid, PresenceEl, - StateData2), - StateData2; - _ -> StateData + #presence{} -> + Presence = + ejabberd_hooks:run_fold(c2s_update_presence, + LServer, + StateData#state.pres_last, + [LUser, LServer]), + StateData2 = StateData#state{pres_last = Presence}, + presence_update(StateData2#state.jid, Presence, + StateData2), + StateData2; + undefined -> StateData end, fsm_next_state(StateName, NewStateData); handle_info({send_filtered, Feature, From, To, Packet}, StateName, StateData) -> @@ -1765,7 +1471,7 @@ handle_info({send_filtered, Feature, From, To, Packet}, StateName, StateData) -> jid:to_string(To)]), StateData; true -> - FinalPacket = jlib:replace_from_to(From, To, Packet), + FinalPacket = xmpp:set_from_to(Packet, From, To), case StateData#state.jid of To -> case privacy_check_packet(StateData, From, To, @@ -1814,6 +1520,7 @@ handle_info(Info, StateName, StateData) -> ?ERROR_MSG("Unexpected info: ~p", [Info]), fsm_next_state(StateName, StateData). +-spec print_state(state()) -> state(). print_state(State = #state{pres_t = T, pres_f = F, pres_a = A}) -> State#state{pres_t = {pres_t, (?SETS):size(T)}, pres_f = {pres_f, (?SETS):size(F)}, @@ -1833,18 +1540,16 @@ terminate(_Reason, StateName, StateData) -> [StateData#state.socket, jid:to_string(StateData#state.jid)]), From = StateData#state.jid, - Packet = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"status">>, attrs = [], - children = - [{xmlcdata, - <<"Replaced by new connection">>}]}]}, + Lang = StateData#state.lang, + Status = <<"Replaced by new connection">>, + Packet = #presence{ + type = unavailable, + status = xmpp:mk_text(Status, Lang)}, ejabberd_sm:close_session_unset_presence(StateData#state.sid, StateData#state.user, StateData#state.server, StateData#state.resource, - <<"Replaced by new connection">>), + Status), presence_broadcast(StateData, From, StateData#state.pres_a, Packet); _ -> @@ -1860,9 +1565,7 @@ terminate(_Reason, StateName, StateData) -> StateData#state.resource); _ -> From = StateData#state.jid, - Packet = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = []}, + Packet = #presence{type = unavailable}, ejabberd_sm:close_session_unset_presence(StateData#state.sid, StateData#state.user, StateData#state.server, @@ -1897,7 +1600,7 @@ terminate(_Reason, StateName, StateData) -> %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- - +-spec change_shaper(state(), jid()) -> ok. change_shaper(StateData, JID) -> Shaper = acl:access_matches(StateData#state.shaper, #{usr => jid:split(JID), ip => StateData#state.ip}, @@ -1905,6 +1608,7 @@ change_shaper(StateData, JID) -> (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper). +-spec send_text(state(), iodata()) -> ok | {error, any()}. send_text(StateData, Text) when StateData#state.mgmt_state == pending -> ?DEBUG("Cannot send text while waiting for resumption: ~p", [Text]); send_text(StateData, Text) when StateData#state.xml_socket -> @@ -1924,15 +1628,29 @@ send_text(StateData, Text) -> ?DEBUG("Send XML on stream = ~p", [Text]), (StateData#state.sockmod):send(StateData#state.socket, Text). +-spec send_element(state(), xmlel() | xmpp_element()) -> ok | {error, any()}. send_element(StateData, El) when StateData#state.mgmt_state == pending -> ?DEBUG("Cannot send element while waiting for resumption: ~p", [El]); -send_element(StateData, El) when StateData#state.xml_socket -> +send_element(StateData, #xmlel{} = El) when StateData#state.xml_socket -> ?DEBUG("Send XML on stream = ~p", [fxml:element_to_binary(El)]), (StateData#state.sockmod):send_xml(StateData#state.socket, {xmlstreamelement, El}); -send_element(StateData, El) -> - send_text(StateData, fxml:element_to_binary(El)). +send_element(StateData, #xmlel{} = El) -> + send_text(StateData, fxml:element_to_binary(El)); +send_element(StateData, Pkt) -> + send_element(StateData, xmpp:encode(Pkt, ?NS_CLIENT)). +-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> state(). +send_error(StateData, Stanza, Error) -> + Type = xmpp:get_type(Stanza), + if Type == error; Type == result; + Type == <<"error">>; Type == <<"result">> -> + StateData; + true -> + send_stanza(StateData, xmpp:make_error(Stanza, Error)) + end. + +-spec send_stanza(state(), xmpp_element()) -> state(). send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive -> csi_filter_stanza(StateData, Stanza); send_stanza(StateData, Stanza) when StateData#state.mgmt_state == pending -> @@ -1944,8 +1662,9 @@ send_stanza(StateData, Stanza) -> send_element(StateData, Stanza), StateData. +-spec send_packet(state(), xmpp_element()) -> state(). send_packet(StateData, Packet) -> - case is_stanza(Packet) of + case xmpp:is_stanza(Packet) of true -> send_stanza(StateData, Packet); false -> @@ -1953,40 +1672,23 @@ send_packet(StateData, Packet) -> StateData end. -send_header(StateData, Server, Version, Lang) - when StateData#state.xml_socket -> - VersionAttr = case Version of - <<"">> -> []; - _ -> [{<<"version">>, Version}] - end, - LangAttr = case Lang of - <<"">> -> []; - _ -> [{<<"xml:lang">>, Lang}] - end, - Header = {xmlstreamstart, <<"stream:stream">>, - VersionAttr ++ - LangAttr ++ - [{<<"xmlns">>, <<"jabber:client">>}, - {<<"xmlns:stream">>, - <<"http://etherx.jabber.org/streams">>}, - {<<"id">>, StateData#state.streamid}, - {<<"from">>, Server}]}, - (StateData#state.sockmod):send_xml(StateData#state.socket, - Header); +-spec send_header(state(), binary(), binary(), binary()) -> ok | {error, any()}. send_header(StateData, Server, Version, Lang) -> - VersionStr = case Version of - <<"">> -> <<"">>; - _ -> [<<" version='">>, Version, <<"'">>] - end, - LangStr = case Lang of - <<"">> -> <<"">>; - _ -> [<<" xml:lang='">>, Lang, <<"'">>] - end, - Header = io_lib:format(?STREAM_HEADER, - [StateData#state.streamid, Server, VersionStr, - LangStr]), - send_text(StateData, iolist_to_binary(Header)). + Header = #xmlel{name = Name, attrs = Attrs} = + xmpp:encode(#stream_start{version = Version, + lang = Lang, + xmlns = ?NS_CLIENT, + stream_xmlns = ?NS_STREAM, + id = StateData#state.streamid, + from = jid:make(Server)}), + if StateData#state.xml_socket -> + (StateData#state.sockmod):send_xml(StateData#state.socket, + {xmlstreamstart, Name, Attrs}); + true -> + send_text(StateData, fxml:element_to_header(Header)) + end. +-spec send_trailer(state()) -> ok | {error, any()}. send_trailer(StateData) when StateData#state.mgmt_state == pending -> ?DEBUG("Cannot send stream trailer while waiting for resumption", []); @@ -1997,66 +1699,27 @@ send_trailer(StateData) send_trailer(StateData) -> send_text(StateData, ?STREAM_TRAILER). +-spec new_id() -> binary(). new_id() -> randoms:get_string(). +-spec new_uniq_id() -> binary(). new_uniq_id() -> iolist_to_binary([randoms:get_string(), - jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]). - -is_auth_packet(El) -> - case jlib:iq_query_info(El) of - #iq{id = ID, type = Type, xmlns = ?NS_AUTH, sub_el = SubEl} -> - #xmlel{children = Els} = SubEl, - {auth, ID, Type, - get_auth_tags(Els, <<"">>, <<"">>, <<"">>, <<"">>)}; - _ -> false - end. - -is_stanza(#xmlel{name = Name, attrs = Attrs}) when Name == <<"message">>; - Name == <<"presence">>; - Name == <<"iq">> -> - case fxml:get_attr(<<"xmlns">>, Attrs) of - {value, NS} when NS /= <<"jabber:client">>, - NS /= <<"jabber:server">> -> - false; - _ -> - true - end; -is_stanza(_El) -> - false. - -get_auth_tags([#xmlel{name = Name, children = Els} | L], - U, P, D, R) -> - CData = fxml:get_cdata(Els), - case Name of - <<"username">> -> get_auth_tags(L, CData, P, D, R); - <<"password">> -> get_auth_tags(L, U, CData, D, R); - <<"digest">> -> get_auth_tags(L, U, P, CData, R); - <<"resource">> -> get_auth_tags(L, U, P, D, CData); - _ -> get_auth_tags(L, U, P, D, R) - end; -get_auth_tags([_ | L], U, P, D, R) -> - get_auth_tags(L, U, P, D, R); -get_auth_tags([], U, P, D, R) -> - {U, P, D, R}. - -%% Copied from ejabberd_socket.erl --record(socket_state, {sockmod, socket, receiver}). + integer_to_binary(p1_time_compat:unique_integer([positive]))]). +-spec get_conn_type(state()) -> c2s | c2s_tls | c2s_compressed | websocket | + c2s_compressed_tls | http_bind. get_conn_type(StateData) -> - case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of - gen_tcp -> c2s; - fast_tls -> c2s_tls; - ezlib -> - case ezlib:get_sockmod((StateData#state.socket)#socket_state.socket) of - gen_tcp -> c2s_compressed; - fast_tls -> c2s_compressed_tls - end; - ejabberd_http_bind -> http_bind; - ejabberd_http_ws -> websocket; - _ -> unknown + case (StateData#state.sockmod):get_transport(StateData#state.socket) of + tcp -> c2s; + tls -> c2s_tls; + tcp_zlib -> c2s_compressed; + tls_zlib -> c2s_compressed_tls; + http_bind -> http_bind; + websocket -> websocket end. +-spec process_presence_probe(jid(), jid(), state()) -> ok. process_presence_probe(From, To, StateData) -> LFrom = jid:tolower(From), LBFrom = setelement(3, LFrom, <<"">>), @@ -2070,8 +1733,9 @@ process_presence_probe(From, To, StateData) -> (?SETS):is_element(LBFrom, StateData#state.pres_f))), if Cond -> %% To is the one sending the presence (the probe target) - Packet = jlib:add_delay_info(StateData#state.pres_last, To, - StateData#state.pres_timestamp), + Packet = xmpp_util:add_delay_info( + StateData#state.pres_last, To, + StateData#state.pres_timestamp), case privacy_check_packet(StateData, To, From, Packet, out) of deny -> ok; @@ -2092,14 +1756,12 @@ process_presence_probe(From, To, StateData) -> end. %% User updates his presence (non-directed presence packet) +-spec presence_update(jid(), presence(), state()) -> state(). presence_update(From, Packet, StateData) -> - #xmlel{attrs = Attrs} = Packet, - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"unavailable">> -> - Status = case fxml:get_subtag(Packet, <<"status">>) of - false -> <<"">>; - StatusTag -> fxml:get_tag_cdata(StatusTag) - end, + #presence{type = Type} = Packet, + case Type of + unavailable -> + Status = xmpp:get_text(Packet#presence.status), Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn}, {auth_module, StateData#state.auth_module}], @@ -2111,12 +1773,12 @@ presence_update(From, Packet, StateData) -> StateData#state.pres_a, Packet), StateData#state{pres_last = undefined, pres_timestamp = undefined, pres_a = (?SETS):new()}; - <<"error">> -> StateData; - <<"probe">> -> StateData; - <<"subscribe">> -> StateData; - <<"subscribed">> -> StateData; - <<"unsubscribe">> -> StateData; - <<"unsubscribed">> -> StateData; + error -> StateData; + probe -> StateData; + subscribe -> StateData; + subscribed -> StateData; + unsubscribe -> StateData; + unsubscribed -> StateData; _ -> OldPriority = case StateData#state.pres_last of undefined -> 0; @@ -2154,57 +1816,69 @@ presence_update(From, Packet, StateData) -> end. %% User sends a directed presence packet +-spec presence_track(jid(), jid(), presence(), state()) -> state(). presence_track(From, To, Packet, StateData) -> - #xmlel{attrs = Attrs} = Packet, + #presence{type = Type} = Packet, LTo = jid:tolower(To), User = StateData#state.user, Server = StateData#state.server, - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"unavailable">> -> - A = remove_element(LTo, StateData#state.pres_a), - check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet); - <<"subscribe">> -> - try_roster_subscribe(subscribe, User, Server, From, To, Packet, StateData); - <<"subscribed">> -> - ejabberd_hooks:run(roster_out_subscription, Server, - [User, Server, To, subscribed]), - check_privacy_route(From, StateData, - jid:remove_resource(From), To, Packet); - <<"unsubscribe">> -> - try_roster_subscribe(unsubscribe, User, Server, From, To, Packet, StateData); - <<"unsubscribed">> -> - ejabberd_hooks:run(roster_out_subscription, Server, - [User, Server, To, unsubscribed]), - check_privacy_route(From, StateData, - jid:remove_resource(From), To, Packet); - <<"error">> -> - check_privacy_route(From, StateData, From, To, Packet); - <<"probe">> -> - check_privacy_route(From, StateData, From, To, Packet); - _ -> - A = (?SETS):add_element(LTo, StateData#state.pres_a), - check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet) + Lang = StateData#state.lang, + case privacy_check_packet(StateData, From, To, Packet, out) of + deny -> + ErrText = <<"Your active privacy list has denied " + "the routing of this stanza.">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + send_error(StateData, xmpp:set_from_to(Packet, From, To), Err); + allow when Type == subscribe; Type == subscribed; + Type == unsubscribe; Type == unsubscribed -> + Access = gen_mod:get_module_opt(Server, mod_roster, access, + fun(A) when is_atom(A) -> A end, + all), + MyBareJID = jid:make(User, Server, <<"">>), + case acl:match_rule(Server, Access, MyBareJID) of + deny -> + ErrText = <<"Denied by ACL">>, + Err = xmpp:err_forbidden(ErrText, Lang), + send_error(StateData, xmpp:set_from_to(Packet, From, To), Err); + allow -> + ejabberd_hooks:run(roster_out_subscription, + Server, + [User, Server, To, Type]), + ejabberd_router:route(jid:remove_resource(From), To, Packet), + StateData + end; + allow when Type == error; Type == probe -> + ejabberd_router:route(From, To, Packet), + StateData; + allow -> + ejabberd_router:route(From, To, Packet), + A = case Type of + available -> + ?SETS:add_element(LTo, StateData#state.pres_a); + unavailable -> + ?SETS:del_element(LTo, StateData#state.pres_a) + end, + StateData#state{pres_a = A} end. +-spec check_privacy_route(jid(), state(), jid(), jid(), stanza()) -> state(). check_privacy_route(From, StateData, FromRoute, To, Packet) -> case privacy_check_packet(StateData, From, To, Packet, - out) - of + out) of deny -> Lang = StateData#state.lang, ErrText = <<"Your active privacy list has denied " - "the routing of this stanza.">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - Err2 = jlib:replace_from_to(To, From, Err), - send_stanza(StateData, Err2); + "the routing of this stanza.">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + send_error(StateData, xmpp:set_from_to(Packet, From, To), Err); allow -> - ejabberd_router:route(FromRoute, To, Packet), + ejabberd_router:route(FromRoute, To, Packet), StateData end. %% Check if privacy rules allow this delivery +-spec privacy_check_packet(state(), jid(), jid(), stanza(), in | out) -> allow | deny. privacy_check_packet(StateData, From, To, Packet, Dir) -> ejabberd_hooks:run_fold(privacy_check_packet, @@ -2213,49 +1887,37 @@ privacy_check_packet(StateData, From, To, Packet, StateData#state.privacy_list, {From, To, Packet}, Dir]). +-spec is_privacy_allow(state(), jid(), jid(), stanza(), in | out) -> boolean(). is_privacy_allow(StateData, From, To, Packet, Dir) -> allow == privacy_check_packet(StateData, From, To, Packet, Dir). -%%% Check ACL before allowing to send a subscription stanza -try_roster_subscribe(Type, User, Server, From, To, Packet, StateData) -> - JID1 = jid:make(User, Server, <<"">>), - Access = gen_mod:get_module_opt(Server, mod_roster, access, fun(A) when is_atom(A) -> A end, all), - case acl:match_rule(Server, Access, JID1) of - deny -> - %% Silently drop this (un)subscription request - StateData; - allow -> - ejabberd_hooks:run(roster_out_subscription, - Server, - [User, Server, To, Type]), - check_privacy_route(From, StateData, jid:remove_resource(From), - To, Packet) - end. - %% Send presence when disconnecting +-spec presence_broadcast(state(), jid(), ?SETS:set(), presence()) -> ok. presence_broadcast(StateData, From, JIDSet, Packet) -> JIDs = ?SETS:to_list(JIDSet), JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out), Server = StateData#state.server, send_multiple(From, Server, JIDs2, Packet). +-spec presence_broadcast_to_trusted( + state(), jid(), ?SETS:set(), ?SETS:set(), presence()) -> ok. %% Send presence when updating presence presence_broadcast_to_trusted(StateData, From, Trusted, JIDSet, Packet) -> - JIDs = ?SETS:to_list(JIDSet), - JIDs_trusted = [JID || JID <- JIDs, ?SETS:is_element(JID, Trusted)], - JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs_trusted, out), + JIDs = ?SETS:to_list(?SETS:intersection(Trusted, JIDSet)), + JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out), Server = StateData#state.server, send_multiple(From, Server, JIDs2, Packet). %% Send presence when connecting +-spec presence_broadcast_first(jid(), state(), presence()) -> state(). presence_broadcast_first(From, StateData, Packet) -> JIDsProbe = ?SETS:fold( fun(JID, L) -> [JID | L] end, [], StateData#state.pres_t), - PacketProbe = #xmlel{name = <<"presence">>, attrs = [{<<"type">>,<<"probe">>}], children = []}, + PacketProbe = #presence{type = probe}, JIDs2Probe = format_and_check_privacy(From, StateData, PacketProbe, JIDsProbe, out), Server = StateData#state.server, send_multiple(From, Server, JIDs2Probe, PacketProbe), @@ -2270,6 +1932,8 @@ presence_broadcast_first(From, StateData, Packet) -> send_multiple(From, Server, JIDs2, Packet), StateData#state{pres_a = As}. +-spec format_and_check_privacy( + jid(), state(), stanza(), [ljid()], in | out) -> [jid()]. format_and_check_privacy(From, StateData, Packet, JIDs, Dir) -> FJIDs = [jid:make(JID) || JID <- JIDs], lists:filter( @@ -2288,15 +1952,11 @@ format_and_check_privacy(From, StateData, Packet, JIDs, Dir) -> end, FJIDs). +-spec send_multiple(jid(), binary(), [jid()], stanza()) -> ok. send_multiple(From, Server, JIDs, Packet) -> ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet). -remove_element(E, Set) -> - case (?SETS):is_element(E, Set) of - true -> (?SETS):del_element(E, Set); - _ -> Set - end. - +-spec roster_change(jid(), both | from | none | remove | to, state()) -> state(). roster_change(IJID, ISubscription, StateData) -> LIJID = jid:tolower(IJID), IsFrom = (ISubscription == both) or (ISubscription == from), @@ -2304,11 +1964,11 @@ roster_change(IJID, ISubscription, StateData) -> OldIsFrom = (?SETS):is_element(LIJID, StateData#state.pres_f), FSet = if IsFrom -> (?SETS):add_element(LIJID, StateData#state.pres_f); - true -> remove_element(LIJID, StateData#state.pres_f) + true -> ?SETS:del_element(LIJID, StateData#state.pres_f) end, TSet = if IsTo -> (?SETS):add_element(LIJID, StateData#state.pres_t); - true -> remove_element(LIJID, StateData#state.pres_t) + true -> ?SETS:del_element(LIJID, StateData#state.pres_t) end, case StateData#state.pres_last of undefined -> @@ -2333,21 +1993,20 @@ roster_change(IJID, ISubscription, StateData) -> pres_t = TSet}; Cond2 -> ?DEBUG("C2: ~p~n", [LIJID]), - PU = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = []}, + PU = #presence{type = unavailable}, case privacy_check_packet(StateData, From, To, PU, out) of deny -> ok; allow -> ejabberd_router:route(From, To, PU) end, - A = remove_element(LIJID, StateData#state.pres_a), + A = ?SETS:del_element(LIJID, StateData#state.pres_a), StateData#state{pres_a = A, pres_f = FSet, pres_t = TSet}; true -> StateData#state{pres_f = FSet, pres_t = TSet} end end. +-spec update_priority(integer(), presence(), state()) -> ok. update_priority(Priority, Packet, StateData) -> Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn}, {auth_module, StateData#state.auth_module}], @@ -2355,20 +2014,16 @@ update_priority(Priority, Packet, StateData) -> StateData#state.user, StateData#state.server, StateData#state.resource, Priority, Packet, Info). -get_priority_from_presence(PresencePacket) -> - case fxml:get_subtag(PresencePacket, <<"priority">>) of - false -> 0; - SubEl -> - case catch - jlib:binary_to_integer(fxml:get_tag_cdata(SubEl)) - of - P when is_integer(P) -> P; - _ -> 0 - end +-spec get_priority_from_presence(presence()) -> integer(). +get_priority_from_presence(#presence{priority = Prio}) -> + case Prio of + undefined -> 0; + _ -> Prio end. -process_privacy_iq(From, To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ, StateData) -> +-spec process_privacy_iq(iq(), state()) -> state(). +process_privacy_iq(#iq{from = From, to = To, + type = Type, lang = Lang} = IQ, StateData) -> Txt = <<"No module is handling this query">>, {Res, NewStateData} = case Type of @@ -2376,16 +2031,15 @@ process_privacy_iq(From, To, R = ejabberd_hooks:run_fold( privacy_iq_get, StateData#state.server, - {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)}, - [From, To, IQ, - StateData#state.privacy_list]), + {error, xmpp:err_feature_not_implemented(Txt, Lang)}, + [IQ, StateData#state.privacy_list]), {R, StateData}; set -> case ejabberd_hooks:run_fold( privacy_iq_set, StateData#state.server, - {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)}, - [From, To, IQ]) + {error, xmpp:err_feature_not_implemented(Txt, Lang)}, + [IQ, StateData#state.privacy_list]) of {result, R, NewPrivList} -> {{result, R}, @@ -2396,21 +2050,21 @@ process_privacy_iq(From, To, end, IQRes = case Res of {result, Result} -> - IQ#iq{type = result, sub_el = Result}; + xmpp:make_iq_result(IQ, Result); {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} + xmpp:make_error(IQ, Error) end, - ejabberd_router:route(To, From, jlib:iq_to_xml(IQRes)), + ejabberd_router:route(To, From, IQRes), NewStateData. +-spec resend_offline_messages(state()) -> ok. resend_offline_messages(#state{ask_offline = true} = StateData) -> case ejabberd_hooks:run_fold(resend_offline_messages_hook, StateData#state.server, [], [StateData#state.user, StateData#state.server]) of Rs -> %%when is_list(Rs) -> - lists:foreach(fun ({route, From, To, - #xmlel{} = Packet}) -> + lists:foreach(fun ({route, From, To, Packet}) -> Pass = case privacy_check_packet(StateData, From, To, Packet, in) @@ -2428,6 +2082,7 @@ resend_offline_messages(#state{ask_offline = true} = StateData) -> resend_offline_messages(_StateData) -> ok. +-spec resend_subscription_requests(state()) -> state(). resend_subscription_requests(#state{user = User, server = Server} = StateData) -> PendingSubscriptions = @@ -2439,52 +2094,43 @@ resend_subscription_requests(#state{user = User, StateData, PendingSubscriptions). +-spec get_showtag(undefined | presence()) -> binary(). get_showtag(undefined) -> <<"unavailable">>; -get_showtag(Presence) -> - case fxml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of - <<"">> -> <<"available">>; - ShowTag -> ShowTag - end. +get_showtag(#presence{show = undefined}) -> <<"available">>; +get_showtag(#presence{show = Show}) -> atom_to_binary(Show, utf8). -get_statustag(undefined) -> <<"">>; -get_statustag(Presence) -> - fxml:get_path_s(Presence, [{elem, <<"status">>}, cdata]). +-spec get_statustag(undefined | presence()) -> binary(). +get_statustag(#presence{status = Status}) -> xmpp:get_text(Status); +get_statustag(undefined) -> <<"">>. -process_unauthenticated_stanza(StateData, El) -> - NewEl = case fxml:get_tag_attr_s(<<"xml:lang">>, El) of - <<"">> -> - case StateData#state.lang of - <<"">> -> El; - Lang -> fxml:replace_tag_attr(<<"xml:lang">>, Lang, El) - end; - _ -> El - end, - case jlib:iq_query_info(NewEl) of - #iq{lang = L} = IQ -> - Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq, - StateData#state.server, empty, - [StateData#state.server, IQ, - StateData#state.ip]), - case Res of - empty -> - Txt = <<"Authentication required">>, - ResIQ = IQ#iq{type = error, - sub_el = [?ERRT_SERVICE_UNAVAILABLE(L, Txt)]}, - Res1 = jlib:replace_from_to(jid:make(<<"">>, - StateData#state.server, - <<"">>), - jid:make(<<"">>, <<"">>, - <<"">>), - jlib:iq_to_xml(ResIQ)), - send_element(StateData, - jlib:remove_attr(<<"to">>, Res1)); - _ -> send_element(StateData, Res) - end; - _ -> - % Drop any stanza, which isn't IQ stanza - ok - end. +-spec process_unauthenticated_stanza(state(), iq()) -> ok | {error, any()}. +process_unauthenticated_stanza(StateData, #iq{type = T, lang = L} = IQ) + when T == set; T == get -> + Lang = if L == undefined; L == <<"">> -> StateData#state.lang; + true -> L + end, + NewIQ = IQ#iq{lang = Lang}, + Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq, + StateData#state.server, empty, + [StateData#state.server, NewIQ, + StateData#state.ip]), + case Res of + empty -> + Txt = <<"Authentication required">>, + Err0 = xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)), + Err1 = Err0#iq{from = jid:make(<<>>, StateData#state.server, <<>>), + to = undefined}, + send_element(StateData, Err1); + _ -> + send_element(StateData, Res) + end; +process_unauthenticated_stanza(_StateData, _) -> + %% Drop any stanza, which isn't IQ stanza + ok. +-spec peerip(ejabberd_socket:sockmod(), + ejabberd_socket:socket()) -> + {inet:ip_address(), non_neg_integer()} | undefined. peerip(SockMod, Socket) -> IP = case SockMod of gen_tcp -> inet:peername(Socket); @@ -2497,9 +2143,11 @@ peerip(SockMod, Socket) -> %% fsm_next_state_pack: Pack the StateData structure to improve %% sharing. +-spec fsm_next_state_pack(state_name(), state()) -> fsm_transition(). fsm_next_state_pack(StateName, StateData) -> fsm_next_state_gc(StateName, pack(StateData)). +-spec fsm_next_state_gc(state_name(), state()) -> fsm_transition(). %% fsm_next_state_gc: Garbage collect the process heap to make use of %% the newly packed StateData structure. fsm_next_state_gc(StateName, PackedStateData) -> @@ -2508,12 +2156,13 @@ fsm_next_state_gc(StateName, PackedStateData) -> %% fsm_next_state: Generate the next_state FSM tuple with different %% timeout, depending on the future state +-spec fsm_next_state(state_name(), state()) -> fsm_transition(). fsm_next_state(session_established, #state{mgmt_max_queue = exceeded} = StateData) -> ?WARNING_MSG("ACK queue too long, terminating session for ~s", [jid:to_string(StateData#state.jid)]), - Err = ?SERRT_POLICY_VIOLATION(StateData#state.lang, - <<"Too many unacked stanzas">>), + Err = xmpp:serr_policy_violation(<<"Too many unacked stanzas">>, + StateData#state.lang), send_element(StateData, Err), {stop, normal, StateData#state{mgmt_resend = false}}; fsm_next_state(session_established, #state{mgmt_state = pending} = StateData) -> @@ -2551,6 +2200,7 @@ fsm_next_state(StateName, StateData) -> %% fsm_reply: Generate the reply FSM tuple with different timeout, %% depending on the future state +-spec fsm_reply(_, state_name(), state()) -> fsm_reply(). fsm_reply(Reply, session_established, StateData) -> {reply, Reply, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT}; @@ -2562,34 +2212,32 @@ fsm_reply(Reply, StateName, StateData) -> {reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}. %% Used by c2s blacklist plugins +-spec is_ip_blacklisted(undefined | {inet:ip_address(), non_neg_integer()}, + binary()) -> false | {true, binary(), binary()}. is_ip_blacklisted(undefined, _Lang) -> false; is_ip_blacklisted({IP, _Port}, Lang) -> ejabberd_hooks:run_fold(check_bl_c2s, false, [IP, Lang]). %% Check from attributes %% returns invalid-from|NewElement -check_from(El, FromJID) -> - case fxml:get_tag_attr(<<"from">>, El) of - false -> - El; - {value, SJID} -> - JID = jid:from_string(SJID), - case JID of - error -> - 'invalid-from'; - #jid{} -> - if - (JID#jid.luser == FromJID#jid.luser) and - (JID#jid.lserver == FromJID#jid.lserver) and - (JID#jid.lresource == FromJID#jid.lresource) -> - El; - (JID#jid.luser == FromJID#jid.luser) and - (JID#jid.lserver == FromJID#jid.lserver) and - (JID#jid.lresource == <<"">>) -> - El; - true -> - 'invalid-from' - end +-spec check_from(stanza(), jid()) -> 'invalid-from' | stanza(). +check_from(Pkt, FromJID) -> + JID = xmpp:get_from(Pkt), + case JID of + undefined -> + Pkt; + #jid{} -> + if + (JID#jid.luser == FromJID#jid.luser) and + (JID#jid.lserver == FromJID#jid.lserver) and + (JID#jid.lresource == FromJID#jid.lresource) -> + Pkt; + (JID#jid.luser == FromJID#jid.luser) and + (JID#jid.lserver == FromJID#jid.lserver) and + (JID#jid.lresource == <<"">>) -> + Pkt; + true -> + 'invalid-from' end end. @@ -2605,6 +2253,7 @@ fsm_limit_opts(Opts) -> end end. +-spec bounce_messages() -> ok. bounce_messages() -> receive {route, From, To, El} -> @@ -2612,93 +2261,58 @@ bounce_messages() -> after 0 -> ok end. -process_compression_request(El, StateName, StateData) -> - case fxml:get_subtag(El, <<"method">>) of +-spec process_compression_request(compress(), state_name(), state()) -> fsm_next(). +process_compression_request(#compress{methods = []}, StateName, StateData) -> + send_element(StateData, #compress_failure{reason = 'setup-failed'}), + fsm_next_state(StateName, StateData); +process_compression_request(#compress{methods = Ms}, StateName, StateData) -> + case lists:member(<<"zlib">>, Ms) of + true -> + Socket = StateData#state.socket, + BCompressed = fxml:element_to_binary(xmpp:encode(#compressed{})), + ZlibSocket = (StateData#state.sockmod):compress(Socket, BCompressed), + fsm_next_state(wait_for_stream, + StateData#state{socket = ZlibSocket, + streamid = new_id()}); false -> send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_COMPRESS}], - children = - [#xmlel{name = <<"setup-failed">>, - attrs = [], children = []}]}), - fsm_next_state(StateName, StateData); - Method -> - case fxml:get_tag_cdata(Method) of - <<"zlib">> -> - Socket = StateData#state.socket, - BCompressed = fxml:element_to_binary( - #xmlel{name = <<"compressed">>, - attrs = [{<<"xmlns">>, - ?NS_COMPRESS}]}), - ZlibSocket = (StateData#state.sockmod):compress( - Socket, BCompressed), - fsm_next_state(wait_for_stream, - StateData#state{socket = ZlibSocket, - streamid = new_id()}); - _ -> - send_element(StateData, - #xmlel{name = <<"failure">>, - attrs = [{<<"xmlns">>, ?NS_COMPRESS}], - children = - [#xmlel{name = <<"unsupported-method">>, - attrs = [], - children = []}]}), - fsm_next_state(StateName, StateData) - end + #compress_failure{reason = 'unsupported-method'}), + fsm_next_state(StateName, StateData) end. %%%---------------------------------------------------------------------- %%% XEP-0191 %%%---------------------------------------------------------------------- +-spec route_blocking( + {block, [jid()]} | {unblock, [jid()]} | unblock_all, state()) -> state(). route_blocking(What, StateData) -> SubEl = case What of - {block, JIDs} -> - #xmlel{name = <<"block">>, - attrs = [{<<"xmlns">>, ?NS_BLOCKING}], - children = - lists:map(fun (JID) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - jid:to_string(JID)}], - children = []} - end, - JIDs)}; - {unblock, JIDs} -> - #xmlel{name = <<"unblock">>, - attrs = [{<<"xmlns">>, ?NS_BLOCKING}], - children = - lists:map(fun (JID) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - jid:to_string(JID)}], - children = []} - end, - JIDs)}; - unblock_all -> - #xmlel{name = <<"unblock">>, - attrs = [{<<"xmlns">>, ?NS_BLOCKING}], children = []} + {block, JIDs} -> + #block{items = JIDs}; + {unblock, JIDs} -> + #unblock{items = JIDs}; + unblock_all -> + #unblock{} end, - PrivPushIQ = #iq{type = set, id = <<"push">>, sub_el = [SubEl]}, - PrivPushEl = - jlib:replace_from_to(jid:remove_resource(StateData#state.jid), - StateData#state.jid, jlib:iq_to_xml(PrivPushIQ)), + PrivPushIQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl], + from = jid:remove_resource(StateData#state.jid), + to = StateData#state.jid}, %% No need to replace active privacy list here, %% blocking pushes are always accompanied by %% Privacy List pushes - send_stanza(StateData, PrivPushEl). + send_stanza(StateData, PrivPushIQ). %%%---------------------------------------------------------------------- %%% XEP-0198 %%%---------------------------------------------------------------------- - +-spec stream_mgmt_enabled(state()) -> boolean(). stream_mgmt_enabled(#state{mgmt_state = disabled}) -> false; stream_mgmt_enabled(_StateData) -> true. +-spec dispatch_stream_mgmt(xmpp_element(), state()) -> state(). dispatch_stream_mgmt(El, #state{mgmt_state = MgmtState} = StateData) when MgmtState == active; MgmtState == pending -> @@ -2706,171 +2320,144 @@ dispatch_stream_mgmt(El, #state{mgmt_state = MgmtState} = StateData) dispatch_stream_mgmt(El, StateData) -> negotiate_stream_mgmt(El, StateData). +-spec negotiate_stream_mgmt(xmpp_element(), state()) -> state(). negotiate_stream_mgmt(_El, #state{resource = <<"">>} = StateData) -> %% XEP-0198 says: "For client-to-server connections, the client MUST NOT %% attempt to enable stream management until after it has completed Resource %% Binding unless it is resuming a previous session". However, it also %% says: "Stream management errors SHOULD be considered recoverable", so we %% won't bail out. - send_element(StateData, ?MGMT_UNEXPECTED_REQUEST(?NS_STREAM_MGMT_3)), + send_element(StateData, #sm_failed{reason = 'unexpected-request', + xmlns = ?NS_STREAM_MGMT_3}), StateData; -negotiate_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) -> - case stream_mgmt_enabled(StateData) of - true -> - case Name of - <<"enable">> -> - handle_enable(StateData#state{mgmt_xmlns = Xmlns}, Attrs); - _ -> - Res = if Name == <<"a">>; - Name == <<"r">>; - Name == <<"resume">> -> - ?MGMT_UNEXPECTED_REQUEST(Xmlns); - true -> - ?MGMT_BAD_REQUEST(Xmlns) - end, - send_element(StateData, Res), - StateData - end; - false -> - send_element(StateData, ?MGMT_SERVICE_UNAVAILABLE(Xmlns)), - StateData - end; - _ -> - send_element(StateData, ?MGMT_UNSUPPORTED_VERSION(?NS_STREAM_MGMT_3)), - StateData - end. - -perform_stream_mgmt(#xmlel{name = Name, attrs = Attrs}, StateData) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - Xmlns when Xmlns == StateData#state.mgmt_xmlns -> - case Name of - <<"r">> -> - handle_r(StateData); - <<"a">> -> - handle_a(StateData, Attrs); - _ -> - Res = if Name == <<"enable">>; - Name == <<"resume">> -> - ?MGMT_UNEXPECTED_REQUEST(Xmlns); - true -> - ?MGMT_BAD_REQUEST(Xmlns) - end, - send_element(StateData, Res), - StateData - end; - _ -> - send_element(StateData, - ?MGMT_UNSUPPORTED_VERSION(StateData#state.mgmt_xmlns)), - StateData - end. - -handle_enable(#state{mgmt_timeout = DefaultTimeout, - mgmt_max_timeout = MaxTimeout} = StateData, Attrs) -> - Timeout = case fxml:get_attr_s(<<"resume">>, Attrs) of - ResumeAttr when ResumeAttr == <<"true">>; - ResumeAttr == <<"1">> -> - MaxAttr = fxml:get_attr_s(<<"max">>, Attrs), - case catch jlib:binary_to_integer(MaxAttr) of - Max when is_integer(Max), Max > 0, Max =< MaxTimeout -> - Max; - _ -> - DefaultTimeout - end; +negotiate_stream_mgmt(Pkt, StateData) -> + Xmlns = xmpp:get_ns(Pkt), + case stream_mgmt_enabled(StateData) of + true -> + case Pkt of + #sm_enable{} -> + handle_enable(StateData#state{mgmt_xmlns = Xmlns}, Pkt); _ -> - 0 + Res = if is_record(Pkt, sm_a); + is_record(Pkt, sm_r); + is_record(Pkt, sm_resume) -> + #sm_failed{reason = 'unexpected-request', + xmlns = Xmlns}; + true -> + #sm_failed{reason = 'bad-request', + xmlns = Xmlns} + end, + send_element(StateData, Res), + StateData + end; + false -> + send_element(StateData, + #sm_failed{reason = 'service-unavailable', + xmlns = Xmlns}), + StateData + end. + +-spec perform_stream_mgmt(xmpp_element(), state()) -> state(). +perform_stream_mgmt(Pkt, StateData) -> + case xmpp:get_ns(Pkt) of + Xmlns when Xmlns == StateData#state.mgmt_xmlns -> + case Pkt of + #sm_r{} -> + handle_r(StateData); + #sm_a{} -> + handle_a(StateData, Pkt); + _ -> + Res = if is_record(Pkt, sm_enable); + is_record(Pkt, sm_resume) -> + #sm_failed{reason = 'unexpected-request', + xmlns = Xmlns}; + true -> + #sm_failed{reason = 'bad-request', + xmlns = Xmlns} + end, + send_element(StateData, Res), + StateData + end; + _ -> + send_element(StateData, + #sm_failed{reason = 'unsupported-version', + xmlns = StateData#state.mgmt_xmlns}) + end. + +-spec handle_enable(state(), sm_enable()) -> state(). +handle_enable(#state{mgmt_timeout = DefaultTimeout, + mgmt_max_timeout = MaxTimeout} = StateData, + #sm_enable{resume = Resume, max = Max}) -> + Timeout = if Resume == false -> + 0; + Max /= undefined, Max > 0, Max =< MaxTimeout -> + Max; + true -> + DefaultTimeout end, - ResAttrs = [{<<"xmlns">>, StateData#state.mgmt_xmlns}] ++ - if Timeout > 0 -> - ?INFO_MSG("Stream management with resumption enabled for ~s", - [jid:to_string(StateData#state.jid)]), - [{<<"id">>, make_resume_id(StateData)}, - {<<"resume">>, <<"true">>}, - {<<"max">>, jlib:integer_to_binary(Timeout)}]; - true -> - ?INFO_MSG("Stream management without resumption enabled for ~s", - [jid:to_string(StateData#state.jid)]), - [] - end, - Res = #xmlel{name = <<"enabled">>, - attrs = ResAttrs, - children = []}, + Res = if Timeout > 0 -> + ?INFO_MSG("Stream management with resumption enabled for ~s", + [jid:to_string(StateData#state.jid)]), + #sm_enabled{xmlns = StateData#state.mgmt_xmlns, + id = make_resume_id(StateData), + resume = true, + max = Timeout}; + true -> + ?INFO_MSG("Stream management without resumption enabled for ~s", + [jid:to_string(StateData#state.jid)]), + #sm_enabled{xmlns = StateData#state.mgmt_xmlns} + end, send_element(StateData, Res), StateData#state{mgmt_state = active, mgmt_queue = queue:new(), mgmt_timeout = Timeout * 1000}. +-spec handle_r(state()) -> state(). handle_r(StateData) -> - H = jlib:integer_to_binary(StateData#state.mgmt_stanzas_in), - Res = #xmlel{name = <<"a">>, - attrs = [{<<"xmlns">>, StateData#state.mgmt_xmlns}, - {<<"h">>, H}], - children = []}, + Res = #sm_a{xmlns = StateData#state.mgmt_xmlns, + h = StateData#state.mgmt_stanzas_in}, send_element(StateData, Res), StateData. -handle_a(StateData, Attrs) -> - case catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs)) of - H when is_integer(H), H >= 0 -> - NewStateData = check_h_attribute(StateData, H), - maybe_renew_ack_request(NewStateData); - _ -> - ?DEBUG("Ignoring invalid ACK element from ~s", - [jid:to_string(StateData#state.jid)]), - StateData - end. +-spec handle_a(state(), sm_a()) -> state(). +handle_a(StateData, #sm_a{h = H}) -> + NewStateData = check_h_attribute(StateData, H), + maybe_renew_ack_request(NewStateData). -handle_resume(StateData, Attrs) -> - R = case fxml:get_attr_s(<<"xmlns">>, Attrs) of - Xmlns when ?IS_SUPPORTED_MGMT_XMLNS(Xmlns) -> - case stream_mgmt_enabled(StateData) of - true -> - case {fxml:get_attr(<<"previd">>, Attrs), - catch jlib:binary_to_integer(fxml:get_attr_s(<<"h">>, Attrs))} - of - {{value, PrevID}, H} when is_integer(H), H >= 0 -> - case inherit_session_state(StateData, PrevID) of +-spec handle_resume(state(), sm_resume()) -> {ok, state()} | error. +handle_resume(StateData, #sm_resume{h = H, previd = PrevID, xmlns = Xmlns}) -> + R = case stream_mgmt_enabled(StateData) of + true -> + case inherit_session_state(StateData, PrevID) of {ok, InheritedState, Info} -> {ok, InheritedState, Info, H}; - {error, Err, InH} -> - {error, ?MGMT_ITEM_NOT_FOUND_H(Xmlns, InH), Err}; - {error, Err} -> - {error, ?MGMT_ITEM_NOT_FOUND(Xmlns), Err} - end; - _ -> - {error, ?MGMT_BAD_REQUEST(Xmlns), - <<"Invalid request">>} - end; - false -> - {error, ?MGMT_SERVICE_UNAVAILABLE(Xmlns), - <<"XEP-0198 disabled">>} - end; - _ -> - {error, ?MGMT_UNSUPPORTED_VERSION(?NS_STREAM_MGMT_3), - <<"Invalid XMLNS">>} + {error, Err, InH} -> + {error, #sm_failed{reason = 'item-not-found', + h = InH, xmlns = Xmlns}, Err}; + {error, Err} -> + {error, #sm_failed{reason = 'item-not-found', + xmlns = Xmlns}, Err} + end; + false -> + {error, #sm_failed{reason = 'service-unavailable', + xmlns = Xmlns}, + <<"XEP-0198 disabled">>} end, case R of {ok, ResumedState, ResumedInfo, NumHandled} -> NewState = check_h_attribute(ResumedState, NumHandled), AttrXmlns = NewState#state.mgmt_xmlns, AttrId = make_resume_id(NewState), - AttrH = jlib:integer_to_binary(NewState#state.mgmt_stanzas_in), - send_element(NewState, - #xmlel{name = <<"resumed">>, - attrs = [{<<"xmlns">>, AttrXmlns}, - {<<"h">>, AttrH}, - {<<"previd">>, AttrId}], - children = []}), + AttrH = NewState#state.mgmt_stanzas_in, + send_element(NewState, #sm_resumed{xmlns = AttrXmlns, + h = AttrH, + previd = AttrId}), SendFun = fun(_F, _T, El, Time) -> NewEl = add_resent_delay_info(NewState, El, Time), send_element(NewState, NewEl) end, handle_unacked_stanzas(NewState, SendFun), - send_element(NewState, - #xmlel{name = <<"r">>, - attrs = [{<<"xmlns">>, AttrXmlns}], - children = []}), + send_element(NewState, #sm_r{xmlns = AttrXmlns}), NewState1 = csi_flush_queue(NewState), NewState2 = ejabberd_hooks:run_fold(c2s_session_resumed, StateData#state.server, @@ -2888,6 +2475,7 @@ handle_resume(StateData, Attrs) -> error end. +-spec check_h_attribute(state(), non_neg_integer()) -> state(). check_h_attribute(#state{mgmt_stanzas_out = NumStanzasOut} = StateData, H) when H > NumStanzasOut -> ?DEBUG("~s acknowledged ~B stanzas, but only ~B were sent", @@ -2898,10 +2486,11 @@ check_h_attribute(#state{mgmt_stanzas_out = NumStanzasOut} = StateData, H) -> [jid:to_string(StateData#state.jid), H, NumStanzasOut]), mgmt_queue_drop(StateData, H). +-spec update_num_stanzas_in(state(), xmpp_element()) -> state(). update_num_stanzas_in(#state{mgmt_state = MgmtState} = StateData, El) when MgmtState == active; MgmtState == pending -> - NewNum = case {is_stanza(El), StateData#state.mgmt_stanzas_in} of + NewNum = case {xmpp:is_stanza(El), StateData#state.mgmt_stanzas_in} of {true, 4294967295} -> 0; {true, Num} -> @@ -2928,7 +2517,7 @@ maybe_request_ack(StateData) -> request_ack(#state{mgmt_xmlns = Xmlns, mgmt_ack_timeout = AckTimeout} = StateData) -> - AckReq = #xmlel{name = <<"r">>, attrs = [{<<"xmlns">>, Xmlns}]}, + AckReq = #sm_r{xmlns = Xmlns}, case {send_element(StateData, AckReq), AckTimeout} of {ok, undefined} -> ok; @@ -2954,6 +2543,7 @@ maybe_renew_ack_request(#state{mgmt_ack_timer = Timer, StateData#state{mgmt_ack_timer = undefined} end. +-spec mgmt_queue_add(state(), xmpp_element()) -> state(). mgmt_queue_add(StateData, El) -> NewNum = case StateData#state.mgmt_stanzas_out of 4294967295 -> @@ -2966,11 +2556,13 @@ mgmt_queue_add(StateData, El) -> mgmt_stanzas_out = NewNum}, check_queue_length(NewState). +-spec mgmt_queue_drop(state(), non_neg_integer()) -> state(). mgmt_queue_drop(StateData, NumHandled) -> NewQueue = jlib:queue_drop_while(fun({N, _T, _E}) -> N =< NumHandled end, StateData#state.mgmt_queue), StateData#state{mgmt_queue = NewQueue}. +-spec check_queue_length(state()) -> state(). check_queue_length(#state{mgmt_max_queue = Limit} = StateData) when Limit == infinity; Limit == exceeded -> @@ -2984,6 +2576,7 @@ check_queue_length(#state{mgmt_queue = Queue, StateData end. +-spec handle_unacked_stanzas(state(), fun((_, _, _, _) -> _)) -> ok. handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData, F) when MgmtState == active; MgmtState == pending; @@ -2996,20 +2589,21 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData, F) ?DEBUG("~B stanza(s) were not acknowledged by ~s", [N, jid:to_string(StateData#state.jid)]), lists:foreach( - fun({_, Time, #xmlel{attrs = Attrs} = El}) -> - From_s = fxml:get_attr_s(<<"from">>, Attrs), - To_s = fxml:get_attr_s(<<"to">>, Attrs), - case {jid:from_string(From_s), jid:from_string(To_s)} of - {#jid{} = From, #jid{} = To} -> - F(From, To, El, Time); - {_, _} -> - ?DEBUG("Dropping stanza due to invalid JID(s)", []) + fun({_, Time, Pkt}) -> + From = xmpp:get_from(Pkt), + To = xmpp:get_to(Pkt), + case {From, To} of + {#jid{}, #jid{}} -> + F(From, To, Pkt, Time); + {_, _} -> + ?DEBUG("Dropping stanza due to invalid JID(s)", []) end end, queue:to_list(Queue)) end; handle_unacked_stanzas(_StateData, _F) -> ok. +-spec handle_unacked_stanzas(state()) -> ok. handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData) when MgmtState == active; MgmtState == pending; @@ -3040,79 +2634,44 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData) false -> fun(From, To, El, _Time) -> Txt = <<"User session terminated">>, - Err = - jlib:make_error_reply( - El, - ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)), - ejabberd_router:route(To, From, Err) + ejabberd_router:route_error( + To, From, El, xmpp:err_service_unavailable(Txt, Lang)) end end, - F = fun(From, _To, #xmlel{name = <<"presence">>}, _Time) -> + F = fun(From, _To, #presence{}, _Time) -> ?DEBUG("Dropping presence stanza from ~s", [jid:to_string(From)]); - (From, To, #xmlel{name = <<"iq">>} = El, _Time) -> + (From, To, #iq{} = El, _Time) -> Txt = <<"User session terminated">>, - Err = jlib:make_error_reply( - El, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)), - ejabberd_router:route(To, From, Err); + ejabberd_router:route_error( + To, From, El, xmpp:err_service_unavailable(Txt, Lang)); + (From, _To, #message{meta = #{carbon_copy := true}}, _Time) -> + %% XEP-0280 says: "When a receiving server attempts to deliver a + %% forked message, and that message bounces with an error for + %% any reason, the receiving server MUST NOT forward that error + %% back to the original sender." Resending such a stanza could + %% easily lead to unexpected results as well. + ?DEBUG("Dropping forwarded message stanza from ~s", + [jid:to_string(From)]); (From, To, El, Time) -> - %% We'll drop the stanza if it was by some - %% encapsulating protocol as per XEP-0297. One such protocol is - %% XEP-0280, which says: "When a receiving server attempts to - %% deliver a forked message, and that message bounces with an - %% error for any reason, the receiving server MUST NOT forward - %% that error back to the original sender." Resending such a - %% stanza could easily lead to unexpected results as well. - case is_encapsulated_forward(El) of + case ejabberd_hooks:run_fold(message_is_archived, + StateData#state.server, false, + [StateData, From, + StateData#state.jid, El]) of true -> - ?DEBUG("Dropping forwarded message stanza from ~s", - [fxml:get_attr_s(<<"from">>, El#xmlel.attrs)]); + ?DEBUG("Dropping archived message stanza from ~p", + [jid:to_string(xmpp:get_from(El))]); false -> - case ejabberd_hooks:run_fold(message_is_archived, - StateData#state.server, - false, - [StateData, From, - StateData#state.jid, El]) of - true -> - ?DEBUG("Dropping archived message stanza from ~s", - [fxml:get_attr_s(<<"from">>, - El#xmlel.attrs)]), - ok; - false -> - ReRoute(From, To, El, Time) - end + ReRoute(From, To, El, Time) end end, handle_unacked_stanzas(StateData, F); handle_unacked_stanzas(_StateData) -> ok. -is_encapsulated_forward(#xmlel{name = <<"message">>} = El) -> - SubTag = case {fxml:get_subtag(El, <<"sent">>), - fxml:get_subtag(El, <<"received">>), - fxml:get_subtag(El, <<"result">>)} of - {false, false, false} -> - false; - {Tag, false, false} -> - Tag; - {false, Tag, false} -> - Tag; - {_, _, Tag} -> - Tag - end, - if SubTag == false -> - false; - true -> - case fxml:get_subtag(SubTag, <<"forwarded">>) of - false -> - false; - _ -> - true - end - end; -is_encapsulated_forward(_El) -> - false. - +-spec inherit_session_state(state(), binary()) -> {ok, state()} | + {error, binary()} | + {error, binary(), non_neg_integer()}. inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) -> case jlib:base64_to_term(ResumeID) of {term, {R, Time}} -> @@ -3173,22 +2732,25 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) -> {error, <<"Invalid 'previd' value">>} end. +-spec resume_session({integer(), pid()}) -> any(). resume_session({Time, PID}) -> (?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 15000). +-spec make_resume_id(state()) -> binary(). make_resume_id(StateData) -> {Time, _} = StateData#state.sid, jlib:term_to_base64({StateData#state.resource, Time}). -add_resent_delay_info(_State, #xmlel{name = <<"iq">>} = El, _Time) -> +-spec add_resent_delay_info(state(), stanza(), erlang:timestamp()) -> stanza(). +add_resent_delay_info(_State, #iq{} = El, _Time) -> El; add_resent_delay_info(#state{server = From}, El, Time) -> - jlib:add_delay_info(El, From, Time, <<"Resent">>). + xmpp_util:add_delay_info(El, jid:make(From), Time, <<"Resent">>). %%%---------------------------------------------------------------------- %%% XEP-0352 %%%---------------------------------------------------------------------- - +-spec csi_filter_stanza(state(), stanza()) -> state(). csi_filter_stanza(#state{csi_state = CsiState, jid = JID, server = Server} = StateData, Stanza) -> {StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_filter_stanza, Server, @@ -3200,6 +2762,7 @@ csi_filter_stanza(#state{csi_state = CsiState, jid = JID, server = Server} = Stanzas), StateData2#state{csi_state = CsiState}. +-spec csi_flush_queue(state()) -> state(). csi_flush_queue(#state{csi_state = CsiState, jid = JID, server = Server} = StateData) -> {StateData1, Stanzas} = ejabberd_hooks:run_fold(csi_flush_queue, Server, @@ -3217,6 +2780,7 @@ csi_flush_queue(#state{csi_state = CsiState, jid = JID, server = Server} = %% Try to reduce the heap footprint of the four presence sets %% by ensuring that we re-use strings and Jids wherever possible. +-spec pack(state()) -> state(). pack(S = #state{pres_a = A, pres_f = F, pres_t = T}) -> {NewA, Pack2} = pack_jid_set(A, gb_trees:empty()), @@ -3253,6 +2817,7 @@ pack_string(String, Pack) -> transform_listen_option(Opt, Opts) -> [Opt|Opts]. +-spec identity([{atom(), binary()}]) -> binary(). identity(Props) -> case proplists:get_value(authzid, Props, <<>>) of <<>> -> proplists:get_value(username, Props, <<>>); diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl index 157700c47..85d7595a2 100644 --- a/src/ejabberd_captcha.erl +++ b/src/ejabberd_captcha.erl @@ -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(<>), - 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(<>)}], 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). diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl index 8d74ad5a2..223163a2b 100644 --- a/src/ejabberd_commands.erl +++ b/src/ejabberd_commands.erl @@ -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)}, diff --git a/src/ejabberd_commands_doc.erl b/src/ejabberd_commands_doc.erl index dc00c5d2a..bb519a600 100644 --- a/src/ejabberd_commands_doc.erl +++ b/src/ejabberd_commands_doc.erl @@ -5,7 +5,7 @@ %%% Created : 20 May 2008 by Badlop %%% %%% -%%% 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)), diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index af26767f8..e930e36b1 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -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}, diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index a96a28016..63adcdf69 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -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)), diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl index b8e706f23..ab5b6a701 100644 --- a/src/ejabberd_frontend_socket.erl +++ b/src/ejabberd_frontend_socket.erl @@ -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}; diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index e6e49d9b2..c0c7bbbd6 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -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' -> <<"">>; diff --git a/src/ejabberd_http_bind.erl b/src/ejabberd_http_bind.erl index 628119e6f..ea64b3cdf 100644 --- a/src/ejabberd_http_bind.erl +++ b/src/ejabberd_http_bind.erl @@ -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}; diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl index e76e8689a..b92345dd4 100644 --- a/src/ejabberd_http_ws.erl +++ b/src/ejabberd_http_ws.erl @@ -39,7 +39,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_http.hrl"). diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index 2ba943693..a5ee6a242 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -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 -> diff --git a/src/ejabberd_mnesia.erl b/src/ejabberd_mnesia.erl new file mode 100644 index 000000000..0e067ad65 --- /dev/null +++ b/src/ejabberd_mnesia.erl @@ -0,0 +1,169 @@ +%%%---------------------------------------------------------------------- +%%% File : mnesia_mnesia.erl +%%% Author : Christophe Romain +%%% Purpose : Handle configurable mnesia schema +%%% Created : 17 Nov 2016 by Christophe Romain +%%% +%%% +%%% 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]). diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl index d11548c22..74e26e8da 100644 --- a/src/ejabberd_oauth.erl +++ b/src/ejabberd_oauth.erl @@ -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; } diff --git a/src/ejabberd_oauth_mnesia.erl b/src/ejabberd_oauth_mnesia.erl index a23f443ed..bdd2d0edd 100644 --- a/src/ejabberd_oauth_mnesia.erl +++ b/src/ejabberd_oauth_mnesia.erl @@ -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)}]), diff --git a/src/ejabberd_oauth_rest.erl b/src/ejabberd_oauth_rest.erl index aadb97084..c932d16f5 100644 --- a/src/ejabberd_oauth_rest.erl +++ b/src/ejabberd_oauth_rest.erl @@ -35,7 +35,7 @@ -include("ejabberd.hrl"). -include("ejabberd_oauth.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("jid.hrl"). init() -> rest:start(?MYNAME), diff --git a/src/ejabberd_oauth_sql.erl b/src/ejabberd_oauth_sql.erl index 9253335ff..3c09362c2 100644 --- a/src/ejabberd_oauth_sql.erl +++ b/src/ejabberd_oauth_sql.erl @@ -36,7 +36,7 @@ -include("ejabberd_oauth.hrl"). -include("ejabberd.hrl"). -include("ejabberd_sql_pt.hrl"). --include("jlib.hrl"). +-include("jid.hrl"). init() -> ok. diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index 758001239..b6f90ccf8 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -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 elements go before and - 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 elements go before and + 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 = <>, diff --git a/src/ejabberd_riak.erl b/src/ejabberd_riak.erl index 575810acc..44628d1c2 100644 --- a/src/ejabberd_riak.erl +++ b/src/ejabberd_riak.erl @@ -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(), diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index e29d6acfb..33093abb0 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -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)) diff --git a/src/ejabberd_router_multicast.erl b/src/ejabberd_router_multicast.erl index fa32c8ed7..c7a190670 100644 --- a/src/ejabberd_router_multicast.erl +++ b/src/ejabberd_router_multicast.erl @@ -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]. diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index 2a17c75cb..4df1761cb 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -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 -> diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index d8d0a400a..395a0fce7 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -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), - <<"">>). - --define(STREAM_TRAILER, <<"">>). - --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, <<"">>). + +-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]. diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index ae3433a6a..b9ce47830 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -50,8 +50,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). -record(state, {socket :: ejabberd_socket:socket_state(), @@ -75,6 +74,17 @@ bridge :: {atom(), atom()}, timer = make_ref() :: reference()}). +-type state_name() :: open_socket | wait_for_stream | + wait_for_validation | wait_for_features | + wait_for_auth_result | wait_for_starttls_proceed | + relay_to_bridge | reopen_socket | wait_before_retry | + stream_established. +-type state() :: #state{}. +-type fsm_stop() :: {stop, normal, state()}. +-type fsm_next() :: {next_state, state_name(), state(), non_neg_integer()} | + {next_state, state_name(), state()}. +-type fsm_transition() :: fsm_stop() | fsm_next(). + %%-define(DBGFSM, true). -ifdef(DBGFSM). @@ -96,23 +106,6 @@ %% Specified in miliseconds. Default value is 5 minutes. -define(MAX_RETRY_DELAY, 300000). --define(STREAM_HEADER, - <<"">>). - --define(STREAM_TRAILER, <<"">>). - --define(INVALID_NAMESPACE_ERR, - fxml:element_to_binary(?SERR_INVALID_NAMESPACE)). - --define(HOST_UNKNOWN_ERR, - fxml:element_to_binary(?SERR_HOST_UNKNOWN)). - --define(INVALID_XML_ERR, - fxml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)). - -define(SOCKET_DEFAULT_RESULT, {error, badarg}). %%%---------------------------------------------------------------------- @@ -229,23 +222,19 @@ open_socket(init, StateData) -> ?SOCKET_DEFAULT_RESULT, AddrList) of {ok, Socket} -> - Version = if StateData#state.use_v10 -> - <<" version='1.0'">>; - true -> <<"">> + Version = if StateData#state.use_v10 -> {1,0}; + true -> undefined end, NewStateData = StateData#state{socket = Socket, tls_enabled = false, streamid = new_id()}, - send_text(NewStateData, - io_lib:format(?STREAM_HEADER, - [StateData#state.myname, - StateData#state.server, Version])), + send_header(NewStateData, Version), {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT}; - {error, _Reason} -> + {error, Reason} -> ?INFO_MSG("s2s connection: ~s -> ~s (remote server " - "not found)", - [StateData#state.myname, StateData#state.server]), + "not found: ~p)", + [StateData#state.myname, StateData#state.server, Reason]), case ejabberd_hooks:run_fold(find_s2s_bridge, undefined, [StateData#state.myname, StateData#state.server]) @@ -259,18 +248,8 @@ open_socket(init, StateData) -> _ -> wait_before_reconnect(StateData) end end; -open_socket(closed, StateData) -> - ?INFO_MSG("s2s connection: ~s -> ~s (stopped in " - "open socket)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -open_socket(timeout, StateData) -> - ?INFO_MSG("s2s connection: ~s -> ~s (timeout in " - "open socket)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -open_socket(_, StateData) -> - {next_state, open_socket, StateData}. +open_socket(Event, StateData) -> + handle_unexpected_event(Event, open_socket, StateData). open_socket1({_, _, _, _} = Addr, Port) -> open_socket2(inet, Addr, Port); @@ -309,466 +288,215 @@ open_socket2(Type, Addr, Port) -> %%---------------------------------------------------------------------- -wait_for_stream({xmlstreamstart, _Name, Attrs}, - StateData) -> - {CertCheckRes, CertCheckMsg, StateData0} = - if StateData#state.tls_certverify, StateData#state.tls_enabled -> - {Res, Msg} = - ejabberd_s2s:check_peer_certificate(ejabberd_socket, - StateData#state.socket, - StateData#state.server), - ?DEBUG("Certificate verification result for ~s: ~s", - [StateData#state.server, Msg]), - {Res, Msg, StateData#state{tls_certverify = false}}; +wait_for_stream({xmlstreamstart, Name, Attrs}, StateData0) -> + {CertCheckRes, CertCheckMsg, StateData} = + if StateData0#state.tls_certverify, StateData0#state.tls_enabled -> + {Res, Msg} = + ejabberd_s2s:check_peer_certificate(ejabberd_socket, + StateData0#state.socket, + StateData0#state.server), + ?DEBUG("Certificate verification result for ~s: ~s", + [StateData0#state.server, Msg]), + {Res, Msg, StateData0#state{tls_certverify = false}}; true -> - {no_verify, <<"Not verified">>, StateData} + {no_verify, <<"Not verified">>, StateData0} end, - RemoteStreamID = fxml:get_attr_s(<<"id">>, Attrs), - NewStateData = StateData0#state{remote_streamid = RemoteStreamID}, - case {fxml:get_attr_s(<<"xmlns">>, Attrs), - fxml:get_attr_s(<<"xmlns:db">>, Attrs), - fxml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>} - of - _ when CertCheckRes == error -> - send_text(NewStateData, - <<(fxml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>, - CertCheckMsg)))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)", - [NewStateData#state.myname, - NewStateData#state.server, - CertCheckMsg]), - {stop, normal, NewStateData}; - {<<"jabber:server">>, <<"jabber:server:dialback">>, - false} -> - send_db_request(NewStateData); - {<<"jabber:server">>, <<"jabber:server:dialback">>, - true} - when NewStateData#state.use_v10 -> - {next_state, wait_for_features, NewStateData, ?FSMTIMEOUT}; - %% Clause added to handle Tigase's workaround for an old ejabberd bug: - {<<"jabber:server">>, <<"jabber:server:dialback">>, - true} - when not NewStateData#state.use_v10 -> - send_db_request(NewStateData); - {<<"jabber:server">>, <<"">>, true} - when NewStateData#state.use_v10 -> - {next_state, wait_for_features, - NewStateData#state{db_enabled = false}, ?FSMTIMEOUT}; - {NSProvided, DB, _} -> - send_text(NewStateData, ?INVALID_NAMESPACE_ERR), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " - "namespace).~nNamespace provided: ~p~nNamespac" - "e expected: \"jabber:server\"~nxmlns:db " - "provided: ~p~nAll attributes: ~p", - [NewStateData#state.myname, NewStateData#state.server, - NSProvided, DB, Attrs]), - {stop, normal, NewStateData} + try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of + _ when CertCheckRes == error -> + send_element(StateData, + xmpp:serr_policy_violation(CertCheckMsg, ?MYLANG)), + ?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)", + [StateData#state.myname, StateData#state.server, + CertCheckMsg]), + {stop, normal, StateData}; + #stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM} + when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM -> + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData}; + #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID, + version = V} when V /= {1,0} -> + send_db_request(StateData#state{remote_streamid = ID}); + #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID} + when StateData#state.use_v10 -> + {next_state, wait_for_features, + StateData#state{remote_streamid = ID}, ?FSMTIMEOUT}; + #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID} + when not StateData#state.use_v10 -> + %% Handle Tigase's workaround for an old ejabberd bug: + send_db_request(StateData#state{remote_streamid = ID}); + #stream_start{id = ID} when StateData#state.use_v10 -> + {next_state, wait_for_features, + StateData#state{db_enabled = false, remote_streamid = ID}, + ?FSMTIMEOUT}; + #stream_start{} -> + send_element(StateData, xmpp:serr_invalid_namespace()), + {stop, normal, StateData}; + _ -> + send_element(StateData, xmpp:serr_invalid_xml()), + {stop, normal, StateData} + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)), + {stop, normal, StateData} end; -wait_for_stream({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " - "xml)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -wait_for_stream({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (xmlstreamend)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -wait_for_stream(timeout, StateData) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (timeout " - "in wait_for_stream)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -wait_for_stream(closed, StateData) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (close " - "in wait_for_stream)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}. +wait_for_stream(Event, StateData) -> + handle_unexpected_event(Event, wait_for_stream, StateData). -wait_for_validation({xmlstreamelement, El}, - StateData) -> - case is_verify_res(El) of - {result, To, From, Id, Type} -> - ?DEBUG("recv result: ~p", [{From, To, Id, Type}]), - case {Type, StateData#state.tls_enabled, - StateData#state.tls_required} - of - {<<"valid">>, Enabled, Required} - when (Enabled == true) or (Required == false) -> - send_queue(StateData, StateData#state.queue), - ?INFO_MSG("Connection established: ~s -> ~s with " - "TLS=~p", - [StateData#state.myname, StateData#state.server, - StateData#state.tls_enabled]), - ejabberd_hooks:run(s2s_connect_hook, - [StateData#state.myname, - StateData#state.server]), - {next_state, stream_established, - StateData#state{queue = queue:new()}}; - {<<"valid">>, Enabled, Required} - when (Enabled == false) and (Required == true) -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS " - "is required but unavailable)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; - _ -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " - "dialback key)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - {verify, To, From, Id, Type} -> - ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]), - case StateData#state.verify of - false -> - NextState = wait_for_validation, - {next_state, NextState, StateData, - get_timeout_interval(NextState)}; - {Pid, _Key, _SID} -> - case Type of - <<"valid">> -> - p1_fsm:send_event(Pid, - {valid, StateData#state.server, - StateData#state.myname}); - _ -> - p1_fsm:send_event(Pid, - {invalid, StateData#state.server, - StateData#state.myname}) - end, - if StateData#state.verify == false -> - {stop, normal, StateData}; - true -> - NextState = wait_for_validation, - {next_state, NextState, StateData, - get_timeout_interval(NextState)} - end - end; - _ -> - {next_state, wait_for_validation, StateData, - (?FSMTIMEOUT) * 3} +wait_for_validation({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_validation, StateData); +wait_for_validation(#db_result{to = To, from = From, type = Type}, StateData) -> + ?DEBUG("recv result: ~p", [{From, To, Type}]), + case {Type, StateData#state.tls_enabled, StateData#state.tls_required} of + {valid, Enabled, Required} when (Enabled == true) or (Required == false) -> + send_queue(StateData, StateData#state.queue), + ?INFO_MSG("Connection established: ~s -> ~s with " + "TLS=~p", + [StateData#state.myname, StateData#state.server, + StateData#state.tls_enabled]), + ejabberd_hooks:run(s2s_connect_hook, + [StateData#state.myname, + StateData#state.server]), + {next_state, stream_established, StateData#state{queue = queue:new()}}; + {valid, Enabled, Required} when (Enabled == false) and (Required == true) -> + ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS " + "is required but unavailable)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData}; + _ -> + ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid " + "dialback key result)", + [StateData#state.myname, StateData#state.server]), + {stop, normal, StateData} + end; +wait_for_validation(#db_verify{to = To, from = From, id = Id, type = Type}, + StateData) -> + ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]), + case StateData#state.verify of + false -> + NextState = wait_for_validation, + {next_state, NextState, StateData, get_timeout_interval(NextState)}; + {Pid, _Key, _SID} -> + case Type of + valid -> + p1_fsm:send_event(Pid, + {valid, StateData#state.server, + StateData#state.myname}); + _ -> + p1_fsm:send_event(Pid, + {invalid, StateData#state.server, + StateData#state.myname}) + end, + if StateData#state.verify == false -> + {stop, normal, StateData}; + true -> + NextState = wait_for_validation, + {next_state, NextState, StateData, get_timeout_interval(NextState)} + end end; -wait_for_validation({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamend)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -wait_for_validation({xmlstreamerror, _}, StateData) -> - ?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamerror)", - [StateData#state.myname, StateData#state.server]), - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - {stop, normal, StateData}; wait_for_validation(timeout, #state{verify = {VPid, VKey, SID}} = StateData) - when is_pid(VPid) and is_binary(VKey) and - is_binary(SID) -> - ?DEBUG("wait_for_validation: ~s -> ~s (timeout " - "in verify connection)", + when is_pid(VPid) and is_binary(VKey) and is_binary(SID) -> + ?DEBUG("wait_for_validation: ~s -> ~s (timeout in verify connection)", [StateData#state.myname, StateData#state.server]), {stop, normal, StateData}; -wait_for_validation(timeout, StateData) -> - ?INFO_MSG("wait_for_validation: ~s -> ~s (connect " - "timeout)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -wait_for_validation(closed, StateData) -> - ?INFO_MSG("wait for validation: ~s -> ~s (closed)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}. +wait_for_validation(Event, StateData) -> + handle_unexpected_event(Event, wait_for_validation, StateData). wait_for_features({xmlstreamelement, El}, StateData) -> - case El of - #xmlel{name = <<"stream:features">>, children = Els} -> - {SASLEXT, StartTLS, StartTLSRequired} = lists:foldl(fun - (#xmlel{name = - <<"mechanisms">>, - attrs = - Attrs1, - children - = - Els1} = - _El1, - {_SEXT, STLS, - STLSReq} = - Acc) -> - case - fxml:get_attr_s(<<"xmlns">>, - Attrs1) - of - ?NS_SASL -> - NewSEXT = - lists:any(fun - (#xmlel{name - = - <<"mechanism">>, - children - = - Els2}) -> - case - fxml:get_cdata(Els2) - of - <<"EXTERNAL">> -> - true; - _ -> - false - end; - (_) -> - false - end, - Els1), - {NewSEXT, - STLS, - STLSReq}; - _ -> Acc - end; - (#xmlel{name = - <<"starttls">>, - attrs = - Attrs1} = - El1, - {SEXT, _STLS, - _STLSReq} = - Acc) -> - case - fxml:get_attr_s(<<"xmlns">>, - Attrs1) - of - ?NS_TLS -> - Req = - case - fxml:get_subtag(El1, - <<"required">>) - of - #xmlel{} -> - true; - false -> - false - end, - {SEXT, - true, - Req}; - _ -> Acc - end; - (_, Acc) -> Acc - end, - {false, false, - false}, - Els), - if not SASLEXT and not StartTLS and - StateData#state.authenticated -> - send_queue(StateData, StateData#state.queue), - ?INFO_MSG("Connection established: ~s -> ~s with " - "SASL EXTERNAL and TLS=~p", - [StateData#state.myname, StateData#state.server, - StateData#state.tls_enabled]), - ejabberd_hooks:run(s2s_connect_hook, - [StateData#state.myname, - StateData#state.server]), - {next_state, stream_established, - StateData#state{queue = queue:new()}}; - SASLEXT and StateData#state.try_auth and - (StateData#state.new /= false) and - (StateData#state.tls_enabled or - not StateData#state.tls_required) -> - send_element(StateData, - #xmlel{name = <<"auth">>, - attrs = - [{<<"xmlns">>, ?NS_SASL}, - {<<"mechanism">>, <<"EXTERNAL">>}], - children = - [{xmlcdata, - jlib:encode_base64(StateData#state.myname)}]}), - {next_state, wait_for_auth_result, - StateData#state{try_auth = false}, ?FSMTIMEOUT}; - StartTLS and StateData#state.tls and - not StateData#state.tls_enabled -> - send_element(StateData, - #xmlel{name = <<"starttls">>, - attrs = [{<<"xmlns">>, ?NS_TLS}], - children = []}), - {next_state, wait_for_starttls_proceed, StateData, - ?FSMTIMEOUT}; - StartTLSRequired and not StateData#state.tls -> - ?DEBUG("restarted: ~p", - [{StateData#state.myname, StateData#state.server}]), - ejabberd_socket:close(StateData#state.socket), - {next_state, reopen_socket, - StateData#state{socket = undefined, use_v10 = false}, - ?FSMTIMEOUT}; - StateData#state.db_enabled -> - send_db_request(StateData); - true -> - ?DEBUG("restarted: ~p", - [{StateData#state.myname, StateData#state.server}]), - ejabberd_socket:close(StateData#state.socket), - {next_state, reopen_socket, - StateData#state{socket = undefined, use_v10 = false}, - ?FSMTIMEOUT} - end; - _ -> - send_text(StateData, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} + decode_element(El, wait_for_features, StateData); +wait_for_features(#stream_features{sub_els = Els}, StateData) -> + {SASLEXT, StartTLS, StartTLSRequired} = + lists:foldl( + fun(#sasl_mechanisms{list = Mechs}, {_, STLS, STLSReq}) -> + {lists:member(<<"EXTERNAL">>, Mechs), STLS, STLSReq}; + (#starttls{required = Required}, {SEXT, _, _}) -> + {SEXT, true, Required}; + (_, Acc) -> + Acc + end, {false, false, false}, Els), + if not SASLEXT and not StartTLS and StateData#state.authenticated -> + send_queue(StateData, StateData#state.queue), + ?INFO_MSG("Connection established: ~s -> ~s with " + "SASL EXTERNAL and TLS=~p", + [StateData#state.myname, StateData#state.server, + StateData#state.tls_enabled]), + ejabberd_hooks:run(s2s_connect_hook, + [StateData#state.myname, + StateData#state.server]), + {next_state, stream_established, + StateData#state{queue = queue:new()}}; + SASLEXT and StateData#state.try_auth and + (StateData#state.new /= false) and + (StateData#state.tls_enabled or + not StateData#state.tls_required) -> + send_element(StateData, + #sasl_auth{mechanism = <<"EXTERNAL">>, + text = StateData#state.myname}), + {next_state, wait_for_auth_result, + StateData#state{try_auth = false}, ?FSMTIMEOUT}; + StartTLS and StateData#state.tls and + not StateData#state.tls_enabled -> + send_element(StateData, #starttls{}), + {next_state, wait_for_starttls_proceed, StateData, ?FSMTIMEOUT}; + StartTLSRequired and not StateData#state.tls -> + ?DEBUG("restarted: ~p", + [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:close(StateData#state.socket), + {next_state, reopen_socket, + StateData#state{socket = undefined, use_v10 = false}, + ?FSMTIMEOUT}; + StateData#state.db_enabled -> + send_db_request(StateData); + true -> + ?DEBUG("restarted: ~p", + [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:close(StateData#state.socket), + {next_state, reopen_socket, + StateData#state{socket = undefined, use_v10 = false}, ?FSMTIMEOUT} end; -wait_for_features({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("wait_for_features: xmlstreamend", []), - {stop, normal, StateData}; -wait_for_features({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("wait for features: xmlstreamerror", []), - {stop, normal, StateData}; -wait_for_features(timeout, StateData) -> - ?INFO_MSG("wait for features: timeout", []), - {stop, normal, StateData}; -wait_for_features(closed, StateData) -> - ?INFO_MSG("wait for features: closed", []), - {stop, normal, StateData}. +wait_for_features(Event, StateData) -> + handle_unexpected_event(Event, wait_for_features, StateData). -wait_for_auth_result({xmlstreamelement, El}, - StateData) -> - case El of - #xmlel{name = <<"success">>, attrs = Attrs} -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_SASL -> - ?DEBUG("auth: ~p", - [{StateData#state.myname, StateData#state.server}]), - ejabberd_socket:reset_stream(StateData#state.socket), - send_text(StateData, - io_lib:format(?STREAM_HEADER, - [StateData#state.myname, - StateData#state.server, - <<" version='1.0'">>])), - {next_state, wait_for_stream, - StateData#state{streamid = new_id(), - authenticated = true}, - ?FSMTIMEOUT}; - _ -> - send_text(StateData, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - #xmlel{name = <<"failure">>, attrs = Attrs} -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_SASL -> - ?DEBUG("restarted: ~p", - [{StateData#state.myname, StateData#state.server}]), - ejabberd_socket:close(StateData#state.socket), - {next_state, reopen_socket, - StateData#state{socket = undefined}, ?FSMTIMEOUT}; - _ -> - send_text(StateData, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - _ -> - send_text(StateData, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; -wait_for_auth_result({xmlstreamend, _Name}, - StateData) -> - ?INFO_MSG("wait for auth result: xmlstreamend", []), - {stop, normal, StateData}; -wait_for_auth_result({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("wait for auth result: xmlstreamerror", []), - {stop, normal, StateData}; -wait_for_auth_result(timeout, StateData) -> - ?INFO_MSG("wait for auth result: timeout", []), - {stop, normal, StateData}; -wait_for_auth_result(closed, StateData) -> - ?INFO_MSG("wait for auth result: closed", []), - {stop, normal, StateData}. +wait_for_auth_result({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_auth_result, StateData); +wait_for_auth_result(#sasl_success{}, StateData) -> + ?DEBUG("auth: ~p", [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:reset_stream(StateData#state.socket), + send_header(StateData, {1,0}), + {next_state, wait_for_stream, + StateData#state{streamid = new_id(), authenticated = true}, + ?FSMTIMEOUT}; +wait_for_auth_result(#sasl_failure{}, StateData) -> + ?DEBUG("restarted: ~p", [{StateData#state.myname, StateData#state.server}]), + ejabberd_socket:close(StateData#state.socket), + {next_state, reopen_socket, + StateData#state{socket = undefined}, ?FSMTIMEOUT}; +wait_for_auth_result(Event, StateData) -> + handle_unexpected_event(Event, wait_for_auth_result, StateData). -wait_for_starttls_proceed({xmlstreamelement, El}, - StateData) -> - case El of - #xmlel{name = <<"proceed">>, attrs = Attrs} -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_TLS -> - ?DEBUG("starttls: ~p", - [{StateData#state.myname, StateData#state.server}]), - Socket = StateData#state.socket, - TLSOpts = case - ejabberd_config:get_option( - {domain_certfile, StateData#state.myname}, - fun iolist_to_binary/1) - of - undefined -> StateData#state.tls_options; - CertFile -> - [{certfile, CertFile} - | lists:keydelete(certfile, 1, - StateData#state.tls_options)] - end, - TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts), - NewStateData = StateData#state{socket = TLSSocket, - streamid = new_id(), - tls_enabled = true, - tls_options = TLSOpts}, - send_text(NewStateData, - io_lib:format(?STREAM_HEADER, - [NewStateData#state.myname, - NewStateData#state.server, - <<" version='1.0'">>])), - {next_state, wait_for_stream, NewStateData, - ?FSMTIMEOUT}; - _ -> - send_text(StateData, - <<(fxml:element_to_binary(?SERR_BAD_FORMAT))/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; - _ -> - ?INFO_MSG("Closing s2s connection: ~s -> ~s (bad " - "format)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData} - end; -wait_for_starttls_proceed({xmlstreamend, _Name}, - StateData) -> - ?INFO_MSG("wait for starttls proceed: xmlstreamend", - []), - {stop, normal, StateData}; -wait_for_starttls_proceed({xmlstreamerror, _}, - StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("wait for starttls proceed: xmlstreamerror", - []), - {stop, normal, StateData}; -wait_for_starttls_proceed(timeout, StateData) -> - ?INFO_MSG("wait for starttls proceed: timeout", []), - {stop, normal, StateData}; -wait_for_starttls_proceed(closed, StateData) -> - ?INFO_MSG("wait for starttls proceed: closed", []), - {stop, normal, StateData}. +wait_for_starttls_proceed({xmlstreamelement, El}, StateData) -> + decode_element(El, wait_for_starttls_proceed, StateData); +wait_for_starttls_proceed(#starttls_proceed{}, StateData) -> + ?DEBUG("starttls: ~p", [{StateData#state.myname, StateData#state.server}]), + Socket = StateData#state.socket, + TLSOpts = case ejabberd_config:get_option( + {domain_certfile, StateData#state.myname}, + fun iolist_to_binary/1) of + undefined -> StateData#state.tls_options; + CertFile -> + [{certfile, CertFile} + | lists:keydelete(certfile, 1, + StateData#state.tls_options)] + end, + TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts), + NewStateData = StateData#state{socket = TLSSocket, + streamid = new_id(), + tls_enabled = true, + tls_options = TLSOpts}, + send_header(NewStateData, {1,0}), + {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT}; +wait_for_starttls_proceed(Event, StateData) -> + handle_unexpected_event(Event, wait_for_starttls_proceed, StateData). reopen_socket({xmlstreamelement, _El}, StateData) -> {next_state, reopen_socket, StateData, ?FSMTIMEOUT}; @@ -797,47 +525,70 @@ relay_to_bridge(_Event, StateData) -> {next_state, relay_to_bridge, StateData}. stream_established({xmlstreamelement, El}, StateData) -> - ?DEBUG("s2S stream established", []), - case is_verify_res(El) of - {verify, VTo, VFrom, VId, VType} -> - ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]), - case StateData#state.verify of - {VPid, _VKey, _SID} -> - case VType of - <<"valid">> -> - p1_fsm:send_event(VPid, - {valid, StateData#state.server, - StateData#state.myname}); - _ -> - p1_fsm:send_event(VPid, - {invalid, StateData#state.server, - StateData#state.myname}) - end; - _ -> ok - end; - _ -> ok + decode_element(El, stream_established, StateData); +stream_established(#db_verify{to = VTo, from = VFrom, id = VId, type = VType}, + StateData) -> + ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]), + case StateData#state.verify of + {VPid, _VKey, _SID} -> + case VType of + valid -> + p1_fsm:send_event(VPid, + {valid, StateData#state.server, + StateData#state.myname}); + _ -> + p1_fsm:send_event(VPid, + {invalid, StateData#state.server, + StateData#state.myname}) + end; + _ -> ok end, {next_state, stream_established, StateData}; -stream_established({xmlstreamend, _Name}, StateData) -> - ?INFO_MSG("Connection closed in stream established: " - "~s -> ~s (xmlstreamend)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -stream_established({xmlstreamerror, _}, StateData) -> - send_text(StateData, - <<(?INVALID_XML_ERR)/binary, - (?STREAM_TRAILER)/binary>>), - ?INFO_MSG("stream established: ~s -> ~s (xmlstreamerror)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -stream_established(timeout, StateData) -> - ?INFO_MSG("stream established: ~s -> ~s (timeout)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}; -stream_established(closed, StateData) -> - ?INFO_MSG("stream established: ~s -> ~s (closed)", - [StateData#state.myname, StateData#state.server]), - {stop, normal, StateData}. +stream_established(Event, StateData) -> + handle_unexpected_event(Event, stream_established, StateData). + +-spec handle_unexpected_event(term(), state_name(), state()) -> fsm_transition(). +handle_unexpected_event(Event, StateName, StateData) -> + case Event of + {xmlstreamerror, _} -> + send_element(StateData, xmpp:serr_not_well_formed()), + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "got invalid XML from peer", + [StateData#state.myname, StateData#state.server, + StateName]), + {stop, normal, StateData}; + {xmlstreamend, _} -> + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "XML stream closed by peer", + [StateData#state.myname, StateData#state.server, + StateName]), + {stop, normal, StateData}; + timeout -> + send_element(StateData, xmpp:serr_connection_timeout()), + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "timed out during establishing an XML stream", + [StateData#state.myname, StateData#state.server, + StateName]), + {stop, normal, StateData}; + closed -> + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "connection socket closed", + [StateData#state.myname, StateData#state.server, + StateName]), + {stop, normal, StateData}; + Pkt when StateName == wait_for_stream; + StateName == wait_for_features; + StateName == wait_for_auth_result; + StateName == wait_for_starttls_proceed -> + send_element(StateData, xmpp:serr_bad_format()), + ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: " + "got unexpected event ~p", + [StateData#state.myname, StateData#state.server, + StateName, Pkt]), + {stop, normal, StateData}; + _ -> + {next_state, StateName, StateData, get_timeout_interval(StateName)} + end. %%---------------------------------------------------------------------- %% Func: StateName/3 @@ -917,7 +668,7 @@ handle_info({send_element, El}, StateName, StateData) -> %% In this state we bounce all message: We are waiting before %% trying to reconnect wait_before_retry -> - bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND), + bounce_element(El, xmpp:err_remote_server_not_found()), {next_state, StateName, StateData}; relay_to_bridge -> {Mod, Fun} = StateData#state.bridge, @@ -926,7 +677,7 @@ handle_info({send_element, El}, StateName, StateData) -> {'EXIT', Reason} -> ?ERROR_MSG("Error while relaying to bridge: ~p", [Reason]), - bounce_element(El, ?ERR_INTERNAL_SERVER_ERROR), + bounce_element(El, xmpp:err_internal_server_error()), wait_before_reconnect(StateData); _ -> {next_state, StateName, StateData} end; @@ -966,12 +717,13 @@ terminate(Reason, StateName, StateData) -> StateData#state.server}, self()) end, - bounce_queue(StateData#state.queue, - ?ERR_REMOTE_SERVER_NOT_FOUND), - bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND), + bounce_queue(StateData#state.queue, xmpp:err_remote_server_not_found()), + bounce_messages(xmpp:err_remote_server_not_found()), case StateData#state.socket of undefined -> ok; - _Socket -> ejabberd_socket:close(StateData#state.socket) + _Socket -> + catch send_trailer(StateData), + ejabberd_socket:close(StateData#state.socket) end, ok. @@ -981,12 +733,32 @@ print_state(State) -> State. %%% Internal functions %%%---------------------------------------------------------------------- +-spec send_text(state(), iodata()) -> ok. send_text(StateData, Text) -> + ?DEBUG("Send Text on stream = ~s", [Text]), ejabberd_socket: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_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, + from = jid:make(StateData#state.myname), + to = jid:make(StateData#state.server), + version = Version}), + send_text(StateData, fxml:element_to_header(Header)). + +-spec send_trailer(state()) -> ok. +send_trailer(StateData) -> + send_text(StateData, <<"">>). + +-spec send_queue(state(), queue:queue()) -> ok. send_queue(StateData, Q) -> case queue:out(Q) of {{value, El}, Q1} -> @@ -995,20 +767,13 @@ send_queue(StateData, Q) -> end. %% Bounce a single message (xmlelement) +-spec bounce_element(stanza(), stanza_error()) -> ok. bounce_element(El, Error) -> - #xmlel{attrs = Attrs} = El, - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - <<"result">> -> ok; - _ -> - Err = jlib:make_error_reply(El, Error), - From = jid:from_string(fxml:get_tag_attr_s(<<"from">>, - El)), - To = jid:from_string(fxml:get_tag_attr_s(<<"to">>, - El)), - ejabberd_router:route(To, From, Err) - end. + From = xmpp:get_from(El), + To = xmpp:get_to(El), + ejabberd_router:route_error(To, From, El, Error). +-spec bounce_queue(queue:queue(), stanza_error()) -> ok. bounce_queue(Q, Error) -> case queue:out(Q) of {{value, El}, Q1} -> @@ -1016,12 +781,15 @@ bounce_queue(Q, Error) -> {empty, _} -> ok end. +-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. +-spec bounce_messages(stanza_error()) -> ok. bounce_messages(Error) -> receive {send_element, El} -> @@ -1029,6 +797,7 @@ bounce_messages(Error) -> after 0 -> ok end. +-spec send_db_request(state()) -> fsm_transition(). send_db_request(StateData) -> Server = StateData#state.server, New = case StateData#state.new of @@ -1045,22 +814,18 @@ send_db_request(StateData) -> {StateData#state.myname, Server}, StateData#state.remote_streamid), send_element(StateData, - #xmlel{name = <<"db:result">>, - attrs = - [{<<"from">>, StateData#state.myname}, - {<<"to">>, Server}], - children = [{xmlcdata, Key1}]}) + #db_result{from = StateData#state.myname, + to = Server, + key = Key1}) end, case StateData#state.verify of false -> ok; {_Pid, Key2, SID} -> send_element(StateData, - #xmlel{name = <<"db:verify">>, - attrs = - [{<<"from">>, StateData#state.myname}, - {<<"to">>, StateData#state.server}, - {<<"id">>, SID}], - children = [{xmlcdata, Key2}]}) + #db_verify{from = StateData#state.myname, + to = StateData#state.server, + id = SID, + key = Key2}) end, {next_state, wait_for_validation, NewStateData, (?FSMTIMEOUT) * 6} @@ -1068,20 +833,6 @@ send_db_request(StateData) -> _:_ -> {stop, normal, NewStateData} end. -is_verify_res(#xmlel{name = Name, attrs = Attrs}) - when Name == <<"db:result">> -> - {result, fxml:get_attr_s(<<"to">>, Attrs), - fxml:get_attr_s(<<"from">>, Attrs), - fxml:get_attr_s(<<"id">>, Attrs), - fxml:get_attr_s(<<"type">>, Attrs)}; -is_verify_res(#xmlel{name = Name, attrs = Attrs}) - 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_attr_s(<<"type">>, Attrs)}; -is_verify_res(_) -> false. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% SRV support @@ -1189,12 +940,14 @@ get_addrs(Host, Family) -> [] end. +-spec outgoing_s2s_port() -> pos_integer(). outgoing_s2s_port() -> ejabberd_config:get_option( outgoing_s2s_port, fun(I) when is_integer(I), I > 0, I =< 65536 -> I end, 5269). +-spec outgoing_s2s_families() -> [ipv4 | ipv6]. outgoing_s2s_families() -> ejabberd_config:get_option( outgoing_s2s_families, @@ -1206,6 +959,7 @@ outgoing_s2s_families() -> Families end, [ipv4, ipv6]). +-spec outgoing_s2s_timeout() -> pos_integer(). outgoing_s2s_timeout() -> ejabberd_config:get_option( outgoing_s2s_timeout, @@ -1255,21 +1009,24 @@ log_s2s_out(_, Myname, Server, Tls) -> %% Calculate timeout depending on which state we are in: %% Can return integer > 0 | infinity +-spec get_timeout_interval(state_name()) -> pos_integer() | infinity. get_timeout_interval(StateName) -> case StateName of %% Validation implies dialback: Networking can take longer: wait_for_validation -> (?FSMTIMEOUT) * 6; %% When stream is established, we only rely on S2S Timeout timer: stream_established -> infinity; + relay_to_bridge -> infinity; + open_socket -> infinity; _ -> ?FSMTIMEOUT end. %% This function is intended to be called at the end of a state %% function that want to wait for a reconnect delay before stopping. +-spec wait_before_reconnect(state()) -> fsm_next(). wait_before_reconnect(StateData) -> - bounce_queue(StateData#state.queue, - ?ERR_REMOTE_SERVER_NOT_FOUND), - bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND), + bounce_queue(StateData#state.queue, xmpp:err_remote_server_not_found()), + bounce_messages(xmpp:err_remote_server_not_found()), cancel_timer(StateData#state.timer), Delay = case StateData#state.delay_to_retry of undefined_delay -> @@ -1281,6 +1038,7 @@ wait_before_reconnect(StateData) -> StateData#state{timer = Timer, delay_to_retry = Delay, queue = queue:new()}}. +-spec get_max_retry_delay() -> pos_integer(). get_max_retry_delay() -> case ejabberd_config:get_option( s2s_max_retry_delay, @@ -1290,6 +1048,7 @@ get_max_retry_delay() -> end. %% Terminate s2s_out connections that are in state wait_before_retry +-spec terminate_if_waiting_delay(binary(), binary()) -> ok. terminate_if_waiting_delay(From, To) -> FromTo = {From, To}, Pids = ejabberd_s2s:get_connections_pids(FromTo), @@ -1298,6 +1057,7 @@ terminate_if_waiting_delay(From, To) -> end, Pids). +-spec fsm_limit_opts() -> [{max_queue, pos_integer()}]. fsm_limit_opts() -> case ejabberd_config:get_option( max_fsm_queue, @@ -1306,6 +1066,29 @@ fsm_limit_opts() -> N -> [{max_queue, N}] end. +-spec decode_element(xmlel(), state_name(), state()) -> fsm_next(). +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} -> + Type = xmpp:get_type(El), + case xmpp:is_stanza(El) of + true when Type /= <<"result">>, Type /= <<"error">> -> + Lang = xmpp:get_lang(El), + Txt = xmpp:format_error(Why), + Err = xmpp:make_error(El, xmpp:err_bad_request(Txt, Lang)), + send_element(StateData, Err); + false -> + ok + end, + {next_state, StateName, StateData, get_timeout_interval(StateName)} + end. + opt_type(domain_certfile) -> fun iolist_to_binary/1; opt_type(max_fsm_queue) -> fun (I) when is_integer(I), I > 0 -> I end; diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 26374c1f1..35cfe15af 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -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, - <<"">>). - --define(STREAM_TRAILER, <<"">>). - --define(INVALID_HEADER_ERR, - <<"Invalid " - "Stream Header">>). - --define(INVALID_HANDSHAKE_ERR, - <<"Invalid Handshake">>). - --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, <<"">>), - 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, <<"">>). + +-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). diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 3369b7ca0..56dc3092e 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -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), diff --git a/src/ejabberd_sm_mnesia.erl b/src/ejabberd_sm_mnesia.erl index b900da315..ed38ceee9 100644 --- a/src/ejabberd_sm_mnesia.erl +++ b/src/ejabberd_sm_mnesia.erl @@ -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), diff --git a/src/ejabberd_sm_redis.erl b/src/ejabberd_sm_redis.erl index 2bfd2d8d1..049f1de58 100644 --- a/src/ejabberd_sm_redis.erl +++ b/src/ejabberd_sm_redis.erl @@ -19,7 +19,6 @@ -include("ejabberd.hrl"). -include("ejabberd_sm.hrl"). -include("logger.hrl"). --include("jlib.hrl"). %%%=================================================================== %%% API diff --git a/src/ejabberd_sm_sql.erl b/src/ejabberd_sm_sql.erl index 8871bbca4..2a7b80c19 100644 --- a/src/ejabberd_sm_sql.erl +++ b/src/ejabberd_sm_sql.erl @@ -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), diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl index 887b4a0f3..e26fc8652 100644 --- a/src/ejabberd_socket.erl +++ b/src/ejabberd_socket.erl @@ -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. - diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 27c2815ba..8db8b6c5f 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -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], diff --git a/src/ejabberd_sql_sup.erl b/src/ejabberd_sql_sup.erl index 682414557..93bc10ac5 100644 --- a/src/ejabberd_sql_sup.erl +++ b/src/ejabberd_sql_sup.erl @@ -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). diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl index 3f6c05667..5d52a041d 100644 --- a/src/ejabberd_system_monitor.erl +++ b/src/ejabberd_system_monitor.erl @@ -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()); diff --git a/src/ejabberd_web.erl b/src/ejabberd_web.erl index 459423aa4..523feb9c7 100644 --- a/src/ejabberd_web.erl +++ b/src/ejabberd_web.erl @@ -34,7 +34,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_http.hrl"). diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index fb57fa560..3836beda7 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -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(<>, <<"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}) -> <>; 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, <>}; diff --git a/src/ejabberd_websocket.erl b/src/ejabberd_websocket.erl index 0cdd9bac5..76568aa2d 100644 --- a/src/ejabberd_websocket.erl +++ b/src/ejabberd_websocket.erl @@ -47,7 +47,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_http.hrl"). diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl index 1dd88f837..2792d08c1 100644 --- a/src/ejabberd_xmlrpc.erl +++ b/src/ejabberd_xmlrpc.erl @@ -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", diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl index 7bace05dd..9b7fdbbef 100644 --- a/src/ejd2sql.erl +++ b/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 = <>, + {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, <>} -> + 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, <>} -> + case file:read(Fd, ValSize) of + {ok, <>} -> + 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 + <> -> + NewBuf = <>, + read_lines(Fd, N-1, <<"">>, [NewBuf|Acc]); + _ -> + NewBuf = <>, + read_lines(Fd, N, NewBuf, Acc) + end; + {ok, Data} -> + NewBuf = <>, + 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) -> diff --git a/src/ext_mod.erl b/src/ext_mod.erl index 842bb09fc..5b970623b 100644 --- a/src/ext_mod.erl +++ b/src/ext_mod.erl @@ -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 diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index c2b4252c9..4a7a03c27 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -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(). diff --git a/src/gen_pubsub_node.erl b/src/gen_pubsub_node.erl index 27cb032bd..5690c8a1d 100644 --- a/src/gen_pubsub_node.erl +++ b/src/gen_pubsub_node.erl @@ -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(), diff --git a/src/gen_pubsub_nodetree.erl b/src/gen_pubsub_nodetree.erl index 73583af02..a18bc8d39 100644 --- a/src/gen_pubsub_nodetree.erl +++ b/src/gen_pubsub_nodetree.erl @@ -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(), diff --git a/src/http_p1.erl b/src/http_p1.erl deleted file mode 100644 index f430bbe11..000000000 --- a/src/http_p1.erl +++ /dev/null @@ -1,358 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : http_p1.erl -%%% Author : Emilio Bustos -%%% Purpose : Provide a common API for inets / lhttpc / ibrowse -%%% Created : 29 Jul 2010 by Emilio Bustos -%%% -%%% -%%% 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 - diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl index 099387c9a..fd70f8b1e 100644 --- a/src/jd2ejd.erl +++ b/src/jd2ejd.erl @@ -32,8 +32,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). %%%---------------------------------------------------------------------- %%% API @@ -112,45 +111,40 @@ process_xdb(User, Server, xdb_data(_User, _Server, {xmlcdata, _CData}) -> ok; xdb_data(User, Server, #xmlel{attrs = Attrs} = El) -> - From = jid:make(User, Server, <<"">>), + From = jid:make(User, Server), + LUser = From#jid.luser, + LServer = From#jid.lserver, case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_AUTH -> Password = fxml:get_tag_cdata(El), ejabberd_auth:set_password(User, Server, Password), ok; ?NS_ROSTER -> - catch mod_roster:set_items(User, Server, El), ok; + catch mod_roster:set_items(User, Server, xmpp:decode(El)), + ok; ?NS_LAST -> TimeStamp = fxml:get_attr_s(<<"last">>, Attrs), Status = fxml:get_tag_cdata(El), catch mod_last:store_last_info(User, Server, - jlib:binary_to_integer(TimeStamp), + binary_to_integer(TimeStamp), Status), ok; ?NS_VCARD -> - catch mod_vcard:process_sm_iq(From, - jid:make(<<"">>, Server, <<"">>), - #iq{type = set, xmlns = ?NS_VCARD, - sub_el = El}), + catch mod_vcard:set_vcard(User, LServer, El), ok; <<"jabber:x:offline">> -> process_offline(Server, From, El), ok; XMLNS -> case fxml:get_attr_s(<<"j_private_flag">>, Attrs) of <<"1">> -> - catch mod_private:process_sm_iq(From, - jid:make(<<"">>, Server, - <<"">>), - #iq{type = set, - xmlns = ?NS_PRIVATE, - sub_el = - #xmlel{name = - <<"query">>, - attrs = [], - children = - [jlib:remove_attr(<<"j_private_flag">>, - jlib:remove_attr(<<"xdbns">>, - El))]}}); + NewAttrs = lists:filter( + fun({<<"j_private_flag">>, _}) -> false; + ({<<"xdbns">>, _}) -> false; + (_) -> true + end, Attrs), + catch mod_private:set_data( + LUser, LServer, + [{XMLNS, El#xmlel{attrs = NewAttrs}}]); _ -> ?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS]) end, @@ -159,18 +153,21 @@ xdb_data(User, Server, #xmlel{attrs = Attrs} = El) -> process_offline(Server, To, #xmlel{children = Els}) -> LServer = jid:nameprep(Server), - lists:foreach(fun (#xmlel{attrs = Attrs} = El) -> - FromS = fxml:get_attr_s(<<"from">>, Attrs), - From = case FromS of - <<"">> -> - jid:make(<<"">>, Server, <<"">>); - _ -> jid:from_string(FromS) - end, - case From of - error -> ok; - _ -> - ejabberd_hooks:run(offline_message_hook, - LServer, [From, To, El]) - end - end, - Els). + lists:foreach( + fun(#xmlel{} = El) -> + try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of + #message{from = JID} -> + From = case JID of + undefined -> jid:make(Server); + _ -> JID + end, + ejabberd_hooks:run(offline_message_hook, + LServer, [From, To, El]); + _ -> + ok + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + ?ERROR_MSG("failed to decode XML '~s': ~s", + [fxml:element_to_binary(El), Txt]) + end + end, Els). diff --git a/src/jid.erl b/src/jid.erl deleted file mode 100644 index a730bd949..000000000 --- a/src/jid.erl +++ /dev/null @@ -1,256 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author Evgeny Khramtsov -%%% @doc -%%% JID processing library -%%% @end -%%% Created : 24 Nov 2015 by Evgeny Khramtsov -%%% -%%% -%%% 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(jid). - -%% API --export([start/0, - make/1, - make/3, - split/1, - from_string/1, - to_string/1, - is_nodename/1, - nodeprep/1, - nameprep/1, - resourceprep/1, - tolower/1, - remove_resource/1, - replace_resource/2]). - --include("jlib.hrl"). - --export_type([jid/0]). - -%%%=================================================================== -%%% API -%%%=================================================================== --spec start() -> ok. - -start() -> - {ok, Owner} = ets_owner(), - SplitPattern = binary:compile_pattern([<<"@">>, <<"/">>]), - %% Table is public to allow ETS insert to fix / update the table even if table already exist - %% with another owner. - catch ets:new(jlib, [named_table, public, set, {keypos, 1}, {heir, Owner, undefined}]), - ets:insert(jlib, {string_to_jid_pattern, SplitPattern}), - ok. - -ets_owner() -> - case whereis(jlib_ets) of - undefined -> - Pid = spawn(fun() -> ets_keepalive() end), - case catch register(jlib_ets, Pid) of - true -> - {ok, Pid}; - Error -> Error - end; - Pid -> - {ok,Pid} - end. - -%% Process used to keep jlib ETS table alive in case the original owner dies. -%% The table need to be public, otherwise subsequent inserts would fail. -ets_keepalive() -> - receive - _ -> - ets_keepalive() - end. - --spec make(binary(), binary(), binary()) -> jid() | error. - -make(User, Server, Resource) -> - case nodeprep(User) of - error -> error; - LUser -> - case nameprep(Server) of - error -> error; - LServer -> - case resourceprep(Resource) of - error -> error; - LResource -> - #jid{user = User, server = Server, resource = Resource, - luser = LUser, lserver = LServer, - lresource = LResource} - end - end - end. - --spec make({binary(), binary(), binary()}) -> jid() | error. - -make({User, Server, Resource}) -> - make(User, Server, Resource). - -%% This is the reverse of make_jid/1 --spec split(jid()) -> {binary(), binary(), binary()} | error. - -split(#jid{user = U, server = S, resource = R}) -> - {U, S, R}; -split(_) -> - error. - --spec from_string(binary() | string()) -> jid() | error. -from_string(S) when is_list(S) -> - %% We do not accept list because we want to enforce good practice of - %% using binaries for string. However, we do not let it crash to avoid - %% losing associated ets table. - {error, need_jid_as_binary}; -from_string(S) when is_binary(S) -> - SplitPattern = ets:lookup_element(jlib, string_to_jid_pattern, 2), - Size = size(S), - End = Size-1, - case binary:match(S, SplitPattern) of - {0, _} -> - error; - {End, _} -> - error; - {Pos1, _} -> - case binary:at(S, Pos1) of - $/ -> - make(<<>>, - binary:part(S, 0, Pos1), - binary:part(S, Pos1+1, Size-Pos1-1)); - _ -> - Pos1N = Pos1+1, - case binary:match(S, SplitPattern, [{scope, {Pos1+1, Size-Pos1-1}}]) of - {End, _} -> - error; - {Pos1N, _} -> - error; - {Pos2, _} -> - case binary:at(S, Pos2) of - $/ -> - make(binary:part(S, 0, Pos1), - binary:part(S, Pos1+1, Pos2-Pos1-1), - binary:part(S, Pos2+1, Size-Pos2-1)); - _ -> error - end; - _ -> - make(binary:part(S, 0, Pos1), - binary:part(S, Pos1+1, Size-Pos1-1), - <<>>) - end - end; - _ -> - make(<<>>, S, <<>>) - end. - --spec to_string(jid() | ljid()) -> binary(). - -to_string(#jid{user = User, server = Server, - resource = Resource}) -> - to_string({User, Server, Resource}); -to_string({N, S, R}) -> - Node = iolist_to_binary(N), - Server = iolist_to_binary(S), - Resource = iolist_to_binary(R), - S1 = case Node of - <<"">> -> <<"">>; - _ -> <> - end, - S2 = <>, - S3 = case Resource of - <<"">> -> S2; - _ -> <> - end, - S3. - --spec is_nodename(binary()) -> boolean(). - -is_nodename(Node) -> - N = nodeprep(Node), - (N /= error) and (N /= <<>>). - --define(LOWER(Char), - if Char >= $A, Char =< $Z -> Char + 32; - true -> Char - end). - --spec nodeprep(binary()) -> binary() | error. - -nodeprep("") -> <<>>; -nodeprep(S) when byte_size(S) < 1024 -> - R = stringprep:nodeprep(S), - if byte_size(R) < 1024 -> R; - true -> error - end; -nodeprep(_) -> error. - --spec nameprep(binary()) -> binary() | error. - -nameprep(S) when byte_size(S) < 1024 -> - R = stringprep:nameprep(S), - if byte_size(R) < 1024 -> R; - true -> error - end; -nameprep(_) -> error. - --spec resourceprep(binary()) -> binary() | error. - -resourceprep(S) when byte_size(S) < 1024 -> - R = stringprep:resourceprep(S), - if byte_size(R) < 1024 -> R; - true -> error - end; -resourceprep(_) -> error. - --spec tolower(jid() | ljid()) -> error | ljid(). - -tolower(#jid{luser = U, lserver = S, - lresource = R}) -> - {U, S, R}; -tolower({U, S, R}) -> - case nodeprep(U) of - error -> error; - LUser -> - case nameprep(S) of - error -> error; - LServer -> - case resourceprep(R) of - error -> error; - LResource -> {LUser, LServer, LResource} - end - end - end. - --spec remove_resource(jid()) -> jid(); - (ljid()) -> ljid(). - -remove_resource(#jid{} = JID) -> - JID#jid{resource = <<"">>, lresource = <<"">>}; -remove_resource({U, S, _R}) -> {U, S, <<"">>}. - --spec replace_resource(jid(), binary()) -> error | jid(). - -replace_resource(JID, Resource) -> - case resourceprep(Resource) of - error -> error; - LResource -> - JID#jid{resource = Resource, lresource = LResource} - end. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== diff --git a/src/jlib.erl b/src/jlib.erl index 3384e670e..096ef4012 100644 --- a/src/jlib.erl +++ b/src/jlib.erl @@ -35,27 +35,33 @@ binary_to_integer/1, integer_to_binary/1]}). +-export([tolower/1, term_to_base64/1, base64_to_term/1, + decode_base64/1, encode_base64/1, ip_to_list/1, + atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1, + l2i/1, i2l/1, i2l/2, queue_drop_while/2, + expr_to_term/1, term_to_expr/1]). + +%% The following functions are used by gen_iq_handler.erl for providing backward +%% compatibility and must not be used in other parts of the code +%% Use xmpp:decode() and xmpp:encode() instead +-export([iq_query_info/1, iq_to_xml/1]). + +%% The following functions are deprecated and will be removed soon +%% Use functions from xmpp.erl and xmpp_util.erl instead -export([make_result_iq_reply/1, make_error_reply/3, make_error_reply/2, make_error_element/2, make_correct_from_to_attrs/3, replace_from_to_attrs/3, replace_from_to/3, replace_from_attrs/2, replace_from/2, - remove_attr/2, tolower/1, - get_iq_namespace/1, iq_query_info/1, + remove_attr/2, get_iq_namespace/1, iq_query_or_response_info/1, is_iq_request_type/1, - iq_to_xml/1, parse_xdata_submit/1, - unwrap_carbon/1, is_standalone_chat_state/1, + parse_xdata_submit/1, unwrap_carbon/1, is_standalone_chat_state/1, add_delay_info/3, add_delay_info/4, timestamp_to_legacy/1, timestamp_to_iso_basic/1, timestamp_to_iso/2, now_to_utc_string/1, now_to_local_string/1, datetime_string_to_timestamp/1, - term_to_base64/1, base64_to_term/1, - decode_base64/1, encode_base64/1, ip_to_list/1, rsm_encode/1, rsm_encode/2, rsm_decode/1, binary_to_integer/1, binary_to_integer/2, - integer_to_binary/1, integer_to_binary/2, - atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1, - l2i/1, i2l/1, i2l/2, queue_drop_while/2, - expr_to_term/1, term_to_expr/1]). + integer_to_binary/1, integer_to_binary/2]). %% The following functions are deprecated and will be removed soon %% Use corresponding functions from jid.erl instead @@ -74,7 +80,38 @@ {resourceprep, 1}, {jid_tolower, 1}, {jid_remove_resource, 1}, - {jid_replace_resource, 2}]). + {jid_replace_resource, 2}, + {add_delay_info, 3}, + {add_delay_info, 4}, + {make_result_iq_reply, 1}, + {make_error_reply, 3}, + {make_error_reply, 2}, + {make_error_element, 2}, + {make_correct_from_to_attrs, 3}, + {replace_from_to_attrs, 3}, + {replace_from_to, 3}, + {replace_from_attrs, 2}, + {replace_from, 2}, + {remove_attr, 2}, + {get_iq_namespace, 1}, + {iq_query_or_response_info, 1}, + {is_iq_request_type, 1}, + {parse_xdata_submit, 1}, + {unwrap_carbon, 1}, + {is_standalone_chat_state, 1}, + {timestamp_to_legacy, 1}, + {timestamp_to_iso_basic, 1}, + {timestamp_to_iso, 2}, + {now_to_utc_string, 1}, + {now_to_local_string, 1}, + {datetime_string_to_timestamp, 1}, + {rsm_encode, 1}, + {rsm_encode, 2}, + {rsm_decode, 1}, + {binary_to_integer, 1}, + {binary_to_integer, 2}, + {integer_to_binary, 1}, + {integer_to_binary, 2}]). -include("ejabberd.hrl"). -include("jlib.hrl"). @@ -582,7 +619,7 @@ add_delay_info(El, From, Time) -> binary()) -> xmlel(). add_delay_info(El, From, Time, Desc) -> - DelayTag = create_delay_tag(Time, From, Desc), + DelayTag = create_delay_tag(Time, From, Desc), fxml:append_subtags(El, [DelayTag]). -spec create_delay_tag(erlang:timestamp(), jid() | ljid() | binary(), binary()) @@ -640,14 +677,14 @@ timestamp_to_iso({{Year, Month, Day}, %% http://xmpp.org/extensions/xep-0091.html#time timestamp_to_legacy({{Year, Month, Day}, {Hour, Minute, Second}}) -> - iolist_to_binary(io_lib:format("~4..0B~2..0B~2..0BT~2..0B:~2..0B:~2..0B", + (str:format("~4..0B~2..0B~2..0BT~2..0B:~2..0B:~2..0B", [Year, Month, Day, Hour, Minute, Second])). -spec timestamp_to_iso_basic(calendar:datetime()) -> binary(). %% This is the ISO 8601 basic bormat timestamp_to_iso_basic({{Year, Month, Day}, {Hour, Minute, Second}}) -> - iolist_to_binary(io_lib:format("~4..0B~2..0B~2..0BT~2..0B~2..0B~2..0B", + (str:format("~4..0B~2..0B~2..0BT~2..0B~2..0B~2..0B", [Year, Month, Day, Hour, Minute, Second])). -spec now_to_utc_string(erlang:timestamp()) -> binary(). @@ -666,7 +703,7 @@ now_to_utc_string({MegaSecs, Secs, MicroSecs}, Precision) -> Max -> now_to_utc_string({MegaSecs, Secs + 1, 0}, Precision); FracOfSec -> - list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT" + (str:format("~4..0B-~2..0B-~2..0BT" "~2..0B:~2..0B:~2..0B.~*..0BZ", [Year, Month, Day, Hour, Minute, Second, Precision, FracOfSec])) @@ -688,7 +725,7 @@ now_to_local_string({MegaSecs, Secs, MicroSecs}) -> end, {{Year, Month, Day}, {Hour, Minute, Second}} = LocalTime, - list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B.~6." + (str:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B.~6." ".0B~s~2..0B:~2..0B", [Year, Month, Day, Hour, Minute, Second, MicroSecs, Sign, H, M])). diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl index e12e0de1e..5b582d82b 100644 --- a/src/mod_adhoc.erl +++ b/src/mod_adhoc.erl @@ -31,18 +31,15 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_local_iq/3, - process_sm_iq/3, get_local_commands/5, +-export([start/2, stop/1, process_local_iq/1, + process_sm_iq/1, get_local_commands/5, get_local_identity/5, get_local_features/5, get_sm_commands/5, get_sm_identity/5, get_sm_features/5, ping_item/4, ping_command/4, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). - --include("adhoc.hrl"). +-include("xmpp.hrl"). start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, @@ -107,12 +104,9 @@ get_local_commands(Acc, _From, {result, I} -> I; _ -> [] end, - Nodes = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Server}, {<<"node">>, ?NS_COMMANDS}, - {<<"name">>, - translate:translate(Lang, <<"Commands">>)}], - children = []}], + Nodes = [#disco_item{jid = jid:make(Server), + node = ?NS_COMMANDS, + name = translate:translate(Lang, <<"Commands">>)}], {result, Items ++ Nodes} end; get_local_commands(_Acc, From, @@ -140,13 +134,9 @@ get_sm_commands(Acc, _From, {result, I} -> I; _ -> [] end, - Nodes = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, jid:to_string(To)}, - {<<"node">>, ?NS_COMMANDS}, - {<<"name">>, - translate:translate(Lang, <<"Commands">>)}], - children = []}], + Nodes = [#disco_item{jid = To, + node = ?NS_COMMANDS, + name = translate:translate(Lang, <<"Commands">>)}], {result, Items ++ Nodes} end; get_sm_commands(_Acc, From, @@ -160,21 +150,14 @@ get_sm_commands(Acc, _From, _To, _Node, _Lang) -> Acc. %% On disco info request to the ad-hoc node, return automation/command-list. get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-list">>}, - {<<"name">>, - translate:translate(Lang, <<"Commands">>)}], - children = []} + [#identity{category = <<"automation">>, + type = <<"command-list">>, + name = translate:translate(Lang, <<"Commands">>)} | Acc]; get_local_identity(Acc, _From, _To, <<"ping">>, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}, - {<<"name">>, translate:translate(Lang, <<"Ping">>)}], - children = []} + [#identity{category = <<"automation">>, + type = <<"command-node">>, + name = translate:translate(Lang, <<"Ping">>)} | Acc]; get_local_identity(Acc, _From, _To, _Node, _Lang) -> Acc. @@ -183,18 +166,16 @@ get_local_identity(Acc, _From, _To, _Node, _Lang) -> %% On disco info request to the ad-hoc node, return automation/command-list. get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-list">>}, - {<<"name">>, - translate:translate(Lang, <<"Commands">>)}], - children = []} + [#identity{category = <<"automation">>, + type = <<"command-list">>, + name = translate:translate(Lang, <<"Commands">>)} | Acc]; get_sm_identity(Acc, _From, _To, _Node, _Lang) -> Acc. %------------------------------------------------------------------------- - +-spec get_local_features({error, stanza_error()} | {result, [binary()]} | empty, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | {result, [binary()]} | empty. get_local_features(Acc, _From, _To, <<"">>, _Lang) -> Feats = case Acc of {result, I} -> I; @@ -225,62 +206,67 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. %------------------------------------------------------------------------- -process_local_iq(From, To, IQ) -> - process_adhoc_request(From, To, IQ, - adhoc_local_commands). +process_local_iq(IQ) -> + process_adhoc_request(IQ, local). -process_sm_iq(From, To, IQ) -> - process_adhoc_request(From, To, IQ, adhoc_sm_commands). +process_sm_iq(IQ) -> + process_adhoc_request(IQ, sm). -process_adhoc_request(From, To, - #iq{sub_el = SubEl, lang = Lang} = IQ, Hook) -> - ?DEBUG("About to parse ~p...", [IQ]), - case adhoc:parse_request(IQ) of - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]}; - #adhoc_request{} = AdhocRequest -> - Host = To#jid.lserver, - case ejabberd_hooks:run_fold(Hook, Host, empty, - [From, To, AdhocRequest]) - of - ignore -> ignore; - empty -> - Txt = <<"No hook has processed this command">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]}; - Command -> IQ#iq{type = result, sub_el = [Command]} - end - end. +process_adhoc_request(#iq{from = From, to = To, + type = set, lang = Lang, + sub_els = [#adhoc_command{} = SubEl]} = IQ, Type) -> + Host = To#jid.lserver, + Res = case Type of + local -> + ejabberd_hooks:run_fold(adhoc_local_commands, Host, empty, + [From, To, SubEl]); + sm -> + ejabberd_hooks:run_fold(adhoc_sm_commands, Host, empty, + [From, To, SubEl]) + end, + case Res of + ignore -> + ignore; + empty -> + Txt = <<"No hook has processed this command">>, + xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); + {error, Error} -> + xmpp:make_error(IQ, Error); + Command -> + xmpp:make_iq_result(IQ, Command) + end; +process_adhoc_request(#iq{} = IQ, _Hooks) -> + xmpp:make_error(IQ, xmpp:err_bad_request()). +-spec ping_item(empty | {error, stanza_error()} | {result, [disco_item()]}, + jid(), jid(), binary()) -> {result, [disco_item()]}. ping_item(Acc, _From, #jid{server = Server} = _To, Lang) -> Items = case Acc of {result, I} -> I; _ -> [] end, - Nodes = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Server}, {<<"node">>, <<"ping">>}, - {<<"name">>, translate:translate(Lang, <<"Ping">>)}], - children = []}], + Nodes = [#disco_item{jid = jid:make(Server), + node = <<"ping">>, + name = translate:translate(Lang, <<"Ping">>)}], {result, Items ++ Nodes}. +-spec ping_command(adhoc_command(), jid(), jid(), adhoc_command()) -> + adhoc_command() | {error, stanza_error()}. ping_command(_Acc, _From, _To, - #adhoc_request{lang = Lang, node = <<"ping">>, - sessionid = _Sessionid, action = Action} = - Request) -> - if Action == <<"">>; Action == <<"execute">> -> - adhoc:produce_response(Request, - #adhoc_response{status = completed, - notes = - [{<<"info">>, - translate:translate(Lang, - <<"Pong">>)}]}); + #adhoc_command{lang = Lang, node = <<"ping">>, + action = Action} = Request) -> + if Action == execute -> + xmpp_util:make_adhoc_response( + Request, + #adhoc_command{ + status = completed, + notes = [#adhoc_note{ + type = info, + data = translate:translate(Lang, <<"Pong">>)}]}); true -> Txt = <<"Incorrect value of 'action' attribute">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} + {error, xmpp:err_bad_request(Txt, Lang)} end; ping_command(Acc, _From, _To, _Request) -> Acc. diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 48732ea35..4b117d50a 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -5,7 +5,7 @@ %%% Created : 10 Aug 2008 by Badlop %%% %%% -%%% ejabberd, Copyright (C) 2002-2008 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 @@ -30,31 +30,61 @@ -include("logger.hrl"). --export([start/2, stop/1, compile/1, get_cookie/0, - remove_node/1, set_password/3, check_password/3, - check_password_hash/4, delete_old_users/1, - delete_old_users_vhost/2, ban_account/3, +-export([start/2, stop/1, mod_opt_type/1, + get_commands_spec/0, depends/2]). + +% Commands API +-export([ + % Adminsys + compile/1, get_cookie/0, remove_node/1, export2sql/2, + restart_module/2, + + % Sessions num_active_users/2, num_resources/2, resource_num/3, kick_session/4, status_num/2, status_num/1, status_list/2, status_list/1, connected_users_info/0, connected_users_vhost/1, set_presence/7, - user_sessions_info/2, set_nickname/3, get_vcard/3, + get_presence/2, user_sessions_info/2, get_last/2, + + % Accounts + set_password/3, check_password_hash/4, delete_old_users/1, + delete_old_users_vhost/2, ban_account/3, check_password/3, + + % vCard + set_nickname/3, get_vcard/3, get_vcard/4, get_vcard_multi/4, set_vcard/4, - set_vcard/5, add_rosteritem/7, delete_rosteritem/4, + set_vcard/5, + + % Roster + add_rosteritem/7, delete_rosteritem/4, process_rosteritems/5, get_roster/2, push_roster/3, - push_roster_all/1, push_alltoall/2, get_last/2, - private_get/4, private_set/3, srg_create/5, + push_roster_all/1, push_alltoall/2, + + % Private storage + private_get/4, private_set/3, + + % Shared roster + srg_create/5, srg_delete/2, srg_list/1, srg_get_info/2, srg_get_members/2, srg_user_add/4, srg_user_del/4, - send_message/5, send_stanza/3, send_stanza_c2s/4, privacy_set/3, - stats/1, stats/2, mod_opt_type/1, get_commands_spec/0, depends/2]). + + % Send message + send_message/5, send_stanza/3, send_stanza_c2s/4, + + % Privacy list + privacy_set/3, + + % Stats + stats/1, stats/2 + ]). -include("ejabberd.hrl"). -include("ejabberd_commands.hrl"). -include("mod_roster.hrl"). +-include("mod_privacy.hrl"). -include("ejabberd_sm.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). %%% %%% gen_mod @@ -124,9 +154,30 @@ get_commands_spec() -> result = {res, rescode}, result_example = ok, result_desc = "Status code: 0 on success, 1 otherwise"}, + #ejabberd_commands{name = export2sql, tags = [mnesia], + desc = "Export Mnesia tables to files in directory", + module = ?MODULE, function = export2sql, + args = [{host, string}, {path, string}], + args_example = ["myserver.com","/tmp/export/sql"], + args_desc = ["Server name", "File to write sql export"], + result = {res, rescode}, + result_example = ok, + result_desc = "Status code: 0 on success, 1 otherwise"}, + #ejabberd_commands{name = restart_module, tags = [erlang], + desc = "Stop an ejabberd module, reload code and start", + module = ?MODULE, function = restart_module, + args = [{host, binary}, {module, binary}], + args_example = ["myserver.com","mod_admin_extra"], + args_desc = ["Server name", "Module to restart"], + result = {res, integer}, + result_example = 0, + result_desc = "Returns integer code:\n" + " - 0: code reloaded, module restarted\n" + " - 1: error: module not loaded\n" + " - 2: code not reloaded, but module restarted"}, #ejabberd_commands{name = num_active_users, tags = [accounts, stats], desc = "Get number of users active in the last days", - policy = admin, + policy = admin, module = ?MODULE, function = num_active_users, args = [{host, binary}, {days, integer}], args_example = [<<"myserver.com">>, 3], @@ -175,8 +226,8 @@ get_commands_spec() -> desc = "Check if the password hash is correct", longdesc = "Allowed hash methods: md5, sha.", module = ?MODULE, function = check_password_hash, - args = [{user, binary}, {host, binary}, {passwordhash, string}, - {hashmethod, string}], + args = [{user, binary}, {host, binary}, {passwordhash, binary}, + {hashmethod, binary}], args_example = [<<"peter">>, <<"myserver.com">>, <<"5ebe2294ecd0e0f08eab7690d2a6ee69">>, <<"md5">>], args_desc = ["User name to check", "Server to check", @@ -235,7 +286,7 @@ get_commands_spec() -> result_desc = "Status code: 0 on success, 1 otherwise"}, #ejabberd_commands{name = status_num_host, tags = [session, stats], desc = "Number of logged users with this status in host", - policy = admin, + policy = admin, module = ?MODULE, function = status_num, args = [{host, binary}, {status, binary}], args_example = [<<"myserver.com">>, <<"dnd">>], @@ -245,7 +296,7 @@ get_commands_spec() -> result_desc = "Number of connected sessions with given status type"}, #ejabberd_commands{name = status_num, tags = [session, stats], desc = "Number of logged users with this status", - policy = admin, + policy = admin, module = ?MODULE, function = status_num, args = [{status, binary}], args_example = [<<"dnd">>], @@ -297,11 +348,11 @@ get_commands_spec() -> ]}} }}}, #ejabberd_commands{name = connected_users_vhost, - tags = [session], - desc = "Get the list of established sessions in a vhost", - module = ?MODULE, function = connected_users_vhost, - args = [{host, binary}], - result = {connected_users_vhost, {list, {sessions, string}}}}, + tags = [session], + desc = "Get the list of established sessions in a vhost", + module = ?MODULE, function = connected_users_vhost, + args = [{host, binary}], + result = {connected_users_vhost, {list, {sessions, string}}}}, #ejabberd_commands{name = user_sessions_info, tags = [session], desc = "Get information about all sessions of a user", @@ -322,6 +373,28 @@ get_commands_spec() -> ]}} }}}, + #ejabberd_commands{name = get_presence, tags = [session], + desc = + "Retrieve the resource with highest priority, " + "and its presence (show and status message) " + "for a given user.", + longdesc = + "The 'jid' value contains the user jid " + "with resource.\nThe 'show' value contains " + "the user presence flag. It can take " + "limited values:\n - available\n - chat " + "(Free for chat)\n - away\n - dnd (Do " + "not disturb)\n - xa (Not available, " + "extended away)\n - unavailable (Not " + "connected)\n\n'status' is a free text " + "defined by the user client.", + module = ?MODULE, function = get_presence, + args = [{user, binary}, {server, binary}], + result = + {presence, + {tuple, + [{jid, string}, {show, string}, + {status, string}]}}}, #ejabberd_commands{name = set_presence, tags = [session], desc = "Set presence of a session", @@ -533,7 +606,7 @@ get_commands_spec() -> #ejabberd_commands{name = get_offline_count, tags = [offline], desc = "Get the number of unread offline messages", - policy = user, + policy = user, module = mod_offline, function = count_offline_messages, args = [], result = {value, integer}}, @@ -561,13 +634,13 @@ get_commands_spec() -> #ejabberd_commands{name = stats, tags = [stats], desc = "Get statistical value: registeredusers onlineusers onlineusersnode uptimeseconds processes", - policy = admin, + policy = admin, module = ?MODULE, function = stats, args = [{name, binary}], result = {stat, integer}}, #ejabberd_commands{name = stats_host, tags = [stats], desc = "Get statistical value for this host: registeredusers onlineusers", - policy = admin, + policy = admin, module = ?MODULE, function = stats, args = [{name, binary}, {host, binary}], result = {stat, integer}} @@ -588,6 +661,48 @@ remove_node(Node) -> mnesia:del_table_copy(schema, list_to_atom(Node)), ok. +restart_module(Host, Module) when is_binary(Module) -> + restart_module(Host, jlib:binary_to_atom(Module)); +restart_module(Host, Module) when is_atom(Module) -> + List = gen_mod:loaded_modules_with_opts(Host), + case proplists:get_value(Module, List) of + undefined -> + % not a running module, force code reload anyway + code:purge(Module), + code:delete(Module), + code:load_file(Module), + 1; + Opts -> + gen_mod:stop_module(Host, Module), + case code:soft_purge(Module) of + true -> + code:delete(Module), + code:load_file(Module), + gen_mod:start_module(Host, Module, Opts), + 0; + false -> + gen_mod:start_module(Host, Module, Opts), + 2 + end + end. + +export2sql(Host, Directory) -> + Tables = [{export_last, last}, + {export_offline, offline}, + {export_passwd, passwd}, + {export_private_storage, private_storage}, + {export_roster, roster}, + {export_vcard, vcard}, + {export_vcard_search, vcard_search}], + Export = fun({TableFun, Table}) -> + Filename = filename:join([Directory, atom_to_list(Table)++".txt"]), + io:format("Trying to export Mnesia table '~p' on Host '~s' to file '~s'~n", [Table, Host, Filename]), + Res = (catch ejd2sql:TableFun(Host, Filename)), + io:format(" Result: ~p~n", [Res]) + end, + lists:foreach(Export, Tables), + ok. + %%% %%% Accounts %%% @@ -606,10 +721,10 @@ check_password_hash(User, Host, PasswordHash, HashMethod) -> {A, _} when is_tuple(A) -> scrammed; {_, <<"md5">>} -> get_md5(AccountPass); {_, <<"sha">>} -> get_sha(AccountPass); - {_, Method} -> + {_, Method} -> ?ERROR_MSG("check_password_hash called " - "with hash method: ~p", [Method]), - undefined + "with hash method: ~p", [Method]), + undefined end, case AccountPassHash of scrammed -> @@ -627,10 +742,11 @@ get_sha(AccountPass) -> || X <- binary_to_list(p1_sha:sha1(AccountPass))]). num_active_users(Host, Days) -> - list_last_activity(Host, true, Days). + DB_Type = gen_mod:db_type(Host, mod_last), + list_last_activity(Host, true, Days, DB_Type). %% Code based on ejabberd/src/web/ejabberd_web_admin.erl -list_last_activity(Host, Integral, Days) -> +list_last_activity(Host, Integral, Days, mnesia) -> TimeStamp = p1_time_compat:system_time(seconds), TS = TimeStamp - Days * 86400, case catch mnesia:dirty_select( @@ -656,7 +772,11 @@ list_last_activity(Host, Integral, Days) -> end, lists:nth(Days, Hist ++ Tail) end - end. + end; +list_last_activity(_Host, _Integral, _Days, DB_Type) -> + throw({error, iolist_to_binary(io_lib:format("Unsupported backend: ~p", + [DB_Type]))}). + histogram(Values, Integral) -> histogram(lists:sort(Values), Integral, 0, 0, []). histogram([H | T], Integral, Current, Count, Hist) when Current == H -> @@ -760,7 +880,9 @@ set_random_password(User, Server, Reason) -> set_password_auth(User, Server, NewPass). build_random_password(Reason) -> - Date = jlib:timestamp_to_legacy(calendar:universal_time()), + {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time(), + Date = str:format("~4..0B~2..0B~2..0BT~2..0B:~2..0B:~2..0B", + [Year, Month, Day, Hour, Minute, Second]), RandomString = randoms:get_string(), <<"BANNED_ACCOUNT--", Date/binary, "--", RandomString/binary, "--", Reason/binary>>. @@ -865,9 +987,9 @@ connected_users_vhost(Host) -> %% Code copied from ejabberd_sm.erl and customized dirty_get_sessions_list2() -> Ss = mnesia:dirty_select( - session, + session, [{#session{usr = '$1', sid = '$2', priority = '$3', info = '$4', - _ = '_'}, + _ = '_'}, [], [['$1', '$2', '$3', '$4']]}]), lists:filter(fun([_USR, _SID, _Priority, Info]) -> @@ -879,24 +1001,40 @@ stringize(String) -> %% Replace newline characters with other code ejabberd_regexp:greplace(String, <<"\n">>, <<"\\n">>). +get_presence(U, S) -> + Pids = [ejabberd_sm:get_session_pid(U, S, R) + || R <- ejabberd_sm:get_user_resources(U, S)], + OnlinePids = [Pid || Pid <- Pids, Pid=/=none], + case OnlinePids of + [] -> + {jid:to_string({U, S, <<>>}), <<"unavailable">>, <<"">>}; + [SessionPid|_] -> + {_User, Resource, Show, Status} = + ejabberd_c2s:get_presence(SessionPid), + FullJID = jid:to_string({U, S, Resource}), + {FullJID, Show, Status} + end. + set_presence(User, Host, Resource, Type, Show, Status, Priority) when is_integer(Priority) -> BPriority = integer_to_binary(Priority), set_presence(User, Host, Resource, Type, Show, Status, BPriority); -set_presence(User, Host, Resource, Type, Show, Status, Priority) -> +set_presence(User, Host, Resource, Type, Show, Status, Priority0) -> + Priority = if is_integer(Priority0) -> Priority0; + true -> binary_to_integer(Priority0) + end, case ejabberd_sm:get_session_pid(User, Host, Resource) of none -> error; Pid -> - USR = jid:to_string(jid:make(User, Host, Resource)), - US = jid:to_string(jid:make(User, Host, <<>>)), - Message = {route_xmlstreamelement, - {xmlel, <<"presence">>, - [{<<"from">>, USR}, {<<"to">>, US}, {<<"type">>, Type}], - [{xmlel, <<"show">>, [], [{xmlcdata, Show}]}, - {xmlel, <<"status">>, [], [{xmlcdata, Status}]}, - {xmlel, <<"priority">>, [], [{xmlcdata, Priority}]}]}}, - Pid ! Message, + From = jid:make(User, Host, Resource), + To = jid:make(User, Host), + Presence = #presence{from = From, to = To, + type = jlib:binary_to_atom(Type), + show = jlib:binary_to_atom(Show), + status = xmpp:mk_text(Status), + priority = Priority}, + Pid ! {route, From, To, Presence}, ok end. @@ -934,20 +1072,12 @@ user_sessions_info(User, Host) -> %%% set_nickname(User, Host, Nickname) -> - R = mod_vcard:process_sm_iq( - {jid, User, Host, <<>>, User, Host, <<>>}, - {jid, User, Host, <<>>, User, Host, <<>>}, - {iq, <<>>, set, <<>>, <<"en">>, - {xmlel, <<"vCard">>, [ - {<<"xmlns">>, <<"vcard-temp">>}], [ - {xmlel, <<"NICKNAME">>, [], [{xmlcdata, Nickname}]} - ] - }}), - case R of - {iq, <<>>, result, <<>>, _L, []} -> - ok; - _ -> - error + VCard = xmpp:encode(#vcard_temp{nickname = Nickname}), + case mod_vcard:set_vcard(User, jid:nameprep(Host), VCard) of + {error, badarg} -> + error; + ok -> + ok end. get_vcard(User, Host, Name) -> @@ -971,26 +1101,17 @@ set_vcard(User, Host, Name, Subname, SomeContent) -> %% %% Internal vcard -get_module_resource(Server) -> - case gen_mod:get_module_opt(Server, ?MODULE, module_resource, fun(A) -> A end, none) of - none -> list_to_binary(atom_to_list(?MODULE)); - R when is_binary(R) -> R - end. - get_vcard_content(User, Server, Data) -> - [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}), - JID = jid:make(User, Server, get_module_resource(Server)), - IQ = #iq{type = get, xmlns = ?NS_VCARD}, - IQr = Module:Function(JID, JID, IQ), - [A1] = IQr#iq.sub_el, - case A1#xmlel.children of - [_|_] -> - case get_vcard(Data, A1) of + case mod_vcard:get_vcard(jid:nodeprep(User), jid:nameprep(Server)) of + [_|_] = Els -> + case get_vcard(Data, Els) of [false] -> throw(error_no_value_found_in_vcard); ElemList -> ?DEBUG("ELS ~p", [ElemList]), [fxml:get_tag_cdata(Elem) || Elem <- ElemList] end; [] -> - throw(error_no_vcard_found) + throw(error_no_vcard_found); + error -> + throw(database_failure) end. get_vcard([<<"TEL">>, TelType], {_, _, _, OldEls}) -> @@ -1015,25 +1136,19 @@ set_vcard_content(User, Server, Data, SomeContent) -> [Bin | _] when is_binary(Bin) -> SomeContent; Bin when is_binary(Bin) -> [SomeContent] end, - [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}), - JID = jid:make(User, Server, get_module_resource(Server)), - IQ = #iq{type = get, xmlns = ?NS_VCARD}, - IQr = Module:Function(JID, JID, IQ), - %% Get old vcard - A4 = case IQr#iq.sub_el of + A4 = case mod_vcard:get_vcard(jid:nodeprep(User), jid:nameprep(Server)) of [A1] -> {_, _, _, A2} = A1, update_vcard_els(Data, ContentList, A2); [] -> - update_vcard_els(Data, ContentList, []) + update_vcard_els(Data, ContentList, []); + error -> + throw(database_failure) end, - %% Build new vcard SubEl = {xmlel, <<"vCard">>, [{<<"xmlns">>,<<"vcard-temp">>}], A4}, - IQ2 = #iq{type=set, sub_el = SubEl}, - - Module:Function(JID, JID, IQ2), + mod_vcard:set_vcard(User, jid:nameprep(Server), SubEl), ok. take_vcard_tel(TelType, [{xmlel, <<"TEL">>, _, SubEls}=OldEl | OldEls], NewEls, Taken) -> @@ -1094,11 +1209,7 @@ add_rosteritem(LU, LS, User, Server, Nick, Group, Subscription, Xattrs) -> subscribe(LU, LS, User, Server, Nick, Group, Subscription, _Xattrs) -> ItemEl = build_roster_item(User, Server, {add, Nick, Subscription, Group}), - mod_roster:set_items( - LU, LS, - {xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_ROSTER}], - [ItemEl]}). + mod_roster:set_items(LU, LS, #roster_query{items = [ItemEl]}). delete_rosteritem(LocalUser, LocalServer, User, Server) -> case unsubscribe(LocalUser, LocalServer, User, Server) of @@ -1111,11 +1222,7 @@ delete_rosteritem(LocalUser, LocalServer, User, Server) -> unsubscribe(LU, LS, User, Server) -> ItemEl = build_roster_item(User, Server, remove), - mod_roster:set_items( - LU, LS, - {xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_ROSTER}], - [ItemEl]}). + mod_roster:set_items(LU, LS, #roster_query{items = [ItemEl]}). %% ----------------------------- %% Get Roster @@ -1205,30 +1312,17 @@ push_roster_item(LU, LS, R, U, S, Action) -> ejabberd_router:route(jid:remove_resource(LJID), LJID, ResIQ). build_roster_item(U, S, {add, Nick, Subs, Group}) -> - GNames = binary:split(Group,<<";">>, [global]), - GroupEls = [{xmlel, <<"group">>, [], [{xmlcdata, GName}]} || GName <- GNames], - {xmlel, <<"item">>, - [{<<"jid">>, jid:to_string(jid:make(U, S, <<>>))}, - {<<"name">>, Nick}, - {<<"subscription">>, Subs}], - GroupEls - }; + Groups = binary:split(Group,<<";">>, [global]), + #roster_item{jid = jid:make(U, S), + name = Nick, + subscription = jlib:binary_to_atom(Subs), + groups = Groups}; build_roster_item(U, S, remove) -> - {xmlel, <<"item">>, - [{<<"jid">>, jid:to_string(jid:make(U, S, <<>>))}, - {<<"subscription">>, <<"remove">>}], - [] - }. + #roster_item{jid = jid:make(U, S), subscription = remove}. build_iq_roster_push(Item) -> - {xmlel, <<"iq">>, - [{<<"type">>, <<"set">>}, {<<"id">>, <<"push">>}], - [{xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_ROSTER}], - [Item] - } - ] - }. + #iq{type = set, id = <<"push">>, + sub_els = [#roster_query{items = [Item]}]}. build_broadcast(U, S, {add, _Nick, Subs, _Group}) -> build_broadcast(U, S, list_to_atom(binary_to_list(Subs))); @@ -1274,17 +1368,9 @@ get_last(User, Server) -> %% Cluth private_get(Username, Host, Element, Ns) -> - From = jid:make(Username, Host, <<>>), - To = jid:make(Username, Host, <<>>), - IQ = {iq, <<>>, get, ?NS_PRIVATE, <<>>, - {xmlel, <<"query">>, - [{<<"xmlns">>,?NS_PRIVATE}], - [{xmlel, Element, [{<<"xmlns">>, Ns}], []}]}}, - ResIq = mod_private:process_sm_iq(From, To, IQ), - [{xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_PRIVATE}], - [SubEl]}] = ResIq#iq.sub_el, - binary_to_list(fxml:element_to_binary(SubEl)). + Els = mod_private:get_data(jid:nodeprep(Username), jid:nameprep(Host), + [Ns, Element]), + binary_to_list(fxml:element_to_binary(xmpp:encode(#private{xml_els = Els}))). private_set(Username, Host, ElementString) -> case fxml_stream:parse_element(ElementString) of @@ -1297,13 +1383,9 @@ private_set(Username, Host, ElementString) -> end. private_set2(Username, Host, Xml) -> - From = jid:make(Username, Host, <<>>), - To = jid:make(Username, Host, <<>>), - IQ = {iq, <<>>, set, ?NS_PRIVATE, <<>>, - {xmlel, <<"query">>, - [{<<"xmlns">>, ?NS_PRIVATE}], - [Xml]}}, - mod_private:process_sm_iq(From, To, IQ), + NS = fxml:get_tag_attr_s(<<"xmlns">>, Xml), + mod_private:set_data(jid:nodeprep(Username), jid:nameprep(Host), + [{NS, Xml}]), ok. %%% @@ -1366,23 +1448,25 @@ send_message(Type, From, To, Subject, Body) -> ejabberd_router:route(FromJID, ToJID, Packet). build_packet(Type, Subject, Body) -> - Tail = if Subject == <<"">>; Type == <<"chat">> -> []; - true -> [{xmlel, <<"subject">>, [], [{xmlcdata, Subject}]}] - end, - {xmlel, <<"message">>, - [{<<"type">>, Type}, {<<"id">>, randoms:get_string()}], - [{xmlel, <<"body">>, [], [{xmlcdata, Body}]} | Tail] - }. + #message{type = jlib:binary_to_atom(Type), + body = xmpp:mk_text(Body), + subject = xmpp:mk_text(Subject)}. send_stanza(FromString, ToString, Stanza) -> - case fxml_stream:parse_element(Stanza) of - {error, Error} -> - {error, Error}; - XmlEl -> - #xmlel{attrs = Attrs} = XmlEl, - From = jid:from_string(proplists:get_value(<<"from">>, Attrs, FromString)), - To = jid:from_string(proplists:get_value(<<"to">>, Attrs, ToString)), - ejabberd_router:route(From, To, XmlEl) + try + #xmlel{} = El = fxml_stream:parse_element(Stanza), + #jid{} = From = jid:from_string(FromString), + #jid{} = To = jid:to_string(ToString), + Pkt = xmpp:decode(El, ?NS_CLIENT, [ignore_els]), + ejabberd_router:route(From, To, Pkt) + catch _:{xmpp_codec, Why} -> + io:format("incorrect stanza: ~s~n", [xmpp:format_error(Why)]), + {error, Why}; + _:{badmatch, {error, Why}} -> + io:format("invalid xml: ~p~n", [Why]), + {error, Why}; + _:{badmatch, error} -> + {error, "JID malformed"} end. send_stanza_c2s(Username, Host, Resource, Stanza) -> @@ -1398,17 +1482,16 @@ send_stanza_c2s(Username, Host, Resource, Stanza) -> end. privacy_set(Username, Host, QueryS) -> - From = jid:make(Username, Host, <<"">>), - To = jid:make(<<"">>, Host, <<"">>), + From = jid:make(Username, Host), + To = jid:make(Host), QueryEl = fxml_stream:parse_element(QueryS), - StanzaEl = {xmlel, <<"iq">>, [{<<"type">>, <<"set">>}], [QueryEl]}, - IQ = jlib:iq_query_info(StanzaEl), - ejabberd_hooks:run_fold( - privacy_iq_set, - Host, - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}, - [From, To, IQ] - ), + SubEl = xmpp:decode(QueryEl), + IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl], + from = From, to = To}, + ejabberd_hooks:run_fold(privacy_iq_set, + Host, + {error, xmpp:err_feature_not_implemented()}, + [IQ, #userlist{}]), ok. %%% @@ -1589,5 +1672,4 @@ is_glob_match(String, <<"!", Glob/binary>>) -> is_glob_match(String, Glob) -> is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)). -mod_opt_type(module_resource) -> fun (A) -> A end; -mod_opt_type(_) -> [module_resource]. +mod_opt_type(_) -> []. diff --git a/src/mod_announce.erl b/src/mod_announce.erl index d74c46bf9..2e182ed1e 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -31,20 +31,19 @@ -behaviour(gen_mod). --export([start/2, init/0, stop/1, export/1, import/1, - import/3, announce/3, send_motd/1, disco_identity/5, +-export([start/2, init/0, stop/1, export/1, import_info/0, + import_start/2, import/5, announce/3, send_motd/1, disco_identity/5, disco_features/5, disco_items/5, depends/2, send_announcement_to_all/3, announce_commands/4, announce_items/4, mod_opt_type/1]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). --include("adhoc.hrl"). +-include("xmpp.hrl"). -include("mod_announce.hrl"). -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #motd{} | #motd_users{}) -> ok | pass. +-callback import(binary(), binary(), [binary()]) -> ok. -callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}. -callback set_motd(binary(), xmlel()) -> {atomic, any()}. -callback delete_motd(binary()) -> {atomic, any()}. @@ -131,41 +130,36 @@ stop(Host) -> {wait, Proc}. %% Announcing via messages to a custom resource -announce(From, #jid{luser = <<>>} = To, #xmlel{name = <<"message">>} = Packet) -> +-spec announce(jid(), jid(), stanza()) -> ok | stop. +announce(From, #jid{luser = <<>>} = To, #message{} = Packet) -> Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME), - case To#jid.lresource of - <<"announce/all">> -> - Proc ! {announce_all, From, To, Packet}, - stop; - <<"announce/all-hosts/all">> -> - Proc ! {announce_all_hosts_all, From, To, Packet}, - stop; - <<"announce/online">> -> - Proc ! {announce_online, From, To, Packet}, - stop; - <<"announce/all-hosts/online">> -> - Proc ! {announce_all_hosts_online, From, To, Packet}, - stop; - <<"announce/motd">> -> - Proc ! {announce_motd, From, To, Packet}, - stop; - <<"announce/all-hosts/motd">> -> - Proc ! {announce_all_hosts_motd, From, To, Packet}, - stop; - <<"announce/motd/update">> -> - Proc ! {announce_motd_update, From, To, Packet}, - stop; - <<"announce/all-hosts/motd/update">> -> - Proc ! {announce_all_hosts_motd_update, From, To, Packet}, - stop; - <<"announce/motd/delete">> -> - Proc ! {announce_motd_delete, From, To, Packet}, - stop; - <<"announce/all-hosts/motd/delete">> -> - Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, - stop; - _ -> - ok + Res = case To#jid.lresource of + <<"announce/all">> -> + Proc ! {announce_all, From, To, Packet}; + <<"announce/all-hosts/all">> -> + Proc ! {announce_all_hosts_all, From, To, Packet}; + <<"announce/online">> -> + Proc ! {announce_online, From, To, Packet}; + <<"announce/all-hosts/online">> -> + Proc ! {announce_all_hosts_online, From, To, Packet}; + <<"announce/motd">> -> + Proc ! {announce_motd, From, To, Packet}; + <<"announce/all-hosts/motd">> -> + Proc ! {announce_all_hosts_motd, From, To, Packet}; + <<"announce/motd/update">> -> + Proc ! {announce_motd_update, From, To, Packet}; + <<"announce/all-hosts/motd/update">> -> + Proc ! {announce_all_hosts_motd_update, From, To, Packet}; + <<"announce/motd/delete">> -> + Proc ! {announce_motd_delete, From, To, Packet}; + <<"announce/all-hosts/motd/delete">> -> + Proc ! {announce_all_hosts_motd_delete, From, To, Packet}; + _ -> + ok + end, + case Res of + ok -> ok; + _ -> stop end; announce(_From, _To, _Packet) -> ok. @@ -173,10 +167,9 @@ announce(_From, _To, _Packet) -> %%------------------------------------------------------------------------- %% Announcing via ad-hoc commands -define(INFO_COMMAND(Lang, Node), - [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}, - {<<"name">>, get_title(Lang, Node)}]}]). + [#identity{category = <<"automation">>, + type = <<"command-node">>, + name = get_title(Lang, Node)}]). disco_identity(Acc, _From, _To, Node, Lang) -> LNode = tokenize(Node), @@ -210,7 +203,7 @@ disco_identity(Acc, _From, _To, Node, Lang) -> -define(INFO_RESULT(Allow, Feats, Lang), case Allow of deny -> - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; allow -> {result, Feats} end). @@ -226,7 +219,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) - acl:match_rule(global, Access2, From)} of {deny, deny} -> Txt = <<"Denied by ACL">>, - {error, ?ERRT_FORBIDDEN(Lang, Txt)}; + {error, xmpp:err_forbidden(Txt, Lang)}; _ -> {result, []} end @@ -269,26 +262,19 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) -> %%------------------------------------------------------------------------- -define(NODE_TO_ITEM(Lang, Server, Node), -( - #xmlel{ - name = <<"item">>, - attrs = [ - {<<"jid">>, Server}, - {<<"node">>, Node}, - {<<"name">>, get_title(Lang, Node)} - ] - } -)). + #disco_item{jid = jid:make(Server), + node = Node, + name = get_title(Lang, Node)}). -define(ITEMS_RESULT(Allow, Items, Lang), case Allow of deny -> - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; allow -> {result, Items} end). -disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<>>, Lang) -> +disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<"">>, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; @@ -353,7 +339,10 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) -> end. %%------------------------------------------------------------------------- - +-spec announce_items(empty | {error, stanza_error()} | {result, [disco_item()]}, + jid(), jid(), binary()) -> {error, stanza_error()} | + {result, [disco_item()]} | + empty. announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) -> Access1 = get_access(LServer), Nodes1 = case acl:match_rule(LServer, Access1, From) of @@ -393,15 +382,16 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) commands_result(Allow, From, To, Request) -> case Allow of deny -> - Lang = Request#adhoc_request.lang, - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + Lang = Request#adhoc_command.lang, + {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; allow -> announce_commands(From, To, Request) end. - +-spec announce_commands(adhoc_command(), jid(), jid(), adhoc_command()) -> + adhoc_command() | {error, stanza_error()}. announce_commands(Acc, From, #jid{lserver = LServer} = To, - #adhoc_request{ node = Node} = Request) -> + #adhoc_command{node = Node} = Request) -> LNode = tokenize(Node), F = fun() -> Access = get_access(global), @@ -440,59 +430,35 @@ announce_commands(Acc, From, #jid{lserver = LServer} = To, %%------------------------------------------------------------------------- announce_commands(From, To, - #adhoc_request{lang = Lang, + #adhoc_command{lang = Lang, node = Node, - action = Action, - xdata = XData} = Request) -> - %% If the "action" attribute is not present, it is - %% understood as "execute". If there was no - %% element in the first response (which there isn't in our - %% case), "execute" and "complete" are equivalent. - ActionIsExecute = lists:member(Action, [<<>>, <<"execute">>, <<"complete">>]), - if Action == <<"cancel">> -> + sid = SID, + xdata = XData, + action = Action} = Request) -> + ActionIsExecute = Action == execute orelse Action == complete, + if Action == cancel -> %% User cancels request - adhoc:produce_response(Request, #adhoc_response{status = canceled}); - XData == false, ActionIsExecute -> + #adhoc_command{status = canceled, lang = Lang, node = Node, + sid = SID}; + XData == undefined, ActionIsExecute -> %% User requests form - Elements = generate_adhoc_form(Lang, Node, To#jid.lserver), - adhoc:produce_response(Request, - #adhoc_response{status = executing,elements = [Elements]}); - XData /= false, ActionIsExecute -> - %% User returns form. - case jlib:parse_xdata_submit(XData) of - invalid -> - {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)}; - Fields -> - handle_adhoc_form(From, To, Request, Fields) - end; + Form = generate_adhoc_form(Lang, Node, To#jid.lserver), + #adhoc_command{status = executing, lang = Lang, node = Node, + sid = SID, xdata = Form}; + XData /= undefined, ActionIsExecute -> + handle_adhoc_form(From, To, Request); true -> - Txt = <<"Incorrect action or data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} + Txt = <<"Unexpected action">>, + {error, xmpp:err_bad_request(Txt, Lang)} end. --define(VVALUE(Val), -( - #xmlel{ - name = <<"value">>, - children = [{xmlcdata, Val}] - } -)). - -define(TVFIELD(Type, Var, Val), -( - #xmlel{ - name = <<"field">>, - attrs = [{<<"type">>, Type}, {<<"var">>, Var}], - children = vvaluel(Val) - } -)). - --define(HFIELD(), ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, ?NS_ADMIN)). + #xdata_field{type = Type, var = Var, values = vvaluel(Val)}). vvaluel(Val) -> case Val of <<>> -> []; - _ -> [?VVALUE(Val)] + _ -> [Val] end. generate_adhoc_form(Lang, Node, ServerHost) -> @@ -503,49 +469,27 @@ generate_adhoc_form(Lang, Node, ServerHost) -> true -> {<<>>, <<>>} end, - #xmlel{ - name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = [ - ?HFIELD(), - #xmlel{name = <<"title">>, children = [{xmlcdata, get_title(Lang, Node)}]} - ] - ++ - if (LNode == ?NS_ADMINL("delete-motd")) - or (LNode == ?NS_ADMINL("delete-motd-allhosts")) -> - [#xmlel{ - name = <<"field">>, - attrs = [ - {<<"var">>, <<"confirm">>}, - {<<"type">>, <<"boolean">>}, - {<<"label">>, - translate:translate(Lang, <<"Really delete message of the day?">>)} - ], - children = [ - #xmlel{name = <<"value">>, children = [{xmlcdata, <<"true">>}]} - ] - } - ]; - true -> - [#xmlel{ - name = <<"field">>, - attrs = [ - {<<"var">>, <<"subject">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, translate:translate(Lang, <<"Subject">>)}], - children = vvaluel(OldSubject) - }, - #xmlel{ - name = <<"field">>, - attrs = [ - {<<"var">>, <<"body">>}, - {<<"type">>, <<"text-multi">>}, - {<<"label">>, translate:translate(Lang, <<"Message body">>)}], - children = vvaluel(OldBody) - } - ] - - end}. + Fs = if (LNode == ?NS_ADMINL("delete-motd")) + or (LNode == ?NS_ADMINL("delete-motd-allhosts")) -> + [#xdata_field{type = boolean, + var = <<"confirm">>, + label = translate:translate( + Lang, <<"Really delete message of the day?">>), + values = [<<"true">>]}]; + true -> + [#xdata_field{type = 'text-single', + var = <<"subject">>, + label = translate:translate(Lang, <<"Subject">>), + values = vvaluel(OldSubject)}, + #xdata_field{type = 'text-multi', + var = <<"body">>, + label = translate:translate(Lang, <<"Message body">>), + values = vvaluel(OldBody)}] + end, + #xdata{type = form, + title = get_title(Lang, Node), + fields = [#xdata_field{type = hidden, var = <<"FORM_TYPE">>, + values = [?NS_ADMIN]}|Fs]}. join_lines([]) -> <<>>; @@ -558,103 +502,73 @@ join_lines([], Acc) -> iolist_to_binary(lists:reverse(tl(Acc))). handle_adhoc_form(From, #jid{lserver = LServer} = To, - #adhoc_request{lang = Lang, - node = Node, - sessionid = SessionID}, - Fields) -> - Confirm = case lists:keysearch(<<"confirm">>, 1, Fields) of - {value, {<<"confirm">>, [<<"true">>]}} -> - true; - {value, {<<"confirm">>, [<<"1">>]}} -> - true; - _ -> - false + #adhoc_command{lang = Lang, node = Node, + sid = SessionID, xdata = XData}) -> + Confirm = case xmpp_util:get_xdata_values(<<"confirm">>, XData) of + [<<"true">>] -> true; + [<<"1">>] -> true; + _ -> false end, - Subject = case lists:keysearch(<<"subject">>, 1, Fields) of - {value, {<<"subject">>, SubjectLines}} -> - %% There really shouldn't be more than one - %% subject line, but can we stop them? - join_lines(SubjectLines); - _ -> - <<>> - end, - Body = case lists:keysearch(<<"body">>, 1, Fields) of - {value, {<<"body">>, BodyLines}} -> - join_lines(BodyLines); - _ -> - <<>> - end, - Response = #adhoc_response{lang = Lang, - node = Node, - sessionid = SessionID, - status = completed}, - Packet = #xmlel{ - name = <<"message">>, - attrs = [{<<"type">>, <<"headline">>}], - children = if Subject /= <<>> -> - [#xmlel{name = <<"subject">>, children = [{xmlcdata, Subject}]}]; - true -> - [] - end - ++ - if Body /= <<>> -> - [#xmlel{name = <<"body">>, children = [{xmlcdata, Body}]}]; - true -> - [] - end - }, + Subject = join_lines(xmpp_util:get_xdata_values(<<"subject">>, XData)), + Body = join_lines(xmpp_util:get_xdata_values(<<"body">>, XData)), + Response = #adhoc_command{lang = Lang, node = Node, sid = SessionID, + status = completed}, + Packet = #message{type = headline, + body = xmpp:mk_text(Body), + subject = xmpp:mk_text(Subject)}, Proc = gen_mod:get_module_proc(LServer, ?PROCNAME), case {Node, Body} of {?NS_ADMIN_DELETE_MOTD, _} -> if Confirm -> Proc ! {announce_motd_delete, From, To, Packet}, - adhoc:produce_response(Response); + Response; true -> - adhoc:produce_response(Response) + Response end; {?NS_ADMIN_DELETE_MOTD_ALLHOSTS, _} -> if Confirm -> Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, - adhoc:produce_response(Response); + Response; true -> - adhoc:produce_response(Response) + Response end; {_, <<>>} -> %% An announce message with no body is definitely an operator error. %% Throw an error and give him/her a chance to send message again. - {error, ?ERRT_NOT_ACCEPTABLE(Lang, - <<"No body provided for announce message">>)}; + {error, xmpp:err_not_acceptable( + <<"No body provided for announce message">>, Lang)}; %% Now send the packet to ?PROCNAME. %% We don't use direct announce_* functions because it %% leads to large delay in response and queries processing {?NS_ADMIN_ANNOUNCE, _} -> Proc ! {announce_online, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} -> Proc ! {announce_all_hosts_online, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_ANNOUNCE_ALL, _} -> Proc ! {announce_all, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} -> Proc ! {announce_all_hosts_all, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_SET_MOTD, _} -> Proc ! {announce_motd, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_SET_MOTD_ALLHOSTS, _} -> Proc ! {announce_all_hosts_motd, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_EDIT_MOTD, _} -> Proc ! {announce_motd_update, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} -> Proc ! {announce_all_hosts_motd_update, From, To, Packet}, - adhoc:produce_response(Response); - _ -> + Response; + Junk -> %% This can't happen, as we haven't registered any other %% command nodes. - {error, ?ERR_INTERNAL_SERVER_ERROR} + ?ERROR_MSG("got unexpected node/body = ~p", [Junk]), + {error, xmpp:err_internal_server_error()} end. get_title(Lang, <<"announce">>) -> @@ -687,12 +601,9 @@ announce_all(From, To, Packet) -> Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> - Local = jid:make(<<>>, To#jid.server, <<>>), + Local = jid:make(To#jid.server), lists:foreach( fun({User, Server}) -> Dest = jid:make(User, Server, <<>>), @@ -704,12 +615,9 @@ announce_all_hosts_all(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> - Local = jid:make(<<>>, To#jid.server, <<>>), + Local = jid:make(To#jid.server), lists:foreach( fun({User, Server}) -> Dest = jid:make(User, Server, <<>>), @@ -722,10 +630,7 @@ announce_online(From, To, Packet) -> Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> announce_online1(ejabberd_sm:get_vh_session_list(Host), To#jid.server, @@ -736,10 +641,7 @@ announce_all_hosts_online(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> announce_online1(ejabberd_sm:dirty_get_sessions_list(), To#jid.server, @@ -747,7 +649,7 @@ announce_all_hosts_online(From, To, Packet) -> end. announce_online1(Sessions, Server, Packet) -> - Local = jid:make(<<>>, Server, <<>>), + Local = jid:make(Server), lists:foreach( fun({U, S, R}) -> Dest = jid:make(U, S, R), @@ -759,10 +661,7 @@ announce_motd(From, To, Packet) -> Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> announce_motd(Host, Packet) end. @@ -771,10 +670,7 @@ announce_all_hosts_motd(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> Hosts = ?MYHOSTS, [announce_motd(Host, Packet) || Host <- Hosts] @@ -793,10 +689,7 @@ announce_motd_update(From, To, Packet) -> Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> announce_motd_update(Host, Packet) end. @@ -805,10 +698,7 @@ announce_all_hosts_motd_update(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> Hosts = ?MYHOSTS, [announce_motd_update(Host, Packet) || Host <- Hosts] @@ -817,17 +707,14 @@ announce_all_hosts_motd_update(From, To, Packet) -> announce_motd_update(LServer, Packet) -> announce_motd_delete(LServer), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:set_motd(LServer, Packet). + Mod:set_motd(LServer, xmpp:encode(Packet)). announce_motd_delete(From, To, Packet) -> Host = To#jid.lserver, Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> announce_motd_delete(Host) end. @@ -836,10 +723,7 @@ announce_all_hosts_motd_delete(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), - ejabberd_router:route(To, From, Err); + route_forbidden_error(From, To, Packet); allow -> Hosts = ?MYHOSTS, [announce_motd_delete(Host) || Host <- Hosts] @@ -849,17 +733,24 @@ announce_motd_delete(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:delete_motd(LServer). +-spec send_motd(jid()) -> ok | {atomic, any()}. send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= <<>> -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:get_motd(LServer) of {ok, Packet} -> - case Mod:is_motd_user(LUser, LServer) of - false -> - Local = jid:make(<<>>, LServer, <<>>), - ejabberd_router:route(Local, JID, Packet), - Mod:set_motd_user(LUser, LServer); - true -> - ok + try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of + Msg -> + case Mod:is_motd_user(LUser, LServer) of + false -> + Local = jid:make(LServer), + ejabberd_router:route(Local, JID, Msg), + Mod:set_motd_user(LUser, LServer); + true -> + ok + end + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("failed to decode motd packet ~p: ~s", + [Packet, xmpp:format_error(Why)]) end; error -> ok @@ -871,31 +762,24 @@ get_stored_motd(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:get_motd(LServer) of {ok, Packet} -> - {fxml:get_subtag_cdata(Packet, <<"subject">>), - fxml:get_subtag_cdata(Packet, <<"body">>)}; + try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of + #message{body = Body, subject = Subject} -> + {xmpp:get_text(Subject), xmpp:get_text(Body)} + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("failed to decode motd packet ~p: ~s", + [Packet, xmpp:format_error(Why)]) + end; error -> {<<>>, <<>>} end. %% This function is similar to others, but doesn't perform any ACL verification send_announcement_to_all(Host, SubjectS, BodyS) -> - SubjectEls = if SubjectS /= <<>> -> - [#xmlel{name = <<"subject">>, children = [{xmlcdata, SubjectS}]}]; - true -> - [] - end, - BodyEls = if BodyS /= <<>> -> - [#xmlel{name = <<"body">>, children = [{xmlcdata, BodyS}]}]; - true -> - [] - end, - Packet = #xmlel{ - name = <<"message">>, - attrs = [{<<"type">>, <<"headline">>}], - children = SubjectEls ++ BodyEls - }, + Packet = #message{type = headline, + body = xmpp:mk_text(BodyS), + subject = xmpp:mk_text(SubjectS)}, Sessions = ejabberd_sm:dirty_get_sessions_list(), - Local = jid:make(<<>>, Host, <<>>), + Local = jid:make(Host), lists:foreach( fun({U, S, R}) -> Dest = jid:make(U, S, R), @@ -909,26 +793,32 @@ get_access(Host) -> fun(A) -> A end, none). --spec add_store_hint(xmlel()) -> xmlel(). - +-spec add_store_hint(stanza()) -> stanza(). add_store_hint(El) -> - Hint = #xmlel{name = <<"store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]}, - fxml:append_subtags(El, [Hint]). + xmpp:set_subtag(El, #hint{type = store}). + +-spec route_forbidden_error(jid(), jid(), stanza()) -> ok. +route_forbidden_error(From, To, Packet) -> + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang), + ejabberd_router:route_error(To, From, Packet, Err). %%------------------------------------------------------------------------- export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). +import_info() -> + [{<<"motd">>, 3}]. -import(LServer, DBType, LA) -> +import_start(LServer, DBType) -> Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, LA). + Mod:init(LServer, []). -mod_opt_type(access) -> - fun acl:access_rules_validator/1; +import(LServer, {sql, _}, DBType, Tab, List) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Tab, List). + +mod_opt_type(access) -> fun acl:access_rules_validator/1; mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(_) -> [access, db_type]. diff --git a/src/mod_announce_mnesia.erl b/src/mod_announce_mnesia.erl index c43eb853b..47753965d 100644 --- a/src/mod_announce_mnesia.erl +++ b/src/mod_announce_mnesia.erl @@ -11,9 +11,9 @@ %% API -export([init/2, set_motd_users/2, set_motd/2, delete_motd/1, - get_motd/1, is_motd_user/2, set_motd_user/2, import/2]). + get_motd/1, is_motd_user/2, set_motd_user/2, import/3]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_announce.hrl"). -include("logger.hrl"). @@ -21,11 +21,11 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(motd, + ejabberd_mnesia:create(?MODULE, motd, [{disc_copies, [node()]}, {attributes, record_info(fields, motd)}]), - mnesia:create_table(motd_users, + ejabberd_mnesia:create(?MODULE, motd_users, [{disc_copies, [node()]}, {attributes, record_info(fields, motd_users)}]), @@ -81,10 +81,11 @@ set_motd_user(LUser, LServer) -> end, mnesia:transaction(F). -import(_LServer, #motd{} = Motd) -> - mnesia:dirty_write(Motd); -import(_LServer, #motd_users{} = Users) -> - mnesia:dirty_write(Users). +import(LServer, <<"motd">>, [<<>>, XML, _TimeStamp]) -> + El = fxml_stream:parse_element(XML), + mnesia:dirty_write(#motd{server = LServer, packet = El}); +import(LServer, <<"motd">>, [LUser, <<>>, _TimeStamp]) -> + mnesia:dirty_write(#motd_users{us = {LUser, LServer}}). %%%=================================================================== %%% Internal functions diff --git a/src/mod_announce_riak.erl b/src/mod_announce_riak.erl index 7ced0b3ce..242adee0c 100644 --- a/src/mod_announce_riak.erl +++ b/src/mod_announce_riak.erl @@ -11,9 +11,9 @@ %% API -export([init/2, set_motd_users/2, set_motd/2, delete_motd/1, - get_motd/1, is_motd_user/2, set_motd_user/2, import/2]). + get_motd/1, is_motd_user/2, set_motd_user/2, import/3]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_announce.hrl"). %%%=================================================================== @@ -71,11 +71,13 @@ set_motd_user(LUser, LServer) -> #motd_users{us = {LUser, LServer}}, motd_users_schema(), [{'2i', [{<<"server">>, LServer}]}])}. -import(_LServer, #motd{} = Motd) -> - ejabberd_riak:put(Motd, motd_schema()); -import(_LServer, #motd_users{us = {_, S}} = Users) -> +import(LServer, <<"motd">>, [<<>>, XML, _TimeStamp]) -> + El = fxml_stream:parse_element(XML), + ejabberd_riak:put(#motd{server = LServer, packet = El}, motd_schema()); +import(LServer, <<"motd">>, [LUser, <<>>, _TimeStamp]) -> + Users = #motd_users{us = {LUser, LServer}}, ejabberd_riak:put(Users, motd_users_schema(), - [{'2i', [{<<"server">>, S}]}]). + [{'2i', [{<<"server">>, LServer}]}]). %%%=================================================================== %%% Internal functions diff --git a/src/mod_announce_sql.erl b/src/mod_announce_sql.erl index 762c97ad6..90e3f9d75 100644 --- a/src/mod_announce_sql.erl +++ b/src/mod_announce_sql.erl @@ -13,10 +13,10 @@ %% API -export([init/2, set_motd_users/2, set_motd/2, delete_motd/1, - get_motd/1, is_motd_user/2, set_motd_user/2, import/1, - import/2, export/1]). + get_motd/1, is_motd_user/2, set_motd_user/2, import/3, + export/1]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_announce.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -108,19 +108,8 @@ export(_Server) -> [] end}]. -import(LServer) -> - [{<<"select xml from motd where username='';">>, - fun([XML]) -> - El = fxml_stream:parse_element(XML), - #motd{server = LServer, packet = El} - end}, - {<<"select username from motd where xml='';">>, - fun([LUser]) -> - #motd_users{us = {LUser, LServer}} - end}]. - -import(_, _) -> - pass. +import(_, _, _) -> + ok. %%%=================================================================== %%% Internal functions diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl index 818d53259..d2b187d26 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -29,13 +29,13 @@ -protocol({xep, 191, '1.2'}). --export([start/2, stop/1, process_iq/3, - process_iq_set/4, process_iq_get/5, mod_opt_type/1, depends/2]). +-export([start/2, stop/1, process_iq/1, + process_iq_set/3, process_iq_get/3, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_privacy.hrl"). @@ -43,6 +43,8 @@ -callback unblock_by_filter(binary(), binary(), function()) -> {atomic, any()}. -callback process_blocklist_get(binary(), binary()) -> [listitem()] | error. +-type block_event() :: {block, [jid()]} | {unblock, [jid()]} | unblock_all. + start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), @@ -66,55 +68,64 @@ stop(Host) -> depends(_Host, _Opts) -> [{mod_privacy, hard}]. -process_iq(_From, _To, IQ) -> - SubEl = IQ#iq.sub_el, - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. +-spec process_iq(iq()) -> iq(). +process_iq(IQ) -> + xmpp:make_error(IQ, xmpp:err_not_allowed()). -process_iq_get(_, From, _To, - #iq{xmlns = ?NS_BLOCKING, lang = Lang, - sub_el = #xmlel{name = <<"blocklist">>}}, - _) -> +-spec process_iq_get({error, stanza_error()} | {result, xmpp_element() | undefined}, + iq(), userlist()) -> + {error, stanza_error()} | + {result, xmpp_element() | undefined}. +process_iq_get(_, #iq{lang = Lang, from = From, + sub_els = [#block_list{}]}, _) -> #jid{luser = LUser, lserver = LServer} = From, - {stop, process_blocklist_get(LUser, LServer, Lang)}; -process_iq_get(Acc, _, _, _, _) -> Acc. + process_blocklist_get(LUser, LServer, Lang); +process_iq_get(Acc, _, _) -> Acc. -process_iq_set(_, From, _To, - #iq{xmlns = ?NS_BLOCKING, lang = Lang, - sub_el = - #xmlel{name = SubElName, children = SubEls}}) -> +-spec process_iq_set({error, stanza_error()} | + {result, xmpp_element() | undefined} | + {result, xmpp_element() | undefined, userlist()}, + iq(), userlist()) -> + {error, stanza_error()} | + {result, xmpp_element() | undefined} | + {result, xmpp_element() | undefined, userlist()}. +process_iq_set(Acc, #iq{from = From, lang = Lang, sub_els = [SubEl]}, _) -> #jid{luser = LUser, lserver = LServer} = From, - Res = case {SubElName, fxml:remove_cdata(SubEls)} of - {<<"block">>, []} -> - Txt = <<"No items found in this query">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {<<"block">>, Els} -> - JIDs = parse_blocklist_items(Els, []), - process_blocklist_block(LUser, LServer, JIDs, Lang); - {<<"unblock">>, []} -> - process_blocklist_unblock_all(LUser, LServer, Lang); - {<<"unblock">>, Els} -> - JIDs = parse_blocklist_items(Els, []), - process_blocklist_unblock(LUser, LServer, JIDs, Lang); - _ -> - Txt = <<"Unknown blocking command">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end, - {stop, Res}; -process_iq_set(Acc, _, _, _) -> Acc. + case SubEl of + #block{items = []} -> + Txt = <<"No items found in this query">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + #block{items = Items} -> + JIDs = [jid:tolower(Item) || Item <- Items], + process_blocklist_block(LUser, LServer, JIDs, Lang); + #unblock{items = []} -> + process_blocklist_unblock_all(LUser, LServer, Lang); + #unblock{items = Items} -> + JIDs = [jid:tolower(Item) || Item <- Items], + process_blocklist_unblock(LUser, LServer, JIDs, Lang); + _ -> + Acc + end; +process_iq_set(Acc, _, _) -> Acc. +-spec list_to_blocklist_jids([listitem()], [ljid()]) -> [ljid()]. list_to_blocklist_jids([], JIDs) -> JIDs; list_to_blocklist_jids([#listitem{type = jid, action = deny, value = JID} = Item | Items], JIDs) -> - case Item of - #listitem{match_all = true} -> Match = true; - #listitem{match_iq = true, match_message = true, - match_presence_in = true, match_presence_out = true} -> - Match = true; - _ -> Match = false - end, + Match = case Item of + #listitem{match_all = true} -> + true; + #listitem{match_iq = true, + match_message = true, + match_presence_in = true, + match_presence_out = true} -> + true; + _ -> + false + end, if Match -> list_to_blocklist_jids(Items, [JID | JIDs]); true -> list_to_blocklist_jids(Items, JIDs) end; @@ -122,20 +133,10 @@ list_to_blocklist_jids([#listitem{type = jid, list_to_blocklist_jids([_ | Items], JIDs) -> list_to_blocklist_jids(Items, JIDs). -parse_blocklist_items([], JIDs) -> JIDs; -parse_blocklist_items([#xmlel{name = <<"item">>, - attrs = Attrs} - | Els], - JIDs) -> - case fxml:get_attr(<<"jid">>, Attrs) of - {value, JID1} -> - JID = jid:tolower(jid:from_string(JID1)), - parse_blocklist_items(Els, [JID | JIDs]); - false -> parse_blocklist_items(Els, JIDs) - end; -parse_blocklist_items([_ | Els], JIDs) -> - parse_blocklist_items(Els, JIDs). - +-spec process_blocklist_block(binary(), binary(), [ljid()], + binary()) -> + {error, stanza_error()} | + {result, undefined, userlist()}. process_blocklist_block(LUser, LServer, JIDs, Lang) -> Filter = fun (List) -> AlreadyBlocked = list_to_blocklist_jids(List, []), @@ -161,13 +162,17 @@ process_blocklist_block(LUser, LServer, JIDs, Lang) -> broadcast_list_update(LUser, LServer, Default, UserList), broadcast_blocklist_event(LUser, LServer, - {block, JIDs}), - {result, [], UserList}; + {block, [jid:make(J) || J <- JIDs]}), + {result, undefined, UserList}; _Err -> ?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]), - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} + {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)} end. +-spec process_blocklist_unblock_all(binary(), binary(), binary()) -> + {error, stanza_error()} | + {result, undefined} | + {result, undefined, userlist()}. process_blocklist_unblock_all(LUser, LServer, Lang) -> Filter = fun (List) -> lists:filter(fun (#listitem{action = A}) -> A =/= deny @@ -176,18 +181,22 @@ process_blocklist_unblock_all(LUser, LServer, Lang) -> end, Mod = db_mod(LServer), case Mod:unblock_by_filter(LUser, LServer, Filter) of - {atomic, ok} -> {result, []}; + {atomic, ok} -> {result, undefined}; {atomic, {ok, Default, List}} -> UserList = make_userlist(Default, List), broadcast_list_update(LUser, LServer, Default, UserList), broadcast_blocklist_event(LUser, LServer, unblock_all), - {result, [], UserList}; + {result, undefined, UserList}; _Err -> ?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]), - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} + {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)} end. +-spec process_blocklist_unblock(binary(), binary(), [ljid()], binary()) -> + {error, stanza_error()} | + {result, undefined} | + {result, undefined, userlist()}. process_blocklist_unblock(LUser, LServer, JIDs, Lang) -> Filter = fun (List) -> lists:filter(fun (#listitem{action = deny, type = jid, @@ -199,56 +208,50 @@ process_blocklist_unblock(LUser, LServer, JIDs, Lang) -> end, Mod = db_mod(LServer), case Mod:unblock_by_filter(LUser, LServer, Filter) of - {atomic, ok} -> {result, []}; + {atomic, ok} -> {result, undefined}; {atomic, {ok, Default, List}} -> UserList = make_userlist(Default, List), broadcast_list_update(LUser, LServer, Default, UserList), broadcast_blocklist_event(LUser, LServer, - {unblock, JIDs}), - {result, [], UserList}; + {unblock, [jid:make(J) || J <- JIDs]}), + {result, undefined, UserList}; _Err -> ?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]), - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} + {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)} end. +-spec make_userlist(binary(), [listitem()]) -> userlist(). make_userlist(Name, List) -> NeedDb = mod_privacy:is_list_needdb(List), #userlist{name = Name, list = List, needdb = NeedDb}. +-spec broadcast_list_update(binary(), binary(), binary(), userlist()) -> ok. broadcast_list_update(LUser, LServer, Name, UserList) -> - ejabberd_sm:route(jid:make(LUser, LServer, - <<"">>), + ejabberd_sm:route(jid:make(LUser, LServer, <<"">>), jid:make(LUser, LServer, <<"">>), {broadcast, {privacy_list, UserList, Name}}). +-spec broadcast_blocklist_event(binary(), binary(), block_event()) -> ok. broadcast_blocklist_event(LUser, LServer, Event) -> JID = jid:make(LUser, LServer, <<"">>), ejabberd_sm:route(JID, JID, {broadcast, {blocking, Event}}). +-spec process_blocklist_get(binary(), binary(), binary()) -> + {error, stanza_error()} | {result, block_list()}. process_blocklist_get(LUser, LServer, Lang) -> Mod = db_mod(LServer), case Mod:process_blocklist_get(LUser, LServer) of error -> - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; + {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)}; List -> - JIDs = list_to_blocklist_jids(List, []), - Items = lists:map(fun (JID) -> - ?DEBUG("JID: ~p", [JID]), - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - jid:to_string(JID)}], - children = []} - end, - JIDs), - {result, - [#xmlel{name = <<"blocklist">>, - attrs = [{<<"xmlns">>, ?NS_BLOCKING}], - children = Items}]} + LJIDs = list_to_blocklist_jids(List, []), + Items = [jid:make(J) || J <- LJIDs], + {result, #block_list{items = Items}} end. +-spec db_mod(binary()) -> module(). db_mod(LServer) -> DBType = gen_mod:db_type(LServer, mod_privacy), gen_mod:db_mod(DBType, ?MODULE). diff --git a/src/mod_blocking_mnesia.erl b/src/mod_blocking_mnesia.erl index 5a4bde64c..b64202717 100644 --- a/src/mod_blocking_mnesia.erl +++ b/src/mod_blocking_mnesia.erl @@ -14,7 +14,6 @@ -export([process_blocklist_block/3, unblock_by_filter/3, process_blocklist_get/2]). --include("jlib.hrl"). -include("mod_privacy.hrl"). %%%=================================================================== diff --git a/src/mod_blocking_riak.erl b/src/mod_blocking_riak.erl index 5dd5cfa92..1f15591ef 100644 --- a/src/mod_blocking_riak.erl +++ b/src/mod_blocking_riak.erl @@ -14,7 +14,6 @@ -export([process_blocklist_block/3, unblock_by_filter/3, process_blocklist_get/2]). --include("jlib.hrl"). -include("mod_privacy.hrl"). %%%=================================================================== diff --git a/src/mod_blocking_sql.erl b/src/mod_blocking_sql.erl index bffe5bd25..402d6de19 100644 --- a/src/mod_blocking_sql.erl +++ b/src/mod_blocking_sql.erl @@ -14,7 +14,6 @@ -export([process_blocklist_block/3, unblock_by_filter/3, process_blocklist_get/2]). --include("jlib.hrl"). -include("mod_privacy.hrl"). %%%=================================================================== diff --git a/src/mod_bosh.erl b/src/mod_bosh.erl new file mode 100644 index 000000000..038218739 --- /dev/null +++ b/src/mod_bosh.erl @@ -0,0 +1,296 @@ +%%%------------------------------------------------------------------- +%%% File : mod_bosh.erl +%%% Author : Evgeniy Khramtsov +%%% Purpose : This module acts as a bridge to ejabberd_bosh which implements +%%% the real stuff, this is to handle the new pluggable architecture +%%% for extending ejabberd's http service. +%%% Created : 20 Jul 2011 by Evgeniy Khramtsov +%%% +%%% +%%% 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(mod_bosh). + +-author('steve@zeank.in-berlin.de'). + +%%-define(ejabberd_debug, true). + +-behaviour(gen_server). +-behaviour(gen_mod). + +-export([start_link/0]). +-export([start/2, stop/1, process/2, open_session/2, + close_session/1, find_session/1]). + +-export([init/1, handle_call/3, handle_cast/2, + handle_info/2, terminate/2, code_change/3, + depends/2, mod_opt_type/1]). + +-include("ejabberd.hrl"). +-include("logger.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). +-include("jlib.hrl"). + +-include("ejabberd_http.hrl"). + +-include("bosh.hrl"). + +-record(bosh, {sid = <<"">> :: binary() | '_', + timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_', + pid = self() :: pid() | '$1'}). + +-record(state, {}). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +process([], #request{method = 'POST', data = <<>>}) -> + ?DEBUG("Bad Request: no data", []), + {400, ?HEADER(?CT_XML), + #xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, <<"400 Bad Request">>}]}}; +process([], + #request{method = 'POST', data = Data, ip = IP, headers = Hdrs}) -> + ?DEBUG("Incoming data: ~p", [Data]), + Type = get_type(Hdrs), + ejabberd_bosh:process_request(Data, IP, Type); +process([], #request{method = 'GET', data = <<>>}) -> + {200, ?HEADER(?CT_XML), get_human_html_xmlel()}; +process([], #request{method = 'OPTIONS', data = <<>>}) -> + {200, ?OPTIONS_HEADER, []}; +process(_Path, _Request) -> + ?DEBUG("Bad Request: ~p", [_Request]), + {400, ?HEADER(?CT_XML), + #xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, <<"400 Bad Request">>}]}}. + +get_human_html_xmlel() -> + Heading = <<"ejabberd ", (jlib:atom_to_binary(?MODULE))/binary>>, + #xmlel{name = <<"html">>, + attrs = + [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}], + children = + [#xmlel{name = <<"head">>, attrs = [], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = [{xmlcdata, Heading}]}]}, + #xmlel{name = <<"body">>, attrs = [], + children = + [#xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, Heading}]}, + #xmlel{name = <<"p">>, attrs = [], + children = + [{xmlcdata, <<"An implementation of ">>}, + #xmlel{name = <<"a">>, + attrs = + [{<<"href">>, + <<"http://xmpp.org/extensions/xep-0206.html">>}], + children = + [{xmlcdata, + <<"XMPP over BOSH (XEP-0206)">>}]}]}, + #xmlel{name = <<"p">>, attrs = [], + children = + [{xmlcdata, + <<"This web page is only informative. To " + "use HTTP-Bind you need a Jabber/XMPP " + "client that supports it.">>}]}]}]}. + +open_session(SID, Pid) -> + Session = #bosh{sid = SID, timestamp = p1_time_compat:timestamp(), pid = Pid}, + lists:foreach( + fun(Node) when Node == node() -> + gen_server:call(?MODULE, {write, Session}); + (Node) -> + cluster_send({?MODULE, Node}, {write, Session}) + end, ejabberd_cluster:get_nodes()). + +close_session(SID) -> + case mnesia:dirty_read(bosh, SID) of + [Session] -> + lists:foreach( + fun(Node) when Node == node() -> + gen_server:call(?MODULE, {delete, Session}); + (Node) -> + cluster_send({?MODULE, Node}, {delete, Session}) + end, ejabberd_cluster:get_nodes()); + [] -> + ok + end. + +write_session(#bosh{pid = Pid1, sid = SID, timestamp = T1} = S1) -> + case mnesia:dirty_read(bosh, SID) of + [#bosh{pid = Pid2, timestamp = T2} = S2] -> + if Pid1 == Pid2 -> + mnesia:dirty_write(S1); + T1 < T2 -> + cluster_send(Pid2, replaced), + mnesia:dirty_write(S1); + true -> + cluster_send(Pid1, replaced), + mnesia:dirty_write(S2) + end; + [] -> + mnesia:dirty_write(S1) + end. + +delete_session(#bosh{sid = SID, pid = Pid1}) -> + case mnesia:dirty_read(bosh, SID) of + [#bosh{pid = Pid2}] -> + if Pid1 == Pid2 -> + mnesia:dirty_delete(bosh, SID); + true -> + ok + end; + [] -> + ok + end. + +find_session(SID) -> + case mnesia:dirty_read(bosh, SID) of + [#bosh{pid = Pid}] -> + {ok, Pid}; + [] -> + error + end. + +start(Host, Opts) -> + setup_database(), + start_jiffy(Opts), + TmpSup = gen_mod:get_module_proc(Host, ?PROCNAME), + TmpSupSpec = {TmpSup, + {ejabberd_tmp_sup, start_link, [TmpSup, ejabberd_bosh]}, + permanent, infinity, supervisor, [ejabberd_tmp_sup]}, + ProcSpec = {?MODULE, + {?MODULE, start_link, []}, + transient, 2000, worker, [?MODULE]}, + case supervisor:start_child(ejabberd_sup, ProcSpec) of + {ok, _} -> + supervisor:start_child(ejabberd_sup, TmpSupSpec); + {error, {already_started, _}} -> + supervisor:start_child(ejabberd_sup, TmpSupSpec); + Err -> + Err + end. + +stop(Host) -> + TmpSup = gen_mod:get_module_proc(Host, ?PROCNAME), + supervisor:terminate_child(ejabberd_sup, TmpSup), + supervisor:delete_child(ejabberd_sup, TmpSup). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== +init([]) -> + {ok, #state{}}. + +handle_call({write, Session}, _From, State) -> + Res = write_session(Session), + {reply, Res, State}; +handle_call({delete, Session}, _From, State) -> + Res = delete_session(Session), + {reply, Res, State}; +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({write, Session}, State) -> + write_session(Session), + {noreply, State}; +handle_info({delete, Session}, State) -> + delete_session(Session), + {noreply, State}; +handle_info(_Info, State) -> + ?ERROR_MSG("got unexpected info: ~p", [_Info]), + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +setup_database() -> + case catch mnesia:table_info(bosh, attributes) of + [sid, pid] -> + mnesia:delete_table(bosh); + _ -> + ok + end, + ejabberd_mnesia:create(?MODULE, bosh, + [{ram_copies, [node()]}, {local_content, true}, + {attributes, record_info(fields, bosh)}]), + mnesia:add_table_copy(bosh, node(), ram_copies). + +start_jiffy(Opts) -> + case gen_mod:get_opt(json, Opts, + fun(false) -> false; + (true) -> true + end, false) of + false -> + ok; + true -> + case catch ejabberd:start_app(jiffy) of + ok -> + ok; + Err -> + ?WARNING_MSG("Failed to start JSON codec (jiffy): ~p. " + "JSON support will be disabled", [Err]) + end + end. + +get_type(Hdrs) -> + try + {_, S} = lists:keyfind('Content-Type', 1, Hdrs), + [T|_] = str:tokens(S, <<";">>), + [_, <<"json">>] = str:tokens(T, <<"/">>), + json + catch _:_ -> + xml + end. + +cluster_send(NodePid, Msg) -> + erlang:send(NodePid, Msg, [noconnect, nosuspend]). + +depends(_Host, _Opts) -> + []. + +mod_opt_type(json) -> + fun (false) -> false; + (true) -> true + end; +mod_opt_type(max_concat) -> + fun (unlimited) -> unlimited; + (N) when is_integer(N), N > 0 -> N + end; +mod_opt_type(max_inactivity) -> + fun (I) when is_integer(I), I > 0 -> I end; +mod_opt_type(max_pause) -> + fun (I) when is_integer(I), I > 0 -> I end; +mod_opt_type(prebind) -> + fun (B) when is_boolean(B) -> B end; +mod_opt_type(_) -> + [json, max_concat, max_inactivity, max_pause, prebind]. diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 3ed3149bb..3a4492f5c 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -54,33 +54,17 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). +-include("mod_caps.hrl"). -define(PROCNAME, ejabberd_mod_caps). -define(BAD_HASH_LIFETIME, 600). --record(caps, -{ - node = <<"">> :: binary(), - version = <<"">> :: binary(), - hash = <<"">> :: binary(), - exts = [] :: [binary()] -}). - --type caps() :: #caps{}. - --export_type([caps/0]). - --record(caps_features, -{ - node_pair = {<<"">>, <<"">>} :: {binary(), binary()}, - features = [] :: [binary()] | pos_integer() -}). - -record(state, {host = <<"">> :: binary()}). -callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), {binary(), binary()}, [binary() | pos_integer()]) -> ok. -callback caps_read(binary(), {binary(), binary()}) -> {ok, non_neg_integer() | [binary()]} | error. -callback caps_write(binary(), {binary(), binary()}, @@ -103,6 +87,7 @@ stop(Host) -> supervisor:terminate_child(ejabberd_sup, Proc), supervisor:delete_child(ejabberd_sup, Proc). +-spec get_features(binary(), nothing | caps()) -> [binary()]. get_features(_Host, nothing) -> []; get_features(Host, #caps{node = Node, version = Version, exts = Exts}) -> @@ -119,65 +104,37 @@ get_features(Host, #caps{node = Node, version = Version, end, [], SubNodes). --spec read_caps([xmlel()]) -> nothing | caps(). +-spec read_caps(#presence{}) -> nothing | caps(). +read_caps(Presence) -> + case xmpp:get_subtag(Presence, #caps{}) of + false -> nothing; + Caps -> Caps + end. -read_caps(Els) -> read_caps(Els, nothing). - -read_caps([#xmlel{name = <<"c">>, attrs = Attrs} - | Tail], - Result) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_CAPS -> - Node = fxml:get_attr_s(<<"node">>, Attrs), - Version = fxml:get_attr_s(<<"ver">>, Attrs), - Hash = fxml:get_attr_s(<<"hash">>, Attrs), - Exts = str:tokens(fxml:get_attr_s(<<"ext">>, Attrs), - <<" ">>), - read_caps(Tail, - #caps{node = Node, hash = Hash, version = Version, - exts = Exts}); - _ -> read_caps(Tail, Result) - end; -read_caps([#xmlel{name = <<"x">>, attrs = Attrs} - | Tail], - Result) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC_USER -> nothing; - _ -> read_caps(Tail, Result) - end; -read_caps([_ | Tail], Result) -> - read_caps(Tail, Result); -read_caps([], Result) -> Result. - -user_send_packet(#xmlel{name = <<"presence">>, attrs = Attrs, - children = Els} = Pkt, +-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza(). +user_send_packet(#presence{type = available} = Pkt, _C2SState, #jid{luser = User, lserver = Server} = From, #jid{luser = User, lserver = Server, lresource = <<"">>}) -> - Type = fxml:get_attr_s(<<"type">>, Attrs), - if Type == <<"">>; Type == <<"available">> -> - case read_caps(Els) of - nothing -> ok; - #caps{version = Version, exts = Exts} = Caps -> - feature_request(Server, From, Caps, [Version | Exts]) - end; - true -> ok + case read_caps(Pkt) of + nothing -> ok; + #caps{version = Version, exts = Exts} = Caps -> + feature_request(Server, From, Caps, [Version | Exts]) end, Pkt; user_send_packet(Pkt, _C2SState, _From, _To) -> Pkt. -user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs, - children = Els} = Pkt, +-spec user_receive_packet(stanza(), ejabberd_c2s:state(), + jid(), jid(), jid()) -> stanza(). +user_receive_packet(#presence{type = available} = Pkt, _C2SState, #jid{lserver = Server}, From, _To) -> - Type = fxml:get_attr_s(<<"type">>, Attrs), IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS), - if IsRemote and - ((Type == <<"">>) or (Type == <<"available">>)) -> - case read_caps(Els) of + if IsRemote -> + case read_caps(Pkt) of nothing -> ok; #caps{version = Version, exts = Exts} = Caps -> feature_request(Server, From, Caps, [Version | Exts]) @@ -188,20 +145,19 @@ user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs, user_receive_packet(Pkt, _C2SState, _JID, _From, _To) -> Pkt. --spec caps_stream_features([xmlel()], binary()) -> [xmlel()]. +-spec caps_stream_features([xmpp_element()], binary()) -> [xmpp_element()]. caps_stream_features(Acc, MyHost) -> case make_my_disco_hash(MyHost) of <<"">> -> Acc; Hash -> - [#xmlel{name = <<"c">>, - attrs = - [{<<"xmlns">>, ?NS_CAPS}, {<<"hash">>, <<"sha-1">>}, - {<<"node">>, ?EJABBERD_URI}, {<<"ver">>, Hash}], - children = []} - | Acc] + [#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI, version = Hash}|Acc] end. +-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty, + jid(), jid(), + binary(), binary()) -> + {error, stanza_error()} | {result, [binary()]} | empty. disco_features(Acc, From, To, Node, Lang) -> case is_valid_node(Node) of true -> @@ -212,6 +168,9 @@ disco_features(Acc, From, To, Node, Lang) -> Acc end. +-spec disco_identity([identity()], jid(), jid(), + binary(), binary()) -> + [identity()]. disco_identity(Acc, From, To, Node, Lang) -> case is_valid_node(Node) of true -> @@ -222,24 +181,28 @@ disco_identity(Acc, From, To, Node, Lang) -> Acc end. -disco_info(Acc, Host, Module, Node, Lang) -> +-spec disco_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()]; + ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()]. +disco_info(Acc, Host, Module, Node, Lang) when is_atom(Module) -> case is_valid_node(Node) of true -> ejabberd_hooks:run_fold(disco_info, Host, [], [Host, Module, <<"">>, Lang]); false -> Acc - end. + end; +disco_info(Acc, _, _, _Node, _Lang) -> + Acc. +-spec c2s_presence_in(ejabberd_c2s:state(), {jid(), jid(), presence()}) -> + ejabberd_c2s:state(). c2s_presence_in(C2SState, - {From, To, {_, _, Attrs, Els}}) -> - Type = fxml:get_attr_s(<<"type">>, Attrs), + {From, To, #presence{type = Type} = Presence}) -> Subscription = ejabberd_c2s:get_subscription(From, C2SState), - Insert = ((Type == <<"">>) or (Type == <<"available">>)) + Insert = (Type == available) and ((Subscription == both) or (Subscription == to)), - Delete = (Type == <<"unavailable">>) or - (Type == <<"error">>), + Delete = (Type == unavailable) or (Type == error), if Insert or Delete -> LFrom = jid:tolower(From), Rs = case ejabberd_c2s:get_aux_field(caps_resources, @@ -248,7 +211,7 @@ c2s_presence_in(C2SState, {ok, Rs1} -> Rs1; error -> gb_trees:empty() end, - Caps = read_caps(Els), + Caps = read_caps(Presence), NewRs = case Caps of nothing when Insert == true -> Rs; _ when Insert == true -> @@ -272,6 +235,9 @@ c2s_presence_in(C2SState, true -> C2SState end. +-spec c2s_filter_packet(boolean(), binary(), ejabberd_c2s:state(), + {pep_message, binary()}, jid(), stanza()) -> + boolean(). c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) -> case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of {ok, Rs} -> @@ -287,6 +253,9 @@ c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) -> end; c2s_filter_packet(Acc, _, _, _, _, _) -> Acc. +-spec c2s_broadcast_recipients([ljid()], binary(), ejabberd_c2s:state(), + {pep_message, binary()}, jid(), stanza()) -> + [ljid()]. c2s_broadcast_recipients(InAcc, Host, C2SState, {pep_message, Feature}, _From, _Packet) -> case ejabberd_c2s:get_aux_field(caps_resources, @@ -306,6 +275,7 @@ c2s_broadcast_recipients(InAcc, Host, C2SState, end; c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc. +-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}]. depends(_Host, _Opts) -> []. @@ -377,6 +347,7 @@ terminate(_Reason, State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. +-spec feature_request(binary(), jid(), caps(), [binary()]) -> any(). feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) -> Node = Caps#caps.node, @@ -392,15 +363,9 @@ feature_request(Host, From, Caps, _ -> true end, if NeedRequest -> - IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, ?NS_DISCO_INFO}, - {<<"node">>, - <>}], - children = []}]}, + IQ = #iq{type = get, + sub_els = [#disco_info{node = <>}]}, cache_tab:insert(caps_features, NodePair, now_ts(), caps_write_fun(Host, NodePair, now_ts())), F = fun (IQReply) -> @@ -415,39 +380,43 @@ feature_request(Host, From, Caps, end; feature_request(_Host, _From, _Caps, []) -> ok. -feature_response(#iq{type = result, - sub_el = [#xmlel{children = Els}]}, +-spec feature_response(iq(), binary(), jid(), caps(), [binary()]) -> any(). +feature_response(#iq{type = result, sub_els = [El]}, Host, From, Caps, [SubNode | SubNodes]) -> NodePair = {Caps#caps.node, SubNode}, - case check_hash(Caps, Els) of - true -> - Features = lists:flatmap(fun (#xmlel{name = - <<"feature">>, - attrs = FAttrs}) -> - [fxml:get_attr_s(<<"var">>, FAttrs)]; - (_) -> [] - end, - Els), - cache_tab:insert(caps_features, NodePair, - Features, - caps_write_fun(Host, NodePair, Features)); - false -> ok + try + DiscoInfo = xmpp:decode(El), + case check_hash(Caps, DiscoInfo) of + true -> + Features = DiscoInfo#disco_info.features, + cache_tab:insert(caps_features, NodePair, + Features, + caps_write_fun(Host, NodePair, Features)); + false -> ok + end + catch _:{xmpp_codec, _Why} -> + ok end, feature_request(Host, From, Caps, SubNodes); feature_response(_IQResult, Host, From, Caps, [_SubNode | SubNodes]) -> feature_request(Host, From, Caps, SubNodes). +-spec caps_read_fun(binary(), {binary(), binary()}) + -> fun(() -> {ok, [binary()] | non_neg_integer()} | error). caps_read_fun(Host, Node) -> LServer = jid:nameprep(Host), Mod = gen_mod:db_mod(LServer, ?MODULE), fun() -> Mod:caps_read(LServer, Node) end. +-spec caps_write_fun(binary(), {binary(), binary()}, + [binary()] | non_neg_integer()) -> fun(). caps_write_fun(Host, Node, Features) -> LServer = jid:nameprep(Host), Mod = gen_mod:db_mod(LServer, ?MODULE), fun() -> Mod:caps_write(LServer, Node, Features) end. +-spec make_my_disco_hash(binary()) -> binary(). make_my_disco_hash(Host) -> JID = jid:make(<<"">>, Host, <<"">>), case {ejabberd_hooks:run_fold(disco_local_features, @@ -458,125 +427,79 @@ make_my_disco_hash(Host) -> [Host, undefined, <<"">>, <<"">>])} of {{result, Features}, Identities, Info} -> - Feats = lists:map(fun ({{Feat, _Host}}) -> - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, Feat}], - children = []}; - (Feat) -> - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, Feat}], - children = []} + Feats = lists:map(fun ({{Feat, _Host}}) -> Feat; + (Feat) -> Feat end, Features), - make_disco_hash(Identities ++ Info ++ Feats, sha1); + DiscoInfo = #disco_info{identities = Identities, + features = Feats, + xdata = Info}, + make_disco_hash(DiscoInfo, sha); _Err -> <<"">> end. -make_disco_hash(DiscoEls, Algo) -> - Concat = list_to_binary([concat_identities(DiscoEls), - concat_features(DiscoEls), concat_info(DiscoEls)]), +-type digest_type() :: md5 | sha | sha224 | sha256 | sha384 | sha512. +-spec make_disco_hash(disco_info(), digest_type()) -> binary(). +make_disco_hash(DiscoInfo, Algo) -> + Concat = list_to_binary([concat_identities(DiscoInfo), + concat_features(DiscoInfo), concat_info(DiscoInfo)]), jlib:encode_base64(case Algo of md5 -> erlang:md5(Concat); - sha1 -> p1_sha:sha1(Concat); + sha -> p1_sha:sha1(Concat); sha224 -> p1_sha:sha224(Concat); sha256 -> p1_sha:sha256(Concat); sha384 -> p1_sha:sha384(Concat); sha512 -> p1_sha:sha512(Concat) end). -check_hash(Caps, Els) -> +-spec check_hash(caps(), disco_info()) -> boolean(). +check_hash(Caps, DiscoInfo) -> case Caps#caps.hash of <<"md5">> -> - Caps#caps.version == make_disco_hash(Els, md5); + Caps#caps.version == make_disco_hash(DiscoInfo, md5); <<"sha-1">> -> - Caps#caps.version == make_disco_hash(Els, sha1); + Caps#caps.version == make_disco_hash(DiscoInfo, sha); <<"sha-224">> -> - Caps#caps.version == make_disco_hash(Els, sha224); + Caps#caps.version == make_disco_hash(DiscoInfo, sha224); <<"sha-256">> -> - Caps#caps.version == make_disco_hash(Els, sha256); + Caps#caps.version == make_disco_hash(DiscoInfo, sha256); <<"sha-384">> -> - Caps#caps.version == make_disco_hash(Els, sha384); + Caps#caps.version == make_disco_hash(DiscoInfo, sha384); <<"sha-512">> -> - Caps#caps.version == make_disco_hash(Els, sha512); + Caps#caps.version == make_disco_hash(DiscoInfo, sha512); _ -> true end. -concat_features(Els) -> - lists:usort(lists:flatmap(fun (#xmlel{name = - <<"feature">>, - attrs = Attrs}) -> - [[fxml:get_attr_s(<<"var">>, Attrs), $<]]; - (_) -> [] - end, - Els)). +-spec concat_features(disco_info()) -> iolist(). +concat_features(#disco_info{features = Features}) -> + lists:usort([[Feat, $<] || Feat <- Features]). -concat_identities(Els) -> - lists:sort(lists:flatmap(fun (#xmlel{name = - <<"identity">>, - attrs = Attrs}) -> - [[fxml:get_attr_s(<<"category">>, Attrs), - $/, fxml:get_attr_s(<<"type">>, Attrs), - $/, - fxml:get_attr_s(<<"xml:lang">>, Attrs), - $/, fxml:get_attr_s(<<"name">>, Attrs), - $<]]; - (_) -> [] - end, - Els)). +-spec concat_identities(disco_info()) -> iolist(). +concat_identities(#disco_info{identities = Identities}) -> + lists:sort( + [[Cat, $/, T, $/, Lang, $/, Name, $<] || + #identity{category = Cat, type = T, + lang = Lang, name = Name} <- Identities]). -concat_info(Els) -> - lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>, - attrs = Attrs, children = Fields}) -> - case {fxml:get_attr_s(<<"xmlns">>, Attrs), - fxml:get_attr_s(<<"type">>, Attrs)} - of - {?NS_XDATA, <<"result">>} -> - [concat_xdata_fields(Fields)]; - _ -> [] - end; - (_) -> [] - end, - Els)). +-spec concat_info(disco_info()) -> iolist(). +concat_info(#disco_info{xdata = Xs}) -> + lists:sort( + [concat_xdata_fields(X) || #xdata{type = result} = X <- Xs]). -concat_xdata_fields(Fields) -> - [Form, Res] = lists:foldl(fun (#xmlel{name = - <<"field">>, - attrs = Attrs, children = Els} = - El, - [FormType, VarFields] = Acc) -> - case fxml:get_attr_s(<<"var">>, Attrs) of - <<"">> -> Acc; - <<"FORM_TYPE">> -> - [fxml:get_subtag_cdata(El, - <<"value">>), - VarFields]; - Var -> - [FormType, - [[[Var, $<], - lists:sort(lists:flatmap(fun - (#xmlel{name - = - <<"value">>, - children - = - VEls}) -> - [[fxml:get_cdata(VEls), - $<]]; - (_) -> - [] - end, - Els))] - | VarFields]] - end; - (_, Acc) -> Acc - end, - [<<"">>, []], Fields), +-spec concat_xdata_fields(xdata()) -> iolist(). +concat_xdata_fields(#xdata{fields = Fields} = X) -> + Form = xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X), + Res = [[Var, $<, lists:sort([[Val, $<] || Val <- Values])] + || #xdata_field{var = Var, values = Values} <- Fields, + is_binary(Var), Var /= <<"FORM_TYPE">>], [Form, $<, lists:sort(Res)]. +-spec gb_trees_fold(fun((_, _, T) -> T), T, gb_trees:tree()) -> T. gb_trees_fold(F, Acc, Tree) -> Iter = gb_trees:iterator(Tree), gb_trees_fold_iter(F, Acc, Iter). +-spec gb_trees_fold_iter(fun((_, _, T) -> T), T, gb_trees:iter()) -> T. gb_trees_fold_iter(F, Acc, Iter) -> case gb_trees:next(Iter) of {Key, Val, NewIter} -> @@ -585,9 +508,11 @@ gb_trees_fold_iter(F, Acc, Iter) -> _ -> Acc end. +-spec now_ts() -> integer(). now_ts() -> p1_time_compat:system_time(seconds). +-spec is_valid_node(binary()) -> boolean(). is_valid_node(Node) -> case str:tokens(Node, <<"#">>) of [?EJABBERD_URI|_] -> @@ -596,9 +521,6 @@ is_valid_node(Node) -> false end. -caps_features_schema() -> - {record_info(fields, caps_features), #caps_features{}}. - export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). @@ -614,7 +536,7 @@ import_start(LServer, DBType) -> import(_LServer, {sql, _}, _DBType, <<"caps_features">>, [Node, SubNode, Feature, _TimeStamp]) -> - Feature1 = case catch jlib:binary_to_integer(Feature) of + Feature1 = case catch binary_to_integer(Feature) of I when is_integer(I), I>0 -> I; _ -> Feature end, @@ -630,24 +552,8 @@ import_next(_LServer, _DBType, '$end_of_table') -> ok; import_next(LServer, DBType, NodePair) -> Features = [F || {_, F} <- ets:lookup(caps_features_tmp, NodePair)], - case Features of - [I] when is_integer(I), DBType == mnesia -> - mnesia:dirty_write( - #caps_features{node_pair = NodePair, features = I}); - [I] when is_integer(I), DBType == riak -> - ejabberd_riak:put( - #caps_features{node_pair = NodePair, features = I}, - caps_features_schema()); - _ when DBType == mnesia -> - mnesia:dirty_write( - #caps_features{node_pair = NodePair, features = Features}); - _ when DBType == riak -> - ejabberd_riak:put( - #caps_features{node_pair = NodePair, features = Features}, - caps_features_schema()); - _ when DBType == sql -> - ok - end, + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, NodePair, Features), import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)). mod_opt_type(cache_life_time) -> diff --git a/src/mod_caps_mnesia.erl b/src/mod_caps_mnesia.erl index 0bf04b2c3..ed22841e8 100644 --- a/src/mod_caps_mnesia.erl +++ b/src/mod_caps_mnesia.erl @@ -10,7 +10,7 @@ -behaviour(mod_caps). %% API --export([init/2, caps_read/2, caps_write/3]). +-export([init/2, caps_read/2, caps_write/3, import/3]). -include("mod_caps.hrl"). -include("logger.hrl"). @@ -27,7 +27,7 @@ init(_Host, _Opts) -> _ -> mnesia:delete_table(caps_features) end, - mnesia:create_table(caps_features, + ejabberd_mnesia:create(?MODULE, caps_features, [{disc_only_copies, [node()]}, {local_content, true}, {attributes, @@ -46,6 +46,13 @@ caps_write(_LServer, Node, Features) -> mnesia:dirty_write(#caps_features{node_pair = Node, features = Features}). +import(_LServer, NodePair, [I]) when is_integer(I) -> + mnesia:dirty_write( + #caps_features{node_pair = NodePair, features = I}); +import(_LServer, NodePair, Features) -> + mnesia:dirty_write( + #caps_features{node_pair = NodePair, features = Features}). + %%%=================================================================== %%% Internal functions %%%=================================================================== diff --git a/src/mod_caps_riak.erl b/src/mod_caps_riak.erl index 6e59ba867..a504bb6ce 100644 --- a/src/mod_caps_riak.erl +++ b/src/mod_caps_riak.erl @@ -10,7 +10,7 @@ -behaviour(mod_caps). %% API --export([init/2, caps_read/2, caps_write/3]). +-export([init/2, caps_read/2, caps_write/3, import/3]). -include("mod_caps.hrl"). @@ -31,6 +31,15 @@ caps_write(_LServer, Node, Features) -> features = Features}, caps_features_schema()). +import(_LServer, NodePair, [I]) when is_integer(I) -> + ejabberd_riak:put( + #caps_features{node_pair = NodePair, features = I}, + caps_features_schema()); +import(_LServer, NodePair, Features) -> + ejabberd_riak:put( + #caps_features{node_pair = NodePair, features = Features}, + caps_features_schema()). + %%%=================================================================== %%% Internal functions %%%=================================================================== diff --git a/src/mod_caps_sql.erl b/src/mod_caps_sql.erl index 5faff98b6..fee0f0960 100644 --- a/src/mod_caps_sql.erl +++ b/src/mod_caps_sql.erl @@ -12,7 +12,7 @@ -compile([{parse_transform, ejabberd_sql_pt}]). %% API --export([init/2, caps_read/2, caps_write/3, export/1]). +-export([init/2, caps_read/2, caps_write/3, export/1, import/3]). -include("mod_caps.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -29,7 +29,7 @@ caps_read(LServer, {Node, SubNode}) -> ?SQL("select @(feature)s from caps_features where" " node=%(Node)s and subnode=%(SubNode)s")) of {selected, [{H}|_] = Fs} -> - case catch jlib:binary_to_integer(H) of + case catch binary_to_integer(H) of Int when is_integer(Int), Int>=0 -> {ok, Int}; _ -> @@ -53,12 +53,15 @@ export(_Server) -> [] end}]. +import(_, _, _) -> + ok. + %%%=================================================================== %%% Internal functions %%%=================================================================== sql_write_features_t({Node, SubNode}, Features) -> NewFeatures = if is_integer(Features) -> - [jlib:integer_to_binary(Features)]; + [integer_to_binary(Features)]; true -> Features end, diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl index e1e6d63a2..5839a65b2 100644 --- a/src/mod_carboncopy.erl +++ b/src/mod_carboncopy.erl @@ -36,37 +36,29 @@ stop/1]). -export([user_send_packet/4, user_receive_packet/5, - iq_handler2/3, iq_handler1/3, remove_connection/4, + iq_handler/1, remove_connection/4, is_carbon_copy/1, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -define(PROCNAME, ?MODULE). +-type direction() :: sent | received. + -callback init(binary(), gen_mod:opts()) -> any(). -callback enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}. -callback disable(binary(), binary(), binary()) -> ok | {error, any()}. -callback list(binary(), binary()) -> [{binary(), binary()}]. -is_carbon_copy(Packet) -> - is_carbon_copy(Packet, <<"sent">>) orelse - is_carbon_copy(Packet, <<"received">>). - -is_carbon_copy(Packet, Direction) -> - case fxml:get_subtag(Packet, Direction) of - #xmlel{name = Direction, attrs = Attrs} -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_CARBONS_2 -> true; - ?NS_CARBONS_1 -> true; - _ -> false - end; - _ -> false - end. +-spec is_carbon_copy(stanza()) -> boolean(). +is_carbon_copy(#message{meta = #{carbon_copy := true}}) -> + true; +is_carbon_copy(_) -> + false. start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts,fun gen_iq_handler:check_type/1, one_queue), - mod_disco:register_feature(Host, ?NS_CARBONS_1), mod_disco:register_feature(Host, ?NS_CARBONS_2), Mod = gen_mod:db_mod(Host, ?MODULE), Mod:init(Host, Opts), @@ -74,54 +66,53 @@ start(Host, Opts) -> %% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90) ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89), ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler2, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1, ?MODULE, iq_handler1, IQDisc). + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler, IQDisc). stop(Host) -> - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2), mod_disco:unregister_feature(Host, ?NS_CARBONS_2), - mod_disco:unregister_feature(Host, ?NS_CARBONS_1), %% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90) ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89), ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89), ejabberd_hooks:delete(unset_presence_hook,Host, ?MODULE, remove_connection, 10). -iq_handler2(From, To, IQ) -> - iq_handler(From, To, IQ, ?NS_CARBONS_2). -iq_handler1(From, To, IQ) -> - iq_handler(From, To, IQ, ?NS_CARBONS_1). - -iq_handler(From, _To, - #iq{type=set, lang = Lang, - sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)-> - ?DEBUG("carbons IQ received: ~p", [IQ]), +-spec iq_handler(iq()) -> iq(). +iq_handler(#iq{type = set, lang = Lang, from = From, + sub_els = [El]} = IQ) when is_record(El, carbons_enable); + is_record(El, carbons_disable) -> {U, S, R} = jid:tolower(From), - Result = case Operation of - <<"enable">>-> - ?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]), - enable(S,U,R,CC); - <<"disable">>-> - ?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]), - disable(S, U, R) - end, + Result = case El of + #carbons_enable{} -> + ?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]), + enable(S, U, R, ?NS_CARBONS_2); + #carbons_disable{} -> + ?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]), + disable(S, U, R) + end, case Result of - ok -> + ok -> ?DEBUG("carbons IQ result: ok", []), - IQ#iq{type=result, sub_el=[]}; + xmpp:make_iq_result(IQ); {error,_Error} -> ?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]), Txt = <<"Database failure">>, - IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]} + xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end; - -iq_handler(_From, _To, #iq{lang = Lang, sub_el = SubEl} = IQ, _CC)-> +iq_handler(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Only or tags are allowed">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); +iq_handler(#iq{type = get, lang = Lang} = IQ)-> Txt = <<"Value 'get' of 'type' attribute is not allowed">>, - IQ#iq{type=error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}. + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). +-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> + stanza() | {stop, stanza()}. user_send_packet(Packet, _C2SState, From, To) -> check_and_forward(From, To, Packet, sent). +-spec user_receive_packet(stanza(), ejabberd_c2s:state(), + jid(), jid(), jid()) -> + stanza() | {stop, stanza()}. user_receive_packet(Packet, _C2SState, JID, _From, To) -> check_and_forward(JID, To, Packet, received). @@ -129,11 +120,13 @@ user_receive_packet(Packet, _C2SState, JID, _From, To) -> % - registered to the user_send_packet hook, to be called only once even for multicast % - do not support "private" message mode, and do not modify the original packet in any way % - we also replicate "read" notifications +-spec check_and_forward(jid(), jid(), stanza(), direction()) -> + stanza() | {stop, stanza()}. check_and_forward(JID, To, Packet, Direction)-> case is_chat_message(Packet) andalso - not is_muc_pm(To, Packet) andalso - fxml:get_subtag(Packet, <<"private">>) == false andalso - fxml:get_subtag(Packet, <<"no-copy">>) == false of + not is_muc_pm(To, Packet) andalso + xmpp:has_subtag(Packet, #carbons_private{}) == false andalso + xmpp:has_subtag(Packet, #hint{type = 'no-copy'}) == false of true -> case is_carbon_copy(Packet) of false -> @@ -148,6 +141,7 @@ check_and_forward(JID, To, Packet, Direction)-> Packet end. +-spec remove_connection(binary(), binary(), binary(), binary()) -> ok. remove_connection(User, Server, Resource, _Status)-> disable(Server, User, Resource), ok. @@ -155,11 +149,12 @@ remove_connection(User, Server, Resource, _Status)-> %%% Internal %% Direction = received | sent +-spec send_copies(jid(), jid(), message(), direction()) -> ok. send_copies(JID, To, Packet, Direction)-> {U, S, R} = jid:tolower(JID), PrioRes = ejabberd_sm:get_user_present_resources(U, S), {_, AvailRs} = lists:unzip(PrioRes), - {MaxPrio, MaxRes} = case catch lists:max(PrioRes) of + {MaxPrio, _MaxRes} = case catch lists:max(PrioRes) of {Prio, Res} -> {Prio, Res}; _ -> {0, undefined} end, @@ -172,19 +167,19 @@ send_copies(JID, To, Packet, Direction)-> end, %% list of JIDs that should receive a carbon copy of this message (excluding the %% receiver(s) of the original message - TargetJIDs = case {IsBareTo, R} of - {true, MaxRes} -> + TargetJIDs = case {IsBareTo, Packet} of + {true, #message{meta = #{sm_copy := true}}} -> + %% The message was sent to our bare JID, and we currently have + %% multiple resources with the same highest priority, so the session + %% manager routes the message to each of them. We create carbon + %% copies only from one of those resources in order to avoid + %% duplicates. + []; + {true, _} -> OrigTo = fun(Res) -> lists:member({MaxPrio, Res}, PrioRes) end, [ {jid:make({U, S, CCRes}), CC_Version} || {CCRes, CC_Version} <- list(U, S), lists:member(CCRes, AvailRs), not OrigTo(CCRes) ]; - {true, _} -> - %% The message was sent to our bare JID, and we currently have - %% multiple resources with the same highest priority, so the session - %% manager routes the message to each of them. We create carbon - %% copies only from one of those resources (the one where R equals - %% MaxRes) in order to avoid duplicates. - []; {false, _} -> [ {jid:make({U, S, CCRes}), CC_Version} || {CCRes, CC_Version} <- list(U, S), @@ -192,93 +187,60 @@ send_copies(JID, To, Packet, Direction)-> %TargetJIDs = lists:delete(JID, [ jid:make({U, S, CCRes}) || CCRes <- list(U, S) ]), end, - lists:map(fun({Dest,Version}) -> + lists:map(fun({Dest, _Version}) -> {_, _, Resource} = jid:tolower(Dest), ?DEBUG("Sending: ~p =/= ~p", [R, Resource]), Sender = jid:make({U, S, <<>>}), %{xmlelement, N, A, C} = Packet, - New = build_forward_packet(JID, Packet, Sender, Dest, Direction, Version), + New = build_forward_packet(JID, Packet, Sender, Dest, Direction), ejabberd_router:route(Sender, Dest, New) end, TargetJIDs), ok. -build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_2) -> - #xmlel{name = <<"message">>, - attrs = [{<<"xmlns">>, <<"jabber:client">>}, - {<<"type">>, message_type(Packet)}, - {<<"from">>, jid:to_string(Sender)}, - {<<"to">>, jid:to_string(Dest)}], - children = [ - #xmlel{name = list_to_binary(atom_to_list(Direction)), - attrs = [{<<"xmlns">>, ?NS_CARBONS_2}], - children = [ - #xmlel{name = <<"forwarded">>, - attrs = [{<<"xmlns">>, ?NS_FORWARD}], - children = [ - complete_packet(JID, Packet, Direction)]} - ]} - ]}; -build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) -> - #xmlel{name = <<"message">>, - attrs = [{<<"xmlns">>, <<"jabber:client">>}, - {<<"type">>, message_type(Packet)}, - {<<"from">>, jid:to_string(Sender)}, - {<<"to">>, jid:to_string(Dest)}], - children = [ - #xmlel{name = list_to_binary(atom_to_list(Direction)), - attrs = [{<<"xmlns">>, ?NS_CARBONS_1}]}, - #xmlel{name = <<"forwarded">>, - attrs = [{<<"xmlns">>, ?NS_FORWARD}], - children = [complete_packet(JID, Packet, Direction)]} - ]}. - +-spec build_forward_packet(jid(), message(), jid(), jid(), direction()) -> message(). +build_forward_packet(JID, #message{type = T} = Msg, Sender, Dest, Direction) -> + Forwarded = #forwarded{xml_els = [xmpp:encode(complete_packet(JID, Msg, Direction))]}, + Carbon = case Direction of + sent -> #carbons_sent{forwarded = Forwarded}; + received -> #carbons_received{forwarded = Forwarded} + end, + #message{from = Sender, to = Dest, type = T, sub_els = [Carbon], + meta = #{carbon_copy => true}}. +-spec enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}. enable(Host, U, R, CC)-> ?DEBUG("enabling for ~p", [U]), Mod = gen_mod:db_mod(Host, ?MODULE), Mod:enable(U, Host, R, CC). +-spec disable(binary(), binary(), binary()) -> ok | {error, any()}. disable(Host, U, R)-> ?DEBUG("disabling for ~p", [U]), Mod = gen_mod:db_mod(Host, ?MODULE), Mod:disable(U, Host, R). -complete_packet(From, #xmlel{name = <<"message">>, attrs = OrigAttrs} = Packet, sent) -> +-spec complete_packet(jid(), message(), direction()) -> message(). +complete_packet(From, #message{from = undefined} = Msg, sent) -> %% if this is a packet sent by user on this host, then Packet doesn't %% include the 'from' attribute. We must add it. - Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}), - case proplists:get_value(<<"from">>, Attrs) of - undefined -> - Packet#xmlel{attrs = [{<<"from">>, jid:to_string(From)}|Attrs]}; - _ -> - Packet#xmlel{attrs = Attrs} - end; -complete_packet(_From, #xmlel{name = <<"message">>, attrs=OrigAttrs} = Packet, received) -> - Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}), - Packet#xmlel{attrs = Attrs}. + Msg#message{from = From}; +complete_packet(_From, Msg, _Direction) -> + Msg. -message_type(#xmlel{attrs = Attrs}) -> - case fxml:get_attr(<<"type">>, Attrs) of - {value, Type} -> Type; - false -> <<"normal">> - end. - -is_chat_message(#xmlel{name = <<"message">>} = Packet) -> - case message_type(Packet) of - <<"chat">> -> true; - <<"normal">> -> has_non_empty_body(Packet); - _ -> false - end; -is_chat_message(_Packet) -> false. +-spec is_chat_message(stanza()) -> boolean(). +is_chat_message(#message{type = chat}) -> + true; +is_chat_message(#message{type = normal, body = [_|_]}) -> + true; +is_chat_message(_) -> + false. is_muc_pm(#jid{lresource = <<>>}, _Packet) -> false; is_muc_pm(_To, Packet) -> - fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) =/= false. - -has_non_empty_body(Packet) -> - fxml:get_subtag_cdata(Packet, <<"body">>) =/= <<"">>. + xmpp:has_subtag(Packet, #muc_user{}). +-spec list(binary(), binary()) -> [{binary(), binary()}]. %% list {resource, cc_version} with carbons enabled for given user and host list(User, Server) -> Mod = gen_mod:db_mod(Server, ?MODULE), diff --git a/src/mod_carboncopy_mnesia.erl b/src/mod_carboncopy_mnesia.erl index bf69bd21c..4cc7e6049 100644 --- a/src/mod_carboncopy_mnesia.erl +++ b/src/mod_carboncopy_mnesia.erl @@ -30,7 +30,7 @@ init(_Host, _Opts) -> %% probably table don't exist ok end, - mnesia:create_table(carboncopy, + ejabberd_mnesia:create(?MODULE, carboncopy, [{ram_copies, [node()]}, {attributes, record_info(fields, carboncopy)}, {type, bag}]), diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl index 9d37d4f5b..2bae7a4f8 100644 --- a/src/mod_client_state.erl +++ b/src/mod_client_state.erl @@ -39,7 +39,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -define(CSI_QUEUE_MAX, 100). @@ -151,67 +151,68 @@ depends(_Host, _Opts) -> %% ejabberd_hooks callbacks. %%-------------------------------------------------------------------- --spec filter_presence({term(), [xmlel()]}, binary(), jid(), xmlel()) - -> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}. +-spec filter_presence({ejabberd_c2s:state(), [stanza()]}, binary(), jid(), stanza()) + -> {ejabberd_c2s:state(), [stanza()]} | + {stop, {ejabberd_c2s:state(), [stanza()]}}. filter_presence({C2SState, _OutStanzas} = Acc, Host, To, - #xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) -> - case fxml:get_attr(<<"type">>, Attrs) of - {value, Type} when Type /= <<"unavailable">> -> - Acc; - _ -> - ?DEBUG("Got availability presence stanza for ~s", - [jid:to_string(To)]), - queue_add(presence, Stanza, Host, C2SState) + #presence{type = Type} = Stanza) -> + if Type == available; Type == unavailable -> + ?DEBUG("Got availability presence stanza for ~s", + [jid:to_string(To)]), + queue_add(presence, Stanza, Host, C2SState); + true -> + Acc end; filter_presence(Acc, _Host, _To, _Stanza) -> Acc. --spec filter_chat_states({term(), [xmlel()]}, binary(), jid(), xmlel()) - -> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}. +-spec filter_chat_states({ejabberd_c2s:state(), [stanza()]}, binary(), jid(), stanza()) + -> {ejabberd_c2s:state(), [stanza()]} | + {stop, {ejabberd_c2s:state(), [stanza()]}}. filter_chat_states({C2SState, _OutStanzas} = Acc, Host, To, - #xmlel{name = <<"message">>} = Stanza) -> - case jlib:is_standalone_chat_state(Stanza) of - true -> - From = fxml:get_tag_attr_s(<<"from">>, Stanza), - case {jid:from_string(From), To} of - {#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} -> - %% Don't queue (carbon copies of) chat states from other - %% resources, as they might be used to sync the state of - %% conversations across clients. - Acc; - _ -> + #message{from = From} = Stanza) -> + case xmpp_util:is_standalone_chat_state(Stanza) of + true -> + case {From, To} of + {#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} -> + %% Don't queue (carbon copies of) chat states from other + %% resources, as they might be used to sync the state of + %% conversations across clients. + Acc; + _ -> ?DEBUG("Got standalone chat state notification for ~s", [jid:to_string(To)]), - queue_add(chatstate, Stanza, Host, C2SState) - end; - false -> - Acc + queue_add(chatstate, Stanza, Host, C2SState) + end; + false -> + Acc end; filter_chat_states(Acc, _Host, _To, _Stanza) -> Acc. --spec filter_pep({term(), [xmlel()]}, binary(), jid(), xmlel()) - -> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}. +-spec filter_pep({ejabberd_c2s:state(), [stanza()]}, binary(), jid(), stanza()) + -> {ejabberd_c2s:state(), [stanza()]} | + {stop, {ejabberd_c2s:state(), [stanza()]}}. -filter_pep({C2SState, _OutStanzas} = Acc, Host, To, - #xmlel{name = <<"message">>} = Stanza) -> +filter_pep({C2SState, _OutStanzas} = Acc, Host, To, #message{} = Stanza) -> case get_pep_node(Stanza) of - {value, Node} -> - ?DEBUG("Got PEP notification for ~s", [jid:to_string(To)]), - queue_add({pep, Node}, Stanza, Host, C2SState); - false -> - Acc + undefined -> + Acc; + Node -> + ?DEBUG("Got PEP notification for ~s", [jid:to_string(To)]), + queue_add({pep, Node}, Stanza, Host, C2SState) end; filter_pep(Acc, _Host, _To, _Stanza) -> Acc. --spec filter_other({term(), [xmlel()]}, binary(), jid(), xmlel()) - -> {term(), [xmlel()]}. +-spec filter_other({ejabberd_c2s:state(), [stanza()]}, binary(), jid(), stanza()) + -> {ejabberd_c2s:state(), [stanza()]}. filter_other({C2SState, _OutStanzas}, Host, To, Stanza) -> ?DEBUG("Won't add stanza for ~s to CSI queue", [jid:to_string(To)]), queue_take(Stanza, Host, C2SState). --spec flush_queue({term(), [xmlel()]}, binary(), jid()) -> {term(), [xmlel()]}. +-spec flush_queue({ejabberd_c2s:state(), [stanza()]}, binary(), jid()) + -> {ejabberd_c2s:state(), [stanza()]}. flush_queue({C2SState, _OutStanzas}, Host, JID) -> ?DEBUG("Going to flush CSI queue of ~s", [jid:to_string(JID)]), @@ -219,20 +220,17 @@ flush_queue({C2SState, _OutStanzas}, Host, JID) -> NewState = set_queue([], C2SState), {NewState, get_stanzas(Queue, Host)}. --spec add_stream_feature([xmlel()], binary) -> [xmlel()]. +-spec add_stream_feature([stanza()], binary) -> [stanza()]. add_stream_feature(Features, _Host) -> - Feature = #xmlel{name = <<"csi">>, - attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}], - children = []}, - [Feature | Features]. + [#feature_csi{xmlns = <<"urn:xmpp:csi:0">>} | Features]. %%-------------------------------------------------------------------- %% Internal functions. %%-------------------------------------------------------------------- --spec queue_add(csi_type(), xmlel(), binary(), term()) - -> {stop, {term(), [xmlel()]}}. +-spec queue_add(csi_type(), stanza(), binary(), term()) + -> {stop, {term(), [stanza()]}}. queue_add(Type, Stanza, Host, C2SState) -> case get_queue(C2SState) of @@ -242,19 +240,19 @@ queue_add(Type, Stanza, Host, C2SState) -> {stop, {NewState, get_stanzas(Queue, Host) ++ [Stanza]}}; Queue -> ?DEBUG("Adding stanza to CSI queue", []), - From = fxml:get_tag_attr_s(<<"from">>, Stanza), - Key = {jid:tolower(jid:from_string(From)), Type}, + From = xmpp:get_from(Stanza), + Key = {jid:tolower(From), Type}, Entry = {Key, p1_time_compat:timestamp(), Stanza}, NewQueue = lists:keystore(Key, 1, Queue, Entry), NewState = set_queue(NewQueue, C2SState), {stop, {NewState, []}} end. --spec queue_take(xmlel(), binary(), term()) -> {term(), [xmlel()]}. +-spec queue_take(stanza(), binary(), term()) -> {term(), [stanza()]}. queue_take(Stanza, Host, C2SState) -> - From = fxml:get_tag_attr_s(<<"from">>, Stanza), - {LUser, LServer, _LResource} = jid:tolower(jid:from_string(From)), + From = xmpp:get_from(Stanza), + {LUser, LServer, _LResource} = jid:tolower(From), {Selected, Rest} = lists:partition( fun({{{U, S, _R}, _Type}, _Time, _Stanza}) -> U == LUser andalso S == LServer @@ -277,32 +275,23 @@ get_queue(C2SState) -> [] end. --spec get_stanzas(csi_queue(), binary()) -> [xmlel()]. +-spec get_stanzas(csi_queue(), binary()) -> [stanza()]. get_stanzas(Queue, Host) -> lists:map(fun({_Key, Time, Stanza}) -> - jlib:add_delay_info(Stanza, Host, Time, - <<"Client Inactive">>) + xmpp_util:add_delay_info(Stanza, jid:make(Host), Time, + <<"Client Inactive">>) end, Queue). --spec get_pep_node(xmlel()) -> {value, binary()} | false. +-spec get_pep_node(message()) -> binary() | undefined. -get_pep_node(#xmlel{name = <<"message">>} = Stanza) -> - From = fxml:get_tag_attr_s(<<"from">>, Stanza), - case jid:from_string(From) of - #jid{luser = <<>>} -> % It's not PEP. - false; - _ -> - case fxml:get_subtag_with_xmlns(Stanza, <<"event">>, - ?NS_PUBSUB_EVENT) of - #xmlel{children = Els} -> - case fxml:remove_cdata(Els) of - [#xmlel{name = <<"items">>, attrs = ItemsAttrs}] -> - fxml:get_attr(<<"node">>, ItemsAttrs); - _ -> - false - end; - false -> - false - end +get_pep_node(#message{from = #jid{luser = <<>>}}) -> + %% It's not PEP. + undefined; +get_pep_node(#message{} = Msg) -> + case xmpp:get_subtag(Msg, #ps_event{}) of + #ps_event{items = #ps_items{node = Node}} -> + Node; + _ -> + undefined end. diff --git a/src/mod_configure.erl b/src/mod_configure.erl index 03779d027..436d736e6 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -40,10 +40,8 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_sm.hrl"). --include("adhoc.hrl"). -define(T(Lang, Text), translate:translate(Lang, Text)). @@ -102,29 +100,19 @@ depends(_Host, _Opts) -> %%%----------------------------------------------------------------------- -define(INFO_IDENTITY(Category, Type, Name, Lang), - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, Category}, {<<"type">>, Type}, - {<<"name">>, ?T(Lang, Name)}], - children = []}]). + [#identity{category = Category, type = Type, name = ?T(Lang, Name)}]). -define(INFO_COMMAND(Name, Lang), ?INFO_IDENTITY(<<"automation">>, <<"command-node">>, Name, Lang)). -define(NODEJID(To, Name, Node), - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, jid:to_string(To)}, - {<<"name">>, ?T(Lang, Name)}, {<<"node">>, Node}], - children = []}). + #disco_item{jid = To, name = ?T(Lang, Name), node = Node}). -define(NODE(Name, Node), - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Server}, {<<"name">>, ?T(Lang, Name)}, - {<<"node">>, Node}], - children = []}). + #disco_item{jid = jid:make(Server), + node = Node, + name = ?T(Lang, Name)}). -define(NS_ADMINX(Sub), <<(?NS_ADMIN)/binary, "#", Sub/binary>>). @@ -204,7 +192,7 @@ get_local_identity(Acc, _From, _To, Node, Lang) -> -define(INFO_RESULT(Allow, Feats, Lang), case Allow of - deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; allow -> {result, Feats} end). @@ -282,7 +270,10 @@ get_local_features(Acc, From, end. %%%----------------------------------------------------------------------- - +-spec adhoc_sm_items(empty | {error, stanza_error()} | {result, [disco_item()]}, + jid(), jid(), binary()) -> {error, stanza_error()} | + {result, [disco_item()]} | + empty. adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To, Lang) -> case acl:match_rule(LServer, configure, From) of @@ -291,12 +282,8 @@ adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To, {result, Its} -> Its; empty -> [] end, - Nodes = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, jid:to_string(To)}, - {<<"name">>, ?T(Lang, <<"Configuration">>)}, - {<<"node">>, <<"config">>}], - children = []}], + Nodes = [#disco_item{jid = To, node = <<"config">>, + name = ?T(Lang, <<"Configuration">>)}], {result, Items ++ Nodes}; _ -> Acc end. @@ -323,7 +310,7 @@ get_sm_items(Acc, From, Items ++ Nodes ++ get_user_resources(User, Server)}; {allow, <<"config">>} -> {result, []}; {_, <<"config">>} -> - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; _ -> Acc end end. @@ -331,18 +318,17 @@ get_sm_items(Acc, From, get_user_resources(User, Server) -> Rs = ejabberd_sm:get_user_resources(User, Server), lists:map(fun (R) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - <>}, - {<<"name">>, User}], - children = []} + #disco_item{jid = jid:make(User, Server, R), + name = User} end, lists:sort(Rs)). %%%----------------------------------------------------------------------- +-spec adhoc_local_items(empty | {error, stanza_error()} | {result, [disco_item()]}, + jid(), jid(), binary()) -> {error, stanza_error()} | + {result, [disco_item()]} | + empty. adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To, Lang) -> case acl:match_rule(LServer, configure, From) of @@ -383,25 +369,19 @@ recursively_get_local_items(PermLev, LServer, Node, {result, Res} -> Res; {error, _Error} -> [] end, - Nodes = lists:flatten(lists:map(fun (N) -> - S = fxml:get_tag_attr_s(<<"jid">>, - N), - Nd = fxml:get_tag_attr_s(<<"node">>, - N), - if (S /= Server) or - (Nd == <<"">>) -> - []; - true -> - [N, - recursively_get_local_items(PermLev, - LServer, - Nd, - Server, - Lang)] - end - end, - Items)), - Nodes. + lists:flatten( + lists:map( + fun(#disco_item{jid = #jid{server = S}, node = Nd} = Item) -> + if (S /= Server) or + (Nd == <<"">>) -> + []; + true -> + [Item, + recursively_get_local_items( + PermLev, LServer, Nd, Server, Lang)] + end + end, + Items)). get_permission_level(JID) -> case acl:match_rule(global, configure, JID) of @@ -453,7 +433,7 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, _ -> LNode = tokenize(Node), Allow = acl:match_rule(LServer, configure, From), - Err = ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>), + Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang), case LNode of [<<"config">>] -> ?ITEMS_RESULT(Allow, LNode, {error, Err}); @@ -570,27 +550,18 @@ get_local_items({_, Host}, _Lang) -> Users = ejabberd_auth:get_vh_registered_users(Host), SUsers = lists:sort([{S, U} || {U, S} <- Users]), - case catch begin - [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>), - N1 = jlib:binary_to_integer(S1), - N2 = jlib:binary_to_integer(S2), - Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), - lists:map(fun ({S, U}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - <>}, - {<<"name">>, - <>}], - children = []} - end, - Sub) - end - of - {'EXIT', _Reason} -> ?ERR_NOT_ACCEPTABLE; - Res -> {result, Res} + try + [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>), + N1 = binary_to_integer(S1), + N2 = binary_to_integer(S2), + Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), + {result, lists:map( + fun({S, U}) -> + #disco_item{jid = jid:make(U, S), + name = <>} + end, Sub)} + catch _:_ -> + xmpp:err_not_acceptable() end; get_local_items({_, Host}, [<<"outgoing s2s">>], _Server, Lang) -> @@ -676,24 +647,18 @@ get_local_items(_Host, _Lang) -> {result, []}; get_local_items(_Host, _, _Server, _Lang) -> - {error, ?ERR_ITEM_NOT_FOUND}. + {error, xmpp:err_item_not_found()}. get_online_vh_users(Host) -> case catch ejabberd_sm:get_vh_session_list(Host) of {'EXIT', _Reason} -> []; USRs -> SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]), - lists:map(fun ({S, U, R}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - <>}, - {<<"name">>, - <>}], - children = []} - end, - SURs) + lists:map( + fun({S, U, R}) -> + #disco_item{jid = jid:make(U, S, R), + name = <>} + end, SURs) end. get_all_vh_users(Host) -> @@ -704,16 +669,10 @@ get_all_vh_users(Host) -> SUsers = lists:sort([{S, U} || {U, S} <- Users]), case length(SUsers) of N when N =< 100 -> - lists:map(fun ({S, U}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - <>}, - {<<"name">>, - <>}], - children = []} - end, - SUsers); + lists:map(fun({S, U}) -> + #disco_item{jid = jid:make(U, S), + name = <>} + end, SUsers); N -> NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) + 1, @@ -721,22 +680,18 @@ get_all_vh_users(Host) -> lists:map(fun (K) -> L = K + M - 1, Node = <<"@", - (iolist_to_binary(integer_to_list(K)))/binary, + (integer_to_binary(K))/binary, "-", - (iolist_to_binary(integer_to_list(L)))/binary>>, + (integer_to_binary(L))/binary>>, {FS, FU} = lists:nth(K, SUsers), {LS, LU} = if L < N -> lists:nth(L, SUsers); true -> lists:last(SUsers) end, Name = <>, - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Host}, - {<<"node">>, - <<"all users/", Node/binary>>}, - {<<"name">>, Name}], - children = []} + #disco_item{jid = jid:make(Host), + node = <<"all users/", Node/binary>>, + name = Name} end, lists:seq(1, N, M)) end @@ -750,59 +705,43 @@ get_outgoing_s2s(Host, Lang) -> TConns = [TH || {FH, TH} <- Connections, Host == FH orelse str:suffix(DotHost, FH)], - lists:map(fun (T) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Host}, - {<<"node">>, - <<"outgoing s2s/", T/binary>>}, - {<<"name">>, - iolist_to_binary(io_lib:format(?T(Lang, - <<"To ~s">>), - [T]))}], - children = []} - end, - lists:usort(TConns)) + lists:map( + fun (T) -> + Name = str:format(?T(Lang, <<"To ~s">>),[T]), + #disco_item{jid = jid:make(Host), + node = <<"outgoing s2s/", T/binary>>, + name = Name} + end, lists:usort(TConns)) end. get_outgoing_s2s(Host, Lang, To) -> case catch ejabberd_s2s:dirty_get_connections() of {'EXIT', _Reason} -> []; Connections -> - lists:map(fun ({F, _T}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Host}, - {<<"node">>, - <<"outgoing s2s/", To/binary, "/", - F/binary>>}, - {<<"name">>, - iolist_to_binary(io_lib:format(?T(Lang, - <<"From ~s">>), - [F]))}], - children = []} - end, - lists:keysort(1, - lists:filter(fun (E) -> element(2, E) == To - end, - Connections))) + lists:map( + fun ({F, _T}) -> + Node = <<"outgoing s2s/", To/binary, "/", F/binary>>, + Name = str:format(?T(Lang, <<"From ~s">>), [F]), + #disco_item{jid = jid:make(Host), node = Node, name = Name} + end, + lists:keysort(1, + lists:filter(fun (E) -> element(2, E) == To + end, + Connections))) end. get_running_nodes(Server, _Lang) -> case catch mnesia:system_info(running_db_nodes) of {'EXIT', _Reason} -> []; DBNodes -> - lists:map(fun (N) -> - S = iolist_to_binary(atom_to_list(N)), - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Server}, - {<<"node">>, - <<"running nodes/", S/binary>>}, - {<<"name">>, S}], - children = []} - end, - lists:sort(DBNodes)) + lists:map( + fun (N) -> + S = iolist_to_binary(atom_to_list(N)), + #disco_item{jid = jid:make(Server), + node = <<"running nodes/", S/binary>>, + name = S} + end, + lists:sort(DBNodes)) end. get_stopped_nodes(_Lang) -> @@ -812,17 +751,14 @@ get_stopped_nodes(_Lang) -> of {'EXIT', _Reason} -> []; DBNodes -> - lists:map(fun (N) -> - S = iolist_to_binary(atom_to_list(N)), - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, ?MYNAME}, - {<<"node">>, - <<"stopped nodes/", S/binary>>}, - {<<"name">>, S}], - children = []} - end, - lists:sort(DBNodes)) + lists:map( + fun (N) -> + S = iolist_to_binary(atom_to_list(N)), + #disco_item{jid = jid:make(?MYNAME), + node = <<"stopped nodes/", S/binary>>, + name = S} + end, + lists:sort(DBNodes)) end. %%------------------------------------------------------------------------- @@ -830,13 +766,15 @@ get_stopped_nodes(_Lang) -> -define(COMMANDS_RESULT(LServerOrGlobal, From, To, Request, Lang), case acl:match_rule(LServerOrGlobal, configure, From) of - deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; allow -> adhoc_local_commands(From, To, Request) end). +-spec adhoc_local_commands(adhoc_command(), jid(), jid(), adhoc_command()) -> + adhoc_command() | {error, stanza_error()}. adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To, - #adhoc_request{node = Node, lang = Lang} = Request) -> + #adhoc_command{node = Node, lang = Lang} = Request) -> LNode = tokenize(Node), case LNode of [<<"running nodes">>, _ENode, <<"DB">>] -> @@ -860,171 +798,107 @@ adhoc_local_commands(Acc, From, adhoc_local_commands(From, #jid{lserver = LServer} = _To, - #adhoc_request{lang = Lang, node = Node, - sessionid = SessionID, action = Action, - xdata = XData} = - Request) -> + #adhoc_command{lang = Lang, node = Node, + sid = SessionID, action = Action, + xdata = XData} = Request) -> LNode = tokenize(Node), - ActionIsExecute = lists:member(Action, - [<<"">>, <<"execute">>, <<"complete">>]), - if Action == <<"cancel">> -> - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); - XData == false, ActionIsExecute -> + ActionIsExecute = Action == execute orelse Action == complete, + if Action == cancel -> + #adhoc_command{status = canceled, lang = Lang, + node = Node, sid = SessionID}; + XData == undefined, ActionIsExecute -> case get_form(LServer, LNode, Lang) of {result, Form} -> - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = Form}); + xmpp_util:make_adhoc_response( + Request, + #adhoc_command{status = executing, xdata = Form}); {result, Status, Form} -> - adhoc:produce_response(Request, - #adhoc_response{status = Status, - elements = Form}); + xmpp_util:make_adhoc_response( + Request, + #adhoc_command{status = Status, xdata = Form}); {error, Error} -> {error, Error} end; - XData /= false, ActionIsExecute -> - case jlib:parse_xdata_submit(XData) of - invalid -> - {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)}; - Fields -> - case catch set_form(From, LServer, LNode, Lang, Fields) - of - {result, Res} -> - adhoc:produce_response(#adhoc_response{lang = Lang, - node = Node, - sessionid = - SessionID, - elements = Res, - status = - completed}); - {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; - {error, Error} -> {error, Error} - end - end; + XData /= undefined, ActionIsExecute -> + case catch set_form(From, LServer, LNode, Lang, XData) of + {result, Res} -> + xmpp_util:make_adhoc_response( + Request, + #adhoc_command{xdata = Res, status = completed}); + {'EXIT', _} -> {error, xmpp:err_bad_request()}; + {error, Error} -> {error, Error} + end; true -> - {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect action or data form">>)} + {error, xmpp:err_bad_request(<<"Unexpected action">>, Lang)} end. -define(TVFIELD(Type, Var, Val), - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, Type}, {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). + #xdata_field{type = Type, var = Var, values = [Val]}). -define(HFIELD(), - ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, (?NS_ADMIN))). + ?TVFIELD(hidden, <<"FORM_TYPE">>, (?NS_ADMIN))). -define(TLFIELD(Type, Label, Var), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)}, - {<<"var">>, Var}], - children = []}). + #xdata_field{type = Type, label = ?T(Lang, Label), var = Var}). -define(XFIELD(Type, Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). + #xdata_field{type = Type, label = ?T(Lang, Label), + var = Var, values = [Val]}). -define(XMFIELD(Type, Label, Var, Vals), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, {<<"label">>, ?T(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]} - || Val <- Vals]}). + #xdata_field{type = Type, label = ?T(Lang, Label), + var = Var, values = Vals}). -define(TABLEFIELD(Table, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, iolist_to_binary(atom_to_list(Table))}, - {<<"var">>, iolist_to_binary(atom_to_list(Table))}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary(atom_to_list(Val))}]}, - #xmlel{name = <<"option">>, - attrs = [{<<"label">>, ?T(Lang, <<"RAM copy">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, <<"ram_copies">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - ?T(Lang, <<"RAM and disc copy">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, <<"disc_copies">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, ?T(Lang, <<"Disc only copy">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"disc_only_copies">>}]}]}, - #xmlel{name = <<"option">>, - attrs = [{<<"label">>, ?T(Lang, <<"Remote copy">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, <<"unknown">>}]}]}]}). + #xdata_field{ + type = 'list-single', + label = iolist_to_binary(atom_to_list(Table)), + var = iolist_to_binary(atom_to_list(Table)), + values = [iolist_to_binary(atom_to_list(Val))], + options = [#xdata_option{label = ?T(Lang, <<"RAM copy">>), + value = <<"ram_copies">>}, + #xdata_option{label = ?T(Lang, <<"RAM and disc copy">>), + value = <<"disc_copies">>}, + #xdata_option{label = ?T(Lang, <<"Disc only copy">>), + value = <<"disc_only_copies">>}, + #xdata_option{label = ?T(Lang, <<"Remote copy">>), + value = <<"unknown">>}]}). get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>], Lang) -> case search_running_node(ENode) of false -> Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; + {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of {badrpc, Reason} -> ?ERROR_MSG("RPC call mnesia:system_info(tables) on node " "~s failed: ~p", [Node, Reason]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; + {error, xmpp:err_internal_server_error()}; Tables -> STables = lists:sort(Tables), - {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(?T(Lang, - <<"Database Tables Configuration at ">>))/binary, - ENode/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, - <<"Choose storage type of tables">>)}]} - | lists:map(fun (Table) -> - case ejabberd_cluster:call(Node, mnesia, - table_info, - [Table, - storage_type]) - of - {badrpc, _} -> - ?TABLEFIELD(Table, - unknown); - Type -> - ?TABLEFIELD(Table, Type) - end - end, - STables)]}]} + Title = <<(?T(Lang, <<"Database Tables Configuration at ">>))/binary, + ENode/binary>>, + Instr = ?T(Lang, <<"Choose storage type of tables">>), + try + Fs = lists:map( + fun(Table) -> + case ejabberd_cluster:call( + Node, mnesia, table_info, + [Table, storage_type]) of + Type when is_atom(Type) -> + ?TABLEFIELD(Table, Type) + end + end, STables), + {result, #xdata{title = Title, + type = form, + instructions = [Instr], + fields = [?HFIELD()|Fs]}} + catch _:{case_clause, {badrpc, Reason}} -> + ?ERROR_MSG("RPC call mnesia:table_info/2 " + "on node ~s failed: ~p", [Node, Reason]), + {error, xmpp:err_internal_server_error()} + end end end; get_form(Host, @@ -1033,37 +907,26 @@ get_form(Host, case search_running_node(ENode) of false -> Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; + {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case ejabberd_cluster:call(Node, gen_mod, loaded_modules, [Host]) of {badrpc, Reason} -> ?ERROR_MSG("RPC call gen_mod:loaded_modules(~s) on node " "~s failed: ~p", [Host, Node, Reason]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; + {error, xmpp:err_internal_server_error()}; Modules -> SModules = lists:sort(Modules), - {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(?T(Lang, - <<"Stop Modules at ">>))/binary, - ENode/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, - <<"Choose modules to stop">>)}]} - | lists:map(fun (M) -> - S = jlib:atom_to_binary(M), - ?XFIELD(<<"boolean">>, S, S, - <<"0">>) - end, - SModules)]}]} + Title = <<(?T(Lang, <<"Stop Modules at ">>))/binary, + ENode/binary>>, + Instr = ?T(Lang, <<"Choose modules to stop">>), + Fs = lists:map(fun(M) -> + S = jlib:atom_to_binary(M), + ?XFIELD(boolean, S, S, <<"0">>) + end, SModules), + {result, #xdata{title = Title, + type = form, + instructions = [Instr], + fields = [?HFIELD()|Fs]}} end end; get_form(_Host, @@ -1071,153 +934,88 @@ get_form(_Host, <<"start">>], Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(?T(Lang, <<"Start Modules at ">>))/binary, - ENode/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, - <<"Enter list of {Module, [Options]}">>)}]}, - ?XFIELD(<<"text-multi">>, - <<"List of modules to start">>, <<"modules">>, - <<"[].">>)]}]}; + #xdata{title = <<(?T(Lang, <<"Start Modules at ">>))/binary, ENode/binary>>, + type = form, + instructions = [?T(Lang, <<"Enter list of {Module, [Options]}">>)], + fields = [?HFIELD(), + ?XFIELD('text-multi', + <<"List of modules to start">>, <<"modules">>, + <<"[].">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"backup">>, <<"backup">>], Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(?T(Lang, <<"Backup to File at ">>))/binary, - ENode/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, <<"Enter path to backup file">>)}]}, - ?XFIELD(<<"text-single">>, <<"Path to File">>, - <<"path">>, <<"">>)]}]}; + #xdata{title = <<(?T(Lang, <<"Backup to File at ">>))/binary, ENode/binary>>, + type = form, + instructions = [?T(Lang, <<"Enter path to backup file">>)], + fields = [?HFIELD(), + ?XFIELD('text-single', <<"Path to File">>, + <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"backup">>, <<"restore">>], Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(?T(Lang, - <<"Restore Backup from File at ">>))/binary, - ENode/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, <<"Enter path to backup file">>)}]}, - ?XFIELD(<<"text-single">>, <<"Path to File">>, - <<"path">>, <<"">>)]}]}; + #xdata{title = <<(?T(Lang, <<"Restore Backup from File at ">>))/binary, + ENode/binary>>, + type = form, + instructions = [?T(Lang, <<"Enter path to backup file">>)], + fields = [?HFIELD(), + ?XFIELD('text-single', <<"Path to File">>, + <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"backup">>, <<"textfile">>], Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(?T(Lang, - <<"Dump Backup to Text File at ">>))/binary, - ENode/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, <<"Enter path to text file">>)}]}, - ?XFIELD(<<"text-single">>, <<"Path to File">>, - <<"path">>, <<"">>)]}]}; + #xdata{title = <<(?T(Lang, <<"Dump Backup to Text File at ">>))/binary, + ENode/binary>>, + type = form, + instructions = [?T(Lang, <<"Enter path to text file">>)], + fields = [?HFIELD(), + ?XFIELD('text-single', <<"Path to File">>, + <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"import">>, <<"file">>], Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(?T(Lang, - <<"Import User from File at ">>))/binary, - ENode/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, - <<"Enter path to jabberd14 spool file">>)}]}, - ?XFIELD(<<"text-single">>, <<"Path to File">>, - <<"path">>, <<"">>)]}]}; + #xdata{title = <<(?T(Lang, <<"Import User from File at ">>))/binary, + ENode/binary>>, + type = form, + instructions = [?T(Lang, <<"Enter path to jabberd14 spool file">>)], + fields = [?HFIELD(), + ?XFIELD('text-single', <<"Path to File">>, + <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"import">>, <<"dir">>], Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(?T(Lang, - <<"Import Users from Dir at ">>))/binary, - ENode/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, - <<"Enter path to jabberd14 spool dir">>)}]}, - ?XFIELD(<<"text-single">>, <<"Path to Dir">>, - <<"path">>, <<"">>)]}]}; + #xdata{title = <<(?T(Lang, <<"Import Users from Dir at ">>))/binary, + ENode/binary>>, + type = form, + instructions = [?T(Lang, <<"Enter path to jabberd14 spool dir">>)], + fields = [?HFIELD(), + ?XFIELD('text-single', <<"Path to Dir">>, + <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, _ENode, <<"restart">>], Lang) -> - Make_option = fun (LabelNum, LabelUnit, Value) -> - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - <>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Value}]}]} - end, + Make_option = + fun (LabelNum, LabelUnit, Value) -> + #xdata_option{ + label = <>, + value = Value} + end, {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, ?T(Lang, <<"Restart Service">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, ?T(Lang, <<"Time delay">>)}, - {<<"var">>, <<"delay">>}], - children = + #xdata{title = ?T(Lang, <<"Restart Service">>), + type = form, + fields = [?HFIELD(), + #xdata_field{ + type = 'list-single', + label = ?T(Lang, <<"Time delay">>), + var = <<"delay">>, + required = true, + options = [Make_option(<<"">>, <<"immediately">>, <<"1">>), Make_option(<<"15 ">>, <<"seconds">>, <<"15">>), Make_option(<<"30 ">>, <<"seconds">>, <<"30">>), @@ -1229,55 +1027,35 @@ get_form(_Host, Make_option(<<"5 ">>, <<"minutes">>, <<"300">>), Make_option(<<"10 ">>, <<"minutes">>, <<"600">>), Make_option(<<"15 ">>, <<"minutes">>, <<"900">>), - Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>), - #xmlel{name = <<"required">>, attrs = [], - children = []}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"fixed">>}, - {<<"label">>, - ?T(Lang, - <<"Send announcement to all online users " - "on all hosts">>)}], - children = []}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"subject">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, ?T(Lang, <<"Subject">>)}], - children = []}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"announcement">>}, - {<<"type">>, <<"text-multi">>}, - {<<"label">>, ?T(Lang, <<"Message body">>)}], - children = []}]}]}; + Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]}, + #xdata_field{type = fixed, + label = ?T(Lang, + <<"Send announcement to all online users " + "on all hosts">>)}, + #xdata_field{var = <<"subject">>, + type = 'text-single', + label = ?T(Lang, <<"Subject">>)}, + #xdata_field{var = <<"announcement">>, + type = 'text-multi', + label = ?T(Lang, <<"Message body">>)}]}}; get_form(_Host, [<<"running nodes">>, _ENode, <<"shutdown">>], Lang) -> - Make_option = fun (LabelNum, LabelUnit, Value) -> - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - <>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Value}]}]} - end, + Make_option = + fun (LabelNum, LabelUnit, Value) -> + #xdata_option{ + label = <>, + value = Value} + end, {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, ?T(Lang, <<"Shut Down Service">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, ?T(Lang, <<"Time delay">>)}, - {<<"var">>, <<"delay">>}], - children = + #xdata{title = ?T(Lang, <<"Shut Down Service">>), + type = form, + fields = [?HFIELD(), + #xdata_field{ + type = 'list-single', + label = ?T(Lang, <<"Time delay">>), + var = <<"delay">>, + required = true, + options = [Make_option(<<"">>, <<"immediately">>, <<"1">>), Make_option(<<"15 ">>, <<"seconds">>, <<"15">>), Make_option(<<"30 ">>, <<"seconds">>, <<"30">>), @@ -1289,323 +1067,185 @@ get_form(_Host, Make_option(<<"5 ">>, <<"minutes">>, <<"300">>), Make_option(<<"10 ">>, <<"minutes">>, <<"600">>), Make_option(<<"15 ">>, <<"minutes">>, <<"900">>), - Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>), - #xmlel{name = <<"required">>, attrs = [], - children = []}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"fixed">>}, - {<<"label">>, - ?T(Lang, - <<"Send announcement to all online users " - "on all hosts">>)}], - children = []}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"subject">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, ?T(Lang, <<"Subject">>)}], - children = []}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"announcement">>}, - {<<"type">>, <<"text-multi">>}, - {<<"label">>, ?T(Lang, <<"Message body">>)}], - children = []}]}]}; + Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]}, + #xdata_field{type = fixed, + label = ?T(Lang, + <<"Send announcement to all online users " + "on all hosts">>)}, + #xdata_field{var = <<"subject">>, + type = 'text-single', + label = ?T(Lang, <<"Subject">>)}, + #xdata_field{var = <<"announcement">>, + type = 'text-multi', + label = ?T(Lang, <<"Message body">>)}]}}; get_form(Host, [<<"config">>, <<"acls">>], Lang) -> + ACLs = str:tokens( + str:format("~p.", + [mnesia:dirty_select( + acl, + ets:fun2ms( + fun({acl, {Name, H}, Spec}) when H == Host -> + {acl, Name, Spec} + end))]), + <<"\n">>), {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, - <<"Access Control List Configuration">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-multi">>}, - {<<"label">>, - ?T(Lang, <<"Access control lists">>)}, - {<<"var">>, <<"acls">>}], - children = - lists:map(fun (S) -> - #xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, S}]} - end, - str:tokens(iolist_to_binary(io_lib:format("~p.", - [mnesia:dirty_select(acl, - [{{acl, - {'$1', - '$2'}, - '$3'}, - [{'==', - '$2', - Host}], - [{{acl, - '$1', - '$3'}}]}])])), - <<"\n">>))}]}]}; + #xdata{title = ?T(Lang, <<"Access Control List Configuration">>), + type = form, + fields = [?HFIELD(), + #xdata_field{type = 'text-multi', + label = ?T(Lang, <<"Access control lists">>), + var = <<"acls">>, + values = ACLs}]}}; get_form(Host, [<<"config">>, <<"access">>], Lang) -> + Accs = str:tokens( + str:format("~p.", + [mnesia:dirty_select( + access, + ets:fun2ms( + fun({access, {Name, H}, Acc}) when H == Host -> + {access, Name, Acc} + end))]), + <<"\n">>), {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, <<"Access Configuration">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-multi">>}, - {<<"label">>, ?T(Lang, <<"Access rules">>)}, - {<<"var">>, <<"access">>}], - children = - lists:map(fun (S) -> - #xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, S}]} - end, - str:tokens(iolist_to_binary(io_lib:format("~p.", - [mnesia:dirty_select(access, - [{{access, - {'$1', - '$2'}, - '$3'}, - [{'==', - '$2', - Host}], - [{{access, - '$1', - '$3'}}]}])])), - <<"\n">>))}]}]}; + #xdata{title = ?T(Lang, <<"Access Configuration">>), + type = form, + fields = [?HFIELD(), + #xdata_field{type = 'text-multi', + label = ?T(Lang, <<"Access rules">>), + var = <<"access">>, + values = Accs}]}}; get_form(_Host, ?NS_ADMINL(<<"add-user">>), Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = [{xmlcdata, ?T(Lang, <<"Add User">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-single">>}, - {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, - {<<"var">>, <<"accountjid">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-private">>}, - {<<"label">>, ?T(Lang, <<"Password">>)}, - {<<"var">>, <<"password">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-private">>}, - {<<"label">>, - ?T(Lang, <<"Password Verification">>)}, - {<<"var">>, <<"password-verify">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}]}]}; + #xdata{title = ?T(Lang, <<"Add User">>), + type = form, + fields = [?HFIELD(), + #xdata_field{type = 'jid-single', + label = ?T(Lang, <<"Jabber ID">>), + required = true, + var = <<"accountjid">>}, + #xdata_field{type = 'text-private', + label = ?T(Lang, <<"Password">>), + required = true, + var = <<"password">>}, + #xdata_field{type = 'text-private', + label = ?T(Lang, <<"Password Verification">>), + required = true, + var = <<"password-verify">>}]}}; get_form(_Host, ?NS_ADMINL(<<"delete-user">>), Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = [{xmlcdata, ?T(Lang, <<"Delete User">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-multi">>}, - {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, - {<<"var">>, <<"accountjids">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}]}]}; + #xdata{title = ?T(Lang, <<"Delete User">>), + type = form, + fields = [?HFIELD(), + #xdata_field{type = 'jid-multi', + label = ?T(Lang, <<"Jabber ID">>), + required = true, + var = <<"accountjids">>}]}}; get_form(_Host, ?NS_ADMINL(<<"end-user-session">>), Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, ?T(Lang, <<"End User Session">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-single">>}, - {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, - {<<"var">>, <<"accountjid">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}]}]}; + #xdata{title = ?T(Lang, <<"End User Session">>), + type = form, + fields = [?HFIELD(), + #xdata_field{type = 'jid-single', + label = ?T(Lang, <<"Jabber ID">>), + required = true, + var = <<"accountjid">>}]}}; get_form(_Host, ?NS_ADMINL(<<"get-user-password">>), Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, ?T(Lang, <<"Get User Password">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-single">>}, - {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, - {<<"var">>, <<"accountjid">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}]}]}; + #xdata{title = ?T(Lang, <<"Get User Password">>), + type = form, + fields = [?HFIELD(), + #xdata_field{type = 'jid-single', + label = ?T(Lang, <<"Jabber ID">>), + var = <<"accountjid">>, + required = true}]}}; get_form(_Host, ?NS_ADMINL(<<"change-user-password">>), Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, ?T(Lang, <<"Get User Password">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-single">>}, - {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, - {<<"var">>, <<"accountjid">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-private">>}, - {<<"label">>, ?T(Lang, <<"Password">>)}, - {<<"var">>, <<"password">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}]}]}; + #xdata{title = ?T(Lang, <<"Get User Password">>), + type = form, + fields = [?HFIELD(), + #xdata_field{type = 'jid-single', + label = ?T(Lang, <<"Jabber ID">>), + required = true, + var = <<"accountjid">>}, + #xdata_field{type = 'text-private', + label = ?T(Lang, <<"Password">>), + required = true, + var = <<"password">>}]}}; get_form(_Host, ?NS_ADMINL(<<"get-user-lastlogin">>), Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - ?T(Lang, <<"Get User Last Login Time">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-single">>}, - {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, - {<<"var">>, <<"accountjid">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}]}]}; + #xdata{title = ?T(Lang, <<"Get User Last Login Time">>), + type = form, + fields = [?HFIELD(), + #xdata_field{type = 'jid-single', + label = ?T(Lang, <<"Jabber ID">>), + var = <<"accountjid">>, + required = true}]}}; get_form(_Host, ?NS_ADMINL(<<"user-stats">>), Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, ?T(Lang, <<"Get User Statistics">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-single">>}, - {<<"label">>, ?T(Lang, <<"Jabber ID">>)}, - {<<"var">>, <<"accountjid">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}]}]}; + #xdata{title = ?T(Lang, <<"Get User Statistics">>), + type = form, + fields = [?HFIELD(), + #xdata_field{type = 'jid-single', + label = ?T(Lang, <<"Jabber ID">>), + var = <<"accountjid">>, + required = true}]}}; get_form(Host, ?NS_ADMINL(<<"get-registered-users-num">>), Lang) -> - Num = list_to_binary( - io_lib:format("~p", - [ejabberd_auth:get_vh_registered_users_number(Host)])), + Num = integer_to_binary(ejabberd_auth:get_vh_registered_users_number(Host)), {result, completed, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-single">>}, - {<<"label">>, - ?T(Lang, <<"Number of registered users">>)}, - {<<"var">>, <<"registeredusersnum">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Num}]}]}]}]}; + #xdata{type = form, + fields = [?HFIELD(), + #xdata_field{type = 'text-single', + label = ?T(Lang, <<"Number of registered users">>), + var = <<"registeredusersnum">>, + values = [Num]}]}}; get_form(Host, ?NS_ADMINL(<<"get-online-users-num">>), Lang) -> - Num = list_to_binary( - io_lib:format("~p", - [length(ejabberd_sm:get_vh_session_list(Host))])), + Num = integer_to_binary(ejabberd_sm:get_vh_session_number(Host)), {result, completed, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-single">>}, - {<<"label">>, - ?T(Lang, <<"Number of online users">>)}, - {<<"var">>, <<"onlineusersnum">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Num}]}]}]}]}; + #xdata{type = form, + fields = [?HFIELD(), + #xdata_field{type = 'text-single', + label = ?T(Lang, <<"Number of online users">>), + var = <<"onlineusersnum">>, + values = [Num]}]}}; get_form(_Host, _, _Lang) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. + {error, xmpp:err_service_unavailable()}. set_form(_From, _Host, [<<"running nodes">>, ENode, <<"DB">>], Lang, XData) -> case search_running_node(ENode) of false -> Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; + {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> - lists:foreach(fun ({SVar, SVals}) -> - Table = jlib:binary_to_atom(SVar), - Type = case SVals of - [<<"unknown">>] -> unknown; - [<<"ram_copies">>] -> ram_copies; - [<<"disc_copies">>] -> disc_copies; - [<<"disc_only_copies">>] -> - disc_only_copies; - _ -> false - end, - if Type == false -> ok; - Type == unknown -> - mnesia:del_table_copy(Table, Node); - true -> - case mnesia:add_table_copy(Table, Node, - Type) - of - {aborted, _} -> - mnesia:change_table_copy_type(Table, - Node, - Type); - _ -> ok - end - end - end, - XData), - {result, []} + lists:foreach( + fun(#xdata_field{var = SVar, values = SVals}) -> + Table = jlib:binary_to_atom(SVar), + Type = case SVals of + [<<"unknown">>] -> unknown; + [<<"ram_copies">>] -> ram_copies; + [<<"disc_copies">>] -> disc_copies; + [<<"disc_only_copies">>] -> disc_only_copies; + _ -> false + end, + if Type == false -> ok; + Type == unknown -> + mnesia:del_table_copy(Table, Node); + true -> + case mnesia:add_table_copy(Table, Node, Type) of + {aborted, _} -> + mnesia:change_table_copy_type( + Table, Node, Type); + _ -> ok + end + end + end, XData#xdata.fields), + {result, undefined} end; set_form(_From, Host, [<<"running nodes">>, ENode, <<"modules">>, <<"stop">>], @@ -1613,187 +1253,193 @@ set_form(_From, Host, case search_running_node(ENode) of false -> Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; + {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> - lists:foreach(fun ({Var, Vals}) -> - case Vals of - [<<"1">>] -> - Module = jlib:binary_to_atom(Var), - ejabberd_cluster:call(Node, gen_mod, stop_module, - [Host, Module]); - _ -> ok - end - end, - XData), - {result, []} + lists:foreach( + fun(#xdata_field{var = Var, values = Vals}) -> + case Vals of + [<<"1">>] -> + Module = jlib:binary_to_atom(Var), + ejabberd_cluster:call(Node, gen_mod, stop_module, + [Host, Module]); + _ -> ok + end + end, XData#xdata.fields), + {result, undefined} end; set_form(_From, Host, [<<"running nodes">>, ENode, <<"modules">>, <<"start">>], Lang, XData) -> case search_running_node(ENode) of - false -> - Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; - Node -> - case lists:keysearch(<<"modules">>, 1, XData) of - false -> - Txt = <<"No 'modules' found in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, {_, Strings}} -> - String = lists:foldl(fun (S, Res) -> - <> - end, - <<"">>, Strings), - case erl_scan:string(binary_to_list(String)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, Modules} -> - lists:foreach(fun ({Module, Args}) -> - ejabberd_cluster:call(Node, gen_mod, - start_module, - [Host, Module, Args]) - end, - Modules), - {result, []}; - _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)} - end; - _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)} - end - end + false -> + Txt = <<"No running node found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + Node -> + case xmpp_util:get_xdata_values(<<"modules">>, XData) of + [] -> + Txt = <<"No 'modules' found in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + Strings -> + String = lists:foldl(fun (S, Res) -> + <> + end, <<"">>, Strings), + case erl_scan:string(binary_to_list(String)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, Modules} -> + lists:foreach( + fun ({Module, Args}) -> + ejabberd_cluster:call( + Node, gen_mod, start_module, + [Host, Module, Args]) + end, + Modules), + {result, undefined}; + _ -> + Txt = <<"Parse failed">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end; + _ -> + Txt = <<"Scan failed">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end + end end; set_form(_From, _Host, [<<"running nodes">>, ENode, <<"backup">>, <<"backup">>], Lang, XData) -> case search_running_node(ENode) of - false -> - Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; - Node -> - case lists:keysearch(<<"path">>, 1, XData) of - false -> - Txt = <<"No 'path' found in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, {_, [String]}} -> - case ejabberd_cluster:call(Node, mnesia, backup, [String]) of - {badrpc, Reason} -> - ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s " - "failed: ~p", [String, Node, Reason]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {error, Reason} -> - ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s " - "failed: ~p", [String, Node, Reason]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> {result, []} - end; - _ -> - Txt = <<"Incorrect value of 'path' in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end + false -> + Txt = <<"No running node found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + Node -> + case xmpp_util:get_xdata_values(<<"path">>, XData) of + [] -> + Txt = <<"No 'path' found in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + [String] -> + case ejabberd_cluster:call(Node, mnesia, backup, [String]) of + {badrpc, Reason} -> + ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s " + "failed: ~p", [String, Node, Reason]), + {error, xmpp:err_internal_server_error()}; + {error, Reason} -> + ?ERROR_MSG("RPC call mnesia:backup(~s) to node ~s " + "failed: ~p", [String, Node, Reason]), + {error, xmpp:err_internal_server_error()}; + _ -> + {result, undefined} + end; + _ -> + Txt = <<"Incorrect value of 'path' in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end end; set_form(_From, _Host, [<<"running nodes">>, ENode, <<"backup">>, <<"restore">>], Lang, XData) -> case search_running_node(ENode) of - false -> - Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; - Node -> - case lists:keysearch(<<"path">>, 1, XData) of - false -> - Txt = <<"No 'path' found in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, {_, [String]}} -> - case ejabberd_cluster:call(Node, ejabberd_admin, restore, [String]) - of - {badrpc, Reason} -> - ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node " - "~s failed: ~p", [String, Node, Reason]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {error, Reason} -> - ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node " - "~s failed: ~p", [String, Node, Reason]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> {result, []} - end; - _ -> - Txt = <<"Incorrect value of 'path' in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end + false -> + Txt = <<"No running node found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + Node -> + case xmpp_util:get_xdata_values(<<"path">>, XData) of + [] -> + Txt = <<"No 'path' found in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + [String] -> + case ejabberd_cluster:call(Node, ejabberd_admin, + restore, [String]) of + {badrpc, Reason} -> + ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node " + "~s failed: ~p", [String, Node, Reason]), + {error, xmpp:err_internal_server_error()}; + {error, Reason} -> + ?ERROR_MSG("RPC call ejabberd_admin:restore(~s) to node " + "~s failed: ~p", [String, Node, Reason]), + {error, xmpp:err_internal_server_error()}; + _ -> + {result, undefined} + end; + _ -> + Txt = <<"Incorrect value of 'path' in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end end; set_form(_From, _Host, [<<"running nodes">>, ENode, <<"backup">>, <<"textfile">>], Lang, XData) -> case search_running_node(ENode) of - false -> - Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; - Node -> - case lists:keysearch(<<"path">>, 1, XData) of - false -> - Txt = <<"No 'path' found in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, {_, [String]}} -> - case ejabberd_cluster:call(Node, ejabberd_admin, dump_to_textfile, - [String]) - of - {badrpc, Reason} -> - ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) " - "to node ~s failed: ~p", [String, Node, Reason]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {error, Reason} -> - ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) " - "to node ~s failed: ~p", [String, Node, Reason]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> {result, []} - end; - _ -> - Txt = <<"Incorrect value of 'path' in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end + false -> + Txt = <<"No running node found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + Node -> + case xmpp_util:get_xdata_values(<<"path">>, XData) of + [] -> + Txt = <<"No 'path' found in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + [String] -> + case ejabberd_cluster:call(Node, ejabberd_admin, + dump_to_textfile, [String]) of + {badrpc, Reason} -> + ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) " + "to node ~s failed: ~p", [String, Node, Reason]), + {error, xmpp:err_internal_server_error()}; + {error, Reason} -> + ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~s) " + "to node ~s failed: ~p", [String, Node, Reason]), + {error, xmpp:err_internal_server_error()}; + _ -> + {result, undefined} + end; + _ -> + Txt = <<"Incorrect value of 'path' in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end end; set_form(_From, _Host, [<<"running nodes">>, ENode, <<"import">>, <<"file">>], Lang, XData) -> case search_running_node(ENode) of - false -> - Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; - Node -> - case lists:keysearch(<<"path">>, 1, XData) of - false -> - Txt = <<"No 'path' found in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, {_, [String]}} -> - ejabberd_cluster:call(Node, jd2ejd, import_file, [String]), - {result, []}; - _ -> - Txt = <<"Incorrect value of 'path' in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end + false -> + Txt = <<"No running node found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + Node -> + case xmpp_util:get_xdata_values(<<"path">>, XData) of + [] -> + Txt = <<"No 'path' found in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + [String] -> + ejabberd_cluster:call(Node, jd2ejd, import_file, [String]), + {result, undefined}; + _ -> + Txt = <<"Incorrect value of 'path' in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end end; set_form(_From, _Host, [<<"running nodes">>, ENode, <<"import">>, <<"dir">>], Lang, XData) -> case search_running_node(ENode) of - false -> - Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; - Node -> - case lists:keysearch(<<"path">>, 1, XData) of - false -> - Txt = <<"No 'path' found in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, {_, [String]}} -> - ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]), - {result, []}; - _ -> - Txt = <<"Incorrect value of 'path' in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end + false -> + Txt = <<"No running node found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + Node -> + case xmpp_util:get_xdata_values(<<"path">>, XData) of + [] -> + Txt = <<"No 'path' found in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + [String] -> + ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]), + {result, undefined}; + _ -> + Txt = <<"Incorrect value of 'path' in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end end; set_form(From, Host, [<<"running nodes">>, ENode, <<"restart">>], _Lang, @@ -1805,75 +1451,72 @@ set_form(From, Host, stop_node(From, Host, ENode, stop, XData); set_form(_From, Host, [<<"config">>, <<"acls">>], Lang, XData) -> - case lists:keysearch(<<"acls">>, 1, XData) of - {value, {_, Strings}} -> - String = lists:foldl(fun (S, Res) -> - <> - end, - <<"">>, Strings), - case erl_scan:string(binary_to_list(String)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, ACLs} -> - acl:add_list(Host, ACLs, true), - {result, []}; - _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)} - end; - _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)} - end; - _ -> - Txt = <<"No 'acls' found in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} + case xmpp_util:get_xdata_values(<<"acls">>, XData) of + [] -> + Txt = <<"No 'acls' found in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + Strings -> + String = lists:foldl(fun (S, Res) -> + <> + end, <<"">>, Strings), + case erl_scan:string(binary_to_list(String)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, ACLs} -> + acl:add_list(Host, ACLs, true), + {result, undefined}; + _ -> + Txt = <<"Parse failed">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end; + _ -> + {error, xmpp:err_bad_request(<<"Scan failed">>, Lang)} + end end; set_form(_From, Host, [<<"config">>, <<"access">>], Lang, XData) -> - SetAccess = fun (Rs) -> - mnesia:transaction(fun () -> - Os = mnesia:select(access, - [{{access, - {'$1', - '$2'}, - '$3'}, - [{'==', - '$2', - Host}], - ['$_']}]), - lists:foreach(fun (O) -> - mnesia:delete_object(O) - end, - Os), - lists:foreach(fun ({access, - Name, - Rules}) -> - mnesia:write({access, - {Name, - Host}, - Rules}) - end, - Rs) - end) - end, - case lists:keysearch(<<"access">>, 1, XData) of - {value, {_, Strings}} -> - String = lists:foldl(fun (S, Res) -> - <> - end, - <<"">>, Strings), - case erl_scan:string(binary_to_list(String)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, Rs} -> - case SetAccess(Rs) of - {atomic, _} -> {result, []}; - _ -> {error, ?ERR_BAD_REQUEST} - end; - _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Parse failed">>)} - end; - _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Scan failed">>)} - end; - _ -> - Txt = <<"No 'access' found in data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} + SetAccess = + fun(Rs) -> + mnesia:transaction( + fun () -> + Os = mnesia:select( + access, + ets:fun2ms( + fun({access, {_, H}, _} = O) when H == Host -> + O + end)), + lists:foreach(fun mnesia:delete_object/1, Os), + lists:foreach( + fun({access, Name, Rules}) -> + mnesia:write({access, {Name, Host}, Rules}) + end, Rs) + end) + end, + case xmpp_util:get_xdata_values(<<"access">>, XData) of + [] -> + Txt = <<"No 'access' found in data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + Strings -> + String = lists:foldl(fun (S, Res) -> + <> + end, <<"">>, Strings), + case erl_scan:string(binary_to_list(String)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, Rs} -> + case SetAccess(Rs) of + {atomic, _} -> + {result, undefined}; + _ -> + {error, xmpp:err_bad_request()} + end; + _ -> + Txt = <<"Parse failed">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end; + _ -> + {error, xmpp:err_bad_request(<<"Scan failed">>, Lang)} + end end; set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang, XData) -> @@ -1887,7 +1530,7 @@ set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang, true = Server == Host orelse get_permission_level(From) == global, ejabberd_auth:try_register(User, Server, Password), - {result, []}; + {result, undefined}; set_form(From, Host, ?NS_ADMINL(<<"delete-user">>), _Lang, XData) -> AccountStringList = get_values(<<"accountjids">>, @@ -1905,7 +1548,7 @@ set_form(From, Host, ?NS_ADMINL(<<"delete-user">>), AccountStringList), [ejabberd_auth:remove_user(User, Server) || {User, Server} <- ASL2], - {result, []}; + {result, undefined}; set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>), Lang, XData) -> AccountString = get_value(<<"accountjid">>, XData), @@ -1914,14 +1557,14 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>), LServer = JID#jid.lserver, true = LServer == Host orelse get_permission_level(From) == global, - Xmlelement = ?SERRT_POLICY_VIOLATION(Lang, <<"has been kicked">>), + Xmlelement = xmpp:serr_policy_violation(<<"has been kicked">>, Lang), case JID#jid.lresource of <<>> -> SIs = mnesia:dirty_select(session, [{#session{usr = {LUser, LServer, '_'}, sid = '$1', info = '$2', - _ = '_'}, + _ = '_'}, [], [{{'$1', '$2'}}]}]), Pids = [P || {{_, P}, Info} <- SIs, not proplists:get_bool(offline, Info)], @@ -1934,14 +1577,14 @@ set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>), [{#session{usr = {LUser, LServer, R}, sid = '$1', info = '$2', - _ = '_'}, + _ = '_'}, [], [{{'$1', '$2'}}]}]), case proplists:get_bool(offline, Info) of true -> ok; false -> Pid ! {kick, kicked_by_admin, Xmlelement} end end, - {result, []}; + {result, undefined}; set_form(From, Host, ?NS_ADMINL(<<"get-user-password">>), Lang, XData) -> AccountString = get_value(<<"accountjid">>, XData), @@ -1953,14 +1596,12 @@ set_form(From, Host, Password = ejabberd_auth:get_password(User, Server), true = is_binary(Password), {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - ?XFIELD(<<"jid-single">>, <<"Jabber ID">>, - <<"accountjid">>, AccountString), - ?XFIELD(<<"text-single">>, <<"Password">>, - <<"password">>, Password)]}]}; + #xdata{type = form, + fields = [?HFIELD(), + ?XFIELD('jid-single', <<"Jabber ID">>, + <<"accountjid">>, AccountString), + ?XFIELD('text-single', <<"Password">>, + <<"password">>, Password)]}}; set_form(From, Host, ?NS_ADMINL(<<"change-user-password">>), _Lang, XData) -> AccountString = get_value(<<"accountjid">>, XData), @@ -1972,7 +1613,7 @@ set_form(From, Host, get_permission_level(From) == global, true = ejabberd_auth:is_user_exists(User, Server), ejabberd_auth:set_password(User, Server, Password), - {result, []}; + {result, undefined}; set_form(From, Host, ?NS_ADMINL(<<"get-user-lastlogin">>), Lang, XData) -> AccountString = get_value(<<"accountjid">>, XData), @@ -1992,22 +1633,19 @@ set_form(From, Host, TimeStamp = {Shift div 1000000, 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(Lang, <<"Online">>) end, {result, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = - [?HFIELD(), - ?XFIELD(<<"jid-single">>, <<"Jabber ID">>, - <<"accountjid">>, AccountString), - ?XFIELD(<<"text-single">>, <<"Last login">>, - <<"lastlogin">>, FLast)]}]}; + #xdata{type = form, + fields = [?HFIELD(), + ?XFIELD('jid-single', <<"Jabber ID">>, + <<"accountjid">>, AccountString), + ?XFIELD('text-single', <<"Last login">>, + <<"lastlogin">>, FLast)]}}; set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang, XData) -> AccountString = get_value(<<"accountjid">>, XData), @@ -2021,33 +1659,29 @@ set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang, IPs1 = [ejabberd_sm:get_user_ip(User, Server, Resource) || Resource <- Resources], IPs = [<<(jlib:ip_to_list(IP))/binary, ":", - (jlib:integer_to_binary(Port))/binary>> + (integer_to_binary(Port))/binary>> || {IP, Port} <- IPs1], Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]), - Rostersize = jlib:integer_to_binary(erlang:length(Items)), + Rostersize = integer_to_binary(erlang:length(Items)), {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - ?XFIELD(<<"jid-single">>, <<"Jabber ID">>, - <<"accountjid">>, AccountString), - ?XFIELD(<<"text-single">>, <<"Roster size">>, - <<"rostersize">>, Rostersize), - ?XMFIELD(<<"text-multi">>, <<"IP addresses">>, - <<"ipaddresses">>, IPs), - ?XMFIELD(<<"text-multi">>, <<"Resources">>, - <<"onlineresources">>, Resources)]}]}; + #xdata{type = form, + fields = [?HFIELD(), + ?XFIELD('jid-single', <<"Jabber ID">>, + <<"accountjid">>, AccountString), + ?XFIELD('text-single', <<"Roster size">>, + <<"rostersize">>, Rostersize), + ?XMFIELD('text-multi', <<"IP addresses">>, + <<"ipaddresses">>, IPs), + ?XMFIELD('text-multi', <<"Resources">>, + <<"onlineresources">>, Resources)]}}; set_form(_From, _Host, _, _Lang, _XData) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. + {error, xmpp:err_service_unavailable()}. get_value(Field, XData) -> hd(get_values(Field, XData)). get_values(Field, XData) -> - {value, {_, ValueList}} = lists:keysearch(Field, 1, - XData), - ValueList. + xmpp_util:get_xdata_values(Field, XData). search_running_node(SNode) -> search_running_node(SNode, @@ -2061,57 +1695,34 @@ search_running_node(SNode, [Node | Nodes]) -> end. stop_node(From, Host, ENode, Action, XData) -> - Delay = jlib:binary_to_integer(get_value(<<"delay">>, - XData)), + Delay = binary_to_integer(get_value(<<"delay">>, XData)), Subject = case get_value(<<"subject">>, XData) of - <<"">> -> []; - S -> - [#xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"subject">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, S}]}]}] + <<"">> -> + []; + S -> + [#xdata_field{var = <<"subject">>, values = [S]}] end, - Announcement = case get_values(<<"announcement">>, - XData) - of - [] -> []; - As -> - [#xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"body">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Line}]} - || Line <- As]}] + Announcement = case get_values(<<"announcement">>, XData) of + [] -> + []; + As -> + [#xdata_field{var = <<"body">>, values = As}] end, case Subject ++ Announcement of - [] -> ok; - SubEls -> - Request = #adhoc_request{node = - ?NS_ADMINX(<<"announce-allhosts">>), - action = <<"complete">>, - xdata = - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_XDATA}, - {<<"type">>, <<"submit">>}], - children = SubEls}, - others = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_XDATA}, - {<<"type">>, <<"submit">>}], - children = SubEls}]}, - To = jid:make(<<"">>, Host, <<"">>), - mod_announce:announce_commands(empty, From, To, Request) + [] -> + ok; + Fields -> + Request = #adhoc_command{node = ?NS_ADMINX(<<"announce-allhosts">>), + action = complete, + xdata = #xdata{type = submit, + fields = Fields}}, + To = jid:make(Host), + mod_announce:announce_commands(empty, From, To, Request) end, Time = timer:seconds(Delay), Node = jlib:binary_to_atom(ENode), - {ok, _} = timer:apply_after(Time, rpc, call, - [Node, init, Action, []]), - {result, []}. + {ok, _} = timer:apply_after(Time, rpc, call, [Node, init, Action, []]), + {result, undefined}. get_last_info(User, Server) -> case gen_mod:is_loaded(Server, mod_last) of @@ -2120,113 +1731,83 @@ get_last_info(User, Server) -> end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - +-spec adhoc_sm_commands(adhoc_command(), jid(), jid(), adhoc_command()) -> adhoc_command(). adhoc_sm_commands(_Acc, From, - #jid{user = User, server = Server, lserver = LServer} = - _To, - #adhoc_request{lang = Lang, node = <<"config">>, - action = Action, xdata = XData} = - Request) -> + #jid{user = User, server = Server, lserver = LServer}, + #adhoc_command{lang = Lang, node = <<"config">>, + action = Action, xdata = XData} = Request) -> case acl:match_rule(LServer, configure, From) of - deny -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; - allow -> - ActionIsExecute = lists:member(Action, - [<<"">>, <<"execute">>, - <<"complete">>]), - if Action == <<"cancel">> -> - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); - XData == false, ActionIsExecute -> - case get_sm_form(User, Server, <<"config">>, Lang) of - {result, Form} -> - adhoc:produce_response(Request, - #adhoc_response{status = - executing, - elements = Form}); - {error, Error} -> {error, Error} - end; - XData /= false, ActionIsExecute -> - case jlib:parse_xdata_submit(XData) of - invalid -> - Txt = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - Fields -> - set_sm_form(User, Server, <<"config">>, Request, Fields) - end; - true -> - Txt = <<"Incorrect action or data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end + deny -> + {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; + allow -> + ActionIsExecute = Action == execute orelse Action == complete, + if Action == cancel -> + xmpp_util:make_adhoc_response( + Request, #adhoc_command{status = canceled}); + XData == undefined, ActionIsExecute -> + case get_sm_form(User, Server, <<"config">>, Lang) of + {result, Form} -> + xmpp_util:make_adhoc_response( + Request, #adhoc_command{status = executing, + xdata = Form}); + {error, Error} -> + {error, Error} + end; + XData /= undefined, ActionIsExecute -> + set_sm_form(User, Server, <<"config">>, Request); + true -> + Txt = <<"Unexpected action">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end end; adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc. get_sm_form(User, Server, <<"config">>, Lang) -> {result, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [?HFIELD(), - #xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(?T(Lang, <<"Administration of ">>))/binary, - User/binary>>}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, ?T(Lang, <<"Action on user">>)}, - {<<"var">>, <<"action">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, <<"edit">>}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - ?T(Lang, <<"Edit Properties">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"edit">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - ?T(Lang, <<"Remove User">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"remove">>}]}]}]}, - ?XFIELD(<<"text-private">>, <<"Password">>, - <<"password">>, - (ejabberd_auth:get_password_s(User, Server)))]}]}; + #xdata{type = form, + title = <<(?T(Lang, <<"Administration of ">>))/binary, User/binary>>, + fields = + [?HFIELD(), + #xdata_field{ + type = 'list-single', + label = ?T(Lang, <<"Action on user">>), + var = <<"action">>, + values = [<<"edit">>], + options = [#xdata_option{ + label = ?T(Lang, <<"Edit Properties">>), + value = <<"edit">>}, + #xdata_option{ + label = ?T(Lang, <<"Remove User">>), + value = <<"remove">>}]}, + ?XFIELD('text-private', <<"Password">>, + <<"password">>, + ejabberd_auth:get_password_s(User, Server))]}}; get_sm_form(_User, _Server, _Node, _Lang) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. + {error, xmpp:err_service_unavailable()}. set_sm_form(User, Server, <<"config">>, - #adhoc_request{lang = Lang, node = Node, - sessionid = SessionID}, - XData) -> - Response = #adhoc_response{lang = Lang, node = Node, - sessionid = SessionID, status = completed}, - case lists:keysearch(<<"action">>, 1, XData) of - {value, {_, [<<"edit">>]}} -> - case lists:keysearch(<<"password">>, 1, XData) of - {value, {_, [Password]}} -> - ejabberd_auth:set_password(User, Server, Password), - adhoc:produce_response(Response); - _ -> - Txt = <<"No 'password' found in data form">>, - {error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)} - end; - {value, {_, [<<"remove">>]}} -> - catch ejabberd_auth:remove_user(User, Server), - adhoc:produce_response(Response); - _ -> - Txt = <<"Incorrect value of 'action' in data form">>, - {error, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)} + #adhoc_command{lang = Lang, node = Node, + sid = SessionID, xdata = XData}) -> + Response = #adhoc_command{lang = Lang, node = Node, + sid = SessionID, status = completed}, + case xmpp_util:get_xdata_values(<<"action">>, XData) of + [<<"edit">>] -> + case xmpp_util:get_xdata_values(<<"password">>, XData) of + [Password] -> + ejabberd_auth:set_password(User, Server, Password), + xmpp_util:make_adhoc_response(Response); + _ -> + Txt = <<"No 'password' found in data form">>, + {error, xmpp:err_not_acceptable(Txt, Lang)} + end; + [<<"remove">>] -> + catch ejabberd_auth:remove_user(User, Server), + xmpp_util:make_adhoc_response(Response); + _ -> + Txt = <<"Incorrect value of 'action' in data form">>, + {error, xmpp:err_not_acceptable(Txt, Lang)} end; -set_sm_form(_User, _Server, _Node, _Request, _Fields) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. +set_sm_form(_User, _Server, _Node, _Request) -> + {error, xmpp:err_service_unavailable()}. mod_opt_type(_) -> []. diff --git a/src/mod_configure2.erl b/src/mod_configure2.erl deleted file mode 100644 index 85b7740d0..000000000 --- a/src/mod_configure2.erl +++ /dev/null @@ -1,223 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_configure2.erl -%%% Author : Alexey Shchepin -%%% Purpose : Support for online configuration of ejabberd -%%% Created : 26 Oct 2003 by Alexey Shchepin -%%% -%%% -%%% 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(mod_configure2). - --behaviour(ejabberd_config). - --author('alexey@process-one.net'). - --behaviour(gen_mod). - --export([start/2, stop/1, process_local_iq/3, - mod_opt_type/1, opt_type/1, depends/2]). - --include("ejabberd.hrl"). --include("logger.hrl"). - --include("jlib.hrl"). - --define(NS_ECONFIGURE, - <<"http://ejabberd.jabberstudio.org/protocol/con" - "figure">>). - -start(Host, Opts) -> - IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, - one_queue), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, - ?NS_ECONFIGURE, ?MODULE, process_local_iq, - IQDisc), - ok. - -stop(Host) -> - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, - ?NS_ECONFIGURE). - -process_local_iq(From, To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case acl:match_rule(To#jid.lserver, configure, From) of - deny -> - Txt = <<"Denied by ACL">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - allow -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)]}; - %%case fxml:get_tag_attr_s("type", SubEl) of - %% "cancel" -> - %% IQ#iq{type = result, - %% sub_el = [{xmlelement, "query", - %% [{"xmlns", XMLNS}], []}]}; - %% "submit" -> - %% XData = jlib:parse_xdata_submit(SubEl), - %% case XData of - %% invalid -> - %% IQ#iq{type = error, - %% sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - %% _ -> - %% Node = - %% string:tokens( - %% fxml:get_tag_attr_s("node", SubEl), - %% "/"), - %% case set_form(Node, Lang, XData) of - %% {result, Res} -> - %% IQ#iq{type = result, - %% sub_el = [{xmlelement, "query", - %% [{"xmlns", XMLNS}], - %% Res - %% }]}; - %% {error, Error} -> - %% IQ#iq{type = error, - %% sub_el = [SubEl, Error]} - %% end - %% end; - %% _ -> - %% IQ#iq{type = error, - %% sub_el = [SubEl, ?ERR_NOT_ALLOWED]} - %%end; - get -> - case process_get(SubEl, Lang) of - {result, Res} -> IQ#iq{type = result, sub_el = [Res]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end - end - end. - -process_get(#xmlel{name = <<"info">>}, _Lang) -> - S2SConns = ejabberd_s2s:dirty_get_connections(), - TConns = lists:usort([element(2, C) || C <- S2SConns]), - Attrs = [{<<"registered-users">>, - iolist_to_binary(integer_to_list(mnesia:table_info(passwd, - size)))}, - {<<"online-users">>, - iolist_to_binary(integer_to_list(mnesia:table_info(presence, - size)))}, - {<<"running-nodes">>, - iolist_to_binary(integer_to_list(length(mnesia:system_info(running_db_nodes))))}, - {<<"stopped-nodes">>, - iolist_to_binary(integer_to_list(length(lists:usort(mnesia:system_info(db_nodes) - ++ - mnesia:system_info(extra_db_nodes)) - -- - mnesia:system_info(running_db_nodes))))}, - {<<"outgoing-s2s-servers">>, - iolist_to_binary(integer_to_list(length(TConns)))}], - {result, - #xmlel{name = <<"info">>, - attrs = [{<<"xmlns">>, ?NS_ECONFIGURE} | Attrs], - children = []}}; -process_get(#xmlel{name = <<"welcome-message">>, - attrs = Attrs}, _Lang) -> - {Subj, Body} = ejabberd_config:get_option( - welcome_message, - fun({Subj, Body}) -> - {iolist_to_binary(Subj), - iolist_to_binary(Body)} - end, - {<<"">>, <<"">>}), - {result, - #xmlel{name = <<"welcome-message">>, attrs = Attrs, - children = - [#xmlel{name = <<"subject">>, attrs = [], - children = [{xmlcdata, Subj}]}, - #xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Body}]}]}}; -process_get(#xmlel{name = <<"registration-watchers">>, - attrs = Attrs}, _Lang) -> - SubEls = ejabberd_config:get_option( - registration_watchers, - fun(JIDs) when is_list(JIDs) -> - lists:map( - fun(J) -> - #xmlel{name = <<"jid">>, attrs = [], - children = [{xmlcdata, - iolist_to_binary(J)}]} - end, JIDs) - end, []), - {result, - #xmlel{name = <<"registration_watchers">>, - attrs = Attrs, children = SubEls}}; -process_get(#xmlel{name = <<"acls">>, attrs = Attrs}, _Lang) -> - Str = iolist_to_binary(io_lib:format("~p.", - [ets:tab2list(acl)])), - {result, - #xmlel{name = <<"acls">>, attrs = Attrs, - children = [{xmlcdata, Str}]}}; -process_get(#xmlel{name = <<"access">>, - attrs = Attrs}, _Lang) -> - Str = iolist_to_binary(io_lib:format("~p.", - [ets:select(local_config, - [{{local_config, {access, '$1'}, - '$2'}, - [], - [{{access, '$1', - '$2'}}]}])])), - {result, - #xmlel{name = <<"access">>, attrs = Attrs, - children = [{xmlcdata, Str}]}}; -process_get(#xmlel{name = <<"last">>, attrs = Attrs}, Lang) -> - case catch mnesia:dirty_select(last_activity, - [{{last_activity, '_', '$1', '_'}, [], - ['$1']}]) - of - {'EXIT', _Reason} -> - Txt = <<"Database failure">>, - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)}; - Vals -> - TimeStamp = p1_time_compat:system_time(seconds), - Str = list_to_binary( - [[jlib:integer_to_binary(TimeStamp - V), - <<" ">>] || V <- Vals]), - {result, - #xmlel{name = <<"last">>, attrs = Attrs, - children = [{xmlcdata, Str}]}} - end; -%%process_get({xmlelement, Name, Attrs, SubEls}) -> -%% {result, }; -process_get(_, _) -> {error, ?ERR_BAD_REQUEST}. - -depends(_Host, _Opts) -> - []. - -mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; -mod_opt_type(_) -> [iqdisc]. - -opt_type(registration_watchers) -> - fun (JIDs) when is_list(JIDs) -> - lists:map(fun (J) -> - #xmlel{name = <<"jid">>, attrs = [], - children = - [{xmlcdata, iolist_to_binary(J)}]} - end, - JIDs) - end; -opt_type(welcome_message) -> - fun ({Subj, Body}) -> - {iolist_to_binary(Subj), iolist_to_binary(Body)} - end; -opt_type(_) -> [registration_watchers, welcome_message]. diff --git a/src/mod_delegation.erl b/src/mod_delegation.erl index f2d1a13b5..2cf9525fc 100644 --- a/src/mod_delegation.erl +++ b/src/mod_delegation.erl @@ -1,538 +1,362 @@ -%%%-------------------------------------------------------------------------------------- +%%%------------------------------------------------------------------- %%% File : mod_delegation.erl %%% Author : Anna Mukharram -%%% Purpose : This module is an implementation for XEP-0355: Namespace Delegation -%%%-------------------------------------------------------------------------------------- - +%%% Purpose : XEP-0355: Namespace Delegation +%%% +%%% +%%% 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(mod_delegation). -author('amuhar3@gmail.com'). --behaviour(gen_mod). - -protocol({xep, 0355, '0.3'}). --export([start/2, stop/1, depends/2, mod_opt_type/1]). +-behaviour(gen_server). +-behaviour(gen_mod). --export([advertise_delegations/1, process_iq/3, - disco_local_features/5, disco_sm_features/5, - disco_local_identity/5, disco_sm_identity/5, disco_info/5, clean/0]). +%% API +-export([start_link/2]). +-export([start/2, stop/1, mod_opt_type/1, depends/2]). +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). +-export([component_connected/1, component_disconnected/2, + ejabberd_local/1, ejabberd_sm/1, decode_iq_subel/1, + disco_local_features/5, disco_sm_features/5, + disco_local_identity/5, disco_sm_identity/5]). --include_lib("stdlib/include/ms_transform.hrl"). +-include("ejabberd.hrl"). +-include("logger.hrl"). +-include("xmpp.hrl"). --include("ejabberd_service.hrl"). +-type disco_acc() :: {error, stanza_error()} | {result, [binary()]} | empty. +-record(state, {server_host = <<"">> :: binary(), + delegations = dict:new() :: ?TDICT}). --define(CLEAN_INTERVAL, timer:minutes(10)). - -%%%-------------------------------------------------------------------------------------- -%%% API -%%%-------------------------------------------------------------------------------------- - -start(Host, _Opts) -> - mod_disco:register_feature(Host, ?NS_DELEGATION), - %% start timer for hooks_tmp table cleaning - timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []), - - ejabberd_hooks:add(disco_local_features, Host, ?MODULE, - disco_local_features, 500), %% This hook should be the last - ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, - disco_local_identity, 500), - ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, - disco_sm_identity, 500), - ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, - disco_sm_features, 500), - ejabberd_hooks:add(disco_info, Host, ?MODULE, - disco_info, 500). +%%%=================================================================== +%%% API +%%%=================================================================== +start_link(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?MODULE), + gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). +start(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?MODULE), + PingSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, + transient, 2000, worker, [?MODULE]}, + supervisor:start_child(ejabberd_sup, PingSpec). stop(Host) -> - mod_disco:unregister_feature(Host, ?NS_DELEGATION), - ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, - disco_local_features, 500), - ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, - disco_local_identity, 500), - ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, - disco_sm_identity, 500), - ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, - disco_sm_features, 500), - ejabberd_hooks:delete(disco_info, Host, ?MODULE, - disco_info, 500). + Proc = gen_mod:get_module_proc(Host, ?MODULE), + gen_server:call(Proc, stop), + supervisor:delete_child(ejabberd_sup, Proc). -depends(_Host, _Opts) -> []. +mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; +mod_opt_type(namespaces) -> validate_fun(); +mod_opt_type(_) -> + [namespaces, iqdisc]. -mod_opt_type(_Opt) -> []. +depends(_, _) -> + []. -%%%-------------------------------------------------------------------------------------- -%%% 4.2 Functions to advertise service of delegated namespaces -%%%-------------------------------------------------------------------------------------- -attribute_tag(Attrs) -> - lists:map(fun(Attr) -> - #xmlel{name = <<"attribute">>, attrs = [{<<"name">> , Attr}]} - end, Attrs). +-spec decode_iq_subel(xmpp_element()) -> xmpp_element(); + (xmlel()) -> xmlel(). +%% Tell gen_iq_handler not to auto-decode IQ payload +decode_iq_subel(El) -> + El. -delegations(From, To, Delegations) -> - {Elem0, DelegatedNs} = - lists:foldl(fun({Ns, FiltAttr}, {Acc, AccNs}) -> - case ets:insert_new(delegated_namespaces, - {Ns, FiltAttr, self(), To, {}, {}}) of - true -> - Attrs = - if - FiltAttr == [] -> - ?DEBUG("namespace ~s is delegated to ~s with" - " no filtering attributes ~n",[Ns, To]), - []; - true -> - ?DEBUG("namespace ~s is delegated to ~s with" - " ~p filtering attributes ~n",[Ns, To, FiltAttr]), - attribute_tag(FiltAttr) - end, - add_iq_handlers(Ns), - {[#xmlel{name = <<"delegated">>, - attrs = [{<<"namespace">>, Ns}], - children = Attrs}| Acc], [{Ns, FiltAttr}|AccNs]}; - false -> {Acc, AccNs} - end - end, {[], []}, Delegations), - case Elem0 of - [] -> {ignore, DelegatedNs}; - _ -> - Elem1 = #xmlel{name = <<"delegation">>, - attrs = [{<<"xmlns">>, ?NS_DELEGATION}], - children = Elem0}, - Id = randoms:get_string(), - {#xmlel{name = <<"message">>, - attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}], - children = [Elem1]}, DelegatedNs} - end. +-spec component_connected(binary()) -> ok. +component_connected(Host) -> + lists:foreach( + fun(ServerHost) -> + Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), + gen_server:cast(Proc, {component_connected, Host}) + end, ?MYHOSTS). -add_iq_handlers(Ns) -> - lists:foreach(fun(Host) -> - IQDisc = - gen_mod:get_module_opt(Host, ?MODULE, iqdisc, - fun gen_iq_handler:check_type/1, one_queue), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, - Ns, ?MODULE, - process_iq, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, - Ns, ?MODULE, - process_iq, IQDisc) - end, ?MYHOSTS). +-spec component_disconnected(binary(), binary()) -> ok. +component_disconnected(Host, _Reason) -> + lists:foreach( + fun(ServerHost) -> + Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), + gen_server:cast(Proc, {component_disconnected, Host}) + end, ?MYHOSTS). -advertise_delegations(#state{delegations = []}) -> []; -advertise_delegations(StateData) -> - {Delegated, DelegatedNs} = - delegations(?MYNAME, StateData#state.host, StateData#state.delegations), - if - Delegated /= ignore -> - ejabberd_service:send_element(StateData, Delegated), - % server asks available features for delegated namespaces - disco_info(StateData#state{delegations = DelegatedNs}); - true -> ok - end, - DelegatedNs. +-spec ejabberd_local(iq()) -> iq(). +ejabberd_local(IQ) -> + process_iq(IQ, ejabberd_local). -%%%-------------------------------------------------------------------------------------- -%%% Delegated namespaces hook -%%%-------------------------------------------------------------------------------------- +-spec ejabberd_sm(iq()) -> iq(). +ejabberd_sm(IQ) -> + process_iq(IQ, ejabberd_sm). -check_filter_attr([], _Children) -> true; -check_filter_attr(_FilterAttr, []) -> false; -check_filter_attr(FilterAttr, [#xmlel{} = Stanza|_]) -> - Attrs = proplists:get_keys(Stanza#xmlel.attrs), - lists:all(fun(Attr) -> - lists:member(Attr, Attrs) - end, FilterAttr); -check_filter_attr(_FilterAttr, _Children) -> false. +-spec disco_local_features(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc(). +disco_local_features(Acc, From, To, Node, Lang) -> + disco_features(Acc, From, To, Node, Lang, ejabberd_local). --spec get_client_server([attr()]) -> {jid(), jid()}. +-spec disco_sm_features(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc(). +disco_sm_features(Acc, From, To, Node, Lang) -> + disco_features(Acc, From, To, Node, Lang, ejabberd_sm). -get_client_server(Attrs) -> - Client = fxml:get_attr_s(<<"from">>, Attrs), - ClientJID = jid:from_string(Client), - ServerJID = jid:from_string(ClientJID#jid.lserver), - {ClientJID, ServerJID}. +-spec disco_local_identity(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc(). +disco_local_identity(Acc, From, To, Node, Lang) -> + disco_identity(Acc, From, To, Node, Lang, ejabberd_local). -decapsulate_result(#xmlel{children = []}) -> ok; -decapsulate_result(#xmlel{children = Children}) -> - decapsulate_result0(Children). +-spec disco_sm_identity(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc(). +disco_sm_identity(Acc, From, To, Node, Lang) -> + disco_identity(Acc, From, To, Node, Lang, ejabberd_sm). -decapsulate_result0([]) -> ok; -decapsulate_result0([#xmlel{name = <<"delegation">>, - attrs = [{<<"xmlns">>, ?NS_DELEGATION}]} = Packet]) -> - decapsulate_result1(Packet#xmlel.children); -decapsulate_result0(_Children) -> ok. +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== +init([Host, _Opts]) -> + ejabberd_hooks:add(component_connected, ?MODULE, + component_connected, 50), + ejabberd_hooks:add(component_disconnected, ?MODULE, + component_disconnected, 50), + ejabberd_hooks:add(disco_local_features, Host, ?MODULE, + disco_local_features, 50), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, + disco_sm_features, 50), + ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, + disco_local_identity, 50), + ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, + disco_sm_identity, 50), + {ok, #state{server_host = Host}}. -decapsulate_result1([]) -> ok; -decapsulate_result1([#xmlel{name = <<"forwarded">>, - attrs = [{<<"xmlns">>, ?NS_FORWARD}]} = Packet]) -> - decapsulate_result2(Packet#xmlel.children); -decapsulate_result1(_Children) -> ok. +handle_call(get_delegations, _From, State) -> + {reply, {ok, State#state.delegations}, State}; +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. -decapsulate_result2([]) -> ok; -decapsulate_result2([#xmlel{name = <<"iq">>, attrs = Attrs} = Packet]) -> - Ns = fxml:get_attr_s(<<"xmlns">>, Attrs), - if - Ns /= <<"jabber:client">> -> - ok; - true -> Packet +handle_cast({component_connected, Host}, State) -> + ServerHost = State#state.server_host, + To = jid:make(Host), + NSAttrsAccessList = gen_mod:get_module_opt( + ServerHost, ?MODULE, namespaces, + validate_fun(), []), + lists:foreach( + fun({NS, _Attrs, Access}) -> + case acl:match_rule(ServerHost, Access, To) of + allow -> + send_disco_queries(ServerHost, Host, NS); + deny -> + ok + end + end, NSAttrsAccessList), + {noreply, State}; +handle_cast({disco_info, Type, Host, NS, Info}, State) -> + From = jid:make(State#state.server_host), + To = jid:make(Host), + case dict:find({NS, Type}, State#state.delegations) of + error -> + Msg = #message{from = From, to = To, + sub_els = [#delegation{delegated = [#delegated{ns = NS}]}]}, + Delegations = dict:store({NS, Type}, {Host, Info}, State#state.delegations), + gen_iq_handler:add_iq_handler(Type, State#state.server_host, NS, + ?MODULE, Type, one_queue), + ejabberd_router:route(From, To, Msg), + ?INFO_MSG("Namespace '~s' is delegated to external component '~s'", + [NS, Host]), + {noreply, State#state{delegations = Delegations}}; + {ok, {AnotherHost, _}} -> + ?WARNING_MSG("Failed to delegate namespace '~s' to " + "external component '~s' because it's already " + "delegated to '~s'", + [NS, Host, AnotherHost]), + {noreply, State} end; -decapsulate_result2(_Children) -> ok. +handle_cast({component_disconnected, Host}, State) -> + ServerHost = State#state.server_host, + Delegations = + dict:filter( + fun({NS, Type}, {H, _}) when H == Host -> + ?INFO_MSG("Remove delegation of namespace '~s' " + "from external component '~s'", + [NS, Host]), + gen_iq_handler:remove_iq_handler(Type, ServerHost, NS), + false; + (_, _) -> + true + end, State#state.delegations), + {noreply, State#state{delegations = Delegations}}; +handle_cast(_Msg, State) -> + {noreply, State}. --spec check_iq(xmlel(), xmlel()) -> xmlel() | ignore. +handle_info(_Info, State) -> + {noreply, State}. -check_iq(#xmlel{attrs = Attrs} = Packet, - #xmlel{attrs = AttrsOrigin} = OriginPacket) -> - % Id attribute of OriginPacket Must be equil to Packet Id attribute - Id1 = fxml:get_attr_s(<<"id">>, Attrs), - Id2 = fxml:get_attr_s(<<"id">>, AttrsOrigin), - % From attribute of OriginPacket Must be equil to Packet To attribute - From = fxml:get_attr_s(<<"from">>, AttrsOrigin), - To = fxml:get_attr_s(<<"to">>, Attrs), - % Type attribute Must be error or result - Type = fxml:get_attr_s(<<"type">>, Attrs), - if - ((Type == <<"result">>) or (Type == <<"error">>)), - Id1 == Id2, To == From -> - NewPacket = jlib:remove_attr(<<"xmlns">>, Packet), - %% We can send the decapsulated stanza from Server to Client (To) - NewPacket; - true -> - %% service-unavailable - Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE), - Err +terminate(_Reason, State) -> + %% Note: we don't remove component_* hooks because they are global + %% and might be registered within a module on another virtual host + ServerHost = State#state.server_host, + ejabberd_hooks:delete(disco_local_features, ServerHost, ?MODULE, + disco_local_features, 50), + ejabberd_hooks:delete(disco_sm_features, ServerHost, ?MODULE, + disco_sm_features, 50), + ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, + disco_local_identity, 50), + ejabberd_hooks:delete(disco_sm_identity, ServerHost, ?MODULE, + disco_sm_identity, 50), + lists:foreach( + fun({NS, Type}) -> + gen_iq_handler:remove_iq_handler(Type, ServerHost, NS) + end, dict:fetch_keys(State#state.delegations)). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +-spec get_delegations(binary()) -> ?TDICT. +get_delegations(Host) -> + Proc = gen_mod:get_module_proc(Host, ?MODULE), + try gen_server:call(Proc, get_delegations) of + {ok, Delegations} -> Delegations + catch exit:{noproc, _} -> + %% No module is loaded for this virtual host + dict:new() + end. + +-spec process_iq(iq(), ejabberd_local | ejabberd_sm) -> ignore | iq(). +process_iq(#iq{to = To, lang = Lang, sub_els = [SubEl]} = IQ, Type) -> + LServer = To#jid.lserver, + NS = xmpp:get_ns(SubEl), + Delegations = get_delegations(LServer), + case dict:find({NS, Type}, Delegations) of + {ok, {Host, _}} -> + Delegation = #delegation{ + forwarded = #forwarded{xml_els = [xmpp:encode(IQ)]}}, + NewFrom = jid:make(LServer), + NewTo = jid:make(Host), + ejabberd_local:route_iq( + NewFrom, NewTo, + #iq{type = set, + from = NewFrom, + to = NewTo, + sub_els = [Delegation]}, + fun(Result) -> process_iq_result(IQ, Result) end), + ignore; + error -> + Txt = <<"Failed to map delegated namespace to external component">>, + xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) + end. + +-spec process_iq_result(iq(), iq()) -> ok. +process_iq_result(#iq{from = From, to = To, id = ID, lang = Lang} = IQ, + #iq{type = result} = ResIQ) -> + try + #delegation{forwarded = #forwarded{xml_els = [SubEl]}} = + xmpp:get_subtag(ResIQ, #delegation{}), + case xmpp:decode(SubEl, ?NS_CLIENT, [ignore_els]) of + #iq{from = To, to = From, type = Type, id = ID} = Reply + when Type == error; Type == result -> + ejabberd_router:route(To, From, Reply) + end + catch _:_ -> + ?ERROR_MSG("got iq-result with invalid delegated " + "payload:~n~s", [xmpp:pp(ResIQ)]), + Txt = <<"External component failure">>, + Err = xmpp:err_internal_server_error(Txt, Lang), + ejabberd_router:route_error(To, From, IQ, Err) end; -check_iq(_Packet, _OriginPacket) -> ignore. +process_iq_result(#iq{from = From, to = To}, #iq{type = error} = ResIQ) -> + Err = xmpp:set_from_to(ResIQ, To, From), + ejabberd_router:route(To, From, Err); +process_iq_result(#iq{from = From, to = To, lang = Lang} = IQ, timeout) -> + Txt = <<"External component timeout">>, + Err = xmpp:err_internal_server_error(Txt, Lang), + ejabberd_router:route_error(To, From, IQ, Err). --spec manage_service_result(atom(), atom(), binary(), xmlel()) -> ok. +-spec send_disco_queries(binary(), binary(), binary()) -> ok. +send_disco_queries(LServer, Host, NS) -> + From = jid:make(LServer), + To = jid:make(Host), + lists:foreach( + fun({Type, Node}) -> + ejabberd_local:route_iq( + From, To, #iq{type = get, from = From, to = To, + sub_els = [#disco_info{node = Node}]}, + fun(#iq{type = result, sub_els = [SubEl]}) -> + try xmpp:decode(SubEl) of + #disco_info{} = Info-> + Proc = gen_mod:get_module_proc(LServer, ?MODULE), + gen_server:cast( + Proc, {disco_info, Type, Host, NS, Info}); + _ -> + ok + catch _:{xmpp_codec, _} -> + ok + end; + (_) -> + ok + end) + end, [{ejabberd_local, <<(?NS_DELEGATION)/binary, "::", NS/binary>>}, + {ejabberd_sm, <<(?NS_DELEGATION)/binary, ":bare:", NS/binary>>}]). -manage_service_result(HookRes, HookErr, Service, OriginPacket) -> - fun(Packet) -> - {ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs), - Server = ClientJID#jid.lserver, - - ets:delete(hooks_tmp, {HookRes, Server}), - ets:delete(hooks_tmp, {HookErr, Server}), - % Check Packet "from" attribute - % It Must be equil to current service host - From = fxml:get_attr_s(<<"from">> , Packet#xmlel.attrs), - if - From == Service -> - % decapsulate iq result - ResultIQ = decapsulate_result(Packet), - ServResponse = check_iq(ResultIQ, OriginPacket), - if - ServResponse /= ignore -> - ejabberd_router:route(ServerJID, ClientJID, ServResponse); - true -> ok - end; - true -> - % service unavailable - Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(ServerJID, ClientJID, Err) - end - end. - --spec manage_service_error(atom(), atom(), xmlel()) -> ok. - -manage_service_error(HookRes, HookErr, OriginPacket) -> - fun(_Packet) -> - {ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs), - Server = ClientJID#jid.lserver, - ets:delete(hooks_tmp, {HookRes, Server}), - ets:delete(hooks_tmp, {HookErr, Server}), - Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(ServerJID, ClientJID, Err) - end. - - --spec forward_iq(binary(), binary(), xmlel()) -> ok. - -forward_iq(Server, Service, Packet) -> - Elem0 = #xmlel{name = <<"forwarded">>, - attrs = [{<<"xmlns">>, ?NS_FORWARD}], children = [Packet]}, - Elem1 = #xmlel{name = <<"delegation">>, - attrs = [{<<"xmlns">>, ?NS_DELEGATION}], children = [Elem0]}, - Id = randoms:get_string(), - Elem2 = #xmlel{name = <<"iq">>, - attrs = [{<<"from">>, Server}, {<<"to">>, Service}, - {<<"type">>, <<"set">>}, {<<"id">>, Id}], - children = [Elem1]}, - - HookRes = {iq, result, Id}, - HookErr = {iq, error, Id}, - - FunRes = manage_service_result(HookRes, HookErr, Service, Packet), - FunErr = manage_service_error(HookRes, HookErr, Packet), - - Timestamp = p1_time_compat:system_time(seconds), - ets:insert(hooks_tmp, {{HookRes, Server}, FunRes, Timestamp}), - ets:insert(hooks_tmp, {{HookErr, Server}, FunErr, Timestamp}), - - From = jid:make(<<"">>, Server, <<"">>), - To = jid:make(<<"">>, Service, <<"">>), - ejabberd_router:route(From, To, Elem2). - -process_iq(From, #jid{lresource = <<"">>} = To, - #iq{type = Type, xmlns = XMLNS} = IQ) -> - %% check if stanza directed to server - %% or directed to the bare JID of the sender - case ((Type == get) or (Type == set)) of - true -> - Packet = jlib:iq_to_xml(IQ), - #xmlel{name = <<"iq">>, attrs = Attrs, children = Children} = Packet, - AttrsNew = [{<<"xmlns">>, <<"jabber:client">>} | Attrs], - AttrsNew2 = jlib:replace_from_to_attrs(jid:to_string(From), - jid:to_string(To), AttrsNew), - case ets:lookup(delegated_namespaces, XMLNS) of - [{XMLNS, FiltAttr, _Pid, ServiceHost, _, _}] -> - case check_filter_attr(FiltAttr, Children) of - true -> - forward_iq(From#jid.server, ServiceHost, - Packet#xmlel{attrs = AttrsNew2}); - _ -> ok - end; - [] -> ok - end, - ignore; - _ -> - ignore +-spec disco_features(disco_acc(), jid(), jid(), binary(), binary(), + ejabberd_local | ejabberd_sm) -> disco_acc(). +disco_features(Acc, _From, To, <<"">>, _Lang, Type) -> + Delegations = get_delegations(To#jid.lserver), + Features = my_features(Type) ++ + lists:flatmap( + fun({{_, T}, {_, Info}}) when T == Type -> + Info#disco_info.features; + (_) -> + [] + end, dict:to_list(Delegations)), + case Acc of + empty when Features /= [] -> {result, Features}; + {result, Fs} -> {result, Fs ++ Features}; + _ -> Acc end; -process_iq(_From, _To, _IQ) -> ignore. +disco_features(Acc, _, _, _, _, _) -> + Acc. -%%%-------------------------------------------------------------------------------------- -%%% 7. Discovering Support -%%%-------------------------------------------------------------------------------------- +-spec disco_identity(disco_acc(), jid(), jid(), binary(), binary(), + ejabberd_local | ejabberd_sm) -> disco_acc(). +disco_identity(Acc, _From, To, <<"">>, _Lang, Type) -> + Delegations = get_delegations(To#jid.lserver), + Identities = lists:flatmap( + fun({{_, T}, {_, Info}}) when T == Type -> + Info#disco_info.identities; + (_) -> + [] + end, dict:to_list(Delegations)), + case Acc of + empty when Identities /= [] -> {result, Identities}; + {result, Ids} -> {result, Ids ++ Identities}; + Acc -> Acc + end; +disco_identity(Acc, _From, _To, _Node, _Lang, _Type) -> + Acc. -decapsulate_features(#xmlel{attrs = Attrs} = Packet, Node) -> - case fxml:get_attr_s(<<"node">>, Attrs) of - Node -> - PREFIX = << ?NS_DELEGATION/binary, "::" >>, - Size = byte_size(PREFIX), - BARE_PREFIX = << ?NS_DELEGATION/binary, ":bare:" >>, - SizeBare = byte_size(BARE_PREFIX), +my_features(ejabberd_local) -> [?NS_DELEGATION]; +my_features(ejabberd_sm) -> []. - Features = [Feat || #xmlel{attrs = [{<<"var">>, Feat}]} <- - fxml:get_subtags(Packet, <<"feature">>)], - - Identity = [I || I <- fxml:get_subtags(Packet, <<"identity">>)], - - Exten = [I || I <- fxml:get_subtags_with_xmlns(Packet, <<"x">>, ?NS_XDATA)], - - case Node of - << PREFIX:Size/binary, NS/binary >> -> - ets:update_element(delegated_namespaces, NS, - {5, {Features, Identity, Exten}}); - << BARE_PREFIX:SizeBare/binary, NS/binary >> -> - ets:update_element(delegated_namespaces, NS, - {6, {Features, Identity, Exten}}); - _ -> ok - end; - _ -> ok - end; -decapsulate_features(_Packet, _Node) -> ok. - --spec disco_result(atom(), atom(), binary()) -> ok. - -disco_result(HookRes, HookErr, Node) -> - fun(Packet) -> - Tag = fxml:get_subtag_with_xmlns(Packet, <<"query">>, ?NS_DISCO_INFO), - decapsulate_features(Tag, Node), - - ets:delete(hooks_tmp, {HookRes, ?MYNAME}), - ets:delete(hooks_tmp, {HookErr, ?MYNAME}) +validate_fun() -> + fun(L) -> + lists:map( + fun({NS, Opts}) -> + Attrs = proplists:get_value(filtering, Opts, []), + Access = proplists:get_value(access, Opts, none), + {NS, Attrs, Access} + end, L) end. - --spec disco_error(atom(), atom()) -> ok. - -disco_error(HookRes, HookErr) -> - fun(_Packet) -> - ets:delete(hooks_tmp, {HookRes, ?MYNAME}), - ets:delete(hooks_tmp, {HookErr, ?MYNAME}) - end. - --spec disco_info(state()) -> ok. - -disco_info(StateData) -> - disco_info(StateData, <<"::">>), - disco_info(StateData, <<":bare:">>). - --spec disco_info(state(), binary()) -> ok. - -disco_info(StateData, Sep) -> - lists:foreach(fun({Ns, _FilterAttr}) -> - Id = randoms:get_string(), - Node = << ?NS_DELEGATION/binary, Sep/binary, Ns/binary >>, - - HookRes = {iq, result, Id}, - HookErr = {iq, error, Id}, - - FunRes = disco_result(HookRes, HookErr, Node), - FunErr = disco_error(HookRes, HookErr), - - Timestamp = p1_time_compat:system_time(seconds), - ets:insert(hooks_tmp, {{HookRes, ?MYNAME}, FunRes, Timestamp}), - ets:insert(hooks_tmp, {{HookErr, ?MYNAME}, FunErr, Timestamp}), - - Tag = #xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}, - {<<"node">>, Node}], - children = []}, - DiscoReq = #xmlel{name = <<"iq">>, - attrs = [{<<"type">>, <<"get">>}, {<<"id">>, Id}, - {<<"from">>, ?MYNAME}, - {<<"to">>, StateData#state.host }], - children = [Tag]}, - ejabberd_service:send_element(StateData, DiscoReq) - - end, StateData#state.delegations). - - -disco_features(Acc, Bare) -> - Fun = fun(Feat) -> - ets:foldl(fun({Ns, _, _, _, _, _}, A) -> - A or str:prefix(Ns, Feat) - end, false, delegated_namespaces) - end, - % delete feature namespace which is delegated to service - Features = lists:filter(fun ({{Feature, _Host}}) -> - not Fun(Feature); - (Feature) when is_binary(Feature) -> - not Fun(Feature) - end, Acc), - % add service features - FeaturesList = - ets:foldl(fun({_, _, _, _, {Feats, _, _}, {FeatsBare, _, _}}, A) -> - if - Bare -> A ++ FeatsBare; - true -> A ++ Feats - end; - (_, A) -> A - end, Features, delegated_namespaces), - {result, FeaturesList}. - -disco_identity(Acc, Bare) -> - % filter delegated identites - Fun = fun(Ident) -> - ets:foldl(fun({_, _, _, _, {_ , I, _}, {_ , IBare, _}}, A) -> - Identity = - if - Bare -> IBare; - true -> I - end, - (fxml:get_attr_s(<<"category">> , Ident) == - fxml:get_attr_s(<<"category">>, Identity)) and - (fxml:get_attr_s(<<"type">> , Ident) == - fxml:get_attr_s(<<"type">>, Identity)) or A; - (_, A) -> A - end, false, delegated_namespaces) - end, - - Identities = - lists:filter(fun (#xmlel{attrs = Attrs}) -> - not Fun(Attrs) - end, Acc), - % add service features - ets:foldl(fun({_, _, _, _, {_, I, _}, {_, IBare, _}}, A) -> - if - Bare -> A ++ IBare; - true -> A ++ I - end; - (_, A) -> A - end, Identities, delegated_namespaces). - -%% xmlns from value element - --spec get_field_value([xmlel()]) -> binary(). - -get_field_value([]) -> <<"">>; -get_field_value([Elem| Elems]) -> - case (fxml:get_attr_s(<<"var">>, Elem#xmlel.attrs) == <<"FORM_TYPE">>) and - (fxml:get_attr_s(<<"type">>, Elem#xmlel.attrs) == <<"hidden">>) of - true -> - Ns = fxml:get_subtag_cdata(Elem, <<"value">>), - if - Ns /= <<"">> -> Ns; - true -> get_field_value(Elems) - end; - _ -> get_field_value(Elems) - end. - -get_info(Acc, Bare) -> - Fun = fun(Feat) -> - ets:foldl(fun({Ns, _, _, _, _, _}, A) -> - (A or str:prefix(Ns, Feat)) - end, false, delegated_namespaces) - end, - Exten = lists:filter(fun(Xmlel) -> - Tags = fxml:get_subtags(Xmlel, <<"field">>), - case get_field_value(Tags) of - <<"">> -> true; - Value -> not Fun(Value) - end - end, Acc), - ets:foldl(fun({_, _, _, _, {_, _, Ext}, {_, _, ExtBare}}, A) -> - if - Bare -> A ++ ExtBare; - true -> A ++ Ext - end; - (_, A) -> A - end, Exten, delegated_namespaces). - -%% 7.2.1 General Case - -disco_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> - Acc; -disco_local_features(Acc, _From, _To, <<>>, _Lang) -> - FeatsOld = case Acc of - {result, I} -> I; - _ -> [] - end, - disco_features(FeatsOld, false); -disco_local_features(Acc, _From, _To, _Node, _Lang) -> - Acc. - -disco_local_identity(Acc, _From, _To, <<>>, _Lang) -> - disco_identity(Acc, false); -disco_local_identity(Acc, _From, _To, _Node, _Lang) -> - Acc. - -%% 7.2.2 Rediction Of Bare JID Disco Info - -disco_sm_features({error, ?ERR_ITEM_NOT_FOUND}, _From, - #jid{lresource = <<"">>}, <<>>, _Lang) -> - disco_features([], true); -disco_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> - Acc; -disco_sm_features(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) -> - FeatsOld = case Acc of - {result, I} -> I; - _ -> [] - end, - disco_features(FeatsOld, true); -disco_sm_features(Acc, _From, _To, _Node, _Lang) -> - Acc. - -disco_sm_identity(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) -> - disco_identity(Acc, true); -disco_sm_identity(Acc, _From, _To, _Node, _Lang) -> - Acc. - -disco_info(Acc, #jid{}, #jid{lresource = <<"">>}, <<>>, _Lang) -> - get_info(Acc, true); -disco_info(Acc, _Host, _Mod, <<>>, _Lang) -> - get_info(Acc, false); -disco_info(Acc, _Host, _Mod, _Node, _Lang) -> - Acc. - -%% clean hooks_tmp table - -clean() -> - ?DEBUG("cleaning ~p ETS table~n", [hooks_tmp]), - Now = p1_time_compat:system_time(seconds), - catch ets:select_delete(hooks_tmp, - ets:fun2ms(fun({_, _, Timestamp}) -> - Now - 300 >= Timestamp - end)), - %% start timer for table cleaning - timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []). diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 2e7b80c18..953d1da10 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -32,10 +32,10 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_local_iq_items/3, - process_local_iq_info/3, get_local_identity/5, +-export([start/2, stop/1, process_local_iq_items/1, + process_local_iq_info/1, get_local_identity/5, get_local_features/5, get_local_services/5, - process_sm_iq_items/3, process_sm_iq_info/3, + process_sm_iq_items/1, process_sm_iq_info/1, get_sm_identity/5, get_sm_features/5, get_sm_items/5, get_info/5, register_feature/2, unregister_feature/2, register_extra_domain/2, unregister_extra_domain/2, @@ -44,8 +44,8 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). - +-include("xmpp.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). -include("mod_roster.hrl"). start(Host, Opts) -> @@ -126,158 +126,131 @@ stop(Host) -> {{'_', Host}}), ok. +-spec register_feature(binary(), binary()) -> true. register_feature(Host, Feature) -> catch ets:new(disco_features, [named_table, ordered_set, public]), ets:insert(disco_features, {{Feature, Host}}). +-spec unregister_feature(binary(), binary()) -> true. unregister_feature(Host, Feature) -> catch ets:new(disco_features, [named_table, ordered_set, public]), ets:delete(disco_features, {Feature, Host}). +-spec register_extra_domain(binary(), binary()) -> true. register_extra_domain(Host, Domain) -> catch ets:new(disco_extra_domains, [named_table, ordered_set, public]), ets:insert(disco_extra_domains, {{Domain, Host}}). +-spec unregister_extra_domain(binary(), binary()) -> true. unregister_extra_domain(Host, Domain) -> catch ets:new(disco_extra_domains, [named_table, ordered_set, public]), ets:delete(disco_extra_domains, {Domain, Host}). -process_local_iq_items(From, To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - Node = fxml:get_tag_attr_s(<<"node">>, SubEl), - Host = To#jid.lserver, - case ejabberd_hooks:run_fold(disco_local_items, Host, - empty, [From, To, Node, Lang]) - of - {result, Items} -> - ANode = case Node of - <<"">> -> []; - _ -> [{<<"node">>, Node}] - end, - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, ?NS_DISCO_ITEMS} | ANode], - children = Items}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end +-spec process_local_iq_items(iq()) -> iq(). +process_local_iq_items(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_local_iq_items(#iq{type = get, lang = Lang, + from = From, to = To, + sub_els = [#disco_items{node = Node}]} = IQ) -> + Host = To#jid.lserver, + case ejabberd_hooks:run_fold(disco_local_items, Host, + empty, [From, To, Node, Lang]) of + {result, Items} -> + xmpp:make_iq_result(IQ, #disco_items{node = Node, items = Items}); + {error, Error} -> + xmpp:make_error(IQ, Error) end. -process_local_iq_info(From, To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - Host = To#jid.lserver, - Node = fxml:get_tag_attr_s(<<"node">>, SubEl), - Identity = ejabberd_hooks:run_fold(disco_local_identity, - Host, [], [From, To, Node, Lang]), - Info = ejabberd_hooks:run_fold(disco_info, Host, [], - [Host, ?MODULE, Node, Lang]), - case ejabberd_hooks:run_fold(disco_local_features, Host, - empty, [From, To, Node, Lang]) - of - {result, Features} -> - ANode = case Node of - <<"">> -> []; - _ -> [{<<"node">>, Node}] - end, - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, ?NS_DISCO_INFO} | ANode], - children = - Identity ++ - Info ++ features_to_xml(Features)}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end +-spec process_local_iq_info(iq()) -> iq(). +process_local_iq_info(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_local_iq_info(#iq{type = get, lang = Lang, + from = From, to = To, + sub_els = [#disco_info{node = Node}]} = IQ) -> + Host = To#jid.lserver, + Identity = ejabberd_hooks:run_fold(disco_local_identity, + Host, [], [From, To, Node, Lang]), + Info = ejabberd_hooks:run_fold(disco_info, Host, [], + [Host, ?MODULE, Node, Lang]), + case ejabberd_hooks:run_fold(disco_local_features, Host, + empty, [From, To, Node, Lang]) of + {result, Features} -> + xmpp:make_iq_result(IQ, #disco_info{node = Node, + identities = Identity, + xdata = Info, + features = Features}); + {error, Error} -> + xmpp:make_error(IQ, Error) end. -get_local_identity(Acc, _From, _To, <<>>, _Lang) -> - Acc ++ - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"server">>}, {<<"type">>, <<"im">>}, - {<<"name">>, <<"ejabberd">>}], - children = []}]; +-spec get_local_identity([identity()], jid(), jid(), + binary(), binary()) -> [identity()]. +get_local_identity(Acc, _From, _To, <<"">>, _Lang) -> + Acc ++ [#identity{category = <<"server">>, + type = <<"im">>, + name = <<"ejabberd">>}]; get_local_identity(Acc, _From, _To, _Node, _Lang) -> Acc. +-spec get_local_features({error, stanza_error()} | {result, [binary()]} | empty, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | {result, [binary()]}. get_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> Acc; -get_local_features(Acc, _From, To, <<>>, _Lang) -> +get_local_features(Acc, _From, To, <<"">>, _Lang) -> Feats = case Acc of - {result, Features} -> Features; - empty -> [] + {result, Features} -> Features; + empty -> [] end, Host = To#jid.lserver, {result, ets:select(disco_features, - [{{{'_', Host}}, [], ['$_']}]) - ++ Feats}; + ets:fun2ms(fun({{F, H}}) when H == Host -> F end)) + ++ Feats}; get_local_features(Acc, _From, _To, _Node, Lang) -> case Acc of {result, _Features} -> Acc; empty -> Txt = <<"No features available">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)} + {error, xmpp:err_item_not_found(Txt, Lang)} end. -features_to_xml(FeatureList) -> - [#xmlel{name = <<"feature">>, - attrs = [{<<"var">>, Feat}], children = []} - || Feat - <- lists:usort(lists:map(fun ({{Feature, _Host}}) -> - Feature; - (Feature) when is_binary(Feature) -> - Feature - end, - FeatureList))]. - -domain_to_xml({Domain}) -> - #xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}], - children = []}; -domain_to_xml(Domain) -> - #xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}], - children = []}. - +-spec get_local_services({error, stanza_error()} | {result, [disco_item()]} | empty, + jid(), jid(), + binary(), binary()) -> + {error, stanza_error()} | {result, [disco_item()]}. get_local_services({error, _Error} = Acc, _From, _To, _Node, _Lang) -> Acc; -get_local_services(Acc, _From, To, <<>>, _Lang) -> +get_local_services(Acc, _From, To, <<"">>, _Lang) -> Items = case Acc of {result, Its} -> Its; empty -> [] end, Host = To#jid.lserver, {result, - lists:usort(lists:map(fun domain_to_xml/1, - get_vh_services(Host) ++ - ets:select(disco_extra_domains, - [{{{'$1', Host}}, [], ['$1']}]))) - ++ Items}; + lists:usort( + lists:map( + fun(Domain) -> #disco_item{jid = jid:make(Domain)} end, + get_vh_services(Host) ++ + ets:select(disco_extra_domains, + ets:fun2ms( + fun({{D, H}}) when H == Host -> D end)))) + ++ Items}; get_local_services({result, _} = Acc, _From, _To, _Node, _Lang) -> Acc; get_local_services(empty, _From, _To, _Node, Lang) -> - {error, ?ERRT_ITEM_NOT_FOUND(Lang, <<"No services available">>)}. + {error, xmpp:err_item_not_found(<<"No services available">>, Lang)}. +-spec get_vh_services(binary()) -> [binary()]. get_vh_services(Host) -> Hosts = lists:sort(fun (H1, H2) -> byte_size(H1) >= byte_size(H2) @@ -300,47 +273,38 @@ get_vh_services(Host) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -process_sm_iq_items(From, To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - case is_presence_subscribed(From, To) of - true -> - Host = To#jid.lserver, - Node = fxml:get_tag_attr_s(<<"node">>, SubEl), - case ejabberd_hooks:run_fold(disco_sm_items, Host, - empty, [From, To, Node, Lang]) - of - {result, Items} -> - ANode = case Node of - <<"">> -> []; - _ -> [{<<"node">>, Node}] - end, - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, ?NS_DISCO_ITEMS} - | ANode], - children = Items}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end; - false -> - Txt = <<"Not subscribed">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]} - end +-spec process_sm_iq_items(iq()) -> iq(). +process_sm_iq_items(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_sm_iq_items(#iq{type = get, lang = Lang, + from = From, to = To, + sub_els = [#disco_items{node = Node}]} = IQ) -> + case is_presence_subscribed(From, To) of + true -> + Host = To#jid.lserver, + case ejabberd_hooks:run_fold(disco_sm_items, Host, + empty, [From, To, Node, Lang]) of + {result, Items} -> + xmpp:make_iq_result( + IQ, #disco_items{node = Node, items = Items}); + {error, Error} -> + xmpp:make_error(IQ, Error) + end; + false -> + Txt = <<"Not subscribed">>, + xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang)) end. +-spec get_sm_items({error, stanza_error()} | {result, [disco_item()]} | empty, + jid(), jid(), + binary(), binary()) -> + {error, stanza_error()} | {result, [disco_item()]}. get_sm_items({error, _Error} = Acc, _From, _To, _Node, _Lang) -> Acc; get_sm_items(Acc, From, - #jid{user = User, server = Server} = To, <<>>, _Lang) -> + #jid{user = User, server = Server} = To, <<"">>, _Lang) -> Items = case Acc of {result, Its} -> Its; empty -> [] @@ -357,12 +321,13 @@ get_sm_items(empty, From, To, _Node, Lang) -> #jid{luser = LFrom, lserver = LSFrom} = From, #jid{luser = LTo, lserver = LSTo} = To, case {LFrom, LSFrom} of - {LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND}; + {LTo, LSTo} -> {error, xmpp:err_item_not_found()}; _ -> Txt = <<"Query to another users is forbidden">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)} + {error, xmpp:err_not_allowed(Txt, Lang)} end. +-spec is_presence_subscribed(jid(), jid()) -> boolean(). is_presence_subscribed(#jid{luser = User, lserver = Server}, #jid{luser = User, lserver = Server}) -> true; is_presence_subscribed(#jid{luser = FromUser, lserver = FromServer}, @@ -377,86 +342,68 @@ is_presence_subscribed(#jid{luser = FromUser, lserver = FromServer}, ejabberd_hooks:run_fold(roster_get, ToServer, [], [{ToUser, ToServer}])). -process_sm_iq_info(From, To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - case is_presence_subscribed(From, To) of - true -> - Host = To#jid.lserver, - Node = fxml:get_tag_attr_s(<<"node">>, SubEl), - Identity = ejabberd_hooks:run_fold(disco_sm_identity, - Host, [], - [From, To, Node, Lang]), - Info = ejabberd_hooks:run_fold(disco_info, Host, [], +-spec process_sm_iq_info(iq()) -> iq(). +process_sm_iq_info(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_sm_iq_info(#iq{type = get, lang = Lang, + from = From, to = To, + sub_els = [#disco_info{node = Node}]} = IQ) -> + case is_presence_subscribed(From, To) of + true -> + Host = To#jid.lserver, + Identity = ejabberd_hooks:run_fold(disco_sm_identity, + Host, [], [From, To, Node, Lang]), - case ejabberd_hooks:run_fold(disco_sm_features, Host, - empty, [From, To, Node, Lang]) - of - {result, Features} -> - ANode = case Node of - <<"">> -> []; - _ -> [{<<"node">>, Node}] - end, - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, ?NS_DISCO_INFO} - | ANode], - children = - Identity ++ Info ++ - features_to_xml(Features)}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end; - false -> - Txt = <<"Not subscribed">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]} - end + Info = ejabberd_hooks:run_fold(disco_info, Host, [], + [From, To, Node, Lang]), + case ejabberd_hooks:run_fold(disco_sm_features, Host, + empty, [From, To, Node, Lang]) of + {result, Features} -> + xmpp:make_iq_result(IQ, #disco_info{node = Node, + identities = Identity, + xdata = Info, + features = Features}); + {error, Error} -> + xmpp:make_error(IQ, Error) + end; + false -> + Txt = <<"Not subscribed">>, + xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang)) end. +-spec get_sm_identity([identity()], jid(), jid(), + binary(), binary()) -> [identity()]. get_sm_identity(Acc, _From, #jid{luser = LUser, lserver = LServer}, _Node, _Lang) -> Acc ++ case ejabberd_auth:is_user_exists(LUser, LServer) of true -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"account">>}, - {<<"type">>, <<"registered">>}], - children = []}]; + [#identity{category = <<"account">>, type = <<"registered">>}]; _ -> [] end. +-spec get_sm_features({error, stanza_error()} | {result, [binary()]} | empty, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | {result, [binary()]}. get_sm_features(empty, From, To, _Node, Lang) -> #jid{luser = LFrom, lserver = LSFrom} = From, #jid{luser = LTo, lserver = LSTo} = To, case {LFrom, LSFrom} of - {LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND}; + {LTo, LSTo} -> {error, xmpp:err_item_not_found()}; _ -> Txt = <<"Query to another users is forbidden">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)} + {error, xmpp:err_not_allowed(Txt, Lang)} end; get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. +-spec get_user_resources(binary(), binary()) -> [disco_item()]. get_user_resources(User, Server) -> Rs = ejabberd_sm:get_user_resources(User, Server), - lists:map(fun (R) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - <>}, - {<<"name">>, User}], - children = []} - end, - lists:sort(Rs)). + [#disco_item{jid = jid:make(User, Server, Resource), name = User} + || Resource <- lists:sort(Rs)]. +-spec transform_module_options(gen_mod:opts()) -> gen_mod:opts(). transform_module_options(Opts) -> lists:map( fun({server_info, Infos}) -> @@ -477,27 +424,22 @@ transform_module_options(Opts) -> %%% Support for: XEP-0157 Contact Addresses for XMPP Services -get_info(_A, Host, Mod, Node, _Lang) when Node == <<>> -> +-spec get_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()]; + ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()]. +get_info(_A, Host, Mod, Node, _Lang) when is_atom(Mod), Node == <<"">> -> Module = case Mod of undefined -> ?MODULE; _ -> Mod end, - Serverinfo_fields = get_fields_xml(Host, Module), - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = - [#xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, ?NS_SERVERINFO}]}]}] - ++ Serverinfo_fields}]; + [#xdata{type = result, + fields = [#xdata_field{type = hidden, + var = <<"FORM_TYPE">>, + values = [?NS_SERVERINFO]} + | get_fields(Host, Module)]}]; get_info(Acc, _, _, _Node, _) -> Acc. -get_fields_xml(Host, Module) -> +-spec get_fields(binary(), module()) -> [xdata_field()]. +get_fields(Host, Module) -> Fields = gen_mod:get_module_opt( Host, ?MODULE, server_info, fun(L) -> @@ -509,31 +451,17 @@ get_fields_xml(Host, Module) -> {Mods, Name, URLs} end, L) end, []), - Fields_good = lists:filter(fun ({Modules, _, _}) -> - case Modules of - all -> true; - Modules -> - lists:member(Module, Modules) - end - end, - Fields), - fields_to_xml(Fields_good). - -fields_to_xml(Fields) -> - [field_to_xml(Field) || Field <- Fields]. - -field_to_xml({_, Var, Values}) -> - Values_xml = values_to_xml(Values), - #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}], - children = Values_xml}. - -values_to_xml(Values) -> - lists:map(fun (Value) -> - #xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Value}]} - end, - Values). + Fields1 = lists:filter(fun ({Modules, _, _}) -> + case Modules of + all -> true; + Modules -> + lists:member(Module, Modules) + end + end, + Fields), + [#xdata_field{var = Var, values = Values} || {_, Var, Values} <- Fields1]. +-spec depends(binary(), gen_mod:opts()) -> []. depends(_Host, _Opts) -> []. diff --git a/src/mod_echo.erl b/src/mod_echo.erl index da3f5cf0f..e7d64dd67 100644 --- a/src/mod_echo.erl +++ b/src/mod_echo.erl @@ -42,7 +42,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -record(state, {host = <<"">> :: binary()}). @@ -118,10 +118,10 @@ handle_cast(_Msg, State) -> {noreply, State}. handle_info({route, From, To, Packet}, State) -> Packet2 = case From#jid.user of <<"">> -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"User part of JID in 'from' is empty">>, - jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, Txt)); + xmpp:make_error( + Packet, xmpp:err_bad_request(Txt, Lang)); _ -> Packet end, do_client_version(disabled, To, From), @@ -168,37 +168,27 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %% using exactly the same JID. We add a (mostly) random resource to %% try to guarantee that the received response matches the request sent. %% Finally, the received response is printed in the ejabberd log file. + +%% THIS IS **NOT** HOW TO WRITE ejabberd CODE. THIS CODE IS RETARDED. + do_client_version(disabled, _From, _To) -> ok; do_client_version(enabled, From, To) -> - ToS = jid:to_string(To), - Random_resource = - iolist_to_binary(integer_to_list(randoms:uniform(100000))), + Random_resource = randoms:get_string(), From2 = From#jid{resource = Random_resource, lresource = Random_resource}, - Packet = #xmlel{name = <<"iq">>, - attrs = [{<<"to">>, ToS}, {<<"type">>, <<"get">>}], - children = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_VERSION}], - children = []}]}, + ID = randoms:get_string(), + Packet = #iq{from = From, to = To, type = get, + id = randoms:get_string(), + sub_els = [#version{}]}, ejabberd_router:route(From2, To, Packet), - Els = receive - {route, To, From2, IQ} -> - #xmlel{name = <<"query">>, children = List} = - fxml:get_subtag(IQ, <<"query">>), - List - after 5000 -> % Timeout in miliseconds: 5 seconds - [] - end, - Values = [{Name, Value} - || #xmlel{name = Name, attrs = [], - children = [{xmlcdata, Value}]} - <- Els], - Values_string1 = [io_lib:format("~n~s: ~p", [N, V]) - || {N, V} <- Values], - Values_string2 = iolist_to_binary(Values_string1), - ?INFO_MSG("Information of the client: ~s~s", - [ToS, Values_string2]). + receive + {route, To, From2, + #iq{id = ID, type = result, sub_els = [#version{} = V]}} -> + ?INFO_MSG("Version of the client ~s:~n~s", + [jid:to_string(To), xmpp:pp(V)]) + after 5000 -> % Timeout in miliseconds: 5 seconds + [] + end. depends(_Host, _Opts) -> []. diff --git a/src/mod_fail2ban.erl b/src/mod_fail2ban.erl index c57ac21b0..cc3b4bf7f 100644 --- a/src/mod_fail2ban.erl +++ b/src/mod_fail2ban.erl @@ -52,6 +52,8 @@ start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?MODULE), gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). +-spec c2s_auth_result(boolean(), binary(), binary(), + {inet:ip_address(), non_neg_integer()}) -> ok. c2s_auth_result(false, _User, LServer, {Addr, _Port}) -> case is_whitelisted(LServer, Addr) of true -> @@ -71,11 +73,15 @@ c2s_auth_result(false, _User, LServer, {Addr, _Port}) -> ets:insert(failed_auth, {Addr, N+1, UnbanTS, MaxFailures}); [] -> ets:insert(failed_auth, {Addr, 1, UnbanTS, MaxFailures}) - end + end, + ok end; c2s_auth_result(true, _User, _Server, _AddrPort) -> ok. +-spec check_bl_c2s({true, binary(), binary()} | false, + {inet:ip_address(), non_neg_integer()}, + binary()) -> {stop, {true, binary(), binary()}} | false. check_bl_c2s(_Acc, Addr, Lang) -> case ets:lookup(failed_auth, Addr) of [{Addr, N, TS, MaxFailures}] when N >= MaxFailures -> diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl index 491383769..3700060cb 100644 --- a/src/mod_http_api.erl +++ b/src/mod_http_api.erl @@ -77,7 +77,7 @@ -export([start/2, stop/1, process/2, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("logger.hrl"). -include("ejabberd_http.hrl"). @@ -134,28 +134,28 @@ depends(_Host, _Opts) -> extract_auth(#request{auth = HTTPAuth, ip = {IP, _}}) -> Info = case HTTPAuth of - {SJID, Pass} -> - case jid:from_string(SJID) of + {SJID, Pass} -> + case jid:from_string(SJID) of #jid{luser = User, lserver = Server} -> - case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of + case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of true -> #{usr => {User, Server, <<"">>}, caller_server => Server}; false -> {error, invalid_auth} - end; - _ -> + end; + _ -> {error, invalid_auth} - end; - {oauth, Token, _} -> + end; + {oauth, Token, _} -> case ejabberd_oauth:check_token(Token) of {ok, {U, S}, Scope} -> #{usr => {U, S, <<"">>}, oauth_scope => Scope, caller_server => S}; {false, Reason} -> {error, Reason} - end; - _ -> + end; + _ -> #{} - end, + end, case Info of Map when is_map(Map) -> Map#{caller_module => ?MODULE, ip => IP}; @@ -186,9 +186,9 @@ process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) -> throw:{error, unknown_command} -> json_format({404, 44, <<"Command not found.">>}); _:{error,{_,invalid_json}} = _Err -> - ?DEBUG("Bad Request: ~p", [_Err]), - badrequest_response(<<"Invalid JSON input">>); - _:_Error -> + ?DEBUG("Bad Request: ~p", [_Err]), + badrequest_response(<<"Invalid JSON input">>); + _:_Error -> ?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]), badrequest_response() end; @@ -247,11 +247,11 @@ extract_args(Data) -> get_api_version(#request{path = Path}) -> get_api_version(lists:reverse(Path)); get_api_version([<<"v", String/binary>> | Tail]) -> - case catch jlib:binary_to_integer(String) of - N when is_integer(N) -> - N; - _ -> - get_api_version(Tail) + case catch binary_to_integer(String) of + N when is_integer(N) -> + N; + _ -> + get_api_version(Tail) end; get_api_version([_Head | Tail]) -> get_api_version(Tail); @@ -273,7 +273,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> fun ({Key, binary}, Acc) -> [{Key, <<>>}|Acc]; ({Key, string}, Acc) -> - [{Key, <<>>}|Acc]; + [{Key, ""}|Acc]; ({Key, integer}, Acc) -> [{Key, 0}|Acc]; ({Key, {list, _}}, Acc) -> @@ -295,7 +295,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> {401, jlib:atom_to_binary(Why)}; throw:{not_allowed, Msg} -> {401, iolist_to_binary(Msg)}; - throw:{error, account_unprivileged} -> + throw:{error, account_unprivileged} -> {403, 31, <<"Command need to be run with admin priviledge.">>}; throw:{error, access_rules_unauthorized} -> {403, 32, <<"AccessRules: Account associated to token does not have the right to perform the operation.">>}; @@ -406,10 +406,10 @@ format_arg(Elements, {list, ElementsDef}) format_arg(Arg, integer) when is_integer(Arg) -> Arg; format_arg(Arg, binary) when is_list(Arg) -> process_unicode_codepoints(Arg); format_arg(Arg, binary) when is_binary(Arg) -> Arg; -format_arg(Arg, string) when is_list(Arg) -> process_unicode_codepoints(Arg); -format_arg(Arg, string) when is_binary(Arg) -> Arg; +format_arg(Arg, string) when is_list(Arg) -> Arg; +format_arg(Arg, string) when is_binary(Arg) -> binary_to_list(Arg); format_arg(undefined, binary) -> <<>>; -format_arg(undefined, string) -> <<>>; +format_arg(undefined, string) -> ""; format_arg(Arg, Format) -> ?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]), throw({invalid_parameter, @@ -431,24 +431,24 @@ match(Args, Spec) -> format_command_result(Cmd, Auth, Result, Version) -> {_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version), case {ResultFormat, Result} of - {{_, rescode}, V} when V == true; V == ok -> - {200, 0}; - {{_, rescode}, _} -> - {200, 1}; + {{_, rescode}, V} when V == true; V == ok -> + {200, 0}; + {{_, rescode}, _} -> + {200, 1}; {_, {error, ErrorAtom, Code, Msg}} -> format_error_result(ErrorAtom, Code, Msg); {{_, restuple}, {V, Text}} when V == true; V == ok -> {200, iolist_to_binary(Text)}; {{_, restuple}, {ErrorAtom, Msg}} -> format_error_result(ErrorAtom, 0, Msg); - {{_, {list, _}}, _V} -> - {_, L} = format_result(Result, ResultFormat), - {200, L}; - {{_, {tuple, _}}, _V} -> - {_, T} = format_result(Result, ResultFormat), - {200, T}; - _ -> - {200, {[format_result(Result, ResultFormat)]}} + {{_, {list, _}}, _V} -> + {_, L} = format_result(Result, ResultFormat), + {200, L}; + {{_, {tuple, _}}, _V} -> + {_, T} = format_result(Result, ResultFormat), + {200, T}; + _ -> + {200, {[format_result(Result, ResultFormat)]}} end. format_result(Atom, {Name, atom}) -> @@ -503,8 +503,8 @@ unauthorized_response() -> invalid_token_response() -> json_error(401, 10, <<"Oauth Token is invalid or expired.">>). -outofscope_response() -> - json_error(401, 11, <<"Token does not grant usage to command required scope.">>). +%% outofscope_response() -> +%% json_error(401, 11, <<"Token does not grant usage to command required scope.">>). badrequest_response() -> badrequest_response(<<"400 Bad Request">>). diff --git a/src/mod_http_bind.erl b/src/mod_http_bind.erl index 9a3a379f7..68500f2c4 100644 --- a/src/mod_http_bind.erl +++ b/src/mod_http_bind.erl @@ -42,7 +42,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_http.hrl"). @@ -89,7 +89,7 @@ stop(_Host) -> setup_database() -> migrate_database(), - mnesia:create_table(http_bind, + ejabberd_mnesia:create(?MODULE, http_bind, [{ram_copies, [node()]}, {attributes, record_info(fields, http_bind)}]). diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl index 37e02edd8..a896cb8b4 100644 --- a/src/mod_http_fileserver.erl +++ b/src/mod_http_fileserver.erl @@ -51,15 +51,12 @@ -include("ejabberd.hrl"). -include("logger.hrl"). -include("ejabberd_http.hrl"). - --include("jlib.hrl"). - -include_lib("kernel/include/file.hrl"). -record(state, {host, docroot, accesslog, accesslogfd, directory_indices, custom_headers, default_content_type, - content_types = []}). + content_types = [], user_access = none}). -define(PROCNAME, ejabberd_mod_http_fileserver). @@ -136,7 +133,8 @@ start_link(Host, Opts) -> init([Host, Opts]) -> try initialize(Host, Opts) of {DocRoot, AccessLog, AccessLogFD, DirectoryIndices, - CustomHeaders, DefaultContentType, ContentTypes} -> + CustomHeaders, DefaultContentType, ContentTypes, + UserAccess} -> {ok, #state{host = Host, accesslog = AccessLog, accesslogfd = AccessLogFD, @@ -144,7 +142,8 @@ init([Host, Opts]) -> directory_indices = DirectoryIndices, custom_headers = CustomHeaders, default_content_type = DefaultContentType, - content_types = ContentTypes}} + content_types = ContentTypes, + user_access = UserAccess}} catch throw:Reason -> {stop, Reason} @@ -168,7 +167,15 @@ initialize(Host, Opts) -> []), DefaultContentType = gen_mod:get_opt(default_content_type, Opts, fun iolist_to_binary/1, - ?DEFAULT_CONTENT_TYPE), + ?DEFAULT_CONTENT_TYPE), + UserAccess0 = gen_mod:get_opt(must_authenticate_with, Opts, + mod_opt_type(must_authenticate_with), + []), + UserAccess = case UserAccess0 of + [] -> none; + _ -> + dict:from_list(UserAccess0) + end, ContentTypes = build_list_content_types( gen_mod:get_opt(content_types, Opts, fun(L) when is_list(L) -> @@ -183,7 +190,7 @@ initialize(Host, Opts) -> [str:join([[$*, K, " -> ", V] || {K, V} <- ContentTypes], <<", ">>)]), {DocRoot, AccessLog, AccessLogFD, DirectoryIndices, - CustomHeaders, DefaultContentType, ContentTypes}. + CustomHeaders, DefaultContentType, ContentTypes, UserAccess}. %% @spec (AdminCTs::[CT], Default::[CT]) -> [CT] @@ -249,10 +256,11 @@ try_open_log(FN, Host) -> %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- -handle_call({serve, LocalPath}, _From, State) -> - Reply = serve(LocalPath, State#state.docroot, State#state.directory_indices, +handle_call({serve, LocalPath, Auth}, _From, State) -> + Reply = serve(LocalPath, Auth, State#state.docroot, State#state.directory_indices, State#state.custom_headers, - State#state.default_content_type, State#state.content_types), + State#state.default_content_type, State#state.content_types, + State#state.user_access), {reply, Reply, State}; handle_call(_Request, _From, State) -> {reply, ok, State}. @@ -308,9 +316,9 @@ code_change(_OldVsn, State, _Extra) -> %% @doc Handle an HTTP request. %% LocalPath is the part of the requested URL path that is "local to the module". %% Returns the page to be sent back to the client and/or HTTP status code. -process(LocalPath, Request) -> +process(LocalPath, #request{host = Host, auth = Auth} = Request) -> ?DEBUG("Requested ~p", [LocalPath]), - try gen_server:call(get_proc_name(Request#request.host), {serve, LocalPath}) of + try gen_server:call(get_proc_name(Host), {serve, LocalPath, Auth}) of {FileSize, Code, Headers, Contents} -> add_to_log(FileSize, Code, Request), {Code, Headers, Contents} @@ -321,21 +329,38 @@ process(LocalPath, Request) -> ejabberd_web:error(not_found) end. -serve(LocalPath, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentType, ContentTypes) -> - FileName = filename:join(filename:split(DocRoot) ++ LocalPath), - case file:read_file_info(FileName) of - {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND; - {error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND; - {error, eacces} -> ?HTTP_ERR_FORBIDDEN; - {ok, #file_info{type = directory}} -> serve_index(FileName, - DirectoryIndices, - CustomHeaders, - DefaultContentType, - ContentTypes); - {ok, FileInfo} -> serve_file(FileInfo, FileName, - CustomHeaders, - DefaultContentType, - ContentTypes) + +serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentType, + ContentTypes, UserAccess) -> + CanProceed = case {UserAccess, Auth} of + {none, _} -> true; + {_, {User, Pass}} -> + case dict:find(User, UserAccess) of + {ok, Pass} -> true; + _ -> false + end; + _ -> + false + end, + case CanProceed of + true -> + FileName = filename:join(filename:split(DocRoot) ++ LocalPath), + case file:read_file_info(FileName) of + {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND; + {error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND; + {error, eacces} -> ?HTTP_ERR_FORBIDDEN; + {ok, #file_info{type = directory}} -> serve_index(FileName, + DirectoryIndices, + CustomHeaders, + DefaultContentType, + ContentTypes); + {ok, FileInfo} -> serve_file(FileInfo, FileName, + CustomHeaders, + DefaultContentType, + ContentTypes) + end; + _ -> + ?HTTP_ERR_FORBIDDEN end. %% Troll through the directory indices attempting to find one which @@ -469,6 +494,14 @@ mod_opt_type(default_content_type) -> mod_opt_type(directory_indices) -> fun (L) when is_list(L) -> L end; mod_opt_type(docroot) -> fun (A) -> A end; +mod_opt_type(must_authenticate_with) -> + fun (L) when is_list(L) -> + lists:map(fun(UP) when is_binary(UP) -> + [K, V] = binary:split(UP, <<":">>), + {K, V} + end, L) + end; mod_opt_type(_) -> [accesslog, content_types, custom_headers, - default_content_type, directory_indices, docroot]. + default_content_type, directory_indices, docroot, + must_authenticate_with]. diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl index b166f2b66..37eaad27a 100644 --- a/src/mod_http_upload.erl +++ b/src/mod_http_upload.erl @@ -35,7 +35,7 @@ -define(FORMAT(Error), file:format_error(Error)). -define(URL_ENC(URL), binary_to_list(ejabberd_http:url_encode(URL))). -define(ADDR_TO_STR(IP), ejabberd_config:may_hide_data(jlib:ip_to_list(IP))). --define(STR_TO_INT(Str, B), jlib:binary_to_integer(iolist_to_binary(Str), B)). +-define(STR_TO_INT(Str, B), binary_to_integer(iolist_to_binary(Str), B)). -define(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>). -define(CONTENT_TYPES, [{<<".avi">>, <<"video/avi">>}, @@ -92,7 +92,7 @@ -include("ejabberd.hrl"). -include("ejabberd_http.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("logger.hrl"). -record(state, @@ -360,9 +360,9 @@ handle_cast(Request, State) -> -spec handle_info(timeout | _, state()) -> {noreply, state()}. -handle_info({route, From, To, #xmlel{name = <<"iq">>} = Stanza}, State) -> - Request = jlib:iq_query_info(Stanza), - {Reply, NewState} = case process_iq(From, Request, State) of +handle_info({route, From, To, #iq{} = Packet}, State) -> + IQ = xmpp:decode_els(Packet), + {Reply, NewState} = case process_iq(From, IQ, State) of R when is_record(R, iq) -> {R, State}; {R, S} -> @@ -371,7 +371,7 @@ handle_info({route, From, To, #xmlel{name = <<"iq">>} = Stanza}, State) -> {none, State} end, if Reply /= none -> - ejabberd_router:route(To, From, jlib:iq_to_xml(Reply)); + ejabberd_router:route(To, From, Reply); true -> ok end, @@ -531,100 +531,58 @@ expand_host(Subject, Host) -> %% XMPP request handling. --spec process_iq(jid(), iq_request() | reply | invalid, state()) - -> {iq_reply(), state()} | iq_reply() | not_request. +-spec process_iq(jid(), iq(), state()) -> {iq(), state()} | iq() | not_request. process_iq(_From, - #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ, + #iq{type = get, lang = Lang, sub_els = [#disco_info{}]} = IQ, #state{server_host = ServerHost, name = Name}) -> AddInfo = ejabberd_hooks:run_fold(disco_info, ServerHost, [], [ServerHost, ?MODULE, <<"">>, <<"">>]), - IQ#iq{type = result, - sub_el = [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}], - children = iq_disco_info(ServerHost, Lang, Name) - ++ AddInfo}]}; -process_iq(From, - #iq{type = get, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ, - #state{server_host = ServerHost, access = Access} = State) - when XMLNS == ?NS_HTTP_UPLOAD; - XMLNS == ?NS_HTTP_UPLOAD_OLD -> + xmpp:make_iq_result(IQ, iq_disco_info(ServerHost, Lang, Name, AddInfo)); +process_iq(From, #iq{type = get, lang = Lang, + sub_els = [#upload_request{filename = File, + size = Size, + 'content-type' = CType, + xmlns = XMLNS}]} = IQ, + #state{server_host = ServerHost, access = Access} = State) -> case acl:match_rule(ServerHost, Access, From) of allow -> - case parse_request(SubEl, Lang) of - {ok, File, Size, ContentType} -> - case create_slot(State, From, File, Size, ContentType, - Lang) of - {ok, Slot} -> - {ok, Timer} = timer:send_after(?SLOT_TIMEOUT, - {slot_timed_out, - Slot}), - NewState = add_slot(Slot, Size, Timer, State), - SlotEl = slot_el(Slot, State, XMLNS), - {IQ#iq{type = result, sub_el = [SlotEl]}, NewState}; - {ok, PutURL, GetURL} -> - SlotEl = slot_el(PutURL, GetURL, XMLNS), - IQ#iq{type = result, sub_el = [SlotEl]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end; + ContentType = yield_content_type(CType), + case create_slot(State, From, File, Size, ContentType, Lang) of + {ok, Slot} -> + {ok, Timer} = timer:send_after(?SLOT_TIMEOUT, + {slot_timed_out, + Slot}), + NewState = add_slot(Slot, Size, Timer, State), + NewSlot = mk_slot(Slot, State, XMLNS), + {xmpp:make_iq_result(IQ, NewSlot), NewState}; + {ok, PutURL, GetURL} -> + Slot = mk_slot(PutURL, GetURL, XMLNS), + xmpp:make_iq_result(IQ, Slot); {error, Error} -> - ?DEBUG("Cannot parse request from ~s", - [jid:to_string(From)]), - IQ#iq{type = error, sub_el = [SubEl, Error]} + xmpp:make_error(IQ, Error) end; deny -> ?DEBUG("Denying HTTP upload slot request from ~s", [jid:to_string(From)]), Txt = <<"Denied by ACL">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]} + xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)) end; -process_iq(_From, #iq{sub_el = SubEl} = IQ, _State) -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; -process_iq(_From, reply, _State) -> - not_request; -process_iq(_From, invalid, _State) -> +process_iq(_From, #iq{type = T} = IQ, _State) when T == get; T == set -> + xmpp:make_error(IQ, xmpp:err_not_allowed()); +process_iq(_From, #iq{}, _State) -> not_request. --spec parse_request(xmlel(), binary()) - -> {ok, binary(), pos_integer(), binary()} | {error, xmlel()}. - -parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) -> - case fxml:get_attr(<<"xmlns">>, Attrs) of - {value, XMLNS} when XMLNS == ?NS_HTTP_UPLOAD; - XMLNS == ?NS_HTTP_UPLOAD_OLD -> - case {fxml:get_subtag_cdata(Request, <<"filename">>), - fxml:get_subtag_cdata(Request, <<"size">>), - fxml:get_subtag_cdata(Request, <<"content-type">>)} of - {File, SizeStr, ContentType} when byte_size(File) > 0 -> - case catch jlib:binary_to_integer(SizeStr) of - Size when is_integer(Size), Size > 0 -> - {ok, File, Size, yield_content_type(ContentType)}; - _ -> - Text = <<"Please specify file size.">>, - {error, ?ERRT_BAD_REQUEST(Lang, Text)} - end; - _ -> - Text = <<"Please specify file name.">>, - {error, ?ERRT_BAD_REQUEST(Lang, Text)} - end; - _ -> - Text = <<"No or invalid XML namespace">>, - {error, ?ERRT_BAD_REQUEST(Lang, Text)} - end; -parse_request(_El, _Lang) -> {error, ?ERR_BAD_REQUEST}. - -spec create_slot(state(), jid(), binary(), pos_integer(), binary(), binary()) -> {ok, slot()} | {ok, binary(), binary()} | {error, xmlel()}. create_slot(#state{service_url = undefined, max_size = MaxSize}, JID, File, Size, _ContentType, Lang) when MaxSize /= infinity, Size > MaxSize -> - Text = <<"File larger than ", (jlib:integer_to_binary(MaxSize))/binary, - " Bytes.">>, + Text = {<<"File larger than ~w bytes">>, [MaxSize]}, ?INFO_MSG("Rejecting file ~s from ~s (too large: ~B bytes)", [File, jid:to_string(JID), Size]), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, Text)}; + {error, xmpp:err_not_acceptable(Text, Lang)}; create_slot(#state{service_url = undefined, jid_in_url = JIDinURL, secret_length = SecretLength, @@ -642,8 +600,8 @@ create_slot(#state{service_url = undefined, [jid:to_string(JID), File]), {ok, [UserStr, RandStr, FileStr]}; deny -> - {error, ?ERR_SERVICE_UNAVAILABLE}; - #xmlel{} = Error -> + {error, xmpp:err_service_unavailable()}; + #stanza_error{} = Error -> {error, Error} end; create_slot(#state{service_url = ServiceURL}, @@ -651,7 +609,7 @@ create_slot(#state{service_url = ServiceURL}, Lang) -> Options = [{body_format, binary}, {full_result, false}], HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}], - SizeStr = jlib:integer_to_binary(Size), + SizeStr = integer_to_binary(Size), GetRequest = binary_to_list(ServiceURL) ++ "?jid=" ++ ?URL_ENC(jid:to_string({U, S, <<"">>})) ++ "&name=" ++ ?URL_ENC(File) ++ @@ -669,28 +627,28 @@ create_slot(#state{service_url = ServiceURL}, ?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p", [jid:to_string(JID), ServiceURL, Lines]), Txt = <<"Failed to parse HTTP response">>, - {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)} + {error, xmpp:err_service_unavailable(Txt, Lang)} end; {ok, {402, _Body}} -> ?INFO_MSG("Got status code 402 for ~s from <~s>", [jid:to_string(JID), ServiceURL]), - {error, ?ERR_RESOURCE_CONSTRAINT}; + {error, xmpp:err_resource_constraint()}; {ok, {403, _Body}} -> ?INFO_MSG("Got status code 403 for ~s from <~s>", [jid:to_string(JID), ServiceURL]), - {error, ?ERR_NOT_ALLOWED}; + {error, xmpp:err_not_allowed()}; {ok, {413, _Body}} -> ?INFO_MSG("Got status code 413 for ~s from <~s>", [jid:to_string(JID), ServiceURL]), - {error, ?ERR_NOT_ACCEPTABLE}; + {error, xmpp:err_not_acceptable()}; {ok, {Code, _Body}} -> ?ERROR_MSG("Got unexpected status code for ~s from <~s>: ~B", [jid:to_string(JID), ServiceURL, Code]), - {error, ?ERR_SERVICE_UNAVAILABLE}; + {error, xmpp:err_service_unavailable()}; {error, Reason} -> ?ERROR_MSG("Error requesting upload slot for ~s from <~s>: ~p", [jid:to_string(JID), ServiceURL, Reason]), - {error, ?ERR_SERVICE_UNAVAILABLE} + {error, xmpp:err_service_unavailable()} end. -spec add_slot(slot(), pos_integer(), timer:tref(), state()) -> state(). @@ -710,19 +668,15 @@ del_slot(Slot, #state{slots = Slots} = State) -> NewSlots = maps:remove(Slot, Slots), State#state{slots = NewSlots}. --spec slot_el(slot() | binary(), state() | binary(), binary()) -> xmlel(). +-spec mk_slot(slot(), state(), binary()) -> upload_slot(); + (binary(), binary(), binary()) -> upload_slot(). -slot_el(Slot, #state{put_url = PutPrefix, get_url = GetPrefix}, XMLNS) -> +mk_slot(Slot, #state{put_url = PutPrefix, get_url = GetPrefix}, XMLNS) -> PutURL = str:join([PutPrefix | Slot], <<$/>>), GetURL = str:join([GetPrefix | Slot], <<$/>>), - slot_el(PutURL, GetURL, XMLNS); -slot_el(PutURL, GetURL, XMLNS) -> - #xmlel{name = <<"slot">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = [#xmlel{name = <<"put">>, - children = [{xmlcdata, PutURL}]}, - #xmlel{name = <<"get">>, - children = [{xmlcdata, GetURL}]}]}. + mk_slot(PutURL, GetURL, XMLNS); +mk_slot(PutURL, GetURL, XMLNS) -> + #upload_slot{get = GetURL, put = PutURL, xmlns = XMLNS}. -spec make_user_string(jid(), sha1 | node) -> binary(). @@ -762,44 +716,30 @@ map_int_to_char(N) when N =< 61 -> N + 61. % Lower-case character. yield_content_type(<<"">>) -> ?DEFAULT_CONTENT_TYPE; yield_content_type(Type) -> Type. --spec iq_disco_info(binary(), binary(), binary()) -> [xmlel()]. +-spec iq_disco_info(binary(), binary(), binary(), [xdata()]) -> [xmlel()]. -iq_disco_info(Host, Lang, Name) -> +iq_disco_info(Host, Lang, Name, AddInfo) -> Form = case gen_mod:get_module_opt(Host, ?MODULE, max_size, fun(I) when is_integer(I), I > 0 -> I; (infinity) -> infinity end, 104857600) of infinity -> - []; + AddInfo; MaxSize -> - MaxSizeStr = jlib:integer_to_binary(MaxSize), - Fields = [#xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"hidden">>}, - {<<"var">>, <<"FORM_TYPE">>}], - children = [#xmlel{name = <<"value">>, - children = - [{xmlcdata, - ?NS_HTTP_UPLOAD}]}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"max-file-size">>}], - children = [#xmlel{name = <<"value">>, - children = - [{xmlcdata, - MaxSizeStr}]}]}], - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"result">>}], - children = Fields}] + MaxSizeStr = integer_to_binary(MaxSize), + Fields = [#xdata_field{type = hidden, + var = <<"FORM_TYPE">>, + values = [?NS_HTTP_UPLOAD]}, + #xdata_field{var = <<"max-file-size">>, + values = [MaxSizeStr]}], + [#xdata{type = result, fields = Fields}|AddInfo] end, - [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"store">>}, - {<<"type">>, <<"file">>}, - {<<"name">>, translate:translate(Lang, Name)}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_HTTP_UPLOAD}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_HTTP_UPLOAD_OLD}]} | Form]. + #disco_info{identities = [#identity{category = <<"store">>, + type = <<"file">>, + name = translate:translate(Lang, Name)}], + features = [?NS_HTTP_UPLOAD, ?NS_HTTP_UPLOAD_OLD], + xdata = Form}. %% HTTP request handling. @@ -984,20 +924,14 @@ convert(Path, #media_info{type = T, width = W, height = H}) -> thumb_el(Path, URI) -> ContentType = guess_content_type(Path), - case identify(Path) of - {ok, #media_info{height = H, width = W}} -> - #xmlel{name = <<"thumbnail">>, - attrs = [{<<"xmlns">>, ?NS_THUMBS_1}, - {<<"media-type">>, ContentType}, - {<<"uri">>, URI}, - {<<"height">>, jlib:integer_to_binary(H)}, - {<<"width">>, jlib:integer_to_binary(W)}]}; - pass -> - #xmlel{name = <<"thumbnail">>, - attrs = [{<<"xmlns">>, ?NS_THUMBS_1}, - {<<"uri">>, URI}, - {<<"media-type">>, ContentType}]} - end. + xmpp:encode( + case identify(Path) of + {ok, #media_info{height = H, width = W}} -> + #thumbnail{'media-type' = ContentType, uri = URI, + height = H, width = W}; + pass -> + #thumbnail{uri = URI, 'media-type' = ContentType} + end). %%-------------------------------------------------------------------- %% Remove user. diff --git a/src/mod_http_upload_quota.erl b/src/mod_http_upload_quota.erl index 3051a4e87..9522cd3d4 100644 --- a/src/mod_http_upload_quota.erl +++ b/src/mod_http_upload_quota.erl @@ -53,7 +53,7 @@ %% ejabberd_hooks callback. -export([handle_slot_request/5]). --include("jlib.hrl"). +-include("jid.hrl"). -include("logger.hrl"). -include_lib("kernel/include/file.hrl"). @@ -263,8 +263,8 @@ code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) -> %% ejabberd_hooks callback. %%-------------------------------------------------------------------- --spec handle_slot_request(term(), jid(), binary(), non_neg_integer(), binary()) - -> term(). +-spec handle_slot_request(allow | deny, jid(), binary(), + non_neg_integer(), binary()) -> allow | deny. handle_slot_request(allow, #jid{lserver = ServerHost} = JID, Path, Size, _Lang) -> diff --git a/src/mod_ip_blacklist.erl b/src/mod_ip_blacklist.erl index 4f54ecd79..ab17a8891 100644 --- a/src/mod_ip_blacklist.erl +++ b/src/mod_ip_blacklist.erl @@ -89,9 +89,9 @@ loop(_State) -> receive stop -> ok end. %% TODO: Support comment lines starting by % update_bl_c2s() -> ?INFO_MSG("Updating C2S Blacklist", []), - case httpc:request(?BLC2S) of + case p1_http:get(?BLC2S) of {ok, 200, _Headers, Body} -> - IPs = str:tokens(Body, <<"\n">>), + IPs = str:tokens(iolist_to_binary(Body), <<"\n">>), ets:delete_all_objects(bl_c2s), lists:foreach(fun (IP) -> ets:insert(bl_c2s, @@ -109,6 +109,10 @@ update_bl_c2s() -> %% Return: false: IP not blacklisted %% true: IP is blacklisted %% IPV4 IP tuple: +-spec is_ip_in_c2s_blacklist( + {true, binary(), binary()} | false, + {inet:ip_address(), non_neg_integer()}, + binary()) -> {stop, {true, binary(), binary()}} | false. is_ip_in_c2s_blacklist(_Val, IP, Lang) when is_tuple(IP) -> BinaryIP = jlib:ip_to_list(IP), case ets:lookup(bl_c2s, BinaryIP) of diff --git a/src/mod_irc.erl b/src/mod_irc.erl index e2203a306..f43a6653d 100644 --- a/src/mod_irc.erl +++ b/src/mod_irc.erl @@ -34,7 +34,8 @@ %% API -export([start_link/2, start/2, stop/1, export/1, import/1, import/3, closed_connection/3, get_connection_params/3, - data_to_binary/2]). + data_to_binary/2, process_disco_info/1, process_disco_items/1, + process_register/1, process_vcard/1, process_command/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, @@ -42,11 +43,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). - --include("adhoc.hrl"). - +-include("xmpp.hrl"). -include("mod_irc.hrl"). -define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>). @@ -69,10 +66,8 @@ -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), #irc_custom{}) -> ok | pass. --callback get_data(binary(), binary(), {binary(), binary()}) -> - error | empty | irc_data(). --callback set_data(binary(), binary(), {binary(), binary()}, irc_data()) -> - {atomic, any()}. +-callback get_data(binary(), binary(), jid()) -> error | empty | irc_data(). +-callback set_data(binary(), binary(), jid(), irc_data()) -> {atomic, any()}. %%==================================================================== %% API @@ -125,6 +120,18 @@ init([Host, Opts]) -> catch ets:new(irc_connection, [named_table, public, {keypos, #irc_connection.jid_server_host}]), + IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, + one_queue), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, + ?MODULE, process_disco_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER, + ?MODULE, process_register, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD, + ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_COMMANDS, + ?MODULE, process_command, IQDisc), ejabberd_router:register_route(MyHost, Host), {ok, #state{host = MyHost, server_host = Host, @@ -176,8 +183,13 @@ handle_info(_Info, State) -> {noreply, State}. %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- -terminate(_Reason, State) -> - ejabberd_router:unregister_route(State#state.host), ok. +terminate(_Reason, #state{host = MyHost}) -> + ejabberd_router:unregister_route(MyHost), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_COMMANDS). %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} @@ -203,287 +215,221 @@ stop_supervisor(Host) -> supervisor:terminate_child(ejabberd_sup, Proc), supervisor:delete_child(ejabberd_sup, Proc). -do_route(Host, ServerHost, Access, From, To, Packet) -> +do_route(Host, ServerHost, Access, From, + #jid{luser = LUser, lresource = LResource} = To, Packet) -> case acl:match_rule(ServerHost, Access, From) of - allow -> do_route1(Host, ServerHost, From, To, Packet); - _ -> - #xmlel{attrs = Attrs} = Packet, - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - ErrText = <<"Access denied by service policy">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(To, From, Err) + allow -> + case Packet of + #iq{} when LUser == <<"">>, LResource == <<"">> -> + ejabberd_router:process_iq(From, To, Packet); + #iq{} when LUser == <<"">>, LResource /= <<"">> -> + Err = xmpp:err_service_unavailable(), + ejabberd_router:route_error(To, From, Packet, Err); + _ -> + sm_route(Host, ServerHost, From, To, Packet) + end; + deny -> + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang), + ejabberd_router:route_error(To, From, Packet, Err) end. -do_route1(Host, ServerHost, From, To, Packet) -> +process_disco_info(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_info(#iq{type = get, lang = Lang, to = To, + sub_els = [#disco_info{node = Node}]} = IQ) -> + ServerHost = ejabberd_router:host_of_route(To#jid.lserver), + Info = ejabberd_hooks:run_fold(disco_info, ServerHost, + [], [ServerHost, ?MODULE, <<"">>, <<"">>]), + case iq_disco(ServerHost, Node, Lang) of + undefined -> + xmpp:make_iq_result(IQ, #disco_info{}); + DiscoInfo -> + xmpp:make_iq_result(IQ, DiscoInfo#disco_info{xdata = Info}) + end. + +process_disco_items(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_items(#iq{type = get, lang = Lang, to = To, + sub_els = [#disco_items{node = Node}]} = IQ) -> + case Node of + <<"">> -> + xmpp:make_iq_result(IQ, #disco_items{}); + <<"join">> -> + xmpp:make_iq_result(IQ, #disco_items{}); + <<"register">> -> + xmpp:make_iq_result(IQ, #disco_items{}); + ?NS_COMMANDS -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + xmpp:make_iq_result( + IQ, #disco_items{node = Node, + items = command_items(ServerHost, Host, Lang)}); + _ -> + Txt = <<"Node not found">>, + xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) + end. + +process_register(#iq{type = get, to = To, from = From, lang = Lang} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + case get_form(ServerHost, Host, From, Lang) of + {result, Res} -> + xmpp:make_iq_result(IQ, Res); + {error, Error} -> + xmpp:make_error(IQ, Error) + end; +process_register(#iq{type = set, lang = Lang, to = To, from = From, + sub_els = [#register{xdata = #xdata{} = X}]} = IQ) -> + case X#xdata.type of + cancel -> + xmpp:make_iq_result(IQ, #register{}); + submit -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + case set_form(ServerHost, Host, From, Lang, X) of + {result, Res} -> + xmpp:make_iq_result(IQ, Res); + {error, Error} -> + xmpp:make_error(IQ, Error) + end; + _ -> + Txt = <<"Incorrect value of 'type' attribute">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) + end; +process_register(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"No data form found">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)). + +process_vcard(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_vcard(#iq{type = get, lang = Lang} = IQ) -> + xmpp:make_iq_result(IQ, iq_get_vcard(Lang)). + +process_command(#iq{type = get, lang = Lang} = IQ) -> + Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_command(#iq{type = set, lang = Lang, to = To, from = From, + sub_els = [#adhoc_command{node = Node} = Request]} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + case lists:keyfind(Node, 1, commands(ServerHost)) of + {_, _, Function} -> + try Function(From, To, Request) of + ignore -> + ignore; + {error, Error} -> + xmpp:make_error(IQ, Error); + Command -> + xmpp:make_iq_result(IQ, Command) + catch E:R -> + ?ERROR_MSG("ad-hoc handler failed: ~p", + [{E, {R, erlang:get_stacktrace()}}]), + xmpp:make_error(IQ, xmpp:err_internal_server_error()) + end; + _ -> + Txt = <<"Node not found">>, + xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) + end. + +sm_route(Host, ServerHost, From, To, Packet) -> #jid{user = ChanServ, resource = Resource} = To, - #xmlel{} = Packet, - case ChanServ of - <<"">> -> - case Resource of - <<"">> -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS, - sub_el = SubEl, lang = Lang} = - IQ -> - Node = fxml:get_tag_attr_s(<<"node">>, SubEl), - Info = ejabberd_hooks:run_fold(disco_info, ServerHost, - [], - [ServerHost, ?MODULE, - <<"">>, <<"">>]), - case iq_disco(ServerHost, Node, Lang) of - [] -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = []}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - DiscoInfo -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - DiscoInfo ++ Info}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)) - end; - #iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS, - sub_el = SubEl, lang = Lang} = - IQ -> - Node = fxml:get_tag_attr_s(<<"node">>, SubEl), - case Node of - <<>> -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = []}]}, - Res = jlib:iq_to_xml(ResIQ); - <<"join">> -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = []}]}, - Res = jlib:iq_to_xml(ResIQ); - <<"register">> -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = []}]}, - Res = jlib:iq_to_xml(ResIQ); - ?NS_COMMANDS -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}, - {<<"node">>, Node}], - children = - command_items(ServerHost, - Host, - Lang)}]}, - Res = jlib:iq_to_xml(ResIQ); - _ -> - Txt = <<"Node not found">>, - Res = jlib:make_error_reply( - Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)) - end, - ejabberd_router:route(To, From, Res); - #iq{xmlns = ?NS_REGISTER} = IQ -> - process_register(ServerHost, Host, From, To, IQ); - #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, - lang = Lang} = - IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - #iq{type = set, xmlns = ?NS_COMMANDS, lang = Lang, - sub_el = SubEl} = - IQ -> - Request = adhoc:parse_request(IQ), - case lists:keysearch(Request#adhoc_request.node, 1, - commands(ServerHost)) - of - {value, {_, _, Function}} -> - case catch Function(From, To, Request) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p~nfor ad-hoc handler of ~p", - [Reason, {From, To, IQ}]), - Res = IQ#iq{type = error, - sub_el = - [SubEl, - ?ERR_INTERNAL_SERVER_ERROR]}; - ignore -> Res = ignore; - {error, Error} -> - Res = IQ#iq{type = error, - sub_el = [SubEl, Error]}; - Command -> - Res = IQ#iq{type = result, sub_el = [Command]} - end, - if Res /= ignore -> - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - true -> ok - end; - _ -> - Txt = <<"Node not found">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)), - ejabberd_router:route(To, From, Err) - end; - #iq{} = _IQ -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> ok - end; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err) - end; - _ -> - case str:tokens(ChanServ, <<"%">>) of - [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of - [] -> - ?DEBUG("open new connection~n", []), - {Username, Encoding, Port, Password} = - get_connection_params(Host, ServerHost, From, Server), - ConnectionUsername = case Packet of + case str:tokens(ChanServ, <<"%">>) of + [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] -> + case ets:lookup(irc_connection, {From, Server, Host}) of + [] -> + ?DEBUG("open new connection~n", []), + {Username, Encoding, Port, Password} = + get_connection_params(Host, ServerHost, From, Server), + ConnectionUsername = case Packet of %% If the user tries to join a %% chatroom, the packet for sure %% contains the desired username. - #xmlel{name = <<"presence">>} -> - Resource; + #presence{} -> Resource; %% Otherwise, there is no firm %% conclusion from the packet. %% Better to use the configured %% username (which defaults to the %% username part of the JID). _ -> Username - end, - Ident = extract_ident(Packet), - RemoteAddr = extract_ip_address(Packet), - RealName = get_realname(ServerHost), - WebircPassword = get_webirc_password(ServerHost), - {ok, Pid} = mod_irc_connection:start(From, Host, - ServerHost, Server, - ConnectionUsername, - Encoding, Port, - Password, Ident, RemoteAddr, RealName, WebircPassword, ?MODULE), - ets:insert(irc_connection, - #irc_connection{jid_server_host = - {From, Server, Host}, - pid = Pid}), - mod_irc_connection:route_chan(Pid, Channel, Resource, - Packet), - ok; - [R] -> - Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", [Pid]), - mod_irc_connection:route_chan(Pid, Channel, Resource, - Packet), - ok - end; - _ -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - case str:tokens(ChanServ, <<"!">>) of - [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of + end, + Ident = extract_ident(Packet), + RemoteAddr = extract_ip_address(Packet), + RealName = get_realname(ServerHost), + WebircPassword = get_webirc_password(ServerHost), + {ok, Pid} = mod_irc_connection:start( + From, Host, ServerHost, Server, + ConnectionUsername, Encoding, Port, + Password, Ident, RemoteAddr, RealName, + WebircPassword, ?MODULE), + ets:insert(irc_connection, + #irc_connection{ + jid_server_host = {From, Server, Host}, + pid = Pid}), + mod_irc_connection:route_chan(Pid, Channel, Resource, Packet); + [R] -> + Pid = R#irc_connection.pid, + ?DEBUG("send to process ~p~n", [Pid]), + mod_irc_connection:route_chan(Pid, Channel, Resource, Packet) + end; + _ -> + Lang = xmpp:get_lang(Packet), + case str:tokens(ChanServ, <<"!">>) of + [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] -> + case ets:lookup(irc_connection, {From, Server, Host}) of [] -> Txt = <<"IRC connection 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); [R] -> Pid = R#irc_connection.pid, ?DEBUG("send to process ~p~n", [Pid]), - mod_irc_connection:route_nick(Pid, Nick, Packet), - ok - end; - _ -> - Txt = <<"Failed to parse chanserv">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_router:route(To, From, Err) - end - end + mod_irc_connection:route_nick(Pid, Nick, Packet) + end; + _ -> + Txt = <<"Failed to parse chanserv">>, + Err = xmpp:err_bad_request(Txt, Lang), + ejabberd_router:route_error(To, From, Packet, Err) + end end. closed_connection(Host, From, Server) -> ets:delete(irc_connection, {From, Server, Host}). -iq_disco(_ServerHost, <<>>, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"irc">>}, - {<<"name">>, - translate:translate(Lang, <<"IRC Transport">>)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_REGISTER}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}]; +iq_disco(_ServerHost, <<"">>, Lang) -> + #disco_info{ + identities = [#identity{category = <<"conference">>, + type = <<"irc">>, + name = translate:translate(Lang, <<"IRC Transport">>)}], + features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_MUC, + ?NS_REGISTER, ?NS_VCARD, ?NS_COMMANDS]}; iq_disco(ServerHost, Node, Lang) -> - case lists:keysearch(Node, 1, commands(ServerHost)) of - {value, {_, Name, _}} -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}, - {<<"name">>, translate:translate(Lang, Name)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_XDATA}], children = []}]; - _ -> [] + case lists:keyfind(Node, 1, commands(ServerHost)) of + {_, Name, _} -> + #disco_info{ + identities = [#identity{category = <<"automation">>, + type = <<"command-node">>, + name = translate:translate(Lang, Name)}], + features = [?NS_COMMANDS, ?NS_XDATA]}; + _ -> + undefined end. iq_get_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_irc">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd IRC module">>))/binary, - "\nCopyright (c) 2003-2016 ProcessOne">>}]}]. + Desc = translate:translate(Lang, <<"ejabberd IRC module">>), + #vcard_temp{fn = <<"ejabberd/mod_irc">>, + url = ?EJABBERD_URI, + desc = <>}. command_items(ServerHost, Host, Lang) -> - lists:map(fun ({Node, Name, _Function}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Host}, {<<"node">>, Node}, - {<<"name">>, - translate:translate(Lang, Name)}], - children = []} - end, - commands(ServerHost)). + lists:map(fun({Node, Name, _Function}) -> + #disco_item{jid = jid:make(Host), + node = Node, + name = translate:translate(Lang, Name)} + end, commands(ServerHost)). commands(ServerHost) -> [{<<"join">>, <<"Join channel">>, fun adhoc_join/3}, @@ -494,243 +440,118 @@ commands(ServerHost) -> adhoc_register(ServerHost, From, To, Request) end}]. -process_register(ServerHost, Host, From, To, - #iq{} = IQ) -> - case catch process_irc_register(ServerHost, Host, 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 - end. - -find_xdata_el(#xmlel{children = SubEls}) -> - find_xdata_el1(SubEls). - -find_xdata_el1([]) -> false; -find_xdata_el1([#xmlel{name = Name, attrs = Attrs, - children = SubEls} - | Els]) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - #xmlel{name = Name, attrs = Attrs, children = SubEls}; - _ -> find_xdata_el1(Els) - end; -find_xdata_el1([_ | Els]) -> find_xdata_el1(Els). - -process_irc_register(ServerHost, Host, From, _To, - #iq{type = Type, xmlns = XMLNS, lang = Lang, - sub_el = SubEl} = - IQ) -> - case Type of - set -> - XDataEl = find_xdata_el(SubEl), - case XDataEl of - false -> - Txt1 = <<"No data form found">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, Txt1)]}; - #xmlel{attrs = Attrs} -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"cancel">> -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = []}]}; - <<"submit">> -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - Txt2 = <<"Incorrect data form">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt2)]}; - _ -> - Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, - SubEl), - <<"/">>), - case set_form(ServerHost, Host, From, Node, Lang, - XData) - of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = Res}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end - end; - _ -> - Txt3 = <<"Incorrect value of 'type' attribute">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt3)]} - end - end; - get -> - Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl), - <<"/">>), - case get_form(ServerHost, Host, From, Node, Lang) of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = Res}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end - end. - get_data(ServerHost, Host, From) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_data(LServer, Host, From). -get_form(ServerHost, Host, From, [], Lang) -> +get_form(ServerHost, Host, From, Lang) -> #jid{user = User, server = Server} = From, DefaultEncoding = get_default_encoding(Host), Customs = case get_data(ServerHost, Host, From) of - error -> + error -> Txt1 = <<"Database failure">>, - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt1)}; - empty -> {User, []}; - Data -> get_username_and_connection_params(Data) + {error, xmpp:err_internal_server_error(Txt1, Lang)}; + empty -> {User, []}; + Data -> get_username_and_connection_params(Data) end, case Customs of - {error, _Error} -> Customs; - {Username, ConnectionsParams} -> - {result, - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need an x:data capable client to " - "configure mod_irc settings">>)}]}, - #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Registration in mod_irc for ">>))/binary, - User/binary, "@", Server/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Enter username, encodings, ports and " - "passwords you wish to use for connecting " - "to IRC servers">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"IRC Username">>)}, - {<<"var">>, <<"username">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Username}]}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"fixed">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"If you want to specify" - " different ports, " - "passwords, encodings " - "for IRC servers, " - "fill this list with " - "values in format " - "'{\"irc server\", " - "\"encoding\", port, " - "\"password\"}'. " - "By default this " - "service use \"~s\" " - "encoding, port ~p, " - "empty password.">>), - [DefaultEncoding, - ?DEFAULT_IRC_PORT]))}]}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"fixed">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Example: [{\"irc.lucky.net\", \"koi8-r\", " - "6667, \"secret\"}, {\"vendetta.fef.net\", " - "\"iso8859-1\", 7000}, {\"irc.sometestserver.n" - "et\", \"utf-8\"}].">>)}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-multi">>}, - {<<"label">>, - translate:translate(Lang, - <<"Connections parameters">>)}, - {<<"var">>, <<"connections_params">>}], - children = - lists:map(fun (S) -> - #xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, S}]} - end, - str:tokens(list_to_binary( - io_lib:format( - "~p.", - [conn_params_to_list( - ConnectionsParams)])), - <<"\n">>))}]}]} - end; -get_form(_ServerHost, _Host, _, _, _Lang) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. + {error, _Error} -> + Customs; + {Username, ConnectionsParams} -> + Fs = [#xdata_field{type = 'text-single', + label = translate:translate(Lang, <<"IRC Username">>), + var = <<"username">>, + values = [Username]}, + #xdata_field{type = fixed, + values = [str:format( + translate:translate( + Lang, + <<"If you want to specify" + " different ports, " + "passwords, encodings " + "for IRC servers, " + "fill this list with " + "values in format " + "'{\"irc server\", " + "\"encoding\", port, " + "\"password\"}'. " + "By default this " + "service use \"~s\" " + "encoding, port ~p, " + "empty password.">>), + [DefaultEncoding, ?DEFAULT_IRC_PORT])]}, + #xdata_field{type = fixed, + values = [translate:translate( + Lang, + <<"Example: [{\"irc.lucky.net\", \"koi8-r\", " + "6667, \"secret\"}, {\"vendetta.fef.net\", " + "\"iso8859-1\", 7000}, {\"irc.sometestserver.n" + "et\", \"utf-8\"}].">>)]}, + #xdata_field{type = 'text-multi', + label = translate:translate( + Lang, <<"Connections parameters">>), + var = <<"connections_params">>, + values = str:tokens(str:format( + "~p.", + [conn_params_to_list( + ConnectionsParams)]), + <<"\n">>)}], + X = #xdata{type = form, + title = <<(translate:translate( + Lang, <<"Registration in mod_irc for ">>))/binary, + User/binary, "@", Server/binary>>, + instructions = + [translate:translate( + Lang, + <<"Enter username, encodings, ports and " + "passwords you wish to use for connecting " + "to IRC servers">>)], + fields = Fs}, + {result, + #register{instructions = + translate:translate(Lang, + <<"You need an x:data capable client to " + "configure mod_irc settings">>), + xdata = X}} + end. set_data(ServerHost, Host, From, Data) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:set_data(LServer, Host, From, data_to_binary(From, Data)). -set_form(ServerHost, Host, From, [], Lang, XData) -> - case {lists:keysearch(<<"username">>, 1, XData), - lists:keysearch(<<"connections_params">>, 1, XData)} - of - {{value, {_, [Username]}}, {value, {_, Strings}}} -> - EncString = lists:foldl(fun (S, Res) -> - <> - end, - <<"">>, Strings), - case erl_scan:string(binary_to_list(EncString)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, ConnectionsParams} -> - case set_data(ServerHost, Host, From, - [{username, Username}, - {connections_params, ConnectionsParams}]) - of - {atomic, _} -> {result, []}; - _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Database failure">>)} - end; - _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Parse error">>)} - end; - _ -> {error, ?ERRT_NOT_ACCEPTABLE(Lang, <<"Scan error">>)} - end; - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end; -set_form(_ServerHost, _Host, _, _, _Lang, _XData) -> - {error, ?ERR_SERVICE_UNAVAILABLE}. +set_form(ServerHost, Host, From, Lang, XData) -> + case {xmpp_util:get_xdata_values(<<"username">>, XData), + xmpp_util:get_xdata_values(<<"connections_params">>, XData)} of + {[Username], [_|_] = Strings} -> + EncString = lists:foldl(fun (S, Res) -> + <> + end, <<"">>, Strings), + case erl_scan:string(binary_to_list(EncString)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, ConnectionsParams} -> + case set_data(ServerHost, Host, From, + [{username, Username}, + {connections_params, ConnectionsParams}]) of + {atomic, _} -> + {result, undefined}; + _ -> + Txt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(Txt, Lang)} + end; + _ -> + Txt = <<"Parse error">>, + {error, xmpp:err_not_acceptable(Txt, Lang)} + end; + _ -> + {error, xmpp:err_not_acceptable(<<"Scan error">>, Lang)} + end; + _ -> + Txt = <<"Incorrect value in data form">>, + {error, xmpp:err_not_acceptable(Txt, Lang)} + end. get_connection_params(Host, From, IRCServer) -> [_ | HostTail] = str:tokens(Host, <<".">>), @@ -805,212 +626,115 @@ get_connection_params(Host, ServerHost, From, iolist_to_binary(NewPassword)} end. -adhoc_join(_From, _To, - #adhoc_request{action = <<"cancel">>} = Request) -> - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); -adhoc_join(From, To, - #adhoc_request{lang = Lang, node = _Node, - action = _Action, xdata = XData} = - Request) -> - if XData == false -> - Form = #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Join IRC channel">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"channel">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"IRC channel (don't put the first #)">>)}], - children = - [#xmlel{name = <<"required">>, - attrs = [], children = []}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"server">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"IRC server">>)}], - children = - [#xmlel{name = <<"required">>, - attrs = [], children = []}]}]}, - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form]}); +adhoc_join(_From, _To, #adhoc_command{action = cancel} = Request) -> + xmpp_util:make_adhoc_response(Request, #adhoc_command{status = canceled}); +adhoc_join(_From, _To, #adhoc_command{lang = Lang, xdata = undefined} = Request) -> + X = #xdata{type = form, + title = translate:translate(Lang, <<"Join IRC channel">>), + fields = [#xdata_field{var = <<"channel">>, + type = 'text-single', + label = translate:translate( + Lang, <<"IRC channel (don't put the first #)">>), + required = true}, + #xdata_field{var = <<"server">>, + type = 'text-single', + label = translate:translate(Lang, <<"IRC server">>), + required = true}]}, + xmpp_util:make_adhoc_response( + Request, #adhoc_command{status = executing, xdata = X}); +adhoc_join(From, To, #adhoc_command{lang = Lang, xdata = X} = Request) -> + Channel = case xmpp_util:get_xdata_values(<<"channel">>, X) of + [C] -> C; + _ -> false + end, + Server = case xmpp_util:get_xdata_values(<<"server">>, X) of + [S] -> S; + _ -> false + end, + if Channel /= false, Server /= false -> + RoomJID = jid:make(<>, + To#jid.server), + Reason = translate:translate(Lang, <<"Join the IRC channel here.">>), + BodyTxt = {<<"Join the IRC channel in this Jabber ID: ~s">>, + [jid:to_string(RoomJID)]}, + Invite = #message{ + body = xmpp:mk_text(BodyTxt, Lang), + sub_els = [#muc_user{ + invites = [#muc_invite{from = From, + reason = Reason}]}, + #x_conference{reason = Reason, + jid = RoomJID}]}, + ejabberd_router:route(RoomJID, From, Invite), + xmpp_util:make_adhoc_response( + Request, #adhoc_command{status = completed}); true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - Txt1 = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt1)}; - Fields -> - Channel = case lists:keysearch(<<"channel">>, 1, Fields) - of - {value, {<<"channel">>, [C]}} -> C; - _ -> false - end, - Server = case lists:keysearch(<<"server">>, 1, Fields) - of - {value, {<<"server">>, [S]}} -> S; - _ -> false - end, - if Channel /= false, Server /= false -> - RoomJID = <>, - Invite = #xmlel{name = <<"message">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"invite">>, - attrs = - [{<<"from">>, - jid:to_string(From)}], - children = - [#xmlel{name - = - <<"reason">>, - attrs - = - [], - children - = - [{xmlcdata, - translate:translate(Lang, - <<"Join the IRC channel here.">>)}]}]}]}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_XCONFERENCE}], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Join the IRC channel here.">>)}]}, - #xmlel{name = <<"body">>, - attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Join the IRC channel in this Jabber ID: ~s">>), - [RoomJID]))}]}]}, - ejabberd_router:route(jid:from_string(RoomJID), From, - Invite), - adhoc:produce_response(Request, - #adhoc_response{status = - completed}); - true -> {error, ?ERR_BAD_REQUEST} - end - end + Txt = <<"Missing 'channel' or 'server' in the data form">>, + {error, xmpp:err_bad_request(Txt, Lang)} end. +-spec adhoc_register(binary(), jid(), jid(), adhoc_command()) -> + adhoc_command() | {error, stanza_error()}. adhoc_register(_ServerHost, _From, _To, - #adhoc_request{action = <<"cancel">>} = Request) -> - adhoc:produce_response(Request, - #adhoc_response{status = canceled}); + #adhoc_command{action = cancel} = Request) -> + xmpp_util:make_adhoc_response(Request, #adhoc_command{status = canceled}); adhoc_register(ServerHost, From, To, - #adhoc_request{lang = Lang, node = _Node, xdata = XData, - action = Action} = - Request) -> + #adhoc_command{lang = Lang, xdata = X, + action = Action} = Request) -> #jid{user = User} = From, #jid{lserver = Host} = To, - if XData == false -> - case get_data(ServerHost, Host, From) of - error -> Username = User, ConnectionsParams = []; - empty -> Username = User, ConnectionsParams = []; - Data -> - {Username, ConnectionsParams} = - get_username_and_connection_params(Data) - end, - Error = false; + {Username, ConnectionsParams} = + if X == undefined -> + case get_data(ServerHost, Host, From) of + error -> {User, []}; + empty -> {User, []}; + Data -> get_username_and_connection_params(Data) + end; + true -> + {case xmpp_util:get_xdata_values(<<"username">>, X) of + [U] -> U; + _ -> User + end, parse_connections_params(X)} + end, + if Action == complete -> + case set_data(ServerHost, Host, From, + [{username, Username}, + {connections_params, ConnectionsParams}]) of + {atomic, _} -> + xmpp_util:make_adhoc_response( + Request, #adhoc_command{status = completed}); + _ -> + Txt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(Txt, Lang)} + end; true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - Txt1 = <<"Incorrect data form">>, - Error = {error, ?ERRT_BAD_REQUEST(Lang, Txt1)}, - Username = false, - ConnectionsParams = false; - Fields -> - Username = case lists:keysearch(<<"username">>, 1, - Fields) - of - {value, {<<"username">>, U}} -> U; - _ -> User - end, - ConnectionsParams = parse_connections_params(Fields), - Error = false - end - end, - if Error /= false -> Error; - Action == <<"complete">> -> - case set_data(ServerHost, Host, From, - [{username, Username}, - {connections_params, ConnectionsParams}]) - of - {atomic, _} -> - adhoc:produce_response(Request, - #adhoc_response{status = completed}); - _ -> - Txt2 = <<"Database failure">>, - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt2)} - end; - true -> - Form = generate_adhoc_register_form(Lang, Username, - ConnectionsParams), - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form], - actions = - [<<"next">>, - <<"complete">>]}) + Form = generate_adhoc_register_form(Lang, Username, + ConnectionsParams), + xmpp_util:make_adhoc_response( + Request, #adhoc_command{ + status = executing, + xdata = Form, + actions = #adhoc_actions{next = true, + complete = true}}) end. generate_adhoc_register_form(Lang, Username, ConnectionsParams) -> - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, <<"IRC settings">>)}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Enter username and encodings you wish " - "to use for connecting to IRC servers. " - " Press 'Next' to get more fields to " - "fill in. Press 'Complete' to save settings.">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"username">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, <<"IRC username">>)}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}, - #xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Username}]}]}] - ++ - generate_connection_params_fields(Lang, - ConnectionsParams, 1, [])}. + #xdata{type = form, + title = translate:translate(Lang, <<"IRC settings">>), + instructions = [translate:translate( + Lang, + <<"Enter username and encodings you wish " + "to use for connecting to IRC servers. " + " Press 'Next' to get more fields to " + "fill in. Press 'Complete' to save settings.">>)], + fields = [#xdata_field{ + var = <<"username">>, + type = 'text-single', + label = translate:translate(Lang, <<"IRC username">>), + required = true, + values = [Username]} + | generate_connection_params_fields( + Lang, ConnectionsParams, 1, [])]}. generate_connection_params_fields(Lang, [], Number, Acc) -> @@ -1053,99 +777,69 @@ generate_connection_params_field(Lang, Server, Encoding, Port; true -> ?DEFAULT_IRC_PORT end, - PortUsed = - iolist_to_binary(integer_to_list(PortUsedInt)), + PortUsed = integer_to_binary(PortUsedInt), PasswordUsed = case Password of <<>> -> <<>>; _ -> Password end, - NumberString = - iolist_to_binary(integer_to_list(Number)), - [#xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"password", NumberString/binary>>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - iolist_to_binary( - io_lib:format( - translate:translate(Lang, <<"Password ~b">>), - [Number]))}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, PasswordUsed}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"port", NumberString/binary>>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - iolist_to_binary( - io_lib:format(translate:translate(Lang, <<"Port ~b">>), - [Number]))}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, PortUsed}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"encoding", NumberString/binary>>}, - {<<"type">>, <<"list-single">>}, - {<<"label">>, - list_to_binary( - io_lib:format(translate:translate( - Lang, - <<"Encoding for server ~b">>), - [Number]))}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, EncodingUsed}]} - | lists:map(fun (E) -> - #xmlel{name = <<"option">>, - attrs = [{<<"label">>, E}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, E}]}]} - end, - ?POSSIBLE_ENCODINGS)]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"server", NumberString/binary>>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, - list_to_binary( - io_lib:format(translate:translate(Lang, <<"Server ~b">>), - [Number]))}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Server}]}]}]. + NumberString = integer_to_binary(Number), + [#xdata_field{var = <<"password", NumberString/binary>>, + type = 'text-single', + label = str:format( + translate:translate(Lang, <<"Password ~b">>), + [Number]), + values = [PasswordUsed]}, + #xdata_field{var = <<"port", NumberString/binary>>, + type = 'text-single', + label = str:format( + translate:translate(Lang, <<"Port ~b">>), + [Number]), + values = [PortUsed]}, + #xdata_field{var = <<"encoding", NumberString/binary>>, + type = 'list-single', + label = str:format( + translate:translate(Lang, <<"Encoding for server ~b">>), + [Number]), + values = [EncodingUsed], + options = [#xdata_option{label = E, value = E} + || E <- ?POSSIBLE_ENCODINGS]}, + #xdata_field{var = <<"server", NumberString/binary>>, + type = 'text-single', + label = str:format( + translate:translate(Lang, <<"Server ~b">>), + [Number]), + values = [Server]}]. -parse_connections_params(Fields) -> +parse_connections_params(#xdata{fields = Fields}) -> Servers = lists:flatmap( - fun({<<"server", Var/binary>>, Value}) -> - [{Var, Value}]; + fun(#xdata_field{var = <<"server", Var/binary>>, + values = Values}) -> + [{Var, Values}]; (_) -> [] end, Fields), Encodings = lists:flatmap( - fun({<<"encoding", Var/binary>>, Value}) -> - [{Var, Value}]; + fun(#xdata_field{var = <<"encoding", Var/binary>>, + values = Values}) -> + [{Var, Values}]; (_) -> [] end, Fields), Ports = lists:flatmap( - fun({<<"port", Var/binary>>, Value}) -> - [{Var, Value}]; + fun(#xdata_field{var = <<"port", Var/binary>>, + values = Values}) -> + [{Var, Values}]; (_) -> [] end, Fields), Passwords = lists:flatmap( - fun({<<"password", Var/binary>>, Value}) -> - [{Var, Value}]; + fun(#xdata_field{var = <<"password", Var/binary>>, + values = Values}) -> + [{Var, Values}]; (_) -> [] end, Fields), - parse_connections_params(Servers, Encodings, Ports, - Passwords). + parse_connections_params(Servers, Encodings, Ports, Passwords). retrieve_connections_params(ConnectionParams, ServerN) -> @@ -1263,28 +957,19 @@ mod_opt_type(host) -> fun iolist_to_binary/1; mod_opt_type(_) -> [access, db_type, default_encoding, host]. +-spec extract_ident(stanza()) -> binary(). extract_ident(Packet) -> - case fxml:get_subtag(Packet, <<"headers">>) of - {xmlel, _Name, _Attrs, Headers} -> - extract_header(<<"X-Irc-Ident">>, Headers); - _ -> - "chatmovil" - end. + Hdrs = extract_headers(Packet), + proplists:get_value(<<"X-Irc-Ident">>, Hdrs, <<"chatmovil">>). +-spec extract_ip_address(stanza()) -> binary(). extract_ip_address(Packet) -> - case fxml:get_subtag(Packet, <<"headers">>) of - {xmlel, _Name, _Attrs, Headers} -> - extract_header(<<"X-Forwarded-For">>, Headers); - _ -> - "127.0.0.1" - end. + Hdrs = extract_headers(Packet), + proplists:get_value(<<"X-Forwarded-For">>, Hdrs, <<"127.0.0.1">>). -extract_header(HeaderName, [{xmlel, _Name, _Attrs, [{xmlcdata, Value}]} | Tail]) -> - case fxml:get_attr(<<"name">>, _Attrs) of - {value, HeaderName} -> - binary_to_list(Value); - _ -> - extract_header(HeaderName, Tail) - end; -extract_header(_HeaderName, _Headers) -> - false. +-spec extract_headers(stanza()) -> [{binary(), binary()}]. +extract_headers(Packet) -> + case xmpp:get_subtag(Packet, #shim{}) of + #shim{headers = Hs} -> Hs; + false -> [] + end. diff --git a/src/mod_irc_connection.erl b/src/mod_irc_connection.erl index 098c8c286..2e604203c 100644 --- a/src/mod_irc_connection.erl +++ b/src/mod_irc_connection.erl @@ -41,8 +41,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). -define(SETS, gb_sets). @@ -66,6 +65,8 @@ inbuf = <<"">> :: binary(), outbuf = <<"">> :: binary()}). +-type state() :: #state{}. + %-define(DBGFSM, true). -ifdef(DBGFSM). @@ -228,27 +229,13 @@ code_change(_OldVsn, StateName, StateData, _Extra) -> (iolist_to_binary(S))/binary>>} end). -get_password_from_presence(#xmlel{name = <<"presence">>, - children = Els}) -> - case lists:filter(fun (El) -> - case El of - #xmlel{name = <<"x">>, attrs = Attrs} -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC -> true; - _ -> false - end; - _ -> false - end - end, - Els) - of - [ElXMUC | _] -> - case fxml:get_subtag(ElXMUC, <<"password">>) of - #xmlel{name = <<"password">>} = PasswordTag -> - {true, fxml:get_tag_cdata(PasswordTag)}; - _ -> false - end; - _ -> false +-spec get_password_from_presence(presence()) -> {true, binary()} | false. +get_password_from_presence(#presence{} = Pres) -> + case xmpp:get_subtag(Pres, #muc{}) of + #muc{password = Password} -> + {true, Password}; + _ -> + false end. %%---------------------------------------------------------------------- @@ -257,284 +244,243 @@ get_password_from_presence(#xmlel{name = <<"presence">>, %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- -handle_info({route_chan, Channel, Resource, - #xmlel{name = <<"presence">>, attrs = Attrs} = - Presence}, +handle_info({route_chan, _, _, #presence{type = error}}, _, StateData) -> + {stop, normal, StateData}; +handle_info({route_chan, Channel, _, #presence{type = unavailable}}, StateName, StateData) -> - NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of - <<"unavailable">> -> - send_stanza_unavailable(Channel, StateData), - S1 = (?SEND((io_lib:format("PART #~s\r\n", - [Channel])))), - S1#state{channels = - dict:erase(Channel, S1#state.channels)}; - <<"subscribe">> -> StateData; - <<"subscribed">> -> StateData; - <<"unsubscribe">> -> StateData; - <<"unsubscribed">> -> StateData; - <<"error">> -> stop; - _ -> - Nick = case Resource of - <<"">> -> StateData#state.nick; - _ -> Resource - end, - S1 = if Nick /= StateData#state.nick -> - S11 = (?SEND((io_lib:format("NICK ~s\r\n", - [Nick])))), - S11#state{nickchannel = Channel}; - true -> StateData + send_stanza_unavailable(Channel, StateData), + S1 = (?SEND((io_lib:format("PART #~s\r\n", [Channel])))), + S2 = S1#state{channels = dict:erase(Channel, S1#state.channels)}, + {next_state, StateName, S2}; +handle_info({route_chan, Channel, Resource, + #presence{type = available} = Presence}, + StateName, StateData) -> + Nick = case Resource of + <<"">> -> StateData#state.nick; + _ -> Resource + end, + S1 = if Nick /= StateData#state.nick -> + S11 = (?SEND((io_lib:format("NICK ~s\r\n", [Nick])))), + S11#state{nickchannel = Channel}; + true -> StateData + end, + {next_state, StateName, + case dict:is_key(Channel, S1#state.channels) of + true -> S1; + _ -> + case get_password_from_presence(Presence) of + {true, Password} -> + S2 = ?SEND((io_lib:format("JOIN #~s ~s\r\n", + [Channel, Password]))); + _ -> + S2 = ?SEND((io_lib:format("JOIN #~s\r\n", [Channel]))) + end, + S2#state{channels = dict:store(Channel, ?SETS:new(), + S1#state.channels)} + end}; +handle_info({route_chan, Channel, _Resource, #message{type = groupchat} = Msg}, + StateName, StateData) -> + {next_state, StateName, + case xmpp:get_text(Msg#message.subject) of + <<"">> -> + ejabberd_router:route( + jid:make( + iolist_to_binary([Channel, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, Msg), + Body = xmpp:get_text(Msg#message.body), + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG #~s :\001ACTION ~s\001\r\n", + [Channel, S]) end, - case dict:is_key(Channel, S1#state.channels) of - true -> S1; - _ -> - case get_password_from_presence(Presence) of - {true, Password} -> - S2 = - (?SEND((io_lib:format("JOIN #~s ~s\r\n", - [Channel, - Password])))); - _ -> - S2 = (?SEND((io_lib:format("JOIN #~s\r\n", - [Channel])))) - end, - S2#state{channels = - dict:store(Channel, (?SETS):new(), - S1#state.channels)} - end - end, - if NewStateData == stop -> {stop, normal, StateData}; - true -> - case dict:fetch_keys(NewStateData#state.channels) of - [] -> {stop, normal, NewStateData}; - _ -> {next_state, StateName, NewStateData} - end - end; + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr( + Rest, str:str(Rest, <<" ">>) + 1)), + Res = io_lib:format("PRIVMSG ~s :\001~s\001\r\n", + [CtcpDest, CtcpCmd]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("PRIVMSG #~s :~s\r\n", + [Channel, S]) + end, Strings)), + ?SEND(Res) + end; + Subject -> + Strings = str:tokens(Subject, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("TOPIC #~s :~s\r\n", + [Channel, S]) + end, + Strings)), + ?SEND(Res) + end}; +handle_info({route_chan, _Channel, Resource, #message{type = Type} = Msg}, + StateName, StateData) when Type == chat; Type == normal -> + Body = xmpp:get_text(Msg#message.body), + {next_state, StateName, + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :\001ACTION ~s\001\r\n", + [Resource, S]) + end, Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr( + Rest, str:str(Rest, <<" ">>) + 1)), + Res = io_lib:format("PRIVMSG ~s :~s\r\n", + [CtcpDest, + <<"\001", CtcpCmd/binary, "\001">>]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("PRIVMSG ~s :~s\r\n", + [Resource, S]) + end, Strings)), + ?SEND(Res) + end}; +handle_info({route_chan, _, _, #message{type = error}}, _, StateData) -> + {stop, normal, StateData}; handle_info({route_chan, Channel, Resource, - #xmlel{name = <<"message">>, attrs = Attrs} = El}, - StateName, StateData) -> - NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of - <<"groupchat">> -> - case fxml:get_path_s(El, [{elem, <<"subject">>}, cdata]) - of - <<"">> -> - ejabberd_router:route( - jid:make( - iolist_to_binary([Channel, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), - StateData#state.user, El), - Body = fxml:get_path_s(El, - [{elem, <<"body">>}, - cdata]), - case Body of - <<"/quote ", Rest/binary>> -> - ?SEND(<>); - <<"/msg ", Rest/binary>> -> - ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); - <<"/me ", Rest/binary>> -> - Strings = str:tokens(Rest, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG #~s :\001ACTION ~s\001\r\n", - [Channel, S]) - end, - Strings)), - ?SEND(Res); - <<"/ctcp ", Rest/binary>> -> - Words = str:tokens(Rest, <<" ">>), - case Words of - [CtcpDest | _] -> - CtcpCmd = str:to_upper( - str:substr(Rest, - str:str(Rest, - <<" ">>) - + 1)), - Res = - io_lib:format("PRIVMSG ~s :\001~s\001\r\n", - [CtcpDest, - CtcpCmd]), - ?SEND(Res); - _ -> ok - end; - _ -> - Strings = str:tokens(Body, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format("PRIVMSG #~s :~s\r\n", - [Channel, S]) - end, - Strings)), - ?SEND(Res) - end; - Subject -> - Strings = str:tokens(Subject, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format("TOPIC #~s :~s\r\n", - [Channel, S]) - end, - Strings)), - ?SEND(Res) - end; - Type - when Type == <<"chat">>; - Type == <<"">>; - Type == <<"normal">> -> - Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]), - case Body of - <<"/quote ", Rest/binary>> -> - ?SEND(<>); - <<"/msg ", Rest/binary>> -> - ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); - <<"/me ", Rest/binary>> -> - Strings = str:tokens(Rest, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG ~s :\001ACTION ~s\001\r\n", - [Resource, S]) - end, - Strings)), - ?SEND(Res); - <<"/ctcp ", Rest/binary>> -> - Words = str:tokens(Rest, <<" ">>), - case Words of - [CtcpDest | _] -> - CtcpCmd = str:to_upper( - str:substr(Rest, - str:str(Rest, - <<" ">>) - + 1)), - Res = io_lib:format("PRIVMSG ~s :~s\r\n", - [CtcpDest, - <<"\001", - CtcpCmd/binary, - "\001">>]), - ?SEND(Res); - _ -> ok - end; - _ -> - Strings = str:tokens(Body, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG ~s :~s\r\n", - [Resource, S]) - end, - Strings)), - ?SEND(Res) - end; - <<"error">> -> stop; - _ -> StateData - end, - if NewStateData == stop -> {stop, normal, StateData}; - true -> {next_state, StateName, NewStateData} - end; -handle_info({route_chan, Channel, Resource, - #xmlel{name = <<"iq">>} = El}, - StateName, StateData) -> + #iq{type = T, sub_els = [_]} = Packet}, + StateName, StateData) when T == set; T == get -> From = StateData#state.user, - To = jid:make(iolist_to_binary([Channel, <<"%">>, - StateData#state.server]), - StateData#state.host, StateData#state.nick), - _ = case jlib:iq_query_info(El) of - #iq{xmlns = ?NS_MUC_ADMIN} = IQ -> - iq_admin(StateData, Channel, From, To, IQ); - #iq{xmlns = ?NS_VERSION} -> - Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n", - [Resource]), - _ = (?SEND(Res)), - Err = jlib:make_error_reply(El, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{xmlns = ?NS_TIME} -> - Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n", - [Resource]), - _ = (?SEND(Res)), - Err = jlib:make_error_reply(El, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{xmlns = ?NS_VCARD} -> - Res = io_lib:format("WHOIS ~s \r\n", [Resource]), - _ = (?SEND(Res)), - Err = jlib:make_error_reply(El, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{} -> - Err = jlib:make_error_reply(El, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> ok - end, + To = jid:make(iolist_to_binary([Channel, <<"%">>, StateData#state.server]), + StateData#state.host, StateData#state.nick), + try xmpp:decode_els(Packet) of + #iq{sub_els = [SubEl]} = IQ -> + case xmpp:get_ns(SubEl) of + ?NS_MUC_ADMIN -> + iq_admin(StateData, Channel, From, To, IQ); + ?NS_VERSION -> + Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n", + [Resource]), + _ = (?SEND(Res)), + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, Packet, Err); + ?NS_TIME -> + Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n", + [Resource]), + _ = (?SEND(Res)), + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, Packet, Err); + ?NS_VCARD -> + Res = io_lib:format("WHOIS ~s \r\n", [Resource]), + _ = (?SEND(Res)), + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, Packet, Err); + _ -> + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, Packet, Err) + end + catch _:{xmpp_codec, Why} -> + Err = xmpp:err_bad_request( + xmpp:format_error(Why), xmpp:get_lang(Packet)), + ejabberd_router:route_error(To, From, Packet, Err) + end, {next_state, StateName, StateData}; -handle_info({route_chan, _Channel, _Resource, _Packet}, - StateName, StateData) -> +handle_info({route_chan, Channel, _, #iq{} = IQ}, StateName, StateData) -> + From = StateData#state.user, + To = jid:make(iolist_to_binary([Channel, <<"%">>, StateData#state.server]), + StateData#state.host, StateData#state.nick), + Err = xmpp:err_feature_not_implemented(), + ejabberd_router:route_error(To, From, IQ, Err), {next_state, StateName, StateData}; -handle_info({route_nick, Nick, - #xmlel{name = <<"message">>, attrs = Attrs} = El}, +handle_info({route_nick, Nick, #message{type = chat} = Msg}, StateName, StateData) -> - NewStateData = case fxml:get_attr_s(<<"type">>, Attrs) of - <<"chat">> -> - Body = fxml:get_path_s(El, [{elem, <<"body">>}, cdata]), - case Body of - <<"/quote ", Rest/binary>> -> - ?SEND(<>); - <<"/msg ", Rest/binary>> -> - ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); - <<"/me ", Rest/binary>> -> - Strings = str:tokens(Rest, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG ~s :\001ACTION ~s\001\r\n", - [Nick, S]) - end, - Strings)), - ?SEND(Res); - <<"/ctcp ", Rest/binary>> -> - Words = str:tokens(Rest, <<" ">>), - case Words of - [CtcpDest | _] -> - CtcpCmd = str:to_upper( - str:substr(Rest, - str:str(Rest, - <<" ">>) - + 1)), - Res = io_lib:format("PRIVMSG ~s :~s\r\n", - [CtcpDest, - <<"\001", - CtcpCmd/binary, - "\001">>]), - ?SEND(Res); - _ -> ok - end; - _ -> - Strings = str:tokens(Body, <<"\n">>), - Res = iolist_to_binary( - lists:map( - fun (S) -> - io_lib:format( - "PRIVMSG ~s :~s\r\n", - [Nick, S]) - end, - Strings)), - ?SEND(Res) - end; - <<"error">> -> stop; - _ -> StateData - end, - if NewStateData == stop -> {stop, normal, StateData}; - true -> {next_state, StateName, NewStateData} - end; + Body = xmpp:get_text(Msg#message.body), + {next_state, StateName, + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :\001ACTION ~s\001\r\n", + [Nick, S]) + end, + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr(Rest, + str:str(Rest, + <<" ">>) + + 1)), + Res = io_lib:format("PRIVMSG ~s :~s\r\n", + [CtcpDest, + <<"\001", + CtcpCmd/binary, + "\001">>]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :~s\r\n", + [Nick, S]) + end, + Strings)), + ?SEND(Res) + end}; +handle_info({route_nick, _, #message{type = error}}, _, StateData) -> + {stop, normal, StateData}; handle_info({route_nick, _Nick, _Packet}, StateName, StateData) -> {next_state, StateName, StateData}; @@ -561,13 +507,13 @@ handle_info({ircstring, <<$:, String/binary>>}, {error, {error, error_unknown_num(StateData, String, - <<"cancel">>), + cancel), StateData}}; [_, <<$5, _, _>> | _] -> {error, {error, error_unknown_num(StateData, String, - <<"cancel">>), + cancel), StateData}}; _ -> ?DEBUG("unknown irc command '~s'~n", @@ -638,12 +584,12 @@ handle_info({ircstring, <<$:, String/binary>>}, [From, <<"MODE">>, <<$#, Chan/binary>>, <<"+o">>, Nick | _] -> process_mode_o(StateData, Chan, From, Nick, - <<"admin">>, <<"moderator">>), + admin, moderator), StateData; [From, <<"MODE">>, <<$#, Chan/binary>>, <<"-o">>, Nick | _] -> process_mode_o(StateData, Chan, From, Nick, - <<"member">>, <<"participant">>), + member, participant), StateData; [From, <<"KICK">>, <<$#, Chan/binary>>, Nick | _] -> process_kick(StateData, Chan, From, Nick, String), @@ -702,11 +648,8 @@ terminate(_Reason, _StateName, FullStateData) -> {Error, StateData} = case FullStateData of {error, SError, SStateData} -> {SError, SStateData}; _ -> - {#xmlel{name = <<"error">>, - attrs = [{<<"code">>, <<"502">>}], - children = - [{xmlcdata, - <<"Server Connect Failed">>}]}, + {xmpp:err_internal_server_error( + <<"Server Connect Failed">>, ?MYLANG), FullStateData} end, (StateData#state.mod):closed_connection(StateData#state.host, @@ -714,9 +657,7 @@ terminate(_Reason, _StateName, FullStateData) -> StateData#state.server), bounce_messages(<<"Server Connect Failed">>), lists:foreach(fun (Chan) -> - Stanza = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"error">>}], - children = [Error]}, + Stanza = xmpp:make_error(#presence{}, Error), send_stanza(Chan, StateData, Stanza) end, dict:fetch_keys(StateData#state.channels)), @@ -726,34 +667,24 @@ terminate(_Reason, _StateName, FullStateData) -> end, ok. +-spec send_stanza(binary(), state(), stanza()) -> ok. send_stanza(Chan, StateData, Stanza) -> ejabberd_router:route( - jid:make( - iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, + StateData#state.nick), StateData#state.user, Stanza). +-spec send_stanza_unavailable(binary(), state()) -> ok. send_stanza_unavailable(Chan, StateData) -> - Affiliation = <<"member">>, - Role = <<"none">>, - Stanza = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - Affiliation}, - {<<"role">>, Role}], - children = []}, - #xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"110">>}], - children = []}]}]}, + Affiliation = member, + Role = none, + Stanza = #presence{ + type = unavailable, + sub_els = [#muc_user{ + items = [#muc_item{affiliation = Affiliation, + role = Role}], + status_codes = [110]}]}, send_stanza(Chan, StateData, Stanza). %%%---------------------------------------------------------------------- @@ -776,20 +707,14 @@ send_text(#state{socket = Socket, encoding = Encoding}, bounce_messages(Reason) -> receive - {send_element, El} -> - #xmlel{attrs = Attrs} = El, - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - _ -> - Err = jlib:make_error_reply(El, <<"502">>, Reason), - From = jid:from_string(fxml:get_attr_s(<<"from">>, - Attrs)), - To = jid:from_string(fxml:get_attr_s(<<"to">>, - Attrs)), - ejabberd_router:route(To, From, Err) - end, - bounce_messages(Reason) - after 0 -> ok + {send_element, El} -> + From = xmpp:get_from(El), + To = xmpp:get_to(El), + Lang = xmpp:get_lang(El), + Err = xmpp:err_internal_server_error(Reason, Lang), + ejabberd_router:route_error(To, From, El, Err), + bounce_messages(Reason) + after 0 -> ok end. route_chan(Pid, Channel, Resource, Packet) -> @@ -831,62 +756,43 @@ process_channel_list_user(StateData, Chan, User) -> end, {User2, Affiliation, Role} = case User1 of <<$@, U2/binary>> -> - {U2, <<"admin">>, <<"moderator">>}; + {U2, admin, moderator}; <<$+, U2/binary>> -> - {U2, <<"member">>, <<"participant">>}; + {U2, member, participant}; <<$%, U2/binary>> -> - {U2, <<"admin">>, <<"moderator">>}; + {U2, admin, moderator}; <<$&, U2/binary>> -> - {U2, <<"admin">>, <<"moderator">>}; + {U2, admin, moderator}; <<$~, U2/binary>> -> - {U2, <<"admin">>, <<"moderator">>}; - _ -> {User1, <<"member">>, <<"participant">>} + {U2, admin, moderator}; + _ -> {User1, member, participant} end, - ejabberd_router:route(jid:make(iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, User2), - StateData#state.user, - #xmlel{name = <<"presence">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - Affiliation}, - {<<"role">>, - Role}], - children = []}]}]}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, User2), + StateData#state.user, + #presence{ + sub_els = [#muc_user{items = [#muc_item{affiliation = Affiliation, + role = Role}]}]}), case catch dict:update(Chan, fun (Ps) -> (?SETS):add_element(User2, Ps) end, - StateData#state.channels) - of - {'EXIT', _} -> StateData; - NS -> StateData#state{channels = NS} + StateData#state.channels) of + {'EXIT', _} -> StateData; + NS -> StateData#state{channels = NS} end. process_channel_topic(StateData, Chan, String) -> Msg = ejabberd_regexp:replace(String, <<".*332[^:]*:">>, <<"">>), - Msg1 = filter_message(Msg), - ejabberd_router:route(jid:make(iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"subject">>, attrs = [], - children = [{xmlcdata, Msg1}]}, - #xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - <<"Topic for #", Chan/binary, - ": ", Msg1/binary>>}]}]}). + Subject = filter_message(Msg), + Body = <<"Topic for #", Chan/binary, ": ", Subject/binary>>, + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host), + StateData#state.user, + #message{type = groupchat, + subject = xmpp:mk_text(Subject), + body = xmpp:mk_text(Body)}). process_channel_topic_who(StateData, Chan, String) -> Words = str:tokens(String, <<" ">>), @@ -901,30 +807,17 @@ process_channel_topic_who(StateData, Chan, String) -> _ -> String end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = groupchat, body = xmpp:mk_text(Msg2)}). error_nick_in_use(_StateData, String) -> Msg = ejabberd_regexp:replace(String, <<".*433 +[^ ]* +">>, <<"">>), Msg1 = filter_message(Msg), - #xmlel{name = <<"error">>, - attrs = - [{<<"code">>, <<"409">>}, {<<"type">>, <<"cancel">>}], - children = - [#xmlel{name = <<"conflict">>, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}, - #xmlel{name = <<"text">>, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], - children = [{xmlcdata, Msg1}]}]}. + xmpp:err_conflict(Msg1, ?MYLANG). process_nick_in_use(StateData, String) -> Error = error_nick_in_use(StateData, String), @@ -933,121 +826,73 @@ process_nick_in_use(StateData, String) -> % Shouldn't happen with a well behaved server StateData; Chan -> - ejabberd_router:route(jid:make(iolist_to_binary([Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"error">>}], - children = [Error]}), - StateData#state{nickchannel = undefined} + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + xmpp:make_error(#presence{}, Error)), + StateData#state{nickchannel = undefined} end. process_num_error(StateData, String) -> - Error = error_unknown_num(StateData, String, - <<"continue">>), - lists:foreach(fun (Chan) -> - ejabberd_router:route( - jid:make( - iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = - [{<<"type">>, - <<"error">>}], - children = [Error]}) - end, - dict:fetch_keys(StateData#state.channels)), + Error = error_unknown_num(StateData, String, continue), + lists:foreach( + fun(Chan) -> + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + xmpp:make_error(#message{}, Error)) + end, dict:fetch_keys(StateData#state.channels)), StateData. process_endofwhois(StateData, _String, Nick) -> - ejabberd_router:route(jid:make(iolist_to_binary([Nick, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - <<"End of WHOIS">>}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]), + StateData#state.host), + StateData#state.user, + #message{type = chat, body = xmpp:mk_text(<<"End of WHOIS">>)}). process_whois311(StateData, String, Nick, Ident, Irchost) -> Fullname = ejabberd_regexp:replace(String, <<".*311[^:]*:">>, <<"">>), - ejabberd_router:route(jid:make(iolist_to_binary([Nick, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - [<<"WHOIS: ">>, - Nick, - <<" is ">>, - Ident, - <<"@">>, - Irchost, - <<" : ">>, - Fullname])}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = chat, + body = xmpp:mk_text( + iolist_to_binary( + [<<"WHOIS: ">>, Nick, <<" is ">>, Ident, + <<"@">>, Irchost, <<" : ">>, Fullname]))}). process_whois312(StateData, String, Nick, Ircserver) -> Ircserverdesc = ejabberd_regexp:replace(String, <<".*312[^:]*:">>, <<"">>), - ejabberd_router:route(jid:make(iolist_to_binary([Nick, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - [<<"WHOIS: ">>, - Nick, - <<" use ">>, - Ircserver, - <<" : ">>, - Ircserverdesc])}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = chat, + body = xmpp:mk_text( + iolist_to_binary( + [<<"WHOIS: ">>, Nick, <<" use ">>, Ircserver, + <<" : ">>, Ircserverdesc]))}). process_whois319(StateData, String, Nick) -> Chanlist = ejabberd_regexp:replace(String, <<".*319[^:]*:">>, <<"">>), - ejabberd_router:route(jid:make(iolist_to_binary( - [Nick, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - [<<"WHOIS: ">>, - Nick, - <<" is on ">>, - Chanlist])}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Nick, <<"!">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = chat, + body = xmpp:mk_text( + iolist_to_binary( + [<<"WHOIS: ">>, Nick, <<" is on ">>, Chanlist]))}). process_chanprivmsg(StateData, Chan, From, String) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1059,17 +904,11 @@ process_chanprivmsg(StateData, Chan, From, String) -> _ -> Msg end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #message{type = groupchat, body = xmpp:mk_text(Msg2)}). process_channotice(StateData, Chan, From, String) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1081,17 +920,11 @@ process_channotice(StateData, Chan, From, String) -> _ -> <<"/me NOTICE: ", Msg/binary>> end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #message{type = groupchat, body = xmpp:mk_text(Msg2)}). process_privmsg(StateData, _Nick, From, String) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1103,17 +936,11 @@ process_privmsg(StateData, _Nick, From, String) -> _ -> Msg end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary( - [FromUser, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([FromUser, <<"!">>, StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #message{type = chat, body = xmpp:mk_text(Msg2)}). process_notice(StateData, _Nick, From, String) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1125,17 +952,11 @@ process_notice(StateData, _Nick, From, String) -> _ -> <<"/me NOTICE: ", Msg/binary>> end, Msg2 = filter_message(Msg1), - ejabberd_router:route(jid:make(iolist_to_binary( - [FromUser, - <<"!">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([FromUser, <<"!">>, StateData#state.server]), + StateData#state.host), + StateData#state.user, + #message{type = chat, body = xmpp:mk_text(Msg2)}). process_version(StateData, _Nick, From) -> [FromUser | _] = str:tokens(From, <<"!">>), @@ -1160,54 +981,30 @@ process_topic(StateData, Chan, From, String) -> Msg = ejabberd_regexp:replace(String, <<".*TOPIC[^:]*:">>, <<"">>), Msg1 = filter_message(Msg), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"subject">>, attrs = [], - children = [{xmlcdata, Msg1}]}, - #xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - <<"/me has changed the subject to: ", - Msg1/binary>>}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #message{type = groupchat, + subject = xmpp:mk_text(Msg1), + body = xmpp:mk_text(<<"/me has changed the subject to: ", + Msg1/binary>>)}). process_part(StateData, Chan, From, String) -> [FromUser | FromIdent] = str:tokens(From, <<"!">>), Msg = ejabberd_regexp:replace(String, <<".*PART[^:]*:">>, <<"">>), Msg1 = filter_message(Msg), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"none">>}], - children = []}]}, - #xmlel{name = <<"status">>, attrs = [], - children = - [{xmlcdata, - list_to_binary( - [Msg1, " (", - FromIdent, ")"])}]}]}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #presence{type = unavailable, + sub_els = [#muc_user{ + items = [#muc_item{affiliation = member, + role = none}]}], + status = xmpp:mk_text( + list_to_binary([Msg1, " (", FromIdent, ")"]))}), case catch dict:update(Chan, fun (Ps) -> remove_element(FromUser, Ps) end, StateData#state.channels) @@ -1221,81 +1018,40 @@ process_quit(StateData, From, String) -> Msg = ejabberd_regexp:replace(String, <<".*QUIT[^:]*:">>, <<"">>), Msg1 = filter_message(Msg), - dict:map(fun (Chan, Ps) -> - case (?SETS):is_member(FromUser, Ps) of - true -> - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - FromUser), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name = - <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name - = - <<"item">>, - attrs - = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"none">>}], - children - = - []}]}, - #xmlel{name = - <<"status">>, - attrs = [], - children = - [{xmlcdata, - list_to_binary( - [Msg1, " (", - FromIdent, - ")"])}]}]}), - remove_element(FromUser, Ps); - _ -> Ps - end - end, - StateData#state.channels), + dict:map( + fun(Chan, Ps) -> + case (?SETS):is_member(FromUser, Ps) of + true -> + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, + FromUser), + StateData#state.user, + #presence{type = unavailable, + sub_els = [#muc_user{ + items = [#muc_item{ + affiliation = member, + role = none}]}], + status = xmpp:mk_text( + list_to_binary([Msg1, " (", FromIdent, ")"]))}), + remove_element(FromUser, Ps); + _ -> + Ps + end + end, StateData#state.channels), StateData. process_join(StateData, Channel, From, _String) -> [FromUser | FromIdent] = str:tokens(From, <<"!">>), [Chan | _] = binary:split(Channel, <<":#">>), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - #xmlel{name = <<"presence">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"participant">>}], - children = []}]}, - #xmlel{name = <<"status">>, attrs = [], - children = - [{xmlcdata, - list_to_binary(FromIdent)}]}]}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #presence{ + sub_els = [#muc_user{items = [#muc_item{affiliation = member, + role = participant}]}], + status = xmpp:mk_text(list_to_binary(FromIdent))}), case catch dict:update(Chan, fun (Ps) -> (?SETS):add_element(FromUser, Ps) end, StateData#state.channels) @@ -1306,160 +1062,67 @@ process_join(StateData, Channel, From, _String) -> process_mode_o(StateData, Chan, _From, Nick, Affiliation, Role) -> - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - #xmlel{name = <<"presence">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - Affiliation}, - {<<"role">>, - Role}], - children = []}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #presence{ + sub_els = [#muc_user{items = [#muc_item{affiliation = Affiliation, + role = Role}]}]}). process_kick(StateData, Chan, From, Nick, String) -> Msg = lists:last(str:tokens(String, <<":">>)), Msg2 = <>, - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, <<"">>), - StateData#state.user, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg2}]}]}), - ejabberd_router:route(jid:make(iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - <<"none">>}, - {<<"role">>, - <<"none">>}], - children = []}, - #xmlel{name = <<"status">>, - attrs = - [{<<"code">>, - <<"307">>}], - children = []}]}]}). + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host), + StateData#state.user, + #message{type = groupchat, body = xmpp:mk_text(Msg2)}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, <<"%">>, StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #presence{type = unavailable, + sub_els = [#muc_user{items = [#muc_item{ + affiliation = none, + role = none}], + status_codes = [307]}]}). process_nick(StateData, From, NewNick) -> [FromUser | _] = str:tokens(From, <<"!">>), [Nick | _] = binary:split(NewNick, <<":">>), - NewChans = dict:map(fun (Chan, Ps) -> - case (?SETS):is_member(FromUser, Ps) of - true -> - ejabberd_router:route(jid:make( - iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - FromUser), - StateData#state.user, - #xmlel{name = - <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name - = - <<"x">>, - attrs - = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children - = - [#xmlel{name - = - <<"item">>, - attrs - = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"participant">>}, - {<<"nick">>, - Nick}], - children - = - []}, - #xmlel{name - = - <<"status">>, - attrs - = - [{<<"code">>, - <<"303">>}], - children - = - []}]}]}), - ejabberd_router:route(jid:make( - iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - Nick), - StateData#state.user, - #xmlel{name = - <<"presence">>, - attrs = [], - children = - [#xmlel{name - = - <<"x">>, - attrs - = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children - = - [#xmlel{name - = - <<"item">>, - attrs - = - [{<<"affiliation">>, - <<"member">>}, - {<<"role">>, - <<"participant">>}], - children - = - []}]}]}), - (?SETS):add_element(Nick, - remove_element(FromUser, - Ps)); - _ -> Ps - end - end, - StateData#state.channels), + NewChans = + dict:map( + fun(Chan, Ps) -> + case (?SETS):is_member(FromUser, Ps) of + true -> + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, + FromUser), + StateData#state.user, + #presence{ + type = unavailable, + sub_els = [#muc_user{ + items = [#muc_item{ + affiliation = member, + role = participant, + nick = Nick}], + status_codes = [303]}]}), + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #presence{ + sub_els = [#muc_user{ + items = [#muc_item{ + affiliation = member, + role = participant}]}]}), + (?SETS):add_element(Nick, remove_element(FromUser, Ps)); + _ -> Ps + end + end, StateData#state.channels), if FromUser == StateData#state.nick -> StateData#state{nick = Nick, nickchannel = undefined, channels = NewChans}; @@ -1467,43 +1130,23 @@ process_nick(StateData, From, NewNick) -> end. process_error(StateData, String) -> - lists:foreach(fun (Chan) -> - ejabberd_router:route(jid:make( - iolist_to_binary( - [Chan, - <<"%">>, - StateData#state.server]), - StateData#state.host, - StateData#state.nick), - StateData#state.user, - #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"error">>}], - children = - [#xmlel{name = - <<"error">>, - attrs = - [{<<"code">>, - <<"502">>}], - children = - [{xmlcdata, - String}]}]}) - end, - dict:fetch_keys(StateData#state.channels)). + lists:foreach( + fun(Chan) -> + ejabberd_router:route( + jid:make(iolist_to_binary([Chan, $%, StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + xmpp:make_error( + #presence{}, + xmpp:err_internal_server_error(String, ?MYLANG))) + end, dict:fetch_keys(StateData#state.channels)). error_unknown_num(_StateData, String, Type) -> Msg = ejabberd_regexp:replace(String, <<".*[45][0-9][0-9] +[^ ]* +">>, <<"">>), Msg1 = filter_message(Msg), - #xmlel{name = <<"error">>, - attrs = [{<<"code">>, <<"500">>}, {<<"type">>, Type}], - children = - [#xmlel{name = <<"undefined-condition">>, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}, - #xmlel{name = <<"text">>, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], - children = [{xmlcdata, Msg1}]}]}. + xmpp:err_undefined_condition(Type, Msg1, ?MYLANG). remove_element(E, Set) -> case (?SETS):is_element(E, Set) of @@ -1512,49 +1155,31 @@ remove_element(E, Set) -> end. iq_admin(StateData, Channel, From, To, - #iq{type = Type, xmlns = XMLNS, sub_el = SubEl} = IQ) -> - case catch process_iq_admin(StateData, Channel, Type, - SubEl) - of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - Res -> - if Res /= ignore -> - ResIQ = case Res of - {result, ResEls} -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = ResEls}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); - true -> ok - end + #iq{type = Type, sub_els = [SubEl]} = IQ) -> + try process_iq_admin(StateData, Channel, Type, SubEl) of + {result, Result} -> + ejabberd_router:route(To, From, xmpp:make_iq_result(IQ, Result)); + {error, Error} -> + ejabberd_router:route_error(To, From, IQ, Error) + catch E:R -> + ?ERROR_MSG("failed to process admin query from ~s: ~p", + [jid:to_string(From), {E, {R, erlang:get_stacktrace()}}]), + ejabberd_router:route_error( + To, From, IQ, xmpp:err_internal_server_error()) end. -process_iq_admin(StateData, Channel, set, SubEl) -> - case fxml:get_subtag(SubEl, <<"item">>) of - false -> {error, ?ERR_BAD_REQUEST}; - ItemEl -> - Nick = fxml:get_tag_attr_s(<<"nick">>, ItemEl), - Affiliation = fxml:get_tag_attr_s(<<"affiliation">>, - ItemEl), - Role = fxml:get_tag_attr_s(<<"role">>, ItemEl), - Reason = fxml:get_path_s(ItemEl, - [{elem, <<"reason">>}, cdata]), - process_admin(StateData, Channel, Nick, Affiliation, - Role, Reason) - end; -process_iq_admin(_StateData, _Channel, get, _SubEl) -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}. +process_iq_admin(_StateData, _Channel, set, #muc_admin{items = []}) -> + {error, xmpp:err_bad_request()}; +process_iq_admin(StateData, Channel, set, #muc_admin{items = [Item|_]}) -> + process_admin(StateData, Channel, Item); +process_iq_admin(_StateData, _Channel, _, _SubEl) -> + {error, xmpp:err_feature_not_implemented()}. -process_admin(_StateData, _Channel, <<"">>, - _Affiliation, _Role, _Reason) -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}; -process_admin(StateData, Channel, Nick, _Affiliation, - <<"none">>, Reason) -> +process_admin(_StateData, _Channel, #muc_item{nick = <<"">>}) -> + {error, xmpp:err_feature_not_implemented()}; +process_admin(StateData, Channel, #muc_item{nick = Nick, + reason = Reason, + role = none}) -> case Reason of <<"">> -> send_text(StateData, @@ -1564,10 +1189,9 @@ process_admin(StateData, Channel, Nick, _Affiliation, io_lib:format("KICK #~s ~s :~s\r\n", [Channel, Nick, Reason])) end, - {result, []}; -process_admin(_StateData, _Channel, _Nick, _Affiliation, - _Role, _Reason) -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}. + {result, undefined}; +process_admin(_StateData, _Channel, _Item) -> + {error, xmpp:err_feature_not_implemented()}. filter_message(Msg) -> list_to_binary( @@ -1589,5 +1213,5 @@ unixtime2string(Unixtime) -> {0, 0, 0}}), {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time_to_local_time(calendar:gregorian_seconds_to_datetime(Secs)), - iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", + (str:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Year, Month, Day, Hour, Minute, Second])). diff --git a/src/mod_irc_mnesia.erl b/src/mod_irc_mnesia.erl index 9f8117ad3..e23f5a268 100644 --- a/src/mod_irc_mnesia.erl +++ b/src/mod_irc_mnesia.erl @@ -13,7 +13,7 @@ %% API -export([init/2, get_data/3, set_data/4, import/2]). --include("jlib.hrl"). +-include("jid.hrl"). -include("mod_irc.hrl"). -include("logger.hrl"). @@ -21,7 +21,7 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(irc_custom, + ejabberd_mnesia:create(?MODULE, irc_custom, [{disc_copies, [node()]}, {attributes, record_info(fields, irc_custom)}]), update_table(). diff --git a/src/mod_irc_riak.erl b/src/mod_irc_riak.erl index 6ac7befdf..a71859c5c 100644 --- a/src/mod_irc_riak.erl +++ b/src/mod_irc_riak.erl @@ -13,7 +13,7 @@ %% API -export([init/2, get_data/3, set_data/4, import/2]). --include("jlib.hrl"). +-include("jid.hrl"). -include("mod_irc.hrl"). %%%=================================================================== diff --git a/src/mod_irc_sql.erl b/src/mod_irc_sql.erl index 8aa428e54..7905db91f 100644 --- a/src/mod_irc_sql.erl +++ b/src/mod_irc_sql.erl @@ -15,7 +15,7 @@ %% API -export([init/2, get_data/3, set_data/4, import/1, import/2, export/1]). --include("jlib.hrl"). +-include("jid.hrl"). -include("mod_irc.hrl"). -include("ejabberd_sql_pt.hrl"). diff --git a/src/mod_last.erl b/src/mod_last.erl index ce9148841..463eac051 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -33,16 +33,16 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_local_iq/3, export/1, - process_sm_iq/3, on_presence_update/4, import/1, - import/3, store_last_info/4, get_last_info/2, +-export([start/2, stop/1, process_local_iq/1, export/1, + process_sm_iq/1, on_presence_update/4, import_info/0, + import/5, import_start/2, store_last_info/4, get_last_info/2, remove_user/2, transform_options/1, mod_opt_type/1, opt_type/1, register_user/2, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_privacy.hrl"). -include("mod_last.hrl"). @@ -87,25 +87,14 @@ stop(Host) -> %%% Uptime of ejabberd node %%% -process_local_iq(_From, _To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - Sec = get_node_uptime(), - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, ?NS_LAST}, - {<<"seconds">>, - iolist_to_binary(integer_to_list(Sec))}], - children = []}]} - end. +-spec process_local_iq(iq()) -> iq(). +process_local_iq(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_local_iq(#iq{type = get} = IQ) -> + xmpp:make_iq_result(IQ, #last{seconds = get_node_uptime()}). -%% @spec () -> integer() +-spec get_node_uptime() -> non_neg_integer(). %% @doc Get the uptime of the ejabberd node, expressed in seconds. %% When ejabberd is starting, ejabberd_config:start/0 stores the datetime. get_node_uptime() -> @@ -118,6 +107,7 @@ get_node_uptime() -> p1_time_compat:system_time(seconds) - Now end. +-spec now_to_seconds(erlang:timestamp()) -> non_neg_integer(). now_to_seconds({MegaSecs, Secs, _MicroSecs}) -> MegaSecs * 1000000 + Secs. @@ -125,83 +115,63 @@ now_to_seconds({MegaSecs, Secs, _MicroSecs}) -> %%% Serve queries about user last online %%% -process_sm_iq(From, To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - User = To#jid.luser, - Server = To#jid.lserver, - {Subscription, _Groups} = - ejabberd_hooks:run_fold(roster_get_jid_info, Server, - {none, []}, [User, Server, From]), - if (Subscription == both) or (Subscription == from) or - (From#jid.luser == To#jid.luser) and - (From#jid.lserver == To#jid.lserver) -> - UserListRecord = - ejabberd_hooks:run_fold(privacy_get_user_list, Server, - #userlist{}, [User, Server]), - case ejabberd_hooks:run_fold(privacy_check_packet, - Server, allow, - [User, Server, UserListRecord, - {To, From, - #xmlel{name = <<"presence">>, - attrs = [], - children = []}}, - out]) - of - allow -> get_last_iq(IQ, SubEl, User, Server); - deny -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} - end; - true -> - Txt = <<"Not subscribed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]} - end +-spec process_sm_iq(iq()) -> iq(). +process_sm_iq(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) -> + User = To#jid.luser, + Server = To#jid.lserver, + {Subscription, _Groups} = + ejabberd_hooks:run_fold(roster_get_jid_info, Server, + {none, []}, [User, Server, From]), + if (Subscription == both) or (Subscription == from) or + (From#jid.luser == To#jid.luser) and + (From#jid.lserver == To#jid.lserver) -> + UserListRecord = + ejabberd_hooks:run_fold(privacy_get_user_list, Server, + #userlist{}, [User, Server]), + case ejabberd_hooks:run_fold(privacy_check_packet, + Server, allow, + [User, Server, UserListRecord, + {To, From, #presence{}}, out]) of + allow -> get_last_iq(IQ, User, Server); + deny -> xmpp:make_error(IQ, xmpp:err_forbidden()) + end; + true -> + Txt = <<"Not subscribed">>, + xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang)) end. %% @spec (LUser::string(), LServer::string()) -> %% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason} +-spec get_last(binary(), binary()) -> {ok, non_neg_integer(), binary()} | + not_found | {error, any()}. get_last(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_last(LUser, LServer). -get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) -> +-spec get_last_iq(iq(), binary(), binary()) -> iq(). +get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) -> case ejabberd_sm:get_user_resources(LUser, LServer) of [] -> case get_last(LUser, LServer) of {error, _Reason} -> Txt = <<"Database failure">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}; + xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)); not_found -> Txt = <<"No info about last activity found">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}; + xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)); {ok, TimeStamp, Status} -> TimeStamp2 = p1_time_compat:system_time(seconds), Sec = TimeStamp2 - TimeStamp, - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, ?NS_LAST}, - {<<"seconds">>, - iolist_to_binary(integer_to_list(Sec))}], - children = [{xmlcdata, Status}]}]} + xmpp:make_iq_result(IQ, #last{seconds = Sec, status = Status}) end; _ -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, ?NS_LAST}, - {<<"seconds">>, <<"0">>}], - children = []}]} + xmpp:make_iq_result(IQ, #last{seconds = 0}) end. +-spec register_user(binary(), binary()) -> {atomic, any()}. register_user(User, Server) -> on_presence_update( User, @@ -209,42 +179,56 @@ register_user(User, Server) -> <<"RegisterResource">>, <<"Registered but didn't login">>). +-spec on_presence_update(binary(), binary(), binary(), binary()) -> {atomic, any()}. on_presence_update(User, Server, _Resource, Status) -> TimeStamp = p1_time_compat:system_time(seconds), store_last_info(User, Server, TimeStamp, Status). +-spec store_last_info(binary(), binary(), non_neg_integer(), binary()) -> + {atomic, any()}. store_last_info(User, Server, TimeStamp, Status) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:store_last_info(LUser, LServer, TimeStamp, Status). -%% @spec (LUser::string(), LServer::string()) -> -%% {ok, TimeStamp::integer(), Status::string()} | not_found +-spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} | + not_found. get_last_info(LUser, LServer) -> case get_last(LUser, LServer) of {error, _Reason} -> not_found; Res -> Res end. +-spec remove_user(binary(), binary()) -> any(). remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:remove_user(LUser, LServer). +import_info() -> + [{<<"last">>, 3}]. + +import_start(LServer, DBType) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:init(LServer, []). + +import(LServer, {sql, _}, DBType, <<"last">>, [LUser, TimeStamp, State]) -> + TS = case TimeStamp of + <<"">> -> 0; + _ -> binary_to_integer(TimeStamp) + end, + LA = #last_activity{us = {LUser, LServer}, + timestamp = TS, + status = State}, + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, LA). + export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). - -import(LServer, DBType, LA) -> - Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, LA). - transform_options(Opts) -> lists:foldl(fun transform_options/2, [], Opts). diff --git a/src/mod_last_mnesia.erl b/src/mod_last_mnesia.erl index 7a1610abb..269ed4ba0 100644 --- a/src/mod_last_mnesia.erl +++ b/src/mod_last_mnesia.erl @@ -19,7 +19,7 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(last_activity, + ejabberd_mnesia:create(?MODULE, last_activity, [{disc_copies, [node()]}, {attributes, record_info(fields, last_activity)}]), diff --git a/src/mod_last_sql.erl b/src/mod_last_sql.erl index 13b028c6f..0351e668c 100644 --- a/src/mod_last_sql.erl +++ b/src/mod_last_sql.erl @@ -13,7 +13,7 @@ %% API -export([init/2, get_last/2, store_last_info/4, remove_user/2, - import/1, import/2, export/1]). + import/2, export/1]). -include("mod_last.hrl"). -include("logger.hrl"). @@ -43,9 +43,6 @@ store_last_info(LUser, LServer, TimeStamp, Status) -> remove_user(LUser, LServer) -> sql_queries:del_last(LServer, LUser). -import(_LServer, _LA) -> - pass. - export(_Server) -> [{last_activity, fun(Host, #last_activity{us = {LUser, LServer}, @@ -58,15 +55,5 @@ export(_Server) -> [] end}]. -import(LServer) -> - [{<<"select username, seconds, state from last">>, - fun([LUser, TimeStamp, State]) -> - #last_activity{us = {LUser, LServer}, - timestamp = jlib:binary_to_integer( - TimeStamp), - status = State} - end}]. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== +import(_LServer, _LA) -> + pass. diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 8f6492047..edb0d1485 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -34,12 +34,12 @@ -export([start/2, stop/1, depends/2]). -export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5, - process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5, - remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4, + process_iq_v0_2/1, process_iq_v0_3/1, disco_sm_features/5, + remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2, muc_filter_message/5, message_is_archived/5, delete_old_messages/2, - get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]). + get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/3]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("logger.hrl"). -include("mod_muc_room.hrl"). -include("ejabberd_commands.hrl"). @@ -54,17 +54,13 @@ -callback delete_old_messages(binary() | global, erlang:timestamp(), all | chat | groupchat) -> any(). --callback extended_fields() -> [xmlel()]. +-callback extended_fields() -> [mam_query:property() | #xdata_field{}]. -callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat, jid(), binary(), recv | send) -> {ok, binary()} | any(). -callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any(). -callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error. --callback select(binary(), jid(), jid(), - none | erlang:timestamp(), - none | erlang:timestamp(), - none | ljid() | {text, binary()}, - none | #rsm_in{}, - chat | groupchat) -> +-callback select(binary(), jid(), jid(), mam_query:result(), + #rsm_set{} | undefined, chat | groupchat) -> {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}. %%%=================================================================== @@ -174,72 +170,46 @@ stop(Host) -> depends(_Host, _Opts) -> []. +-spec remove_user(binary(), binary()) -> ok. remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:remove_user(LUser, LServer). + Mod:remove_user(LUser, LServer), + cache_tab:dirty_delete(archive_prefs, {LUser, LServer}, fun() -> ok end), + ok. +-spec remove_room(binary(), binary(), binary()) -> ok. remove_room(LServer, Name, Host) -> LName = jid:nodeprep(Name), LHost = jid:nameprep(Host), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:remove_room(LServer, LName, LHost). + Mod:remove_room(LServer, LName, LHost), + ok. -get_room_config(X, RoomState, _From, Lang) -> +-spec get_room_config([muc_roomconfig:property()], mod_muc_room:state(), + jid(), binary()) -> [muc_roomconfig:property()]. +get_room_config(Fields, RoomState, _From, _Lang) -> Config = RoomState#state.config, - Label = <<"Enable message archiving">>, - Var = <<"muc#roomconfig_mam">>, - Val = case Config#config.mam of - true -> <<"1">>; - _ -> <<"0">> - end, - XField = #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"boolean">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}, - X ++ [XField]. + Fields ++ [{mam, Config#config.mam}]. -set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) -> - try - Val = case Vals of - [<<"0">>|_] -> false; - [<<"false">>|_] -> false; - [<<"1">>|_] -> true; - [<<"true">>|_] -> true - end, - {#config.mam, Val} - catch _:{case_clause, _} -> - Txt = <<"Value of '~s' should be boolean">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)} - end; -set_room_option(Acc, _Opt, _Vals, _Lang) -> +-spec set_room_option({pos_integer(), _}, muc_roomconfig:property(), binary()) + -> {pos_integer(), _}. +set_room_option(_Acc, {mam, Val}, _Lang) -> + {#config.mam, Val}; +set_room_option(Acc, _Property, _Lang) -> Acc. -user_receive_packet(Pkt, C2SState, JID, Peer, To) -> +-spec user_receive_packet(stanza(), ejabberd_c2s:state(), jid(), jid(), jid()) -> stanza(). +user_receive_packet(Pkt, C2SState, JID, Peer, _To) -> LUser = JID#jid.luser, LServer = JID#jid.lserver, - IsBareCopy = is_bare_copy(JID, To), case should_archive(Pkt, LServer) of - true when not IsBareCopy -> + true -> NewPkt = strip_my_archived_tag(Pkt, LServer), case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of {ok, ID} -> - Archived = #xmlel{name = <<"archived">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_MAM_TMP}, - {<<"id">>, ID}]}, - StanzaID = #xmlel{name = <<"stanza-id">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_SID_0}, - {<<"id">>, ID}]}, - NewEls = [Archived, StanzaID|NewPkt#xmlel.children], - NewPkt#xmlel{children = NewEls}; + set_stanza_id(NewPkt, JID, ID); _ -> NewPkt end; @@ -247,25 +217,17 @@ user_receive_packet(Pkt, C2SState, JID, Peer, To) -> Pkt end. +-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza(). user_send_packet(Pkt, C2SState, JID, Peer) -> LUser = JID#jid.luser, LServer = JID#jid.lserver, case should_archive(Pkt, LServer) of true -> NewPkt = strip_my_archived_tag(Pkt, LServer), - case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt), + case store_msg(C2SState, xmpp:set_from_to(NewPkt, JID, Peer), LUser, LServer, Peer, send) of {ok, ID} -> - Archived = #xmlel{name = <<"archived">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_MAM_TMP}, - {<<"id">>, ID}]}, - StanzaID = #xmlel{name = <<"stanza-id">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_SID_0}, - {<<"id">>, ID}]}, - NewEls = [Archived, StanzaID|NewPkt#xmlel.children], - NewPkt#xmlel{children = NewEls}; + set_stanza_id(NewPkt, JID, ID); _ -> NewPkt end; @@ -273,10 +235,14 @@ user_send_packet(Pkt, C2SState, JID, Peer) -> Pkt end. +-spec user_send_packet_strip_tag(stanza(), ejabberd_c2s:state(), + jid(), jid()) -> stanza(). user_send_packet_strip_tag(Pkt, _C2SState, JID, _Peer) -> LServer = JID#jid.lserver, strip_my_archived_tag(Pkt, LServer). +-spec muc_filter_message(message(), mod_muc_room:state(), + jid(), jid(), binary()) -> message(). muc_filter_message(Pkt, #state{config = Config} = MUCState, RoomJID, From, FromNick) -> if Config#config.mam -> @@ -285,16 +251,7 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState, StorePkt = strip_x_jid_tags(NewPkt), case store_muc(MUCState, StorePkt, RoomJID, From, FromNick) of {ok, ID} -> - Archived = #xmlel{name = <<"archived">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_MAM_TMP}, - {<<"id">>, ID}]}, - StanzaID = #xmlel{name = <<"stanza-id">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_SID_0}, - {<<"id">>, ID}]}, - NewEls = [Archived, StanzaID|NewPkt#xmlel.children], - NewPkt#xmlel{children = NewEls}; + set_stanza_id(NewPkt, RoomJID, ID); _ -> NewPkt end; @@ -302,75 +259,87 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState, Pkt end. +set_stanza_id(Pkt, JID, ID) -> + BareJID = jid:remove_resource(JID), + Archived = #mam_archived{by = BareJID, id = ID}, + StanzaID = #stanza_id{by = BareJID, id = ID}, + NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)], + xmpp:set_els(Pkt, NewEls). + % Query archive v0.2 -process_iq_v0_2(#jid{lserver = LServer} = From, - #jid{lserver = LServer} = To, - #iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) -> - Fs = parse_query_v0_2(SubEl), - process_iq(LServer, From, To, IQ, SubEl, Fs, chat); -process_iq_v0_2(From, To, IQ) -> - process_iq(From, To, IQ). +process_iq_v0_2(#iq{from = #jid{lserver = LServer}, + to = #jid{lserver = LServer}, + type = get, sub_els = [#mam_query{}]} = IQ) -> + process_iq(LServer, IQ, chat); +process_iq_v0_2(IQ) -> + process_iq(IQ). % Query archive v0.3 -process_iq_v0_3(#jid{lserver = LServer} = From, - #jid{lserver = LServer} = To, - #iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) -> - process_iq(LServer, From, To, IQ, SubEl, get_xdata_fields(SubEl), chat); -process_iq_v0_3(#jid{lserver = LServer}, - #jid{lserver = LServer}, - #iq{type = get, sub_el = #xmlel{name = <<"query">>}} = IQ) -> +process_iq_v0_3(#iq{from = #jid{lserver = LServer}, + to = #jid{lserver = LServer}, + type = set, sub_els = [#mam_query{}]} = IQ) -> + process_iq(LServer, IQ, chat); +process_iq_v0_3(#iq{from = #jid{lserver = LServer}, + to = #jid{lserver = LServer}, + type = get, sub_els = [#mam_query{}]} = IQ) -> process_iq(LServer, IQ); -process_iq_v0_3(From, To, IQ) -> - process_iq(From, To, IQ). +process_iq_v0_3(IQ) -> + process_iq(IQ). -muc_process_iq(#iq{type = set, - sub_el = #xmlel{name = <<"query">>, - attrs = Attrs} = SubEl} = IQ, - MUCState, From, To) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 -> - muc_process_iq(IQ, MUCState, From, To, get_xdata_fields(SubEl)); - _ -> - IQ +-spec muc_process_iq(ignore | iq(), mod_muc_room:state()) -> ignore | iq(). +muc_process_iq(#iq{type = T, lang = Lang, + from = From, + sub_els = [#mam_query{xmlns = NS}]} = IQ, + MUCState) + when (T == set andalso (NS == ?NS_MAM_0 orelse NS == ?NS_MAM_1)) orelse + (T == get andalso NS == ?NS_MAM_TMP) -> + case may_enter_room(From, MUCState) of + true -> + LServer = MUCState#state.server_host, + Role = mod_muc_room:get_role(From, MUCState), + process_iq(LServer, IQ, {groupchat, Role, MUCState}); + false -> + Text = <<"Only members may query archives of this room">>, + xmpp:make_error(IQ, xmpp:err_forbidden(Text, Lang)) end; muc_process_iq(#iq{type = get, - sub_el = #xmlel{name = <<"query">>, - attrs = Attrs} = SubEl} = IQ, - MUCState, From, To) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MAM_TMP -> - muc_process_iq(IQ, MUCState, From, To, parse_query_v0_2(SubEl)); - NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 -> - LServer = MUCState#state.server_host, - process_iq(LServer, IQ); - _ -> - IQ - end; -muc_process_iq(IQ, _MUCState, _From, _To) -> + sub_els = [#mam_query{xmlns = NS}]} = IQ, + MUCState) when NS == ?NS_MAM_0; NS == ?NS_MAM_1 -> + LServer = MUCState#state.server_host, + process_iq(LServer, IQ); +muc_process_iq(IQ, _MUCState) -> IQ. -get_xdata_fields(SubEl) -> - case {fxml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA), - fxml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of - {#xmlel{} = XData, false} -> - jlib:parse_xdata_submit(XData); - {#xmlel{} = XData, #xmlel{}} -> - [{<<"set">>, SubEl} | jlib:parse_xdata_submit(XData)]; - {false, #xmlel{}} -> - [{<<"set">>, SubEl}]; - {false, false} -> - [] - end. +parse_query(#mam_query{xmlns = ?NS_MAM_TMP, + start = Start, 'end' = End, + with = With, withtext = Text}, _Lang) -> + {ok, [{start, Start}, {'end', End}, + {with, With}, {withtext, Text}]}; +parse_query(#mam_query{xdata = #xdata{}} = Query, Lang) -> + X = xmpp_util:set_xdata_field( + #xdata_field{var = <<"FORM_TYPE">>, + type = hidden, values = [?NS_MAM_1]}, + Query#mam_query.xdata), + try mam_query:decode(X#xdata.fields) of + Form -> {ok, Form} + catch _:{mam_query, Why} -> + Txt = mam_query:format_error(Why), + {error, xmpp:err_bad_request(Txt, Lang)} + end; +parse_query(#mam_query{}, _Lang) -> + {ok, []}. disco_sm_features(empty, From, To, Node, Lang) -> disco_sm_features({result, []}, From, To, Node, Lang); disco_sm_features({result, OtherFeatures}, #jid{luser = U, lserver = S}, - #jid{luser = U, lserver = S}, <<>>, _Lang) -> + #jid{luser = U, lserver = S}, <<"">>, _Lang) -> {result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1 | OtherFeatures]}; disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. +-spec message_is_archived(boolean(), ejabberd_c2s:state(), + jid(), jid(), message()) -> boolean(). message_is_archived(true, _C2SState, _Peer, _JID, _Pkt) -> true; message_is_archived(false, C2SState, Peer, @@ -415,210 +384,159 @@ delete_old_messages(_TypeBin, _Days) -> %%% Internal functions %%%=================================================================== -process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) -> - NS = case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MAM_0 -> - ?NS_MAM_0; - _ -> - ?NS_MAM_1 - end, - CommonFields = [#xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"hidden">>}, - {<<"var">>, <<"FORM_TYPE">>}], - children = [#xmlel{name = <<"value">>, - children = [{xmlcdata, NS}]}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"jid-single">>}, - {<<"var">>, <<"with">>}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"text-single">>}, - {<<"var">>, <<"start">>}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"text-single">>}, - {<<"var">>, <<"end">>}]}], +process_iq(LServer, #iq{sub_els = [#mam_query{xmlns = NS}]} = IQ) -> Mod = gen_mod:db_mod(LServer, ?MODULE), + CommonFields = [{with, undefined}, + {start, undefined}, + {'end', undefined}], ExtendedFields = Mod:extended_fields(), - Fields = ExtendedFields ++ CommonFields, - Form = #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = Fields}, - IQ#iq{type = result, - sub_el = [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, NS}], - children = [Form]}]}. + Fields = mam_query:encode(CommonFields ++ ExtendedFields), + X = xmpp_util:set_xdata_field( + #xdata_field{var = <<"FORM_TYPE">>, type = hidden, values = [NS]}, + #xdata{type = form, fields = Fields}), + xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = X}). % Preference setting (both v0.2 & v0.3) -process_iq(#jid{luser = LUser, lserver = LServer}, - #jid{lserver = LServer}, - #iq{type = set, lang = Lang, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) -> - try {case fxml:get_tag_attr_s(<<"default">>, SubEl) of - <<"always">> -> always; - <<"never">> -> never; - <<"roster">> -> roster - end, - lists:foldl( - fun(#xmlel{name = <<"always">>, children = Els}, {A, N}) -> - {get_jids(Els) ++ A, N}; - (#xmlel{name = <<"never">>, children = Els}, {A, N}) -> - {A, get_jids(Els) ++ N}; - (_, {A, N}) -> - {A, N} - end, {[], []}, SubEl#xmlel.children)} of - {Default, {Always0, Never0}} -> - Always = lists:usort(Always0), - Never = lists:usort(Never0), - case write_prefs(LUser, LServer, LServer, Default, Always, Never) of - ok -> - NewPrefs = prefs_el(Default, Always, Never, IQ#iq.xmlns), - IQ#iq{type = result, sub_el = [NewPrefs]}; - _Err -> - Txt = <<"Database failure">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]} - end - catch _:_ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} +process_iq(#iq{type = set, lang = Lang, + sub_els = [#mam_prefs{default = undefined, xmlns = NS}]} = IQ) -> + Why = {missing_attr, <<"default">>, <<"prefs">>, NS}, + ErrTxt = xmpp:format_error(Why), + xmpp:make_error(IQ, xmpp:err_bad_request(ErrTxt, Lang)); +process_iq(#iq{from = #jid{luser = LUser, lserver = LServer}, + to = #jid{lserver = LServer}, + type = set, lang = Lang, + sub_els = [#mam_prefs{xmlns = NS, + default = Default, + always = Always0, + never = Never0}]} = IQ) -> + Always = lists:usort(get_jids(Always0)), + Never = lists:usort(get_jids(Never0)), + case write_prefs(LUser, LServer, LServer, Default, Always, Never) of + ok -> + NewPrefs = prefs_el(Default, Always, Never, NS), + xmpp:make_iq_result(IQ, NewPrefs); + _Err -> + Txt = <<"Database failure">>, + xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end; -process_iq(#jid{luser = LUser, lserver = LServer}, - #jid{lserver = LServer}, - #iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) -> +process_iq(#iq{from = #jid{luser = LUser, lserver = LServer}, + to = #jid{lserver = LServer}, + type = get, sub_els = [#mam_prefs{xmlns = NS}]} = IQ) -> Prefs = get_prefs(LUser, LServer), PrefsEl = prefs_el(Prefs#archive_prefs.default, Prefs#archive_prefs.always, Prefs#archive_prefs.never, - IQ#iq.xmlns), - IQ#iq{type = result, sub_el = [PrefsEl]}; -process_iq(_, _, #iq{sub_el = SubEl} = IQ) -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. + NS), + xmpp:make_iq_result(IQ, PrefsEl); +process_iq(IQ) -> + xmpp:make_error(IQ, xmpp:err_not_allowed()). -process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) -> +process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang, + sub_els = [SubEl]} = IQ, MsgType) -> case MsgType of chat -> maybe_activate_mam(LUser, LServer); {groupchat, _Role, _MUCState} -> ok end, - case catch lists:foldl( - fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) -> - {{_, _, _} = jlib:datetime_string_to_timestamp(Data), - End, With, RSM}; - ({<<"end">>, [Data|_]}, {Start, _, With, RSM}) -> - {Start, - {_, _, _} = jlib:datetime_string_to_timestamp(Data), - With, RSM}; - ({<<"with">>, [Data|_]}, {Start, End, _, RSM}) -> - {Start, End, jid:tolower(jid:from_string(Data)), RSM}; - ({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) -> - {Start, End, {text, Data}, RSM}; - ({<<"set">>, El}, {Start, End, With, _}) -> - {Start, End, With, jlib:rsm_decode(El)}; - (_, Acc) -> - Acc - end, {none, [], none, none}, Fs) of - {'EXIT', _} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - {_Start, _End, _With, #rsm_in{index = Index}} when is_integer(Index) -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}; - {Start, End, With, RSM} -> - NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl), - select_and_send(LServer, From, To, Start, End, - With, limit_max(RSM, NS), IQ, MsgType) + case SubEl of + #mam_query{rsm = #rsm_set{index = I}} when is_integer(I) -> + xmpp:make_error(IQ, xmpp:err_feature_not_implemented()); + #mam_query{rsm = RSM, xmlns = NS} -> + case parse_query(SubEl, Lang) of + {ok, Query} -> + NewRSM = limit_max(RSM, NS), + select_and_send(LServer, Query, NewRSM, IQ, MsgType); + {error, Err} -> + xmpp:make_error(IQ, Err) + end end. -muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ, MUCState, From, To, Fs) -> - case may_enter_room(From, MUCState) of - true -> - LServer = MUCState#state.server_host, - Role = mod_muc_room:get_role(From, MUCState), - process_iq(LServer, From, To, IQ, SubEl, Fs, - {groupchat, Role, MUCState}); - false -> - Text = <<"Only members may query archives of this room">>, - Error = ?ERRT_FORBIDDEN(Lang, Text), - IQ#iq{type = error, sub_el = [SubEl, Error]} - end. - -parse_query_v0_2(Query) -> - lists:flatmap( - fun (#xmlel{name = <<"start">>} = El) -> - [{<<"start">>, [fxml:get_tag_cdata(El)]}]; - (#xmlel{name = <<"end">>} = El) -> - [{<<"end">>, [fxml:get_tag_cdata(El)]}]; - (#xmlel{name = <<"with">>} = El) -> - [{<<"with">>, [fxml:get_tag_cdata(El)]}]; - (#xmlel{name = <<"withtext">>} = El) -> - [{<<"withtext">>, [fxml:get_tag_cdata(El)]}]; - (#xmlel{name = <<"set">>}) -> - [{<<"set">>, Query}]; - (_) -> - [] - end, Query#xmlel.children). - -should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) -> +should_archive(#message{type = error}, _LServer) -> + false; +should_archive(#message{meta = #{sm_copy := true}}, _LServer) -> + false; +should_archive(#message{body = Body, subject = Subject, + type = Type} = Pkt, LServer) -> case is_resent(Pkt, LServer) of true -> false; false -> - case {check_store_hint(Pkt), - fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs)} of - {_Hint, <<"error">>} -> - false; - {store, _Type} -> + case check_store_hint(Pkt) of + store -> true; - {no_store, _Type} -> + no_store -> false; - {none, <<"groupchat">>} -> + none when Type == groupchat; Type == headline -> false; - {none, <<"headline">>} -> - false; - {none, _Type} -> - case fxml:get_subtag_cdata(Pkt, <<"body">>) of - <<>> -> - %% Empty body - false; - _ -> - true - end + none -> + xmpp:get_text(Body) /= <<>> orelse + xmpp:get_text(Subject) /= <<>> end end; -should_archive(#xmlel{}, _LServer) -> +should_archive(_, _LServer) -> false. +-spec strip_my_archived_tag(stanza(), binary()) -> stanza(). strip_my_archived_tag(Pkt, LServer) -> + Els = xmpp:get_els(Pkt), NewEls = lists:filter( - fun(#xmlel{name = Tag, attrs = Attrs}) - when Tag == <<"archived">>; Tag == <<"stanza-id">> -> - case catch jid:nameprep( - fxml:get_attr_s( - <<"by">>, Attrs)) of - LServer -> - false; - _ -> - true - end; - (_) -> - true - end, Pkt#xmlel.children), - Pkt#xmlel{children = NewEls}. + fun(El) -> + Name = xmpp:get_name(El), + NS = xmpp:get_ns(El), + if (Name == <<"archived">> andalso NS == ?NS_MAM_TMP); + (Name == <<"stanza-id">> andalso NS == ?NS_SID_0) -> + try xmpp:decode(El) of + #mam_archived{by = By} -> + By#jid.lserver /= LServer; + #stanza_id{by = By} -> + By#jid.lserver /= LServer + catch _:{xmpp_codec, _} -> + false + end; + true -> + true + end + end, Els), + xmpp:set_els(Pkt, NewEls). +-spec strip_x_jid_tags(stanza()) -> stanza(). strip_x_jid_tags(Pkt) -> + Els = xmpp:get_els(Pkt), NewEls = lists:filter( - fun(#xmlel{name = <<"x">>} = XEl) -> - not lists:any(fun(ItemEl) -> - fxml:get_tag_attr(<<"jid">>, ItemEl) - /= false - end, fxml:get_subtags(XEl, <<"item">>)); - (_) -> - true - end, Pkt#xmlel.children), - Pkt#xmlel{children = NewEls}. + fun(El) -> + case xmpp:get_name(El) of + <<"x">> -> + NS = xmpp:get_ns(El), + Items = if NS == ?NS_MUC_USER; + NS == ?NS_MUC_ADMIN; + NS == ?NS_MUC_OWNER -> + try xmpp:decode(El) of + #muc_user{items = Is} -> Is; + #muc_admin{items = Is} -> Is; + #muc_owner{items = Is} -> Is + catch _:{xmpp_codec, _} -> + [] + end; + true -> + [] + end, + not lists:any( + fun(#muc_item{jid = JID}) -> + JID /= undefined + end, Items); + _ -> + true + end + end, Els), + xmpp:set_els(Pkt, NewEls). should_archive_peer(C2SState, #archive_prefs{default = Default, always = Always, never = Never}, Peer) -> - LPeer = jid:tolower(Peer), + LPeer = jid:remove_resource(jid:tolower(Peer)), case lists:member(LPeer, Always) of true -> true; @@ -642,30 +560,28 @@ should_archive_peer(C2SState, end end. -should_archive_muc(Pkt) -> - case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of - <<"groupchat">> -> - case check_store_hint(Pkt) of - store -> - true; - no_store -> - false; - none -> - case fxml:get_subtag_cdata(Pkt, <<"body">>) of - <<>> -> - case fxml:get_subtag_cdata(Pkt, <<"subject">>) of - <<>> -> - false; - _ -> - true - end; +should_archive_muc(#message{type = groupchat, + body = Body, subject = Subj} = Pkt) -> + case check_store_hint(Pkt) of + store -> + true; + no_store -> + false; + none -> + case xmpp:get_text(Body) of + <<"">> -> + case xmpp:get_text(Subj) of + <<"">> -> + false; _ -> true - end - end; - _ -> - false - end. + end; + _ -> + true + end + end; +should_archive_muc(_) -> + false. check_store_hint(Pkt) -> case has_store_hint(Pkt) of @@ -680,30 +596,24 @@ check_store_hint(Pkt) -> end end. + +-spec has_store_hint(message()) -> boolean(). has_store_hint(Message) -> - fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS) - /= false. + xmpp:has_subtag(Message, #hint{type = 'store'}). +-spec has_no_store_hint(message()) -> boolean(). has_no_store_hint(Message) -> - fxml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS) - /= false orelse - fxml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS) - /= false orelse - fxml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS) - /= false orelse - fxml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS) - /= false. + xmpp:has_subtag(Message, #hint{type = 'no-store'}) orelse + xmpp:has_subtag(Message, #hint{type = 'no-storage'}) orelse + xmpp:has_subtag(Message, #hint{type = 'no-permanent-store'}) orelse + xmpp:has_subtag(Message, #hint{type = 'no-permanent-storage'}). +-spec is_resent(message(), binary()) -> boolean(). is_resent(Pkt, LServer) -> - case fxml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of - #xmlel{attrs = Attrs} -> - case fxml:get_attr(<<"by">>, Attrs) of - {value, LServer} -> - true; - _ -> - false - end; - false -> + case xmpp:get_subtag(Pkt, #stanza_id{}) of + #stanza_id{by = #jid{lserver = LServer}} -> + true; + _ -> false end. @@ -724,7 +634,8 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) -> pass; NewPkt -> Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:store(NewPkt, LServer, US, chat, Peer, <<"">>, Dir) + El = xmpp:encode(NewPkt), + Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir) end; false -> pass @@ -741,7 +652,8 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) -> pass; NewPkt -> Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:store(NewPkt, LServer, {U, S}, groupchat, Peer, Nick, recv) + El = xmpp:encode(NewPkt), + Mod:store(El, LServer, {U, S}, groupchat, Peer, Nick, recv) end; false -> pass @@ -783,20 +695,10 @@ get_prefs(LUser, LServer) -> end. prefs_el(Default, Always, Never, NS) -> - Default1 = jlib:atom_to_binary(Default), - JFun = fun(L) -> - [#xmlel{name = <<"jid">>, - children = [{xmlcdata, jid:to_string(J)}]} - || J <- L] - end, - Always1 = #xmlel{name = <<"always">>, - children = JFun(Always)}, - Never1 = #xmlel{name = <<"never">>, - children = JFun(Never)}, - #xmlel{name = <<"prefs">>, - attrs = [{<<"xmlns">>, NS}, - {<<"default">>, Default1}], - children = [Always1, Never1]}. + #mam_prefs{default = Default, + always = [jid:make(LJ) || LJ <- Always], + never = [jid:make(LJ) || LJ <- Never], + xmlns = NS}. maybe_activate_mam(LUser, LServer) -> ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE, @@ -825,72 +727,68 @@ maybe_activate_mam(LUser, LServer) -> ok end. -select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) -> - {Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End, - With, RSM, MsgType), +select_and_send(LServer, Query, RSM, #iq{from = From, to = To} = IQ, MsgType) -> + {Msgs, IsComplete, Count} = + case MsgType of + chat -> + select(LServer, From, From, Query, RSM, MsgType); + {groupchat, _Role, _MUCState} -> + select(LServer, From, To, Query, RSM, MsgType) + end, SortedMsgs = lists:keysort(2, Msgs), - send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ). + send(SortedMsgs, Count, IsComplete, IQ). -select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) -> - case MsgType of - chat -> - select(LServer, From, From, Start, End, With, RSM, MsgType); - {groupchat, _Role, _MUCState} -> - select(LServer, From, To, Start, End, With, RSM, MsgType) - end. - -select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM, +select(_LServer, JidRequestor, JidArchive, Query, RSM, {groupchat, _Role, #state{config = #config{mam = false}, history = History}} = MsgType) -> + Start = proplists:get_value(start, Query), + End = proplists:get_value('end', Query), #lqueue{len = L, queue = Q} = History, - {Msgs0, _} = - lists:mapfoldl( - fun({Nick, Pkt, _HaveSubject, UTCDateTime, _Size}, I) -> - Now = datetime_to_now(UTCDateTime, I), + Msgs = + lists:flatmap( + fun({Nick, Pkt, _HaveSubject, Now, _Size}) -> TS = now_to_usec(Now), case match_interval(Now, Start, End) and match_rsm(Now, RSM) of true -> - {[{jlib:integer_to_binary(TS), TS, - msg_to_el(#archive_msg{ - type = groupchat, - timestamp = Now, - peer = undefined, - nick = Nick, - packet = Pkt}, - MsgType, JidRequestor, JidArchive)}], - I+1}; + [{integer_to_binary(TS), TS, + msg_to_el(#archive_msg{ + type = groupchat, + timestamp = Now, + peer = undefined, + nick = Nick, + packet = Pkt}, + MsgType, JidRequestor, JidArchive)}]; false -> - {[], I+1} + [] end - end, 0, queue:to_list(Q)), - Msgs = lists:flatten(Msgs0), + end, queue:to_list(Q)), case RSM of - #rsm_in{max = Max, direction = before} -> + #rsm_set{max = Max, before = Before} when is_binary(Before) -> {NewMsgs, IsComplete} = filter_by_max(lists:reverse(Msgs), Max), {NewMsgs, IsComplete, L}; - #rsm_in{max = Max} -> + #rsm_set{max = Max} -> {NewMsgs, IsComplete} = filter_by_max(Msgs, Max), {NewMsgs, IsComplete, L}; _ -> {Msgs, true, L} end; -select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) -> +select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, - MsgType). + Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType). msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer}, MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) -> Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, JidArchive, Peer, MsgType, Nick), - Pkt3 = #xmlel{name = <<"forwarded">>, - attrs = [{<<"xmlns">>, ?NS_FORWARD}], - children = [fxml:replace_tag_attr( - <<"xmlns">>, <<"jabber:client">>, Pkt2)]}, - jlib:add_delay_info(Pkt3, LServer, TS). + #forwarded{xml_els = [xmpp:encode(Pkt2)], + delay = #delay{stamp = TS, from = jid:make(LServer)}}. -maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive, +maybe_update_from_to(#xmlel{} = El, JidRequestor, JidArchive, Peer, + {groupchat, _, _} = MsgType, Nick) -> + Pkt = xmpp:decode(El, ?NS_CLIENT, [ignore_els]), + maybe_update_from_to(Pkt, JidRequestor, JidArchive, Peer, MsgType, Nick); +maybe_update_from_to(#message{sub_els = Els} = Pkt, JidRequestor, JidArchive, Peer, {groupchat, Role, #state{config = #config{anonymous = Anon}}}, Nick) -> @@ -906,110 +804,58 @@ maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive, end, Items = case ExposeJID of true -> - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = [{<<"jid">>, - jid:to_string(Peer)}]}]}]; + [#muc_user{items = [#muc_item{jid = Peer}]}]; false -> [] end, - Pkt1 = Pkt#xmlel{children = Items ++ Els}, - Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1), - jlib:remove_attr(<<"to">>, Pkt2); + Pkt#message{from = jid:replace_resource(JidArchive, Nick), + to = undefined, + sub_els = Items ++ Els}; maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) -> Pkt. -is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) -> - PrioRes = ejabberd_sm:get_user_present_resources(U, S), - MaxRes = case catch lists:max(PrioRes) of - {_Prio, Res} when is_binary(Res) -> - Res; - _ -> - undefined - end, - IsBareTo = case To of - #jid{lresource = <<"">>} -> - true; - #jid{lresource = LRes} -> - %% Unavailable resources are handled like bare JIDs. - lists:keyfind(LRes, 2, PrioRes) =:= false - end, - case {IsBareTo, R} of - {true, MaxRes} -> - ?DEBUG("Recipient of message to bare JID has top priority: ~s@~s/~s", - [U, S, R]), - false; - {true, _R} -> - %% The message was sent to our bare JID, and we currently have - %% multiple resources with the same highest priority, so the session - %% manager routes the message to each of them. We store the message - %% only from the resource where R equals MaxRes. - ?DEBUG("Additional recipient of message to bare JID: ~s@~s/~s", - [U, S, R]), - true; - {false, _R} -> - false - end. - -send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) -> - QID = fxml:get_tag_attr_s(<<"queryid">>, SubEl), - NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl), - QIDAttr = if QID /= <<>> -> - [{<<"queryid">>, QID}]; - true -> - [] - end, - CompleteAttr = if NS == ?NS_MAM_TMP -> - []; - NS == ?NS_MAM_0; NS == ?NS_MAM_1 -> - [{<<"complete">>, jlib:atom_to_binary(IsComplete)}] - end, - Hint = [#xmlel{name = <<"no-store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]}], +-spec send([{binary(), integer(), xmlel()}], + non_neg_integer(), boolean(), iq()) -> iq() | ignore. +send(Msgs, Count, IsComplete, + #iq{from = From, to = To, + sub_els = [#mam_query{id = QID, xmlns = NS}]} = IQ) -> + Hint = #hint{type = 'no-store'}, Els = lists:map( fun({ID, _IDInt, El}) -> - #xmlel{name = <<"message">>, - children = [#xmlel{name = <<"result">>, - attrs = [{<<"xmlns">>, NS}, - {<<"id">>, ID}|QIDAttr], - children = [El]} | Hint]} + #message{sub_els = [#mam_result{xmlns = NS, + id = ID, + queryid = QID, + sub_els = [El]}]} end, Msgs), - RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS), + RSMOut = make_rsm_out(Msgs, Count), + Result = if NS == ?NS_MAM_TMP -> + #mam_query{xmlns = NS, id = QID, rsm = RSMOut}; + true -> + #mam_fin{xmlns = NS, id = QID, rsm = RSMOut, + complete = IsComplete} + end, if NS == ?NS_MAM_TMP; NS == ?NS_MAM_1 -> lists:foreach( fun(El) -> ejabberd_router:route(To, From, El) end, Els), - IQ#iq{type = result, sub_el = RSMOut}; + xmpp:make_iq_result(IQ, Result); NS == ?NS_MAM_0 -> - ejabberd_router:route( - To, From, jlib:iq_to_xml(IQ#iq{type = result, sub_el = []})), + ejabberd_router:route(To, From, xmpp:make_iq_result(IQ)), lists:foreach( fun(El) -> ejabberd_router:route(To, From, El) end, Els), - ejabberd_router:route( - To, From, #xmlel{name = <<"message">>, - children = RSMOut ++ Hint}), + ejabberd_router:route(To, From, #message{sub_els = [Result, Hint]}), ignore end. -make_rsm_out([], _, Count, Attrs, NS) -> - Tag = if NS == ?NS_MAM_TMP -> <<"query">>; - true -> <<"fin">> - end, - [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs], - children = jlib:rsm_encode(#rsm_out{count = Count})}]; -make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) -> +-spec make_rsm_out([{binary(), integer(), xmlel()}], non_neg_integer()) -> rsm_set(). +make_rsm_out([], Count) -> + #rsm_set{count = Count}; +make_rsm_out([{FirstID, _, _}|_] = Msgs, Count) -> {LastID, _, _} = lists:last(Msgs), - Tag = if NS == ?NS_MAM_TMP -> <<"query">>; - true -> <<"fin">> - end, - [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs], - children = jlib:rsm_encode( - #rsm_out{first = FirstID, count = Count, - last = LastID})}]. + #rsm_set{first = #rsm_first{data = FirstID}, last = LastID, count = Count}. filter_by_max(Msgs, undefined) -> {Msgs, true}; @@ -1018,25 +864,28 @@ filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 -> filter_by_max(_Msgs, _Junk) -> {[], true}. +-spec limit_max(rsm_set(), binary()) -> rsm_set() | undefined. limit_max(RSM, ?NS_MAM_TMP) -> RSM; % XEP-0313 v0.2 doesn't require clients to support RSM. -limit_max(none, _NS) -> - #rsm_in{max = ?DEF_PAGE_SIZE}; -limit_max(#rsm_in{max = Max} = RSM, _NS) when not is_integer(Max) -> - RSM#rsm_in{max = ?DEF_PAGE_SIZE}; -limit_max(#rsm_in{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE -> - RSM#rsm_in{max = ?MAX_PAGE_SIZE}; +limit_max(undefined, _NS) -> + #rsm_set{max = ?DEF_PAGE_SIZE}; +limit_max(#rsm_set{max = Max} = RSM, _NS) when not is_integer(Max) -> + RSM#rsm_set{max = ?DEF_PAGE_SIZE}; +limit_max(#rsm_set{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE -> + RSM#rsm_set{max = ?MAX_PAGE_SIZE}; limit_max(RSM, _NS) -> RSM. +match_interval(Now, Start, undefined) -> + Now >= Start; match_interval(Now, Start, End) -> (Now >= Start) and (Now =< End). -match_rsm(Now, #rsm_in{id = ID, direction = aft}) when ID /= <<"">> -> - Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))), +match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> -> + Now1 = (catch usec_to_now(binary_to_integer(ID))), Now > Now1; -match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> -> - Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))), +match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> -> + Now1 = (catch usec_to_now(binary_to_integer(ID))), Now < Now1; match_rsm(_Now, _) -> true. @@ -1051,20 +900,10 @@ usec_to_now(Int) -> Sec = Secs rem 1000000, {MSec, Sec, USec}. -datetime_to_now(DateTime, USecs) -> - Seconds = calendar:datetime_to_gregorian_seconds(DateTime) - - calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), - {Seconds div 1000000, Seconds rem 1000000, USecs}. - -get_jids(Els) -> - lists:flatmap( - fun(#xmlel{name = <<"jid">>} = El) -> - J = jid:from_string(fxml:get_tag_cdata(El)), - [jid:tolower(jid:remove_resource(J)), - jid:tolower(J)]; - (_) -> - [] - end, Els). +get_jids(undefined) -> + []; +get_jids(Js) -> + [jid:tolower(jid:remove_resource(J)) || J <- Js]. get_commands_spec() -> [#ejabberd_commands{name = delete_old_mam_messages, tags = [purge], diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl index be14d0fff..89ab92ff1 100644 --- a/src/mod_mam_mnesia.erl +++ b/src/mod_mam_mnesia.erl @@ -12,10 +12,10 @@ %% API -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, - extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]). + extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]). -include_lib("stdlib/include/ms_transform.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("logger.hrl"). -include("mod_mam.hrl"). @@ -32,11 +32,11 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(archive_msg, + ejabberd_mnesia:create(?MODULE, archive_msg, [{disc_only_copies, [node()]}, {type, bag}, {attributes, record_info(fields, archive_msg)}]), - mnesia:create_table(archive_prefs, + ejabberd_mnesia:create(?MODULE, archive_prefs, [{disc_only_copies, [node()]}, {attributes, record_info(fields, archive_prefs)}]). @@ -97,7 +97,7 @@ store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir) -> _ -> LPeer = {PUser, PServer, _} = jid:tolower(Peer), TS = p1_time_compat:timestamp(), - ID = jlib:integer_to_binary(now_to_usec(TS)), + ID = integer_to_binary(now_to_usec(TS)), F = fun() -> mnesia:write( #archive_msg{us = {LUser, LServer}, @@ -132,8 +132,14 @@ get_prefs(LUser, LServer) -> select(_LServer, JidRequestor, #jid{luser = LUser, lserver = LServer} = JidArchive, - Start, End, With, RSM, MsgType) -> - MS = make_matchspec(LUser, LServer, Start, End, With), + Query, RSM, MsgType) -> + Start = proplists:get_value(start, Query), + End = proplists:get_value('end', Query), + With = proplists:get_value(with, Query), + LWith = if With /= undefined -> jid:tolower(With); + true -> undefined + end, + MS = make_matchspec(LUser, LServer, Start, End, LWith), Msgs = mnesia:dirty_select(archive_msg, MS), SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs), {FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM), @@ -141,7 +147,7 @@ select(_LServer, JidRequestor, Result = {lists:map( fun(Msg) -> {Msg#archive_msg.id, - jlib:binary_to_integer(Msg#archive_msg.id), + binary_to_integer(Msg#archive_msg.id), mod_mam:msg_to_el(Msg, MsgType, JidRequestor, JidArchive)} end, FilteredMsgs), IsComplete, Count}, @@ -154,6 +160,9 @@ select(_LServer, JidRequestor, now_to_usec({MSec, Sec, USec}) -> (MSec*1000000 + Sec)*1000000 + USec. +make_matchspec(LUser, LServer, Start, undefined, With) -> + %% List is always greater than a tuple + make_matchspec(LUser, LServer, Start, [], With); make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) -> ets:fun2ms( fun(#archive_msg{timestamp = TS, @@ -174,7 +183,7 @@ make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) -> Peer == With -> Msg end); -make_matchspec(LUser, LServer, Start, End, none) -> +make_matchspec(LUser, LServer, Start, End, undefined) -> ets:fun2ms( fun(#archive_msg{timestamp = TS, us = US, @@ -184,28 +193,27 @@ make_matchspec(LUser, LServer, Start, End, none) -> Msg end). -filter_by_rsm(Msgs, none) -> +filter_by_rsm(Msgs, undefined) -> {Msgs, true}; -filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 -> +filter_by_rsm(_Msgs, #rsm_set{max = Max}) when Max < 0 -> {[], true}; -filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) -> - NewMsgs = case Direction of - aft when ID /= <<"">> -> +filter_by_rsm(Msgs, #rsm_set{max = Max, before = Before, 'after' = After}) -> + NewMsgs = if is_binary(After), After /= <<"">> -> lists:filter( fun(#archive_msg{id = I}) -> - ?BIN_GREATER_THAN(I, ID) + ?BIN_GREATER_THAN(I, After) end, Msgs); - before when ID /= <<"">> -> + is_binary(Before), Before /= <<"">> -> lists:foldl( fun(#archive_msg{id = I} = Msg, Acc) - when ?BIN_LESS_THAN(I, ID) -> + when ?BIN_LESS_THAN(I, Before) -> [Msg|Acc]; (_, Acc) -> Acc end, [], Msgs); - before when ID == <<"">> -> + is_binary(Before), Before == <<"">> -> lists:reverse(Msgs); - _ -> + true -> Msgs end, filter_by_max(NewMsgs, Max). diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index 20ed8d4f1..c500745a3 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -14,10 +14,10 @@ %% API -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, - extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]). + extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]). -include_lib("stdlib/include/ms_transform.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_mam.hrl"). -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -51,13 +51,11 @@ delete_old_messages(ServerHost, TimeStamp, Type) -> ok. extended_fields() -> - [#xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"text-single">>}, - {<<"var">>, <<"withtext">>}]}]. + [{withtext, <<"">>}]. store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) -> TSinteger = p1_time_compat:system_time(micro_seconds), - ID = jlib:integer_to_binary(TSinteger), + ID = integer_to_binary(TSinteger), SUser = case Type of chat -> LUser; groupchat -> jid:to_string({LUser, LHost, <<>>}) @@ -126,13 +124,12 @@ get_prefs(LUser, LServer) -> end. select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, - Start, End, With, RSM, MsgType) -> + MAMQuery, RSM, MsgType) -> User = case MsgType of chat -> LUser; {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive) end, - {Query, CountQuery} = make_sql_query(User, LServer, - Start, End, With, RSM), + {Query, CountQuery} = make_sql_query(User, LServer, MAMQuery, RSM), % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a % reasonable limit on how many stanzas may be pushed to a client in one % request. If a query returns a number of stanzas greater than this limit @@ -142,10 +139,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, case {ejabberd_sql:sql_query(LServer, Query), ejabberd_sql:sql_query(LServer, CountQuery)} of {{selected, _, Res}, {selected, _, [[Count]]}} -> - {Max, Direction} = case RSM of - #rsm_in{max = M, direction = D} -> {M, D}; - _ -> {undefined, undefined} - end, + {Max, Direction, _} = get_max_direction_id(RSM), {Res1, IsComplete} = if Max >= 0 andalso Max /= undefined andalso length(Res) > Max -> if Direction == before -> @@ -160,14 +154,14 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, fun([TS, XML, PeerBin, Kind, Nick]) -> try #xmlel{} = El = fxml_stream:parse_element(XML), - Now = usec_to_now(jlib:binary_to_integer(TS)), + Now = usec_to_now(binary_to_integer(TS)), PeerJid = jid:tolower(jid:from_string(PeerBin)), T = case Kind of <<"">> -> chat; null -> chat; _ -> jlib:binary_to_atom(Kind) end, - [{TS, jlib:binary_to_integer(TS), + [{TS, binary_to_integer(TS), mod_mam:msg_to_el(#archive_msg{timestamp = Now, packet = El, type = T, @@ -182,7 +176,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, [Err, TS, XML, PeerBin, Kind, Nick]), [] end - end, Res1), IsComplete, jlib:binary_to_integer(Count)}; + end, Res1), IsComplete, binary_to_integer(Count)}; _ -> {[], false, 0} end. @@ -200,15 +194,12 @@ usec_to_now(Int) -> Sec = Secs rem 1000000, {MSec, Sec, USec}. -make_sql_query(User, LServer, Start, End, With, RSM) -> - {Max, Direction, ID} = case RSM of - #rsm_in{} -> - {RSM#rsm_in.max, - RSM#rsm_in.direction, - RSM#rsm_in.id}; - none -> - {none, none, <<>>} - end, +make_sql_query(User, LServer, MAMQuery, RSM) -> + Start = proplists:get_value(start, MAMQuery), + End = proplists:get_value('end', MAMQuery), + With = proplists:get_value(with, MAMQuery), + WithText = proplists:get_value(withtext, MAMQuery), + {Max, Direction, ID} = get_max_direction_id(RSM), ODBCType = ejabberd_config:get_option( {sql_type, LServer}, ejabberd_sql:opt_type(sql_type)), @@ -219,21 +210,22 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> _ -> fun ejabberd_sql:escape/1 end, LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql -> - [<<" limit ">>, jlib:integer_to_binary(Max+1)]; + [<<" limit ">>, integer_to_binary(Max+1)]; true -> [] end, TopClause = if is_integer(Max), Max >= 0, ODBCType == mssql -> - [<<" TOP ">>, jlib:integer_to_binary(Max+1)]; + [<<" TOP ">>, integer_to_binary(Max+1)]; true -> [] end, - WithClause = case With of - {text, <<>>} -> - []; - {text, Txt} -> - [<<" and match (txt) against ('">>, - Escape(Txt), <<"')">>]; + WithTextClause = if is_binary(WithText), WithText /= <<>> -> + [<<" and match (txt) against ('">>, + Escape(WithText), <<"')">>]; + true -> + [] + end, + WithClause = case catch jid:tolower(With) of {_, _, <<>>} -> [<<" and bare_peer='">>, Escape(jid:to_string(With)), @@ -242,15 +234,15 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> [<<" and peer='">>, Escape(jid:to_string(With)), <<"'">>]; - none -> + _ -> [] end, - PageClause = case catch jlib:binary_to_integer(ID) of + PageClause = case catch binary_to_integer(ID) of I when is_integer(I), I >= 0 -> case Direction of before -> [<<" AND timestamp < ">>, ID]; - aft -> + 'after' -> [<<" AND timestamp > ">>, ID]; _ -> [] @@ -261,14 +253,14 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> StartClause = case Start of {_, _, _} -> [<<" and timestamp >= ">>, - jlib:integer_to_binary(now_to_usec(Start))]; + integer_to_binary(now_to_usec(Start))]; _ -> [] end, EndClause = case End of {_, _, _} -> [<<" and timestamp <= ">>, - jlib:integer_to_binary(now_to_usec(End))]; + integer_to_binary(now_to_usec(End))]; _ -> [] end, @@ -276,7 +268,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick" " FROM archive WHERE username='">>, - SUser, <<"'">>, WithClause, StartClause, EndClause, + SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause, PageClause], QueryPage = @@ -294,4 +286,20 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> end, {QueryPage, [<<"SELECT COUNT(*) FROM archive WHERE username='">>, - SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}. + SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause, <<";">>]}. + +-spec get_max_direction_id(rsm_set() | undefined) -> + {integer() | undefined, + before | 'after' | undefined, + binary()}. +get_max_direction_id(RSM) -> + case RSM of + #rsm_set{max = Max, before = Before} when is_binary(Before) -> + {Max, before, Before}; + #rsm_set{max = Max, 'after' = After} when is_binary(After) -> + {Max, 'after', After}; + #rsm_set{max = Max} -> + {Max, undefined, <<>>}; + _ -> + {undefined, undefined, <<>>} + end. diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl index f1d487e0e..7861542c5 100644 --- a/src/mod_metrics.erl +++ b/src/mod_metrics.erl @@ -31,13 +31,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). - --define(HOOKS, [offline_message_hook, - sm_register_connection_hook, sm_remove_connection_hook, - user_send_packet, user_receive_packet, - s2s_send_packet, s2s_receive_packet, - remove_user, register_user]). +-include("xmpp.hrl"). -export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1, depends/2]). @@ -53,12 +47,26 @@ %%==================================================================== start(Host, _Opts) -> - [ejabberd_hooks:add(Hook, Host, ?MODULE, Hook, 20) - || Hook <- ?HOOKS]. + ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, offline_message_hook, 20), + ejabberd_hooks:add(sm_register_connection_hook, Host, ?MODULE, sm_register_connection_hook, 20), + ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, sm_remove_connection_hook, 20), + ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 20), + ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, user_receive_packet, 20), + ejabberd_hooks:add(s2s_send_packet, Host, ?MODULE, s2s_send_packet, 20), + ejabberd_hooks:add(s2s_receive_packet, Host, ?MODULE, s2s_receive_packet, 20), + ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 20), + ejabberd_hooks:add(register_user, Host, ?MODULE, register_user, 20). stop(Host) -> - [ejabberd_hooks:delete(Hook, Host, ?MODULE, Hook, 20) - || Hook <- ?HOOKS]. + ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, offline_message_hook, 20), + ejabberd_hooks:delete(sm_register_connection_hook, Host, ?MODULE, sm_register_connection_hook, 20), + ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, sm_remove_connection_hook, 20), + ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, user_send_packet, 20), + ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, user_receive_packet, 20), + ejabberd_hooks:delete(s2s_send_packet, Host, ?MODULE, s2s_send_packet, 20), + ejabberd_hooks:delete(s2s_receive_packet, Host, ?MODULE, s2s_receive_packet, 20), + ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 20), + ejabberd_hooks:delete(register_user, Host, ?MODULE, register_user, 20). depends(_Host, _Opts) -> []. @@ -66,29 +74,41 @@ depends(_Host, _Opts) -> %%==================================================================== %% Hooks handlers %%==================================================================== - +-spec offline_message_hook(jid(), jid(), message()) -> any(). offline_message_hook(_From, #jid{lserver=LServer}, _Packet) -> push(LServer, offline_message). +-spec sm_register_connection_hook(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any(). sm_register_connection_hook(_SID, #jid{lserver=LServer}, _Info) -> push(LServer, sm_register_connection). + +-spec sm_remove_connection_hook(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any(). sm_remove_connection_hook(_SID, #jid{lserver=LServer}, _Info) -> push(LServer, sm_remove_connection). +-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza(). user_send_packet(Packet, _C2SState, #jid{lserver=LServer}, _To) -> push(LServer, user_send_packet), Packet. + +-spec user_receive_packet(stanza(), ejabberd_c2s:state(), jid(), jid(), jid()) -> stanza(). user_receive_packet(Packet, _C2SState, _JID, _From, #jid{lserver=LServer}) -> push(LServer, user_receive_packet), Packet. +-spec s2s_send_packet(jid(), jid(), stanza()) -> any(). s2s_send_packet(#jid{lserver=LServer}, _To, _Packet) -> push(LServer, s2s_send_packet). + +-spec s2s_receive_packet(jid(), jid(), stanza()) -> any(). s2s_receive_packet(_From, #jid{lserver=LServer}, _Packet) -> push(LServer, s2s_receive_packet). +-spec remove_user(binary(), binary()) -> any(). remove_user(_User, Server) -> push(jid:nameprep(Server), remove_user). + +-spec register_user(binary(), binary()) -> any(). register_user(_User, Server) -> push(jid:nameprep(Server), register_user). diff --git a/src/mod_mix.erl b/src/mod_mix.erl index a81efd5ce..f7bd0ec9a 100644 --- a/src/mod_mix.erl +++ b/src/mod_mix.erl @@ -12,7 +12,7 @@ -behaviour(gen_mod). %% API --export([start_link/2, start/2, stop/1, process_iq/3, +-export([start_link/2, start/2, stop/1, process_iq/1, disco_items/5, disco_identity/5, disco_info/5, disco_features/5, mod_opt_type/1, depends/2]). @@ -21,8 +21,7 @@ terminate/2, code_change/3]). -include("logger.hrl"). --include("jlib.hrl"). --include("pubsub.hrl"). +-include("xmpp.hrl"). -define(PROCNAME, ejabberd_mod_mix). -define(NODES, [?NS_MIX_NODES_MESSAGES, @@ -53,88 +52,67 @@ stop(Host) -> supervisor:delete_child(ejabberd_sup, Proc), ok. +-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty, + jid(), jid(), binary(), binary()) -> {result, [binary()]}. disco_features(_Acc, _From, _To, _Node, _Lang) -> {result, [?NS_MIX_0]}. disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> -> - To_s = jid:to_string(jid:remove_resource(To)), - {result, [#xmlel{name = <<"item">>, - attrs = [{<<"jid">>, To_s}, - {<<"node">>, Node}]} || Node <- ?NODES]}; + BareTo = jid:remove_resource(To), + {result, [#disco_item{jid = BareTo, node = Node} || Node <- ?NODES]}; disco_items(_Acc, _From, _To, _Node, _Lang) -> {result, []}. disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> -> - Acc ++ [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"name">>, <<"MIX service">>}, - {<<"type">>, <<"text">>}]}]; + Acc ++ [#identity{category = <<"conference">>, + name = <<"MIX service">>, + type = <<"text">>}]; disco_identity(Acc, _From, _To, _Node, _Lang) -> - Acc ++ [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"mix">>}]}]. + Acc ++ [#identity{category = <<"conference">>, + type = <<"mix">>}]. +-spec disco_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()]; + ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()]. disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) -> - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"result">>}], - children = [#xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = [#xmlel{name = <<"value">>, - children = [{xmlcdata, - ?NS_MIX_SERVICEINFO_0}]}]}]}]; + [#xdata{type = result, + fields = [#xdata_field{var = <<"FORM_TYPE">>, + type = hidden, + values = [?NS_MIX_SERVICEINFO_0]}]}]; disco_info(Acc, _From, _To, _Node, _Lang) -> Acc. -process_iq(From, To, - #iq{type = set, sub_el = #xmlel{name = <<"join">>} = SubEl} = IQ) -> - Nodes = lists:flatmap( - fun(#xmlel{name = <<"subscribe">>, attrs = Attrs}) -> - Node = fxml:get_attr_s(<<"node">>, Attrs), - case lists:member(Node, ?NODES) of - true -> [Node]; - false -> [] - end; - (_) -> - [] - end, SubEl#xmlel.children), +process_iq(#iq{type = set, from = From, to = To, + sub_els = [#mix_join{subscribe = SubNodes}]} = IQ) -> + Nodes = [Node || Node <- SubNodes, lists:member(Node, ?NODES)], case subscribe_nodes(From, To, Nodes) of {result, _} -> case publish_participant(From, To) of {result, _} -> - LFrom_s = jid:to_string(jid:tolower(jid:remove_resource(From))), - Subscribe = [#xmlel{name = <<"subscribe">>, - attrs = [{<<"node">>, Node}]} || Node <- Nodes], - IQ#iq{type = result, - sub_el = [#xmlel{name = <<"join">>, - attrs = [{<<"jid">>, LFrom_s}, - {<<"xmlns">>, ?NS_MIX_0}], - children = Subscribe}]}; + BareFrom = jid:remove_resource(From), + xmpp:make_iq_result( + IQ, #mix_join{jid = BareFrom, subscribe = Nodes}); {error, Err} -> - IQ#iq{type = error, sub_el = [SubEl, Err]} + xmpp:make_error(IQ, Err) end; {error, Err} -> - IQ#iq{type = error, sub_el = [SubEl, Err]} + xmpp:make_error(IQ, Err) end; -process_iq(From, To, - #iq{type = set, sub_el = #xmlel{name = <<"leave">>} = SubEl} = IQ) -> +process_iq(#iq{type = set, from = From, to = To, + sub_els = [#mix_leave{}]} = IQ) -> case delete_participant(From, To) of {result, _} -> case unsubscribe_nodes(From, To, ?NODES) of {result, _} -> - IQ#iq{type = result, sub_el = []}; + xmpp:make_iq_result(IQ); {error, Err} -> - IQ#iq{type = error, sub_el = [SubEl, Err]} + xmpp:make_error(IQ, Err) end; {error, Err} -> - IQ#iq{type = error, sub_el = [SubEl, Err]} + xmpp:make_error(IQ, Err) end; -process_iq(_From, _To, #iq{sub_el = SubEl, lang = Lang} = IQ) -> +process_iq(#iq{lang = Lang} = IQ) -> Txt = <<"Unsupported MIX query">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}. + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)). %%%=================================================================== %%% gen_server callbacks @@ -185,8 +163,8 @@ handle_info({route, From, To, Packet}, State) -> try ?ERROR_MSG("failed to route packet ~p from '~s' to '~s': ~p", [Packet, jid:to_string(From), jid:to_string(To), Err]), - ErrPkt = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR), - ejabberd_router:route_error(To, From, ErrPkt, Packet) + Error = xmpp:err_internal_server_error(), + ejabberd_router:route_error(To, From, Packet, Error) catch _:_ -> ok end; @@ -220,39 +198,29 @@ code_change(_OldVsn, State, _Extra) -> %%%=================================================================== %%% Internal functions %%%=================================================================== -do_route(_State, From, To, #xmlel{name = <<"iq">>} = Packet) -> - if To#jid.luser == <<"">> -> - ejabberd_local:process_iq(From, To, Packet); - true -> - ejabberd_sm:process_iq(From, To, Packet) - end; -do_route(_State, From, To, #xmlel{name = <<"presence">>} = Packet) +do_route(_State, From, To, #iq{} = Packet) -> + ejabberd_router:process_iq(From, To, Packet); +do_route(_State, From, To, #presence{type = unavailable}) when To#jid.luser /= <<"">> -> - case fxml:get_tag_attr_s(<<"type">>, Packet) of - <<"unavailable">> -> - delete_presence(From, To); - _ -> - ok - end; + delete_presence(From, To); do_route(_State, _From, _To, _Packet) -> ok. subscribe_nodes(From, To, Nodes) -> LTo = jid:tolower(jid:remove_resource(To)), LFrom = jid:tolower(jid:remove_resource(From)), - From_s = jid:to_string(LFrom), lists:foldl( fun(_Node, {error, _} = Err) -> Err; (Node, {result, _}) -> - case mod_pubsub:subscribe_node(LTo, Node, From, From_s, []) of + case mod_pubsub:subscribe_node(LTo, Node, From, From, []) of {error, _} = Err -> case is_item_not_found(Err) of true -> case mod_pubsub:create_node( LTo, To#jid.lserver, Node, LFrom, <<"mix">>) of {result, _} -> - mod_pubsub:subscribe_node(LTo, Node, From, From_s, []); + mod_pubsub:subscribe_node(LTo, Node, From, From, []); Error -> Error end; @@ -266,13 +234,12 @@ subscribe_nodes(From, To, Nodes) -> unsubscribe_nodes(From, To, Nodes) -> LTo = jid:tolower(jid:remove_resource(To)), - LFrom = jid:tolower(jid:remove_resource(From)), - From_s = jid:to_string(LFrom), + BareFrom = jid:remove_resource(From), lists:foldl( fun(_Node, {error, _} = Err) -> Err; (Node, {result, _} = Result) -> - case mod_pubsub:unsubscribe_node(LTo, Node, From, From_s, <<"">>) of + case mod_pubsub:unsubscribe_node(LTo, Node, From, BareFrom, <<"">>) of {error, _} = Err -> case is_not_subscribed(Err) of true -> Result; @@ -284,15 +251,14 @@ unsubscribe_nodes(From, To, Nodes) -> end, {result, []}, Nodes). publish_participant(From, To) -> - LFrom = jid:tolower(jid:remove_resource(From)), + BareFrom = jid:remove_resource(From), + LFrom = jid:tolower(BareFrom), LTo = jid:tolower(jid:remove_resource(To)), - Participant = #xmlel{name = <<"participant">>, - attrs = [{<<"xmlns">>, ?NS_MIX_0}, - {<<"jid">>, jid:to_string(LFrom)}]}, + Participant = #mix_participant{jid = BareFrom}, ItemID = p1_sha:sha(jid:to_string(LFrom)), mod_pubsub:publish_item( LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS, - From, ItemID, [Participant]). + From, ItemID, [xmpp:encode(Participant)]). delete_presence(From, To) -> LFrom = jid:tolower(From), @@ -300,8 +266,8 @@ delete_presence(From, To) -> case mod_pubsub:get_items(LTo, ?NS_MIX_NODES_PRESENCE) of Items when is_list(Items) -> lists:foreach( - fun(#pubsub_item{modification = {_, LJID}, - itemid = {ItemID, _}}) when LJID == LFrom -> + fun({pubsub_item, {ItemID, _}, _, {_, LJID}, _}) + when LJID == LFrom -> delete_item(From, To, ?NS_MIX_NODES_PRESENCE, ItemID); (_) -> ok @@ -329,19 +295,16 @@ delete_item(From, To, Node, ItemID) -> end end. -is_item_not_found({error, ErrEl}) -> - case fxml:get_subtag_with_xmlns( - ErrEl, <<"item-not-found">>, ?NS_STANZAS) of - #xmlel{} -> true; - _ -> false - end. +-spec is_item_not_found({error, stanza_error()}) -> boolean(). +is_item_not_found({error, #stanza_error{reason = 'item-not-found'}}) -> true; +is_item_not_found({error, _}) -> false. -is_not_subscribed({error, ErrEl}) -> - case fxml:get_subtag_with_xmlns( - ErrEl, <<"not-subscribed">>, ?NS_PUBSUB_ERRORS) of - #xmlel{} -> true; - _ -> false - end. +-spec is_not_subscribed({error, stanza_error()}) -> boolean(). +is_not_subscribed({error, #stanza_error{sub_els = Els}}) -> + %% TODO: make xmpp:get_els function working for any XMPP element + %% with sub_els field + xmpp:has_subtag(#message{sub_els = Els}, + #ps_error{type = 'not-subscribed'}). depends(_Host, _Opts) -> [{mod_pubsub, hard}]. diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 6b878b05b..298749329 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -43,11 +43,17 @@ forget_room/3, create_room/5, shutdown_rooms/1, - process_iq_disco_items/5, + process_disco_info/1, + process_disco_items/1, + process_vcard/1, + process_register/1, + process_muc_unique/1, + process_mucsub/1, broadcast_service_message/2, export/1, - import/1, - import/3, + import_info/0, + import/5, + import_start/2, opts_to_binary/1, can_use_nick/4]). @@ -57,8 +63,8 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). +-include("xmpp.hrl"). -include("mod_muc.hrl"). -record(state, @@ -74,7 +80,7 @@ -type muc_room_opts() :: [{atom(), any()}]. -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #muc_room{} | #muc_registered{}) -> ok | pass. +-callback import(binary(), binary(), [binary()]) -> ok. -callback store_room(binary(), binary(), binary(), list()) -> {atomic, any()}. -callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error. -callback forget_room(binary(), binary(), binary()) -> {atomic, any()}. @@ -153,17 +159,6 @@ forget_room(ServerHost, Host, Name) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:forget_room(LServer, Host, Name). -process_iq_disco_items(Host, From, To, MaxRoomsDiscoItems, - #iq{lang = Lang} = IQ) -> - Rsm = jlib:rsm_decode(IQ), - DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el), - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}], - children = iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, DiscoNode, Rsm)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)). - can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false; can_use_nick(ServerHost, Host, JID, Nick) -> LServer = jid:nameprep(ServerHost), @@ -175,12 +170,16 @@ can_use_nick(ServerHost, Host, JID, Nick) -> %%==================================================================== init([Host, Opts]) -> + IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, + one_queue), MyHost = gen_mod:get_opt_host(Host, Opts, <<"conference.@HOST@">>), Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod:init(Host, [{host, MyHost}|Opts]), - mnesia:create_table(muc_online_room, + update_tables(), + ejabberd_mnesia:create(?MODULE, muc_online_room, [{ram_copies, [node()]}, + {type, ordered_set}, {attributes, record_info(fields, muc_online_room)}]), mnesia:add_table_copy(muc_online_room, node(), ram_copies), catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]), @@ -258,6 +257,18 @@ init([Host, Opts]) -> RoomShaper = gen_mod:get_opt(room_shaper, Opts, fun(A) when is_atom(A) -> A end, none), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER, + ?MODULE, process_register, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD, + ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB, + ?MODULE, process_mucsub, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE, + ?MODULE, process_muc_unique, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, + ?MODULE, process_disco_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items, IQDisc), ejabberd_router:register_route(MyHost, Host), load_permanent_rooms(MyHost, Host, {Access, AccessCreate, AccessAdmin, AccessPersistent}, @@ -319,8 +330,14 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) -> {noreply, State}; handle_info(_Info, State) -> {noreply, State}. -terminate(_Reason, State) -> - ejabberd_router:unregister_route(State#state.host), +terminate(_Reason, #state{host = MyHost}) -> + ejabberd_router:unregister_route(MyHost), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -330,215 +347,196 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- do_route(Host, ServerHost, Access, HistorySize, RoomShaper, - From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems) -> + From, To, Packet, DefRoomOpts, _MaxRoomsDiscoItems) -> {AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent} = Access, case acl:match_rule(ServerHost, AccessRoute, From) of allow -> do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, - From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems); - _ -> - #xmlel{attrs = Attrs} = Packet, - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), + From, To, Packet, DefRoomOpts); + deny -> + Lang = xmpp:get_lang(Packet), ErrText = <<"Access denied by service policy">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route_error(To, From, Err, Packet) + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error(To, From, Packet, Err) end. - -do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, - From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems) -> - {_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access, - {Room, _, Nick} = jid:tolower(To), - #xmlel{name = Name, attrs = Attrs} = Packet, - case Room of - <<"">> -> - case Nick of - <<"">> -> - case Name of - <<"iq">> -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS, - sub_el = _SubEl, lang = Lang} = - IQ -> - Info = ejabberd_hooks:run_fold(disco_info, - ServerHost, [], - [ServerHost, ?MODULE, - <<"">>, <<"">>]), - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - iq_disco_info( - ServerHost, Lang) ++ - Info}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ -> - spawn(?MODULE, process_iq_disco_items, - [Host, From, To, MaxRoomsDiscoItems, IQ]); - #iq{type = get, xmlns = (?NS_REGISTER) = XMLNS, - lang = Lang, sub_el = _SubEl} = - IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - iq_get_register_info(ServerHost, - Host, - From, - Lang)}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - #iq{type = set, xmlns = (?NS_REGISTER) = XMLNS, - lang = Lang, sub_el = SubEl} = - IQ -> - case process_iq_register_set(ServerHost, Host, From, - SubEl, Lang) - of - {result, IQRes} -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = IQRes}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - {error, Error} -> - Err = jlib:make_error_reply(Packet, Error), - ejabberd_router:route(To, From, Err) - end; - #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, - lang = Lang, sub_el = _SubEl} = - IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - #iq{type = get, xmlns = ?NS_MUCSUB, - sub_el = #xmlel{name = <<"subscriptions">>} = SubEl} = IQ -> - RoomJIDs = get_subscribed_rooms(ServerHost, Host, From), - Subs = lists:map( - fun(J) -> - #xmlel{name = <<"subscription">>, - attrs = [{<<"jid">>, - jid:to_string(J)}]} - end, RoomJIDs), - Res = IQ#iq{type = result, - sub_el = [SubEl#xmlel{children = Subs}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - #iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"unique">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_UNIQUE}], - children = - [iq_get_unique(From)]}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - #iq{} -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> ok - end; - <<"message">> -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - _ -> - case acl:match_rule(ServerHost, AccessAdmin, From) - of - allow -> - Msg = fxml:get_path_s(Packet, - [{elem, <<"body">>}, - cdata]), - broadcast_service_message(Host, Msg); - _ -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - ErrText = - <<"Only service administrators are allowed " - "to send service messages">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, - ErrText)), - ejabberd_router:route(To, From, Err) - end - end; - <<"presence">> -> ok - end; - _ -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - <<"result">> -> ok; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_ITEM_NOT_FOUND), - ejabberd_router:route(To, From, Err) - end - end; - _ -> - case mnesia:dirty_read(muc_online_room, {Room, Host}) of - [] -> - case is_create_request(Packet) of - true -> - case check_user_can_create_room(ServerHost, - AccessCreate, From, Room) and - check_create_roomid(ServerHost, Room) of - true -> - {ok, Pid} = start_new_room(Host, ServerHost, Access, - Room, HistorySize, - RoomShaper, From, Nick, DefRoomOpts), - register_room(Host, Room, Pid), - mod_muc_room:route(Pid, From, Nick, Packet), - ok; - false -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - ErrText = <<"Room creation is denied by service policy">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(To, From, Err) - end; - false -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - ErrText = <<"Conference room does not exist">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), - ejabberd_router:route(To, From, Err) - end; - [R] -> - Pid = R#muc_online_room.pid, - ?DEBUG("MUC: send to process ~p~n", [Pid]), - mod_muc_room:route(Pid, From, Nick, Packet), - ok +do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper, + From, #jid{luser = <<"">>, lresource = <<"">>} = To, + #iq{} = IQ, _DefRoomOpts) -> + ejabberd_local:process_iq(From, To, IQ); +do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper, + From, #jid{luser = <<"">>, lresource = <<"">>} = To, + #message{lang = Lang, body = Body, type = Type} = Packet, _) -> + {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = Access, + if Type == error -> + ok; + true -> + case acl:match_rule(ServerHost, AccessAdmin, From) of + allow -> + Msg = xmpp:get_text(Body), + broadcast_service_message(Host, Msg); + deny -> + ErrText = <<"Only service administrators are allowed " + "to send service messages">>, + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error(To, From, Packet, Err) end + end; +do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper, + From, #jid{luser = <<"">>} = To, Packet, _DefRoomOpts) -> + Err = xmpp:err_service_unavailable(), + ejabberd_router:route_error(To, From, Packet, Err); +do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, + From, To, Packet, DefRoomOpts) -> + {_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access, + {Room, _, Nick} = jid:tolower(To), + case mnesia:dirty_read(muc_online_room, {Room, Host}) of + [] -> + case is_create_request(Packet) of + true -> + case check_user_can_create_room( + ServerHost, AccessCreate, From, Room) and + check_create_roomid(ServerHost, Room) of + true -> + {ok, Pid} = start_new_room( + Host, ServerHost, Access, + Room, HistorySize, + RoomShaper, From, Nick, DefRoomOpts), + register_room(Host, Room, Pid), + mod_muc_room:route(Pid, From, Nick, Packet), + ok; + false -> + Lang = xmpp:get_lang(Packet), + ErrText = <<"Room creation is denied by service policy">>, + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error(To, From, Packet, Err) + end; + false -> + Lang = xmpp:get_lang(Packet), + ErrText = <<"Conference room does not exist">>, + Err = xmpp:err_item_not_found(ErrText, Lang), + ejabberd_router:route_error(To, From, Packet, Err) + end; + [R] -> + Pid = R#muc_online_room.pid, + ?DEBUG("MUC: send to process ~p~n", [Pid]), + mod_muc_room:route(Pid, From, Nick, Packet), + ok end. --spec is_create_request(xmlel()) -> boolean(). -is_create_request(#xmlel{name = <<"presence">>} = Packet) -> - <<"">> == fxml:get_tag_attr_s(<<"type">>, Packet); -is_create_request(#xmlel{name = <<"iq">>} = Packet) -> - case jlib:iq_query_info(Packet) of - #iq{type = set, xmlns = ?NS_MUCSUB, - sub_el = #xmlel{name = <<"subscribe">>}} -> - true; - #iq{type = get, xmlns = ?NS_MUC_OWNER, sub_el = SubEl} -> - [] == fxml:remove_cdata(SubEl#xmlel.children); - _ -> - false +-spec process_vcard(iq()) -> iq(). +process_vcard(#iq{type = get, lang = Lang, sub_els = [#vcard_temp{}]} = IQ) -> + Desc = translate:translate(Lang, <<"ejabberd MUC module">>), + xmpp:make_iq_result( + IQ, #vcard_temp{fn = <<"ejabberd/mod_muc">>, + url = ?EJABBERD_URI, + desc = <>}); +process_vcard(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_vcard(#iq{lang = Lang} = IQ) -> + Txt = <<"No module is handling this query">>, + xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). + +-spec process_register(iq()) -> iq(). +process_register(#iq{type = get, from = From, to = To, lang = Lang, + sub_els = [#register{}]} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + xmpp:make_iq_result(IQ, iq_get_register_info(ServerHost, Host, From, Lang)); +process_register(#iq{type = set, from = From, to = To, + lang = Lang, sub_els = [El = #register{}]} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + case process_iq_register_set(ServerHost, Host, From, El, Lang) of + {result, Result} -> + xmpp:make_iq_result(IQ, Result); + {error, Err} -> + xmpp:make_error(IQ, Err) + end. + +-spec process_disco_info(iq()) -> iq(). +process_disco_info(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_info(#iq{type = get, to = To, lang = Lang, + sub_els = [#disco_info{node = <<"">>}]} = IQ) -> + ServerHost = ejabberd_router:host_of_route(To#jid.lserver), + X = ejabberd_hooks:run_fold(disco_info, ServerHost, [], + [ServerHost, ?MODULE, <<"">>, Lang]), + MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of + true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1]; + false -> [] + end, + Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, + ?NS_REGISTER, ?NS_MUC, ?NS_RSM, + ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | MAMFeatures], + Identity = #identity{category = <<"conference">>, + type = <<"text">>, + name = translate:translate(Lang, <<"Chatrooms">>)}, + xmpp:make_iq_result( + IQ, #disco_info{features = Features, + identities = [Identity], + xdata = X}); +process_disco_info(#iq{type = get, lang = Lang, + sub_els = [#disco_info{}]} = IQ) -> + xmpp:make_error(IQ, xmpp:err_item_not_found(<<"Node not found">>, Lang)); +process_disco_info(#iq{lang = Lang} = IQ) -> + Txt = <<"No module is handling this query">>, + xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). + +-spec process_disco_items(iq()) -> iq(). +process_disco_items(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_items(#iq{type = get, from = From, to = To, lang = Lang, + sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + MaxRoomsDiscoItems = gen_mod:get_module_opt( + ServerHost, ?MODULE, max_rooms_discoitems, + fun(I) when is_integer(I), I>=0 -> I end, + 100), + case iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM) of + {error, Err} -> + xmpp:make_error(IQ, Err); + {result, Result} -> + xmpp:make_iq_result(IQ, Result) end; +process_disco_items(#iq{lang = Lang} = IQ) -> + Txt = <<"No module is handling this query">>, + xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). + +-spec process_muc_unique(iq()) -> iq(). +process_muc_unique(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_muc_unique(#iq{from = From, type = get, + sub_els = [#muc_unique{}]} = IQ) -> + Name = p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(), + randoms:get_string()])), + xmpp:make_iq_result(IQ, #muc_unique{name = Name}). + +-spec process_mucsub(iq()) -> iq(). +process_mucsub(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_mucsub(#iq{type = get, from = From, to = To, + sub_els = [#muc_subscriptions{}]} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + RoomJIDs = get_subscribed_rooms(ServerHost, Host, From), + xmpp:make_iq_result(IQ, #muc_subscriptions{list = RoomJIDs}); +process_mucsub(#iq{lang = Lang} = IQ) -> + Txt = <<"No module is handling this query">>, + xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). + +-spec is_create_request(stanza()) -> boolean(). +is_create_request(#presence{type = available}) -> + true; +is_create_request(#iq{type = T} = IQ) when T == get; T == set -> + xmpp:has_subtag(IQ, #muc_subscribe{}) orelse + xmpp:has_subtag(IQ, #muc_owner{}); is_create_request(_) -> false. @@ -603,207 +601,151 @@ register_room(Host, Room, Pid) -> end, mnesia:transaction(F). - -iq_disco_info(ServerHost, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, - translate:translate(Lang, <<"Chatrooms">>)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_REGISTER}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_RSM}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUCSUB}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++ - case gen_mod:is_loaded(ServerHost, mod_mam) of - true -> - [#xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MAM_TMP}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MAM_0}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MAM_1}]}]; - false -> - [] - end. - -iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, <<>>, none) -> - Rooms = get_vh_rooms(Host), - case erlang:length(Rooms) < MaxRoomsDiscoItems of - true -> - iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}); - false -> - iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, <<"nonemptyrooms">>, none) - end; -iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, <<"nonemptyrooms">>, none) -> - XmlEmpty = #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, <<"conference.localhost">>}, - {<<"node">>, <<"emptyrooms">>}, - {<<"name">>, translate:translate(Lang, <<"Empty Rooms">>)}], - children = []}, - Query = {get_disco_item, only_non_empty, From, Lang}, - [XmlEmpty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)]; -iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, <<"emptyrooms">>, none) -> - iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang}); -iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, _DiscoNode, Rsm) -> - {Rooms, RsmO} = get_vh_rooms(Host, Rsm), - RsmOut = jlib:rsm_encode(RsmO), - iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut. - -iq_disco_items_list(Host, Rooms, Query) -> - lists:zf(fun (#muc_online_room{name_host = - {Name, _Host}, - pid = Pid}) -> - case catch gen_fsm:sync_send_all_state_event(Pid, - Query, - 100) - of - {item, Desc} -> - flush(), - {true, - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - jid:to_string({Name, Host, - <<"">>})}, - {<<"name">>, Desc}], - children = []}}; - _ -> false - end - end, Rooms). - -get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})-> - AllRooms = lists:sort(get_vh_rooms(Host)), - Count = erlang:length(AllRooms), - Guard = case Direction of - _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}]; - aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}]; - before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}]; - _ -> [{'==', {element, 2, '$1'}, Host}] +-spec iq_disco_items(binary(), jid(), binary(), integer(), binary(), + rsm_set() | undefined) -> + {result, disco_items()} | {error, stanza_error()}. +iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM) + when Node == <<"">>; Node == <<"nonemptyrooms">>; Node == <<"emptyrooms">> -> + Count = get_vh_rooms_count(Host), + Query = if Node == <<"">>, RSM == undefined, Count > MaxRoomsDiscoItems -> + {get_disco_item, only_non_empty, From, Lang}; + Node == <<"nonemptyrooms">> -> + {get_disco_item, only_non_empty, From, Lang}; + Node == <<"emptyrooms">> -> + {get_disco_item, 0, From, Lang}; + true -> + {get_disco_item, all, From, Lang} end, - L = lists:sort( - mnesia:dirty_select(muc_online_room, - [{#muc_online_room{name_host = '$1', _ = '_'}, - Guard, - ['$_']}])), - L2 = if - Index == undefined andalso Direction == before -> - lists:reverse(lists:sublist(lists:reverse(L), 1, M)); - Index == undefined -> - lists:sublist(L, 1, M); - Index > Count orelse Index < 0 -> - []; - true -> - lists:sublist(L, Index+1, M) - end, - if L2 == [] -> {L2, #rsm_out{count = Count}}; - true -> - H = hd(L2), - NewIndex = get_room_pos(H, AllRooms), - T = lists:last(L2), - {F, _} = H#muc_online_room.name_host, - {Last, _} = T#muc_online_room.name_host, - {L2, - #rsm_out{first = F, last = Last, count = Count, - index = NewIndex}} + Items = get_vh_rooms(Host, Query, RSM), + ResRSM = case Items of + [_|_] when RSM /= undefined -> + #disco_item{jid = #jid{luser = First}} = hd(Items), + #disco_item{jid = #jid{luser = Last}} = lists:last(Items), + #rsm_set{first = #rsm_first{data = First}, + last = Last, + count = Count}; + [] when RSM /= undefined -> + #rsm_set{count = Count}; + _ -> + undefined + end, + {result, #disco_items{node = Node, items = Items, rsm = ResRSM}}; +iq_disco_items(_Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM) -> + {error, xmpp:err_item_not_found(<<"Node not found">>, Lang)}. + +-spec get_vh_rooms(binary, term(), rsm_set() | undefined) -> [disco_item()]. +get_vh_rooms(Host, Query, + #rsm_set{max = Max, 'after' = After, before = undefined}) + when is_binary(After), After /= <<"">> -> + lists:reverse(get_vh_rooms(next, {After, Host}, Host, Query, 0, Max, [])); +get_vh_rooms(Host, Query, + #rsm_set{max = Max, 'after' = undefined, before = Before}) + when is_binary(Before), Before /= <<"">> -> + get_vh_rooms(prev, {Before, Host}, Host, Query, 0, Max, []); +get_vh_rooms(Host, Query, + #rsm_set{max = Max, 'after' = undefined, before = <<"">>}) -> + get_vh_rooms(last, {<<"">>, Host}, Host, Query, 0, Max, []); +get_vh_rooms(Host, Query, #rsm_set{max = Max}) -> + lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Host, Query, 0, Max, [])); +get_vh_rooms(Host, Query, undefined) -> + lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Host, Query, 0, undefined, [])). + +-spec get_vh_rooms(prev | next | last | first, + {binary(), binary()}, binary(), term(), + non_neg_integer(), non_neg_integer() | undefined, + [disco_item()]) -> [disco_item()]. +get_vh_rooms(_Action, _Key, _Host, _Query, Count, Max, Items) when Count >= Max -> + Items; +get_vh_rooms(Action, Key, Host, Query, Count, Max, Items) -> + Call = fun() -> + case Action of + prev -> mnesia:dirty_prev(muc_online_room, Key); + next -> mnesia:dirty_next(muc_online_room, Key); + last -> mnesia:dirty_last(muc_online_room); + first -> mnesia:dirty_first(muc_online_room) + end + end, + NewAction = case Action of + last -> prev; + first -> next; + _ -> Action + end, + try Call() of + '$end_of_table' -> + Items; + {_, Host} = NewKey -> + case get_room_disco_item(NewKey, Query) of + {ok, Item} -> + get_vh_rooms(NewAction, NewKey, Host, Query, + Count + 1, Max, [Item|Items]); + {error, _} -> + get_vh_rooms(NewAction, NewKey, Host, Query, + Count, Max, Items) + end; + NewKey -> + get_vh_rooms(NewAction, NewKey, Host, Query, Count, Max, Items) + catch _:{aborted, {badarg, _}} -> + Items end. -get_subscribed_rooms(_ServerHost, Host1, From) -> - Rooms = get_vh_rooms(Host1), +-spec get_room_disco_item({binary(), binary()}, term()) -> {ok, disco_item()} | + {error, timeout | notfound}. +get_room_disco_item({Name, Host}, Query) -> + case mnesia:dirty_read(muc_online_room, {Name, Host}) of + [#muc_online_room{pid = Pid}|_] -> + RoomJID = jid:make(Name, Host), + try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of + {item, Desc} -> + {ok, #disco_item{jid = RoomJID, name = Desc}}; + false -> + {error, notfound} + catch _:{timeout, _} -> + {error, timeout}; + _:{noproc, _} -> + {error, notfound} + end; + _ -> + {error, notfound} + end. + +get_subscribed_rooms(_ServerHost, Host, From) -> + Rooms = get_vh_rooms(Host), BareFrom = jid:remove_resource(From), lists:flatmap( - fun(#muc_online_room{name_host = {Name, Host}, pid = Pid}) -> + fun(#muc_online_room{name_host = {Name, _}, pid = Pid}) -> case gen_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of - true -> [jid:make(Name, Host, <<>>)]; + true -> [jid:make(Name, Host)]; false -> [] end; (_) -> [] end, Rooms). -%% @doc Return the position of desired room in the list of rooms. -%% The room must exist in the list. The count starts in 0. -%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer() -get_room_pos(Desired, Rooms) -> - get_room_pos(Desired, Rooms, 0). - -get_room_pos(Desired, [HeadRoom | _], HeadPosition) - when Desired#muc_online_room.name_host == - HeadRoom#muc_online_room.name_host -> - HeadPosition; -get_room_pos(Desired, [_ | Rooms], HeadPosition) -> - get_room_pos(Desired, Rooms, HeadPosition + 1). - -flush() -> receive _ -> flush() after 0 -> ok end. - --define(XFIELD(Type, Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - -iq_get_unique(From) -> - {xmlcdata, - p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(), - randoms:get_string()]))}. - get_nick(ServerHost, Host, From) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_nick(LServer, Host, From). iq_get_register_info(ServerHost, Host, From, Lang) -> - {Nick, Registered} = case get_nick(ServerHost, Host, - From) - of - error -> {<<"">>, []}; - N -> - {N, - [#xmlel{name = <<"registered">>, attrs = [], - children = []}]} + {Nick, Registered} = case get_nick(ServerHost, Host, From) of + error -> {<<"">>, false}; + N -> {N, true} end, - Registered ++ - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need a client that supports x:data " - "to register the nickname">>)}]}, - #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Nickname Registration at ">>))/binary, - Host/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Enter nickname you want to register">>)}]}, - ?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>, - Nick)]}]. + Title = <<(translate:translate( + Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>, + Inst = translate:translate(Lang, <<"Enter nickname you want to register">>), + Fields = muc_register:encode( + [{roomnick, Nick}], + fun(T) -> translate:translate(Lang, T) end), + X = #xdata{type = form, title = Title, + instructions = [Inst], fields = Fields}, + #register{nick = Nick, + registered = Registered, + instructions = + translate:translate( + Lang, <<"You need a client that supports x:data " + "to register the nickname">>), + xdata = X}. set_nick(ServerHost, Host, From, Nick) -> LServer = jid:nameprep(ServerHost), @@ -813,66 +755,44 @@ set_nick(ServerHost, Host, From, Nick) -> iq_set_register_info(ServerHost, Host, From, Nick, Lang) -> case set_nick(ServerHost, Host, From, Nick) of - {atomic, ok} -> {result, []}; + {atomic, ok} -> {result, undefined}; {atomic, false} -> ErrText = <<"That nickname is registered by another " "person">>, - {error, ?ERRT_CONFLICT(Lang, ErrText)}; + {error, xmpp:err_conflict(ErrText, Lang)}; _ -> Txt = <<"Database failure">>, - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)} + {error, xmpp:err_internal_server_error(Txt, Lang)} end. -process_iq_register_set(ServerHost, Host, From, SubEl, - Lang) -> - #xmlel{children = Els} = SubEl, - case fxml:get_subtag(SubEl, <<"remove">>) of - false -> - case fxml:remove_cdata(Els) of - [#xmlel{name = <<"x">>} = XEl] -> - case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), - fxml:get_tag_attr_s(<<"type">>, XEl)} - of - {?NS_XDATA, <<"cancel">>} -> {result, []}; - {?NS_XDATA, <<"submit">>} -> - XData = jlib:parse_xdata_submit(XEl), - case XData of - invalid -> - Txt = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - _ -> - case lists:keysearch(<<"nick">>, 1, XData) of - {value, {_, [Nick]}} when Nick /= <<"">> -> - iq_set_register_info(ServerHost, Host, From, - Nick, Lang); - _ -> - ErrText = - <<"You must fill in field \"Nickname\" " - "in the form">>, - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)} - end - end; - _ -> {error, ?ERR_BAD_REQUEST} - end; - _ -> {error, ?ERR_BAD_REQUEST} - end; - _ -> - iq_set_register_info(ServerHost, Host, From, <<"">>, - Lang) +process_iq_register_set(ServerHost, Host, From, + #register{remove = true}, Lang) -> + iq_set_register_info(ServerHost, Host, From, <<"">>, Lang); +process_iq_register_set(_ServerHost, _Host, _From, + #register{xdata = #xdata{type = cancel}}, _Lang) -> + {result, undefined}; +process_iq_register_set(ServerHost, Host, From, + #register{nick = Nick, xdata = XData}, Lang) -> + case XData of + #xdata{type = submit, fields = Fs} -> + try + Options = muc_register:decode(Fs), + N = proplists:get_value(roomnick, Options), + iq_set_register_info(ServerHost, Host, From, N, Lang) + catch _:{muc_register, Why} -> + ErrText = muc_register:format_error(Why), + {error, xmpp:err_bad_request(ErrText, Lang)} + end; + #xdata{} -> + Txt = <<"Incorrect data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + _ when is_binary(Nick), Nick /= <<"">> -> + iq_set_register_info(ServerHost, Host, From, Nick, Lang); + _ -> + ErrText = <<"You must fill in field \"Nickname\" in the form">>, + {error, xmpp:err_not_acceptable(ErrText, Lang)} end. -iq_get_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_muc">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd MUC module">>))/binary, - "\nCopyright (c) 2003-2016 ProcessOne">>}]}]. - broadcast_service_message(Host, Msg) -> lists:foreach( fun(#muc_online_room{pid = Pid}) -> @@ -887,6 +807,13 @@ get_vh_rooms(Host) -> [{'==', {element, 2, '$1'}, Host}], ['$_']}]). +-spec get_vh_rooms_count(binary()) -> non_neg_integer(). +get_vh_rooms_count(Host) -> + ets:select_count(muc_online_room, + ets:fun2ms( + fun(#muc_online_room{name_host = {_, H}}) -> + H == Host + end)). clean_table_from_bad_node(Node) -> F = fun() -> @@ -916,6 +843,23 @@ clean_table_from_bad_node(Node, Host) -> end, mnesia:async_dirty(F). +update_tables() -> + try + case mnesia:table_info(muc_online_room, type) of + ordered_set -> ok; + _ -> + case mnesia:delete_table(muc_online_room) of + {atomic, ok} -> ok; + Err -> erlang:error(Err) + end + end + catch _:{aborted, {no_exists, muc_online_room}} -> ok; + _:{aborted, {no_exists, muc_online_room, type}} -> ok; + E:R -> + ?ERROR_MSG("failed to update mnesia table '~s': ~p", + [muc_online_room, {E, R}]) + end. + opts_to_binary(Opts) -> lists:map( fun({title, Title}) -> @@ -958,13 +902,16 @@ export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). +import_info() -> + [{<<"muc_room">>, 4}, {<<"muc_registered">>, 4}]. -import(LServer, DBType, Data) -> +import_start(LServer, DBType) -> Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, Data). + Mod:init(LServer, []). + +import(LServer, {sql, _}, DBType, Tab, L) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Tab, L). mod_opt_type(access) -> fun acl:access_rules_validator/1; diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index bd1c55f66..91ccce559 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -26,7 +26,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_muc_room.hrl"). -include("mod_muc.hrl"). -include("ejabberd_http.hrl"). @@ -270,7 +270,7 @@ web_menu_host(Acc, _Host, Lang) -> -define(TDTD(L, N), ?XE(<<"tr">>, [?XCT(<<"td">>, L), - ?XC(<<"td">>, jlib:integer_to_binary(N)) + ?XC(<<"td">>, integer_to_binary(N)) ])). web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) -> @@ -312,7 +312,7 @@ get_sort_query(Q) -> get_sort_query2(Q) -> {value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q), - Integer = jlib:binary_to_integer(String), + Integer = binary_to_integer(String), case Integer >= 0 of true -> {ok, {normal, Integer}}; false -> {ok, {reverse, abs(Integer)}} @@ -338,7 +338,7 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) -> {Titles_TR, _} = lists:mapfoldl( fun(Title, Num_column) -> - NCS = jlib:integer_to_binary(Num_column), + NCS = integer_to_binary(Num_column), TD = ?XE(<<"td">>, [?CT(Title), ?C(<<" ">>), ?AC(<<"?sort=", NCS/binary>>, <<"<">>), @@ -388,7 +388,7 @@ build_info_room({Name, Host, Pid}) -> false -> Last_message1 = queue:last(History), {_, _, _, Ts_last, _} = Last_message1, - jlib:timestamp_to_legacy(Ts_last) + xmpp_util:encode_timestamp(Ts_last) end, {<>, @@ -412,7 +412,7 @@ prepare_room_info(Room_info) -> Just_created, Title} = Room_info, [NameHost, - jlib:integer_to_binary(Num_participants), + integer_to_binary(Num_participants), Ts_last_message, jlib:atom_to_binary(Public), jlib:atom_to_binary(Persistent), @@ -806,7 +806,7 @@ format_room_option(OptionString, ValueString) -> password -> ValueString; subject ->ValueString; subject_author ->ValueString; - max_users -> jlib:binary_to_integer(ValueString); + max_users -> binary_to_integer(ValueString); _ -> jlib:binary_to_atom(ValueString) end, {Option, Value}. @@ -871,7 +871,7 @@ get_options(Config) -> Fields = [jlib:atom_to_binary(Field) || Field <- record_info(fields, config)], [config | ValuesRaw] = tuple_to_list(Config), Values = lists:map(fun(V) when is_atom(V) -> jlib:atom_to_binary(V); - (V) when is_integer(V) -> jlib:integer_to_binary(V); + (V) when is_integer(V) -> integer_to_binary(V); (V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V]))); (V) -> V end, ValuesRaw), lists:zip(Fields, Values). diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl index d5ced9116..2675db9b5 100644 --- a/src/mod_muc_log.erl +++ b/src/mod_muc_log.erl @@ -46,7 +46,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_muc.hrl"). -include("mod_muc_room.hrl"). @@ -196,15 +196,13 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. add_to_log2(text, {Nick, Packet}, Room, Opts, State) -> case has_no_permanent_store_hint(Packet) of false -> - case {fxml:get_subtag(Packet, <<"subject">>), - fxml:get_subtag(Packet, <<"body">>)} - of - {false, false} -> ok; - {false, SubEl} -> - Message = {body, fxml:get_tag_cdata(SubEl)}, + case {Packet#message.subject, Packet#message.body} of + {[], []} -> ok; + {[], Body} -> + Message = {body, xmpp:get_text(Body)}, add_message_to_log(Nick, Message, Room, Opts, State); - {SubEl, _} -> - Message = {subject, fxml:get_tag_cdata(SubEl)}, + {Subj, _} -> + Message = {subject, xmpp:get_text(Subj)}, add_message_to_log(Nick, Message, Room, Opts, State) end; true -> ok @@ -249,18 +247,18 @@ build_filename_string(TimeStamp, OutDir, RoomJID, {Dir, Filename, Rel} = case DirType of subdirs -> SYear = - iolist_to_binary(io_lib:format("~4..0w", + (str:format("~4..0w", [Year])), SMonth = - iolist_to_binary(io_lib:format("~2..0w", + (str:format("~2..0w", [Month])), - SDay = iolist_to_binary(io_lib:format("~2..0w", + SDay = (str:format("~2..0w", [Day])), {fjoin([SYear, SMonth]), SDay, <<"../..">>}; plain -> Date = - iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w", + (str:format("~4..0w-~2..0w-~2..0w", [Year, Month, Day])), @@ -729,7 +727,7 @@ fw(F, S, FileFormat) when is_atom(FileFormat) -> fw(F, S, [], FileFormat). fw(F, S, O, FileFormat) -> - S1 = list_to_binary(io_lib:format(binary_to_list(S) ++ "~n", O)), + S1 = (str:format(binary_to_list(S) ++ "~n", O)), S2 = case FileFormat of html -> S1; @@ -1035,7 +1033,7 @@ roomconfig_to_string(Options, Lang, FileFormat) -> max_users -> <<"
", OptText/binary, ": \"", - (htmlize(jlib:integer_to_binary(T), + (htmlize(integer_to_binary(T), FileFormat))/binary, "\"
">>; title -> @@ -1053,7 +1051,7 @@ roomconfig_to_string(Options, Lang, FileFormat) -> allow_private_messages_from_visitors -> <<"
", OptText/binary, ": \"", - (htmlize(?T((jlib:atom_to_binary(T))), + (htmlize(?T(jlib:atom_to_binary(T)), FileFormat))/binary, "\"
">>; _ -> <<"\"", T/binary, "\"">> @@ -1168,7 +1166,7 @@ get_room_occupants(RoomJIDString) -> [{U#user.jid, U#user.nick, U#user.role} || {_, U} <- (?DICT):to_list(StateData#state.users)]. --spec get_room_state(binary(), binary()) -> muc_room_state(). +-spec get_room_state(binary(), binary()) -> mod_muc_room:state(). get_room_state(RoomName, MucService) -> case mnesia:dirty_read(muc_online_room, @@ -1180,7 +1178,7 @@ get_room_state(RoomName, MucService) -> [] -> #state{} end. --spec get_room_state(pid()) -> muc_room_state(). +-spec get_room_state(pid()) -> mod_muc_room:state(). get_room_state(RoomPid) -> {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid, @@ -1204,14 +1202,10 @@ fjoin(FileList) -> list_to_binary(filename:join([binary_to_list(File) || File <- FileList])). has_no_permanent_store_hint(Packet) -> - fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS) - =/= false orelse - fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS) - =/= false orelse - fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS) - =/= false orelse - fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS) - =/= false. + xmpp:has_subtag(Packet, #hint{type = 'no-store'}) orelse + xmpp:has_subtag(Packet, #hint{type = 'no-storage'}) orelse + xmpp:has_subtag(Packet, #hint{type = 'no-permanent-store'}) orelse + xmpp:has_subtag(Packet, #hint{type = 'no-permanent-storage'}). mod_opt_type(access_log) -> fun (A) when is_atom(A) -> A end; diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl index e3ae36978..8f570746c 100644 --- a/src/mod_muc_mnesia.erl +++ b/src/mod_muc_mnesia.erl @@ -9,11 +9,15 @@ -module(mod_muc_mnesia). -behaviour(mod_muc). +-behaviour(mod_muc_room). %% API --export([init/2, import/2, store_room/4, restore_room/3, forget_room/3, +-export([init/2, import/3, store_room/4, restore_room/3, forget_room/3, can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]). +-export([set_affiliation/6, set_affiliations/4, get_affiliation/5, + get_affiliations/3, search_affiliation/4]). +-include("jlib.hrl"). -include("mod_muc.hrl"). -include("logger.hrl"). @@ -22,11 +26,11 @@ %%%=================================================================== init(_Host, Opts) -> MyHost = proplists:get_value(host, Opts), - mnesia:create_table(muc_room, + ejabberd_mnesia:create(?MODULE, muc_room, [{disc_copies, [node()]}, {attributes, record_info(fields, muc_room)}]), - mnesia:create_table(muc_registered, + ejabberd_mnesia:create(?MODULE, muc_registered, [{disc_copies, [node()]}, {attributes, record_info(fields, muc_registered)}]), @@ -113,10 +117,33 @@ set_nick(_LServer, Host, From, Nick) -> end, mnesia:transaction(F). -import(_LServer, #muc_room{} = R) -> - mnesia:dirty_write(R); -import(_LServer, #muc_registered{} = R) -> - mnesia:dirty_write(R). +set_affiliation(_ServerHost, _Room, _Host, _JID, _Affiliation, _Reason) -> + {error, not_implemented}. + +set_affiliations(_ServerHost, _Room, _Host, _Affiliations) -> + {error, not_implemented}. + +get_affiliation(_ServerHost, _Room, _Host, _LUser, _LServer) -> + {error, not_implemented}. + +get_affiliations(_ServerHost, _Room, _Host) -> + {error, not_implemented}. + +search_affiliation(_ServerHost, _Room, _Host, _Affiliation) -> + {error, not_implemented}. + +import(_LServer, <<"muc_room">>, + [Name, RoomHost, SOpts, _TimeStamp]) -> + Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)), + mnesia:dirty_write( + #muc_room{name_host = {Name, RoomHost}, + opts = Opts}); +import(_LServer, <<"muc_registered">>, + [J, RoomHost, Nick, _TimeStamp]) -> + #jid{user = U, server = S} = jid:from_string(J), + mnesia:dirty_write( + #muc_registered{us_host = {{U, S}, RoomHost}, + nick = Nick}). %%%=================================================================== %%% Internal functions diff --git a/src/mod_muc_riak.erl b/src/mod_muc_riak.erl index bc6e5959a..ada08ace6 100644 --- a/src/mod_muc_riak.erl +++ b/src/mod_muc_riak.erl @@ -9,11 +9,15 @@ -module(mod_muc_riak). -behaviour(mod_muc). +-behaviour(mod_muc_room). %% API --export([init/2, import/2, store_room/4, restore_room/3, forget_room/3, +-export([init/2, import/3, store_room/4, restore_room/3, forget_room/3, can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]). +-export([set_affiliation/6, set_affiliations/4, get_affiliation/5, + get_affiliations/3, search_affiliation/4]). +-include("jlib.hrl"). -include("mod_muc.hrl"). %%%=================================================================== @@ -101,11 +105,33 @@ set_nick(LServer, Host, From, Nick) -> end end}. -import(_LServer, #muc_room{} = R) -> - ejabberd_riak:put(R, muc_room_schema()); -import(_LServer, #muc_registered{us_host = {_, Host}, nick = Nick} = R) -> +set_affiliation(_ServerHost, _Room, _Host, _JID, _Affiliation, _Reason) -> + {error, not_implemented}. + +set_affiliations(_ServerHost, _Room, _Host, _Affiliations) -> + {error, not_implemented}. + +get_affiliation(_ServerHost, _Room, _Host, _LUser, _LServer) -> + {error, not_implemented}. + +get_affiliations(_ServerHost, _Room, _Host) -> + {error, not_implemented}. + +search_affiliation(_ServerHost, _Room, _Host, _Affiliation) -> + {error, not_implemented}. + +import(_LServer, <<"muc_room">>, + [Name, RoomHost, SOpts, _TimeStamp]) -> + Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)), + ejabberd_riak:put( + #muc_room{name_host = {Name, RoomHost}, opts = Opts}, + muc_room_schema()); +import(_LServer, <<"muc_registered">>, + [J, RoomHost, Nick, _TimeStamp]) -> + #jid{user = U, server = S} = jid:from_string(J), + R = #muc_registered{us_host = {{U, S}, RoomHost}, nick = Nick}, ejabberd_riak:put(R, muc_registered_schema(), - [{'2i', [{<<"nick_host">>, {Nick, Host}}]}]). + [{'2i', [{<<"nick_host">>, {Nick, RoomHost}}]}]). %%%=================================================================== %%% Internal functions diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 6010e0bbf..957220540 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -51,7 +51,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_muc_room.hrl"). @@ -72,6 +72,23 @@ -endif. +-type state() :: #state{}. +-type fsm_stop() :: {stop, normal, state()}. +-type fsm_next() :: {next_state, normal_state, state()}. +-type fsm_transition() :: fsm_stop() | fsm_next(). + +-export_type([state/0]). + +-callback set_affiliation(binary(), binary(), binary(), jid(), affiliation(), + binary()) -> ok | {error, any()}. +-callback set_affiliations(binary(), binary(), binary(), + ?TDICT) -> ok | {error, any()}. +-callback get_affiliation(binary(), binary(), binary(), + binary(), binary()) -> {ok, affiliation()} | {error, any()}. +-callback get_affiliations(binary(), binary(), binary()) -> {ok, ?TDICT} | {error, any()}. +-callback search_affiliation(binary(), binary(), binary(), affiliation()) -> + {ok, [{ljid(), {affiliation(), binary()}}]} | {error, any()}. + %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- @@ -133,343 +150,193 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) -> {ok, normal_state, State}. normal_state({route, From, <<"">>, - #xmlel{name = <<"message">>, attrs = Attrs, - children = Els} = - Packet}, - StateData) -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), + #message{type = Type, lang = Lang} = Packet}, StateData) -> case is_user_online(From, StateData) orelse is_subscriber(From, StateData) orelse - is_user_allowed_message_nonparticipant(From, StateData) - of - true -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"groupchat">> -> - Activity = get_user_activity(From, StateData), - Now = p1_time_compat:system_time(micro_seconds), - MinMessageInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_message_interval, fun(MMI) when is_number(MMI) -> MMI end, 0) - * 1000000), - Size = element_size(Packet), - {MessageShaper, MessageShaperInterval} = - shaper:update(Activity#activity.message_shaper, Size), - if Activity#activity.message /= undefined -> - ErrText = <<"Traffic rate limit is exceeded">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_RESOURCE_CONSTRAINT(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData}; - Now >= - Activity#activity.message_time + MinMessageInterval, - MessageShaperInterval == 0 -> - {RoomShaper, RoomShaperInterval} = - shaper:update(StateData#state.room_shaper, Size), - RoomQueueEmpty = - queue:is_empty(StateData#state.room_queue), - if RoomShaperInterval == 0, RoomQueueEmpty -> - NewActivity = Activity#activity{message_time = - Now, - message_shaper = - MessageShaper}, - StateData1 = store_user_activity(From, - NewActivity, - StateData), - StateData2 = StateData1#state{room_shaper = - RoomShaper}, - process_groupchat_message(From, Packet, - StateData2); - true -> - StateData1 = if RoomQueueEmpty -> - erlang:send_after(RoomShaperInterval, - self(), - process_room_queue), - StateData#state{room_shaper = - RoomShaper}; - true -> StateData - end, - NewActivity = Activity#activity{message_time = - Now, - message_shaper = - MessageShaper, - message = Packet}, - RoomQueue = queue:in({message, From}, - StateData#state.room_queue), - StateData2 = store_user_activity(From, - NewActivity, - StateData1), - StateData3 = StateData2#state{room_queue = - RoomQueue}, - {next_state, normal_state, StateData3} - end; - true -> - MessageInterval = (Activity#activity.message_time + - MinMessageInterval - - Now) - div 1000, - Interval = lists:max([MessageInterval, - MessageShaperInterval]), - erlang:send_after(Interval, self(), - {process_user_message, From}), - NewActivity = Activity#activity{message = Packet, - message_shaper = - MessageShaper}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - {next_state, normal_state, StateData1} - end; - <<"error">> -> - case is_user_online(From, StateData) of - true -> - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, - NewState = expulse_participant(Packet, From, StateData, - translate:translate(Lang, - ErrorText)), - close_room_if_temporary_and_empty(NewState); - _ -> {next_state, normal_state, StateData} - end; - <<"chat">> -> - ErrText = - <<"It is not allowed to send private messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData}; - Type when (Type == <<"">>) or (Type == <<"normal">>) -> - IsInvitation = is_invitation(Els), - IsVoiceRequest = is_voice_request(Els) and - is_visitor(From, StateData), - IsVoiceApprovement = is_voice_approvement(Els) and - not is_visitor(From, StateData), - if IsInvitation -> - case catch check_invitation(From, Packet, Lang, StateData) - of - {error, Error} -> - Err = jlib:make_error_reply(Packet, Error), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData}; - IJIDs -> - Config = StateData#state.config, - case Config#config.members_only of - true -> - NSD = process_invitees(IJIDs, StateData), - store_room(NSD), - {next_state, normal_state, NSD}; - false -> {next_state, normal_state, StateData} - end - end; - IsVoiceRequest -> - NewStateData = case - (StateData#state.config)#config.allow_voice_requests - of - true -> - MinInterval = - (StateData#state.config)#config.voice_request_min_interval, - BareFrom = - jid:remove_resource(jid:tolower(From)), - NowPriority = -p1_time_compat:system_time(micro_seconds), - CleanPriority = NowPriority + - MinInterval * - 1000000, - Times = - clean_treap(StateData#state.last_voice_request_time, - CleanPriority), - case treap:lookup(BareFrom, Times) - of - error -> - Times1 = - treap:insert(BareFrom, - NowPriority, - true, Times), - NSD = - StateData#state{last_voice_request_time - = - Times1}, - send_voice_request(From, NSD), - NSD; - {ok, _, _} -> - ErrText = - <<"Please, wait for a while before sending " - "new voice request">>, - Err = - jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData#state{last_voice_request_time - = Times} - end; - false -> - ErrText = - <<"Voice requests are disabled in this " - "conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData - end, - {next_state, normal_state, NewStateData}; - IsVoiceApprovement -> - NewStateData = case is_moderator(From, StateData) of - true -> - case - extract_jid_from_voice_approvement(Els) - of - error -> - ErrText = - <<"Failed to extract JID from your voice " - "request approval">>, - Err = - jlib:make_error_reply(Packet, - ?ERRT_BAD_REQUEST(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData; - {ok, TargetJid} -> - case is_visitor(TargetJid, - StateData) - of - true -> - Reason = <<>>, - NSD = - set_role(TargetJid, - participant, - StateData), - catch - send_new_presence(TargetJid, - Reason, - NSD, - StateData), - NSD; - _ -> StateData - end - end; - _ -> - ErrText = - <<"Only moderators can approve voice requests">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ALLOWED(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData - end, - {next_state, normal_state, NewStateData}; - true -> {next_state, normal_state, StateData} - end; - _ -> - ErrText = <<"Improper message type">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} - end; - _ -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - _ -> - handle_roommessage_from_nonparticipant(Packet, Lang, - StateData, From) - end, - {next_state, normal_state, StateData} + is_user_allowed_message_nonparticipant(From, StateData) of + true when Type == groupchat -> + Activity = get_user_activity(From, StateData), + Now = p1_time_compat:system_time(micro_seconds), + MinMessageInterval = trunc(gen_mod:get_module_opt( + StateData#state.server_host, + mod_muc, min_message_interval, + fun(MMI) when is_number(MMI) -> MMI end, 0) + * 1000000), + Size = element_size(Packet), + {MessageShaper, MessageShaperInterval} = + shaper:update(Activity#activity.message_shaper, Size), + if Activity#activity.message /= undefined -> + ErrText = <<"Traffic rate limit is exceeded">>, + Err = xmpp:err_resource_constraint(ErrText, Lang), + ejabberd_router:route_error(StateData#state.jid, From, Packet, Err), + {next_state, normal_state, StateData}; + Now >= Activity#activity.message_time + MinMessageInterval, + MessageShaperInterval == 0 -> + {RoomShaper, RoomShaperInterval} = + shaper:update(StateData#state.room_shaper, Size), + RoomQueueEmpty = queue:is_empty(StateData#state.room_queue), + if RoomShaperInterval == 0, RoomQueueEmpty -> + NewActivity = Activity#activity{ + message_time = Now, + message_shaper = MessageShaper}, + StateData1 = store_user_activity(From, + NewActivity, + StateData), + StateData2 = StateData1#state{room_shaper = + RoomShaper}, + process_groupchat_message(From, Packet, + StateData2); + true -> + StateData1 = if RoomQueueEmpty -> + erlang:send_after(RoomShaperInterval, + self(), + process_room_queue), + StateData#state{room_shaper = + RoomShaper}; + true -> StateData + end, + NewActivity = Activity#activity{ + message_time = Now, + message_shaper = MessageShaper, + message = Packet}, + RoomQueue = queue:in({message, From}, + StateData#state.room_queue), + StateData2 = store_user_activity(From, + NewActivity, + StateData1), + StateData3 = StateData2#state{room_queue = RoomQueue}, + {next_state, normal_state, StateData3} + end; + true -> + MessageInterval = (Activity#activity.message_time + + MinMessageInterval - Now) div 1000, + Interval = lists:max([MessageInterval, + MessageShaperInterval]), + erlang:send_after(Interval, self(), + {process_user_message, From}), + NewActivity = Activity#activity{ + message = Packet, + message_shaper = MessageShaper}, + StateData1 = store_user_activity(From, NewActivity, StateData), + {next_state, normal_state, StateData1} + end; + true when Type == error -> + case is_user_online(From, StateData) of + true -> + ErrorText = <<"It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room">>, + NewState = expulse_participant(Packet, From, StateData, + translate:translate(Lang, + ErrorText)), + close_room_if_temporary_and_empty(NewState); + _ -> + {next_state, normal_state, StateData} + end; + true when Type == chat -> + ErrText = <<"It is not allowed to send private messages " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error(StateData#state.jid, From, Packet, Err), + {next_state, normal_state, StateData}; + true when Type == normal -> + {next_state, normal_state, + try xmpp:decode_els(Packet) of + Pkt -> process_normal_message(From, Pkt, StateData) + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + Err = xmpp:err_bad_request(Txt, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Packet, Err), + StateData + end}; + true -> + ErrText = <<"Improper message type">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error(StateData#state.jid, From, Packet, Err), + {next_state, normal_state, StateData}; + false when Type /= error -> + handle_roommessage_from_nonparticipant(Packet, StateData, From), + {next_state, normal_state, StateData}; + false -> + {next_state, normal_state, StateData} end; normal_state({route, From, <<"">>, - #xmlel{name = <<"iq">>} = Packet}, - StateData) -> - case jlib:iq_query_info(Packet) of - reply -> - {next_state, normal_state, StateData}; - IQ0 -> - case ejabberd_hooks:run_fold( - muc_process_iq, - StateData#state.server_host, - IQ0, [StateData, From, StateData#state.jid]) of - ignore -> - {next_state, normal_state, StateData}; - #iq{type = T} = IQRes when T == error; T == result -> - ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)), - {next_state, normal_state, StateData}; - #iq{type = Type, xmlns = XMLNS, lang = Lang, - sub_el = #xmlel{name = SubElName, attrs = Attrs} = SubEl} = IQ - when (XMLNS == (?NS_MUC_ADMIN)) or - (XMLNS == (?NS_MUC_OWNER)) - or (XMLNS == (?NS_DISCO_INFO)) - or (XMLNS == (?NS_DISCO_ITEMS)) - or (XMLNS == (?NS_VCARD)) - or (XMLNS == (?NS_MUCSUB)) - or (XMLNS == (?NS_CAPTCHA)) -> - Res1 = case XMLNS of - ?NS_MUC_ADMIN -> - process_iq_admin(From, Type, Lang, SubEl, StateData); - ?NS_MUC_OWNER -> - process_iq_owner(From, Type, Lang, SubEl, StateData); - ?NS_DISCO_INFO -> - case fxml:get_attr(<<"node">>, Attrs) of - false -> process_iq_disco_info(From, Type, Lang, StateData); - {value, _} -> - Txt = <<"Disco info is not available for this node">>, - {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)} - end; - ?NS_DISCO_ITEMS -> - process_iq_disco_items(From, Type, Lang, StateData); - ?NS_VCARD -> - process_iq_vcard(From, Type, Lang, SubEl, StateData); - ?NS_MUCSUB -> - process_iq_mucsub(From, Packet, IQ, StateData); - ?NS_CAPTCHA -> - process_iq_captcha(From, Type, Lang, SubEl, StateData) - end, - {IQRes, NewStateData} = - case Res1 of - {result, Res, SD} -> - {IQ#iq{type = result, - sub_el = - [#xmlel{name = SubElName, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = Res}]}, - SD}; - {ignore, SD} -> {ignore, SD}; - {error, Error, ResStateData} -> - {IQ#iq{type = error, - sub_el = [SubEl, Error]}, - ResStateData}; - {error, Error} -> - {IQ#iq{type = error, - sub_el = [SubEl, Error]}, - StateData} - end, - if IQRes /= ignore -> - ejabberd_router:route( - StateData#state.jid, From, jlib:iq_to_xml(IQRes)); - true -> - ok + #iq{type = Type, lang = Lang, sub_els = [_]} = IQ0}, + StateData) when Type == get; Type == set -> + try + case ejabberd_hooks:run_fold( + muc_process_iq, + StateData#state.server_host, + xmpp:set_from_to(xmpp:decode_els(IQ0), + From, StateData#state.jid), + [StateData]) of + ignore -> + {next_state, normal_state, StateData}; + #iq{type = T} = IQRes when T == error; T == result -> + ejabberd_router:route(StateData#state.jid, From, IQRes), + {next_state, normal_state, StateData}; + #iq{sub_els = [SubEl]} = IQ -> + Res1 = case xmpp:get_ns(SubEl) of + ?NS_MUC_ADMIN -> + process_iq_admin(From, IQ, StateData); + ?NS_MUC_OWNER -> + process_iq_owner(From, IQ, StateData); + ?NS_DISCO_INFO when SubEl#disco_info.node == <<>> -> + process_iq_disco_info(From, IQ, StateData); + ?NS_DISCO_ITEMS -> + process_iq_disco_items(From, IQ, StateData); + ?NS_VCARD -> + process_iq_vcard(From, IQ, StateData); + ?NS_MUCSUB -> + process_iq_mucsub(From, IQ, StateData); + ?NS_CAPTCHA -> + process_iq_captcha(From, IQ, StateData); + _ -> + Txt = <<"The feature requested is not " + "supported by the conference">>, + {error, xmpp:err_service_unavailable(Txt, Lang)} + end, + {IQRes, NewStateData} = + case Res1 of + {result, Res, SD} -> + {xmpp:make_iq_result(IQ, Res), SD}; + {result, Res} -> + {xmpp:make_iq_result(IQ, Res), StateData}; + {ignore, SD} -> + {ignore, SD}; + {error, Error, ResStateData} -> + {xmpp:make_error(IQ0, Error), ResStateData}; + {error, Error} -> + {xmpp:make_error(IQ0, Error), StateData} end, - case NewStateData of - stop -> {stop, normal, StateData}; - _ -> {next_state, normal_state, NewStateData} - end; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} - end + if IQRes /= ignore -> + ejabberd_router:route(StateData#state.jid, From, IQRes); + true -> + ok + end, + case NewStateData of + stop -> + {stop, normal, StateData}; + _ when NewStateData#state.just_created -> + close_room_if_temporary_and_empty(NewStateData); + _ -> + {next_state, normal_state, NewStateData} + end + end + catch _:{xmpp_codec, Why} -> + ErrTxt = xmpp:format_error(Why), + Err = xmpp:err_bad_request(ErrTxt, Lang), + ejabberd_router:route_error(StateData#state.jid, From, IQ0, Err) end; -normal_state({route, From, Nick, - #xmlel{name = <<"presence">>} = Packet}, - StateData) -> +normal_state({route, From, <<"">>, #iq{} = IQ}, StateData) -> + Err = xmpp:err_bad_request(), + ejabberd_router:route_error(StateData#state.jid, From, IQ, Err), + case StateData#state.just_created of + true -> {stop, normal, StateData}; + false -> {next_state, normal_state, StateData} + end; +normal_state({route, From, Nick, #presence{} = Packet}, StateData) -> Activity = get_user_activity(From, StateData), Now = p1_time_compat:system_time(micro_seconds), MinPresenceInterval = @@ -479,184 +346,134 @@ normal_state({route, From, Nick, I end, 0) * 1000000), - if (Now >= - Activity#activity.presence_time + MinPresenceInterval) - and (Activity#activity.presence == undefined) -> - NewActivity = Activity#activity{presence_time = Now}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - process_presence(From, Nick, Packet, StateData1); + if (Now >= Activity#activity.presence_time + MinPresenceInterval) + and (Activity#activity.presence == undefined) -> + NewActivity = Activity#activity{presence_time = Now}, + StateData1 = store_user_activity(From, NewActivity, + StateData), + process_presence(From, Nick, Packet, StateData1); true -> - if Activity#activity.presence == undefined -> - Interval = (Activity#activity.presence_time + - MinPresenceInterval - - Now) - div 1000, - erlang:send_after(Interval, self(), - {process_user_presence, From}); - true -> ok - end, - NewActivity = Activity#activity{presence = - {Nick, Packet}}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - {next_state, normal_state, StateData1} + if Activity#activity.presence == undefined -> + Interval = (Activity#activity.presence_time + + MinPresenceInterval - Now) div 1000, + erlang:send_after(Interval, self(), + {process_user_presence, From}); + true -> ok + end, + NewActivity = Activity#activity{presence = {Nick, Packet}}, + StateData1 = store_user_activity(From, NewActivity, + StateData), + {next_state, normal_state, StateData1} end; normal_state({route, From, ToNick, - #xmlel{name = <<"message">>, attrs = Attrs} = Packet}, + #message{type = Type, lang = Lang} = Packet}, StateData) -> - Type = fxml:get_attr_s(<<"type">>, Attrs), - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - case decide_fate_message(Type, Packet, From, StateData) - of - {expulse_sender, Reason} -> - ?DEBUG(Reason, []), - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, - NewState = expulse_participant(Packet, From, StateData, - translate:translate(Lang, ErrorText)), - {next_state, normal_state, NewState}; - forget_message -> {next_state, normal_state, StateData}; - continue_delivery -> - case - {(StateData#state.config)#config.allow_private_messages, - is_user_online(From, StateData) orelse - is_subscriber(From, StateData)} - of - {true, true} -> - case Type of - <<"groupchat">> -> - ErrText = - <<"It is not allowed to send private messages " - "of type \"groupchat\"">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_BAD_REQUEST(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err); - _ -> - case find_jids_by_nick(ToNick, StateData) of - false -> - ErrText = - <<"Recipient is not in the conference room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_ITEM_NOT_FOUND(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err); + case decide_fate_message(Packet, From, StateData) of + {expulse_sender, Reason} -> + ?DEBUG(Reason, []), + ErrorText = <<"It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room">>, + NewState = expulse_participant(Packet, From, StateData, + translate:translate(Lang, ErrorText)), + {next_state, normal_state, NewState}; + forget_message -> + {next_state, normal_state, StateData}; + continue_delivery -> + case {(StateData#state.config)#config.allow_private_messages, + is_user_online(From, StateData) orelse + is_subscriber(From, StateData)} of + {true, true} when Type == groupchat -> + ErrText = <<"It is not allowed to send private messages " + "of type \"groupchat\"">>, + Err = xmpp:err_bad_request(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); + {true, true} -> + case find_jids_by_nick(ToNick, StateData) of + [] -> + ErrText = <<"Recipient is not in the conference room">>, + Err = xmpp:err_item_not_found(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); ToJIDs -> SrcIsVisitor = is_visitor(From, StateData), - DstIsModerator = is_moderator(hd(ToJIDs), - StateData), + DstIsModerator = is_moderator(hd(ToJIDs), StateData), PmFromVisitors = (StateData#state.config)#config.allow_private_messages_from_visitors, if SrcIsVisitor == false; PmFromVisitors == anyone; (PmFromVisitors == moderators) and - DstIsModerator -> + DstIsModerator -> {FromNick, _} = get_participant_data(From, StateData), - FromNickJID = - jid:replace_resource(StateData#state.jid, - FromNick), - X = #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}]}, - PrivMsg = fxml:append_subtags(Packet, [X]), - [ejabberd_router:route(FromNickJID, ToJID, PrivMsg) - || ToJID <- ToJIDs]; + FromNickJID = + jid:replace_resource(StateData#state.jid, + FromNick), + X = #muc_user{}, + PrivMsg = xmpp:set_subtag(Packet, X), + [ejabberd_router:route(FromNickJID, ToJID, PrivMsg) + || ToJID <- ToJIDs]; true -> - ErrText = - <<"It is not allowed to send private messages">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) + ErrText = <<"It is not allowed to send private messages">>, + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err) end - end - end; - {true, false} -> - ErrText = - <<"Only occupants are allowed to send messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err); - {false, _} -> - ErrText = - <<"It is not allowed to send private messages">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) - end, + end; + {true, false} -> + ErrText = <<"Only occupants are allowed to send messages " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); + {false, _} -> + ErrText = <<"It is not allowed to send private messages">>, + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err) + end, {next_state, normal_state, StateData} end; normal_state({route, From, ToNick, - #xmlel{name = <<"iq">>, attrs = Attrs} = Packet}, + #iq{id = StanzaId, lang = Lang} = Packet}, StateData) -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - StanzaId = fxml:get_attr_s(<<"id">>, Attrs), case {(StateData#state.config)#config.allow_query_users, - is_user_online_iq(StanzaId, From, StateData)} - of - {true, {true, NewId, FromFull}} -> - case find_jid_by_nick(ToNick, StateData) of - false -> - case jlib:iq_query_info(Packet) of - reply -> ok; - _ -> - ErrText = <<"Recipient is not in the conference room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_ITEM_NOT_FOUND(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) - end; - ToJID -> - {ok, #user{nick = FromNick}} = - (?DICT):find(jid:tolower(FromFull), - StateData#state.users), - {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID, - StanzaId, NewId, Packet), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - FromNick), - ToJID2, Packet2) - end; - {_, {false, _, _}} -> - case jlib:iq_query_info(Packet) of - reply -> ok; - _ -> - ErrText = - <<"Only occupants are allowed to send queries " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) - end; - _ -> - case jlib:iq_query_info(Packet) of - reply -> ok; - _ -> - ErrText = <<"Queries to the conference members are " - "not allowed in this room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ALLOWED(Lang, ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) - end + is_user_online_iq(StanzaId, From, StateData)} of + {true, {true, NewId, FromFull}} -> + case find_jid_by_nick(ToNick, StateData) of + false -> + ErrText = <<"Recipient is not in the conference room">>, + Err = xmpp:err_item_not_found(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); + ToJID -> + {ok, #user{nick = FromNick}} = + (?DICT):find(jid:tolower(FromFull), StateData#state.users), + {ToJID2, Packet2} = handle_iq_vcard(ToJID, NewId, Packet), + ejabberd_router:route( + jid:replace_resource(StateData#state.jid, FromNick), + ToJID2, Packet2) + end; + {_, {false, _, _}} -> + ErrText = <<"Only occupants are allowed to send queries " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); + _ -> + ErrText = <<"Queries to the conference members are " + "not allowed in this room">>, + Err = xmpp:err_not_allowed(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err) end, {next_state, normal_state, StateData}; normal_state(_Event, StateData) -> @@ -664,11 +481,7 @@ normal_state(_Event, StateData) -> handle_event({service_message, Msg}, _StateName, StateData) -> - MessagePkt = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg}]}]}, + MessagePkt = #message{type = groupchat, body = xmpp:mk_text(Msg)}, send_wrapped_multiple( StateData#state.jid, get_users_and_subscribers(StateData), @@ -680,22 +493,9 @@ handle_event({service_message, Msg}, _StateName, {next_state, normal_state, NSD}; handle_event({destroy, Reason}, _StateName, StateData) -> - {result, [], stop} = destroy_room(#xmlel{name = - <<"destroy">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_OWNER}], - children = - case Reason of - none -> []; - _Else -> - [#xmlel{name = - <<"reason">>, - attrs = [], - children = - [{xmlcdata, - Reason}]}] - end}, - StateData), + {result, undefined, stop} = + destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason}, + StateData), ?INFO_MSG("Destroyed MUC room ~s with reason: ~p", [jid:to_string(StateData#state.jid), Reason]), add_to_log(room_existence, destroyed, StateData), @@ -703,7 +503,7 @@ handle_event({destroy, Reason}, _StateName, handle_event(destroy, StateName, StateData) -> ?INFO_MSG("Destroyed MUC room ~s", [jid:to_string(StateData#state.jid)]), - handle_event({destroy, none}, StateName, StateData); + handle_event({destroy, undefined}, StateName, StateData); handle_event({set_affiliations, Affiliations}, StateName, StateData) -> {next_state, StateName, @@ -712,8 +512,7 @@ handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. handle_sync_event({get_disco_item, Filter, JID, Lang}, _From, StateName, StateData) -> - Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0, - StateData#state.users), + Len = ?DICT:size(StateData#state.users), Reply = case (Filter == all) or (Filter == Len) or ((Filter /= 0) and (Len /= 0)) of true -> get_roomdesc_reply(JID, StateData, @@ -734,40 +533,39 @@ handle_sync_event(get_state, _From, StateName, {reply, {ok, StateData}, StateName, StateData}; handle_sync_event({change_config, Config}, _From, StateName, StateData) -> - {result, [], NSD} = change_config(Config, StateData), + {result, undefined, NSD} = change_config(Config, StateData), {reply, {ok, NSD#state.config}, StateName, NSD}; handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) -> {reply, {ok, NewStateData}, StateName, NewStateData}; handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) -> - NSD = process_item_change(Item, StateData, UJID), - {reply, {ok, NSD}, StateName, NSD}; + case process_item_change(Item, StateData, UJID) of + {error, _} = Err -> + {reply, Err, StateName, StateData}; + NSD -> + {reply, {ok, NSD}, StateName, NSD} + end; handle_sync_event(get_subscribers, _From, StateName, StateData) -> JIDs = lists:map(fun jid:make/1, ?DICT:fetch_keys(StateData#state.subscribers)), {reply, {ok, JIDs}, StateName, StateData}; handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From, StateName, StateData) -> - SubEl = #xmlel{name = <<"subscribe">>, - attrs = [{<<"xmlns">>, ?NS_MUCSUB}, {<<"nick">>, Nick}], - children = [#xmlel{name = <<"event">>, - attrs = [{<<"node">>, Node}]} - || Node <- Nodes]}, IQ = #iq{type = set, id = randoms:get_string(), - xmlns = ?NS_MUCSUB, sub_el = SubEl}, - Packet = jlib:iq_to_xml(IQ#iq{sub_el = [SubEl]}), + from = From, sub_els = [#muc_subscribe{nick = Nick, + events = Nodes}]}, Config = StateData#state.config, CaptchaRequired = Config#config.captcha_protected, PasswordProtected = Config#config.password_protected, TmpConfig = Config#config{captcha_protected = false, password_protected = false}, TmpState = StateData#state{config = TmpConfig}, - case process_iq_mucsub(From, Packet, IQ, TmpState) of - {result, _, NewState} -> + case process_iq_mucsub(From, IQ, TmpState) of + {result, #muc_subscribe{events = NewNodes}, NewState} -> NewConfig = (NewState#state.config)#config{ captcha_protected = CaptchaRequired, password_protected = PasswordProtected}, - {reply, {ok, get_subscription_nodes(Packet)}, StateName, + {reply, {ok, NewNodes}, StateName, NewState#state{config = NewConfig}}; {ignore, NewState} -> NewConfig = (NewState#state.config)#config{ @@ -785,12 +583,9 @@ handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From, {reply, {error, get_error_text(Err)}, StateName, StateData} end; handle_sync_event({muc_unsubscribe, From}, _From, StateName, StateData) -> - SubEl = #xmlel{name = <<"unsubscribe">>, - attrs = [{<<"xmlns">>, ?NS_MUCSUB}]}, IQ = #iq{type = set, id = randoms:get_string(), - xmlns = ?NS_MUCSUB, sub_el = SubEl}, - Packet = jlib:iq_to_xml(IQ), - case process_iq_mucsub(From, Packet, IQ, StateData) of + from = From, sub_els = [#muc_unsubscribe{}]}, + case process_iq_mucsub(From, IQ, StateData) of {result, _, NewState} -> {reply, ok, StateName, NewState}; {ignore, NewState} -> @@ -875,12 +670,11 @@ handle_info({captcha_failed, From}, normal_state, {ok, {Nick, Packet}} -> Robots = (?DICT):erase(From, StateData#state.robots), Txt = <<"The CAPTCHA verification has failed">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_AUTHORIZED(?MYLANG, Txt)), - ejabberd_router:route % TODO: s/Nick/""/ - (jid:replace_resource(StateData#state.jid, - Nick), - From, Err), + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_not_authorized(Txt, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, Nick), + From, Packet, Err), StateData#state{robots = Robots}; _ -> StateData end, @@ -899,22 +693,12 @@ terminate(Reason, _StateName, StateData) -> "because of a system shutdown">>; _ -> <<"Room terminates">> end, - ItemAttrs = [{<<"affiliation">>, <<"none">>}, - {<<"role">>, <<"none">>}], - ReasonEl = #xmlel{name = <<"reason">>, attrs = [], - children = [{xmlcdata, ReasonT}]}, - Packet = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = ItemAttrs, - children = [ReasonEl]}, - #xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"332">>}], - children = []}]}]}, + Packet = #presence{ + type = unavailable, + sub_els = [#muc_user{items = [#muc_item{affiliation = none, + reason = ReasonT, + role = none}], + status_codes = [332]}]}, (?DICT):fold(fun (LJID, Info, _) -> Nick = Info#user.nick, case Reason of @@ -937,14 +721,12 @@ terminate(Reason, _StateName, StateData) -> %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- - +-spec route(pid(), jid(), binary(), stanza()) -> ok. route(Pid, From, ToNick, Packet) -> gen_fsm:send_event(Pid, {route, From, ToNick, Packet}). -process_groupchat_message(From, - #xmlel{name = <<"message">>, attrs = Attrs} = Packet, - StateData) -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), +-spec process_groupchat_message(jid(), message(), state()) -> fsm_next(). +process_groupchat_message(From, #message{lang = Lang} = Packet, StateData) -> IsSubscriber = is_subscriber(From, StateData), case is_user_online(From, StateData) orelse IsSubscriber orelse is_user_allowed_message_nonparticipant(From, StateData) @@ -988,7 +770,7 @@ process_groupchat_message(From, drop -> {next_state, normal_state, StateData}; NewPacket1 -> - NewPacket = fxml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}), + NewPacket = xmpp:remove_subtag(NewPacket1, #nick{}), Node = if Subject == false -> ?NS_MUCSUB_NODES_MESSAGES; true -> ?NS_MUCSUB_NODES_SUBJECT end, @@ -1007,41 +789,167 @@ process_groupchat_message(From, {next_state, normal_state, NewStateData2} end; _ -> - Err = case - (StateData#state.config)#config.allow_change_subj - of + Err = case (StateData#state.config)#config.allow_change_subj of true -> - ?ERRT_FORBIDDEN(Lang, - <<"Only moderators and participants are " - "allowed to change the subject in this " - "room">>); + xmpp:err_forbidden( + <<"Only moderators and participants are " + "allowed to change the subject in this " + "room">>, Lang); _ -> - ?ERRT_FORBIDDEN(Lang, - <<"Only moderators are allowed to change " - "the subject in this room">>) + xmpp:err_forbidden( + <<"Only moderators are allowed to change " + "the subject in this room">>, Lang) end, - ejabberd_router:route(StateData#state.jid, From, - jlib:make_error_reply(Packet, Err)), + ejabberd_router:route_error( + StateData#state.jid, From, Packet, Err), {next_state, normal_state, StateData} end; true -> ErrText = <<"Visitors are not allowed to send messages " "to all occupants">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Packet, Err), {next_state, normal_state, StateData} end; false -> - ErrText = - <<"Only occupants are allowed to send messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), + ErrText = <<"Only occupants are allowed to send messages " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error(StateData#state.jid, From, Packet, Err), {next_state, normal_state, StateData} end. +-spec process_normal_message(jid(), message(), state()) -> state(). +process_normal_message(From, #message{lang = Lang} = Pkt, StateData) -> + Action = lists:foldl( + fun(_, {error, _} = Err) -> + Err; + (#muc_user{invites = [#muc_invite{to = undefined}]}, _) -> + Txt = <<"No 'to' attribute found">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + (#muc_user{invites = [I]}, _) -> + {ok, I}; + (#muc_user{invites = [_|_]}, _) -> + Txt = <<"Multiple invitations are not allowed">>, + {error, xmpp:err_resource_constraint(Txt, Lang)}; + (#xdata{type = submit, fields = Fs}, _) -> + try {ok, muc_request:decode(Fs)} + catch _:{muc_request, Why} -> + Txt = muc_request:format_error(Why), + {error, xmpp:err_bad_request(Txt, Lang)} + end; + (_, Acc) -> + Acc + end, ok, xmpp:get_els(Pkt)), + case Action of + {ok, #muc_invite{} = Invitation} -> + process_invitation(From, Pkt, Invitation, StateData); + {ok, [{role, participant}]} -> + process_voice_request(From, Pkt, StateData); + {ok, VoiceApproval} -> + process_voice_approval(From, Pkt, VoiceApproval, StateData); + {error, Err} -> + ejabberd_router:route_error(StateData#state.jid, From, Pkt, Err), + StateData; + ok -> + StateData + end. + +-spec process_invitation(jid(), message(), muc_invite(), state()) -> state(). +process_invitation(From, Pkt, Invitation, StateData) -> + Lang = xmpp:get_lang(Pkt), + case check_invitation(From, Invitation, Lang, StateData) of + {error, Error} -> + ejabberd_router:route_error(StateData#state.jid, From, Pkt, Error), + StateData; + IJID -> + Config = StateData#state.config, + case Config#config.members_only of + true -> + case get_affiliation(IJID, StateData) of + none -> + NSD = set_affiliation(IJID, member, StateData), + send_affiliation(IJID, member, StateData), + store_room(NSD), + NSD; + _ -> + StateData + end; + false -> + StateData + end + end. + +-spec process_voice_request(jid(), message(), state()) -> state(). +process_voice_request(From, Pkt, StateData) -> + Lang = xmpp:get_lang(Pkt), + case (StateData#state.config)#config.allow_voice_requests of + true -> + MinInterval = (StateData#state.config)#config.voice_request_min_interval, + BareFrom = jid:remove_resource(jid:tolower(From)), + NowPriority = -p1_time_compat:system_time(micro_seconds), + CleanPriority = NowPriority + MinInterval * 1000000, + Times = clean_treap(StateData#state.last_voice_request_time, + CleanPriority), + case treap:lookup(BareFrom, Times) of + error -> + Times1 = treap:insert(BareFrom, + NowPriority, + true, Times), + NSD = StateData#state{last_voice_request_time = Times1}, + send_voice_request(From, Lang, NSD), + NSD; + {ok, _, _} -> + ErrText = <<"Please, wait for a while before sending " + "new voice request">>, + Err = xmpp:err_resource_constraint(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), + StateData#state{last_voice_request_time = Times} + end; + false -> + ErrText = <<"Voice requests are disabled in this conference">>, + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), + StateData + end. + +-spec process_voice_approval(jid(), message(), [muc_request:property()], state()) -> state(). +process_voice_approval(From, Pkt, VoiceApproval, StateData) -> + Lang = xmpp:get_lang(Pkt), + case is_moderator(From, StateData) of + true -> + case lists:keyfind(jid, 1, VoiceApproval) of + {_, TargetJid} -> + Allow = proplists:get_bool(request_allow, VoiceApproval), + case is_visitor(TargetJid, StateData) of + true when Allow -> + Reason = <<>>, + NSD = set_role(TargetJid, participant, StateData), + catch send_new_presence( + TargetJid, Reason, NSD, StateData), + NSD; + _ -> + StateData + end; + false -> + ErrText = <<"Failed to extract JID from your voice " + "request approval">>, + Err = xmpp:err_bad_request(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), + StateData + end; + false -> + ErrText = <<"Only moderators can approve voice requests">>, + Err = xmpp:err_not_allowed(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), + StateData + end. + %% @doc Check if this non participant can send message to room. %% %% XEP-0045 v1.23: @@ -1049,6 +957,7 @@ process_groupchat_message(From, %% an implementation MAY allow users with certain privileges %% (e.g., a room owner, room admin, or service-level admin) %% to send messages to the room even if those users are not occupants. +-spec is_user_allowed_message_nonparticipant(jid(), state()) -> boolean(). is_user_allowed_message_nonparticipant(JID, StateData) -> case get_service_affiliation(JID, StateData) of @@ -1058,6 +967,7 @@ is_user_allowed_message_nonparticipant(JID, %% @doc Get information of this participant, or default values. %% If the JID is not a participant, return values for a service message. +-spec get_participant_data(jid(), state()) -> {binary(), role()}. get_participant_data(From, StateData) -> case (?DICT):find(jid:tolower(From), StateData#state.users) @@ -1074,13 +984,11 @@ get_participant_data(From, StateData) -> end end. -process_presence(From, Nick, - #xmlel{name = <<"presence">>, attrs = Attrs0} = Packet0, - StateData) -> - Type0 = fxml:get_attr_s(<<"type">>, Attrs0), +-spec process_presence(jid(), binary(), presence(), state()) -> fsm_transition(). +process_presence(From, Nick, #presence{type = Type0} = Packet0, StateData) -> IsOnline = is_user_online(From, StateData), - if Type0 == <<"">>; - IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) -> + if Type0 == available; + IsOnline and ((Type0 == unavailable) or (Type0 == error)) -> case ejabberd_hooks:run_fold(muc_filter_presence, StateData#state.server_host, Packet0, @@ -1089,107 +997,91 @@ process_presence(From, Nick, From, Nick]) of drop -> {next_state, normal_state, StateData}; - #xmlel{attrs = Attrs} = Packet -> - Type = fxml:get_attr_s(<<"type">>, Attrs), - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - StateData1 = case Type of - <<"unavailable">> -> - NewPacket = case - {(StateData#state.config)#config.allow_visitor_status, - is_visitor(From, StateData)} - of - {false, true} -> - strip_status(Packet); - _ -> Packet - end, - NewState = add_user_presence_un(From, NewPacket, - StateData), - case (?DICT):find(Nick, StateData#state.nicks) of - {ok, [_, _ | _]} -> ok; - _ -> send_new_presence(From, NewState, StateData) - end, - Reason = case fxml:get_subtag(NewPacket, - <<"status">>) - of - false -> <<"">>; - Status_el -> - fxml:get_tag_cdata(Status_el) - end, - remove_online_user(From, NewState, Reason); - <<"error">> -> - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, - expulse_participant(Packet, From, StateData, - translate:translate(Lang, - ErrorText)); - <<"">> -> - if not IsOnline -> - add_new_user(From, Nick, Packet, StateData); - true -> - case is_nick_change(From, Nick, StateData) of - true -> - case {nick_collision(From, Nick, StateData), - mod_muc:can_use_nick(StateData#state.server_host, - StateData#state.host, - From, Nick), - {(StateData#state.config)#config.allow_visitor_nickchange, - is_visitor(From, StateData)}} - of - {_, _, {false, true}} -> - ErrText = - <<"Visitors are not allowed to change their " - "nicknames in this room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ALLOWED(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - {true, _, _} -> - Lang = fxml:get_attr_s(<<"xml:lang">>, - Attrs), - ErrText = - <<"That nickname is already in use by another " - "occupant">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - Nick), % TODO: s/Nick/""/ - From, Err), - StateData; - {_, false, _} -> - ErrText = - <<"That nickname is registered by another " - "person">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - _ -> - change_nick(From, Nick, StateData) - end; - _NotNickChange -> - Stanza = maybe_strip_status_from_presence( - From, Packet, StateData), - NewState = add_user_presence(From, Stanza, - StateData), - send_new_presence(From, NewState, StateData), - NewState - end - end - end, - close_room_if_temporary_and_empty(StateData1) + #presence{} = Packet -> + close_room_if_temporary_and_empty( + do_process_presence(From, Nick, Packet, StateData)) end; true -> - {next_state, normal_state, StateData} + {next_state, normal_state, StateData} end. +-spec do_process_presence(jid(), binary(), presence(), state()) -> + state(). +do_process_presence(From, Nick, #presence{type = available, lang = Lang} = Packet, + StateData) -> + case is_user_online(From, StateData) of + false -> + add_new_user(From, Nick, Packet, StateData); + true -> + case is_nick_change(From, Nick, StateData) of + true -> + case {nick_collision(From, Nick, StateData), + mod_muc:can_use_nick(StateData#state.server_host, + StateData#state.host, + From, Nick), + {(StateData#state.config)#config.allow_visitor_nickchange, + is_visitor(From, StateData)}} of + {_, _, {false, true}} -> + ErrText = <<"Visitors are not allowed to change their " + "nicknames in this room">>, + Err = xmpp:err_not_allowed(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, Nick), + From, Packet, Err), + StateData; + {true, _, _} -> + ErrText = <<"That nickname is already in use by another " + "occupant">>, + Err = xmpp:err_conflict(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, Nick), + From, Packet, Err), + StateData; + {_, false, _} -> + ErrText = <<"That nickname is registered by another " + "person">>, + Err = xmpp:err_conflict(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, Nick), + From, Packet, Err), + StateData; + _ -> + change_nick(From, Nick, StateData) + end; + false -> + Stanza = maybe_strip_status_from_presence( + From, Packet, StateData), + NewState = add_user_presence(From, Stanza, + StateData), + send_new_presence(From, NewState, StateData), + NewState + end + end; +do_process_presence(From, Nick, #presence{type = unavailable} = Packet, + StateData) -> + NewPacket = case {(StateData#state.config)#config.allow_visitor_status, + is_visitor(From, StateData)} of + {false, true} -> + strip_status(Packet); + _ -> Packet + end, + NewState = add_user_presence_un(From, NewPacket, StateData), + case (?DICT):find(Nick, StateData#state.nicks) of + {ok, [_, _ | _]} -> ok; + _ -> send_new_presence(From, NewState, StateData) + end, + Reason = xmpp:get_text(NewPacket#presence.status), + remove_online_user(From, NewState, Reason); +do_process_presence(From, _Nick, #presence{type = error, lang = Lang} = Packet, + StateData) -> + ErrorText = <<"It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room">>, + expulse_participant(Packet, From, StateData, + translate:translate(Lang, ErrorText)). + +-spec maybe_strip_status_from_presence(jid(), presence(), + state()) -> presence(). maybe_strip_status_from_presence(From, Packet, StateData) -> case {(StateData#state.config)#config.allow_visitor_status, is_visitor(From, StateData)} of @@ -1198,6 +1090,7 @@ maybe_strip_status_from_presence(From, Packet, StateData) -> _Allowed -> Packet end. +-spec close_room_if_temporary_and_empty(state()) -> fsm_transition(). close_room_if_temporary_and_empty(StateData1) -> case not (StateData1#state.config)#config.persistent andalso (?DICT):size(StateData1#state.users) == 0 @@ -1237,15 +1130,18 @@ get_users_and_subscribers(StateData) -> end end, StateData#state.users, StateData#state.subscribers). +-spec is_user_online(jid(), state()) -> boolean(). is_user_online(JID, StateData) -> LJID = jid:tolower(JID), (?DICT):is_key(LJID, StateData#state.users). +-spec is_subscriber(jid(), state()) -> boolean(). is_subscriber(JID, StateData) -> LJID = jid:tolower(jid:remove_resource(JID)), (?DICT):is_key(LJID, StateData#state.subscribers). %% Check if the user is occupant of the room, or at least is an admin or owner. +-spec is_occupant_or_admin(jid(), state()) -> boolean(). is_occupant_or_admin(JID, StateData) -> FAffiliation = get_affiliation(JID, StateData), FRole = get_role(JID, StateData), @@ -1260,6 +1156,8 @@ is_occupant_or_admin(JID, StateData) -> %%% %%% Handle IQ queries of vCard %%% +-spec is_user_online_iq(binary(), jid(), state()) -> + {boolean(), binary(), jid()}. is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource /= <<"">> -> {is_user_online(JID, StateData), StanzaId, JID}; @@ -1267,99 +1165,61 @@ is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource == <<"">> -> try stanzaid_unpack(StanzaId) of {OriginalId, Resource} -> - JIDWithResource = jid:replace_resource(JID, - Resource), + JIDWithResource = jid:replace_resource(JID, Resource), {is_user_online(JIDWithResource, StateData), OriginalId, JIDWithResource} catch _:_ -> {is_user_online(JID, StateData), StanzaId, JID} end. -handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, - Packet) -> +-spec handle_iq_vcard(jid(), binary(), iq()) -> {jid(), iq()}. +handle_iq_vcard(ToJID, NewId, #iq{type = Type, sub_els = SubEls} = IQ) -> ToBareJID = jid:remove_resource(ToJID), - IQ = jlib:iq_query_info(Packet), - handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, - NewId, IQ, Packet). - -handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId, - _NewId, #iq{type = get, xmlns = ?NS_VCARD}, Packet) - when ToBareJID /= ToJID -> - {ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)}; -handle_iq_vcard2(_FromFull, ToJID, _ToBareJID, - _StanzaId, NewId, _IQ, Packet) -> - {ToJID, change_stanzaid(NewId, Packet)}. + case SubEls of + [SubEl] when Type == get, ToBareJID /= ToJID -> + case xmpp:get_ns(SubEl) of + ?NS_VCARD -> + {ToBareJID, change_stanzaid(ToJID, IQ)}; + _ -> + {ToJID, xmpp:set_id(IQ, NewId)} + end; + _ -> + {ToJID, xmpp:set_id(IQ, NewId)} + end. +-spec stanzaid_pack(binary(), binary()) -> binary(). stanzaid_pack(OriginalId, Resource) -> <<"berd", (jlib:encode_base64(<<"ejab\000", OriginalId/binary, "\000", Resource/binary>>))/binary>>. +-spec stanzaid_unpack(binary()) -> {binary(), binary()}. stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) -> StanzaId = jlib:decode_base64(StanzaIdBase64), [<<"ejab">>, OriginalId, Resource] = str:tokens(StanzaId, <<"\000">>), {OriginalId, Resource}. -change_stanzaid(NewId, Packet) -> - #xmlel{name = Name, attrs = Attrs, children = Els} = - jlib:remove_attr(<<"id">>, Packet), - #xmlel{name = Name, attrs = [{<<"id">>, NewId} | Attrs], - children = Els}. - -change_stanzaid(PreviousId, ToJID, Packet) -> +-spec change_stanzaid(jid(), iq()) -> iq(). +change_stanzaid(ToJID, #iq{id = PreviousId} = Packet) -> NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource), - change_stanzaid(NewId, Packet). - -%%% -%%% - -role_to_list(Role) -> - case Role of - moderator -> <<"moderator">>; - participant -> <<"participant">>; - visitor -> <<"visitor">>; - none -> <<"none">> - end. - -affiliation_to_list(Affiliation) -> - case Affiliation of - owner -> <<"owner">>; - admin -> <<"admin">>; - member -> <<"member">>; - outcast -> <<"outcast">>; - none -> <<"none">> - end. - -list_to_role(Role) -> - case Role of - <<"moderator">> -> moderator; - <<"participant">> -> participant; - <<"visitor">> -> visitor; - <<"none">> -> none - end. - -list_to_affiliation(Affiliation) -> - case Affiliation of - <<"owner">> -> owner; - <<"admin">> -> admin; - <<"member">> -> member; - <<"outcast">> -> outcast; - <<"none">> -> none - end. + xmpp:set_id(Packet, NewId). %% Decide the fate of the message and its sender %% Returns: continue_delivery | forget_message | {expulse_sender, Reason} -decide_fate_message(<<"error">>, Packet, From, - StateData) -> - PD = case check_error_kick(Packet) of +-spec decide_fate_message(message(), jid(), state()) -> + continue_delivery | forget_message | + {expulse_sender, binary()}. +decide_fate_message(#message{type = error} = Msg, + From, StateData) -> + Err = xmpp:get_error(Msg), + PD = case check_error_kick(Err) of %% If this is an error stanza and its condition matches a criteria true -> - Reason = - io_lib:format("This participant is considered a ghost " - "and is expulsed: ~s", - [jid:to_string(From)]), + Reason = str:format("This participant is considered a ghost " + "and is expulsed: ~s", + [jid:to_string(From)]), {expulse_sender, Reason}; false -> continue_delivery end, @@ -1371,40 +1231,37 @@ decide_fate_message(<<"error">>, Packet, From, end; Other -> Other end; -decide_fate_message(_, _, _, _) -> continue_delivery. +decide_fate_message(_, _, _) -> continue_delivery. %% Check if the elements of this error stanza indicate %% that the sender is a dead participant. %% If so, return true to kick the participant. -check_error_kick(Packet) -> - case get_error_condition(Packet) of - <<"gone">> -> true; - <<"internal-server-error">> -> true; - <<"item-not-found">> -> true; - <<"jid-malformed">> -> true; - <<"recipient-unavailable">> -> true; - <<"redirect">> -> true; - <<"remote-server-not-found">> -> true; - <<"remote-server-timeout">> -> true; - <<"service-unavailable">> -> true; - _ -> false - end. +-spec check_error_kick(stanza_error()) -> boolean(). +check_error_kick(#stanza_error{reason = Reason}) -> + case Reason of + #gone{} -> true; + 'internal-server-error' -> true; + 'item-not-found' -> true; + 'jid-malformed' -> true; + 'recipient-unavailable' -> true; + #redirect{} -> true; + 'remote-server-not-found' -> true; + 'remote-server-timeout' -> true; + 'service-unavailable' -> true; + _ -> false + end; +check_error_kick(undefined) -> + false. -get_error_condition(Packet) -> - case catch get_error_condition2(Packet) of - {condition, ErrorCondition} -> ErrorCondition; - {'EXIT', _} -> <<"badformed error stanza">> - end. - -get_error_condition2(Packet) -> - #xmlel{children = EEls} = fxml:get_subtag(Packet, - <<"error">>), - [Condition] = [Name - || #xmlel{name = Name, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], - children = []} - <- EEls], - {condition, Condition}. +-spec get_error_condition(stanza_error()) -> string(). +get_error_condition(#stanza_error{reason = Reason}) -> + case Reason of + #gone{} -> "gone"; + #redirect{} -> "redirect"; + Atom -> atom_to_list(Atom) + end; +get_error_condition(undefined) -> + "undefined". get_error_text(Error) -> case fxml:get_subtag_with_xmlns(Error, <<"text">>, ?NS_STANZAS) of @@ -1414,31 +1271,28 @@ get_error_text(Error) -> <<"">> end. +-spec make_reason(stanza(), jid(), state(), binary()) -> binary(). make_reason(Packet, From, StateData, Reason1) -> {ok, #user{nick = FromNick}} = (?DICT):find(jid:tolower(From), StateData#state.users), - Condition = get_error_condition(Packet), - iolist_to_binary(io_lib:format(Reason1, [FromNick, Condition])). + Condition = get_error_condition(xmpp:get_error(Packet)), + str:format(Reason1, [FromNick, Condition]). +-spec expulse_participant(stanza(), jid(), state(), binary()) -> + state(). expulse_participant(Packet, From, StateData, Reason1) -> Reason2 = make_reason(Packet, From, StateData, Reason1), NewState = add_user_presence_un(From, - #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name = <<"status">>, - attrs = [], - children = - [{xmlcdata, - Reason2}]}]}, + #presence{type = unavailable, + status = xmpp:mk_text(Reason2)}, StateData), send_new_presence(From, NewState, StateData), remove_online_user(From, NewState). +-spec set_affiliation(jid(), affiliation(), state()) -> state(). set_affiliation(JID, Affiliation, StateData) -> set_affiliation(JID, Affiliation, StateData, <<"">>). +-spec set_affiliation(jid(), affiliation(), state(), binary()) -> state(). set_affiliation(JID, Affiliation, StateData, Reason) -> LJID = jid:remove_resource(jid:tolower(JID)), Affiliations = case Affiliation of @@ -1450,6 +1304,7 @@ set_affiliation(JID, Affiliation, StateData, Reason) -> end, StateData#state{affiliations = Affiliations}. +-spec get_affiliation(jid(), state()) -> affiliation(). get_affiliation(JID, StateData) -> {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = @@ -1490,6 +1345,7 @@ get_affiliation(JID, StateData) -> _ -> Res end. +-spec get_service_affiliation(jid(), state()) -> owner | none. get_service_affiliation(JID, StateData) -> {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = @@ -1501,6 +1357,7 @@ get_service_affiliation(JID, StateData) -> _ -> none end. +-spec set_role(jid(), role(), state()) -> state(). set_role(JID, Role, StateData) -> LJID = jid:tolower(JID), LJIDs = case LJID of @@ -1549,6 +1406,7 @@ set_role(JID, Role, StateData) -> end, StateData#state{users = Users, nicks = Nicks}. +-spec get_role(jid(), state()) -> role(). get_role(JID, StateData) -> LJID = jid:tolower(JID), case (?DICT):find(LJID, StateData#state.users) of @@ -1556,6 +1414,7 @@ get_role(JID, StateData) -> _ -> none end. +-spec get_default_role(affiliation(), state()) -> role(). get_default_role(Affiliation, StateData) -> case Affiliation of owner -> moderator; @@ -1574,12 +1433,15 @@ get_default_role(Affiliation, StateData) -> end end. +-spec is_visitor(jid(), state()) -> boolean(). is_visitor(Jid, StateData) -> get_role(Jid, StateData) =:= visitor. +-spec is_moderator(jid(), state()) -> boolean(). is_moderator(Jid, StateData) -> get_role(Jid, StateData) =:= moderator. +-spec get_max_users(state()) -> non_neg_integer(). get_max_users(StateData) -> MaxUsers = (StateData#state.config)#config.max_users, ServiceMaxUsers = get_service_max_users(StateData), @@ -1587,18 +1449,21 @@ get_max_users(StateData) -> true -> ServiceMaxUsers end. +-spec get_service_max_users(state()) -> pos_integer(). get_service_max_users(StateData) -> gen_mod:get_module_opt(StateData#state.server_host, mod_muc, max_users, fun(I) when is_integer(I), I>0 -> I end, ?MAX_USERS_DEFAULT). +-spec get_max_users_admin_threshold(state()) -> pos_integer(). get_max_users_admin_threshold(StateData) -> gen_mod:get_module_opt(StateData#state.server_host, mod_muc, max_users_admin_threshold, fun(I) when is_integer(I), I>0 -> I end, 5). +-spec get_user_activity(jid(), state()) -> #activity{}. get_user_activity(JID, StateData) -> case treap:lookup(jid:tolower(JID), StateData#state.activity) @@ -1619,6 +1484,7 @@ get_user_activity(JID, StateData) -> presence_shaper = PresenceShaper} end. +-spec store_user_activity(jid(), #activity{}, state()) -> state(). store_user_activity(JID, UserActivity, StateData) -> MinMessageInterval = trunc(gen_mod:get_module_opt(StateData#state.server_host, @@ -1680,6 +1546,7 @@ store_user_activity(JID, UserActivity, StateData) -> end, StateData1. +-spec clean_treap(treap:treap(), integer()) -> treap:treap(). clean_treap(Treap, CleanPriority) -> case treap:is_empty(Treap) of true -> Treap; @@ -1691,6 +1558,7 @@ clean_treap(Treap, CleanPriority) -> end end. +-spec prepare_room_queue(state()) -> state(). prepare_room_queue(StateData) -> case queue:out(StateData#state.room_queue) of {{value, {message, From}}, _RoomQueue} -> @@ -1714,6 +1582,7 @@ prepare_room_queue(StateData) -> {empty, _} -> StateData end. +-spec update_online_user(jid(), #user{}, state()) -> state(). update_online_user(JID, #user{nick = Nick} = User, StateData) -> LJID = jid:tolower(JID), Nicks1 = case (?DICT):find(LJID, StateData#state.users) of @@ -1759,14 +1628,17 @@ set_subscriber(JID, Nick, Nodes, StateData) -> store_room(NewStateData), NewStateData. +-spec add_online_user(jid(), binary(), role(), state()) -> state(). add_online_user(JID, Nick, Role, StateData) -> tab_add_online_user(JID, StateData), User = #user{jid = JID, nick = Nick, role = Role}, update_online_user(JID, User, StateData). +-spec remove_online_user(jid(), state()) -> state(). remove_online_user(JID, StateData) -> remove_online_user(JID, StateData, <<"">>). +-spec remove_online_user(jid(), state(), binary()) -> state(). remove_online_user(JID, StateData, Reason) -> LJID = jid:tolower(JID), {ok, #user{nick = Nick}} = (?DICT):find(LJID, @@ -1784,38 +1656,23 @@ remove_online_user(JID, StateData, Reason) -> end, StateData#state{users = Users, nicks = Nicks}. -filter_presence(#xmlel{name = <<"presence">>, - attrs = Attrs, children = Els}) -> - FEls = lists:filter(fun (El) -> - case El of - {xmlcdata, _} -> false; - #xmlel{attrs = Attrs1} -> - XMLNS = fxml:get_attr_s(<<"xmlns">>, - Attrs1), - NS_MUC = ?NS_MUC, - Size = byte_size(NS_MUC), - case XMLNS of - <> -> - false; - _ -> - true - end - end - end, - Els), - #xmlel{name = <<"presence">>, attrs = Attrs, - children = FEls}. +-spec filter_presence(presence()) -> presence(). +filter_presence(Presence) -> + Els = lists:filter( + fun(El) -> + XMLNS = xmpp:get_ns(El), + case catch binary:part(XMLNS, 0, size(?NS_MUC)) of + ?NS_MUC -> false; + _ -> true + end + end, xmpp:get_els(Presence)), + xmpp:set_els(Presence, Els). -strip_status(#xmlel{name = <<"presence">>, - attrs = Attrs, children = Els}) -> - FEls = lists:filter(fun (#xmlel{name = <<"status">>}) -> - false; - (_) -> true - end, - Els), - #xmlel{name = <<"presence">>, attrs = Attrs, - children = FEls}. +-spec strip_status(presence()) -> presence(). +strip_status(Presence) -> + Presence#presence{status = []}. +-spec add_user_presence(jid(), presence(), state()) -> state(). add_user_presence(JID, Presence, StateData) -> LJID = jid:tolower(JID), FPresence = filter_presence(Presence), @@ -1826,6 +1683,7 @@ add_user_presence(JID, Presence, StateData) -> StateData#state.users), StateData#state{users = Users}. +-spec add_user_presence_un(jid(), presence(), state()) -> state(). add_user_presence_un(JID, Presence, StateData) -> LJID = jid:tolower(JID), FPresence = filter_presence(Presence), @@ -1839,18 +1697,20 @@ add_user_presence_un(JID, Presence, StateData) -> %% Find and return a list of the full JIDs of the users of Nick. %% Return jid record. +-spec find_jids_by_nick(binary(), state()) -> [jid()]. find_jids_by_nick(Nick, StateData) -> Nicks = ?DICT:merge(fun(_, Val, _) -> Val end, StateData#state.nicks, StateData#state.subscriber_nicks), case (?DICT):find(Nick, Nicks) of - {ok, [User]} -> [jid:make(User)]; - {ok, Users} -> [jid:make(LJID) || LJID <- Users]; - error -> false + {ok, [User]} -> [jid:make(User)]; + {ok, Users} -> [jid:make(LJID) || LJID <- Users]; + error -> [] end. %% Find and return the full JID of the user of Nick with %% highest-priority presence. Return jid record. +-spec find_jid_by_nick(binary(), state()) -> jid() | false. find_jid_by_nick(Nick, StateData) -> case (?DICT):find(Nick, StateData#state.nicks) of {ok, [User]} -> jid:make(User); @@ -1875,6 +1735,8 @@ find_jid_by_nick(Nick, StateData) -> error -> false end. +-spec higher_presence(undefined | presence(), + undefined | presence()) -> boolean(). higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined -> Pri1 = get_priority_from_presence(Pres1), Pri2 = get_priority_from_presence(Pres2), @@ -1882,26 +1744,20 @@ higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined -> higher_presence(Pres1, Pres2) -> Pres1 > Pres2. -get_priority_from_presence(PresencePacket) -> - case fxml:get_subtag(PresencePacket, <<"priority">>) of - false -> 0; - SubEl -> - case catch - jlib:binary_to_integer(fxml:get_tag_cdata(SubEl)) - of - P when is_integer(P) -> P; - _ -> 0 - end +-spec get_priority_from_presence(presence()) -> integer(). +get_priority_from_presence(#presence{priority = Prio}) -> + case Prio of + undefined -> 0; + _ -> Prio end. -find_nick_by_jid(Jid, StateData) -> - [{_, #user{nick = Nick}}] = lists:filter(fun ({_, - #user{jid = FJid}}) -> - FJid == Jid - end, - (?DICT):to_list(StateData#state.users)), +-spec find_nick_by_jid(jid(), state()) -> binary(). +find_nick_by_jid(JID, StateData) -> + LJID = jid:tolower(JID), + {ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users), Nick. +-spec is_nick_change(jid(), binary(), state()) -> boolean(). is_nick_change(JID, Nick, StateData) -> LJID = jid:tolower(JID), case Nick of @@ -1912,6 +1768,7 @@ is_nick_change(JID, Nick, StateData) -> Nick /= OldNick end. +-spec nick_collision(jid(), binary(), state()) -> boolean(). nick_collision(User, Nick, StateData) -> UserOfNick = case find_jid_by_nick(Nick, StateData) of false -> @@ -1925,10 +1782,13 @@ nick_collision(User, Nick, StateData) -> jid:remove_resource(jid:tolower(UserOfNick)) /= jid:remove_resource(jid:tolower(User))). -add_new_user(From, Nick, - #xmlel{name = Name, attrs = Attrs, children = Els} = Packet, - StateData) -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), +-spec add_new_user(jid(), binary(), presence() | iq(), state()) -> + state() | + {error, stanza_error()} | + {ignore, state()} | + {result, xmpp_element(), state()}. +add_new_user(From, Nick, Packet, StateData) -> + Lang = xmpp:get_lang(Packet), UserRoomJID = jid:replace_resource(StateData#state.jid, Nick), MaxUsers = get_max_users(StateData), MaxAdminUsers = MaxUsers + @@ -1945,7 +1805,7 @@ add_new_user(From, Nick, fun(I) when is_integer(I), I>0 -> I end, 10), Collision = nick_collision(From, Nick, StateData), - IsSubscribeRequest = Name /= <<"presence">>, + IsSubscribeRequest = not is_record(Packet, presence), case {(ServiceAffiliation == owner orelse ((Affiliation == admin orelse Affiliation == owner) andalso NUsers < MaxAdminUsers) @@ -1958,72 +1818,66 @@ add_new_user(From, Nick, of {false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers -> Txt = <<"Too many users in this conference">>, - Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_resource_constraint(Txt, Lang), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error(UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end; {false, _, _, _} when NConferences >= MaxConferences -> Txt = <<"You have joined too many conferences">>, - Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_resource_constraint(Txt, Lang), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error(UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end; {false, _, _, _} -> - Err = ?ERR_SERVICE_UNAVAILABLE, - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_service_unavailable(), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error(UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end; {_, _, _, none} -> Err = case Affiliation of outcast -> ErrText = <<"You have been banned from this room">>, - ?ERRT_FORBIDDEN(Lang, ErrText); + xmpp:err_forbidden(ErrText, Lang); _ -> ErrText = <<"Membership is required to enter this room">>, - ?ERRT_REGISTRATION_REQUIRED(Lang, ErrText) + xmpp:err_registration_required(ErrText, Lang) end, - ErrPacket = jlib:make_error_reply(Packet, Err), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error(UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end; {_, true, _, _} -> ErrText = <<"That nickname is already in use by another occupant">>, - Err = ?ERRT_CONFLICT(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_conflict(ErrText, Lang), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error(UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end; {_, _, false, _} -> ErrText = <<"That nickname is registered by another person">>, - Err = ?ERRT_CONFLICT(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_conflict(ErrText, Lang), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error(UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end; {_, _, _, Role} -> case check_password(ServiceAffiliation, Affiliation, - Els, From, StateData) + Packet, From, StateData) of true -> Nodes = get_subscription_nodes(Packet), @@ -2035,11 +1889,9 @@ add_new_user(From, Nick, StateData)), send_existing_presences(From, NewState), send_initial_presence(From, NewState, StateData), - Shift = count_stanza_shift(Nick, Els, NewState), - case send_history(From, Shift, NewState) of - true -> ok; - _ -> send_subject(From, StateData) - end, + History = get_history(Nick, Packet, NewState), + send_history(From, History, NewState), + send_subject(From, StateData), NewState; true -> set_subscriber(From, Nick, Nodes, StateData) @@ -2053,30 +1905,28 @@ add_new_user(From, Nick, NewStateData#state{robots = Robots} end, if not IsSubscribeRequest -> ResultState; - true -> {result, subscription_nodes_to_events(Nodes), ResultState} + true -> {result, subscribe_result(Packet), ResultState} end; nopass -> ErrText = <<"A password is required to enter this room">>, - Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_not_authorized(ErrText, Lang), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error(UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end; captcha_required -> - SID = fxml:get_attr_s(<<"id">>, Attrs), + SID = xmpp:get_id(Packet), RoomJID = StateData#state.jid, To = jid:replace_resource(RoomJID, Nick), Limiter = {From#jid.luser, From#jid.lserver}, case ejabberd_captcha:create_captcha(SID, RoomJID, To, Lang, Limiter, From) of - {ok, ID, CaptchaEls} -> - MsgPkt = #xmlel{name = <<"message">>, - attrs = [{<<"id">>, ID}], - children = CaptchaEls}, + {ok, ID, Body, CaptchaEls} -> + MsgPkt = #message{id = ID, body = Body, + sub_els = CaptchaEls}, Robots = (?DICT):store(From, {Nick, Packet}, StateData#state.robots), ejabberd_router:route(RoomJID, From, MsgPkt), @@ -2088,49 +1938,51 @@ add_new_user(From, Nick, end; {error, limit} -> ErrText = <<"Too many CAPTCHA requests">>, - Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_resource_constraint(ErrText, Lang), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error( + UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end; _ -> ErrText = <<"Unable to generate a CAPTCHA">>, - Err = ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_internal_server_error(ErrText, Lang), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error( + UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end end; _ -> ErrText = <<"Incorrect password">>, - Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_not_authorized(ErrText, Lang), if not IsSubscribeRequest -> - ejabberd_router:route(UserRoomJID, From, ErrPacket), + ejabberd_router:route_error( + UserRoomJID, From, Packet, Err), StateData; true -> - {error, Err, StateData} + {error, Err} end end end. -check_password(owner, _Affiliation, _Els, _From, +-spec check_password(affiliation(), affiliation(), + stanza(), jid(), state()) -> boolean() | nopass. +check_password(owner, _Affiliation, _Packet, _From, _StateData) -> %% Don't check pass if user is owner in MUC service (access_admin option) true; -check_password(_ServiceAffiliation, Affiliation, Els, +check_password(_ServiceAffiliation, Affiliation, Packet, From, StateData) -> case (StateData#state.config)#config.password_protected of false -> check_captcha(Affiliation, From, StateData); true -> - Pass = extract_password(Els), + Pass = extract_password(Packet), case Pass of false -> nopass; _ -> @@ -2141,6 +1993,7 @@ check_password(_ServiceAffiliation, Affiliation, Els, end end. +-spec check_captcha(affiliation(), jid(), state()) -> true | captcha_required. check_captcha(Affiliation, From, StateData) -> case (StateData#state.config)#config.captcha_protected andalso ejabberd_captcha:is_feature_available() @@ -2169,108 +2022,55 @@ check_captcha(Affiliation, From, StateData) -> _ -> true end. -extract_password([]) -> false; -extract_password([#xmlel{attrs = Attrs} = El | Els]) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC -> - case fxml:get_subtag(El, <<"password">>) of - false -> false; - SubEl -> fxml:get_tag_cdata(SubEl) - end; - _ -> extract_password(Els) - end; -extract_password([_ | Els]) -> extract_password(Els). - -count_stanza_shift(Nick, Els, StateData) -> - HL = lqueue_to_list(StateData#state.history), - Since = extract_history(Els, <<"since">>), - Shift0 = case Since of - false -> 0; - _ -> - Sin = calendar:datetime_to_gregorian_seconds(Since), - count_seconds_shift(Sin, HL) - end, - Seconds = extract_history(Els, <<"seconds">>), - Shift1 = case Seconds of - false -> 0; - _ -> - Sec = calendar:datetime_to_gregorian_seconds(calendar:universal_time()) - - Seconds, - count_seconds_shift(Sec, HL) - end, - MaxStanzas = extract_history(Els, <<"maxstanzas">>), - Shift2 = case MaxStanzas of - false -> 0; - _ -> count_maxstanzas_shift(MaxStanzas, HL) - end, - MaxChars = extract_history(Els, <<"maxchars">>), - Shift3 = case MaxChars of - false -> 0; - _ -> count_maxchars_shift(Nick, MaxChars, HL) - end, - lists:max([Shift0, Shift1, Shift2, Shift3]). - -count_seconds_shift(Seconds, HistoryList) -> - lists:sum(lists:map(fun ({_Nick, _Packet, _HaveSubject, - TimeStamp, _Size}) -> - T = - calendar:datetime_to_gregorian_seconds(TimeStamp), - if T < Seconds -> 1; - true -> 0 - end - end, - HistoryList)). - -count_maxstanzas_shift(MaxStanzas, HistoryList) -> - S = length(HistoryList) - MaxStanzas, - if S =< 0 -> 0; - true -> S +-spec extract_password(stanza()) -> binary() | false. +extract_password(Packet) -> + case xmpp:get_subtag(Packet, #muc{}) of + #muc{password = Password} when is_binary(Password) -> + Password; + _ -> + false end. -count_maxchars_shift(Nick, MaxSize, HistoryList) -> - NLen = byte_size(Nick) + 1, - Sizes = lists:map(fun ({_Nick, _Packet, _HaveSubject, - _TimeStamp, Size}) -> - Size + NLen - end, - HistoryList), - calc_shift(MaxSize, Sizes). - -calc_shift(MaxSize, Sizes) -> - Total = lists:sum(Sizes), - calc_shift(MaxSize, Total, 0, Sizes). - -calc_shift(_MaxSize, _Size, Shift, []) -> Shift; -calc_shift(MaxSize, Size, Shift, [S | TSizes]) -> - if MaxSize >= Size -> Shift; - true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes) +-spec get_history(binary(), stanza(), state()) -> lqueue(). +get_history(Nick, Packet, #state{history = History}) -> + case xmpp:get_subtag(Packet, #muc{}) of + #muc{history = #muc_history{} = MUCHistory} -> + Now = p1_time_compat:timestamp(), + Q = History#lqueue.queue, + {NewQ, Len} = filter_history(Q, MUCHistory, Now, Nick, queue:new(), 0, 0), + History#lqueue{queue = NewQ, len = Len}; + _ -> + History end. -extract_history([], _Type) -> false; -extract_history([#xmlel{attrs = Attrs} = El | Els], - Type) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC -> - AttrVal = fxml:get_path_s(El, - [{elem, <<"history">>}, {attr, Type}]), - case Type of - <<"since">> -> - case jlib:datetime_string_to_timestamp(AttrVal) of - undefined -> false; - TS -> calendar:now_to_universal_time(TS) - end; - _ -> - case catch jlib:binary_to_integer(AttrVal) of - IntVal when is_integer(IntVal) and (IntVal >= 0) -> - IntVal; - _ -> false - end - end; - _ -> extract_history(Els, Type) - end; -extract_history([_ | Els], Type) -> - extract_history(Els, Type). +-spec filter_history(?TQUEUE, muc_history(), erlang:timestamp(), binary(), + ?TQUEUE, non_neg_integer(), non_neg_integer()) -> + {?TQUEUE, non_neg_integer()}. +filter_history(Queue, #muc_history{since = Since, + seconds = Seconds, + maxstanzas = MaxStanzas, + maxchars = MaxChars} = MUC, + Now, Nick, AccQueue, NumStanzas, NumChars) -> + case queue:out_r(Queue) of + {{value, {_, _, _, TimeStamp, Size} = Elem}, NewQueue} -> + NowDiff = timer:now_diff(Now, TimeStamp) div 1000000, + Chars = Size + byte_size(Nick) + 1, + if (NumStanzas < MaxStanzas) andalso + (TimeStamp > Since) andalso + (NowDiff =< Seconds) andalso + (NumChars + Chars =< MaxChars) -> + filter_history(NewQueue, MUC, Now, Nick, + queue:in_r(Elem, AccQueue), + NumStanzas + 1, + NumChars + Chars); + true -> + {AccQueue, NumStanzas} + end; + {empty, _} -> + {AccQueue, NumStanzas} + end. +-spec is_room_overcrowded(state()) -> boolean(). is_room_overcrowded(StateData) -> MaxUsersPresence = gen_mod:get_module_opt(StateData#state.server_host, mod_muc, max_users_presence, @@ -2278,22 +2078,27 @@ is_room_overcrowded(StateData) -> ?DEFAULT_MAX_USERS_PRESENCE), (?DICT):size(StateData#state.users) > MaxUsersPresence. +-spec presence_broadcast_allowed(jid(), state()) -> boolean(). presence_broadcast_allowed(JID, StateData) -> Role = get_role(JID, StateData), lists:member(Role, (StateData#state.config)#config.presence_broadcast). +-spec send_initial_presence(jid(), state(), state()) -> ok. send_initial_presence(NJID, StateData, OldStateData) -> send_new_presence1(NJID, <<"">>, true, StateData, OldStateData). +-spec send_update_presence(jid(), state(), state()) -> ok. send_update_presence(JID, StateData, OldStateData) -> send_update_presence(JID, <<"">>, StateData, OldStateData). +-spec send_update_presence(jid(), binary(), state(), state()) -> ok. send_update_presence(JID, Reason, StateData, OldStateData) -> case is_room_overcrowded(StateData) of true -> ok; false -> send_update_presence1(JID, Reason, StateData, OldStateData) end. +-spec send_update_presence1(jid(), binary(), state(), state()) -> ok. send_update_presence1(JID, Reason, StateData, OldStateData) -> LJID = jid:tolower(JID), LJIDs = case LJID of @@ -2317,12 +2122,15 @@ send_update_presence1(JID, Reason, StateData, OldStateData) -> end, LJIDs). +-spec send_new_presence(jid(), state(), state()) -> ok. send_new_presence(NJID, StateData, OldStateData) -> send_new_presence(NJID, <<"">>, false, StateData, OldStateData). +-spec send_new_presence(jid(), binary(), state(), state()) -> ok. send_new_presence(NJID, Reason, StateData, OldStateData) -> send_new_presence(NJID, Reason, false, StateData, OldStateData). +-spec send_new_presence(jid(), binary(), boolean(), state(), state()) -> ok. send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> case is_room_overcrowded(StateData) of true -> ok; @@ -2330,6 +2138,7 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> OldStateData) end. +-spec is_ra_changed(jid() | ljid(), boolean(), state(), state()) -> boolean(). is_ra_changed(_, _IsInitialPresence = true, _, _) -> false; is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) -> @@ -2348,6 +2157,7 @@ is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) -> (NewRole /= OldRole) or (NewAff /= OldAff) end. +-spec send_new_presence1(jid(), binary(), boolean(), state(), state()) -> ok. send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> LNJID = jid:tolower(NJID), #user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users), @@ -2360,15 +2170,9 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> {Role1, Presence1} = case presence_broadcast_allowed(NJID, StateData) of true -> {Role0, Presence0}; - false -> - {none, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = []} - } + false -> {none, #presence{type = unavailable}} end, Affiliation = get_affiliation(LJID, StateData), - SAffiliation = affiliation_to_list(Affiliation), UserList = case not (presence_broadcast_allowed(NJID, StateData) orelse presence_broadcast_allowed(NJID, OldStateData)) of @@ -2379,62 +2183,38 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> end, lists:foreach( fun({LUJID, Info}) -> - {Role, Presence} = if LNJID == LUJID -> {Role0, Presence0}; + IsSelfPresence = LNJID == LUJID, + {Role, Presence} = if IsSelfPresence -> {Role0, Presence0}; true -> {Role1, Presence1} end, - SRole = role_to_list(Role), - ItemAttrs = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jid:to_string(RealJID)}, - {<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}]; - _ -> - [{<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}] - end, - ItemEls = case Reason of - <<"">> -> []; - _ -> - [#xmlel{name = <<"reason">>, - attrs = [], - children = - [{xmlcdata, Reason}]}] - end, - StatusEls = status_els(IsInitialPresence, NJID, Info, - StateData), - Pres = if Presence == undefined -> #xmlel{name = <<"presence">>}; + Item0 = #muc_item{affiliation = Affiliation, + role = Role}, + Item1 = case Info#user.role == moderator orelse + (StateData#state.config)#config.anonymous + == false orelse IsSelfPresence of + true -> Item0#muc_item{jid = RealJID}; + false -> Item0 + end, + Item = Item1#muc_item{reason = Reason}, + StatusCodes = status_codes(IsInitialPresence, IsSelfPresence, + StateData), + Pres = if Presence == undefined -> #presence{}; true -> Presence end, - Packet = fxml:append_subtags(Pres, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs - = - ItemAttrs, - children - = - ItemEls} - | StatusEls]}]), + Packet = xmpp:set_subtag( + Pres, #muc_user{items = [Item], + status_codes = StatusCodes}), Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of true -> ?NS_MUCSUB_NODES_AFFILIATIONS; false -> ?NS_MUCSUB_NODES_PRESENCE end, send_wrapped(jid:replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet, Node1, StateData), - Type = fxml:get_tag_attr_s(<<"type">>, Packet), + Type = xmpp:get_type(Packet), IsSubscriber = is_subscriber(Info#user.jid, StateData), IsOccupant = Info#user.last_presence /= undefined, if (IsSubscriber and not IsOccupant) and - (IsInitialPresence or (Type == <<"unavailable">>)) -> + (IsInitialPresence or (Type == unavailable)) -> Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS, send_wrapped(jid:replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet, Node2, StateData); @@ -2444,12 +2224,14 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> end, UserList). +-spec send_existing_presences(jid(), state()) -> ok. send_existing_presences(ToJID, StateData) -> case is_room_overcrowded(StateData) of true -> ok; false -> send_existing_presences1(ToJID, StateData) end. +-spec send_existing_presences1(jid(), state()) -> ok. send_existing_presences1(ToJID, StateData) -> LToJID = jid:tolower(ToJID), {ok, #user{jid = RealToJID, role = Role}} = @@ -2469,46 +2251,23 @@ send_existing_presences1(ToJID, StateData) -> {_, false} -> ok; _ -> FromAffiliation = get_affiliation(LJID, StateData), - ItemAttrs = case Role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jid:to_string(FromJID)}, - {<<"affiliation">>, - affiliation_to_list(FromAffiliation)}, - {<<"role">>, - role_to_list(FromRole)}]; - _ -> - [{<<"affiliation">>, - affiliation_to_list(FromAffiliation)}, - {<<"role">>, - role_to_list(FromRole)}] - end, - Packet = fxml:append_subtags( - Presence, - [#xmlel{name = - <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name - = - <<"item">>, - attrs - = - ItemAttrs, - children - = - []}]}]), + Item0 = #muc_item{affiliation = FromAffiliation, + role = FromRole}, + Item = case Role == moderator orelse + (StateData#state.config)#config.anonymous + == false of + true -> Item0#muc_item{jid = FromJID}; + false -> Item0 + end, + Packet = xmpp:set_subtag( + Presence, #muc_user{items = [Item]}), send_wrapped(jid:replace_resource(StateData#state.jid, FromNick), RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData) end end, (?DICT):to_list(StateData#state.nicks)). +-spec set_nick(jid(), binary(), state()) -> state(). set_nick(JID, Nick, State) -> LJID = jid:tolower(JID), {ok, #user{nick = OldNick}} = (?DICT):find(LJID, State#state.users), @@ -2531,6 +2290,7 @@ set_nick(JID, Nick, State) -> end, State#state{users = Users, nicks = Nicks}. +-spec change_nick(jid(), binary(), state()) -> state(). change_nick(JID, Nick, StateData) -> LJID = jid:tolower(JID), {ok, #user{nick = OldNick}} = (?DICT):find(LJID, StateData#state.users), @@ -2551,6 +2311,7 @@ change_nick(JID, Nick, StateData) -> add_to_log(nickchange, {OldNick, Nick}, StateData), NewStateData. +-spec send_nick_changing(jid(), binary(), state(), boolean(), boolean()) -> ok. send_nick_changing(JID, OldNick, StateData, SendOldUnavailable, SendNewAvailable) -> {ok, @@ -2559,104 +2320,48 @@ send_nick_changing(JID, OldNick, StateData, (?DICT):find(jid:tolower(JID), StateData#state.users), Affiliation = get_affiliation(JID, StateData), - SAffiliation = affiliation_to_list(Affiliation), - SRole = role_to_list(Role), - lists:foreach(fun ({_LJID, Info}) when Presence /= undefined -> - ItemAttrs1 = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jid:to_string(RealJID)}, - {<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}, - {<<"nick">>, Nick}]; - _ -> - [{<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}, - {<<"nick">>, Nick}] - end, - ItemAttrs2 = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jid:to_string(RealJID)}, - {<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}]; - _ -> - [{<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}] - end, - Status110 = case JID == Info#user.jid of - true -> - [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"110">>}] - }]; - false -> - [] - end, - Packet1 = #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs = - ItemAttrs1, - children = - []}, - #xmlel{name = - <<"status">>, - attrs = - [{<<"code">>, - <<"303">>}], - children = - []}|Status110]}]}, - Packet2 = fxml:append_subtags(Presence, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name - = - <<"item">>, - attrs - = - ItemAttrs2, - children - = - []}|Status110]}]), - if SendOldUnavailable -> - send_wrapped(jid:replace_resource(StateData#state.jid, - OldNick), - Info#user.jid, Packet1, - ?NS_MUCSUB_NODES_PRESENCE, - StateData); - true -> ok + lists:foreach( + fun({LJID, Info}) when Presence /= undefined -> + IsSelfPresence = LJID == jid:tolower(JID), + Item0 = #muc_item{affiliation = Affiliation, role = Role}, + Item = case Info#user.role == moderator orelse + (StateData#state.config)#config.anonymous + == false orelse IsSelfPresence of + true -> Item0#muc_item{jid = RealJID}; + false -> Item0 + end, + Status110 = case IsSelfPresence of + true -> [110]; + false -> [] end, - if SendNewAvailable -> - send_wrapped(jid:replace_resource(StateData#state.jid, - Nick), - Info#user.jid, Packet2, - ?NS_MUCSUB_NODES_PRESENCE, - StateData); - true -> ok - end; - (_) -> - ok - end, + Packet1 = #presence{ + type = unavailable, + sub_els = [#muc_user{ + items = [Item#muc_item{nick = Nick}], + status_codes = [303|Status110]}]}, + Packet2 = xmpp:set_subtag(Presence, + #muc_user{items = [Item], + status_codes = Status110}), + if SendOldUnavailable -> + send_wrapped( + jid:replace_resource(StateData#state.jid, OldNick), + Info#user.jid, Packet1, ?NS_MUCSUB_NODES_PRESENCE, + StateData); + true -> ok + end, + if SendNewAvailable -> + send_wrapped( + jid:replace_resource(StateData#state.jid, Nick), + Info#user.jid, Packet2, ?NS_MUCSUB_NODES_PRESENCE, + StateData); + true -> ok + end; + (_) -> + ok + end, ?DICT:to_list(get_users_and_subscribers(StateData))). +-spec maybe_send_affiliation(jid(), affiliation(), state()) -> ok. maybe_send_affiliation(JID, Affiliation, StateData) -> LJID = jid:tolower(JID), Users = get_users_and_subscribers(StateData), @@ -2674,21 +2379,16 @@ maybe_send_affiliation(JID, Affiliation, StateData) -> true -> ok; % The new affiliation is published via presence. false -> - send_affiliation(LJID, Affiliation, StateData) + send_affiliation(JID, Affiliation, StateData) end. -send_affiliation(LJID, Affiliation, StateData) -> - ItemAttrs = [{<<"jid">>, jid:to_string(LJID)}, - {<<"affiliation">>, affiliation_to_list(Affiliation)}, - {<<"role">>, <<"none">>}], - Message = #xmlel{name = <<"message">>, - attrs = [{<<"id">>, randoms:get_string()}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = ItemAttrs}]}]}, +-spec send_affiliation(jid(), affiliation(), state()) -> ok. +send_affiliation(JID, Affiliation, StateData) -> + Item = #muc_item{jid = JID, + affiliation = Affiliation, + role = none}, + Message = #message{id = randoms:get_string(), + sub_els = [#muc_user{items = [Item]}]}, Users = get_users_and_subscribers(StateData), Recipients = case (StateData#state.config)#config.anonymous of true -> @@ -2703,43 +2403,33 @@ send_affiliation(LJID, Affiliation, StateData) -> send_wrapped_multiple(StateData#state.jid, Recipients, Message, ?NS_MUCSUB_NODES_AFFILIATIONS, StateData). -status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) -> - Status = case IsInitialPresence of - true -> - S1 = case StateData#state.just_created of - true -> - [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"201">>}], - children = []}]; - false -> [] - end, - S2 = case (StateData#state.config)#config.anonymous of - true -> S1; - false -> - [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"100">>}], - children = []} | S1] - end, - S3 = case (StateData#state.config)#config.logging of - true -> - [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"170">>}], - children = []} | S2]; - false -> S2 - end, - S3; - false -> [] - end, - [#xmlel{name = <<"status">>, - attrs = - [{<<"code">>, - <<"110">>}], - children = []} | Status]; -status_els(_IsInitialPresence, _JID, _Info, _StateData) -> []. +-spec status_codes(boolean(), boolean(), state()) -> [pos_integer()]. +status_codes(IsInitialPresence, _IsSelfPresence = true, StateData) -> + S0 = [110], + case IsInitialPresence of + true -> + S1 = case StateData#state.just_created of + true -> [201|S0]; + false -> S0 + end, + S2 = case (StateData#state.config)#config.anonymous of + true -> S1; + false -> [100|S1] + end, + S3 = case (StateData#state.config)#config.logging of + true -> [170|S2]; + false -> S2 + end, + S3; + false -> S0 + end; +status_codes(_IsInitialPresence, _IsSelfPresence = false, _StateData) -> []. +-spec lqueue_new(non_neg_integer()) -> lqueue(). lqueue_new(Max) -> #lqueue{queue = queue:new(), len = 0, max = Max}. +-spec lqueue_in(term(), lqueue()) -> lqueue(). %% If the message queue limit is set to 0, do not store messages. lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ; %% Otherwise, rotate messages in the queue store. @@ -2752,76 +2442,71 @@ lqueue_in(Item, true -> #lqueue{queue = Q2, len = Len + 1, max = Max} end. +-spec lqueue_cut(queue:queue(), non_neg_integer()) -> queue:queue(). lqueue_cut(Q, 0) -> Q; lqueue_cut(Q, N) -> {_, Q1} = queue:out(Q), lqueue_cut(Q1, N - 1). +-spec lqueue_to_list(lqueue()) -> list(). lqueue_to_list(#lqueue{queue = Q1}) -> queue:to_list(Q1). - +-spec add_message_to_history(binary(), jid(), message(), state()) -> state(). add_message_to_history(FromNick, FromJID, Packet, StateData) -> - HaveSubject = case fxml:get_subtag(Packet, <<"subject">>) - of - false -> false; - _ -> true - end, - TimeStamp = p1_time_compat:timestamp(), - AddrPacket = case (StateData#state.config)#config.anonymous of - true -> Packet; - false -> - Address = #xmlel{name = <<"address">>, - attrs = [{<<"type">>, <<"ofrom">>}, - {<<"jid">>, - jid:to_string(FromJID)}], - children = []}, - Addresses = #xmlel{name = <<"addresses">>, - attrs = [{<<"xmlns">>, ?NS_ADDRESS}], - children = [Address]}, - fxml:append_subtags(Packet, [Addresses]) - end, - TSPacket = jlib:add_delay_info(AddrPacket, StateData#state.jid, TimeStamp), - SPacket = - jlib:replace_from_to(jid:replace_resource(StateData#state.jid, - FromNick), - StateData#state.jid, TSPacket), - Size = element_size(SPacket), - Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, - calendar:now_to_universal_time(TimeStamp), Size}, - StateData#state.history), add_to_log(text, {FromNick, Packet}, StateData), - StateData#state{history = Q1}. + case check_subject(Packet) of + false -> + TimeStamp = p1_time_compat:timestamp(), + AddrPacket = case (StateData#state.config)#config.anonymous of + true -> Packet; + false -> + Addresses = #addresses{ + list = [#address{type = ofrom, + jid = FromJID}]}, + xmpp:set_subtag(Packet, Addresses) + end, + TSPacket = xmpp_util:add_delay_info( + AddrPacket, StateData#state.jid, TimeStamp), + SPacket = xmpp:set_from_to( + TSPacket, + jid:replace_resource(StateData#state.jid, FromNick), + StateData#state.jid), + Size = element_size(SPacket), + Q1 = lqueue_in({FromNick, TSPacket, false, + TimeStamp, Size}, + StateData#state.history), + StateData#state{history = Q1}; + _ -> + StateData + end. -send_history(JID, Shift, StateData) -> - lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp, - _Size}, - B) -> - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - Nick), - JID, Packet), - B or HaveSubject - end, - false, - lists:nthtail(Shift, - lqueue_to_list(StateData#state.history))). +-spec send_history(jid(), lqueue(), state()) -> boolean(). +send_history(JID, History, StateData) -> + lists:foreach( + fun({Nick, Packet, _HaveSubject, _TimeStamp, _Size}) -> + ejabberd_router:route( + jid:replace_resource(StateData#state.jid, Nick), + JID, Packet) + end, lqueue_to_list(History)). -send_subject(_JID, #state{subject_author = <<"">>}) -> ok; +-spec send_subject(jid(), state()) -> ok. send_subject(JID, #state{subject_author = Nick} = StateData) -> - Subject = StateData#state.subject, - Packet = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"subject">>, attrs = [], - children = [{xmlcdata, Subject}]}]}, + Subject = case StateData#state.subject of + <<"">> -> [#text{}]; + Subj -> xmpp:mk_text(Subj) + end, + Packet = #message{type = groupchat, subject = Subject}, ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), JID, Packet). -check_subject(Packet) -> - case fxml:get_subtag(Packet, <<"subject">>) of - false -> false; - SubjEl -> fxml:get_tag_cdata(SubjEl) - end. +-spec check_subject(message()) -> false | binary(). +check_subject(#message{subject = [_|_] = Subj, body = [], + thread = undefined}) -> + xmpp:get_text(Subj); +check_subject(_) -> + false. +-spec can_change_subject(role(), boolean(), state()) -> boolean(). can_change_subject(Role, IsSubscriber, StateData) -> case (StateData#state.config)#config.allow_change_subj of @@ -2832,99 +2517,85 @@ can_change_subject(Role, IsSubscriber, StateData) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Admin stuff -process_iq_admin(From, set, Lang, SubEl, StateData) -> - #xmlel{children = Items} = SubEl, +-spec process_iq_admin(jid(), iq(), #state{}) -> {error, stanza_error()} | + {result, undefined, #state{}} | + {result, muc_admin()}. +process_iq_admin(_From, #iq{lang = Lang, sub_els = [#muc_admin{items = []}]}, + _StateData) -> + Txt = <<"No 'item' element found">>, + {error, xmpp:err_bad_request(Txt, Lang)}; +process_iq_admin(From, #iq{type = set, lang = Lang, + sub_els = [#muc_admin{items = Items}]}, + StateData) -> process_admin_items_set(From, Items, Lang, StateData); -process_iq_admin(From, get, Lang, SubEl, StateData) -> - case fxml:get_subtag(SubEl, <<"item">>) of - false -> - Txt = <<"No 'item' element found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - Item -> - FAffiliation = get_affiliation(From, StateData), - FRole = get_role(From, StateData), - case fxml:get_tag_attr(<<"role">>, Item) of - false -> - case fxml:get_tag_attr(<<"affiliation">>, Item) of - false -> - Txt = <<"No 'affiliation' attribute found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; - SAffiliation -> - if (FAffiliation == owner) or - (FAffiliation == admin) or - ((FAffiliation == member) and not - (StateData#state.config)#config.anonymous) -> - Items = items_with_affiliation(SAffiliation, - StateData), - {result, Items, StateData}; - true -> - ErrText = - <<"Administrator privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} - end - end - end; - {value, StrRole} -> - case catch list_to_role(StrRole) of - {'EXIT', _} -> - Txt = <<"Incorrect value of 'role' attribute">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - SRole -> - if FRole == moderator -> - Items = items_with_role(SRole, StateData), - {result, Items, StateData}; - true -> - ErrText = <<"Moderator privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} - end - end - end - end. +process_iq_admin(From, #iq{type = get, lang = Lang, + sub_els = [#muc_admin{items = [Item]}]}, + StateData) -> + FAffiliation = get_affiliation(From, StateData), + FRole = get_role(From, StateData), + case Item of + #muc_item{role = undefined, affiliation = undefined} -> + Txt = <<"Neither 'role' nor 'affiliation' attribute found">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + #muc_item{role = undefined, affiliation = Affiliation} -> + if (FAffiliation == owner) or + (FAffiliation == admin) or + ((FAffiliation == member) and + not (StateData#state.config)#config.anonymous) -> + Items = items_with_affiliation(Affiliation, StateData), + {result, #muc_admin{items = Items}}; + true -> + ErrText = <<"Administrator privileges required">>, + {error, xmpp:err_forbidden(ErrText, Lang)} + end; + #muc_item{role = Role} -> + if FRole == moderator -> + Items = items_with_role(Role, StateData), + {result, #muc_admin{items = Items}}; + true -> + ErrText = <<"Moderator privileges required">>, + {error, xmpp:err_forbidden(ErrText, Lang)} + end + end; +process_iq_admin(_From, #iq{type = get, lang = Lang}, _StateData) -> + ErrText = <<"Too many elements">>, + {error, xmpp:err_bad_request(ErrText, Lang)}. +-spec items_with_role(role(), state()) -> [muc_item()]. items_with_role(SRole, StateData) -> lists:map(fun ({_, U}) -> user_to_item(U, StateData) end, search_role(SRole, StateData)). +-spec items_with_affiliation(affiliation(), state()) -> [muc_item()]. items_with_affiliation(SAffiliation, StateData) -> - lists:map(fun ({JID, {Affiliation, Reason}}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - affiliation_to_list(Affiliation)}, - {<<"jid">>, jid:to_string(JID)}], - children = - [#xmlel{name = <<"reason">>, attrs = [], - children = [{xmlcdata, Reason}]}]}; - ({JID, Affiliation}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - affiliation_to_list(Affiliation)}, - {<<"jid">>, jid:to_string(JID)}], - children = []} - end, - search_affiliation(SAffiliation, StateData)). + lists:map( + fun({JID, {Affiliation, Reason}}) -> + #muc_item{affiliation = Affiliation, jid = jid:make(JID), + reason = Reason}; + ({JID, Affiliation}) -> + #muc_item{affiliation = Affiliation, jid = jid:make(JID)} + end, + search_affiliation(SAffiliation, StateData)). +-spec user_to_item(#user{}, state()) -> muc_item(). user_to_item(#user{role = Role, nick = Nick, jid = JID}, StateData) -> Affiliation = get_affiliation(JID, StateData), - #xmlel{name = <<"item">>, - attrs = - [{<<"role">>, role_to_list(Role)}, - {<<"affiliation">>, affiliation_to_list(Affiliation)}, - {<<"nick">>, Nick}, - {<<"jid">>, jid:to_string(JID)}], - children = []}. + #muc_item{role = Role, + affiliation = Affiliation, + nick = Nick, + jid = JID}. +-spec search_role(role(), state()) -> [{ljid(), #user{}}]. search_role(Role, StateData) -> lists:filter(fun ({_, #user{role = R}}) -> Role == R end, (?DICT):to_list(StateData#state.users)). +-spec search_affiliation(affiliation(), state()) -> + [{ljid(), + affiliation() | {affiliation(), binary()}}]. search_affiliation(Affiliation, StateData) -> lists:filter(fun ({_, A}) -> case A of @@ -2934,263 +2605,183 @@ search_affiliation(Affiliation, StateData) -> end, (?DICT):to_list(StateData#state.affiliations)). +-spec process_admin_items_set(jid(), [muc_item()], binary(), + #state{}) -> {result, undefined, #state{}} | + {error, stanza_error()}. process_admin_items_set(UJID, Items, Lang, StateData) -> UAffiliation = get_affiliation(UJID, StateData), URole = get_role(UJID, StateData), - case find_changed_items(UJID, UAffiliation, URole, - Items, Lang, StateData, []) + case catch find_changed_items(UJID, UAffiliation, URole, + Items, Lang, StateData, []) of {result, Res} -> ?INFO_MSG("Processing MUC admin query from ~s in " "room ~s:~n ~p", [jid:to_string(UJID), jid:to_string(StateData#state.jid), Res]), - NSD = lists:foldl(process_item_change(UJID), - StateData, lists:flatten(Res)), - store_room(NSD), - {result, [], NSD}; - Err -> Err + case lists:foldl(process_item_change(UJID), + StateData, lists:flatten(Res)) of + {error, _} = Err -> + Err; + NSD -> + store_room(NSD), + {result, undefined, NSD} + end; + {error, Err} -> {error, Err} end. +-spec process_item_change(jid()) -> function(). process_item_change(UJID) -> - fun(E, SD) -> - process_item_change(E, SD, UJID) + fun(_, {error, _} = Err) -> + Err; + (Item, SD) -> + process_item_change(Item, SD, UJID) end. -process_item_change(E, SD, UJID) -> - case catch case E of - {JID, affiliation, owner, _} when JID#jid.luser == <<"">> -> - %% If the provided JID does not have username, - %% forget the affiliation completely - SD; - {JID, role, none, Reason} -> - catch - send_kickban_presence(UJID, JID, - Reason, - <<"307">>, - SD), - set_role(JID, none, SD); - {JID, affiliation, none, Reason} -> - case (SD#state.config)#config.members_only of - true -> - catch - send_kickban_presence(UJID, JID, - Reason, - <<"321">>, - none, - SD), - maybe_send_affiliation(JID, none, SD), - SD1 = set_affiliation(JID, none, SD), - set_role(JID, none, SD1); - _ -> - SD1 = set_affiliation(JID, none, SD), - send_update_presence(JID, SD1, SD), - maybe_send_affiliation(JID, none, SD1), - SD1 - end; - {JID, affiliation, outcast, Reason} -> - catch - send_kickban_presence(UJID, JID, - Reason, - <<"301">>, - outcast, - SD), - maybe_send_affiliation(JID, outcast, SD), - set_affiliation(JID, - outcast, - set_role(JID, none, SD), - Reason); - {JID, affiliation, A, Reason} - when (A == admin) or (A == owner) -> - SD1 = set_affiliation(JID, A, SD, Reason), - SD2 = set_role(JID, moderator, SD1), - send_update_presence(JID, Reason, SD2, SD), - maybe_send_affiliation(JID, A, SD2), - SD2; - {JID, affiliation, member, Reason} -> - SD1 = set_affiliation(JID, member, SD, Reason), - SD2 = set_role(JID, participant, SD1), - send_update_presence(JID, Reason, SD2, SD), - maybe_send_affiliation(JID, member, SD2), - SD2; - {JID, role, Role, Reason} -> - SD1 = set_role(JID, Role, SD), - catch - send_new_presence(JID, Reason, SD1, SD), - SD1; - {JID, affiliation, A, _Reason} -> - SD1 = set_affiliation(JID, A, SD), - send_update_presence(JID, SD1, SD), - maybe_send_affiliation(JID, A, SD1), - SD1 - end - of - {'EXIT', ErrReason} -> - ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n", [ErrReason]), - SD; - NSD -> NSD +-type admin_action() :: {jid(), affiliation | role, + affiliation() | role(), binary()}. + +-spec process_item_change(admin_action(), state(), jid()) -> state() | {error, stanza_error()}. +process_item_change(Item, SD, UJID) -> + try case Item of + {JID, affiliation, owner, _} when JID#jid.luser == <<"">> -> + %% If the provided JID does not have username, + %% forget the affiliation completely + SD; + {JID, role, none, Reason} -> + send_kickban_presence(UJID, JID, Reason, 307, SD), + set_role(JID, none, SD); + {JID, affiliation, none, Reason} -> + case (SD#state.config)#config.members_only of + true -> + send_kickban_presence(UJID, JID, Reason, 321, none, SD), + maybe_send_affiliation(JID, none, SD), + SD1 = set_affiliation(JID, none, SD), + set_role(JID, none, SD1); + _ -> + SD1 = set_affiliation(JID, none, SD), + send_update_presence(JID, Reason, SD1, SD), + maybe_send_affiliation(JID, none, SD1), + SD1 + end; + {JID, affiliation, outcast, Reason} -> + send_kickban_presence(UJID, JID, Reason, 301, outcast, SD), + maybe_send_affiliation(JID, outcast, SD), + set_affiliation(JID, outcast, set_role(JID, none, SD), Reason); + {JID, affiliation, A, Reason} when (A == admin) or (A == owner) -> + SD1 = set_affiliation(JID, A, SD, Reason), + SD2 = set_role(JID, moderator, SD1), + send_update_presence(JID, Reason, SD2, SD), + maybe_send_affiliation(JID, A, SD2), + SD2; + {JID, affiliation, member, Reason} -> + SD1 = set_affiliation(JID, member, SD, Reason), + SD2 = set_role(JID, participant, SD1), + send_update_presence(JID, Reason, SD2, SD), + maybe_send_affiliation(JID, member, SD2), + SD2; + {JID, role, Role, Reason} -> + SD1 = set_role(JID, Role, SD), + send_new_presence(JID, Reason, SD1, SD), + SD1; + {JID, affiliation, A, _Reason} -> + SD1 = set_affiliation(JID, A, SD), + send_update_presence(JID, SD1, SD), + maybe_send_affiliation(JID, A, SD1), + SD1 + end + catch E:R -> + ?ERROR_MSG("failed to set item ~p from ~s: ~p", + [Item, jid:to_string(UJID), + {E, {R, erlang:get_stacktrace()}}]), + {error, xmpp:err_internal_server_error()} end. +-spec find_changed_items(jid(), affiliation(), role(), + [muc_item()], binary(), state(), [admin_action()]) -> + {result, [admin_action()]}. find_changed_items(_UJID, _UAffiliation, _URole, [], _Lang, _StateData, Res) -> {result, Res}; +find_changed_items(_UJID, _UAffiliation, _URole, + [#muc_item{jid = undefined, nick = undefined}|_], + Lang, _StateData, _Res) -> + Txt = <<"Neither 'jid' nor 'nick' attribute found">>, + throw({error, xmpp:err_bad_request(Txt, Lang)}); +find_changed_items(_UJID, _UAffiliation, _URole, + [#muc_item{role = undefined, affiliation = undefined}|_], + Lang, _StateData, _Res) -> + Txt = <<"Neither 'role' nor 'affiliation' attribute found">>, + throw({error, xmpp:err_bad_request(Txt, Lang)}); find_changed_items(UJID, UAffiliation, URole, - [{xmlcdata, _} | Items], Lang, StateData, Res) -> - find_changed_items(UJID, UAffiliation, URole, Items, - Lang, StateData, Res); -find_changed_items(UJID, UAffiliation, URole, - [#xmlel{name = <<"item">>, attrs = Attrs} = Item - | Items], + [#muc_item{jid = J, nick = Nick, reason = Reason, + role = Role, affiliation = Affiliation}|Items], Lang, StateData, Res) -> - TJID = case fxml:get_attr(<<"jid">>, Attrs) of - {value, S} -> - case jid:from_string(S) of - error -> - ErrText = iolist_to_binary( - io_lib:format(translate:translate( - Lang, - <<"Jabber ID ~s is invalid">>), - [S])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - J -> {value, [J]} - end; - _ -> - case fxml:get_attr(<<"nick">>, Attrs) of - {value, N} -> - case find_jids_by_nick(N, StateData) of - false -> - ErrText = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Nickname ~s does not exist in the room">>), - [N])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - J -> {value, J} - end; - _ -> - Txt1 = <<"No 'nick' attribute found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt1)} - end - end, - case TJID of - {value, [JID | _] = JIDs} -> - TAffiliation = get_affiliation(JID, StateData), - TRole = get_role(JID, StateData), - case fxml:get_attr(<<"role">>, Attrs) of - false -> - case fxml:get_attr(<<"affiliation">>, Attrs) of - false -> - Txt2 = <<"No 'affiliation' attribute found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt2)}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> - ErrText1 = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Invalid affiliation: ~s">>), - [StrAffiliation])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)}; - SAffiliation -> - ServiceAf = get_service_affiliation(JID, StateData), - CanChangeRA = case can_change_ra(UAffiliation, - URole, - TAffiliation, - TRole, affiliation, - SAffiliation, - ServiceAf) - of - nothing -> nothing; - true -> true; - check_owner -> - case search_affiliation(owner, - StateData) - of - [{OJID, _}] -> - jid:remove_resource(OJID) - /= - jid:tolower(jid:remove_resource(UJID)); - _ -> true - end; - _ -> false - end, - case CanChangeRA of - nothing -> - find_changed_items(UJID, UAffiliation, URole, - Items, Lang, StateData, - Res); - true -> - Reason = fxml:get_path_s(Item, - [{elem, <<"reason">>}, - cdata]), - MoreRes = [{jid:remove_resource(Jidx), - affiliation, SAffiliation, Reason} - || Jidx <- JIDs], - find_changed_items(UJID, UAffiliation, URole, - Items, Lang, StateData, - [MoreRes | Res]); - false -> - Txt3 = <<"Changing role/affiliation is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt3)} - end - end - end; - {value, StrRole} -> - case catch list_to_role(StrRole) of - {'EXIT', _} -> - ErrText1 = iolist_to_binary( - io_lib:format(translate:translate( - Lang, - <<"Invalid role: ~s">>), - [StrRole])), - {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)}; - SRole -> - ServiceAf = get_service_affiliation(JID, StateData), - CanChangeRA = case can_change_ra(UAffiliation, URole, - TAffiliation, TRole, - role, SRole, ServiceAf) - of - nothing -> nothing; - true -> true; - check_owner -> - case search_affiliation(owner, - StateData) - of - [{OJID, _}] -> - jid:remove_resource(OJID) - /= - jid:tolower(jid:remove_resource(UJID)); - _ -> true - end; - _ -> false - end, - case CanChangeRA of - nothing -> - find_changed_items(UJID, UAffiliation, URole, Items, - Lang, StateData, Res); - true -> - Reason = fxml:get_path_s(Item, - [{elem, <<"reason">>}, - cdata]), - MoreRes = [{Jidx, role, SRole, Reason} - || Jidx <- JIDs], - find_changed_items(UJID, UAffiliation, URole, Items, - Lang, StateData, - [MoreRes | Res]); - _ -> - Txt4 = <<"Changing role/affiliation is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt4)} - end + [JID | _] = JIDs = + if J /= undefined -> + [J]; + Nick /= <<"">> -> + case find_jids_by_nick(Nick, StateData) of + [] -> + ErrText = {<<"Nickname ~s does not exist in the room">>, + [Nick]}, + throw({error, xmpp:err_not_acceptable(ErrText, Lang)}); + JIDList -> + JIDList end - end; - Err -> Err - end; -find_changed_items(_UJID, _UAffiliation, _URole, _Items, - _Lang, _StateData, _Res) -> - {error, ?ERR_BAD_REQUEST}. + end, + {RoleOrAff, RoleOrAffValue} = if Role == undefined -> + {affiliation, Affiliation}; + true -> + {role, Role} + end, + TAffiliation = get_affiliation(JID, StateData), + TRole = get_role(JID, StateData), + ServiceAf = get_service_affiliation(JID, StateData), + CanChangeRA = case can_change_ra(UAffiliation, + URole, + TAffiliation, + TRole, RoleOrAff, RoleOrAffValue, + ServiceAf) of + nothing -> nothing; + true -> true; + check_owner -> + case search_affiliation(owner, StateData) of + [{OJID, _}] -> + jid:remove_resource(OJID) + /= + jid:tolower(jid:remove_resource(UJID)); + _ -> true + end; + _ -> false + end, + case CanChangeRA of + nothing -> + find_changed_items(UJID, UAffiliation, URole, + Items, Lang, StateData, + Res); + true -> + MoreRes = case RoleOrAff of + affiliation -> + [{jid:remove_resource(Jidx), + RoleOrAff, RoleOrAffValue, Reason} + || Jidx <- JIDs]; + role -> + [{Jidx, RoleOrAff, RoleOrAffValue, Reason} + || Jidx <- JIDs] + end, + find_changed_items(UJID, UAffiliation, URole, + Items, Lang, StateData, + [MoreRes | Res]); + false -> + Txt = <<"Changing role/affiliation is not allowed">>, + throw({error, xmpp:err_not_allowed(Txt, Lang)}) + end. +-spec can_change_ra(affiliation(), role(), affiliation(), role(), + affiliation, affiliation(), affiliation()) -> boolean(); + (affiliation(), role(), affiliation(), role(), + role, role(), affiliation()) -> boolean(). can_change_ra(_FAffiliation, _FRole, owner, _TRole, affiliation, owner, owner) -> %% A room owner tries to add as persistent owner a @@ -3315,11 +2906,15 @@ can_change_ra(_FAffiliation, _FRole, _TAffiliation, _TRole, role, _Value, _ServiceAf) -> false. +-spec send_kickban_presence(jid(), jid(), binary(), + pos_integer(), state()) -> ok. send_kickban_presence(UJID, JID, Reason, Code, StateData) -> NewAffiliation = get_affiliation(JID, StateData), send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, StateData). +-spec send_kickban_presence(jid(), jid(), binary(), pos_integer(), + affiliation(), state()) -> ok. send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, StateData) -> LJID = jid:tolower(JID), @@ -3348,77 +2943,51 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, end, LJIDs). +-spec send_kickban_presence1(jid(), jid(), binary(), pos_integer(), + affiliation(), state()) -> ok. send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation, StateData) -> {ok, #user{jid = RealJID, nick = Nick}} = (?DICT):find(jid:tolower(UJID), StateData#state.users), - SAffiliation = affiliation_to_list(Affiliation), - BannedJIDString = jid:to_string(RealJID), ActorNick = get_actor_nick(MJID, StateData), - lists:foreach(fun ({_LJID, Info}) -> - JidAttrList = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, BannedJIDString}]; - false -> [] - end, - ItemAttrs = [{<<"affiliation">>, SAffiliation}, - {<<"role">>, <<"none">>}] - ++ JidAttrList, - ItemEls = case Reason of - <<"">> -> []; - _ -> - [#xmlel{name = <<"reason">>, - attrs = [], - children = - [{xmlcdata, Reason}]}] - end, - ItemElsActor = case MJID of - <<"">> -> []; - _ -> [#xmlel{name = <<"actor">>, - attrs = - [{<<"nick">>, ActorNick}]}] - end, - Packet = #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs = - ItemAttrs, - children = - ItemElsActor ++ ItemEls}, - #xmlel{name = - <<"status">>, - attrs = - [{<<"code">>, - Code}], - children = - []}]}]}, - RoomJIDNick = jid:replace_resource( - StateData#state.jid, Nick), - send_wrapped(RoomJIDNick, Info#user.jid, Packet, - ?NS_MUCSUB_NODES_AFFILIATIONS, StateData), + lists:foreach( + fun({LJID, Info}) -> + IsSelfPresence = jid:tolower(UJID) == LJID, + Item0 = #muc_item{affiliation = Affiliation, + role = none}, + Item1 = case Info#user.role == moderator orelse + (StateData#state.config)#config.anonymous + == false orelse IsSelfPresence of + true -> Item0#muc_item{jid = RealJID}; + false -> Item0 + end, + Item2 = Item1#muc_item{reason = Reason}, + Item = case ActorNick of + <<"">> -> Item2; + _ -> Item2#muc_item{actor = #muc_actor{nick = ActorNick}} + end, + Codes = if IsSelfPresence -> [110, Code]; + true -> [Code] + end, + Packet = #presence{type = unavailable, + sub_els = [#muc_user{items = [Item], + status_codes = Codes}]}, + RoomJIDNick = jid:replace_resource(StateData#state.jid, Nick), + send_wrapped(RoomJIDNick, Info#user.jid, Packet, + ?NS_MUCSUB_NODES_AFFILIATIONS, StateData), IsSubscriber = is_subscriber(Info#user.jid, StateData), - IsOccupant = Info#user.last_presence /= undefined, - if (IsSubscriber and not IsOccupant) -> - send_wrapped(RoomJIDNick, Info#user.jid, Packet, - ?NS_MUCSUB_NODES_PARTICIPANTS, StateData); - true -> - ok - end - end, + IsOccupant = Info#user.last_presence /= undefined, + if (IsSubscriber and not IsOccupant) -> + send_wrapped(RoomJIDNick, Info#user.jid, Packet, + ?NS_MUCSUB_NODES_PARTICIPANTS, StateData); + true -> + ok + end + end, (?DICT):to_list(get_users_and_subscribers(StateData))). +-spec get_actor_nick(binary() | jid(), state()) -> binary(). get_actor_nick(<<"">>, _StateData) -> <<"">>; get_actor_nick(MJID, StateData) -> @@ -3429,97 +2998,94 @@ get_actor_nick(MJID, StateData) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Owner stuff - -process_iq_owner(From, set, Lang, SubEl, StateData) -> +-spec process_iq_owner(jid(), iq(), state()) -> + {result, undefined | muc_owner()} | + {result, undefined | muc_owner(), state() | stop} | + {error, stanza_error()}. +process_iq_owner(From, #iq{type = set, lang = Lang, + sub_els = [#muc_owner{destroy = Destroy, + config = Config, + items = Items}]}, + StateData) -> FAffiliation = get_affiliation(From, StateData), - case FAffiliation of - owner -> - #xmlel{children = Els} = SubEl, - case fxml:remove_cdata(Els) of - [#xmlel{name = <<"x">>} = XEl] -> - case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), - fxml:get_tag_attr_s(<<"type">>, XEl)} - of - {?NS_XDATA, <<"cancel">>} -> {result, [], StateData}; - {?NS_XDATA, <<"submit">>} -> - case is_allowed_log_change(XEl, StateData, From) andalso - is_allowed_persistent_change(XEl, StateData, From) - andalso - is_allowed_room_name_desc_limits(XEl, StateData) - andalso - is_password_settings_correct(XEl, StateData) - of - true -> set_config(XEl, StateData, Lang); - false -> {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - Txt = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end; - [#xmlel{name = <<"destroy">>} = SubEl1] -> - ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", - [jid:to_string(StateData#state.jid), - jid:to_string(From)]), - add_to_log(room_existence, destroyed, StateData), - destroy_room(SubEl1, StateData); - Items -> - process_admin_items_set(From, Items, Lang, StateData) - end; - _ -> - ErrText = <<"Owner privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + if FAffiliation /= owner -> + ErrText = <<"Owner privileges required">>, + {error, xmpp:err_forbidden(ErrText, Lang)}; + Destroy /= undefined, Config == undefined, Items == [] -> + ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", + [jid:to_string(StateData#state.jid), jid:to_string(From)]), + add_to_log(room_existence, destroyed, StateData), + destroy_room(Destroy, StateData); + Config /= undefined, Destroy == undefined, Items == [] -> + case Config of + #xdata{type = cancel} -> + {result, undefined}; + #xdata{type = submit, fields = Fs} -> + try muc_roomconfig:decode(Fs) of + Options -> + case is_allowed_log_change(Options, StateData, From) andalso + is_allowed_persistent_change(Options, StateData, From) andalso + is_allowed_room_name_desc_limits(Options, StateData) andalso + is_password_settings_correct(Options, StateData) of + true -> + set_config(Options, StateData, Lang); + false -> + {error, xmpp:err_not_acceptable()} + end + catch _:{muc_roomconfig, Why} -> + Txt = muc_roomconfig:format_error(Why), + {error, xmpp:err_bad_request(Txt, Lang)} + end; + _ -> + Txt = <<"Incorrect data form">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end; + Items /= [], Config == undefined, Destroy == undefined -> + process_admin_items_set(From, Items, Lang, StateData); + true -> + {error, xmpp:err_bad_request()} end; -process_iq_owner(From, get, Lang, SubEl, StateData) -> +process_iq_owner(From, #iq{type = get, lang = Lang, + sub_els = [#muc_owner{destroy = Destroy, + config = Config, + items = Items}]}, + StateData) -> FAffiliation = get_affiliation(From, StateData), - case FAffiliation of - owner -> - #xmlel{children = Els} = SubEl, - case fxml:remove_cdata(Els) of - [] -> get_config(Lang, StateData, From); - [Item] -> - case fxml:get_tag_attr(<<"affiliation">>, Item) of - false -> - Txt = <<"No 'affiliation' attribute found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> - ErrText = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Invalid affiliation: ~s">>), - [StrAffiliation])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - SAffiliation -> - Items = items_with_affiliation(SAffiliation, - StateData), - {result, Items, StateData} - end - end; - _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end; - _ -> - ErrText = <<"Owner privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + if FAffiliation /= owner -> + ErrText = <<"Owner privileges required">>, + {error, xmpp:err_forbidden(ErrText, Lang)}; + Destroy == undefined, Config == undefined -> + case Items of + [] -> + {result, + #muc_owner{config = get_config(Lang, StateData, From)}}; + [#muc_item{affiliation = undefined}] -> + Txt = <<"No 'affiliation' attribute found">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + [#muc_item{affiliation = Affiliation}] -> + Items = items_with_affiliation(Affiliation, StateData), + {result, #muc_owner{items = Items}}; + [_|_] -> + Txt = <<"Too many elements">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end; + true -> + {error, xmpp:err_bad_request()} end. -is_allowed_log_change(XEl, StateData, From) -> - case lists:keymember(<<"muc#roomconfig_enablelogging">>, - 1, jlib:parse_xdata_submit(XEl)) - of - false -> true; - true -> - allow == - mod_muc_log:check_access_log(StateData#state.server_host, - From) +-spec is_allowed_log_change(muc_roomconfig:result(), state(), jid()) -> boolean(). +is_allowed_log_change(Options, StateData, From) -> + case proplists:is_defined(enablelogging, Options) of + false -> true; + true -> + allow == + mod_muc_log:check_access_log(StateData#state.server_host, + From) end. -is_allowed_persistent_change(XEl, StateData, From) -> - case - lists:keymember(<<"muc#roomconfig_persistentroom">>, 1, - jlib:parse_xdata_submit(XEl)) - of +-spec is_allowed_persistent_change(muc_roomconfig:result(), state(), jid()) -> boolean(). +is_allowed_persistent_change(Options, StateData, From) -> + case proplists:is_defined(persistentroom, Options) of false -> true; true -> {_AccessRoute, _AccessCreate, _AccessAdmin, @@ -3532,103 +3098,46 @@ is_allowed_persistent_change(XEl, StateData, From) -> %% Check if the Room Name and Room Description defined in the Data Form %% are conformant to the configured limits -is_allowed_room_name_desc_limits(XEl, StateData) -> - IsNameAccepted = case - lists:keysearch(<<"muc#roomconfig_roomname">>, 1, - jlib:parse_xdata_submit(XEl)) - of - {value, {_, [N]}} -> - byte_size(N) =< - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_room_name, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> I - end, infinity); - _ -> true - end, - IsDescAccepted = case - lists:keysearch(<<"muc#roomconfig_roomdesc">>, 1, - jlib:parse_xdata_submit(XEl)) - of - {value, {_, [D]}} -> - byte_size(D) =< - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_room_desc, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> - I - end, infinity); - _ -> true - end, - IsNameAccepted and IsDescAccepted. +-spec is_allowed_room_name_desc_limits(muc_roomconfig:result(), state()) -> boolean(). +is_allowed_room_name_desc_limits(Options, StateData) -> + RoomName = proplists:get_value(roomname, Options, <<"">>), + RoomDesc = proplists:get_value(roomdesc, Options, <<"">>), + MaxRoomName = gen_mod:get_module_opt( + StateData#state.server_host, + mod_muc, max_room_name, + fun(infinity) -> infinity; + (I) when is_integer(I), + I>0 -> I + end, infinity), + MaxRoomDesc = gen_mod:get_module_opt( + StateData#state.server_host, + mod_muc, max_room_desc, + fun(infinity) -> infinity; + (I) when is_integer(I), + I>0 -> + I + end, infinity), + (byte_size(RoomName) =< MaxRoomName) + andalso (byte_size(RoomDesc) =< MaxRoomDesc). %% Return false if: %% "the password for a password-protected room is blank" -is_password_settings_correct(XEl, StateData) -> +-spec is_password_settings_correct(muc_roomconfig:result(), state()) -> boolean(). +is_password_settings_correct(Options, StateData) -> Config = StateData#state.config, OldProtected = Config#config.password_protected, OldPassword = Config#config.password, - NewProtected = case - lists:keysearch(<<"muc#roomconfig_passwordprotectedroom">>, - 1, jlib:parse_xdata_submit(XEl)) - of - {value, {_, [<<"1">>]}} -> true; - {value, {_, [<<"0">>]}} -> false; - _ -> undefined - end, - NewPassword = case - lists:keysearch(<<"muc#roomconfig_roomsecret">>, 1, - jlib:parse_xdata_submit(XEl)) - of - {value, {_, [P]}} -> P; - _ -> undefined - end, - case {OldProtected, NewProtected, OldPassword, - NewPassword} - of - {true, undefined, <<"">>, undefined} -> false; - {true, undefined, _, <<"">>} -> false; - {_, true, <<"">>, undefined} -> false; - {_, true, _, <<"">>} -> false; - _ -> true + NewProtected = proplists:get_value(passwordprotectedroom, Options), + NewPassword = proplists:get_value(roomsecret, Options), + case {OldProtected, NewProtected, OldPassword, NewPassword} of + {true, undefined, <<"">>, undefined} -> false; + {true, undefined, _, <<"">>} -> false; + {_, true, <<"">>, undefined} -> false; + {_, true, _, <<"">>} -> false; + _ -> true end. --define(XFIELD(Type, Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(BOOLXFIELD(Label, Var, Val), - ?XFIELD(<<"boolean">>, Label, Var, - case Val of - true -> <<"1">>; - _ -> <<"0">> - end)). - --define(STRINGXFIELD(Label, Var, Val), - ?XFIELD(<<"text-single">>, Label, Var, Val)). - --define(PRIVATEXFIELD(Label, Var, Val), - ?XFIELD(<<"text-private">>, Label, Var, Val)). - --define(JIDMULTIXFIELD(Label, Var, JIDList), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-multi">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, jid:to_string(JID)}]} - || JID <- JIDList]}). - +-spec get_default_room_maxusers(state()) -> non_neg_integer(). get_default_room_maxusers(RoomState) -> DefRoomOpts = gen_mod:get_module_opt(RoomState#state.server_host, @@ -3638,564 +3147,163 @@ get_default_room_maxusers(RoomState) -> RoomState2 = set_opts(DefRoomOpts, RoomState), (RoomState2#state.config)#config.max_users. +-spec get_config(binary(), state(), jid()) -> xdata(). get_config(Lang, StateData, From) -> - {_AccessRoute, _AccessCreate, _AccessAdmin, - AccessPersistent} = + {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = StateData#state.access, ServiceMaxUsers = get_service_max_users(StateData), - DefaultRoomMaxUsers = - get_default_room_maxusers(StateData), + DefaultRoomMaxUsers = get_default_room_maxusers(StateData), Config = StateData#state.config, - {MaxUsersRoomInteger, MaxUsersRoomString} = case - get_max_users(StateData) - of - N when is_integer(N) -> - {N, - jlib:integer_to_binary(N)}; - _ -> {0, <<"none">>} - end, - Res = [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Configuration of room ~s">>), - [jid:to_string(StateData#state.jid)]))}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"hidden">>}, - {<<"var">>, <<"FORM_TYPE">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"http://jabber.org/protocol/muc#roomconfig">>}]}]}, - ?STRINGXFIELD(<<"Room title">>, - <<"muc#roomconfig_roomname">>, (Config#config.title)), - ?STRINGXFIELD(<<"Room description">>, - <<"muc#roomconfig_roomdesc">>, - (Config#config.description))] - ++ - case acl:match_rule(StateData#state.server_host, - AccessPersistent, From) - of - allow -> - [?BOOLXFIELD(<<"Make room persistent">>, - <<"muc#roomconfig_persistentroom">>, - (Config#config.persistent))]; - _ -> [] - end - ++ - [?BOOLXFIELD(<<"Make room public searchable">>, - <<"muc#roomconfig_publicroom">>, - (Config#config.public)), - ?BOOLXFIELD(<<"Make participants list public">>, - <<"public_list">>, (Config#config.public_list)), - ?BOOLXFIELD(<<"Make room password protected">>, - <<"muc#roomconfig_passwordprotectedroom">>, - (Config#config.password_protected)), - ?PRIVATEXFIELD(<<"Password">>, - <<"muc#roomconfig_roomsecret">>, - case Config#config.password_protected of - true -> Config#config.password; - false -> <<"">> - end), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Maximum Number of Occupants">>)}, - {<<"var">>, <<"muc#roomconfig_maxusers">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, MaxUsersRoomString}]}] - ++ - if is_integer(ServiceMaxUsers) -> []; - true -> - [#xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"No limit">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - <<"none">>}]}]}] - end - ++ - [#xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - jlib:integer_to_binary(N)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - jlib:integer_to_binary(N)}]}]} - || N - <- lists:usort([ServiceMaxUsers, - DefaultRoomMaxUsers, - MaxUsersRoomInteger - | ?MAX_USERS_DEFAULT_LIST]), - N =< ServiceMaxUsers]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Present real Jabber IDs to">>)}, - {<<"var">>, <<"muc#roomconfig_whois">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - if Config#config.anonymous -> - <<"moderators">>; - true -> <<"anyone">> - end}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"moderators only">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"moderators">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"anyone">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"anyone">>}]}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-multi">>}, - {<<"label">>, - translate:translate(Lang, - <<"Roles for which Presence is Broadcasted">>)}, - {<<"var">>, <<"muc#roomconfig_presencebroadcast">>}], - children = - lists:map( - fun(Role) -> - #xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - atom_to_binary(Role, utf8)}]} - end, Config#config.presence_broadcast - ) ++ - [#xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"Moderator">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"moderator">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"Participant">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"participant">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"Visitor">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"visitor">>}]}]} - ]}, - ?BOOLXFIELD(<<"Make room members-only">>, - <<"muc#roomconfig_membersonly">>, - (Config#config.members_only)), - ?BOOLXFIELD(<<"Make room moderated">>, - <<"muc#roomconfig_moderatedroom">>, - (Config#config.moderated)), - ?BOOLXFIELD(<<"Default users as participants">>, - <<"members_by_default">>, - (Config#config.members_by_default)), - ?BOOLXFIELD(<<"Allow users to change the subject">>, - <<"muc#roomconfig_changesubject">>, - (Config#config.allow_change_subj)), - ?BOOLXFIELD(<<"Allow users to send private messages">>, - <<"allow_private_messages">>, - (Config#config.allow_private_messages)), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Allow visitors to send private messages to">>)}, - {<<"var">>, - <<"allow_private_messages_from_visitors">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - case - Config#config.allow_private_messages_from_visitors - of - anyone -> <<"anyone">>; - moderators -> <<"moderators">>; - nobody -> <<"nobody">> - end}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"nobody">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, <<"nobody">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"moderators only">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"moderators">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"anyone">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"anyone">>}]}]}]}, - ?BOOLXFIELD(<<"Allow users to query other users">>, - <<"allow_query_users">>, - (Config#config.allow_query_users)), - ?BOOLXFIELD(<<"Allow users to send invites">>, - <<"muc#roomconfig_allowinvites">>, - (Config#config.allow_user_invites)), - ?BOOLXFIELD(<<"Allow visitors to send status text in " - "presence updates">>, - <<"muc#roomconfig_allowvisitorstatus">>, - (Config#config.allow_visitor_status)), - ?BOOLXFIELD(<<"Allow visitors to change nickname">>, - <<"muc#roomconfig_allowvisitornickchange">>, - (Config#config.allow_visitor_nickchange)), - ?BOOLXFIELD(<<"Allow visitors to send voice requests">>, - <<"muc#roomconfig_allowvoicerequests">>, - (Config#config.allow_voice_requests)), - ?BOOLXFIELD(<<"Allow subscription">>, - <<"muc#roomconfig_allow_subscription">>, - (Config#config.allow_subscription)), - ?STRINGXFIELD(<<"Minimum interval between voice requests " - "(in seconds)">>, - <<"muc#roomconfig_voicerequestmininterval">>, - (jlib:integer_to_binary(Config#config.voice_request_min_interval)))] - ++ - case ejabberd_captcha:is_feature_available() of - true -> - [?BOOLXFIELD(<<"Make room CAPTCHA protected">>, - <<"captcha_protected">>, - (Config#config.captcha_protected))]; - false -> [] - end ++ - [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>, - <<"muc#roomconfig_captcha_whitelist">>, - ((?SETS):to_list(Config#config.captcha_whitelist)))] - ++ - case - mod_muc_log:check_access_log(StateData#state.server_host, - From) - of - allow -> - [?BOOLXFIELD(<<"Enable logging">>, - <<"muc#roomconfig_enablelogging">>, - (Config#config.logging))]; - _ -> [] - end, - X = ejabberd_hooks:run_fold(get_room_config, - StateData#state.server_host, - Res, - [StateData, From, Lang]), - {result, - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need an x:data capable client to " - "configure room">>)}]}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = X}], - StateData}. + {MaxUsersRoomInteger, MaxUsersRoomString} = + case get_max_users(StateData) of + N when is_integer(N) -> + {N, N}; + _ -> {0, none} + end, + Title = str:format( + translate:translate(Lang, <<"Configuration of room ~s">>), + [jid:to_string(StateData#state.jid)]), + Fs = [{roomname, Config#config.title}, + {roomdesc, Config#config.description}] ++ + case acl:match_rule(StateData#state.server_host, AccessPersistent, From) of + allow -> [{persistentroom, Config#config.persistent}]; + deny -> [] + end ++ + [{publicroom, Config#config.public}, + {public_list, Config#config.public_list}, + {passwordprotectedroom, Config#config.password_protected}, + {roomsecret, case Config#config.password_protected of + true -> Config#config.password; + false -> <<"">> + end}, + {maxusers, MaxUsersRoomString, + [if is_integer(ServiceMaxUsers) -> []; + true -> [{<<"No limit">>, <<"none">>}] + end] ++ [{integer_to_binary(N), N} + || N <- lists:usort([ServiceMaxUsers, + DefaultRoomMaxUsers, + MaxUsersRoomInteger + | ?MAX_USERS_DEFAULT_LIST]), + N =< ServiceMaxUsers]}, + {whois, if Config#config.anonymous -> moderators; + true -> anyone + end}, + {presencebroadcast, Config#config.presence_broadcast}, + {membersonly, Config#config.members_only}, + {moderatedroom, Config#config.moderated}, + {members_by_default, Config#config.members_by_default}, + {changesubject, Config#config.allow_change_subj}, + {allow_private_messages, Config#config.allow_private_messages}, + {allow_private_messages_from_visitors, + Config#config.allow_private_messages_from_visitors}, + {allow_query_users, Config#config.allow_query_users}, + {allowinvites, Config#config.allow_user_invites}, + {allow_visitor_status, Config#config.allow_visitor_status}, + {allow_visitor_nickchange, Config#config.allow_visitor_nickchange}, + {allow_voice_requests, Config#config.allow_voice_requests}, + {allow_subscription, Config#config.allow_subscription}, + {voice_request_min_interval, Config#config.voice_request_min_interval}] + ++ + case ejabberd_captcha:is_feature_available() of + true -> [{captcha_protected, Config#config.captcha_protected}]; + false -> [] + end ++ + [{captcha_whitelist, + lists:map(fun jid:make/1, ?SETS:to_list(Config#config.captcha_whitelist))}] + ++ + case mod_muc_log:check_access_log(StateData#state.server_host, From) of + allow -> [{enablelogging, Config#config.logging}]; + deny -> [] + end, + Fields = ejabberd_hooks:run_fold(get_room_config, + StateData#state.server_host, + Fs, + [StateData, From, Lang]), + #xdata{type = form, title = Title, + fields = muc_roomconfig:encode( + Fields, fun(T) -> translate:translate(Lang, T) end)}. -set_config(XEl, StateData, Lang) -> - XData = jlib:parse_xdata_submit(XEl), - case XData of - invalid -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)}; - _ -> - case set_xoption(XData, StateData#state.config, - StateData#state.server_host, Lang) of - #config{} = Config -> - Res = change_config(Config, StateData), - {result, _, NSD} = Res, - Type = case {(StateData#state.config)#config.logging, - Config#config.logging} - of - {true, false} -> roomconfig_change_disabledlogging; - {false, true} -> roomconfig_change_enabledlogging; - {_, _} -> roomconfig_change - end, - Users = [{U#user.jid, U#user.nick, U#user.role} - || {_, U} <- (?DICT):to_list(StateData#state.users)], - add_to_log(Type, Users, NSD), - Res; - Err -> Err - end +-spec set_config(muc_roomconfig:result(), state(), binary()) -> + {error, stanza_error()} | {result, undefined, state()}. +set_config(Options, StateData, Lang) -> + try + #config{} = Config = set_config(Options, StateData#state.config, + StateData#state.server_host, Lang), + {result, _, NSD} = Res = change_config(Config, StateData), + Type = case {(StateData#state.config)#config.logging, + Config#config.logging} + of + {true, false} -> roomconfig_change_disabledlogging; + {false, true} -> roomconfig_change_enabledlogging; + {_, _} -> roomconfig_change + end, + Users = [{U#user.jid, U#user.nick, U#user.role} + || {_, U} <- (?DICT):to_list(StateData#state.users)], + add_to_log(Type, Users, NSD), + Res + catch _:{badmatch, {error, #stanza_error{}} = Err} -> + Err end. --define(SET_BOOL_XOPT(Opt, Val), - case Val of - <<"0">> -> - set_xoption(Opts, Config#config{Opt = false}, ServerHost, Lang); - <<"false">> -> - set_xoption(Opts, Config#config{Opt = false}, ServerHost, Lang); - <<"1">> -> set_xoption(Opts, Config#config{Opt = true}, ServerHost, Lang); - <<"true">> -> - set_xoption(Opts, Config#config{Opt = true}, ServerHost, Lang); - _ -> - Txt = <<"Value of '~s' should be boolean">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)} - end). +get_config_opt_name(Pos) -> + Fs = [config|record_info(fields, config)], + lists:nth(Pos, Fs). --define(SET_NAT_XOPT(Opt, Val), - case catch jlib:binary_to_integer(Val) of - I when is_integer(I), I > 0 -> - set_xoption(Opts, Config#config{Opt = I}, ServerHost, Lang); - _ -> - Txt = <<"Value of '~s' should be integer">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)} - end). - --define(SET_STRING_XOPT(Opt, Val), - set_xoption(Opts, Config#config{Opt = Val}, ServerHost, Lang)). - --define(SET_JIDMULTI_XOPT(Opt, Vals), - begin - Set = lists:foldl(fun ({U, S, R}, Set1) -> - (?SETS):add_element({U, S, R}, Set1); - (#jid{luser = U, lserver = S, lresource = R}, - Set1) -> - (?SETS):add_element({U, S, R}, Set1); - (_, Set1) -> Set1 - end, - (?SETS):empty(), Vals), - set_xoption(Opts, Config#config{Opt = Set}, ServerHost, Lang) - end). - -set_xoption([], Config, _ServerHost, _Lang) -> Config; -set_xoption([{<<"muc#roomconfig_roomname">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_STRING_XOPT(title, Val); -set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_STRING_XOPT(description, Val); -set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(allow_change_subj, Val); -set_xoption([{<<"allow_query_users">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(allow_query_users, Val); -set_xoption([{<<"allow_private_messages">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(allow_private_messages, Val); -set_xoption([{<<"allow_private_messages_from_visitors">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - case Val of - <<"anyone">> -> - ?SET_STRING_XOPT(allow_private_messages_from_visitors, - anyone); - <<"moderators">> -> - ?SET_STRING_XOPT(allow_private_messages_from_visitors, - moderators); - <<"nobody">> -> - ?SET_STRING_XOPT(allow_private_messages_from_visitors, - nobody); - _ -> - Txt = <<"Value of 'allow_private_messages_from_visitors' " - "should be anyone|moderators|nobody">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end; -set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(allow_visitor_status, Val); -set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(allow_visitor_nickchange, Val); -set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(public, Val); -set_xoption([{<<"public_list">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(public_list, Val); -set_xoption([{<<"muc#roomconfig_persistentroom">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(persistent, Val); -set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(moderated, Val); -set_xoption([{<<"members_by_default">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(members_by_default, Val); -set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(members_only, Val); -set_xoption([{<<"captcha_protected">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(captcha_protected, Val); -set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(allow_user_invites, Val); -set_xoption([{<<"muc#roomconfig_allow_subscription">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(allow_subscription, Val); -set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(password_protected, Val); -set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_STRING_XOPT(password, Val); -set_xoption([{<<"anonymous">>, [Val]} | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(anonymous, Val); -set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts], - Config, ServerHost, Lang) -> - Roles = - lists:foldl( - fun(_S, error) -> error; - (S, {M, P, V}) -> - case S of - <<"moderator">> -> {true, P, V}; - <<"participant">> -> {M, true, V}; - <<"visitor">> -> {M, P, true}; - _ -> error - end - end, {false, false, false}, Vals), - case Roles of - error -> - Txt = <<"Value of 'muc#roomconfig_presencebroadcast' should " - "be moderator|participant|visitor">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {M, P, V} -> - Res = - if M -> [moderator]; true -> [] end ++ - if P -> [participant]; true -> [] end ++ - if V -> [visitor]; true -> [] end, - set_xoption(Opts, Config#config{presence_broadcast = Res}, - ServerHost, Lang) - end; -set_xoption([{<<"muc#roomconfig_allowvoicerequests">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(allow_voice_requests, Val); -set_xoption([{<<"muc#roomconfig_voicerequestmininterval">>, - [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_NAT_XOPT(voice_request_min_interval, Val); -set_xoption([{<<"muc#roomconfig_whois">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - case Val of - <<"moderators">> -> - ?SET_BOOL_XOPT(anonymous, - (iolist_to_binary(integer_to_list(1)))); - <<"anyone">> -> - ?SET_BOOL_XOPT(anonymous, - (iolist_to_binary(integer_to_list(0)))); - _ -> - Txt = <<"Value of 'muc#roomconfig_whois' should be " - "moderators|anyone">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end; -set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - case Val of - <<"none">> -> ?SET_STRING_XOPT(max_users, none); - _ -> ?SET_NAT_XOPT(max_users, Val) - end; -set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]} - | Opts], - Config, ServerHost, Lang) -> - ?SET_BOOL_XOPT(logging, Val); -set_xoption([{<<"muc#roomconfig_captcha_whitelist">>, - Vals} - | Opts], - Config, ServerHost, Lang) -> - JIDs = [jid:from_string(Val) || Val <- Vals], - ?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs); -set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config, ServerHost, Lang) -> - set_xoption(Opts, Config, ServerHost, Lang); -set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) -> - Txt = <<"Unknown option '~s'">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - Err = {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}, - case ejabberd_hooks:run_fold(set_room_option, - ServerHost, - Err, - [Opt, Vals, Lang]) of - {error, Reason} -> - {error, Reason}; - {Pos, Val} -> - set_xoption(Opts, setelement(Pos, Config, Val), ServerHost, Lang) - end. +-spec set_config([muc_roomconfig:property()], #config{}, + binary(), binary()) -> #config{} | {error, stanza_error()}. +set_config(Opts, Config, ServerHost, Lang) -> + lists:foldl( + fun(_, {error, _} = Err) -> Err; + ({roomname, Title}, C) -> C#config{title = Title}; + ({roomdesc, Desc}, C) -> C#config{description = Desc}; + ({changesubject, V}, C) -> C#config{allow_change_subj = V}; + ({allow_query_users, V}, C) -> C#config{allow_query_users = V}; + ({allow_private_messages, V}, C) -> + C#config{allow_private_messages = V}; + ({allow_private_messages_from_visitors, V}, C) -> + C#config{allow_private_messages_from_visitors = V}; + ({allow_visitor_status, V}, C) -> C#config{allow_visitor_status = V}; + ({allow_visitor_nickchange, V}, C) -> + C#config{allow_visitor_nickchange = V}; + ({publicroom, V}, C) -> C#config{public = V}; + ({public_list, V}, C) -> C#config{public_list = V}; + ({persistentroom, V}, C) -> C#config{persistent = V}; + ({moderatedroom, V}, C) -> C#config{moderated = V}; + ({members_by_default, V}, C) -> C#config{members_by_default = V}; + ({membersonly, V}, C) -> C#config{members_only = V}; + ({captcha_protected, V}, C) -> C#config{captcha_protected = V}; + ({allowinvites, V}, C) -> C#config{allow_user_invites = V}; + ({allow_subscription, V}, C) -> C#config{allow_subscription = V}; + ({passwordprotectedroom, V}, C) -> C#config{password_protected = V}; + ({roomsecret, V}, C) -> C#config{password = V}; + ({anonymous, V}, C) -> C#config{anonymous = V}; + ({presencebroadcast, V}, C) -> C#config{presence_broadcast = V}; + ({allow_voice_requests, V}, C) -> C#config{allow_voice_requests = V}; + ({voice_request_min_interval, V}, C) -> + C#config{voice_request_min_interval = V}; + ({whois, moderators}, C) -> C#config{anonymous = true}; + ({whois, anyone}, C) -> C#config{anonymous = false}; + ({maxusers, V}, C) -> C#config{max_users = V}; + ({enablelogging, V}, C) -> C#config{logging = V}; + ({captcha_whitelist, Js}, C) -> + LJIDs = [jid:tolower(J) || J <- Js], + C#config{captcha_whitelist = ?SETS:from_list(LJIDs)}; + ({O, V} = Opt, C) -> + case ejabberd_hooks:run_fold(set_room_option, + ServerHost, + {0, undefined}, + [Opt, Lang]) of + {0, undefined} -> + ?ERROR_MSG("set_room_option hook failed for " + "option '~s' with value ~p", [O, V]), + Txt = {<<"Failed to process option '~s'">>, [O]}, + {error, xmpp:err_internal_server_error(Txt, Lang)}; + {Pos, Val} -> + setelement(Pos, C, Val) + end + end, Config, Opts). +-spec change_config(#config{}, state()) -> {result, undefined, state()}. change_config(Config, StateData) -> send_config_change_info(Config, StateData), NSD = remove_subscriptions(StateData#state{config = Config}), @@ -4214,57 +3322,59 @@ change_config(Config, StateData) -> Config#config.members_only} of {false, true} -> - NSD1 = remove_nonmembers(NSD), {result, [], NSD1}; - _ -> {result, [], NSD} + NSD1 = remove_nonmembers(NSD), {result, undefined, NSD1}; + _ -> {result, undefined, NSD} end. +-spec send_config_change_info(#config{}, state()) -> ok. send_config_change_info(Config, #state{config = Config}) -> ok; send_config_change_info(New, #state{config = Old} = StateData) -> Codes = case {Old#config.logging, New#config.logging} of - {false, true} -> [<<"170">>]; - {true, false} -> [<<"171">>]; + {false, true} -> [170]; + {true, false} -> [171]; _ -> [] end ++ case {Old#config.anonymous, New#config.anonymous} of - {true, false} -> [<<"172">>]; - {false, true} -> [<<"173">>]; + {true, false} -> [172]; + {false, true} -> [173]; _ -> [] end ++ case Old#config{anonymous = New#config.anonymous, + vcard = New#config.vcard, logging = New#config.logging} of New -> []; - _ -> [<<"104">>] + _ -> [104] end, - StatusEls = [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, Code}], - children = []} || Code <- Codes], - Message = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}, - {<<"id">>, randoms:get_string()}], - children = [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = StatusEls}]}, - send_wrapped_multiple(StateData#state.jid, + if Codes /= [] -> + Message = #message{type = groupchat, + id = randoms:get_string(), + sub_els = [#muc_user{status_codes = Codes}]}, + send_wrapped_multiple(StateData#state.jid, get_users_and_subscribers(StateData), - Message, - ?NS_MUCSUB_NODES_CONFIG, - StateData). + Message, + ?NS_MUCSUB_NODES_CONFIG, + StateData); + true -> + ok + end. +-spec remove_nonmembers(state()) -> state(). remove_nonmembers(StateData) -> lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) -> Affiliation = get_affiliation(JID, SD), case Affiliation of none -> catch send_kickban_presence(<<"">>, JID, <<"">>, - <<"322">>, SD), + 322, SD), set_role(JID, none, SD); _ -> SD end end, StateData, (?DICT):to_list(get_users_and_subscribers(StateData))). +-spec set_opts([{atom(), any()}], state()) -> state(). set_opts([], StateData) -> StateData; set_opts([{Opt, Val} | Opts], StateData) -> NSD = case Opt of @@ -4403,9 +3513,10 @@ set_opts([{Opt, Val} | Opts], StateData) -> end, set_opts(Opts, NSD). --define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}). - +-define(MAKE_CONFIG_OPT(Opt), + {get_config_opt_name(Opt), element(Opt, Config)}). +-spec make_opts(state()) -> [{atom(), any()}]. make_opts(StateData) -> Config = StateData#state.config, Subscribers = (?DICT):fold( @@ -4414,28 +3525,29 @@ make_opts(StateData) -> Sub#subscriber.nick, Sub#subscriber.nodes}|Acc] end, [], StateData#state.subscribers), - [?MAKE_CONFIG_OPT(title), ?MAKE_CONFIG_OPT(description), - ?MAKE_CONFIG_OPT(allow_change_subj), - ?MAKE_CONFIG_OPT(allow_query_users), - ?MAKE_CONFIG_OPT(allow_private_messages), - ?MAKE_CONFIG_OPT(allow_private_messages_from_visitors), - ?MAKE_CONFIG_OPT(allow_visitor_status), - ?MAKE_CONFIG_OPT(allow_visitor_nickchange), - ?MAKE_CONFIG_OPT(public), ?MAKE_CONFIG_OPT(public_list), - ?MAKE_CONFIG_OPT(persistent), - ?MAKE_CONFIG_OPT(moderated), - ?MAKE_CONFIG_OPT(members_by_default), - ?MAKE_CONFIG_OPT(members_only), - ?MAKE_CONFIG_OPT(allow_user_invites), - ?MAKE_CONFIG_OPT(password_protected), - ?MAKE_CONFIG_OPT(captcha_protected), - ?MAKE_CONFIG_OPT(password), ?MAKE_CONFIG_OPT(anonymous), - ?MAKE_CONFIG_OPT(logging), ?MAKE_CONFIG_OPT(max_users), - ?MAKE_CONFIG_OPT(allow_voice_requests), - ?MAKE_CONFIG_OPT(allow_subscription), - ?MAKE_CONFIG_OPT(mam), - ?MAKE_CONFIG_OPT(voice_request_min_interval), - ?MAKE_CONFIG_OPT(vcard), + [?MAKE_CONFIG_OPT(#config.title), ?MAKE_CONFIG_OPT(#config.description), + ?MAKE_CONFIG_OPT(#config.allow_change_subj), + ?MAKE_CONFIG_OPT(#config.allow_query_users), + ?MAKE_CONFIG_OPT(#config.allow_private_messages), + ?MAKE_CONFIG_OPT(#config.allow_private_messages_from_visitors), + ?MAKE_CONFIG_OPT(#config.allow_visitor_status), + ?MAKE_CONFIG_OPT(#config.allow_visitor_nickchange), + ?MAKE_CONFIG_OPT(#config.public), ?MAKE_CONFIG_OPT(#config.public_list), + ?MAKE_CONFIG_OPT(#config.persistent), + ?MAKE_CONFIG_OPT(#config.moderated), + ?MAKE_CONFIG_OPT(#config.members_by_default), + ?MAKE_CONFIG_OPT(#config.members_only), + ?MAKE_CONFIG_OPT(#config.allow_user_invites), + ?MAKE_CONFIG_OPT(#config.password_protected), + ?MAKE_CONFIG_OPT(#config.captcha_protected), + ?MAKE_CONFIG_OPT(#config.password), ?MAKE_CONFIG_OPT(#config.anonymous), + ?MAKE_CONFIG_OPT(#config.logging), ?MAKE_CONFIG_OPT(#config.max_users), + ?MAKE_CONFIG_OPT(#config.allow_voice_requests), + ?MAKE_CONFIG_OPT(#config.allow_subscription), + ?MAKE_CONFIG_OPT(#config.mam), + ?MAKE_CONFIG_OPT(#config.presence_broadcast), + ?MAKE_CONFIG_OPT(#config.voice_request_min_interval), + ?MAKE_CONFIG_OPT(#config.vcard), {captcha_whitelist, (?SETS):to_list((StateData#state.config)#config.captcha_whitelist)}, {affiliations, @@ -4444,32 +3556,22 @@ make_opts(StateData) -> {subject_author, StateData#state.subject_author}, {subscribers, Subscribers}]. +-spec destroy_room(muc_destroy(), state()) -> {result, undefined, stop}. destroy_room(DEl, StateData) -> - lists:foreach(fun ({_LJID, Info}) -> - Nick = Info#user.nick, - ItemAttrs = [{<<"affiliation">>, <<"none">>}, - {<<"role">>, <<"none">>}], - Packet = #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs = - ItemAttrs, - children = - []}, - DEl]}]}, - send_wrapped(jid:replace_resource(StateData#state.jid, - Nick), - Info#user.jid, Packet, - ?NS_MUCSUB_NODES_CONFIG, StateData) - end, + Destroy = DEl#muc_destroy{xmlns = ?NS_MUC_USER}, + lists:foreach( + fun({_LJID, Info}) -> + Nick = Info#user.nick, + Item = #muc_item{affiliation = none, + role = none}, + Packet = #presence{ + type = unavailable, + sub_els = [#muc_user{items = [Item], + destroy = Destroy}]}, + send_wrapped(jid:replace_resource(StateData#state.jid, Nick), + Info#user.jid, Packet, + ?NS_MUCSUB_NODES_CONFIG, StateData) + end, (?DICT):to_list(get_users_and_subscribers(StateData))), case (StateData#state.config)#config.persistent of true -> @@ -4477,191 +3579,163 @@ destroy_room(DEl, StateData) -> StateData#state.host, StateData#state.room); false -> ok end, - {result, [], stop}. + {result, undefined, stop}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Disco --define(FEATURE(Var), - #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Var}], - children = []}). - -define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse), case Opt of - true -> ?FEATURE(Fiftrue); - false -> ?FEATURE(Fiffalse) + true -> Fiftrue; + false -> Fiffalse end). -process_iq_disco_info(_From, set, Lang, _StateData) -> +-spec process_iq_disco_info(jid(), iq(), state()) -> + {result, disco_info()} | {error, stanza_error()}. +process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}; -process_iq_disco_info(_From, get, Lang, StateData) -> + {error, xmpp:err_not_allowed(Txt, Lang)}; +process_iq_disco_info(_From, #iq{type = get, lang = Lang}, StateData) -> Config = StateData#state.config, - {result, - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, get_title(StateData)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC}], children = []}, - ?CONFIG_OPT_TO_FEATURE((Config#config.public), - <<"muc_public">>, <<"muc_hidden">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.persistent), - <<"muc_persistent">>, <<"muc_temporary">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.members_only), - <<"muc_membersonly">>, <<"muc_open">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous), - <<"muc_semianonymous">>, <<"muc_nonanonymous">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.moderated), - <<"muc_moderated">>, <<"muc_unmoderated">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected), - <<"muc_passwordprotected">>, <<"muc_unsecured">>)] - ++ case Config#config.allow_subscription of - true -> [?FEATURE(?NS_MUCSUB)]; - false -> [] - end - ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam), - Config#config.mam} of - {true, true} -> - [?FEATURE(?NS_MAM_TMP), - ?FEATURE(?NS_MAM_0), - ?FEATURE(?NS_MAM_1)]; - _ -> - [] - end - ++ iq_disco_info_extras(Lang, StateData), - StateData}. - --define(RFIELDT(Type, Var, Val), - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, Type}, {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(RFIELD(Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). + Feats = [?NS_VCARD, ?NS_MUC, + ?CONFIG_OPT_TO_FEATURE((Config#config.public), + <<"muc_public">>, <<"muc_hidden">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.persistent), + <<"muc_persistent">>, <<"muc_temporary">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.members_only), + <<"muc_membersonly">>, <<"muc_open">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous), + <<"muc_semianonymous">>, <<"muc_nonanonymous">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.moderated), + <<"muc_moderated">>, <<"muc_unmoderated">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected), + <<"muc_passwordprotected">>, <<"muc_unsecured">>)] + ++ case Config#config.allow_subscription of + true -> [?NS_MUCSUB]; + false -> [] + end + ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam), + Config#config.mam} of + {true, true} -> + [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1]; + _ -> + [] + end, + {result, #disco_info{xdata = [iq_disco_info_extras(Lang, StateData)], + identities = [#identity{category = <<"conference">>, + type = <<"text">>, + name = get_title(StateData)}], + features = Feats}}. +-spec iq_disco_info_extras(binary(), state()) -> xdata(). iq_disco_info_extras(Lang, StateData) -> - Len = (?DICT):size(StateData#state.users), - RoomDescription = - (StateData#state.config)#config.description, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = - [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>, - <<"http://jabber.org/protocol/muc#roominfo">>), - ?RFIELD(<<"Room description">>, - <<"muc#roominfo_description">>, RoomDescription), - ?RFIELD(<<"Number of occupants">>, - <<"muc#roominfo_occupants">>, - (iolist_to_binary(integer_to_list(Len))))]}]. + Fs = [{description, (StateData#state.config)#config.description}, + {occupants, ?DICT:size(StateData#state.users)}], + #xdata{type = result, + fields = muc_roominfo:encode( + Fs, fun(T) -> translate:translate(Lang, T) end)}. -process_iq_disco_items(_From, set, Lang, _StateData) -> +-spec process_iq_disco_items(jid(), iq(), state()) -> + {error, stanza_error()} | {result, disco_items()}. +process_iq_disco_items(_From, #iq{type = set, lang = Lang}, _StateData) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}; -process_iq_disco_items(From, get, Lang, StateData) -> + {error, xmpp:err_not_allowed(Txt, Lang)}; +process_iq_disco_items(From, #iq{type = get}, StateData) -> case (StateData#state.config)#config.public_list of true -> - {result, get_mucroom_disco_items(StateData), StateData}; + {result, get_mucroom_disco_items(StateData)}; _ -> case is_occupant_or_admin(From, StateData) of true -> - {result, get_mucroom_disco_items(StateData), StateData}; + {result, get_mucroom_disco_items(StateData)}; _ -> - Txt = <<"Only occupants or administrators can perform this query">>, - {error, ?ERRT_FORBIDDEN(Lang, Txt)} + %% If the list of occupants is private, + %% the room MUST return an empty element + %% (http://xmpp.org/extensions/xep-0045.html#disco-roomitems) + {result, #disco_items{}} end end. -process_iq_captcha(_From, get, Lang, _SubEl, - _StateData) -> +-spec process_iq_captcha(jid(), iq(), state()) -> {error, stanza_error()} | + {result, undefined}. +process_iq_captcha(_From, #iq{type = get, lang = Lang}, _StateData) -> Txt = <<"Value 'get' of 'type' attribute is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}; -process_iq_captcha(_From, set, Lang, SubEl, - StateData) -> + {error, xmpp:err_not_allowed(Txt, Lang)}; +process_iq_captcha(_From, #iq{type = set, lang = Lang, sub_els = [SubEl]}, + _StateData) -> case ejabberd_captcha:process_reply(SubEl) of - ok -> {result, [], StateData}; + ok -> {result, undefined}; {error, malformed} -> Txt = <<"Incorrect CAPTCHA submit">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; + {error, xmpp:err_bad_request(Txt, Lang)}; _ -> Txt = <<"The CAPTCHA verification has failed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)} + {error, xmpp:err_not_allowed(Txt, Lang)} end. -process_iq_vcard(_From, get, _Lang, _SubEl, StateData) -> +-spec process_iq_vcard(jid(), iq(), state()) -> + {result, vcard_temp() | xmlel()} | + {result, undefined, state()} | + {error, stanza_error()}. +process_iq_vcard(_From, #iq{type = get}, StateData) -> #state{config = #config{vcard = VCardRaw}} = StateData, case fxml_stream:parse_element(VCardRaw) of - #xmlel{children = VCardEls} -> - {result, VCardEls, StateData}; + #xmlel{} = VCard -> + {result, VCard}; {error, _} -> - {result, [], StateData} + {error, xmpp:err_item_not_found()} end; -process_iq_vcard(From, set, Lang, SubEl, StateData) -> +process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [SubEl]}, + StateData) -> case get_affiliation(From, StateData) of owner -> - VCardRaw = fxml:element_to_binary(SubEl), + VCardRaw = fxml:element_to_binary(xmpp:encode(SubEl)), Config = StateData#state.config, NewConfig = Config#config{vcard = VCardRaw}, change_config(NewConfig, StateData); _ -> ErrText = <<"Owner privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + {error, xmpp:err_forbidden(ErrText, Lang)} end. -process_iq_mucsub(From, Packet, +-spec process_iq_mucsub(jid(), iq(), state()) -> + {error, stanza_error()} | + {result, undefined | muc_subscribe(), state()} | + {ignore, state()}. +process_iq_mucsub(_From, #iq{type = set, lang = Lang, + sub_els = [#muc_subscribe{}]}, + #state{config = #config{allow_subscription = false}}) -> + {error, xmpp:err_not_allowed(<<"Subscriptions are not allowed">>, Lang)}; +process_iq_mucsub(From, #iq{type = set, lang = Lang, - sub_el = #xmlel{name = <<"subscribe">>} = SubEl}, - #state{config = Config} = StateData) -> - case fxml:get_tag_attr_s(<<"nick">>, SubEl) of - <<"">> -> - Err = ?ERRT_BAD_REQUEST(Lang, <<"Missing 'nick' attribute">>), - {error, Err}; - Nick when Config#config.allow_subscription -> - LBareJID = jid:tolower(jid:remove_resource(From)), - case (?DICT):find(LBareJID, StateData#state.subscribers) of - {ok, #subscriber{nick = Nick1}} when Nick1 /= Nick -> - Nodes = get_subscription_nodes(Packet), - case {nick_collision(From, Nick, StateData), - mod_muc:can_use_nick(StateData#state.server_host, - StateData#state.host, - From, Nick)} of - {true, _} -> - ErrText = <<"That nickname is already in use by another occupant">>, - {error, ?ERRT_CONFLICT(Lang, ErrText)}; - {_, false} -> - ErrText = <<"That nickname is registered by another person">>, - {error, ?ERRT_CONFLICT(Lang, ErrText)}; - _ -> - NewStateData = set_subscriber(From, Nick, Nodes, StateData), - {result, subscription_nodes_to_events(Nodes), NewStateData} - end; - {ok, #subscriber{}} -> - Nodes = get_subscription_nodes(Packet), + sub_els = [#muc_subscribe{nick = Nick}]} = Packet, + StateData) -> + LBareJID = jid:tolower(jid:remove_resource(From)), + case (?DICT):find(LBareJID, StateData#state.subscribers) of + {ok, #subscriber{nick = Nick1}} when Nick1 /= Nick -> + Nodes = get_subscription_nodes(Packet), + case {nick_collision(From, Nick, StateData), + mod_muc:can_use_nick(StateData#state.server_host, + StateData#state.host, + From, Nick)} of + {true, _} -> + ErrText = <<"That nickname is already in use by another occupant">>, + {error, xmpp:err_conflict(ErrText, Lang)}; + {_, false} -> + ErrText = <<"That nickname is registered by another person">>, + {error, xmpp:err_conflict(ErrText, Lang)}; + _ -> NewStateData = set_subscriber(From, Nick, Nodes, StateData), - {result, subscription_nodes_to_events(Nodes), NewStateData}; - error -> - add_new_user(From, Nick, Packet, StateData) + {result, subscribe_result(Packet), NewStateData} end; - _ -> - Err = ?ERRT_NOT_ALLOWED(Lang, <<"Subscriptions are not allowed">>), - {error, Err} + {ok, #subscriber{}} -> + Nodes = get_subscription_nodes(Packet), + NewStateData = set_subscriber(From, Nick, Nodes, StateData), + {result, subscribe_result(Packet), NewStateData}; + error -> + add_new_user(From, Nick, Packet, StateData) end; -process_iq_mucsub(From, _Packet, - #iq{type = set, - sub_el = #xmlel{name = <<"unsubscribe">>}}, +process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]}, StateData) -> LBareJID = jid:tolower(jid:remove_resource(From)), case ?DICT:find(LBareJID, StateData#state.subscribers) of @@ -4671,31 +3745,28 @@ process_iq_mucsub(From, _Packet, NewStateData = StateData#state{subscribers = Subscribers, subscriber_nicks = Nicks}, store_room(NewStateData), - {result, [], NewStateData}; - error -> - {result, [], StateData} + {result, undefined, NewStateData}; + _ -> + {result, undefined, StateData} end; -process_iq_mucsub(From, _Packet, - #iq{type = get, lang = Lang, - sub_el = #xmlel{name = <<"subscriptions">>}}, +process_iq_mucsub(From, #iq{type = get, lang = Lang, + sub_els = [#muc_subscriptions{}]}, StateData) -> FAffiliation = get_affiliation(From, StateData), FRole = get_role(From, StateData), if FRole == moderator; FAffiliation == owner; FAffiliation == admin -> - Subs = dict:fold( + JIDs = dict:fold( fun(_, #subscriber{jid = J}, Acc) -> - SJID = jid:to_string(J), - [#xmlel{name = <<"subscription">>, - attrs = [{<<"jid">>, SJID}]}|Acc] + [J|Acc] end, [], StateData#state.subscribers), - {result, Subs, StateData}; + {result, #muc_subscriptions{list = JIDs}, StateData}; true -> Txt = <<"Moderator privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, Txt)} + {error, xmpp:err_forbidden(Txt, Lang)} end; -process_iq_mucsub(_From, _Packet, #iq{lang = Lang}, _StateData) -> - Txt = <<"Unrecognized subscription command">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}. +process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) -> + Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + {error, xmpp:err_bad_request(Txt, Lang)}. remove_subscriptions(StateData) -> if not (StateData#state.config)#config.allow_subscription -> @@ -4705,41 +3776,32 @@ remove_subscriptions(StateData) -> StateData end. -get_subscription_nodes(#xmlel{name = <<"iq">>} = Packet) -> - case fxml:get_subtag_with_xmlns(Packet, <<"subscribe">>, ?NS_MUCSUB) of - #xmlel{children = Els} -> - lists:flatmap( - fun(#xmlel{name = <<"event">>, attrs = Attrs}) -> - Node = fxml:get_attr_s(<<"node">>, Attrs), - case lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE, - ?NS_MUCSUB_NODES_MESSAGES, - ?NS_MUCSUB_NODES_AFFILIATIONS, - ?NS_MUCSUB_NODES_SUBJECT, - ?NS_MUCSUB_NODES_CONFIG, - ?NS_MUCSUB_NODES_PARTICIPANTS]) of - true -> - [Node]; - false -> - [] - end; - (_) -> - [] - end, Els); - false -> - [] - end; +-spec get_subscription_nodes(iq()) -> [binary()]. +get_subscription_nodes(#iq{sub_els = [#muc_subscribe{events = Nodes}]}) -> + lists:filter( + fun(Node) -> + lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE, + ?NS_MUCSUB_NODES_MESSAGES, + ?NS_MUCSUB_NODES_AFFILIATIONS, + ?NS_MUCSUB_NODES_SUBJECT, + ?NS_MUCSUB_NODES_CONFIG, + ?NS_MUCSUB_NODES_PARTICIPANTS]) + end, Nodes); get_subscription_nodes(_) -> []. -subscription_nodes_to_events(Nodes) -> - [#xmlel{name = <<"event">>, attrs = [{<<"node">>, Node}]} || Node <- Nodes]. +-spec subscribe_result(iq()) -> muc_subscribe(). +subscribe_result(#iq{sub_els = [#muc_subscribe{nick = Nick}]} = Packet) -> + #muc_subscribe{nick = Nick, events = get_subscription_nodes(Packet)}. +-spec get_title(state()) -> binary(). get_title(StateData) -> case (StateData#state.config)#config.title of <<"">> -> StateData#state.room; Name -> Name end. +-spec get_roomdesc_reply(jid(), state(), binary()) -> {item, binary()} | false. get_roomdesc_reply(JID, StateData, Tail) -> IsOccupantOrAdmin = is_occupant_or_admin(JID, StateData), @@ -4753,378 +3815,132 @@ get_roomdesc_reply(JID, StateData, Tail) -> true -> false end. +-spec get_roomdesc_tail(state(), binary()) -> binary(). get_roomdesc_tail(StateData, Lang) -> Desc = case (StateData#state.config)#config.public of true -> <<"">>; _ -> translate:translate(Lang, <<"private, ">>) end, - Len = (?DICT):fold(fun (_, _, Acc) -> Acc + 1 end, 0, - StateData#state.users), - <<" (", Desc/binary, - (iolist_to_binary(integer_to_list(Len)))/binary, ")">>. + Len = (?DICT):size(StateData#state.users), + <<" (", Desc/binary, (integer_to_binary(Len))/binary, ")">>. +-spec get_mucroom_disco_items(state()) -> disco_items(). get_mucroom_disco_items(StateData) -> - lists:map(fun ({_LJID, Info}) -> + Items = lists:map( + fun({_LJID, Info}) -> Nick = Info#user.nick, - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - jid:to_string({StateData#state.room, - StateData#state.host, - Nick})}, - {<<"name">>, Nick}], - children = []} + #disco_item{jid = jid:make(StateData#state.room, + StateData#state.host, + Nick), + name = Nick} end, - (?DICT):to_list(StateData#state.users)). + (?DICT):to_list(StateData#state.users)), + #disco_items{items = Items}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Voice request support -is_voice_request(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = - El, - false) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - case jlib:parse_xdata_submit(El) of - [_ | _] = Fields -> - case {lists:keysearch(<<"FORM_TYPE">>, 1, - Fields), - lists:keysearch(<<"muc#role">>, 1, - Fields)} - of - {{value, - {_, - [<<"http://jabber.org/protocol/muc#request">>]}}, - {value, {_, [<<"participant">>]}}} -> - true; - _ -> false - end; - _ -> false - end; - _ -> false - end; - (_, Acc) -> Acc - end, - false, Els). - +-spec prepare_request_form(jid(), binary(), binary()) -> message(). prepare_request_form(Requester, Nick, Lang) -> - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"normal">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Voice request">>)}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Either approve or decline the voice " - "request.">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"http://jabber.org/protocol/muc#request">>}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"muc#role">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"participant">>}]}]}, - ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>, - (jid:to_string(Requester))), - ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, - Nick), - ?BOOLXFIELD(<<"Grant voice to this person?">>, - <<"muc#request_allow">>, - (jlib:binary_to_atom(<<"false">>)))]}]}. + Title = translate:translate(Lang, <<"Voice request">>), + Instruction = translate:translate( + Lang, <<"Either approve or decline the voice request.">>), + Fs = muc_request:encode([{role, participant}, + {jid, Requester}, + {roomnick, Nick}, + {request_allow, false}], + fun(T) -> translate:translate(Lang, T) end), + #message{type = normal, + sub_els = [#xdata{type = form, + title = Title, + instructions = [Instruction], + fields = Fs}]}. -send_voice_request(From, StateData) -> +-spec send_voice_request(jid(), binary(), state()) -> ok. +send_voice_request(From, Lang, StateData) -> Moderators = search_role(moderator, StateData), FromNick = find_nick_by_jid(From, StateData), lists:foreach(fun ({_, User}) -> ejabberd_router:route( StateData#state.jid, User#user.jid, - prepare_request_form(From, FromNick, <<"">>)) + prepare_request_form(From, FromNick, Lang)) end, Moderators). -is_voice_approvement(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = - El, - false) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - case jlib:parse_xdata_submit(El) of - [_ | _] = Fs -> - case {lists:keysearch(<<"FORM_TYPE">>, 1, - Fs), - lists:keysearch(<<"muc#role">>, 1, - Fs), - lists:keysearch(<<"muc#request_allow">>, - 1, Fs)} - of - {{value, - {_, - [<<"http://jabber.org/protocol/muc#request">>]}}, - {value, {_, [<<"participant">>]}}, - {value, {_, [Flag]}}} - when Flag == <<"true">>; - Flag == <<"1">> -> - true; - _ -> false - end; - _ -> false - end; - _ -> false - end; - (_, Acc) -> Acc - end, - false, Els). - -extract_jid_from_voice_approvement(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>} = El, error) -> - Fields = case jlib:parse_xdata_submit(El) of - invalid -> []; - Res -> Res - end, - lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) -> - case jid:from_string(JIDStr) of - error -> error; - J -> {ok, J} - end; - (_, Acc) -> Acc - end, - error, Fields); - (_, Acc) -> Acc - end, - error, Els). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Invitation support -is_invitation(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = - El, - false) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC_USER -> - case fxml:get_subtag(El, <<"invite">>) of - false -> false; - _ -> true - end; - _ -> false - end; - (_, Acc) -> Acc - end, - false, Els). - -process_invitees(Invetees, StateDataIni) -> - lists:foldl( - fun(IJID, StateData) -> - case get_affiliation(IJID, StateData) of - none -> - NSD = set_affiliation(IJID, member, - StateData), - send_affiliation(IJID, member, - StateData), - NSD; - _ -> StateData - end - end, - StateDataIni, - Invetees). - -check_invitation(From, Packet, Lang, StateData) -> +-spec check_invitation(jid(), muc_invite(), binary(), state()) -> {error, stanza_error()} | jid(). +check_invitation(From, Invitation, Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), - CanInvite = - (StateData#state.config)#config.allow_user_invites - orelse - FAffiliation == admin orelse FAffiliation == owner, - - InviteEls = case fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) of - false -> - Txt1 = <<"No 'x' element found">>, - throw({error, ?ERRT_BAD_REQUEST(Lang, Txt1)}); - XEl -> - case fxml:get_subtags(XEl, <<"invite">>) of - false -> - Txt2 = <<"No 'invite' element found">>, - throw({error, ?ERRT_BAD_REQUEST(Lang, Txt2)}); - InviteEl1 -> - InviteEl1 - end - end, + CanInvite = (StateData#state.config)#config.allow_user_invites + orelse + FAffiliation == admin orelse FAffiliation == owner, case CanInvite of - false -> - Txt3 = <<"Invitations are not allowed in this conference">>, - throw({error, ?ERRT_NOT_ALLOWED(Lang, Txt3)}); - true -> - process_invitations(From, InviteEls, Lang, StateData) - end. - -process_invitations(From, InviteEls, Lang, StateData) -> - lists:map( - fun(InviteEl) -> - Reason = fxml:get_path_s(InviteEl, - [{elem, <<"reason">>}, cdata]), - ContinueEl = case fxml:get_path_s(InviteEl, - [{elem, <<"continue">>}]) - of - <<>> -> []; - Continue1 -> [Continue1] + false -> + Txt = <<"Invitations are not allowed in this conference">>, + {error, xmpp:err_not_allowed(Txt, Lang)}; + true -> + #muc_invite{to = JID, reason = Reason} = Invitation, + Invite = Invitation#muc_invite{to = undefined, from = From}, + Password = case (StateData#state.config)#config.password_protected of + true -> + (StateData#state.config)#config.password; + false -> + undefined end, - IEl = [#xmlel{name = <<"invite">>, - attrs = [{<<"from">>, jid:to_string(From)}], - children = - [#xmlel{name = <<"reason">>, attrs = [], - children = [{xmlcdata, Reason}]}] - ++ ContinueEl}], - PasswdEl = case - (StateData#state.config)#config.password_protected - of - true -> - [#xmlel{name = <<"password">>, attrs = [], - children = - [{xmlcdata, - (StateData#state.config)#config.password}]}]; - _ -> [] - end, - Body = #xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - [io_lib:format( - translate:translate( - Lang, - <<"~s invites you to the room ~s">>), - [jid:to_string(From), - jid:to_string({StateData#state.room, - StateData#state.host, - <<"">>})]), - case - (StateData#state.config)#config.password_protected - of - true -> - <<", ", - (translate:translate(Lang, - <<"the password is">>))/binary, - " '", - ((StateData#state.config)#config.password)/binary, - "'">>; - _ -> <<"">> - end - , - case Reason of - <<"">> -> <<"">>; - _ -> <<" (", Reason/binary, ") ">> - end])}]}, - Msg = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"normal">>}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = IEl ++ PasswdEl}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XCONFERENCE}, - {<<"jid">>, - jid:to_string({StateData#state.room, - StateData#state.host, - <<"">>})}], - children = [{xmlcdata, Reason}]}, - Body]}, - JID = case - jid:from_string(fxml:get_tag_attr_s(<<"to">>, - InviteEl)) - of - error -> - Txt = <<"Incorrect value of 'to' attribute">>, - throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)}); - JID1 -> JID1 - end, - ejabberd_hooks:run(muc_invite, StateData#state.server_host, - [StateData#state.jid, StateData#state.config, From, JID, Reason]), - ejabberd_router:route(StateData#state.jid, JID, Msg), - JID - end, - InviteEls). + XUser = #muc_user{password = Password, invites = [Invite]}, + XConference = #x_conference{jid = jid:make(StateData#state.room, + StateData#state.host), + reason = Reason}, + Body = iolist_to_binary( + [io_lib:format( + translate:translate( + Lang, + <<"~s invites you to the room ~s">>), + [jid:to_string(From), + jid:to_string({StateData#state.room, + StateData#state.host, + <<"">>})]), + case (StateData#state.config)#config.password_protected of + true -> + <<", ", + (translate:translate( + Lang, <<"the password is">>))/binary, + " '", + ((StateData#state.config)#config.password)/binary, + "'">>; + _ -> <<"">> + end, + case Reason of + <<"">> -> <<"">>; + _ -> <<" (", Reason/binary, ") ">> + end]), + Msg = #message{type = normal, + body = xmpp:mk_text(Body), + sub_els = [XUser, XConference]}, + ejabberd_router:route(StateData#state.jid, JID, Msg), + JID + end. %% Handle a message sent to the room by a non-participant. %% If it is a decline, send to the inviter. %% Otherwise, an error message is sent to the sender. -handle_roommessage_from_nonparticipant(Packet, Lang, - StateData, From) -> - case catch check_decline_invitation(Packet) of - {true, Decline_data} -> - send_decline_invitation(Decline_data, - StateData#state.jid, From); - _ -> - send_error_only_occupants(Packet, Lang, - StateData#state.jid, From) +-spec handle_roommessage_from_nonparticipant(message(), state(), jid()) -> ok. +handle_roommessage_from_nonparticipant(Packet, StateData, From) -> + case xmpp:get_subtag(Packet, #muc_user{}) of + #muc_user{decline = #muc_decline{to = #jid{} = To} = Decline} = XUser -> + NewDecline = Decline#muc_decline{to = undefined, from = From}, + NewXUser = XUser#muc_user{decline = NewDecline}, + NewPacket = xmpp:set_subtag(Packet, NewXUser), + ejabberd_router:route(StateData#state.jid, To, NewPacket); + _ -> + ErrText = <<"Only occupants are allowed to send messages " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, xmpp:get_lang(Packet)), + ejabberd_router:route_error(StateData#state.jid, From, Packet, Err) end. -%% Check in the packet is a decline. -%% If so, also returns the splitted packet. -%% This function must be catched, -%% because it crashes when the packet is not a decline message. -check_decline_invitation(Packet) -> - #xmlel{name = <<"message">>} = Packet, - XEl = fxml:get_subtag(Packet, <<"x">>), - (?NS_MUC_USER) = fxml:get_tag_attr_s(<<"xmlns">>, XEl), - DEl = fxml:get_subtag(XEl, <<"decline">>), - ToString = fxml:get_tag_attr_s(<<"to">>, DEl), - ToJID = jid:from_string(ToString), - {true, {Packet, XEl, DEl, ToJID}}. - -%% Send the decline to the inviter user. -%% The original stanza must be slightly modified. -send_decline_invitation({Packet, XEl, DEl, ToJID}, - RoomJID, FromJID) -> - FromString = - jid:to_string(jid:remove_resource(FromJID)), - #xmlel{name = <<"decline">>, attrs = DAttrs, - children = DEls} = - DEl, - DAttrs2 = lists:keydelete(<<"to">>, 1, DAttrs), - DAttrs3 = [{<<"from">>, FromString} | DAttrs2], - DEl2 = #xmlel{name = <<"decline">>, attrs = DAttrs3, - children = DEls}, - XEl2 = replace_subelement(XEl, DEl2), - Packet2 = replace_subelement(Packet, XEl2), - ejabberd_router:route(RoomJID, ToJID, Packet2). - -%% Given an element and a new subelement, -%% replace the instance of the subelement in element with the new subelement. -replace_subelement(#xmlel{name = Name, attrs = Attrs, - children = SubEls}, - NewSubEl) -> - {_, NameNewSubEl, _, _} = NewSubEl, - SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl), - #xmlel{name = Name, attrs = Attrs, children = SubEls2}. - -send_error_only_occupants(Packet, Lang, RoomJID, From) -> - ErrText = - <<"Only occupants are allowed to send messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - ejabberd_router:route(RoomJID, From, Err). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Logging @@ -5145,6 +3961,7 @@ add_to_log(Type, Data, StateData) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Users number checking +-spec tab_add_online_user(jid(), state()) -> ok. tab_add_online_user(JID, StateData) -> {LUser, LServer, LResource} = jid:tolower(JID), US = {LUser, LServer}, @@ -5152,8 +3969,10 @@ tab_add_online_user(JID, StateData) -> Host = StateData#state.host, catch ets:insert(muc_online_users, #muc_online_users{us = US, resource = LResource, - room = Room, host = Host}). + room = Room, host = Host}), + ok. +-spec tab_remove_online_user(jid(), state()) -> ok. tab_remove_online_user(JID, StateData) -> {LUser, LServer, LResource} = jid:tolower(JID), US = {LUser, LServer}, @@ -5161,8 +3980,10 @@ tab_remove_online_user(JID, StateData) -> Host = StateData#state.host, catch ets:delete_object(muc_online_users, #muc_online_users{us = US, resource = LResource, - room = Room, host = Host}). + room = Room, host = Host}), + ok. +-spec tab_count_user(jid()) -> non_neg_integer(). tab_count_user(JID) -> {LUser, LServer, _} = jid:tolower(JID), US = {LUser, LServer}, @@ -5173,9 +3994,11 @@ tab_count_user(JID) -> _ -> 0 end. +-spec element_size(stanza()) -> non_neg_integer(). element_size(El) -> - byte_size(fxml:element_to_binary(El)). + byte_size(fxml:element_to_binary(xmpp:encode(El, ?NS_CLIENT))). +-spec store_room(state()) -> ok. store_room(StateData) -> if (StateData#state.config)#config.persistent -> mod_muc:store_room(StateData#state.server_host, @@ -5185,6 +4008,7 @@ store_room(StateData) -> ok end. +-spec send_wrapped(jid(), jid(), stanza(), binary(), state()) -> ok. send_wrapped(From, To, Packet, Node, State) -> LTo = jid:tolower(To), LBareTo = jid:tolower(jid:remove_resource(To)), @@ -5196,41 +4020,37 @@ send_wrapped(From, To, Packet, Node, State) -> if IsOffline -> case ?DICT:find(LBareTo, State#state.subscribers) of {ok, #subscriber{nodes = Nodes, jid = JID}} -> - case lists:member(Node, Nodes) of - true -> + case lists:member(Node, Nodes) of + true -> NewPacket = wrap(From, JID, Packet, Node), ejabberd_router:route(State#state.jid, JID, NewPacket); - false -> - ok - end; - _ -> + false -> + ok + end; + _ -> ok end; true -> ejabberd_router:route(From, To, Packet) end. +-spec wrap(jid(), jid(), stanza(), binary()) -> message(). wrap(From, To, Packet, Node) -> - Pkt1 = jlib:replace_from_to(From, To, Packet), - Pkt2 = #xmlel{attrs = Attrs} = jlib:remove_attr(<<"xmlns">>, Pkt1), - Pkt3 = Pkt2#xmlel{attrs = [{<<"xmlns">>, <<"jabber:client">>}|Attrs]}, - Item = #xmlel{name = <<"item">>, - attrs = [{<<"id">>, randoms:get_string()}], - children = [Pkt3]}, - Items = #xmlel{name = <<"items">>, attrs = [{<<"node">>, Node}], - children = [Item]}, - Event = #xmlel{name = <<"event">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}], - children = [Items]}, - #xmlel{name = <<"message">>, children = [Event]}. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Multicast + El = xmpp:encode(xmpp:set_from_to(Packet, From, To)), + #message{ + sub_els = [#ps_event{ + items = #ps_items{ + node = Node, + items = [#ps_item{ + id = randoms:get_string(), + xml_els = [El]}]}}]}. +%% -spec send_multiple(jid(), binary(), [#user{}], stanza()) -> ok. %% send_multiple(From, Server, Users, Packet) -> %% JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)], %% ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet). +-spec send_wrapped_multiple(jid(), [#user{}], stanza(), binary(), state()) -> ok. send_wrapped_multiple(From, Users, Packet, Node, State) -> lists:foreach( fun({_, #user{jid = To}}) -> @@ -5239,10 +4059,6 @@ send_wrapped_multiple(From, Users, Packet, Node, State) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Detect messange stanzas that don't have meaninful content - -has_body_or_subject(Packet) -> - [] /= lists:dropwhile(fun - (#xmlel{name = <<"body">>}) -> false; - (#xmlel{name = <<"subject">>}) -> false; - (_) -> true - end, Packet#xmlel.children). +-spec has_body_or_subject(message()) -> boolean(). +has_body_or_subject(#message{body = Body, subject = Subj}) -> + Body /= [] orelse Subj /= []. diff --git a/src/mod_muc_sql.erl b/src/mod_muc_sql.erl index ff7ec1ebb..4b6be7d06 100644 --- a/src/mod_muc_sql.erl +++ b/src/mod_muc_sql.erl @@ -11,13 +11,16 @@ -compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_muc). +-behaviour(mod_muc_room). %% API -export([init/2, store_room/4, restore_room/3, forget_room/3, can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4, - import/1, import/2, export/1]). + import/3, export/1]). +-export([set_affiliation/6, set_affiliations/4, get_affiliation/5, + get_affiliations/3, search_affiliation/4]). --include("jlib.hrl"). +-include("jid.hrl"). -include("mod_muc.hrl"). -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -127,6 +130,21 @@ set_nick(LServer, Host, From, Nick) -> end, ejabberd_sql:sql_transaction(LServer, F). +set_affiliation(_ServerHost, _Room, _Host, _JID, _Affiliation, _Reason) -> + {error, not_implemented}. + +set_affiliations(_ServerHost, _Room, _Host, _Affiliations) -> + {error, not_implemented}. + +get_affiliation(_ServerHost, _Room, _Host, _LUser, _LServer) -> + {error, not_implemented}. + +get_affiliations(_ServerHost, _Room, _Host) -> + {error, not_implemented}. + +search_affiliation(_ServerHost, _Room, _Host, _Affiliation) -> + {error, not_implemented}. + export(_Server) -> [{muc_room, fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) -> @@ -158,21 +176,8 @@ export(_Server) -> end end}]. -import(_LServer) -> - [{<<"select name, host, opts from muc_room;">>, - fun([Name, RoomHost, SOpts]) -> - Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)), - #muc_room{name_host = {Name, RoomHost}, opts = Opts} - end}, - {<<"select jid, host, nick from muc_registered;">>, - fun([J, RoomHost, Nick]) -> - #jid{user = U, server = S} = jid:from_string(J), - #muc_registered{us_host = {{U, S}, RoomHost}, - nick = Nick} - end}]. - -import(_, _) -> - pass. +import(_, _, _) -> + ok. %%%=================================================================== %%% Internal functions diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl index df385c28c..fbd2402ee 100644 --- a/src/mod_multicast.erl +++ b/src/mod_multicast.erl @@ -45,16 +45,20 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -record(state, {lserver, lservice, access, service_limits}). +-type state() :: #state{}. -record(multicastc, {rserver, response, ts}). %% ts: timestamp (in seconds) when the cache item was last updated --record(dest, {jid_string, jid_jid, type, full_xml}). +-record(dest, {jid_string = none :: binary(), + jid_jid :: jid(), + type :: atom(), + full_xml :: address()}). %% jid_string = string() %% jid_jid = jid() @@ -168,10 +172,8 @@ handle_cast(_Msg, State) -> {noreply, State}. %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- -handle_info({route, From, To, - #xmlel{name = <<"iq">>, attrs = Attrs} = Packet}, - State) -> - case catch handle_iq(From, To, #xmlel{attrs = Attrs} = Packet, State) of +handle_info({route, From, To, #iq{} = Packet}, State) -> + case catch handle_iq(From, To, Packet, State) of {'EXIT', Reason} -> ?ERROR_MSG("Error when processing IQ stanza: ~p", [Reason]); @@ -179,13 +181,10 @@ handle_info({route, From, To, end, {noreply, State}; %% XEP33 allows only 'message' and 'presence' stanza type -handle_info({route, From, To, - #xmlel{name = Stanza_type} = Packet}, +handle_info({route, From, To, Packet}, #state{lservice = LServiceS, lserver = LServerS, access = Access, service_limits = SLimits} = - State) - when (Stanza_type == <<"message">>) or - (Stanza_type == <<"presence">>) -> + State) when ?is_stanza(Packet) -> route_untrusted(LServiceS, LServerS, Access, SLimits, From, To, Packet), {noreply, State}; @@ -220,91 +219,58 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%% IQ Request Processing %%%------------------------ -handle_iq(From, To, #xmlel{attrs = Attrs} = Packet, State) -> - IQ = jlib:iq_query_info(Packet), - case catch process_iq(From, IQ, State) of - Result when is_record(Result, iq) -> - ejabberd_router:route(To, From, jlib:iq_to_xml(Result)); - {'EXIT', Reason} -> - ?ERROR_MSG("Error when processing IQ stanza: ~p", - [Reason]), - Err = jlib:make_error_reply(Packet, - ?ERR_INTERNAL_SERVER_ERROR), - ejabberd_router:route(To, From, Err); - reply -> - LServiceS = jts(To), - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"result">> -> - process_iqreply_result(From, LServiceS, Packet, State); - <<"error">> -> - process_iqreply_error(From, LServiceS, Packet) - end; - ok -> ok +handle_iq(From, To, Packet, State) -> + try + IQ = xmpp:decode_els(Packet), + case process_iq(From, IQ, State) of + {result, SubEl} -> + ejabberd_router:route(To, From, xmpp:make_iq_result(Packet, SubEl)); + {error, Error} -> + ejabberd_router:route_error(To, From, Packet, Error); + reply -> + LServiceS = jid:to_string(To), + case Packet#iq.type of + result -> + process_iqreply_result(From, LServiceS, IQ); + error -> + process_iqreply_error(From, LServiceS, IQ) + end + end + catch _:{xmpp_codec, Why} -> + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_bad_request(xmpp:format_error(Why), Lang), + ejabberd_router:route_error(To, From, Packet, Err) end. -process_iq(From, - #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = - IQ, - State) -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}], - children = iq_disco_info(From, Lang, State)}]}; -%% disco#items request -process_iq(_, - #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}], - children = []}]}; -%% vCard request -process_iq(_, - #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, - _) -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, ?NS_VCARD}], - children = iq_vcard(Lang)}]}; -%% Unknown "set" or "get" request -process_iq(_, #iq{type = Type, sub_el = SubEl} = IQ, _) - when Type == get; Type == set -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}; -%% IQ "result" or "error". -process_iq(_, reply, _) -> reply; -%% IQ "result" or "error". -process_iq(_, _, _) -> ok. +-spec process_iq(jid(), iq(), state()) -> {result, xmpp_element()} | + {error, stanza_error()} | reply. +process_iq(From, #iq{type = get, lang = Lang, + sub_els = [#disco_info{}]}, State) -> + {result, iq_disco_info(From, Lang, State)}; +process_iq(_, #iq{type = get, sub_els = [#disco_items{}]}, _) -> + {result, #disco_items{}}; +process_iq(_, #iq{type = get, lang = Lang, sub_els = [#vcard_temp{}]}, _) -> + {result, iq_vcard(Lang)}; +process_iq(_, #iq{type = T}, _) when T == set; T == get -> + {error, xmpp:err_service_unavailable()}; +process_iq(_, _, _) -> + reply. --define(FEATURE(Feat), - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, Feat}], children = []}). +-define(FEATURE(Feat), Feat). iq_disco_info(From, Lang, State) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"service">>}, - {<<"type">>, <<"multicast">>}, - {<<"name">>, - translate:translate(Lang, <<"Multicast">>)}], - children = []}, - ?FEATURE((?NS_DISCO_INFO)), ?FEATURE((?NS_DISCO_ITEMS)), - ?FEATURE((?NS_VCARD)), ?FEATURE((?NS_ADDRESS))] - ++ iq_disco_info_extras(From, State). + #disco_info{ + identities = [#identity{category = <<"service">>, + type = <<"multicast">>, + name = translate:translate(Lang, <<"Multicast">>)}], + features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_VCARD, ?NS_ADDRESS], + xdata = iq_disco_info_extras(From, State)}. iq_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_multicast">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd Multicast service">>))/binary, - "\nCopyright (c) 2002-2016 ProcessOne">>}]}]. + Desc = translate:translate(Lang, <<"ejabberd Multicast service">>), + #vcard_temp{fn = <<"ejabberd/mod_multicast">>, + url = ?EJABBERD_URI, + desc = <>}. %%%------------------------- %%% Route @@ -313,19 +279,14 @@ iq_vcard(Lang) -> route_trusted(LServiceS, LServerS, FromJID, Destinations, Packet) -> Packet_stripped = Packet, - AAttrs = [{<<"xmlns">>, ?NS_ADDRESS}], + AAttrs = [], Delivereds = [], - Dests2 = lists:map(fun (D) -> - DS = jts(D), - XML = #xmlel{name = <<"address">>, - attrs = - [{<<"type">>, <<"bcc">>}, - {<<"jid">>, DS}], - children = []}, - #dest{jid_string = DS, jid_jid = D, - type = <<"bcc">>, full_xml = XML} - end, - Destinations), + Dests2 = lists:map( + fun(D) -> + #dest{jid_string = jid:to_string(D), + jid_jid = D, type = bcc, + full_xml = #address{type = bcc, jid = D}} + end, Destinations), Groups = group_dests(Dests2), route_common(LServerS, LServiceS, FromJID, Groups, Delivereds, Packet_stripped, AAttrs). @@ -363,20 +324,19 @@ route_untrusted(LServiceS, LServerS, Access, SLimits, route_untrusted2(LServiceS, LServerS, Access, SLimits, FromJID, Packet) -> ok = check_access(LServerS, Access, FromJID), - {ok, Packet_stripped, AAttrs, Addresses} = - strip_addresses_element(Packet), - {To_deliver, Delivereds} = - split_addresses_todeliver(Addresses), + {ok, Packet_stripped, Addresses} = strip_addresses_element(Packet), + {To_deliver, Delivereds} = split_addresses_todeliver(Addresses), Dests = convert_dest_record(To_deliver), {Dests2, Not_jids} = split_dests_jid(Dests), report_not_jid(FromJID, Packet, Not_jids), - ok = check_limit_dests(SLimits, FromJID, Packet, - Dests2), + ok = check_limit_dests(SLimits, FromJID, Packet, Dests2), Groups = group_dests(Dests2), ok = check_relay(FromJID#jid.server, LServerS, Groups), route_common(LServerS, LServiceS, FromJID, Groups, - Delivereds, Packet_stripped, AAttrs). + Delivereds, Packet_stripped, []). +-spec route_common(binary(), binary(), jid(), [#group{}], + [address()], stanza(), list()) -> any(). route_common(LServerS, LServiceS, FromJID, Groups, Delivereds, Packet_stripped, AAttrs) -> Groups2 = look_cached_servers(LServerS, Groups), @@ -435,52 +395,39 @@ check_access(LServerS, Access, From) -> %%% Strip 'addresses' XML element %%%------------------------- +-spec strip_addresses_element(stanza()) -> {ok, stanza(), [address()]}. strip_addresses_element(Packet) -> - case fxml:get_subtag(Packet, <<"addresses">>) of - #xmlel{name = <<"addresses">>, attrs = AAttrs, - children = Addresses} -> - case fxml:get_attr_s(<<"xmlns">>, AAttrs) of - ?NS_ADDRESS -> - #xmlel{name = Name, attrs = Attrs, children = Els} = - Packet, - Els_stripped = lists:keydelete(<<"addresses">>, 2, Els), - Packet_stripped = #xmlel{name = Name, attrs = Attrs, - children = Els_stripped}, - {ok, Packet_stripped, AAttrs, fxml:remove_cdata(Addresses)}; - _ -> throw(ewxmlns) - end; - _ -> throw(eadsele) + case xmpp:get_subtag(Packet, #addresses{}) of + #addresses{list = Addrs} -> + PacketStripped = xmpp:remove_subtag(Packet, #addresses{}), + {ok, PacketStripped, Addrs}; + undefined -> + throw(eadsele) end. %%%------------------------- %%% Split Addresses %%%------------------------- +-spec split_addresses_todeliver([address()]) -> {[address()], [address()]}. split_addresses_todeliver(Addresses) -> - lists:partition(fun (XML) -> - case XML of - #xmlel{name = <<"address">>, attrs = Attrs} -> - case fxml:get_attr_s(<<"delivered">>, Attrs) of - <<"true">> -> false; - _ -> - Type = fxml:get_attr_s(<<"type">>, - Attrs), - case Type of - <<"to">> -> true; - <<"cc">> -> true; - <<"bcc">> -> true; - _ -> false - end - end; - _ -> false - end - end, - Addresses). + lists:partition( + fun(#address{delivered = true}) -> + false; + (#address{type = Type}) -> + case Type of + to -> true; + cc -> true; + bcc -> true; + _ -> false + end + end, Addresses). %%%------------------------- %%% Check does not exceed limit of destinations %%%------------------------- +-spec check_limit_dests(_, jid(), stanza(), [address()]) -> ok. check_limit_dests(SLimits, FromJID, Packet, Addresses) -> SenderT = sender_type(FromJID), @@ -497,24 +444,22 @@ check_limit_dests(SLimits, FromJID, Packet, %%% Convert Destination XML to record %%%------------------------- -convert_dest_record(XMLs) -> - lists:map(fun (XML) -> - case fxml:get_tag_attr_s(<<"jid">>, XML) of - <<"">> -> #dest{jid_string = none, full_xml = XML}; - JIDS -> - Type = fxml:get_tag_attr_s(<<"type">>, XML), - JIDJ = stj(JIDS), - #dest{jid_string = JIDS, jid_jid = JIDJ, - type = Type, full_xml = XML} - end - end, - XMLs). +-spec convert_dest_record([address()]) -> [#dest{}]. +convert_dest_record(Addrs) -> + lists:map( + fun(#address{jid = undefined} = Addr) -> + #dest{jid_string = none, full_xml = Addr}; + (#address{jid = JID, type = Type} = Addr) -> + #dest{jid_string = jid:to_string(JID), jid_jid = JID, + type = Type, full_xml = Addr} + end, Addrs). %%%------------------------- %%% Split destinations by existence of JID %%% and send error messages for other dests %%%------------------------- +-spec split_dests_jid([#dest{}]) -> {[#dest{}], [#dest{}]}. split_dests_jid(Dests) -> lists:partition(fun (Dest) -> case Dest#dest.jid_string of @@ -524,8 +469,9 @@ split_dests_jid(Dests) -> end, Dests). +-spec report_not_jid(jid(), stanza(), #dest{}) -> any(). report_not_jid(From, Packet, Dests) -> - Dests2 = [fxml:element_to_binary(Dest#dest.full_xml) + Dests2 = [fxml:element_to_binary(xmpp:encode(Dest#dest.full_xml)) || Dest <- Dests], [route_error(From, From, Packet, jid_malformed, <<"This service can not process the address: ", @@ -536,6 +482,7 @@ report_not_jid(From, Packet, Dests) -> %%% Group destinations by their servers %%%------------------------- +-spec group_dests([#dest{}]) -> [#group{}]. group_dests(Dests) -> D = lists:foldl(fun (Dest, Dict) -> ServerS = (Dest#dest.jid_jid)#jid.server, @@ -575,18 +522,17 @@ build_other_xml(Dests) -> lists:foldl(fun (Dest, R) -> XML = Dest#dest.full_xml, case Dest#dest.type of - <<"to">> -> [add_delivered(XML) | R]; - <<"cc">> -> [add_delivered(XML) | R]; - <<"bcc">> -> R; + to -> [add_delivered(XML) | R]; + cc -> [add_delivered(XML) | R]; + bcc -> R; _ -> [XML | R] end end, [], Dests). -add_delivered(#xmlel{name = Name, attrs = Attrs, - children = Els}) -> - Attrs2 = [{<<"delivered">>, <<"true">>} | Attrs], - #xmlel{name = Name, attrs = Attrs2, children = Els}. +-spec add_delivered(address()) -> address(). +add_delivered(Addr) -> + Addr#address{delivered = true}. %%%------------------------- %%% Add preliminary packets @@ -636,7 +582,7 @@ decide_action_group(Group) -> route_packet(From, ToDest, Packet, AAttrs, Others, Addresses) -> Dests = case ToDest#dest.type of - <<"bcc">> -> []; + bcc -> []; _ -> [ToDest] end, route_packet2(From, ToDest#dest.jid_string, Dests, @@ -652,20 +598,20 @@ route_packet_multicast(From, ToS, Packet, AAttrs, Dests, Addresses) || DFragment <- Fragmented_dests]. -route_packet2(From, ToS, Dests, Packet, AAttrs, +-spec route_packet2(jid(), binary(), [#dest{}], stanza(), list(), [address()]) -> ok. +route_packet2(From, ToS, Dests, Packet, _AAttrs, Addresses) -> - #xmlel{name = T, attrs = A, children = C} = Packet, - C2 = case append_dests(Dests, Addresses) of - [] -> C; - ACs -> - [#xmlel{name = <<"addresses">>, attrs = AAttrs, - children = ACs} - | C] - end, - Packet2 = #xmlel{name = T, attrs = A, children = C2}, + Els = case append_dests(Dests, Addresses) of + [] -> + xmpp:get_els(Packet); + ACs -> + [#addresses{list = ACs}|xmpp:get_els(Packet)] + end, + Packet2 = xmpp:set_els(Packet, Els), ToJID = stj(ToS), ejabberd_router:route(From, ToJID, Packet2). +-spec append_dests([#dest{}], {[address()], [address()]} | [address()]) -> [address()]. append_dests(_Dests, {Others, Addresses}) -> Addresses++Others; append_dests([], Addresses) -> Addresses; @@ -676,12 +622,14 @@ append_dests([Dest | Dests], Addresses) -> %%% Check relay %%%------------------------- +-spec check_relay(binary(), binary(), [#group{}]) -> ok. check_relay(RS, LS, Gs) -> case check_relay_required(RS, LS, Gs) of false -> ok; true -> throw(edrelay) end. +-spec check_relay_required(binary(), binary(), [#group{}]) -> boolean(). check_relay_required(RServer, LServerS, Groups) -> case lists:suffix(str:tokens(LServerS, <<".">>), str:tokens(RServer, <<".">>)) of @@ -689,6 +637,7 @@ check_relay_required(RServer, LServerS, Groups) -> false -> check_relay_required(LServerS, Groups) end. +-spec check_relay_required(binary(), [#group{}]) -> boolean(). check_relay_required(LServerS, Groups) -> lists:any(fun (Group) -> Group#group.server /= LServerS end, @@ -701,19 +650,16 @@ check_relay_required(LServerS, Groups) -> send_query_info(RServerS, LServiceS) -> case str:str(RServerS, <<"echo.">>) of 1 -> false; - _ -> send_query(RServerS, LServiceS, ?NS_DISCO_INFO) + _ -> send_query(RServerS, LServiceS, #disco_info{}) end. send_query_items(RServerS, LServiceS) -> - send_query(RServerS, LServiceS, ?NS_DISCO_ITEMS). + send_query(RServerS, LServiceS, #disco_items{}). -send_query(RServerS, LServiceS, XMLNS) -> - Packet = #xmlel{name = <<"iq">>, - attrs = [{<<"to">>, RServerS}, {<<"type">>, <<"get">>}], - children = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = []}]}, +-spec send_query(binary(), binary(), [disco_info()|disco_items()]) -> ok. +send_query(RServerS, LServiceS, SubEl) -> + Packet = #iq{id = randoms:get_string(), + type = get, sub_els = [SubEl]}, ejabberd_router:route(stj(LServiceS), stj(RServerS), Packet). @@ -733,49 +679,40 @@ process_iqreply_error(From, LServiceS, _Packet) -> %%% Check protocol support: Receive response: Disco %%%------------------------- -process_iqreply_result(From, LServiceS, Packet, State) -> - #xmlel{name = <<"query">>, attrs = Attrs2, - children = Els2} = - fxml:get_subtag(Packet, <<"query">>), - case fxml:get_attr_s(<<"xmlns">>, Attrs2) of - ?NS_DISCO_INFO -> - process_discoinfo_result(From, LServiceS, Els2, State); - ?NS_DISCO_ITEMS -> - process_discoitems_result(From, LServiceS, Els2) +-spec process_iqreply_result(jid(), binary(), iq()) -> any(). +process_iqreply_result(From, LServiceS, #iq{sub_els = [SubEl]}) -> + case SubEl of + #disco_info{} -> + process_discoinfo_result(From, LServiceS, SubEl); + #disco_items{} -> + process_discoitems_result(From, LServiceS, SubEl); + _ -> + ok end. %%%------------------------- %%% Check protocol support: Receive response: Disco Info %%%------------------------- -process_discoinfo_result(From, LServiceS, Els, - _State) -> +process_discoinfo_result(From, LServiceS, DiscoInfo) -> FromS = jts(From), case search_waiter(FromS, LServiceS, info) of {found_waiter, Waiter} -> - process_discoinfo_result2(From, FromS, LServiceS, Els, + process_discoinfo_result2(From, FromS, LServiceS, DiscoInfo, Waiter); _ -> ok end. -process_discoinfo_result2(From, FromS, LServiceS, Els, +process_discoinfo_result2(From, FromS, LServiceS, + #disco_info{features = Feats} = DiscoInfo, Waiter) -> - Multicast_support = - lists:any( - fun(XML) -> - case XML of - #xmlel{name = <<"feature">>, attrs = Attrs} -> - (?NS_ADDRESS) == fxml:get_attr_s(<<"var">>, Attrs); - _ -> false - end - end, - Els), + Multicast_support = lists:member(?NS_ADDRESS, Feats), Group = Waiter#waiter.group, RServer = Group#group.server, case Multicast_support of true -> SenderT = sender_type(From), - RLimits = get_limits_xml(Els, SenderT), + RLimits = get_limits_xml(DiscoInfo, SenderT), add_response(RServer, {multicast_supported, FromS, RLimits}), FromM = Waiter#waiter.sender, DestsM = Group#group.dests, @@ -799,90 +736,58 @@ process_discoinfo_result2(From, FromS, LServiceS, Els, end end. -get_limits_xml(Els, SenderT) -> - LimitOpts = get_limits_els(Els), +get_limits_xml(DiscoInfo, SenderT) -> + LimitOpts = get_limits_els(DiscoInfo), build_remote_limit_record(LimitOpts, SenderT). -get_limits_els(Els) -> - lists:foldl(fun (XML, R) -> - case XML of - #xmlel{name = <<"x">>, attrs = Attrs, - children = SubEls} -> - case ((?NS_XDATA) == - fxml:get_attr_s(<<"xmlns">>, Attrs)) - and - (<<"result">> == - fxml:get_attr_s(<<"type">>, Attrs)) - of - true -> get_limits_fields(SubEls) ++ R; - false -> R - end; - _ -> R - end - end, - [], Els). +-spec get_limits_els(disco_info()) -> [{atom(), integer()}]. +get_limits_els(DiscoInfo) -> + lists:flatmap( + fun(#xdata{type = result} = X) -> + get_limits_fields(X); + (_) -> + [] + end, DiscoInfo#disco_info.xdata). -get_limits_fields(Fields) -> - {Head, Tail} = lists:partition(fun (Field) -> - case Field of - #xmlel{name = <<"field">>, - attrs = Attrs} -> - (<<"FORM_TYPE">> == - fxml:get_attr_s(<<"var">>, - Attrs)) - and - (<<"hidden">> == - fxml:get_attr_s(<<"type">>, - Attrs)); - _ -> false - end - end, - Fields), +-spec get_limits_fields(xdata()) -> [{atom(), integer()}]. +get_limits_fields(X) -> + {Head, Tail} = lists:partition( + fun(#xdata_field{var = Var, type = Type}) -> + Var == <<"FORM_TYPE">> andalso Type == hidden + end, X#xdata.fields), case Head of [] -> []; _ -> get_limits_values(Tail) end. -get_limits_values(Values) -> - lists:foldl(fun (Value, R) -> - case Value of - #xmlel{name = <<"field">>, attrs = Attrs, - children = SubEls} -> - [#xmlel{name = <<"value">>, children = SubElsV}] = - SubEls, - Number = fxml:get_cdata(SubElsV), - Name = fxml:get_attr_s(<<"var">>, Attrs), - [{jlib:binary_to_atom(Name), - jlib:binary_to_integer(Number)} - | R]; - _ -> R - end - end, - [], Values). +-spec get_limits_values([xdata_field()]) -> [{atom(), integer()}]. +get_limits_values(Fields) -> + lists:flatmap( + fun(#xdata_field{var = Name, values = [Number]}) -> + try + [{binary_to_atom(Name, utf8), binary_to_integer(Number)}] + catch _:badarg -> + [] + end; + (_) -> + [] + end, Fields). %%%------------------------- %%% Check protocol support: Receive response: Disco Items %%%------------------------- -process_discoitems_result(From, LServiceS, Els) -> +process_discoitems_result(From, LServiceS, #disco_items{items = Items}) -> FromS = jts(From), case search_waiter(FromS, LServiceS, items) of {found_waiter, Waiter} -> - List = lists:foldl( - fun(XML, Res) -> - case XML of - #xmlel{name = <<"item">>, attrs = Attrs} -> - SJID = fxml:get_attr_s(<<"jid">>, Attrs), - case jid:from_string(SJID) of - #jid{luser = <<"">>, - lresource = <<"">>} -> - [SJID | Res]; - _ -> Res - end; - _ -> Res - end - end, - [], Els), + List = lists:flatmap( + fun(#disco_item{jid = #jid{luser = <<"">>, + lresource = <<"">>} = J}) -> + [J]; + (_) -> + [] + end, Items), case List of [] -> received_awaiter(FromS, Waiter, LServiceS); @@ -935,7 +840,7 @@ received_awaiter(JID, Waiter, LServiceS) -> %%%------------------------- create_cache() -> - mnesia:create_table(multicastc, + ejabberd_mnesia:create(?MODULE, multicastc, [{ram_copies, [node()]}, {attributes, record_info(fields, multicastc)}]). @@ -1109,9 +1014,7 @@ get_limit_value(Name, Default, LimitOpts) -> false -> {default, Default} end. -type_of_stanza(#xmlel{name = <<"message">>}) -> message; -type_of_stanza(#xmlel{name = <<"presence">>}) -> - presence. +type_of_stanza(Stanza) -> element(1, Stanza). get_limit_number(message, Limits) -> Limits#limits.message; @@ -1144,17 +1047,10 @@ fragment_dests(Dests, Limit_number) -> %% Some parts of code are borrowed from mod_muc_room.erl -define(RFIELDT(Type, Var, Val), - #xmlel{name = <<"field">>, - attrs = [{<<"var">>, Var}, {<<"type">>, Type}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). + #xdata_field{type = Type, var = Var, values = [Val]}). -define(RFIELDV(Var, Val), - #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). + #xdata_field{var = Var, values = [Val]}). iq_disco_info_extras(From, State) -> SenderT = sender_type(From), @@ -1162,12 +1058,9 @@ iq_disco_info_extras(From, State) -> case iq_disco_info_extras2(SenderT, Service_limits) of [] -> []; List_limits_xmpp -> - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = - [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>, (?NS_ADDRESS))] - ++ List_limits_xmpp}] + #xdata{type = result, + fields = [?RFIELDT(hidden, <<"FORM_TYPE">>, ?NS_ADDRESS) + | List_limits_xmpp]} end. sender_type(From) -> @@ -1198,22 +1091,20 @@ to_binary(A) -> list_to_binary(hd(io_lib:format("~p", [A]))). %%%------------------------- route_error(From, To, Packet, ErrType, ErrText) -> - #xmlel{attrs = Attrs} = Packet, - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - Reply = make_reply(ErrType, Lang, ErrText), - Err = jlib:make_error_reply(Packet, Reply), - ejabberd_router:route(From, To, Err). + Lang = xmpp:get_lang(Packet), + Err = make_reply(ErrType, Lang, ErrText), + ejabberd_router:route_error(From, To, Packet, Err). make_reply(bad_request, Lang, ErrText) -> - ?ERRT_BAD_REQUEST(Lang, ErrText); + xmpp:err_bad_request(ErrText, Lang); make_reply(jid_malformed, Lang, ErrText) -> - ?ERRT_JID_MALFORMED(Lang, ErrText); + xmpp:err_jid_malformed(ErrText, Lang); make_reply(not_acceptable, Lang, ErrText) -> - ?ERRT_NOT_ACCEPTABLE(Lang, ErrText); + xmpp:err_not_acceptable(ErrText, Lang); make_reply(internal_server_error, Lang, ErrText) -> - ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText); + xmpp:err_internal_server_error(ErrText, Lang); make_reply(forbidden, Lang, ErrText) -> - ?ERRT_FORBIDDEN(Lang, ErrText). + xmpp:err_forbidden(ErrText, Lang). stj(String) -> jid:from_string(String). diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 1d9417117..42bc46631 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -49,12 +49,13 @@ get_sm_identity/5, get_sm_items/5, get_info/5, - handle_offline_query/3, + handle_offline_query/1, remove_expired_messages/1, remove_old_messages/2, remove_user/2, - import/1, - import/3, + import_info/0, + import_start/2, + import/5, export/1, get_queue_length/2, count_offline_messages/2, @@ -73,7 +74,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_http.hrl"). @@ -90,7 +91,7 @@ -type us() :: {binary(), binary()}. -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #offline_msg{}) -> ok | pass. +-callback import(#offline_msg{}) -> ok. -callback store_messages(binary(), us(), [#offline_msg{}], non_neg_integer(), non_neg_integer()) -> {atomic, any()}. @@ -99,10 +100,11 @@ -callback remove_expired_messages(binary()) -> {atomic, any()}. -callback remove_old_messages(non_neg_integer(), binary()) -> {atomic, any()}. -callback remove_user(binary(), binary()) -> {atomic, any()}. --callback read_message_headers(binary(), binary()) -> any(). +-callback read_message_headers(binary(), binary()) -> + [{non_neg_integer(), jid(), jid(), undefined | erlang:timestamp(), xmlel()}]. -callback read_message(binary(), binary(), non_neg_integer()) -> {ok, #offline_msg{}} | error. --callback remove_message(binary(), binary(), non_neg_integer()) -> ok. +-callback remove_message(binary(), binary(), non_neg_integer()) -> ok | {error, any()}. -callback read_all_messages(binary(), binary()) -> [#offline_msg{}]. -callback remove_all_messages(binary(), binary()) -> {atomic, any()}. -callback count_messages(binary(), binary()) -> non_neg_integer(). @@ -268,12 +270,10 @@ get_sm_features(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S} get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. -get_sm_identity(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, +get_sm_identity(Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, _Lang) -> - Identity = #xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"message-list">>}]}, - [Identity]; + [#identity{category = <<"automation">>, + type = <<"message-list">>}|Acc]; get_sm_identity(Acc, _From, _To, _Node, _Lang) -> Acc. @@ -282,15 +282,16 @@ get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID, ?NS_FLEX_OFFLINE, _Lang) -> case ejabberd_sm:get_session_pid(U, S, R) of Pid when is_pid(Pid) -> - Hdrs = read_message_headers(U, S), - BareJID = jid:to_string(jid:remove_resource(JID)), + Mod = gen_mod:db_mod(S, ?MODULE), + Hdrs = Mod:read_message_headers(U, S), + BareJID = jid:remove_resource(JID), Pid ! dont_ask_offline, {result, lists:map( - fun({Node, From, _To, _El}) -> - #xmlel{name = <<"item">>, - attrs = [{<<"jid">>, BareJID}, - {<<"node">>, Node}, - {<<"name">>, jid:to_string(From)}]} + fun({Seq, From, _To, _TS, _El}) -> + Node = integer_to_binary(Seq), + #disco_item{jid = BareJID, + node = Node, + name = jid:to_string(From)} end, Hdrs)}; none -> {result, []} @@ -298,59 +299,70 @@ get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID, get_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc. +-spec get_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()]; + ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()]. get_info(_Acc, #jid{luser = U, lserver = S, lresource = R}, - #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, _Lang) -> - N = jlib:integer_to_binary(count_offline_messages(U, S)), + #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, Lang) -> case ejabberd_sm:get_session_pid(U, S, R) of Pid when is_pid(Pid) -> Pid ! dont_ask_offline; none -> ok end, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"result">>}], - children = [#xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = [#xmlel{name = <<"value">>, - children = [{xmlcdata, - ?NS_FLEX_OFFLINE}]}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"number_of_messages">>}], - children = [#xmlel{name = <<"value">>, - children = [{xmlcdata, N}]}]}]}]; + [#xdata{type = result, + fields = flex_offline:encode( + [{number_of_messages, count_offline_messages(U, S)}], + fun(T) -> translate:translate(Lang, T) end)}]; get_info(Acc, _From, _To, _Node, _Lang) -> Acc. -handle_offline_query(#jid{luser = U, lserver = S} = From, - #jid{luser = U, lserver = S} = _To, - #iq{type = Type, sub_el = SubEl} = IQ) -> - case Type of - get -> - case fxml:get_subtag(SubEl, <<"fetch">>) of - #xmlel{} -> - handle_offline_fetch(From); - false -> - handle_offline_items_view(From, SubEl) - end; - set -> - case fxml:get_subtag(SubEl, <<"purge">>) of - #xmlel{} -> - delete_all_msgs(U, S); - false -> - handle_offline_items_remove(From, SubEl) - end - end, - IQ#iq{type = result, sub_el = []}; -handle_offline_query(_From, _To, #iq{sub_el = SubEl, lang = Lang} = IQ) -> +-spec handle_offline_query(iq()) -> iq(). +handle_offline_query(#iq{from = #jid{luser = U1, lserver = S1}, + to = #jid{luser = U2, lserver = S2}, + lang = Lang, + sub_els = [#offline{}]} = IQ) + when {U1, S1} /= {U2, S2} -> Txt = <<"Query to another users is forbidden">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}. + xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)); +handle_offline_query(#iq{from = #jid{luser = U, lserver = S} = From, + to = #jid{luser = U, lserver = S} = _To, + type = Type, lang = Lang, + sub_els = [#offline{} = Offline]} = IQ) -> + case {Type, Offline} of + {get, #offline{fetch = true, items = [], purge = false}} -> + %% TODO: report database errors + handle_offline_fetch(From), + xmpp:make_iq_result(IQ); + {get, #offline{fetch = false, items = [_|_] = Items, purge = false}} -> + case handle_offline_items_view(From, Items) of + true -> xmpp:make_iq_result(IQ); + false -> xmpp:make_error(IQ, xmpp:err_item_not_found()) + end; + {set, #offline{fetch = false, items = [], purge = true}} -> + case delete_all_msgs(U, S) of + {atomic, ok} -> + xmpp:make_iq_result(IQ); + _Err -> + Txt = <<"Database failure">>, + xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) + end; + {set, #offline{fetch = false, items = [_|_] = Items, purge = false}} -> + case handle_offline_items_remove(From, Items) of + true -> xmpp:make_iq_result(IQ); + false -> xmpp:make_error(IQ, xmpp:err_item_not_found()) + end; + _ -> + xmpp:make_error(IQ, xmpp:err_bad_request()) + end; +handle_offline_query(#iq{lang = Lang} = IQ) -> + Txt = <<"No module is handling this query">>, + xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -handle_offline_items_view(JID, #xmlel{children = Items}) -> +-spec handle_offline_items_view(jid(), [offline_item()]) -> boolean(). +handle_offline_items_view(JID, Items) -> {U, S, R} = jid:tolower(JID), - lists:foreach( - fun(Node) -> + lists:foldl( + fun(#offline_item{node = Node, action = view}, Acc) -> case fetch_msg_by_node(JID, Node) of {ok, OfflineMsg} -> case offline_msg_to_route(S, OfflineMsg) of @@ -361,46 +373,28 @@ handle_offline_items_view(JID, #xmlel{children = Items}) -> Pid ! {route, From, To, NewEl}; none -> ok - end; + end, + Acc or true; error -> - ok + Acc or false end; error -> - ok + Acc or false end - end, get_nodes_from_items(Items, <<"view">>)). + end, false, Items). -handle_offline_items_remove(JID, #xmlel{children = Items}) -> - lists:foreach( - fun(Node) -> - remove_msg_by_node(JID, Node) - end, get_nodes_from_items(Items, <<"remove">>)). +-spec handle_offline_items_remove(jid(), [offline_item()]) -> boolean(). +handle_offline_items_remove(JID, Items) -> + lists:foldl( + fun(#offline_item{node = Node, action = remove}, Acc) -> + Acc or remove_msg_by_node(JID, Node) + end, false, Items). -get_nodes_from_items(Items, Action) -> - lists:flatmap( - fun(#xmlel{name = <<"item">>, attrs = Attrs}) -> - case fxml:get_attr_s(<<"action">>, Attrs) of - Action -> - case fxml:get_attr_s(<<"node">>, Attrs) of - <<"">> -> - []; - TS -> - [TS] - end; - _ -> - [] - end; - (_) -> - [] - end, Items). - -set_offline_tag(#xmlel{children = Els} = El, Node) -> - OfflineEl = #xmlel{name = <<"offline">>, - attrs = [{<<"xmlns">>, ?NS_FLEX_OFFLINE}], - children = [#xmlel{name = <<"item">>, - attrs = [{<<"node">>, Node}]}]}, - El#xmlel{children = [OfflineEl|Els]}. +-spec set_offline_tag(message(), binary()) -> message(). +set_offline_tag(Msg, Node) -> + xmpp:set_subtag(Msg, #offline{items = [#offline_item{node = Node}]}). +-spec handle_offline_fetch(jid()) -> ok. handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) -> case ejabberd_sm:get_session_pid(U, S, R) of none -> @@ -408,12 +402,13 @@ handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) -> Pid when is_pid(Pid) -> Pid ! dont_ask_offline, lists:foreach( - fun({Node, From, To, El}) -> + fun({Node, El}) -> NewEl = set_offline_tag(El, Node), - Pid ! {route, From, To, NewEl} - end, read_message_headers(U, S)) + Pid ! {route, xmpp:get_from(El), xmpp:get_to(El), NewEl} + end, read_messages(U, S)) end. +-spec fetch_msg_by_node(jid(), binary()) -> error | {ok, #offline_msg{}}. fetch_msg_by_node(To, Seq) -> case catch binary_to_integer(Seq) of I when is_integer(I), I >= 0 -> @@ -425,33 +420,32 @@ fetch_msg_by_node(To, Seq) -> error end. +-spec remove_msg_by_node(jid(), binary()) -> boolean(). remove_msg_by_node(To, Seq) -> case catch binary_to_integer(Seq) of I when is_integer(I), I>= 0 -> LUser = To#jid.luser, LServer = To#jid.lserver, Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:remove_message(LUser, LServer, I); + Mod:remove_message(LUser, LServer, I), + true; _ -> - ok + false end. -need_to_store(LServer, Packet) -> - case has_offline_tag(Packet) of +-spec need_to_store(binary(), message()) -> boolean(). +need_to_store(_LServer, #message{type = error}) -> false; +need_to_store(LServer, #message{type = Type} = Packet) -> + case xmpp:has_subtag(Packet, #offline{}) of false -> - case {check_store_hint(Packet), - fxml:get_tag_attr_s(<<"type">>, Packet)} of - {_Hint, <<"error">>} -> - false; - {store, _Type} -> + case check_store_hint(Packet) of + store -> true; - {no_store, _Type} -> + no_store -> false; - {none, <<"groupchat">>} -> + none when Type == headline; Type == groupchat -> false; - {none, <<"headline">>} -> - false; - {none, _Type} -> + none -> case gen_mod:get_module_opt( LServer, ?MODULE, store_empty_body, fun(V) when is_boolean(V) -> V; @@ -461,15 +455,16 @@ need_to_store(LServer, Packet) -> true -> true; false -> - fxml:get_subtag(Packet, <<"body">>) /= false; + Packet#message.body /= []; unless_chat_state -> - not jlib:is_standalone_chat_state(Packet) + not xmpp_util:is_standalone_chat_state(Packet) end end; true -> false end. +-spec store_packet(jid(), jid(), message()) -> ok | stop. store_packet(From, To, Packet) -> case need_to_store(To#jid.lserver, Packet) of true -> @@ -482,14 +477,14 @@ store_packet(From, To, Packet) -> ok; NewPacket -> TimeStamp = p1_time_compat:timestamp(), - #xmlel{children = Els} = NewPacket, - Expire = find_x_expire(TimeStamp, Els), + Expire = find_x_expire(TimeStamp, NewPacket), gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) ! - #offline_msg{us = {LUser, LServer}, - timestamp = TimeStamp, - expire = Expire, - from = From, to = To, - packet = NewPacket}, + #offline_msg{us = {LUser, LServer}, + timestamp = TimeStamp, + expire = Expire, + from = From, + to = To, + packet = NewPacket}, stop end; _ -> ok @@ -497,6 +492,7 @@ store_packet(From, To, Packet) -> false -> ok end. +-spec check_store_hint(message()) -> store | no_store | none. check_store_hint(Packet) -> case has_store_hint(Packet) of true -> @@ -510,89 +506,43 @@ check_store_hint(Packet) -> end end. +-spec has_store_hint(message()) -> boolean(). has_store_hint(Packet) -> - fxml:get_subtag_with_xmlns(Packet, <<"store">>, ?NS_HINTS) =/= false. + xmpp:has_subtag(Packet, #hint{type = 'store'}). +-spec has_no_store_hint(message()) -> boolean(). has_no_store_hint(Packet) -> - fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS) =/= false - orelse - fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS) =/= false. - -has_offline_tag(Packet) -> - fxml:get_subtag_with_xmlns(Packet, <<"offline">>, ?NS_FLEX_OFFLINE) =/= false. + xmpp:has_subtag(Packet, #hint{type = 'no-store'}) + orelse + xmpp:has_subtag(Packet, #hint{type = 'no-storage'}). %% Check if the packet has any content about XEP-0022 -check_event(From, To, Packet) -> - #xmlel{name = Name, attrs = Attrs, children = Els} = - Packet, - case find_x_event(Els) of - false -> true; - El -> - case fxml:get_subtag(El, <<"id">>) of - false -> - case fxml:get_subtag(El, <<"offline">>) of - false -> true; - _ -> - ID = case fxml:get_tag_attr_s(<<"id">>, Packet) of - <<"">> -> - #xmlel{name = <<"id">>, attrs = [], - children = []}; - S -> - #xmlel{name = <<"id">>, attrs = [], - children = [{xmlcdata, S}]} - end, - ejabberd_router:route(To, From, - #xmlel{name = Name, attrs = Attrs, - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_EVENT}], - children = - [ID, - #xmlel{name - = - <<"offline">>, - attrs - = - [], - children - = - []}]}]}), - true - end; - _ -> false - end +-spec check_event(jid(), jid(), message()) -> boolean(). +check_event(From, To, #message{id = ID} = Msg) -> + case xmpp:get_subtag(Msg, #xevent{}) of + false -> + true; + #xevent{id = undefined, offline = false} -> + true; + #xevent{id = undefined, offline = true} -> + NewMsg = Msg#message{sub_els = [#xevent{id = ID, offline = true}]}, + ejabberd_router:route(To, From, xmpp:set_from_to(NewMsg, To, From)), + true; + _ -> + false end. -%% Check if the packet has subelements about XEP-0022 -find_x_event([]) -> false; -find_x_event([{xmlcdata, _} | Els]) -> - find_x_event(Els); -find_x_event([El | Els]) -> - case fxml:get_tag_attr_s(<<"xmlns">>, El) of - ?NS_EVENT -> El; - _ -> find_x_event(Els) - end. - -find_x_expire(_, []) -> never; -find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) -> - find_x_expire(TimeStamp, Els); -find_x_expire(TimeStamp, [El | Els]) -> - case fxml:get_tag_attr_s(<<"xmlns">>, El) of - ?NS_EXPIRE -> - Val = fxml:get_tag_attr_s(<<"seconds">>, El), - case catch jlib:binary_to_integer(Val) of - {'EXIT', _} -> never; - Int when Int > 0 -> - {MegaSecs, Secs, MicroSecs} = TimeStamp, - S = MegaSecs * 1000000 + Secs + Int, - MegaSecs1 = S div 1000000, - Secs1 = S rem 1000000, - {MegaSecs1, Secs1, MicroSecs}; - _ -> never - end; - _ -> find_x_expire(TimeStamp, Els) +-spec find_x_expire(erlang:timestamp(), message()) -> erlang:timestamp() | never. +find_x_expire(TimeStamp, Msg) -> + case xmpp:get_subtag(Msg, #expire{}) of + #expire{seconds = Int} -> + {MegaSecs, Secs, MicroSecs} = TimeStamp, + S = MegaSecs * 1000000 + Secs + Int, + MegaSecs1 = S div 1000000, + Secs1 = S rem 1000000, + {MegaSecs1, Secs1, MicroSecs}; + false -> + never end. resend_offline_messages(User, Server) -> @@ -601,13 +551,19 @@ resend_offline_messages(User, Server) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:pop_messages(LUser, LServer) of {ok, Rs} -> - lists:foreach(fun (R) -> - ejabberd_sm ! offline_msg_to_route(LServer, R) - end, - lists:keysort(#offline_msg.timestamp, Rs)); + lists:foreach( + fun(R) -> + case offline_msg_to_route(LServer, R) of + error -> ok; + RouteMsg -> ejabberd_sm ! RouteMsg + end + end, lists:keysort(#offline_msg.timestamp, Rs)); _ -> ok end. +-spec pop_offline_messages([{route, jid(), jid(), message()}], + binary(), binary()) -> + [{route, jid(), jid(), message()}]. pop_offline_messages(Ls, User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -616,23 +572,26 @@ pop_offline_messages(Ls, User, Server) -> {ok, Rs} -> TS = p1_time_compat:timestamp(), Ls ++ - lists:map(fun (R) -> - offline_msg_to_route(LServer, R) - end, - lists:filter( - fun(#offline_msg{packet = Pkt} = R) -> - #xmlel{children = Els} = Pkt, - Expire = case R#offline_msg.expire of - undefined -> - find_x_expire(TS, Els); - Exp -> - Exp - end, - case Expire of - never -> true; - TimeStamp -> TS < TimeStamp - end - end, Rs)); + lists:flatmap( + fun(R) -> + case offline_msg_to_route(LServer, R) of + error -> []; + RouteMsg -> [RouteMsg] + end + end, + lists:filter( + fun(#offline_msg{packet = Pkt} = R) -> + Expire = case R#offline_msg.expire of + undefined -> + find_x_expire(TS, Pkt); + Exp -> + Exp + end, + case Expire of + never -> true; + TimeStamp -> TS < TimeStamp + end + end, Rs)); _ -> Ls end. @@ -647,27 +606,26 @@ remove_old_messages(Days, Server) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:remove_old_messages(Days, LServer). +-spec remove_user(binary(), binary()) -> ok. remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:remove_user(LUser, LServer). + Mod:remove_user(LUser, LServer), + ok. %% Helper functions: %% Warn senders that their messages have been discarded: discard_warn_sender(Msgs) -> - lists:foreach(fun (#offline_msg{from = From, to = To, - packet = Packet}) -> - ErrText = <<"Your contact offline message queue is " - "full. The message has been discarded.">>, - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Err = jlib:make_error_reply(Packet, - ?ERRT_RESOURCE_CONSTRAINT(Lang, - ErrText)), - ejabberd_router:route(To, From, Err) - end, - Msgs). + lists:foreach( + fun(#offline_msg{from = From, to = To, packet = Packet}) -> + ErrText = <<"Your contact offline message queue is " + "full. The message has been discarded.">>, + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_resource_constraint(ErrText, Lang), + ejabberd_router:route_error(To, From, Packet, Err) + end, Msgs). webadmin_page(_, Host, #request{us = _US, path = [<<"user">>, U, <<"queue">>], @@ -677,51 +635,61 @@ webadmin_page(_, Host, webadmin_page(Acc, _, _) -> Acc. get_offline_els(LUser, LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Hdrs = Mod:read_message_headers(LUser, LServer), - lists:map( - fun({_Seq, From, To, Packet}) -> - jlib:replace_from_to(From, To, Packet) - end, Hdrs). + [Packet || {_Seq, Packet} <- read_messages(LUser, LServer)]. +-spec offline_msg_to_route(binary(), #offline_msg{}) -> + {route, jid(), jid(), message()} | error. offline_msg_to_route(LServer, #offline_msg{} = R) -> - El = case R#offline_msg.timestamp of - undefined -> - R#offline_msg.packet; - TS -> - jlib:add_delay_info(R#offline_msg.packet, LServer, TS, - <<"Offline Storage">>) - end, - {route, R#offline_msg.from, R#offline_msg.to, El}. + try xmpp:decode(R#offline_msg.packet, ?NS_CLIENT, [ignore_els]) of + Pkt -> + NewPkt = add_delay_info(Pkt, LServer, R#offline_msg.timestamp), + {route, R#offline_msg.from, R#offline_msg.to, NewPkt} + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("failed to decode packet ~p of user ~s: ~s", + [R#offline_msg.packet, jid:to_string(R#offline_msg.to), + xmpp:format_error(Why)]), + error + end. -read_message_headers(LUser, LServer) -> +-spec read_messages(binary(), binary()) -> [{binary(), message()}]. +read_messages(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - lists:map( - fun({Seq, From, To, El}) -> + lists:flatmap( + fun({Seq, From, To, TS, El}) -> Node = integer_to_binary(Seq), - {Node, From, To, El} + try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of + Pkt -> + Node = integer_to_binary(Seq), + Pkt1 = add_delay_info(Pkt, LServer, TS), + Pkt2 = xmpp:set_from_to(Pkt1, From, To), + [{Node, Pkt2}] + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("failed to decode packet ~p " + "of user ~s: ~s", + [El, jid:to_string(To), + xmpp:format_error(Why)]), + [] + end end, Mod:read_message_headers(LUser, LServer)). format_user_queue(Hdrs) -> lists:map( - fun({Seq, From, To, El}) -> + fun({Seq, From, To, TS, El}) -> ID = integer_to_binary(Seq), FPacket = ejabberd_web_admin:pretty_print_xml(El), SFrom = jid:to_string(From), STo = jid:to_string(To), - Stamp = fxml:get_path_s(El, [{elem, <<"delay">>}, - {attr, <<"stamp">>}]), - Time = case jlib:datetime_string_to_timestamp(Stamp) of + Time = case TS of + undefined -> + Stamp = fxml:get_path_s(El, [{elem, <<"delay">>}, + {attr, <<"stamp">>}]), + try xmpp_util:decode_timestamp(Stamp) of + {_, _, _} = Now -> format_time(Now) + catch _:_ -> + <<"">> + end; {_, _, _} = Now -> - {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:now_to_local_time(Now), - iolist_to_binary( - io_lib:format( - "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Year, Month, Day, Hour, Minute, - Second])); - _ -> - <<"">> + format_time(Now) end, ?XE(<<"tr">>, [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], @@ -733,6 +701,11 @@ format_user_queue(Hdrs) -> [?XC(<<"pre">>, FPacket)])]) end, Hdrs). +format_time(Now) -> + {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(Now), + str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", + [Year, Month, Day, Hour, Minute, Second]). + user_queue(User, Server, Query, Lang) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -743,7 +716,7 @@ user_queue(User, Server, Query, Lang) -> Hdrs = get_messages_subset(US, Server, HdrsAll), FMsgs = format_user_queue(Hdrs), [?XC(<<"h1">>, - list_to_binary(io_lib:format(?T(<<"~s's Offline Messages Queue">>), + (str:format(?T(<<"~s's Offline Messages Queue">>), [us_to_list(US)])))] ++ case Res of @@ -827,7 +800,7 @@ webadmin_user(Acc, User, Server, Lang) -> QueueLen = count_offline_messages(jid:nodeprep(User), jid:nameprep(Server)), FQueueLen = [?AC(<<"queue/">>, - (iolist_to_binary(integer_to_list(QueueLen))))], + (integer_to_binary(QueueLen)))], Acc ++ [?XCT(<<"h3">>, <<"Offline Messages:">>)] ++ FQueueLen ++ @@ -835,6 +808,7 @@ webadmin_user(Acc, User, Server, Lang) -> ?INPUTT(<<"submit">>, <<"removealloffline">>, <<"Remove All Offline Messages">>)]. +-spec delete_all_msgs(binary(), binary()) -> {atomic, any()}. delete_all_msgs(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -858,23 +832,54 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server, Acc. %% Returns as integer the number of offline messages for a given user +-spec count_offline_messages(binary(), binary()) -> non_neg_integer(). count_offline_messages(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:count_messages(LUser, LServer). +-spec add_delay_info(message(), binary(), + undefined | erlang:timestamp()) -> message(). +add_delay_info(Packet, _LServer, undefined) -> + Packet; +add_delay_info(Packet, LServer, {_, _, _} = TS) -> + xmpp_util:add_delay_info(Packet, jid:make(LServer), TS, + <<"Offline storage">>). + export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). +import_info() -> + [{<<"spool">>, 4}]. -import(LServer, DBType, Data) -> +import_start(LServer, DBType) -> Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, Data). + Mod:import(LServer, []). + +import(LServer, {sql, _}, DBType, <<"spool">>, + [LUser, XML, _Seq, _TimeStamp]) -> + El = fxml_stream:parse_element(XML), + From = #jid{} = jid:from_string( + fxml:get_attr_s(<<"from">>, El#xmlel.attrs)), + To = #jid{} = jid:from_string( + fxml:get_attr_s(<<"to">>, El#xmlel.attrs)), + Stamp = fxml:get_path_s(El, [{elem, <<"delay">>}, + {attr, <<"stamp">>}]), + TS = try xmpp_util:decode_timestamp(Stamp) of + {MegaSecs, Secs, _} -> + {MegaSecs, Secs, 0} + catch _:_ -> + p1_time_compat:timestamp() + end, + US = {LUser, LServer}, + Expire = find_x_expire(TS, El#xmlel.children), + Msg = #offline_msg{us = US, packet = El, + from = From, to = To, + timestamp = TS, expire = Expire}, + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(Msg). mod_opt_type(access_max_user_messages) -> fun acl:shaper_rules_validator/1; diff --git a/src/mod_offline_mnesia.erl b/src/mod_offline_mnesia.erl index 6a1d9e309..fb75f618e 100644 --- a/src/mod_offline_mnesia.erl +++ b/src/mod_offline_mnesia.erl @@ -13,9 +13,9 @@ -export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1, remove_old_messages/2, remove_user/2, read_message_headers/2, read_message/3, remove_message/3, read_all_messages/2, - remove_all_messages/2, count_messages/2, import/2]). + remove_all_messages/2, count_messages/2, import/1]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_offline.hrl"). -include("logger.hrl"). @@ -25,7 +25,7 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(offline_msg, + ejabberd_mnesia:create(?MODULE, offline_msg, [{disc_only_copies, [node()]}, {type, bag}, {attributes, record_info(fields, offline_msg)}]), update_table(). @@ -42,7 +42,11 @@ store_messages(_Host, US, Msgs, Len, MaxOfflineMsgs) -> mnesia:write_lock_table(offline_msg); true -> ok end, - lists:foreach(fun (M) -> mnesia:write(M) end, Msgs) + lists:foreach( + fun(#offline_msg{packet = Pkt} = M) -> + El = xmpp:encode(Pkt), + mnesia:write(M#offline_msg{packet = El}) + end, Msgs) end end, mnesia:transaction(F). @@ -107,9 +111,7 @@ read_message_headers(LUser, LServer) -> fun(#offline_msg{from = From, to = To, packet = Pkt, timestamp = TS}) -> Seq = now_to_integer(TS), - NewPkt = jlib:add_delay_info(Pkt, LServer, TS, - <<"Offline Storage">>), - {Seq, From, To, NewPkt} + {Seq, From, To, TS, Pkt} end, Msgs), lists:keysort(1, Hdrs). @@ -127,12 +129,16 @@ read_message(LUser, LServer, I) -> remove_message(LUser, LServer, I) -> US = {LUser, LServer}, TS = integer_to_now(I), - Msgs = mnesia:dirty_match_object( - offline_msg, #offline_msg{us = US, timestamp = TS, _ = '_'}), - lists:foreach( - fun(Msg) -> - mnesia:dirty_delete_object(Msg) - end, Msgs). + case mnesia:dirty_match_object( + offline_msg, #offline_msg{us = US, timestamp = TS, _ = '_'}) of + [] -> + {error, notfound}; + Msgs -> + lists:foreach( + fun(Msg) -> + mnesia:dirty_delete_object(Msg) + end, Msgs) + end. read_all_messages(LUser, LServer) -> US = {LUser, LServer}, @@ -158,7 +164,7 @@ count_messages(LUser, LServer) -> _ -> 0 end. -import(_LServer, #offline_msg{} = Msg) -> +import(#offline_msg{} = Msg) -> mnesia:dirty_write(Msg). %%%=================================================================== diff --git a/src/mod_offline_riak.erl b/src/mod_offline_riak.erl index 217e8f828..824abc89c 100644 --- a/src/mod_offline_riak.erl +++ b/src/mod_offline_riak.erl @@ -13,9 +13,9 @@ -export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1, remove_old_messages/2, remove_user/2, read_message_headers/2, read_message/3, remove_message/3, read_all_messages/2, - remove_all_messages/2, count_messages/2, import/2]). + remove_all_messages/2, count_messages/2, import/1]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_offline.hrl"). %%%=================================================================== @@ -36,9 +36,12 @@ store_messages(Host, {User, _}, Msgs, Len, MaxOfflineMsgs) -> try lists:foreach( fun(#offline_msg{us = US, + packet = Pkt, timestamp = TS} = M) -> + El = xmpp:encode(Pkt), ok = ejabberd_riak:put( - M, offline_msg_schema(), + M#offline_msg{packet = El}, + offline_msg_schema(), [{i, TS}, {'2i', [{<<"us">>, US}]}]) end, Msgs), {atomic, ok} @@ -85,9 +88,7 @@ read_message_headers(LUser, LServer) -> fun(#offline_msg{from = From, to = To, packet = Pkt, timestamp = TS}) -> Seq = now_to_integer(TS), - NewPkt = jlib:add_delay_info( - Pkt, LServer, TS, <<"Offline Storage">>), - {Seq, From, To, NewPkt} + {Seq, From, To, TS, Pkt} end, Rs), lists:keysort(1, Hdrs); _Err -> @@ -132,7 +133,7 @@ count_messages(LUser, LServer) -> 0 end. -import(_LServer, #offline_msg{us = US, timestamp = TS} = M) -> +import(#offline_msg{us = US, timestamp = TS} = M) -> ejabberd_riak:put(M, offline_msg_schema(), [{i, TS}, {'2i', [{<<"us">>, US}]}]). diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl index d9de50e04..e626df425 100644 --- a/src/mod_offline_sql.erl +++ b/src/mod_offline_sql.erl @@ -15,10 +15,9 @@ -export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1, remove_old_messages/2, remove_user/2, read_message_headers/2, read_message/3, remove_message/3, read_all_messages/2, - remove_all_messages/2, count_messages/2, import/1, import/2, - export/1]). + remove_all_messages/2, count_messages/2, import/1, export/1]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_offline.hrl"). -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -41,14 +40,14 @@ store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) -> LUser = (M#offline_msg.to)#jid.luser, From = M#offline_msg.from, To = M#offline_msg.to, - Packet = - jlib:replace_from_to(From, To, - M#offline_msg.packet), - NewPacket = - jlib:add_delay_info(Packet, Host, - M#offline_msg.timestamp, - <<"Offline Storage">>), - XML = fxml:element_to_binary(NewPacket), + Packet = xmpp:set_from_to( + M#offline_msg.packet, From, To), + NewPacket = xmpp_util:add_delay_info( + Packet, jid:make(Host), + M#offline_msg.timestamp, + <<"Offline Storage">>), + XML = fxml:element_to_binary( + xmpp:encode(NewPacket)), sql_queries:add_spool_sql(LUser, XML) end, Msgs), @@ -103,8 +102,9 @@ read_message_headers(LUser, LServer) -> case xml_to_offline_msg(XML) of {ok, #offline_msg{from = From, to = To, + timestamp = TS, packet = El}} -> - [{Seq, From, To, El}]; + [{Seq, From, To, TS, El}]; _ -> [] end @@ -171,44 +171,29 @@ export(_Server) -> [{offline_msg, fun(Host, #offline_msg{us = {LUser, LServer}, timestamp = TimeStamp, from = From, to = To, - packet = Packet}) + packet = El}) when LServer == Host -> - Packet1 = jlib:replace_from_to(From, To, Packet), - Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp, - <<"Offline Storage">>), - XML = fxml:element_to_binary(Packet2), - [?SQL("delete from spool where username=%(LUser)s;"), - ?SQL("insert into spool(username, xml) values (" - "%(LUser)s, %(XML)s);")]; + try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of + Packet -> + Packet1 = xmpp:set_from_to(Packet, From, To), + Packet2 = xmpp_util:add_delay_info( + Packet1, jid:make(LServer), + TimeStamp, <<"Offline Storage">>), + XML = fxml:element_to_binary(xmpp:encode(Packet2)), + [?SQL("delete from spool where username=%(LUser)s;"), + ?SQL("insert into spool(username, xml) values (" + "%(LUser)s, %(XML)s);")] + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("failed to decode packet ~p of user ~s@~s: ~s", + [El, LUser, LServer, xmpp:format_error(Why)]), + [] + end; (_Host, _R) -> [] end}]. -import(LServer) -> - [{<<"select username, xml from spool;">>, - fun([LUser, XML]) -> - El = #xmlel{} = fxml_stream:parse_element(XML), - From = #jid{} = jid:from_string( - fxml:get_attr_s(<<"from">>, El#xmlel.attrs)), - To = #jid{} = jid:from_string( - fxml:get_attr_s(<<"to">>, El#xmlel.attrs)), - Stamp = fxml:get_path_s(El, [{elem, <<"delay">>}, - {attr, <<"stamp">>}]), - TS = case jlib:datetime_string_to_timestamp(Stamp) of - {_, _, _} = Now -> - Now; - undefined -> - p1_time_compat:timestamp() - end, - Expire = mod_offline:find_x_expire(TS, El#xmlel.children), - #offline_msg{us = {LUser, LServer}, - from = From, to = To, - packet = El, - timestamp = TS, expire = Expire} - end}]. - -import(_, _) -> - pass. +import(_) -> + ok. %%%=================================================================== %%% Internal functions diff --git a/src/mod_ping.erl b/src/mod_ping.erl index d1b3f9322..5e861b7f7 100644 --- a/src/mod_ping.erl +++ b/src/mod_ping.erl @@ -36,7 +36,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -define(SUPERVISOR, ejabberd_sup). @@ -54,7 +54,7 @@ -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2, code_change/3]). --export([iq_ping/3, user_online/3, user_offline/3, +-export([iq_ping/1, user_online/3, user_offline/3, user_send/4, mod_opt_type/1, depends/2]). -record(state, @@ -73,10 +73,12 @@ start_link(Host, Opts) -> gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). +-spec start_ping(binary(), jid()) -> ok. start_ping(Host, JID) -> Proc = gen_mod:get_module_proc(Host, ?MODULE), gen_server:cast(Proc, {start_ping, JID}). +-spec stop_ping(binary(), jid()) -> ok. stop_ping(Host, JID) -> Proc = gen_mod:get_module_proc(Host, ?MODULE), gen_server:cast(Proc, {stop_ping, JID}). @@ -181,10 +183,7 @@ handle_cast({iq_pong, JID, timeout}, State) -> handle_cast(_Msg, State) -> {noreply, State}. handle_info({timeout, _TRef, {ping, JID}}, State) -> - IQ = #iq{type = get, - sub_el = - [#xmlel{name = <<"ping">>, - attrs = [{<<"xmlns">>, ?NS_PING}], children = []}]}, + IQ = #iq{type = get, sub_els = [#ping{}]}, Pid = self(), F = fun (Response) -> gen_server:cast(Pid, {iq_pong, JID, Response}) @@ -201,23 +200,22 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%==================================================================== %% Hook callbacks %%==================================================================== -iq_ping(_From, _To, - #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) -> - case {Type, SubEl} of - {get, #xmlel{name = <<"ping">>}} -> - IQ#iq{type = result, sub_el = []}; - _ -> - Txt = <<"Ping query is incorrect">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]} - end. +-spec iq_ping(iq()) -> iq(). +iq_ping(#iq{type = get, sub_els = [#ping{}]} = IQ) -> + xmpp:make_iq_result(IQ); +iq_ping(#iq{lang = Lang} = IQ) -> + Txt = <<"Ping query is incorrect">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)). +-spec user_online(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok. user_online(_SID, JID, _Info) -> start_ping(JID#jid.lserver, JID). +-spec user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok. user_offline(_SID, JID, _Info) -> stop_ping(JID#jid.lserver, JID). +-spec user_send(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza(). user_send(Packet, _C2SState, JID, _From) -> start_ping(JID#jid.lserver, JID), Packet. @@ -225,6 +223,7 @@ user_send(Packet, _C2SState, JID, _From) -> %%==================================================================== %% Internal functions %%==================================================================== +-spec add_timer(jid(), non_neg_integer(), map()) -> map(). add_timer(JID, Interval, Timers) -> LJID = jid:tolower(JID), NewTimers = case maps:find(LJID, Timers) of @@ -237,6 +236,7 @@ add_timer(JID, Interval, Timers) -> {ping, JID}), maps:put(LJID, TRef, NewTimers). +-spec del_timer(jid(), map()) -> map(). del_timer(JID, Timers) -> LJID = jid:tolower(JID), case maps:find(LJID, Timers) of @@ -246,6 +246,7 @@ del_timer(JID, Timers) -> _ -> Timers end. +-spec cancel_timer(reference()) -> ok. cancel_timer(TRef) -> case erlang:cancel_timer(TRef) of false -> diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl index e6f2cfbab..955e53f6f 100644 --- a/src/mod_pres_counter.erl +++ b/src/mod_pres_counter.erl @@ -33,7 +33,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -record(pres_counter, {dir, start, count, logged = false}). @@ -51,28 +51,27 @@ stop(Host) -> depends(_Host, _Opts) -> []. +-spec check_packet(allow | deny, binary(), binary(), _, + {jid(), jid(), stanza()}, in | out) -> allow | deny. check_packet(_, _User, Server, _PrivacyList, - {From, To, #xmlel{name = Name, attrs = Attrs}}, Dir) -> - case Name of - <<"presence">> -> - IsSubscription = case fxml:get_attr_s(<<"type">>, Attrs) - of - <<"subscribe">> -> true; - <<"subscribed">> -> true; - <<"unsubscribe">> -> true; - <<"unsubscribed">> -> true; - _ -> false - end, - if IsSubscription -> - JID = case Dir of - in -> To; - out -> From - end, - update(Server, JID, Dir); - true -> allow - end; - _ -> allow - end. + {From, To, #presence{type = Type}}, Dir) -> + IsSubscription = case Type of + subscribe -> true; + subscribed -> true; + unsubscribe -> true; + unsubscribed -> true; + _ -> false + end, + if IsSubscription -> + JID = case Dir of + in -> To; + out -> From + end, + update(Server, JID, Dir); + true -> allow + end; +check_packet(_, _User, _Server, _PrivacyList, _Pkt, _Dir) -> + allow. update(Server, JID, Dir) -> StormCount = gen_mod:get_module_opt(Server, ?MODULE, count, diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl index 18ff78371..d6936e1b7 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -31,25 +31,26 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_iq/3, export/1, import/1, - process_iq_set/4, process_iq_get/5, get_user_list/3, - check_packet/6, remove_user/2, +-export([start/2, stop/1, process_iq/1, export/1, import_info/0, + process_iq_set/3, process_iq_get/3, get_user_list/3, + check_packet/6, remove_user/2, encode_list_item/1, is_list_needdb/1, updated_list/3, - item_to_xml/1, get_user_lists/2, import/3, + import_start/2, import_stop/2, + item_to_xml/1, get_user_lists/2, import/5, set_privacy_list/1, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_privacy.hrl"). -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #privacy{}) -> ok | pass. --callback process_lists_get(binary(), binary()) -> {none | binary(), [xmlel()]} | error. +-callback import(#privacy{}) -> ok. +-callback process_lists_get(binary(), binary()) -> {none | binary(), [binary()]} | error. -callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found. --callback process_default_set(binary(), binary(), {value, binary()} | false) -> {atomic, any()}. +-callback process_default_set(binary(), binary(), binary() | none) -> {atomic, any()}. -callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error. -callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}. -callback set_privacy_list(#privacy{}) -> any(). @@ -96,335 +97,293 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY). -process_iq(_From, _To, IQ) -> - SubEl = IQ#iq.sub_el, - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. +-spec process_iq(iq()) -> iq(). +process_iq(IQ) -> + xmpp:make_error(IQ, xmpp:err_not_allowed()). -process_iq_get(_, From, _To, #iq{lang = Lang, sub_el = SubEl}, +-spec process_iq_get({error, stanza_error()} | {result, xmpp_element() | undefined}, + iq(), userlist()) -> {error, stanza_error()} | + {result, xmpp_element() | undefined}. +process_iq_get(_, #iq{lang = Lang, + sub_els = [#privacy_query{default = Default, + active = Active}]}, + _) when Default /= undefined; Active /= undefined -> + Txt = <<"Only element is allowed in this query">>, + {error, xmpp:err_bad_request(Txt, Lang)}; +process_iq_get(_, #iq{from = From, lang = Lang, + sub_els = [#privacy_query{lists = Lists}]}, #userlist{name = Active}) -> #jid{luser = LUser, lserver = LServer} = From, - #xmlel{children = Els} = SubEl, - case fxml:remove_cdata(Els) of - [] -> process_lists_get(LUser, LServer, Active, Lang); - [#xmlel{name = Name, attrs = Attrs}] -> - case Name of - <<"list">> -> - ListName = fxml:get_attr(<<"name">>, Attrs), - process_list_get(LUser, LServer, ListName, Lang); - _ -> - Txt = <<"Unsupported tag name">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end; - _ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Too many elements">>)} - end. + case Lists of + [] -> + process_lists_get(LUser, LServer, Active, Lang); + [#privacy_list{name = ListName}] -> + process_list_get(LUser, LServer, ListName, Lang); + _ -> + Txt = <<"Too many elements">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end; +process_iq_get(Acc, _, _) -> + Acc. +-spec process_lists_get(binary(), binary(), binary(), binary()) -> + {error, stanza_error()} | {result, privacy_query()}. process_lists_get(LUser, LServer, Active, Lang) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:process_lists_get(LUser, LServer) of - error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; - {_Default, []} -> - {result, - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_PRIVACY}], children = []}]}; - {Default, LItems} -> - DItems = case Default of - none -> LItems; - _ -> - [#xmlel{name = <<"default">>, - attrs = [{<<"name">>, Default}], children = []} - | LItems] - end, - ADItems = case Active of - none -> DItems; - _ -> - [#xmlel{name = <<"active">>, - attrs = [{<<"name">>, Active}], children = []} - | DItems] - end, - {result, - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_PRIVACY}], - children = ADItems}]} + error -> + Txt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(Txt, Lang)}; + {_Default, []} -> + {result, #privacy_query{}}; + {Default, ListNames} -> + {result, + #privacy_query{active = Active, + default = Default, + lists = [#privacy_list{name = ListName} + || ListName <- ListNames]}} end. -process_list_get(LUser, LServer, {value, Name}, Lang) -> +-spec process_list_get(binary(), binary(), binary(), binary()) -> + {error, stanza_error()} | {result, privacy_query()}. +process_list_get(LUser, LServer, Name, Lang) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:process_list_get(LUser, LServer, Name) of - error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; - not_found -> {error, ?ERR_ITEM_NOT_FOUND}; - Items -> - LItems = lists:map(fun item_to_xml/1, Items), - {result, - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_PRIVACY}], - children = - [#xmlel{name = <<"list">>, attrs = [{<<"name">>, Name}], - children = LItems}]}]} - end; -process_list_get(_LUser, _LServer, false, _Lang) -> - {error, ?ERR_BAD_REQUEST}. - -item_to_xml(Item) -> - Attrs1 = [{<<"action">>, - action_to_list(Item#listitem.action)}, - {<<"order">>, order_to_list(Item#listitem.order)}], - Attrs2 = case Item#listitem.type of - none -> Attrs1; - Type -> - [{<<"type">>, type_to_list(Item#listitem.type)}, - {<<"value">>, value_to_list(Type, Item#listitem.value)} - | Attrs1] - end, - SubEls = case Item#listitem.match_all of - true -> []; - false -> - SE1 = case Item#listitem.match_iq of - true -> - [#xmlel{name = <<"iq">>, attrs = [], - children = []}]; - false -> [] - end, - SE2 = case Item#listitem.match_message of - true -> - [#xmlel{name = <<"message">>, attrs = [], - children = []} - | SE1]; - false -> SE1 - end, - SE3 = case Item#listitem.match_presence_in of - true -> - [#xmlel{name = <<"presence-in">>, attrs = [], - children = []} - | SE2]; - false -> SE2 - end, - SE4 = case Item#listitem.match_presence_out of - true -> - [#xmlel{name = <<"presence-out">>, attrs = [], - children = []} - | SE3]; - false -> SE3 - end, - SE4 - end, - #xmlel{name = <<"item">>, attrs = Attrs2, - children = SubEls}. - -action_to_list(Action) -> - case Action of - allow -> <<"allow">>; - deny -> <<"deny">> + error -> + Txt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(Txt, Lang)}; + not_found -> + Txt = <<"No privacy list with this name found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + Items -> + LItems = lists:map(fun encode_list_item/1, Items), + {result, + #privacy_query{ + lists = [#privacy_list{name = Name, items = LItems}]}} end. -order_to_list(Order) -> - iolist_to_binary(integer_to_list(Order)). +-spec item_to_xml(listitem()) -> xmlel(). +item_to_xml(ListItem) -> + xmpp:encode(encode_list_item(ListItem)). -type_to_list(Type) -> +-spec encode_list_item(listitem()) -> privacy_item(). +encode_list_item(#listitem{action = Action, + order = Order, + type = Type, + match_all = MatchAll, + match_iq = MatchIQ, + match_message = MatchMessage, + match_presence_in = MatchPresenceIn, + match_presence_out = MatchPresenceOut, + value = Value}) -> + Item = #privacy_item{action = Action, + order = Order, + type = case Type of + none -> undefined; + Type -> Type + end, + value = encode_value(Type, Value)}, + case MatchAll of + true -> + Item; + false -> + Item#privacy_item{message = MatchMessage, + iq = MatchIQ, + presence_in = MatchPresenceIn, + presence_out = MatchPresenceOut} + end. + +-spec encode_value(listitem_type(), listitem_value()) -> binary(). +encode_value(Type, Val) -> case Type of - jid -> <<"jid">>; - group -> <<"group">>; - subscription -> <<"subscription">> + jid -> jid:to_string(Val); + group -> Val; + subscription -> + case Val of + both -> <<"both">>; + to -> <<"to">>; + from -> <<"from">>; + none -> <<"none">> + end; + none -> <<"">> end. -value_to_list(Type, Val) -> +-spec decode_value(jid | subscription | group | undefined, binary()) -> + listitem_value(). +decode_value(Type, Value) -> case Type of - jid -> jid:to_string(Val); - group -> Val; - subscription -> - case Val of - both -> <<"both">>; - to -> <<"to">>; - from -> <<"from">>; - none -> <<"none">> - end + jid -> jid:tolower(jid:from_string(Value)); + subscription -> + case Value of + <<"from">> -> from; + <<"to">> -> to; + <<"both">> -> both; + <<"none">> -> none + end; + group when Value /= <<"">> -> Value; + undefined -> none end. -list_to_action(S) -> - case S of - <<"allow">> -> allow; - <<"deny">> -> deny - end. - -process_iq_set(_, From, _To, #iq{lang = Lang, sub_el = SubEl}) -> +-spec process_iq_set({error, stanza_error()} | + {result, xmpp_element() | undefined} | + {result, xmpp_element() | undefined, userlist()}, + iq(), #userlist{}) -> + {error, stanza_error()} | + {result, xmpp_element() | undefined} | + {result, xmpp_element() | undefined, userlist()}. +process_iq_set(_, #iq{from = From, lang = Lang, + sub_els = [#privacy_query{default = Default, + active = Active, + lists = Lists}]}, + #userlist{} = UserList) -> #jid{luser = LUser, lserver = LServer} = From, - #xmlel{children = Els} = SubEl, - case fxml:remove_cdata(Els) of - [#xmlel{name = Name, attrs = Attrs, - children = SubEls}] -> - ListName = fxml:get_attr(<<"name">>, Attrs), - case Name of - <<"list">> -> - process_list_set(LUser, LServer, ListName, - fxml:remove_cdata(SubEls), Lang); - <<"active">> -> - process_active_set(LUser, LServer, ListName); - <<"default">> -> - process_default_set(LUser, LServer, ListName, Lang); - _ -> - Txt = <<"Unsupported tag name">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end; - _ -> {error, ?ERR_BAD_REQUEST} - end. + case Lists of + [#privacy_list{items = Items, name = ListName}] + when Default == undefined, Active == undefined -> + process_lists_set(LUser, LServer, ListName, Items, UserList, Lang); + [] when Default == undefined, Active /= undefined -> + process_active_set(LUser, LServer, Active, Lang); + [] when Active == undefined, Default /= undefined -> + process_default_set(LUser, LServer, Default, Lang); + _ -> + Txt = <<"The stanza MUST contain only one element, " + "one element, or one element">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end; +process_iq_set(Acc, _, _) -> + Acc. +-spec process_default_set(binary(), binary(), none | binary(), + binary()) -> {error, stanza_error()} | {result, undefined}. process_default_set(LUser, LServer, Value, Lang) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:process_default_set(LUser, LServer, Value) of - {atomic, error} -> - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; - {atomic, not_found} -> {error, ?ERR_ITEM_NOT_FOUND}; - {atomic, ok} -> {result, []}; - _ -> {error, ?ERR_INTERNAL_SERVER_ERROR} + {atomic, error} -> + Txt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(Txt, Lang)}; + {atomic, not_found} -> + Txt = <<"No privacy list with this name found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + {atomic, ok} -> + {result, undefined}; + Err -> + ?ERROR_MSG("failed to set default list '~s' for user ~s@~s: ~p", + [Value, LUser, LServer, Err]), + {error, xmpp:err_internal_server_error()} end. -process_active_set(LUser, LServer, {value, Name}) -> +-spec process_active_set(binary(), binary(), none | binary(), binary()) -> + {error, stanza_error()} | + {result, undefined, userlist()}. +process_active_set(_LUser, _LServer, none, _Lang) -> + {result, undefined, #userlist{}}; +process_active_set(LUser, LServer, Name, Lang) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:process_active_set(LUser, LServer, Name) of - error -> {error, ?ERR_ITEM_NOT_FOUND}; - Items -> - NeedDb = is_list_needdb(Items), - {result, [], - #userlist{name = Name, list = Items, needdb = NeedDb}} - end; -process_active_set(_LUser, _LServer, false) -> - {result, [], #userlist{}}. + error -> + Txt = <<"No privacy list with this name found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + Items -> + NeedDb = is_list_needdb(Items), + {result, undefined, + #userlist{name = Name, list = Items, needdb = NeedDb}} + end. +-spec set_privacy_list(privacy()) -> any(). set_privacy_list(#privacy{us = {_, LServer}} = Privacy) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:set_privacy_list(Privacy). -process_list_set(LUser, LServer, {value, Name}, Els, Lang) -> - case parse_items(Els) of - false -> {error, ?ERR_BAD_REQUEST}; - remove -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - case Mod:remove_privacy_list(LUser, LServer, Name) of - {atomic, conflict} -> - Txt = <<"Cannot remove default list">>, - {error, ?ERRT_CONFLICT(Lang, Txt)}; - {atomic, ok} -> - ejabberd_sm:route(jid:make(LUser, LServer, - <<"">>), - jid:make(LUser, LServer, <<"">>), - {broadcast, {privacy_list, - #userlist{name = Name, - list = []}, - Name}}), - {result, []}; - _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} - end; - List -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - case Mod:set_privacy_list(LUser, LServer, Name, List) of - {atomic, ok} -> - NeedDb = is_list_needdb(List), - ejabberd_sm:route(jid:make(LUser, LServer, - <<"">>), - jid:make(LUser, LServer, <<"">>), - {broadcast, {privacy_list, - #userlist{name = Name, - list = List, - needdb = NeedDb}, - Name}}), - {result, []}; - _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} - end +-spec process_lists_set(binary(), binary(), binary(), [privacy_item()], + #userlist{}, binary()) -> {error, stanza_error()} | + {result, undefined}. +process_lists_set(_LUser, _LServer, Name, [], #userlist{name = Name}, Lang) -> + Txt = <<"Cannot remove active list">>, + {error, xmpp:err_conflict(Txt, Lang)}; +process_lists_set(LUser, LServer, Name, [], _UserList, Lang) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:remove_privacy_list(LUser, LServer, Name) of + {atomic, conflict} -> + Txt = <<"Cannot remove default list">>, + {error, xmpp:err_conflict(Txt, Lang)}; + {atomic, not_found} -> + Txt = <<"No privacy list with this name found">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; + {atomic, ok} -> + ejabberd_sm:route(jid:make(LUser, LServer, + <<"">>), + jid:make(LUser, LServer, <<"">>), + {broadcast, {privacy_list, + #userlist{name = Name, + list = []}, + Name}}), + {result, undefined}; + Err -> + ?ERROR_MSG("failed to remove privacy list '~s' for user ~s@~s: ~p", + [Name, LUser, LServer, Err]), + Txt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(Txt, Lang)} end; -process_list_set(_LUser, _LServer, false, _Els, _Lang) -> - {error, ?ERR_BAD_REQUEST}. +process_lists_set(LUser, LServer, Name, Items, _UserList, Lang) -> + case catch lists:map(fun decode_item/1, Items) of + {error, Why} -> + Txt = xmpp:format_error(Why), + {error, xmpp:err_bad_request(Txt, Lang)}; + List -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:set_privacy_list(LUser, LServer, Name, List) of + {atomic, ok} -> + NeedDb = is_list_needdb(List), + ejabberd_sm:route(jid:make(LUser, LServer, + <<"">>), + jid:make(LUser, LServer, <<"">>), + {broadcast, {privacy_list, + #userlist{name = Name, + list = List, + needdb = NeedDb}, + Name}}), + {result, undefined}; + Err -> + ?ERROR_MSG("failed to set privacy list '~s' " + "for user ~s@~s: ~p", + [Name, LUser, LServer, Err]), + Txt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(Txt, Lang)} + end + end. -parse_items([]) -> remove; -parse_items(Els) -> parse_items(Els, []). - -parse_items([], Res) -> - lists:keysort(#listitem.order, Res); -parse_items([#xmlel{name = <<"item">>, attrs = Attrs, - children = SubEls} - | Els], - Res) -> - Type = fxml:get_attr(<<"type">>, Attrs), - Value = fxml:get_attr(<<"value">>, Attrs), - SAction = fxml:get_attr(<<"action">>, Attrs), - SOrder = fxml:get_attr(<<"order">>, Attrs), - Action = case catch list_to_action(element(2, SAction)) - of - {'EXIT', _} -> false; - Val -> Val - end, - Order = case catch jlib:binary_to_integer(element(2, - SOrder)) - of - {'EXIT', _} -> false; - IntVal -> - if IntVal >= 0 -> IntVal; - true -> false - end +-spec decode_item(privacy_item()) -> listitem(). +decode_item(#privacy_item{order = Order, + action = Action, + type = T, + value = V, + message = MatchMessage, + iq = MatchIQ, + presence_in = MatchPresenceIn, + presence_out = MatchPresenceOut}) -> + Value = try decode_value(T, V) + catch _:_ -> + throw({error, {bad_attr_value, <<"value">>, + <<"item">>, ?NS_PRIVACY}}) end, - if (Action /= false) and (Order /= false) -> - I1 = #listitem{action = Action, order = Order}, - I2 = case {Type, Value} of - {{value, T}, {value, V}} -> - case T of - <<"jid">> -> - case jid:from_string(V) of - error -> false; - JID -> - I1#listitem{type = jid, - value = jid:tolower(JID)} - end; - <<"group">> -> I1#listitem{type = group, value = V}; - <<"subscription">> -> - case V of - <<"none">> -> - I1#listitem{type = subscription, - value = none}; - <<"both">> -> - I1#listitem{type = subscription, - value = both}; - <<"from">> -> - I1#listitem{type = subscription, - value = from}; - <<"to">> -> - I1#listitem{type = subscription, value = to}; - _ -> false - end - end; - {{value, _}, false} -> false; - _ -> I1 - end, - case I2 of - false -> false; - _ -> - case parse_matches(I2, fxml:remove_cdata(SubEls)) of - false -> false; - I3 -> parse_items(Els, [I3 | Res]) - end - end; - true -> false - end; -parse_items(_, _Res) -> false. - -parse_matches(Item, []) -> - Item#listitem{match_all = true}; -parse_matches(Item, Els) -> parse_matches1(Item, Els). - -parse_matches1(Item, []) -> Item; -parse_matches1(Item, - [#xmlel{name = <<"message">>} | Els]) -> - parse_matches1(Item#listitem{match_message = true}, - Els); -parse_matches1(Item, [#xmlel{name = <<"iq">>} | Els]) -> - parse_matches1(Item#listitem{match_iq = true}, Els); -parse_matches1(Item, - [#xmlel{name = <<"presence-in">>} | Els]) -> - parse_matches1(Item#listitem{match_presence_in = true}, - Els); -parse_matches1(Item, - [#xmlel{name = <<"presence-out">>} | Els]) -> - parse_matches1(Item#listitem{match_presence_out = true}, - Els); -parse_matches1(_Item, [#xmlel{} | _Els]) -> false. + Type = case T of + undefined -> none; + _ -> T + end, + ListItem = #listitem{order = Order, + action = Action, + type = Type, + value = Value}, + if not (MatchMessage or MatchIQ or MatchPresenceIn or MatchPresenceOut) -> + ListItem#listitem{match_all = true}; + true -> + ListItem#listitem{match_iq = MatchIQ, + match_message = MatchMessage, + match_presence_in = MatchPresenceIn, + match_presence_out = MatchPresenceOut} + end. +-spec is_list_needdb([listitem()]) -> boolean(). is_list_needdb(Items) -> lists:any(fun (X) -> case X#listitem.type of @@ -435,6 +394,7 @@ is_list_needdb(Items) -> end, Items). +-spec get_user_list(userlist(), binary(), binary()) -> userlist(). get_user_list(_Acc, User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -444,6 +404,7 @@ get_user_list(_Acc, User, Server) -> #userlist{name = Default, list = Items, needdb = NeedDb}. +-spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error. get_user_lists(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -453,6 +414,8 @@ get_user_lists(User, Server) -> %% From is the sender, To is the destination. %% If Dir = out, User@Server is the sender account (From). %% If Dir = in, User@Server is the destination account (To). +-spec check_packet(allow | deny, binary(), binary(), userlist(), + {jid(), jid(), stanza()}, in | out) -> allow | deny. check_packet(_, _User, _Server, _UserList, {#jid{luser = <<"">>, lserver = Server} = _From, #jid{lserver = Server} = _To, _}, @@ -470,22 +433,16 @@ check_packet(_, _User, _Server, _UserList, allow; check_packet(_, User, Server, #userlist{list = List, needdb = NeedDb}, - {From, To, #xmlel{name = PName, attrs = Attrs}}, Dir) -> + {From, To, Packet}, Dir) -> case List of [] -> allow; _ -> - PType = case PName of - <<"message">> -> message; - <<"iq">> -> iq; - <<"presence">> -> - case fxml:get_attr_s(<<"type">>, Attrs) of - %% notification - <<"">> -> presence; - <<"unavailable">> -> presence; - %% subscribe, subscribed, unsubscribe, - %% unsubscribed, error, probe, or other - _ -> other - end + PType = case Packet of + #message{} -> message; + #iq{} -> iq; + #presence{type = available} -> presence; + #presence{type = unavailable} -> presence; + _ -> other end, PType2 = case {PType, Dir} of {message, in} -> message; @@ -511,6 +468,10 @@ check_packet(_, User, Server, Groups) end. +-spec check_packet_aux([listitem()], + message | iq | presence_in | presence_out | other, + ljid(), none | both | from | to, [binary()]) -> + allow | deny. %% Ptype = mesage | iq | presence_in | presence_out | other check_packet_aux([], _PType, _JID, _Subscription, _Groups) -> @@ -521,21 +482,18 @@ check_packet_aux([Item | List], PType, JID, Item, case is_ptype_match(Item, PType) of true -> - case Type of - none -> Action; - _ -> - case is_type_match(Type, Value, JID, Subscription, - Groups) - of - true -> Action; - false -> - check_packet_aux(List, PType, JID, Subscription, Groups) - end - end; + case is_type_match(Type, Value, JID, Subscription, Groups) of + true -> Action; + false -> + check_packet_aux(List, PType, JID, Subscription, Groups) + end; false -> check_packet_aux(List, PType, JID, Subscription, Groups) end. +-spec is_ptype_match(listitem(), + message | iq | presence_in | presence_out | other) -> + boolean(). is_ptype_match(Item, PType) -> case Item#listitem.match_all of true -> true; @@ -549,6 +507,10 @@ is_ptype_match(Item, PType) -> end end. +-spec is_type_match(none | jid | subscription | group, listitem_value(), + ljid(), none | both | from | to, [binary()]) -> boolean(). +is_type_match(none, _Value, _JID, _Subscription, _Groups) -> + true; is_type_match(Type, Value, JID, Subscription, Groups) -> case Type of jid -> @@ -569,30 +531,122 @@ is_type_match(Type, Value, JID, Subscription, Groups) -> group -> lists:member(Value, Groups) end. +-spec remove_user(binary(), binary()) -> any(). remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:remove_user(LUser, LServer). +-spec updated_list(userlist(), userlist(), userlist()) -> userlist(). updated_list(_, #userlist{name = OldName} = Old, #userlist{name = NewName} = New) -> if OldName == NewName -> New; true -> Old end. +numeric_to_binary(<<0, 0, _/binary>>) -> + <<"0">>; +numeric_to_binary(<<0, _, _:6/binary, T/binary>>) -> + Res = lists:foldl( + fun(X, Sum) -> + Sum*10000 + X + end, 0, [X || <> <= T]), + integer_to_binary(Res). + +bool_to_binary(<<0>>) -> <<"0">>; +bool_to_binary(<<1>>) -> <<"1">>. + +prepare_list_data(mysql, [ID|Row]) -> + [binary_to_integer(ID)|Row]; +prepare_list_data(pgsql, [<>, + SType, SValue, SAction, SOrder, SMatchAll, + SMatchIQ, SMatchMessage, SMatchPresenceIn, + SMatchPresenceOut]) -> + [ID, SType, SValue, SAction, + numeric_to_binary(SOrder), + bool_to_binary(SMatchAll), + bool_to_binary(SMatchIQ), + bool_to_binary(SMatchMessage), + bool_to_binary(SMatchPresenceIn), + bool_to_binary(SMatchPresenceOut)]. + +prepare_id(mysql, ID) -> + binary_to_integer(ID); +prepare_id(pgsql, <>) -> + ID. + +import_info() -> + [{<<"privacy_default_list">>, 2}, + {<<"privacy_list_data">>, 10}, + {<<"privacy_list">>, 4}]. + +import_start(LServer, DBType) -> + ets:new(privacy_default_list_tmp, [private, named_table]), + ets:new(privacy_list_data_tmp, [private, named_table, bag]), + ets:new(privacy_list_tmp, [private, named_table, bag, + {keypos, #privacy.us}]), + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:init(LServer, []). + +import(LServer, {sql, _}, _DBType, <<"privacy_default_list">>, [LUser, Name]) -> + US = {LUser, LServer}, + ets:insert(privacy_default_list_tmp, {US, Name}), + ok; +import(LServer, {sql, SQLType}, _DBType, <<"privacy_list_data">>, Row1) -> + [ID|Row] = prepare_list_data(SQLType, Row1), + case mod_privacy_sql:raw_to_item(Row) of + [Item] -> + IS = {ID, LServer}, + ets:insert(privacy_list_data_tmp, {IS, Item}), + ok; + [] -> + ok + end; +import(LServer, {sql, SQLType}, _DBType, <<"privacy_list">>, + [LUser, Name, ID, _TimeStamp]) -> + US = {LUser, LServer}, + IS = {prepare_id(SQLType, ID), LServer}, + Default = case ets:lookup(privacy_default_list_tmp, US) of + [{_, Name}] -> Name; + _ -> none + end, + case [Item || {_, Item} <- ets:lookup(privacy_list_data_tmp, IS)] of + [_|_] = Items -> + Privacy = #privacy{us = {LUser, LServer}, + default = Default, + lists = [{Name, Items}]}, + ets:insert(privacy_list_tmp, Privacy), + ets:delete(privacy_list_data_tmp, IS), + ok; + _ -> + ok + end. + +import_stop(_LServer, DBType) -> + import_next(DBType, ets:first(privacy_list_tmp)), + ets:delete(privacy_default_list_tmp), + ets:delete(privacy_list_data_tmp), + ets:delete(privacy_list_tmp), + ok. + +import_next(_DBType, '$end_of_table') -> + ok; +import_next(DBType, US) -> + [P|_] = Ps = ets:lookup(privacy_list_tmp, US), + Lists = lists:flatmap( + fun(#privacy{lists = Lists}) -> + Lists + end, Ps), + Privacy = P#privacy{lists = Lists}, + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(Privacy), + import_next(DBType, ets:next(privacy_list_tmp, US)). + export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). - -import(LServer, DBType, Data) -> - Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, Data). - depends(_Host, _Opts) -> []. diff --git a/src/mod_privacy_mnesia.erl b/src/mod_privacy_mnesia.erl index 4026b7f64..eca6f8ecd 100644 --- a/src/mod_privacy_mnesia.erl +++ b/src/mod_privacy_mnesia.erl @@ -15,9 +15,9 @@ process_default_set/3, process_active_set/3, remove_privacy_list/3, set_privacy_list/1, set_privacy_list/4, get_user_list/2, get_user_lists/2, - remove_user/2, import/2]). + remove_user/2, import/1]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_privacy.hrl"). -include("logger.hrl"). @@ -25,7 +25,7 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(privacy, + ejabberd_mnesia:create(?MODULE, privacy, [{disc_copies, [node()]}, {attributes, record_info(fields, privacy)}]), update_table(). @@ -35,11 +35,7 @@ process_lists_get(LUser, LServer) -> {'EXIT', _Reason} -> error; [] -> {none, []}; [#privacy{default = Default, lists = Lists}] -> - LItems = lists:map(fun ({N, _}) -> - #xmlel{name = <<"list">>, - attrs = [{<<"name">>, N}], - children = []} - end, Lists), + LItems = lists:map(fun ({N, _}) -> N end, Lists), {Default, LItems} end. @@ -54,7 +50,15 @@ process_list_get(LUser, LServer, Name) -> end end. -process_default_set(LUser, LServer, {value, Name}) -> +process_default_set(LUser, LServer, none) -> + F = fun () -> + case mnesia:read({privacy, {LUser, LServer}}) of + [] -> ok; + [R] -> mnesia:write(R#privacy{default = none}) + end + end, + mnesia:transaction(F); +process_default_set(LUser, LServer, Name) -> F = fun () -> case mnesia:read({privacy, {LUser, LServer}}) of [] -> not_found; @@ -68,14 +72,6 @@ process_default_set(LUser, LServer, {value, Name}) -> end end end, - mnesia:transaction(F); -process_default_set(LUser, LServer, false) -> - F = fun () -> - case mnesia:read({privacy, {LUser, LServer}}) of - [] -> ok; - [R] -> mnesia:write(R#privacy{default = none}) - end - end, mnesia:transaction(F). process_active_set(LUser, LServer, Name) -> @@ -148,7 +144,7 @@ remove_user(LUser, LServer) -> F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end, mnesia:transaction(F). -import(_LServer, #privacy{} = P) -> +import(#privacy{} = P) -> mnesia:dirty_write(P). %%%=================================================================== diff --git a/src/mod_privacy_riak.erl b/src/mod_privacy_riak.erl index 0c43e74f9..b96a08cbc 100644 --- a/src/mod_privacy_riak.erl +++ b/src/mod_privacy_riak.erl @@ -15,11 +15,11 @@ process_default_set/3, process_active_set/3, remove_privacy_list/3, set_privacy_list/1, set_privacy_list/4, get_user_list/2, get_user_lists/2, - remove_user/2, import/2]). + remove_user/2, import/1]). -export([privacy_schema/0]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_privacy.hrl"). %%%=================================================================== @@ -31,12 +31,7 @@ init(_Host, _Opts) -> process_lists_get(LUser, LServer) -> case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of {ok, #privacy{default = Default, lists = Lists}} -> - LItems = lists:map(fun ({N, _}) -> - #xmlel{name = <<"list">>, - attrs = [{<<"name">>, N}], - children = []} - end, - Lists), + LItems = lists:map(fun ({N, _}) -> N end, Lists), {Default, LItems}; {error, notfound} -> {none, []}; @@ -57,7 +52,15 @@ process_list_get(LUser, LServer, Name) -> error end. -process_default_set(LUser, LServer, {value, Name}) -> +process_default_set(LUser, LServer, none) -> + {atomic, + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, R} -> + ejabberd_riak:put(R#privacy{default = none}, privacy_schema()); + {error, _} -> + ok + end}; +process_default_set(LUser, LServer, Name) -> {atomic, case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of {ok, #privacy{lists = Lists} = P} -> @@ -71,14 +74,6 @@ process_default_set(LUser, LServer, {value, Name}) -> end; {error, _} -> not_found - end}; -process_default_set(LUser, LServer, false) -> - {atomic, - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, R} -> - ejabberd_riak:put(R#privacy{default = none}, privacy_schema()); - {error, _} -> - ok end}. process_active_set(LUser, LServer, Name) -> @@ -150,7 +145,7 @@ get_user_lists(LUser, LServer) -> remove_user(LUser, LServer) -> {atomic, ejabberd_riak:delete(privacy, {LUser, LServer})}. -import(_LServer, #privacy{} = P) -> +import(#privacy{} = P) -> ejabberd_riak:put(P, privacy_schema()). %%%=================================================================== diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl index a700db391..1984237c6 100644 --- a/src/mod_privacy_sql.erl +++ b/src/mod_privacy_sql.erl @@ -17,7 +17,7 @@ process_default_set/3, process_active_set/3, remove_privacy_list/3, set_privacy_list/1, set_privacy_list/4, get_user_list/2, get_user_lists/2, - remove_user/2, import/1, import/2, export/1]). + remove_user/2, import/1, export/1]). -export([item_to_raw/1, raw_to_item/1, sql_add_privacy_list/2, @@ -28,7 +28,7 @@ sql_get_privacy_list_id_t/2, sql_set_default_privacy_list/2, sql_set_privacy_list/2]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_privacy.hrl"). -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -47,12 +47,7 @@ process_lists_get(LUser, LServer) -> end, case catch sql_get_privacy_list_names(LUser, LServer) of {selected, Names} -> - LItems = lists:map(fun ({N}) -> - #xmlel{name = <<"list">>, - attrs = [{<<"name">>, N}], - children = []} - end, - Names), + LItems = lists:map(fun ({N}) -> N end, Names), {Default, LItems}; _ -> error end. @@ -69,7 +64,15 @@ process_list_get(LUser, LServer, Name) -> _ -> error end. -process_default_set(LUser, LServer, {value, Name}) -> +process_default_set(LUser, LServer, none) -> + case catch sql_unset_default_privacy_list(LUser, + LServer) + of + {'EXIT', _Reason} -> {atomic, error}; + {error, _Reason} -> {atomic, error}; + _ -> {atomic, ok} + end; +process_default_set(LUser, LServer, Name) -> F = fun () -> case sql_get_privacy_list_names_t(LUser) of {selected, []} -> not_found; @@ -80,15 +83,7 @@ process_default_set(LUser, LServer, {value, Name}) -> end end end, - sql_queries:sql_transaction(LServer, F); -process_default_set(LUser, LServer, false) -> - case catch sql_unset_default_privacy_list(LUser, - LServer) - of - {'EXIT', _Reason} -> {atomic, error}; - {error, _Reason} -> {atomic, error}; - _ -> {atomic, ok} - end. + sql_queries:sql_transaction(LServer, F). process_active_set(LUser, LServer, Name) -> case catch sql_get_privacy_list_id(LUser, LServer, Name) of @@ -203,7 +198,7 @@ export(Server) -> [<<"select id from privacy_list order by " "id desc limit 1;">>]) of {selected, [<<"id">>], [[I]]} -> - put(id, jlib:binary_to_integer(I)); + put(id, binary_to_integer(I)); _ -> put(id, 0) end, @@ -254,37 +249,8 @@ get_id() -> put(id, ID + 1), ID + 1. -import(LServer) -> - [{<<"select username from privacy_list;">>, - fun([LUser]) -> - Default = case sql_get_default_privacy_list_t(LUser) of - {selected, [<<"name">>], []} -> - none; - {selected, [<<"name">>], [[DefName]]} -> - DefName; - _ -> - none - end, - {selected, [<<"name">>], Names} = - sql_get_privacy_list_names_t(LUser), - Lists = lists:flatmap( - fun([Name]) -> - case sql_get_privacy_list_data_t(LUser, Name) of - {selected, _, RItems} -> - [{Name, - lists:map(fun raw_to_item/1, - RItems)}]; - _ -> - [] - end - end, Names), - #privacy{default = Default, - us = {LUser, LServer}, - lists = Lists} - end}]. - -import(_, _) -> - pass. +import(_) -> + ok. %%%=================================================================== %%% Internal functions @@ -368,9 +334,6 @@ sql_get_privacy_list_id_t(LUser, Name) -> sql_get_privacy_list_data(LUser, LServer, Name) -> sql_queries:get_privacy_list_data(LServer, LUser, Name). -sql_get_privacy_list_data_t(LUser, Name) -> - sql_queries:get_privacy_list_data_t(LUser, Name). - sql_get_privacy_list_data_by_id(ID, LServer) -> sql_queries:get_privacy_list_data_by_id(LServer, ID). diff --git a/src/mod_private.erl b/src/mod_private.erl index f0e4632f6..d11cad36f 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -31,25 +31,22 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_sm_iq/3, import/3, - remove_user/2, get_data/2, export/1, import/1, - mod_opt_type/1, set_data/3, depends/2]). +-export([start/2, stop/1, process_sm_iq/1, import_info/0, + remove_user/2, get_data/2, get_data/3, export/1, + import/5, import_start/2, mod_opt_type/1, set_data/3, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_private.hrl"). -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #private_storage{}) -> ok | pass. +-callback import(binary(), binary(), [binary()]) -> ok. -callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}. -callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error. -callback get_all_data(binary(), binary()) -> [xmlel()]. - --define(Xmlel_Query(Attrs, Children), - #xmlel{name = <<"query">>, attrs = Attrs, - children = Children}). +-callback remove_user(binary(), binary()) -> {atomic, any()}. start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, @@ -67,111 +64,80 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE). -process_sm_iq(#jid{luser = LUser, lserver = LServer}, - #jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ) - when IQ#iq.type == set -> - case IQ#iq.sub_el of - #xmlel{name = <<"query">>, children = Xmlels} -> - case filter_xmlels(Xmlels) of - [] -> - Txt = <<"No private data found in this query">>, - IQ#iq{type = error, - sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]}; - Data -> - set_data(LUser, LServer, Data), - IQ#iq{type = result, sub_el = []} - end; - _ -> - Txt = <<"No query found">>, - IQ#iq{type = error, - sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]} +-spec process_sm_iq(iq()) -> iq(). +process_sm_iq(#iq{type = Type, lang = Lang, + from = #jid{luser = LUser, lserver = LServer}, + to = #jid{luser = LUser, lserver = LServer}, + sub_els = [#private{xml_els = Els0}]} = IQ) -> + case filter_xmlels(Els0) of + [] -> + Txt = <<"No private data found in this query">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); + Data when Type == set -> + set_data(LUser, LServer, Data), + xmpp:make_iq_result(IQ); + Data when Type == get -> + StorageEls = get_data(LUser, LServer, Data), + xmpp:make_iq_result(IQ, #private{xml_els = StorageEls}) end; -%% -process_sm_iq(#jid{luser = LUser, lserver = LServer}, - #jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ) - when IQ#iq.type == get -> - case IQ#iq.sub_el of - #xmlel{name = <<"query">>, attrs = Attrs, - children = Xmlels} -> - case filter_xmlels(Xmlels) of - [] -> - Txt = <<"No private data found in this query">>, - IQ#iq{type = error, - sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]}; - Data -> - case catch get_data(LUser, LServer, Data) of - {'EXIT', _Reason} -> - Txt = <<"Database failure">>, - IQ#iq{type = error, - sub_el = - [IQ#iq.sub_el, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}; - Storage_Xmlels -> - IQ#iq{type = result, - sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]} - end - end; - _ -> - Txt = <<"No query found">>, - IQ#iq{type = error, - sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]} - end; -%% -process_sm_iq(_From, _To, #iq{lang = Lang} = IQ) -> +process_sm_iq(#iq{lang = Lang} = IQ) -> Txt = <<"Query to another users is forbidden">>, - IQ#iq{type = error, - sub_el = [IQ#iq.sub_el, ?ERRT_FORBIDDEN(Lang, Txt)]}. + xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)). -filter_xmlels(Xmlels) -> filter_xmlels(Xmlels, []). - -filter_xmlels([], Data) -> lists:reverse(Data); -filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels], - Data) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - <<"">> -> []; - XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data]) - end; -filter_xmlels([_ | Xmlels], Data) -> - filter_xmlels(Xmlels, Data). +-spec filter_xmlels([xmlel()]) -> [{binary(), xmlel()}]. +filter_xmlels(Els) -> + lists:flatmap( + fun(#xmlel{} = El) -> + case fxml:get_tag_attr_s(<<"xmlns">>, El) of + <<"">> -> []; + NS -> [{NS, El}] + end + end, Els). +-spec set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}. set_data(LUser, LServer, Data) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:set_data(LUser, LServer, Data). +-spec get_data(binary(), binary(), [{binary(), xmlel()}]) -> [xmlel()]. get_data(LUser, LServer, Data) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - get_data(LUser, LServer, Data, Mod, []). - -get_data(_LUser, _LServer, [], _Mod, Storage_Xmlels) -> - lists:reverse(Storage_Xmlels); -get_data(LUser, LServer, [{XmlNS, Xmlel} | Data], Mod, Storage_Xmlels) -> - case Mod:get_data(LUser, LServer, XmlNS) of - {ok, Storage_Xmlel} -> - get_data(LUser, LServer, Data, Mod, [Storage_Xmlel | Storage_Xmlels]); - error -> - get_data(LUser, LServer, Data, Mod, [Xmlel | Storage_Xmlels]) - end. + lists:map( + fun({NS, El}) -> + case Mod:get_data(LUser, LServer, NS) of + {ok, StorageEl} -> + StorageEl; + error -> + El + end + end, Data). +-spec get_data(binary(), binary()) -> [xmlel()]. get_data(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_all_data(LUser, LServer). +-spec remove_user(binary(), binary()) -> any(). remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(Server, ?MODULE), Mod:remove_user(LUser, LServer). +import_info() -> + [{<<"private_storage">>, 4}]. + +import_start(LServer, DBType) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:init(LServer, []). + export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). - -import(LServer, DBType, PD) -> +import(LServer, {sql, _}, DBType, Tab, L) -> Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, PD). + Mod:import(LServer, Tab, L). depends(_Host, _Opts) -> []. diff --git a/src/mod_private_mnesia.erl b/src/mod_private_mnesia.erl index 7a852c4f8..42e5ddfd8 100644 --- a/src/mod_private_mnesia.erl +++ b/src/mod_private_mnesia.erl @@ -11,9 +11,9 @@ %% API -export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2, - import/2]). + import/3]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_private.hrl"). -include("logger.hrl"). @@ -21,7 +21,7 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(private_storage, + ejabberd_mnesia:create(?MODULE, private_storage, [{disc_only_copies, [node()]}, {attributes, record_info(fields, private_storage)}]), @@ -72,7 +72,10 @@ remove_user(LUser, LServer) -> end, mnesia:transaction(F). -import(_LServer, #private_storage{} = PS) -> +import(LServer, <<"private_storage">>, + [LUser, XMLNS, XML, _TimeStamp]) -> + El = #xmlel{} = fxml_stream:parse_element(XML), + PS = #private_storage{usns = {LUser, LServer, XMLNS}, xml = El}, mnesia:dirty_write(PS). %%%=================================================================== diff --git a/src/mod_private_riak.erl b/src/mod_private_riak.erl index 11cfa4770..7b091c4a6 100644 --- a/src/mod_private_riak.erl +++ b/src/mod_private_riak.erl @@ -12,9 +12,9 @@ %% API -export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2, - import/2]). + import/3]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_private.hrl"). %%%=================================================================== @@ -56,7 +56,10 @@ remove_user(LUser, LServer) -> {atomic, ejabberd_riak:delete_by_index(private_storage, <<"us">>, {LUser, LServer})}. -import(_LServer, #private_storage{usns = {LUser, LServer, _}} = PS) -> +import(LServer, <<"private_storage">>, + [LUser, XMLNS, XML, _TimeStamp]) -> + El = #xmlel{} = fxml_stream:parse_element(XML), + PS = #private_storage{usns = {LUser, LServer, XMLNS}, xml = El}, ejabberd_riak:put(PS, private_storage_schema(), [{'2i', [{<<"us">>, {LUser, LServer}}]}]). diff --git a/src/mod_private_sql.erl b/src/mod_private_sql.erl index 0e9d1b61e..b459916e4 100644 --- a/src/mod_private_sql.erl +++ b/src/mod_private_sql.erl @@ -12,9 +12,9 @@ %% API -export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2, - import/1, import/2, export/1]). + import/3, export/1]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_private.hrl"). %%%=================================================================== @@ -77,16 +77,8 @@ export(_Server) -> [] end}]. -import(LServer) -> - [{<<"select username, namespace, data from private_storage;">>, - fun([LUser, XMLNS, XML]) -> - El = #xmlel{} = fxml_stream:parse_element(XML), - #private_storage{usns = {LUser, LServer, XMLNS}, - xml = El} - end}]. - -import(_, _) -> - pass. +import(_, _, _) -> + ok. %%%=================================================================== %%% Internal functions diff --git a/src/mod_privilege.erl b/src/mod_privilege.erl index af6dacec4..c1ac5a3fc 100644 --- a/src/mod_privilege.erl +++ b/src/mod_privilege.erl @@ -1,363 +1,375 @@ -%%%-------------------------------------------------------------------------------------- +%%%------------------------------------------------------------------- %%% File : mod_privilege.erl %%% Author : Anna Mukharram -%%% Purpose : This module is an implementation for XEP-0356: Privileged Entity -%%%-------------------------------------------------------------------------------------- - +%%% Purpose : XEP-0356: Privileged Entity +%%% +%%% +%%% 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(mod_privilege). -author('amuhar3@gmail.com'). -protocol({xep, 0356, '0.2.1'}). --export([advertise_permissions/1, initial_presences/1, process_presence/1, - process_roster_presence/1, compare_presences/2, - process_message/4, process_iq/4]). +-behaviour(gen_server). +-behaviour(gen_mod). --include("ejabberd_service.hrl"). +%% API +-export([start_link/2]). +-export([start/2, stop/1, mod_opt_type/1, depends/2]). +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). +-export([component_connected/1, component_disconnected/2, + roster_access/2, process_message/3, + process_presence_out/4, process_presence_in/5]). --include("mod_privacy.hrl"). +-include("ejabberd.hrl"). +-include("logger.hrl"). +-include("xmpp.hrl"). -%%%-------------------------------------------------------------------------------------- -%%% Functions to advertise services of allowed permission -%%%-------------------------------------------------------------------------------------- +-record(state, {server_host = <<"">> :: binary(), + permissions = dict:new() :: ?TDICT}). --spec permissions(binary(), binary(), list()) -> xmlel(). +%%%=================================================================== +%%% API +%%%=================================================================== +start_link(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?MODULE), + gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). -permissions(From, To, PrivAccess) -> - Perms = lists:map(fun({Access, Type}) -> - ?DEBUG("Advertise service ~s of allowed permission: ~s = ~s~n", - [To, Access, Type]), - #xmlel{name = <<"perm">>, - attrs = [{<<"access">>, - atom_to_binary(Access,latin1)}, - {<<"type">>, Type}]} - end, PrivAccess), - Stanza = #xmlel{name = <<"privilege">>, - attrs = [{<<"xmlns">> ,?NS_PRIVILEGE}], - children = Perms}, - Id = randoms:get_string(), - #xmlel{name = <<"message">>, - attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}], - children = [Stanza]}. +start(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?MODULE), + PingSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, + transient, 2000, worker, [?MODULE]}, + supervisor:start_child(ejabberd_sup, PingSpec). -advertise_permissions(#state{privilege_access = []}) -> ok; -advertise_permissions(StateData) -> - Stanza = - permissions(?MYNAME, StateData#state.host, StateData#state.privilege_access), - ejabberd_service:send_element(StateData, Stanza). +stop(Host) -> + Proc = gen_mod:get_module_proc(Host, ?MODULE), + gen_server:call(Proc, stop), + supervisor:delete_child(ejabberd_sup, Proc). -%%%-------------------------------------------------------------------------------------- -%%% Process presences -%%%-------------------------------------------------------------------------------------- +mod_opt_type(roster) -> v_roster(); +mod_opt_type(message) -> v_message(); +mod_opt_type(presence) -> v_presence(); +mod_opt_type(_) -> + [roster, message, presence]. -initial_presences(StateData) -> - Pids = ejabberd_sm:get_all_pids(), +depends(_, _) -> + []. + +-spec component_connected(binary()) -> ok. +component_connected(Host) -> lists:foreach( - fun(Pid) -> - {User, Server, Resource, PresenceLast} = ejabberd_c2s:get_last_presence(Pid), - From = #jid{user = User, server = Server, resource = Resource}, - To = jid:from_string(StateData#state.host), - PacketNew = jlib:replace_from_to(From, To, PresenceLast), - ejabberd_service:send_element(StateData, PacketNew) - end, Pids). + fun(ServerHost) -> + Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), + gen_server:cast(Proc, {component_connected, Host}) + end, ?MYHOSTS). -%% hook user_send_packet(Packet, C2SState, From, To) -> Packet -%% for Managed Entity Presence -process_presence(Pid) -> - fun(#xmlel{name = <<"presence">>} = Packet, _C2SState, From, _To) -> - case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of - T when (T == <<"">>) or (T == <<"unavailable">>) -> - Pid ! {user_presence, Packet, From}; - _ -> ok - end, - Packet; - (Packet, _C2SState, _From, _To) -> - Packet - end. -%% s2s_receive_packet(From, To, Packet) -> ok -%% for Roster Presence -%% From subscription "from" or "both" -process_roster_presence(Pid) -> - fun(From, To, #xmlel{name = <<"presence">>} = Packet) -> - case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of - T when (T == <<"">>) or (T == <<"unavailable">>) -> - Server = To#jid.server, - User = To#jid.user, - PrivList = ejabberd_hooks:run_fold(privacy_get_user_list, - Server, #userlist{}, [User, Server]), - case privacy_check_packet(Server, User, PrivList, From, To, Packet, in) of - allow -> - Pid ! {roster_presence, Packet, From}; - _ -> ok - end, - ok; - _ -> ok - end; - (_From, _To, _Packet) -> ok +-spec component_disconnected(binary(), binary()) -> ok. +component_disconnected(Host, _Reason) -> + lists:foreach( + fun(ServerHost) -> + Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), + gen_server:cast(Proc, {component_disconnected, Host}) + end, ?MYHOSTS). + +-spec process_message(jid(), jid(), stanza()) -> stop | ok. +process_message(#jid{luser = <<"">>, lresource = <<"">>} = From, + #jid{lresource = <<"">>} = To, + #message{lang = Lang, type = T} = Msg) when T /= error -> + Host = From#jid.lserver, + ServerHost = To#jid.lserver, + Permissions = get_permissions(ServerHost), + case dict:find(Host, Permissions) of + {ok, Access} -> + case proplists:get_value(message, Access, none) of + outgoing -> + forward_message(From, To, Msg); + none -> + Txt = <<"Insufficient privilege">>, + Err = xmpp:err_forbidden(Txt, Lang), + ejabberd_router:route_error(To, From, Msg, Err) + end, + stop; + error -> + %% Component is disconnected + ok + end; +process_message(_From, _To, _Stanza) -> + ok. + +-spec roster_access(boolean(), iq()) -> boolean(). +roster_access(true, _) -> + true; +roster_access(false, #iq{from = From, to = To, type = Type}) -> + Host = From#jid.lserver, + ServerHost = To#jid.lserver, + Permissions = get_permissions(ServerHost), + case dict:find(Host, Permissions) of + {ok, Access} -> + Permission = proplists:get_value(roster, Access, none), + (Permission == both) + orelse (Permission == get andalso Type == get) + orelse (Permission == set andalso Type == set); + error -> + %% Component is disconnected + false end. -%%%-------------------------------------------------------------------------------------- -%%% Manage Roster -%%%-------------------------------------------------------------------------------------- +-spec process_presence_out(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza(). +process_presence_out(#presence{type = Type} = Pres, _C2SState, + #jid{luser = LUser, lserver = LServer} = From, + #jid{luser = LUser, lserver = LServer, lresource = <<"">>}) + when Type == available; Type == unavailable -> + %% Self-presence processing + Permissions = get_permissions(LServer), + lists:foreach( + fun({Host, Access}) -> + Permission = proplists:get_value(presence, Access, none), + if Permission == roster; Permission == managed_entity -> + To = jid:make(Host), + ejabberd_router:route( + From, To, xmpp:set_from_to(Pres, From, To)); + true -> + ok + end + end, dict:to_list(Permissions)), + Pres; +process_presence_out(Acc, _, _, _) -> + Acc. -process_iq(StateData, FromJID, ToJID, Packet) -> - IQ = jlib:iq_query_or_response_info(Packet), - case IQ of - #iq{xmlns = ?NS_ROSTER} -> - case (ToJID#jid.luser /= <<"">>) and - (FromJID#jid.luser == <<"">>) and - lists:member(ToJID#jid.lserver, ?MYHOSTS) of - true -> - AccessType = - proplists:get_value(roster, StateData#state.privilege_access, none), - case IQ#iq.type of - get when (AccessType == <<"both">>) or (AccessType == <<"get">>) -> - RosterIQ = roster_management(ToJID, FromJID, IQ), - ejabberd_service:send_element(StateData, RosterIQ); - set when (AccessType == <<"both">>) or (AccessType == <<"set">>) -> - %% check if user ToJID exist - #jid{lserver = Server, luser = User} = ToJID, - case ejabberd_auth:is_user_exists(User,Server) of - true -> - ResIQ = roster_management(ToJID, FromJID, IQ), - ejabberd_service:send_element(StateData, ResIQ); - _ -> ok - end; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), - ejabberd_service:send_element(StateData, Err) - end; - _ -> - ejabberd_router:route(FromJID, ToJID, Packet) - end; - #iq{type = Type, id = Id} when (Type == error) or (Type == result) -> % for XEP-0355 - Hook = {iq, Type, Id}, - Host = ToJID#jid.lserver, - case (ToJID#jid.luser == <<"">>) and - (FromJID#jid.luser == <<"">>) and - lists:member(ToJID#jid.lserver, ?MYHOSTS) of - true -> - case ets:lookup(hooks_tmp, {Hook, Host}) of - [{_, Function, _Timestamp}] -> - catch apply(Function, [Packet]); - [] -> - ejabberd_router:route(FromJID, ToJID, Packet) - end; - _ -> - ejabberd_router:route(FromJID, ToJID, Packet) - end; - _ -> - ejabberd_router:route(FromJID, ToJID, Packet) +-spec process_presence_in(stanza(), ejabberd_c2s:state(), + jid(), jid(), jid()) -> stanza(). +process_presence_in(#presence{type = Type} = Pres, _C2SState, _, + #jid{luser = U, lserver = S} = From, + #jid{luser = LUser, lserver = LServer}) + when {U, S} /= {LUser, LServer} andalso + (Type == available orelse Type == unavailable) -> + Permissions = get_permissions(LServer), + lists:foreach( + fun({Host, Access}) -> + case proplists:get_value(presence, Access, none) of + roster -> + Permission = proplists:get_value(roster, Access, none), + if Permission == both; Permission == get -> + To = jid:make(Host), + ejabberd_router:route( + From, To, xmpp:set_from_to(Pres, From, To)); + true -> + ok + end; + true -> + ok + end + end, dict:to_list(Permissions)), + Pres; +process_presence_in(Acc, _, _, _, _) -> + Acc. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== +init([Host, _Opts]) -> + ejabberd_hooks:add(component_connected, ?MODULE, + component_connected, 50), + ejabberd_hooks:add(component_disconnected, ?MODULE, + component_disconnected, 50), + ejabberd_hooks:add(local_send_to_resource_hook, Host, ?MODULE, + process_message, 50), + ejabberd_hooks:add(roster_remote_access, Host, ?MODULE, + roster_access, 50), + ejabberd_hooks:add(user_send_packet, Host, ?MODULE, + process_presence_out, 50), + ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, + process_presence_in, 50), + {ok, #state{server_host = Host}}. + +handle_call(get_permissions, _From, State) -> + {reply, {ok, State#state.permissions}, State}; +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast({component_connected, Host}, State) -> + ServerHost = State#state.server_host, + From = jid:make(ServerHost), + To = jid:make(Host), + RosterPerm = get_roster_permission(ServerHost, Host), + PresencePerm = get_presence_permission(ServerHost, Host), + MessagePerm = get_message_permission(ServerHost, Host), + if RosterPerm /= none, PresencePerm /= none, MessagePerm /= none -> + Priv = #privilege{perms = [#privilege_perm{access = message, + type = MessagePerm}, + #privilege_perm{access = roster, + type = RosterPerm}, + #privilege_perm{access = presence, + type = PresencePerm}]}, + ?INFO_MSG("Granting permissions to external " + "component '~s': roster = ~s, presence = ~s, " + "message = ~s", + [Host, RosterPerm, PresencePerm, MessagePerm]), + Msg = #message{from = From, to = To, sub_els = [Priv]}, + ejabberd_router:route(From, To, Msg), + Permissions = dict:store(Host, [{roster, RosterPerm}, + {presence, PresencePerm}, + {message, MessagePerm}], + State#state.permissions), + {noreply, State#state{permissions = Permissions}}; + true -> + ?INFO_MSG("Granting no permissions to external component '~s'", + [Host]), + {noreply, State} + end; +handle_cast({component_disconnected, Host}, State) -> + Permissions = dict:erase(Host, State#state.permissions), + {noreply, State#state{permissions = Permissions}}; +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State) -> + %% Note: we don't remove component_* hooks because they are global + %% and might be registered within a module on another virtual host + Host = State#state.server_host, + ejabberd_hooks:delete(local_send_to_resource_hook, Host, ?MODULE, + process_message, 50), + ejabberd_hooks:delete(roster_remote_access, Host, ?MODULE, + roster_access, 50), + ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, + process_presence_out, 50), + ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, + process_presence_in, 50). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +get_permissions(ServerHost) -> + Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), + try gen_server:call(Proc, get_permissions) of + {ok, Permissions} -> + Permissions + catch exit:{noproc, _} -> + %% No module is loaded for this virtual host + dict:new() end. -roster_management(FromJID, ToJID, IQ) -> - ResIQ = mod_roster:process_iq(FromJID, FromJID, IQ), - ResXml = jlib:iq_to_xml(ResIQ), - jlib:replace_from_to(FromJID, ToJID, ResXml). - -%%%-------------------------------------------------------------------------------------- -%%% Message permission -%%%-------------------------------------------------------------------------------------- - -process_message(StateData, FromJID, ToJID, #xmlel{children = Children} = Packet) -> - %% if presence was send from service to server, - case lists:member(ToJID#jid.lserver, ?MYHOSTS) and - (ToJID#jid.luser == <<"">>) and - (FromJID#jid.luser == <<"">>) of %% service - true -> - %% if stanza contains privilege element - case Children of - [#xmlel{name = <<"privilege">>, - attrs = [{<<"xmlns">>, ?NS_PRIVILEGE}], - children = [#xmlel{name = <<"forwarded">>, - attrs = [{<<"xmlns">>, ?NS_FORWARD}], - children = Children2}]}] -> - %% 1 case : privilege service send subscription message - %% on behalf of the client - %% 2 case : privilege service send message on behalf - %% of the client - case Children2 of - %% it isn't case of 0356 extension - [#xmlel{name = <<"presence">>} = Child] -> - forward_subscribe(StateData, Child, Packet); - [#xmlel{name = <<"message">>} = Child] -> %% xep-0356 - forward_message(StateData, Child, Packet); - _ -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"invalid forwarded element">>, - Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_service:send_element(StateData, Err) - end; - _ -> - ejabberd_router:route(FromJID, ToJID, Packet) - end; - - _ -> - ejabberd_router:route(FromJID, ToJID, Packet) +forward_message(From, To, Msg) -> + ServerHost = To#jid.lserver, + Lang = xmpp:get_lang(Msg), + case xmpp:get_subtag(Msg, #privilege{}) of + #privilege{forwarded = #forwarded{xml_els = [SubEl]}} -> + try xmpp:decode(SubEl, ?NS_CLIENT, [ignore_els]) of + #message{} = NewMsg -> + case NewMsg#message.from of + #jid{lresource = <<"">>, lserver = ServerHost} -> + ejabberd_router:route( + xmpp:get_from(NewMsg), xmpp:get_to(NewMsg), NewMsg); + _ -> + Lang = xmpp:get_lang(Msg), + Txt = <<"Invalid 'from' attribute in forwarded message">>, + Err = xmpp:err_forbidden(Txt, Lang), + ejabberd_router:route_error(To, From, Msg, Err) + end; + _ -> + Txt = <<"Message not found in forwarded payload">>, + Err = xmpp:err_bad_request(Txt, Lang), + ejabberd_router:route_error(To, From, Msg, Err) + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + Err = xmpp:err_bad_request(Txt, Lang), + ejabberd_router:route_error(To, From, Msg, Err) + end; + _ -> + Txt = <<"Invalid element">>, + Err = xmpp:err_bad_request(Txt, Lang), + ejabberd_router:route_error(To, From, Msg, Err) end. -forward_subscribe(StateData, Presence, Packet) -> - PrivAccess = StateData#state.privilege_access, - T = proplists:get_value(roster, PrivAccess, none), - Type = fxml:get_attr_s(<<"type">>, Presence#xmlel.attrs), - if - ((T == <<"both">>) or (T == <<"set">>)) and (Type == <<"subscribe">>) -> - From = fxml:get_attr_s(<<"from">>, Presence#xmlel.attrs), - FromJ = jid:from_string(From), - To = fxml:get_attr_s(<<"to">>, Presence#xmlel.attrs), - ToJ = case To of - <<"">> -> error; - _ -> jid:from_string(To) - end, - if - (ToJ /= error) and (FromJ /= error) -> - Server = FromJ#jid.lserver, - User = FromJ#jid.luser, - case (FromJ#jid.lresource == <<"">>) and - lists:member(Server, ?MYHOSTS) of - true -> - if - (Server /= ToJ#jid.lserver) or - (User /= ToJ#jid.luser) -> - %% 0356 server MUST NOT allow the privileged entity - %% to do anything that the managed entity could not do - try_roster_subscribe(Server,User, FromJ, ToJ, Presence); - true -> %% we don't want presence sent to self - ok - end; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), - ejabberd_service:send_element(StateData, Err) - end; - true -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Incorrect stanza from/to JID">>, - Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_service:send_element(StateData, Err) - end; - true -> - Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), - ejabberd_service:send_element(StateData, Err) +get_roster_permission(ServerHost, Host) -> + Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, roster, + v_roster(), []), + case match_rule(ServerHost, Host, Perms, both) of + allow -> + both; + deny -> + Get = match_rule(ServerHost, Host, Perms, get), + Set = match_rule(ServerHost, Host, Perms, set), + if Get == allow, Set == allow -> both; + Get == allow -> get; + Set == allow -> set; + true -> none + end end. -forward_message(StateData, Message, Packet) -> - PrivAccess = StateData#state.privilege_access, - T = proplists:get_value(message, PrivAccess, none), - if - (T == <<"outgoing">>) -> - From = fxml:get_attr_s(<<"from">>, Message#xmlel.attrs), - FromJ = jid:from_string(From), - To = fxml:get_attr_s(<<"to">>, Message#xmlel.attrs), - ToJ = case To of - <<"">> -> FromJ; - _ -> jid:from_string(To) - end, - if - (ToJ /= error) and (FromJ /= error) -> - Server = FromJ#jid.server, - User = FromJ#jid.user, - case (FromJ#jid.lresource == <<"">>) and - lists:member(Server, ?MYHOSTS) of - true -> - %% there are no restriction on to attribute - PrivList = ejabberd_hooks:run_fold(privacy_get_user_list, - Server, #userlist{}, - [User, Server]), - check_privacy_route(Server, User, PrivList, - FromJ, ToJ, Message); - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), - ejabberd_service:send_element(StateData, Err) - end; - true -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - Txt = <<"Incorrect stanza from/to JID">>, - Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_service:send_element(StateData, Err) - end; - true -> - Err = jlib:make_error_reply(Packet,?ERR_FORBIDDEN), - ejabberd_service:send_element(StateData, Err) +get_message_permission(ServerHost, Host) -> + Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, message, + v_message(), []), + case match_rule(ServerHost, Host, Perms, outgoing) of + allow -> outgoing; + deny -> none end. -%%%-------------------------------------------------------------------------------------- -%%% helper functions -%%%-------------------------------------------------------------------------------------- - -compare_presences(undefined, _Presence) -> false; -compare_presences(#xmlel{attrs = Attrs, children = Child}, - #xmlel{attrs = Attrs2, children = Child2}) -> - Id1 = fxml:get_attr_s(<<"id">>, Attrs), - Id2 = fxml:get_attr_s(<<"id">>, Attrs2), - if - (Id1 /= Id2) -> - false; - (Id1 /= <<"">>) and (Id1 == Id2) -> - true; - true -> - case not compare_attrs(Attrs, Attrs2) of - true -> false; - _ -> - compare_elements(Child, Child2) - end +get_presence_permission(ServerHost, Host) -> + Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, presence, + v_presence(), []), + case match_rule(ServerHost, Host, Perms, roster) of + allow -> + roster; + deny -> + case match_rule(ServerHost, Host, Perms, managed_entity) of + allow -> managed_entity; + deny -> none + end end. +match_rule(ServerHost, Host, Perms, Type) -> + Access = proplists:get_value(Type, Perms, none), + acl:match_rule(ServerHost, Access, jid:make(Host)). -compare_elements([],[]) -> true; -compare_elements(Tags1, Tags2) when length(Tags1) == length(Tags2) -> - compare_tags(Tags1,Tags2); -compare_elements(_Tags1, _Tags2) -> false. - -compare_tags([],[]) -> true; -compare_tags([{xmlcdata, CData}|Tags1], [{xmlcdata, CData}|Tags2]) -> - compare_tags(Tags1, Tags2); -compare_tags([{xmlcdata, _CData1}|_Tags1], [{xmlcdata, _CData2}|_Tags2]) -> - false; -compare_tags([#xmlel{} = Stanza1|Tags1], [#xmlel{} = Stanza2|Tags2]) -> - case (Stanza1#xmlel.name == Stanza2#xmlel.name) and - compare_attrs(Stanza1#xmlel.attrs, Stanza2#xmlel.attrs) and - compare_tags(Stanza1#xmlel.children, Stanza2#xmlel.children) of - true -> - compare_tags(Tags1,Tags2); - false -> - false +v_roster() -> + fun(Props) -> + lists:map( + fun({both, ACL}) -> {both, acl:access_rules_validator(ACL)}; + ({get, ACL}) -> {get, acl:access_rules_validator(ACL)}; + ({set, ACL}) -> {set, acl:access_rules_validator(ACL)} + end, Props) end. -%% attr() :: {Name, Value} --spec compare_attrs([attr()], [attr()]) -> boolean(). -compare_attrs([],[]) -> true; -compare_attrs(Attrs1, Attrs2) when length(Attrs1) == length(Attrs2) -> - lists:foldl(fun(Attr,Acc) -> lists:member(Attr, Attrs2) and Acc end, true, Attrs1); -compare_attrs(_Attrs1, _Attrs2) -> false. - -%% Check if privacy rules allow this delivery -%% from ejabberd_c2s.erl -privacy_check_packet(Server, User, PrivList, From, To, Packet , Dir) -> - ejabberd_hooks:run_fold(privacy_check_packet, - Server, allow, [User, Server, PrivList, - {From, To, Packet}, Dir]). - -check_privacy_route(Server, User, PrivList, From, To, Packet) -> - case privacy_check_packet(Server, User, PrivList, From, To, Packet, out) of - allow -> - ejabberd_router:route(From, To, Packet); - _ -> ok %% who should receive error : service or user? +v_message() -> + fun(Props) -> + lists:map( + fun({outgoing, ACL}) -> {outgoing, acl:access_rules_validator(ACL)} + end, Props) end. -try_roster_subscribe(Server,User, From, To, Packet) -> - Access = - gen_mod:get_module_opt(Server, mod_roster, access, - fun(A) when is_atom(A) -> A end, all), - case acl:match_rule(Server, Access, From) of - deny -> - ok; - allow -> - ejabberd_hooks:run(roster_out_subscription, Server, - [User, Server, To, subscribe]), - PrivList = ejabberd_hooks:run_fold(privacy_get_user_list, - Server, - #userlist{}, - [User, Server]), - check_privacy_route(Server, User, PrivList, From, To, Packet) +v_presence() -> + fun(Props) -> + lists:map( + fun({managed_entity, ACL}) -> + {managed_entity, acl:access_rules_validator(ACL)}; + ({roster, ACL}) -> + {roster, acl:access_rules_validator(ACL)} + end, Props) end. diff --git a/src/mod_proxy65.erl b/src/mod_proxy65.erl index beea35725..2d0d9ae0a 100644 --- a/src/mod_proxy65.erl +++ b/src/mod_proxy65.erl @@ -93,12 +93,10 @@ mod_opt_type(auth_type) -> end; mod_opt_type(recbuf) -> fun (I) when is_integer(I), I > 0 -> I end; -mod_opt_type(shaper) -> - fun (A) when is_atom(A) -> A end; +mod_opt_type(shaper) -> fun acl:shaper_rules_validator/1; mod_opt_type(sndbuf) -> fun (I) when is_integer(I), I > 0 -> I end; -mod_opt_type(access) -> - fun (A) when is_atom(A) -> A end; +mod_opt_type(access) -> fun acl:access_rules_validator/1; mod_opt_type(host) -> fun iolist_to_binary/1; mod_opt_type(hostname) -> fun iolist_to_binary/1; mod_opt_type(ip) -> diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl index 7db6f9da2..0f69086e0 100644 --- a/src/mod_proxy65_service.erl +++ b/src/mod_proxy65_service.erl @@ -33,24 +33,17 @@ -export([init/1, handle_info/2, handle_call/3, handle_cast/2, terminate/2, code_change/3]). --export([start_link/2, add_listener/2, +-export([start_link/2, add_listener/2, process_disco_info/1, + process_disco_items/1, process_vcard/1, process_bytestreams/1, transform_module_options/1, delete_listener/1]). -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). -define(PROCNAME, ejabberd_mod_proxy65_service). --record(state, - {myhost = <<"">> :: binary(), - serverhost = <<"">> :: binary(), - name = <<"">> :: binary(), - stream_addr = [] :: [attr()], - port = 0 :: inet:port_number(), - ip = {127,0,0,1} :: inet:ip_address(), - acl = none :: atom()}). +-record(state, {myhost = <<"">> :: binary()}). %%%------------------------ %%% gen_server callbacks @@ -62,34 +55,32 @@ start_link(Host, Opts) -> [Host, Opts], []). init([Host, Opts]) -> - State = parse_options(Host, Opts), - ejabberd_router:register_route(State#state.myhost, Host), - {ok, State}. + IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, + one_queue), + MyHost = gen_mod:get_opt_host(Host, Opts, <<"proxy.@HOST@">>), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, + ?MODULE, process_disco_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD, + ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS, + ?MODULE, process_bytestreams, IQDisc), + ejabberd_router:register_route(MyHost, Host), + {ok, #state{myhost = MyHost}}. terminate(_Reason, #state{myhost = MyHost}) -> - ejabberd_router:unregister_route(MyHost), ok. + ejabberd_router:unregister_route(MyHost), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS). -handle_info({route, From, To, - #xmlel{name = <<"iq">>} = Packet}, - State) -> - IQ = jlib:iq_query_info(Packet), - case catch process_iq(From, IQ, State) of - Result when is_record(Result, iq) -> - ejabberd_router:route(To, From, jlib:iq_to_xml(Result)); - {'EXIT', Reason} -> - ?ERROR_MSG("Error when processing IQ stanza: ~p", - [Reason]), - Err = jlib:make_error_reply(Packet, - ?ERR_INTERNAL_SERVER_ERROR), - ejabberd_router:route(To, From, Err); - _ -> ok - end, +handle_info({route, From, To, #iq{} = Packet}, State) -> + ejabberd_router:process_iq(From, To, Packet), {noreply, State}; handle_info(_Info, State) -> {noreply, State}. -handle_call(get_port_ip, _From, State) -> - {reply, {port_ip, State#state.port, State#state.ip}, - State}; handle_call(_Request, _From, State) -> {reply, ok, State}. @@ -102,185 +93,116 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%------------------------ add_listener(Host, Opts) -> - State = parse_options(Host, Opts), NewOpts = [Host | Opts], - ejabberd_listener:add_listener({State#state.port, - State#state.ip}, + ejabberd_listener:add_listener(get_port_ip(Host), mod_proxy65_stream, NewOpts). delete_listener(Host) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - {port_ip, Port, IP} = gen_server:call(Proc, - get_port_ip), - catch ejabberd_listener:delete_listener({Port, IP}, + catch ejabberd_listener:delete_listener(get_port_ip(Host), mod_proxy65_stream). %%%------------------------ %%% IQ Processing %%%------------------------ +-spec process_disco_info(iq()) -> iq(). +process_disco_info(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_info(#iq{type = get, to = To, lang = Lang} = IQ) -> + Host = ejabberd_router:host_of_route(To#jid.lserver), + Name = gen_mod:get_module_opt(Host, mod_proxy65, name, + fun iolist_to_binary/1, + <<"SOCKS5 Bytestreams">>), + Info = ejabberd_hooks:run_fold(disco_info, Host, + [], [Host, ?MODULE, <<"">>, <<"">>]), + xmpp:make_iq_result( + IQ, #disco_info{xdata = Info, + identities = [#identity{category = <<"proxy">>, + type = <<"bytestreams">>, + name = translate:translate(Lang, Name)}], + features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, + ?NS_VCARD, ?NS_BYTESTREAMS]}). -%% disco#info request -process_iq(_, - #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = - IQ, - #state{name = Name, serverhost = ServerHost}) -> - Info = ejabberd_hooks:run_fold(disco_info, ServerHost, - [], [ServerHost, ?MODULE, <<"">>, <<"">>]), - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}], - children = iq_disco_info(Lang, Name) ++ Info}]}; -%% disco#items request -process_iq(_, - #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}], - children = []}]}; -%% vCard request -process_iq(_, - #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, - _) -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, ?NS_VCARD}], - children = iq_vcard(Lang)}]}; -%% bytestreams info request -process_iq(JID, - #iq{type = get, sub_el = SubEl, lang = Lang, - xmlns = ?NS_BYTESTREAMS} = - IQ, - #state{acl = ACL, stream_addr = StreamAddr, - serverhost = ServerHost}) -> +-spec process_disco_items(iq()) -> iq(). +process_disco_items(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_items(#iq{type = get} = IQ) -> + xmpp:make_iq_result(IQ, #disco_items{}). + +-spec process_vcard(iq()) -> iq(). +process_vcard(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_vcard(#iq{type = get, lang = Lang} = IQ) -> + Desc = translate:translate(Lang, <<"ejabberd SOCKS5 Bytestreams module">>), + xmpp:make_iq_result( + IQ, #vcard_temp{fn = <<"ejabberd/mod_proxy65">>, + url = ?EJABBERD_URI, + desc = <>}). + +-spec process_bytestreams(iq()) -> iq(). +process_bytestreams(#iq{type = get, from = JID, to = To, lang = Lang} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + ACL = gen_mod:get_module_opt(ServerHost, mod_proxy65, access, + fun acl:access_rules_validator/1, + all), case acl:match_rule(ServerHost, ACL, JID) of - allow -> - StreamHostEl = [#xmlel{name = <<"streamhost">>, - attrs = StreamAddr, children = []}], - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_BYTESTREAMS}], - children = StreamHostEl}]}; - deny -> - Txt = <<"Denied by ACL">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]} + allow -> + StreamHost = get_streamhost(Host, ServerHost), + xmpp:make_iq_result(IQ, #bytestreams{hosts = [StreamHost]}); + deny -> + xmpp:make_error(IQ, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)) end; -%% bytestream activation request -process_iq(InitiatorJID, - #iq{type = set, sub_el = SubEl, lang = Lang, - xmlns = ?NS_BYTESTREAMS} = - IQ, - #state{acl = ACL, serverhost = ServerHost}) -> +process_bytestreams(#iq{type = set, lang = Lang, + sub_els = [#bytestreams{sid = SID}]} = IQ) + when SID == <<"">> orelse length(SID) > 128 -> + Why = {bad_attr_value, <<"sid">>, <<"query">>, ?NS_BYTESTREAMS}, + Txt = xmpp:format_error(Why), + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); +process_bytestreams(#iq{type = set, lang = Lang, + sub_els = [#bytestreams{activate = undefined}]} = IQ) -> + Why = {missing_cdata, <<"">>, <<"activate">>, ?NS_BYTESTREAMS}, + Txt = xmpp:format_error(Why), + xmpp:make_error(IQ, xmpp:err_jid_malformed(Txt, Lang)); +process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To, + sub_els = [#bytestreams{activate = TargetJID, + sid = SID}]} = IQ) -> + ServerHost = ejabberd_router:host_of_route(To#jid.lserver), + ACL = gen_mod:get_module_opt(ServerHost, mod_proxy65, access, + fun acl:access_rules_validator/1, + all), case acl:match_rule(ServerHost, ACL, InitiatorJID) of - allow -> - ActivateEl = fxml:get_path_s(SubEl, - [{elem, <<"activate">>}]), - SID = fxml:get_tag_attr_s(<<"sid">>, SubEl), - case catch - jid:from_string(fxml:get_tag_cdata(ActivateEl)) - of - TargetJID - when is_record(TargetJID, jid), SID /= <<"">>, - byte_size(SID) =< 128, TargetJID /= InitiatorJID -> - Target = - jid:to_string(jid:tolower(TargetJID)), - Initiator = - jid:to_string(jid:tolower(InitiatorJID)), - SHA1 = p1_sha:sha(<>), - case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, - TargetJID, ServerHost) - of - ok -> IQ#iq{type = result, sub_el = []}; - false -> - Txt = <<"Failed to activate bytestream">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]}; - limit -> - Txt = <<"Too many active bytestreams">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)]}; - conflict -> - Txt = <<"Bytestream already activated">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_CONFLICT(Lang, Txt)]}; - _ -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} - end; - _ -> - Txt = <<"Malformed JID">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]} - end; - deny -> - Txt = <<"Denied by ACL">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]} - end; -%% Unknown "set" or "get" request -process_iq(_, #iq{type = Type, sub_el = SubEl} = IQ, _) - when Type == get; Type == set -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}; -%% IQ "result" or "error". -process_iq(_, _, _) -> ok. - + allow -> + Target = jid:to_string(jid:tolower(TargetJID)), + Initiator = jid:to_string(jid:tolower(InitiatorJID)), + SHA1 = p1_sha:sha(<>), + case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, + TargetJID, ServerHost) of + ok -> + xmpp:make_iq_result(IQ); + false -> + Txt = <<"Failed to activate bytestream">>, + xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); + limit -> + Txt = <<"Too many active bytestreams">>, + xmpp:make_error(IQ, xmpp:err_resource_constraint(Txt, Lang)); + conflict -> + Txt = <<"Bytestream already activated">>, + xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang)); + Err -> + ?ERROR_MSG("failed to activate bytestream from ~s to ~s: ~p", + [Initiator, Target, Err]), + xmpp:make_error(IQ, xmpp:err_internal_server_error()) + end; + deny -> + Txt = <<"Denied by ACL">>, + xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)) + end. %%%------------------------- %%% Auxiliary functions. %%%------------------------- --define(FEATURE(Feat), - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, Feat}], children = []}). - -iq_disco_info(Lang, Name) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"proxy">>}, - {<<"type">>, <<"bytestreams">>}, - {<<"name">>, translate:translate(Lang, Name)}], - children = []}, - ?FEATURE((?NS_DISCO_INFO)), ?FEATURE((?NS_VCARD)), - ?FEATURE((?NS_BYTESTREAMS))]. - -iq_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_proxy65">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd SOCKS5 Bytestreams module">>))/binary, - "\nCopyright (c) 2003-2016 ProcessOne">>}]}]. - -parse_options(ServerHost, Opts) -> - MyHost = gen_mod:get_opt_host(ServerHost, Opts, - <<"proxy.@HOST@">>), - Port = gen_mod:get_opt(port, Opts, - fun(P) when is_integer(P), P>0, P<65536 -> P end, - 7777), - ACL = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1, - all), - Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1, - <<"SOCKS5 Bytestreams">>), - IP = gen_mod:get_opt(ip, Opts, - fun(S) -> - {ok, Addr} = inet_parse:address( - binary_to_list( - iolist_to_binary(S))), - Addr - end, get_my_ip()), - HostName = gen_mod:get_opt(hostname, Opts, - fun iolist_to_binary/1, - jlib:ip_to_list(IP)), - StreamAddr = [{<<"jid">>, MyHost}, - {<<"host">>, HostName}, - {<<"port">>, jlib:integer_to_binary(Port)}], - #state{myhost = MyHost, serverhost = ServerHost, - name = Name, port = Port, ip = IP, - stream_addr = StreamAddr, acl = ACL}. - transform_module_options(Opts) -> lists:map( fun({ip, IP}) when is_tuple(IP) -> @@ -291,6 +213,33 @@ transform_module_options(Opts) -> Opt end, Opts). +-spec get_streamhost(binary(), binary()) -> streamhost(). +get_streamhost(Host, ServerHost) -> + {Port, IP} = get_port_ip(ServerHost), + HostName = gen_mod:get_module_opt(ServerHost, mod_proxy65, hostname, + fun iolist_to_binary/1, + jlib:ip_to_list(IP)), + #streamhost{jid = jid:make(Host), + host = HostName, + port = Port}. + +-spec get_port_ip(binary()) -> {pos_integer(), inet:ip_address()}. +get_port_ip(Host) -> + Port = gen_mod:get_module_opt(Host, mod_proxy65, port, + fun(P) when is_integer(P), P>0, P<65536 -> + P + end, + 7777), + IP = gen_mod:get_module_opt(Host, mod_proxy65, ip, + fun(S) -> + {ok, Addr} = inet_parse:address( + binary_to_list( + iolist_to_binary(S))), + Addr + end, get_my_ip()), + {Port, IP}. + +-spec get_my_ip() -> inet:ip_address(). get_my_ip() -> {ok, MyHostName} = inet:gethostname(), case inet:getaddr(MyHostName, inet) of diff --git a/src/mod_proxy65_sm.erl b/src/mod_proxy65_sm.erl index d86b06c4b..b1d33b5d9 100644 --- a/src/mod_proxy65_sm.erl +++ b/src/mod_proxy65_sm.erl @@ -38,14 +38,12 @@ -record(state, {max_connections = infinity :: non_neg_integer() | infinity}). --include("jlib.hrl"). - -record(bytestream, {sha1 = <<"">> :: binary() | '$1', target :: pid() | '_', initiator :: pid() | '_', active = false :: boolean() | '_', - jid_i = {<<"">>, <<"">>, <<"">>} :: ljid() | '_'}). + jid_i = {<<"">>, <<"">>, <<"">>} :: jid:ljid() | '_'}). -define(PROCNAME, ejabberd_mod_proxy65_sm). @@ -64,7 +62,7 @@ start_link(Host, Opts) -> []). init([Opts]) -> - mnesia:create_table(bytestream, + ejabberd_mnesia:create(?MODULE, bytestream, [{ram_copies, [node()]}, {attributes, record_info(fields, bytestream)}]), mnesia:add_table_copy(bytestream, node(), ram_copies), diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 31170bc74..717796fec 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -41,8 +41,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("adhoc.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("pubsub.hrl"). -define(STDTREE, <<"tree">>). @@ -58,7 +57,9 @@ disco_sm_features/5, disco_sm_items/5]). %% exported iq handlers --export([iq_sm/3]). +-export([iq_sm/1, process_disco_info/1, process_disco_items/1, + process_pubsub/1, process_pubsub_owner/1, process_vcard/1, + process_commands/1]). %% exports for console debug manual use -export([create_node/5, create_node/7, delete_node/3, @@ -68,12 +69,21 @@ tree_action/3, node_action/4, node_call/4]). %% general helpers for plugins --export([subscription_to_string/1, affiliation_to_string/1, - string_to_subscription/1, string_to_affiliation/1, - extended_error/2, extended_error/3, service_jid/1, +-export([extended_error/2, service_jid/1, tree/1, tree/2, plugin/2, plugins/1, config/3, host/1, serverhost/1]). +%% pubsub#errors +-export([err_closed_node/0, err_configuration_required/0, + err_invalid_jid/0, err_invalid_options/0, err_invalid_payload/0, + err_invalid_subid/0, err_item_forbidden/0, err_item_required/0, + err_jid_required/0, err_max_items_exceeded/0, err_max_nodes_exceeded/0, + err_nodeid_required/0, err_not_in_roster_group/0, err_not_subscribed/0, + err_payload_too_big/0, err_payload_required/0, + err_pending_subscription/0, err_presence_subscription_required/0, + err_subid_required/0, err_too_many_subscriptions/0, err_unsupported/1, + err_unsupported_access_model/0]). + %% API and gen_server callbacks -export([start_link/2, start/2, stop/1, init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -241,7 +251,7 @@ init([ServerHost, Opts]) -> Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), ejabberd_router:register_route(Host, ServerHost), Access = gen_mod:get_opt(access_createnode, Opts, - fun(A) when is_atom(A) -> A end, all), + fun acl:access_rules_validator/1, all), PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, fun(A) when is_boolean(A) -> A end, true), IQDisc = gen_mod:get_opt(iqdisc, Opts, @@ -252,13 +262,13 @@ init([ServerHost, Opts]) -> fun(A) when is_integer(A) andalso A >= 0 -> A end, ?MAXITEMS), MaxSubsNode = gen_mod:get_opt(max_subscriptions_node, Opts, fun(A) when is_integer(A) andalso A >= 0 -> A end, undefined), - pubsub_index:init(Host, ServerHost, Opts), + [pubsub_index:init(Host, ServerHost, Opts) || gen_mod:db_type(ServerHost, ?MODULE)==mnesia], {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts), DefaultModule = plugin(Host, hd(Plugins)), BaseOptions = DefaultModule:options(), DefaultNodeCfg = gen_mod:get_opt(default_node_config, Opts, fun(A) when is_list(A) -> filter_node_options(A, BaseOptions) end, []), - mnesia:create_table(pubsub_last_item, + ejabberd_mnesia:create(?MODULE, pubsub_last_item, [{ram_copies, [node()]}, {attributes, record_info(fields, pubsub_last_item)}]), mod_disco:register_feature(ServerHost, ?NS_PUBSUB), @@ -295,6 +305,18 @@ init([ServerHost, Opts]) -> ?MODULE, remove_user, 50), ejabberd_hooks:add(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, + ?MODULE, process_disco_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB, + ?MODULE, process_pubsub, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER, + ?MODULE, process_pubsub_owner, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD, + ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS, + ?MODULE, process_commands, IQDisc), case lists:member(?PEPNODE, Plugins) of true -> ejabberd_hooks:add(caps_add, ServerHost, @@ -487,27 +509,21 @@ send_loop(State) -> %% disco hooks handling functions %% --spec disco_local_identity(Acc :: [xmlel()], _From :: jid(), - To :: jid(), Node :: <<>> | mod_pubsub:nodeId(), - Lang :: binary()) -> [xmlel()]. - +-spec disco_local_identity([identity()], jid(), jid(), + binary(), binary()) -> [identity()]. disco_local_identity(Acc, _From, To, <<>>, _Lang) -> case lists:member(?PEPNODE, plugins(host(To#jid.lserver))) of true -> - [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"pep">>}]} - | Acc]; + [#identity{category = <<"pubsub">>, type = <<"pep">>} | Acc]; false -> Acc end; disco_local_identity(Acc, _From, _To, _Node, _Lang) -> Acc. --spec disco_local_features(Acc :: [xmlel()], _From :: jid(), - To :: jid(), Node :: <<>> | mod_pubsub:nodeId(), - Lang :: binary()) -> [binary(),...]. - +-spec disco_local_features({error, stanza_error()} | {result, [binary()]} | empty, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | {result, [binary()]} | empty. disco_local_features(Acc, _From, To, <<>>, _Lang) -> Host = host(To#jid.lserver), Feats = case Acc of @@ -518,88 +534,83 @@ disco_local_features(Acc, _From, To, <<>>, _Lang) -> disco_local_features(Acc, _From, _To, _Node, _Lang) -> Acc. +-spec disco_local_items({error, stanza_error()} | {result, [disco_item()]} | empty, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | {result, [disco_item()]} | empty. disco_local_items(Acc, _From, _To, <<>>, _Lang) -> Acc; disco_local_items(Acc, _From, _To, _Node, _Lang) -> Acc. -%disco_sm_identity(Acc, From, To, Node, Lang) -% when is_binary(Node) -> -% disco_sm_identity(Acc, From, To, iolist_to_binary(Node), -% Lang); --spec disco_sm_identity(Acc :: empty | [xmlel()], From :: jid(), - To :: jid(), Node :: mod_pubsub:nodeId(), - Lang :: binary()) -> [xmlel()]. - -disco_sm_identity(empty, From, To, Node, Lang) -> - disco_sm_identity([], From, To, Node, Lang); +-spec disco_sm_identity([identity()], jid(), jid(), + binary(), binary()) -> [identity()]. disco_sm_identity(Acc, From, To, Node, _Lang) -> disco_identity(jid:tolower(jid:remove_resource(To)), Node, From) ++ Acc. +-spec disco_identity(binary(), binary(), jid()) -> [identity()]. disco_identity(_Host, <<>>, _From) -> - [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"pep">>}]}]; + [#identity{category = <<"pubsub">>, type = <<"pep">>}]; disco_identity(Host, Node, From) -> - Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) -> - Owners = node_owners_call(Host, Type, Nidx, O), - case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of - {result, _} -> - {result, [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"pep">>}]}, - #xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"leaf">>} - | case get_option(Options, title) of - false -> []; - [Title] -> [{<<"name">>, Title}] - end]}]}; - _ -> - {result, []} - end - end, + Action = + fun(#pubsub_node{id = Nidx, type = Type, + options = Options, owners = O}) -> + Owners = node_owners_call(Host, Type, Nidx, O), + case get_allowed_items_call(Host, Nidx, From, Type, + Options, Owners) of + {result, _} -> + {result, [#identity{category = <<"pubsub">>, + type = <<"pep">>}, + #identity{category = <<"pubsub">>, + type = <<"leaf">>, + name = case get_option(Options, title) of + false -> <<>>; + [Title] -> Title + end}]}; + _ -> + {result, []} + end + end, case transaction(Host, Node, Action, sync_dirty) of {result, {_, Result}} -> Result; _ -> [] end. --spec disco_sm_features(Acc :: empty | {result, Features::[Feature::binary()]}, - From :: jid(), To :: jid(), Node :: mod_pubsub:nodeId(), - Lang :: binary()) -> {result, Features::[Feature::binary()]}. -%disco_sm_features(Acc, From, To, Node, Lang) -% when is_binary(Node) -> -% disco_sm_features(Acc, From, To, iolist_to_binary(Node), -% Lang); +-spec disco_sm_features({error, stanza_error()} | {result, [binary()]} | empty, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | {result, [binary()]}. disco_sm_features(empty, From, To, Node, Lang) -> disco_sm_features({result, []}, From, To, Node, Lang); disco_sm_features({result, OtherFeatures} = _Acc, From, To, Node, _Lang) -> {result, - OtherFeatures ++ - disco_features(jid:tolower(jid:remove_resource(To)), Node, From)}; + OtherFeatures ++ + disco_features(jid:tolower(jid:remove_resource(To)), Node, From)}; disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. +-spec disco_features(ljid(), binary(), jid()) -> [binary()]. disco_features(Host, <<>>, _From) -> [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]; disco_features(Host, Node, From) -> - Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) -> - Owners = node_owners_call(Host, Type, Nidx, O), - case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of - {result, _} -> {result, [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]}; - _ -> {result, []} - end - end, + Action = + fun(#pubsub_node{id = Nidx, type = Type, + options = Options, owners = O}) -> + Owners = node_owners_call(Host, Type, Nidx, O), + case get_allowed_items_call(Host, Nidx, From, + Type, Options, Owners) of + {result, _} -> + {result, + [?NS_PUBSUB | + [feature(F) || F <- plugin_features(Host, <<"pep">>)]]}; + _ -> + {result, []} + end + end, case transaction(Host, Node, Action, sync_dirty) of {result, {_, Result}} -> Result; _ -> [] end. --spec disco_sm_items(Acc :: empty | {result, [xmlel()]}, From :: jid(), - To :: jid(), Node :: mod_pubsub:nodeId(), - Lang :: binary()) -> {result, [xmlel()]}. -%disco_sm_items(Acc, From, To, Node, Lang) -% when is_binary(Node) -> -% disco_sm_items(Acc, From, To, iolist_to_binary(Node), -% Lang); +-spec disco_sm_items({error, stanza_error()} | {result, [disco_item()]} | empty, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | {result, [disco_item()]}. disco_sm_items(empty, From, To, Node, Lang) -> disco_sm_items({result, []}, From, To, Node, Lang); disco_sm_items({result, OtherItems}, From, To, Node, _Lang) -> @@ -607,48 +618,48 @@ disco_sm_items({result, OtherItems}, From, To, Node, _Lang) -> disco_items(jid:tolower(jid:remove_resource(To)), Node, From))}; disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc. --spec disco_items(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - From :: jid()) -> [xmlel()]. +-spec disco_items(ljid(), binary(), jid()) -> [disco_item()]. disco_items(Host, <<>>, From) -> - Action = fun (#pubsub_node{nodeid = {_, Node}, - options = Options, type = Type, id = Nidx, owners = O}, - Acc) -> - Owners = node_owners_call(Host, Type, Nidx, O), - case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of - {result, _} -> - [#xmlel{name = <<"item">>, - attrs = [{<<"node">>, (Node)}, - {<<"jid">>, jid:to_string(Host)} - | case get_option(Options, title) of - false -> []; - [Title] -> [{<<"name">>, Title}] - end]} - | Acc]; - _ -> - Acc - end - end, + Action = + fun(#pubsub_node{nodeid = {_, Node}, options = Options, + type = Type, id = Nidx, owners = O}, Acc) -> + Owners = node_owners_call(Host, Type, Nidx, O), + case get_allowed_items_call(Host, Nidx, From, + Type, Options, Owners) of + {result, _} -> + [#disco_item{node = Node, + jid = jid:make(Host), + name = case get_option(Options, title) of + false -> <<>>; + [Title] -> Title + end} | Acc]; + _ -> + Acc + end + end, NodeBloc = fun() -> - {result, - lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))} - end, + {result, + lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))} + end, case transaction(Host, NodeBloc, sync_dirty) of {result, Items} -> Items; _ -> [] end; disco_items(Host, Node, From) -> - Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) -> - Owners = node_owners_call(Host, Type, Nidx, O), - case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of - {result, Items} -> - {result, [#xmlel{name = <<"item">>, - attrs = [{<<"jid">>, jid:to_string(Host)}, - {<<"name">>, ItemId}]} - || #pubsub_item{itemid = {ItemId, _}} <- Items]}; - _ -> - {result, []} - end - end, + Action = + fun(#pubsub_node{id = Nidx, type = Type, + options = Options, owners = O}) -> + Owners = node_owners_call(Host, Type, Nidx, O), + case get_allowed_items_call(Host, Nidx, From, + Type, Options, Owners) of + {result, Items} -> + {result, [#disco_item{jid = jid:make(Host), + name = ItemId} + || #pubsub_item{itemid = {ItemId, _}} <- Items]}; + _ -> + {result, []} + end + end, case transaction(Host, Node, Action, sync_dirty) of {result, {_, Result}} -> Result; _ -> [] @@ -658,6 +669,7 @@ disco_items(Host, Node, From) -> %% presence hooks handling functions %% +-spec caps_add(jid(), jid(), [binary()]) -> ok. caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) when Host =/= S -> %% When a remote contact goes online while the local user is offline, the @@ -673,9 +685,11 @@ caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID caps_add(_From, _To, _Feature) -> ok. +-spec caps_update(jid(), jid(), [binary()]) -> ok. caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) -> presence(Host, {presence, U, S, [R], JID}). +-spec presence_probe(jid(), jid(), pid()) -> ok. presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) -> presence(S, {presence, JID, Pid}), presence(S, {presence, U, S, [R], JID}); @@ -695,12 +709,16 @@ presence(ServerHost, Presence) -> undefined -> init_send_loop(ServerHost); Pid -> {Pid, undefined} end, - SendLoop ! Presence. + SendLoop ! Presence, + ok. %% ------- %% subscription hooks handling functions %% +-spec out_subscription( + binary(), binary(), jid(), + subscribed | unsubscribed | subscribe | unsubscribe) -> boolean(). out_subscription(User, Server, JID, subscribed) -> Owner = jid:make(User, Server, <<>>), {PUser, PServer, PResource} = jid:tolower(JID), @@ -713,6 +731,9 @@ out_subscription(User, Server, JID, subscribed) -> out_subscription(_, _, _, _) -> true. +-spec in_subscription(boolean(), binary(), binary(), jid(), + subscribe | subscribed | unsubscribe | unsubscribed, + binary()) -> true. in_subscription(_, User, Server, Owner, unsubscribed, _) -> unsubscribe_user(jid:make(User, Server, <<>>), Owner), true; @@ -761,6 +782,7 @@ unsubscribe_user(Host, Entity, Owner) -> %% user remove hook handling function %% +-spec remove_user(binary(), binary()) -> ok. remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -800,7 +822,8 @@ remove_user(User, Server) -> Affs) end, plugins(Host)) - end). + end), + ok. handle_call(server_host, _From, State) -> {reply, State#state.server_host, State}; @@ -822,9 +845,6 @@ handle_call(stop, _From, State) -> %% @private handle_cast(_Msg, State) -> {noreply, State}. --spec handle_info(_ :: {route, From::jid(), To::jid(), Packet::xmlel()}, - State :: state()) -> {noreply, state()}. - %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | @@ -832,9 +852,12 @@ handle_cast(_Msg, State) -> {noreply, State}. %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- %% @private -handle_info({route, From, To, Packet}, - #state{server_host = ServerHost, access = Access, plugins = Plugins} = State) -> - case catch do_route(ServerHost, Access, Plugins, To#jid.lserver, From, To, Packet) of +handle_info({route, From, To, #iq{} = IQ}, + State) when To#jid.lresource == <<"">> -> + ejabberd_router:process_iq(From, To, IQ), + {noreply, State}; +handle_info({route, From, To, Packet}, State) -> + case catch do_route(To#jid.lserver, From, To, Packet) of {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); _ -> ok end, @@ -889,6 +912,12 @@ terminate(_Reason, ?MODULE, remove_user, 50), ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS), mod_disco:unregister_feature(ServerHost, ?NS_PUBSUB), case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of undefined -> @@ -906,187 +935,166 @@ terminate(_Reason, %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. --spec do_route(ServerHost :: binary(), Access :: atom(), - Plugins :: [binary(),...], Host :: mod_pubsub:hostPubsub(), - From :: jid(), To :: jid(), Packet :: xmlel()) -> ok. - %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -do_route(ServerHost, Access, Plugins, Host, From, To, Packet) -> - #xmlel{name = Name, attrs = Attrs} = Packet, - case To of - #jid{luser = <<>>, lresource = <<>>} -> - case Name of - <<"iq">> -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, lang = Lang} = IQ -> - #xmlel{attrs = QAttrs} = SubEl, - Node = fxml:get_attr_s(<<"node">>, QAttrs), - Info = ejabberd_hooks:run_fold(disco_info, ServerHost, - [], - [ServerHost, ?MODULE, <<>>, <<>>]), - Res = case iq_disco_info(Host, Node, From, Lang) of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = QAttrs, - children = IQRes ++ Info}]}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = get, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} = IQ -> - #xmlel{attrs = QAttrs} = SubEl, - Node = fxml:get_attr_s(<<"node">>, QAttrs), - Res = case iq_disco_items(Host, Node, From, jlib:rsm_decode(IQ)) of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = QAttrs, - children = IQRes}]}) - %{error, Error} -> - % jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = IQType, xmlns = ?NS_PUBSUB, lang = Lang, sub_el = SubEl} = IQ -> - Res = case iq_pubsub(Host, ServerHost, From, IQType, - SubEl, Lang, Access, Plugins) - of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER, lang = Lang, sub_el = SubEl} = IQ -> - Res = case iq_pubsub_owner(Host, ServerHost, From, - IQType, SubEl, Lang) - of - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, lang = Lang, sub_el = _SubEl} = IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - #iq{type = set, xmlns = ?NS_COMMANDS} = IQ -> - Res = case iq_command(Host, ServerHost, From, IQ, Access, Plugins) of - {error, Error} -> - jlib:make_error_reply(Packet, Error); - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes}) - end, - ejabberd_router:route(To, From, Res); - #iq{} -> - Err = jlib:make_error_reply(Packet, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> - ok - end; - <<"message">> -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> - ok; - _ -> - case find_authorization_response(Packet) of - none -> - ok; - invalid -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - Txt = <<"Incorrect authorization response">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_router:route(To, From, Err); - XFields -> - handle_authorization_response(Host, From, To, Packet, XFields) - end - end; - _ -> - ok - end; - _ -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> - ok; - <<"result">> -> - ok; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND), - ejabberd_router:route(To, From, Err) - end +-spec process_disco_info(iq()) -> iq(). +process_disco_info(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_info(#iq{from = From, to = To, lang = Lang, type = get, + sub_els = [#disco_info{node = Node}]} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + Info = ejabberd_hooks:run_fold(disco_info, ServerHost, + [], + [ServerHost, ?MODULE, <<>>, <<>>]), + case iq_disco_info(Host, Node, From, Lang) of + {result, IQRes} -> + xmpp:make_iq_result(IQ, IQRes#disco_info{node = Node, xdata = Info}); + {error, Error} -> + xmpp:make_error(IQ, Error) end. -command_disco_info(_Host, ?NS_COMMANDS, _From) -> - IdentityEl = #xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-list">>}]}, - {result, [IdentityEl]}; -command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING, _From) -> - IdentityEl = #xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}]}, - FeaturesEl = #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}]}, - {result, [IdentityEl, FeaturesEl]}. +-spec process_disco_items(iq()) -> iq(). +process_disco_items(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_items(#iq{type = get, from = From, to = To, + sub_els = [#disco_items{node = Node} = SubEl]} = IQ) -> + Host = To#jid.lserver, + case iq_disco_items(Host, Node, From, SubEl#disco_items.rsm) of + {result, IQRes} -> + xmpp:make_iq_result(IQ, IQRes#disco_items{node = Node}); + {error, Error} -> + xmpp:make_error(IQ, Error) + end. +-spec process_pubsub(iq()) -> iq(). +process_pubsub(#iq{to = To} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + Access = config(ServerHost, access), + case iq_pubsub(Host, Access, IQ) of + {result, IQRes} -> + xmpp:make_iq_result(IQ, IQRes); + {error, Error} -> + xmpp:make_error(IQ, Error) + end. + +-spec process_pubsub_owner(iq()) -> iq(). +process_pubsub_owner(#iq{to = To} = IQ) -> + Host = To#jid.lserver, + case iq_pubsub_owner(Host, IQ) of + {result, IQRes} -> + xmpp:make_iq_result(IQ, IQRes); + {error, Error} -> + xmpp:make_error(IQ, Error) + end. + +-spec process_vcard(iq()) -> iq(). +process_vcard(#iq{type = get, lang = Lang} = IQ) -> + xmpp:make_iq_result(IQ, iq_get_vcard(Lang)); +process_vcard(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). + +-spec process_commands(iq()) -> iq(). +process_commands(#iq{type = set, to = To, from = From, + sub_els = [#adhoc_command{} = Request]} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + Plugins = config(ServerHost, plugins), + Access = config(ServerHost, access), + case adhoc_request(Host, ServerHost, From, Request, Access, Plugins) of + {error, Error} -> + xmpp:make_error(IQ, Error); + Response -> + xmpp:make_iq_result( + IQ, xmpp_util:make_adhoc_response(Request, Response)) + end; +process_commands(#iq{type = get, lang = Lang} = IQ) -> + Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). + +-spec do_route(binary(), jid(), jid(), stanza()) -> ok. +do_route(Host, From, To, Packet) -> + case To of + #jid{luser = <<>>, lresource = <<>>} -> + case Packet of + #message{type = T} when T /= error -> + case find_authorization_response(Packet) of + undefined -> + ok; + {error, Err} -> + ejabberd_router:route_error(To, From, Packet, Err); + AuthResponse -> + handle_authorization_response( + Host, From, To, Packet, AuthResponse) + end; + _ -> + Err = xmpp:err_service_unavailable(), + ejabberd_router:route_error(To, From, Packet, Err) + end; + _ -> + Err = xmpp:err_item_not_found(), + ejabberd_router:route_error(To, From, Packet, Err) + end. + +-spec command_disco_info(binary(), binary(), jid()) -> {result, disco_info()}. +command_disco_info(_Host, ?NS_COMMANDS, _From) -> + {result, #disco_info{identities = [#identity{category = <<"automation">>, + type = <<"command-list">>}]}}; +command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING, _From) -> + {result, #disco_info{identities = [#identity{category = <<"automation">>, + type = <<"command-node">>}], + features = [?NS_COMMANDS]}}. + +-spec node_disco_info(binary(), binary(), jid()) -> {result, disco_info()} | + {error, stanza_error()}. node_disco_info(Host, Node, From) -> node_disco_info(Host, Node, From, true, true). +-spec node_disco_info(binary(), binary(), jid(), boolean(), boolean()) -> + {result, disco_info()} | {error, stanza_error()}. node_disco_info(Host, Node, _From, _Identity, _Features) -> - Action = fun (#pubsub_node{type = Type, options = Options}) -> - NodeType = case get_option(Options, node_type) of - collection -> <<"collection">>; - _ -> <<"leaf">> - end, - I = #xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"pubsub">>}, - {<<"type">>, NodeType}]}, - F = [#xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_PUBSUB}]} - | [#xmlel{name = <<"feature">>, - attrs = [{<<"var">>, feature(F)}]} - || F <- plugin_features(Host, Type)]], - {result, [I | F]} - end, + Action = + fun(#pubsub_node{type = Type, options = Options}) -> + NodeType = case get_option(Options, node_type) of + collection -> <<"collection">>; + _ -> <<"leaf">> + end, + Is = [#identity{category = <<"pubsub">>, type = NodeType}], + Fs = [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, Type)]], + {result, #disco_info{identities = Is, features = Fs}} + end, case transaction(Host, Node, Action, sync_dirty) of {result, {_, Result}} -> {result, Result}; Other -> Other end. +-spec iq_disco_info(binary(), binary(), jid(), binary()) + -> {result, disco_info()} | {error, stanza_error()}. iq_disco_info(Host, SNode, From, Lang) -> [Node | _] = case SNode of - <<>> -> [<<>>]; - _ -> str:tokens(SNode, <<"!">>) - end, - % Node = string_to_node(RealSNode), + <<>> -> [<<>>]; + _ -> str:tokens(SNode, <<"!">>) + end, case Node of <<>> -> - {result, [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"pubsub">>}, - {<<"type">>, <<"service">>}, - {<<"name">>, translate:translate(Lang, <<"Publish-Subscribe">>)}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_INFO}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_ITEMS}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_PUBSUB}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_COMMANDS}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}]}] - ++ [#xmlel{name = <<"feature">>, - attrs = [{<<"var">>, feature(F)}]} - || F <- features(Host, Node)]}; + {result, + #disco_info{ + identities = [#identity{ + category = <<"pubsub">>, + type = <<"service">>, + name = translate:translate( + Lang, <<"Publish-Subscribe">>)}], + features = [?NS_DISCO_INFO, + ?NS_DISCO_ITEMS, + ?NS_PUBSUB, + ?NS_COMMANDS, + ?NS_VCARD | + [feature(F) || F <- features(Host, Node)]]}}; ?NS_COMMANDS -> command_disco_info(Host, Node, From); ?NS_PUBSUB_GET_PENDING -> @@ -1095,34 +1103,34 @@ iq_disco_info(Host, SNode, From, Lang) -> node_disco_info(Host, Node, From) end. --spec iq_disco_items(Host :: mod_pubsub:host(), Node :: <<>> | mod_pubsub:nodeId(), - From :: jid(), Rsm :: none | rsm_in()) -> {result, [xmlel()]}. +-spec iq_disco_items(host(), binary(), jid(), undefined | rsm_set()) -> + {result, disco_items()} | {error, stanza_error()}. iq_disco_items(Host, <<>>, From, _RSM) -> - {result, - lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, options = Options}) -> - Attrs = case get_option(Options, title) of - false -> - [{<<"jid">>, Host} - | nodeAttr(SubNode)]; - Title -> - [{<<"jid">>, Host}, - {<<"name">>, Title} - | nodeAttr(SubNode)] - end, - #xmlel{name = <<"item">>, attrs = Attrs} - end, - tree_action(Host, get_subnodes, [Host, <<>>, From]))}; + Items = + lists:map( + fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) -> + case get_option(Options, title) of + false -> + #disco_item{jid = jid:make(Host), + node = SubNode}; + Title -> + #disco_item{jid = jid:make(Host), + name = Title, + node = SubNode} + end + end, tree_action(Host, get_subnodes, [Host, <<>>, From])), + {result, #disco_items{items = Items}}; iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) -> - {result, [#xmlel{name = <<"item">>, - attrs = [{<<"jid">>, Host}, - {<<"node">>, ?NS_PUBSUB_GET_PENDING}, - {<<"name">>, <<"Get Pending">>}]}]}; + {result, + #disco_items{items = [#disco_item{jid = jid:make(Host), + node = ?NS_PUBSUB_GET_PENDING, + name = <<"Get Pending">>}]}}; iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) -> - {result, []}; + {result, #disco_items{}}; iq_disco_items(Host, Item, From, RSM) -> case str:tokens(Item, <<"!">>) of [_Node, _ItemId] -> - {result, []}; + {result, #disco_items{}}; [Node] -> Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = O}) -> Owners = node_owners_call(Host, Type, Nidx, O), @@ -1130,28 +1138,28 @@ iq_disco_items(Host, Item, From, RSM) -> From, Type, Options, Owners, RSM) of {result, R} -> R; - _ -> {[], none} + _ -> {[], undefined} end, - Nodes = lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) -> - Attrs = case get_option(SubOptions, title) of - false -> - [{<<"jid">>, Host} - | nodeAttr(SubNode)]; - Title -> - [{<<"jid">>, Host}, - {<<"name">>, Title} - | nodeAttr(SubNode)] - end, - #xmlel{name = <<"item">>, attrs = Attrs} - end, - tree_call(Host, get_subnodes, [Host, Node, From])), - Items = lists:map(fun (#pubsub_item{itemid = {RN, _}}) -> - {result, Name} = node_call(Host, Type, get_item_name, [Host, Node, RN]), - #xmlel{name = <<"item">>, - attrs = [{<<"jid">>, Host}, {<<"name">>, Name}]} - end, - NodeItems), - {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)} + Nodes = lists:map( + fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) -> + case get_option(SubOptions, title) of + false -> + #disco_item{jid = jid:make(Host), + node = SubNode}; + Title -> + #disco_item{jid = jid:make(Host), + name = Title, + node = SubNode} + end + end, tree_call(Host, get_subnodes, [Host, Node, From])), + Items = lists:map( + fun(#pubsub_item{itemid = {RN, _}}) -> + {result, Name} = node_call(Host, Type, get_item_name, [Host, Node, RN]), + #disco_item{jid = jid:make(Host), name = Name} + end, NodeItems), + {result, + #disco_items{items = Nodes ++ Items, + rsm = RsmOut}} end, case transaction(Host, Node, Action, sync_dirty) of {result, {_, Result}} -> {result, Result}; @@ -1159,477 +1167,367 @@ iq_disco_items(Host, Item, From, RSM) -> end end. --spec iq_sm(From :: jid(), To :: jid(), IQ :: iq_request()) -> iq_result() | iq_error(). -iq_sm(From, To, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) -> - ServerHost = To#jid.lserver, +-spec iq_sm(iq()) -> iq(). +iq_sm(#iq{to = To, sub_els = [SubEl]} = IQ) -> LOwner = jid:tolower(jid:remove_resource(To)), - Res = case XMLNS of - ?NS_PUBSUB -> - iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang); - ?NS_PUBSUB_OWNER -> - iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, Lang) - end, + Res = case xmpp:get_ns(SubEl) of + ?NS_PUBSUB -> + iq_pubsub(LOwner, all, IQ); + ?NS_PUBSUB_OWNER -> + iq_pubsub_owner(LOwner, IQ) + end, case Res of - {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes}; - {error, Error} -> IQ#iq{type = error, sub_el = [Error, SubEl]} + {result, IQRes} -> + xmpp:make_iq_result(IQ, IQRes); + {error, Error} -> + xmpp:make_error(IQ, Error) end. +-spec iq_get_vcard(binary()) -> vcard_temp(). iq_get_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_pubsub">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = [{xmlcdata, - <<(translate:translate(Lang, <<"ejabberd Publish-Subscribe module">>))/binary, - "\nCopyright (c) 2004-2016 ProcessOne">>}]}]. + Desc = translate:translate(Lang, <<"ejabberd Publish-Subscribe module">>), + #vcard_temp{fn = <<"ejabberd/mod_pubsub">>, + url = ?EJABBERD_URI, + desc = <>}. --spec iq_pubsub(Host :: mod_pubsub:host(), ServerHost :: binary(), From :: jid(), - IQType :: 'get' | 'set', SubEl :: xmlel(), Lang :: binary()) -> - {result, [xmlel()]} | {error, xmlel()}. - -iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) -> - iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(Host)). - --spec iq_pubsub(Host :: mod_pubsub:host(), ServerHost :: binary(), From :: jid(), - IQType :: 'get' | 'set', SubEl :: xmlel(), Lang :: binary(), - Access :: atom(), Plugins :: [binary(),...]) -> - {result, [xmlel()]} | {error, xmlel()}. - -iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> - #xmlel{children = SubEls} = SubEl, - case fxml:remove_cdata(SubEls) of - [#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] -> - Node = fxml:get_attr_s(<<"node">>, Attrs), - case {IQType, Name} of - {set, <<"create">>} -> - Config = case Rest of - [#xmlel{name = <<"configure">>, children = C}] -> C; - _ -> [] - end, - Type = case fxml:get_attr_s(<<"type">>, Attrs) of - <<>> -> hd(Plugins); - T -> T - end, - case lists:member(Type, Plugins) of - false -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"create-nodes">>)}; - true -> - create_node(Host, ServerHost, Node, From, Type, Access, Config) - end; - {set, <<"publish">>} -> - case fxml:remove_cdata(Els) of - [#xmlel{name = <<"item">>, attrs = ItemAttrs, - children = Payload}] -> - ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs), - PubOpts = case [C || #xmlel{name = <<"publish-options">>, - children = [C]} <- Rest] of - [XEl] -> - case jlib:parse_xdata_submit(XEl) of - invalid -> []; - Form -> Form - end; - _ -> [] - end, - publish_item(Host, ServerHost, Node, From, ItemId, Payload, PubOpts, Access); - [] -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; - _ -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)} - end; - {set, <<"retract">>} -> - ForceNotify = case fxml:get_attr_s(<<"notify">>, Attrs) of - <<"1">> -> true; - <<"true">> -> true; - _ -> false - end, - case fxml:remove_cdata(Els) of - [#xmlel{name = <<"item">>, attrs = ItemAttrs}] -> - ItemId = fxml:get_attr_s(<<"id">>, ItemAttrs), - delete_item(Host, Node, From, ItemId, ForceNotify); - _ -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-required">>)} - end; - {set, <<"subscribe">>} -> - Config = case Rest of - [#xmlel{name = <<"options">>, children = C}] -> C; - _ -> [] - end, - JID = fxml:get_attr_s(<<"jid">>, Attrs), - subscribe_node(Host, Node, From, JID, Config); - {set, <<"unsubscribe">>} -> - JID = fxml:get_attr_s(<<"jid">>, Attrs), - SubId = fxml:get_attr_s(<<"subid">>, Attrs), - unsubscribe_node(Host, Node, From, JID, SubId); - {get, <<"items">>} -> - MaxItems = fxml:get_attr_s(<<"max_items">>, Attrs), - SubId = fxml:get_attr_s(<<"subid">>, Attrs), - ItemIds = lists:foldl(fun - (#xmlel{name = <<"item">>, attrs = ItemAttrs}, Acc) -> - case fxml:get_attr_s(<<"id">>, ItemAttrs) of - <<>> -> Acc; - ItemId -> [ItemId | Acc] - end; - (_, Acc) -> - Acc - end, - [], fxml:remove_cdata(Els)), - get_items(Host, Node, From, SubId, MaxItems, ItemIds, jlib:rsm_decode(SubEl)); - {get, <<"subscriptions">>} -> - get_subscriptions(Host, Node, From, Plugins); - {get, <<"affiliations">>} -> - get_affiliations(Host, Node, From, Plugins); - {get, <<"options">>} -> - SubId = fxml:get_attr_s(<<"subid">>, Attrs), - JID = fxml:get_attr_s(<<"jid">>, Attrs), - get_options(Host, Node, JID, SubId, Lang); - {set, <<"options">>} -> - SubId = fxml:get_attr_s(<<"subid">>, Attrs), - JID = fxml:get_attr_s(<<"jid">>, Attrs), - set_options(Host, Node, JID, SubId, Els); +-spec iq_pubsub(binary() | ljid(), atom(), iq()) -> + {result, pubsub()} | {error, stanza_error()}. +iq_pubsub(Host, Access, #iq{from = From, type = IQType, lang = Lang, + sub_els = [SubEl]}) -> + case {IQType, SubEl} of + {set, #pubsub{create = Node, configure = Configure, + _ = undefined}} when is_binary(Node) -> + ServerHost = serverhost(Host), + Plugins = config(ServerHost, plugins), + Config = case Configure of + {_, XData} -> decode_node_config(XData, Host, Lang); + undefined -> [] + end, + Type = hd(Plugins), + case Config of + {error, _} = Err -> + Err; _ -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED} + create_node(Host, ServerHost, Node, From, Type, Access, Config) end; - Other -> - ?INFO_MSG("Too many actions: ~p", [Other]), - {error, ?ERR_BAD_REQUEST} - end. - - --spec iq_pubsub_owner(Host :: mod_pubsub:host(), ServerHost :: binary(), From :: jid(), - IQType :: 'get' | 'set', SubEl :: xmlel(), Lang :: binary()) -> - {result, [xmlel()]} | {error, xmlel()}. - -iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> - #xmlel{children = SubEls} = SubEl, - Action = fxml:remove_cdata(SubEls), - case Action of - [#xmlel{name = Name, attrs = Attrs, children = Els}] -> - Node = fxml:get_attr_s(<<"node">>, Attrs), - case {IQType, Name} of - {get, <<"configure">>} -> - get_configure(Host, ServerHost, Node, From, Lang); - {set, <<"configure">>} -> - set_configure(Host, Node, From, Els, Lang); - {get, <<"default">>} -> - get_default(Host, Node, From, Lang); - {set, <<"delete">>} -> - delete_node(Host, Node, From); - {set, <<"purge">>} -> - purge_node(Host, Node, From); - {get, <<"subscriptions">>} -> - get_subscriptions(Host, Node, From); - {set, <<"subscriptions">>} -> - set_subscriptions(Host, Node, From, fxml:remove_cdata(Els)); - {get, <<"affiliations">>} -> - get_affiliations(Host, Node, From); - {set, <<"affiliations">>} -> - set_affiliations(Host, Node, From, fxml:remove_cdata(Els)); + {set, #pubsub{publish = #ps_publish{node = Node, items = Items}, + publish_options = XData, _ = undefined}} -> + ServerHost = serverhost(Host), + case Items of + [#ps_item{id = ItemId, xml_els = Payload}] -> + case decode_publish_options(XData, Lang) of + {error, _} = Err -> + Err; + PubOpts -> + publish_item(Host, ServerHost, Node, From, ItemId, + Payload, PubOpts, Access) + end; + [] -> + {error, extended_error(xmpp:err_bad_request(), err_item_required())}; _ -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED} + {error, extended_error(xmpp:err_bad_request(), err_invalid_payload())} end; + {set, #pubsub{retract = #ps_retract{node = Node, notify = Notify, items = Items}, + _ = undefined}} -> + case Items of + [#ps_item{id = ItemId}] -> + if ItemId /= <<>> -> + delete_item(Host, Node, From, ItemId, Notify); + true -> + {error, extended_error(xmpp:err_bad_request(), + err_item_required())} + end; + [] -> + {error, extended_error(xmpp:err_bad_request(), err_item_required())}; + _ -> + {error, extended_error(xmpp:err_bad_request(), err_invalid_payload())} + end; + {set, #pubsub{subscribe = #ps_subscribe{node = Node, jid = JID}, + options = Options, _ = undefined}} -> + Config = case Options of + #ps_options{xdata = XData} -> + decode_subscribe_options(XData, Lang); + _ -> + [] + end, + case Config of + {error, _} = Err -> + Err; + _ -> + subscribe_node(Host, Node, From, JID, Config) + end; + {set, #pubsub{unsubscribe = #ps_unsubscribe{node = Node, jid = JID, subid = SubId}, + _ = undefined}} -> + unsubscribe_node(Host, Node, From, JID, SubId); + {get, #pubsub{items = #ps_items{node = Node, + max_items = MaxItems, + subid = SubId, + items = Items}, + rsm = RSM, _ = undefined}} -> + ItemIds = [ItemId || #ps_item{id = ItemId} <- Items, ItemId /= <<>>], + get_items(Host, Node, From, SubId, MaxItems, ItemIds, RSM); + {get, #pubsub{subscriptions = {Node, _}, _ = undefined}} -> + Plugins = config(serverhost(Host), plugins), + get_subscriptions(Host, Node, From, Plugins); + {get, #pubsub{affiliations = {Node, _}, _ = undefined}} -> + Plugins = config(serverhost(Host), plugins), + get_affiliations(Host, Node, From, Plugins); + {get, #pubsub{options = #ps_options{node = Node, subid = SubId, jid = JID}, + _ = undefined}} -> + get_options(Host, Node, JID, SubId, Lang); + {set, #pubsub{options = #ps_options{node = Node, subid = SubId, + jid = JID, xdata = XData}, + _ = undefined}} -> + case decode_subscribe_options(XData, Lang) of + {error, _} = Err -> + Err; + Config -> + set_options(Host, Node, JID, SubId, Config) + end; + {set, #pubsub{}} -> + {error, xmpp:err_bad_request()}; _ -> - ?INFO_MSG("Too many actions: ~p", [Action]), - {error, ?ERR_BAD_REQUEST} + {error, xmpp:err_feature_not_implemented()} end. -iq_command(Host, ServerHost, From, IQ, Access, Plugins) -> - case adhoc:parse_request(IQ) of - Req when is_record(Req, adhoc_request) -> - case adhoc_request(Host, ServerHost, From, Req, Access, Plugins) of - Resp when is_record(Resp, adhoc_response) -> - {result, [adhoc:produce_response(Req, Resp)]}; - Error -> - Error +-spec iq_pubsub_owner(binary() | ljid(), iq()) -> {result, pubsub_owner() | undefined} | + {error, stanza_error()}. +iq_pubsub_owner(Host, #iq{type = IQType, from = From, + lang = Lang, sub_els = [SubEl]}) -> + case {IQType, SubEl} of + {get, #pubsub_owner{configure = {Node, undefined}, _ = undefined}} -> + ServerHost = serverhost(Host), + get_configure(Host, ServerHost, Node, From, Lang); + {set, #pubsub_owner{configure = {Node, XData}, _ = undefined}} -> + case XData of + undefined -> + {error, xmpp:err_bad_request(<<"No data form found">>, Lang)}; + #xdata{type = cancel} -> + {result, #pubsub_owner{}}; + #xdata{type = submit} -> + case decode_node_config(XData, Host, Lang) of + {error, _} = Err -> + Err; + Config -> + set_configure(Host, Node, From, Config, Lang) + end; + #xdata{} -> + {error, xmpp:err_bad_request(<<"Incorrect data form">>, Lang)} end; - Err -> Err + {get, #pubsub_owner{default = {Node, undefined}, _ = undefined}} -> + get_default(Host, Node, From, Lang); + {set, #pubsub_owner{delete = {Node, _}, _ = undefined}} -> + delete_node(Host, Node, From); + {set, #pubsub_owner{purge = Node, _ = undefined}} when Node /= undefined -> + purge_node(Host, Node, From); + {get, #pubsub_owner{subscriptions = {Node, []}, _ = undefined}} -> + get_subscriptions(Host, Node, From); + {set, #pubsub_owner{subscriptions = {Node, Subs}, _ = undefined}} -> + set_subscriptions(Host, Node, From, Subs); + {get, #pubsub_owner{affiliations = {Node, []}, _ = undefined}} -> + get_affiliations(Host, Node, From); + {set, #pubsub_owner{affiliations = {Node, Affs}, _ = undefined}} -> + set_affiliations(Host, Node, From, Affs); + {_, #pubsub_owner{}} -> + {error, xmpp:err_bad_request()}; + _ -> + {error, xmpp:err_feature_not_implemented()} end. -%% @doc

Processes an Ad Hoc Command.

+-spec adhoc_request(binary(), binary(), jid(), adhoc_command(), + atom(), [binary()]) -> adhoc_command() | {error, stanza_error()}. adhoc_request(Host, _ServerHost, Owner, - #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, - lang = Lang, action = <<"execute">>, - xdata = false}, - _Access, Plugins) -> + #adhoc_command{node = ?NS_PUBSUB_GET_PENDING, lang = Lang, + action = execute, xdata = undefined}, + _Access, Plugins) -> send_pending_node_form(Host, Owner, Lang, Plugins); adhoc_request(Host, _ServerHost, Owner, - #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, - action = <<"execute">>, xdata = XData, lang = Lang}, - _Access, _Plugins) -> - ParseOptions = case XData of - #xmlel{name = <<"x">>} = XEl -> - case jlib:parse_xdata_submit(XEl) of - invalid -> - Txt = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - XData2 -> - case set_xoption(Host, XData2, []) of - NewOpts when is_list(NewOpts) -> {result, NewOpts}; - Err -> Err - end - end; - _ -> - Txt = <<"No data form found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end, - case ParseOptions of - {result, XForm} -> - case lists:keysearch(node, 1, XForm) of - {value, {_, Node}} -> send_pending_auth_events(Host, Node, Owner); - false -> {error, extended_error(?ERR_BAD_REQUEST, <<"bad-payload">>)} - end; - Error -> Error + #adhoc_command{node = ?NS_PUBSUB_GET_PENDING, lang = Lang, + action = execute, xdata = #xdata{} = XData} = Request, + _Access, _Plugins) -> + case decode_get_pending(XData, Lang) of + {error, _} = Err -> + Err; + Config -> + Node = proplists:get_value(node, Config), + case send_pending_auth_events(Host, Node, Owner, Lang) of + ok -> + xmpp_util:make_adhoc_response( + Request, #adhoc_command{action = completed}); + Err -> + Err + end end; adhoc_request(_Host, _ServerHost, _Owner, - #adhoc_request{action = <<"cancel">>}, _Access, - _Plugins) -> - #adhoc_response{status = canceled}; -adhoc_request(Host, ServerHost, Owner, - #adhoc_request{action = <<>>} = R, Access, Plugins) -> - adhoc_request(Host, ServerHost, Owner, - R#adhoc_request{action = <<"execute">>}, Access, - Plugins); + #adhoc_command{action = cancel}, _Access, _Plugins) -> + #adhoc_command{status = canceled}; adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) -> ?DEBUG("Couldn't process ad hoc command:~n~p", [Other]), - {error, ?ERR_ITEM_NOT_FOUND}. + {error, xmpp:err_item_not_found()}. -%% @doc

Sends the process pending subscriptions XForm for Host to Owner.

+-spec send_pending_node_form(binary(), jid(), binary(), + [binary()]) -> adhoc_command() | {error, stanza_error()}. send_pending_node_form(Host, Owner, _Lang, Plugins) -> Filter = fun (Type) -> lists:member(<<"get-pending">>, plugin_features(Host, Type)) end, case lists:filter(Filter, Plugins) of [] -> - Err = extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"get-pending">>), + Err = extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('get-pending')), {error, Err}; Ps -> - XOpts = [#xmlel{name = <<"option">>, attrs = [], - children = [#xmlel{name = <<"value">>, - attrs = [], - children = [{xmlcdata, Node}]}]} - || Node <- get_pending_nodes(Host, Owner, Ps)], - XForm = #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = [#xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"list-single">>}, - {<<"var">>, <<"pubsub#node">>}], - children = lists:usort(XOpts)}]}, - #adhoc_response{status = executing, - defaultaction = <<"execute">>, elements = [XForm]} + case get_pending_nodes(Host, Owner, Ps) of + {ok, Nodes} -> + XForm = #xdata{type = form, + fields = pubsub_get_pending:encode( + [{node, Nodes}])}, + #adhoc_command{status = executing, action = execute, + xdata = XForm}; + Err -> + Err + end end. +-spec get_pending_nodes(binary(), jid(), [binary()]) -> {ok, [binary()]} | + {error, stanza_error()}. get_pending_nodes(Host, Owner, Plugins) -> Tr = fun (Type) -> case node_call(Host, Type, get_pending_nodes, [Host, Owner]) of {result, Nodes} -> Nodes; _ -> [] end - end, + end, Action = fun() -> {result, lists:flatmap(Tr, Plugins)} end, case transaction(Host, Action, sync_dirty) of - {result, Res} -> Res; + {result, Res} -> {ok, Res}; Err -> Err end. %% @doc

Send a subscription approval form to Owner for all pending %% subscriptions on Host and Node.

-send_pending_auth_events(Host, Node, Owner) -> +-spec send_pending_auth_events(binary(), binary(), jid(), + binary()) -> adhoc_command() | {error, stanza_error()}. +send_pending_auth_events(Host, Node, Owner, Lang) -> ?DEBUG("Sending pending auth events for ~s on ~s:~s", - [jid:to_string(Owner), Host, Node]), - Action = fun (#pubsub_node{id = Nidx, type = Type}) -> - case lists:member(<<"get-pending">>, plugin_features(Host, Type)) of - true -> - case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of - {result, owner} -> node_call(Host, Type, get_node_subscriptions, [Nidx]); - _ -> {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)} - end; - false -> - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"get-pending">>)} - end - end, + [jid:to_string(Owner), Host, Node]), + Action = + fun(#pubsub_node{id = Nidx, type = Type}) -> + case lists:member(<<"get-pending">>, plugin_features(Host, Type)) of + true -> + case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of + {result, owner} -> + node_call(Host, Type, get_node_subscriptions, [Nidx]); + _ -> + {error, xmpp:err_forbidden( + <<"Owner privileges required">>, Lang)} + end; + false -> + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('get-pending'))} + end + end, case transaction(Host, Node, Action, sync_dirty) of {result, {N, Subs}} -> - lists:foreach(fun - ({J, pending, _SubId}) -> send_authorization_request(N, jid:make(J)); - ({J, pending}) -> send_authorization_request(N, jid:make(J)); - (_) -> ok - end, - Subs), - #adhoc_response{}; + lists:foreach( + fun({J, pending, _SubId}) -> send_authorization_request(N, jid:make(J)); + ({J, pending}) -> send_authorization_request(N, jid:make(J)); + (_) -> ok + end, Subs), + #adhoc_command{}; Err -> Err end. %%% authorization handling - -send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id = Nidx, owners = O}, - Subscriber) -> +-spec send_authorization_request(#pubsub_node{}, jid()) -> ok. +send_authorization_request(#pubsub_node{nodeid = {Host, Node}, + type = Type, id = Nidx, owners = O}, + Subscriber) -> + %% TODO: pass lang to this function Lang = <<"en">>, - Stanza = #xmlel{name = <<"message">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, <<"PubSub subscriber request">>)}]}, - #xmlel{name = <<"instructions">>, - attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Choose whether to approve this entity's " - "subscription.">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, ?NS_PUBSUB_SUB_AUTH}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"pubsub#node">>}, - {<<"type">>, - <<"text-single">>}, - {<<"label">>, translate:translate(Lang, <<"Node ID">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, Node}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, - <<"pubsub#subscriber_jid">>}, - {<<"type">>, <<"jid-single">>}, - {<<"label">>, - translate:translate(Lang, <<"Subscriber Address">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, jid:to_string(Subscriber)}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, - <<"pubsub#allow">>}, - {<<"type">>, <<"boolean">>}, - {<<"label">>, - translate:translate(Lang, - <<"Allow this Jabber ID to subscribe to " - "this pubsub node?">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, <<"false">>}]}]}]}]}, - lists:foreach(fun (Owner) -> - ejabberd_router:route(service_jid(Host), jid:make(Owner), Stanza) - end, - node_owners_action(Host, Type, Nidx, O)). + Fs = pubsub_subscribe_authorization:encode( + [{node, Node}, + {subscriber_jid, Subscriber}, + {allow, false}], + fun(T) -> translate:translate(Lang, T) end), + X = #xdata{type = form, + title = translate:translate( + Lang, <<"PubSub subscriber request">>), + instructions = [translate:translate( + Lang, + <<"Choose whether to approve this entity's " + "subscription.">>)], + fields = Fs}, + Stanza = #message{sub_els = [X]}, + lists:foreach( + fun (Owner) -> + ejabberd_router:route(service_jid(Host), jid:make(Owner), Stanza) + end, node_owners_action(Host, Type, Nidx, O)). +-spec find_authorization_response(message()) -> undefined | + pubsub_subscribe_authorization:result() | + {error, stanza_error()}. find_authorization_response(Packet) -> - #xmlel{children = Els} = Packet, - XData1 = lists:map(fun - (#xmlel{name = <<"x">>, attrs = XAttrs} = XEl) -> - case fxml:get_attr_s(<<"xmlns">>, XAttrs) of - ?NS_XDATA -> - case fxml:get_attr_s(<<"type">>, XAttrs) of - <<"cancel">> -> none; - _ -> jlib:parse_xdata_submit(XEl) - end; - _ -> - none - end; - (_) -> - none - end, - fxml:remove_cdata(Els)), - XData = lists:filter(fun (E) -> E /= none end, XData1), - case XData of - [invalid] -> - invalid; - [] -> - none; - [XFields] when is_list(XFields) -> - ?DEBUG("XFields: ~p", [XFields]), - case lists:keysearch(<<"FORM_TYPE">>, 1, XFields) of - {value, {_, [?NS_PUBSUB_SUB_AUTH]}} -> XFields; - _ -> invalid - end + case xmpp:get_subtag(Packet, #xdata{}) of + #xdata{type = cancel} -> + undefined; + #xdata{type = submit, fields = Fs} -> + try pubsub_subscribe_authorization:decode(Fs) of + Result -> Result + catch _:{pubsub_subscribe_authorization, Why} -> + Lang = xmpp:get_lang(Packet), + Txt = pubsub_subscribe_authorization:format_error(Why), + {error, xmpp:err_bad_request(Txt, Lang)} + end; + #xdata{} -> + {error, xmpp:err_bad_request()}; + false -> + undefined end. %% @doc Send a message to JID with the supplied Subscription +-spec send_authorization_approval(binary(), jid(), binary(), subscribed | none) -> ok. send_authorization_approval(Host, JID, SNode, Subscription) -> - SubAttrs = case Subscription of - %{S, SID} -> - % [{<<"subscription">>, subscription_to_string(S)}, - % {<<"subid">>, SID}]; - S -> - [{<<"subscription">>, subscription_to_string(S)}] - end, - Stanza = event_stanza(<<"subscription">>, - [{<<"jid">>, jid:to_string(JID)} - | nodeAttr(SNode)] - ++ SubAttrs), + Event = #ps_event{subscription = + #ps_subscription{jid = JID, + node = SNode, + type = Subscription}}, + Stanza = #message{sub_els = [Event]}, ejabberd_router:route(service_jid(Host), JID, Stanza). -handle_authorization_response(Host, From, To, Packet, XFields) -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), - case {lists:keysearch(<<"pubsub#node">>, 1, XFields), - lists:keysearch(<<"pubsub#subscriber_jid">>, 1, XFields), - lists:keysearch(<<"pubsub#allow">>, 1, XFields)} - of - {{value, {_, [Node]}}, - {value, {_, [SSubscriber]}}, - {value, {_, [SAllow]}}} -> - FromLJID = jid:tolower(jid:remove_resource(From)), - Subscriber = jid:from_string(SSubscriber), - Allow = case SAllow of - <<"1">> -> true; - <<"true">> -> true; - _ -> false - end, - Action = fun (#pubsub_node{type = Type, id = Nidx, owners = O}) -> - Owners = node_owners_call(Host, Type, Nidx, O), - case lists:member(FromLJID, Owners) of - true -> - {result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]), - update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs); - false -> - {error, ?ERRT_FORBIDDEN(Lang, <<"You're not an owner">>)} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {error, Error} -> - Err = jlib:make_error_reply(Packet, Error), - ejabberd_router:route(To, From, Err); - {result, {_, _NewSubscription}} -> - %% XXX: notify about subscription state change, section 12.11 - ok; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR), - ejabberd_router:route(To, From, Err) - end; +-spec handle_authorization_response(binary(), jid(), jid(), message(), + pubsub_subscribe_authorization:result()) -> ok. +handle_authorization_response(Host, From, To, Packet, Response) -> + Node = proplists:get_value(node, Response), + Subscriber = proplists:get_value(subscriber_jid, Response), + Allow = proplists:get_value(allow, Response), + Lang = xmpp:get_lang(Packet), + FromLJID = jid:tolower(jid:remove_resource(From)), + Action = + fun(#pubsub_node{type = Type, id = Nidx, owners = O}) -> + Owners = node_owners_call(Host, Type, Nidx, O), + case lists:member(FromLJID, Owners) of + true -> + {result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]), + update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs); + false -> + {error, xmpp:err_forbidden(<<"Owner privileges required">>, Lang)} + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {error, Error} -> + ejabberd_router:route_error(To, From, Packet, Error); + {result, {_, _NewSubscription}} -> + %% XXX: notify about subscription state change, section 12.11 + ok; _ -> - Txt = <<"Incorrect data form">>, - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)), - ejabberd_router:route(To, From, Err) + Err = xmpp:err_internal_server_error(), + ejabberd_router:route_error(To, From, Packet, Err) end. +-spec update_auth(binary(), binary(), _, _, jid() | error, boolean(), _) -> + {result, ok} | {error, stanza_error()}. update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) -> Sub= lists:filter(fun ({pending, _}) -> true; @@ -1647,68 +1545,9 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) -> {result, ok}; _ -> Txt = <<"No pending subscriptions found">>, - {error, ?ERRT_UNEXPECTED_REQUEST(?MYLANG, Txt)} + {error, xmpp:err_unexpected_request(Txt, ?MYLANG)} end. --define(XFIELD(Type, Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(BOOLXFIELD(Label, Var, Val), - ?XFIELD(<<"boolean">>, Label, Var, - case Val of - true -> <<"1">>; - _ -> <<"0">> - end)). - --define(STRINGXFIELD(Label, Var, Val), - ?XFIELD(<<"text-single">>, Label, Var, Val)). - --define(STRINGMXFIELD(Label, Var, Vals), - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"text-multi">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, V}]} - || V <- Vals]}). - --define(XFIELDOPT(Type, Label, Var, Val, Opts), - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = [#xmlel{name = <<"option">>, attrs = [], - children = [#xmlel{name = <<"value">>, - attrs = [], - children = [{xmlcdata, Opt}]}]} - || Opt <- Opts] - ++ - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - --define(LISTXFIELD(Label, Var, Val, Opts), - ?XFIELDOPT(<<"list-single">>, Label, Var, Val, Opts)). - --define(LISTMXFIELD(Label, Var, Vals, Opts), - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"list-multi">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = [#xmlel{name = <<"option">>, attrs = [], - children = [#xmlel{name = <<"value">>, - attrs = [], - children = [{xmlcdata, Opt}]}]} - || Opt <- Opts] - ++ - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]} - || Val <- Vals]}). - %% @doc

Create new pubsub nodes

%%

In addition to method-specific error conditions, there are several general reasons why the node creation request might fail:

%%
    @@ -1726,120 +1565,88 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) -> %%
  • nodetree create_node checks if nodeid already exists
  • %%
  • node plugin create_node just sets default affiliation/subscription
  • %%
--spec create_node(Host :: mod_pubsub:host(), ServerHost :: binary(), - Node :: <<>> | mod_pubsub:nodeId(), Owner :: jid(), - Type :: binary()) -> {result, [xmlel(),...]} | {error, xmlel()}. +-spec create_node(host(), binary(), binary(), jid(), + binary()) -> {result, pubsub()} | {error, stanza_error()}. create_node(Host, ServerHost, Node, Owner, Type) -> create_node(Host, ServerHost, Node, Owner, Type, all, []). --spec create_node(Host :: mod_pubsub:host(), ServerHost :: binary(), - Node :: <<>> | mod_pubsub:nodeId(), Owner :: jid(), - Type :: binary(), Access :: atom(), Configuration :: [xmlel()]) -> - {result, [xmlel(),...]} | {error, xmlel()}. - +-spec create_node(host(), binary(), binary(), jid(), binary(), + atom(), [{binary(), [binary()]}]) -> {result, pubsub()} | {error, stanza_error()}. create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) -> case lists:member(<<"instant-nodes">>, plugin_features(Host, Type)) of true -> Node = randoms:get_string(), case create_node(Host, ServerHost, Node, Owner, Type, Access, Configuration) of {result, _} -> - {result, [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [#xmlel{name = <<"create">>, - attrs = nodeAttr(Node)}]}]}; + {result, #pubsub{create = Node}}; Error -> Error end; false -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"nodeid-required">>)} + {error, extended_error(xmpp:err_not_acceptable(), err_nodeid_required())} end; create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> Type = select_type(ServerHost, Host, Node, GivenType), - ParseOptions = case fxml:remove_cdata(Configuration) of - [] -> - {result, node_options(Host, Type)}; - [#xmlel{name = <<"x">>} = XEl] -> - case jlib:parse_xdata_submit(XEl) of - invalid -> - Txt = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)}; - XData -> - case set_xoption(Host, XData, node_options(Host, Type)) of - NewOpts when is_list(NewOpts) -> {result, NewOpts}; - Err -> Err - end + NodeOptions = merge_config(Configuration, node_options(Host, Type)), + CreateNode = + fun() -> + Parent = case node_call(Host, Type, node_to_path, [Node]) of + {result, [Node]} -> + <<>>; + {result, Path} -> + element(2, node_call(Host, Type, path_to_node, + [lists:sublist(Path, length(Path)-1)])) + end, + Parents = case Parent of + <<>> -> []; + _ -> [Parent] + end, + case node_call(Host, Type, create_node_permission, + [Host, ServerHost, Node, Parent, Owner, Access]) of + {result, true} -> + case tree_call(Host, create_node, + [Host, Node, Type, Owner, NodeOptions, Parents]) + of + {ok, Nidx} -> + SubsByDepth = get_node_subs_by_depth(Host, Node, Owner), + case node_call(Host, Type, create_node, [Nidx, Owner]) of + {result, Result} -> {result, {Nidx, SubsByDepth, Result}}; + Error -> Error + end; + {error, {virtual, Nidx}} -> + case node_call(Host, Type, create_node, [Nidx, Owner]) of + {result, Result} -> {result, {Nidx, [], Result}}; + Error -> Error + end; + Error -> + Error + end; + _ -> + Txt = <<"You're not allowed to create nodes">>, + {error, xmpp:err_forbidden(Txt, ?MYLANG)} + end + end, + Reply = #pubsub{create = Node}, + case transaction(Host, CreateNode, transaction) of + {result, {Nidx, SubsByDepth, {Result, broadcast}}} -> + broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth), + ejabberd_hooks:run(pubsub_create_node, ServerHost, + [ServerHost, Host, Node, Nidx, NodeOptions]), + case Result of + default -> {result, Reply}; + _ -> {result, Result} end; - _ -> - ?INFO_MSG("Node ~p; bad configuration: ~p", [Node, Configuration]), - Txt = <<"No data form found">>, - {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)} - end, - case ParseOptions of - {result, NodeOptions} -> - CreateNode = fun () -> - Parent = case node_call(Host, Type, node_to_path, [Node]) of - {result, [Node]} -> - <<>>; - {result, Path} -> - element(2, node_call(Host, Type, path_to_node, [lists:sublist(Path, length(Path)-1)])) - end, - Parents = case Parent of - <<>> -> []; - _ -> [Parent] - end, - case node_call(Host, Type, create_node_permission, - [Host, ServerHost, Node, Parent, Owner, Access]) - of - {result, true} -> - case tree_call(Host, create_node, - [Host, Node, Type, Owner, NodeOptions, Parents]) - of - {ok, Nidx} -> - SubsByDepth = get_node_subs_by_depth(Host, Node, Owner), - case node_call(Host, Type, create_node, [Nidx, Owner]) of - {result, Result} -> {result, {Nidx, SubsByDepth, Result}}; - Error -> Error - end; - {error, {virtual, Nidx}} -> - case node_call(Host, Type, create_node, [Nidx, Owner]) of - {result, Result} -> {result, {Nidx, [], Result}}; - Error -> Error - end; - Error -> - Error - end; - _ -> - Txt1 = <<"You're not allowed to create nodes">>, - {error, ?ERRT_FORBIDDEN(?MYLANG, Txt1)} - end - end, - Reply = [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [#xmlel{name = <<"create">>, - attrs = nodeAttr(Node)}]}], - case transaction(Host, CreateNode, transaction) of - {result, {Nidx, SubsByDepth, {Result, broadcast}}} -> - broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth), - ejabberd_hooks:run(pubsub_create_node, ServerHost, - [ServerHost, Host, Node, Nidx, NodeOptions]), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {Nidx, _SubsByDepth, Result}} -> - ejabberd_hooks:run(pubsub_create_node, ServerHost, - [ServerHost, Host, Node, Nidx, NodeOptions]), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - Error -> - %% in case we change transaction to sync_dirty... - %% node_call(Host, Type, delete_node, [Host, Node]), - %% tree_call(Host, delete_node, [Host, Node]), - Error + {result, {Nidx, _SubsByDepth, Result}} -> + ejabberd_hooks:run(pubsub_create_node, ServerHost, + [ServerHost, Host, Node, Nidx, NodeOptions]), + case Result of + default -> {result, Reply}; + _ -> {result, Result} end; Error -> + %% in case we change transaction to sync_dirty... + %% node_call(Host, Type, delete_node, [Host, Node]), + %% tree_call(Host, delete_node, [Host, Node]), Error end. @@ -1850,10 +1657,9 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> %%
  • The node is the root collection node, which cannot be deleted.
  • %%
  • The specified node does not exist.
  • %% --spec delete_node(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - Owner :: jid()) -> {result, [xmlel(),...]} | {error, xmlel()}. +-spec delete_node(host(), binary(), jid()) -> {result, pubsub_owner()} | {error, stanza_error()}. delete_node(_Host, <<>>, _Owner) -> - {error, ?ERRT_NOT_ALLOWED(?MYLANG, <<"No node specified">>)}; + {error, xmpp:err_not_allowed(<<"No node specified">>, ?MYLANG)}; delete_node(Host, Node, Owner) -> Action = fun (#pubsub_node{type = Type, id = Nidx}) -> case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of @@ -1865,10 +1671,10 @@ delete_node(Host, Node, Owner) -> Error -> Error end; _ -> - {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)} + {error, xmpp:err_forbidden(<<"Owner privileges required">>, ?MYLANG)} end end, - Reply = [], + Reply = undefined, ServerHost = serverhost(Host), case transaction(Host, Node, Action, transaction) of {result, {_, {SubsByDepth, {Result, broadcast, Removed}}}} -> @@ -1927,16 +1733,15 @@ delete_node(Host, Node, Owner) -> %%
  • The node does not support subscriptions.
  • %%
  • The node does not exist.
  • %% --spec subscribe_node(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - From :: jid(), JID :: binary(), Configuration :: [xmlel()]) -> - {result, [xmlel(),...]} | {error, xmlel()}. +-spec subscribe_node(host(), binary(), jid(), binary(), [{binary(), [binary()]}]) -> + {result, pubsub()} | {error, stanza_error()}. subscribe_node(Host, Node, From, JID, Configuration) -> SubModule = subscription_plugin(Host), SubOpts = case SubModule:parse_options_xform(Configuration) of {result, GoodSubOpts} -> GoodSubOpts; _ -> invalid end, - Subscriber = string_to_ljid(JID), + Subscriber = jid:tolower(JID), Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx, owners = O}) -> Features = plugin_features(Host, Type), SubscribeFeature = lists:member(<<"subscribe">>, Features), @@ -1962,21 +1767,21 @@ subscribe_node(Host, Node, From, JID, Configuration) -> true end, if not SubscribeFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscribe">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('subscribe'))}; not SubscribeConfig -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscribe">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('subscribe'))}; HasOptions andalso not OptionsFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscription-options">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('subscription-options'))}; SubOpts == invalid -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)}; + {error, extended_error(xmpp:err_bad_request(), + err_invalid_options())}; not CanSubscribe -> %% fallback to closest XEP compatible result, assume we are not allowed to subscribe - {error, - extended_error(?ERR_NOT_ALLOWED, <<"closed-node">>)}; + {error, extended_error(xmpp:err_not_allowed(), + err_closed_node())}; true -> Owners = node_owners_call(Host, Type, Nidx, O), {PS, RG} = get_presence_and_roster_permissions(Host, Subscriber, @@ -1987,19 +1792,13 @@ subscribe_node(Host, Node, From, JID, Configuration) -> end end, Reply = fun (Subscription) -> - SubAttrs = case Subscription of - {subscribed, SubId} -> - [{<<"subscription">>, subscription_to_string(subscribed)}, - {<<"subid">>, SubId}, {<<"node">>, Node}]; - Other -> - [{<<"subscription">>, subscription_to_string(Other)}, - {<<"node">>, Node}] - end, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [#xmlel{name = <<"subscription">>, - attrs = [{<<"jid">>, jid:to_string(Subscriber)} - | SubAttrs]}]}] + Sub = case Subscription of + {subscribed, SubId} -> + #ps_subscription{type = subscribed, subid = SubId}; + Other -> + #ps_subscription{type = Other} + end, + #pubsub{subscription = Sub#ps_subscription{jid = Subscriber, node = Node}} end, case transaction(Host, Node, Action, sync_dirty) of {result, {TNode, {Result, subscribed, SubId, send_last}}} -> @@ -2038,14 +1837,10 @@ subscribe_node(Host, Node, From, JID, Configuration) -> %%
  • The node does not exist.
  • %%
  • The request specifies a subscription ID that is not valid or current.
  • %% --spec unsubscribe_node(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - From :: jid(), JID :: binary() | ljid(), - SubId :: mod_pubsub:subId()) -> - {result, []} | {error, xmlel()}. - -unsubscribe_node(Host, Node, From, JID, SubId) when is_binary(JID) -> - unsubscribe_node(Host, Node, From, string_to_ljid(JID), SubId); -unsubscribe_node(Host, Node, From, Subscriber, SubId) -> +-spec unsubscribe_node(host(), binary(), jid(), jid(), binary()) -> + {result, undefined} | {error, stanza_error()}. +unsubscribe_node(Host, Node, From, JID, SubId) -> + Subscriber = jid:tolower(JID), Action = fun (#pubsub_node{type = Type, id = Nidx}) -> node_call(Host, Type, unsubscribe_node, [Nidx, From, Subscriber, SubId]) end, @@ -2053,9 +1848,8 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) -> {result, {_, default}} -> ServerHost = serverhost(Host), ejabberd_hooks:run(pubsub_unsubscribe_node, ServerHost, - [ServerHost, Host, Node, Subscriber, SubId]), - {result, []}; - % {result, {_, Result}} -> {result, Result}; + [ServerHost, Host, Node, Subscriber, SubId]), + {result, undefined}; Error -> Error end. @@ -2070,12 +1864,8 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) -> %%
  • The item contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node.
  • %%
  • The request does not match the node configuration.
  • %% --spec publish_item(Host :: mod_pubsub:host(), ServerHost :: binary(), - Node :: mod_pubsub:nodeId(), Publisher :: jid(), - ItemId :: <<>> | mod_pubsub:itemId(), - Payload :: mod_pubsub:payload()) -> - {result, [xmlel(),...]} | {error, xmlel()}. - +-spec publish_item(host(), binary(), binary(), jid(), binary(), + [xmlel()]) -> {result, pubsub()} | {error, stanza_error()}. publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, [], all). publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, PubOpts, Access) -> @@ -2092,34 +1882,31 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access PayloadSize = byte_size(term_to_binary(Payload)) - 2, PayloadMaxSize = get_option(Options, max_payload_size), if not PublishFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"publish">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported(publish))}; PayloadSize > PayloadMaxSize -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"payload-too-big">>)}; + {error, extended_error(xmpp:err_not_acceptable(), + err_payload_too_big())}; (PayloadCount == 0) and (Payload == []) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"payload-required">>)}; + {error, extended_error(xmpp:err_bad_request(), + err_payload_required())}; (PayloadCount > 1) or (PayloadCount == 0) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}; + {error, extended_error(xmpp:err_bad_request(), + err_invalid_payload())}; (DeliverPayloads == false) and (PersistItems == false) and (PayloadSize > 0) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-forbidden">>)}; + {error, extended_error(xmpp:err_bad_request(), + err_item_forbidden())}; ((DeliverPayloads == true) or (PersistItems == true)) and (PayloadSize == 0) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; + {error, extended_error(xmpp:err_bad_request(), + err_item_required())}; true -> node_call(Host, Type, publish_item, [Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, PubOpts]) end end, - Reply = [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [#xmlel{name = <<"publish">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"item">>, - attrs = itemAttr(ItemId)}]}]}], + Reply = #pubsub{publish = #ps_publish{node = Node, + items = [#ps_item{id = ItemId}]}}, case transaction(Host, Node, Action, sync_dirty) of {result, {TNode, {Result, Broadcast, Removed}}} -> Nidx = TNode#pubsub_node.id, @@ -2161,29 +1948,20 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access {result, Reply}; {result, {_, Result}} -> {result, Result}; - {error, _} = Error -> - case is_item_not_found(Error) of + {error, #stanza_error{reason = 'item-not-found'}} -> + Type = select_type(ServerHost, Host, Node), + case lists:member(<<"auto-create">>, plugin_features(Host, Type)) of true -> - Type = select_type(ServerHost, Host, Node), - case lists:member(<<"auto-create">>, plugin_features(Host, Type)) of - true -> - case create_node(Host, ServerHost, Node, Publisher, Type, Access, []) of - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [#xmlel{name = <<"create">>, - attrs = [{<<"node">>, NewNode}]}]}]} -> + case create_node(Host, ServerHost, Node, Publisher, Type, Access, []) of + {result, #pubsub{create = NewNode}} -> publish_item(Host, ServerHost, NewNode, Publisher, ItemId, - Payload, PubOpts, Access); - _ -> - {error, ?ERR_ITEM_NOT_FOUND} - end; - false -> - Txt = <<"Automatic node creation is not enabled">>, - {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, Txt)} + Payload, PubOpts, Access); + _ -> + {error, xmpp:err_item_not_found()} end; false -> - Error + Txt = <<"Automatic node creation is not enabled">>, + {error, xmpp:err_item_not_found(Txt, ?MYLANG)} end; Error -> Error @@ -2200,14 +1978,12 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access %%
  • The node does not support persistent items.
  • %%
  • The service does not support the deletion of items.
  • %% --spec delete_item(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - Publisher :: jid(), ItemId :: mod_pubsub:itemId()) -> - {result, []} | {error, xmlel()}. - +-spec delete_item(host(), binary(), jid(), binary()) -> {result, undefined} | + {error, stanza_error()}. delete_item(Host, Node, Publisher, ItemId) -> delete_item(Host, Node, Publisher, ItemId, false). delete_item(_, <<>>, _, _, _) -> - {error, extended_error(?ERR_BAD_REQUEST, <<"node-required">>)}; + {error, extended_error(xmpp:err_bad_request(), err_nodeid_required())}; delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) -> Features = plugin_features(Host, Type), @@ -2218,16 +1994,16 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> %% %% Request does not specify an item %% {error, extended_error(?ERR_BAD_REQUEST, "item-required")}; not PersistentFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"persistent-items">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('persistent-items'))}; not DeleteFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"delete-items">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('delete-items'))}; true -> node_call(Host, Type, delete_item, [Nidx, Publisher, PublishModel, ItemId]) end end, - Reply = [], + Reply = undefined, case transaction(Host, Node, Action, sync_dirty) of {result, {TNode, {Result, broadcast}}} -> Nidx = TNode#pubsub_node.id, @@ -2258,10 +2034,8 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> %%
  • The node is not configured to persist items.
  • %%
  • The specified node does not exist.
  • %% --spec purge_node(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - Owner :: jid()) -> - {result, []} | {error, xmlel()}. - +-spec purge_node(mod_pubsub:host(), binary(), jid()) -> {result, undefined} | + {error, stanza_error()}. purge_node(Host, Node, Owner) -> Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) -> Features = plugin_features(Host, Type), @@ -2269,18 +2043,18 @@ purge_node(Host, Node, Owner) -> PersistentFeature = lists:member(<<"persistent-items">>, Features), PersistentConfig = get_option(Options, persist_items), if not PurgeFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"purge-nodes">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('purge-nodes'))}; not PersistentFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"persistent-items">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('persistent-items'))}; not PersistentConfig -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"persistent-items">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('persistent-items'))}; true -> node_call(Host, Type, purge_node, [Nidx, Owner]) end end, - Reply = [], + Reply = undefined, case transaction(Host, Node, Action, sync_dirty) of {result, {TNode, {Result, broadcast}}} -> Nidx = TNode#pubsub_node.id, @@ -2305,76 +2079,62 @@ purge_node(Host, Node, Owner) -> %%

    The permission are not checked in this function.

    %% @todo We probably need to check that the user doing the query has the right %% to read the items. --spec get_items(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - From :: jid(), SubId :: mod_pubsub:subId(), - SMaxItems :: binary(), ItemIds :: [mod_pubsub:itemId()], - Rsm :: none | rsm_in()) -> - {result, [xmlel(),...]} | {error, xmlel()}. - +-spec get_items(host(), binary(), jid(), binary(), + binary(), [binary()], undefined | rsm_set()) -> + {result, pubsub()} | {error, stanza_error()}. get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) -> - MaxItems = if SMaxItems == <<>> -> - case get_max_items_node(Host) of - undefined -> ?MAXITEMS; - Max -> Max - end; - true -> - case catch jlib:binary_to_integer(SMaxItems) of - {'EXIT', _} -> - Txt = <<"Value of 'max_items' should be integer">>, - {error, ?ERRT_BAD_REQUEST(?MYLANG, Txt)}; - Val -> Val - end - end, - case MaxItems of - {error, Error} -> - {error, Error}; - _ -> - Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx, owners = O}) -> - Features = plugin_features(Host, Type), - RetreiveFeature = lists:member(<<"retrieve-items">>, Features), - PersistentFeature = lists:member(<<"persistent-items">>, Features), - AccessModel = get_option(Options, access_model), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - if not RetreiveFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"retrieve-items">>)}; - not PersistentFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"persistent-items">>)}; - true -> - Owners = node_owners_call(Host, Type, Nidx, O), - {PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, - AccessModel, AllowedGroups), - node_call(Host, Type, get_items, - [Nidx, From, AccessModel, PS, RG, SubId, RSM]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, {Items, RsmOut}}} -> - SendItems = case ItemIds of - [] -> - Items; - _ -> - lists:filter(fun (#pubsub_item{itemid = {ItemId, _}}) -> - lists:member(ItemId, ItemIds) - end, - Items) - end, - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = itemsEls(lists:sublist(SendItems, MaxItems))} - | jlib:rsm_encode(RsmOut)]}]}; - Error -> - Error - end + MaxItems = if SMaxItems == undefined -> + case get_max_items_node(Host) of + undefined -> ?MAXITEMS; + Max -> Max + end; + true -> + SMaxItems + end, + Action = + fun(#pubsub_node{options = Options, type = Type, + id = Nidx, owners = O}) -> + Features = plugin_features(Host, Type), + RetreiveFeature = lists:member(<<"retrieve-items">>, Features), + PersistentFeature = lists:member(<<"persistent-items">>, Features), + AccessModel = get_option(Options, access_model), + AllowedGroups = get_option(Options, roster_groups_allowed, []), + if not RetreiveFeature -> + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('retrieve-items'))}; + not PersistentFeature -> + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('persistent-items'))}; + true -> + Owners = node_owners_call(Host, Type, Nidx, O), + {PS, RG} = get_presence_and_roster_permissions( + Host, From, Owners, AccessModel, AllowedGroups), + node_call(Host, Type, get_items, + [Nidx, From, AccessModel, PS, RG, SubId, RSM]) + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_, {Items, RsmOut}}} -> + SendItems = case ItemIds of + [] -> + Items; + _ -> + lists:filter( + fun(#pubsub_item{itemid = {ItemId, _}}) -> + lists:member(ItemId, ItemIds) + end, Items) + end, + {result, + #pubsub{items = #ps_items{node = Node, + items = itemsEls(lists:sublist(SendItems, MaxItems))}, + rsm = RsmOut}}; + Error -> + Error end. get_items(Host, Node) -> Action = fun (#pubsub_node{type = Type, id = Nidx}) -> - node_call(Host, Type, get_items, [Nidx, service_jid(Host), none]) + node_call(Host, Type, get_items, [Nidx, service_jid(Host), undefined]) end, case transaction(Host, Node, Action, sync_dirty) of {result, {_, {Items, _}}} -> Items; @@ -2391,7 +2151,7 @@ get_item(Host, Node, ItemId) -> end. get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) -> - case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, none) of + case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, undefined) of {result, {Items, _RSM}} -> {result, Items}; Error -> Error end. @@ -2407,7 +2167,7 @@ get_last_item(Host, Type, Nidx, LJID) -> LastItem -> LastItem end. get_last_item(Host, Type, Nidx, LJID, mnesia) -> - case node_action(Host, Type, get_items, [Nidx, LJID, none]) of + case node_action(Host, Type, get_items, [Nidx, LJID, undefined]) of {result, {[LastItem|_], _}} -> LastItem; _ -> undefined end; @@ -2422,7 +2182,7 @@ get_last_item(_Host, _Type, _Nidx, _LJID, _) -> get_last_items(Host, Type, Nidx, LJID, Number) -> get_last_items(Host, Type, Nidx, LJID, Number, gen_mod:db_type(serverhost(Host), ?MODULE)). get_last_items(Host, Type, Nidx, LJID, Number, mnesia) -> - case node_action(Host, Type, get_items, [Nidx, LJID, none]) of + case node_action(Host, Type, get_items, [Nidx, LJID, undefined]) of {result, {Items, _}} -> lists:sublist(Items, Number); _ -> [] end; @@ -2473,227 +2233,200 @@ dispatch_items(From, To, _Node, Stanza) -> ejabberd_router:route(service_jid(From), jid:make(To), Stanza). %% @doc

    Return the list of affiliations as an XMPP response.

    --spec get_affiliations(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - JID :: jid(), Plugins :: [binary()]) -> - {result, [xmlel(),...]} | {error, xmlel()}. - +-spec get_affiliations(host(), binary(), jid(), [binary()]) -> + {result, pubsub()} | {error, stanza_error()}. get_affiliations(Host, Node, JID, Plugins) when is_list(Plugins) -> - Result = lists:foldl( fun (Type, {Status, Acc}) -> - Features = plugin_features(Host, Type), - RetrieveFeature = lists:member(<<"retrieve-affiliations">>, Features), - if not RetrieveFeature -> - {{error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"retrieve-affiliations">>)}, - Acc}; - true -> - {result, Affs} = node_action(Host, Type, - get_entity_affiliations, - [Host, JID]), - {Status, [Affs | Acc]} - end - end, - {ok, []}, Plugins), + Result = + lists:foldl( + fun(Type, {Status, Acc}) -> + Features = plugin_features(Host, Type), + RetrieveFeature = lists:member(<<"retrieve-affiliations">>, Features), + if not RetrieveFeature -> + {{error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('retrieve-affiliations'))}, + Acc}; + true -> + {result, Affs} = node_action(Host, Type, + get_entity_affiliations, + [Host, JID]), + {Status, [Affs | Acc]} + end + end, + {ok, []}, Plugins), case Result of {ok, Affs} -> - Entities = lists:flatmap(fun - ({_, none}) -> - []; - ({#pubsub_node{nodeid = {_, NodeId}}, Aff}) -> - if (Node == <<>>) or (Node == NodeId) -> - [#xmlel{name = <<"affiliation">>, - attrs = [{<<"affiliation">>, affiliation_to_string(Aff)} - | nodeAttr(NodeId)]}]; - true -> - [] - end; - (_) -> - [] - end, - lists:usort(lists:flatten(Affs))), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [#xmlel{name = <<"affiliations">>, attrs = [], - children = Entities}]}]}; + Entities = lists:flatmap( + fun({_, none}) -> + []; + ({#pubsub_node{nodeid = {_, NodeId}}, Aff}) -> + if (Node == <<>>) or (Node == NodeId) -> + [#ps_affiliation{node = NodeId, + type = Aff}]; + true -> + [] + end; + (_) -> + [] + end, lists:usort(lists:flatten(Affs))), + {result, #pubsub{affiliations = {<<>>, Entities}}}; {Error, _} -> Error end. --spec get_affiliations(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - JID :: jid()) -> - {result, [xmlel(),...]} | {error, xmlel()}. - +-spec get_affiliations(host(), binary(), jid()) -> + {result, pubsub_owner()} | {error, stanza_error()}. get_affiliations(Host, Node, JID) -> - Action = fun (#pubsub_node{type = Type, id = Nidx}) -> - Features = plugin_features(Host, Type), - RetrieveFeature = lists:member(<<"modify-affiliations">>, Features), - {result, Affiliation} = node_call(Host, Type, get_affiliation, [Nidx, JID]), - if not RetrieveFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"modify-affiliations">>)}; - Affiliation /= owner -> - {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}; - true -> - node_call(Host, Type, get_node_affiliations, [Nidx]) - end - end, + Action = + fun(#pubsub_node{type = Type, id = Nidx}) -> + Features = plugin_features(Host, Type), + RetrieveFeature = lists:member(<<"modify-affiliations">>, Features), + {result, Affiliation} = node_call(Host, Type, get_affiliation, [Nidx, JID]), + if not RetrieveFeature -> + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('modify-affiliations'))}; + Affiliation /= owner -> + {error, xmpp:err_forbidden(<<"Owner privileges required">>, ?MYLANG)}; + true -> + node_call(Host, Type, get_node_affiliations, [Nidx]) + end + end, case transaction(Host, Node, Action, sync_dirty) of {result, {_, []}} -> - {error, ?ERR_ITEM_NOT_FOUND}; + {error, xmpp:err_item_not_found()}; {result, {_, Affs}} -> - Entities = lists:flatmap(fun - ({_, none}) -> - []; - ({AJID, Aff}) -> - [#xmlel{name = <<"affiliation">>, - attrs = [{<<"jid">>, jid:to_string(AJID)}, - {<<"affiliation">>, affiliation_to_string(Aff)}]}] - end, - Affs), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = [#xmlel{name = <<"affiliations">>, - attrs = nodeAttr(Node), children = Entities}]}]}; + Entities = lists:flatmap( + fun({_, none}) -> + []; + ({AJID, Aff}) -> + [#ps_affiliation{jid = AJID, type = Aff}] + end, Affs), + {result, #pubsub_owner{affiliations = {Node, Entities}}}; Error -> Error end. --spec set_affiliations(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), - From :: jid(), EntitiesEls :: [xmlel()]) -> - {result, []} | {error, xmlel()}. - -set_affiliations(Host, Node, From, EntitiesEls) -> +-spec set_affiliations(host(), binary(), jid(), [ps_affiliation()]) -> + {result, undefined} | {error, stanza_error()}. +set_affiliations(Host, Node, From, Affs) -> Owner = jid:tolower(jid:remove_resource(From)), - Entities = lists:foldl(fun - (_, error) -> - error; - (El, Acc) -> - case El of - #xmlel{name = <<"affiliation">>, attrs = Attrs} -> - JID = jid:from_string(fxml:get_attr_s(<<"jid">>, Attrs)), - Affiliation = string_to_affiliation(fxml:get_attr_s(<<"affiliation">>, Attrs)), - if (JID == error) or (Affiliation == false) -> error; - true -> [{jid:tolower(JID), Affiliation} | Acc] - end - end - end, - [], EntitiesEls), - case Entities of - error -> - {error, ?ERR_BAD_REQUEST}; - _ -> - Action = fun (#pubsub_node{type = Type, id = Nidx, owners = O} = N) -> - Owners = node_owners_call(Host, Type, Nidx, O), - case lists:member(Owner, Owners) of - true -> - OwnerJID = jid:make(Owner), - FilteredEntities = case Owners of - [Owner] -> [E || E <- Entities, element(1, E) =/= OwnerJID]; - _ -> Entities + Action = + fun(#pubsub_node{type = Type, id = Nidx, owners = O} = N) -> + Owners = node_owners_call(Host, Type, Nidx, O), + case lists:member(Owner, Owners) of + true -> + OwnerJID = jid:make(Owner), + FilteredAffs = + case Owners of + [Owner] -> + [Aff || Aff <- Affs, + Aff#ps_affiliation.jid /= OwnerJID]; + _ -> + Affs end, - lists:foreach(fun ({JID, Affiliation}) -> - node_call(Host, Type, set_affiliation, [Nidx, JID, Affiliation]), - case Affiliation of - owner -> - NewOwner = jid:tolower(jid:remove_resource(JID)), - NewOwners = [NewOwner | Owners], - tree_call(Host, + lists:foreach( + fun(#ps_affiliation{jid = JID, type = Affiliation}) -> + node_call(Host, Type, set_affiliation, [Nidx, JID, Affiliation]), + case Affiliation of + owner -> + NewOwner = jid:tolower(jid:remove_resource(JID)), + NewOwners = [NewOwner | Owners], + tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]); - none -> - OldOwner = jid:tolower(jid:remove_resource(JID)), - case lists:member(OldOwner, Owners) of - true -> - NewOwners = Owners -- [OldOwner], - tree_call(Host, + none -> + OldOwner = jid:tolower(jid:remove_resource(JID)), + case lists:member(OldOwner, Owners) of + true -> + NewOwners = Owners -- [OldOwner], + tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]); - _ -> - ok - end; - _ -> - ok - end - end, - FilteredEntities), - {result, []}; - _ -> - {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end + _ -> + ok + end; + _ -> + ok + end + end, FilteredAffs), + {result, undefined}; + _ -> + {error, xmpp:err_forbidden( + <<"Owner privileges required">>, ?MYLANG)} + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_, Result}} -> {result, Result}; + Other -> Other end. +-spec get_options(binary(), binary(), jid(), binary(), binary()) -> + {result, xdata()} | {error, stanza_error()}. get_options(Host, Node, JID, SubId, Lang) -> Action = fun (#pubsub_node{type = Type, id = Nidx}) -> case lists:member(<<"subscription-options">>, plugin_features(Host, Type)) of true -> get_options_helper(Host, JID, Lang, Node, Nidx, SubId, Type); false -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscription-options">>)} + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('subscription-options'))} end end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_Node, XForm}} -> {result, [XForm]}; + {result, {_Node, XForm}} -> {result, XForm}; Error -> Error end. +-spec get_options_helper(binary(), jid(), binary(), binary(), _, binary(), + binary()) -> {result, pubsub()} | {error, stanza_error()}. get_options_helper(Host, JID, Lang, Node, Nidx, SubId, Type) -> - Subscriber = string_to_ljid(JID), + Subscriber = jid:tolower(JID), {result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]), SubIds = [Id || {Sub, Id} <- Subs, Sub == subscribed], case {SubId, SubIds} of {_, []} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"not-subscribed">>)}; + {error, extended_error(xmpp:err_not_acceptable(), + err_not_subscribed())}; {<<>>, [SID]} -> read_sub(Host, Node, Nidx, Subscriber, SID, Lang); {<<>>, _} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)}; + {error, extended_error(xmpp:err_not_acceptable(), + err_subid_required())}; {_, _} -> ValidSubId = lists:member(SubId, SubIds), if ValidSubId -> read_sub(Host, Node, Nidx, Subscriber, SubId, Lang); true -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)} + {error, extended_error(xmpp:err_not_acceptable(), + err_invalid_subid())} end end. +-spec read_sub(binary(), binary(), nodeIdx(), ljid(), binary(), binary()) -> {result, pubsub()}. read_sub(Host, Node, Nidx, Subscriber, SubId, Lang) -> SubModule = subscription_plugin(Host), - Children = case SubModule:get_subscription(Subscriber, Nidx, SubId) of - {error, notfound} -> - []; - {result, #pubsub_subscription{options = Options}} -> - {result, XdataEl} = SubModule:get_options_xform(Lang, Options), - [XdataEl] - end, - OptionsEl = #xmlel{name = <<"options">>, - attrs = [{<<"jid">>, jid:to_string(Subscriber)}, - {<<"subid">>, SubId} - | nodeAttr(Node)], - children = Children}, - PubsubEl = #xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [OptionsEl]}, - {result, PubsubEl}. + XData = case SubModule:get_subscription(Subscriber, Nidx, SubId) of + {error, notfound} -> + undefined; + {result, #pubsub_subscription{options = Options}} -> + {result, X} = SubModule:get_options_xform(Lang, Options), + X + end, + {result, #pubsub{options = #ps_options{jid = jid:make(Subscriber), + subid = SubId, + node = Node, + xdata = XData}}}. +-spec set_options(binary(), binary(), jid(), binary(), + [{binary(), [binary()]}]) -> + {result, undefined} | {error, stanza_error()}. set_options(Host, Node, JID, SubId, Configuration) -> Action = fun (#pubsub_node{type = Type, id = Nidx}) -> case lists:member(<<"subscription-options">>, plugin_features(Host, Type)) of true -> set_options_helper(Host, Configuration, JID, Nidx, SubId, Type); false -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"subscription-options">>)} + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('subscription-options'))} end end, case transaction(Host, Node, Action, sync_dirty) of @@ -2701,56 +2434,53 @@ set_options(Host, Node, JID, SubId, Configuration) -> Error -> Error end. +-spec set_options_helper(binary(), [{binary(), [binary()]}], jid(), + nodeIdx(), binary(), binary()) -> + {result, undefined} | {error, stanza_error()}. set_options_helper(Host, Configuration, JID, Nidx, SubId, Type) -> SubModule = subscription_plugin(Host), SubOpts = case SubModule:parse_options_xform(Configuration) of {result, GoodSubOpts} -> GoodSubOpts; _ -> invalid end, - Subscriber = string_to_ljid(JID), + Subscriber = jid:tolower(JID), {result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]), SubIds = [Id || {Sub, Id} <- Subs, Sub == subscribed], case {SubId, SubIds} of {_, []} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"not-subscribed">>)}; + {error, extended_error(xmpp:err_not_acceptable(), err_not_subscribed())}; {<<>>, [SID]} -> write_sub(Host, Nidx, Subscriber, SID, SubOpts); {<<>>, _} -> - {error, - extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)}; + {error, extended_error(xmpp:err_not_acceptable(), err_subid_required())}; {_, _} -> write_sub(Host, Nidx, Subscriber, SubId, SubOpts) end. +-spec write_sub(binary(), nodeIdx(), ljid(), binary(), _) -> {result, undefined} | + {error, stanza_error()}. write_sub(_Host, _Nidx, _Subscriber, _SubId, invalid) -> - {error, - extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)}; + {error, extended_error(xmpp:err_bad_request(), err_invalid_options())}; write_sub(_Host, _Nidx, _Subscriber, _SubId, []) -> - {result, []}; + {result, undefined}; write_sub(Host, Nidx, Subscriber, SubId, Options) -> SubModule = subscription_plugin(Host), case SubModule:set_subscription(Subscriber, Nidx, SubId, Options) of - {result, _} -> {result, []}; - {error, _} -> {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)} + {result, _} -> {result, undefined}; + {error, _} -> {error, extended_error(xmpp:err_not_acceptable(), + err_invalid_subid())} end. -%% @spec (Host, Node, JID, Plugins) -> {error, Reason} | {result, Response} -%% Host = host() -%% Node = pubsubNode() -%% JID = jid() -%% Plugins = [Plugin::string()] -%% Reason = stanzaError() -%% Response = [pubsubIQResponse()] %% @doc

    Return the list of subscriptions as an XMPP response.

    +-spec get_subscriptions(host(), binary(), jid(), [binary()]) -> + {result, pubsub()} | {error, stanza_error()}. get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> Result = lists:foldl(fun (Type, {Status, Acc}) -> Features = plugin_features(Host, Type), RetrieveFeature = lists:member(<<"retrieve-subscriptions">>, Features), if not RetrieveFeature -> - {{error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"retrieve-subscriptions">>)}, + {{error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('retrieve-subscriptions'))}, Acc}; true -> Subscriber = jid:remove_resource(JID), @@ -2769,14 +2499,9 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> ({#pubsub_node{nodeid = {_, SubsNode}}, Sub}) -> case Node of <<>> -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"subscription">>, subscription_to_string(Sub)} - | nodeAttr(SubsNode)]}]; + [#ps_subscription{node = SubsNode, type = Sub}]; SubsNode -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"subscription">>, subscription_to_string(Sub)}]}]; + [#ps_subscription{type = Sub}]; _ -> [] end; @@ -2785,88 +2510,65 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> ({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubId, SubJID}) -> case Node of <<>> -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, jid:to_string(SubJID)}, - {<<"subid">>, SubId}, - {<<"subscription">>, subscription_to_string(Sub)} - | nodeAttr(SubsNode)]}]; + [#ps_subscription{jid = SubJID, + subid = SubId, + type = Sub, + node = SubsNode}]; SubsNode -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, jid:to_string(SubJID)}, - {<<"subid">>, SubId}, - {<<"subscription">>, subscription_to_string(Sub)}]}]; + [#ps_subscription{jid = SubJID, + subid = SubId, + type = Sub}]; _ -> [] end; ({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubJID}) -> case Node of <<>> -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, jid:to_string(SubJID)}, - {<<"subscription">>, subscription_to_string(Sub)} - | nodeAttr(SubsNode)]}]; + [#ps_subscription{jid = SubJID, + type = Sub, + node = SubsNode}]; SubsNode -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, jid:to_string(SubJID)}, - {<<"subscription">>, subscription_to_string(Sub)}]}]; + [#ps_subscription{jid = SubJID, type = Sub}]; _ -> [] end end, lists:usort(lists:flatten(Subs))), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB}], - children = [#xmlel{name = <<"subscriptions">>, attrs = [], - children = Entities}]}]}; + {result, #pubsub{subscriptions = {<<>>, Entities}}}; {Error, _} -> Error end. +-spec get_subscriptions(host(), binary(), jid()) -> {result, pubsub_owner()} | + {error, stanza_error()}. get_subscriptions(Host, Node, JID) -> Action = fun (#pubsub_node{type = Type, id = Nidx}) -> Features = plugin_features(Host, Type), RetrieveFeature = lists:member(<<"manage-subscriptions">>, Features), {result, Affiliation} = node_call(Host, Type, get_affiliation, [Nidx, JID]), if not RetrieveFeature -> - {error, - extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"manage-subscriptions">>)}; + {error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('manage-subscriptions'))}; Affiliation /= owner -> - {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)}; + {error, xmpp:err_forbidden(<<"Owner privileges required">>, ?MYLANG)}; true -> node_call(Host, Type, get_node_subscriptions, [Nidx]) end end, case transaction(Host, Node, Action, sync_dirty) of {result, {_, Subs}} -> - Entities = lists:flatmap(fun - ({_, none}) -> - []; - ({_, pending, _}) -> - []; - ({AJID, Sub}) -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, jid:to_string(AJID)}, - {<<"subscription">>, subscription_to_string(Sub)}]}]; + Entities = + lists:flatmap( + fun({_, none}) -> + []; + ({_, pending, _}) -> + []; + ({AJID, Sub}) -> + [#ps_subscription{jid = AJID, type = Sub}]; ({AJID, Sub, SubId}) -> - [#xmlel{name = <<"subscription">>, - attrs = - [{<<"jid">>, jid:to_string(AJID)}, - {<<"subscription">>, subscription_to_string(Sub)}, - {<<"subid">>, SubId}]}] - end, - Subs), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = [#xmlel{name = <<"subscriptions">>, - attrs = nodeAttr(Node), - children = Entities}]}]}; + [#ps_subscription{jid = AJID, type = Sub, subid = SubId}] + end, Subs), + {result, #pubsub_owner{subscriptions = {Node, Entities}}}; Error -> Error end. @@ -2894,74 +2596,57 @@ get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) -> get_subscriptions_for_send_last(_Host, _PType, _, _JID, _LJID, _BJID) -> []. -set_subscriptions(Host, Node, From, EntitiesEls) -> +-spec set_subscriptions(host(), binary(), jid(), [ps_subscription()]) -> + {result, undefined} | {error, stanza_error()}. +set_subscriptions(Host, Node, From, Entities) -> Owner = jid:tolower(jid:remove_resource(From)), - Entities = lists:foldl(fun - (_, error) -> - error; - (El, Acc) -> - case El of - #xmlel{name = <<"subscription">>, attrs = Attrs} -> - JID = jid:from_string(fxml:get_attr_s(<<"jid">>, Attrs)), - Sub = string_to_subscription(fxml:get_attr_s(<<"subscription">>, Attrs)), - SubId = fxml:get_attr_s(<<"subid">>, Attrs), - if (JID == error) or (Sub == false) -> error; - true -> [{jid:tolower(JID), Sub, SubId} | Acc] - end - end - end, - [], EntitiesEls), - case Entities of - error -> - {error, ?ERR_BAD_REQUEST}; - _ -> - Notify = fun (JID, Sub, _SubId) -> - Stanza = event_stanza( - [#xmlel{name = <<"subscription">>, - attrs = [{<<"jid">>, jid:to_string(JID)}, - {<<"subscription">>, subscription_to_string(Sub)} - | nodeAttr(Node)]}]), - ejabberd_router:route(service_jid(Host), jid:make(JID), Stanza) - end, - Action = fun (#pubsub_node{type = Type, id = Nidx, owners = O}) -> - Owners = node_owners_call(Host, Type, Nidx, O), - case lists:member(Owner, Owners) of - true -> - Result = lists:foldl(fun ({JID, Sub, SubId}, Acc) -> - case - node_call(Host, Type, - set_subscriptions, - [Nidx, JID, Sub, SubId]) - of - {error, Err} -> - [{error, Err} | Acc]; - _ -> - Notify(JID, Sub, SubId), - Acc - end - end, - [], Entities), - case Result of - [] -> {result, []}; - [{error, E}|_] -> {error, E} - end; - _ -> - {error, ?ERRT_FORBIDDEN(?MYLANG, <<"You're not an owner">>)} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end + Notify = fun(#ps_subscription{jid = JID, type = Sub}) -> + Stanza = #message{ + sub_els = [#ps_event{ + subscription = #ps_subscription{ + jid = JID, + type = Sub, + node = Node}}]}, + ejabberd_router:route(service_jid(Host), JID, Stanza) + end, + Action = + fun(#pubsub_node{type = Type, id = Nidx, owners = O}) -> + Owners = node_owners_call(Host, Type, Nidx, O), + case lists:member(Owner, Owners) of + true -> + Result = + lists:foldl( + fun(_, {error, _} = Err) -> + Err; + (#ps_subscription{jid = JID, type = Sub, + subid = SubId} = Entity, _) -> + case node_call(Host, Type, + set_subscriptions, + [Nidx, JID, Sub, SubId]) of + {error, _} = Err -> + Err; + _ -> + Notify(Entity) + end + end, ok, Entities), + case Result of + ok -> {result, undefined}; + {error, _} = Err -> Err + end; + _ -> + {error, xmpp:err_forbidden( + <<"Owner privileges required">>, ?MYLANG)} + + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_, Result}} -> {result, Result}; + Other -> Other end. --spec get_presence_and_roster_permissions(Host :: mod_pubsub:host(), - From :: ljid(), Owners :: [ljid(),...], - AccessModel :: mod_pubsub:accessModel(), - AllowedGroups :: [binary()]) -> - {PresenceSubscription::boolean(), - RosterGroup::boolean()}. - +-spec get_presence_and_roster_permissions( + host(), ljid(), [ljid()], accessModel(), + [binary()]) -> {boolean(), boolean()}. get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) -> if (AccessModel == presence) or (AccessModel == roster) -> case Host of @@ -2993,36 +2678,10 @@ get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, A get_roster_info(OwnerUser, OwnerServer, JID, AllowedGroups) -> get_roster_info(OwnerUser, OwnerServer, jid:tolower(JID), AllowedGroups). -string_to_affiliation(<<"owner">>) -> owner; -string_to_affiliation(<<"publisher">>) -> publisher; -string_to_affiliation(<<"publish-only">>) -> publish_only; -string_to_affiliation(<<"member">>) -> member; -string_to_affiliation(<<"outcast">>) -> outcast; -string_to_affiliation(<<"none">>) -> none; -string_to_affiliation(_) -> false. - -string_to_subscription(<<"subscribed">>) -> subscribed; -string_to_subscription(<<"pending">>) -> pending; -string_to_subscription(<<"unconfigured">>) -> unconfigured; -string_to_subscription(<<"none">>) -> none; -string_to_subscription(_) -> false. - -affiliation_to_string(owner) -> <<"owner">>; -affiliation_to_string(publisher) -> <<"publisher">>; -affiliation_to_string(publish_only) -> <<"publish-only">>; -affiliation_to_string(member) -> <<"member">>; -affiliation_to_string(outcast) -> <<"outcast">>; -affiliation_to_string(_) -> <<"none">>. - -subscription_to_string(subscribed) -> <<"subscribed">>; -subscription_to_string(pending) -> <<"pending">>; -subscription_to_string(unconfigured) -> <<"unconfigured">>; -subscription_to_string(_) -> <<"none">>. - --spec service_jid(Host :: mod_pubsub:host()) -> jid(). +-spec service_jid(jid() | ljid() | binary()) -> jid(). service_jid(#jid{} = Jid) -> Jid; service_jid({U, S, R}) -> jid:make(U, S, R); -service_jid(Host) -> jid:make(<<>>, Host, <<>>). +service_jid(Host) -> jid:make(Host). %% @spec (LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean() %% LJID = jid() @@ -3053,7 +2712,7 @@ sub_option_can_deliver(_, _, {deliver, false}) -> false; sub_option_can_deliver(_, _, {expire, When}) -> p1_time_compat:timestamp() < When; sub_option_can_deliver(_, _, _) -> true. --spec presence_can_deliver(Entity :: ljid(), _ :: boolean()) -> boolean(). +-spec presence_can_deliver(ljid(), boolean()) -> boolean(). presence_can_deliver(_, false) -> true; presence_can_deliver({User, Server, Resource}, true) -> @@ -3074,10 +2733,7 @@ presence_can_deliver({User, Server, Resource}, true) -> false, Ss) end. --spec state_can_deliver(Entity::ljid(), - SubOptions :: mod_pubsub:subOptions() | []) -> - [ljid()]. - +-spec state_can_deliver(ljid(), subOptions() | []) -> [ljid()]. state_can_deliver({U, S, R}, []) -> [{U, S, R}]; state_can_deliver({U, S, R}, SubOptions) -> case lists:keysearch(show_values, 1, SubOptions) of @@ -3097,10 +2753,7 @@ state_can_deliver({U, S, R}, SubOptions) -> [], Resources) end. --spec get_resource_state(Entity :: ljid(), ShowValues :: [binary()], - JIDs :: [ljid()]) -> - [ljid()]. - +-spec get_resource_state(ljid(), [binary()], [ljid()]) -> [ljid()]. get_resource_state({U, S, R}, ShowValues, JIDs) -> case ejabberd_sm:get_session_pid(U, S, R) of none -> @@ -3119,8 +2772,7 @@ get_resource_state({U, S, R}, ShowValues, JIDs) -> end end. --spec payload_xmlelements(Payload :: mod_pubsub:payload()) -> - Count :: non_neg_integer(). +-spec payload_xmlelements([xmlel()]) -> non_neg_integer(). payload_xmlelements(Payload) -> payload_xmlelements(Payload, 0). @@ -3134,63 +2786,55 @@ items_event_stanza(Node, Options, Items) -> MoreEls = case Items of [LastItem] -> {ModifNow, ModifUSR} = LastItem#pubsub_item.modification, - DateTime = calendar:now_to_datetime(ModifNow), - {T_string, Tz_string} = jlib:timestamp_to_iso(DateTime, utc), - [#xmlel{name = <<"delay">>, attrs = [{<<"xmlns">>, ?NS_DELAY}, - {<<"from">>, jid:to_string(ModifUSR)}, - {<<"stamp">>, <>}], - children = [{xmlcdata, <<>>}]}]; + [#delay{stamp = ModifNow, from = jid:make(ModifUSR)}]; _ -> [] end, - BaseStanza = event_stanza_with_els([#xmlel{name = <<"items">>, - attrs = nodeAttr(Node), - children = itemsEls(Items)}], - MoreEls), + BaseStanza = #message{ + sub_els = [#ps_event{items = #ps_items{ + node = Node, + items = itemsEls(Items)}} + | MoreEls]}, NotificationType = get_option(Options, notification_type, headline), add_message_type(BaseStanza, NotificationType). -event_stanza(Els) -> - event_stanza_with_els(Els, []). -event_stanza_with_els(Els, MoreEls) -> - #xmlel{name = <<"message">>, attrs = [], - children = [#xmlel{name = <<"event">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}], - children = Els} - | MoreEls]}. - -event_stanza(Event, EvAttr) -> - event_stanza_with_els([#xmlel{name = Event, attrs = EvAttr}], []). - %%%%%% broadcast functions broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions, ItemId, From, Payload, Removed) -> case get_collection_subscriptions(Host, Node) of SubsByDepth when is_list(SubsByDepth) -> - Content = case get_option(NodeOptions, deliver_payloads) of - true -> Payload; - false -> [] - end, - Attrs = case get_option(NodeOptions, itemreply, none) of - owner -> itemAttr(ItemId); %% owner not supported - publisher -> itemAttr(ItemId, {<<"publisher">>, jid:to_string(From)}); - none -> itemAttr(ItemId) - end, - Stanza = event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"item">>, attrs = Attrs, - children = Content}]}]), + EventItem0 = case get_option(NodeOptions, deliver_payloads) of + true -> #ps_item{xml_els = Payload, id = ItemId}; + false -> #ps_item{id = ItemId} + end, + EventItem = case get_option(NodeOptions, itemreply, none) of + owner -> %% owner not supported + EventItem0; + publisher -> + EventItem0#ps_item{ + publisher = jid:to_string(From)}; + none -> + EventItem0 + end, + Stanza = #message{ + sub_els = + [#ps_event{items = + #ps_items{node = Node, + items = [EventItem]}}]}, broadcast_stanza(Host, From, Node, Nidx, Type, - NodeOptions, SubsByDepth, items, Stanza, true), + NodeOptions, SubsByDepth, items, Stanza, true), case Removed of [] -> ok; _ -> case get_option(NodeOptions, notify_retract) of true -> - RetractStanza = event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"retract">>, attrs = itemAttr(RId)} || RId <- Removed]}]), + RetractStanza = #message{ + sub_els = + [#ps_event{ + items = #ps_items{ + node = Node, + retract = Removed}}]}, broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, items, RetractStanza, true); @@ -3212,9 +2856,12 @@ broadcast_retract_items(Host, Node, Nidx, Type, NodeOptions, ItemIds, ForceNotif true -> case get_collection_subscriptions(Host, Node) of SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), - children = [#xmlel{name = <<"retract">>, attrs = itemAttr(ItemId)} || ItemId <- ItemIds]}]), + Stanza = #message{ + sub_els = + [#ps_event{ + items = #ps_items{ + node = Node, + retract = ItemIds}}]}, broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, items, Stanza, true), {result, true}; @@ -3230,8 +2877,7 @@ broadcast_purge_node(Host, Node, Nidx, Type, NodeOptions) -> true -> case get_collection_subscriptions(Host, Node) of SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [#xmlel{name = <<"purge">>, attrs = nodeAttr(Node)}]), + Stanza = #message{sub_els = [#ps_event{purge = Node}]}, broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, nodes, Stanza, false), {result, true}; @@ -3249,8 +2895,7 @@ broadcast_removed_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) -> [] -> {result, false}; _ -> - Stanza = event_stanza( - [#xmlel{name = <<"delete">>, attrs = nodeAttr(Node)}]), + Stanza = #message{sub_els = [#ps_event{delete = {Node, <<>>}}]}, broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, nodes, Stanza, false), {result, true} @@ -3262,7 +2907,7 @@ broadcast_removed_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) -> broadcast_created_node(_, _, _, _, _, []) -> {result, false}; broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) -> - Stanza = event_stanza([#xmlel{name = <<"create">>, attrs = nodeAttr(Node)}]), + Stanza = #message{sub_els = [#ps_event{create = Node}]}, broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, nodes, Stanza, true), {result, true}. @@ -3273,13 +2918,15 @@ broadcast_config_notification(Host, Node, Nidx, Type, NodeOptions, Lang) -> SubsByDepth when is_list(SubsByDepth) -> Content = case get_option(NodeOptions, deliver_payloads) of true -> - [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = get_configure_xfields(Type, NodeOptions, Lang, [])}]; + #xdata{type = result, + fields = get_configure_xfields( + Type, NodeOptions, Lang, [])}; false -> - [] + undefined end, - Stanza = event_stanza( - [#xmlel{name = <<"configuration">>, attrs = nodeAttr(Node), children = Content}]), + Stanza = #message{ + sub_els = [#ps_event{ + configuration = {Node, Content}}]}, broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, nodes, Stanza, false), {result, true}; @@ -3371,8 +3018,9 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO ejabberd_c2s:broadcast(C2SPid, {pep_message, <<((Node))/binary, "+notify">>}, _Sender = jid:make(LUser, LServer, <<"">>), - _StanzaToSend = add_extended_headers(Stanza, - _ReplyTo = extended_headers([jid:to_string(Publisher)]))); + _StanzaToSend = add_extended_headers( + Stanza, + _ReplyTo = extended_headers([Publisher]))); _ -> ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza]) end; @@ -3438,9 +3086,11 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth), JIDSubs. +-spec user_resources(binary(), binary()) -> [binary()]. user_resources(User, Server) -> ejabberd_sm:get_user_resources(User, Server). +-spec user_resource(binary(), binary(), binary()) -> binary(). user_resource(User, Server, <<>>) -> case user_resources(User, Server) of [R | _] -> R; @@ -3450,26 +3100,19 @@ user_resource(_, _, Resource) -> Resource. %%%%%%% Configuration handling - +-spec get_configure(host(), binary(), binary(), jid(), + binary()) -> {error, stanza_error()} | {result, pubsub_owner()}. get_configure(Host, ServerHost, Node, From, Lang) -> Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx}) -> case node_call(Host, Type, get_affiliation, [Nidx, From]) of {result, owner} -> Groups = ejabberd_hooks:run_fold(roster_groups, ServerHost, [], [ServerHost]), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"configure">>, - attrs = nodeAttr(Node), - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - get_configure_xfields(Type, Options, Lang, Groups)}]}]}]}; + Fs = get_configure_xfields(Type, Options, Lang, Groups), + {result, #pubsub_owner{ + configure = + {Node, #xdata{type = form, fields = Fs}}}}; _ -> - {error, ?ERRT_FORBIDDEN(Lang, <<"You're not an owner">>)} + {error, xmpp:err_forbidden(<<"Owner privileges required">>, Lang)} end end, case transaction(Host, Node, Action, sync_dirty) of @@ -3477,20 +3120,14 @@ get_configure(Host, ServerHost, Node, From, Lang) -> Other -> Other end. +-spec get_default(host(), binary(), jid(), binary()) -> {result, pubsub_owner()}. get_default(Host, Node, _From, Lang) -> Type = select_type(Host, Host, Node), Options = node_options(Host, Type), - {result, - [#xmlel{name = <<"pubsub">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], - children = - [#xmlel{name = <<"default">>, attrs = [], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = get_configure_xfields(Type, Options, Lang, [])}]}]}]}. + Fs = get_configure_xfields(Type, Options, Lang, []), + {result, #pubsub_owner{default = {<<>>, #xdata{type = form, fields = Fs}}}}. +-spec match_option(#pubsub_node{} | [{atom(), any()}], atom(), any()) -> boolean(). match_option(Node, Var, Val) when is_record(Node, pubsub_node) -> match_option(Node#pubsub_node.options, Var, Val); match_option(Options, Var, Val) when is_list(Options) -> @@ -3498,21 +3135,26 @@ match_option(Options, Var, Val) when is_list(Options) -> match_option(_, _, _) -> false. +-spec get_option([{atom(), any()}], atom()) -> any(). get_option([], _) -> false; get_option(Options, Var) -> get_option(Options, Var, false). +-spec get_option([{atom(), any()}], atom(), any()) -> any(). get_option(Options, Var, Def) -> case lists:keysearch(Var, 1, Options) of {value, {_Val, Ret}} -> Ret; _ -> Def end. +-spec node_options(host(), binary()) -> [{atom(), any()}]. node_options(Host, Type) -> case config(Host, default_node_config) of undefined -> node_plugin_options(Host, Type); [] -> node_plugin_options(Host, Type); Config -> Config end. + +-spec node_plugin_options(host(), binary()) -> [{atom(), any()}]. node_plugin_options(Host, Type) -> Module = plugin(Host, Type), case catch Module:options() of @@ -3522,12 +3164,15 @@ node_plugin_options(Host, Type) -> Result -> Result end. + +-spec filter_node_options([{atom(), any()}], [{atom(), any()}]) -> [{atom(), any()}]. filter_node_options(Options, BaseOptions) -> lists:foldl(fun({Key, Val}, Acc) -> DefaultValue = proplists:get_value(Key, Options, Val), [{Key, DefaultValue}|Acc] end, [], BaseOptions). +-spec node_owners_action(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()]. node_owners_action(Host, Type, Nidx, []) -> case gen_mod:db_type(serverhost(Host), ?MODULE) of sql -> @@ -3541,6 +3186,7 @@ node_owners_action(Host, Type, Nidx, []) -> node_owners_action(_Host, _Type, _Nidx, Owners) -> Owners. +-spec node_owners_call(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()]. node_owners_call(Host, Type, Nidx, []) -> case gen_mod:db_type(serverhost(Host), ?MODULE) of sql -> @@ -3565,6 +3211,7 @@ node_owners_call(_Host, _Type, _Nidx, Owners) -> %% @todo In practice, the current data structure means that we cannot manage %% millions of items on a given node. This should be addressed in a new %% version. +-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer(). max_items(Host, Options) -> case get_option(Options, persist_items) of true -> @@ -3585,83 +3232,17 @@ max_items(Host, Options) -> end end. --define(BOOL_CONFIG_FIELD(Label, Var), - ?BOOLXFIELD(Label, - <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, - (get_option(Options, Var)))). - --define(STRING_CONFIG_FIELD(Label, Var), - ?STRINGXFIELD(Label, - <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, - (get_option(Options, Var, <<>>)))). - --define(INTEGER_CONFIG_FIELD(Label, Var), - ?STRINGXFIELD(Label, - <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, - (jlib:integer_to_binary(get_option(Options, Var))))). - --define(JLIST_CONFIG_FIELD(Label, Var, Opts), - ?LISTXFIELD(Label, - <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, - (jid:to_string(get_option(Options, Var))), - [jid:to_string(O) || O <- Opts])). - --define(ALIST_CONFIG_FIELD(Label, Var, Opts), - ?LISTXFIELD(Label, - <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, - (atom_to_binary(get_option(Options, Var), latin1)), - [atom_to_binary(O, latin1) || O <- Opts])). - --define(LISTM_CONFIG_FIELD(Label, Var, Opts), - ?LISTMXFIELD(Label, - <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, - (get_option(Options, Var)), Opts)). - --define(NLIST_CONFIG_FIELD(Label, Var), - ?STRINGMXFIELD(Label, - <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, - get_option(Options, Var, []))). - +-spec get_configure_xfields(_, pubsub_node_config:result(), + binary(), [binary()]) -> [xdata_field()]. get_configure_xfields(_Type, Options, Lang, Groups) -> - [?XFIELD(<<"hidden">>, <<>>, <<"FORM_TYPE">>, (?NS_PUBSUB_NODE_CONFIG)), - ?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>, - deliver_payloads), - ?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>, - deliver_notifications), - ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuration changes">>, - notify_config), - ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is deleted">>, - notify_delete), - ?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed from the node">>, - notify_retract), - ?BOOL_CONFIG_FIELD(<<"Persist items to storage">>, - persist_items), - ?STRING_CONFIG_FIELD(<<"A friendly name for the node">>, - title), - ?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>, - max_items), - ?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>, - subscribe), - ?ALIST_CONFIG_FIELD(<<"Specify the access model">>, - access_model, [open, authorize, presence, roster, whitelist]), - ?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>, - roster_groups_allowed, Groups), - ?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>, - publish_model, [publishers, subscribers, open]), - ?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher goes offline">>, - purge_offline), - ?ALIST_CONFIG_FIELD(<<"Specify the event message type">>, - notification_type, [headline, normal]), - ?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>, - max_payload_size), - ?ALIST_CONFIG_FIELD(<<"When to send the last published item">>, - send_last_published_item, [never, on_sub, on_sub_and_presence]), - ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available users">>, - presence_based_delivery), - ?NLIST_CONFIG_FIELD(<<"The collections with which a node is affiliated">>, - collection), - ?ALIST_CONFIG_FIELD(<<"Whether owners or publisher should receive replies to items">>, - itemreply, [none, owner, publisher])]. + pubsub_node_config:encode( + lists:map( + fun({roster_groups_allowed, Value}) -> + {roster_groups_allowed, Value, Groups}; + (Opt) -> + Opt + end, Options), + fun(Txt) -> translate:translate(Lang, Txt) end). %%

    There are several reasons why the node configuration request might fail:

    %%
      @@ -3671,180 +3252,134 @@ get_configure_xfields(_Type, Options, Lang, Groups) -> %%
    • The node has no configuration options.
    • %%
    • The specified node does not exist.
    • %%
    -set_configure(Host, Node, From, Els, Lang) -> - case fxml:remove_cdata(Els) of - [#xmlel{name = <<"x">>} = XEl] -> - case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), fxml:get_tag_attr_s(<<"type">>, XEl)} of - {?NS_XDATA, <<"cancel">>} -> - {result, []}; - {?NS_XDATA, <<"submit">>} -> - Action = fun (#pubsub_node{options = Options, type = Type, id = Nidx} = N) -> - case node_call(Host, Type, get_affiliation, [Nidx, From]) of - {result, owner} -> - case jlib:parse_xdata_submit(XEl) of - invalid -> - Txt = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - XData -> - OldOpts = case Options of - [] -> node_options(Host, Type); - _ -> Options - end, - case set_xoption(Host, XData, OldOpts) of - NewOpts when is_list(NewOpts) -> - case tree_call(Host, - set_node, - [N#pubsub_node{options = NewOpts}]) - of - {result, Nidx} -> {result, ok}; - ok -> {result, ok}; - Err -> Err - end; - Error -> - Error - end - end; - _ -> - Txt = <<"You're not an owner">>, - {error, ?ERRT_FORBIDDEN(Lang, Txt)} - end - end, - case transaction(Host, Node, Action, transaction) of - {result, {TNode, ok}} -> - Nidx = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang), - {result, []}; - Other -> - Other - end; - _ -> - Txt = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end; - _ -> - Txt = <<"No data form found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} +-spec set_configure(host(), binary(), jid(), [{binary(), [binary()]}], + binary()) -> {result, undefined} | {error, stanza_error()}. +set_configure(_Host, <<>>, _From, _Config, _Lang) -> + {error, extended_error(xmpp:err_bad_request(), err_nodeid_required())}; +set_configure(Host, Node, From, Config, Lang) -> + Action = + fun(#pubsub_node{options = Options, type = Type, id = Nidx} = N) -> + case node_call(Host, Type, get_affiliation, [Nidx, From]) of + {result, owner} -> + OldOpts = case Options of + [] -> node_options(Host, Type); + _ -> Options + end, + NewOpts = merge_config(Config, OldOpts), + case tree_call(Host, + set_node, + [N#pubsub_node{options = NewOpts}]) of + {result, Nidx} -> {result, ok}; + ok -> {result, ok}; + Err -> Err + end; + _ -> + {error, xmpp:err_forbidden( + <<"Owner privileges required">>, Lang)} + end + end, + case transaction(Host, Node, Action, transaction) of + {result, {TNode, ok}} -> + Nidx = TNode#pubsub_node.id, + Type = TNode#pubsub_node.type, + Options = TNode#pubsub_node.options, + broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang), + {result, undefined}; + Other -> + Other end. -add_opt(Key, Value, Opts) -> - [{Key, Value} | lists:keydelete(Key, 1, Opts)]. +-spec merge_config([proplists:property()], [proplists:property()]) -> [proplists:property()]. +merge_config(Config1, Config2) -> + lists:foldl( + fun({Opt, Val}, Acc) -> + lists:keystore(Opt, 1, Acc, {Opt, Val}) + end, Config2, Config1). --define(SET_BOOL_XOPT(Opt, Val), - BoolVal = case Val of - <<"0">> -> false; - <<"1">> -> true; - <<"false">> -> false; - <<"true">> -> true; - _ -> error - end, - case BoolVal of - error -> - Txt = <<"Value of '~s' should be boolean">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}; - _ -> set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts)) - end). +-spec decode_node_config(undefined | xdata(), binary(), binary()) -> + pubsub_node_config:result() | + {error, stanza_error()}. +decode_node_config(undefined, _, _) -> + []; +decode_node_config(#xdata{fields = Fs}, Host, Lang) -> + try + Config = pubsub_node_config:decode(Fs), + Max = get_max_items_node(Host), + case {check_opt_range(max_items, Config, Max), + check_opt_range(max_payload_size, Config, ?MAX_PAYLOAD_SIZE)} of + {true, true} -> + Config; + {true, false} -> + erlang:error( + {pubsub_node_config, + {bad_var_value, <<"pubsub#max_payload_size">>, + ?NS_PUBSUB_NODE_CONFIG}}); + {false, _} -> + erlang:error( + {pubsub_node_config, + {bad_var_value, <<"pubsub#max_items">>, + ?NS_PUBSUB_NODE_CONFIG}}) + end + catch _:{pubsub_node_config, Why} -> + Txt = pubsub_node_config:format_error(Why), + {error, xmpp:err_resource_constraint(Txt, Lang)} + end. --define(SET_STRING_XOPT(Opt, Val), - set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). +-spec decode_subscribe_options(undefined | xdata(), binary()) -> + pubsub_subscribe_options:result() | + {error, stanza_error()}. +decode_subscribe_options(undefined, _) -> + []; +decode_subscribe_options(#xdata{fields = Fs}, Lang) -> + try pubsub_subscribe_options:decode(Fs) + catch _:{pubsub_subscribe_options, Why} -> + Txt = pubsub_subscribe_options:format_error(Why), + {error, xmpp:err_resource_constraint(Txt, Lang)} + end. --define(SET_INTEGER_XOPT(Opt, Val, Min, Max), - case catch jlib:binary_to_integer(Val) of - IVal when is_integer(IVal), IVal >= Min -> - if (Max =:= undefined) orelse (IVal =< Max) -> - set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts)); - true -> - Txt = <<"Incorrect value of '~s'">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)} - end; - _ -> - Txt = <<"Value of '~s' should be integer">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)} - end). +-spec decode_publish_options(undefined | xdata(), binary()) -> + pubsub_publish_options:result() | + {error, stanza_error()}. +decode_publish_options(undefined, _) -> + []; +decode_publish_options(#xdata{fields = Fs}, Lang) -> + try pubsub_publish_options:decode(Fs) + catch _:{pubsub_publish_options, Why} -> + Txt = pubsub_publish_options:format_error(Why), + {error, xmpp:err_resource_constraint(Txt, Lang)} + end. --define(SET_ALIST_XOPT(Opt, Val, Vals), - case lists:member(Val, [atom_to_binary(V, latin1) || V <- Vals]) of - true -> - set_xoption(Host, Opts, add_opt(Opt, jlib:binary_to_atom(Val), NewOpts)); - false -> - Txt = <<"Incorrect value of '~s'">>, - ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)} - end). +-spec decode_get_pending(xdata(), binary()) -> + pubsub_get_pending:result() | + {error, stanza_error()}. +decode_get_pending(#xdata{fields = Fs}, Lang) -> + try pubsub_get_pending:decode(Fs) + catch _:{pubsub_get_pending, Why} -> + Txt = pubsub_get_pending:format_error(Why), + {error, xmpp:err_resource_constraint(Txt, Lang)} + end; +decode_get_pending(undefined, Lang) -> + {error, xmpp:err_bad_request(<<"No data form found">>, Lang)}. --define(SET_LIST_XOPT(Opt, Val), - set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). - -set_xoption(_Host, [], NewOpts) -> NewOpts; -set_xoption(Host, [{<<"FORM_TYPE">>, _} | Opts], NewOpts) -> - set_xoption(Host, Opts, NewOpts); -set_xoption(Host, [{<<"pubsub#roster_groups_allowed">>, Value} | Opts], NewOpts) -> - ?SET_LIST_XOPT(roster_groups_allowed, Value); -set_xoption(Host, [{<<"pubsub#deliver_payloads">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(deliver_payloads, Val); -set_xoption(Host, [{<<"pubsub#deliver_notifications">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(deliver_notifications, Val); -set_xoption(Host, [{<<"pubsub#notify_config">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(notify_config, Val); -set_xoption(Host, [{<<"pubsub#notify_delete">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(notify_delete, Val); -set_xoption(Host, [{<<"pubsub#notify_retract">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(notify_retract, Val); -set_xoption(Host, [{<<"pubsub#persist_items">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(persist_items, Val); -set_xoption(Host, [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) -> - MaxItems = get_max_items_node(Host), - ?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems); -set_xoption(Host, [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(subscribe, Val); -set_xoption(Host, [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(access_model, Val, [open, authorize, presence, roster, whitelist]); -set_xoption(Host, [{<<"pubsub#publish_model">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]); -set_xoption(Host, [{<<"pubsub#notification_type">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(notification_type, Val, [headline, normal]); -set_xoption(Host, [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(node_type, Val, [leaf, collection]); -set_xoption(Host, [{<<"pubsub#max_payload_size">>, [Val]} | Opts], NewOpts) -> - ?SET_INTEGER_XOPT(max_payload_size, Val, 0, (?MAX_PAYLOAD_SIZE)); -set_xoption(Host, [{<<"pubsub#send_last_published_item">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]); -set_xoption(Host, [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(presence_based_delivery, Val); -set_xoption(Host, [{<<"pubsub#purge_offline">>, [Val]} | Opts], NewOpts) -> - ?SET_BOOL_XOPT(purge_offline, Val); -set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts], NewOpts) -> - ?SET_STRING_XOPT(title, Value); -set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts], NewOpts) -> - ?SET_STRING_XOPT(type, Value); -set_xoption(Host, [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) -> - ?SET_STRING_XOPT(body_xslt, Value); -set_xoption(Host, [{<<"pubsub#collection">>, Value} | Opts], NewOpts) -> - % NewValue = [string_to_node(V) || V <- Value], - ?SET_LIST_XOPT(collection, Value); -set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], NewOpts) -> - % NewValue = string_to_node(Value), - ?SET_LIST_XOPT(node, Value); -set_xoption(Host, [{<<"pubsub#itemreply">>, [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(itemreply, Val, [none, owner, publisher]); -set_xoption(Host, [_ | Opts], NewOpts) -> - set_xoption(Host, Opts, NewOpts). +-spec check_opt_range(atom(), [proplists:property()], non_neg_integer()) -> boolean(). +check_opt_range(Opt, Opts, Max) -> + Val = proplists:get_value(Opt, Opts, Max), + Val =< Max. +-spec get_max_items_node(host()) -> undefined | non_neg_integer(). get_max_items_node(Host) -> config(Host, max_items_node, undefined). +-spec get_max_subscriptions_node(host()) -> undefined | non_neg_integer(). get_max_subscriptions_node(Host) -> config(Host, max_subscriptions_node, undefined). %%%% last item cache handling - +-spec is_last_item_cache_enabled(host()) -> boolean(). is_last_item_cache_enabled(Host) -> config(Host, last_item_cache, false). +-spec set_cached_item(host(), nodeIdx(), binary(), binary(), [xmlel()]) -> ok. set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) -> set_cached_item(ServerHost, Nidx, ItemId, Publisher, Payload); set_cached_item(Host, Nidx, ItemId, Publisher, Payload) -> @@ -3855,6 +3390,7 @@ set_cached_item(Host, Nidx, ItemId, Publisher, Payload) -> _ -> ok end. +-spec unset_cached_item(host(), nodeIdx()) -> ok. unset_cached_item({_, ServerHost, _}, Nidx) -> unset_cached_item(ServerHost, Nidx); unset_cached_item(Host, Nidx) -> @@ -3863,9 +3399,7 @@ unset_cached_item(Host, Nidx) -> _ -> ok end. --spec get_cached_item(Host :: mod_pubsub:host(), Nidx :: mod_pubsub:nodeIdx()) -> - undefined | mod_pubsub:pubsubItem(). - +-spec get_cached_item(host(), nodeIdx()) -> undefined | pubsubItem(). get_cached_item({_, ServerHost, _}, Nidx) -> get_cached_item(ServerHost, Nidx); get_cached_item(Host, Nidx) -> @@ -3886,21 +3420,24 @@ get_cached_item(Host, Nidx) -> end. %%%% plugin handling - +-spec host(binary()) -> binary(). host(ServerHost) -> config(ServerHost, host, <<"pubsub.", ServerHost/binary>>). +-spec serverhost(host()) -> binary(). serverhost({_U, ServerHost, _R})-> serverhost(ServerHost); serverhost(Host) -> ejabberd_router:host_of_route(Host). +-spec tree(host()) -> atom(). tree(Host) -> case config(Host, nodetree) of undefined -> tree(Host, ?STDTREE); Tree -> Tree end. +-spec tree(host(), binary() | atom()) -> atom(). tree(_Host, <<"virtual">>) -> nodetree_virtual; % special case, virtual does not use any backend tree(Host, Name) -> @@ -3910,6 +3447,7 @@ tree(Host, Name) -> _ -> Name end. +-spec plugin(host(), binary() | atom()) -> atom(). plugin(Host, Name) -> case gen_mod:db_type(serverhost(Host), ?MODULE) of mnesia -> jlib:binary_to_atom(<<"node_", Name/binary>>); @@ -3917,6 +3455,7 @@ plugin(Host, Name) -> _ -> Name end. +-spec plugins(host()) -> [binary()]. plugins(Host) -> case config(Host, plugins) of undefined -> [?STDNODE]; @@ -3924,6 +3463,9 @@ plugins(Host) -> Plugins -> Plugins end. +-spec subscription_plugin(host()) -> pubsub_subscription | + pubsub_subscription_sql | + none. subscription_plugin(Host) -> case gen_mod:db_type(serverhost(Host), ?MODULE) of mnesia -> pubsub_subscription; @@ -3931,9 +3473,11 @@ subscription_plugin(Host) -> _ -> none end. +-spec config(binary(), any()) -> any(). config(ServerHost, Key) -> config(ServerHost, Key, undefined). +-spec config(host(), any(), any()) -> any(). config({_User, Host, _Resource}, Key, Default) -> config(Host, Key, Default); config(ServerHost, Key, Default) -> @@ -3942,6 +3486,7 @@ config(ServerHost, Key, Default) -> _ -> Default end. +-spec select_type(binary(), host(), binary(), binary()) -> binary(). select_type(ServerHost, Host, Node, Type) -> SelectedType = case Host of {_User, _Server, _Resource} -> @@ -3958,36 +3503,39 @@ select_type(ServerHost, Host, Node, Type) -> false -> hd(ConfiguredTypes) end. +-spec select_type(binary(), host(), binary()) -> binary(). select_type(ServerHost, Host, Node) -> select_type(ServerHost, Host, Node, hd(plugins(Host))). +-spec feature(binary()) -> binary(). feature(<<"rsm">>) -> ?NS_RSM; feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>. +-spec features() -> [binary()]. features() -> [% see plugin "access-authorize", % OPTIONAL - <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree - <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep - <<"access-whitelist">>, % OPTIONAL - <<"collections">>, % RECOMMENDED - <<"config-node">>, % RECOMMENDED - <<"create-and-configure">>, % RECOMMENDED - <<"item-ids">>, % RECOMMENDED - <<"last-published">>, % RECOMMENDED - <<"member-affiliation">>, % RECOMMENDED - <<"presence-notifications">>, % OPTIONAL - <<"presence-subscribe">>, % RECOMMENDED - <<"publisher-affiliation">>, % RECOMMENDED - <<"publish-only-affiliation">>, % OPTIONAL - <<"retrieve-default">>, - <<"shim">>]. % RECOMMENDED + <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree + <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep + <<"access-whitelist">>, % OPTIONAL + <<"collections">>, % RECOMMENDED + <<"config-node">>, % RECOMMENDED + <<"create-and-configure">>, % RECOMMENDED + <<"item-ids">>, % RECOMMENDED + <<"last-published">>, % RECOMMENDED + <<"member-affiliation">>, % RECOMMENDED + <<"presence-notifications">>, % OPTIONAL + <<"presence-subscribe">>, % RECOMMENDED + <<"publisher-affiliation">>, % RECOMMENDED + <<"publish-only-affiliation">>, % OPTIONAL + <<"retrieve-default">>, + <<"shim">>]. % RECOMMENDED % see plugin "retrieve-items", % RECOMMENDED % see plugin "retrieve-subscriptions", % RECOMMENDED % see plugin "subscribe", % REQUIRED % see plugin "subscription-options", % OPTIONAL % see plugin "subscription-notifications" % OPTIONAL - +-spec plugin_features(binary(), binary()) -> [binary()]. plugin_features(Host, Type) -> Module = plugin(Host, Type), case catch Module:features() of @@ -3995,6 +3543,7 @@ plugin_features(Host, Type) -> Result -> Result end. +-spec features(binary(), binary()) -> [binary()]. features(Host, <<>>) -> lists:usort(lists:foldl(fun (Plugin, Acc) -> Acc ++ plugin_features(Host, Plugin) @@ -4030,11 +3579,13 @@ tree_action(Host, Function, Args) -> Result; {aborted, Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR} + ErrTxt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(ErrTxt, ?MYLANG)} end; Other -> ?ERROR_MSG("unsupported backend: ~p~n", [Other]), - {error, ?ERR_INTERNAL_SERVER_ERROR} + ErrTxt = <<"Database failure">>, + {error, xmpp:err_internal_server_error(ErrTxt, ?MYLANG)} end. %% @doc

    node plugin call.

    @@ -4090,7 +3641,7 @@ transaction(Host, Fun, Trans) -> transaction_retry(Host, ServerHost, Fun, Trans, DBType, Retry). transaction_retry(_Host, _ServerHost, _Fun, _Trans, _DBType, 0) -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; + {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)}; transaction_retry(Host, ServerHost, Fun, Trans, DBType, Count) -> Res = case DBType of mnesia -> @@ -4115,75 +3666,126 @@ transaction_retry(Host, ServerHost, Fun, Trans, DBType, Count) -> {error, Error}; {aborted, Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; + {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)}; {'EXIT', {timeout, _} = Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [Reason]), transaction_retry(Host, ServerHost, Fun, Trans, DBType, Count - 1); {'EXIT', Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; + {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)}; Other -> ?ERROR_MSG("transaction return internal error: ~p~n", [Other]), - {error, ?ERR_INTERNAL_SERVER_ERROR} + {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)} end. %%%% helpers %% Add pubsub-specific error element -extended_error(Error, Ext) -> - extended_error(Error, Ext, [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}]). +-spec extended_error(stanza_error(), ps_error()) -> stanza_error(). +extended_error(StanzaErr, PubSubErr) -> + StanzaErr#stanza_error{sub_els = [PubSubErr]}. -extended_error(Error, unsupported, Feature) -> - %% Give a uniq identifier - extended_error(Error, <<"unsupported">>, - [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}, - {<<"feature">>, Feature}]); -extended_error(#xmlel{name = Error, attrs = Attrs, children = SubEls}, Ext, ExtAttrs) -> - #xmlel{name = Error, attrs = Attrs, - children = lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs} | SubEls])}. +-spec err_closed_node() -> ps_error(). +err_closed_node() -> + #ps_error{type = 'closed-node'}. -is_item_not_found({error, ErrEl}) -> - case fxml:get_subtag_with_xmlns( - ErrEl, <<"item-not-found">>, ?NS_STANZAS) of - #xmlel{} -> true; - _ -> false - end. +-spec err_configuration_required() -> ps_error(). +err_configuration_required() -> + #ps_error{type = 'configuration-required'}. -string_to_ljid(JID) -> - case jid:from_string(JID) of - error -> - {<<>>, <<>>, <<>>}; - J -> - case jid:tolower(J) of - error -> {<<>>, <<>>, <<>>}; - J1 -> J1 - end - end. +-spec err_invalid_jid() -> ps_error(). +err_invalid_jid() -> + #ps_error{type = 'invalid-jid'}. + +-spec err_invalid_options() -> ps_error(). +err_invalid_options() -> + #ps_error{type = 'invalid-options'}. + +-spec err_invalid_payload() -> ps_error(). +err_invalid_payload() -> + #ps_error{type = 'invalid-payload'}. + +-spec err_invalid_subid() -> ps_error(). +err_invalid_subid() -> + #ps_error{type = 'invalid-subid'}. + +-spec err_item_forbidden() -> ps_error(). +err_item_forbidden() -> + #ps_error{type = 'item-forbidden'}. + +-spec err_item_required() -> ps_error(). +err_item_required() -> + #ps_error{type = 'item-required'}. + +-spec err_jid_required() -> ps_error(). +err_jid_required() -> + #ps_error{type = 'jid-required'}. + +-spec err_max_items_exceeded() -> ps_error(). +err_max_items_exceeded() -> + #ps_error{type = 'max-items-exceeded'}. + +-spec err_max_nodes_exceeded() -> ps_error(). +err_max_nodes_exceeded() -> + #ps_error{type = 'max-nodes-exceeded'}. + +-spec err_nodeid_required() -> ps_error(). +err_nodeid_required() -> + #ps_error{type = 'nodeid-required'}. + +-spec err_not_in_roster_group() -> ps_error(). +err_not_in_roster_group() -> + #ps_error{type = 'not-in-roster-group'}. + +-spec err_not_subscribed() -> ps_error(). +err_not_subscribed() -> + #ps_error{type = 'not-subscribed'}. + +-spec err_payload_too_big() -> ps_error(). +err_payload_too_big() -> + #ps_error{type = 'payload-too-big'}. + +-spec err_payload_required() -> ps_error(). +err_payload_required() -> + #ps_error{type = 'payload-required'}. + +-spec err_pending_subscription() -> ps_error(). +err_pending_subscription() -> + #ps_error{type = 'pending-subscription'}. + +-spec err_presence_subscription_required() -> ps_error(). +err_presence_subscription_required() -> + #ps_error{type = 'presence-subscription-required'}. + +-spec err_subid_required() -> ps_error(). +err_subid_required() -> + #ps_error{type = 'subid-required'}. + +-spec err_too_many_subscriptions() -> ps_error(). +err_too_many_subscriptions() -> + #ps_error{type = 'too-many-subscriptions'}. + +-spec err_unsupported(ps_feature()) -> ps_error(). +err_unsupported(Feature) -> + #ps_error{type = 'unsupported', feature = Feature}. + +-spec err_unsupported_access_model() -> ps_error(). +err_unsupported_access_model() -> + #ps_error{type = 'unsupported-access-model'}. -spec uniqid() -> mod_pubsub:itemId(). uniqid() -> {T1, T2, T3} = p1_time_compat:timestamp(), - iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). - -nodeAttr(Node) -> [{<<"node">>, Node}]. - -itemAttr([]) -> []; -itemAttr(ItemId) -> [{<<"id">>, ItemId}]. -itemAttr(ItemId, From) -> [{<<"id">>, ItemId}, From]. + (str:format("~.16B~.16B~.16B", [T1, T2, T3])). +-spec itemsEls([#pubsub_item{}]) -> [ps_item()]. itemsEls(Items) -> - [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload} - || #pubsub_item{itemid = {ItemId, _}, payload = Payload} <- Items]. + [#ps_item{id = ItemId, xml_els = Payload} + || #pubsub_item{itemid = {ItemId, _}, payload = Payload} <- Items]. --spec add_message_type(Message :: xmlel(), Type :: atom()) -> xmlel(). - -add_message_type(Message, normal) -> Message; -add_message_type(#xmlel{name = <<"message">>, attrs = Attrs, children = Els}, Type) -> - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, jlib:atom_to_binary(Type)} | Attrs], - children = Els}; -add_message_type(XmlEl, _Type) -> - XmlEl. +-spec add_message_type(message(), message_type()) -> message(). +add_message_type(#message{} = Message, Type) -> + Message#message{type = Type}. %% Place of changed at the bottom of the stanza %% cf. http://xmpp.org/extensions/xep-0060.html#publisher-publish-success-subid @@ -4191,40 +3793,34 @@ add_message_type(XmlEl, _Type) -> %% "[SHIM Headers] SHOULD be included after the event notification information %% (i.e., as the last child of the stanza)". -add_shim_headers(Stanza, HeaderEls) -> - add_headers(Stanza, <<"headers">>, ?NS_SHIM, HeaderEls). +-spec add_shim_headers(stanza(), [{binary(), binary()}]) -> stanza(). +add_shim_headers(Stanza, Headers) -> + xmpp:set_subtag(Stanza, #shim{headers = Headers}). -add_extended_headers(Stanza, HeaderEls) -> - add_headers(Stanza, <<"addresses">>, ?NS_ADDRESS, HeaderEls). - -add_headers(#xmlel{name = Name, attrs = Attrs, children = Els}, HeaderName, HeaderNS, HeaderEls) -> - HeaderEl = #xmlel{name = HeaderName, - attrs = [{<<"xmlns">>, HeaderNS}], - children = HeaderEls}, - #xmlel{name = Name, attrs = Attrs, - children = lists:append(Els, [HeaderEl])}. +-spec add_extended_headers(stanza(), [address()]) -> stanza(). +add_extended_headers(Stanza, Addrs) -> + xmpp:set_subtag(Stanza, #addresses{list = Addrs}). +-spec subid_shim([binary()]) -> [{binary(), binary()}]. subid_shim(SubIds) -> - [#xmlel{name = <<"header">>, - attrs = [{<<"name">>, <<"SubId">>}], - children = [{xmlcdata, SubId}]} - || SubId <- SubIds]. + [{<<"SubId">>, SubId} || SubId <- SubIds]. %% The argument is a list of Jids because this function could be used %% with the 'pubsub#replyto' (type=jid-multi) node configuration. +-spec extended_headers([jid()]) -> [address()]. extended_headers(Jids) -> - [#xmlel{name = <<"address">>, - attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}]} - || Jid <- Jids]. + [#address{type = replyto, jid = Jid} || Jid <- Jids]. +-spec on_user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok. on_user_offline(_, JID, _) -> {User, Server, Resource} = jid:tolower(JID), case user_resources(User, Server) of [] -> purge_offline({User, Server, Resource}); - _ -> true + _ -> ok end. +-spec purge_offline(ljid()) -> ok. purge_offline(LJID) -> Host = host(element(2, LJID)), Plugins = plugins(Host), @@ -4232,8 +3828,8 @@ purge_offline(LJID) -> Features = plugin_features(Host, Type), case lists:member(<<"retrieve-affiliations">>, plugin_features(Host, Type)) of false -> - {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, <<"retrieve-affiliations">>)}, + {{error, extended_error(xmpp:err_feature_not_implemented(), + err_unsupported('retrieve-affiliations'))}, Acc}; true -> Items = lists:member(<<"retract-items">>, Features) @@ -4267,6 +3863,7 @@ purge_offline(LJID) -> ?DEBUG("on_user_offline ~p", [Error]) end. +-spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}. purge_offline(Host, LJID, Node) -> Nidx = Node#pubsub_node.id, Type = Node#pubsub_node.type, @@ -4301,8 +3898,7 @@ purge_offline(Host, LJID, Node) -> Error end. -mod_opt_type(access_createnode) -> - fun (A) when is_atom(A) -> A end; +mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1; mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(host) -> fun iolist_to_binary/1; mod_opt_type(ignore_pep_from_offline) -> diff --git a/src/mod_register.erl b/src/mod_register.erl index 45cd78fef..b96ebecbd 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -35,14 +35,13 @@ -export([start/2, stop/1, stream_feature_register/2, unauthenticated_iq_register/4, try_register/5, - process_iq/3, send_registration_notifications/3, + process_iq/1, send_registration_notifications/3, transform_options/1, transform_module_options/1, mod_opt_type/1, opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, @@ -55,7 +54,7 @@ start(Host, Opts) -> stream_feature_register, 50), ejabberd_hooks:add(c2s_unauthenticated_iq, Host, ?MODULE, unauthenticated_iq_register, 50), - mnesia:create_table(mod_register_ip, + ejabberd_mnesia:create(?MODULE, mod_register_ip, [{ram_copies, [node()]}, {local_content, true}, {attributes, [key, value]}]), mnesia:add_table_copy(mod_register_ip, node(), @@ -75,333 +74,228 @@ stop(Host) -> depends(_Host, _Opts) -> []. +-spec stream_feature_register([xmpp_element()], binary()) -> [xmpp_element()]. stream_feature_register(Acc, Host) -> AF = gen_mod:get_module_opt(Host, ?MODULE, access_from, fun(A) -> A end, all), - case (AF /= none) and lists:keymember(<<"mechanisms">>, 2, Acc) of + case (AF /= none) and lists:keymember(sasl_mechanisms, 1, Acc) of true -> - [#xmlel{name = <<"register">>, - attrs = [{<<"xmlns">>, ?NS_FEATURE_IQREGISTER}], - children = []} - | Acc]; + [#feature_register{}|Acc]; false -> Acc end. +-spec unauthenticated_iq_register(empty | iq(), binary(), iq(), + {inet:ip_address(), non_neg_integer()}) -> + empty | iq(). unauthenticated_iq_register(_Acc, Server, - #iq{xmlns = ?NS_REGISTER} = IQ, IP) -> + #iq{sub_els = [#register{}]} = IQ, IP) -> Address = case IP of {A, _Port} -> A; _ -> undefined end, - ResIQ = process_iq(jid:make(<<"">>, <<"">>, - <<"">>), - jid:make(<<"">>, Server, <<"">>), IQ, Address), - Res1 = jlib:replace_from_to(jid:make(<<"">>, - Server, <<"">>), - jid:make(<<"">>, <<"">>, <<"">>), - jlib:iq_to_xml(ResIQ)), - jlib:remove_attr(<<"to">>, Res1); + ResIQ = process_iq(xmpp:set_from_to(IQ, jid:make(<<>>), jid:make(Server)), + Address), + xmpp:set_from_to(ResIQ, jid:make(Server), undefined); unauthenticated_iq_register(Acc, _Server, _IQ, _IP) -> Acc. -process_iq(From, To, IQ) -> - process_iq(From, To, IQ, jid:tolower(From)). +process_iq(#iq{from = From} = IQ) -> + process_iq(IQ, jid:tolower(From)). -process_iq(From, To, - #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = - IQ, - Source) -> - IsCaptchaEnabled = case - gen_mod:get_module_opt(To#jid.lserver, ?MODULE, - captcha_protected, - fun(B) when is_boolean(B) -> B end, - false) - of - true -> true; - _ -> false - end, - case Type of - set -> - UTag = fxml:get_subtag(SubEl, <<"username">>), - PTag = fxml:get_subtag(SubEl, <<"password">>), - RTag = fxml:get_subtag(SubEl, <<"remove">>), - Server = To#jid.lserver, - Access = gen_mod:get_module_opt(Server, ?MODULE, access, - fun(A) -> A end, - all), - AllowRemove = allow == - acl:match_rule(Server, Access, From), - if (UTag /= false) and (RTag /= false) and - AllowRemove -> - User = fxml:get_tag_cdata(UTag), - case From of - #jid{user = User, lserver = Server} -> - ejabberd_auth:remove_user(User, Server), - IQ#iq{type = result, sub_el = []}; - _ -> - if PTag /= false -> - Password = fxml:get_tag_cdata(PTag), - case ejabberd_auth:remove_user(User, Server, - Password) - of - ok -> IQ#iq{type = result, sub_el = []}; - %% TODO FIXME: This piece of - %% code does not work since - %% the code have been changed - %% to allow several auth - %% modules. lists:foreach can - %% only return ok: - not_allowed -> - Txt = <<"Removal is not allowed">>, - IQ#iq{type = error, - sub_el = [SubEl, - ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - not_exists -> - Txt = <<"No such user">>, - IQ#iq{type = error, - sub_el = - [SubEl, - ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]}; - Err -> - ?ERROR_MSG("failed to remove user ~s@~s: ~p", - [User, Server, Err]), - IQ#iq{type = error, - sub_el = - [SubEl, - ?ERR_INTERNAL_SERVER_ERROR]} - end; - true -> - Txt = <<"No password in this query">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]} - end - end; - (UTag == false) and (RTag /= false) and AllowRemove -> - case From of - #jid{user = User, lserver = Server, - resource = Resource} -> - ResIQ = #iq{type = result, xmlns = ?NS_REGISTER, - id = ID, sub_el = []}, - ejabberd_router:route(jid:make(User, Server, - Resource), - jid:make(User, Server, - Resource), - jlib:iq_to_xml(ResIQ)), - ejabberd_auth:remove_user(User, Server), - ignore; - _ -> - Txt = <<"The query is only allowed from local users">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]} - end; - (UTag /= false) and (PTag /= false) -> - User = fxml:get_tag_cdata(UTag), - Password = fxml:get_tag_cdata(PTag), - try_register_or_set_password(User, Server, Password, - From, IQ, SubEl, Source, Lang, - not IsCaptchaEnabled); - IsCaptchaEnabled -> - case ejabberd_captcha:process_reply(SubEl) of - ok -> - case process_xdata_submit(SubEl) of - {ok, User, Password} -> - try_register_or_set_password(User, Server, - Password, From, IQ, - SubEl, Source, Lang, - true); - _ -> - Txt = <<"Incorrect data form">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]} - end; - {error, malformed} -> - Txt = <<"Incorrect CAPTCHA submit">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}; - _ -> - ErrText = <<"The CAPTCHA verification has failed">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, ErrText)]} - end; - true -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} - end; - get -> - {IsRegistered, UsernameSubels, QuerySubels} = case From - of - #jid{user = User, - lserver = - Server} -> - case - ejabberd_auth:is_user_exists(User, - Server) - of - true -> - {true, - [{xmlcdata, - User}], - [#xmlel{name - = - <<"registered">>, - attrs - = - [], - children - = - []}]}; - false -> - {false, - [{xmlcdata, - User}], - []} - end; - _ -> {false, [], []} - end, - if IsCaptchaEnabled and not IsRegistered -> - TopInstrEl = #xmlel{name = <<"instructions">>, - attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need a client that supports x:data " - "and CAPTCHA to register">>)}]}, - InstrEl = #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Choose a username and password to register " - "with this server">>)}]}, - UField = #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-single">>}, - {<<"label">>, - translate:translate(Lang, <<"User">>)}, - {<<"var">>, <<"username">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}, - PField = #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"text-private">>}, - {<<"label">>, - translate:translate(Lang, - <<"Password">>)}, - {<<"var">>, <<"password">>}], - children = - [#xmlel{name = <<"required">>, attrs = [], - children = []}]}, - case ejabberd_captcha:create_captcha_x(ID, To, Lang, - Source, - [InstrEl, UField, - PField]) - of - {ok, CaptchaEls} -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_REGISTER}], - children = - [TopInstrEl | CaptchaEls]}]}; - {error, limit} -> - ErrText = <<"Too many CAPTCHA requests">>, - IQ#iq{type = error, - sub_el = - [SubEl, - ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)]}; - _Err -> - ErrText = <<"Unable to generate a CAPTCHA">>, - IQ#iq{type = error, - sub_el = - [SubEl, - ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText)]} - end; - true -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_REGISTER}], - children = - [#xmlel{name = <<"instructions">>, - attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Choose a username and password to register " - "with this server">>)}]}, - #xmlel{name = <<"username">>, - attrs = [], - children = UsernameSubels}, - #xmlel{name = <<"password">>, - attrs = [], children = []} - | QuerySubels]}]} - end +process_iq(#iq{from = From, to = To} = IQ, Source) -> + IsCaptchaEnabled = + case gen_mod:get_module_opt(To#jid.lserver, ?MODULE, + captcha_protected, + fun(B) when is_boolean(B) -> B end, + false) of + true -> true; + false -> false + end, + Server = To#jid.lserver, + Access = gen_mod:get_module_opt(Server, ?MODULE, access, + fun(A) -> A end, all), + AllowRemove = allow == acl:match_rule(Server, Access, From), + process_iq(IQ, Source, IsCaptchaEnabled, AllowRemove). + +process_iq(#iq{type = set, lang = Lang, + sub_els = [#register{remove = true}]} = IQ, + _Source, _IsCaptchaEnabled, _AllowRemove = false) -> + Txt = <<"Denied by ACL">>, + xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)); +process_iq(#iq{type = set, lang = Lang, to = To, from = From, + sub_els = [#register{remove = true, + username = User, + password = Password}]} = IQ, + _Source, _IsCaptchaEnabled, _AllowRemove = true) -> + Server = To#jid.lserver, + if is_binary(User) -> + case From of + #jid{user = User, lserver = Server} -> + ejabberd_auth:remove_user(User, Server), + xmpp:make_iq_result(IQ); + _ -> + if is_binary(Password) -> + ejabberd_auth:remove_user(User, Server, Password), + xmpp:make_iq_result(IQ); + true -> + Txt = <<"No 'password' found in this query">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) + end + end; + true -> + case From of + #jid{luser = LUser, lserver = Server} -> + ResIQ = xmpp:make_iq_result(IQ), + ejabberd_router:route(From, From, ResIQ), + ejabberd_auth:remove_user(LUser, Server), + ignore; + _ -> + Txt = <<"The query is only allowed from local users">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)) + end + end; +process_iq(#iq{type = set, to = To, + sub_els = [#register{username = User, + password = Password}]} = IQ, + Source, IsCaptchaEnabled, _AllowRemove) when is_binary(User), + is_binary(Password) -> + Server = To#jid.lserver, + try_register_or_set_password( + User, Server, Password, IQ, Source, not IsCaptchaEnabled); +process_iq(#iq{type = set, to = To, + lang = Lang, sub_els = [#register{xdata = #xdata{} = X}]} = IQ, + Source, true, _AllowRemove) -> + Server = To#jid.lserver, + case ejabberd_captcha:process_reply(X) of + ok -> + case process_xdata_submit(X) of + {ok, User, Password} -> + try_register_or_set_password( + User, Server, Password, IQ, Source, true); + _ -> + Txt = <<"Incorrect data form">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) + end; + {error, malformed} -> + Txt = <<"Incorrect CAPTCHA submit">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); + _ -> + ErrText = <<"The CAPTCHA verification has failed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(ErrText, Lang)) + end; +process_iq(#iq{type = set} = IQ, _Source, _IsCaptchaEnabled, _AllowRemove) -> + xmpp:make_error(IQ, xmpp:err_bad_request()); +process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ, + Source, IsCaptchaEnabled, _AllowRemove) -> + Server = To#jid.lserver, + {IsRegistered, Username} = + case From of + #jid{user = User, lserver = Server} -> + case ejabberd_auth:is_user_exists(User, Server) of + true -> + {true, User}; + false -> + {false, User} + end; + _ -> + {false, <<"">>} + end, + Instr = translate:translate( + Lang, <<"Choose a username and password to register " + "with this server">>), + if IsCaptchaEnabled and not IsRegistered -> + TopInstr = translate:translate( + Lang, <<"You need a client that supports x:data " + "and CAPTCHA to register">>), + UField = #xdata_field{type = 'text-single', + label = translate:translate(Lang, <<"User">>), + var = <<"username">>, + required = true}, + PField = #xdata_field{type = 'text-private', + label = translate:translate(Lang, <<"Password">>), + var = <<"password">>, + required = true}, + X = #xdata{type = form, instructions = [Instr], + fields = [UField, PField]}, + case ejabberd_captcha:create_captcha_x(ID, To, Lang, Source, X) of + {ok, CaptchaEls} -> + xmpp:make_iq_result( + IQ, #register{instructions = TopInstr, + sub_els = CaptchaEls}); + {error, limit} -> + ErrText = <<"Too many CAPTCHA requests">>, + xmpp:make_error( + IQ, xmpp:err_resource_constraint(ErrText, Lang)); + _Err -> + ErrText = <<"Unable to generate a CAPTCHA">>, + xmpp:make_error( + IQ, xmpp:err_internal_server_error(ErrText, Lang)) + end; + true -> + xmpp:make_iq_result( + IQ, + #register{instructions = Instr, + username = Username, + password = <<"">>, + registered = IsRegistered}) end. try_register_or_set_password(User, Server, Password, - From, IQ, SubEl, Source, Lang, CaptchaSucceed) -> + #iq{from = From, lang = Lang} = IQ, + Source, CaptchaSucceed) -> case From of - #jid{user = User, lserver = Server} -> - try_set_password(User, Server, Password, IQ, SubEl, - Lang); - _ when CaptchaSucceed -> - case check_from(From, Server) of - allow -> - case try_register(User, Server, Password, Source, Lang) - of - ok -> IQ#iq{type = result, sub_el = []}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end; - deny -> - Txt = <<"Denied by ACL">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]} - end; - _ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]} + #jid{user = User, lserver = Server} -> + try_set_password(User, Server, Password, IQ); + _ when CaptchaSucceed -> + case check_from(From, Server) of + allow -> + case try_register(User, Server, Password, Source, Lang) of + ok -> + xmpp:make_iq_result(IQ); + {error, Error} -> + xmpp:make_error(IQ, Error) + end; + deny -> + Txt = <<"Denied by ACL">>, + xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)) + end; + _ -> + xmpp:make_error(IQ, xmpp:err_not_allowed()) end. %% @doc Try to change password and return IQ response -try_set_password(User, Server, Password, IQ, SubEl, - Lang) -> +try_set_password(User, Server, Password, #iq{lang = Lang} = IQ) -> case is_strong_password(Server, Password) of true -> - case ejabberd_auth:set_password(User, Server, Password) - of - ok -> IQ#iq{type = result, sub_el = []}; + case ejabberd_auth:set_password(User, Server, Password) of + ok -> + xmpp:make_iq_result(IQ); {error, empty_password} -> Txt = <<"Empty password">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}; + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); {error, not_allowed} -> Txt = <<"Changing password is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); {error, invalid_jid} -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_JID_MALFORMED]}; + xmpp:make_error(IQ, xmpp:err_jid_malformed()); Err -> ?ERROR_MSG("failed to register user ~s@~s: ~p", [User, Server, Err]), - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} + xmpp:make_error(IQ, xmpp:err_internal_server_error()) end; error_preparing_password -> ErrText = <<"The password contains unacceptable characters">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]}; + xmpp:make_error(IQ, xmpp:err_not_acceptable(ErrText, Lang)); false -> ErrText = <<"The password is too weak">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]} + xmpp:make_error(IQ, xmpp:err_not_acceptable(ErrText, Lang)) end. try_register(User, Server, Password, SourceRaw, Lang) -> case jid:is_nodename(User) of - false -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Malformed username">>)}; + false -> {error, xmpp:err_bad_request(<<"Malformed username">>, Lang)}; _ -> JID = jid:make(User, Server, <<"">>), Access = gen_mod:get_module_opt(Server, ?MODULE, access, @@ -411,8 +305,8 @@ try_register(User, Server, Password, SourceRaw, Lang) -> case {acl:match_rule(Server, Access, JID), check_ip_access(SourceRaw, IPAccess)} of - {deny, _} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; - {_, deny} -> {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + {deny, _} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; + {_, deny} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; {allow, allow} -> Source = may_remove_resource(SourceRaw), case check_timeout(Source) of @@ -432,35 +326,35 @@ try_register(User, Server, Password, SourceRaw, Lang) -> case Error of {atomic, exists} -> Txt = <<"User already exists">>, - {error, ?ERRT_CONFLICT(Lang, Txt)}; + {error, xmpp:err_conflict(Txt, Lang)}; {error, invalid_jid} -> - {error, ?ERR_JID_MALFORMED}; + {error, xmpp:err_jid_malformed()}; {error, not_allowed} -> - {error, ?ERR_NOT_ALLOWED}; + {error, xmpp:err_not_allowed()}; {error, too_many_users} -> Txt = <<"Too many users registered">>, - {error, ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt)}; + {error, xmpp:err_resource_constraint(Txt, Lang)}; {error, _} -> ?ERROR_MSG("failed to register user " "~s@~s: ~p", [User, Server, Error]), - {error, ?ERR_INTERNAL_SERVER_ERROR} + {error, xmpp:err_internal_server_error()} end end; error_preparing_password -> remove_timeout(Source), ErrText = <<"The password contains unacceptable characters">>, - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; + {error, xmpp:err_not_acceptable(ErrText, Lang)}; false -> remove_timeout(Source), ErrText = <<"The password is too weak">>, - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)} + {error, xmpp:err_not_acceptable(ErrText, Lang)} end; false -> ErrText = <<"Users are not allowed to register accounts " "so quickly">>, - {error, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)} + {error, xmpp:err_resource_constraint(ErrText, Lang)} end end end. @@ -479,20 +373,10 @@ send_welcome_message(JID) -> of {<<"">>, <<"">>} -> ok; {Subj, Body} -> - ejabberd_router:route(jid:make(<<"">>, Host, - <<"">>), - JID, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"normal">>}], - children = - [#xmlel{name = <<"subject">>, - attrs = [], - children = - [{xmlcdata, Subj}]}, - #xmlel{name = <<"body">>, - attrs = [], - children = - [{xmlcdata, Body}]}]}); + ejabberd_router:route( + jid:make(Host), JID, + #message{subject = xmpp:mk_text(Subj), + body = xmpp:mk_text(Body)}); _ -> ok end. @@ -507,7 +391,7 @@ send_registration_notifications(Mod, UJID, Source) -> [] -> ok; JIDs when is_list(JIDs) -> Body = - iolist_to_binary(io_lib:format("[~s] The account ~s was registered from " + (str:format("[~s] The account ~s was registered from " "IP address ~s on node ~w using ~p.", [get_time_string(), jid:to_string(UJID), @@ -516,13 +400,9 @@ send_registration_notifications(Mod, UJID, Source) -> lists:foreach( fun(JID) -> ejabberd_router:route( - jid:make(<<"">>, Host, <<"">>), - JID, - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"chat">>}], - children = [#xmlel{name = <<"body">>, - attrs = [], - children = [{xmlcdata,Body}]}]}) + jid:make(Host), JID, + #message{type = chat, + body = xmpp:mk_text(Body)}) end, JIDs) end. @@ -633,17 +513,11 @@ write_time({{Y, Mo, D}, {H, Mi, S}}) -> io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", [Y, Mo, D, H, Mi, S]). -process_xdata_submit(El) -> - case fxml:get_subtag(El, <<"x">>) of - false -> error; - Xdata -> - Fields = jlib:parse_xdata_submit(Xdata), - case catch {proplists:get_value(<<"username">>, Fields), - proplists:get_value(<<"password">>, Fields)} - of - {[User | _], [Pass | _]} -> {ok, User, Pass}; - _ -> error - end +process_xdata_submit(X) -> + case {xmpp_util:get_xdata_values(<<"username">>, X), + xmpp_util:get_xdata_values(<<"password">>, X)} of + {[User], [Pass]} -> {ok, User, Pass}; + _ -> error end. is_strong_password(Server, Password) -> @@ -738,14 +612,11 @@ check_ip_access(undefined, _IPAccess) -> check_ip_access(IPAddress, IPAccess) -> acl:match_rule(global, IPAccess, IPAddress). -mod_opt_type(access) -> - fun acl:access_rules_validator/1; -mod_opt_type(access_from) -> - fun (A) when is_atom(A) -> A end; +mod_opt_type(access) -> fun acl:access_rules_validator/1; +mod_opt_type(access_from) -> fun acl:access_rules_validator/1; mod_opt_type(captcha_protected) -> fun (B) when is_boolean(B) -> B end; -mod_opt_type(ip_access) -> - fun acl:access_rules_validator/1; +mod_opt_type(ip_access) -> fun acl:access_rules_validator/1; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(password_strength) -> fun (N) when is_number(N), N >= 0 -> N end; diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl index 76de1677f..20b370fb9 100644 --- a/src/mod_register_web.erl +++ b/src/mod_register_web.erl @@ -60,7 +60,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_http.hrl"). @@ -334,7 +334,7 @@ build_captcha_li_list2(Lang, IP) -> case ejabberd_captcha:create_captcha(SID, From, To, Lang, IP, Args) of - {ok, Id, _} -> + {ok, Id, _, _} -> {_, {CImg, CText, CId, CKey}} = ejabberd_captcha:build_captcha_html(Id, Lang), [?XE(<<"li">>, diff --git a/src/mod_roster.erl b/src/mod_roster.erl index a75041bc7..89578571c 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -41,20 +41,21 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_iq/3, export/1, - import/1, process_local_iq/3, get_user_roster/2, - import/3, get_subscription_lists/3, get_roster/2, +-export([start/2, stop/1, process_iq/1, export/1, + import_info/0, process_local_iq/1, get_user_roster/2, + import/5, get_subscription_lists/3, get_roster/2, + import_start/2, import_stop/2, get_in_pending_subscriptions/3, in_subscription/6, out_subscription/4, set_items/3, remove_user/2, - get_jid_info/4, item_to_xml/1, webadmin_page/3, + get_jid_info/4, encode_item/1, webadmin_page/3, webadmin_user/4, get_versioning_feature/2, roster_versioning_enabled/1, roster_version/2, - mod_opt_type/1, set_roster/1, depends/2]). + mod_opt_type/1, set_roster/1, del_roster/3, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_roster.hrl"). @@ -65,7 +66,7 @@ -export_type([subscription/0]). -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #roster{} | #roster_version{}) -> ok | pass. +-callback import(binary(), binary(), #roster{} | [binary()]) -> ok. -callback read_roster_version(binary(), binary()) -> binary() | error. -callback write_roster_version(binary(), binary(), boolean(), binary()) -> any(). -callback get_roster(binary(), binary()) -> [#roster{}]. @@ -139,25 +140,60 @@ stop(Host) -> depends(_Host, _Opts) -> []. -process_iq(From, To, IQ) when ((From#jid.luser == <<"">>) andalso (From#jid.resource == <<"">>)) -> - process_iq_manager(From, To, IQ); - -process_iq(From, To, IQ) -> - #iq{sub_el = SubEl, lang = Lang} = IQ, - #jid{lserver = LServer} = From, - case lists:member(LServer, ?MYHOSTS) of - true -> process_local_iq(From, To, IQ); - _ -> - Txt = <<"The query is only allowed from local users">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]} +process_iq(#iq{from = #jid{luser = U, lserver = S}, + to = #jid{luser = U, lserver = S}} = IQ) -> + process_local_iq(IQ); +process_iq(#iq{lang = Lang, to = To} = IQ) -> + case ejabberd_hooks:run_fold(roster_remote_access, + To#jid.lserver, false, [IQ]) of + false -> + Txt = <<"Query to another users is forbidden">>, + xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)); + true -> + process_local_iq(IQ) end. -process_local_iq(From, To, #iq{type = Type} = IQ) -> - case Type of - set -> try_process_iq_set(From, To, IQ); - get -> process_iq_get(From, To, IQ) - end. +process_local_iq(#iq{type = set,lang = Lang, + sub_els = [#roster_query{ + items = [#roster_item{ask = Ask}]}]} = IQ) + when Ask /= undefined -> + Txt = <<"Possessing 'ask' attribute is not allowed by RFC6121">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); +process_local_iq(#iq{type = set, from = From, lang = Lang, + sub_els = [#roster_query{ + items = [#roster_item{} = Item]}]} = IQ) -> + case has_duplicated_groups(Item#roster_item.groups) of + true -> + Txt = <<"Duplicated groups are not allowed by RFC6121">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); + false -> + #jid{server = Server} = From, + Access = gen_mod:get_module_opt(Server, ?MODULE, + access, fun(A) -> A end, all), + case acl:match_rule(Server, Access, From) of + deny -> + Txt = <<"Denied by ACL">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); + allow -> + process_iq_set(IQ) + end + end; +process_local_iq(#iq{type = set, lang = Lang, + sub_els = [#roster_query{items = [_|_]}]} = IQ) -> + Txt = <<"Multiple elements are not allowed by RFC6121">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); +process_local_iq(#iq{type = get, lang = Lang, + sub_els = [#roster_query{items = Items}]} = IQ) -> + case Items of + [] -> + process_iq_get(IQ); + [_|_] -> + Txt = <<"The query must not contain elements">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) + end; +process_local_iq(#iq{lang = Lang} = IQ) -> + Txt = <<"No module is handling this query">>, + xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). roster_hash(Items) -> p1_sha:sha(term_to_binary(lists:sort([R#roster{groups = @@ -176,13 +212,11 @@ roster_version_on_db(Host) -> false). %% Returns a list that may contain an xmlelement with the XEP-237 feature if it's enabled. +-spec get_versioning_feature([xmpp_element()], binary()) -> [xmpp_element()]. get_versioning_feature(Acc, Host) -> case roster_versioning_enabled(Host) of true -> - Feature = #xmlel{name = <<"ver">>, - attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}], - children = []}, - [Feature | Acc]; + [#rosterver_feature{}|Acc]; false -> [] end. @@ -221,84 +255,64 @@ write_roster_version(LUser, LServer, InTransaction) -> %% - roster versioning is not used by the client OR %% - roster versioning is used by server and client, BUT the server isn't storing versions on db OR %% - the roster version from client don't match current version. -process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) -> - LUser = From#jid.luser, - LServer = From#jid.lserver, +process_iq_get(#iq{to = To, lang = Lang, + sub_els = [#roster_query{ver = RequestedVersion}]} = IQ) -> + LUser = To#jid.luser, + LServer = To#jid.lserver, US = {LUser, LServer}, - try {ItemsToSend, VersionToSend} = case - {fxml:get_tag_attr(<<"ver">>, SubEl), - roster_versioning_enabled(LServer), - roster_version_on_db(LServer)} - of - {{value, RequestedVersion}, true, - true} -> - case read_roster_version(LUser, - LServer) - of - error -> - RosterVersion = - write_roster_version(LUser, - LServer), - {lists:map(fun item_to_xml/1, - ejabberd_hooks:run_fold(roster_get, - To#jid.lserver, - [], - [US])), - RosterVersion}; - RequestedVersion -> - {false, false}; - NewVersion -> - {lists:map(fun item_to_xml/1, - ejabberd_hooks:run_fold(roster_get, - To#jid.lserver, - [], - [US])), - NewVersion} - end; - {{value, RequestedVersion}, true, - false} -> - RosterItems = - ejabberd_hooks:run_fold(roster_get, - To#jid.lserver, - [], - [US]), - case roster_hash(RosterItems) of - RequestedVersion -> - {false, false}; - New -> - {lists:map(fun item_to_xml/1, - RosterItems), - New} - end; - _ -> - {lists:map(fun item_to_xml/1, - ejabberd_hooks:run_fold(roster_get, - To#jid.lserver, - [], - [US])), - false} - end, - IQ#iq{type = result, - sub_el = - case {ItemsToSend, VersionToSend} of - {false, false} -> []; - {Items, false} -> - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_ROSTER}], - children = Items}]; - {Items, Version} -> - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, ?NS_ROSTER}, - {<<"ver">>, Version}], - children = Items}] - end} - catch - _:_ -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} + try {ItemsToSend, VersionToSend} = + case {roster_versioning_enabled(LServer), + roster_version_on_db(LServer)} of + {true, true} when RequestedVersion /= undefined -> + case read_roster_version(LUser, LServer) of + error -> + RosterVersion = write_roster_version(LUser, LServer), + {lists:map(fun encode_item/1, + ejabberd_hooks:run_fold( + roster_get, To#jid.lserver, [], [US])), + RosterVersion}; + RequestedVersion -> + {false, false}; + NewVersion -> + {lists:map(fun encode_item/1, + ejabberd_hooks:run_fold( + roster_get, To#jid.lserver, [], [US])), + NewVersion} + end; + {true, false} when RequestedVersion /= undefined -> + RosterItems = ejabberd_hooks:run_fold( + roster_get, To#jid.lserver, [], [US]), + case roster_hash(RosterItems) of + RequestedVersion -> + {false, false}; + New -> + {lists:map(fun encode_item/1, RosterItems), New} + end; + _ -> + {lists:map(fun encode_item/1, + ejabberd_hooks:run_fold( + roster_get, To#jid.lserver, [], [US])), + false} + end, + xmpp:make_iq_result( + IQ, + case {ItemsToSend, VersionToSend} of + {false, false} -> + undefined; + {Items, false} -> + #roster_query{items = Items}; + {Items, Version} -> + #roster_query{items = Items, + ver = Version} + end) + catch E:R -> + ?ERROR_MSG("failed to process roster get for ~s: ~p", + [jid:to_string(To), {E, {R, erlang:get_stacktrace()}}]), + Txt = <<"Roster module has failed">>, + xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end. +-spec get_user_roster([#roster{}], {binary(), binary()}) -> [#roster{}]. get_user_roster(Acc, {LUser, LServer}) -> Items = get_roster(LUser, LServer), lists:filter(fun (#roster{subscription = none, @@ -317,147 +331,91 @@ set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) -> transaction( LServer, fun() -> - roster_subscribe_t(LUser, LServer, LJID, Item) + update_roster_t(LUser, LServer, LJID, Item) end). -item_to_xml(Item) -> - Attrs1 = [{<<"jid">>, - jid:to_string(Item#roster.jid)}], - Attrs2 = case Item#roster.name of - <<"">> -> Attrs1; - Name -> [{<<"name">>, Name} | Attrs1] - end, - Attrs3 = case Item#roster.subscription of - none -> [{<<"subscription">>, <<"none">>} | Attrs2]; - from -> [{<<"subscription">>, <<"from">>} | Attrs2]; - to -> [{<<"subscription">>, <<"to">>} | Attrs2]; - both -> [{<<"subscription">>, <<"both">>} | Attrs2]; - remove -> [{<<"subscription">>, <<"remove">>} | Attrs2] - end, - Attrs4 = case ask_to_pending(Item#roster.ask) of - out -> [{<<"ask">>, <<"subscribe">>} | Attrs3]; - both -> [{<<"ask">>, <<"subscribe">>} | Attrs3]; - _ -> Attrs3 - end, - SubEls1 = lists:map(fun (G) -> - #xmlel{name = <<"group">>, attrs = [], - children = [{xmlcdata, G}]} - end, - Item#roster.groups), - SubEls = SubEls1 ++ Item#roster.xs, - #xmlel{name = <<"item">>, attrs = Attrs4, - children = SubEls}. +del_roster(LUser, LServer, LJID) -> + transaction( + LServer, + fun() -> + del_roster_t(LUser, LServer, LJID) + end). + +encode_item(Item) -> + #roster_item{jid = jid:make(Item#roster.jid), + name = Item#roster.name, + subscription = Item#roster.subscription, + ask = case ask_to_pending(Item#roster.ask) of + out -> subscribe; + both -> subscribe; + _ -> undefined + end, + groups = Item#roster.groups}. + +decode_item(#roster_item{subscription = remove} = Item, R, _) -> + R#roster{jid = jid:tolower(Item#roster_item.jid), + name = <<"">>, + subscription = remove, + ask = none, + groups = [], + askmessage = <<"">>, + xs = []}; +decode_item(Item, R, Managed) -> + R#roster{jid = jid:tolower(Item#roster_item.jid), + name = Item#roster_item.name, + subscription = case Item#roster_item.subscription of + Sub when Managed -> Sub; + _ -> R#roster.subscription + end, + groups = Item#roster_item.groups}. get_roster_by_jid_t(LUser, LServer, LJID) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_roster_by_jid(LUser, LServer, LJID). -try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) -> - #jid{server = Server} = From, - Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) -> A end, all), - case acl:match_rule(Server, Access, From) of - deny -> - Txt = <<"Denied by ACL">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - allow -> - process_iq_set(From, To, IQ) +process_iq_set(#iq{from = From, to = To, + sub_els = [#roster_query{items = QueryItems}]} = IQ) -> + #jid{user = User, luser = LUser, lserver = LServer} = To, + Managed = {From#jid.luser, From#jid.lserver} /= {LUser, LServer}, + F = fun () -> + lists:map( + fun(#roster_item{jid = JID1} = QueryItem) -> + LJID = jid:tolower(JID1), + Item = get_roster_by_jid_t(LUser, LServer, LJID), + Item2 = decode_item(QueryItem, Item, Managed), + Item3 = ejabberd_hooks:run_fold(roster_process_item, + LServer, Item2, + [LServer]), + case Item3#roster.subscription of + remove -> del_roster_t(LUser, LServer, LJID); + _ -> update_roster_t(LUser, LServer, LJID, Item3) + end, + case roster_version_on_db(LServer) of + true -> write_roster_version_t(LUser, LServer); + false -> ok + end, + {Item, Item3} + end, QueryItems) + end, + case transaction(LServer, F) of + {atomic, ItemPairs} -> + lists:foreach( + fun({OldItem, Item}) -> + push_item(User, LServer, To, Item), + case Item#roster.subscription of + remove -> + send_unsubscribing_presence(To, OldItem); + _ -> + ok + end + end, ItemPairs), + xmpp:make_iq_result(IQ); + E -> + ?ERROR_MSG("roster set failed:~nIQ = ~s~nError = ~p", + [xmpp:pp(IQ), E]), + xmpp:make_error(IQ, xmpp:err_internal_server_error()) end. -process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) -> - #xmlel{children = Els} = SubEl, - Managed = is_managed_from_id(Id), - lists:foreach(fun (El) -> process_item_set(From, To, El, Managed) - end, - Els), - IQ#iq{type = result, sub_el = []}. - -process_item_set(From, To, - #xmlel{attrs = Attrs, children = Els}, Managed) -> - JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>, - Attrs)), - #jid{user = User, luser = LUser, lserver = LServer} = - From, - case JID1 of - error -> ok; - _ -> - LJID = jid:tolower(JID1), - F = fun () -> - Item = get_roster_by_jid_t(LUser, LServer, LJID), - Item1 = process_item_attrs_managed(Item, Attrs, Managed), - Item2 = process_item_els(Item1, Els), - Item3 = ejabberd_hooks:run_fold(roster_process_item, - LServer, Item2, - [LServer]), - case Item3#roster.subscription of - remove -> del_roster_t(LUser, LServer, LJID); - _ -> update_roster_t(LUser, LServer, LJID, Item3) - end, - send_itemset_to_managers(From, Item3, Managed), - case roster_version_on_db(LServer) of - true -> write_roster_version_t(LUser, LServer); - false -> ok - end, - {Item, Item3} - end, - case transaction(LServer, F) of - {atomic, {OldItem, Item}} -> - push_item(User, LServer, To, Item), - case Item#roster.subscription of - remove -> - send_unsubscribing_presence(From, OldItem), ok; - _ -> ok - end; - E -> - ?DEBUG("ROSTER: roster item set error: ~p~n", [E]), ok - end - end; -process_item_set(_From, _To, _, _Managed) -> ok. - -process_item_attrs(Item, [{Attr, Val} | Attrs]) -> - case Attr of - <<"jid">> -> - case jid:from_string(Val) of - error -> process_item_attrs(Item, Attrs); - JID1 -> - JID = {JID1#jid.luser, JID1#jid.lserver, - JID1#jid.lresource}, - process_item_attrs(Item#roster{jid = JID}, Attrs) - end; - <<"name">> -> - process_item_attrs(Item#roster{name = Val}, Attrs); - <<"subscription">> -> - case Val of - <<"remove">> -> - process_item_attrs(Item#roster{subscription = remove}, - Attrs); - _ -> process_item_attrs(Item, Attrs) - end; - <<"ask">> -> process_item_attrs(Item, Attrs); - _ -> process_item_attrs(Item, Attrs) - end; -process_item_attrs(Item, []) -> Item. - -process_item_els(Item, - [#xmlel{name = Name, attrs = Attrs, children = SEls} - | Els]) -> - case Name of - <<"group">> -> - Groups = [fxml:get_cdata(SEls) | Item#roster.groups], - process_item_els(Item#roster{groups = Groups}, Els); - _ -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - <<"">> -> process_item_els(Item, Els); - _ -> - XEls = [#xmlel{name = Name, attrs = Attrs, - children = SEls} - | Item#roster.xs], - process_item_els(Item#roster{xs = XEls}, Els) - end - end; -process_item_els(Item, [{xmlcdata, _} | Els]) -> - process_item_els(Item, Els); -process_item_els(Item, []) -> Item. - push_item(User, Server, From, Item) -> ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>), jid:make(User, Server, <<"">>), @@ -480,21 +438,19 @@ push_item(User, Server, Resource, From, Item) -> push_item(User, Server, Resource, From, Item, RosterVersion) -> - ExtraAttrs = case RosterVersion of - not_found -> []; - _ -> [{<<"ver">>, RosterVersion}] - end, - ResIQ = #iq{type = set, xmlns = ?NS_ROSTER, + Ver = case RosterVersion of + not_found -> undefined; + _ -> RosterVersion + end, + ResIQ = #iq{type = set, %% @doc Roster push, calculate and include the version attribute. %% TODO: don't push to those who didn't load roster id = <<"push", (randoms:get_string())/binary>>, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_ROSTER} | ExtraAttrs], - children = [item_to_xml(Item)]}]}, + sub_els = [#roster_query{ver = Ver, + items = [encode_item(Item)]}]}, ejabberd_router:route(From, jid:make(User, Server, Resource), - jlib:iq_to_xml(ResIQ)). + ResIQ). push_item_version(Server, User, From, Item, RosterVersion) -> @@ -504,6 +460,8 @@ push_item_version(Server, User, From, Item, end, ejabberd_sm:get_user_resources(User, Server)). +-spec get_subscription_lists({[ljid()], [ljid()]}, binary(), binary()) + -> {[ljid()], [ljid()]}. get_subscription_lists(_Acc, User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -536,10 +494,16 @@ transaction(LServer, F) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:transaction(LServer, F). +-spec in_subscription(boolean(), binary(), binary(), jid(), + subscribe | subscribed | unsubscribe | unsubscribed, + binary()) -> boolean(). in_subscription(_, User, Server, JID, Type, Reason) -> process_subscription(in, User, Server, JID, Type, Reason). +-spec out_subscription( + binary(), binary(), jid(), + subscribed | unsubscribed | subscribe | unsubscribe) -> boolean(). out_subscription(User, Server, JID, Type) -> process_subscription(out, User, Server, JID, Type, <<"">>). @@ -583,8 +547,7 @@ process_subscription(Direction, User, Server, JID1, {Subscription, Pending} -> NewItem = Item#roster{subscription = Subscription, ask = Pending, - askmessage = - iolist_to_binary(AskMessage)}, + askmessage = AskMessage}, roster_subscribe_t(LUser, LServer, LJID, NewItem), case roster_version_on_db(LServer) of true -> write_roster_version_t(LUser, LServer); @@ -598,16 +561,8 @@ process_subscription(Direction, User, Server, JID1, case AutoReply of none -> ok; _ -> - T = case AutoReply of - subscribed -> <<"subscribed">>; - unsubscribed -> <<"unsubscribed">> - end, - ejabberd_router:route(jid:make(User, Server, - <<"">>), - JID1, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, T}], - children = []}) + ejabberd_router:route(jid:make(User, Server, <<"">>), + JID1, #presence{type = AutoReply}) end, case Push of {push, Item} -> @@ -739,12 +694,14 @@ in_auto_reply(from, out, unsubscribe) -> unsubscribed; in_auto_reply(both, none, unsubscribe) -> unsubscribed; in_auto_reply(_, _, _) -> none. +-spec remove_user(binary(), binary()) -> ok. remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), send_unsubscription_to_rosteritems(LUser, LServer), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:remove_user(LUser, LServer). + Mod:remove_user(LUser, LServer), + ok. %% For each contact with Subscription: %% Both or From, send a "unsubscribed" presence stanza; @@ -769,35 +726,29 @@ send_unsubscribing_presence(From, Item) -> _ -> false end, if IsTo -> - send_presence_type(jid:remove_resource(From), - jid:make(Item#roster.jid), - <<"unsubscribe">>); + ejabberd_router:route(jid:remove_resource(From), + jid:make(Item#roster.jid), + #presence{type = unsubscribe}); true -> ok end, if IsFrom -> - send_presence_type(jid:remove_resource(From), - jid:make(Item#roster.jid), - <<"unsubscribed">>); + ejabberd_router:route(jid:remove_resource(From), + jid:make(Item#roster.jid), + #presence{type = unsubscribed}); true -> ok end, ok. -send_presence_type(From, To, Type) -> - ejabberd_router:route(From, To, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, Type}], children = []}). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -set_items(User, Server, SubEl) -> - #xmlel{children = Els} = SubEl, +-spec set_items(binary(), binary(), roster_query()) -> any(). +set_items(User, Server, #roster_query{items = Items}) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), F = fun () -> - lists:foreach(fun (El) -> - process_item_set_t(LUser, LServer, El) - end, - Els) + lists:foreach(fun (Item) -> + process_item_set_t(LUser, LServer, Item) + end, Items) end, transaction(LServer, F). @@ -809,65 +760,19 @@ del_roster_t(LUser, LServer, LJID) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:del_roster(LUser, LServer, LJID). -process_item_set_t(LUser, LServer, - #xmlel{attrs = Attrs, children = Els}) -> - JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>, - Attrs)), - case JID1 of - error -> ok; - _ -> - JID = {JID1#jid.user, JID1#jid.server, - JID1#jid.resource}, - LJID = {JID1#jid.luser, JID1#jid.lserver, - JID1#jid.lresource}, - Item = #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = JID}, - Item1 = process_item_attrs_ws(Item, Attrs), - Item2 = process_item_els(Item1, Els), - case Item2#roster.subscription of - remove -> del_roster_t(LUser, LServer, LJID); - _ -> update_roster_t(LUser, LServer, LJID, Item2) - end +process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) -> + JID = {JID1#jid.user, JID1#jid.server, <<>>}, + LJID = {JID1#jid.luser, JID1#jid.lserver, <<>>}, + Item = #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = JID}, + Item2 = decode_item(QueryItem, Item, _Managed = true), + case Item2#roster.subscription of + remove -> del_roster_t(LUser, LServer, LJID); + _ -> update_roster_t(LUser, LServer, LJID, Item2) end; process_item_set_t(_LUser, _LServer, _) -> ok. -process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) -> - case Attr of - <<"jid">> -> - case jid:from_string(Val) of - error -> process_item_attrs_ws(Item, Attrs); - JID1 -> - JID = {JID1#jid.luser, JID1#jid.lserver, - JID1#jid.lresource}, - process_item_attrs_ws(Item#roster{jid = JID}, Attrs) - end; - <<"name">> -> - process_item_attrs_ws(Item#roster{name = Val}, Attrs); - <<"subscription">> -> - case Val of - <<"remove">> -> - process_item_attrs_ws(Item#roster{subscription = - remove}, - Attrs); - <<"none">> -> - process_item_attrs_ws(Item#roster{subscription = none}, - Attrs); - <<"both">> -> - process_item_attrs_ws(Item#roster{subscription = both}, - Attrs); - <<"from">> -> - process_item_attrs_ws(Item#roster{subscription = from}, - Attrs); - <<"to">> -> - process_item_attrs_ws(Item#roster{subscription = to}, - Attrs); - _ -> process_item_attrs_ws(Item, Attrs) - end; - <<"ask">> -> process_item_attrs_ws(Item, Attrs); - _ -> process_item_attrs_ws(Item, Attrs) - end; -process_item_attrs_ws(Item, []) -> Item. - +-spec get_in_pending_subscriptions([presence()], binary(), binary()) -> [presence()]. get_in_pending_subscriptions(Ls, User, Server) -> LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), @@ -876,31 +781,18 @@ get_in_pending_subscriptions(Ls, User, Server) -> get_in_pending_subscriptions(Ls, User, Server, Mod) -> JID = jid:make(User, Server, <<"">>), Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver), - Ls ++ lists:map(fun (R) -> - Message = R#roster.askmessage, - Status = if is_binary(Message) -> (Message); - true -> <<"">> - end, - #xmlel{name = <<"presence">>, - attrs = - [{<<"from">>, - jid:to_string(R#roster.jid)}, - {<<"to">>, jid:to_string(JID)}, - {<<"type">>, <<"subscribe">>}], - children = - [#xmlel{name = <<"status">>, - attrs = [], - children = - [{xmlcdata, Status}]}]} - end, - lists:filter(fun (R) -> - case R#roster.ask of - in -> true; - both -> true; - _ -> false - end - end, - Result)). + Ls ++ lists:flatmap( + fun(#roster{ask = Ask} = R) when Ask == in; Ask == both -> + Message = R#roster.askmessage, + Status = if is_binary(Message) -> (Message); + true -> <<"">> + end, + [#presence{from = R#roster.jid, to = JID, + type = subscribe, + status = xmpp:mk_text(Status)}]; + (_) -> + [] + end, Result). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -910,6 +802,8 @@ read_subscription_and_groups(User, Server, LJID) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:read_subscription_and_groups(LUser, LServer, LJID). +-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid()) + -> {subscription(), [binary()]}. get_jid_info(_, User, Server, JID) -> LJID = jid:tolower(JID), case read_subscription_and_groups(User, Server, LJID) of @@ -1070,10 +964,7 @@ user_roster_parse_query(User, Server, Items, Query) -> user_roster_subscribe_jid(User, Server, JID) -> out_subscription(User, Server, JID, subscribe), UJID = jid:make(User, Server, <<"">>), - ejabberd_router:route(UJID, JID, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"subscribe">>}], - children = []}). + ejabberd_router:route(UJID, JID, #presence{type = subscribe}). user_roster_item_parse_query(User, Server, Items, Query) -> @@ -1089,12 +980,7 @@ user_roster_item_parse_query(User, Server, Items, subscribed), UJID = jid:make(User, Server, <<"">>), ejabberd_router:route(UJID, JID1, - #xmlel{name = - <<"presence">>, - attrs = - [{<<"type">>, - <<"subscribed">>}], - children = []}), + #presence{type = subscribed}), throw(submitted); false -> case lists:keysearch(<<"remove", @@ -1102,29 +988,17 @@ user_roster_item_parse_query(User, Server, Items, 1, Query) of {value, _} -> - UJID = jid:make(User, Server, - <<"">>), - process_iq_set(UJID, UJID, - #iq{type = set, - sub_el = - #xmlel{name = - <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_ROSTER}], - children = - [#xmlel{name - = - <<"item">>, - attrs - = - [{<<"jid">>, - jid:to_string(JID)}, - {<<"subscription">>, - <<"remove">>}], - children - = - []}]}}), + UJID = jid:make(User, Server), + RosterItem = #roster_item{ + jid = jid:make(JID), + subscription = remove}, + process_iq_set( + #iq{type = set, + from = UJID, + to = UJID, + id = randoms:get_string(), + sub_els = [#roster_query{ + items = [RosterItem]}]}), throw(submitted); false -> ok end @@ -1141,101 +1015,42 @@ webadmin_user(Acc, _User, _Server, Lang) -> [?XE(<<"h3">>, [?ACT(<<"roster/">>, <<"Roster">>)])]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Implement XEP-0321 Remote Roster Management - -process_iq_manager(From, To, IQ) -> - %% Check what access is allowed for From to To - MatchDomain = From#jid.lserver, - case is_domain_managed(MatchDomain, To#jid.lserver) of - true -> - process_iq_manager2(MatchDomain, To, IQ); - false -> - #iq{sub_el = SubEl, lang = Lang} = IQ, - Txt = <<"Roster management is not allowed from this domain">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]} - end. - -process_iq_manager2(MatchDomain, To, IQ) -> - %% If IQ is SET, filter the input IQ - IQFiltered = maybe_filter_request(MatchDomain, IQ), - %% Call the standard function with reversed JIDs - IdInitial = IQFiltered#iq.id, - ResIQ = process_iq(To, To, IQFiltered#iq{id = <<"roster-remotely-managed">>}), - %% Filter the output IQ - filter_stanza(MatchDomain, ResIQ#iq{id = IdInitial}). - -is_domain_managed(ContactHost, UserHost) -> - Managers = gen_mod:get_module_opt(UserHost, ?MODULE, managers, - fun(B) when is_list(B) -> B end, - []), - lists:member(ContactHost, Managers). - -maybe_filter_request(MatchDomain, IQ) when IQ#iq.type == set -> - filter_stanza(MatchDomain, IQ); -maybe_filter_request(_MatchDomain, IQ) -> - IQ. - -filter_stanza(_MatchDomain, #iq{sub_el = []} = IQ) -> - IQ; -filter_stanza(MatchDomain, #iq{sub_el = [SubEl | _]} = IQ) -> - #iq{sub_el = SubElFiltered} = IQRes = - filter_stanza(MatchDomain, IQ#iq{sub_el = SubEl}), - IQRes#iq{sub_el = [SubElFiltered]}; -filter_stanza(MatchDomain, #iq{sub_el = SubEl} = IQ) -> - #xmlel{name = Type, attrs = Attrs, children = Items} = SubEl, - ItemsFiltered = lists:filter( - fun(Item) -> - is_item_of_domain(MatchDomain, Item) end, Items), - SubElFiltered = #xmlel{name=Type, attrs = Attrs, children = ItemsFiltered}, - IQ#iq{sub_el = SubElFiltered}. - -is_item_of_domain(MatchDomain, #xmlel{} = El) -> - lists:any(fun(Attr) -> is_jid_of_domain(MatchDomain, Attr) end, El#xmlel.attrs); -is_item_of_domain(_MatchDomain, {xmlcdata, _}) -> - false. - -is_jid_of_domain(MatchDomain, {<<"jid">>, JIDString}) -> - case jid:from_string(JIDString) of - JID when JID#jid.lserver == MatchDomain -> true; - _ -> false - end; -is_jid_of_domain(_, _) -> - false. - -process_item_attrs_managed(Item, Attrs, true) -> - process_item_attrs_ws(Item, Attrs); -process_item_attrs_managed(Item, _Attrs, false) -> - process_item_attrs(Item, _Attrs). - -send_itemset_to_managers(_From, _Item, true) -> - ok; -send_itemset_to_managers(From, Item, false) -> - {_, UserHost} = Item#roster.us, - {_ContactUser, ContactHost, _ContactResource} = Item#roster.jid, - %% Check if the component is an allowed manager - IsManager = is_domain_managed(ContactHost, UserHost), - case IsManager of - true -> push_item(<<"">>, ContactHost, <<"">>, From, Item); - false -> ok - end. - -is_managed_from_id(<<"roster-remotely-managed">>) -> - true; -is_managed_from_id(_Id) -> - false. +has_duplicated_groups(Groups) -> + GroupsPrep = lists:usort([jid:resourceprep(G) || G <- Groups]), + not (length(GroupsPrep) == length(Groups)). export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). +import_info() -> + [{<<"roster_version">>, 2}, + {<<"rostergroups">>, 3}, + {<<"rosterusers">>, 10}]. -import(LServer, DBType, R) -> +import_start(LServer, DBType) -> Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, R). + ets:new(rostergroups_tmp, [private, named_table, bag]), + Mod:init(LServer, []), + ok. + +import_stop(_LServer, _DBType) -> + ets:delete(rostergroups_tmp), + ok. + +import(LServer, {sql, _}, _DBType, <<"rostergroups">>, [LUser, SJID, Group]) -> + LJID = jid:tolower(jid:from_string(SJID)), + ets:insert(rostergroups_tmp, {{LUser, LServer, LJID}, Group}), + ok; +import(LServer, {sql, _}, DBType, <<"rosterusers">>, Row) -> + I = mod_roster_sql:raw_to_record(LServer, lists:sublist(Row, 9)), + Groups = [G || {_, G} <- ets:lookup(rostergroups_tmp, I#roster.usj)], + RosterItem = I#roster{groups = Groups}, + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, <<"rosterusers">>, RosterItem); +import(LServer, {sql, _}, DBType, <<"roster_version">>, [LUser, Ver]) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, <<"roster_version">>, [LUser, Ver]). mod_opt_type(access) -> fun acl:access_rules_validator/1; diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl index ddfa34d68..04bdf72e7 100644 --- a/src/mod_roster_mnesia.erl +++ b/src/mod_roster_mnesia.erl @@ -15,9 +15,8 @@ get_roster/2, get_roster_by_jid/3, get_only_items/2, roster_subscribe/4, get_roster_by_jid_with_groups/3, remove_user/2, update_roster/4, del_roster/3, transaction/2, - read_subscription_and_groups/3, import/2]). + read_subscription_and_groups/3, import/3, create_roster/1]). --include("jlib.hrl"). -include("mod_roster.hrl"). -include("logger.hrl"). @@ -25,10 +24,10 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(roster, + ejabberd_mnesia:create(?MODULE, roster, [{disc_copies, [node()]}, {attributes, record_info(fields, roster)}]), - mnesia:create_table(roster_version, + ejabberd_mnesia:create(?MODULE, roster_version, [{disc_copies, [node()]}, {attributes, record_info(fields, roster_version)}]), @@ -104,9 +103,13 @@ read_subscription_and_groups(LUser, LServer, LJID) -> transaction(_LServer, F) -> mnesia:transaction(F). -import(_LServer, #roster{} = R) -> +create_roster(RItem) -> + mnesia:dirty_write(RItem). + +import(_LServer, <<"rosterusers">>, #roster{} = R) -> mnesia:dirty_write(R); -import(_LServer, #roster_version{} = RV) -> +import(LServer, <<"roster_version">>, [LUser, Ver]) -> + RV = #roster_version{us = {LUser, LServer}, version = Ver}, mnesia:dirty_write(RV). %%%=================================================================== diff --git a/src/mod_roster_riak.erl b/src/mod_roster_riak.erl index 38e873827..40992d77d 100644 --- a/src/mod_roster_riak.erl +++ b/src/mod_roster_riak.erl @@ -13,12 +13,11 @@ %% API -export([init/2, read_roster_version/2, write_roster_version/4, - get_roster/2, get_roster_by_jid/3, + get_roster/2, get_roster_by_jid/3, create_roster/1, roster_subscribe/4, get_roster_by_jid_with_groups/3, remove_user/2, update_roster/4, del_roster/3, transaction/2, - read_subscription_and_groups/3, get_only_items/2, import/2]). + read_subscription_and_groups/3, get_only_items/2, import/3]). --include("jlib.hrl"). -include("mod_roster.hrl"). %%%=================================================================== @@ -97,10 +96,17 @@ read_subscription_and_groups(LUser, LServer, LJID) -> error end. -import(_LServer, #roster{us = {LUser, LServer}} = R) -> - ejabberd_riak:put(R, roster_schema(), +create_roster(#roster{us = {LUser, LServer}} = RItem) -> + ejabberd_riak:put( + RItem, roster_schema(), + [{'2i', [{<<"us">>, {LUser, LServer}}]}]). + +import(_LServer, <<"rosterusers">>, RosterItem) -> + {LUser, LServer} = RosterItem#roster.us, + ejabberd_riak:put(RosterItem, roster_schema(), [{'2i', [{<<"us">>, {LUser, LServer}}]}]); -import(_LServer, #roster_version{} = RV) -> +import(LServer, <<"roster_version">>, [LUser, Ver]) -> + RV = #roster_version{us = {LUser, LServer}, version = Ver}, ejabberd_riak:put(RV, roster_version_schema()). %%%=================================================================== diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl index 61f59a990..2fc6b112e 100644 --- a/src/mod_roster_sql.erl +++ b/src/mod_roster_sql.erl @@ -18,9 +18,8 @@ roster_subscribe/4, get_roster_by_jid_with_groups/3, remove_user/2, update_roster/4, del_roster/3, transaction/2, read_subscription_and_groups/3, get_only_items/2, - import/1, import/2, export/1]). + import/3, export/1, raw_to_record/2]). --include("jlib.hrl"). -include("mod_roster.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -186,27 +185,8 @@ export(_Server) -> [] end}]. -import(LServer) -> - [{<<"select username, jid, nick, subscription, " - "ask, askmessage, server, subscribe, type from rosterusers;">>, - fun([LUser, JID|_] = Row) -> - Item = raw_to_record(LServer, Row), - Username = ejabberd_sql:escape(LUser), - SJID = ejabberd_sql:escape(JID), - {selected, _, Rows} = - ejabberd_sql:sql_query_t( - [<<"select grp from rostergroups where username='">>, - Username, <<"' and jid='">>, SJID, <<"'">>]), - Groups = [Grp || [Grp] <- Rows], - Item#roster{groups = Groups} - end}, - {<<"select username, version from roster_version;">>, - fun([LUser, Ver]) -> - #roster_version{us = {LUser, LServer}, version = Ver} - end}]. - -import(_, _) -> - pass. +import(_, _, _) -> + ok. %%%=================================================================== %%% Internal functions diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl index ae264bbc9..ea7768bca 100644 --- a/src/mod_service_log.erl +++ b/src/mod_service_log.erl @@ -35,7 +35,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). start(Host, _Opts) -> ejabberd_hooks:add(user_send_packet, Host, ?MODULE, @@ -54,17 +54,18 @@ stop(Host) -> depends(_Host, _Opts) -> []. +-spec log_user_send(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza(). log_user_send(Packet, _C2SState, From, To) -> log_packet(From, To, Packet, From#jid.lserver), Packet. +-spec log_user_receive(stanza(), ejabberd_c2s:state(), jid(), jid(), jid()) -> stanza(). log_user_receive(Packet, _C2SState, _JID, From, To) -> log_packet(From, To, Packet, To#jid.lserver), Packet. -log_packet(From, To, - #xmlel{name = Name, attrs = Attrs, children = Els}, - Host) -> +-spec log_packet(jid(), jid(), stanza(), binary()) -> ok. +log_packet(From, To, Packet, Host) -> Loggers = gen_mod:get_module_opt(Host, ?MODULE, loggers, fun(L) -> lists:map( @@ -76,22 +77,11 @@ log_packet(From, To, end end, L) end, []), - ServerJID = #jid{user = <<"">>, server = Host, - resource = <<"">>, luser = <<"">>, lserver = Host, - lresource = <<"">>}, - NewAttrs = - jlib:replace_from_to_attrs(jid:to_string(From), - jid:to_string(To), Attrs), - FixedPacket = #xmlel{name = Name, attrs = NewAttrs, - children = Els}, + ServerJID = jid:make(Host), + FixedPacket = xmpp:set_from_to(Packet, From, To), lists:foreach(fun (Logger) -> ejabberd_router:route(ServerJID, - #jid{user = <<"">>, - server = Logger, - resource = <<"">>, - luser = <<"">>, - lserver = Logger, - lresource = <<"">>}, + jid:make(Logger), #xmlel{name = <<"route">>, attrs = [], children = diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index b472e1aab..8ef0f41b5 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -29,10 +29,10 @@ -behaviour(gen_mod). --export([start/2, stop/1, item_to_xml/1, export/1, - import/1, webadmin_menu/3, webadmin_page/3, +-export([start/2, stop/1, export/1, + import_info/0, webadmin_menu/3, webadmin_page/3, get_user_roster/2, get_subscription_lists/3, - get_jid_info/4, import/3, process_item/2, + get_jid_info/4, import/5, process_item/2, import_start/2, in_subscription/6, out_subscription/4, user_available/1, unset_presence/4, register_user/2, remove_user/2, list_groups/1, create_group/2, create_group/3, @@ -44,7 +44,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_roster.hrl"). @@ -56,7 +56,7 @@ -type group_options() :: [{atom(), any()}]. -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #sr_user{} | #sr_group{}) -> ok | pass. +-callback import(binary(), binary(), [binary()]) -> ok. -callback list_groups(binary()) -> [binary()]. -callback groups_with_opts(binary()) -> [{binary(), group_options()}]. -callback create_group(binary(), binary(), group_options()) -> {atomic, any()}. @@ -135,6 +135,7 @@ stop(Host) -> depends(_Host, _Opts) -> []. +-spec get_user_roster([#roster{}], {binary(), binary()}) -> [#roster{}]. get_user_roster(Items, US) -> {U, S} = US, DisplayedGroups = get_user_displayed_groups(US), @@ -172,56 +173,37 @@ get_user_roster(Items, US) -> end end, SRUsers, Items), - ModVcard = get_vcard_module(S), SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}}, us = US, jid = {U1, S1, <<"">>}, - name = get_rosteritem_name(ModVcard, U1, S1), + name = get_rosteritem_name(U1, S1), subscription = both, ask = none, groups = GroupNames} || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)], SRItems ++ NewItems1. -get_vcard_module(Server) -> - Modules = gen_mod:loaded_modules(Server), - [M - || M <- Modules, - (M == mod_vcard) or (M == mod_vcard_ldap)]. - -get_rosteritem_name([], _, _) -> <<"">>; -get_rosteritem_name([ModVcard], U, S) -> - From = jid:make(<<"">>, S, jlib:atom_to_binary(?MODULE)), - To = jid:make(U, S, <<"">>), - case lists:member(To#jid.lserver, ?MYHOSTS) of +get_rosteritem_name(U, S) -> + case gen_mod:is_loaded(S, mod_vcard) of true -> - IQ = {iq, <<"">>, get, <<"vcard-temp">>, <<"">>, - #xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, <<"vcard-temp">>}], - children = []}}, - IQ_Vcard = ModVcard:process_sm_iq(From, To, IQ), - case catch get_rosteritem_name_vcard(IQ_Vcard#iq.sub_el) of - {'EXIT', Err} -> - ?ERROR_MSG("Error found when trying to get the " - "vCard of ~s@~s in ~p:~n ~p", - [U, S, ModVcard, Err]), - <<"">>; - NickName -> - NickName - end; + SubEls = mod_vcard:get_vcard(U, S), + get_rosteritem_name_vcard(SubEls); false -> <<"">> end. -get_rosteritem_name_vcard([]) -> <<"">>; -get_rosteritem_name_vcard([Vcard]) -> +-spec get_rosteritem_name_vcard([xmlel()]) -> binary(). +get_rosteritem_name_vcard([Vcard|_]) -> case fxml:get_path_s(Vcard, [{elem, <<"NICKNAME">>}, cdata]) of <<"">> -> fxml:get_path_s(Vcard, [{elem, <<"FN">>}, cdata]); Nickname -> Nickname - end. + end; +get_rosteritem_name_vcard(_) -> + <<"">>. %% This function rewrites the roster entries when moving or renaming %% them in the user contact list. +-spec process_item(#roster{}, binary()) -> #roster{}. process_item(RosterItem, Host) -> USFrom = {UserFrom, ServerFrom} = RosterItem#roster.us, {UserTo, ServerTo, ResourceTo} = RosterItem#roster.jid, @@ -305,17 +287,15 @@ set_new_rosteritems(UserFrom, ServerFrom, UserTo, RIFrom. set_item(User, Server, Resource, Item) -> - ResIQ = #iq{type = set, xmlns = ?NS_ROSTER, - id = <<"push", (randoms:get_string())/binary>>, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_ROSTER}], - children = [mod_roster:item_to_xml(Item)]}]}, - ejabberd_router:route(jid:make(User, Server, - Resource), - jid:make(<<"">>, Server, <<"">>), - jlib:iq_to_xml(ResIQ)). + ResIQ = #iq{type = set, id = <<"push", (randoms:get_string())/binary>>, + sub_els = [#roster_query{ + items = [mod_roster:encode_item(Item)]}]}, + ejabberd_router:route(jid:make(User, Server, Resource), + jid:make(Server), + ResIQ). +-spec get_subscription_lists({[ljid()], [ljid()]}, binary(), binary()) + -> {[ljid()], [ljid()]}. get_subscription_lists({F, T}, User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -328,6 +308,8 @@ get_subscription_lists({F, T}, User, Server) -> SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers], {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}. +-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid()) + -> {subscription(), [binary()]}. get_jid_info({Subscription, Groups}, User, Server, JID) -> LUser = jid:nodeprep(User), @@ -356,10 +338,16 @@ get_jid_info({Subscription, Groups}, User, Server, error -> {Subscription, Groups} end. +-spec in_subscription(boolean(), binary(), binary(), jid(), + subscribe | subscribed | unsubscribe | unsubscribed, + binary()) -> boolean(). in_subscription(Acc, User, Server, JID, Type, _Reason) -> process_subscription(in, User, Server, JID, Type, Acc). +-spec out_subscription( + binary(), binary(), jid(), + subscribed | unsubscribed | subscribe | unsubscribe) -> boolean(). out_subscription(UserFrom, ServerFrom, JIDTo, unsubscribed) -> #jid{luser = UserTo, lserver = ServerTo} = JIDTo, @@ -574,13 +562,13 @@ add_user_to_group(Host, US, Group) -> {LUser, LServer} = US, case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of match -> - GroupOpts = (?MODULE):get_group_opts(Host, Group), + GroupOpts = mod_shared_roster:get_group_opts(Host, Group), MoreGroupOpts = case LUser of <<"@all@">> -> [{all_users, true}]; <<"@online@">> -> [{online_users, true}]; _ -> [] end, - (?MODULE):set_group_opts(Host, Group, + mod_shared_roster:set_group_opts(Host, Group, GroupOpts ++ MoreGroupOpts); nomatch -> DisplayedToGroups = displayed_to_groups(Group, Host), @@ -612,7 +600,7 @@ remove_user_from_group(Host, US, Group) -> {LUser, LServer} = US, case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of match -> - GroupOpts = (?MODULE):get_group_opts(Host, Group), + GroupOpts = mod_shared_roster:get_group_opts(Host, Group), NewGroupOpts = case LUser of <<"@all@">> -> lists:filter(fun (X) -> X /= {all_users, true} @@ -623,7 +611,7 @@ remove_user_from_group(Host, US, Group) -> end, GroupOpts) end, - (?MODULE):set_group_opts(Host, Group, NewGroupOpts); + mod_shared_roster:set_group_opts(Host, Group, NewGroupOpts); nomatch -> Mod = gen_mod:db_mod(Host, ?MODULE), Result = Mod:remove_user_from_group(Host, US, Group), @@ -653,12 +641,15 @@ broadcast_members_to_user(LUser, LServer, Group, Host, Subscription) -> broadcast_subscription(U, S, {LUser, LServer, <<"">>}, Subscription) end, Members). +-spec register_user(binary(), binary()) -> ok. register_user(User, Server) -> Groups = get_user_groups({User, Server}), [push_user_to_displayed(User, Server, Group, Server, both, displayed_to_groups(Group, Server)) - || Group <- Groups]. + || Group <- Groups], + ok. +-spec remove_user(binary(), binary()) -> ok. remove_user(User, Server) -> push_user_to_members(User, Server, remove). @@ -730,13 +721,9 @@ displayed_to_groups(GroupName, LServer) -> [Name || {Name, _} <- Gs]. push_item(User, Server, Item) -> - Stanza = jlib:iq_to_xml(#iq{type = set, - xmlns = ?NS_ROSTER, - id = <<"push", (randoms:get_string())/binary>>, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_ROSTER}], - children = [item_to_xml(Item)]}]}), + Stanza = #iq{type = set, id = <<"push", (randoms:get_string())/binary>>, + sub_els = [#roster_query{ + items = [mod_roster:encode_item(Item)]}]}, lists:foreach(fun (Resource) -> JID = jid:make(User, Server, Resource), ejabberd_router:route(jid:remove_resource(JID), JID, Stanza) @@ -752,38 +739,7 @@ push_roster_item(User, Server, ContactU, ContactS, groups = [GroupName]}, push_item(User, Server, Item). -item_to_xml(Item) -> - Attrs1 = [{<<"jid">>, - jid:to_string(Item#roster.jid)}], - Attrs2 = case Item#roster.name of - <<"">> -> Attrs1; - Name -> [{<<"name">>, Name} | Attrs1] - end, - Attrs3 = case Item#roster.subscription of - none -> [{<<"subscription">>, <<"none">>} | Attrs2]; - from -> [{<<"subscription">>, <<"from">>} | Attrs2]; - to -> [{<<"subscription">>, <<"to">>} | Attrs2]; - both -> [{<<"subscription">>, <<"both">>} | Attrs2]; - remove -> [{<<"subscription">>, <<"remove">>} | Attrs2] - end, - Attrs4 = case ask_to_pending(Item#roster.ask) of - out -> [{<<"ask">>, <<"subscribe">>} | Attrs3]; - both -> [{<<"ask">>, <<"subscribe">>} | Attrs3]; - _ -> Attrs3 - end, - SubEls1 = lists:map(fun (G) -> - #xmlel{name = <<"group">>, attrs = [], - children = [{xmlcdata, G}]} - end, - Item#roster.groups), - SubEls = SubEls1 ++ Item#roster.xs, - #xmlel{name = <<"item">>, attrs = Attrs4, - children = SubEls}. - -ask_to_pending(subscribe) -> out; -ask_to_pending(unsubscribe) -> none; -ask_to_pending(Ask) -> Ask. - +-spec user_available(jid()) -> ok. user_available(New) -> LUser = New#jid.luser, LServer = New#jid.lserver, @@ -807,6 +763,7 @@ user_available(New) -> _ -> ok end. +-spec unset_presence(binary(), binary(), binary(), binary()) -> ok. unset_presence(LUser, LServer, Resource, Status) -> Resources = ejabberd_sm:get_user_resources(LUser, LServer), @@ -850,7 +807,7 @@ webadmin_page(Acc, _, _) -> Acc. list_shared_roster_groups(Host, Query, Lang) -> Res = list_sr_groups_parse_query(Host, Query), - SRGroups = (?MODULE):list_groups(Host), + SRGroups = mod_shared_roster:list_groups(Host), FGroups = (?XAE(<<"table">>, [], [?XE(<<"tbody">>, (lists:map(fun (Group) -> @@ -901,15 +858,15 @@ list_sr_groups_parse_query(Host, Query) -> list_sr_groups_parse_addnew(Host, Query) -> case lists:keysearch(<<"namenew">>, 1, Query) of {value, {_, Group}} when Group /= <<"">> -> - (?MODULE):create_group(Host, Group), ok; + mod_shared_roster:create_group(Host, Group), ok; _ -> error end. list_sr_groups_parse_delete(Host, Query) -> - SRGroups = (?MODULE):list_groups(Host), + SRGroups = mod_shared_roster:list_groups(Host), lists:foreach(fun (Group) -> case lists:member({<<"selected">>, Group}, Query) of - true -> (?MODULE):delete_group(Host, Group); + true -> mod_shared_roster:delete_group(Host, Group); _ -> ok end end, @@ -919,14 +876,14 @@ list_sr_groups_parse_delete(Host, Query) -> shared_roster_group(Host, Group, Query, Lang) -> Res = shared_roster_group_parse_query(Host, Group, Query), - GroupOpts = (?MODULE):get_group_opts(Host, Group), + GroupOpts = mod_shared_roster:get_group_opts(Host, Group), Name = get_opt(GroupOpts, name, <<"">>), Description = get_opt(GroupOpts, description, <<"">>), AllUsers = get_opt(GroupOpts, all_users, false), OnlineUsers = get_opt(GroupOpts, online_users, false), DisplayedGroups = get_opt(GroupOpts, displayed_groups, []), - Members = (?MODULE):get_group_explicit_users(Host, + Members = mod_shared_roster:get_group_explicit_users(Host, Group), FMembers = iolist_to_binary( [if AllUsers -> <<"@all@\n">>; @@ -950,21 +907,21 @@ shared_roster_group(Host, Group, Query, Lang) -> [?XCT(<<"td">>, <<"Description:">>), ?XE(<<"td">>, [?TEXTAREA(<<"description">>, - jlib:integer_to_binary(lists:max([3, + integer_to_binary(lists:max([3, DescNL])), <<"20">>, Description)])]), ?XE(<<"tr">>, [?XCT(<<"td">>, <<"Members:">>), ?XE(<<"td">>, [?TEXTAREA(<<"members">>, - jlib:integer_to_binary(lists:max([3, + integer_to_binary(lists:max([3, byte_size(FMembers)])), <<"20">>, FMembers)])]), ?XE(<<"tr">>, [?XCT(<<"td">>, <<"Displayed Groups:">>), ?XE(<<"td">>, [?TEXTAREA(<<"dispgroups">>, - jlib:integer_to_binary(lists:max([3, length(FDisplayedGroups)])), + integer_to_binary(lists:max([3, length(FDisplayedGroups)])), <<"20">>, list_to_binary(FDisplayedGroups))])])])])), (?H1GL((?T(<<"Shared Roster Groups">>)), @@ -1003,7 +960,7 @@ shared_roster_group_parse_query(Host, Group, Query) -> DispGroupsOpt = if DispGroups == [] -> []; true -> [{displayed_groups, DispGroups}] end, - OldMembers = (?MODULE):get_group_explicit_users(Host, + OldMembers = mod_shared_roster:get_group_explicit_users(Host, Group), SJIDs = str:tokens(SMembers, <<", \r\n">>), NewMembers = lists:foldl(fun (_SJID, error) -> error; @@ -1040,7 +997,7 @@ shared_roster_group_parse_query(Host, Group, Query) -> RemovedDisplayedGroups = CurrentDisplayedGroups -- DispGroups, displayed_groups_update(OldMembers, RemovedDisplayedGroups, remove), displayed_groups_update(OldMembers, AddedDisplayedGroups, both), - (?MODULE):set_group_opts(Host, Group, + mod_shared_roster:set_group_opts(Host, Group, NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ @@ -1050,13 +1007,13 @@ shared_roster_group_parse_query(Host, Group, Query) -> AddedMembers = NewMembers -- OldMembers, RemovedMembers = OldMembers -- NewMembers, lists:foreach(fun (US) -> - (?MODULE):remove_user_from_group(Host, + mod_shared_roster:remove_user_from_group(Host, US, Group) end, RemovedMembers), lists:foreach(fun (US) -> - (?MODULE):add_user_to_group(Host, US, + mod_shared_roster:add_user_to_group(Host, US, Group) end, AddedMembers), @@ -1115,13 +1072,16 @@ export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). +import_info() -> + [{<<"sr_group">>, 3}, {<<"sr_user">>, 3}]. -import(LServer, DBType, Data) -> +import_start(LServer, DBType) -> Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, Data). + Mod:init(LServer, []). + +import(LServer, {sql, _}, DBType, Tab, L) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Tab, L). mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(_) -> [db_type]. diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index 22f50d302..97ead9f3d 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -45,7 +45,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_roster.hrl"). -include("eldap.hrl"). @@ -111,6 +111,7 @@ depends(_Host, _Opts) -> %%-------------------------------------------------------------------- %% Hooks %%-------------------------------------------------------------------- +-spec get_user_roster([#roster{}], {binary(), binary()}) -> [#roster{}]. get_user_roster(Items, {U, S} = US) -> SRUsers = get_user_to_groups_map(US, true), {NewItems1, SRUsersRest} = lists:mapfoldl(fun (Item, @@ -143,6 +144,7 @@ get_user_roster(Items, {U, S} = US) -> %% This function in use to rewrite the roster entries when moving or renaming %% them in the user contact list. +-spec process_item(#roster{}, binary()) -> #roster{}. process_item(RosterItem, _Host) -> USFrom = RosterItem#roster.us, {User, Server, _Resource} = RosterItem#roster.jid, @@ -158,6 +160,8 @@ process_item(RosterItem, _Host) -> _ -> RosterItem#roster{subscription = both, ask = none} end. +-spec get_subscription_lists({[ljid()], [ljid()]}, binary(), binary()) + -> {[ljid()], [ljid()]}. get_subscription_lists({F, T}, User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -170,6 +174,8 @@ get_subscription_lists({F, T}, User, Server) -> SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers], {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}. +-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid()) + -> {subscription(), [binary()]}. get_jid_info({Subscription, Groups}, User, Server, JID) -> LUser = jid:nodeprep(User), @@ -187,10 +193,16 @@ get_jid_info({Subscription, Groups}, User, Server, error -> {Subscription, Groups} end. +-spec in_subscription(boolean(), binary(), binary(), jid(), + subscribe | subscribed | unsubscribe | unsubscribed, + binary()) -> boolean(). in_subscription(Acc, User, Server, JID, Type, _Reason) -> process_subscription(in, User, Server, JID, Type, Acc). +-spec out_subscription( + binary(), binary(), jid(), + subscribed | unsubscribed | subscribe | unsubscribe) -> boolean(). out_subscription(User, Server, JID, Type) -> process_subscription(out, User, Server, JID, Type, false). diff --git a/src/mod_shared_roster_mnesia.erl b/src/mod_shared_roster_mnesia.erl index ca2e55e2f..ed4525041 100644 --- a/src/mod_shared_roster_mnesia.erl +++ b/src/mod_shared_roster_mnesia.erl @@ -15,21 +15,21 @@ delete_group/2, get_group_opts/2, set_group_opts/3, get_user_groups/2, get_group_explicit_users/2, get_user_displayed_groups/3, is_user_in_group/3, - add_user_to_group/3, remove_user_from_group/3, import/2]). + add_user_to_group/3, remove_user_from_group/3, import/3]). --include("jlib.hrl"). -include("mod_roster.hrl"). -include("mod_shared_roster.hrl"). -include("logger.hrl"). +-include("xmpp.hrl"). %%%=================================================================== %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(sr_group, + ejabberd_mnesia:create(?MODULE, sr_group, [{disc_copies, [node()]}, {attributes, record_info(fields, sr_group)}]), - mnesia:create_table(sr_user, + ejabberd_mnesia:create(?MODULE, sr_user, [{disc_copies, [node()]}, {type, bag}, {attributes, record_info(fields, sr_user)}]), update_tables(), @@ -119,10 +119,14 @@ remove_user_from_group(Host, US, Group) -> F = fun () -> mnesia:delete_object(R) end, mnesia:transaction(F). -import(_LServer, #sr_group{} = G) -> +import(LServer, <<"sr_group">>, [Group, SOpts, _TimeStamp]) -> + G = #sr_group{group_host = {Group, LServer}, + opts = ejabberd_sql:decode_term(SOpts)}, mnesia:dirty_write(G); -import(_LServer, #sr_user{} = U) -> - mnesia:dirty_write(U). +import(LServer, <<"sr_user">>, [SJID, Group, _TimeStamp]) -> + #jid{luser = U, lserver = S} = jid:from_string(SJID), + User = #sr_user{us = {U, S}, group_host = {Group, LServer}}, + mnesia:dirty_write(User). %%%=================================================================== %%% Internal functions diff --git a/src/mod_shared_roster_riak.erl b/src/mod_shared_roster_riak.erl index 0df35e37d..bdb750981 100644 --- a/src/mod_shared_roster_riak.erl +++ b/src/mod_shared_roster_riak.erl @@ -15,11 +15,11 @@ delete_group/2, get_group_opts/2, set_group_opts/3, get_user_groups/2, get_group_explicit_users/2, get_user_displayed_groups/3, is_user_in_group/3, - add_user_to_group/3, remove_user_from_group/3, import/2]). + add_user_to_group/3, remove_user_from_group/3, import/3]). --include("jlib.hrl"). -include("mod_roster.hrl"). -include("mod_shared_roster.hrl"). +-include("xmpp.hrl"). %%%=================================================================== %%% API @@ -121,13 +121,17 @@ add_user_to_group(Host, US, Group) -> remove_user_from_group(Host, US, Group) -> {atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})}. -import(_LServer, #sr_group{group_host = {_, Host}} = G) -> - ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]); -import(_LServer, #sr_user{us = US, group_host = {Group, Host}} = User) -> +import(LServer, <<"sr_group">>, [Group, SOpts, _TimeStamp]) -> + G = #sr_group{group_host = {Group, LServer}, + opts = ejabberd_sql:decode_term(SOpts)}, + ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, LServer}]}]); +import(LServer, <<"sr_user">>, [SJID, Group|_]) -> + #jid{luser = U, lserver = S} = jid:from_string(SJID), + User = #sr_user{us = {U, S}, group_host = {Group, LServer}}, ejabberd_riak:put(User, sr_user_schema(), - [{i, {US, {Group, Host}}}, - {'2i', [{<<"us">>, US}, - {<<"group_host">>, {Group, Host}}]}]). + [{i, {{U, S}, {Group, LServer}}}, + {'2i', [{<<"us">>, {U, S}}, + {<<"group_host">>, {Group, LServer}}]}]). %%%=================================================================== %%% Internal functions diff --git a/src/mod_shared_roster_sql.erl b/src/mod_shared_roster_sql.erl index 2b186fd0b..9f723f839 100644 --- a/src/mod_shared_roster_sql.erl +++ b/src/mod_shared_roster_sql.erl @@ -17,10 +17,10 @@ delete_group/2, get_group_opts/2, set_group_opts/3, get_user_groups/2, get_group_explicit_users/2, get_user_displayed_groups/3, is_user_in_group/3, - add_user_to_group/3, remove_user_from_group/3, import/1, - import/2, export/1]). + add_user_to_group/3, remove_user_from_group/3, import/3, + export/1]). --include("jlib.hrl"). +-include("jid.hrl"). -include("mod_roster.hrl"). -include("mod_shared_roster.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -177,20 +177,8 @@ export(_Server) -> [] end}]. -import(LServer) -> - [{<<"select name, opts from sr_group;">>, - fun([Group, SOpts]) -> - #sr_group{group_host = {Group, LServer}, - opts = ejabberd_sql:decode_term(SOpts)} - end}, - {<<"select jid, grp from sr_user;">>, - fun([SJID, Group]) -> - #jid{luser = U, lserver = S} = jid:from_string(SJID), - #sr_user{us = {U, S}, group_host = {Group, LServer}} - end}]. - -import(_, _) -> - pass. +import(_, _, _) -> + ok. %%%=================================================================== %%% Internal functions diff --git a/src/mod_sic.erl b/src/mod_sic.erl index 49b65a0ee..4bb4eb9eb 100644 --- a/src/mod_sic.erl +++ b/src/mod_sic.erl @@ -31,73 +31,66 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_local_iq/3, - process_sm_iq/3, mod_opt_type/1, depends/2]). +-export([start/2, stop/1, process_local_iq/1, + process_sm_iq/1, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). - --define(NS_SIC, <<"urn:xmpp:sic:0">>). +-include("xmpp.hrl"). start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, - ?NS_SIC, ?MODULE, process_local_iq, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, - ?NS_SIC, ?MODULE, process_sm_iq, IQDisc). + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_SIC_0, + ?MODULE, process_local_iq, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_SIC_0, + ?MODULE, process_sm_iq, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_SIC_1, + ?MODULE, process_local_iq, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_SIC_1, + ?MODULE, process_sm_iq, IQDisc). stop(Host) -> - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, - ?NS_SIC), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, - ?NS_SIC). + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SIC_0), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_SIC_0), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SIC_1), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_SIC_1). depends(_Host, _Opts) -> []. -process_local_iq(#jid{user = User, server = Server, - resource = Resource}, - _To, #iq{type = get, sub_el = _SubEl} = IQ) -> +process_local_iq(#iq{from = #jid{user = User, server = Server, + resource = Resource}, + type = get} = IQ) -> get_ip({User, Server, Resource}, IQ); -process_local_iq(_From, _To, - #iq{type = set, sub_el = SubEl, lang = Lang} = IQ) -> +process_local_iq(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}. + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). -process_sm_iq(#jid{user = User, server = Server, - resource = Resource}, - #jid{user = User, server = Server}, - #iq{type = get, sub_el = _SubEl} = IQ) -> +process_sm_iq(#iq{from = #jid{user = User, server = Server, + resource = Resource}, + to = #jid{user = User, server = Server}, + type = get} = IQ) -> get_ip({User, Server, Resource}, IQ); -process_sm_iq(_From, _To, - #iq{type = get, sub_el = SubEl, lang = Lang} = IQ) -> +process_sm_iq(#iq{type = get, lang = Lang} = IQ) -> Txt = <<"Query to another users is forbidden">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]}; -process_sm_iq(_From, _To, - #iq{type = set, sub_el = SubEl, lang = Lang} = IQ) -> + xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)); +process_sm_iq(#iq{type = set, lang = Lang} = IQ) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}. + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). get_ip({User, Server, Resource}, - #iq{lang = Lang, - sub_el = - #xmlel{name = Name, attrs = Attrs} = SubEl} = - IQ) -> + #iq{lang = Lang, sub_els = [#sic{xmlns = NS}]} = IQ) -> case ejabberd_sm:get_user_ip(User, Server, Resource) of - {IP, _} when is_tuple(IP) -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = Name, attrs = Attrs, - children = - [{xmlcdata, - iolist_to_binary(jlib:ip_to_list(IP))}]}]}; - _ -> - Txt = <<"User session not found">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]} + {IP, Port} when is_tuple(IP) -> + Result = case NS of + ?NS_SIC_0 -> #sic{ip = IP, xmlns = NS}; + ?NS_SIC_1 -> #sic{ip = IP, port = Port, xmlns = NS} + end, + xmpp:make_iq_result(IQ, Result); + _ -> + Txt = <<"User session not found">>, + xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) end. mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl index 04ae55ae7..06daf0810 100644 --- a/src/mod_sip_proxy.erl +++ b/src/mod_sip_proxy.erl @@ -299,7 +299,7 @@ add_record_route_and_set_uri(URI, LServer, #sip{hdrs = Hdrs} = Req) -> case need_record_route(LServer) of true -> RR_URI = get_configured_record_route(LServer), - TS = list_to_binary(integer_to_list(p1_time_compat:system_time(seconds))), + TS = (integer_to_binary(p1_time_compat:system_time(seconds))), Sign = make_sign(TS, Hdrs), User = <>, NewRR_URI = RR_URI#uri{user = User}, @@ -339,7 +339,7 @@ make_sign(TS, Hdrs) -> is_signed_by_me(TS_Sign, Hdrs) -> try [TSBin, Sign] = str:tokens(TS_Sign, <<"-">>), - TS = list_to_integer(binary_to_list(TSBin)), + TS = (binary_to_integer(TSBin)), NowTS = p1_time_compat:system_time(seconds), true = (NowTS - TS) =< ?SIGN_LIFETIME, Sign == make_sign(TSBin, Hdrs) diff --git a/src/mod_sip_registrar.erl b/src/mod_sip_registrar.erl index fcaa42365..4ae8e077b 100644 --- a/src/mod_sip_registrar.erl +++ b/src/mod_sip_registrar.erl @@ -179,7 +179,7 @@ ping(SIPSocket) -> %%%=================================================================== init([]) -> update_table(), - mnesia:create_table(sip_session, + ejabberd_mnesia:create(?MODULE, sip_session, [{ram_copies, [node()]}, {type, bag}, {attributes, record_info(fields, sip_session)}]), @@ -355,7 +355,7 @@ min_expires() -> 60. to_integer(Bin, Min, Max) -> - case catch list_to_integer(binary_to_list(Bin)) of + case catch (binary_to_integer(Bin)) of N when N >= Min, N =< Max -> {ok, N}; _ -> diff --git a/src/mod_stats.erl b/src/mod_stats.erl index 66bbb5b5b..e43409e06 100644 --- a/src/mod_stats.erl +++ b/src/mod_stats.erl @@ -31,65 +31,39 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_local_iq/3, - mod_opt_type/1, depends/2]). +-export([start/2, stop/1, process_iq/1, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, - ?NS_STATS, ?MODULE, process_local_iq, IQDisc). + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_STATS, + ?MODULE, process_iq, IQDisc). stop(Host) -> - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, - ?NS_STATS). + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_STATS). depends(_Host, _Opts) -> []. -process_local_iq(_From, To, - #iq{id = _ID, type = Type, xmlns = XMLNS, - sub_el = SubEl, lang = Lang} = - IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - #xmlel{children = Els} = SubEl, - Node = str:tokens(fxml:get_tag_attr_s(<<"node">>, SubEl), - <<"/">>), - Names = get_names(Els, []), - case get_local_stats(To#jid.server, Node, Names, Lang) of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, XMLNS}], - children = Res}]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end +process_iq(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_iq(#iq{type = get, to = To, lang = Lang, + sub_els = [#stats{} = Stats]} = IQ) -> + Node = str:tokens(Stats#stats.node, <<"/">>), + Names = [Name || #stat{name = Name} <- Stats#stats.list], + case get_local_stats(To#jid.server, Node, Names, Lang) of + {result, List} -> + xmpp:make_iq_result(IQ, Stats#stats{list = List}); + {error, Error} -> + xmpp:make_error(IQ, Error) end. -get_names([], Res) -> Res; -get_names([#xmlel{name = <<"stat">>, attrs = Attrs} - | Els], - Res) -> - Name = fxml:get_attr_s(<<"name">>, Attrs), - case Name of - <<"">> -> get_names(Els, Res); - _ -> get_names(Els, [Name | Res]) - end; -get_names([_ | Els], Res) -> get_names(Els, Res). - --define(STAT(Name), - #xmlel{name = <<"stat">>, attrs = [{<<"name">>, Name}], - children = []}). +-define(STAT(Name), #stat{name = Name}). get_local_stats(_Server, [], [], _Lang) -> {result, @@ -115,7 +89,7 @@ get_local_stats(_Server, [<<"running nodes">>, ENode], case search_running_node(ENode) of false -> Txt = <<"No running node found">>, - {error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)}; + {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> {result, lists:map(fun (Name) -> get_node_stat(Node, Name) end, @@ -123,29 +97,21 @@ get_local_stats(_Server, [<<"running nodes">>, ENode], end; get_local_stats(_Server, _, _, Lang) -> Txt = <<"No statistics found for this item">>, - {error, ?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)}. + {error, xmpp:err_feature_not_implemented(Txt, Lang)}. --define(STATVAL(Val, Unit), - #xmlel{name = <<"stat">>, - attrs = - [{<<"name">>, Name}, {<<"units">>, Unit}, - {<<"value">>, Val}], - children = []}). +-define(STATVAL(Val, Unit), #stat{name = Name, units = Unit, value = Val}). -define(STATERR(Code, Desc), - #xmlel{name = <<"stat">>, attrs = [{<<"name">>, Name}], - children = - [#xmlel{name = <<"error">>, - attrs = [{<<"code">>, Code}], - children = [{xmlcdata, Desc}]}]}). + #stat{name = Name, + error = #stat_error{code = Code, reason = Desc}}). get_local_stat(Server, [], Name) when Name == <<"users/online">> -> case catch ejabberd_sm:get_vh_session_list(Server) of {'EXIT', _Reason} -> - ?STATERR(<<"500">>, <<"Internal Server Error">>); + ?STATERR(500, <<"Internal Server Error">>); Users -> - ?STATVAL((iolist_to_binary(integer_to_list(length(Users)))), + ?STATVAL((integer_to_binary(length(Users))), <<"users">>) end; get_local_stat(Server, [], Name) @@ -154,15 +120,15 @@ get_local_stat(Server, [], Name) ejabberd_auth:get_vh_registered_users_number(Server) of {'EXIT', _Reason} -> - ?STATERR(<<"500">>, <<"Internal Server Error">>); + ?STATERR(500, <<"Internal Server Error">>); NUsers -> - ?STATVAL((iolist_to_binary(integer_to_list(NUsers))), + ?STATVAL((integer_to_binary(NUsers)), <<"users">>) end; get_local_stat(_Server, [], Name) when Name == <<"users/all-hosts/online">> -> Users = ejabberd_sm:connected_users_number(), - ?STATVAL((iolist_to_binary(integer_to_list(Users))), <<"users">>); + ?STATVAL((integer_to_binary(Users)), <<"users">>); get_local_stat(_Server, [], Name) when Name == <<"users/all-hosts/total">> -> NumUsers = lists:foldl(fun (Host, Total) -> @@ -170,10 +136,10 @@ get_local_stat(_Server, [], Name) + Total end, 0, ?MYHOSTS), - ?STATVAL((iolist_to_binary(integer_to_list(NumUsers))), + ?STATVAL((integer_to_binary(NumUsers)), <<"users">>); get_local_stat(_Server, _, Name) -> - ?STATERR(<<"404">>, <<"Not Found">>). + ?STATERR(404, <<"Not Found">>). get_node_stat(Node, Name) when Name == <<"time/uptime">> -> @@ -181,11 +147,9 @@ get_node_stat(Node, Name) [wall_clock]) of {badrpc, _Reason} -> - ?STATERR(<<"500">>, <<"Internal Server Error">>); + ?STATERR(500, <<"Internal Server Error">>); CPUTime -> - ?STATVAL(list_to_binary( - io_lib:format("~.3f", - [element(1, CPUTime) / 1000])), + ?STATVAL(str:format("~.3f", [element(1, CPUTime) / 1000]), <<"seconds">>) end; get_node_stat(Node, Name) @@ -193,11 +157,9 @@ get_node_stat(Node, Name) case catch ejabberd_cluster:call(Node, erlang, statistics, [runtime]) of {badrpc, _Reason} -> - ?STATERR(<<"500">>, <<"Internal Server Error">>); + ?STATERR(500, <<"Internal Server Error">>); RunTime -> - ?STATVAL(list_to_binary( - io_lib:format("~.3f", - [element(1, RunTime) / 1000])), + ?STATVAL(str:format("~.3f", [element(1, RunTime) / 1000]), <<"seconds">>) end; get_node_stat(Node, Name) @@ -206,9 +168,9 @@ get_node_stat(Node, Name) dirty_get_my_sessions_list, []) of {badrpc, _Reason} -> - ?STATERR(<<"500">>, <<"Internal Server Error">>); + ?STATERR(500, <<"Internal Server Error">>); Users -> - ?STATVAL((iolist_to_binary(integer_to_list(length(Users)))), + ?STATVAL((integer_to_binary(length(Users))), <<"users">>) end; get_node_stat(Node, Name) @@ -217,9 +179,9 @@ get_node_stat(Node, Name) [transaction_commits]) of {badrpc, _Reason} -> - ?STATERR(<<"500">>, <<"Internal Server Error">>); + ?STATERR(500, <<"Internal Server Error">>); Transactions -> - ?STATVAL((iolist_to_binary(integer_to_list(Transactions))), + ?STATVAL((integer_to_binary(Transactions)), <<"transactions">>) end; get_node_stat(Node, Name) @@ -228,9 +190,9 @@ get_node_stat(Node, Name) [transaction_failures]) of {badrpc, _Reason} -> - ?STATERR(<<"500">>, <<"Internal Server Error">>); + ?STATERR(500, <<"Internal Server Error">>); Transactions -> - ?STATVAL((iolist_to_binary(integer_to_list(Transactions))), + ?STATVAL((integer_to_binary(Transactions)), <<"transactions">>) end; get_node_stat(Node, Name) @@ -239,9 +201,9 @@ get_node_stat(Node, Name) [transaction_restarts]) of {badrpc, _Reason} -> - ?STATERR(<<"500">>, <<"Internal Server Error">>); + ?STATERR(500, <<"Internal Server Error">>); Transactions -> - ?STATVAL((iolist_to_binary(integer_to_list(Transactions))), + ?STATVAL((integer_to_binary(Transactions)), <<"transactions">>) end; get_node_stat(Node, Name) @@ -250,13 +212,13 @@ get_node_stat(Node, Name) [transaction_log_writes]) of {badrpc, _Reason} -> - ?STATERR(<<"500">>, <<"Internal Server Error">>); + ?STATERR(500, <<"Internal Server Error">>); Transactions -> - ?STATVAL((iolist_to_binary(integer_to_list(Transactions))), + ?STATVAL((integer_to_binary(Transactions)), <<"transactions">>) end; get_node_stat(_, Name) -> - ?STATERR(<<"404">>, <<"Not Found">>). + ?STATERR(404, <<"Not Found">>). search_running_node(SNode) -> search_running_node(SNode, diff --git a/src/mod_time.erl b/src/mod_time.erl index 90296f3d8..0aeb6831c 100644 --- a/src/mod_time.erl +++ b/src/mod_time.erl @@ -32,13 +32,13 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_local_iq/3, +-export([start/2, stop/1, process_local_iq/1, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, @@ -50,41 +50,18 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_TIME). -process_local_iq(_From, _To, - #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - Now_universal = calendar:universal_time(), - Now_local = calendar:universal_time_to_local_time(Now_universal), - {UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal, - utc), - Seconds_diff = - calendar:datetime_to_gregorian_seconds(Now_local) - - calendar:datetime_to_gregorian_seconds(Now_universal), - {Hd, Md, _} = - calendar:seconds_to_time(abs(Seconds_diff)), - {_, TZO_diff} = jlib:timestamp_to_iso({{0, 1, 1}, - {0, 0, 0}}, - {sign(Seconds_diff), {Hd, Md}}), - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"time">>, - attrs = [{<<"xmlns">>, ?NS_TIME}], - children = - [#xmlel{name = <<"tzo">>, attrs = [], - children = [{xmlcdata, TZO_diff}]}, - #xmlel{name = <<"utc">>, attrs = [], - children = - [{xmlcdata, - <>}]}]}]} - end. - -sign(N) when N < 0 -> <<"-">>; -sign(_) -> <<"+">>. +process_local_iq(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_local_iq(#iq{type = get} = IQ) -> + Now = p1_time_compat:timestamp(), + Now_universal = calendar:now_to_universal_time(Now), + Now_local = calendar:universal_time_to_local_time(Now_universal), + Seconds_diff = + calendar:datetime_to_gregorian_seconds(Now_local) - + calendar:datetime_to_gregorian_seconds(Now_universal), + {Hd, Md, _} = calendar:seconds_to_time(abs(Seconds_diff)), + xmpp:make_iq_result(IQ, #time{tzo = {Hd, Md}, utc = Now}). depends(_Host, _Opts) -> []. diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index aca9d7462..843281ef8 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -33,13 +33,15 @@ -behaviour(gen_mod). -export([start/2, init/3, stop/1, get_sm_features/5, - process_local_iq/3, process_sm_iq/3, string2lower/1, - remove_user/2, export/1, import/1, import/3, depends/2, - mod_opt_type/1, set_vcard/3, make_vcard_search/4]). + process_local_iq/1, process_sm_iq/1, string2lower/1, + remove_user/2, export/1, import_info/0, import/5, import_start/2, + depends/2, process_search/1, process_vcard/1, get_vcard/2, + disco_items/5, disco_features/5, disco_identity/5, + decode_iq_subel/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_vcard.hrl"). -define(JUD_MATCHES, 30). @@ -47,13 +49,17 @@ -define(PROCNAME, ejabberd_mod_vcard). -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #vcard{} | #vcard_search{}) -> ok | pass. +-callback stop(binary()) -> any(). +-callback import(binary(), binary(), [binary()]) -> ok. -callback get_vcard(binary(), binary()) -> [xmlel()] | error. -callback set_vcard(binary(), binary(), xmlel(), #vcard_search{}) -> {atomic, any()}. +-callback search_fields(binary()) -> [{binary(), binary()}]. +-callback search_reported(binary()) -> [{binary(), binary()}]. -callback search(binary(), [{binary(), [binary()]}], boolean(), - infinity | pos_integer()) -> [binary()]. + infinity | pos_integer()) -> [{binary(), binary()}]. -callback remove_user(binary(), binary()) -> {atomic, any()}. +-callback is_search_supported(binary()) -> boolean(). start(Host, Opts) -> Mod = gen_mod:db_mod(Host, Opts, ?MODULE), @@ -68,11 +74,30 @@ start(Host, Opts) -> ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc), ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), - MyHost = gen_mod:get_opt_host(Host, Opts, - <<"vjud.@HOST@">>), + MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>), Search = gen_mod:get_opt(search, Opts, fun(B) when is_boolean(B) -> B end, false), + if Search -> + ejabberd_hooks:add( + disco_local_items, MyHost, ?MODULE, disco_items, 100), + ejabberd_hooks:add( + disco_local_features, MyHost, ?MODULE, disco_features, 100), + ejabberd_hooks:add( + disco_local_identity, MyHost, ?MODULE, disco_identity, 100), + gen_iq_handler:add_iq_handler( + ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc), + gen_iq_handler:add_iq_handler( + ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler( + ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco, + process_local_iq_items, IQDisc), + gen_iq_handler:add_iq_handler( + ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco, + process_local_iq_info, IQDisc); + true -> + ok + end, register(gen_mod:get_module_proc(Host, ?PROCNAME), spawn(?MODULE, init, [MyHost, Host, Search])). @@ -81,18 +106,35 @@ init(Host, ServerHost, Search) -> false -> loop(Host, ServerHost); _ -> ejabberd_router:register_route(Host, ServerHost), + Mod = gen_mod:db_mod(ServerHost, ?MODULE), + case Mod:is_search_supported(ServerHost) of + false -> + ?WARNING_MSG("vcard search functionality is " + "not implemented for ~s backend", + [gen_mod:db_type(ServerHost, ?MODULE)]); + true -> + ejabberd_router:register_route(Host, ServerHost) + end, loop(Host, ServerHost) end. loop(Host, ServerHost) -> receive {route, From, To, Packet} -> - case catch do_route(ServerHost, From, To, Packet) of + case catch do_route(From, To, Packet) of {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); _ -> ok end, loop(Host, ServerHost); - stop -> ejabberd_router:unregister_route(Host), ok; + stop -> + ejabberd_router:unregister_route(Host), + ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100), + ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100), + ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SEARCH), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO); _ -> loop(Host, ServerHost) end. @@ -105,10 +147,22 @@ stop(Host) -> ?NS_VCARD), ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50), + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:stop(Host), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc ! stop, {wait, Proc}. +do_route(From, To, #xmlel{name = <<"iq">>} = El) -> + ejabberd_router:process_iq(From, To, El); +do_route(From, To, #iq{} = IQ) -> + ejabberd_router:process_iq(From, To, IQ); +do_route(_, _, _) -> + ok. + +-spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]}, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | empty | {result, [binary()]}. get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> Acc; @@ -123,67 +177,118 @@ get_sm_features(Acc, _From, _To, Node, _Lang) -> _ -> Acc end. -process_local_iq(_From, _To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, ?NS_VCARD}], - children = - [#xmlel{name = <<"FN">>, attrs = [], - children = - [{xmlcdata, <<"ejabberd">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Erlang Jabber Server">>))/binary, - "\nCopyright (c) 2002-2016 ProcessOne">>}]}, - #xmlel{name = <<"BDAY">>, attrs = [], - children = - [{xmlcdata, <<"2002-11-16">>}]}]}]} +-spec decode_iq_subel(xmpp_element() | xmlel()) -> xmpp_element() | xmlel(). +%% Tell gen_iq_handler not to decode vcard elements +decode_iq_subel(El) -> + case xmpp:get_ns(El) of + ?NS_VCARD -> xmpp:encode(El); + _ -> xmpp:decode(El) end. -process_sm_iq(From, To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - #jid{user = User, lserver = LServer} = From, - case lists:member(LServer, ?MYHOSTS) of - true -> - set_vcard(User, LServer, SubEl), - IQ#iq{type = result, sub_el = []}; - false -> - Txt = <<"The query is only allowed from local users">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]} - end; - get -> - #jid{luser = LUser, lserver = LServer} = To, - case get_vcard(LUser, LServer) of - error -> - Txt = <<"Database failure">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}; - [] -> - IQ#iq{type = result, - sub_el = [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, ?NS_VCARD}], - children = []}]}; - Els -> IQ#iq{type = result, sub_el = Els} - end +-spec process_local_iq(iq()) -> iq(). +process_local_iq(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_local_iq(#iq{type = get, lang = Lang} = IQ) -> + Desc = translate:translate(Lang, <<"Erlang Jabber Server">>), + xmpp:make_iq_result( + IQ, #vcard_temp{fn = <<"ejabberd">>, + url = ?EJABBERD_URI, + desc = <>, + bday = <<"2002-11-16">>}). + +-spec process_sm_iq(iq()) -> iq(). +process_sm_iq(#iq{type = set, lang = Lang, from = From, + sub_els = [SubEl]} = IQ) -> + #jid{user = User, lserver = LServer} = From, + case lists:member(LServer, ?MYHOSTS) of + true -> + set_vcard(User, LServer, SubEl), + xmpp:make_iq_result(IQ); + false -> + Txt = <<"The query is only allowed from local users">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)) + end; +process_sm_iq(#iq{type = get, from = From, to = To, lang = Lang} = IQ) -> + #jid{luser = LUser, lserver = LServer} = To, + case get_vcard(LUser, LServer) of + error -> + Txt = <<"Database failure">>, + xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)); + [] -> + xmpp:make_iq_result(IQ, #vcard_temp{}); + Els -> + IQ#iq{type = result, to = From, from = To, sub_els = Els} end. +-spec process_vcard(iq()) -> iq(). +process_vcard(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_vcard(#iq{type = get, lang = Lang} = IQ) -> + Desc = translate:translate(Lang, <<"ejabberd vCard module">>), + xmpp:make_iq_result( + IQ, #vcard_temp{fn = <<"ejabberd/mod_vcard">>, + url = ?EJABBERD_URI, + desc = <>}). + +-spec process_search(iq()) -> iq(). +process_search(#iq{type = get, to = To, lang = Lang} = IQ) -> + ServerHost = ejabberd_router:host_of_route(To#jid.lserver), + xmpp:make_iq_result(IQ, mk_search_form(To, ServerHost, Lang)); +process_search(#iq{type = set, to = To, lang = Lang, + sub_els = [#search{xdata = #xdata{type = submit, + fields = Fs}}]} = IQ) -> + ServerHost = ejabberd_router:host_of_route(To#jid.lserver), + ResultXData = search_result(Lang, To, ServerHost, Fs), + xmpp:make_iq_result(IQ, #search{xdata = ResultXData}); +process_search(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Incorrect data form">>, + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)). + +-spec disco_items({error, stanza_error()} | {result, [disco_item()]} | empty, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | {result, [disco_item()]}. +disco_items(empty, _From, _To, <<"">>, _Lang) -> + {result, []}; +disco_items(empty, _From, _To, _Node, Lang) -> + {error, xmpp:err_item_not_found(<<"No services available">>, Lang)}; +disco_items(Acc, _From, _To, _Node, _Lang) -> + Acc. + +-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty, + jid(), jid(), binary(), binary()) -> + {error, stanza_error()} | {result, [binary()]}. +disco_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> + Acc; +disco_features(Acc, _From, _To, <<"">>, _Lang) -> + Features = case Acc of + {result, Fs} -> Fs; + empty -> [] + end, + {result, [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, + ?NS_VCARD, ?NS_SEARCH | Features]}; +disco_features(empty, _From, _To, _Node, Lang) -> + Txt = <<"No features available">>, + {error, xmpp:err_item_not_found(Txt, Lang)}; +disco_features(Acc, _From, _To, _Node, _Lang) -> + Acc. + +-spec disco_identity([identity()], jid(), jid(), + binary(), binary()) -> [identity()]. +disco_identity(Acc, _From, _To, <<"">>, Lang) -> + [#identity{category = <<"directory">>, + type = <<"user">>, + name = translate:translate(Lang, <<"vCard User Search">>)}|Acc]; +disco_identity(Acc, _From, _To, _Node, _Lang) -> + Acc. + +-spec get_vcard(binary(), binary()) -> [xmlel()] | error. get_vcard(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_vcard(LUser, LServer). +-spec make_vcard_search(binary(), binary(), binary(), xmlel()) -> #vcard_search{}. make_vcard_search(User, LUser, LServer, VCARD) -> FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]), Family = fxml:get_path_s(VCARD, @@ -250,6 +355,7 @@ make_vcard_search(User, LUser, LServer, VCARD) -> orgunit = OrgUnit, lorgunit = LOrgUnit}. +-spec set_vcard(binary(), binary(), xmlel()) -> {error, badarg} | ok. set_vcard(User, LServer, VCARD) -> case jid:nodeprep(User) of error -> @@ -262,307 +368,64 @@ set_vcard(User, LServer, VCARD) -> [LUser, LServer, VCARD]) end. +-spec string2lower(binary()) -> binary(). string2lower(String) -> case stringprep:tolower(String) of Lower when is_binary(Lower) -> Lower; error -> str:to_lower(String) end. --define(TLFIELD(Type, Label, Var), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = []}). +-spec mk_tfield(binary(), binary(), binary()) -> xdata_field(). +mk_tfield(Label, Var, Lang) -> + #xdata_field{type = 'text-single', + label = translate:translate(Lang, Label), + var = Var}. --define(FORM(JID), - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need an x:data capable client to " - "search">>)}]}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Search users in ">>))/binary, - (jid:to_string(JID))/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Fill in the form to search for any matching " - "Jabber User (Add * to the end of field " - "to match substring)">>)}]}, - ?TLFIELD(<<"text-single">>, <<"User">>, <<"user">>), - ?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>), - ?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>), - ?TLFIELD(<<"text-single">>, <<"Middle Name">>, - <<"middle">>), - ?TLFIELD(<<"text-single">>, <<"Family Name">>, - <<"last">>), - ?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>), - ?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>), - ?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>), - ?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>), - ?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>), - ?TLFIELD(<<"text-single">>, <<"Organization Name">>, - <<"orgname">>), - ?TLFIELD(<<"text-single">>, <<"Organization Unit">>, - <<"orgunit">>)]}]). +-spec mk_field(binary(), binary()) -> xdata_field(). +mk_field(Var, Val) -> + #xdata_field{var = Var, values = [Val]}. -do_route(ServerHost, From, To, Packet) -> - #jid{user = User, resource = Resource} = To, - if (User /= <<"">>) or (Resource /= <<"">>) -> - Err = jlib:make_error_reply(Packet, - ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err); - true -> - IQ = jlib:iq_query_info(Packet), - case IQ of - #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang, - sub_el = SubEl} -> - case Type of - set -> - XDataEl = find_xdata_el(SubEl), - case XDataEl of - false -> - Txt = <<"Data form not found">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_router:route(To, From, Err); - _ -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - Txt = <<"Incorrect data form">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_router:route(To, From, Err); - _ -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_SEARCH}], - children = - [#xmlel{name = - <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_XDATA}, - {<<"type">>, - <<"result">>}], - children - = - search_result(Lang, - To, - ServerHost, - XData)}]}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(ResIQ)) - end - end; - get -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_SEARCH}], - children = ?FORM(To)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)) - end; - #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)), - ejabberd_router:route(To, From, Err); - get -> - Info = ejabberd_hooks:run_fold(disco_info, ServerHost, - [], - [ServerHost, ?MODULE, - <<"">>, <<"">>]), - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_DISCO_INFO}], - children = - [#xmlel{name = - <<"identity">>, - attrs = - [{<<"category">>, - <<"directory">>}, - {<<"type">>, - <<"user">>}, - {<<"name">>, - translate:translate(Lang, - <<"vCard User Search">>)}], - children = []}, - #xmlel{name = - <<"feature">>, - attrs = - [{<<"var">>, - ?NS_DISCO_INFO}], - children = []}, - #xmlel{name = - <<"feature">>, - attrs = - [{<<"var">>, - ?NS_SEARCH}], - children = []}, - #xmlel{name = - <<"feature">>, - attrs = - [{<<"var">>, - ?NS_VCARD}], - children = []}] - ++ Info}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)) - end; - #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)), - ejabberd_router:route(To, From, Err); - get -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_DISCO_ITEMS}], - children = []}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)) - end; - #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, ?NS_VCARD}], - children = iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err) - end - end. +-spec mk_search_form(jid(), binary(), binary()) -> search(). +mk_search_form(JID, ServerHost, Lang) -> + Title = <<(translate:translate(Lang, <<"Search users in ">>))/binary, + (jid:to_string(JID))/binary>>, + Mod = gen_mod:db_mod(ServerHost, ?MODULE), + SearchFields = Mod:search_fields(ServerHost), + Fs = [mk_tfield(Label, Var, Lang) || {Label, Var} <- SearchFields], + X = #xdata{type = form, + title = Title, + instructions = + [translate:translate( + Lang, + <<"Fill in the form to search for any matching " + "Jabber User (Add * to the end of field " + "to match substring)">>)], + fields = Fs}, + #search{instructions = + translate:translate( + Lang, <<"You need an x:data capable client to search">>), + xdata = X}. -iq_get_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd vCard module">>))/binary, - "\nCopyright (c) 2003-2016 ProcessOne">>}]}]. +-spec search_result(binary(), jid(), binary(), [xdata_field()]) -> xdata(). +search_result(Lang, JID, ServerHost, XFields) -> + Mod = gen_mod:db_mod(ServerHost, ?MODULE), + Reported = [mk_tfield(Label, Var, Lang) || + {Label, Var} <- Mod:search_reported(ServerHost)], + #xdata{type = result, + title = <<(translate:translate(Lang, + <<"Search Results for ">>))/binary, + (jid:to_string(JID))/binary>>, + reported = Reported, + items = lists:map(fun (Item) -> item_to_field(Item) end, + search(ServerHost, XFields))}. -find_xdata_el(#xmlel{children = SubEls}) -> - find_xdata_el1(SubEls). +-spec item_to_field([{binary(), binary()}]) -> [xdata_field()]. +item_to_field(Items) -> + [mk_field(Var, Value) || {Var, Value} <- Items]. -find_xdata_el1([]) -> false; -find_xdata_el1([#xmlel{name = Name, attrs = Attrs, - children = SubEls} - | Els]) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - #xmlel{name = Name, attrs = Attrs, children = SubEls}; - _ -> find_xdata_el1(Els) - end; -find_xdata_el1([_ | Els]) -> find_xdata_el1(Els). - --define(LFIELD(Label, Var), - #xmlel{name = <<"field">>, - attrs = - [{<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = []}). - -search_result(Lang, JID, ServerHost, Data) -> - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Search Results for ">>))/binary, - (jid:to_string(JID))/binary>>}]}, - #xmlel{name = <<"reported">>, attrs = [], - children = - [?TLFIELD(<<"text-single">>, <<"Jabber ID">>, - <<"jid">>), - ?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>), - ?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>), - ?TLFIELD(<<"text-single">>, <<"Middle Name">>, - <<"middle">>), - ?TLFIELD(<<"text-single">>, <<"Family Name">>, - <<"last">>), - ?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>), - ?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>), - ?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>), - ?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>), - ?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>), - ?TLFIELD(<<"text-single">>, <<"Organization Name">>, - <<"orgname">>), - ?TLFIELD(<<"text-single">>, <<"Organization Unit">>, - <<"orgunit">>)]}] - ++ - lists:map(fun (R) -> record_to_item(ServerHost, R) end, - search(ServerHost, Data)). - --define(FIELD(Var, Val), - #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - -record_to_item(LServer, - [Username, FN, Family, Given, Middle, Nickname, BDay, - CTRY, Locality, EMail, OrgName, OrgUnit]) -> - #xmlel{name = <<"item">>, attrs = [], - children = - [?FIELD(<<"jid">>, - <>), - ?FIELD(<<"fn">>, FN), ?FIELD(<<"last">>, Family), - ?FIELD(<<"first">>, Given), - ?FIELD(<<"middle">>, Middle), - ?FIELD(<<"nick">>, Nickname), ?FIELD(<<"bday">>, BDay), - ?FIELD(<<"ctry">>, CTRY), - ?FIELD(<<"locality">>, Locality), - ?FIELD(<<"email">>, EMail), - ?FIELD(<<"orgname">>, OrgName), - ?FIELD(<<"orgunit">>, OrgUnit)]}; -record_to_item(_LServer, #vcard_search{} = R) -> - {User, Server} = R#vcard_search.user, - #xmlel{name = <<"item">>, attrs = [], - children = - [?FIELD(<<"jid">>, <>), - ?FIELD(<<"fn">>, (R#vcard_search.fn)), - ?FIELD(<<"last">>, (R#vcard_search.family)), - ?FIELD(<<"first">>, (R#vcard_search.given)), - ?FIELD(<<"middle">>, (R#vcard_search.middle)), - ?FIELD(<<"nick">>, (R#vcard_search.nickname)), - ?FIELD(<<"bday">>, (R#vcard_search.bday)), - ?FIELD(<<"ctry">>, (R#vcard_search.ctry)), - ?FIELD(<<"locality">>, (R#vcard_search.locality)), - ?FIELD(<<"email">>, (R#vcard_search.email)), - ?FIELD(<<"orgname">>, (R#vcard_search.orgname)), - ?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}. - -search(LServer, Data) -> +-spec search(binary(), [xdata_field()]) -> [binary()]. +search(LServer, XFields) -> + Data = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- XFields], Mod = gen_mod:db_mod(LServer, ?MODULE), AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all, fun(B) when is_boolean(B) -> B end, @@ -576,24 +439,28 @@ search(LServer, Data) -> Mod:search(LServer, Data, AllowReturnAll, MaxMatch). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec remove_user(binary(), binary()) -> any(). remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:remove_user(LUser, LServer). +import_info() -> + [{<<"vcard">>, 3}, {<<"vcard_search">>, 24}]. + +import_start(LServer, DBType) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:init(LServer, []). + +import(LServer, {sql, _}, DBType, Tab, L) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Tab, L). + export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). - -import(LServer, DBType, VCard) -> - Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, VCard). - depends(_Host, _Opts) -> []. diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index a0ad305a9..47504c39d 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -1,53 +1,33 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_vcard_ldap.erl -%%% Author : Alexey Shchepin -%%% Purpose : Support for VCards from LDAP storage. -%%% Created : 2 Jan 2003 by Alexey Shchepin +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc %%% -%%% -%%% 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. -%%% -%%%---------------------u------------------------------------------------- - +%%% @end +%%% Created : 29 Jul 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- -module(mod_vcard_ldap). -behaviour(ejabberd_config). --author('alexey@process-one.net'). - -behaviour(gen_server). +-behaviour(mod_vcard). --behaviour(gen_mod). +%% API +-export([start_link/2]). +-export([init/2, stop/1, get_vcard/2, set_vcard/4, search/4, + remove_user/2, import/3, search_fields/1, search_reported/1, + mod_opt_type/1, opt_type/1]). +-export([is_search_supported/1]). -%% gen_server callbacks. --export([init/1, handle_info/2, handle_call/3, - handle_cast/2, terminate/2, code_change/3]). - --export([start/2, start_link/2, stop/1, - get_sm_features/5, process_local_iq/3, process_sm_iq/3, - remove_user/1, route/4, transform_module_options/1, - mod_opt_type/1, opt_type/1, depends/2]). +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). -include("ejabberd.hrl"). -include("logger.hrl"). - -include("eldap.hrl"). - --include("jlib.hrl"). +-include("xmpp.hrl"). -define(PROCNAME, ejabberd_mod_vcard_ldap). @@ -74,59 +54,14 @@ deref_aliases = never :: never | searching | finding | always, matches = 0 :: non_neg_integer()}). --define(VCARD_MAP, - [{<<"NICKNAME">>, <<"%u">>, []}, - {<<"FN">>, <<"%s">>, [<<"displayName">>]}, - {<<"FAMILY">>, <<"%s">>, [<<"sn">>]}, - {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]}, - {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]}, - {<<"ORGNAME">>, <<"%s">>, [<<"o">>]}, - {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]}, - {<<"CTRY">>, <<"%s">>, [<<"c">>]}, - {<<"LOCALITY">>, <<"%s">>, [<<"l">>]}, - {<<"STREET">>, <<"%s">>, [<<"street">>]}, - {<<"REGION">>, <<"%s">>, [<<"st">>]}, - {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]}, - {<<"TITLE">>, <<"%s">>, [<<"title">>]}, - {<<"URL">>, <<"%s">>, [<<"labeleduri">>]}, - {<<"DESC">>, <<"%s">>, [<<"description">>]}, - {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]}, - {<<"EMAIL">>, <<"%s">>, [<<"mail">>]}, - {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]}, - {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]}, - {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}]). +%%%=================================================================== +%%% API +%%%=================================================================== +start_link(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). --define(SEARCH_FIELDS, - [{<<"User">>, <<"%u">>}, - {<<"Full Name">>, <<"displayName">>}, - {<<"Given Name">>, <<"givenName">>}, - {<<"Middle Name">>, <<"initials">>}, - {<<"Family Name">>, <<"sn">>}, - {<<"Nickname">>, <<"%u">>}, - {<<"Birthday">>, <<"birthDay">>}, - {<<"Country">>, <<"c">>}, {<<"City">>, <<"l">>}, - {<<"Email">>, <<"mail">>}, - {<<"Organization Name">>, <<"o">>}, - {<<"Organization Unit">>, <<"ou">>}]). - --define(SEARCH_REPORTED, - [{<<"Full Name">>, <<"FN">>}, - {<<"Given Name">>, <<"FIRST">>}, - {<<"Middle Name">>, <<"MIDDLE">>}, - {<<"Family Name">>, <<"LAST">>}, - {<<"Nickname">>, <<"NICK">>}, - {<<"Birthday">>, <<"BDAY">>}, - {<<"Country">>, <<"CTRY">>}, - {<<"City">>, <<"LOCALITY">>}, - {<<"Email">>, <<"EMAIL">>}, - {<<"Organization Name">>, <<"ORGNAME">>}, - {<<"Organization Unit">>, <<"ORGUNIT">>}]). - -handle_cast(_Request, State) -> {noreply, State}. - -code_change(_OldVsn, State, _Extra) -> {ok, State}. - -start(Host, Opts) -> +init(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, transient, 1000, worker, [?MODULE]}, @@ -138,141 +73,131 @@ stop(Host) -> supervisor:terminate_child(ejabberd_sup, Proc), supervisor:delete_child(ejabberd_sup, Proc). -depends(_Host, _Opts) -> - []. +is_search_supported(_LServer) -> + true. -terminate(_Reason, State) -> - Host = State#state.serverhost, - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, - ?NS_VCARD), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, - ?NS_VCARD), - ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, - get_sm_features, 50), - case State#state.search of - true -> - ejabberd_router:unregister_route(State#state.myhost); - _ -> ok +get_vcard(LUser, LServer) -> + {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME), + VCardMap = State#state.vcard_map, + case find_ldap_user(LUser, State) of + #eldap_entry{attributes = Attributes} -> + VCard = ldap_attributes_to_vcard(Attributes, VCardMap, + {LUser, LServer}), + [xmpp:encode(VCard)]; + _ -> + [] end. -start_link(Host, Opts) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, - [Host, Opts], []). +set_vcard(_LUser, _LServer, _VCard, _VCardSearch) -> + {atomic, not_implemented}. +search_fields(LServer) -> + {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME), + State#state.search_fields. + +search_reported(LServer) -> + {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME), + State#state.search_reported. + +search(LServer, Data, _AllowReturnAll, MaxMatch) -> + {ok, State} = eldap_utils:get_state(LServer, ?PROCNAME), + Base = State#state.base, + SearchFilter = State#state.search_filter, + Eldap_ID = State#state.eldap_id, + UIDs = State#state.uids, + ReportedAttrs = State#state.search_reported_attrs, + Filter = eldap:'and'([SearchFilter, + eldap_utils:make_filter(Data, UIDs)]), + case eldap_pool:search(Eldap_ID, + [{base, Base}, {filter, Filter}, {limit, MaxMatch}, + {deref_aliases, State#state.deref_aliases}, + {attributes, ReportedAttrs}]) + of + #eldap_search_result{entries = E} -> + search_items(E, State); + _ -> + [] + end. + +search_items(Entries, State) -> + LServer = State#state.serverhost, + SearchReported = State#state.search_reported, + VCardMap = State#state.vcard_map, + UIDs = State#state.uids, + Attributes = lists:map(fun (E) -> + #eldap_entry{attributes = Attrs} = E, Attrs + end, + Entries), + lists:flatmap( + fun(Attrs) -> + case eldap_utils:find_ldap_attrs(UIDs, Attrs) of + {U, UIDAttrFormat} -> + case eldap_utils:get_user_part(U, UIDAttrFormat) of + {ok, Username} -> + case ejabberd_auth:is_user_exists(Username, + LServer) of + true -> + RFields = lists:map( + fun({_, VCardName}) -> + {VCardName, + map_vcard_attr(VCardName, + Attrs, + VCardMap, + {Username, + ?MYNAME})} + end, + SearchReported), + J = <>, + [{<<"jid">>, J} | RFields]; + _ -> + [] + end; + _ -> + [] + end; + <<"">> -> + [] + end + end, Attributes). + +remove_user(_User, _Server) -> + {atomic, not_implemented}. + +import(_, _, _) -> + pass. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== init([Host, Opts]) -> State = parse_options(Host, Opts), - IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, - one_queue), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, - ?NS_VCARD, ?MODULE, process_local_iq, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, - ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc), - ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, - get_sm_features, 50), eldap_pool:start_link(State#state.eldap_id, State#state.servers, State#state.backups, State#state.port, State#state.dn, State#state.password, State#state.tls_options), - case State#state.search of - true -> - ejabberd_router:register_route(State#state.myhost, Host); - _ -> ok - end, {ok, State}. -handle_info({route, From, To, Packet}, State) -> - case catch do_route(State, From, To, Packet) of - Pid when is_pid(Pid) -> ok; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_INTERNAL_SERVER_ERROR), - ejabberd_router:route(To, From, Err) - end, - {noreply, State}; -handle_info(_Info, State) -> {noreply, State}. - -get_sm_features({error, _Error} = Acc, _From, _To, - _Node, _Lang) -> - Acc; -get_sm_features(Acc, _From, _To, Node, _Lang) -> - case Node of - <<"">> -> - case Acc of - {result, Features} -> {result, [?NS_VCARD | Features]}; - empty -> {result, [?NS_VCARD]} - end; - _ -> Acc - end. - -process_local_iq(_From, _To, - #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, ?NS_VCARD}], - children = - [#xmlel{name = <<"FN">>, attrs = [], - children = - [{xmlcdata, <<"ejabberd">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Erlang Jabber Server">>))/binary, - "\nCopyright (c) 2002-2016 ProcessOne">>}]}, - #xmlel{name = <<"BDAY">>, attrs = [], - children = - [{xmlcdata, <<"2002-11-16">>}]}]}]} - end. - -process_sm_iq(_From, #jid{lserver = LServer} = To, - #iq{sub_el = SubEl} = IQ) -> - case catch process_vcard_ldap(To, IQ, LServer) of - {'EXIT', _} -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}; - Other -> Other - end. - -process_vcard_ldap(To, IQ, Server) -> - {ok, State} = eldap_utils:get_state(Server, ?PROCNAME), - #iq{type = Type, sub_el = SubEl, lang = Lang} = IQ, - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - #jid{luser = LUser} = To, - LServer = State#state.serverhost, - case ejabberd_auth:is_user_exists(LUser, LServer) of - true -> - VCardMap = State#state.vcard_map, - case find_ldap_user(LUser, State) of - #eldap_entry{attributes = Attributes} -> - Vcard = ldap_attributes_to_vcard(Attributes, VCardMap, - {LUser, LServer}), - IQ#iq{type = result, sub_el = Vcard}; - _ -> IQ#iq{type = result, sub_el = []} - end; - _ -> IQ#iq{type = result, sub_el = []} - end - end. - handle_call(get_state, _From, State) -> {reply, {ok, State}, State}; -handle_call(stop, _From, State) -> - {stop, normal, ok, State}; handle_call(_Request, _From, State) -> - {reply, bad_request, State}. + Reply = ok, + {reply, Reply, State}. +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== find_ldap_user(User, State) -> Base = State#state.base, RFC2254_Filter = State#state.user_filter, @@ -300,412 +225,48 @@ ldap_attributes_to_vcard(Attributes, VCardMap, UD) -> UD)} end, VCardMap), - Elts = [ldap_attribute_to_vcard(vCard, Attr) - || Attr <- Attrs], - NElts = [ldap_attribute_to_vcard(vCardN, Attr) - || Attr <- Attrs], - OElts = [ldap_attribute_to_vcard(vCardO, Attr) - || Attr <- Attrs], - AElts = [ldap_attribute_to_vcard(vCardA, Attr) - || Attr <- Attrs], - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, ?NS_VCARD}], - children = - lists:append([X || X <- Elts, X /= none], - [#xmlel{name = <<"N">>, attrs = [], - children = [X || X <- NElts, X /= none]}, - #xmlel{name = <<"ORG">>, attrs = [], - children = [X || X <- OElts, X /= none]}, - #xmlel{name = <<"ADR">>, attrs = [], - children = - [X || X <- AElts, X /= none]}])}]. + lists:foldl(fun ldap_attribute_to_vcard/2, #vcard_temp{}, Attrs). -ldap_attribute_to_vcard(vCard, {<<"fn">>, Value}) -> - #xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCard, - {<<"nickname">>, Value}) -> - #xmlel{name = <<"NICKNAME">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCard, {<<"title">>, Value}) -> - #xmlel{name = <<"TITLE">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCard, {<<"bday">>, Value}) -> - #xmlel{name = <<"BDAY">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCard, {<<"url">>, Value}) -> - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCard, {<<"desc">>, Value}) -> - #xmlel{name = <<"DESC">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCard, {<<"role">>, Value}) -> - #xmlel{name = <<"ROLE">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCard, {<<"tel">>, Value}) -> - #xmlel{name = <<"TEL">>, attrs = [], - children = - [#xmlel{name = <<"VOICE">>, attrs = [], children = []}, - #xmlel{name = <<"WORK">>, attrs = [], children = []}, - #xmlel{name = <<"NUMBER">>, attrs = [], - children = [{xmlcdata, Value}]}]}; -ldap_attribute_to_vcard(vCard, {<<"email">>, Value}) -> - #xmlel{name = <<"EMAIL">>, attrs = [], - children = - [#xmlel{name = <<"INTERNET">>, attrs = [], - children = []}, - #xmlel{name = <<"PREF">>, attrs = [], children = []}, - #xmlel{name = <<"USERID">>, attrs = [], - children = [{xmlcdata, Value}]}]}; -ldap_attribute_to_vcard(vCard, {<<"photo">>, Value}) -> - #xmlel{name = <<"PHOTO">>, attrs = [], - children = - [#xmlel{name = <<"TYPE">>, attrs = [], - children = [{xmlcdata, <<"image/jpeg">>}]}, - #xmlel{name = <<"BINVAL">>, attrs = [], - children = [{xmlcdata, jlib:encode_base64(Value)}]}]}; -ldap_attribute_to_vcard(vCardN, - {<<"family">>, Value}) -> - #xmlel{name = <<"FAMILY">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCardN, {<<"given">>, Value}) -> - #xmlel{name = <<"GIVEN">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCardN, - {<<"middle">>, Value}) -> - #xmlel{name = <<"MIDDLE">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCardO, - {<<"orgname">>, Value}) -> - #xmlel{name = <<"ORGNAME">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCardO, - {<<"orgunit">>, Value}) -> - #xmlel{name = <<"ORGUNIT">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCardA, - {<<"locality">>, Value}) -> - #xmlel{name = <<"LOCALITY">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCardA, - {<<"street">>, Value}) -> - #xmlel{name = <<"STREET">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCardA, {<<"ctry">>, Value}) -> - #xmlel{name = <<"CTRY">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCardA, - {<<"region">>, Value}) -> - #xmlel{name = <<"REGION">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(vCardA, {<<"pcode">>, Value}) -> - #xmlel{name = <<"PCODE">>, attrs = [], - children = [{xmlcdata, Value}]}; -ldap_attribute_to_vcard(_, _) -> none. - --define(TLFIELD(Type, Label, Var), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = []}). - --define(FORM(JID, SearchFields), - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need an x:data capable client to " - "search">>)}]}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Search users in ">>))/binary, - (jid:to_string(JID))/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Fill in fields to search for any matching " - "Jabber User">>)}]}] - ++ - lists:map(fun ({X, Y}) -> - ?TLFIELD(<<"text-single">>, X, Y) - end, - SearchFields)}]). - -do_route(State, From, To, Packet) -> - spawn(?MODULE, route, [State, From, To, Packet]). - -route(State, From, To, Packet) -> - #jid{user = User, resource = Resource} = To, - ServerHost = State#state.serverhost, - if (User /= <<"">>) or (Resource /= <<"">>) -> - Err = jlib:make_error_reply(Packet, - ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err); - true -> - IQ = jlib:iq_query_info(Packet), - case IQ of - #iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang, - sub_el = SubEl} -> - case Type of - set -> - XDataEl = find_xdata_el(SubEl), - case XDataEl of - false -> - Txt = <<"Data form not found">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_router:route(To, From, Err); - _ -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - Txt = <<"Incorrect data form">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, Txt)), - ejabberd_router:route(To, From, Err); - _ -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_SEARCH}], - children = - [#xmlel{name = - <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_XDATA}, - {<<"type">>, - <<"result">>}], - children - = - search_result(Lang, - To, - State, - XData)}]}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(ResIQ)) - end - end; - get -> - SearchFields = State#state.search_fields, - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_SEARCH}], - children = - ?FORM(To, SearchFields)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)) - end; - #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)), - ejabberd_router:route(To, From, Err); - get -> - Info = ejabberd_hooks:run_fold(disco_info, ServerHost, - [], - [ServerHost, ?MODULE, - <<"">>, <<"">>]), - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_DISCO_INFO}], - children = - [#xmlel{name = - <<"identity">>, - attrs = - [{<<"category">>, - <<"directory">>}, - {<<"type">>, - <<"user">>}, - {<<"name">>, - translate:translate(Lang, - <<"vCard User Search">>)}], - children = []}, - #xmlel{name = - <<"feature">>, - attrs = - [{<<"var">>, - ?NS_SEARCH}], - children = []}, - #xmlel{name = - <<"feature">>, - attrs = - [{<<"var">>, - ?NS_VCARD}], - children = []}] - ++ Info}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)) - end; - #iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)), - ejabberd_router:route(To, From, Err); - get -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - ?NS_DISCO_ITEMS}], - children = []}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)) - end; - #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} -> - ResIQ = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = [{<<"xmlns">>, ?NS_VCARD}], - children = iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err) - end +-spec ldap_attribute_to_vcard({binary(), binary()}, vcard_temp()) -> vcard_temp(). +ldap_attribute_to_vcard({Attr, Value}, V) -> + Ts = V#vcard_temp.tel, + Es = V#vcard_temp.email, + N = case V#vcard_temp.n of + undefined -> #vcard_name{}; + _ -> V#vcard_temp.n + end, + O = case V#vcard_temp.org of + undefined -> #vcard_org{}; + _ -> V#vcard_temp.org + end, + A = case V#vcard_temp.adr of + [] -> #vcard_adr{}; + As -> hd(As) + end, + case Attr of + <<"fn">> -> V#vcard_temp{fn = Value}; + <<"nickname">> -> V#vcard_temp{nickname = Value}; + <<"title">> -> V#vcard_temp{title = Value}; + <<"bday">> -> V#vcard_temp{bday = Value}; + <<"url">> -> V#vcard_temp{url = Value}; + <<"desc">> -> V#vcard_temp{desc = Value}; + <<"role">> -> V#vcard_temp{role = Value}; + <<"tel">> -> V#vcard_temp{tel = [#vcard_tel{number = Value}|Ts]}; + <<"email">> -> V#vcard_temp{email = [#vcard_email{userid = Value}|Es]}; + <<"photo">> -> V#vcard_temp{photo = #vcard_photo{binval = Value}}; + <<"family">> -> V#vcard_temp{n = N#vcard_name{family = Value}}; + <<"given">> -> V#vcard_temp{n = N#vcard_name{given = Value}}; + <<"middle">> -> V#vcard_temp{n = N#vcard_name{middle = Value}}; + <<"orgname">> -> V#vcard_temp{org = O#vcard_org{name = Value}}; + <<"orgunit">> -> V#vcard_temp{org = O#vcard_org{units = [Value]}}; + <<"locality">> -> V#vcard_temp{adr = [A#vcard_adr{locality = Value}]}; + <<"street">> -> V#vcard_temp{adr = [A#vcard_adr{street = Value}]}; + <<"ctry">> -> V#vcard_temp{adr = [A#vcard_adr{ctry = Value}]}; + <<"region">> -> V#vcard_temp{adr = [A#vcard_adr{region = Value}]}; + <<"pcode">> -> V#vcard_temp{adr = [A#vcard_adr{pcode = Value}]}; + _ -> V end. -iq_get_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd vCard module">>))/binary, - "\nCopyright (c) 2003-2016 ProcessOne">>}]}]. - --define(LFIELD(Label, Var), - #xmlel{name = <<"field">>, - attrs = - [{<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = []}). - -search_result(Lang, JID, State, Data) -> - SearchReported = State#state.search_reported, - Header = [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Search Results for ">>))/binary, - (jid:to_string(JID))/binary>>}]}, - #xmlel{name = <<"reported">>, attrs = [], - children = - [?TLFIELD(<<"text-single">>, <<"Jabber ID">>, - <<"jid">>)] - ++ - lists:map(fun ({Name, Value}) -> - ?TLFIELD(<<"text-single">>, Name, - Value) - end, - SearchReported)}], - case search(State, Data) of - error -> Header; - Result -> Header ++ Result - end. - --define(FIELD(Var, Val), - #xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - -search(State, Data) -> - Base = State#state.base, - SearchFilter = State#state.search_filter, - Eldap_ID = State#state.eldap_id, - UIDs = State#state.uids, - Limit = State#state.matches, - ReportedAttrs = State#state.search_reported_attrs, - Filter = eldap:'and'([SearchFilter, - eldap_utils:make_filter(Data, UIDs)]), - case eldap_pool:search(Eldap_ID, - [{base, Base}, {filter, Filter}, {limit, Limit}, - {deref_aliases, State#state.deref_aliases}, - {attributes, ReportedAttrs}]) - of - #eldap_search_result{entries = E} -> - search_items(E, State); - _ -> error - end. - -search_items(Entries, State) -> - LServer = State#state.serverhost, - SearchReported = State#state.search_reported, - VCardMap = State#state.vcard_map, - UIDs = State#state.uids, - Attributes = lists:map(fun (E) -> - #eldap_entry{attributes = Attrs} = E, Attrs - end, - Entries), - lists:flatmap(fun (Attrs) -> - case eldap_utils:find_ldap_attrs(UIDs, Attrs) of - {U, UIDAttrFormat} -> - case eldap_utils:get_user_part(U, UIDAttrFormat) - of - {ok, Username} -> - case - ejabberd_auth:is_user_exists(Username, - LServer) - of - true -> - RFields = lists:map(fun ({_, - VCardName}) -> - {VCardName, - map_vcard_attr(VCardName, - Attrs, - VCardMap, - {Username, - ?MYNAME})} - end, - SearchReported), - Result = [?FIELD(<<"jid">>, - <>)] - ++ - [?FIELD(Name, Value) - || {Name, Value} - <- RFields], - [#xmlel{name = <<"item">>, - attrs = [], - children = Result}]; - _ -> [] - end; - _ -> [] - end; - <<"">> -> [] - end - end, - Attributes). - -remove_user(_User) -> true. - -%%%----------------------- -%%% Auxiliary functions. -%%%----------------------- - map_vcard_attr(VCardName, Attributes, Pattern, UD) -> Res = lists:filter(fun ({Name, _, _}) -> eldap_utils:case_insensitive_match(Name, @@ -725,19 +286,54 @@ process_pattern(Str, {User, Domain}, AttrValues) -> [{<<"%u">>, User}, {<<"%d">>, Domain}] ++ [{<<"%s">>, V, 1} || V <- AttrValues]). -find_xdata_el(#xmlel{children = SubEls}) -> - find_xdata_el1(SubEls). +default_vcard_map() -> + [{<<"NICKNAME">>, <<"%u">>, []}, + {<<"FN">>, <<"%s">>, [<<"displayName">>]}, + {<<"FAMILY">>, <<"%s">>, [<<"sn">>]}, + {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]}, + {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]}, + {<<"ORGNAME">>, <<"%s">>, [<<"o">>]}, + {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]}, + {<<"CTRY">>, <<"%s">>, [<<"c">>]}, + {<<"LOCALITY">>, <<"%s">>, [<<"l">>]}, + {<<"STREET">>, <<"%s">>, [<<"street">>]}, + {<<"REGION">>, <<"%s">>, [<<"st">>]}, + {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]}, + {<<"TITLE">>, <<"%s">>, [<<"title">>]}, + {<<"URL">>, <<"%s">>, [<<"labeleduri">>]}, + {<<"DESC">>, <<"%s">>, [<<"description">>]}, + {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]}, + {<<"EMAIL">>, <<"%s">>, [<<"mail">>]}, + {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]}, + {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]}, + {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}]. -find_xdata_el1([]) -> false; -find_xdata_el1([#xmlel{name = Name, attrs = Attrs, - children = SubEls} - | Els]) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - #xmlel{name = Name, attrs = Attrs, children = SubEls}; - _ -> find_xdata_el1(Els) - end; -find_xdata_el1([_ | Els]) -> find_xdata_el1(Els). +default_search_fields() -> + [{<<"User">>, <<"%u">>}, + {<<"Full Name">>, <<"displayName">>}, + {<<"Given Name">>, <<"givenName">>}, + {<<"Middle Name">>, <<"initials">>}, + {<<"Family Name">>, <<"sn">>}, + {<<"Nickname">>, <<"%u">>}, + {<<"Birthday">>, <<"birthDay">>}, + {<<"Country">>, <<"c">>}, + {<<"City">>, <<"l">>}, + {<<"Email">>, <<"mail">>}, + {<<"Organization Name">>, <<"o">>}, + {<<"Organization Unit">>, <<"ou">>}]. + +default_search_reported() -> + [{<<"Full Name">>, <<"FN">>}, + {<<"Given Name">>, <<"FIRST">>}, + {<<"Middle Name">>, <<"MIDDLE">>}, + {<<"Family Name">>, <<"LAST">>}, + {<<"Nickname">>, <<"NICK">>}, + {<<"Birthday">>, <<"BDAY">>}, + {<<"Country">>, <<"CTRY">>}, + {<<"City">>, <<"LOCALITY">>}, + {<<"Email">>, <<"EMAIL">>}, + {<<"Organization Name">>, <<"ORGNAME">>}, + {<<"Organization Unit">>, <<"ORGUNIT">>}]. parse_options(Host, Opts) -> MyHost = gen_mod:get_opt_host(Host, Opts, @@ -784,19 +380,19 @@ parse_options(Host, Opts) -> [iolist_to_binary(E) || E <- L]} end, Ls) - end, ?VCARD_MAP), + end, default_vcard_map()), SearchFields = gen_mod:get_opt(ldap_search_fields, Opts, fun(Ls) -> [{iolist_to_binary(S), iolist_to_binary(P)} || {S, P} <- Ls] - end, ?SEARCH_FIELDS), + end, default_search_fields()), SearchReported = gen_mod:get_opt(ldap_search_reported, Opts, fun(Ls) -> [{iolist_to_binary(S), iolist_to_binary(P)} || {S, P} <- Ls] - end, ?SEARCH_REPORTED), + end, default_search_reported()), UIDAttrs = [UAttr || {UAttr, _} <- UIDs], VCardMapAttrs = lists:usort(lists:append([A || {_, _, A} <- VCardMap]) @@ -834,27 +430,11 @@ parse_options(Host, Opts) -> search_reported_attrs = SearchReportedAttrs, matches = Matches}. -transform_module_options(Opts) -> - lists:map( - fun({ldap_vcard_map, Map}) -> - NewMap = lists:map( - fun({Field, Pattern, Attrs}) -> - {Field, [{Pattern, Attrs}]}; - (Opt) -> - Opt - end, Map), - {ldap_vcard_map, NewMap}; - (Opt) -> - Opt - end, Opts). - check_filter(F) -> NewF = iolist_to_binary(F), {ok, _} = eldap_filter:parse(NewF), NewF. -mod_opt_type(host) -> fun iolist_to_binary/1; -mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(ldap_filter) -> fun check_filter/1; mod_opt_type(ldap_search_fields) -> fun (Ls) -> @@ -882,12 +462,6 @@ mod_opt_type(ldap_vcard_map) -> end, Ls) end; -mod_opt_type(matches) -> - fun (infinity) -> 0; - (I) when is_integer(I), I > 0 -> I - end; -mod_opt_type(search) -> - fun (B) when is_boolean(B) -> B end; mod_opt_type(deref_aliases) -> fun (never) -> never; (searching) -> searching; @@ -926,9 +500,9 @@ mod_opt_type(ldap_tls_verify) -> (false) -> false end; mod_opt_type(_) -> - [host, iqdisc, ldap_filter, ldap_search_fields, + [ldap_filter, ldap_search_fields, ldap_search_reported, ldap_uids, ldap_vcard_map, - matches, search, deref_aliases, ldap_backups, ldap_base, + deref_aliases, ldap_backups, ldap_base, ldap_deref_aliases, ldap_encrypt, ldap_password, ldap_port, ldap_rootdn, ldap_servers, ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth, diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl index 781a135c8..40ea36381 100644 --- a/src/mod_vcard_mnesia.erl +++ b/src/mod_vcard_mnesia.erl @@ -11,10 +11,12 @@ -behaviour(mod_vcard). %% API --export([init/2, import/2, get_vcard/2, set_vcard/4, search/4, remove_user/2]). +-export([init/2, stop/1, import/3, get_vcard/2, set_vcard/4, search/4, + search_fields/1, search_reported/1, remove_user/2]). +-export([is_search_supported/1]). -include("ejabberd.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_vcard.hrl"). -include("logger.hrl"). @@ -22,10 +24,10 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(vcard, + ejabberd_mnesia:create(?MODULE, vcard, [{disc_only_copies, [node()]}, {attributes, record_info(fields, vcard)}]), - mnesia:create_table(vcard_search, + ejabberd_mnesia:create(?MODULE, vcard_search, [{disc_copies, [node()]}, {attributes, record_info(fields, vcard_search)}]), @@ -43,6 +45,12 @@ init(_Host, _Opts) -> mnesia:add_table_index(vcard_search, lorgname), mnesia:add_table_index(vcard_search, lorgunit). +stop(_Host) -> + ok. + +is_search_supported(_ServerHost) -> + true. + get_vcard(LUser, LServer) -> US = {LUser, LServer}, F = fun () -> mnesia:read({vcard, US}) end, @@ -71,15 +79,44 @@ search(LServer, Data, AllowReturnAll, MaxMatch) -> {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), []; Rs -> + Fields = lists:map(fun record_to_item/1, Rs), case MaxMatch of infinity -> - Rs; + Fields; Val -> - lists:sublist(Rs, Val) + lists:sublist(Fields, Val) end end end. +search_fields(_LServer) -> + [{<<"User">>, <<"user">>}, + {<<"Full Name">>, <<"fn">>}, + {<<"Name">>, <<"first">>}, + {<<"Middle Name">>, <<"middle">>}, + {<<"Family Name">>, <<"last">>}, + {<<"Nickname">>, <<"nick">>}, + {<<"Birthday">>, <<"bday">>}, + {<<"Country">>, <<"ctry">>}, + {<<"City">>, <<"locality">>}, + {<<"Email">>, <<"email">>}, + {<<"Organization Name">>, <<"orgname">>}, + {<<"Organization Unit">>, <<"orgunit">>}]. + +search_reported(_LServer) -> + [{<<"Jabber ID">>, <<"jid">>}, + {<<"Full Name">>, <<"fn">>}, + {<<"Name">>, <<"first">>}, + {<<"Middle Name">>, <<"middle">>}, + {<<"Family Name">>, <<"last">>}, + {<<"Nickname">>, <<"nick">>}, + {<<"Birthday">>, <<"bday">>}, + {<<"Country">>, <<"ctry">>}, + {<<"City">>, <<"locality">>}, + {<<"Email">>, <<"email">>}, + {<<"Organization Name">>, <<"orgname">>}, + {<<"Organization Unit">>, <<"orgunit">>}]. + remove_user(LUser, LServer) -> US = {LUser, LServer}, F = fun () -> @@ -88,10 +125,29 @@ remove_user(LUser, LServer) -> end, mnesia:transaction(F). -import(_LServer, #vcard{} = VCard) -> +import(LServer, <<"vcard">>, [LUser, XML, _TimeStamp]) -> + #xmlel{} = El = fxml_stream:parse_element(XML), + VCard = #vcard{us = {LUser, LServer}, vcard = El}, mnesia:dirty_write(VCard); -import(_LServer, #vcard_search{} = S) -> - mnesia:dirty_write(S). +import(LServer, <<"vcard_search">>, + [User, LUser, FN, LFN, + Family, LFamily, Given, LGiven, + Middle, LMiddle, Nickname, LNickname, + BDay, LBDay, CTRY, LCTRY, Locality, LLocality, + EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) -> + mnesia:dirty_write( + #vcard_search{us = {LUser, LServer}, + user = {User, LServer}, luser = LUser, + fn = FN, lfn = LFN, family = Family, + lfamily = LFamily, given = Given, + lgiven = LGiven, middle = Middle, + lmiddle = LMiddle, nickname = Nickname, + lnickname = LNickname, bday = BDay, + lbday = LBDay, ctry = CTRY, lctry = LCTRY, + locality = Locality, llocality = LLocality, + email = EMail, lemail = LEMail, + orgname = OrgName, lorgname = LOrgName, + orgunit = OrgUnit, lorgunit = LOrgUnit}). %%%=================================================================== %%% Internal functions @@ -211,3 +267,19 @@ parts_to_string(Parts) -> str:strip(list_to_binary( lists:map(fun (S) -> <> end, Parts)), right, $.). + +-spec record_to_item(#vcard_search{}) -> [{binary(), binary()}]. +record_to_item(R) -> + {User, Server} = R#vcard_search.user, + [{<<"jid">>, <>}, + {<<"fn">>, (R#vcard_search.fn)}, + {<<"last">>, (R#vcard_search.family)}, + {<<"first">>, (R#vcard_search.given)}, + {<<"middle">>, (R#vcard_search.middle)}, + {<<"nick">>, (R#vcard_search.nickname)}, + {<<"bday">>, (R#vcard_search.bday)}, + {<<"ctry">>, (R#vcard_search.ctry)}, + {<<"locality">>, (R#vcard_search.locality)}, + {<<"email">>, (R#vcard_search.email)}, + {<<"orgname">>, (R#vcard_search.orgname)}, + {<<"orgunit">>, (R#vcard_search.orgunit)}]. diff --git a/src/mod_vcard_riak.erl b/src/mod_vcard_riak.erl index 386347387..411ec45fa 100644 --- a/src/mod_vcard_riak.erl +++ b/src/mod_vcard_riak.erl @@ -12,9 +12,10 @@ %% API -export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2, - import/2]). + search_fields/1, search_reported/1, import/3, stop/1]). +-export([is_search_supported/1]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_vcard.hrl"). %%%=================================================================== @@ -23,6 +24,12 @@ init(_Host, _Opts) -> ok. +stop(_Host) -> + ok. + +is_search_supported(_LServer) -> + false. + get_vcard(LUser, LServer) -> case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of {ok, R} -> @@ -89,10 +96,18 @@ set_vcard(LUser, LServer, VCARD, search(_LServer, _Data, _AllowReturnAll, _MaxMatch) -> []. +search_fields(_LServer) -> + []. + +search_reported(_LServer) -> + []. + remove_user(LUser, LServer) -> {atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}. -import(_LServer, #vcard{us = {LUser, LServer}, vcard = El} = VCard) -> +import(LServer, <<"vcard">>, [LUser, XML, _TimeStamp]) -> + El = fxml_stream:parse_element(XML), + VCard = #vcard{us = {LUser, LServer}, vcard = El}, #vcard_search{fn = FN, lfn = LFN, family = Family, @@ -141,7 +156,7 @@ import(_LServer, #vcard{us = {LUser, LServer}, vcard = El} = VCard) -> {<<"lorgname">>, LOrgName}, {<<"orgunit">>, OrgUnit}, {<<"lorgunit">>, LOrgUnit}]}]); -import(_LServer, #vcard_search{}) -> +import(_LServer, <<"vcard_search">>, _) -> ok. %%%=================================================================== diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl index b8234bf9c..7fd64c44e 100644 --- a/src/mod_vcard_sql.erl +++ b/src/mod_vcard_sql.erl @@ -13,10 +13,11 @@ -behaviour(mod_vcard). %% API --export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2, - import/1, import/2, export/1]). +-export([init/2, stop/1, get_vcard/2, set_vcard/4, search/4, remove_user/2, + search_fields/1, search_reported/1, import/3, export/1]). +-export([is_search_supported/1]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_vcard.hrl"). -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -27,6 +28,12 @@ init(_Host, _Opts) -> ok. +stop(_Host) -> + ok. + +is_search_supported(_LServer) -> + true. + get_vcard(LUser, LServer) -> case catch sql_queries:get_vcard(LServer, LUser) of {selected, [{SVCARD}]} -> @@ -79,7 +86,7 @@ search(LServer, Data, AllowReturnAll, MaxMatch) -> infinity -> <<"">>; Val -> - [<<" LIMIT ">>, jlib:integer_to_binary(Val)] + [<<" LIMIT ">>, integer_to_binary(Val)] end, case catch ejabberd_sql:sql_query( LServer, @@ -93,12 +100,40 @@ search(LServer, Data, AllowReturnAll, MaxMatch) -> <<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>, <<"locality">>, <<"email">>, <<"orgname">>, <<"orgunit">>], Rs} when is_list(Rs) -> - Rs; + [row_to_item(LServer, R) || R <- Rs]; Error -> ?ERROR_MSG("~p", [Error]), [] end end. +search_fields(_LServer) -> + [{<<"User">>, <<"user">>}, + {<<"Full Name">>, <<"fn">>}, + {<<"Name">>, <<"first">>}, + {<<"Middle Name">>, <<"middle">>}, + {<<"Family Name">>, <<"last">>}, + {<<"Nickname">>, <<"nick">>}, + {<<"Birthday">>, <<"bday">>}, + {<<"Country">>, <<"ctry">>}, + {<<"City">>, <<"locality">>}, + {<<"Email">>, <<"email">>}, + {<<"Organization Name">>, <<"orgname">>}, + {<<"Organization Unit">>, <<"orgunit">>}]. + +search_reported(_LServer) -> + [{<<"Jabber ID">>, <<"jid">>}, + {<<"Full Name">>, <<"fn">>}, + {<<"Name">>, <<"first">>}, + {<<"Middle Name">>, <<"middle">>}, + {<<"Family Name">>, <<"last">>}, + {<<"Nickname">>, <<"nick">>}, + {<<"Birthday">>, <<"bday">>}, + {<<"Country">>, <<"ctry">>}, + {<<"City">>, <<"locality">>}, + {<<"Email">>, <<"email">>}, + {<<"Organization Name">>, <<"orgname">>}, + {<<"Organization Unit">>, <<"orgunit">>}]. + remove_user(LUser, LServer) -> ejabberd_sql:sql_transaction( LServer, @@ -157,37 +192,8 @@ export(_Server) -> [] end}]. -import(LServer) -> - [{<<"select username, vcard from vcard;">>, - fun([LUser, SVCard]) -> - #xmlel{} = VCARD = fxml_stream:parse_element(SVCard), - #vcard{us = {LUser, LServer}, vcard = VCARD} - end}, - {<<"select username, lusername, fn, lfn, family, lfamily, " - "given, lgiven, middle, lmiddle, nickname, lnickname, " - "bday, lbday, ctry, lctry, locality, llocality, email, " - "lemail, orgname, lorgname, orgunit, lorgunit from vcard_search;">>, - fun([User, LUser, FN, LFN, - Family, LFamily, Given, LGiven, - Middle, LMiddle, Nickname, LNickname, - BDay, LBDay, CTRY, LCTRY, Locality, LLocality, - EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) -> - #vcard_search{us = {LUser, LServer}, - user = {User, LServer}, luser = LUser, - fn = FN, lfn = LFN, family = Family, - lfamily = LFamily, given = Given, - lgiven = LGiven, middle = Middle, - lmiddle = LMiddle, nickname = Nickname, - lnickname = LNickname, bday = BDay, - lbday = LBDay, ctry = CTRY, lctry = LCTRY, - locality = Locality, llocality = LLocality, - email = EMail, lemail = LEMail, - orgname = OrgName, lorgname = LOrgName, - orgunit = OrgUnit, lorgunit = LOrgUnit} - end}]. - -import(_, _) -> - pass. +import(_, _, _) -> + ok. %%%=================================================================== %%% Internal functions @@ -240,3 +246,18 @@ make_val(Match, Field, Val) -> <<"">> -> Condition; _ -> [Match, <<" and ">>, Condition] end. + +row_to_item(LServer, [Username, FN, Family, Given, Middle, Nickname, BDay, + CTRY, Locality, EMail, OrgName, OrgUnit]) -> + [{<<"jid">>, <>}, + {<<"fn">>, FN}, + {<<"last">>, Family}, + {<<"first">>, Given}, + {<<"middle">>, Middle}, + {<<"nick">>, Nickname}, + {<<"bday">>, BDay}, + {<<"ctry">>, CTRY}, + {<<"locality">>, Locality}, + {<<"email">>, EMail}, + {<<"orgname">>, OrgName}, + {<<"orgunit">>, OrgUnit}]. diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index f2101df91..4d1dfa2fc 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -13,15 +13,15 @@ -export([start/2, stop/1]). -export([update_presence/3, vcard_set/3, export/1, - import/1, import/3, mod_opt_type/1, depends/2]). + import_info/0, import/5, import_start/2, + mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("mod_vcard_xupdate.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), #vcard_xupdate{}) -> ok | pass. +-callback import(binary(), binary(), [binary()]) -> ok. -callback add_xupdate(binary(), binary(), binary()) -> {atomic, any()}. -callback get_xupdate(binary(), binary()) -> binary() | undefined. -callback remove_xupdate(binary(), binary()) -> {atomic, any()}. @@ -52,15 +52,12 @@ depends(_Host, _Opts) -> %%==================================================================== %% Hooks %%==================================================================== - -update_presence(#xmlel{name = <<"presence">>, attrs = Attrs} = Packet, - User, Host) -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<>> -> presence_with_xupdate(Packet, User, Host); - _ -> Packet - end; +-spec update_presence(presence(), binary(), binary()) -> presence(). +update_presence(#presence{type = available} = Packet, User, Host) -> + presence_with_xupdate(Packet, User, Host); update_presence(Packet, _User, _Host) -> Packet. +-spec vcard_set(binary(), binary(), xmlel()) -> ok. vcard_set(LUser, LServer, VCARD) -> US = {LUser, LServer}, case fxml:get_path_s(VCARD, @@ -93,48 +90,25 @@ remove_xupdate(LUser, LServer) -> %%% Presence stanza rebuilding %%%---------------------------------------------------------------------- -presence_with_xupdate(#xmlel{name = <<"presence">>, - attrs = Attrs, children = Els}, - User, Host) -> - XPhotoEl = build_xphotoel(User, Host), - Els2 = presence_with_xupdate2(Els, [], XPhotoEl), - #xmlel{name = <<"presence">>, attrs = Attrs, - children = Els2}. - -presence_with_xupdate2([], Els2, XPhotoEl) -> - lists:reverse([XPhotoEl | Els2]); -%% This clause assumes that the x element contains only the XMLNS attribute: -presence_with_xupdate2([#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}]} - | Els], - Els2, XPhotoEl) -> - presence_with_xupdate2(Els, Els2, XPhotoEl); -presence_with_xupdate2([El | Els], Els2, XPhotoEl) -> - presence_with_xupdate2(Els, [El | Els2], XPhotoEl). - -build_xphotoel(User, Host) -> +presence_with_xupdate(Presence, User, Host) -> Hash = get_xupdate(User, Host), - PhotoSubEls = case Hash of - Hash when is_binary(Hash) -> [{xmlcdata, Hash}]; - _ -> [] - end, - PhotoEl = [#xmlel{name = <<"photo">>, attrs = [], - children = PhotoSubEls}], - #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}], - children = PhotoEl}. + Presence1 = xmpp:remove_subtag(Presence, #vcard_xupdate{}), + xmpp:set_subtag(Presence1, #vcard_xupdate{hash = Hash}). + +import_info() -> + [{<<"vcard_xupdate">>, 3}]. + +import_start(LServer, DBType) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:init(LServer, []). + +import(LServer, {sql, _}, DBType, Tab, [LUser, Hash, TimeStamp]) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Tab, [LUser, Hash, TimeStamp]). export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). -import(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:import(LServer). - -import(LServer, DBType, LA) -> - Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, LA). - mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(_) -> [db_type]. diff --git a/src/mod_vcard_xupdate_mnesia.erl b/src/mod_vcard_xupdate_mnesia.erl index f1b1693e4..454d97e25 100644 --- a/src/mod_vcard_xupdate_mnesia.erl +++ b/src/mod_vcard_xupdate_mnesia.erl @@ -10,7 +10,7 @@ -behaviour(mod_vcard_xupdate). %% API --export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]). +-export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2]). -include("mod_vcard_xupdate.hrl"). -include("logger.hrl"). @@ -19,7 +19,7 @@ %%% API %%%=================================================================== init(_Host, _Opts) -> - mnesia:create_table(vcard_xupdate, + ejabberd_mnesia:create(?MODULE, vcard_xupdate, [{disc_copies, [node()]}, {attributes, record_info(fields, vcard_xupdate)}]), @@ -45,8 +45,9 @@ remove_xupdate(LUser, LServer) -> end, mnesia:transaction(F). -import(_LServer, #vcard_xupdate{} = R) -> - mnesia:dirty_write(R). +import(LServer, <<"vcard_xupdate">>, [LUser, Hash, _TimeStamp]) -> + mnesia:dirty_write( + #vcard_xupdate{us = {LUser, LServer}, hash = Hash}). %%%=================================================================== %%% Internal functions diff --git a/src/mod_vcard_xupdate_riak.erl b/src/mod_vcard_xupdate_riak.erl index 242485bf2..cff77f887 100644 --- a/src/mod_vcard_xupdate_riak.erl +++ b/src/mod_vcard_xupdate_riak.erl @@ -11,7 +11,7 @@ -behaviour(mod_vcard_xupdate). %% API --export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]). +-export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2]). -include("mod_vcard_xupdate.hrl"). @@ -36,8 +36,10 @@ get_xupdate(LUser, LServer) -> remove_xupdate(LUser, LServer) -> {atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})}. -import(_LServer, #vcard_xupdate{} = R) -> - ejabberd_riak:put(R, vcard_xupdate_schema()). +import(LServer, <<"vcard_xupdate">>, [LUser, Hash, _TimeStamp]) -> + ejabberd_riak:put( + #vcard_xupdate{us = {LUser, LServer}, hash = Hash}, + vcard_xupdate_schema()). %%%=================================================================== %%% Internal functions diff --git a/src/mod_vcard_xupdate_sql.erl b/src/mod_vcard_xupdate_sql.erl index 938114b8f..fd2716c33 100644 --- a/src/mod_vcard_xupdate_sql.erl +++ b/src/mod_vcard_xupdate_sql.erl @@ -13,8 +13,8 @@ -behaviour(mod_vcard_xupdate). %% API --export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2, - import/1, export/1]). +-export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2, + export/1]). -include("mod_vcard_xupdate.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -62,14 +62,8 @@ export(_Server) -> [] end}]. -import(LServer) -> - [{<<"select username, hash from vcard_xupdate;">>, - fun([LUser, Hash]) -> - #vcard_xupdate{us = {LUser, LServer}, hash = Hash} - end}]. - -import(_LServer, _) -> - pass. +import(_, _, _) -> + ok. %%%=================================================================== %%% Internal functions diff --git a/src/mod_version.erl b/src/mod_version.erl index 8a035763f..36bf796ee 100644 --- a/src/mod_version.erl +++ b/src/mod_version.erl @@ -31,13 +31,13 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_local_iq/3, +-export([start/2, stop/1, process_local_iq/1, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, @@ -50,48 +50,31 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VERSION). -process_local_iq(_From, To, - #iq{id = _ID, type = Type, xmlns = _XMLNS, - sub_el = SubEl, lang = Lang} = - IQ) -> - case Type of - set -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; - get -> - Host = To#jid.lserver, - OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os, - fun(B) when is_boolean(B) -> B end, - true) - of - true -> [get_os()]; - false -> [] - end, - IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_VERSION}], - children = - [#xmlel{name = <<"name">>, attrs = [], - children = - [{xmlcdata, <<"ejabberd">>}]}, - #xmlel{name = <<"version">>, attrs = [], - children = [{xmlcdata, ?VERSION}]}] - ++ OS}]} - end. +process_local_iq(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_local_iq(#iq{type = get, to = To} = IQ) -> + Host = To#jid.lserver, + OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os, + fun(B) when is_boolean(B) -> B end, + true) of + true -> get_os(); + false -> undefined + end, + xmpp:make_iq_result(IQ, #version{name = <<"ejabberd">>, + ver = ?VERSION, + os = OS}). get_os() -> {Osfamily, Osname} = os:type(), OSType = list_to_binary([atom_to_list(Osfamily), $/, atom_to_list(Osname)]), OSVersion = case os:version() of {Major, Minor, Release} -> - iolist_to_binary(io_lib:format("~w.~w.~w", + (str:format("~w.~w.~w", [Major, Minor, Release])); VersionString -> VersionString end, - OS = <>, - #xmlel{name = <<"os">>, attrs = [], - children = [{xmlcdata, OS}]}. + <>. depends(_Host, _Opts) -> []. diff --git a/src/node_buddy.erl b/src/node_buddy.erl index bdaa4a89a..fb2fd1f2e 100644 --- a/src/node_buddy.erl +++ b/src/node_buddy.erl @@ -28,7 +28,6 @@ -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). --include("jlib.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, diff --git a/src/node_club.erl b/src/node_club.erl index 7f6ae6520..837fa6fbb 100644 --- a/src/node_club.erl +++ b/src/node_club.erl @@ -28,7 +28,6 @@ -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). --include("jlib.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, diff --git a/src/node_dag.erl b/src/node_dag.erl index afb610ca7..45f8ade63 100644 --- a/src/node_dag.erl +++ b/src/node_dag.erl @@ -28,7 +28,7 @@ -author('bjc@kublai.com'). -include("pubsub.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, @@ -78,8 +78,9 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> case find_opt(node_type, Options) of collection -> Txt = <<"Publishing items to collection node is not allowed">>, - {error, - ?ERR_EXTENDED(?ERRT_NOT_ALLOWED(?MYLANG, Txt), <<"publish">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_allowed(Txt, ?MYLANG), + mod_pubsub:err_unsupported('publish'))}; _ -> node_hometree:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) diff --git a/src/node_dispatch.erl b/src/node_dispatch.erl index b3af69cd1..0a72b18d5 100644 --- a/src/node_dispatch.erl +++ b/src/node_dispatch.erl @@ -34,7 +34,7 @@ -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, @@ -94,10 +94,12 @@ delete_node(Nodes) -> subscribe_node(_Nidx, _Sender, _Subscriber, _AccessModel, _SendLast, _PresenceSubscription, _RosterGroup, _Options) -> - {error, ?ERR_FORBIDDEN}. + {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(), + mod_pubsub:err_unsupported('subscribe'))}. unsubscribe_node(_Nidx, _Sender, _Subscriber, _SubId) -> - {error, ?ERR_FORBIDDEN}. + {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(), + mod_pubsub:err_unsupported('subscribe'))}. publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, PubOpts) -> @@ -118,10 +120,12 @@ remove_extra_items(_Nidx, _MaxItems, ItemIds) -> {result, {ItemIds, []}}. delete_item(_Nidx, _Publisher, _PublishModel, _ItemId) -> - {error, ?ERR_ITEM_NOT_FOUND}. + {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(), + mod_pubsub:err_unsupported('delete-items'))}. purge_node(_Nidx, _Owner) -> - {error, ?ERR_FORBIDDEN}. + {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(), + mod_pubsub:err_unsupported('purge-nodes'))}. get_entity_affiliations(_Host, _Owner) -> {result, []}. diff --git a/src/node_flat.erl b/src/node_flat.erl index 7ead1d351..55093b0e7 100644 --- a/src/node_flat.erl +++ b/src/node_flat.erl @@ -34,7 +34,7 @@ -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, @@ -50,12 +50,12 @@ path_to_node/1, can_fetch_item/2, is_subscribed/1]). init(_Host, _ServerHost, _Opts) -> - %pubsub_subscription:init(), - mnesia:create_table(pubsub_state, + %pubsub_subscription:init(Host, ServerHost, Opts), + ejabberd_mnesia:create(?MODULE, pubsub_state, [{disc_copies, [node()]}, {type, ordered_set}, {attributes, record_info(fields, pubsub_state)}]), - mnesia:create_table(pubsub_item, + ejabberd_mnesia:create(?MODULE, pubsub_item, [{disc_only_copies, [node()]}, {attributes, record_info(fields, pubsub_item)}]), ItemsFields = record_info(fields, pubsub_item), @@ -84,6 +84,7 @@ options() -> {max_payload_size, ?MAX_PAYLOAD_SIZE}, {send_last_published_item, on_sub_and_presence}, {deliver_notifications, true}, + {title, <<>>}, {presence_based_delivery, false}, {itemreply, none}]. @@ -107,8 +108,8 @@ features() -> <<"retrieve-items">>, <<"retrieve-subscriptions">>, <<"subscribe">>, + %%<<"subscription-options">>, <<"subscription-notifications">>]. -%%<<"subscription-options">> %% @doc Checks if the current user has the permission to create the requested node %%

    In flat node, any unused node name is allowed. The access parameter is also @@ -196,27 +197,27 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, Owner = Affiliation == owner, if not Authorized -> {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)}; + mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_invalid_jid())}; (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; PendingSubscription -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_pending_subscription())}; (AccessModel == presence) and (not PresenceSubscription) and (not Owner) -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_presence_subscription_required())}; (AccessModel == roster) and (not RosterGroup) and (not Owner) -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_not_in_roster_group())}; (AccessModel == whitelist) and (not Whitelisted) and (not Owner) -> {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + mod_pubsub:extended_error((xmpp:err_not_allowed()), mod_pubsub:err_closed_node())}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; %%ForbiddenAnonymous -> %% % Requesting entity is anonymous - %% {error, ?ERR_FORBIDDEN}; + %% {error, xmpp:err_forbidden()}; true -> %%SubId = pubsub_subscription:add_subscription(Subscriber, Nidx, Options), {NewSub, SubId} = case Subscriptions of @@ -265,17 +266,17 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> if %% Requesting entity is prohibited from unsubscribing entity not Authorized -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; %% Entity did not specify SubId %%SubId == "", ?? -> - %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %% {error, mod_pubsub:extended_error(xmpp:err_bad_request(), "subid-required")}; %% Invalid subscription identifier %%InvalidSubId -> - %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + %% {error, mod_pubsub:extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; %% Requesting entity is not a subscriber Subscriptions == [] -> {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}; + mod_pubsub:extended_error(xmpp:err_unexpected_request(), mod_pubsub:err_not_subscribed())}; %% Subid supplied, so use that. SubIdExists -> Sub = first_in_list(fun @@ -289,7 +290,7 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> {result, default}; false -> {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)} + mod_pubsub:extended_error(xmpp:err_unexpected_request(), mod_pubsub:err_not_subscribed())} end; %% Asking to remove all subscriptions to the given node SubId == all -> @@ -302,7 +303,7 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> %% No subid and more than one possible subscription match. true -> {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)} + mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_subid_required())} end. delete_subscriptions(SubKey, Nidx, Subscriptions, SubState) -> @@ -366,7 +367,7 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, or (Affiliation == publisher) or (Affiliation == publish_only)) or (Subscribed == true)) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; true -> if MaxItems > 0 -> Now = p1_time_compat:timestamp(), @@ -425,7 +426,7 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) -> _ -> false end), if not Allowed -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; true -> case lists:member(ItemId, Items) of true -> @@ -450,9 +451,9 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) -> (_, Res) -> Res end, - {error, ?ERR_ITEM_NOT_FOUND}, States); + {error, xmpp:err_item_not_found()}, States); _ -> - {error, ?ERR_ITEM_NOT_FOUND} + {error, xmpp:err_forbidden()} end end end. @@ -474,7 +475,7 @@ purge_node(Nidx, Owner) -> States), {result, {default, broadcast}}; _ -> - {error, ?ERR_FORBIDDEN} + {error, xmpp:err_forbidden()} end. %% @doc

    Return the current affiliations for the given user

    @@ -581,7 +582,7 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) -> case Subscription of none -> {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)}; + mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_not_subscribed())}; _ -> new_subscription(Nidx, Owner, Subscription, SubState) end; @@ -592,7 +593,7 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) -> end; {<<>>, [_ | _]} -> {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}; + mod_pubsub:extended_error((xmpp:err_bad_request()), mod_pubsub:err_subid_required())}; _ -> case Subscription of none -> unsub_with_subid(Nidx, SubId, SubState); @@ -707,7 +708,7 @@ del_state(Nidx, Key) -> %% relational database), or they can even decide not to persist any items.

    get_items(Nidx, _From, _RSM) -> Items = mnesia:match_object(#pubsub_item{itemid = {'_', Nidx}, _ = '_'}), - {result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), none}}. + {result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), undefined}}. get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) -> SubKey = jid:tolower(JID), @@ -721,23 +722,23 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM can_fetch_item(Affiliation, FullSubscriptions), if %%SubId == "", ?? -> %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %{error, mod_pubsub:extended_error(xmpp:err_bad_request(), "subid-required")}; %%InvalidSubId -> %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + %{error, mod_pubsub:extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; (AccessModel == presence) and not PresenceSubscription -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_presence_subscription_required())}; (AccessModel == roster) and not RosterGroup -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_not_in_roster_group())}; (AccessModel == whitelist) and not Whitelisted -> {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + mod_pubsub:extended_error((xmpp:err_not_allowed()), mod_pubsub:err_closed_node())}; (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; @@ -750,7 +751,7 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM get_item(Nidx, ItemId) -> case mnesia:read({pubsub_item, {ItemId, Nidx}}) of [Item] when is_record(Item, pubsub_item) -> {result, Item}; - _ -> {error, ?ERR_ITEM_NOT_FOUND} + _ -> {error, xmpp:err_item_not_found()} end. get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> @@ -762,23 +763,23 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub Whitelisted = can_fetch_item(Affiliation, Subscriptions), if %%SubId == "", ?? -> %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %{error, mod_pubsub:extended_error(xmpp:err_bad_request(), "subid-required")}; %%InvalidSubId -> %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + %{error, mod_pubsub:extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; (AccessModel == presence) and not PresenceSubscription -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_presence_subscription_required())}; (AccessModel == roster) and not RosterGroup -> {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + mod_pubsub:extended_error((xmpp:err_not_authorized()), mod_pubsub:err_not_in_roster_group())}; (AccessModel == whitelist) and not Whitelisted -> {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + mod_pubsub:extended_error((xmpp:err_not_allowed()), mod_pubsub:err_closed_node())}; (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl index 61156ee06..7e5ce788f 100644 --- a/src/node_flat_sql.erl +++ b/src/node_flat_sql.erl @@ -36,7 +36,7 @@ -compile([{parse_transform, ejabberd_sql_pt}]). -include("pubsub.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("ejabberd_sql_pt.hrl"). -export([init/3, terminate/2, options/0, features/0, @@ -61,7 +61,7 @@ encode_host_like/1]). init(_Host, _ServerHost, _Opts) -> - %%pubsub_subscription_sql:init(), + %%pubsub_subscription_sql:init(Host, ServerHost, Opts), ok. terminate(_Host, _ServerHost) -> @@ -81,27 +81,27 @@ create_node(Nidx, Owner) -> J = encode_jid(OwnerKey), A = encode_affiliation(owner), S = encode_subscriptions([]), - catch ejabberd_sql:sql_query_t( - ?SQL("insert into pubsub_state(" - "nodeid, jid, affiliation, subscriptions) " - "values (%(Nidx)d, %(J)s, %(A)s, %(S)s)")), + ejabberd_sql:sql_query_t( + ?SQL("insert into pubsub_state(" + "nodeid, jid, affiliation, subscriptions) " + "values (%(Nidx)d, %(J)s, %(A)s, %(S)s)")), {result, {default, broadcast}}. delete_node(Nodes) -> - Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) -> - Subscriptions = case catch - ejabberd_sql:sql_query_t( - ?SQL("select @(jid)s, @(subscriptions)s " - "from pubsub_state where nodeid=%(Nidx)d")) - of - {selected, RItems} -> - [{decode_jid(SJID), decode_subscriptions(Subs)} || - {SJID, Subs} <- RItems]; - _ -> - [] - end, - {PubsubNode, Subscriptions} - end, Nodes), + Reply = lists:map( + fun(#pubsub_node{id = Nidx} = PubsubNode) -> + Subscriptions = + case ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(subscriptions)s " + "from pubsub_state where nodeid=%(Nidx)d")) of + {selected, RItems} -> + [{decode_jid(SJID), decode_subscriptions(Subs)} + || {SJID, Subs} <- RItems]; + _ -> + [] + end, + {PubsubNode, Subscriptions} + end, Nodes), {result, {default, broadcast, Reply}}. subscribe_node(Nidx, Sender, Subscriber, AccessModel, @@ -118,22 +118,25 @@ subscribe_node(Nidx, Sender, Subscriber, AccessModel, Subscriptions), Owner = Affiliation == owner, if not Authorized -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_bad_request(), mod_pubsub:err_invalid_jid())}; (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; PendingSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"pending-subscription">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_authorized(), + mod_pubsub:err_pending_subscription())}; (AccessModel == presence) and (not PresenceSubscription) and (not Owner) -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_authorized(), + mod_pubsub:err_presence_subscription_required())}; (AccessModel == roster) and (not RosterGroup) and (not Owner) -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_authorized(), + mod_pubsub:err_not_in_roster_group())}; (AccessModel == whitelist) and (not Whitelisted) and (not Owner) -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_allowed(), mod_pubsub:err_closed_node())}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; @@ -177,7 +180,7 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> if %% Requesting entity is prohibited from unsubscribing entity not Authorized -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; %% Entity did not specify SubId %%SubId == "", ?? -> %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; @@ -186,8 +189,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; %% Requesting entity is not a subscriber Subscriptions == [] -> - {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_unexpected_request(), + mod_pubsub:err_not_subscribed())}; %% Subid supplied, so use that. SubIdExists -> Sub = first_in_list(fun @@ -200,8 +204,9 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> delete_subscription(SubKey, Nidx, S, Affiliation, Subscriptions), {result, default}; false -> - {error, - ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), <<"not-subscribed">>)} + {error, mod_pubsub:extended_error( + xmpp:err_unexpected_request(), + mod_pubsub:err_not_subscribed())} end; %% Asking to remove all subscriptions to the given node SubId == all -> @@ -214,8 +219,8 @@ unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> {result, default}; %% No subid and more than one possible subscription match. true -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)} + {error, mod_pubsub:extended_error( + xmpp:err_bad_request(), mod_pubsub:err_subid_required())} end. delete_subscription(SubKey, Nidx, {Subscription, SubId}, Affiliation, Subscriptions) -> @@ -241,7 +246,7 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, or (Affiliation == publisher) or (Affiliation == publish_only)) or (Subscribed == true)) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; true -> if MaxItems > 0 -> PubId = {p1_time_compat:timestamp(), SubKey}, @@ -277,11 +282,11 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) -> _ -> false end), if not Allowed -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; true -> case del_item(Nidx, ItemId) of {updated, 1} -> {result, {default, broadcast}}; - _ -> {error, ?ERR_ITEM_NOT_FOUND} + _ -> {error, xmpp:err_item_not_found()} end end. @@ -299,7 +304,7 @@ purge_node(Nidx, Owner) -> States), {result, {default, broadcast}}; _ -> - {error, ?ERR_FORBIDDEN} + {error, xmpp:err_forbidden()} end. get_entity_affiliations(Host, Owner) -> @@ -307,48 +312,42 @@ get_entity_affiliations(Host, Owner) -> GenKey = jid:remove_resource(SubKey), H = encode_host(Host), J = encode_jid(GenKey), - Reply = case catch - ejabberd_sql:sql_query_t( - ?SQL("select @(node)s, @(type)s, @(i.nodeid)d, @(affiliation)s " - "from pubsub_state i, pubsub_node n where " - "i.nodeid = n.nodeid and jid=%(J)s and host=%(H)s")) - of - {selected, RItems} -> - [{nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), decode_affiliation(A)} - || {N, T, I, A} <- RItems]; - _ -> - [] - end, - {result, Reply}. + {result, + case ejabberd_sql:sql_query_t( + ?SQL("select @(node)s, @(type)s, @(i.nodeid)d, @(affiliation)s " + "from pubsub_state i, pubsub_node n where " + "i.nodeid = n.nodeid and jid=%(J)s and host=%(H)s")) of + {selected, RItems} -> + [{nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), + decode_affiliation(A)} || {N, T, I, A} <- RItems]; + _ -> + [] + end}. get_node_affiliations(Nidx) -> - Reply = case catch - ejabberd_sql:sql_query_t( - ?SQL("select @(jid)s, @(affiliation)s from pubsub_state " - "where nodeid=%(Nidx)d")) - of - {selected, RItems} -> - [{decode_jid(J), decode_affiliation(A)} || {J, A} <- RItems]; - _ -> - [] - end, - {result, Reply}. + {result, + case ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(affiliation)s from pubsub_state " + "where nodeid=%(Nidx)d")) of + {selected, RItems} -> + [{decode_jid(J), decode_affiliation(A)} || {J, A} <- RItems]; + _ -> + [] + end}. get_affiliation(Nidx, Owner) -> SubKey = jid:tolower(Owner), GenKey = jid:remove_resource(SubKey), J = encode_jid(GenKey), - Reply = case catch - ejabberd_sql:sql_query_t( - ?SQL("select @(affiliation)s from pubsub_state " - "where nodeid=%(Nidx)d and jid=%(J)s")) - of - {selected, [{A}]} -> - decode_affiliation(A); - _ -> - none - end, - {result, Reply}. + {result, + case ejabberd_sql:sql_query_t( + ?SQL("select @(affiliation)s from pubsub_state " + "where nodeid=%(Nidx)d and jid=%(J)s")) of + {selected, [{A}]} -> + decode_affiliation(A); + _ -> + none + end}. set_affiliation(Nidx, Owner, Affiliation) -> SubKey = jid:tolower(Owner), @@ -382,26 +381,26 @@ get_entity_subscriptions(Host, Owner) -> "where i.nodeid = n.nodeid and" " jid in (%(SJ)s, %(GJ)s) and host=%(H)s") end, - Reply = case catch ejabberd_sql:sql_query_t(Query) of - {selected, RItems} -> - lists:foldl(fun ({N, T, I, J, S}, Acc) -> - Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> - [{Node, none, Jid} | Acc]; - Subs -> - lists:foldl(fun ({Sub, SubId}, Acc2) -> - [{Node, Sub, SubId, Jid} | Acc2] - end, - Acc, Subs) - end - end, - [], RItems); - _ -> - [] - end, - {result, Reply}. + {result, + case ejabberd_sql:sql_query_t(Query) of + {selected, RItems} -> + lists:foldl( + fun({N, T, I, J, S}, Acc) -> + Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), + Jid = decode_jid(J), + case decode_subscriptions(S) of + [] -> + [{Node, none, Jid} | Acc]; + Subs -> + lists:foldl( + fun({Sub, SubId}, Acc2) -> + [{Node, Sub, SubId, Jid} | Acc2] + end, Acc, Subs) + end + end, [], RItems); + _ -> + [] + end}. -spec get_entity_subscriptions_for_send_last(Host :: mod_pubsub:hostPubsub(), Owner :: jid()) -> @@ -435,66 +434,62 @@ get_entity_subscriptions_for_send_last(Host, Owner) -> "and val='on_sub_and_presence' and" " jid in (%(SJ)s, %(GJ)s) and host=%(H)s") end, - Reply = case catch ejabberd_sql:sql_query_t(Query) of - {selected, RItems} -> - lists:foldl(fun ({N, T, I, J, S}, Acc) -> - Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> - [{Node, none, Jid} | Acc]; - Subs -> - lists:foldl(fun ({Sub, SubId}, Acc2) -> - [{Node, Sub, SubId, Jid}| Acc2] - end, - Acc, Subs) - end - end, - [], RItems); - _ -> - [] - end, - {result, Reply}. + {result, + case ejabberd_sql:sql_query_t(Query) of + {selected, RItems} -> + lists:foldl( + fun ({N, T, I, J, S}, Acc) -> + Node = nodetree_tree_sql:raw_to_node(Host, {N, <<"">>, T, I}), + Jid = decode_jid(J), + case decode_subscriptions(S) of + [] -> + [{Node, none, Jid} | Acc]; + Subs -> + lists:foldl( + fun ({Sub, SubId}, Acc2) -> + [{Node, Sub, SubId, Jid}| Acc2] + end, Acc, Subs) + end + end, [], RItems); + _ -> + [] + end}. get_node_subscriptions(Nidx) -> - Reply = case catch - ejabberd_sql:sql_query_t( - ?SQL("select @(jid)s, @(subscriptions)s from pubsub_state " - "where nodeid=%(Nidx)d")) - of - {selected, RItems} -> - lists:foldl(fun ({J, S}, Acc) -> - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> - [{Jid, none} | Acc]; - Subs -> - lists:foldl(fun ({Sub, SubId}, Acc2) -> - [{Jid, Sub, SubId} | Acc2] - end, - Acc, Subs) - end - end, - [], RItems); - _ -> - [] - end, - {result, Reply}. + {result, + case ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(subscriptions)s from pubsub_state " + "where nodeid=%(Nidx)d")) of + {selected, RItems} -> + lists:foldl( + fun ({J, S}, Acc) -> + Jid = decode_jid(J), + case decode_subscriptions(S) of + [] -> + [{Jid, none} | Acc]; + Subs -> + lists:foldl( + fun ({Sub, SubId}, Acc2) -> + [{Jid, Sub, SubId} | Acc2] + end, Acc, Subs) + end + end, [], RItems); + _ -> + [] + end}. get_subscriptions(Nidx, Owner) -> SubKey = jid:tolower(Owner), J = encode_jid(SubKey), - Reply = case catch - ejabberd_sql:sql_query_t( - ?SQL("select @(subscriptions)s from pubsub_state" - " where nodeid=%(Nidx)d and jid=%(J)s")) - of - {selected, [{S}]} -> - decode_subscriptions(S); - _ -> - [] - end, - {result, Reply}. + {result, + case ejabberd_sql:sql_query_t( + ?SQL("select @(subscriptions)s from pubsub_state" + " where nodeid=%(Nidx)d and jid=%(J)s")) of + {selected, [{S}]} -> + decode_subscriptions(S); + _ -> + [] + end}. set_subscriptions(Nidx, Owner, Subscription, SubId) -> SubKey = jid:tolower(Owner), @@ -503,8 +498,9 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) -> {_, []} -> case Subscription of none -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"not-subscribed">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_bad_request(), + mod_pubsub:err_not_subscribed())}; _ -> new_subscription(Nidx, Owner, Subscription, SubState) end; @@ -514,8 +510,9 @@ set_subscriptions(Nidx, Owner, Subscription, SubId) -> _ -> replace_subscription({Subscription, SID}, SubState) end; {<<>>, [_ | _]} -> - {error, - ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_bad_request(), + mod_pubsub:err_subid_required())}; _ -> case Subscription of none -> unsub_with_subid(Nidx, SubId, SubState); @@ -585,21 +582,19 @@ get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs} end. get_states(Nidx) -> - case catch - ejabberd_sql:sql_query_t( - ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s " - "from pubsub_state where nodeid=%(Nidx)d")) - of + case ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s " + "from pubsub_state where nodeid=%(Nidx)d")) of {selected, RItems} -> {result, - lists:map(fun ({SJID, Aff, Subs}) -> - JID = decode_jid(SJID), - #pubsub_state{stateid = {JID, Nidx}, - items = itemids(Nidx, JID), - affiliation = decode_affiliation(Aff), - subscriptions = decode_subscriptions(Subs)} - end, - RItems)}; + lists:map( + fun({SJID, Aff, Subs}) -> + JID = decode_jid(SJID), + #pubsub_state{stateid = {JID, Nidx}, + items = itemids(Nidx, JID), + affiliation = decode_affiliation(Aff), + subscriptions = decode_subscriptions(Subs)} + end, RItems)}; _ -> {result, []} end. @@ -614,16 +609,14 @@ get_state(Nidx, JID) -> get_state_without_itemids(Nidx, JID) -> J = encode_jid(JID), - case catch - ejabberd_sql:sql_query_t( - ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s " - "from pubsub_state " - "where nodeid=%(Nidx)d and jid=%(J)s")) - of + case ejabberd_sql:sql_query_t( + ?SQL("select @(jid)s, @(affiliation)s, @(subscriptions)s " + "from pubsub_state " + "where nodeid=%(Nidx)d and jid=%(J)s")) of {selected, [{SJID, Aff, Subs}]} -> #pubsub_state{stateid = {decode_jid(SJID), Nidx}, - affiliation = decode_affiliation(Aff), - subscriptions = decode_subscriptions(Subs)}; + affiliation = decode_affiliation(Aff), + subscriptions = decode_subscriptions(Subs)}; _ -> #pubsub_state{stateid = {JID, Nidx}} end. @@ -653,73 +646,60 @@ del_state(Nidx, JID) -> " where jid=%(J)s and nodeid=%(Nidx)d")), ok. -%get_items(Nidx, _From) -> -% case catch -% ejabberd_sql:sql_query_t([<<"select itemid, publisher, creation, modification, payload " -% "from pubsub_item where nodeid='">>, Nidx, -% <<"' order by modification desc;">>]) -% of -% {selected, -% [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} -> -% {result, [raw_to_item(Nidx, RItem) || RItem <- RItems]}; -% _ -> -% {result, []} -% end. - -get_items(Nidx, From, none) -> - MaxItems = case catch - ejabberd_sql:sql_query_t( - ?SQL("select @(val)s from pubsub_node_option " - "where nodeid=%(Nidx)d and name='max_items'")) - of - {selected, [{Value}]} -> - jlib:expr_to_term(Value); - _ -> - ?MAXITEMS - end, - get_items(Nidx, From, #rsm_in{max = MaxItems}); -get_items(Nidx, _From, - #rsm_in{max = M, direction = Direction, id = I, index = IncIndex}) -> - Max = ejabberd_sql:escape(jlib:i2l(M)), - {Way, Order} = case Direction of - aft when I == <<>> -> {<<"is not">>, <<"desc">>}; - aft -> {<<"<">>, <<"desc">>}; - before when I == <<>> -> {<<"is not">>, <<"asc">>}; - before -> {<<">">>, <<"asc">>}; - _ -> {<<"is not">>, <<"desc">>} - end, +get_items(Nidx, From, undefined) -> + MaxItems = case ejabberd_sql:sql_query_t( + ?SQL("select @(val)s from pubsub_node_option " + "where nodeid=%(Nidx)d and name='max_items'")) of + {selected, [{Value}]} -> + jlib:expr_to_term(Value); + _ -> + ?MAXITEMS + end, + get_items(Nidx, From, #rsm_set{max = MaxItems}); +get_items(Nidx, _From, #rsm_set{max = Max, index = IncIndex, + 'after' = After, before = Before}) -> + {Way, Order} = if After == <<>> -> {<<"is not">>, <<"desc">>}; + After /= undefined -> {<<"<">>, <<"desc">>}; + Before == <<>> -> {<<"is not">>, <<"asc">>}; + Before /= undefined -> {<<">">>, <<"asc">>}; + true -> {<<"is not">>, <<"desc">>} + end, SNidx = jlib:i2l(Nidx), - [AttrName, Id] = case I of - undefined when IncIndex =/= undefined -> - case catch - ejabberd_sql:sql_query_t([<<"select modification from pubsub_item pi " - "where exists ( select count(*) as count1 " - "from pubsub_item where nodeid='">>, SNidx, + I = if After /= undefined -> After; + Before /= undefined -> Before; + true -> undefined + end, + [AttrName, Id] = + case I of + undefined when IncIndex =/= undefined -> + case ejabberd_sql:sql_query_t( + [<<"select modification from pubsub_item pi " + "where exists ( select count(*) as count1 " + "from pubsub_item where nodeid='">>, SNidx, <<"' and modification > pi.modification having count1 = ">>, - ejabberd_sql:escape(jlib:i2l(IncIndex)), <<" );">>]) - of - {selected, [_], [[O]]} -> - [<<"modification">>, <<"'", O/binary, "'">>]; - _ -> - [<<"modification">>, <<"null">>] - end; - undefined -> - [<<"modification">>, <<"null">>]; - <<>> -> - [<<"modification">>, <<"null">>]; - I -> - [A, B] = str:tokens(ejabberd_sql:escape(jlib:i2l(I)), <<"@">>), - [A, <<"'", B/binary, "'">>] - end, - Count = case catch - ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item where nodeid='">>, SNidx, <<"';">>]) - of - {selected, [_], [[C]]} -> C; - _ -> <<"0">> - end, + integer_to_binary(IncIndex), <<" );">>]) of + {selected, [_], [[O]]} -> + [<<"modification">>, <<"'", O/binary, "'">>]; + _ -> + [<<"modification">>, <<"null">>] + end; + undefined -> + [<<"modification">>, <<"null">>]; + <<>> -> + [<<"modification">>, <<"null">>]; + I -> + [A, B] = str:tokens(ejabberd_sql:escape(I), <<"@">>), + [A, <<"'", B/binary, "'">>] + end, + Count = case ejabberd_sql:sql_query_t( + [<<"select count(*) from pubsub_item where nodeid='">>, + SNidx, <<"';">>]) of + {selected, [_], [[C]]} -> binary_to_integer(C); + _ -> 0 + end, Query = fun(mssql, _) -> ejabberd_sql:sql_query_t( - [<<"select top ">>, jlib:i2l(Max), + [<<"select top ">>, integer_to_binary(Max), <<" itemid, publisher, creation, modification, payload " "from pubsub_item where nodeid='">>, SNidx, <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>, @@ -729,32 +709,34 @@ get_items(Nidx, _From, [<<"select itemid, publisher, creation, modification, payload " "from pubsub_item where nodeid='">>, SNidx, <<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>, - AttrName, <<" ">>, Order, <<" limit ">>, jlib:i2l(Max), <<" ;">>]) + AttrName, <<" ">>, Order, <<" limit ">>, + integer_to_binary(Max), <<" ;">>]) end, - case catch ejabberd_sql:sql_query_t(Query) of - {selected, - [<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} -> + case ejabberd_sql:sql_query_t(Query) of + {selected, [<<"itemid">>, <<"publisher">>, <<"creation">>, + <<"modification">>, <<"payload">>], RItems} -> case RItems of [[_, _, _, F, _]|_] -> - Index = case catch - ejabberd_sql:sql_query_t([<<"select count(*) from pubsub_item " - "where nodeid='">>, SNidx, <<"' and ">>, - AttrName, <<" > '">>, F, <<"';">>]) - of - %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")]; - {selected, [_], [[In]]} -> In; - _ -> <<"0">> - end, + Index = case catch ejabberd_sql:sql_query_t( + [<<"select count(*) from pubsub_item " + "where nodeid='">>, SNidx, <<"' and ">>, + AttrName, <<" > '">>, F, <<"';">>]) of + {selected, [_], [[In]]} -> binary_to_integer(In); + _ -> 0 + end, [_, _, _, L, _] = lists:last(RItems), - RsmOut = #rsm_out{count = Count, index = Index, - first = <<"modification@", F/binary>>, - last = <<"modification@", (jlib:i2l(L))/binary>>}, + RsmOut = #rsm_set{count = Count, + index = Index, + first = #rsm_first{ + index = Index, + data = <<"modification@", F/binary>>}, + last = <<"modification@", L/binary>>}, {result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], RsmOut}}; [] -> - {result, {[], #rsm_out{count = Count}}} + {result, {[], #rsm_set{count = Count}}} end; _ -> - {result, {[], none}} + {result, {[], undefined}} end. get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) -> @@ -769,18 +751,20 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM %% Entity is subscribed but specifies an invalid subscription ID %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; (AccessModel == presence) and not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_authorized(), + mod_pubsub:err_presence_subscription_required())}; (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_authorized(), + mod_pubsub:err_not_in_roster_group())}; (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_allowed(), mod_pubsub:err_closed_node())}; (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; @@ -820,9 +804,9 @@ get_item(Nidx, ItemId) -> {selected, [RItem]} -> {result, raw_to_item(Nidx, RItem)}; {selected, []} -> - {error, ?ERR_ITEM_NOT_FOUND}; + {error, xmpp:err_item_not_found()}; {'EXIT', _} -> - {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)} + {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)} end. get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> @@ -837,18 +821,20 @@ get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _Sub %% Entity is subscribed but specifies an invalid subscription ID %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; (Affiliation == outcast) or (Affiliation == publish_only) -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; (AccessModel == presence) and not PresenceSubscription -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"presence-subscription-required">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_authorized(), + mod_pubsub:err_presence_subscription_required())}; (AccessModel == roster) and not RosterGroup -> - {error, - ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_authorized(), + mod_pubsub:err_not_in_roster_group())}; (AccessModel == whitelist) and not Whitelisted -> - {error, - ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + {error, mod_pubsub:extended_error( + xmpp:err_not_allowed(), mod_pubsub:err_closed_node())}; (AccessModel == authorize) and not Whitelisted -> - {error, ?ERR_FORBIDDEN}; + {error, xmpp:err_forbidden()}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; @@ -915,7 +901,7 @@ first_in_list(Pred, [H | T]) -> itemids(Nidx, {_U, _S, _R} = JID) -> SJID = encode_jid(JID), - SJIDLike = <<(ejabberd_sql:escape(encode_jid_like(JID)))/binary, "/%">>, + SJIDLike = <<(encode_jid_like(JID))/binary, "/%">>, case catch ejabberd_sql:sql_query_t( ?SQL("select @(itemid)s from pubsub_item where " diff --git a/src/node_hometree.erl b/src/node_hometree.erl index def7b983d..67c5e9332 100644 --- a/src/node_hometree.erl +++ b/src/node_hometree.erl @@ -28,7 +28,6 @@ -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). --include("jlib.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, diff --git a/src/node_hometree_sql.erl b/src/node_hometree_sql.erl index d9af49843..661a2aab4 100644 --- a/src/node_hometree_sql.erl +++ b/src/node_hometree_sql.erl @@ -28,7 +28,6 @@ -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). --include("jlib.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, diff --git a/src/node_mb.erl b/src/node_mb.erl index 3399422e5..c06c08d67 100644 --- a/src/node_mb.erl +++ b/src/node_mb.erl @@ -28,7 +28,6 @@ -author('ecestari@process-one.net'). -include("pubsub.hrl"). --include("jlib.hrl"). %%% @doc The module {@module} is the pep microblog PubSub plugin. %%%

    To be used, mod_pubsub must be configured:

    diff --git a/src/node_mb_sql.erl b/src/node_mb_sql.erl
    index 125674316..0f5c409ff 100644
    --- a/src/node_mb_sql.erl
    +++ b/src/node_mb_sql.erl
    @@ -28,7 +28,6 @@
     -author('holger@zedat.fu-berlin.de').
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
     
     -export([init/3, terminate/2, options/0, features/0,
         create_node_permission/6, create_node/2, delete_node/1,
    diff --git a/src/node_online.erl b/src/node_online.erl
    index 1c2ab5a03..2620e6a49 100644
    --- a/src/node_online.erl
    +++ b/src/node_online.erl
    @@ -28,7 +28,7 @@
     -author('christophe.romain@process-one.net').
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
    +-include("jid.hrl").
     
     -export([init/3, terminate/2, options/0, features/0,
         create_node_permission/6, create_node/2, delete_node/1,
    @@ -57,6 +57,7 @@ terminate(Host, ServerHost) ->
     			  ?MODULE, user_offline, 75),
         ok.
     
    +-spec user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> _.
     user_offline(_SID, #jid{luser=LUser,lserver=LServer}, _Info) ->
         mod_pubsub:remove_user(LUser, LServer).
     
    diff --git a/src/node_pep.erl b/src/node_pep.erl
    index 1677ed4bd..f3b5836cf 100644
    --- a/src/node_pep.erl
    +++ b/src/node_pep.erl
    @@ -31,7 +31,6 @@
     -author('christophe.romain@process-one.net').
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
     -include("logger.hrl").
     
     -export([init/3, terminate/2, options/0, features/0,
    diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl
    index ec7795475..ac42cb94f 100644
    --- a/src/node_pep_sql.erl
    +++ b/src/node_pep_sql.erl
    @@ -31,7 +31,6 @@
     -author('christophe.romain@process-one.net').
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
     -include("logger.hrl").
     
     -export([init/3, terminate/2, options/0, features/0,
    diff --git a/src/node_private.erl b/src/node_private.erl
    index 0cd04b9dd..1888ce33d 100644
    --- a/src/node_private.erl
    +++ b/src/node_private.erl
    @@ -28,7 +28,6 @@
     -author('christophe.romain@process-one.net').
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
     
     -export([init/3, terminate/2, options/0, features/0,
         create_node_permission/6, create_node/2, delete_node/1,
    diff --git a/src/node_public.erl b/src/node_public.erl
    index 0786d9995..ca200e002 100644
    --- a/src/node_public.erl
    +++ b/src/node_public.erl
    @@ -28,7 +28,6 @@
     -author('christophe.romain@process-one.net').
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
     
     -export([init/3, terminate/2, options/0, features/0,
         create_node_permission/6, create_node/2, delete_node/1,
    diff --git a/src/nodetree_dag.erl b/src/nodetree_dag.erl
    index 387d98413..f17f2846d 100644
    --- a/src/nodetree_dag.erl
    +++ b/src/nodetree_dag.erl
    @@ -30,7 +30,7 @@
     -include_lib("stdlib/include/qlc.hrl").
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
    +-include("xmpp.hrl").
     
     -export([init/3, terminate/2, options/0, set_node/1,
         get_node/3, get_node/2, get_node/1, get_nodes/2,
    @@ -69,13 +69,13 @@ create_node(Key, Node, Type, Owner, Options, Parents) ->
     		Other -> Other
     	    end;
     	_ ->
    -	    {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)}
    +	    {error, xmpp:err_conflict(<<"Node already exists">>, ?MYLANG)}
         end.
     
     delete_node(Key, Node) ->
         case find_node(Key, Node) of
     	false ->
    -	    {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
    +	    {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
     	Record ->
     	    lists:foreach(fun (#pubsub_node{options = Opts} = Child) ->
     			NewOpts = remove_config_parent(Node, Opts),
    @@ -99,7 +99,7 @@ get_node(Host, Node, _From) ->
     
     get_node(Host, Node) ->
         case find_node(Host, Node) of
    -	false -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
    +	false -> {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
     	Record -> Record
         end.
     
    @@ -115,7 +115,7 @@ get_nodes(Key) ->
     get_parentnodes(Host, Node, _From) ->
         case find_node(Host, Node) of
     	false ->
    -	    {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
    +	    {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
     	#pubsub_node{parents = Parents} ->
     	    Q = qlc:q([N
     			|| #pubsub_node{nodeid = {NHost, NNode}} = N
    @@ -139,7 +139,7 @@ get_subnodes(Host, <<>>) ->
         get_subnodes_helper(Host, <<>>);
     get_subnodes(Host, Node) ->
         case find_node(Host, Node) of
    -	false -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
    +	false -> {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
     	_ -> get_subnodes_helper(Host, Node)
         end.
     
    @@ -226,13 +226,13 @@ validate_parentage(Key, Owners, [<<>> | T]) ->
     validate_parentage(Key, Owners, [ParentID | T]) ->
         case find_node(Key, ParentID) of
     	false ->
    -	    {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)};
    +	    {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)};
     	#pubsub_node{owners = POwners, options = POptions} ->
     	    NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
     	    MutualOwners = [O || O <- Owners, PO <- POwners, O == PO],
     	    case {MutualOwners, NodeType} of
    -		{[], _} -> {error, ?ERR_FORBIDDEN};
    +		{[], _} -> {error, xmpp:err_forbidden()};
     		{_, collection} -> validate_parentage(Key, Owners, T);
    -		{_, _} -> {error, ?ERR_NOT_ALLOWED}
    +		{_, _} -> {error, xmpp:err_not_allowed()}
     	    end
         end.
    diff --git a/src/nodetree_tree.erl b/src/nodetree_tree.erl
    index 69b50ff9f..eb28e3408 100644
    --- a/src/nodetree_tree.erl
    +++ b/src/nodetree_tree.erl
    @@ -40,7 +40,7 @@
     -include_lib("stdlib/include/qlc.hrl").
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
    +-include("xmpp.hrl").
     
     -export([init/3, terminate/2, options/0, set_node/1,
         get_node/3, get_node/2, get_node/1, get_nodes/2,
    @@ -49,7 +49,7 @@
         delete_node/2]).
     
     init(_Host, _ServerHost, _Options) ->
    -    mnesia:create_table(pubsub_node,
    +    ejabberd_mnesia:create(?MODULE, pubsub_node,
     	[{disc_copies, [node()]},
     	    {attributes, record_info(fields, pubsub_node)}]),
         mnesia:add_table_index(pubsub_node, id),
    @@ -76,13 +76,13 @@ get_node(Host, Node, _From) ->
     get_node(Host, Node) ->
         case mnesia:read({pubsub_node, {Host, Node}}) of
     	[Record] when is_record(Record, pubsub_node) -> Record;
    -	_ -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
    +	_ -> {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)}
         end.
     
     get_node(Nidx) ->
         case mnesia:index_read(pubsub_node, Nidx, #pubsub_node.id) of
     	[Record] when is_record(Record, pubsub_node) -> Record;
    -	_ -> {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
    +	_ -> {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)}
         end.
     
     get_nodes(Host, _From) ->
    @@ -180,10 +180,10 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
     			    options = Options}),
     		    {ok, Nidx};
     		false ->
    -		    {error, ?ERR_FORBIDDEN}
    +		    {error, xmpp:err_forbidden()}
     	    end;
     	_ ->
    -	    {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)}
    +	    {error, xmpp:err_conflict(<<"Node already exists">>, ?MYLANG)}
         end.
     
     delete_node(Host, Node) ->
    diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl
    index 9f6b6d5a7..5e4462160 100644
    --- a/src/nodetree_tree_sql.erl
    +++ b/src/nodetree_tree_sql.erl
    @@ -40,7 +40,7 @@
     -compile([{parse_transform, ejabberd_sql_pt}]).
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
    +-include("xmpp.hrl").
     -include("ejabberd_sql_pt.hrl").
     
     -export([init/3, terminate/2, options/0, set_node/1,
    @@ -97,7 +97,7 @@ set_node(Record) when is_record(Record, pubsub_node) ->
         case Nidx of
     	none ->
     	    Txt = <<"Node index not found">>,
    -	    {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, Txt)};
    +	    {error, xmpp:err_internal_server_error(Txt, ?MYLANG)};
     	_ ->
     	    lists:foreach(fun ({Key, Value}) ->
     			SKey = iolist_to_binary(atom_to_list(Key)),
    @@ -125,9 +125,9 @@ get_node(Host, Node) ->
     	{selected, [RItem]} ->
     	    raw_to_node(Host, RItem);
     	{'EXIT', _Reason} ->
    -	    {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
    +	    {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)};
     	_ ->
    -	    {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
    +	    {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)}
         end.
     
     get_node(Nidx) ->
    @@ -139,9 +139,9 @@ get_node(Nidx) ->
     	{selected, [{Host, Node, Parent, Type}]} ->
     	    raw_to_node(Host, {Node, Parent, Type, Nidx});
     	{'EXIT', _Reason} ->
    -	    {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)};
    +	    {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)};
     	_ ->
    -	    {error, ?ERRT_ITEM_NOT_FOUND(?MYLANG, <<"Node not found">>)}
    +	    {error, xmpp:err_item_not_found(<<"Node not found">>, ?MYLANG)}
         end.
     
     get_nodes(Host, _From) ->
    @@ -249,12 +249,12 @@ create_node(Host, Node, Type, Owner, Options, Parents) ->
     			Other -> Other
     		    end;
     		false ->
    -		    {error, ?ERR_FORBIDDEN}
    +		    {error, xmpp:err_forbidden()}
     	    end;
     	{result, _} ->
    -	    {error, ?ERRT_CONFLICT(?MYLANG, <<"Node already exists">>)};
    +	    {error, xmpp:err_conflict(<<"Node already exists">>, ?MYLANG)};
     	{error, db_fail} ->
    -	    {error, ?ERRT_INTERNAL_SERVER_ERROR(?MYLANG, <<"Database failure">>)}
    +	    {error, xmpp:err_internal_server_error(<<"Database failure">>, ?MYLANG)}
         end.
     
     delete_node(Host, Node) ->
    diff --git a/src/nodetree_virtual.erl b/src/nodetree_virtual.erl
    index 934950dd2..31802db2b 100644
    --- a/src/nodetree_virtual.erl
    +++ b/src/nodetree_virtual.erl
    @@ -35,7 +35,6 @@
     -author('christophe.romain@process-one.net').
     
     -include("pubsub.hrl").
    --include("jlib.hrl").
     
     -export([init/3, terminate/2, options/0, set_node/1,
         get_node/3, get_node/2, get_node/1, get_nodes/2,
    diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl
    index 204cfec2f..fcc472dce 100644
    --- a/src/prosody2ejabberd.erl
    +++ b/src/prosody2ejabberd.erl
    @@ -12,7 +12,7 @@
     -export([from_dir/1]).
     
     -include("ejabberd.hrl").
    --include("jlib.hrl").
    +-include("xmpp.hrl").
     -include("logger.hrl").
     -include("mod_roster.hrl").
     -include("mod_offline.hrl").
    @@ -300,8 +300,8 @@ convert_privacy_item({_, Item}) ->
     	      match_presence_out = MatchPresOut}.
     
     el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) ->
    -    case jlib:datetime_string_to_timestamp(
    -	   fxml:get_attr_s(<<"stamp">>, Attrs)) of
    +    try xmpp_util:decode_timestamp(
    +	  fxml:get_attr_s(<<"stamp">>, Attrs)) of
     	{_, _, _} = TS ->
     	    Attrs1 = lists:filter(
     		       fun(<<"stamp">>) -> false;
    @@ -321,8 +321,8 @@ el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) ->
     			packet = Packet}];
     		_ ->
     		    []
    -	    end;
    -	_ ->
    +	    end
    +    catch _:{bad_timestamp, _} ->
     	    []
         end.
     
    diff --git a/src/pubsub_db_sql.erl b/src/pubsub_db_sql.erl
    index b910a5e7d..986a0b9b2 100644
    --- a/src/pubsub_db_sql.erl
    +++ b/src/pubsub_db_sql.erl
    @@ -75,27 +75,27 @@ add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) ->
     	Opts),
         ok.
     
    -subscription_opt_from_sql({<<"DELIVER">>, Value}) ->
    +subscription_opt_from_sql([<<"DELIVER">>, Value]) ->
         {deliver, sql_to_boolean(Value)};
    -subscription_opt_from_sql({<<"DIGEST">>, Value}) ->
    +subscription_opt_from_sql([<<"DIGEST">>, Value]) ->
         {digest, sql_to_boolean(Value)};
    -subscription_opt_from_sql({<<"DIGEST_FREQUENCY">>, Value}) ->
    +subscription_opt_from_sql([<<"DIGEST_FREQUENCY">>, Value]) ->
         {digest_frequency, sql_to_integer(Value)};
    -subscription_opt_from_sql({<<"EXPIRE">>, Value}) ->
    +subscription_opt_from_sql([<<"EXPIRE">>, Value]) ->
         {expire, sql_to_timestamp(Value)};
    -subscription_opt_from_sql({<<"INCLUDE_BODY">>, Value}) ->
    +subscription_opt_from_sql([<<"INCLUDE_BODY">>, Value]) ->
         {include_body, sql_to_boolean(Value)};
     %%TODO: might be > than 1 show_values value??.
     %%      need to use compact all in only 1 opt.
    -subscription_opt_from_sql({<<"SHOW_VALUES">>, Value}) ->
    +subscription_opt_from_sql([<<"SHOW_VALUES">>, Value]) ->
         {show_values, Value};
    -subscription_opt_from_sql({<<"SUBSCRIPTION_TYPE">>, Value}) ->
    +subscription_opt_from_sql([<<"SUBSCRIPTION_TYPE">>, Value]) ->
         {subscription_type,
     	case Value of
     	    <<"items">> -> items;
     	    <<"nodes">> -> nodes
     	end};
    -subscription_opt_from_sql({<<"SUBSCRIPTION_DEPTH">>, Value}) ->
    +subscription_opt_from_sql([<<"SUBSCRIPTION_DEPTH">>, Value]) ->
         {subscription_depth,
     	case Value of
     	    <<"all">> -> all;
    @@ -127,15 +127,15 @@ subscription_opt_to_sql({subscription_depth, Depth}) ->
     	    N -> integer_to_sql(N)
     	end}.
     
    -integer_to_sql(N) -> iolist_to_binary(integer_to_list(N)).
    +integer_to_sql(N) -> integer_to_binary(N).
     
     boolean_to_sql(true) -> <<"1">>;
     boolean_to_sql(false) -> <<"0">>.
     
    -timestamp_to_sql(T) -> jlib:now_to_utc_string(T).
    +timestamp_to_sql(T) -> xmpp_util:encode_timestamp(T).
     
    -sql_to_integer(N) -> jlib:binary_to_integer(N).
    +sql_to_integer(N) -> binary_to_integer(N).
     
     sql_to_boolean(B) -> B == <<"1">>.
     
    -sql_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T).
    +sql_to_timestamp(T) -> xmpp_util:decode_timestamp(T).
    diff --git a/src/pubsub_index.erl b/src/pubsub_index.erl
    index 983356a18..45361e141 100644
    --- a/src/pubsub_index.erl
    +++ b/src/pubsub_index.erl
    @@ -34,7 +34,7 @@
     -export([init/3, new/1, free/2]).
     
     init(_Host, _ServerHost, _Opts) ->
    -    mnesia:create_table(pubsub_index,
    +    ejabberd_mnesia:create(?MODULE, pubsub_index,
     	[{disc_copies, [node()]},
     	    {attributes, record_info(fields, pubsub_index)}]).
     
    diff --git a/src/pubsub_migrate.erl b/src/pubsub_migrate.erl
    index c493b58f9..a329f3c39 100644
    --- a/src/pubsub_migrate.erl
    +++ b/src/pubsub_migrate.erl
    @@ -253,7 +253,7 @@ update_node_database(Host, ServerHost) ->
     	      end,
     	  {atomic, NewRecords} = mnesia:transaction(F),
     	  {atomic, ok} = mnesia:delete_table(pubsub_node),
    -	  {atomic, ok} = mnesia:create_table(pubsub_node,
    +	  {atomic, ok} = ejabberd_mnesia:create(?MODULE, pubsub_node,
     					     [{disc_copies, [node()]},
     					      {attributes,
     					       record_info(fields,
    @@ -421,7 +421,7 @@ update_state_database(_Host, _ServerHost) ->
     	    {atomic, NewRecs} = mnesia:transaction(fun mnesia:foldl/3,
     						   [F, [], pubsub_state]),
     	    {atomic, ok} = mnesia:delete_table(pubsub_state),
    -	    {atomic, ok} = mnesia:create_table(pubsub_state,
    +	    {atomic, ok} = ejabberd_mnesia:create(?MODULE, pubsub_state,
     					       [{disc_copies, [node()]},
     						{attributes, record_info(fields, pubsub_state)}]),
     	    FNew = fun () ->
    diff --git a/src/pubsub_subscription.erl b/src/pubsub_subscription.erl
    index 3ab502184..0ca066dae 100644
    --- a/src/pubsub_subscription.erl
    +++ b/src/pubsub_subscription.erl
    @@ -28,7 +28,7 @@
     -author("bjc@kublai.com").
     
     %% API
    --export([init/0, subscribe_node/3, unsubscribe_node/3,
    +-export([init/3, subscribe_node/3, unsubscribe_node/3,
         get_subscription/3, set_subscription/4,
         make_subid/0,
         get_options_xform/2, parse_options_xform/1]).
    @@ -39,7 +39,7 @@
     
     -include("pubsub.hrl").
     
    --include("jlib.hrl").
    +-include("xmpp.hrl").
     
     -define(PUBSUB_DELIVER, <<"pubsub#deliver">>).
     -define(PUBSUB_DIGEST, <<"pubsub#digest">>).
    @@ -73,7 +73,7 @@
     %%====================================================================
     %% API
     %%====================================================================
    -init() -> ok = create_table().
    +init(_Host, _ServerHost, _Opts) -> ok = create_table().
     
     subscribe_node(JID, NodeId, Options) ->
         case catch mnesia:sync_dirty(fun add_subscription/3, [JID, NodeId, Options])
    @@ -112,36 +112,21 @@ get_options_xform(Lang, Options) ->
         Keys = [deliver, show_values, subscription_type, subscription_depth],
         XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
         {result,
    -	#xmlel{name = <<"x">>,
    -	    attrs = [{<<"xmlns">>, ?NS_XDATA}],
    -	    children =
    -	    [#xmlel{name = <<"field">>,
    -		    attrs =
    -		    [{<<"var">>, <<"FORM_TYPE">>},
    -			{<<"type">>, <<"hidden">>}],
    -		    children =
    -		    [#xmlel{name = <<"value">>, attrs = [],
    -			    children =
    -			    [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}]
    -	    ++ XFields}}.
    +     #xdata{type = form,
    +	    fields = [#xdata_field{type = hidden,
    +				   var = <<"FORM_TYPE">>,
    +				   values = [?NS_PUBSUB_SUB_OPTIONS]}|
    +		      XFields]}}.
     
     parse_options_xform(XFields) ->
    -    case fxml:remove_cdata(XFields) of
    -	[#xmlel{name = <<"x">>} = XEl] ->
    -	    case jlib:parse_xdata_submit(XEl) of
    -		XData when is_list(XData) ->
    -		    Opts = set_xoption(XData, []),
    -		    {result, Opts};
    -		Other -> Other
    -	    end;
    -	_ -> {result, []}
    -    end.
    +    Opts = set_xoption(XFields, []),
    +    {result, Opts}.
     
     %%====================================================================
     %% Internal functions
     %%====================================================================
     create_table() ->
    -    case mnesia:create_table(pubsub_subscription,
    +    case ejabberd_mnesia:create(?MODULE, pubsub_subscription,
     	    [{disc_copies, [node()]},
     		{attributes,
     		    record_info(fields, pubsub_subscription)},
    @@ -185,7 +170,7 @@ write_subscription(_JID, _NodeId, SubID, Options) ->
     -spec make_subid() -> SubId::mod_pubsub:subId().
     make_subid() ->
         {T1, T2, T3} = p1_time_compat:timestamp(),
    -    iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
    +    (str:format("~.16B~.16B~.16B", [T1, T2, T3])).
     
     %%
     %% Subscription XForm processing.
    @@ -218,26 +203,29 @@ var_xfield(_) -> {error, badarg}.
     val_xfield(deliver = Opt, [Val]) -> xopt_to_bool(Opt, Val);
     val_xfield(digest = Opt, [Val]) -> xopt_to_bool(Opt, Val);
     val_xfield(digest_frequency = Opt, [Val]) ->
    -    case catch jlib:binary_to_integer(Val) of
    +    case catch binary_to_integer(Val) of
     	N when is_integer(N) -> N;
     	_ ->
    -	    Txt = <<"Value of '~s' should be integer">>,
    -	    ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
    -	    {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
    +	    Txt = {<<"Value of '~s' should be integer">>, [Opt]},
    +	    {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
    +    end;
    +val_xfield(expire = Opt, [Val]) ->
    +    try xmpp_util:decode_timestamp(Val)
    +    catch _:{bad_timestamp, _} ->
    +	    Txt = {<<"Value of '~s' should be datetime string">>, [Opt]},
    +	    {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
         end;
    -val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
     val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val);
     val_xfield(show_values, Vals) -> Vals;
     val_xfield(subscription_type, [<<"items">>]) -> items;
     val_xfield(subscription_type, [<<"nodes">>]) -> nodes;
     val_xfield(subscription_depth, [<<"all">>]) -> all;
     val_xfield(subscription_depth = Opt, [Depth]) ->
    -    case catch jlib:binary_to_integer(Depth) of
    +    case catch binary_to_integer(Depth) of
     	N when is_integer(N) -> N;
     	_ ->
    -	    Txt = <<"Value of '~s' should be integer">>,
    -	    ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
    -	    {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
    +	    Txt = {<<"Value of '~s' should be integer">>, [Opt]},
    +	    {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
         end.
     
     %% Convert XForm booleans to Erlang booleans.
    @@ -246,12 +234,8 @@ xopt_to_bool(_, <<"1">>) -> true;
     xopt_to_bool(_, <<"false">>) -> false;
     xopt_to_bool(_, <<"true">>) -> true;
     xopt_to_bool(Option, _) ->
    -    Txt = <<"Value of '~s' should be boolean">>,
    -    ErrTxt = iolist_to_binary(io_lib:format(Txt, [Option])),
    -    {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}.
    -
    --spec get_option_xfield(Lang :: binary(), Key :: atom(),
    -			Options :: mod_pubsub:subOptions()) -> xmlel().
    +    Txt = {<<"Value of '~s' should be boolean">>, [Option]},
    +    {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}.
     
     %% Return a field for an XForm for Key, with data filled in, if
     %% applicable, from Options.
    @@ -261,33 +245,22 @@ get_option_xfield(Lang, Key, Options) ->
         {Type, OptEls} = type_and_options(xfield_type(Key), Lang),
         Vals = case lists:keysearch(Key, 1, Options) of
     	{value, {_, Val}} ->
    -	    [tr_xfield_values(Vals)
    -		|| Vals <- xfield_val(Key, Val)];
    -	false -> []
    +		   [xfield_val(Key, Val)];
    +	       false ->
    +		   []
         end,
    -    #xmlel{name = <<"field">>,
    -	attrs =
    -	[{<<"var">>, Var}, {<<"type">>, Type},
    -	    {<<"label">>, translate:translate(Lang, Label)}],
    -	children = OptEls ++ Vals}.
    +    #xdata_field{type = Type, var = Var,
    +		 label = translate:translate(Lang, Label),
    +		 values = Vals,
    +		 options = OptEls}.
     
     type_and_options({Type, Options}, Lang) ->
         {Type, [tr_xfield_options(O, Lang) || O <- Options]};
     type_and_options(Type, _Lang) -> {Type, []}.
     
     tr_xfield_options({Value, Label}, Lang) ->
    -    #xmlel{name = <<"option">>,
    -	attrs =
    -	[{<<"label">>, translate:translate(Lang, Label)}],
    -	children =
    -	[#xmlel{name = <<"value">>, attrs = [],
    -		children = [{xmlcdata, Value}]}]}.
    -
    -tr_xfield_values(Value) ->
    -    %% Return the XForm variable name for a subscription option key.
    -    %% Return the XForm variable type for a subscription option key.
    -    #xmlel{name = <<"value">>, attrs = [],
    -	children = [{xmlcdata, Value}]}.
    +    #xdata_option{label = translate:translate(Lang, Label),
    +		  value = Value}.
     
     xfield_var(deliver) -> ?PUBSUB_DELIVER;
     %xfield_var(digest) -> ?PUBSUB_DIGEST;
    @@ -298,24 +271,24 @@ xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
     xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
     xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
     
    -xfield_type(deliver) -> <<"boolean">>;
    -%xfield_type(digest) -> <<"boolean">>;
    -%xfield_type(digest_frequency) -> <<"text-single">>;
    -%xfield_type(expire) -> <<"text-single">>;
    -%xfield_type(include_body) -> <<"boolean">>;
    +xfield_type(deliver) -> boolean;
    +%xfield_type(digest) -> boolean;
    +%xfield_type(digest_frequency) -> 'text-single';
    +%xfield_type(expire) -> 'text-single';
    +%xfield_type(include_body) -> boolean;
     xfield_type(show_values) ->
    -    {<<"list-multi">>,
    +    {'list-multi',
     	[{<<"away">>, ?SHOW_VALUE_AWAY_LABEL},
     	    {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL},
     	    {<<"dnd">>, ?SHOW_VALUE_DND_LABEL},
     	    {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL},
     	    {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]};
     xfield_type(subscription_type) ->
    -    {<<"list-single">>,
    +    {'list-single',
     	[{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
     	    {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
     xfield_type(subscription_depth) ->
    -    {<<"list-single">>,
    +    {'list-single',
     	[{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
     	    {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
     
    @@ -334,7 +307,7 @@ xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
     xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
     %xfield_val(digest, Val) -> [bool_to_xopt(Val)];
     %xfield_val(digest_frequency, Val) ->
    -%    [iolist_to_binary(integer_to_list(Val))];
    +%    [integer_to_binary(Val))];
     %xfield_val(expire, Val) ->
     %    [jlib:now_to_utc_string(Val)];
     %xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
    @@ -343,7 +316,7 @@ xfield_val(subscription_type, items) -> [<<"items">>];
     xfield_val(subscription_type, nodes) -> [<<"nodes">>];
     xfield_val(subscription_depth, all) -> [<<"all">>];
     xfield_val(subscription_depth, N) ->
    -    [iolist_to_binary(integer_to_list(N))].
    +    [integer_to_binary(N)].
     
     
     bool_to_xopt(true) -> <<"true">>;
    diff --git a/src/pubsub_subscription_sql.erl b/src/pubsub_subscription_sql.erl
    index 6e598320c..fddfe881e 100644
    --- a/src/pubsub_subscription_sql.erl
    +++ b/src/pubsub_subscription_sql.erl
    @@ -28,14 +28,14 @@
     -author("pablo.polvorin@process-one.net").
     
     %% API
    --export([init/0, subscribe_node/3, unsubscribe_node/3,
    +-export([init/3, subscribe_node/3, unsubscribe_node/3,
         get_subscription/3, set_subscription/4,
         make_subid/0,
         get_options_xform/2, parse_options_xform/1]).
     
     -include("pubsub.hrl").
     
    --include("jlib.hrl").
    +-include("xmpp.hrl").
     
     -define(PUBSUB_DELIVER, <<"pubsub#deliver">>).
     -define(PUBSUB_DIGEST, <<"pubsub#digest">>).
    @@ -71,7 +71,7 @@
     %% API
     %%====================================================================
     
    -init() -> ok = create_table().
    +init(_Host, _ServerHost, _Opts) -> ok = create_table().
     
     -spec subscribe_node(_JID :: _, _NodeId :: _, Options :: [] | mod_pubsub:subOptions()) ->
     			    {result, mod_pubsub:subId()}.
    @@ -117,30 +117,15 @@ get_options_xform(Lang, Options) ->
         Keys = [deliver, show_values, subscription_type, subscription_depth],
         XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
         {result,
    -	#xmlel{name = <<"x">>,
    -	    attrs = [{<<"xmlns">>, ?NS_XDATA}],
    -	    children =
    -	    [#xmlel{name = <<"field">>,
    -		    attrs =
    -		    [{<<"var">>, <<"FORM_TYPE">>},
    -			{<<"type">>, <<"hidden">>}],
    -		    children =
    -		    [#xmlel{name = <<"value">>, attrs = [],
    -			    children =
    -			    [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}]
    -	    ++ XFields}}.
    +     #xdata{type = form,
    +	    fields = [#xdata_field{type = hidden,
    +				   var = <<"FORM_TYPE">>,
    +				   values = [?NS_PUBSUB_SUB_OPTIONS]}|
    +		      XFields]}}.
     
     parse_options_xform(XFields) ->
    -    case fxml:remove_cdata(XFields) of
    -	[#xmlel{name = <<"x">>} = XEl] ->
    -	    case jlib:parse_xdata_submit(XEl) of
    -		XData when is_list(XData) ->
    -		    Opts = set_xoption(XData, []),
    -		    {result, Opts};
    -		Other -> Other
    -	    end;
    -	_ -> {result, []}
    -    end.
    +    Opts = set_xoption(XFields, []),
    +    {result, Opts}.
     
     %%====================================================================
     %% Internal functions
    @@ -150,7 +135,7 @@ create_table() -> ok.
     -spec make_subid() -> mod_pubsub:subId().
     make_subid() ->
         {T1, T2, T3} = p1_time_compat:timestamp(),
    -    iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
    +    (str:format("~.16B~.16B~.16B", [T1, T2, T3])).
     
     %%
     %% Subscription XForm processing.
    @@ -183,26 +168,29 @@ var_xfield(_) -> {error, badarg}.
     val_xfield(deliver = Opt, [Val]) -> xopt_to_bool(Opt, Val);
     val_xfield(digest = Opt, [Val]) -> xopt_to_bool(Opt, Val);
     val_xfield(digest_frequency = Opt, [Val]) ->
    -    case catch jlib:binary_to_integer(Val) of
    +    case catch binary_to_integer(Val) of
     	N when is_integer(N) -> N;
     	_ ->
    -	    Txt = <<"Value of '~s' should be integer">>,
    -	    ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
    -	    {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
    +	    Txt = {<<"Value of '~s' should be integer">>, [Opt]},
    +	    {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
    +    end;
    +val_xfield(expire = Opt, [Val]) ->
    +    try xmpp_util:decode_timestamp(Val)
    +    catch _:{bad_timestamp, _} ->
    +	    Txt = {<<"Value of '~s' should be datetime string">>, [Opt]},
    +	    {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
         end;
    -val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
     val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val);
     val_xfield(show_values, Vals) -> Vals;
     val_xfield(subscription_type, [<<"items">>]) -> items;
     val_xfield(subscription_type, [<<"nodes">>]) -> nodes;
     val_xfield(subscription_depth, [<<"all">>]) -> all;
     val_xfield(subscription_depth = Opt, [Depth]) ->
    -    case catch jlib:binary_to_integer(Depth) of
    +    case catch binary_to_integer(Depth) of
     	N when is_integer(N) -> N;
     	_ ->
    -	    Txt = <<"Value of '~s' should be integer">>,
    -	    ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
    -	    {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}
    +	    Txt = {<<"Value of '~s' should be integer">>, [Opt]},
    +	    {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}
         end.
     
     %% Convert XForm booleans to Erlang booleans.
    @@ -211,9 +199,8 @@ xopt_to_bool(_, <<"1">>) -> true;
     xopt_to_bool(_, <<"false">>) -> false;
     xopt_to_bool(_, <<"true">>) -> true;
     xopt_to_bool(Option, _) ->
    -    Txt = <<"Value of '~s' should be boolean">>,
    -    ErrTxt = iolist_to_binary(io_lib:format(Txt, [Option])),
    -    {error, ?ERRT_NOT_ACCEPTABLE(?MYLANG, ErrTxt)}.
    +    Txt = {<<"Value of '~s' should be boolean">>, [Option]},
    +    {error, xmpp:err_not_acceptable(Txt, ?MYLANG)}.
     
     %% Return a field for an XForm for Key, with data filled in, if
     %% applicable, from Options.
    @@ -222,34 +209,23 @@ get_option_xfield(Lang, Key, Options) ->
         Label = xfield_label(Key),
         {Type, OptEls} = type_and_options(xfield_type(Key), Lang),
         Vals = case lists:keysearch(Key, 1, Options) of
    -	{value, {_, Val}} ->
    -	    [tr_xfield_values(Vals)
    -		|| Vals <- xfield_val(Key, Val)];
    -	false -> []
    -    end,
    -    #xmlel{name = <<"field">>,
    -	attrs =
    -	[{<<"var">>, Var}, {<<"type">>, Type},
    -	    {<<"label">>, translate:translate(Lang, Label)}],
    -	children = OptEls ++ Vals}.
    +	       {value, {_, Val}} ->
    +		   [xfield_val(Key, Val)];
    +	       false ->
    +		   []
    +	   end,
    +    #xdata_field{type = Type, var = Var,
    +		 label = translate:translate(Lang, Label),
    +		 values = Vals,
    +		 options = OptEls}.
     
     type_and_options({Type, Options}, Lang) ->
         {Type, [tr_xfield_options(O, Lang) || O <- Options]};
     type_and_options(Type, _Lang) -> {Type, []}.
     
     tr_xfield_options({Value, Label}, Lang) ->
    -    #xmlel{name = <<"option">>,
    -	attrs =
    -	[{<<"label">>, translate:translate(Lang, Label)}],
    -	children =
    -	[#xmlel{name = <<"value">>, attrs = [],
    -		children = [{xmlcdata, Value}]}]}.
    -
    -tr_xfield_values(Value) ->
    -    %% Return the XForm variable name for a subscription option key.
    -    %% Return the XForm variable type for a subscription option key.
    -    #xmlel{name = <<"value">>, attrs = [],
    -	children = [{xmlcdata, Value}]}.
    +    #xdata_option{label = translate:translate(Lang, Label),
    +		  value = Value}.
     
     xfield_var(deliver) -> ?PUBSUB_DELIVER;
     %xfield_var(digest) -> ?PUBSUB_DIGEST;
    @@ -260,26 +236,26 @@ xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
     xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
     xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
     
    -xfield_type(deliver) -> <<"boolean">>;
    -%xfield_type(digest) -> <<"boolean">>;
    -%xfield_type(digest_frequency) -> <<"text-single">>;
    -%xfield_type(expire) -> <<"text-single">>;
    -%xfield_type(include_body) -> <<"boolean">>;
    +xfield_type(deliver) -> boolean;
    +%xfield_type(digest) -> boolean;
    +%xfield_type(digest_frequency) -> 'text-single';
    +%xfield_type(expire) -> 'text-single';
    +%xfield_type(include_body) -> boolean;
     xfield_type(show_values) ->
    -    {<<"list-multi">>,
    -	[{<<"away">>, ?SHOW_VALUE_AWAY_LABEL},
    -	    {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL},
    -	    {<<"dnd">>, ?SHOW_VALUE_DND_LABEL},
    -	    {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL},
    -	    {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]};
    +    {'list-multi',
    +     [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL},
    +      {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL},
    +      {<<"dnd">>, ?SHOW_VALUE_DND_LABEL},
    +      {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL},
    +      {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]};
     xfield_type(subscription_type) ->
    -    {<<"list-single">>,
    -	[{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
    -	    {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
    +    {'list-single',
    +     [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
    +      {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
     xfield_type(subscription_depth) ->
    -    {<<"list-single">>,
    -	[{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
    -	    {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
    +    {'list-single',
    +     [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
    +      {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
     
     %% Return the XForm variable label for a subscription option key.
     xfield_label(deliver) -> ?DELIVER_LABEL;
    @@ -296,7 +272,7 @@ xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
     xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
     %xfield_val(digest, Val) -> [bool_to_xopt(Val)];
     %xfield_val(digest_frequency, Val) ->
    -%    [iolist_to_binary(integer_to_list(Val))];
    +%    [integer_to_binary(Val))];
     %xfield_val(expire, Val) ->
     %    [jlib:now_to_utc_string(Val)];
     %xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
    @@ -305,7 +281,7 @@ xfield_val(subscription_type, items) -> [<<"items">>];
     xfield_val(subscription_type, nodes) -> [<<"nodes">>];
     xfield_val(subscription_depth, all) -> [<<"all">>];
     xfield_val(subscription_depth, N) ->
    -    [iolist_to_binary(integer_to_list(N))].
    +    [integer_to_binary(N)].
     
     bool_to_xopt(false) -> <<"false">>;
     bool_to_xopt(true) -> <<"true">>.
    diff --git a/src/randoms.erl b/src/randoms.erl
    index 1353f48af..ae477d27d 100644
    --- a/src/randoms.erl
    +++ b/src/randoms.erl
    @@ -38,7 +38,7 @@ start() ->
     
     get_string() ->
         R = crypto:rand_uniform(0, ?THRESHOLD),
    -    jlib:integer_to_binary(R).
    +    integer_to_binary(R).
     
     uniform() ->
         crypto:rand_uniform(0, ?THRESHOLD)/?THRESHOLD.
    diff --git a/src/rest.erl b/src/rest.erl
    index 01b04f66a..091002fa5 100644
    --- a/src/rest.erl
    +++ b/src/rest.erl
    @@ -28,7 +28,7 @@
     -behaviour(ejabberd_config).
     
     -export([start/1, stop/1, get/2, get/3, post/4, delete/2,
    -	 request/6, with_retry/4, opt_type/1]).
    +	 put/4, patch/4, request/6, with_retry/4, opt_type/1]).
     
     -include("logger.hrl").
     
    @@ -36,14 +36,14 @@
     -define(CONNECT_TIMEOUT, 8000).
     
     start(Host) ->
    -    http_p1:start(),
    +    p1_http:start(),
         Pool_size =
     	ejabberd_config:get_option({ext_api_http_pool_size, Host},
     				   fun(X) when is_integer(X), X > 0->
     					   X
     				   end,
     				   100),
    -    http_p1:set_pool_size(Pool_size).
    +    p1_http:set_pool_size(Pool_size).
     
     stop(_Host) ->
         ok.
    @@ -71,18 +71,17 @@ delete(Server, Path) ->
         request(Server, delete, Path, [], "application/json", <<>>).
     
     post(Server, Path, Params, Content) ->
    -    Data = case catch jiffy:encode(Content) of
    -        {'EXIT', Reason} ->
    -            ?ERROR_MSG("HTTP content encodage failed:~n"
    -                       "** Content = ~p~n"
    -                       "** Err = ~p",
    -                       [Content, Reason]),
    -            <<>>;
    -        Encoded ->
    -            Encoded
    -    end,
    +    Data = encode_json(Content),
         request(Server, post, Path, Params, "application/json", Data).
     
    +put(Server, Path, Params, Content) ->
    +    Data = encode_json(Content),
    +    request(Server, put, Path, Params, "application/json", Data).
    +
    +patch(Server, Path, Params, Content) ->
    +    Data = encode_json(Content),
    +    request(Server, patch, Path, Params, "application/json", Data).
    +
     request(Server, Method, Path, Params, Mime, Data) ->
         URI = url(Server, Path, Params),
         Opts = [{connect_timeout, ?CONNECT_TIMEOUT},
    @@ -91,7 +90,7 @@ request(Server, Method, Path, Params, Mime, Data) ->
                 {"content-type", Mime},
                 {"User-Agent", "ejabberd"}],
         Begin = os:timestamp(),
    -    Result = case catch http_p1:request(Method, URI, Hdrs, Data, Opts) of
    +    Result = case catch p1_http:request(Method, URI, Hdrs, Data, Opts) of
             {ok, Code, _, <<>>} ->
                 {ok, Code, []};
             {ok, Code, _, <<" ">>} ->
    @@ -147,6 +146,18 @@ request(Server, Method, Path, Params, Mime, Data) ->
     %%% HTTP helpers
     %%%----------------------------------------------------------------------
     
    +encode_json(Content) ->
    +    case catch jiffy:encode(Content) of
    +        {'EXIT', Reason} ->
    +            ?ERROR_MSG("HTTP content encodage failed:~n"
    +                       "** Content = ~p~n"
    +                       "** Err = ~p",
    +                       [Content, Reason]),
    +            <<>>;
    +        Encoded ->
    +            Encoded
    +    end.
    +
     base_url(Server, Path) ->
         Tail = case iolist_to_binary(Path) of
             <<$/, Ok/binary>> -> Ok;
    diff --git a/src/shaper.erl b/src/shaper.erl
    index eb82b8faa..19c9a049d 100644
    --- a/src/shaper.erl
    +++ b/src/shaper.erl
    @@ -50,7 +50,7 @@
     -spec start() -> ok.
     
     start() ->
    -    mnesia:create_table(shaper,
    +    ejabberd_mnesia:create(?MODULE, shaper,
                             [{ram_copies, [node()]},
                              {local_content, true},
     			 {attributes, record_info(fields, shaper)}]),
    diff --git a/src/str.erl b/src/str.erl
    index 27d21075a..43fd51878 100644
    --- a/src/str.erl
    +++ b/src/str.erl
    @@ -64,6 +64,7 @@
              to_float/1,
              prefix/2,
              suffix/2,
    +	 format/2,
              to_integer/1]).
     
     %%%===================================================================
    @@ -92,7 +93,10 @@ rchr(B, C) ->
     -spec str(binary(), binary()) -> non_neg_integer().
     
     str(B1, B2) ->
    -    string:str(binary_to_list(B1), binary_to_list(B2)).
    +    case binary:match(B1, B2) of
    +	{R, _Len} -> R+1;
    +	_ -> 0
    +    end.
     
     -spec rstr(binary(), binary()) -> non_neg_integer().
     
    @@ -112,7 +116,7 @@ cspan(B1, B2) ->
     -spec copies(binary(), non_neg_integer()) -> binary().
     
     copies(B, N) ->
    -    iolist_to_binary(string:copies(binary_to_list(B), N)).
    +    binary:copy(B, N).
     
     -spec words(binary()) -> pos_integer().
     
    @@ -200,7 +204,7 @@ join(L, Sep) ->
     -spec substr(binary(), pos_integer()) -> binary().
     
     substr(B, N) ->
    -    iolist_to_binary(string:substr(binary_to_list(B), N)).
    +    binary_part(B, N-1, byte_size(B)-N+1).
     
     -spec chr(binary(), char()) -> non_neg_integer().
     
    @@ -220,7 +224,7 @@ chars(C, N) ->
     -spec substr(binary(), pos_integer(), non_neg_integer()) -> binary().
     
     substr(B, S, E) ->
    -    iolist_to_binary(string:substr(binary_to_list(B), S, E)).
    +    binary_part(B, S-1, E).
     
     -spec strip(binary(), both | left | right, char()) -> binary().
     
    @@ -277,6 +281,11 @@ prefix(Prefix, B) ->
     suffix(B1, B2) ->
         lists:suffix(binary_to_list(B1), binary_to_list(B2)).
     
    +-spec format(io:format(), list()) -> binary().
    +
    +format(Format, Args) ->
    +    iolist_to_binary(io_lib:format(Format, Args)).
    +
     %%%===================================================================
     %%% Internal functions
     %%%===================================================================
    diff --git a/test/acl_test.exs b/test/acl_test.exs
    index 4bd8e6989..0ab92ade8 100644
    --- a/test/acl_test.exs
    +++ b/test/acl_test.exs
    @@ -25,7 +25,7 @@ defmodule ACLTest do
     
       setup_all do
         :ok = :mnesia.start
    -    :ok = :jid.start
    +    {:ok, _} = :jid.start
         :stringprep.start
         :ok = :ejabberd_config.start(["domain1", "domain2"], [])
         :ok = :acl.start
    diff --git a/test/announce_tests.erl b/test/announce_tests.erl
    new file mode 100644
    index 000000000..3eea5298c
    --- /dev/null
    +++ b/test/announce_tests.erl
    @@ -0,0 +1,61 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(announce_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [server_jid/1, send_recv/2, recv_message/1, disconnect/1,
    +		send/2, wait_for_master/1, wait_for_slave/1]).
    +
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {announce_single, [sequence], []}.
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {announce_master_slave, [sequence],
    +     [master_slave_test(set_motd)]}.
    +
    +set_motd_master(Config) ->
    +    ServerJID = server_jid(Config),
    +    MotdJID = jid:replace_resource(ServerJID, <<"announce/motd">>),
    +    Body = xmpp:mk_text(<<"motd">>),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_slave(Config),
    +    send(Config, #message{to = MotdJID, body = Body}),
    +    #message{from = ServerJID, body = Body} = recv_message(Config),
    +    disconnect(Config).
    +
    +set_motd_slave(Config) ->
    +    ServerJID = server_jid(Config),
    +    Body = xmpp:mk_text(<<"motd">>),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_master(Config),
    +    #message{from = ServerJID, body = Body} = recv_message(Config),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("announce_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("announce_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("announce_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("announce_" ++ atom_to_list(T) ++ "_slave")]}.
    diff --git a/test/carbons_tests.erl b/test/carbons_tests.erl
    new file mode 100644
    index 000000000..00dd57e3c
    --- /dev/null
    +++ b/test/carbons_tests.erl
    @@ -0,0 +1,196 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(carbons_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [is_feature_advertised/2, disconnect/1, send_recv/2,
    +		recv_presence/1, send/2, get_event/1, recv_message/1,
    +		my_jid/1, wait_for_slave/1, wait_for_master/1,
    +		put_event/2]).
    +
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {carbons_single, [sequence],
    +     [single_test(feature_enabled),
    +      single_test(unsupported_iq)]}.
    +
    +feature_enabled(Config) ->
    +    true = is_feature_advertised(Config, ?NS_CARBONS_2),
    +    disconnect(Config).
    +
    +unsupported_iq(Config) ->
    +    lists:foreach(
    +      fun({Type, SubEl}) ->
    +	      #iq{type = error} =
    +		  send_recv(Config, #iq{type = Type, sub_els = [SubEl]})
    +      end, [{Type, SubEl} ||
    +	       Type <- [get, set],
    +	       SubEl <- [#carbons_sent{forwarded = #forwarded{}},
    +			 #carbons_received{forwarded = #forwarded{}},
    +			 #carbons_private{}]] ++
    +	  [{get, SubEl} || SubEl <- [#carbons_enable{}, #carbons_disable{}]]),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {carbons_master_slave, [sequence],
    +     [master_slave_test(send_recv),
    +      master_slave_test(enable_disable)]}.
    +
    +send_recv_master(Config) ->
    +    Peer = ?config(peer, Config),
    +    prepare_master(Config),
    +    ct:comment("Waiting for the peer to be ready"),
    +    ready = get_event(Config),
    +    send_messages(Config),
    +    ct:comment("Waiting for the peer to disconnect"),
    +    #presence{from = Peer, type = unavailable} = recv_presence(Config),
    +    disconnect(Config).
    +
    +send_recv_slave(Config) ->
    +    prepare_slave(Config),
    +    ok = enable(Config),
    +    put_event(Config, ready),
    +    recv_carbons(Config),
    +    disconnect(Config).
    +
    +enable_disable_master(Config) ->
    +    prepare_master(Config),
    +    ct:comment("Waiting for the peer to be ready"),
    +    ready = get_event(Config),
    +    send_messages(Config),
    +    disconnect(Config).
    +
    +enable_disable_slave(Config) ->
    +    Peer = ?config(peer, Config),
    +    prepare_slave(Config),
    +    ok = enable(Config),
    +    ok = disable(Config),
    +    put_event(Config, ready),
    +    ct:comment("Waiting for the peer to disconnect"),
    +    #presence{from = Peer, type = unavailable} = recv_presence(Config),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("carbons_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("carbons_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("carbons_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("carbons_" ++ atom_to_list(T) ++ "_slave")]}.
    +
    +prepare_master(Config) ->
    +    MyJID = my_jid(Config),
    +    Peer = ?config(peer, Config),
    +    #presence{from = MyJID} = send_recv(Config, #presence{priority = 10}),
    +    wait_for_slave(Config),
    +    ct:comment("Receiving initial presence from the peer"),
    +    #presence{from = Peer} = recv_presence(Config),
    +    Config.
    +
    +prepare_slave(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = enable(Config),
    +    wait_for_master(Config),
    +    #presence{from = MyJID} = send_recv(Config, #presence{priority = 5}),
    +    ct:comment("Receiving initial presence from the peer"),
    +    #presence{from = Peer} = recv_presence(Config),
    +    Config.
    +
    +send_messages(Config) ->
    +    Server = ?config(server, Config),
    +    MyJID = my_jid(Config),
    +    JID = jid:make(randoms:get_string(), Server),
    +    lists:foreach(
    +      fun({send, #message{type = Type} = Msg}) ->
    +	      I = send(Config, Msg#message{to = JID}),
    +	      if Type /= error ->
    +		      #message{id = I, type = error} = recv_message(Config);
    +		 true ->
    +		      ok
    +	      end;
    +	 ({recv, #message{} = Msg}) ->
    +	      ejabberd_router:route(
    +		JID, MyJID, Msg#message{from = JID, to = MyJID}),
    +	      ct:comment("Receiving message ~s", [xmpp:pp(Msg)]),
    +	      #message{} = recv_message(Config)
    +      end, message_iterator(Config)).
    +
    +recv_carbons(Config) ->
    +    Peer = ?config(peer, Config),
    +    BarePeer = jid:remove_resource(Peer),
    +    MyJID = my_jid(Config),
    +    lists:foreach(
    +      fun({_, #message{sub_els = [#hint{type = 'no-copy'}]}}) ->
    +	      ok;
    +	 ({_, #message{sub_els = [#carbons_private{}]}}) ->
    +	      ok;
    +	 ({_, #message{type = T}}) when T /= normal, T /= chat ->
    +	      ok;
    +	 ({Dir, #message{type = T, body = Body} = M})
    +	    when (T == chat) or (T == normal andalso Body /= []) ->
    +	      ct:comment("Receiving carbon ~s", [xmpp:pp(M)]),
    +	      #message{from = BarePeer, to = MyJID} = CarbonMsg =
    +		  recv_message(Config),
    +	      case Dir of
    +		  send ->
    +		      #carbons_sent{forwarded = #forwarded{xml_els = [El]}} =
    +			  xmpp:get_subtag(CarbonMsg, #carbons_sent{}),
    +		      #message{body = Body} = xmpp:decode(El);
    +		  recv ->
    +		      #carbons_received{forwarded = #forwarded{xml_els = [El]}}=
    +			  xmpp:get_subtag(CarbonMsg, #carbons_received{}),
    +		      #message{body = Body} = xmpp:decode(El)
    +	      end;
    +	 (_) ->
    +	      false
    +      end, message_iterator(Config)).
    +
    +enable(Config) ->
    +    case send_recv(
    +	   Config, #iq{type = set,
    +		       sub_els = [#carbons_enable{}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +disable(Config) ->
    +    case send_recv(
    +	   Config, #iq{type = set,
    +		       sub_els = [#carbons_disable{}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +message_iterator(_Config) ->
    +    [{Dir, #message{type = Type, body = Body, sub_els = Els}}
    +     || Dir <- [send, recv],
    +	Type <- [error, chat, normal, groupchat, headline],
    +    	Body <- [[], xmpp:mk_text(<<"body">>)],
    +    	Els <- [[],
    +    		[#hint{type = 'no-copy'}],
    +		[#carbons_private{}]]].
    diff --git a/test/csi_tests.erl b/test/csi_tests.erl
    new file mode 100644
    index 000000000..9a96b8a59
    --- /dev/null
    +++ b/test/csi_tests.erl
    @@ -0,0 +1,147 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(csi_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [disconnect/1, wait_for_slave/1, wait_for_master/1,
    +		send/2, send_recv/2, recv_presence/1, recv_message/1,
    +		server_jid/1]).
    +
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {csi_single, [sequence],
    +     [single_test(feature_enabled)]}.
    +
    +feature_enabled(Config) ->
    +    true = ?config(csi, Config),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {csi_master_slave, [sequence],
    +     [master_slave_test(all)]}.
    +
    +all_master(Config) ->
    +    Peer = ?config(peer, Config),
    +    Presence = #presence{to = Peer},
    +    ChatState = #message{to = Peer, thread = <<"1">>,
    +			 sub_els = [#chatstate{type = active}]},
    +    Message = ChatState#message{body = [#text{data = <<"body">>}]},
    +    PepPayload = xmpp:encode(#presence{}),
    +    PepOne = #message{
    +		to = Peer,
    +		sub_els =
    +		    [#ps_event{
    +			items =
    +			    #ps_items{
    +			       node = <<"foo-1">>,
    +			       items =
    +				   [#ps_item{
    +				       id = <<"pep-1">>,
    +				       xml_els = [PepPayload]}]}}]},
    +    PepTwo = #message{
    +		to = Peer,
    +		sub_els =
    +		    [#ps_event{
    +			items =
    +			    #ps_items{
    +			       node = <<"foo-2">>,
    +			       items =
    +				   [#ps_item{
    +				       id = <<"pep-2">>,
    +				       xml_els = [PepPayload]}]}}]},
    +    %% Wait for the slave to become inactive.
    +    wait_for_slave(Config),
    +    %% Should be queued (but see below):
    +    send(Config, Presence),
    +    %% Should replace the previous presence in the queue:
    +    send(Config, Presence#presence{type = unavailable}),
    +    %% The following two PEP stanzas should be queued (but see below):
    +    send(Config, PepOne),
    +    send(Config, PepTwo),
    +    %% The following two PEP stanzas should replace the previous two:
    +    send(Config, PepOne),
    +    send(Config, PepTwo),
    +    %% Should be queued (but see below):
    +    send(Config, ChatState),
    +    %% Should replace the previous chat state in the queue:
    +    send(Config, ChatState#message{sub_els = [#chatstate{type = composing}]}),
    +    %% Should be sent immediately, together with the queued stanzas:
    +    send(Config, Message),
    +    %% Wait for the slave to become active.
    +    wait_for_slave(Config),
    +    %% Should be delivered, as the client is active again:
    +    send(Config, ChatState),
    +    disconnect(Config).
    +
    +all_slave(Config) ->
    +    Peer = ?config(peer, Config),
    +    change_client_state(Config, inactive),
    +    wait_for_master(Config),
    +    #presence{from = Peer, type = unavailable, sub_els = [#delay{}]} =
    +	recv_presence(Config),
    +    #message{
    +       from = Peer,
    +       sub_els =
    +	   [#ps_event{
    +	       items =
    +		   #ps_items{
    +		      node = <<"foo-1">>,
    +		      items =
    +			  [#ps_item{
    +			      id = <<"pep-1">>}]}},
    +	    #delay{}]} = recv_message(Config),
    +    #message{
    +       from = Peer,
    +       sub_els =
    +	   [#ps_event{
    +	       items =
    +		   #ps_items{
    +		      node = <<"foo-2">>,
    +		      items =
    +			  [#ps_item{
    +			      id = <<"pep-2">>}]}},
    +	    #delay{}]} = recv_message(Config),
    +    #message{from = Peer, thread = <<"1">>,
    +	     sub_els = [#chatstate{type = composing},
    +			#delay{}]} = recv_message(Config),
    +    #message{from = Peer, thread = <<"1">>,
    +	     body = [#text{data = <<"body">>}],
    +	     sub_els = [#chatstate{type = active}]} = recv_message(Config),
    +    change_client_state(Config, active),
    +    wait_for_master(Config),
    +    #message{from = Peer, thread = <<"1">>,
    +	     sub_els = [#chatstate{type = active}]} = recv_message(Config),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("csi_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("csi_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("csi_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("csi_" ++ atom_to_list(T) ++ "_slave")]}.
    +
    +change_client_state(Config, NewState) ->
    +    send(Config, #csi{type = NewState}),
    +    send_recv(Config, #iq{type = get, to = server_jid(Config),
    +			  sub_els = [#ping{}]}).
    diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
    index d3e7ec668..46711ad49 100644
    --- a/test/ejabberd_SUITE.erl
    +++ b/test/ejabberd_SUITE.erl
    @@ -10,22 +10,24 @@
     
     -compile(export_all).
     
    --import(suite, [init_config/1, connect/1, disconnect/1,
    -                recv/0, send/2, send_recv/2, my_jid/1, server_jid/1,
    -                pubsub_jid/1, proxy_jid/1, muc_jid/1, muc_room_jid/1,
    -		mix_jid/1, mix_room_jid/1, get_features/2, re_register/1,
    -                is_feature_advertised/2, subscribe_to_events/1,
    +-import(suite, [init_config/1, connect/1, disconnect/1, recv_message/1,
    +                recv/1, recv_presence/1, send/2, send_recv/2, my_jid/1,
    +		server_jid/1, pubsub_jid/1, proxy_jid/1, muc_jid/1,
    +		muc_room_jid/1, my_muc_jid/1, peer_muc_jid/1,
    +		mix_jid/1, mix_room_jid/1, get_features/2, recv_iq/1,
    +		re_register/1, is_feature_advertised/2, subscribe_to_events/1,
                     is_feature_advertised/3, set_opt/3, auth_SASL/2,
    -                wait_for_master/1, wait_for_slave/1,
    -                make_iq_result/1, start_event_relay/0,
    +                wait_for_master/1, wait_for_slave/1, flush/1,
    +                make_iq_result/1, start_event_relay/0, alt_room_jid/1,
                     stop_event_relay/1, put_event/2, get_event/1,
    -                bind/1, auth/1, open_session/1, zlib/1, starttls/1,
    -		close_socket/1]).
    -
    +                bind/1, auth/1, auth/2, open_session/1, open_session/2,
    +		zlib/1, starttls/1, starttls/2, close_socket/1, init_stream/1,
    +		auth_legacy/2, auth_legacy/3, tcp_connect/1, send_text/2,
    +		set_roster/3, del_roster/1]).
     -include("suite.hrl").
     
     suite() ->
    -    [{timetrap, {seconds,120}}].
    +    [{timetrap, {seconds, 120}}].
     
     init_per_suite(Config) ->
         NewConfig = init_config(Config),
    @@ -35,6 +37,10 @@ init_per_suite(Config) ->
         LDIFFile = filename:join([DataDir, "ejabberd.ldif"]),
         {ok, _} = file:copy(ExtAuthScript, filename:join([CWD, "extauth.py"])),
         {ok, _} = ldap_srv:start(LDIFFile),
    +    inet_db:add_host({127,0,0,1}, [binary_to_list(?S2S_VHOST),
    +				   binary_to_list(?MNESIA_VHOST)]),
    +    inet_db:set_domain(binary_to_list(randoms:get_string())),
    +    inet_db:set_lookup([file, native]),
         start_ejabberd(NewConfig),
         NewConfig.
     
    @@ -78,7 +84,7 @@ init_per_group(Group, Config) ->
     
     do_init_per_group(no_db, Config) ->
         re_register(Config),
    -    Config;
    +    set_opt(persistent_room, false, Config);
     do_init_per_group(mnesia, Config) ->
         mod_muc:shutdown_rooms(?MNESIA_VHOST),
         set_opt(server, ?MNESIA_VHOST, Config);
    @@ -124,9 +130,32 @@ do_init_per_group(riak, Config) ->
     	Err ->
     	    {skip, {riak_not_available, Err}}
         end;
    -do_init_per_group(_GroupName, Config) ->
    +do_init_per_group(s2s, Config) ->
    +    ejabberd_config:add_option(s2s_use_starttls, required_trusted),
    +    ejabberd_config:add_option(domain_certfile, "cert.pem"),
    +    Port = ?config(s2s_port, Config),
    +    set_opt(server, ?COMMON_VHOST,
    +	    set_opt(xmlns, ?NS_SERVER,
    +		    set_opt(type, server,
    +			    set_opt(server_port, Port,
    +				    set_opt(stream_from, ?S2S_VHOST,
    +					    set_opt(lang, <<"">>, Config))))));
    +do_init_per_group(component, Config) ->
    +    Server = ?config(server, Config),
    +    Port = ?config(component_port, Config),
    +    set_opt(xmlns, ?NS_COMPONENT,
    +            set_opt(server, <<"component.", Server/binary>>,
    +                    set_opt(type, component,
    +                            set_opt(server_port, Port,
    +                                    set_opt(stream_version, undefined,
    +                                            set_opt(lang, <<"">>, Config))))));
    +do_init_per_group(GroupName, Config) ->
         Pid = start_event_relay(),
    -    set_opt(event_relay, Pid, Config).
    +    NewConfig = set_opt(event_relay, Pid, Config),
    +    case GroupName of
    +	anonymous -> set_opt(anonymous, true, NewConfig);
    +	_ -> NewConfig
    +    end.
     
     end_per_group(mnesia, _Config) ->
         ok;
    @@ -144,73 +173,134 @@ end_per_group(ldap, _Config) ->
         ok;
     end_per_group(extauth, _Config) ->
         ok;
    -end_per_group(riak, _Config) ->
    +end_per_group(riak, Config) ->
    +    case ejabberd_riak:is_connected() of
    +	true ->
    +	    clear_riak_tables(Config);
    +	false ->
    +	    Config
    +    end;
    +end_per_group(component, _Config) ->
         ok;
    +end_per_group(s2s, _Config) ->
    +    ejabberd_config:add_option(s2s_use_starttls, false);
     end_per_group(_GroupName, Config) ->
         stop_event_relay(Config),
    -    ok.
    +    set_opt(anonymous, false, Config).
     
     init_per_testcase(stop_ejabberd, Config) ->
    -    open_session(bind(auth(connect(Config))));
    +    NewConfig = set_opt(resource, <<"">>,
    +			set_opt(anonymous, true, Config)),
    +    open_session(bind(auth(connect(NewConfig))));
     init_per_testcase(TestCase, OrigConfig) ->
    -    subscribe_to_events(OrigConfig),
    -    Server = ?config(server, OrigConfig),
    -    Resource = ?config(resource, OrigConfig),
    -    MasterResource = ?config(master_resource, OrigConfig),
    -    SlaveResource = ?config(slave_resource, OrigConfig),
         Test = atom_to_list(TestCase),
         IsMaster = lists:suffix("_master", Test),
         IsSlave = lists:suffix("_slave", Test),
    +    if IsMaster or IsSlave ->
    +	    subscribe_to_events(OrigConfig);
    +       true ->
    +	    ok
    +    end,
    +    TestGroup = proplists:get_value(
    +		  name, ?config(tc_group_properties, OrigConfig)),
    +    Server = ?config(server, OrigConfig),
    +    Resource = case TestGroup of
    +		   anonymous ->
    +		       <<"">>;
    +		   legacy_auth ->
    +		       randoms:get_string();
    +		   _ ->
    +		       ?config(resource, OrigConfig)
    +	       end,
    +    MasterResource = ?config(master_resource, OrigConfig),
    +    SlaveResource = ?config(slave_resource, OrigConfig),
    +    Mode = if IsSlave -> slave;
    +	      IsMaster -> master;
    +	      true -> single
    +	   end,
         IsCarbons = lists:prefix("carbons_", Test),
    -    User = if IsMaster or IsCarbons -> <<"test_master!#$%^*()`~+-;_=[]{}|\\">>;
    +    IsReplaced = lists:prefix("replaced_", Test),
    +    User = if IsReplaced -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">>;
    +	      IsCarbons and not (IsMaster or IsSlave) ->
    +		   <<"test_single!#$%^*()`~+-;_=[]{}|\\">>;
    +	      IsMaster or IsCarbons -> <<"test_master!#$%^*()`~+-;_=[]{}|\\">>;
                   IsSlave -> <<"test_slave!#$%^*()`~+-;_=[]{}|\\">>;
                   true -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">>
                end,
    +    Nick = if IsSlave -> ?config(slave_nick, OrigConfig);
    +	      IsMaster -> ?config(master_nick, OrigConfig);
    +	      true -> ?config(nick, OrigConfig)
    +	   end,
         MyResource = if IsMaster and IsCarbons -> MasterResource;
     		    IsSlave and IsCarbons -> SlaveResource;
     		    true -> Resource
     		 end,
         Slave = if IsCarbons ->
     		    jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, SlaveResource);
    +	       IsReplaced ->
    +		    jid:make(User, Server, Resource);
     	       true ->
     		    jid:make(<<"test_slave!#$%^*()`~+-;_=[]{}|\\">>, Server, Resource)
     	    end,
         Master = if IsCarbons ->
     		     jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, MasterResource);
    +		IsReplaced ->
    +		     jid:make(User, Server, Resource);
     		true ->
     		     jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, Resource)
     	     end,
    -    Config = set_opt(user, User,
    -                     set_opt(slave, Slave,
    -                             set_opt(master, Master,
    -				     set_opt(resource, MyResource, OrigConfig)))),
    -    case TestCase of
    -        test_connect ->
    +    Config1 = set_opt(user, User,
    +		      set_opt(slave, Slave,
    +			      set_opt(master, Master,
    +				      set_opt(resource, MyResource,
    +					      set_opt(nick, Nick,
    +						      set_opt(mode, Mode, OrigConfig)))))),
    +    Config2 = if IsSlave ->
    +		      set_opt(peer_nick, ?config(master_nick, Config1), Config1);
    +		 IsMaster ->
    +		      set_opt(peer_nick, ?config(slave_nick, Config1), Config1);
    +		 true ->
    +		      Config1
    +	      end,
    +    Config = if IsSlave -> set_opt(peer, Master, Config2);
    +		IsMaster -> set_opt(peer, Slave, Config2);
    +		true -> Config2
    +	     end,
    +    case Test of
    +        "test_connect" ++ _ ->
                 Config;
    -        test_auth ->
    +	"test_legacy_auth" ++ _ ->
    +	    init_stream(set_opt(stream_version, undefined, Config));
    +        "test_auth" ++ _ ->
                 connect(Config);
    -        test_starttls ->
    +        "test_starttls" ++ _ ->
                 connect(Config);
    -        test_zlib ->
    +        "test_zlib" ->
                 connect(Config);
    -        test_register ->
    +        "test_register" ->
                 connect(Config);
    -        auth_md5 ->
    +        "auth_md5" ->
                 connect(Config);
    -        auth_plain ->
    +        "auth_plain" ->
                 connect(Config);
    -        test_bind ->
    +	"unauthenticated_" ++ _ ->
    +	    connect(Config);
    +        "test_bind" ->
                 auth(connect(Config));
    -	sm_resume ->
    +	"sm_resume" ->
     	    auth(connect(Config));
    -	sm_resume_failed ->
    +	"sm_resume_failed" ->
     	    auth(connect(Config));
    -        test_open_session ->
    +        "test_open_session" ->
                 bind(auth(connect(Config)));
    +	"replaced" ++ _ ->
    +	    auth(connect(Config));
             _ when IsMaster or IsSlave ->
                 Password = ?config(password, Config),
                 ejabberd_auth:try_register(User, Server, Password),
                 open_session(bind(auth(connect(Config))));
    +	_ when TestGroup == s2s_tests ->
    +	    auth(connect(starttls(connect(Config))));
             _ ->
                 open_session(bind(auth(connect(Config))))
         end.
    @@ -218,161 +308,194 @@ init_per_testcase(TestCase, OrigConfig) ->
     end_per_testcase(_TestCase, _Config) ->
         ok.
     
    +legacy_auth_tests() ->
    +    {legacy_auth, [parallel],
    +     [test_legacy_auth,
    +      test_legacy_auth_digest,
    +      test_legacy_auth_no_resource,
    +      test_legacy_auth_bad_jid,
    +      test_legacy_auth_fail]}.
    +
     no_db_tests() ->
    -    [{generic, [sequence],
    -      [test_connect,
    +    [{anonymous, [parallel],
    +      [test_connect_bad_xml,
    +       test_connect_unexpected_xml,
    +       test_connect_unknown_ns,
    +       test_connect_bad_xmlns,
    +       test_connect_bad_ns_stream,
    +       test_connect_bad_lang,
    +       test_connect_bad_to,
    +       test_connect_missing_to,
    +       test_connect,
    +       unauthenticated_iq,
    +       unauthenticated_stanza,
            test_starttls,
            test_zlib,
            test_auth,
            test_bind,
            test_open_session,
    -       presence,
    +       codec_failure,
    +       unsupported_query,
    +       bad_nonza,
    +       invalid_from,
    +       legacy_iq,
            ping,
            version,
            time,
            stats,
    -       sm,
    -       sm_resume,
    -       sm_resume_failed,
            disco]},
    -     {test_proxy65, [parallel],
    -      [proxy65_master, proxy65_slave]}].
    +     {presence_and_s2s, [sequence],
    +      [test_auth_fail,
    +       presence,
    +       s2s_dialback,
    +       s2s_optional,
    +       s2s_required,
    +       s2s_required_trusted]},
    +     sm_tests:single_cases(),
    +     muc_tests:single_cases(),
    +     muc_tests:master_slave_cases(),
    +     proxy65_tests:single_cases(),
    +     proxy65_tests:master_slave_cases(),
    +     replaced_tests:master_slave_cases()].
     
     db_tests(riak) ->
         %% No support for mod_pubsub
         [{single_user, [sequence],
           [test_register,
    +       legacy_auth_tests(),
            auth_plain,
            auth_md5,
            presence_broadcast,
            last,
    -       roster_get,
    +       roster_tests:single_cases(),
            private,
    -       privacy,
    -       blocking,
    -       vcard,
    +       privacy_tests:single_cases(),
    +       vcard_tests:single_cases(),
    +       muc_tests:single_cases(),
    +       offline_tests:single_cases(),
            test_unregister]},
    -     {test_muc_register, [sequence],
    -      [muc_register_master, muc_register_slave]},
    -     {test_roster_subscribe, [parallel],
    -      [roster_subscribe_master,
    -       roster_subscribe_slave]},
    -     {test_flex_offline, [sequence],
    -      [flex_offline_master, flex_offline_slave]},
    -     {test_offline, [sequence],
    -      [offline_master, offline_slave]},
    -     {test_muc, [parallel],
    -      [muc_master, muc_slave]},
    -     {test_announce, [sequence],
    -      [announce_master, announce_slave]},
    -     {test_vcard_xupdate, [parallel],
    -      [vcard_xupdate_master, vcard_xupdate_slave]},
    -     {test_roster_remove, [parallel],
    -      [roster_remove_master,
    -       roster_remove_slave]}];
    +     muc_tests:master_slave_cases(),
    +     privacy_tests:master_slave_cases(),
    +     roster_tests:master_slave_cases(),
    +     offline_tests:master_slave_cases(),
    +     vcard_tests:master_slave_cases(),
    +     announce_tests:master_slave_cases()];
     db_tests(DB) when DB == mnesia; DB == redis ->
         [{single_user, [sequence],
           [test_register,
    +       legacy_auth_tests(),
            auth_plain,
            auth_md5,
            presence_broadcast,
            last,
    -       roster_get,
    -       roster_ver,
    +       roster_tests:single_cases(),
            private,
    -       privacy,
    -       blocking,
    -       vcard,
    -       pubsub,
    +       privacy_tests:single_cases(),
    +       vcard_tests:single_cases(),
    +       pubsub_tests:single_cases(),
    +       muc_tests:single_cases(),
    +       offline_tests:single_cases(),
    +       mam_tests:single_cases(),
    +       mix_tests:single_cases(),
    +       carbons_tests:single_cases(),
    +       csi_tests:single_cases(),
            test_unregister]},
    -     {test_muc_register, [sequence],
    -      [muc_register_master, muc_register_slave]},
    -     {test_mix, [parallel],
    -      [mix_master, mix_slave]},
    -     {test_roster_subscribe, [parallel],
    -      [roster_subscribe_master,
    -       roster_subscribe_slave]},
    -     {test_flex_offline, [sequence],
    -      [flex_offline_master, flex_offline_slave]},
    -     {test_offline, [sequence],
    -      [offline_master, offline_slave]},
    -     {test_old_mam, [parallel],
    -      [mam_old_master, mam_old_slave]},
    -     {test_new_mam, [parallel],
    -      [mam_new_master, mam_new_slave]},
    -     {test_carbons, [parallel],
    -      [carbons_master, carbons_slave]},
    -     {test_client_state, [parallel],
    -      [client_state_master, client_state_slave]},
    -     {test_muc, [parallel],
    -      [muc_master, muc_slave]},
    -     {test_muc_mam, [parallel],
    -      [muc_mam_master, muc_mam_slave]},
    -     {test_announce, [sequence],
    -      [announce_master, announce_slave]},
    -     {test_vcard_xupdate, [parallel],
    -      [vcard_xupdate_master, vcard_xupdate_slave]},
    -     {test_roster_remove, [parallel],
    -      [roster_remove_master,
    -       roster_remove_slave]}];
    +     muc_tests:master_slave_cases(),
    +     privacy_tests:master_slave_cases(),
    +     pubsub_tests:master_slave_cases(),
    +     roster_tests:master_slave_cases(),
    +     offline_tests:master_slave_cases(),
    +     mam_tests:master_slave_cases(),
    +     mix_tests:master_slave_cases(),
    +     vcard_tests:master_slave_cases(),
    +     announce_tests:master_slave_cases(),
    +     carbons_tests:master_slave_cases(),
    +     csi_tests:master_slave_cases()];
     db_tests(_) ->
         %% No support for carboncopy
         [{single_user, [sequence],
           [test_register,
    +       legacy_auth_tests(),
            auth_plain,
            auth_md5,
            presence_broadcast,
            last,
    -       roster_get,
    -       roster_ver,
    +       roster_tests:single_cases(),
            private,
    -       privacy,
    -       blocking,
    -       vcard,
    -       pubsub,
    +       privacy_tests:single_cases(),
    +       vcard_tests:single_cases(),
    +       pubsub_tests:single_cases(),
    +       muc_tests:single_cases(),
    +       offline_tests:single_cases(),
    +       mam_tests:single_cases(),
    +       mix_tests:single_cases(),
            test_unregister]},
    -     {test_muc_register, [sequence],
    -      [muc_register_master, muc_register_slave]},
    -     {test_mix, [parallel],
    -      [mix_master, mix_slave]},
    -     {test_roster_subscribe, [parallel],
    -      [roster_subscribe_master,
    -       roster_subscribe_slave]},
    -     {test_flex_offline, [sequence],
    -      [flex_offline_master, flex_offline_slave]},
    -     {test_offline, [sequence],
    -      [offline_master, offline_slave]},
    -     {test_old_mam, [parallel],
    -      [mam_old_master, mam_old_slave]},
    -     {test_new_mam, [parallel],
    -      [mam_new_master, mam_new_slave]},
    -     {test_muc, [parallel],
    -      [muc_master, muc_slave]},
    -     {test_muc_mam, [parallel],
    -      [muc_mam_master, muc_mam_slave]},
    -     {test_announce, [sequence],
    -      [announce_master, announce_slave]},
    -     {test_vcard_xupdate, [parallel],
    -      [vcard_xupdate_master, vcard_xupdate_slave]},
    -     {test_roster_remove, [parallel],
    -      [roster_remove_master,
    -       roster_remove_slave]}].
    +     muc_tests:master_slave_cases(),
    +     privacy_tests:master_slave_cases(),
    +     pubsub_tests:master_slave_cases(),
    +     roster_tests:master_slave_cases(),
    +     offline_tests:master_slave_cases(),
    +     mam_tests:master_slave_cases(),
    +     mix_tests:master_slave_cases(),
    +     vcard_tests:master_slave_cases(),
    +     announce_tests:master_slave_cases()].
     
     ldap_tests() ->
         [{ldap_tests, [sequence],
           [test_auth,
    +       test_auth_fail,
            vcard_get,
            ldap_shared_roster_get]}].
     
     extauth_tests() ->
         [{extauth_tests, [sequence],
           [test_auth,
    +       test_auth_fail,
            test_unregister]}].
     
    +component_tests() ->
    +    [{component_connect, [parallel],
    +      [test_connect_bad_xml,
    +       test_connect_unexpected_xml,
    +       test_connect_unknown_ns,
    +       test_connect_bad_xmlns,
    +       test_connect_bad_ns_stream,
    +       test_connect_missing_to,
    +       test_connect,
    +       test_auth,
    +       test_auth_fail]},
    +     {component_tests, [sequence],
    +      [test_missing_address,
    +       test_invalid_from,
    +       test_component_send,
    +       bad_nonza,
    +       codec_failure]}].
    +
    +s2s_tests() ->
    +    [{s2s_connect, [parallel],
    +      [test_connect_bad_xml,
    +       test_connect_unexpected_xml,
    +       test_connect_unknown_ns,
    +       test_connect_bad_xmlns,
    +       test_connect_bad_ns_stream,
    +       test_connect,
    +       test_connect_s2s_starttls_required,
    +       test_starttls,
    +       test_connect_missing_from,
    +       test_connect_s2s_unauthenticated_iq,
    +       test_auth_starttls]},
    +     {s2s_tests, [sequence],
    +      [test_missing_address,
    +       test_invalid_from,
    +       bad_nonza,
    +       codec_failure]}].
    +
     groups() ->
         [{ldap, [sequence], ldap_tests()},
          {extauth, [sequence], extauth_tests()},
          {no_db, [sequence], no_db_tests()},
    +     {component, [sequence], component_tests()},
    +     {s2s, [sequence], s2s_tests()},
          {mnesia, [sequence], db_tests(mnesia)},
          {redis, [sequence], db_tests(redis)},
          {mysql, [sequence], db_tests(mysql)},
    @@ -390,6 +513,8 @@ all() ->
          {group, sqlite},
          {group, extauth},
          {group, riak},
    +     {group, component},
    +     {group, s2s},
          stop_ejabberd].
     
     stop_ejabberd(Config) ->
    @@ -398,13 +523,91 @@ stop_ejabberd(Config) ->
         ?recv1({xmlstreamend, <<"stream:stream">>}),
         Config.
     
    +test_connect_bad_xml(Config) ->
    +    Config0 = tcp_connect(Config),
    +    send_text(Config0, <<"<'/>">>),
    +    Version = ?config(stream_version, Config0),
    +    ?recv1(#stream_start{version = Version}),
    +    ?recv1(#stream_error{reason = 'not-well-formed'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config0).
    +
    +test_connect_unexpected_xml(Config) ->
    +    Config0 = tcp_connect(Config),
    +    send(Config0, #caps{}),
    +    Version = ?config(stream_version, Config0),
    +    ?recv1(#stream_start{version = Version}),
    +    ?recv1(#stream_error{reason = 'invalid-xml'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config0).
    +
    +test_connect_unknown_ns(Config) ->
    +    Config0 = init_stream(set_opt(xmlns, <<"wrong">>, Config)),
    +    ?recv1(#stream_error{reason = 'invalid-xml'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config0).
    +
    +test_connect_bad_xmlns(Config) ->
    +    NS = case ?config(type, Config) of
    +	     client -> ?NS_SERVER;
    +	     _ -> ?NS_CLIENT
    +	 end,
    +    Config0 = init_stream(set_opt(xmlns, NS, Config)),
    +    ?recv1(#stream_error{reason = 'invalid-namespace'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config0).
    +
    +test_connect_bad_ns_stream(Config) ->
    +    Config0 = init_stream(set_opt(ns_stream, <<"wrong">>, Config)),
    +    ?recv1(#stream_error{reason = 'invalid-namespace'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config0).
    +
    +test_connect_bad_lang(Config) ->
    +    Lang = iolist_to_binary(lists:duplicate(36, $x)),
    +    Config0 = init_stream(set_opt(lang, Lang, Config)),
    +    ?recv1(#stream_error{reason = 'policy-violation'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config0).
    +
    +test_connect_bad_to(Config) ->
    +    Config0 = init_stream(set_opt(server, <<"wrong.com">>, Config)),
    +    ?recv1(#stream_error{reason = 'host-unknown'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config0).
    +
    +test_connect_missing_to(Config) ->
    +    Config0 = init_stream(set_opt(server, <<"">>, Config)),
    +    ?recv1(#stream_error{reason = 'improper-addressing'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config0).
    +
    +test_connect_missing_from(Config) ->
    +    Config1 = starttls(connect(Config)),
    +    Config2 = set_opt(stream_from, <<"">>, Config1),
    +    Config3 = init_stream(Config2),
    +    ?recv1(#stream_error{reason = 'policy-violation'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config3).
    +
     test_connect(Config) ->
         disconnect(connect(Config)).
     
    +test_connect_s2s_starttls_required(Config) ->
    +    Config1 = connect(Config),
    +    send(Config1, #caps{}),
    +    ?recv1(#stream_error{reason = 'policy-violation'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config1).
    +
    +test_connect_s2s_unauthenticated_iq(Config) ->
    +    Config1 = connect(starttls(connect(Config))),
    +    unauthenticated_iq(Config1).
    +
     test_starttls(Config) ->
         case ?config(starttls, Config) of
             true ->
    -            disconnect(starttls(Config));
    +            disconnect(connect(starttls(Config)));
             _ ->
                 {skipped, 'starttls_not_available'}
         end.
    @@ -432,8 +635,8 @@ test_register(Config) ->
     
     register(Config) ->
         #iq{type = result,
    -        sub_els = [#register{username = none,
    -                             password = none}]} =
    +        sub_els = [#register{username = <<>>,
    +                             password = <<>>}]} =
             send_recv(Config, #iq{type = get, to = server_jid(Config),
                                   sub_els = [#register{}]}),
         #iq{type = result, sub_els = []} =
    @@ -462,6 +665,84 @@ try_unregister(Config) ->
         ?recv1(#stream_error{reason = conflict}),
         Config.
     
    +unauthenticated_stanza(Config) ->
    +    %% Unauthenticated stanza should be silently dropped.
    +    send(Config, #message{to = server_jid(Config)}),
    +    disconnect(Config).
    +
    +unauthenticated_iq(Config) ->
    +    From = my_jid(Config),
    +    To = server_jid(Config),
    +    #iq{type = error} =
    +	send_recv(Config, #iq{type = get, from = From, to = To,
    +			      sub_els = [#disco_info{}]}),
    +    disconnect(Config).
    +
    +bad_nonza(Config) ->
    +    %% Unsupported and invalid nonza should be silently dropped.
    +    send(Config, #caps{}),
    +    send(Config, #stanza_error{type = wrong}),
    +    disconnect(Config).
    +
    +invalid_from(Config) ->
    +    send(Config, #message{from = jid:make(randoms:get_string())}),
    +    ?recv1(#stream_error{reason = 'invalid-from'}),
    +    ?recv1({xmlstreamend, <<"stream:stream">>}),
    +    close_socket(Config).
    +
    +test_missing_address(Config) ->
    +    Server = server_jid(Config),
    +    #iq{type = error} = send_recv(Config, #iq{type = get, from = Server}),
    +    #iq{type = error} = send_recv(Config, #iq{type = get, to = Server}),
    +    disconnect(Config).
    +
    +test_invalid_from(Config) ->
    +    From = jid:make(randoms:get_string()),
    +    To = jid:make(randoms:get_string()),
    +    #iq{type = error} =
    +	send_recv(Config, #iq{type = get, from = From, to = To}),
    +    disconnect(Config).
    +
    +test_component_send(Config) ->
    +    To = jid:make(?COMMON_VHOST),
    +    From = server_jid(Config),
    +    #iq{type = result, from = To, to = From} =
    +	send_recv(Config, #iq{type = get, to = To, from = From,
    +			      sub_els = [#ping{}]}),
    +    disconnect(Config).
    +
    +s2s_dialback(Config) ->
    +    ejabberd_s2s:stop_all_connections(),
    +    ejabberd_config:add_option(s2s_use_starttls, false),
    +    ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
    +    s2s_ping(Config).
    +
    +s2s_optional(Config) ->
    +    ejabberd_s2s:stop_all_connections(),
    +    ejabberd_config:add_option(s2s_use_starttls, optional),
    +    ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
    +    s2s_ping(Config).
    +
    +s2s_required(Config) ->
    +    ejabberd_s2s:stop_all_connections(),
    +    ejabberd_config:add_option(s2s_use_starttls, required),
    +    ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
    +    s2s_ping(Config).
    +
    +s2s_required_trusted(Config) ->
    +    ejabberd_s2s:stop_all_connections(),
    +    ejabberd_config:add_option(s2s_use_starttls, required),
    +    ejabberd_config:add_option(domain_certfile, "cert.pem"),
    +    s2s_ping(Config).
    +
    +s2s_ping(Config) ->
    +    From = my_jid(Config),
    +    To = jid:make(?MNESIA_VHOST),
    +    ID = randoms:get_string(),
    +    ejabberd_s2s:route(From, To, #iq{id = ID, type = get, sub_els = [#ping{}]}),
    +    #iq{type = result, id = ID, sub_els = []} = recv_iq(Config),
    +    disconnect(Config).
    +
     auth_md5(Config) ->
         Mechs = ?config(mechs, Config),
         case lists:member(<<"DIGEST-MD5">>, Mechs) of
    @@ -482,47 +763,61 @@ auth_plain(Config) ->
                 {skipped, 'PLAIN_not_available'}
         end.
     
    +test_legacy_auth(Config) ->
    +    disconnect(auth_legacy(Config, _Digest = false)).
    +
    +test_legacy_auth_digest(Config) ->
    +    disconnect(auth_legacy(Config, _Digest = true)).
    +
    +test_legacy_auth_no_resource(Config0) ->
    +    Config = set_opt(resource, <<"">>, Config0),
    +    disconnect(auth_legacy(Config, _Digest = false, _ShouldFail = true)).
    +
    +test_legacy_auth_bad_jid(Config0) ->
    +    Config = set_opt(user, <<"@">>, Config0),
    +    disconnect(auth_legacy(Config, _Digest = false, _ShouldFail = true)).
    +
    +test_legacy_auth_fail(Config0) ->
    +    Config = set_opt(user, <<"wrong">>, Config0),
    +    disconnect(auth_legacy(Config, _Digest = false, _ShouldFail = true)).
    +
     test_auth(Config) ->
         disconnect(auth(Config)).
     
    +test_auth_starttls(Config) ->
    +    disconnect(auth(connect(starttls(Config)))).
    +
    +test_auth_fail(Config0) ->
    +    Config = set_opt(user, <<"wrong">>,
    +		     set_opt(password, <<"wrong">>, Config0)),
    +    disconnect(auth(Config, _ShouldFail = true)).
    +
     test_bind(Config) ->
         disconnect(bind(Config)).
     
     test_open_session(Config) ->
    -    disconnect(open_session(Config)).
    +    disconnect(open_session(Config, true)).
     
    -roster_get(Config) ->
    -    #iq{type = result, sub_els = [#roster{items = []}]} =
    -        send_recv(Config, #iq{type = get, sub_els = [#roster{}]}),
    +codec_failure(Config) ->
    +    JID = my_jid(Config),
    +    #iq{type = error} =
    +	send_recv(Config, #iq{type = wrong, from = JID, to = JID}),
         disconnect(Config).
     
    -roster_ver(Config) ->
    -    %% Get initial "ver"
    -    #iq{type = result, sub_els = [#roster{ver = Ver1, items = []}]} =
    -        send_recv(Config, #iq{type = get,
    -                              sub_els = [#roster{ver = <<"">>}]}),
    -    %% Should receive empty IQ-result
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config, #iq{type = get,
    -                              sub_els = [#roster{ver = Ver1}]}),
    -    %% Attempting to subscribe to server's JID
    -    send(Config, #presence{type = subscribe, to = server_jid(Config)}),
    -    %% Receive a single roster push with the new "ver"
    -    ?recv1(#iq{type = set, sub_els = [#roster{ver = Ver2}]}),
    -    %% Requesting roster with the previous "ver". Should receive Ver2 again
    -    #iq{type = result, sub_els = [#roster{ver = Ver2}]} =
    -        send_recv(Config, #iq{type = get,
    -                              sub_els = [#roster{ver = Ver1}]}),
    -    %% Now requesting roster with the newest "ver". Should receive empty IQ.
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config, #iq{type = get,
    -                              sub_els = [#roster{ver = Ver2}]}),
    +unsupported_query(Config) ->
    +    ServerJID = server_jid(Config),
    +    #iq{type = error} = send_recv(Config, #iq{type = get, to = ServerJID}),
    +    #iq{type = error} = send_recv(Config, #iq{type = get, to = ServerJID,
    +					      sub_els = [#caps{}]}),
    +    #iq{type = error} = send_recv(Config, #iq{type = get, to = ServerJID,
    +					      sub_els = [#roster_query{},
    +							 #disco_info{},
    +							 #privacy_query{}]}),
         disconnect(Config).
     
     presence(Config) ->
    -    send(Config, #presence{}),
         JID = my_jid(Config),
    -    ?recv1(#presence{from = JID, to = JID}),
    +    #presence{from = JID, to = JID} = send_recv(Config, #presence{}),
         disconnect(Config).
     
     presence_broadcast(Config) ->
    @@ -539,18 +834,18 @@ presence_broadcast(Config) ->
     				      lang = <<"en">>,
     				      name = <<"ejabberd_ct">>}],
     		       node = Node, features = [Feature]},
    -    Caps = #caps{hash = <<"sha-1">>, node = ?EJABBERD_CT_URI, ver = Ver},
    +    Caps = #caps{hash = <<"sha-1">>, node = ?EJABBERD_CT_URI, version = B64Ver},
         send(Config, #presence{sub_els = [Caps]}),
         JID = my_jid(Config),
         %% We receive:
         %% 1) disco#info iq request for CAPS
         %% 2) welcome message
         %% 3) presence broadcast
    -    {IQ, _, _} = ?recv3(#iq{type = get,
    -			    from = ServerJID,
    -			    sub_els = [#disco_info{node = Node}]},
    -			#message{type = normal},
    -			#presence{from = JID, to = JID}),
    +    IQ = #iq{type = get,
    +	     from = ServerJID,
    +	     sub_els = [#disco_info{node = Node}]} = recv_iq(Config),
    +    #message{type = normal} = recv_message(Config),
    +    #presence{from = JID, to = JID} = recv_presence(Config),
         send(Config, #iq{type = result, id = IQ#iq.id,
     		     to = ServerJID, sub_els = [Info]}),
         %% We're trying to read our feature from ejabberd database
    @@ -559,15 +854,20 @@ presence_broadcast(Config) ->
     	lists:foldl(
     	  fun(Time, []) ->
     		  timer:sleep(Time),
    -		  mod_caps:get_features(
    -		    Server,
    -		    mod_caps:read_caps(
    -		      [xmpp_codec:encode(Caps)]));
    +		  mod_caps:get_features(Server, Caps);
     	     (_, Acc) ->
     		  Acc
     	  end, [], [0, 100, 200, 2000, 5000, 10000]),
         disconnect(Config).
     
    +legacy_iq(Config) ->
    +    true = is_feature_advertised(Config, ?NS_EVENT),
    +    ServerJID = server_jid(Config),
    +    #iq{type = result, sub_els = []} =
    +	send_recv(Config, #iq{to = ServerJID, type = get,
    +			      sub_els = [#xevent{}]}),
    +    disconnect(Config).
    +
     ping(Config) ->
         true = is_feature_advertised(Config, ?NS_PING),
         #iq{type = result, sub_els = []} =
    @@ -607,56 +907,6 @@ disco(Config) ->
           end, Items),
         disconnect(Config).
     
    -sm(Config) ->
    -    Server = ?config(server, Config),
    -    ServerJID = jid:make(<<"">>, Server, <<"">>),
    -    %% Send messages of type 'headline' so the server discards them silently
    -    Msg = #message{to = ServerJID, type = headline,
    -		   body = [#text{data = <<"body">>}]},
    -    true = ?config(sm, Config),
    -    %% Enable the session management with resumption enabled
    -    send(Config, #sm_enable{resume = true, xmlns = ?NS_STREAM_MGMT_3}),
    -    ?recv1(#sm_enabled{id = ID, resume = true}),
    -    %% Initial request; 'h' should be 0.
    -    send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
    -    ?recv1(#sm_a{h = 0}),
    -    %% sending two messages and requesting again; 'h' should be 3.
    -    send(Config, Msg),
    -    send(Config, Msg),
    -    send(Config, Msg),
    -    send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
    -    ?recv1(#sm_a{h = 3}),
    -    close_socket(Config),
    -    {save_config, set_opt(sm_previd, ID, Config)}.
    -
    -sm_resume(Config) ->
    -    {sm, SMConfig} = ?config(saved_config, Config),
    -    ID = ?config(sm_previd, SMConfig),
    -    Server = ?config(server, Config),
    -    ServerJID = jid:make(<<"">>, Server, <<"">>),
    -    MyJID = my_jid(Config),
    -    Txt = #text{data = <<"body">>},
    -    Msg = #message{from = ServerJID, to = MyJID, body = [Txt]},
    -    %% Route message. The message should be queued by the C2S process.
    -    ejabberd_router:route(ServerJID, MyJID, xmpp_codec:encode(Msg)),
    -    send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}),
    -    ?recv1(#sm_resumed{previd = ID, h = 3}),
    -    ?recv1(#message{from = ServerJID, to = MyJID, body = [Txt]}),
    -    ?recv1(#sm_r{}),
    -    send(Config, #sm_a{h = 1, xmlns = ?NS_STREAM_MGMT_3}),
    -    %% Send another stanza to increment the server's 'h' for sm_resume_failed.
    -    send(Config, #presence{to = ServerJID}),
    -    close_socket(Config),
    -    {save_config, set_opt(sm_previd, ID, Config)}.
    -
    -sm_resume_failed(Config) ->
    -    {sm_resume, SMConfig} = ?config(saved_config, Config),
    -    ID = ?config(sm_previd, SMConfig),
    -    ct:sleep(5000), % Wait for session to time out.
    -    send(Config, #sm_resume{previd = ID, h = 1, xmlns = ?NS_STREAM_MGMT_3}),
    -    ?recv1(#sm_failed{reason = 'item-not-found', h = 4}),
    -    disconnect(Config).
    -
     private(Config) ->
         Conference = #bookmark_conference{name = <<"Some name">>,
                                           autojoin = true,
    @@ -665,22 +915,23 @@ private(Config) ->
                                                   <<"some.conference.org">>,
                                                   <<>>)},
         Storage = #bookmark_storage{conference = [Conference]},
    -    StorageXMLOut = xmpp_codec:encode(Storage),
    +    StorageXMLOut = xmpp:encode(Storage),
    +    WrongEl = #xmlel{name = <<"wrong">>},
         #iq{type = error} =
    -        send_recv(Config, #iq{type = get, sub_els = [#private{}],
    -                              to = server_jid(Config)}),
    +        send_recv(Config, #iq{type = get,
    +			      sub_els = [#private{xml_els = [WrongEl]}]}),
         #iq{type = result, sub_els = []} =
             send_recv(
               Config, #iq{type = set,
    -                      sub_els = [#private{xml_els = [StorageXMLOut]}]}),
    +                      sub_els = [#private{xml_els = [WrongEl, StorageXMLOut]}]}),
         #iq{type = result,
             sub_els = [#private{xml_els = [StorageXMLIn]}]} =
             send_recv(
               Config,
               #iq{type = get,
    -              sub_els = [#private{xml_els = [xmpp_codec:encode(
    +              sub_els = [#private{xml_els = [xmpp:encode(
                                                    #bookmark_storage{})]}]}),
    -    Storage = xmpp_codec:decode(StorageXMLIn),
    +    Storage = xmpp:decode(StorageXMLIn),
         disconnect(Config).
     
     last(Config) ->
    @@ -690,1685 +941,36 @@ last(Config) ->
                                   to = server_jid(Config)}),
         disconnect(Config).
     
    -privacy(Config) ->
    -    true = is_feature_advertised(Config, ?NS_PRIVACY),
    -    #iq{type = result, sub_els = [#privacy{}]} =
    -        send_recv(Config, #iq{type = get, sub_els = [#privacy{}]}),
    -    JID = <<"tybalt@example.com">>,
    -    I1 = send(Config,
    -              #iq{type = set,
    -                  sub_els = [#privacy{
    -                                lists = [#privacy_list{
    -                                            name = <<"public">>,
    -                                            items =
    -                                                [#privacy_item{
    -                                                    type = jid,
    -                                                    order = 3,
    -                                                    action = deny,
    -                                                    kinds = ['presence-in'],
    -                                                    value = JID}]}]}]}),
    -    {Push1, _} =
    -        ?recv2(
    -           #iq{type = set,
    -               sub_els = [#privacy{
    -                             lists = [#privacy_list{
    -                                         name = <<"public">>}]}]},
    -           #iq{type = result, id = I1, sub_els = []}),
    -    send(Config, make_iq_result(Push1)),
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config, #iq{type = set,
    -                              sub_els = [#privacy{active = <<"public">>}]}),
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config, #iq{type = set,
    -                              sub_els = [#privacy{default = <<"public">>}]}),
    -    #iq{type = result,
    -        sub_els = [#privacy{default = <<"public">>,
    -                            active = <<"public">>,
    -                            lists = [#privacy_list{name = <<"public">>}]}]} =
    -        send_recv(Config, #iq{type = get, sub_els = [#privacy{}]}),
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config,
    -                  #iq{type = set, sub_els = [#privacy{default = none}]}),
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config, #iq{type = set, sub_els = [#privacy{active = none}]}),
    -    I2 = send(Config, #iq{type = set,
    -                          sub_els = [#privacy{
    -                                        lists =
    -                                            [#privacy_list{
    -                                                name = <<"public">>}]}]}),
    -    {Push2, _} =
    -        ?recv2(
    -           #iq{type = set,
    -               sub_els = [#privacy{
    -                             lists = [#privacy_list{
    -                                         name = <<"public">>}]}]},
    -           #iq{type = result, id = I2, sub_els = []}),
    -    send(Config, make_iq_result(Push2)),
    -    disconnect(Config).
    -
    -blocking(Config) ->
    -    true = is_feature_advertised(Config, ?NS_BLOCKING),
    -    JID = jid:make(<<"romeo">>, <<"montague.net">>, <<>>),
    -    #iq{type = result, sub_els = [#block_list{}]} =
    -        send_recv(Config, #iq{type = get, sub_els = [#block_list{}]}),
    -    I1 = send(Config, #iq{type = set,
    -                          sub_els = [#block{items = [JID]}]}),
    -    {Push1, Push2, _} =
    -        ?recv3(
    -           #iq{type = set,
    -               sub_els = [#privacy{lists = [#privacy_list{}]}]},
    -           #iq{type = set,
    -               sub_els = [#block{items = [JID]}]},
    -           #iq{type = result, id = I1, sub_els = []}),
    -    send(Config, make_iq_result(Push1)),
    -    send(Config, make_iq_result(Push2)),
    -    I2 = send(Config, #iq{type = set,
    -                          sub_els = [#unblock{items = [JID]}]}),
    -    {Push3, Push4, _} =
    -        ?recv3(
    -           #iq{type = set,
    -               sub_els = [#privacy{lists = [#privacy_list{}]}]},
    -           #iq{type = set,
    -               sub_els = [#unblock{items = [JID]}]},
    -           #iq{type = result, id = I2, sub_els = []}),
    -    send(Config, make_iq_result(Push3)),
    -    send(Config, make_iq_result(Push4)),
    -    disconnect(Config).
    -
    -vcard(Config) ->
    -    true = is_feature_advertised(Config, ?NS_VCARD),
    -    VCard =
    -        #vcard{fn = <<"Peter Saint-Andre">>,
    -               n = #vcard_name{family = <<"Saint-Andre">>,
    -                               given = <<"Peter">>},
    -               nickname = <<"stpeter">>,
    -               bday = <<"1966-08-06">>,
    -               adr = [#vcard_adr{work = true,
    -                                 extadd = <<"Suite 600">>,
    -                                 street = <<"1899 Wynkoop Street">>,
    -                                 locality = <<"Denver">>,
    -                                 region = <<"CO">>,
    -                                 pcode = <<"80202">>,
    -                                 ctry = <<"USA">>},
    -                      #vcard_adr{home = true,
    -                                 locality = <<"Denver">>,
    -                                 region = <<"CO">>,
    -                                 pcode = <<"80209">>,
    -                                 ctry = <<"USA">>}],
    -               tel = [#vcard_tel{work = true,voice = true,
    -                                 number = <<"303-308-3282">>},
    -                      #vcard_tel{home = true,voice = true,
    -                                 number = <<"303-555-1212">>}],
    -               email = [#vcard_email{internet = true,pref = true,
    -                                     userid = <<"stpeter@jabber.org">>}],
    -               jabberid = <<"stpeter@jabber.org">>,
    -               title = <<"Executive Director">>,role = <<"Patron Saint">>,
    -               org = #vcard_org{name = <<"XMPP Standards Foundation">>},
    -               url = <<"http://www.xmpp.org/xsf/people/stpeter.shtml">>,
    -               desc = <<"More information about me is located on my "
    -                        "personal website: http://www.saint-andre.com/">>},
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config, #iq{type = set, sub_els = [VCard]}),
    -    %% TODO: check if VCard == VCard1.
    -    #iq{type = result, sub_els = [_VCard1]} =
    -        send_recv(Config, #iq{type = get, sub_els = [#vcard{}]}),
    -    disconnect(Config).
    -
     vcard_get(Config) ->
         true = is_feature_advertised(Config, ?NS_VCARD),
         %% TODO: check if VCard corresponds to LDIF data from ejabberd.ldif
         #iq{type = result, sub_els = [_VCard]} =
    -        send_recv(Config, #iq{type = get, sub_els = [#vcard{}]}),
    +        send_recv(Config, #iq{type = get, sub_els = [#vcard_temp{}]}),
         disconnect(Config).
     
     ldap_shared_roster_get(Config) ->
         Item = #roster_item{jid = jid:from_string(<<"user2@ldap.localhost">>), name = <<"Test User 2">>,
                             groups = [<<"group1">>], subscription = both},
    -    #iq{type = result, sub_els = [#roster{items = [Item]}]} =
    -        send_recv(Config, #iq{type = get, sub_els = [#roster{}]}),
    -    disconnect(Config).
    -
    -vcard_xupdate_master(Config) ->
    -    Img = <<137, "PNG\r\n", 26, $\n>>,
    -    ImgHash = p1_sha:sha(Img),
    -    MyJID = my_jid(Config),
    -    Peer = ?config(slave, Config),
    -    wait_for_slave(Config),
    -    send(Config, #presence{}),
    -    ?recv2(#presence{from = MyJID, type = undefined},
    -           #presence{from = Peer, type = undefined}),
    -    VCard = #vcard{photo = #vcard_photo{type = <<"image/png">>, binval = Img}},
    -    I1 = send(Config, #iq{type = set, sub_els = [VCard]}),
    -    ?recv2(#iq{type = result, sub_els = [], id = I1},
    -	   #presence{from = MyJID, type = undefined,
    -		     sub_els = [#vcard_xupdate{photo = ImgHash}]}),
    -    I2 = send(Config, #iq{type = set, sub_els = [#vcard{}]}),
    -    ?recv3(#iq{type = result, sub_els = [], id = I2},
    -	   #presence{from = MyJID, type = undefined,
    -		     sub_els = [#vcard_xupdate{photo = undefined}]},
    -	   #presence{from = Peer, type = unavailable}),
    -    disconnect(Config).
    -
    -vcard_xupdate_slave(Config) ->
    -    Img = <<137, "PNG\r\n", 26, $\n>>,
    -    ImgHash = p1_sha:sha(Img),
    -    MyJID = my_jid(Config),
    -    Peer = ?config(master, Config),
    -    send(Config, #presence{}),
    -    ?recv1(#presence{from = MyJID, type = undefined}),
    -    wait_for_master(Config),
    -    ?recv1(#presence{from = Peer, type = undefined}),
    -    ?recv1(#presence{from = Peer, type = undefined,
    -	      sub_els = [#vcard_xupdate{photo = ImgHash}]}),
    -    ?recv1(#presence{from = Peer, type = undefined,
    -	      sub_els = [#vcard_xupdate{photo = undefined}]}),
    +    #iq{type = result, sub_els = [#roster_query{items = [Item]}]} =
    +        send_recv(Config, #iq{type = get, sub_els = [#roster_query{}]}),
         disconnect(Config).
     
     stats(Config) ->
    -    #iq{type = result, sub_els = [#stats{stat = Stats}]} =
    +    #iq{type = result, sub_els = [#stats{list = Stats}]} =
             send_recv(Config, #iq{type = get, sub_els = [#stats{}],
                                   to = server_jid(Config)}),
         lists:foreach(
           fun(#stat{} = Stat) ->
                   #iq{type = result, sub_els = [_|_]} =
                       send_recv(Config, #iq{type = get,
    -                                        sub_els = [#stats{stat = [Stat]}],
    +                                        sub_els = [#stats{list = [Stat]}],
                                             to = server_jid(Config)})
           end, Stats),
         disconnect(Config).
     
    -pubsub(Config) ->
    -    Features = get_features(Config, pubsub_jid(Config)),
    -    true = lists:member(?NS_PUBSUB, Features),
    -    %% Publish  element within node "presence"
    -    ItemID = randoms:get_string(),
    -    Node = <<"presence!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>,
    -    Item = #pubsub_item{id = ItemID,
    -                        xml_els = [xmpp_codec:encode(#presence{})]},
    -    #iq{type = result,
    -        sub_els = [#pubsub{publish = #pubsub_publish{
    -                             node = Node,
    -                             items = [#pubsub_item{id = ItemID}]}}]} =
    -        send_recv(Config,
    -                  #iq{type = set, to = pubsub_jid(Config),
    -                      sub_els = [#pubsub{publish = #pubsub_publish{
    -                                           node = Node,
    -                                           items = [Item]}}]}),
    -    %% Subscribe to node "presence"
    -    I1 = send(Config,
    -             #iq{type = set, to = pubsub_jid(Config),
    -                 sub_els = [#pubsub{subscribe = #pubsub_subscribe{
    -                                      node = Node,
    -                                      jid = my_jid(Config)}}]}),
    -    ?recv2(
    -       #message{sub_els = [#pubsub_event{}, #delay{}]},
    -       #iq{type = result, id = I1}),
    -    %% Get subscriptions
    -    true = lists:member(?PUBSUB("retrieve-subscriptions"), Features),
    -    #iq{type = result,
    -        sub_els =
    -            [#pubsub{subscriptions =
    -                         {none, [#pubsub_subscription{node = Node}]}}]} =
    -        send_recv(Config, #iq{type = get, to = pubsub_jid(Config),
    -                              sub_els = [#pubsub{subscriptions = {none, []}}]}),
    -    %% Get affiliations
    -    true = lists:member(?PUBSUB("retrieve-affiliations"), Features),
    -    #iq{type = result,
    -        sub_els = [#pubsub{
    -                      affiliations =
    -                          [#pubsub_affiliation{node = Node, type = owner}]}]} =
    -        send_recv(Config, #iq{type = get, to = pubsub_jid(Config),
    -                              sub_els = [#pubsub{affiliations = []}]}),
    -    %% Fetching published items from node "presence"
    -    #iq{type = result,
    -        sub_els = [#pubsub{items = #pubsub_items{
    -                             node = Node,
    -                             items = [Item]}}]} =
    -        send_recv(Config,
    -                  #iq{type = get, to = pubsub_jid(Config),
    -                      sub_els = [#pubsub{items = #pubsub_items{node = Node}}]}),
    -    %% Deleting the item from the node
    -    true = lists:member(?PUBSUB("delete-items"), Features),
    -    I2 = send(Config,
    -              #iq{type = set, to = pubsub_jid(Config),
    -                  sub_els = [#pubsub{retract = #pubsub_retract{
    -                                       node = Node,
    -                                       items = [#pubsub_item{id = ItemID}]}}]}),
    -    ?recv2(
    -       #iq{type = result, id = I2, sub_els = []},
    -       #message{sub_els = [#pubsub_event{
    -                              items = [#pubsub_event_items{
    -                                          node = Node,
    -                                          retract = [ItemID]}]}]}),
    -    %% Unsubscribe from node "presence"
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config,
    -                  #iq{type = set, to = pubsub_jid(Config),
    -                      sub_els = [#pubsub{unsubscribe = #pubsub_unsubscribe{
    -                                           node = Node,
    -                                           jid = my_jid(Config)}}]}),
    -    disconnect(Config).
    -
    -mix_master(Config) ->
    -    MIX = mix_jid(Config),
    -    Room = mix_room_jid(Config),
    -    MyJID = my_jid(Config),
    -    MyBareJID = jid:remove_resource(MyJID),
    -    true = is_feature_advertised(Config, ?NS_MIX_0, MIX),
    -    #iq{type = result,
    -	sub_els =
    -	    [#disco_info{
    -		identities = [#identity{category = <<"conference">>,
    -					type = <<"text">>}],
    -		xdata = [#xdata{type = result, fields = XFields}]}]} =
    -	send_recv(Config, #iq{type = get, to = MIX, sub_els = [#disco_info{}]}),
    -    true = lists:any(
    -	     fun(#xdata_field{var = <<"FORM_TYPE">>,
    -			      values = [?NS_MIX_SERVICEINFO_0]}) -> true;
    -		(_) -> false
    -	     end, XFields),
    -    %% Joining
    -    Nodes = [?NS_MIX_NODES_MESSAGES, ?NS_MIX_NODES_PRESENCE,
    -	     ?NS_MIX_NODES_PARTICIPANTS, ?NS_MIX_NODES_SUBJECT,
    -	     ?NS_MIX_NODES_CONFIG],
    -    I0 = send(Config, #iq{type = set, to = Room,
    -			  sub_els = [#mix_join{subscribe = Nodes}]}),
    -    {_, #message{sub_els =
    -		     [#pubsub_event{
    -			 items = [#pubsub_event_items{
    -				     node = ?NS_MIX_NODES_PARTICIPANTS,
    -				     items = [#pubsub_event_item{
    -						 id = ParticipantID,
    -						 xml_els = [PXML]}]}]}]}} =
    -	?recv2(#iq{type = result, id = I0,
    -		   sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]},
    -	       #message{from = Room}),
    -    #mix_participant{jid = MyBareJID} = xmpp_codec:decode(PXML),
    -    %% Coming online
    -    PresenceID = randoms:get_string(),
    -    Presence = xmpp_codec:encode(#presence{}),
    -    I1 = send(
    -	   Config,
    -	   #iq{type = set, to = Room,
    -	       sub_els =
    -		   [#pubsub{
    -		       publish = #pubsub_publish{
    -				    node = ?NS_MIX_NODES_PRESENCE,
    -				    items = [#pubsub_item{
    -						id = PresenceID,
    -						xml_els = [Presence]}]}}]}),
    -    ?recv2(#iq{type = result, id = I1,
    -	       sub_els =
    -		   [#pubsub{
    -		       publish = #pubsub_publish{
    -				    node = ?NS_MIX_NODES_PRESENCE,
    -				    items = [#pubsub_item{id = PresenceID}]}}]},
    -	   #message{from = Room,
    -		    sub_els =
    -			[#pubsub_event{
    -			    items = [#pubsub_event_items{
    -					node = ?NS_MIX_NODES_PRESENCE,
    -					items = [#pubsub_event_item{
    -						    id = PresenceID,
    -						    xml_els = [Presence]}]}]}]}),
    -    %% Coming offline
    -    send(Config, #presence{type = unavailable, to = Room}),
    -    %% Receiving presence retract event
    -    #message{from = Room,
    -	     sub_els = [#pubsub_event{
    -			   items = [#pubsub_event_items{
    -				       node = ?NS_MIX_NODES_PRESENCE,
    -				       retract = [PresenceID]}]}]} = recv(),
    -    %% Leaving
    -    I2 = send(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}),
    -    ?recv2(#iq{type = result, id = I2, sub_els = []},
    -	   #message{from = Room,
    -		    sub_els =
    -			[#pubsub_event{
    -			    items = [#pubsub_event_items{
    -					node = ?NS_MIX_NODES_PARTICIPANTS,
    -					retract = [ParticipantID]}]}]}),
    -    disconnect(Config).
    -
    -mix_slave(Config) ->
    -    disconnect(Config).
    -
    -roster_subscribe_master(Config) ->
    -    send(Config, #presence{}),
    -    ?recv1(#presence{}),
    -    wait_for_slave(Config),
    -    Peer = ?config(slave, Config),
    -    LPeer = jid:remove_resource(Peer),
    -    send(Config, #presence{type = subscribe, to = LPeer}),
    -    Push1 = ?recv1(#iq{type = set,
    -                sub_els = [#roster{items = [#roster_item{
    -                                               ask = subscribe,
    -                                               subscription = none,
    -                                               jid = LPeer}]}]}),
    -    send(Config, make_iq_result(Push1)),
    -    {Push2, _} = ?recv2(
    -                    #iq{type = set,
    -                        sub_els = [#roster{items = [#roster_item{
    -                                                       subscription = to,
    -                                                       jid = LPeer}]}]},
    -                    #presence{type = subscribed, from = LPeer}),
    -    send(Config, make_iq_result(Push2)),
    -    ?recv1(#presence{type = undefined, from = Peer}),
    -    %% BUG: ejabberd sends previous push again. Is it ok?
    -    Push3 = ?recv1(#iq{type = set,
    -                sub_els = [#roster{items = [#roster_item{
    -                                               subscription = to,
    -                                               jid = LPeer}]}]}),
    -    send(Config, make_iq_result(Push3)),
    -    ?recv1(#presence{type = subscribe, from = LPeer}),
    -    send(Config, #presence{type = subscribed, to = LPeer}),
    -    Push4 = ?recv1(#iq{type = set,
    -                sub_els = [#roster{items = [#roster_item{
    -                                               subscription = both,
    -                                               jid = LPeer}]}]}),
    -    send(Config, make_iq_result(Push4)),
    -    %% Move into a group
    -    Groups = [<<"A">>, <<"B">>],
    -    Item = #roster_item{jid = LPeer, groups = Groups},
    -    I1 = send(Config, #iq{type = set, sub_els = [#roster{items = [Item]}]}),
    -    {Push5, _} = ?recv2(
    -                   #iq{type = set,
    -                       sub_els =
    -                           [#roster{items = [#roster_item{
    -                                                jid = LPeer,
    -                                                subscription = both}]}]},
    -                   #iq{type = result, id = I1, sub_els = []}),
    -    send(Config, make_iq_result(Push5)),
    -    #iq{sub_els = [#roster{items = [#roster_item{groups = G1}]}]} = Push5,
    -    Groups = lists:sort(G1),
    -    wait_for_slave(Config),
    -    ?recv1(#presence{type = unavailable, from = Peer}),
    -    disconnect(Config).
    -
    -roster_subscribe_slave(Config) ->
    -    send(Config, #presence{}),
    -    ?recv1(#presence{}),
    -    wait_for_master(Config),
    -    Peer = ?config(master, Config),
    -    LPeer = jid:remove_resource(Peer),
    -    ?recv1(#presence{type = subscribe, from = LPeer}),
    -    send(Config, #presence{type = subscribed, to = LPeer}),
    -    Push1 = ?recv1(#iq{type = set,
    -                sub_els = [#roster{items = [#roster_item{
    -                                               subscription = from,
    -                                               jid = LPeer}]}]}),
    -    send(Config, make_iq_result(Push1)),
    -    send(Config, #presence{type = subscribe, to = LPeer}),
    -    Push2 = ?recv1(#iq{type = set,
    -                sub_els = [#roster{items = [#roster_item{
    -                                               ask = subscribe,
    -                                               subscription = from,
    -                                               jid = LPeer}]}]}),
    -    send(Config, make_iq_result(Push2)),
    -    {Push3, _} = ?recv2(
    -                    #iq{type = set,
    -                        sub_els = [#roster{items = [#roster_item{
    -                                                       subscription = both,
    -                                                       jid = LPeer}]}]},
    -                    #presence{type = subscribed, from = LPeer}),
    -    send(Config, make_iq_result(Push3)),
    -    ?recv1(#presence{type = undefined, from = Peer}),
    -    wait_for_master(Config),
    -    disconnect(Config).
    -
    -roster_remove_master(Config) ->
    -    MyJID = my_jid(Config),
    -    Peer = ?config(slave, Config),
    -    LPeer = jid:remove_resource(Peer),
    -    Groups = [<<"A">>, <<"B">>],
    -    wait_for_slave(Config),
    -    send(Config, #presence{}),
    -    ?recv2(#presence{from = MyJID, type = undefined},
    -           #presence{from = Peer, type = undefined}),
    -    %% The peer removed us from its roster.
    -    {Push1, Push2, _, _, _} =
    -        ?recv5(
    -           %% TODO: I guess this can be optimized, we don't need
    -           %% to send transient roster push with subscription = 'to'.
    -           #iq{type = set,
    -               sub_els =
    -                   [#roster{items = [#roster_item{
    -                                        jid = LPeer,
    -                                        subscription = to}]}]},
    -           #iq{type = set,
    -               sub_els =
    -                   [#roster{items = [#roster_item{
    -                                        jid = LPeer,
    -                                        subscription = none}]}]},
    -           #presence{type = unsubscribe, from = LPeer},
    -           #presence{type = unsubscribed, from = LPeer},
    -           #presence{type = unavailable, from = Peer}),
    -    send(Config, make_iq_result(Push1)),
    -    send(Config, make_iq_result(Push2)),
    -    #iq{sub_els = [#roster{items = [#roster_item{groups = G1}]}]} = Push1,
    -    #iq{sub_els = [#roster{items = [#roster_item{groups = G2}]}]} = Push2,
    -    Groups = lists:sort(G1), Groups = lists:sort(G2),
    -    disconnect(Config).
    -
    -roster_remove_slave(Config) ->
    -    MyJID = my_jid(Config),
    -    Peer = ?config(master, Config),
    -    LPeer = jid:remove_resource(Peer),
    -    send(Config, #presence{}),
    -    ?recv1(#presence{from = MyJID, type = undefined}),
    -    wait_for_master(Config),
    -    ?recv1(#presence{from = Peer, type = undefined}),
    -    %% Remove the peer from roster.
    -    Item = #roster_item{jid = LPeer, subscription = remove},
    -    I = send(Config, #iq{type = set, sub_els = [#roster{items = [Item]}]}),
    -    {Push, _, _} = ?recv3(
    -                   #iq{type = set,
    -                       sub_els =
    -                           [#roster{items = [#roster_item{
    -                                                jid = LPeer,
    -                                                subscription = remove}]}]},
    -                   #iq{type = result, id = I, sub_els = []},
    -                   #presence{type = unavailable, from = Peer}),
    -    send(Config, make_iq_result(Push)),
    -    disconnect(Config).
    -
    -proxy65_master(Config) ->
    -    Proxy = proxy_jid(Config),
    -    MyJID = my_jid(Config),
    -    Peer = ?config(slave, Config),
    -    wait_for_slave(Config),
    -    send(Config, #presence{}),
    -    ?recv1(#presence{from = MyJID, type = undefined}),
    -    true = is_feature_advertised(Config, ?NS_BYTESTREAMS, Proxy),
    -    #iq{type = result, sub_els = [#bytestreams{hosts = [StreamHost]}]} =
    -        send_recv(
    -          Config,
    -          #iq{type = get, sub_els = [#bytestreams{}], to = Proxy}),
    -    SID = randoms:get_string(),
    -    Data = crypto:rand_bytes(1024),
    -    put_event(Config, {StreamHost, SID, Data}),
    -    Socks5 = socks5_connect(StreamHost, {SID, MyJID, Peer}),
    -    wait_for_slave(Config),
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config,
    -                  #iq{type = set, to = Proxy,
    -                      sub_els = [#bytestreams{activate = Peer, sid = SID}]}),
    -    socks5_send(Socks5, Data),
    -    %%?recv1(#presence{type = unavailable, from = Peer}),
    -    disconnect(Config).
    -
    -proxy65_slave(Config) ->
    -    MyJID = my_jid(Config),
    -    Peer = ?config(master, Config),
    -    send(Config, #presence{}),
    -    ?recv1(#presence{from = MyJID, type = undefined}),
    -    wait_for_master(Config),
    -    {StreamHost, SID, Data} = get_event(Config),
    -    Socks5 = socks5_connect(StreamHost, {SID, Peer, MyJID}),
    -    wait_for_master(Config),
    -    socks5_recv(Socks5, Data),
    -    disconnect(Config).
    -
    -send_messages_to_room(Config, Range) ->
    -    MyNick = ?config(master_nick, Config),
    -    Room = muc_room_jid(Config),
    -    MyNickJID = jid:replace_resource(Room, MyNick),
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -              I = send(Config, #message{to = Room, body = [Text],
    -					type = groupchat}),
    -	      ?recv1(#message{from = MyNickJID, id = I,
    -			      type = groupchat,
    -			      body = [Text]})
    -      end, Range).
    -
    -retrieve_messages_from_room_via_mam(Config, Range) ->
    -    MyNick = ?config(master_nick, Config),
    -    Room = muc_room_jid(Config),
    -    MyNickJID = jid:replace_resource(Room, MyNick),
    -    QID = randoms:get_string(),
    -    I = send(Config, #iq{type = set, to = Room,
    -			 sub_els = [#mam_query{xmlns = ?NS_MAM_1, id = QID}]}),
    -    lists:foreach(
    -      fun(N) ->
    -	      Text = #text{data = integer_to_binary(N)},
    -	      ?recv1(#message{
    -			to = MyJID, from = Room,
    -			sub_els =
    -			    [#mam_result{
    -				xmlns = ?NS_MAM_1,
    -				queryid = QID,
    -				sub_els =
    -				    [#forwarded{
    -					delay = #delay{},
    -					sub_els = [#message{
    -						      from = MyNickJID,
    -						      type = groupchat,
    -						      body = [Text]}]}]}]})
    -      end, Range),
    -    ?recv1(#iq{from = Room, id = I, type = result, sub_els = []}).
    -
    -muc_mam_master(Config) ->
    -    MyJID = my_jid(Config),
    -    MyNick = ?config(master_nick, Config),
    -    Room = muc_room_jid(Config),
    -    MyNickJID = jid:replace_resource(Room, MyNick),
    -    %% Joining
    -    send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
    -    %% Receive self-presence
    -    ?recv1(#presence{from = MyNickJID}),
    -    %% MAM feature should not be advertised at this point,
    -    %% because MAM is not enabled so far
    -    false = is_feature_advertised(Config, ?NS_MAM_1, Room),
    -    %% Fill in some history
    -    send_messages_to_room(Config, lists:seq(1, 21)),
    -    %% We now should be able to retrieve those via MAM, even though
    -    %% MAM is disabled. However, only last 20 messages should be received.
    -    retrieve_messages_from_room_via_mam(Config, lists:seq(2, 21)),
    -    %% Now enable MAM for the conference
    -    %% Retrieve config first
    -    #iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} =
    -        send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}],
    -                              to = Room}),
    -    %% Find the MAM field in the config and enable it
    -    NewFields = lists:flatmap(
    -		  fun(#xdata_field{var = <<"muc#roomconfig_mam">> = Var}) ->
    -			  [#xdata_field{var = Var, values = [<<"1">>]}];
    -		     (_) ->
    -			  []
    -		  end, RoomCfg#xdata.fields),
    -    NewRoomCfg = #xdata{type = submit, fields = NewFields},
    -    I1 = send(Config, #iq{type = set, to = Room,
    -			  sub_els = [#muc_owner{config = NewRoomCfg}]}),
    -    ?recv2(#iq{type = result, id = I1},
    -	   #message{from = Room, type = groupchat,
    -		    sub_els = [#muc_user{status_codes = [104]}]}),
    -    %% Check if MAM has been enabled
    -    true = is_feature_advertised(Config, ?NS_MAM_1, Room),
    -    %% We now sending some messages again
    -    send_messages_to_room(Config, lists:seq(1, 5)),
    -    %% And retrieve them via MAM again.
    -    retrieve_messages_from_room_via_mam(Config, lists:seq(1, 5)),
    -    disconnect(Config).
    -
    -muc_mam_slave(Config) ->
    -    disconnect(Config).
    -
    -muc_master(Config) ->
    -    MyJID = my_jid(Config),
    -    PeerJID = ?config(slave, Config),
    -    PeerBareJID = jid:remove_resource(PeerJID),
    -    PeerJIDStr = jid:to_string(PeerJID),
    -    MUC = muc_jid(Config),
    -    Room = muc_room_jid(Config),
    -    MyNick = ?config(master_nick, Config),
    -    MyNickJID = jid:replace_resource(Room, MyNick),
    -    PeerNick = ?config(slave_nick, Config),
    -    PeerNickJID = jid:replace_resource(Room, PeerNick),
    -    Subject = ?config(room_subject, Config),
    -    Localhost = jid:make(<<"">>, <<"localhost">>, <<"">>),
    -    true = is_feature_advertised(Config, ?NS_MUC, MUC),
    -    %% Joining
    -    send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
    -    %% As per XEP-0045 we MUST receive stanzas in the following order:
    -    %% 1. In-room presence from other occupants
    -    %% 2. In-room presence from the joining entity itself (so-called "self-presence")
    -    %% 3. Room history (if any)
    -    %% 4. The room subject
    -    %% 5. Live messages, presence updates, new user joins, etc.
    -    %% As this is the newly created room, we receive only the 2nd stanza.
    -    ?recv1(#presence{
    -          from = MyNickJID,
    -          sub_els = [#vcard_xupdate{},
    -		     #muc_user{
    -                        status_codes = Codes,
    -                        items = [#muc_item{role = moderator,
    -                                           jid = MyJID,
    -                                           affiliation = owner}]}]}),
    -    %% 110 -> Inform user that presence refers to itself
    -    %% 201 -> Inform user that a new room has been created
    -    [110, 201] = lists:sort(Codes),
    -    %% Request the configuration
    -    #iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} =
    -        send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}],
    -                              to = Room}),
    -    NewFields =
    -        lists:flatmap(
    -          fun(#xdata_field{var = Var, values = OrigVals}) ->
    -                  Vals = case Var of
    -                             <<"FORM_TYPE">> ->
    -                                 OrigVals;
    -                             <<"muc#roomconfig_roomname">> ->
    -                                 [<<"Test room">>];
    -                             <<"muc#roomconfig_roomdesc">> ->
    -                                 [<<"Trying to break the server">>];
    -                             <<"muc#roomconfig_persistentroom">> ->
    -                                 [<<"1">>];
    -			     <<"members_by_default">> ->
    -				 [<<"0">>];
    -			     <<"muc#roomconfig_allowvoicerequests">> ->
    -				 [<<"1">>];
    -			     <<"public_list">> ->
    -				 [<<"1">>];
    -			     <<"muc#roomconfig_publicroom">> ->
    -				 [<<"1">>];
    -                             _ ->
    -                                 []
    -                         end,
    -                  if Vals /= [] ->
    -                          [#xdata_field{values = Vals, var = Var}];
    -                     true ->
    -                          []
    -                  end
    -          end, RoomCfg#xdata.fields),
    -    NewRoomCfg = #xdata{type = submit, fields = NewFields},
    -    ID = send(Config, #iq{type = set, to = Room,
    -			  sub_els = [#muc_owner{config = NewRoomCfg}]}),
    -    ?recv2(#iq{type = result, id = ID},
    -	   #message{from = Room, type = groupchat,
    -		    sub_els = [#muc_user{status_codes = [104]}]}),
    -    %% Set subject
    -    send(Config, #message{to = Room, type = groupchat,
    -                          body = [#text{data = Subject}]}),
    -    ?recv1(#message{from = MyNickJID, type = groupchat,
    -             body = [#text{data = Subject}]}),
    -    %% Sending messages (and thus, populating history for our peer)
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -              I = send(Config, #message{to = Room, body = [Text],
    -					type = groupchat}),
    -	      ?recv1(#message{from = MyNickJID, id = I,
    -		       type = groupchat,
    -		       body = [Text]})
    -      end, lists:seq(1, 5)),
    -    %% Inviting the peer
    -    send(Config, #message{to = Room, type = normal,
    -			  sub_els =
    -			      [#muc_user{
    -				  invites =
    -				      [#muc_invite{to = PeerJID}]}]}),
    -    %% Peer is joining
    -    ?recv1(#presence{from = PeerNickJID,
    -	      sub_els = [#vcard_xupdate{},
    -			 #muc_user{
    -			    items = [#muc_item{role = visitor,
    -					       jid = PeerJID,
    -					       affiliation = none}]}]}),
    -    %% Receiving a voice request
    -    ?recv1(#message{from = Room,
    -	     sub_els = [#xdata{type = form,
    -			       instructions = [_],
    -			       fields = VoiceReqFs}]}),
    -    %% Approving the voice request
    -    ReplyVoiceReqFs =
    -	lists:map(
    -	  fun(#xdata_field{var = Var, values = OrigVals}) ->
    -                  Vals = case {Var, OrigVals} of
    -			     {<<"FORM_TYPE">>,
    -			      [<<"http://jabber.org/protocol/muc#request">>]} ->
    -				 OrigVals;
    -			     {<<"muc#role">>, [<<"participant">>]} ->
    -				 [<<"participant">>];
    -			     {<<"muc#jid">>, [PeerJIDStr]} ->
    -				 [PeerJIDStr];
    -			     {<<"muc#roomnick">>, [PeerNick]} ->
    -				 [PeerNick];
    -			     {<<"muc#request_allow">>, [<<"0">>]} ->
    -				 [<<"1">>]
    -			 end,
    -		  #xdata_field{values = Vals, var = Var}
    -	  end, VoiceReqFs),
    -    send(Config, #message{to = Room,
    -			  sub_els = [#xdata{type = submit,
    -					    fields = ReplyVoiceReqFs}]}),
    -    %% Peer is becoming a participant
    -    ?recv1(#presence{from = PeerNickJID,
    -	      sub_els = [#vcard_xupdate{},
    -			 #muc_user{
    -			    items = [#muc_item{role = participant,
    -					       jid = PeerJID,
    -					       affiliation = none}]}]}),
    -    %% Receive private message from the peer
    -    ?recv1(#message{from = PeerNickJID, body = [#text{data = Subject}]}),
    -    %% Granting membership to the peer and localhost server
    -    I1 = send(Config,
    -	      #iq{type = set, to = Room,
    -		  sub_els =
    -		      [#muc_admin{
    -			  items = [#muc_item{jid = Localhost,
    -					     affiliation = member},
    -				   #muc_item{nick = PeerNick,
    -					     jid = PeerBareJID,
    -					     affiliation = member}]}]}),
    -    %% Peer became a member
    -    ?recv1(#presence{from = PeerNickJID,
    -	      sub_els = [#vcard_xupdate{},
    -			 #muc_user{
    -			    items = [#muc_item{affiliation = member,
    -					       jid = PeerJID,
    -					       role = participant}]}]}),
    -    ?recv1(#message{from = Room,
    -	      sub_els = [#muc_user{
    -			    items = [#muc_item{affiliation = member,
    -					       jid = Localhost,
    -					       role = none}]}]}),
    -    %% BUG: We should not receive any sub_els!
    -    ?recv1(#iq{type = result, id = I1, sub_els = [_|_]}),
    -    %% Receive groupchat message from the peer
    -    ?recv1(#message{type = groupchat, from = PeerNickJID,
    -	     body = [#text{data = Subject}]}),
    -    %% Retrieving a member list
    -    #iq{type = result, sub_els = [#muc_admin{items = MemberList}]} =
    -	send_recv(Config,
    -		  #iq{type = get, to = Room,
    -		      sub_els =
    -			  [#muc_admin{items = [#muc_item{affiliation = member}]}]}),
    -    [#muc_item{affiliation = member,
    -	       jid = Localhost},
    -     #muc_item{affiliation = member,
    -	       jid = MyBareJID}] = lists:keysort(#muc_item.jid, MemberList),
    -    %% Kick the peer
    -    I2 = send(Config,
    -	      #iq{type = set, to = Room,
    -		  sub_els = [#muc_admin{
    -				items = [#muc_item{nick = PeerNick,
    -						   role = none}]}]}),
    -    %% Got notification the peer is kicked
    -    %% 307 -> Inform user that he or she has been kicked from the room
    -    ?recv1(#presence{from = PeerNickJID, type = unavailable,
    -	      sub_els = [#muc_user{
    -			    status_codes = [307],
    -			    items = [#muc_item{affiliation = member,
    -					       jid = PeerJID,
    -					       role = none}]}]}),
    -    %% BUG: We should not receive any sub_els!
    -    ?recv1(#iq{type = result, id = I2, sub_els = [_|_]}),
    -    %% Destroying the room
    -    I3 = send(Config,
    -	      #iq{type = set, to = Room,
    -		  sub_els = [#muc_owner{
    -				destroy = #muc_owner_destroy{
    -					     reason = Subject}}]}),
    -    %% Kicked off
    -    ?recv1(#presence{from = MyNickJID, type = unavailable,
    -              sub_els = [#muc_user{items = [#muc_item{role = none,
    -						      affiliation = none}],
    -				   destroy = #muc_user_destroy{
    -						reason = Subject}}]}),
    -    %% BUG: We should not receive any sub_els!
    -    ?recv1(#iq{type = result, id = I3, sub_els = [_|_]}),
    -    disconnect(Config).
    -
    -muc_slave(Config) ->
    -    MyJID = my_jid(Config),
    -    MyBareJID = jid:remove_resource(MyJID),
    -    PeerJID = ?config(master, Config),
    -    MUC = muc_jid(Config),
    -    Room = muc_room_jid(Config),
    -    MyNick = ?config(slave_nick, Config),
    -    MyNickJID = jid:replace_resource(Room, MyNick),
    -    PeerNick = ?config(master_nick, Config),
    -    PeerNickJID = jid:replace_resource(Room, PeerNick),
    -    Subject = ?config(room_subject, Config),
    -    Localhost = jid:make(<<"">>, <<"localhost">>, <<"">>),
    -    %% Receive an invite from the peer
    -    ?recv1(#message{from = Room, type = normal,
    -	     sub_els =
    -		 [#muc_user{invites =
    -				[#muc_invite{from = PeerJID}]}]}),
    -    %% But before joining we discover the MUC service first
    -    %% to check if the room is in the disco list
    -    #iq{type = result,
    -	sub_els = [#disco_items{items = [#disco_item{jid = Room}]}]} =
    -	send_recv(Config, #iq{type = get, to = MUC,
    -			      sub_els = [#disco_items{}]}),
    -    %% Now check if the peer is in the room. We check this via disco#items
    -    #iq{type = result,
    -	sub_els = [#disco_items{items = [#disco_item{jid = PeerNickJID,
    -						     name = PeerNick}]}]} =
    -	send_recv(Config, #iq{type = get, to = Room,
    -			      sub_els = [#disco_items{}]}),
    -    %% Now joining
    -    send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
    -    %% First presence is from the participant, i.e. from the peer
    -    ?recv1(#presence{
    -       from = PeerNickJID,
    -       sub_els = [#vcard_xupdate{},
    -		  #muc_user{
    -		     status_codes = [],
    -		     items = [#muc_item{role = moderator,
    -					affiliation = owner}]}]}),
    -    %% The next is the self-presence (code 110 means it)
    -    ?recv1(#presence{
    -       from = MyNickJID,
    -       sub_els = [#vcard_xupdate{},
    -		  #muc_user{
    -		     status_codes = [110],
    -		     items = [#muc_item{role = visitor,
    -					affiliation = none}]}]}),
    -    %% Receive the room subject
    -    ?recv1(#message{from = PeerNickJID, type = groupchat,
    -             body = [#text{data = Subject}],
    -	     sub_els = [#delay{}]}),
    -    %% Receive MUC history
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -	      ?recv1(#message{from = PeerNickJID,
    -		       type = groupchat,
    -		       body = [Text],
    -		       sub_els = [#delay{}]})
    -      end, lists:seq(1, 5)),
    -    %% Sending a voice request
    -    VoiceReq = #xdata{
    -		  type = submit,
    -		  fields =
    -		      [#xdata_field{
    -			  var = <<"FORM_TYPE">>,
    -			  values = [<<"http://jabber.org/protocol/muc#request">>]},
    -		       #xdata_field{
    -			  var = <<"muc#role">>,
    -			  type = 'text-single',
    -			  values = [<<"participant">>]}]},
    -    send(Config, #message{to = Room, sub_els = [VoiceReq]}),
    -    %% Becoming a participant
    -    ?recv1(#presence{from = MyNickJID,
    -	      sub_els = [#vcard_xupdate{},
    -			 #muc_user{
    -			    items = [#muc_item{role = participant,
    -					       affiliation = none}]}]}),
    -    %% Sending private message to the peer
    -    send(Config, #message{to = PeerNickJID,
    -			  body = [#text{data = Subject}]}),
    -    %% Becoming a member
    -    ?recv1(#presence{from = MyNickJID,
    -	      sub_els = [#vcard_xupdate{},
    -			 #muc_user{
    -			    items = [#muc_item{role = participant,
    -					       affiliation = member}]}]}),
    -    %% Sending groupchat message
    -    send(Config, #message{to = Room, type = groupchat,
    -			  body = [#text{data = Subject}]}),
    -    %% Receive this message back
    -    ?recv1(#message{type = groupchat, from = MyNickJID,
    -	     body = [#text{data = Subject}]}),
    -    %% We're kicked off
    -    %% 307 -> Inform user that he or she has been kicked from the room
    -    ?recv1(#presence{from = MyNickJID, type = unavailable,
    -	      sub_els = [#muc_user{
    -			    status_codes = [307],
    -			    items = [#muc_item{affiliation = member,
    -					       role = none}]}]}),
    -    disconnect(Config).
    -
    -muc_register_nick(Config, MUC, PrevNick, Nick) ->
    -    {Registered, PrevNickVals} = if PrevNick /= <<"">> ->
    -					 {true, [PrevNick]};
    -				    true ->
    -					 {false, []}
    -				 end,
    -    %% Request register form
    -    #iq{type = result,
    -	sub_els = [#register{registered = Registered,
    -			     xdata = #xdata{type = form,
    -					    fields = FsWithoutNick}}]} =
    -	send_recv(Config, #iq{type = get, to = MUC,
    -			      sub_els = [#register{}]}),
    -    %% Check if 'nick' field presents
    -    #xdata_field{type = 'text-single',
    -		 var = <<"nick">>,
    -		 values = PrevNickVals} =
    -	lists:keyfind(<<"nick">>, #xdata_field.var, FsWithoutNick),
    -    X = #xdata{type = submit,
    -	       fields = [#xdata_field{var = <<"nick">>, values = [Nick]}]},
    -    %% Submitting form
    -    #iq{type = result, sub_els = [_|_]} =
    -	send_recv(Config, #iq{type = set, to = MUC,
    -			      sub_els = [#register{xdata = X}]}),
    -    %% Check if the nick was registered
    -    #iq{type = result,
    -	sub_els = [#register{registered = true,
    -			     xdata = #xdata{type = form,
    -					    fields = FsWithNick}}]} =
    -	send_recv(Config, #iq{type = get, to = MUC,
    -			      sub_els = [#register{}]}),
    -    #xdata_field{type = 'text-single', var = <<"nick">>,
    -		 values = [Nick]} =
    -	lists:keyfind(<<"nick">>, #xdata_field.var, FsWithNick).
    -
    -muc_register_master(Config) ->
    -    MUC = muc_jid(Config),
    -    %% Register nick "master1"
    -    muc_register_nick(Config, MUC, <<"">>, <<"master1">>),
    -    %% Unregister nick "master1" via jabber:register
    -    #iq{type = result, sub_els = [_|_]} =
    -	send_recv(Config, #iq{type = set, to = MUC,
    -			      sub_els = [#register{remove = true}]}),
    -    %% Register nick "master2"
    -    muc_register_nick(Config, MUC, <<"">>, <<"master2">>),
    -    %% Now register nick "master"
    -    muc_register_nick(Config, MUC, <<"master2">>, <<"master">>),
    -    disconnect(Config).
    -
    -muc_register_slave(Config) ->
    -    MUC = muc_jid(Config),
    -    %% Trying to register occupied nick "master"
    -    X = #xdata{type = submit,
    -	       fields = [#xdata_field{var = <<"nick">>,
    -				      values = [<<"master">>]}]},
    -    #iq{type = error} =
    -	send_recv(Config, #iq{type = set, to = MUC,
    -			      sub_els = [#register{xdata = X}]}),
    -    disconnect(Config).
    -
    -announce_master(Config) ->
    -    MyJID = my_jid(Config),
    -    ServerJID = server_jid(Config),
    -    MotdJID = jid:replace_resource(ServerJID, <<"announce/motd">>),
    -    MotdText = #text{data = <<"motd">>},
    -    send(Config, #presence{}),
    -    ?recv1(#presence{from = MyJID}),
    -    %% Set message of the day
    -    send(Config, #message{to = MotdJID, body = [MotdText]}),
    -    %% Receive this message back
    -    ?recv1(#message{from = ServerJID, body = [MotdText]}),
    -    disconnect(Config).
    -
    -announce_slave(Config) ->
    -    MyJID = my_jid(Config),
    -    ServerJID = server_jid(Config),
    -    MotdDelJID = jid:replace_resource(ServerJID, <<"announce/motd/delete">>),
    -    MotdText = #text{data = <<"motd">>},
    -    send(Config, #presence{}),
    -    ?recv2(#presence{from = MyJID},
    -	   #message{from = ServerJID, body = [MotdText]}),
    -    %% Delete message of the day
    -    send(Config, #message{to = MotdDelJID}),
    -    disconnect(Config).
    -
    -flex_offline_master(Config) ->
    -    Peer = ?config(slave, Config),
    -    LPeer = jid:remove_resource(Peer),
    -    lists:foreach(
    -      fun(I) ->
    -	      Body = integer_to_binary(I),
    -	      send(Config, #message{to = LPeer,
    -				    body = [#text{data = Body}],
    -				    subject = [#text{data = <<"subject">>}]})
    -      end, lists:seq(1, 5)),
    -    disconnect(Config).
    -
    -flex_offline_slave(Config) ->
    -    MyJID = my_jid(Config),
    -    MyBareJID = jid:remove_resource(MyJID),
    -    Peer = ?config(master, Config),
    -    Peer_s = jid:to_string(Peer),
    -    true = is_feature_advertised(Config, ?NS_FLEX_OFFLINE),
    -    %% Request disco#info
    -    #iq{type = result,
    -	sub_els = [#disco_info{
    -		      node = ?NS_FLEX_OFFLINE,
    -		      identities = Ids,
    -		      features = Fts,
    -		      xdata = [X]}]} =
    -	send_recv(Config, #iq{type = get,
    -			      sub_els = [#disco_info{
    -					    node = ?NS_FLEX_OFFLINE}]}),
    -    %% Check if we have correct identities
    -    true = lists:any(
    -	     fun(#identity{category = <<"automation">>,
    -			   type = <<"message-list">>}) -> true;
    -		(_) -> false
    -	     end, Ids),
    -    %% Check if we have needed feature
    -    true = lists:member(?NS_FLEX_OFFLINE, Fts),
    -    %% Check xdata, the 'number_of_messages' should be 5
    -    #xdata{type = result,
    -	   fields = [#xdata_field{type = hidden,
    -				  var = <<"FORM_TYPE">>},
    -		     #xdata_field{var = <<"number_of_messages">>,
    -				  values = [<<"5">>]}]} = X,
    -    %% Fetch headers,
    -    #iq{type = result,
    -	sub_els = [#disco_items{
    -		      node = ?NS_FLEX_OFFLINE,
    -		      items = DiscoItems}]} =
    -	send_recv(Config, #iq{type = get,
    -			      sub_els = [#disco_items{
    -					    node = ?NS_FLEX_OFFLINE}]}),
    -    %% Check if headers are correct
    -    Nodes = lists:sort(
    -	      lists:map(
    -		fun(#disco_item{jid = J, name = P, node = N})
    -		      when (J == MyBareJID) and (P == Peer_s) ->
    -			N
    -		end, DiscoItems)),
    -    %% Since headers are received we can send initial presence without a risk
    -    %% of getting offline messages flood
    -    send(Config, #presence{}),
    -    ?recv1(#presence{from = MyJID}),
    -    %% Check full fetch
    -    I0 = send(Config, #iq{type = get, sub_els = [#offline{fetch = true}]}),
    -    lists:foreach(
    -      fun({I, N}) ->
    -	      Text = integer_to_binary(I),
    -	      ?recv1(#message{body = Body, sub_els = SubEls}),
    -	      [#text{data = Text}] = Body,
    -	      #offline{items = [#offline_item{node = N}]} =
    -		  lists:keyfind(offline, 1, SubEls),
    -	      #delay{} = lists:keyfind(delay, 1, SubEls)
    -      end, lists:zip(lists:seq(1, 5), Nodes)),
    -    ?recv1(#iq{type = result, id = I0, sub_els = []}),
    -    %% Fetch 2nd and 4th message
    -    I1 = send(Config,
    -	      #iq{type = get,
    -		  sub_els = [#offline{
    -				items = [#offline_item{
    -					    action = view,
    -					    node = lists:nth(2, Nodes)},
    -					 #offline_item{
    -					    action = view,
    -					    node = lists:nth(4, Nodes)}]}]}),
    -    lists:foreach(
    -      fun({I, N}) ->
    -	      Text = integer_to_binary(I),
    -	      ?recv1(#message{body = [#text{data = Text}], sub_els = SubEls}),
    -	      #offline{items = [#offline_item{node = N}]} =
    -		  lists:keyfind(offline, 1, SubEls)
    -      end, lists:zip([2, 4], [lists:nth(2, Nodes), lists:nth(4, Nodes)])),
    -    ?recv1(#iq{type = result, id = I1, sub_els = []}),
    -    %% Delete 2nd and 4th message
    -    #iq{type = result, sub_els = []} =
    -	send_recv(
    -	  Config,
    -	  #iq{type = set,
    -	      sub_els = [#offline{
    -			    items = [#offline_item{
    -					action = remove,
    -					node = lists:nth(2, Nodes)},
    -				     #offline_item{
    -					action = remove,
    -					node = lists:nth(4, Nodes)}]}]}),
    -    %% Check if messages were deleted
    -    #iq{type = result,
    -	sub_els = [#disco_items{
    -		      node = ?NS_FLEX_OFFLINE,
    -		      items = RemainedItems}]} =
    -	send_recv(Config, #iq{type = get,
    -			      sub_els = [#disco_items{
    -					    node = ?NS_FLEX_OFFLINE}]}),
    -    RemainedNodes = [lists:nth(1, Nodes),
    -		     lists:nth(3, Nodes),
    -		     lists:nth(5, Nodes)],
    -    RemainedNodes = lists:sort(
    -		      lists:map(
    -			fun(#disco_item{node = N}) -> N end,
    -			RemainedItems)),
    -    %% Purge everything left
    -    #iq{type = result, sub_els = []} =
    -	send_recv(Config, #iq{type = set, sub_els = [#offline{purge = true}]}),
    -    %% Check if there is no offline messages
    -    #iq{type = result,
    -	sub_els = [#disco_items{node = ?NS_FLEX_OFFLINE, items = []}]} =
    -	send_recv(Config, #iq{type = get,
    -			      sub_els = [#disco_items{
    -					    node = ?NS_FLEX_OFFLINE}]}),
    -    disconnect(Config).
    -
    -offline_master(Config) ->
    -    Peer = ?config(slave, Config),
    -    LPeer = jid:remove_resource(Peer),
    -    send(Config, #message{to = LPeer,
    -                          body = [#text{data = <<"body">>}],
    -                          subject = [#text{data = <<"subject">>}]}),
    -    disconnect(Config).
    -
    -offline_slave(Config) ->
    -    Peer = ?config(master, Config),
    -    send(Config, #presence{}),
    -    {_, #message{sub_els = SubEls}} =
    -        ?recv2(#presence{},
    -               #message{from = Peer,
    -                        body = [#text{data = <<"body">>}],
    -                        subject = [#text{data = <<"subject">>}]}),
    -    true = lists:keymember(delay, 1, SubEls),
    -    disconnect(Config).
    -
    -carbons_master(Config) ->
    -    MyJID = my_jid(Config),
    -    MyBareJID = jid:remove_resource(MyJID),
    -    Peer = ?config(slave, Config),
    -    Txt = #text{data = <<"body">>},
    -    true = is_feature_advertised(Config, ?NS_CARBONS_2),
    -    send(Config, #presence{priority = 10}),
    -    ?recv1(#presence{from = MyJID}),
    -    wait_for_slave(Config),
    -    ?recv1(#presence{from = Peer}),
    -    %% Enable carbons
    -    #iq{type = result, sub_els = []} =
    -	send_recv(Config,
    -		  #iq{type = set,
    -		      sub_els = [#carbons_enable{}]}),
    -    %% Send a message to bare and full JID
    -    send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}),
    -    send(Config, #message{to = MyJID, type = chat, body = [Txt]}),
    -    send(Config, #message{to = MyBareJID, type = chat, body = [Txt],
    -			  sub_els = [#carbons_private{}]}),
    -    send(Config, #message{to = MyJID, type = chat, body = [Txt],
    -			  sub_els = [#carbons_private{}]}),
    -    %% Receive the messages back
    -    ?recv4(#message{from = MyJID, to = MyBareJID, type = chat,
    -		    body = [Txt], sub_els = []},
    -	   #message{from = MyJID, to = MyJID, type = chat,
    -		    body = [Txt], sub_els = []},
    -	   #message{from = MyJID, to = MyBareJID, type = chat,
    -		    body = [Txt], sub_els = [#carbons_private{}]},
    -	   #message{from = MyJID, to = MyJID, type = chat,
    -		    body = [Txt], sub_els = [#carbons_private{}]}),
    -    %% Disable carbons
    -    #iq{type = result, sub_els = []} =
    -	send_recv(Config,
    -		  #iq{type = set,
    -		      sub_els = [#carbons_disable{}]}),
    -    wait_for_slave(Config),
    -    %% Repeat the same and leave
    -    send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}),
    -    send(Config, #message{to = MyJID, type = chat, body = [Txt]}),
    -    send(Config, #message{to = MyBareJID, type = chat, body = [Txt],
    -			  sub_els = [#carbons_private{}]}),
    -    send(Config, #message{to = MyJID, type = chat, body = [Txt],
    -			  sub_els = [#carbons_private{}]}),
    -    ?recv4(#message{from = MyJID, to = MyBareJID, type = chat,
    -		    body = [Txt], sub_els = []},
    -	   #message{from = MyJID, to = MyJID, type = chat,
    -		    body = [Txt], sub_els = []},
    -	   #message{from = MyJID, to = MyBareJID, type = chat,
    -		    body = [Txt], sub_els = [#carbons_private{}]},
    -	   #message{from = MyJID, to = MyJID, type = chat,
    -		    body = [Txt], sub_els = [#carbons_private{}]}),
    -    disconnect(Config).
    -
    -carbons_slave(Config) ->
    -    MyJID = my_jid(Config),
    -    MyBareJID = jid:remove_resource(MyJID),
    -    Peer = ?config(master, Config),
    -    Txt = #text{data = <<"body">>},
    -    wait_for_master(Config),
    -    send(Config, #presence{priority = 5}),
    -    ?recv2(#presence{from = MyJID}, #presence{from = Peer}),
    -    %% Enable carbons
    -    #iq{type = result, sub_els = []} =
    -	send_recv(Config,
    -		  #iq{type = set,
    -		      sub_els = [#carbons_enable{}]}),
    -    %% Receive messages sent by the peer
    -    ?recv4(
    -       #message{from = MyBareJID, to = MyJID, type = chat,
    -		sub_els =
    -		    [#carbons_sent{
    -			forwarded = #forwarded{
    -				       sub_els =
    -					   [#message{from = Peer,
    -						     to = MyBareJID,
    -						     type = chat,
    -						     body = [Txt]}]}}]},
    -       #message{from = MyBareJID, to = MyJID, type = chat,
    -		sub_els =
    -		    [#carbons_sent{
    -			forwarded = #forwarded{
    -				       sub_els =
    -					   [#message{from = Peer,
    -						     to = Peer,
    -						     type = chat,
    -						     body = [Txt]}]}}]},
    -       #message{from = MyBareJID, to = MyJID, type = chat,
    -		sub_els =
    -		    [#carbons_received{
    -			forwarded = #forwarded{
    -				       sub_els =
    -					   [#message{from = Peer,
    -						     to = MyBareJID,
    -						     type = chat,
    -						     body = [Txt]}]}}]},
    -       #message{from = MyBareJID, to = MyJID, type = chat,
    -		sub_els =
    -		    [#carbons_received{
    -			forwarded = #forwarded{
    -				       sub_els =
    -					   [#message{from = Peer,
    -						     to = Peer,
    -						     type = chat,
    -						     body = [Txt]}]}}]}),
    -    %% Disable carbons
    -    #iq{type = result, sub_els = []} =
    -	send_recv(Config,
    -		  #iq{type = set,
    -		      sub_els = [#carbons_disable{}]}),
    -    wait_for_master(Config),
    -    %% Now we should receive nothing but presence unavailable from the peer
    -    ?recv1(#presence{from = Peer, type = unavailable}),
    -    disconnect(Config).
    -
    -mam_old_master(Config) ->
    -    mam_master(Config, ?NS_MAM_TMP).
    -
    -mam_new_master(Config) ->
    -    mam_master(Config, ?NS_MAM_0).
    -
    -mam_master(Config, NS) ->
    -    true = is_feature_advertised(Config, NS),
    -    MyJID = my_jid(Config),
    -    BareMyJID = jid:remove_resource(MyJID),
    -    Peer = ?config(slave, Config),
    -    send(Config, #presence{}),
    -    ?recv1(#presence{}),
    -    wait_for_slave(Config),
    -    ?recv1(#presence{from = Peer}),
    -    #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = roster}]} =
    -        send_recv(Config,
    -                  #iq{type = set,
    -                      sub_els = [#mam_prefs{xmlns = NS,
    -					    default = roster,
    -                                            never = [MyJID]}]}),
    -    if NS == ?NS_MAM_TMP ->
    -	    FakeArchived = #mam_archived{id = randoms:get_string(),
    -					 by = server_jid(Config)},
    -	    send(Config, #message{to = MyJID,
    -				  sub_els = [FakeArchived],
    -				  body = [#text{data = <<"a">>}]}),
    -	    send(Config, #message{to = BareMyJID,
    -				  sub_els = [FakeArchived],
    -				  body = [#text{data = <<"b">>}]}),
    -	    %% NOTE: The server should strip fake archived tags,
    -	    %% i.e. the sub_els received should be [].
    -	    ?recv2(#message{body = [#text{data = <<"a">>}], sub_els = []},
    -		   #message{body = [#text{data = <<"b">>}], sub_els = []});
    -       true ->
    -	    ok
    -    end,
    -    wait_for_slave(Config),
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -              send(Config,
    -                   #message{to = Peer, body = [Text]})
    -      end, lists:seq(1, 5)),
    -    ?recv1(#presence{type = unavailable, from = Peer}),
    -    mam_query_all(Config, NS),
    -    mam_query_with(Config, Peer, NS),
    -    %% mam_query_with(Config, jid:remove_resource(Peer)),
    -    mam_query_rsm(Config, NS),
    -    #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = never}]} =
    -        send_recv(Config, #iq{type = set,
    -                              sub_els = [#mam_prefs{xmlns = NS,
    -						    default = never}]}),
    -    disconnect(Config).
    -
    -mam_old_slave(Config) ->
    -    mam_slave(Config, ?NS_MAM_TMP).
    -
    -mam_new_slave(Config) ->
    -    mam_slave(Config, ?NS_MAM_0).
    -
    -mam_slave(Config, NS) ->
    -    Peer = ?config(master, Config),
    -    ServerJID = server_jid(Config),
    -    wait_for_master(Config),
    -    send(Config, #presence{}),
    -    ?recv2(#presence{}, #presence{from = Peer}),
    -    #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = always}]} =
    -        send_recv(Config,
    -                  #iq{type = set,
    -                      sub_els = [#mam_prefs{xmlns = NS, default = always}]}),
    -    wait_for_master(Config),
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -	      ?recv1(#message{from = Peer, body = [Text],
    -			      sub_els = [#mam_archived{by = ServerJID}]})
    -      end, lists:seq(1, 5)),
    -    #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = never}]} =
    -        send_recv(Config, #iq{type = set,
    -                              sub_els = [#mam_prefs{xmlns = NS, default = never}]}),
    -    disconnect(Config).
    -
    -mam_query_all(Config, NS) ->
    -    QID = randoms:get_string(),
    -    MyJID = my_jid(Config),
    -    Peer = ?config(slave, Config),
    -    Type = case NS of
    -	       ?NS_MAM_TMP -> get;
    -	       _ -> set
    -	   end,
    -    I = send(Config, #iq{type = Type, sub_els = [#mam_query{xmlns = NS, id = QID}]}),
    -    maybe_recv_iq_result(NS, I),
    -    Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5);
    -	      true -> lists:seq(1, 5) ++ lists:seq(1, 5)
    -	   end,
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -              ?recv1(#message{to = MyJID,
    -                       sub_els =
    -                           [#mam_result{
    -                               queryid = QID,
    -                               sub_els =
    -                                   [#forwarded{
    -                                       delay = #delay{},
    -                                       sub_els =
    -                                           [#message{
    -                                               from = MyJID, to = Peer,
    -                                               body = [Text]}]}]}]})
    -      end, Iter),
    -    if NS == ?NS_MAM_TMP ->
    -	    ?recv1(#iq{type = result, id = I,
    -		       sub_els = [#mam_query{xmlns = NS, id = QID}]});
    -       true ->
    -	    ?recv1(#message{sub_els = [#mam_fin{complete = true, id = QID}]})
    -    end.
    -
    -mam_query_with(Config, JID, NS) ->
    -    MyJID = my_jid(Config),
    -    Peer = ?config(slave, Config),
    -    {Query, Type} = if NS == ?NS_MAM_TMP ->
    -		    {#mam_query{xmlns = NS, with = JID}, get};
    -	       true ->
    -		    Fs = [#xdata_field{var = <<"jid">>,
    -				       values = [jid:to_string(JID)]}],
    -		    {#mam_query{xmlns = NS,
    -			       xdata = #xdata{type = submit, fields = Fs}}, set}
    -	    end,
    -    I = send(Config, #iq{type = Type, sub_els = [Query]}),
    -    Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5);
    -	      true -> lists:seq(1, 5) ++ lists:seq(1, 5)
    -	   end,
    -    maybe_recv_iq_result(NS, I),
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -              ?recv1(#message{to = MyJID,
    -                       sub_els =
    -                           [#mam_result{
    -                               sub_els =
    -                                   [#forwarded{
    -                                       delay = #delay{},
    -                                       sub_els =
    -                                           [#message{
    -                                               from = MyJID, to = Peer,
    -                                               body = [Text]}]}]}]})
    -      end, Iter),
    -    if NS == ?NS_MAM_TMP ->
    -	    ?recv1(#iq{type = result, id = I,
    -		       sub_els = [#mam_query{xmlns = NS}]});
    -       true ->
    -	    ?recv1(#message{sub_els = [#mam_fin{complete = true}]})
    -    end.
    -
    -maybe_recv_iq_result(?NS_MAM_0, I1) ->
    -    ?recv1(#iq{type = result, id = I1});
    -maybe_recv_iq_result(_, _) ->
    -    ok.
    -
    -mam_query_rsm(Config, NS) ->
    -    MyJID = my_jid(Config),
    -    Peer = ?config(slave, Config),
    -    Type = case NS of
    -	       ?NS_MAM_TMP -> get;
    -	       _ -> set
    -	   end,
    -    %% Get the first 3 items out of 5
    -    I1 = send(Config,
    -              #iq{type = Type,
    -                  sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 3}}]}),
    -    maybe_recv_iq_result(NS, I1),
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -              ?recv1(#message{to = MyJID,
    -                       sub_els =
    -                           [#mam_result{
    -			       xmlns = NS,
    -                               sub_els =
    -                                   [#forwarded{
    -                                       delay = #delay{},
    -                                       sub_els =
    -                                           [#message{
    -                                               from = MyJID, to = Peer,
    -                                               body = [Text]}]}]}]})
    -      end, lists:seq(1, 3)),
    -    if NS == ?NS_MAM_TMP ->
    -	    ?recv1(#iq{type = result, id = I1,
    -		       sub_els = [#mam_query{xmlns = NS,
    -					     rsm = #rsm_set{last = Last, count = 5}}]});
    -       true ->
    -	    ?recv1(#message{sub_els = [#mam_fin{
    -					  complete = false,
    -					  rsm = #rsm_set{last = Last, count = 10}}]})
    -    end,
    -    %% Get the next items starting from the `Last`.
    -    %% Limit the response to 2 items.
    -    I2 = send(Config,
    -              #iq{type = Type,
    -                  sub_els = [#mam_query{xmlns = NS,
    -					rsm = #rsm_set{max = 2,
    -                                                       'after' = Last}}]}),
    -    maybe_recv_iq_result(NS, I2),
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -              ?recv1(#message{to = MyJID,
    -                       sub_els =
    -                           [#mam_result{
    -			       xmlns = NS,
    -                               sub_els =
    -                                   [#forwarded{
    -                                       delay = #delay{},
    -                                       sub_els =
    -                                           [#message{
    -                                               from = MyJID, to = Peer,
    -                                               body = [Text]}]}]}]})
    -      end, lists:seq(4, 5)),
    -    if NS == ?NS_MAM_TMP ->
    -	    ?recv1(#iq{type = result, id = I2,
    -		       sub_els = [#mam_query{
    -				     xmlns = NS,
    -				     rsm = #rsm_set{
    -					      count = 5,
    -					      first = #rsm_first{data = First}}}]});
    -       true ->
    -	    ?recv1(#message{
    -		      sub_els = [#mam_fin{
    -				    complete = false,
    -				    rsm = #rsm_set{
    -					      count = 10,
    -					      first = #rsm_first{data = First}}}]})
    -    end,
    -    %% Paging back. Should receive 3 elements: 1, 2, 3.
    -    I3 = send(Config,
    -              #iq{type = Type,
    -                  sub_els = [#mam_query{xmlns = NS,
    -					rsm = #rsm_set{max = 3,
    -                                                       before = First}}]}),
    -    maybe_recv_iq_result(NS, I3),
    -    lists:foreach(
    -      fun(N) ->
    -              Text = #text{data = integer_to_binary(N)},
    -              ?recv1(#message{to = MyJID,
    -                       sub_els =
    -                           [#mam_result{
    -			       xmlns = NS,
    -                               sub_els =
    -                                   [#forwarded{
    -                                       delay = #delay{},
    -                                       sub_els =
    -                                           [#message{
    -                                               from = MyJID, to = Peer,
    -                                               body = [Text]}]}]}]})
    -      end, lists:seq(1, 3)),
    -    if NS == ?NS_MAM_TMP ->
    -	    ?recv1(#iq{type = result, id = I3,
    -		       sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]});
    -       true ->
    -	    ?recv1(#message{
    -		      sub_els = [#mam_fin{complete = true,
    -					  rsm = #rsm_set{count = 10}}]})
    -    end,
    -    %% Getting the item count. Should be 5 (or 10).
    -    I4 = send(Config,
    -	      #iq{type = Type,
    -		  sub_els = [#mam_query{xmlns = NS,
    -					rsm = #rsm_set{max = 0}}]}),
    -    maybe_recv_iq_result(NS, I4),
    -    if NS == ?NS_MAM_TMP ->
    -	    ?recv1(#iq{type = result, id = I4,
    -		       sub_els = [#mam_query{
    -				     xmlns = NS,
    -				     rsm = #rsm_set{count = 5,
    -						    first = undefined,
    -						    last = undefined}}]});
    -       true ->
    -	    ?recv1(#message{
    -		      sub_els = [#mam_fin{
    -				    complete = false,
    -				    rsm = #rsm_set{count = 10,
    -						   first = undefined,
    -						   last = undefined}}]})
    -    end,
    -    %% Should receive 2 last messages
    -    I5 = send(Config,
    -	      #iq{type = Type,
    -		  sub_els = [#mam_query{xmlns = NS,
    -					rsm = #rsm_set{max = 2,
    -						       before = none}}]}),
    -    maybe_recv_iq_result(NS, I5),
    -    lists:foreach(
    -      fun(N) ->
    -	      Text = #text{data = integer_to_binary(N)},
    -	      ?recv1(#message{to = MyJID,
    -			      sub_els =
    -				  [#mam_result{
    -				      xmlns = NS,
    -				      sub_els =
    -					  [#forwarded{
    -					      delay = #delay{},
    -					      sub_els =
    -						  [#message{
    -						      from = MyJID, to = Peer,
    -						      body = [Text]}]}]}]})
    -      end, lists:seq(4, 5)),
    -    if NS == ?NS_MAM_TMP ->
    -	    ?recv1(#iq{type = result, id = I5,
    -		       sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]});
    -       true ->
    -	    ?recv1(#message{
    -		      sub_els = [#mam_fin{complete = false,
    -					  rsm = #rsm_set{count = 10}}]})
    -    end.
    -
    -client_state_master(Config) ->
    -    true = ?config(csi, Config),
    -    Peer = ?config(slave, Config),
    -    Presence = #presence{to = Peer},
    -    ChatState = #message{to = Peer, thread = <<"1">>,
    -			 sub_els = [#chatstate{type = active}]},
    -    Message = ChatState#message{body = [#text{data = <<"body">>}]},
    -    PepPayload = xmpp_codec:encode(#presence{}),
    -    PepOne = #message{
    -		to = Peer,
    -		sub_els =
    -		    [#pubsub_event{
    -			items =
    -			    [#pubsub_event_items{
    -				node = <<"foo-1">>,
    -				items =
    -				    [#pubsub_event_item{
    -					id = <<"pep-1">>,
    -					xml_els = [PepPayload]}]}]}]},
    -    PepTwo = #message{
    -		to = Peer,
    -		sub_els =
    -		    [#pubsub_event{
    -			items =
    -			    [#pubsub_event_items{
    -				node = <<"foo-2">>,
    -				items =
    -				    [#pubsub_event_item{
    -					id = <<"pep-2">>,
    -					xml_els = [PepPayload]}]}]}]},
    -    %% Wait for the slave to become inactive.
    -    wait_for_slave(Config),
    -    %% Should be queued (but see below):
    -    send(Config, Presence),
    -    %% Should replace the previous presence in the queue:
    -    send(Config, Presence#presence{type = unavailable}),
    -    %% The following two PEP stanzas should be queued (but see below):
    -    send(Config, PepOne),
    -    send(Config, PepTwo),
    -    %% The following two PEP stanzas should replace the previous two:
    -    send(Config, PepOne),
    -    send(Config, PepTwo),
    -    %% Should be queued (but see below):
    -    send(Config, ChatState),
    -    %% Should replace the previous chat state in the queue:
    -    send(Config, ChatState#message{sub_els = [#chatstate{type = composing}]}),
    -    %% Should be sent immediately, together with the queued stanzas:
    -    send(Config, Message),
    -    %% Wait for the slave to become active.
    -    wait_for_slave(Config),
    -    %% Should be delivered, as the client is active again:
    -    send(Config, ChatState),
    -    disconnect(Config).
    -
    -client_state_slave(Config) ->
    -    Peer = ?config(master, Config),
    -    change_client_state(Config, inactive),
    -    wait_for_master(Config),
    -    ?recv1(#presence{from = Peer, type = unavailable,
    -		     sub_els = [#delay{}]}),
    -    #message{
    -       from = Peer,
    -       sub_els =
    -	   [#pubsub_event{
    -	       items =
    -		   [#pubsub_event_items{
    -		       node = <<"foo-1">>,
    -		       items =
    -			   [#pubsub_event_item{
    -			       id = <<"pep-1">>}]}]},
    -	    #delay{}]} = recv(),
    -    #message{
    -       from = Peer,
    -       sub_els =
    -	   [#pubsub_event{
    -	       items =
    -		   [#pubsub_event_items{
    -		       node = <<"foo-2">>,
    -		       items =
    -			   [#pubsub_event_item{
    -			       id = <<"pep-2">>}]}]},
    -	    #delay{}]} = recv(),
    -    ?recv1(#message{from = Peer, thread = <<"1">>,
    -		    sub_els = [#chatstate{type = composing},
    -			       #delay{}]}),
    -    ?recv1(#message{from = Peer, thread = <<"1">>,
    -		    body = [#text{data = <<"body">>}],
    -		    sub_els = [#chatstate{type = active}]}),
    -    change_client_state(Config, active),
    -    wait_for_master(Config),
    -    ?recv1(#message{from = Peer, thread = <<"1">>,
    -		    sub_els = [#chatstate{type = active}]}),
    -    disconnect(Config).
    -
     %%%===================================================================
     %%% Aux functions
     %%%===================================================================
    -change_client_state(Config, NewState) ->
    -    send(Config, #csi{type = NewState}),
    -    send_recv(Config, #iq{type = get, to = server_jid(Config),
    -			  sub_els = [#ping{}]}).
    -
     bookmark_conference() ->
         #bookmark_conference{name = <<"Some name">>,
                              autojoin = true,
    @@ -2377,28 +979,22 @@ bookmark_conference() ->
                                      <<"some.conference.org">>,
                                      <<>>)}.
     
    -socks5_connect(#streamhost{host = Host, port = Port},
    -               {SID, JID1, JID2}) ->
    -    Hash = p1_sha:sha([SID, jid:to_string(JID1), jid:to_string(JID2)]),
    -    {ok, Sock} = gen_tcp:connect(binary_to_list(Host), Port,
    -                                 [binary, {active, false}]),
    -    Init = <>,
    -    InitAck = <>,
    -    Req = <>,
    -    Resp = <>,
    -    gen_tcp:send(Sock, Init),
    -    {ok, InitAck} = gen_tcp:recv(Sock, size(InitAck)),
    -    gen_tcp:send(Sock, Req),
    -    {ok, Resp} = gen_tcp:recv(Sock, size(Resp)),
    -    Sock.
    -
    -socks5_send(Sock, Data) ->
    -    ok = gen_tcp:send(Sock, Data).
    -
    -socks5_recv(Sock, Data) ->
    -    {ok, Data} = gen_tcp:recv(Sock, size(Data)).
    +'$handle_undefined_function'(F, [Config]) when is_list(Config) ->
    +    case re:split(atom_to_list(F), "_", [{return, list}, {parts, 2}]) of
    +	[M, T] ->
    +	    Module = list_to_atom(M ++ "_tests"),
    +	    Function = list_to_atom(T),
    +	    case erlang:function_exported(Module, Function, 1) of
    +		true ->
    +		    Module:Function(Config);
    +		false ->
    +		    erlang:error({undef, F})
    +	    end;
    +	_ ->
    +	    erlang:error({undef, F})
    +    end;
    +'$handle_undefined_function'(_, _) ->
    +    erlang:error(undef).
     
     %%%===================================================================
     %%% SQL stuff
    @@ -2480,12 +1076,12 @@ split(Data) ->
     clear_riak_tables(Config) ->
         User = ?config(user, Config),
         Server = ?config(server, Config),
    -    Room = muc_room_jid(Config),
    -    {URoom, SRoom, _} = jid:tolower(Room),
    +    Master = <<"test_master!#$%^*()`~+-;_=[]{}|\\">>,
    +    Slave = <<"test_slave!#$%^*()`~+-;_=[]{}|\\">>,
         ejabberd_auth:remove_user(User, Server),
    -    ejabberd_auth:remove_user(<<"test_slave">>, Server),
    -    ejabberd_auth:remove_user(<<"test_master">>, Server),
    -    mod_muc:forget_room(Server, URoom, SRoom),
    -    ejabberd_riak:delete(muc_registered, {{<<"test_slave">>, Server}, SRoom}),
    -    ejabberd_riak:delete(muc_registered, {{<<"test_master">>, Server}, SRoom}),
    +    ejabberd_auth:remove_user(Master, Server),
    +    ejabberd_auth:remove_user(Slave, Server),
    +    ejabberd_riak:delete(muc_room),
    +    ejabberd_riak:delete(muc_registered),
    +    timer:sleep(timer:seconds(5)),
         Config.
    diff --git a/test/ejabberd_SUITE_data/ca.key b/test/ejabberd_SUITE_data/ca.key
    new file mode 100644
    index 000000000..858100686
    --- /dev/null
    +++ b/test/ejabberd_SUITE_data/ca.key
    @@ -0,0 +1,27 @@
    +-----BEGIN RSA PRIVATE KEY-----
    +MIIEpAIBAAKCAQEAxGSSFSDTbBTk2GwkORLCXoBdYq5YxwPfen8bK+8WjxRb9Thp
    +FsHYfImtDQV0qvcZyWnjUFxRh7Dyw7A2X690nplCdzZ9Gl+5yzzlRefHborMSnNY
    +rnTqx3vs9qiac0A5bzdjMY7XN3VuVwz0XWY6rAiL/7OxunCNUnQz+oswDx7cj1W4
    +bb9pFzBvW5TjaAiziyzS3IxvTc7kYQYJEa99vIlDZ+Ov9rHtiF/5CZ8kHc457B3s
    +uc9hHxO2t0EzmBiqg7wpksJjoJeXaJvT9sKSgW6LXkjBCm/7jm1ElPq+7FCph0qp
    +uIsxMtu15exLKQaSRLcc+tyNkWIZGQ371D2+7wIDAQABAoIBACzcNCozV1fm5ecx
    +vIx05oUjmTFDVfAPyGp4wkIk2OhR5Dd9bTPPj53S7P5+coni67cAQvZGQDFYj/t3
    +MtRkhaT8qRwGDEmL+CqefFidewabGdMfye//sOlkO1qUZMNStkvbQQM+95Ypcszb
    +nq3+/gPx59i+uSg3MXDWLlFand217d8oU4JxmCxHc9ezhkpWsdReiAukWTud+q/5
    +DzyPetaP09z8Ua/YNXuI6IdsvObYxOSCI1hPPuMSQGM4hQiqkHPqPNBIJDwfM9wk
    +WzGom5M7nGitrKynJHdS2VRzsZwFL3Hg0yBXnSY1o8er5A6i5//dS2ISSEN9xHjz
    +9PRRCbECgYEA+yVmv8i5uBLuz/Oeu/iOcX9ZNHfNowuIpInWVyfVhURIc1OwP1Sy
    +uj5Qst2IY+Hm4IVq0sNg3cZdEk+K6RMyc/Qgd7GoYeJNKH1v0RbA6E1zEzqm8Xv+
    +jA3dd7RLb5NTwFv11Qh0BDZfw2e8pCmN4oDp+n8fo7RE3NQGaLb77QsCgYEAyDBE
    +FoYVwXhGaKnhDT1AqM3hkOGBqheJJIxkNUnyMhlU/AxmWtfvTtw7MCP+311bz4Ma
    +h6yUfaEiHQJs2wkPyIaZ8CbbVyP7bXWMZzA/Rnk4dQWZ/VjRYvEzIvmz9di3w5j6
    +P1fWX0QODqcY2CvHyMmPLIysbC0cjVDA4ZpDvC0CgYEAlqvrpuevtCV3rL7F3pPS
    +MXlrdTTi5AyJX91qAEPfr+I1bSsqM/SGfYHhPE34A6SFtPGWEvgwZx0YvWGHPynL
    +PRGbYPPuxzrTe5U1vkVeWoAMp96qRXpUToYK9kPudfP3bRI+vB4kLFrKvRrBa+Oa
    +QeeBeE1IGBiQr8NsTOpq3d0CgYB9R+d0iRlYaKL3oUjcdjbe7Wl6uAXjorMLEmks
    +CEjwHXZX/pKXy4dSPPU1nXFF7DEm3o9d1R1gudSVfw0MztD313TDHC4sjLIuwF/L
    +vB/9RKOWaJkEOe9gEj7EZqy+8I+gcz45IglguUBq3xvnPQ7ck3dsk+TcFidGMQFk
    +rpwxSQKBgQDbdzOJagPep0HVJPkOmF1X4idb1rnQUuMi59I3k6lFTXAaypy6nU69
    +aAUgv7UY4i3XglEhbztk/o51W4/fJ1N8UzbXlBur/pJD8GN2h52ea77CbpOAmDSm
    +Bjjoj92wmYGfBRf7DwJQDgqxvpa0s1cwtYjNf0RmbDPzBsfzrKLKbQ==
    +-----END RSA PRIVATE KEY-----
    diff --git a/test/ejabberd_SUITE_data/ca.pem b/test/ejabberd_SUITE_data/ca.pem
    new file mode 100644
    index 000000000..3daa7f5d6
    --- /dev/null
    +++ b/test/ejabberd_SUITE_data/ca.pem
    @@ -0,0 +1,22 @@
    +-----BEGIN CERTIFICATE-----
    +MIIDtTCCAp2gAwIBAgIJAKI8WTrCnPXzMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
    +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
    +aWRnaXRzIFB0eSBMdGQwHhcNMTUwNDE1MTQxNTI0WhcNNDIwODMxMTQxNTI0WjBF
    +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
    +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
    +CgKCAQEAxGSSFSDTbBTk2GwkORLCXoBdYq5YxwPfen8bK+8WjxRb9ThpFsHYfImt
    +DQV0qvcZyWnjUFxRh7Dyw7A2X690nplCdzZ9Gl+5yzzlRefHborMSnNYrnTqx3vs
    +9qiac0A5bzdjMY7XN3VuVwz0XWY6rAiL/7OxunCNUnQz+oswDx7cj1W4bb9pFzBv
    +W5TjaAiziyzS3IxvTc7kYQYJEa99vIlDZ+Ov9rHtiF/5CZ8kHc457B3suc9hHxO2
    +t0EzmBiqg7wpksJjoJeXaJvT9sKSgW6LXkjBCm/7jm1ElPq+7FCph0qpuIsxMtu1
    +5exLKQaSRLcc+tyNkWIZGQ371D2+7wIDAQABo4GnMIGkMB0GA1UdDgQWBBTQ9mbL
    +xyIyE3pDyrNMsC36DRHp+TB1BgNVHSMEbjBsgBTQ9mbLxyIyE3pDyrNMsC36DRHp
    ++aFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
    +BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKI8WTrCnPXzMAwGA1UdEwQF
    +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGyAi//UQaUhy8RLGc33T36Ni6TnRgpz
    +1xu2aahMe0YfPUZsZwwCP6dK+6fSw7OsRqyXZNZJntlur30yMMDlvjXmV6UDzeS4
    +/HGd/hr0LqruYpmvOKmvT/y8VkmBqsGlcaRNhSJGDzMHAVEQ0hzAJe3Emw5R753p
    +iVRbxPqiOVt4U/gjwtrVumSt1v9O4buWo1lTp0jxK1L6K8YWmETLuxyS3IG+i9Ij
    +DDNyU/UxyocP/mcscUAoV9MJX56exwPC93rPxOlwJT5e5ZMRGnwwUt017dPUrKbA
    +u+24S8uJCKN2w0OzsrqzC6lvxOf0JRfNxxxGr1KZYyEGT7ps1jhTebA=
    +-----END CERTIFICATE-----
    diff --git a/test/ejabberd_SUITE_data/cert.pem b/test/ejabberd_SUITE_data/cert.pem
    index 11e18491f..ee9cf1641 100644
    --- a/test/ejabberd_SUITE_data/cert.pem
    +++ b/test/ejabberd_SUITE_data/cert.pem
    @@ -1,52 +1,54 @@
     -----BEGIN CERTIFICATE-----
    -MIIGbDCCBVSgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTET
    +MIIEmTCCA4GgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTET
     MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
    -dHkgTHRkMB4XDTE2MDUyNDE3NDIyNVoXDTQzMTAxMDE3NDIyNVowVjELMAkGA1UE
    +dHkgTHRkMB4XDTE2MDkyMzA3MDMyNFoXDTQ0MDIwOTA3MDMyNFowVjELMAkGA1UE
     BhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdp
    -ZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAxMGYWN0aXZlMIGfMA0GCSqGSIb3DQEBAQUA
    -A4GNADCBiQKBgQC+GTA1D1+yiXgLqUhJXkSj3hj5FiqlBAfJT/8OSXYifY4M4HYv
    -VQrqER2Fs7jdCaeoGWDvwfK/UOV0b1ROnf+T/2bXFs8EOeqjOz4xG2oexNKVrYj9
    -ICYAgmSh6Hf2cZJM/YCAISje93Xl2J2w/N7oFC1ZXasPoBIZv3Fgg7hTtQIDAQAB
    -o4ID2DCCA9QwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5l
    -cmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFEynWiCoZK4tLDk3KM1wMsbrz9Ug
    -MB8GA1UdIwQYMBaAFND2ZsvHIjITekPKs0ywLfoNEen5MDMGA1UdHwQsMCowKKAm
    -oCSGImh0dHA6Ly9sb2NhbGhvc3Q6NTI4MC9kYXRhL2NybC5kZXIwNgYIKwYBBQUH
    -AQEEKjAoMCYGCCsGAQUFBzABhhpodHRwOi8vbG9jYWxob3N0OjUyODAvb2NzcDAL
    -BgNVHQ8EBAMCBeAwEwYDVR0lBAwwCgYIKwYBBQUHAwkwggLIBgNVHREEggK/MIIC
    -u6A4BggrBgEFBQcIBaAsDCp0ZXN0X3NpbmdsZSEjJCVeKigpYH4rLTtfPVtde318
    -XEBsb2NhbGhvc3SgPwYIKwYBBQUHCAWgMwwxdGVzdF9zaW5nbGUhIyQlXiooKWB+
    -Ky07Xz1bXXt9fFxAbW5lc2lhLmxvY2FsaG9zdKA+BggrBgEFBQcIBaAyDDB0ZXN0
    -X3NpbmdsZSEjJCVeKigpYH4rLTtfPVtde318XEBteXNxbC5sb2NhbGhvc3SgPgYI
    -KwYBBQUHCAWgMgwwdGVzdF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAcGdz
    -cWwubG9jYWxob3N0oD8GCCsGAQUFBwgFoDMMMXRlc3Rfc2luZ2xlISMkJV4qKClg
    -fistO189W117fXxcQHNxbGl0ZS5sb2NhbGhvc3SgQAYIKwYBBQUHCAWgNAwydGVz
    -dF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAZXh0YXV0aC5sb2NhbGhvc3Sg
    -PQYIKwYBBQUHCAWgMQwvdGVzdF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxA
    -bGRhcC5sb2NhbGhvc3SgPQYIKwYBBQUHCAWgMQwvdGVzdF9zaW5nbGUhIyQlXioo
    -KWB+Ky07Xz1bXXt9fFxAcDFkYi5sb2NhbGhvc3SgPQYIKwYBBQUHCAWgMQwvdGVz
    -dF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAcmlhay5sb2NhbGhvc3SgPgYI
    -KwYBBQUHCAWgMgwwdGVzdF9zaW5nbGUhIyQlXiooKWB+Ky07Xz1bXXt9fFxAcmVk
    -aXMubG9jYWxob3N0oD4GCCsGAQUFBwgFoDIMMHRlc3Rfc2luZ2xlISMkJV4qKClg
    -fistO189W117fXxcQG1zc3FsLmxvY2FsaG9zdDANBgkqhkiG9w0BAQUFAAOCAQEA
    -et4jpmpwlE+2bw+/iqCt7sfU/5nPmQ8YtgMB+32wf7DINNJgkwOdkYJpzhlMXKrh
    -/bn8+Ybmq6MbK0r2R91Uu858xQf8VKExQm44qaGSyL5Ug3jsAWb3GLZSaWQo37e9
    -QdDeP8XijCEyr3rum19tRIdiImsRAxJqwfaE4pUSgfCEQMkvb+6//8HSf9RRPToD
    -o6eAg8QerEtTfxerEdW/0K1ozOrzSrQembWOu+JjvANRl+p59j+1YOWHzS/yQeZl
    -K3sjFoCvXPvocRnUznvT+TSdy3ORJSjwfEcP5Crim70amZZ6NeMAxfby9wwmmX0x
    -zkwPCSUXliXke6T88Olj7Q==
    +ZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAxMGYWN0aXZlMIIBIjANBgkqhkiG9w0BAQEF
    +AAOCAQ8AMIIBCgKCAQEAselBnOh089g/VN7gH1m43Vo67kSqh8QRnXZxfjpzt3oP
    +Dl5nd04eNey4ezoSBo7o1hKhj/m5KLxmy1kN+xssyutgzto1FZu8GC2jDyLvByNL
    +h0Z3XLmzdzBzBjosCtllJtzHlVL08SPuuOId5hToiiT8h3ElgNI4L6w+eLzhZIk5
    +Rj1WojGa+pnaTEgoOaZPcNrkOj81o1tgnbLXN7HY3hJKnRp78DmPySq82cRhvfNr
    +ePCs6BJr3y7yYJk0nG+EOaj5BK95YSJondZ8fOZuCigJPMogEaSw0SGsSUiQrPsd
    ++3vZQ+3ctOimnhW7cF3fAM79g+zDdv9N9E3D+inhyQIDAQABo4IBgTCCAX0wCQYD
    +VR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlm
    +aWNhdGUwHQYDVR0OBBYEFJgip1fThIyZu9J+YNz3XKDkOcMKMB8GA1UdIwQYMBaA
    +FND2ZsvHIjITekPKs0ywLfoNEen5MDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9s
    +b2NhbGhvc3Q6NTI4MC9kYXRhL2NybC5kZXIwNgYIKwYBBQUHAQEEKjAoMCYGCCsG
    +AQUFBzABhhpodHRwOi8vbG9jYWxob3N0OjUyODAvb2NzcDALBgNVHQ8EBAMCBeAw
    +JwYDVR0lBCAwHgYIKwYBBQUHAwkGCCsGAQUFBwMBBggrBgEFBQcDAjBfBgNVHREE
    +WDBWoBcGCCsGAQUFBwgFoAsMCWxvY2FsaG9zdKAbBggrBgEFBQcIBaAPDA1zMnMu
    +bG9jYWxob3N0oB4GCCsGAQUFBwgFoBIMEG1uZXNpYS5sb2NhbGhvc3QwDQYJKoZI
    +hvcNAQEFBQADggEBAEwHeECqeEJIz0VFA0OZ0w9+3rfZPX9K59rbJNNnKVATPhk5
    +g5NFpXy1mFTV/3MWjDS1QRbgoXzOYR64S87oez4l3jyDz3YxklyjbbiN3QKaUq5h
    +284Ze6CiRqxIi6V2bhjjp3voMSP8BQ72bX9uAWjqQl7Z16wYuCzV4QzVZRD5p0c1
    +y45WZ6J+sU1GTwEGh0vXZBlDMeTb+53smjEoCxET1ecJmStAvJi+UHiLn63Z3Yzz
    +CTfdAZ/mj+ytaNLVsgrULXrmZAeo064HVqeyLWL8ZBoM0zLs6u14OQOeDCCB62cj
    +UXb9npKmIdfsWvdii6emCVQqKBQmHnlUMCh56tE=
     -----END CERTIFICATE-----
     -----BEGIN RSA PRIVATE KEY-----
    -MIICXAIBAAKBgQC+GTA1D1+yiXgLqUhJXkSj3hj5FiqlBAfJT/8OSXYifY4M4HYv
    -VQrqER2Fs7jdCaeoGWDvwfK/UOV0b1ROnf+T/2bXFs8EOeqjOz4xG2oexNKVrYj9
    -ICYAgmSh6Hf2cZJM/YCAISje93Xl2J2w/N7oFC1ZXasPoBIZv3Fgg7hTtQIDAQAB
    -AoGALddtJJ58eVVlOYqs/+RXsRyR8R9DUV/TcNx1qUBV2KNmafyHA4sCgsd10xQv
    -9D2rzIGyOp8OpswfSSC/t+WqB9+ezSruzMuX6IURdHZbX6aWWX6maICtPKEEkCmI
    -gaLxE/ojuOXnTEBTkVuVWtuFL9PsK/WGi/FIDzJbwqTWJ4ECQQDy9DrBAQM96B6u
    -G4XpFzBsfgJZoS+NaMdCwK+/jgcEpI6oxobK8tuGB6drp5jNSuQ905W9n8XjA6Xq
    -x8/GH9I5AkEAyE5g05HhMlxBWCq+P70pBDIamdHJcPQVL8+6NXkT+mTqqZxxkUy4
    -nMfTh5zE6WfmqYNtrmNBDxXUyaoRSBydXQJACnFnCR7DBekxUGiMc/10LmWoMjQU
    -eC6Vyg/APiqbsJ5mJ2kJKDYSK4uurZjxn3lloCa1HAZ/GgfxHMtj6e86OQJAetq3
    -wIwE12KGIZF1xpo6gfxJHHbzWngaVozN5OYyPq2O0CDH9xpbUK2vK8oXbCDx9J5L
    -s13lFV+Kd3X7y4LhcQJBAKSFg7ht33l8Sa0TdUkY6Tl1NBMCCLf+np+HYrAbQZux
    -2NtR6nj2YqeOpEe1ibWZm8tj3dzlTm1FCOIpa+pm114=
    +MIIEpAIBAAKCAQEAselBnOh089g/VN7gH1m43Vo67kSqh8QRnXZxfjpzt3oPDl5n
    +d04eNey4ezoSBo7o1hKhj/m5KLxmy1kN+xssyutgzto1FZu8GC2jDyLvByNLh0Z3
    +XLmzdzBzBjosCtllJtzHlVL08SPuuOId5hToiiT8h3ElgNI4L6w+eLzhZIk5Rj1W
    +ojGa+pnaTEgoOaZPcNrkOj81o1tgnbLXN7HY3hJKnRp78DmPySq82cRhvfNrePCs
    +6BJr3y7yYJk0nG+EOaj5BK95YSJondZ8fOZuCigJPMogEaSw0SGsSUiQrPsd+3vZ
    +Q+3ctOimnhW7cF3fAM79g+zDdv9N9E3D+inhyQIDAQABAoIBAQCWIyxVx+36YgGA
    +E927VzIkyqJ0tMncbOAYq/228oj4yy6th4l1Kx1fkHdWtnjDxBJFpc9l+u4ArI1r
    +Cao8wIAadmxp48dshtJC7TBv86EXuvdgH11XiPcknGRVWv4T4cX099gN8cX3QcWR
    +jHCC3B4phnD9s8RcZAs6X/cQWQU0mxiHodYJefSXDyRIx9wimXmmW83ZqcsFftXS
    +MI0+jflmRTf07M4gALVL0LlaBkg2FMoNiaKYPTbubcrEMUgTDsoDsjX3Fi43qLdF
    +QTq+lF7HrHQ1EQlngCJupka9JxwZc3Fae6XYlDQvSDPcRxzWJoOuVBPtheGeoU3c
    +PAry9KihAoGBAN8HCb0k4bMN06WZjSzClKhb6eFw4GMbVpDAOwPDl2N+9+pwrGxE
    +ztekrM+VdXVScIj23g6wKd6fPqK6EYuEEu3Hre82e9ApqjJ34p1UcOs9Vs4N3VDy
    +HJnWhEytsc9c03O5nhsK1YAXoGHEPmCYGsg2UA171LDcarnO1WDmpKkNAoGBAMw2
    +sTCC/LBwgsuPZL5fR10wQ1sr1fIheSL+VK10jSRDwNXT2Y4wdCpQXQ6XNi+n98n5
    +VvKaE6PxFqjnKCrUUty8X5+fzVcTKpBYVICceEzpVY9FrKbeY1shMnOBRTCkaQwz
    +8CoEbbQz6SH5s4qW7M8iJdUJ0RulaFDfpmangTStAoGBALMkMxVjZ4rsI0GT2grG
    +7KNi2LTFducEUX8JeR2n4JUBql78S/LXPhGGa2x9z5ACPPQ23tyLccYowSXyMR+Q
    +YafuyO4pJEBrBxNsqnDXH7BEX9I43rkjEAgdf70bk4RNOmdtA+sSw7UUxTVibPwn
    +kPOadKiv+4JoOa2vzkL8X+yNAoGAbU85OUZkC+2tlViEDILjqDYVV8/3DUxtkxWg
    +LdidVDQQHGTxpvK4u42Ywh6empPGRw54RBPFP5PlFTPmhEZytEUAymi3eUyBFBKz
    +6MPYgRLFAZPB/vA7LqRuZPVlG8xljmqeu17zeenveIg4Wo6+44Dbz1UZ4TqAxAlz
    +AK/YsWECgYAPuZnIo9fWJtUAIe5IA2LIqcN0rj3PsZ/tL6eaMXqKZgCYwTvVUGbT
    +XD4O352t+yLM8v2hJGHrIPuHooN2dCadYuzoBvVFsRTZjGpBlAZ+EJ5WfDYFL0qf
    +68O2KZNXaSS8ZARlp9g3C8AFiakm/uWhtSfwx09uSBHJgld1V3GAoA==
     -----END RSA PRIVATE KEY-----
    diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
    index aca547d99..9448df080 100644
    --- a/test/ejabberd_SUITE_data/ejabberd.yml
    +++ b/test/ejabberd_SUITE_data/ejabberd.yml
    @@ -61,6 +61,7 @@ Welcome to this XMPP server."
           mod_version: []
       "sqlite.localhost": 
         sql_type: sqlite
    +    sql_pool_size: 1
         auth_method: sql
         sm_db_type: sql
         modules: 
    @@ -327,7 +328,7 @@ Welcome to this XMPP server."
           mod_time: []
           mod_version: []
       "localhost": 
    -    auth_method: internal
    +    auth_method: [internal, anonymous]
       "ldap.localhost": 
         ldap_servers: 
           - "localhost"
    @@ -337,7 +338,8 @@ Welcome to this XMPP server."
         ldap_base: "ou=users,dc=localhost"
         auth_method: ldap
         modules: 
    -      mod_vcard_ldap: []
    +      mod_vcard:
    +        db_type: ldap
           mod_roster: [] # mod_roster is required by mod_shared_roster
           mod_shared_roster_ldap:
             ldap_auth_check: off
    @@ -387,8 +389,7 @@ access:
       local: 
         local: allow
       max_user_offline_messages: 
    -    admin: 5000
    -    all: 100
    +    all: infinity
       max_user_sessions: 
         all: 10
       muc: 
    @@ -408,6 +409,7 @@ acl:
         user_regexp: ""
     define_macro: 
       CERTFILE: "cert.pem"
    +  CAFILE: "ca.pem"
     language: "en"
     listen: 
       - 
    @@ -427,6 +429,11 @@ listen:
         port: @@web_port@@
         module: ejabberd_http
         captcha: true
    +  - 
    +    port: @@component_port@@
    +    module: ejabberd_service
    +    password: >-
    +      @@password@@
     loglevel: @@loglevel@@
     max_fsm_queue: 1000
     modules: 
    @@ -435,6 +442,8 @@ modules:
       mod_disco: []
       mod_ping: []
       mod_proxy65: []
    +  mod_legacy: []
    +  mod_muc: []
       mod_register: 
         welcome_message: 
           subject: "Welcome!"
    @@ -444,6 +453,11 @@ Welcome to this XMPP server."
       mod_time: []
       mod_version: []
     registration_timeout: infinity
    +route_subdomains: s2s
    +domain_certfile: CERTFILE
    +s2s_use_starttls: false
    +s2s_cafile: CAFILE
    +outgoing_s2s_port: @@s2s_port@@
     shaper: 
       fast: 50000
    -  normal: 1000
    +  normal: 10000
    diff --git a/test/ejabberd_SUITE_data/extauth.py b/test/ejabberd_SUITE_data/extauth.py
    index 84c000144..fa2c9efd0 100755
    --- a/test/ejabberd_SUITE_data/extauth.py
    +++ b/test/ejabberd_SUITE_data/extauth.py
    @@ -7,7 +7,10 @@ def read():
         cmd = pkt[0]
         args_num = len(pkt) - 1
         if cmd == 'auth' and args_num >= 3:
    -        write(True)
    +        if pkt[1] == "wrong":
    +            write(False)
    +        else:
    +            write(True)
         elif cmd == 'isuser' and args_num == 2:
             write(True)
         elif cmd == 'setpass' and args_num >= 3:
    diff --git a/test/ejabberd_SUITE_data/gencerts.sh b/test/ejabberd_SUITE_data/gencerts.sh
    new file mode 100755
    index 000000000..d0acd4b0c
    --- /dev/null
    +++ b/test/ejabberd_SUITE_data/gencerts.sh
    @@ -0,0 +1,18 @@
    +#!/bin/sh
    +# Update openssl.cnf if needed (in particular section [alt_names])
    +
    +rm -rf ssl
    +mkdir -p ssl/newcerts
    +touch ssl/index.txt
    +echo 01 > ssl/serial
    +echo 1000 > ssl/crlnumber
    +openssl genrsa -out ssl/client.key
    +openssl req -new -key ssl/client.key -out ssl/client.csr -config openssl.cnf -batch -subj /C=AU/ST=Some-State/O=Internet\ Widgits\ Pty\ Ltd/CN=active
    +openssl ca -keyfile ca.key -cert ca.pem -in ssl/client.csr -out ssl/client.crt -config openssl.cnf -days 10000 -batch -notext
    +openssl req -new -key ssl/client.key -out ssl/self-signed-client.csr -batch -subj /C=AU/ST=Some-State/O=Internet\ Widgits\ Pty\ Ltd/CN=active
    +openssl x509 -req -in ssl/self-signed-client.csr -signkey ssl/client.key -out ssl/self-signed-client.crt -days 10000
    +cat ssl/client.crt > cert.pem
    +cat ssl/self-signed-client.crt > self-signed-cert.pem
    +cat ssl/client.key >> cert.pem
    +cat ssl/client.key >> self-signed-cert.pem
    +rm -rf ssl
    diff --git a/test/ejabberd_SUITE_data/openssl.cnf b/test/ejabberd_SUITE_data/openssl.cnf
    new file mode 100644
    index 000000000..ff11d1460
    --- /dev/null
    +++ b/test/ejabberd_SUITE_data/openssl.cnf
    @@ -0,0 +1,323 @@
    +#
    +# OpenSSL example configuration file.
    +# This is mostly being used for generation of certificate requests.
    +#
    +
    +# This definition stops the following lines choking if HOME isn't
    +# defined.
    +HOME			= .
    +RANDFILE		= $ENV::HOME/.rnd
    +
    +# Extra OBJECT IDENTIFIER info:
    +#oid_file		= $ENV::HOME/.oid
    +oid_section		= new_oids
    +
    +# To use this configuration file with the "-extfile" option of the
    +# "openssl x509" utility, name here the section containing the
    +# X.509v3 extensions to use:
    +extensions		= v3_req
    +# (Alternatively, use a configuration file that has only
    +# X.509v3 extensions in its main [= default] section.)
    +
    +[ new_oids ]
    +# We can add new OIDs in here for use by 'ca' and 'req'.
    +# Add a simple OID like this:
    +# testoid1=1.2.3.4
    +# Or use config file substitution like this:
    +# testoid2=${testoid1}.5.6
    +
    +####################################################################
    +[ ca ]
    +default_ca	= CA_default		# The default ca section
    +
    +####################################################################
    +[ CA_default ]
    +
    +#dir		= ./demoCA		# Where everything is kept
    +dir		= ssl
    +certs		= $dir/certs		# Where the issued certs are kept
    +crl_dir		= $dir/crl		# Where the issued crl are kept
    +database	= $dir/index.txt	# database index file.
    +#unique_subject	= no			# Set to 'no' to allow creation of
    +					# several ctificates with same subject.
    +new_certs_dir	= $dir/newcerts		# default place for new certs.
    +
    +certificate	= $dir/cacert.pem 	# The CA certificate
    +serial		= $dir/serial 		# The current serial number
    +crlnumber	= $dir/crlnumber	# the current crl number
    +					# must be commented out to leave a V1 CRL
    +crl		= $dir/crl.pem 		# The current CRL
    +private_key	= $dir/private/cakey.pem# The private key
    +RANDFILE	= $dir/private/.rand	# private random number file
    +
    +x509_extensions	= usr_cert		# The extentions to add to the cert
    +
    +# Comment out the following two lines for the "traditional"
    +# (and highly broken) format.
    +name_opt 	= ca_default		# Subject Name options
    +cert_opt 	= ca_default		# Certificate field options
    +
    +# Extension copying option: use with caution.
    +copy_extensions = copy
    +
    +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
    +# so this is commented out by default to leave a V1 CRL.
    +# crlnumber must also be commented out to leave a V1 CRL.
    +# crl_extensions	= crl_ext
    +
    +default_days	= 365			# how long to certify for
    +default_crl_days= 30			# how long before next CRL
    +default_md	= sha1			# which md to use.
    +preserve	= no			# keep passed DN ordering
    +
    +# A few difference way of specifying how similar the request should look
    +# For type CA, the listed attributes must be the same, and the optional
    +# and supplied fields are just that :-)
    +policy		= policy_match
    +
    +# For the CA policy
    +[ policy_match ]
    +countryName		= match
    +stateOrProvinceName	= match
    +organizationName	= match
    +organizationalUnitName	= optional
    +commonName		= optional
    +emailAddress		= optional
    +
    +# For the 'anything' policy
    +# At this point in time, you must list all acceptable 'object'
    +# types.
    +[ policy_anything ]
    +countryName		= optional
    +stateOrProvinceName	= optional
    +localityName		= optional
    +organizationName	= optional
    +organizationalUnitName	= optional
    +commonName		= optional
    +emailAddress		= optional
    +
    +####################################################################
    +[ req ]
    +default_bits		= 1024
    +default_keyfile 	= privkey.pem
    +distinguished_name	= req_distinguished_name
    +attributes		= req_attributes
    +x509_extensions	= v3_ca	# The extentions to add to the self signed cert
    +
    +# Passwords for private keys if not present they will be prompted for
    +# input_password = secret
    +# output_password = secret
    +
    +# This sets a mask for permitted string types. There are several options. 
    +# default: PrintableString, T61String, BMPString.
    +# pkix	 : PrintableString, BMPString.
    +# utf8only: only UTF8Strings.
    +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
    +# MASK:XXXX a literal mask value.
    +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings
    +# so use this option with caution!
    +string_mask = nombstr
    +
    +req_extensions = v3_req # The extensions to add to a certificate request
    +
    +[ req_distinguished_name ]
    +countryName			= Country Name (2 letter code)
    +countryName_default		= AU
    +countryName_min			= 2
    +countryName_max			= 2
    +
    +stateOrProvinceName		= State or Province Name (full name)
    +stateOrProvinceName_default	= Some-State
    +
    +localityName			= Locality Name (eg, city)
    +
    +0.organizationName		= Organization Name (eg, company)
    +0.organizationName_default	= Internet Widgits Pty Ltd
    +
    +# we can do this but it is not needed normally :-)
    +#1.organizationName		= Second Organization Name (eg, company)
    +#1.organizationName_default	= World Wide Web Pty Ltd
    +
    +organizationalUnitName		= Organizational Unit Name (eg, section)
    +#organizationalUnitName_default	=
    +
    +commonName			= Common Name (eg, YOUR name)
    +commonName_max			= 64
    +
    +emailAddress			= Email Address
    +emailAddress_max		= 64
    +
    +# SET-ex3			= SET extension number 3
    +
    +[ req_attributes ]
    +challengePassword		= A challenge password
    +challengePassword_min		= 4
    +challengePassword_max		= 20
    +
    +unstructuredName		= An optional company name
    +
    +[ usr_cert ]
    +
    +# These extensions are added when 'ca' signs a request.
    +
    +# This goes against PKIX guidelines but some CAs do it and some software
    +# requires this to avoid interpreting an end user certificate as a CA.
    +
    +basicConstraints=CA:FALSE
    +
    +# Here are some examples of the usage of nsCertType. If it is omitted
    +# the certificate can be used for anything *except* object signing.
    +
    +# This is OK for an SSL server.
    +# nsCertType			= server
    +
    +# For an object signing certificate this would be used.
    +# nsCertType = objsign
    +
    +# For normal client use this is typical
    +# nsCertType = client, email
    +
    +# and for everything including object signing:
    +# nsCertType = client, email, objsign
    +
    +# This is typical in keyUsage for a client certificate.
    +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
    +
    +# This will be displayed in Netscape's comment listbox.
    +nsComment			= "OpenSSL Generated Certificate"
    +
    +# PKIX recommendations harmless if included in all certificates.
    +subjectKeyIdentifier=hash
    +authorityKeyIdentifier=keyid,issuer
    +
    +# This stuff is for subjectAltName and issuerAltname.
    +# Import the email address.
    +# subjectAltName=email:copy
    +# An alternative to produce certificates that aren't
    +# deprecated according to PKIX.
    +# subjectAltName=email:move
    +
    +# Copy subject details
    +# issuerAltName=issuer:copy
    +
    +#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
    +#nsBaseUrl
    +#nsRevocationUrl
    +#nsRenewalUrl
    +#nsCaPolicyUrl
    +#nsSslServerName
    +
    +crlDistributionPoints = URI:http://localhost:5280/data/crl.der
    +authorityInfoAccess = OCSP;URI:http://localhost:5280/ocsp
    +
    +[ v3_req ]
    +
    +# Extensions to add to a certificate request
    +
    +basicConstraints = CA:FALSE
    +keyUsage = nonRepudiation, digitalSignature, keyEncipherment
    +extendedKeyUsage = OCSPSigning,serverAuth,clientAuth
    +subjectAltName          = @alt_names
    +
    +[alt_names]
    +otherName.1 = 1.3.6.1.5.5.7.8.5;UTF8:"localhost"
    +otherName.2 = 1.3.6.1.5.5.7.8.5;UTF8:"s2s.localhost"
    +otherName.3 = 1.3.6.1.5.5.7.8.5;UTF8:"mnesia.localhost"
    +
    +[ v3_ca ]
    +crlDistributionPoints = URI:http://localhost:5280/data/crl.der
    +
    +# Extensions for a typical CA
    +
    +
    +# PKIX recommendation.
    +
    +subjectKeyIdentifier=hash
    +
    +authorityKeyIdentifier=keyid:always,issuer:always
    +
    +# This is what PKIX recommends but some broken software chokes on critical
    +# extensions.
    +#basicConstraints = critical,CA:true
    +# So we do this instead.
    +basicConstraints = CA:true
    +
    +# Key usage: this is typical for a CA certificate. However since it will
    +# prevent it being used as an test self-signed certificate it is best
    +# left out by default.
    +# keyUsage = cRLSign, keyCertSign
    +
    +# Some might want this also
    +# nsCertType = sslCA, emailCA
    +
    +# Include email address in subject alt name: another PKIX recommendation
    +# subjectAltName=email:copy
    +# Copy issuer details
    +# issuerAltName=issuer:copy
    +
    +# DER hex encoding of an extension: beware experts only!
    +# obj=DER:02:03
    +# Where 'obj' is a standard or added object
    +# You can even override a supported extension:
    +# basicConstraints= critical, DER:30:03:01:01:FF
    +
    +[ crl_ext ]
    +
    +# CRL extensions.
    +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
    +
    +# issuerAltName=issuer:copy
    +authorityKeyIdentifier=keyid:always,issuer:always
    +
    +[ proxy_cert_ext ]
    +# These extensions should be added when creating a proxy certificate
    +
    +# This goes against PKIX guidelines but some CAs do it and some software
    +# requires this to avoid interpreting an end user certificate as a CA.
    +
    +basicConstraints=CA:FALSE
    +
    +# Here are some examples of the usage of nsCertType. If it is omitted
    +# the certificate can be used for anything *except* object signing.
    +
    +# This is OK for an SSL server.
    +# nsCertType			= server
    +
    +# For an object signing certificate this would be used.
    +# nsCertType = objsign
    +
    +# For normal client use this is typical
    +# nsCertType = client, email
    +
    +# and for everything including object signing:
    +# nsCertType = client, email, objsign
    +
    +# This is typical in keyUsage for a client certificate.
    +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
    +
    +# This will be displayed in Netscape's comment listbox.
    +nsComment			= "OpenSSL Generated Certificate"
    +
    +# PKIX recommendations harmless if included in all certificates.
    +subjectKeyIdentifier=hash
    +authorityKeyIdentifier=keyid,issuer:always
    +
    +# This stuff is for subjectAltName and issuerAltname.
    +# Import the email address.
    +# subjectAltName=email:copy
    +# An alternative to produce certificates that aren't
    +# deprecated according to PKIX.
    +# subjectAltName=email:move
    +
    +# Copy subject details
    +# issuerAltName=issuer:copy
    +
    +#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
    +#nsBaseUrl
    +#nsRevocationUrl
    +#nsRenewalUrl
    +#nsCaPolicyUrl
    +#nsSslServerName
    +
    +# This really needs to be in place for it to be a proxy certificate.
    +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
    diff --git a/test/ejabberd_SUITE_data/self-signed-cert.pem b/test/ejabberd_SUITE_data/self-signed-cert.pem
    new file mode 100644
    index 000000000..d6b34f50e
    --- /dev/null
    +++ b/test/ejabberd_SUITE_data/self-signed-cert.pem
    @@ -0,0 +1,46 @@
    +-----BEGIN CERTIFICATE-----
    +MIIDKDCCAhACCQCsLYnJDV1wHDANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJB
    +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
    +cyBQdHkgTHRkMQ8wDQYDVQQDEwZhY3RpdmUwHhcNMTYwOTIzMDcwMzI0WhcNNDQw
    +MjA5MDcwMzI0WjBWMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh
    +MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZhY3Rp
    +dmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx6UGc6HTz2D9U3uAf
    +WbjdWjruRKqHxBGddnF+OnO3eg8OXmd3Th417Lh7OhIGjujWEqGP+bkovGbLWQ37
    +GyzK62DO2jUVm7wYLaMPIu8HI0uHRndcubN3MHMGOiwK2WUm3MeVUvTxI+644h3m
    +FOiKJPyHcSWA0jgvrD54vOFkiTlGPVaiMZr6mdpMSCg5pk9w2uQ6PzWjW2Cdstc3
    +sdjeEkqdGnvwOY/JKrzZxGG982t48KzoEmvfLvJgmTScb4Q5qPkEr3lhImid1nx8
    +5m4KKAk8yiARpLDRIaxJSJCs+x37e9lD7dy06KaeFbtwXd8Azv2D7MN2/030TcP6
    +KeHJAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEAIFwHpNCVUiivAcfkxcUPKp0nn
    +mhGqkMDRrPA7fOCm0ir1Puz4GQ/G4i+tWejzzFoS6kKQl+sUZAUYJdziftJFFoZ7
    +br3q3Xafc2dWa8SHNcHH6lA1OEk8tXlhkNl+EgSLnRGMhIf0iZL2wGjE8Hlig6cu
    +3h+OpbUijXUmq0XdH+ui3wNgXb7+Tosg/Od+lr0fNjkopsk3t1oiVXD4OQBZdUyq
    +V5XValiZjMFDUUBdxBA+l6/Qj3bFmluz+FXI8UwfbinukqADTJzkMeUjEkvmKZWO
    +tb+EU77NIuvg/k7b1yp4lEmATpdUfcGEuhWNtgeh5AqgMxOhAsJ7zUTA80I=
    +-----END CERTIFICATE-----
    +-----BEGIN RSA PRIVATE KEY-----
    +MIIEpAIBAAKCAQEAselBnOh089g/VN7gH1m43Vo67kSqh8QRnXZxfjpzt3oPDl5n
    +d04eNey4ezoSBo7o1hKhj/m5KLxmy1kN+xssyutgzto1FZu8GC2jDyLvByNLh0Z3
    +XLmzdzBzBjosCtllJtzHlVL08SPuuOId5hToiiT8h3ElgNI4L6w+eLzhZIk5Rj1W
    +ojGa+pnaTEgoOaZPcNrkOj81o1tgnbLXN7HY3hJKnRp78DmPySq82cRhvfNrePCs
    +6BJr3y7yYJk0nG+EOaj5BK95YSJondZ8fOZuCigJPMogEaSw0SGsSUiQrPsd+3vZ
    +Q+3ctOimnhW7cF3fAM79g+zDdv9N9E3D+inhyQIDAQABAoIBAQCWIyxVx+36YgGA
    +E927VzIkyqJ0tMncbOAYq/228oj4yy6th4l1Kx1fkHdWtnjDxBJFpc9l+u4ArI1r
    +Cao8wIAadmxp48dshtJC7TBv86EXuvdgH11XiPcknGRVWv4T4cX099gN8cX3QcWR
    +jHCC3B4phnD9s8RcZAs6X/cQWQU0mxiHodYJefSXDyRIx9wimXmmW83ZqcsFftXS
    +MI0+jflmRTf07M4gALVL0LlaBkg2FMoNiaKYPTbubcrEMUgTDsoDsjX3Fi43qLdF
    +QTq+lF7HrHQ1EQlngCJupka9JxwZc3Fae6XYlDQvSDPcRxzWJoOuVBPtheGeoU3c
    +PAry9KihAoGBAN8HCb0k4bMN06WZjSzClKhb6eFw4GMbVpDAOwPDl2N+9+pwrGxE
    +ztekrM+VdXVScIj23g6wKd6fPqK6EYuEEu3Hre82e9ApqjJ34p1UcOs9Vs4N3VDy
    +HJnWhEytsc9c03O5nhsK1YAXoGHEPmCYGsg2UA171LDcarnO1WDmpKkNAoGBAMw2
    +sTCC/LBwgsuPZL5fR10wQ1sr1fIheSL+VK10jSRDwNXT2Y4wdCpQXQ6XNi+n98n5
    +VvKaE6PxFqjnKCrUUty8X5+fzVcTKpBYVICceEzpVY9FrKbeY1shMnOBRTCkaQwz
    +8CoEbbQz6SH5s4qW7M8iJdUJ0RulaFDfpmangTStAoGBALMkMxVjZ4rsI0GT2grG
    +7KNi2LTFducEUX8JeR2n4JUBql78S/LXPhGGa2x9z5ACPPQ23tyLccYowSXyMR+Q
    +YafuyO4pJEBrBxNsqnDXH7BEX9I43rkjEAgdf70bk4RNOmdtA+sSw7UUxTVibPwn
    +kPOadKiv+4JoOa2vzkL8X+yNAoGAbU85OUZkC+2tlViEDILjqDYVV8/3DUxtkxWg
    +LdidVDQQHGTxpvK4u42Ywh6empPGRw54RBPFP5PlFTPmhEZytEUAymi3eUyBFBKz
    +6MPYgRLFAZPB/vA7LqRuZPVlG8xljmqeu17zeenveIg4Wo6+44Dbz1UZ4TqAxAlz
    +AK/YsWECgYAPuZnIo9fWJtUAIe5IA2LIqcN0rj3PsZ/tL6eaMXqKZgCYwTvVUGbT
    +XD4O352t+yLM8v2hJGHrIPuHooN2dCadYuzoBvVFsRTZjGpBlAZ+EJ5WfDYFL0qf
    +68O2KZNXaSS8ZARlp9g3C8AFiakm/uWhtSfwx09uSBHJgld1V3GAoA==
    +-----END RSA PRIVATE KEY-----
    diff --git a/test/ejabberd_commands_mock_test.exs b/test/ejabberd_commands_mock_test.exs
    index 419a989d6..12444f79a 100644
    --- a/test/ejabberd_commands_mock_test.exs
    +++ b/test/ejabberd_commands_mock_test.exs
    @@ -48,7 +48,7 @@ defmodule EjabberdCommandsMockTest do
     			_ -> :ok
     		end
     		:mnesia.start
    -    :ok = :jid.start
    +    {:ok, _} = :jid.start
         :ok = :ejabberd_config.start(["domain1", "domain2"], [])
         {:ok, _} = :ejabberd_access_permissions.start_link()
         :ok = :acl.start
    diff --git a/test/ejabberd_cyrsasl_test.exs b/test/ejabberd_cyrsasl_test.exs
    index d9b949294..1b98048c7 100644
    --- a/test/ejabberd_cyrsasl_test.exs
    +++ b/test/ejabberd_cyrsasl_test.exs
    @@ -27,7 +27,7 @@ defmodule EjabberdCyrsaslTest do
         :p1_sha.load_nif()
         :mnesia.start
         :ok = start_module(:stringprep)
    -    :ok = start_module(:jid)
    +    {:ok, _} = start_module(:jid)
         :ok = :ejabberd_config.start(["domain1"], [])
         :ok = :cyrsasl.start
         cyrstate = :cyrsasl.server_new("domain1", "domain1", "domain1", :ok, &get_password/1,
    @@ -44,12 +44,12 @@ defmodule EjabberdCyrsaslTest do
     
       test "Plain text (correct user wrong pass)", context do
         step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"user1",0,"badpass">>)
    -    assert step1 == {:error, "not-authorized", "user1"}, "got error response"
    +    assert step1 == {:error, :"not-authorized", "user1"}
       end
     
       test "Plain text (wrong user wrong pass)", context do
         step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"nouser1",0,"badpass">>)
    -    assert step1 == {:error, "not-authorized", "nouser1"}, "got error response"
    +    assert step1 == {:error, :"not-authorized", "nouser1"}
       end
     
       test "Anonymous", context do
    diff --git a/test/example_tests.erl b/test/example_tests.erl
    new file mode 100644
    index 000000000..d7965376e
    --- /dev/null
    +++ b/test/example_tests.erl
    @@ -0,0 +1,52 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(example_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, []).
    +
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {example_single, [sequence],
    +     [single_test(foo)]}.
    +
    +foo(Config) ->
    +    Config.
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {example_master_slave, [sequence],
    +     [master_slave_test(foo)]}.
    +
    +foo_master(Config) ->
    +    Config.
    +
    +foo_slave(Config) ->
    +    Config.
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("example_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("example_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("example_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("example_" ++ atom_to_list(T) ++ "_slave")]}.
    diff --git a/test/jid_test.exs b/test/jid_test.exs
    index b75a3603a..7d063b707 100644
    --- a/test/jid_test.exs
    +++ b/test/jid_test.exs
    @@ -29,6 +29,7 @@ defmodule JidTest do
       setup_all do
         :stringprep.start
         :jid.start
    +    :ok
       end
     
       test "create a jid from a binary" do
    diff --git a/test/mam_tests.erl b/test/mam_tests.erl
    new file mode 100644
    index 000000000..d628ddd2a
    --- /dev/null
    +++ b/test/mam_tests.erl
    @@ -0,0 +1,538 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 14 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(mam_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [get_features/1, disconnect/1, my_jid/1, send_recv/2,
    +		wait_for_slave/1, server_jid/1, send/2, get_features/2,
    +		wait_for_master/1, recv_message/1, recv_iq/1, muc_room_jid/1,
    +		muc_jid/1, is_feature_advertised/3, get_event/1, put_event/2]).
    +
    +-include("suite.hrl").
    +-define(VERSIONS, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1]).
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {mam_single, [sequence],
    +     [single_test(feature_enabled),
    +      single_test(get_set_prefs),
    +      single_test(get_form),
    +      single_test(fake_by)]}.
    +
    +feature_enabled(Config) ->
    +    BareMyJID = jid:remove_resource(my_jid(Config)),
    +    RequiredFeatures = sets:from_list(?VERSIONS),
    +    ServerFeatures = sets:from_list(get_features(Config)),
    +    UserFeatures = sets:from_list(get_features(Config, BareMyJID)),
    +    MUCFeatures = get_features(Config, muc_jid(Config)),
    +    ct:comment("Checking if all MAM server features are enabled"),
    +    true = sets:is_subset(RequiredFeatures, ServerFeatures),
    +    ct:comment("Checking if all MAM user features are enabled"),
    +    true = sets:is_subset(RequiredFeatures, UserFeatures),
    +    ct:comment("Checking if all MAM conference service features are enabled"),
    +    true = lists:member(?NS_MAM_1, MUCFeatures),
    +    clean(disconnect(Config)).
    +
    +fake_by(Config) ->
    +    BareServerJID = server_jid(Config),
    +    FullServerJID = jid:replace_resource(BareServerJID, randoms:get_string()),
    +    FullMyJID = my_jid(Config),
    +    BareMyJID = jid:remove_resource(FullMyJID),
    +    Fakes = lists:flatmap(
    +	      fun(JID) ->
    +		      [#mam_archived{id = randoms:get_string(), by = JID},
    +		       #stanza_id{id = randoms:get_string(), by = JID}]
    +	      end, [BareServerJID, FullServerJID, BareMyJID, FullMyJID]),
    +    Body = xmpp:mk_text(<<"body">>),
    +    ForeignJID = jid:make(randoms:get_string()),
    +    Archived = #mam_archived{id = randoms:get_string(), by = ForeignJID},
    +    StanzaID = #stanza_id{id = randoms:get_string(), by = ForeignJID},
    +    #message{body = Body, sub_els = SubEls} =
    +	send_recv(Config, #message{to = FullMyJID,
    +				   body = Body,
    +				   sub_els = [Archived, StanzaID|Fakes]}),
    +    ct:comment("Checking if only foreign tags present"),
    +    [ForeignJID, ForeignJID] = lists:flatmap(
    +				 fun(#mam_archived{by = By}) -> [By];
    +				    (#stanza_id{by = By}) -> [By];
    +				    (_) -> []
    +				 end, SubEls),
    +    clean(disconnect(Config)).
    +
    +get_set_prefs(Config) ->
    +    Range = [{JID, #mam_prefs{xmlns = NS,
    +			      default = Default,
    +			      always = Always,
    +			      never = Never}} ||
    +		JID <- [undefined, server_jid(Config)],
    +		NS <- ?VERSIONS,
    +		Default <- [always, never, roster],
    +		Always <- [[], [jid:from_string(<<"foo@bar.baz">>)]],
    +		Never <- [[], [jid:from_string(<<"baz@bar.foo">>)]]],
    +    lists:foreach(
    +      fun({To, Prefs}) ->
    +	      NS = Prefs#mam_prefs.xmlns,
    +	      #iq{type = result, sub_els = [Prefs]} =
    +		  send_recv(Config, #iq{type = set, to = To,
    +					sub_els = [Prefs]}),
    +	      #iq{type = result, sub_els = [Prefs]} =
    +		  send_recv(Config, #iq{type = get, to = To,
    +					sub_els = [#mam_prefs{xmlns = NS}]})
    +      end, Range),
    +    clean(disconnect(Config)).
    +
    +get_form(Config) ->
    +    ServerJID = server_jid(Config),
    +    Range = [{JID, NS} || JID <- [undefined, ServerJID],
    +			  NS <- ?VERSIONS -- [?NS_MAM_TMP]],
    +    lists:foreach(
    +      fun({To, NS}) ->
    +	      #iq{type = result,
    +		  sub_els = [#mam_query{xmlns = NS,
    +					xdata = #xdata{} = X}]} =
    +		  send_recv(Config, #iq{type = get, to = To,
    +					sub_els = [#mam_query{xmlns = NS}]}),
    +	      [NS] = xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
    +	      true = xmpp_util:has_xdata_var(<<"with">>, X),
    +	      true = xmpp_util:has_xdata_var(<<"start">>, X),
    +	      true = xmpp_util:has_xdata_var(<<"end">>, X)
    +      end, Range),
    +    clean(disconnect(Config)).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {mam_master_slave, [sequence],
    +     [master_slave_test(archived_and_stanza_id),
    +      master_slave_test(query_all),
    +      master_slave_test(query_with),
    +      master_slave_test(query_rsm_max),
    +      master_slave_test(query_rsm_after),
    +      master_slave_test(query_rsm_before),
    +      master_slave_test(muc)]}.
    +
    +archived_and_stanza_id_master(Config) ->
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_slave(Config),
    +    send_messages(Config, lists:seq(1, 5)),
    +    clean(disconnect(Config)).
    +
    +archived_and_stanza_id_slave(Config) ->
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_master(Config),
    +    recv_messages(Config, lists:seq(1, 5)),
    +    clean(disconnect(Config)).
    +
    +query_all_master(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_slave(Config),
    +    send_messages(Config, lists:seq(1, 5)),
    +    query_all(Config, MyJID, Peer),
    +    clean(disconnect(Config)).
    +
    +query_all_slave(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_master(Config),
    +    recv_messages(Config, lists:seq(1, 5)),
    +    query_all(Config, Peer, MyJID),
    +    clean(disconnect(Config)).
    +
    +query_with_master(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_slave(Config),
    +    send_messages(Config, lists:seq(1, 5)),
    +    query_with(Config, MyJID, Peer),
    +    clean(disconnect(Config)).
    +
    +query_with_slave(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_master(Config),
    +    recv_messages(Config, lists:seq(1, 5)),
    +    query_with(Config, Peer, MyJID),
    +    clean(disconnect(Config)).
    +
    +query_rsm_max_master(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_slave(Config),
    +    send_messages(Config, lists:seq(1, 5)),
    +    query_rsm_max(Config, MyJID, Peer),
    +    clean(disconnect(Config)).
    +
    +query_rsm_max_slave(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_master(Config),
    +    recv_messages(Config, lists:seq(1, 5)),
    +    query_rsm_max(Config, Peer, MyJID),
    +    clean(disconnect(Config)).
    +
    +query_rsm_after_master(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_slave(Config),
    +    send_messages(Config, lists:seq(1, 5)),
    +    query_rsm_after(Config, MyJID, Peer),
    +    clean(disconnect(Config)).
    +
    +query_rsm_after_slave(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_master(Config),
    +    recv_messages(Config, lists:seq(1, 5)),
    +    query_rsm_after(Config, Peer, MyJID),
    +    clean(disconnect(Config)).
    +
    +query_rsm_before_master(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_slave(Config),
    +    send_messages(Config, lists:seq(1, 5)),
    +    query_rsm_before(Config, MyJID, Peer),
    +    clean(disconnect(Config)).
    +
    +query_rsm_before_slave(Config) ->
    +    Peer = ?config(peer, Config),
    +    MyJID = my_jid(Config),
    +    ok = set_default(Config, always),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_master(Config),
    +    recv_messages(Config, lists:seq(1, 5)),
    +    query_rsm_before(Config, Peer, MyJID),
    +    clean(disconnect(Config)).
    +
    +muc_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    %% Joining
    +    ok = muc_tests:join_new(Config),
    +    %% MAM feature should not be advertised at this point,
    +    %% because MAM is not enabled so far
    +    false = is_feature_advertised(Config, ?NS_MAM_1, Room),
    +    %% Fill in some history
    +    send_messages_to_room(Config, lists:seq(1, 21)),
    +    %% We now should be able to retrieve those via MAM, even though
    +    %% MAM is disabled. However, only last 20 messages should be received.
    +    recv_messages_from_room(Config, lists:seq(2, 21)),
    +    %% Now enable MAM for the conference
    +    %% Retrieve config first
    +    #iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} =
    +        send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}],
    +                              to = Room}),
    +    %% Find the MAM field in the config and enable it
    +    NewFields = lists:flatmap(
    +		  fun(#xdata_field{var = <<"mam">> = Var}) ->
    +			  [#xdata_field{var = Var, values = [<<"1">>]}];
    +		     (_) ->
    +			  []
    +		  end, RoomCfg#xdata.fields),
    +    NewRoomCfg = #xdata{type = submit, fields = NewFields},
    +    #iq{type = result, sub_els = []} =
    +	send_recv(Config, #iq{type = set, to = Room,
    +			      sub_els = [#muc_owner{config = NewRoomCfg}]}),
    +    #message{from = Room, type = groupchat,
    +	     sub_els = [#muc_user{status_codes = [104]}]} = recv_message(Config),
    +    %% Check if MAM has been enabled
    +    true = is_feature_advertised(Config, ?NS_MAM_1, Room),
    +    %% We now sending some messages again
    +    send_messages_to_room(Config, lists:seq(1, 5)),
    +    %% And retrieve them via MAM again.
    +    recv_messages_from_room(Config, lists:seq(1, 5)),
    +    put_event(Config, disconnect),
    +    clean(disconnect(Config)).
    +
    +muc_slave(Config) ->
    +    disconnect = get_event(Config),
    +    clean(disconnect(Config)).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("mam_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("mam_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("mam_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("mam_" ++ atom_to_list(T) ++ "_slave")]}.
    +
    +clean(Config) ->
    +    {U, S, _} = jid:tolower(my_jid(Config)),
    +    mod_mam:remove_user(U, S),
    +    Config.
    +
    +set_default(Config, Default) ->
    +    lists:foreach(
    +      fun(NS) ->
    +	      ct:comment("Setting default preferences of '~s' to '~s'",
    +			 [NS, Default]),
    +	      #iq{type = result,
    +		  sub_els = [#mam_prefs{xmlns = NS, default = Default}]} =
    +		  send_recv(Config, #iq{type = set,
    +					sub_els = [#mam_prefs{xmlns = NS,
    +							      default = Default}]})
    +      end, ?VERSIONS).
    +
    +send_messages(Config, Range) ->
    +    Peer = ?config(peer, Config),
    +    lists:foreach(
    +      fun(N) ->
    +	      Body = xmpp:mk_text(integer_to_binary(N)),
    +              send(Config, #message{to = Peer, body = Body})
    +      end, Range).
    +
    +recv_messages(Config, Range) ->
    +    Peer = ?config(peer, Config),
    +    lists:foreach(
    +      fun(N) ->
    +	      Body = xmpp:mk_text(integer_to_binary(N)),
    +	      #message{from = Peer, body = Body} = Msg =
    +		  recv_message(Config),
    +	      #mam_archived{by = BareMyJID} =
    +		  xmpp:get_subtag(Msg, #mam_archived{}),
    +	      #stanza_id{by = BareMyJID} =
    +		  xmpp:get_subtag(Msg, #stanza_id{})
    +      end, Range).
    +
    +recv_archived_messages(Config, From, To, QID, Range) ->
    +    MyJID = my_jid(Config),
    +    lists:foreach(
    +      fun(N) ->
    +	      ct:comment("Retreiving ~pth message in range ~p",
    +			 [N, Range]),
    +              Body = xmpp:mk_text(integer_to_binary(N)),
    +              #message{to = MyJID,
    +                       sub_els =
    +                           [#mam_result{
    +                               queryid = QID,
    +                               sub_els =
    +                                   [#forwarded{
    +                                       delay = #delay{},
    +                                       xml_els = [El]}]}]} = recv_message(Config),
    +	      #message{from = From, to = To,
    +		       body = Body} = xmpp:decode(El)
    +      end, Range).
    +
    +maybe_recv_iq_result(Config, ?NS_MAM_0, I) ->
    +    #iq{type = result, id = I} = recv_iq(Config);
    +maybe_recv_iq_result(_, _, _) ->
    +    ok.
    +
    +query_iq_type(?NS_MAM_TMP) -> get;
    +query_iq_type(_) -> set.
    +
    +send_query(Config, #mam_query{xmlns = NS} = Query) ->
    +    Type = query_iq_type(NS),
    +    I = send(Config, #iq{type = Type, sub_els = [Query]}),
    +    maybe_recv_iq_result(Config, NS, I),
    +    I.
    +
    +recv_fin(Config, I, QueryID, ?NS_MAM_1 = NS, IsComplete) ->
    +    ct:comment("Receiving fin iq for namespace '~s'", [NS]),
    +    #iq{type = result, id = I,
    +	sub_els = [#mam_fin{xmlns = NS,
    +			    id = QueryID,
    +			    complete = Complete,
    +			    rsm = RSM}]} = recv_iq(Config),
    +    ct:comment("Checking if complete is ~s", [IsComplete]),
    +    Complete = IsComplete,
    +    RSM;
    +recv_fin(Config, I, QueryID, ?NS_MAM_TMP = NS, _IsComplete) ->
    +    ct:comment("Receiving fin iq for namespace '~s'", [NS]),
    +    #iq{type = result, id = I,
    +	sub_els = [#mam_query{xmlns = NS,
    +			      rsm = RSM,
    +			      id = QueryID}]} = recv_iq(Config),
    +    RSM;
    +recv_fin(Config, _, QueryID, ?NS_MAM_0 = NS, IsComplete) ->
    +    ct:comment("Receiving fin message for namespace '~s'", [NS]),
    +    #message{} = FinMsg = recv_message(Config),
    +    #mam_fin{xmlns = NS,
    +	     id = QueryID,
    +	     complete = Complete,
    +	     rsm = RSM} = xmpp:get_subtag(FinMsg, #mam_fin{xmlns = NS}),
    +    ct:comment("Checking if complete is ~s", [IsComplete]),
    +    Complete = IsComplete,
    +    RSM.
    +
    +send_messages_to_room(Config, Range) ->
    +    MyNick = ?config(master_nick, Config),
    +    Room = muc_room_jid(Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    lists:foreach(
    +      fun(N) ->
    +              Body = xmpp:mk_text(integer_to_binary(N)),
    +	      #message{from = MyNickJID,
    +		       type = groupchat,
    +		       body = Body} =
    +		  send_recv(Config, #message{to = Room, body = Body,
    +					     type = groupchat})
    +      end, Range).
    +
    +recv_messages_from_room(Config, Range) ->
    +    MyNick = ?config(master_nick, Config),
    +    Room = muc_room_jid(Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    MyJID = my_jid(Config),
    +    QID = randoms:get_string(),
    +    Count = length(Range),
    +    I = send(Config, #iq{type = set, to = Room,
    +			 sub_els = [#mam_query{xmlns = ?NS_MAM_1, id = QID}]}),
    +    lists:foreach(
    +      fun(N) ->
    +	      Body = xmpp:mk_text(integer_to_binary(N)),
    +	      #message{
    +		 to = MyJID, from = Room,
    +		 sub_els =
    +		     [#mam_result{
    +			 xmlns = ?NS_MAM_1,
    +			 queryid = QID,
    +			 sub_els =
    +			     [#forwarded{
    +				 delay = #delay{},
    +				 xml_els = [El]}]}]} = recv_message(Config),
    +	      #message{from = MyNickJID,
    +		       type = groupchat,
    +		       body = Body} = xmpp:decode(El)
    +      end, Range),
    +    #iq{from = Room, id = I, type = result,
    +	sub_els = [#mam_fin{xmlns = ?NS_MAM_1,
    +			    id = QID,
    +			    rsm = #rsm_set{count = Count},
    +			    complete = true}]} = recv_iq(Config).
    +
    +query_all(Config, From, To) ->
    +    lists:foreach(
    +      fun(NS) ->
    +	      query_all(Config, From, To, NS)
    +      end, ?VERSIONS).
    +
    +query_all(Config, From, To, NS) ->
    +    QID = randoms:get_string(),
    +    Range = lists:seq(1, 5),
    +    ID = send_query(Config, #mam_query{xmlns = NS, id = QID}),
    +    recv_archived_messages(Config, From, To, QID, Range),
    +    #rsm_set{count = 5} = recv_fin(Config, ID, QID, NS, _Complete = true).
    +
    +query_with(Config, From, To) ->
    +    lists:foreach(
    +      fun(NS) ->
    +	      query_with(Config, From, To, NS)
    +      end, ?VERSIONS).
    +
    +query_with(Config, From, To, NS) ->
    +    Peer = ?config(peer, Config),
    +    BarePeer = jid:remove_resource(Peer),
    +    QID = randoms:get_string(),
    +    Range = lists:seq(1, 5),
    +    lists:foreach(
    +      fun(JID) ->
    +	      ct:comment("Sending query with jid ~s", [jid:to_string(JID)]),
    +	      Query = if NS == ?NS_MAM_TMP ->
    +			      #mam_query{xmlns = NS, with = JID, id = QID};
    +			 true ->
    +			      Fs = mam_query:encode([{with, JID}]),
    +			      #mam_query{xmlns = NS, id = QID,
    +					 xdata = #xdata{type = submit,
    +							fields = Fs}}
    +		      end,
    +	      ID = send_query(Config, Query),
    +	      recv_archived_messages(Config, From, To, QID, Range),
    +	      #rsm_set{count = 5} = recv_fin(Config, ID, QID, NS, true)
    +      end, [Peer, BarePeer]).
    +
    +query_rsm_max(Config, From, To) ->
    +    lists:foreach(
    +      fun(NS) ->
    +	      query_rsm_max(Config, From, To, NS)
    +      end, ?VERSIONS).
    +
    +query_rsm_max(Config, From, To, NS) ->
    +    lists:foreach(
    +      fun(Max) ->
    +	      QID = randoms:get_string(),
    +	      Range = lists:sublist(lists:seq(1, Max), 5),
    +	      Query = #mam_query{xmlns = NS, id = QID, rsm = #rsm_set{max = Max}},
    +	      ID = send_query(Config, Query),
    +	      recv_archived_messages(Config, From, To, QID, Range),
    +	      IsComplete = Max >= 5,
    +	      #rsm_set{count = 5} = recv_fin(Config, ID, QID, NS, IsComplete)
    +      end, lists:seq(0, 6)).
    +
    +query_rsm_after(Config, From, To) ->
    +    lists:foreach(
    +      fun(NS) ->
    +	      query_rsm_after(Config, From, To, NS)
    +      end, ?VERSIONS).
    +
    +query_rsm_after(Config, From, To, NS) ->
    +    lists:foldl(
    +      fun(Range, #rsm_first{data = After}) ->
    +	      ct:comment("Retrieving ~p messages after '~s'",
    +			 [length(Range), After]),
    +	      QID = randoms:get_string(),
    +	      Query = #mam_query{xmlns = NS, id = QID,
    +				 rsm = #rsm_set{'after' = After}},
    +	      ID = send_query(Config, Query),
    +	      recv_archived_messages(Config, From, To, QID, Range),
    +	      #rsm_set{count = 5, first = First} =
    +		  recv_fin(Config, ID, QID, NS, true),
    +	      First
    +      end, #rsm_first{data = undefined},
    +      [lists:seq(N, 5) || N <- lists:seq(1, 6)]).
    +
    +query_rsm_before(Config, From, To) ->
    +    lists:foreach(
    +      fun(NS) ->
    +	      query_rsm_before(Config, From, To, NS)
    +      end, ?VERSIONS).
    +
    +query_rsm_before(Config, From, To, NS) ->
    +    lists:foldl(
    +      fun(Range, Before) ->
    +	      ct:comment("Retrieving ~p messages before '~s'",
    +			 [length(Range), Before]),
    +	      QID = randoms:get_string(),
    +	      Query = #mam_query{xmlns = NS, id = QID,
    +				 rsm = #rsm_set{before = Before}},
    +	      ID = send_query(Config, Query),
    +	      recv_archived_messages(Config, From, To, QID, Range),
    +	      #rsm_set{count = 5, last = Last} =
    +		  recv_fin(Config, ID, QID, NS, true),
    +	      Last
    +      end, <<"">>, lists:reverse([lists:seq(1, N) || N <- lists:seq(0, 5)])).
    diff --git a/test/mix_tests.erl b/test/mix_tests.erl
    new file mode 100644
    index 000000000..56b1b35d7
    --- /dev/null
    +++ b/test/mix_tests.erl
    @@ -0,0 +1,139 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(mix_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [mix_jid/1, mix_room_jid/1, my_jid/1, is_feature_advertised/3,
    +		disconnect/1, send_recv/2, recv_message/1, send/2,
    +		put_event/2, get_event/1]).
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {mix_single, [sequence],
    +     [single_test(feature_enabled)]}.
    +
    +feature_enabled(Config) ->
    +    MIX = mix_jid(Config),
    +    ct:comment("Checking if ~s is set", [?NS_MIX_0]),
    +    true = is_feature_advertised(Config, ?NS_MIX_0, MIX),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {mix_master_slave, [sequence],
    +     [master_slave_test(all)]}.
    +
    +all_master(Config) ->
    +    MIX = mix_jid(Config),
    +    Room = mix_room_jid(Config),
    +    MyJID = my_jid(Config),
    +    MyBareJID = jid:remove_resource(MyJID),
    +    #iq{type = result,
    +	sub_els =
    +	    [#disco_info{
    +		identities = [#identity{category = <<"conference">>,
    +					type = <<"text">>}],
    +		xdata = [#xdata{type = result, fields = XFields}]}]} =
    +	send_recv(Config, #iq{type = get, to = MIX, sub_els = [#disco_info{}]}),
    +    true = lists:any(
    +	     fun(#xdata_field{var = <<"FORM_TYPE">>,
    +			      values = [?NS_MIX_SERVICEINFO_0]}) -> true;
    +		(_) -> false
    +	     end, XFields),
    +    %% Joining
    +    Nodes = [?NS_MIX_NODES_MESSAGES, ?NS_MIX_NODES_PRESENCE,
    +	     ?NS_MIX_NODES_PARTICIPANTS, ?NS_MIX_NODES_SUBJECT,
    +	     ?NS_MIX_NODES_CONFIG],
    +    #iq{type = result,
    +	sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]} =
    +	send_recv(Config, #iq{type = set, to = Room,
    +			      sub_els = [#mix_join{subscribe = Nodes}]}),
    +    #message{from = Room,
    +	     sub_els =
    +		 [#ps_event{
    +		     items = #ps_items{
    +				node = ?NS_MIX_NODES_PARTICIPANTS,
    +				items = [#ps_item{
    +					    id = ParticipantID,
    +					    xml_els = [PXML]}]}}]} =
    +	recv_message(Config),
    +    #mix_participant{jid = MyBareJID} = xmpp:decode(PXML),
    +    %% Coming online
    +    PresenceID = randoms:get_string(),
    +    Presence = xmpp:encode(#presence{}),
    +    #iq{type = result,
    +	sub_els =
    +	    [#pubsub{
    +		publish = #ps_publish{
    +			     node = ?NS_MIX_NODES_PRESENCE,
    +			     items = [#ps_item{id = PresenceID}]}}]} =
    +	send_recv(
    +	  Config,
    +	  #iq{type = set, to = Room,
    +	      sub_els =
    +		  [#pubsub{
    +		      publish = #ps_publish{
    +				   node = ?NS_MIX_NODES_PRESENCE,
    +				   items = [#ps_item{
    +					       id = PresenceID,
    +					       xml_els = [Presence]}]}}]}),
    +    #message{from = Room,
    +	     sub_els =
    +		 [#ps_event{
    +		     items = #ps_items{
    +				node = ?NS_MIX_NODES_PRESENCE,
    +				items = [#ps_item{
    +					    id = PresenceID,
    +					    xml_els = [Presence]}]}}]} =
    +	recv_message(Config),
    +    %% Coming offline
    +    send(Config, #presence{type = unavailable, to = Room}),
    +    %% Receiving presence retract event
    +    #message{from = Room,
    +	     sub_els = [#ps_event{
    +			   items = #ps_items{
    +				      node = ?NS_MIX_NODES_PRESENCE,
    +				      retract = PresenceID}}]} =
    +	recv_message(Config),
    +    %% Leaving
    +    #iq{type = result, sub_els = []} =
    +	send_recv(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}),
    +    #message{from = Room,
    +	     sub_els =
    +		 [#ps_event{
    +		     items = #ps_items{
    +				node = ?NS_MIX_NODES_PARTICIPANTS,
    +				retract = ParticipantID}}]} =
    +	recv_message(Config),
    +    put_event(Config, disconnect),
    +    disconnect(Config).
    +
    +all_slave(Config) ->
    +    disconnect = get_event(Config),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("mix_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("mix_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("mix_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("mix_" ++ atom_to_list(T) ++ "_slave")]}.
    diff --git a/test/mod_admin_extra_test.exs b/test/mod_admin_extra_test.exs
    index fde66f03f..3baf4922f 100644
    --- a/test/mod_admin_extra_test.exs
    +++ b/test/mod_admin_extra_test.exs
    @@ -38,7 +38,7 @@ defmodule EjabberdModAdminExtraTest do
     
     	setup_all do
     		try do
    -      :jid.start
    +			:jid.start
     			:stringprep.start
     			:mnesia.start
     			:p1_sha.load_nif
    diff --git a/test/mod_legacy.erl b/test/mod_legacy.erl
    new file mode 100644
    index 000000000..dba977554
    --- /dev/null
    +++ b/test/mod_legacy.erl
    @@ -0,0 +1,38 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 25 Sep 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(mod_legacy).
    +-behaviour(gen_mod).
    +
    +%% API
    +-export([start/2, stop/1, mod_opt_type/1, depends/2, process_iq/3]).
    +-include("jlib.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +start(Host, Opts) ->
    +    IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
    +                             one_queue),
    +    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_EVENT,
    +				  ?MODULE, process_iq, IQDisc).
    +
    +stop(Host) ->
    +    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?MODULE).
    +
    +mod_opt_type(_) ->
    +    [].
    +
    +depends(_, _) ->
    +    [].
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +process_iq(_From, _To, IQ) ->
    +    IQ#iq{type = result, sub_el = []}.
    diff --git a/test/muc_tests.erl b/test/muc_tests.erl
    new file mode 100644
    index 000000000..d8e6dd8fb
    --- /dev/null
    +++ b/test/muc_tests.erl
    @@ -0,0 +1,1885 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 15 Oct 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(muc_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [recv_presence/1, send_recv/2, my_jid/1, muc_room_jid/1,
    +		send/2, recv_message/1, recv_iq/1, muc_jid/1,
    +		alt_room_jid/1, wait_for_slave/1, wait_for_master/1,
    +		disconnect/1, put_event/2, get_event/1, peer_muc_jid/1,
    +		my_muc_jid/1, get_features/2, set_opt/3]).
    +-include("suite.hrl").
    +-include("jid.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single tests
    +%%%===================================================================
    +single_cases() ->
    +    {muc_single, [sequence],
    +     [single_test(service_presence_error),
    +      single_test(service_message_error),
    +      single_test(service_unknown_ns_iq_error),
    +      single_test(service_iq_set_error),
    +      single_test(service_improper_iq_error),
    +      single_test(service_features),
    +      single_test(service_disco_info_node_error),
    +      single_test(service_disco_items),
    +      single_test(service_unique),
    +      single_test(service_vcard),
    +      single_test(configure_non_existent),
    +      single_test(cancel_configure_non_existent),
    +      single_test(service_subscriptions)]}.
    +
    +service_presence_error(Config) ->
    +    Service = muc_jid(Config),
    +    ServiceResource = jid:replace_resource(Service, randoms:get_string()),
    +    lists:foreach(
    +      fun(To) ->
    +	      send(Config, #presence{type = error, to = To}),
    +	      lists:foreach(
    +		fun(Type) ->
    +			#presence{type = error} = Err =
    +			    send_recv(Config, #presence{type = Type, to = To}),
    +			#stanza_error{reason = 'service-unavailable'} =
    +			    xmpp:get_error(Err)
    +		end, [available, unavailable])
    +      end, [Service, ServiceResource]),
    +    disconnect(Config).
    +
    +service_message_error(Config) ->
    +    Service = muc_jid(Config),
    +    send(Config, #message{type = error, to = Service}),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #message{type = error} = Err1 =
    +		  send_recv(Config, #message{type = Type, to = Service}),
    +	      #stanza_error{reason = 'forbidden'} = xmpp:get_error(Err1)
    +      end, [chat, normal, headline, groupchat]),
    +    ServiceResource = jid:replace_resource(Service, randoms:get_string()),
    +    send(Config, #message{type = error, to = ServiceResource}),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #message{type = error} = Err2 =
    +		  send_recv(Config, #message{type = Type, to = ServiceResource}),
    +	      #stanza_error{reason = 'service-unavailable'} = xmpp:get_error(Err2)
    +      end, [chat, normal, headline, groupchat]),
    +    disconnect(Config).
    +
    +service_unknown_ns_iq_error(Config) ->
    +    Service = muc_jid(Config),
    +    ServiceResource = jid:replace_resource(Service, randoms:get_string()),
    +    lists:foreach(
    +      fun(To) ->
    +	      send(Config, #iq{type = result, to = To}),
    +	      send(Config, #iq{type = error, to = To}),
    +	      lists:foreach(
    +		fun(Type) ->
    +			#iq{type = error} = Err1 =
    +			    send_recv(Config, #iq{type = Type, to = To,
    +						  sub_els = [#presence{}]}),
    +			#stanza_error{reason = 'service-unavailable'} =
    +			    xmpp:get_error(Err1)
    +		end, [set, get])
    +      end, [Service, ServiceResource]),
    +    disconnect(Config).
    +
    +service_iq_set_error(Config) ->
    +    Service = muc_jid(Config),
    +    lists:foreach(
    +      fun(SubEl) ->
    +	      send(Config, #iq{type = result, to = Service,
    +			       sub_els = [SubEl]}),
    +	      #iq{type = error} = Err2 =
    +		  send_recv(Config, #iq{type = set, to = Service,
    +					sub_els = [SubEl]}),
    +	      #stanza_error{reason = 'not-allowed'} =
    +		  xmpp:get_error(Err2)
    +      end, [#disco_items{}, #disco_info{}, #vcard_temp{},
    +	    #muc_unique{}, #muc_subscriptions{}]),
    +    disconnect(Config).
    +
    +service_improper_iq_error(Config) ->
    +    Service = muc_jid(Config),
    +    lists:foreach(
    +      fun(SubEl) ->
    +	      send(Config, #iq{type = result, to = Service,
    +			       sub_els = [SubEl]}),
    +	      lists:foreach(
    +		fun(Type) ->
    +			#iq{type = error} = Err3 =
    +			    send_recv(Config, #iq{type = Type, to = Service,
    +						  sub_els = [SubEl]}),
    +			#stanza_error{reason = Reason} = xmpp:get_error(Err3),
    +			true = Reason /= 'internal-server-error'
    +		end, [set, get])
    +      end, [#disco_item{jid = Service},
    +	    #identity{category = <<"category">>, type = <<"type">>},
    +	    #vcard_email{}, #muc_subscribe{nick = ?config(nick, Config)}]),
    +    disconnect(Config).
    +
    +service_features(Config) ->
    +    ServerHost = ?config(server_host, Config),
    +    MUC = muc_jid(Config),
    +    Features = sets:from_list(get_features(Config, MUC)),
    +    MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of
    +		      true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1];
    +		      false -> []
    +		  end,
    +    RequiredFeatures = sets:from_list(
    +			 [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
    +			  ?NS_REGISTER, ?NS_MUC, ?NS_RSM,
    +			  ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE
    +			  | MAMFeatures]),
    +    ct:comment("Checking if all needed disco features are set"),
    +    true = sets:is_subset(RequiredFeatures, Features),
    +    disconnect(Config).
    +
    +service_disco_info_node_error(Config) ->
    +    MUC = muc_jid(Config),
    +    Node = randoms:get_string(),
    +    #iq{type = error} = Err =
    +	send_recv(Config, #iq{type = get, to = MUC,
    +			      sub_els = [#disco_info{node = Node}]}),
    +    #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err),
    +    disconnect(Config).
    +
    +service_disco_items(Config) ->
    +    #jid{server = Service} = muc_jid(Config),
    +    Rooms = lists:sort(
    +	      lists:map(
    +		fun(I) ->
    +			RoomName = integer_to_binary(I),
    +			jid:make(RoomName, Service)
    +		end, lists:seq(1, 5))),
    +    lists:foreach(
    +      fun(Room) ->
    +	      ok = join_new(Config, Room)
    +      end, Rooms),
    +    Items = disco_items(Config),
    +    Rooms = [J || #disco_item{jid = J} <- Items],
    +    lists:foreach(
    +      fun(Room) ->
    +	      ok = leave(Config, Room)
    +      end, Rooms),
    +    [] = disco_items(Config),
    +    disconnect(Config).
    +
    +service_vcard(Config) ->
    +    MUC = muc_jid(Config),
    +    ct:comment("Retreiving vCard from ~s", [jid:to_string(MUC)]),
    +    #iq{type = result, sub_els = [#vcard_temp{}]} =
    +	send_recv(Config, #iq{type = get, to = MUC, sub_els = [#vcard_temp{}]}),
    +    disconnect(Config).
    +
    +service_unique(Config) ->
    +    MUC = muc_jid(Config),
    +    ct:comment("Requesting muc unique from ~s", [jid:to_string(MUC)]),
    +    #iq{type = result, sub_els = [#muc_unique{name = Name}]} =
    +	send_recv(Config, #iq{type = get, to = MUC, sub_els = [#muc_unique{}]}),
    +    ct:comment("Checking if unique name is set in the response"),
    +    <<_, _/binary>> = Name,
    +    disconnect(Config).
    +
    +configure_non_existent(Config) ->
    +    [_|_] = get_config(Config),
    +    disconnect(Config).
    +
    +cancel_configure_non_existent(Config) ->
    +    Room = muc_room_jid(Config),
    +    #iq{type = result, sub_els = []} =
    +	send_recv(Config,
    +		  #iq{to = Room, type = set,
    +		      sub_els = [#muc_owner{config = #xdata{type = cancel}}]}),
    +    disconnect(Config).
    +
    +service_subscriptions(Config) ->
    +    MUC = #jid{server = Service} = muc_jid(Config),
    +    Rooms = lists:sort(
    +	      lists:map(
    +		fun(I) ->
    +			RoomName = integer_to_binary(I),
    +			jid:make(RoomName, Service)
    +		end, lists:seq(1, 5))),
    +    lists:foreach(
    +      fun(Room) ->
    +	      ok = join_new(Config, Room),
    +	      [104] = set_config(Config, [{allow_subscription, true}], Room),
    +	      [] = subscribe(Config, [], Room)
    +      end, Rooms),
    +    #iq{type = result, sub_els = [#muc_subscriptions{list = JIDs}]} =
    +	send_recv(Config, #iq{type = get, to = MUC,
    +			      sub_els = [#muc_subscriptions{}]}),
    +    Rooms = lists:sort(JIDs),
    +    lists:foreach(
    +      fun(Room) ->
    +	      ok = unsubscribe(Config, Room),
    +	      ok = leave(Config, Room)
    +      end, Rooms),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {muc_master_slave, [sequence],
    +     [master_slave_test(register),
    +      master_slave_test(groupchat_msg),
    +      master_slave_test(private_msg),
    +      master_slave_test(set_subject),
    +      master_slave_test(history),
    +      master_slave_test(invite),
    +      master_slave_test(invite_members_only),
    +      master_slave_test(invite_password_protected),
    +      master_slave_test(voice_request),
    +      master_slave_test(change_role),
    +      master_slave_test(kick),
    +      master_slave_test(change_affiliation),
    +      master_slave_test(destroy),
    +      master_slave_test(vcard),
    +      master_slave_test(nick_change),
    +      master_slave_test(config_title_desc),
    +      master_slave_test(config_public_list),
    +      master_slave_test(config_password),
    +      master_slave_test(config_whois),
    +      master_slave_test(config_members_only),
    +      master_slave_test(config_moderated),
    +      master_slave_test(config_private_messages),
    +      master_slave_test(config_query),
    +      master_slave_test(config_allow_invites),
    +      master_slave_test(config_visitor_status),
    +      master_slave_test(config_allow_voice_requests),
    +      master_slave_test(config_voice_request_interval),
    +      master_slave_test(config_visitor_nickchange),
    +      master_slave_test(join_conflict)]}.
    +
    +join_conflict_master(Config) ->
    +    ok = join_new(Config),
    +    put_event(Config, join),
    +    ct:comment("Waiting for 'leave' command from the slave"),
    +    leave = get_event(Config),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +join_conflict_slave(Config) ->
    +    NewConfig = set_opt(nick, ?config(peer_nick, Config), Config),
    +    ct:comment("Waiting for 'join' command from the master"),
    +    join = get_event(Config),
    +    ct:comment("Fail trying to join the room with conflicting nick"),
    +    #stanza_error{reason = 'conflict'} = join(NewConfig),
    +    put_event(Config, leave),
    +    disconnect(NewConfig).
    +
    +groupchat_msg_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(slave, Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    ok = master_join(Config),
    +    lists:foreach(
    +      fun(I) ->
    +	      Body = xmpp:mk_text(integer_to_binary(I)),
    +	      send(Config, #message{type = groupchat, to = Room,
    +				    body = Body}),
    +	      #message{type = groupchat, from = MyNickJID,
    +		       body = Body} = recv_message(Config)
    +      end, lists:seq(1, 5)),
    +    #muc_user{items = [#muc_item{jid = PeerJID,
    +				 role = none,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +groupchat_msg_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(master_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    {[], _, _} = slave_join(Config),
    +    lists:foreach(
    +      fun(I) ->
    +	      Body = xmpp:mk_text(integer_to_binary(I)),
    +	      #message{type = groupchat, from = PeerNickJID,
    +		       body = Body} = recv_message(Config)
    +      end, lists:seq(1, 5)),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +private_msg_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(slave, Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    ok = master_join(Config),
    +    lists:foreach(
    +      fun(I) ->
    +	      Body = xmpp:mk_text(integer_to_binary(I)),
    +	      send(Config, #message{type = chat, to = PeerNickJID,
    +				    body = Body})
    +      end, lists:seq(1, 5)),
    +    #muc_user{items = [#muc_item{jid = PeerJID,
    +				 role = none,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ct:comment("Fail trying to send a private message to non-existing occupant"),
    +    send(Config, #message{type = chat, to = PeerNickJID}),
    +    #message{from = PeerNickJID, type = error} = ErrMsg = recv_message(Config),
    +    #stanza_error{reason = 'item-not-found'} = xmpp:get_error(ErrMsg),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +private_msg_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(master_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    {[], _, _} = slave_join(Config),
    +    lists:foreach(
    +      fun(I) ->
    +	      Body = xmpp:mk_text(integer_to_binary(I)),
    +	      #message{type = chat, from = PeerNickJID,
    +		       body = Body} = recv_message(Config)
    +      end, lists:seq(1, 5)),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +set_subject_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(slave, Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    Subject1 = xmpp:mk_text(?config(room_subject, Config)),
    +    Subject2 = xmpp:mk_text(<<"new-", (?config(room_subject, Config))/binary>>),
    +    ok = master_join(Config),
    +    ct:comment("Setting 1st subject"),
    +    send(Config, #message{type = groupchat, to = Room,
    +			  subject = Subject1}),
    +    #message{type = groupchat, from = MyNickJID,
    +	     subject = Subject1} = recv_message(Config),
    +    ct:comment("Waiting for the slave to leave"),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ct:comment("Setting 2nd subject"),
    +    send(Config, #message{type = groupchat, to = Room,
    +			  subject = Subject2}),
    +    #message{type = groupchat, from = MyNickJID,
    +	     subject = Subject2} = recv_message(Config),
    +    ct:comment("Asking the slave to join"),
    +    put_event(Config, join),
    +    recv_muc_presence(Config, PeerNickJID, available),
    +    ct:comment("Receiving 1st subject set by the slave"),
    +    #message{type = groupchat, from = PeerNickJID,
    +	     subject = Subject1} = recv_message(Config),
    +    ct:comment("Disallow subject change"),
    +    [104] = set_config(Config, [{changesubject, false}]),
    +    ct:comment("Waiting for the slave to leave"),
    +    #muc_user{items = [#muc_item{jid = PeerJID,
    +				 role = none,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +set_subject_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    MyNickJID = my_muc_jid(Config),
    +    PeerNick = ?config(master_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    Subject1 = xmpp:mk_text(?config(room_subject, Config)),
    +    Subject2 = xmpp:mk_text(<<"new-", (?config(room_subject, Config))/binary>>),
    +    {[], _, _} = slave_join(Config),
    +    ct:comment("Receiving 1st subject set by the master"),
    +    #message{type = groupchat, from = PeerNickJID,
    +	     subject = Subject1} = recv_message(Config),
    +    ok = leave(Config),
    +    ct:comment("Waiting for 'join' command from the master"),
    +    join = get_event(Config),
    +    {[], SubjMsg2, _} = join(Config),
    +    ct:comment("Checking if the master has set 2nd subject during our absence"),
    +    #message{type = groupchat, from = PeerNickJID,
    +	     subject = Subject2} = SubjMsg2,
    +    ct:comment("Setting 1st subject"),
    +    send(Config, #message{to = Room, type = groupchat, subject = Subject1}),
    +    #message{type = groupchat, from = MyNickJID,
    +	     subject = Subject1} = recv_message(Config),
    +    ct:comment("Waiting for the master to disallow subject change"),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Fail trying to change the subject"),
    +    send(Config, #message{to = Room, type = groupchat, subject = Subject2}),
    +    #message{from = Room, type = error} = ErrMsg = recv_message(Config),
    +    #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +history_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    ServerHost = ?config(server_host, Config),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    PeerNickJID = peer_muc_jid(Config),
    +    Size = gen_mod:get_module_opt(ServerHost, mod_muc, history_size,
    +				  fun(I) when is_integer(I), I>=0 -> I end,
    +				  20),
    +    ok = join_new(Config),
    +    ct:comment("Putting ~p+1 messages in the history", [Size]),
    +    %% Only Size messages will be stored
    +    lists:foreach(
    +      fun(I) ->
    +	      Body = xmpp:mk_text(integer_to_binary(I)),
    +	      send(Config, #message{to = Room, type = groupchat,
    +				    body = Body}),
    +	      #message{type = groupchat, from = MyNickJID,
    +		       body = Body} = recv_message(Config)
    +      end, lists:seq(0, Size)),
    +    put_event(Config, join),
    +    lists:foreach(
    +      fun(Type) ->
    +	      recv_muc_presence(Config, PeerNickJID, Type)
    +      end, [available, unavailable,
    +	    available, unavailable,
    +	    available, unavailable,
    +	    available, unavailable]),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +history_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(peer_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    ServerHost = ?config(server_host, Config),
    +    Size = gen_mod:get_module_opt(ServerHost, mod_muc, history_size,
    +				  fun(I) when is_integer(I), I>=0 -> I end,
    +				  20),
    +    ct:comment("Waiting for 'join' command from the master"),
    +    join = get_event(Config),
    +    {History, _, _} = join(Config),
    +    ct:comment("Checking ordering of history events"),
    +    BodyList = [binary_to_integer(xmpp:get_text(Body))
    +		|| #message{type = groupchat, from = From,
    +			    body = Body} <- History,
    +		   From == PeerNickJID],
    +    BodyList = lists:seq(1, Size),
    +    ok = leave(Config),
    +    %% If the client wishes to receive no history, it MUST set the 'maxchars'
    +    %% attribute to a value of "0" (zero)
    +    %% (http://xmpp.org/extensions/xep-0045.html#enter-managehistory)
    +    ct:comment("Checking if maxchars=0 yields to no history"),
    +    {[], _, _} = join(Config, #muc{history = #muc_history{maxchars = 0}}),
    +    ok = leave(Config),
    +    ct:comment("Receiving only 10 last stanzas"),
    +    {History10, _, _} = join(Config,
    +				 #muc{history = #muc_history{maxstanzas = 10}}),
    +    BodyList10 = [binary_to_integer(xmpp:get_text(Body))
    +		  || #message{type = groupchat, from = From,
    +			      body = Body} <- History10,
    +		     From == PeerNickJID],
    +    BodyList10 = lists:nthtail(Size-10, lists:seq(1, Size)),
    +    ok = leave(Config),
    +    #delay{stamp = TS} = xmpp:get_subtag(hd(History), #delay{}),
    +    ct:comment("Receiving all history without the very first element"),
    +    {HistoryWithoutFirst, _, _} = join(Config,
    +					   #muc{history = #muc_history{since = TS}}),
    +    BodyListWithoutFirst = [binary_to_integer(xmpp:get_text(Body))
    +			    || #message{type = groupchat, from = From,
    +					body = Body} <- HistoryWithoutFirst,
    +			       From == PeerNickJID],
    +    BodyListWithoutFirst = lists:nthtail(1, lists:seq(1, Size)),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +invite_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(peer, Config),
    +    ok = join_new(Config),
    +    wait_for_slave(Config),
    +    %% Inviting the peer
    +    send(Config, #message{to = Room, type = normal,
    +			  sub_els =
    +			      [#muc_user{
    +				  invites =
    +				      [#muc_invite{to = PeerJID}]}]}),
    +    #message{from = Room} = DeclineMsg = recv_message(Config),
    +    #muc_user{decline = #muc_decline{from = PeerJID}} =
    +	xmpp:get_subtag(DeclineMsg, #muc_user{}),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +invite_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    wait_for_master(Config),
    +    PeerJID = ?config(master, Config),
    +    #message{from = Room, type = normal} = Msg = recv_message(Config),
    +    #muc_user{invites = [#muc_invite{from = PeerJID}]} =
    +	xmpp:get_subtag(Msg, #muc_user{}),
    +    %% Decline invitation
    +    send(Config,
    +	 #message{to = Room,
    +		  sub_els = [#muc_user{
    +				decline = #muc_decline{to = PeerJID}}]}),
    +    disconnect(Config).
    +
    +invite_members_only_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(slave, Config),
    +    ok = join_new(Config),
    +    %% Setting the room to members-only
    +    [_|_] = set_config(Config, [{membersonly, true}]),
    +    wait_for_slave(Config),
    +    %% Inviting the peer
    +    send(Config, #message{to = Room, type = normal,
    +			  sub_els =
    +			      [#muc_user{
    +				  invites =
    +				      [#muc_invite{to = PeerJID}]}]}),
    +    #message{from = Room, type = normal} = AffMsg = recv_message(Config),
    +    #muc_user{items = [#muc_item{jid = PeerJID, affiliation = member}]} =
    +	xmpp:get_subtag(AffMsg, #muc_user{}),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +invite_members_only_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    wait_for_master(Config),
    +    %% Receiving invitation
    +    #message{from = Room, type = normal} = recv_message(Config),
    +    disconnect(Config).
    +
    +invite_password_protected_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(slave, Config),
    +    Password = randoms:get_string(),
    +    ok = join_new(Config),
    +    [104] = set_config(Config, [{passwordprotectedroom, true},
    +                                    {roomsecret, Password}]),
    +    put_event(Config, Password),
    +    %% Inviting the peer
    +    send(Config, #message{to = Room, type = normal,
    +			  sub_els =
    +			      [#muc_user{
    +				  invites =
    +				      [#muc_invite{to = PeerJID}]}]}),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +invite_password_protected_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    Password = get_event(Config),
    +    %% Receiving invitation
    +    #message{from = Room, type = normal} = Msg = recv_message(Config),
    +    #muc_user{password = Password} = xmpp:get_subtag(Msg, #muc_user{}),
    +    disconnect(Config).
    +
    +voice_request_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(slave, Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    ok = join_new(Config),
    +    [104] = set_config(Config, [{members_by_default, false}]),
    +    wait_for_slave(Config),
    +    #muc_user{
    +       items = [#muc_item{role = visitor,
    +			  jid = PeerJID,
    +			  affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    ct:comment("Receiving voice request"),
    +    #message{from = Room, type = normal} = VoiceReq = recv_message(Config),
    +    #xdata{type = form, fields = Fs} = xmpp:get_subtag(VoiceReq, #xdata{}),
    +    [{jid, PeerJID},
    +     {request_allow, false},
    +     {role, participant},
    +     {roomnick, PeerNick}] = lists:sort(muc_request:decode(Fs)),
    +    ct:comment("Approving voice request"),
    +    ApprovalFs = muc_request:encode([{jid, PeerJID}, {role, participant},
    +				     {nick, PeerNick}, {request_allow, true}]),
    +    send(Config, #message{to = Room, sub_els = [#xdata{type = submit,
    +						       fields = ApprovalFs}]}),
    +    #muc_user{
    +       items = [#muc_item{role = participant,
    +			  jid = PeerJID,
    +			  affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    ct:comment("Waiting for the slave to leave"),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +voice_request_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    MyJID = my_jid(Config),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    wait_for_master(Config),
    +    {[], _, _} = join(Config, visitor),
    +    ct:comment("Requesting voice"),
    +    Fs = muc_request:encode([{role, participant}]),
    +    X = #xdata{type = submit, fields = Fs},
    +    send(Config, #message{to = Room, sub_els = [X]}),
    +    ct:comment("Waiting to become a participant"),
    +    #muc_user{
    +       items = [#muc_item{role = participant,
    +			  jid = MyJID,
    +			  affiliation = none}]} =
    +	recv_muc_presence(Config, MyNickJID, available),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +change_role_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    MyJID = my_jid(Config),
    +    MyNick = ?config(nick, Config),
    +    PeerJID = ?config(slave, Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    ok = join_new(Config),
    +    ct:comment("Waiting for the slave to join"),
    +    wait_for_slave(Config),
    +    #muc_user{items = [#muc_item{role = participant,
    +				 jid = PeerJID,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    lists:foreach(
    +      fun(Role) ->
    +	      ct:comment("Checking if the slave is not in the roles list"),
    +	      case get_role(Config, Role) of
    +		  [#muc_item{jid = MyJID, affiliation = owner,
    +			     role = moderator, nick = MyNick}] when Role == moderator ->
    +		      ok;
    +		  [] ->
    +		      ok
    +	      end,
    +	      Reason = randoms:get_string(),
    +	      put_event(Config, {Role, Reason}),
    +	      ok = set_role(Config, Role, Reason),
    +	      ct:comment("Receiving role change to ~s", [Role]),
    +	      #muc_user{
    +		 items = [#muc_item{role = Role,
    +				    affiliation = none,
    +				    reason = Reason}]} =
    +		  recv_muc_presence(Config, PeerNickJID, available),
    +	      [#muc_item{role = Role, affiliation = none,
    +			 nick = PeerNick}|_] = get_role(Config, Role)
    +      end, [visitor, participant, moderator]),
    +    put_event(Config, disconnect),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +change_role_slave(Config) ->
    +    wait_for_master(Config),
    +    {[], _, _} = join(Config),
    +    change_role_slave(Config, get_event(Config)).
    +
    +change_role_slave(Config, {Role, Reason}) ->
    +    Room = muc_room_jid(Config),
    +    MyNick = ?config(slave_nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    ct:comment("Receiving role change to ~s", [Role]),
    +    #muc_user{status_codes = Codes,
    +	      items = [#muc_item{role = Role,
    +				 affiliation = none,
    +				 reason = Reason}]} =
    +	recv_muc_presence(Config, MyNickJID, available),
    +    true = lists:member(110, Codes),
    +    change_role_slave(Config, get_event(Config));
    +change_role_slave(Config, disconnect) ->
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +change_affiliation_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    MyJID = my_jid(Config),
    +    MyBareJID = jid:remove_resource(MyJID),
    +    MyNick = ?config(nick, Config),
    +    PeerJID = ?config(slave, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    ok = join_new(Config),
    +    ct:comment("Waiting for the slave to join"),
    +    wait_for_slave(Config),
    +    #muc_user{items = [#muc_item{role = participant,
    +				 jid = PeerJID,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    lists:foreach(
    +      fun({Aff, Role, Status}) ->
    +	      ct:comment("Checking if slave is not in affiliation list"),
    +	      case get_affiliation(Config, Aff) of
    +		  [#muc_item{jid = MyBareJID,
    +			     affiliation = owner}] when Aff == owner ->
    +		      ok;
    +		  [] ->
    +		      ok
    +	      end,
    +	      Reason = randoms:get_string(),
    +	      put_event(Config, {Aff, Role, Status, Reason}),
    +	      ok = set_affiliation(Config, Aff, Reason),
    +	      ct:comment("Receiving affiliation change to ~s", [Aff]),
    +	      #muc_user{
    +		 items = [#muc_item{role = Role,
    +				    affiliation = Aff,
    +				    actor = Actor,
    +				    reason = Reason}]} =
    +		  recv_muc_presence(Config, PeerNickJID, Status),
    +	      if Aff == outcast ->
    +		      ct:comment("Checking if actor is set"),
    +		      #muc_actor{nick = MyNick} = Actor;
    +		 true ->
    +		      ok
    +	      end,
    +	      Affs = get_affiliation(Config, Aff),
    +	      ct:comment("Checking if the affiliation was correctly set"),
    +	      case lists:keyfind(PeerBareJID, #muc_item.jid, Affs) of
    +		  false when Aff == none ->
    +		      ok;
    +		  #muc_item{affiliation = Aff} ->
    +		      ok
    +	      end
    +      end, [{member, participant, available}, {none, participant, available},
    +	    {admin, moderator, available}, {owner, moderator, available},
    +	    {outcast, none, unavailable}]),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +change_affiliation_slave(Config) ->
    +    wait_for_master(Config),
    +    {[], _, _} = join(Config),
    +    change_affiliation_slave(Config, get_event(Config)).
    +
    +change_affiliation_slave(Config, {Aff, Role, Status, Reason}) ->
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(master_nick, Config),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    ct:comment("Receiving affiliation change to ~s", [Aff]),
    +    #muc_user{status_codes = Codes,
    +	      items = [#muc_item{role = Role,
    +				 actor = Actor,
    +				 affiliation = Aff,
    +				 reason = Reason}]} =
    +	recv_muc_presence(Config, MyNickJID, Status),
    +    true = lists:member(110, Codes),
    +    if Aff == outcast ->
    +	    ct:comment("Checking for status code '301' (banned)"),
    +	    true = lists:member(301, Codes),
    +	    ct:comment("Checking if actor is set"),
    +	    #muc_actor{nick = PeerNick} = Actor,
    +	    disconnect(Config);
    +       true ->
    +	    change_affiliation_slave(Config, get_event(Config))
    +    end.
    +
    +kick_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    MyNick = ?config(nick, Config),
    +    PeerJID = ?config(slave, Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    Reason = <<"Testing">>,
    +    ok = join_new(Config),
    +    ct:comment("Waiting for the slave to join"),
    +    wait_for_slave(Config),
    +    #muc_user{items = [#muc_item{role = participant,
    +				 jid = PeerJID,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    [#muc_item{role = participant, affiliation = none,
    +	       nick = PeerNick}|_] = get_role(Config, participant),
    +    ct:comment("Kicking slave"),
    +    ok = set_role(Config, none, Reason),
    +    ct:comment("Receiving role change to 'none'"),
    +    #muc_user{
    +       status_codes = Codes,
    +       items = [#muc_item{role = none,
    +			  affiliation = none,
    +			  actor = #muc_actor{nick = MyNick},
    +			  reason = Reason}]} =
    +	recv_muc_presence(Config, PeerNickJID, unavailable),
    +    [] = get_role(Config, participant),
    +    ct:comment("Checking if the code is '307' (kicked)"),
    +    true = lists:member(307, Codes),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +kick_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(master_nick, Config),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    Reason = <<"Testing">>,
    +    wait_for_master(Config),
    +    {[], _, _} = join(Config),
    +    ct:comment("Receiving role change to 'none'"),
    +    #muc_user{status_codes = Codes,
    +	      items = [#muc_item{role = none,
    +				 affiliation = none,
    +				 actor = #muc_actor{nick = PeerNick},
    +				 reason = Reason}]} =
    +	recv_muc_presence(Config, MyNickJID, unavailable),
    +    ct:comment("Checking if codes '110' (self-presence) "
    +	       "and '307' (kicked) are present"),
    +    true = lists:member(110, Codes),
    +    true = lists:member(307, Codes),
    +    disconnect(Config).
    +
    +destroy_master(Config) ->
    +    Reason = <<"Testing">>,
    +    Room = muc_room_jid(Config),
    +    AltRoom = alt_room_jid(Config),
    +    PeerJID = ?config(peer, Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    ok = join_new(Config),
    +    ct:comment("Waiting for slave to join"),
    +    wait_for_slave(Config),
    +    #muc_user{items = [#muc_item{role = participant,
    +				 jid = PeerJID,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    wait_for_slave(Config),
    +    ok = destroy(Config, Reason),
    +    ct:comment("Receiving destruction presence"),
    +    #muc_user{items = [#muc_item{role = none,
    +				 affiliation = none}],
    +	      destroy = #muc_destroy{jid = AltRoom,
    +				     reason = Reason}} =
    +	recv_muc_presence(Config, MyNickJID, unavailable),
    +    disconnect(Config).
    +
    +destroy_slave(Config) ->
    +    Reason = <<"Testing">>,
    +    Room = muc_room_jid(Config),
    +    AltRoom = alt_room_jid(Config),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    wait_for_master(Config),
    +    {[], _, _} = join(Config),
    +    #stanza_error{reason = 'forbidden'} = destroy(Config, Reason),
    +    wait_for_master(Config),
    +    ct:comment("Receiving destruction presence"),
    +    #muc_user{items = [#muc_item{role = none,
    +				 affiliation = none}],
    +	      destroy = #muc_destroy{jid = AltRoom,
    +				     reason = Reason}} =
    +	recv_muc_presence(Config, MyNickJID, unavailable),
    +    disconnect(Config).
    +
    +vcard_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    FN = randoms:get_string(),
    +    VCard = #vcard_temp{fn = FN},
    +    ok = join_new(Config),
    +    ct:comment("Waiting for slave to join"),
    +    wait_for_slave(Config),
    +    #muc_user{items = [#muc_item{role = participant,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    #stanza_error{reason = 'item-not-found'} = get_vcard(Config),
    +    ok = set_vcard(Config, VCard),
    +    VCard = get_vcard(Config),
    +    put_event(Config, VCard),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    leave = get_event(Config),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +vcard_slave(Config) ->
    +    wait_for_master(Config),
    +    {[], _, _} = join(Config),
    +    VCard = get_event(Config),
    +    VCard = get_vcard(Config),
    +    #stanza_error{reason = 'forbidden'} = set_vcard(Config, VCard),
    +    ok = leave(Config),
    +    VCard = get_vcard(Config),
    +    put_event(Config, leave),
    +    disconnect(Config).
    +
    +nick_change_master(Config) ->
    +    NewNick = randoms:get_string(),
    +    PeerJID = ?config(peer, Config),
    +    PeerNickJID = peer_muc_jid(Config),
    +    ok = master_join(Config),
    +    put_event(Config, {new_nick, NewNick}),
    +    ct:comment("Waiting for nickchange presence from the slave"),
    +    #muc_user{status_codes = Codes,
    +	      items = [#muc_item{jid = PeerJID,
    +				 nick = NewNick}]} =
    +	recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ct:comment("Checking if code '303' (nick change) is set"),
    +    true = lists:member(303, Codes),
    +    ct:comment("Waiting for updated presence from the slave"),
    +    PeerNewNickJID = jid:replace_resource(PeerNickJID, NewNick),
    +    recv_muc_presence(Config, PeerNewNickJID, available),
    +    ct:comment("Waiting for the slave to leave"),
    +    recv_muc_presence(Config, PeerNewNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +nick_change_slave(Config) ->
    +    MyJID = my_jid(Config),
    +    MyNickJID = my_muc_jid(Config),
    +    {[], _, _} = slave_join(Config),
    +    {new_nick, NewNick} = get_event(Config),
    +    MyNewNickJID = jid:replace_resource(MyNickJID, NewNick),
    +    ct:comment("Sending new presence"),
    +    send(Config, #presence{to = MyNewNickJID}),
    +    ct:comment("Receiving nickchange self-presence"),
    +    #muc_user{status_codes = Codes1,
    +	      items = [#muc_item{role = participant,
    +				 jid = MyJID,
    +				 nick = NewNick}]} =
    +	recv_muc_presence(Config, MyNickJID, unavailable),
    +    ct:comment("Checking if codes '110' (self-presence) and "
    +	       "'303' (nickchange) are present"),
    +    lists:member(110, Codes1),
    +    lists:member(303, Codes1),
    +    ct:comment("Receiving self-presence update"),
    +    #muc_user{status_codes = Codes2,
    +	      items = [#muc_item{jid = MyJID,
    +				 role = participant}]} =
    +	recv_muc_presence(Config, MyNewNickJID, available),
    +    ct:comment("Checking if code '110' (self-presence) is set"),
    +    lists:member(110, Codes2),
    +    NewConfig = set_opt(nick, NewNick, Config),
    +    ok = leave(NewConfig),
    +    disconnect(NewConfig).
    +
    +config_title_desc_master(Config) ->
    +    Title = randoms:get_string(),
    +    Desc = randoms:get_string(),
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    ok = master_join(Config),
    +    [104] = set_config(Config, [{roomname, Title}, {roomdesc, Desc}]),
    +    RoomCfg = get_config(Config),
    +    Title = proplists:get_value(roomname, RoomCfg),
    +    Desc = proplists:get_value(roomdesc, RoomCfg),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_title_desc_slave(Config) ->
    +    {[], _, _} = slave_join(Config),
    +    [104] = recv_config_change_message(Config),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_public_list_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    ok = join_new(Config),
    +    wait_for_slave(Config),
    +    recv_muc_presence(Config, PeerNickJID, available),
    +    lists:member(<<"muc_public">>, get_features(Config, Room)),
    +    [104] = set_config(Config, [{public_list, false},
    +				    {publicroom, false}]),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    lists:member(<<"muc_hidden">>, get_features(Config, Room)),
    +    wait_for_slave(Config),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_public_list_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    wait_for_master(Config),
    +    PeerNick = ?config(peer_nick, Config),
    +    PeerNickJID = peer_muc_jid(Config),
    +    [#disco_item{jid = Room}] = disco_items(Config),
    +    [#disco_item{jid = PeerNickJID,
    +		 name = PeerNick}] = disco_room_items(Config),
    +    {[], _, _} = join(Config),
    +    [104] = recv_config_change_message(Config),
    +    ok = leave(Config),
    +    [] = disco_items(Config),
    +    [] = disco_room_items(Config),
    +    wait_for_master(Config),
    +    disconnect(Config).
    +
    +config_password_master(Config) ->
    +    Password = randoms:get_string(),
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    ok = join_new(Config),
    +    lists:member(<<"muc_unsecured">>, get_features(Config, Room)),
    +    [104] = set_config(Config, [{passwordprotectedroom, true},
    +				    {roomsecret, Password}]),
    +    lists:member(<<"muc_passwordprotected">>, get_features(Config, Room)),
    +    put_event(Config, Password),
    +    recv_muc_presence(Config, PeerNickJID, available),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_password_slave(Config) ->
    +    Password = get_event(Config),
    +    #stanza_error{reason = 'not-authorized'} = join(Config),
    +    #stanza_error{reason = 'not-authorized'} =
    +	join(Config, #muc{password = randoms:get_string()}),
    +    {[], _, _} = join(Config, #muc{password = Password}),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_whois_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerNickJID = peer_muc_jid(Config),
    +    MyNickJID = my_muc_jid(Config),
    +    ok = master_join(Config),
    +    lists:member(<<"muc_semianonymous">>, get_features(Config, Room)),
    +    [172] = set_config(Config, [{whois, anyone}]),
    +    lists:member(<<"muc_nonanonymous">>, get_features(Config, Room)),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    recv_muc_presence(Config, PeerNickJID, available),
    +    send(Config, #presence{to = Room}),
    +    recv_muc_presence(Config, MyNickJID, available),
    +    [173] = set_config(Config, [{whois, moderators}]),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_whois_slave(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    PeerNickJID = peer_muc_jid(Config),
    +    {[], _, _} = slave_join(Config),
    +    ct:comment("Checking if the room becomes non-anonymous (code '172')"),
    +    [172] = recv_config_change_message(Config),
    +    ct:comment("Re-joining in order to check status codes"),
    +    ok = leave(Config),
    +    {[], _, Codes} = join(Config),
    +    ct:comment("Checking if code '100' (non-anonymous) present"),
    +    true = lists:member(100, Codes),
    +    ct:comment("Receiving presence from peer with JID exposed"),
    +    #muc_user{items = [#muc_item{jid = PeerJID}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    ct:comment("Waiting for the room to become anonymous again (code '173')"),
    +    [173] = recv_config_change_message(Config),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_members_only_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    PeerNickJID = peer_muc_jid(Config),
    +    ok = master_join(Config),
    +    lists:member(<<"muc_open">>, get_features(Config, Room)),
    +    [104] = set_config(Config, [{membersonly, true}]),
    +    #muc_user{status_codes = Codes,
    +	      items = [#muc_item{jid = PeerJID,
    +				 affiliation = none,
    +				 role = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ct:comment("Checking if code '322' (non-member) is set"),
    +    true = lists:member(322, Codes),
    +    lists:member(<<"muc_membersonly">>, get_features(Config, Room)),
    +    ct:comment("Waiting for slave to fail joining the room"),
    +    set_member = get_event(Config),
    +    ok = set_affiliation(Config, member, randoms:get_string()),
    +    #message{from = Room, type = normal} = Msg = recv_message(Config),
    +    #muc_user{items = [#muc_item{jid = PeerBareJID,
    +				 affiliation = member}]} =
    +	xmpp:get_subtag(Msg, #muc_user{}),
    +    ct:comment("Asking peer to join"),
    +    put_event(Config, join),
    +    ct:comment("Waiting for peer to join"),
    +    recv_muc_presence(Config, PeerNickJID, available),
    +    ok = set_affiliation(Config, none, randoms:get_string()),
    +    ct:comment("Waiting for peer to be kicked"),
    +    #muc_user{status_codes = NewCodes,
    +	      items = [#muc_item{affiliation = none,
    +				 role = none}]} =
    +	recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ct:comment("Checking if code '321' (became non-member in "
    +	       "members-only room) is set"),
    +    true = lists:member(321, NewCodes),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_members_only_slave(Config) ->
    +    MyJID = my_jid(Config),
    +    MyNickJID = my_muc_jid(Config),
    +    {[], _, _} = slave_join(Config),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Getting kicked because the room has become members-only"),
    +    #muc_user{status_codes = Codes,
    +	      items = [#muc_item{jid = MyJID,
    +				 role = none,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, MyNickJID, unavailable),
    +    ct:comment("Checking if the code '110' (self-presence) "
    +	       "and '322' (non-member) is set"),
    +    true = lists:member(110, Codes),
    +    true = lists:member(322, Codes),
    +    ct:comment("Fail trying to join members-only room"),
    +    #stanza_error{reason = 'registration-required'} = join(Config),
    +    ct:comment("Asking the peer to set us member"),
    +    put_event(Config, set_member),
    +    ct:comment("Waiting for the peer to ask for join"),
    +    join = get_event(Config),
    +    {[], _, _} = join(Config, participant, member),
    +    #muc_user{status_codes = NewCodes,
    +	      items = [#muc_item{jid = MyJID,
    +				 role = none,
    +				 affiliation = none}]} =
    +	recv_muc_presence(Config, MyNickJID, unavailable),
    +    ct:comment("Checking if the code '110' (self-presence) "
    +	       "and '321' (became non-member in members-only room) is set"),
    +    true = lists:member(110, NewCodes),
    +    true = lists:member(321, NewCodes),
    +    disconnect(Config).
    +
    +config_moderated_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerNickJID = peer_muc_jid(Config),
    +    ok = master_join(Config),
    +    lists:member(<<"muc_moderated">>, get_features(Config, Room)),
    +    ok = set_role(Config, visitor, randoms:get_string()),
    +    #muc_user{items = [#muc_item{role = visitor}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    set_unmoderated = get_event(Config),
    +    [104] = set_config(Config, [{moderatedroom, false}]),
    +    #message{from = PeerNickJID, type = groupchat} = recv_message(Config),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    lists:member(<<"muc_unmoderated">>, get_features(Config, Room)),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_moderated_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    MyNickJID = my_muc_jid(Config),
    +    {[], _, _} = slave_join(Config),
    +    #muc_user{items = [#muc_item{role = visitor}]} =
    +	recv_muc_presence(Config, MyNickJID, available),
    +    send(Config, #message{to = Room, type = groupchat}),
    +    ErrMsg = #message{from = Room, type = error} = recv_message(Config),
    +    #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg),
    +    put_event(Config, set_unmoderated),
    +    [104] = recv_config_change_message(Config),
    +    send(Config, #message{to = Room, type = groupchat}),
    +    #message{from = MyNickJID, type = groupchat} = recv_message(Config),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_private_messages_master(Config) ->
    +    PeerNickJID = peer_muc_jid(Config),
    +    ok = master_join(Config),
    +    ct:comment("Waiting for a private message from the slave"),
    +    #message{from = PeerNickJID, type = chat} = recv_message(Config),
    +    ok = set_role(Config, visitor, <<>>),
    +    ct:comment("Waiting for the peer to become a visitor"),
    +    recv_muc_presence(Config, PeerNickJID, available),
    +    ct:comment("Waiting for a private message from the slave"),
    +    #message{from = PeerNickJID, type = chat} = recv_message(Config),
    +    [104] = set_config(Config, [{allow_private_messages_from_visitors, moderators}]),
    +    ct:comment("Waiting for a private message from the slave"),
    +    #message{from = PeerNickJID, type = chat} = recv_message(Config),
    +    [104] = set_config(Config, [{allow_private_messages_from_visitors, nobody}]),
    +    wait_for_slave(Config),
    +    [104] = set_config(Config, [{allow_private_messages_from_visitors, anyone},
    +				    {allow_private_messages, false}]),
    +    ct:comment("Fail trying to send a private message"),
    +    send(Config, #message{to = PeerNickJID, type = chat}),
    +    #message{from = PeerNickJID, type = error} = ErrMsg = recv_message(Config),
    +    #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg),
    +    ok = set_role(Config, participant, <<>>),
    +    ct:comment("Waiting for the peer to become a participant"),
    +    recv_muc_presence(Config, PeerNickJID, available),
    +    ct:comment("Waiting for the peer to leave"),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_private_messages_slave(Config) ->
    +    MyNickJID = my_muc_jid(Config),
    +    PeerNickJID = peer_muc_jid(Config),
    +    {[], _, _} = slave_join(Config),
    +    ct:comment("Sending a private message"),
    +    send(Config, #message{to = PeerNickJID, type = chat}),
    +    ct:comment("Waiting to become a visitor"),
    +    #muc_user{items = [#muc_item{role = visitor}]} =
    +	recv_muc_presence(Config, MyNickJID, available),
    +    ct:comment("Sending a private message"),
    +    send(Config, #message{to = PeerNickJID, type = chat}),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Sending a private message"),
    +    send(Config, #message{to = PeerNickJID, type = chat}),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Fail trying to send a private message"),
    +    send(Config, #message{to = PeerNickJID, type = chat}),
    +    #message{from = PeerNickJID, type = error} = ErrMsg1 = recv_message(Config),
    +    #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg1),
    +    wait_for_master(Config),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Waiting to become a participant again"),
    +    #muc_user{items = [#muc_item{role = participant}]} =
    +	recv_muc_presence(Config, MyNickJID, available),
    +    ct:comment("Fail trying to send a private message"),
    +    send(Config, #message{to = PeerNickJID, type = chat}),
    +    #message{from = PeerNickJID, type = error} = ErrMsg2 = recv_message(Config),
    +    #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg2),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_query_master(Config) ->
    +    PeerNickJID = peer_muc_jid(Config),
    +    ok = join_new(Config),
    +    wait_for_slave(Config),
    +    recv_muc_presence(Config, PeerNickJID, available),
    +    ct:comment("Receiving IQ query from the slave"),
    +    #iq{type = get, from = PeerNickJID, id = I,
    +	sub_els = [#ping{}]} = recv_iq(Config),
    +    send(Config, #iq{type = result, to = PeerNickJID, id = I}),
    +    [104] = set_config(Config, [{allow_query_users, false}]),
    +    ct:comment("Fail trying to send IQ"),
    +    #iq{type = error, from = PeerNickJID} = Err =
    +	send_recv(Config, #iq{type = get, to = PeerNickJID,
    +			      sub_els = [#ping{}]}),
    +    #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_query_slave(Config) ->
    +    PeerNickJID = peer_muc_jid(Config),
    +    wait_for_master(Config),
    +    ct:comment("Checking if IQ queries are denied from non-occupants"),
    +    #iq{type = error, from = PeerNickJID} = Err1 =
    +	send_recv(Config, #iq{type = get, to = PeerNickJID,
    +			      sub_els = [#ping{}]}),
    +    #stanza_error{reason = 'not-acceptable'} = xmpp:get_error(Err1),
    +    {[], _, _} = join(Config),
    +    ct:comment("Sending IQ to the master"),
    +    #iq{type = result, from = PeerNickJID, sub_els = []} =
    +	send_recv(Config, #iq{to = PeerNickJID, type = get, sub_els = [#ping{}]}),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Fail trying to send IQ"),
    +    #iq{type = error, from = PeerNickJID} = Err2 =
    +	send_recv(Config, #iq{type = get, to = PeerNickJID,
    +			      sub_els = [#ping{}]}),
    +    #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err2),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_allow_invites_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(peer, Config),
    +    PeerNickJID = peer_muc_jid(Config),
    +    ok = master_join(Config),
    +    [104] = set_config(Config, [{allowinvites, true}]),
    +    ct:comment("Receiving an invitation from the slave"),
    +    #message{from = Room, type = normal} = recv_message(Config),
    +    [104] = set_config(Config, [{allowinvites, false}]),
    +    send_invitation = get_event(Config),
    +    ct:comment("Sending an invitation"),
    +    send(Config, #message{to = Room, type = normal,
    +			  sub_els =
    +			      [#muc_user{
    +				  invites =
    +				      [#muc_invite{to = PeerJID}]}]}),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_allow_invites_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(peer, Config),
    +    InviteMsg = #message{to = Room, type = normal,
    +			 sub_els =
    +			     [#muc_user{
    +				 invites =
    +				     [#muc_invite{to = PeerJID}]}]},
    +    {[], _, _} = slave_join(Config),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Sending an invitation"),
    +    send(Config, InviteMsg),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Fail sending an invitation"),
    +    send(Config, InviteMsg),
    +    #message{from = Room, type = error} = Err = recv_message(Config),
    +    #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err),
    +    ct:comment("Checking if the master is still able to send invitations"),
    +    put_event(Config, send_invitation),
    +    #message{from = Room, type = normal} = recv_message(Config),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_visitor_status_master(Config) ->
    +    PeerNickJID = peer_muc_jid(Config),
    +    Status = xmpp:mk_text(randoms:get_string()),
    +    ok = join_new(Config),
    +    [104] = set_config(Config, [{members_by_default, false}]),
    +    ct:comment("Asking the slave to join as a visitor"),
    +    put_event(Config, {join, Status}),
    +    #muc_user{items = [#muc_item{role = visitor}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    ct:comment("Receiving status change from the visitor"),
    +    #presence{from = PeerNickJID, status = Status} = recv_presence(Config),
    +    [104] = set_config(Config, [{allow_visitor_status, false}]),
    +    ct:comment("Receiving status change with  stripped"),
    +    #presence{from = PeerNickJID, status = []} = recv_presence(Config),
    +    ct:comment("Waiting for the slave to leave"),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_visitor_status_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    MyNickJID = my_muc_jid(Config),
    +    ct:comment("Waiting for 'join' command from the master"),
    +    {join, Status} = get_event(Config),
    +    {[], _, _} = join(Config, visitor, none),
    +    ct:comment("Sending status change"),
    +    send(Config, #presence{to = Room, status = Status}),
    +    #presence{from = MyNickJID, status = Status} = recv_presence(Config),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Sending status change again"),
    +    send(Config, #presence{to = Room, status = Status}),
    +    #presence{from = MyNickJID, status = []} = recv_presence(Config),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_allow_voice_requests_master(Config) ->
    +    PeerNickJID = peer_muc_jid(Config),
    +    ok = join_new(Config),
    +    [104] = set_config(Config, [{members_by_default, false}]),
    +    ct:comment("Asking the slave to join as a visitor"),
    +    put_event(Config, join),
    +    #muc_user{items = [#muc_item{role = visitor}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    [104] = set_config(Config, [{allow_voice_requests, false}]),
    +    ct:comment("Waiting for the slave to leave"),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_allow_voice_requests_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    ct:comment("Waiting for 'join' command from the master"),
    +    join = get_event(Config),
    +    {[], _, _} = join(Config, visitor),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Fail sending voice request"),
    +    Fs = muc_request:encode([{role, participant}]),
    +    X = #xdata{type = submit, fields = Fs},
    +    send(Config, #message{to = Room, sub_els = [X]}),
    +    #message{from = Room, type = error} = Err = recv_message(Config),
    +    #stanza_error{reason = 'forbidden'} = xmpp:get_error(Err),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_voice_request_interval_master(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(peer, Config),
    +    PeerNick = ?config(peer_nick, Config),
    +    PeerNickJID = peer_muc_jid(Config),
    +    ok = join_new(Config),
    +    [104] = set_config(Config, [{members_by_default, false}]),
    +    ct:comment("Asking the slave to join as a visitor"),
    +    put_event(Config, join),
    +    #muc_user{items = [#muc_item{role = visitor}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    [104] = set_config(Config, [{voice_request_min_interval, 5}]),
    +    ct:comment("Receiving a voice request from slave"),
    +    #message{from = Room, type = normal} = recv_message(Config),
    +    ct:comment("Deny voice request at first"),
    +    Fs = muc_request:encode([{jid, PeerJID}, {role, participant},
    +			     {nick, PeerNick}, {request_allow, false}]),
    +    send(Config, #message{to = Room, sub_els = [#xdata{type = submit,
    +                                                       fields = Fs}]}),
    +    put_event(Config, denied),
    +    ct:comment("Waiting for repeated voice request from the slave"),
    +    #message{from = Room, type = normal} = recv_message(Config),
    +    ct:comment("Waiting for the slave to leave"),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_voice_request_interval_slave(Config) ->
    +    Room = muc_room_jid(Config),
    +    Fs = muc_request:encode([{role, participant}]),
    +    X = #xdata{type = submit, fields = Fs},
    +    ct:comment("Waiting for 'join' command from the master"),
    +    join = get_event(Config),
    +    {[], _, _} = join(Config, visitor),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Sending voice request"),
    +    send(Config, #message{to = Room, sub_els = [X]}),
    +    ct:comment("Waiting for the master to deny our voice request"),
    +    denied = get_event(Config),
    +    ct:comment("Requesting voice again"),
    +    send(Config, #message{to = Room, sub_els = [X]}),
    +    ct:comment("Receving voice request error because we're sending to fast"),
    +    #message{from = Room, type = error} = Err = recv_message(Config),
    +    #stanza_error{reason = 'resource-constraint'} = xmpp:get_error(Err),
    +    ct:comment("Waiting for 5 seconds"),
    +    timer:sleep(timer:seconds(5)),
    +    ct:comment("Repeating again"),
    +    send(Config, #message{to = Room, sub_els = [X]}),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_visitor_nickchange_master(Config) ->
    +    PeerNickJID = peer_muc_jid(Config),
    +    ok = join_new(Config),
    +    [104] = set_config(Config, [{members_by_default, false}]),
    +    ct:comment("Asking the slave to join as a visitor"),
    +    put_event(Config, join),
    +    ct:comment("Waiting for the slave to join"),
    +    #muc_user{items = [#muc_item{role = visitor}]} =
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    [104] = set_config(Config, [{allow_visitor_nickchange, false}]),
    +    ct:comment("Waiting for the slave to leave"),
    +    recv_muc_presence(Config, PeerNickJID, unavailable),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +config_visitor_nickchange_slave(Config) ->
    +    NewNick = randoms:get_string(),
    +    MyNickJID = my_muc_jid(Config),
    +    MyNewNickJID = jid:replace_resource(MyNickJID, NewNick),
    +    ct:comment("Waiting for 'join' command from the master"),
    +    join = get_event(Config),
    +    {[], _, _} = join(Config, visitor),
    +    [104] = recv_config_change_message(Config),
    +    ct:comment("Fail trying to change nickname"),
    +    send(Config, #presence{to = MyNewNickJID}),
    +    #presence{from = MyNewNickJID, type = error} = Err = recv_presence(Config),
    +    #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err),
    +    ok = leave(Config),
    +    disconnect(Config).
    +
    +register_master(Config) ->
    +    MUC = muc_jid(Config),
    +    %% Register nick "master1"
    +    register_nick(Config, MUC, <<"">>, <<"master1">>),
    +    %% Unregister nick "master1" via jabber:register
    +    #iq{type = result, sub_els = []} =
    +	send_recv(Config, #iq{type = set, to = MUC,
    +			      sub_els = [#register{remove = true}]}),
    +    %% Register nick "master2"
    +    register_nick(Config, MUC, <<"">>, <<"master2">>),
    +    %% Now register nick "master"
    +    register_nick(Config, MUC, <<"master2">>, <<"master">>),
    +    %% Wait for slave to fail trying to register nick "master"
    +    wait_for_slave(Config),
    +    wait_for_slave(Config),
    +    %% Now register empty ("") nick, which means we're unregistering
    +    register_nick(Config, MUC, <<"master">>, <<"">>),
    +    disconnect(Config).
    +
    +register_slave(Config) ->
    +    MUC = muc_jid(Config),
    +    wait_for_master(Config),
    +    %% Trying to register occupied nick "master"
    +    Fs = muc_register:encode([{roomnick, <<"master">>}]),
    +    X = #xdata{type = submit, fields = Fs},
    +    #iq{type = error} =
    +	send_recv(Config, #iq{type = set, to = MUC,
    +			      sub_els = [#register{xdata = X}]}),
    +    wait_for_master(Config),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("muc_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("muc_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("muc_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("muc_" ++ atom_to_list(T) ++ "_slave")]}.
    +
    +recv_muc_presence(Config, From, Type) ->
    +    Pres = #presence{from = From, type = Type} = recv_presence(Config),
    +    xmpp:get_subtag(Pres, #muc_user{}).
    +
    +join_new(Config) ->
    +    join_new(Config, muc_room_jid(Config)).
    +
    +join_new(Config, Room) ->
    +    MyJID = my_jid(Config),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    ct:comment("Joining new room"),
    +    send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
    +    %% As per XEP-0045 we MUST receive stanzas in the following order:
    +    %% 1. In-room presence from other occupants
    +    %% 2. In-room presence from the joining entity itself (so-called "self-presence")
    +    %% 3. Room history (if any)
    +    %% 4. The room subject
    +    %% 5. Live messages, presence updates, new user joins, etc.
    +    %% As this is the newly created room, we receive only the 2nd and 4th stanza.
    +    #muc_user{
    +       status_codes = Codes,
    +       items = [#muc_item{role = moderator,
    +			  jid = MyJID,
    +			  affiliation = owner}]} =
    +	recv_muc_presence(Config, MyNickJID, available),
    +    ct:comment("Checking if codes '110' (self-presence) and "
    +	       "'201' (new room) is set"),
    +    true = lists:member(110, Codes),
    +    true = lists:member(201, Codes),
    +    ct:comment("Receiving empty room subject"),
    +    #message{from = Room, type = groupchat, body = [],
    +	     subject = [#text{data = <<>>}]} = recv_message(Config),
    +    case ?config(persistent_room, Config) of
    +	true ->
    +	    [104] = set_config(Config, [{persistentroom, true}], Room),
    +	    ok;
    +	false ->
    +	    ok
    +    end.
    +
    +recv_history_and_subject(Config) ->
    +    ct:comment("Receiving room history and/or subject"),
    +    recv_history_and_subject(Config, []).
    +
    +recv_history_and_subject(Config, History) ->
    +    Room = muc_room_jid(Config),
    +    #message{type = groupchat, subject = Subj,
    +	     body = Body, thread = Thread} = Msg = recv_message(Config),
    +    case xmpp:get_subtag(Msg, #delay{}) of
    +	#delay{from = Room} ->
    +	    recv_history_and_subject(Config, [Msg|History]);
    +	false when Subj /= [], Body == [], Thread == undefined ->
    +	    {lists:reverse(History), Msg}
    +    end.
    +
    +join(Config) ->
    +    join(Config, participant, none, #muc{}).
    +
    +join(Config, Role) when is_atom(Role) ->
    +    join(Config, Role, none, #muc{});
    +join(Config, #muc{} = SubEl) ->
    +    join(Config, participant, none, SubEl).
    +
    +join(Config, Role, Aff) when is_atom(Role), is_atom(Aff) ->
    +    join(Config, Role, Aff, #muc{});
    +join(Config, Role, #muc{} = SubEl) when is_atom(Role) ->
    +    join(Config, Role, none, SubEl).
    +
    +join(Config, Role, Aff, SubEl) ->
    +    ct:comment("Joining existing room as ~s/~s", [Aff, Role]),
    +    MyJID = my_jid(Config),
    +    Room = muc_room_jid(Config),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    PeerNick = ?config(peer_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    send(Config, #presence{to = MyNickJID, sub_els = [SubEl]}),
    +    case recv_presence(Config) of
    +	#presence{type = error, from = MyNickJID} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{});
    +	#presence{type = available, from = PeerNickJID} = Pres ->
    +	    #muc_user{items = [#muc_item{role = moderator,
    +					 affiliation = owner}]} =
    +		xmpp:get_subtag(Pres, #muc_user{}),
    +	    ct:comment("Receiving initial self-presence"),
    +	    #muc_user{status_codes = Codes,
    +		      items = [#muc_item{role = Role,
    +					 jid = MyJID,
    +					 affiliation = Aff}]} =
    +		recv_muc_presence(Config, MyNickJID, available),
    +	    ct:comment("Checking if code '110' (self-presence) is set"),
    +	    true = lists:member(110, Codes),
    +	    {History, Subj} = recv_history_and_subject(Config),
    +	    {History, Subj, Codes};
    +	#presence{type = available, from = MyNickJID} = Pres ->
    +	    #muc_user{status_codes = Codes,
    +		      items = [#muc_item{role = Role,
    +					 jid = MyJID,
    +					 affiliation = Aff}]} =
    +		xmpp:get_subtag(Pres, #muc_user{}),
    +	    ct:comment("Checking if code '110' (self-presence) is set"),
    +	    true = lists:member(110, Codes),
    +	    {History, Subj} = recv_history_and_subject(Config),
    +	    {empty, History, Subj, Codes}
    +    end.
    +
    +leave(Config) ->
    +    leave(Config, muc_room_jid(Config)).
    +
    +leave(Config, Room) ->
    +    MyJID = my_jid(Config),
    +    MyNick = ?config(nick, Config),
    +    MyNickJID = jid:replace_resource(Room, MyNick),
    +    Mode = ?config(mode, Config),
    +    IsPersistent = ?config(persistent_room, Config),
    +    if Mode /= slave, IsPersistent ->
    +	    [104] = set_config(Config, [{persistentroom, false}], Room);
    +       true ->
    +	    ok
    +    end,
    +    ct:comment("Leaving the room"),
    +    send(Config, #presence{to = MyNickJID, type = unavailable}),
    +    #muc_user{
    +       status_codes = Codes,
    +       items = [#muc_item{role = none, jid = MyJID}]} =
    +	recv_muc_presence(Config, MyNickJID, unavailable),
    +    ct:comment("Checking if code '110' (self-presence) is set"),
    +    true = lists:member(110, Codes),
    +    ok.
    +
    +get_config(Config) ->
    +    ct:comment("Get room config"),
    +    Room = muc_room_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = get, to = Room,
    +		       sub_els = [#muc_owner{}]}) of
    +	#iq{type = result,
    +	    sub_els = [#muc_owner{config = #xdata{type = form} = X}]} ->
    +	    muc_roomconfig:decode(X#xdata.fields);
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +set_config(Config, RoomConfig) ->
    +    set_config(Config, RoomConfig, muc_room_jid(Config)).
    +
    +set_config(Config, RoomConfig, Room) ->
    +    ct:comment("Set room config: ~p", [RoomConfig]),
    +    Fs = case RoomConfig of
    +	     [] -> [];
    +	     _ -> muc_roomconfig:encode(RoomConfig)
    +	 end,
    +    case send_recv(Config,
    +		   #iq{type = set, to = Room,
    +		       sub_els = [#muc_owner{config = #xdata{type = submit,
    +							     fields = Fs}}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    #message{from = Room, type = groupchat} = Msg = recv_message(Config),
    +	    #muc_user{status_codes = Codes} = xmpp:get_subtag(Msg, #muc_user{}),
    +	    lists:sort(Codes);
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +create_persistent(Config) ->
    +    [_|_] = get_config(Config),
    +    [] = set_config(Config, [{persistentroom, true}], false),
    +    ok.
    +
    +destroy(Config) ->
    +    destroy(Config, <<>>).
    +
    +destroy(Config, Reason) ->
    +    Room = muc_room_jid(Config),
    +    AltRoom = alt_room_jid(Config),
    +    ct:comment("Destroying a room"),
    +    case send_recv(Config,
    +		   #iq{type = set, to = Room,
    +		       sub_els = [#muc_owner{destroy = #muc_destroy{
    +							  reason = Reason,
    +							  jid = AltRoom}}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +disco_items(Config) ->
    +    MUC = muc_jid(Config),
    +    ct:comment("Performing disco#items request to ~s", [jid:to_string(MUC)]),
    +    #iq{type = result, from = MUC, sub_els = [DiscoItems]} =
    +	send_recv(Config, #iq{type = get, to = MUC,
    +			      sub_els = [#disco_items{}]}),
    +    lists:keysort(#disco_item.jid, DiscoItems#disco_items.items).
    +
    +disco_room_items(Config) ->
    +    Room = muc_room_jid(Config),
    +    #iq{type = result, from = Room, sub_els = [DiscoItems]} =
    +	send_recv(Config, #iq{type = get, to = Room,
    +			      sub_els = [#disco_items{}]}),
    +    DiscoItems#disco_items.items.
    +
    +get_affiliations(Config, Aff) ->
    +    Room = muc_room_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = get, to = Room,
    +		       sub_els = [#muc_admin{items = [#muc_item{affiliation = Aff}]}]}) of
    +	#iq{type = result, sub_els = [#muc_admin{items = Items}]} ->
    +	    Items;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +master_join(Config) ->
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(slave, Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    PeerNickJID = jid:replace_resource(Room, PeerNick),
    +    ok = join_new(Config),
    +    wait_for_slave(Config),
    +    #muc_user{items = [#muc_item{jid = PeerJID,
    +				 role = participant,
    +				 affiliation = none}]} = 
    +	recv_muc_presence(Config, PeerNickJID, available),
    +    ok.
    +
    +slave_join(Config) ->
    +    wait_for_master(Config),
    +    join(Config).
    +
    +set_role(Config, Role, Reason) ->
    +    ct:comment("Changing role to ~s", [Role]),
    +    Room = muc_room_jid(Config),
    +    PeerNick = ?config(slave_nick, Config),
    +    case send_recv(
    +	   Config,
    +	   #iq{type = set, to = Room,
    +	       sub_els =
    +		   [#muc_admin{
    +		       items = [#muc_item{role = Role,
    +					  reason = Reason,
    +					  nick = PeerNick}]}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +get_role(Config, Role) ->
    +    ct:comment("Requesting list for role '~s'", [Role]),
    +    Room = muc_room_jid(Config),
    +    case send_recv(
    +	   Config,
    +	   #iq{type = get, to = Room,
    +	       sub_els = [#muc_admin{
    +			     items = [#muc_item{role = Role}]}]}) of
    +	#iq{type = result, sub_els = [#muc_admin{items = Items}]} ->
    +	    lists:keysort(#muc_item.affiliation, Items);
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +set_affiliation(Config, Aff, Reason) ->
    +    ct:comment("Changing affiliation to ~s", [Aff]),
    +    Room = muc_room_jid(Config),
    +    PeerJID = ?config(slave, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    case send_recv(
    +	   Config,
    +	   #iq{type = set, to = Room,
    +	       sub_els =
    +		   [#muc_admin{
    +		       items = [#muc_item{affiliation = Aff,
    +					  reason = Reason,
    +					  jid = PeerBareJID}]}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +get_affiliation(Config, Aff) ->
    +    ct:comment("Requesting list for affiliation '~s'", [Aff]),
    +    Room = muc_room_jid(Config),
    +    case send_recv(
    +	   Config,
    +	   #iq{type = get, to = Room,
    +	       sub_els = [#muc_admin{
    +			     items = [#muc_item{affiliation = Aff}]}]}) of
    +	#iq{type = result, sub_els = [#muc_admin{items = Items}]} ->
    +	    Items;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +set_vcard(Config, VCard) ->
    +    Room = muc_room_jid(Config),
    +    ct:comment("Setting vCard for ~s", [jid:to_string(Room)]),
    +    case send_recv(Config, #iq{type = set, to = Room,
    +			       sub_els = [VCard]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +get_vcard(Config) ->
    +    Room = muc_room_jid(Config),
    +    ct:comment("Retreiving vCard from ~s", [jid:to_string(Room)]),
    +    case send_recv(Config, #iq{type = get, to = Room,
    +			       sub_els = [#vcard_temp{}]}) of
    +	#iq{type = result, sub_els = [VCard]} ->
    +	    VCard;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_subtag(Err, #stanza_error{})
    +    end.
    +
    +recv_config_change_message(Config) ->
    +    ct:comment("Receiving configuration change notification message"),
    +    Room = muc_room_jid(Config),
    +    #message{type = groupchat, from = Room} = Msg = recv_message(Config),
    +    #muc_user{status_codes = Codes} = xmpp:get_subtag(Msg, #muc_user{}),
    +    lists:sort(Codes).
    +
    +register_nick(Config, MUC, PrevNick, Nick) ->
    +    PrevRegistered = if PrevNick /= <<"">> -> true;
    +			true -> false
    +		     end,
    +    NewRegistered = if Nick /= <<"">> -> true;
    +		       true -> false
    +		    end,
    +    ct:comment("Requesting registration form"),
    +    #iq{type = result,
    +	sub_els = [#register{registered = PrevRegistered,
    +			     xdata = #xdata{type = form,
    +					    fields = FsWithoutNick}}]} =
    +	send_recv(Config, #iq{type = get, to = MUC,
    +			      sub_els = [#register{}]}),
    +    ct:comment("Checking if previous nick is registered"),
    +    PrevNick = proplists:get_value(
    +		 roomnick, muc_register:decode(FsWithoutNick)),
    +    X = #xdata{type = submit, fields = muc_register:encode([{roomnick, Nick}])},
    +    ct:comment("Submitting registration form"),
    +    #iq{type = result, sub_els = []} =
    +	send_recv(Config, #iq{type = set, to = MUC,
    +			      sub_els = [#register{xdata = X}]}),
    +    ct:comment("Checking if new nick was registered"),
    +    #iq{type = result,
    +	sub_els = [#register{registered = NewRegistered,
    +			     xdata = #xdata{type = form,
    +					    fields = FsWithNick}}]} =
    +	send_recv(Config, #iq{type = get, to = MUC,
    +			      sub_els = [#register{}]}),
    +    Nick = proplists:get_value(
    +	     roomnick, muc_register:decode(FsWithNick)).
    +
    +subscribe(Config, Events, Room) ->
    +    MyNick = ?config(nick, Config),
    +    case send_recv(Config,
    +		   #iq{type = set, to = Room,
    +		       sub_els = [#muc_subscribe{nick = MyNick,
    +						 events = Events}]}) of
    +	#iq{type = result, sub_els = [#muc_subscribe{events = ResEvents}]} ->
    +	    lists:sort(ResEvents);
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +unsubscribe(Config, Room) ->
    +    case send_recv(Config, #iq{type = set, to = Room,
    +			       sub_els = [#muc_unsubscribe{}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    diff --git a/test/offline_tests.erl b/test/offline_tests.erl
    new file mode 100644
    index 000000000..ea34544e3
    --- /dev/null
    +++ b/test/offline_tests.erl
    @@ -0,0 +1,406 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created :  7 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(offline_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [send/2, disconnect/1, my_jid/1, send_recv/2, recv_message/1,
    +		get_features/1, recv/1, get_event/1, server_jid/1,
    +		wait_for_master/1, wait_for_slave/1]).
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +single_cases() ->
    +    {offline_single, [sequence],
    +     [single_test(feature_enabled),
    +      single_test(check_identity),
    +      single_test(send_non_existent),
    +      single_test(view_non_existent),
    +      single_test(remove_non_existent),
    +      single_test(view_non_integer),
    +      single_test(remove_non_integer),
    +      single_test(malformed_iq),
    +      single_test(wrong_user),
    +      single_test(unsupported_iq)]}.
    +
    +feature_enabled(Config) ->
    +    Features = get_features(Config),
    +    ct:comment("Checking if offline features are set"),
    +    true = lists:member(?NS_FEATURE_MSGOFFLINE, Features),
    +    true = lists:member(?NS_FLEX_OFFLINE, Features),
    +    disconnect(Config).
    +
    +check_identity(Config) ->
    +    #iq{type = result,
    +	sub_els = [#disco_info{
    +		      node = ?NS_FLEX_OFFLINE,
    +		      identities = Ids}]} =
    +	send_recv(Config, #iq{type = get,
    +			      sub_els = [#disco_info{
    +					    node = ?NS_FLEX_OFFLINE}]}),
    +    true = lists:any(
    +	     fun(#identity{category = <<"automation">>,
    +			   type = <<"message-list">>}) -> true;
    +		(_) -> false
    +	     end, Ids),
    +    disconnect(Config).
    +
    +send_non_existent(Config) ->
    +    Server = ?config(server, Config),
    +    To = jid:make(<<"non-existent">>, Server),
    +    #message{type = error} = Err = send_recv(Config, #message{to = To}),
    +    #stanza_error{reason = 'service-unavailable'} = xmpp:get_error(Err),
    +    disconnect(Config).
    +
    +view_non_existent(Config) ->
    +    #stanza_error{reason = 'item-not-found'} = view(Config, [randoms:get_string()], false),
    +    disconnect(Config).
    +
    +remove_non_existent(Config) ->
    +    ok = remove(Config, [randoms:get_string()]),
    +    disconnect(Config).
    +
    +view_non_integer(Config) ->
    +    #stanza_error{reason = 'item-not-found'} = view(Config, [<<"foo">>], false),
    +    disconnect(Config).
    +
    +remove_non_integer(Config) ->
    +    #stanza_error{reason = 'item-not-found'} = remove(Config, [<<"foo">>]),
    +    disconnect(Config).
    +
    +malformed_iq(Config) ->
    +    Item = #offline_item{node = randoms:get_string()},
    +    Range = [{Type, SubEl} || Type <- [set, get],
    +			      SubEl <- [#offline{items = [], _ = false},
    +					#offline{items = [Item], _ = true}]]
    +	++ [{set, #offline{items = [], fetch = true, purge = false}},
    +	    {set, #offline{items = [Item], fetch = true, purge = false}},
    +	    {get, #offline{items = [], fetch = false, purge = true}},
    +	    {get, #offline{items = [Item], fetch = false, purge = true}}],
    +    lists:foreach(
    +      fun({Type, SubEl}) ->
    +	      #iq{type = error} = Err =
    +		  send_recv(Config, #iq{type = Type, sub_els = [SubEl]}),
    +	      #stanza_error{reason = 'bad-request'} = xmpp:get_error(Err)
    +      end, Range),
    +    disconnect(Config).
    +
    +wrong_user(Config) ->
    +    Server = ?config(server, Config),
    +    To = jid:make(<<"foo">>, Server),
    +    Item = #offline_item{node = randoms:get_string()},
    +    Range = [{Type, Items, Purge, Fetch} ||
    +		Type <- [set, get],
    +		Items <- [[], [Item]],
    +		Purge <- [false, true],
    +		Fetch <- [false, true]],
    +    lists:foreach(
    +      fun({Type, Items, Purge, Fetch}) ->
    +	      #iq{type = error} = Err =
    +		  send_recv(Config, #iq{type = Type, to = To,
    +					sub_els = [#offline{items = Items,
    +							    purge = Purge,
    +							    fetch = Fetch}]}),
    +	      #stanza_error{reason = 'forbidden'} = xmpp:get_error(Err)
    +      end, Range),
    +    disconnect(Config).
    +
    +unsupported_iq(Config) ->
    +    Item = #offline_item{node = randoms:get_string()},
    +    lists:foreach(
    +      fun(Type) ->
    +	      #iq{type = error} = Err =
    +		  send_recv(Config, #iq{type = Type, sub_els = [Item]}),
    +	      #stanza_error{reason = 'service-unavailable'} = xmpp:get_error(Err)
    +      end, [set, get]),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {offline_master_slave, [sequence],
    +     [master_slave_test(flex),
    +      master_slave_test(send_all)]}.
    +
    +flex_master(Config) ->
    +    send_messages(Config, 5),
    +    disconnect(Config).
    +
    +flex_slave(Config) ->
    +    wait_for_master(Config),
    +    peer_down = get_event(Config),
    +    5 = get_number(Config),
    +    Nodes = get_nodes(Config),
    +    %% Since headers are received we can send initial presence without a risk
    +    %% of getting offline messages flood
    +    #presence{} = send_recv(Config, #presence{}),
    +    ct:comment("Checking fetch"),
    +    Nodes = fetch(Config, lists:seq(1, 5)),
    +    ct:comment("Fetching 2nd and 4th message"),
    +    [2, 4] = view(Config, [lists:nth(2, Nodes), lists:nth(4, Nodes)]),
    +    ct:comment("Deleting 2nd and 4th message"),
    +    ok = remove(Config, [lists:nth(2, Nodes), lists:nth(4, Nodes)]),
    +    ct:comment("Checking if messages were deleted"),
    +    [1, 3, 5] = view(Config, [lists:nth(1, Nodes),
    +			      lists:nth(3, Nodes),
    +			      lists:nth(5, Nodes)]),
    +    ct:comment("Purging everything left"),
    +    ok = purge(Config),
    +    ct:comment("Checking if there are no offline messages"),
    +    0 = get_number(Config),
    +    clean(disconnect(Config)).
    +
    +send_all_master(Config) ->
    +    wait_for_slave(Config),
    +    Peer = ?config(peer, Config),
    +    BarePeer = jid:remove_resource(Peer),
    +    {Deliver, Errors} = message_iterator(Config),
    +    N = lists:foldl(
    +    	  fun(#message{type = error} = Msg, Acc) ->
    +    		  send(Config, Msg#message{to = BarePeer}),
    +    		  Acc;
    +    	     (Msg, Acc) ->
    +    		  I = send(Config, Msg#message{to = BarePeer}),
    +    		  case xmpp:get_subtag(Msg, #xevent{}) of
    +    		      #xevent{offline = true, id = undefined} ->
    +    			  ct:comment("Receiving event-reply for:~n~s",
    +    				     [xmpp:pp(Msg)]),
    +    			  #message{} = Reply = recv_message(Config),
    +    			  #xevent{id = I} = xmpp:get_subtag(Reply, #xevent{});
    +    		      _ ->
    +    			  ok
    +    		  end,
    +    		  Acc + 1
    +    	  end, 0, Deliver),
    +    lists:foreach(
    +      fun(Msg) ->
    +	      #message{type = error} = Err =
    +		  send_recv(Config, Msg#message{to = BarePeer}),
    +	      #stanza_error{reason = 'service-unavailable'} = xmpp:get_error(Err)
    +      end, Errors),
    +    ok = wait_for_complete(Config, N),
    +    disconnect(Config).
    +
    +send_all_slave(Config) ->
    +    ServerJID = server_jid(Config),
    +    Peer = ?config(peer, Config),
    +    wait_for_master(Config),
    +    peer_down = get_event(Config),
    +    #presence{} = send_recv(Config, #presence{}),
    +    {Deliver, _Errors} = message_iterator(Config),
    +    lists:foreach(
    +      fun(#message{type = error}) ->
    +	      ok;
    +	 (#message{type = Type, body = Body, subject = Subject} = Msg) ->
    +	      ct:comment("Receiving message:~n~s", [xmpp:pp(Msg)]),
    +	      #message{from = Peer,
    +		       type = Type,
    +		       body = Body,
    +		       subject = Subject} = RecvMsg = recv_message(Config),
    +	      ct:comment("Checking if delay tag is correctly set"),
    +	      #delay{from = ServerJID} = xmpp:get_subtag(RecvMsg, #delay{})
    +      end, Deliver),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("offline_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("offline_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("offline_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("offline_" ++ atom_to_list(T) ++ "_slave")]}.
    +
    +clean(Config) ->
    +    {U, S, _} = jid:tolower(my_jid(Config)),
    +    mod_offline:remove_user(U, S),
    +    Config.
    +
    +send_messages(Config, Num) ->
    +    send_messages(Config, Num, normal, []).
    +
    +send_messages(Config, Num, Type, SubEls) ->
    +    wait_for_slave(Config),
    +    Peer = ?config(peer, Config),
    +    BarePeer = jid:remove_resource(Peer),
    +    lists:foreach(
    +      fun(I) ->
    +	      Body = integer_to_binary(I),
    +	      send(Config,
    +		   #message{to = BarePeer,
    +			    type = Type,
    +			    body = [#text{data = Body}],
    +			    subject = [#text{data = <<"subject">>}],
    +			    sub_els = SubEls})
    +      end, lists:seq(1, Num)),
    +    ct:comment("Waiting for all messages to be delivered to offline spool"),
    +    ok = wait_for_complete(Config, Num).
    +
    +recv_messages(Config, Num) ->
    +    wait_for_master(Config),
    +    peer_down = get_event(Config),
    +    Peer = ?config(peer, Config),
    +    #presence{} = send_recv(Config, #presence{}),
    +    lists:foreach(
    +      fun(I) ->
    +	      Text = integer_to_binary(I),
    +	      #message{sub_els = SubEls,
    +		       from = Peer,
    +		       body = [#text{data = Text}],
    +		       subject = [#text{data = <<"subject">>}]} =
    +		  recv_message(Config),
    +	      true = lists:keymember(delay, 1, SubEls)
    +      end, lists:seq(1, Num)),
    +    clean(disconnect(Config)).
    +
    +get_number(Config) ->
    +    ct:comment("Getting offline message number"),
    +    #iq{type = result,
    +	sub_els = [#disco_info{
    +		      node = ?NS_FLEX_OFFLINE,
    +		      xdata = [X]}]} =
    +	send_recv(Config, #iq{type = get,
    +			      sub_els = [#disco_info{
    +					    node = ?NS_FLEX_OFFLINE}]}),
    +    Form = flex_offline:decode(X#xdata.fields),
    +    proplists:get_value(number_of_messages, Form).
    +
    +get_nodes(Config) ->
    +    MyJID = my_jid(Config),
    +    MyBareJID = jid:remove_resource(MyJID),
    +    Peer = ?config(peer, Config),
    +    Peer_s = jid:to_string(Peer),
    +    ct:comment("Getting headers"), 
    +    #iq{type = result,
    +	sub_els = [#disco_items{
    +		      node = ?NS_FLEX_OFFLINE,
    +		      items = DiscoItems}]} =
    +	send_recv(Config, #iq{type = get,
    +			      sub_els = [#disco_items{
    +					    node = ?NS_FLEX_OFFLINE}]}),
    +    ct:comment("Checking if headers are correct"),
    +    lists:sort(
    +      lists:map(
    +	fun(#disco_item{jid = J, name = P, node = N})
    +	      when (J == MyBareJID) and (P == Peer_s) ->
    +		N
    +	end, DiscoItems)).
    +
    +fetch(Config, Range) ->
    +    ID = send(Config, #iq{type = get, sub_els = [#offline{fetch = true}]}),
    +    Nodes = lists:map(
    +	      fun(I) ->
    +		      Text = integer_to_binary(I),
    +		      #message{body = Body, sub_els = SubEls} = recv(Config),
    +		      [#text{data = Text}] = Body,
    +		      #offline{items = [#offline_item{node = Node}]} =
    +			  lists:keyfind(offline, 1, SubEls),
    +		      #delay{} = lists:keyfind(delay, 1, SubEls),
    +		      Node
    +	      end, Range),
    +    #iq{id = ID, type = result, sub_els = []} = recv(Config),
    +    Nodes.
    +
    +view(Config, Nodes) ->
    +    view(Config, Nodes, true).
    +
    +view(Config, Nodes, NeedReceive) ->
    +    Items = lists:map(
    +	      fun(Node) ->
    +		      #offline_item{action = view, node = Node}
    +	      end, Nodes),
    +    I = send(Config,
    +	     #iq{type = get, sub_els = [#offline{items = Items}]}),
    +    Range = if NeedReceive ->
    +		    lists:map(
    +		      fun(Node) ->
    +			      #message{body = [#text{data = Text}],
    +				       sub_els = SubEls} = recv(Config),
    +			      #offline{items = [#offline_item{node = Node}]} =
    +				  lists:keyfind(offline, 1, SubEls),
    +			      binary_to_integer(Text)
    +		      end, Nodes);
    +	       true ->
    +		    []
    +	    end,
    +    case recv(Config) of
    +	#iq{id = I, type = result, sub_els = []} -> Range;
    +	#iq{id = I, type = error} = Err -> xmpp:get_error(Err)
    +    end.
    +
    +remove(Config, Nodes) ->
    +    Items = lists:map(
    +	      fun(Node) ->
    +		      #offline_item{action = remove, node = Node}
    +	      end, Nodes),
    +    case send_recv(Config, #iq{type = set,
    +			       sub_els = [#offline{items = Items}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +purge(Config) ->
    +    case send_recv(Config, #iq{type = set,
    +			       sub_els = [#offline{purge = true}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +wait_for_complete(_Config, 0) ->
    +    ok;
    +wait_for_complete(Config, N) ->
    +    {U, S, _} = jid:tolower(?config(peer, Config)),
    +    lists:foldl(
    +      fun(_Time, ok) ->
    +	      ok;
    +	 (Time, Acc) ->
    +	      timer:sleep(Time),
    +	      case mod_offline:count_offline_messages(U, S) of
    +		  N -> ok;
    +		  _ -> Acc
    +	      end
    +      end, error, [0, 100, 200, 2000, 5000, 10000]).
    +
    +message_iterator(Config) ->
    +    ServerJID = server_jid(Config),
    +    ChatStates = [[#chatstate{type = composing}]],
    +    Offline = [[#offline{}]],
    +    Hints = [[#hint{type = T}] || T <- [store, 'no-store']],
    +    XEvent = [[#xevent{id = ID, offline = OfflineFlag}]
    +	      || ID <- [undefined, randoms:get_string()],
    +		 OfflineFlag <- [false, true]],
    +    Delay = [[#delay{stamp = p1_time_compat:timestamp(), from = ServerJID}]],
    +    AllEls = [Els1 ++ Els2 || Els1 <- [[]] ++ ChatStates ++ Delay ++ Hints ++ Offline,
    +			      Els2 <- [[]] ++ XEvent],
    +    All = [#message{type = Type, body = Body, subject = Subject, sub_els = Els}
    +	   || %%Type <- [chat],
    +	      Type <- [error, chat, normal, groupchat, headline],
    +	      Body <- [[], xmpp:mk_text(<<"body">>)],
    +	      Subject <- [[], xmpp:mk_text(<<"subject">>)],
    +	      Els <- AllEls],
    +    lists:partition(
    +      fun(#message{type = error}) -> true;
    +	 (#message{sub_els = [#offline{}|_]}) -> false;
    +	 (#message{sub_els = [_, #xevent{id = I}]}) when I /= undefined -> false;
    +	 (#message{sub_els = [#xevent{id = I}]}) when I /= undefined -> false;
    +	 (#message{sub_els = [#hint{type = store}|_]}) -> true;
    +	 (#message{sub_els = [#hint{type = 'no-store'}|_]}) -> false;
    +	 (#message{body = [], subject = []}) -> false;
    +	 (#message{type = Type}) -> (Type == chat) or (Type == normal);
    +	 (_) -> false
    +      end, All).
    diff --git a/test/privacy_tests.erl b/test/privacy_tests.erl
    new file mode 100644
    index 000000000..640f53d48
    --- /dev/null
    +++ b/test/privacy_tests.erl
    @@ -0,0 +1,822 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 18 Oct 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(privacy_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [disconnect/1, send_recv/2, get_event/1, put_event/2,
    +		recv_iq/1, recv_presence/1, recv_message/1, recv/1,
    +		send/2, my_jid/1, server_jid/1, get_features/1,
    +		set_roster/3, del_roster/1, get_roster/1]).
    +-include("suite.hrl").
    +-include("mod_roster.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single cases
    +%%%===================================================================
    +single_cases() ->
    +    {privacy_single, [sequence],
    +     [single_test(feature_enabled),
    +      single_test(set_get_list),
    +      single_test(get_list_non_existent),
    +      single_test(set_default),
    +      single_test(del_default),
    +      single_test(set_default_non_existent),
    +      single_test(set_active),
    +      single_test(del_active),
    +      single_test(set_active_non_existent),
    +      single_test(remove_list),
    +      single_test(remove_default_list),
    +      single_test(remove_active_list),
    +      %% TODO: this should be fixed
    +      %% single_test(remove_list_non_existent),
    +      single_test(allow_local_server),
    +      single_test(malformed_iq_query),
    +      single_test(malformed_get),
    +      single_test(malformed_set),
    +      single_test(malformed_type_value),
    +      single_test(set_get_block)]}.
    +
    +feature_enabled(Config) ->
    +    Features = get_features(Config),
    +    true = lists:member(?NS_PRIVACY, Features),
    +    true = lists:member(?NS_BLOCKING, Features),
    +    disconnect(Config).
    +
    +set_get_list(Config) ->
    +    ListName = <<"set-get-list">>,
    +    Items = [#privacy_item{order = 0, action = deny,
    +			   type = jid, value = <<"user@jabber.org">>,
    +			   iq = true},
    +	     #privacy_item{order = 1, action = allow,
    +			   type = group, value = <<"group">>,
    +			   message = true},
    +	     #privacy_item{order = 2, action = allow,
    +			   type = subscription, value = <<"both">>,
    +			   presence_in = true},
    +	     #privacy_item{order = 3, action = deny,
    +			   type = subscription, value = <<"from">>,
    +			   presence_out = true},
    +	     #privacy_item{order = 4, action = deny,
    +			   type = subscription, value = <<"to">>,
    +			   iq = true, message = true},
    +	     #privacy_item{order = 5, action = deny,
    +			   type = subscription, value = <<"none">>,
    +			   _ = true},
    +	     #privacy_item{order = 6, action = deny}],
    +    ok = set_items(Config, ListName, Items),
    +    #privacy_list{name = ListName, items = Items1} = get_list(Config, ListName),
    +    Items = lists:keysort(#privacy_item.order, Items1),
    +    del_privacy(disconnect(Config)).
    +
    +get_list_non_existent(Config) ->
    +    ListName = <<"get-list-non-existent">>,
    +    #stanza_error{reason = 'item-not-found'} = get_list(Config, ListName),
    +    disconnect(Config).
    +
    +set_default(Config) ->
    +    ListName = <<"set-default">>,
    +    Item = #privacy_item{order = 0, action = deny},
    +    ok = set_items(Config, ListName, [Item]),
    +    ok = set_default(Config, ListName),
    +    #privacy_query{default = ListName} = get_lists(Config),
    +    del_privacy(disconnect(Config)).
    +
    +del_default(Config) ->
    +    ListName = <<"del-default">>,
    +    Item = #privacy_item{order = 0, action = deny},
    +    ok = set_items(Config, ListName, [Item]),
    +    ok = set_default(Config, ListName),
    +    #privacy_query{default = ListName} = get_lists(Config),
    +    ok = set_default(Config, none),
    +    #privacy_query{default = none} = get_lists(Config),
    +    del_privacy(disconnect(Config)).
    +
    +set_default_non_existent(Config) ->
    +    ListName = <<"set-default-non-existent">>,
    +    #stanza_error{reason = 'item-not-found'} = set_default(Config, ListName),
    +    disconnect(Config).
    +
    +set_active(Config) ->
    +    ListName = <<"set-active">>,
    +    Item = #privacy_item{order = 0, action = deny},
    +    ok = set_items(Config, ListName, [Item]),
    +    ok = set_active(Config, ListName),
    +    #privacy_query{active = ListName} = get_lists(Config),
    +    del_privacy(disconnect(Config)).
    +
    +del_active(Config) ->
    +    ListName = <<"del-active">>,
    +    Item = #privacy_item{order = 0, action = deny},
    +    ok = set_items(Config, ListName, [Item]),
    +    ok = set_active(Config, ListName),
    +    #privacy_query{active = ListName} = get_lists(Config),
    +    ok = set_active(Config, none),
    +    #privacy_query{active = none} = get_lists(Config),
    +    del_privacy(disconnect(Config)).
    +
    +set_active_non_existent(Config) ->
    +    ListName = <<"set-active-non-existent">>,
    +    #stanza_error{reason = 'item-not-found'} = set_active(Config, ListName),
    +    disconnect(Config).
    +
    +remove_list(Config) ->
    +    ListName = <<"remove-list">>,
    +    Item = #privacy_item{order = 0, action = deny},
    +    ok = set_items(Config, ListName, [Item]),
    +    ok = del_list(Config, ListName),
    +    #privacy_query{lists = []} = get_lists(Config),
    +    del_privacy(disconnect(Config)).
    +
    +remove_active_list(Config) ->
    +    ListName = <<"remove-active-list">>,
    +    Item = #privacy_item{order = 0, action = deny},
    +    ok = set_items(Config, ListName, [Item]),
    +    ok = set_active(Config, ListName),
    +    #stanza_error{reason = 'conflict'} = del_list(Config, ListName),
    +    del_privacy(disconnect(Config)).
    +
    +remove_default_list(Config) ->
    +    ListName = <<"remove-default-list">>,
    +    Item = #privacy_item{order = 0, action = deny},
    +    ok = set_items(Config, ListName, [Item]),
    +    ok = set_default(Config, ListName),
    +    #stanza_error{reason = 'conflict'} = del_list(Config, ListName),
    +    del_privacy(disconnect(Config)).
    +
    +remove_list_non_existent(Config) ->
    +    ListName = <<"remove-list-non-existent">>,
    +    #stanza_error{reason = 'item-not-found'} = del_list(Config, ListName),
    +    disconnect(Config).
    +
    +allow_local_server(Config) ->
    +    ListName = <<"allow-local-server">>,
    +    Item = #privacy_item{order = 0, action = deny},
    +    ok = set_items(Config, ListName, [Item]),
    +    ok = set_active(Config, ListName),
    +    %% Whatever privacy rules are set, we should always communicate
    +    %% with our home server
    +    server_send_iqs(Config),
    +    server_recv_iqs(Config),
    +    send_stanzas_to_server_resource(Config),
    +    del_privacy(disconnect(Config)).
    +
    +malformed_iq_query(Config) ->
    +    lists:foreach(
    +      fun(Type) ->
    +	      #iq{type = error} =
    +		  send_recv(Config,
    +			    #iq{type = Type,
    +				sub_els = [#privacy_list{name = <<"foo">>}]})
    +      end, [get, set]),
    +    disconnect(Config).
    +
    +malformed_get(Config) ->
    +    JID = jid:make(randoms:get_string()),
    +    lists:foreach(
    +      fun(SubEl) ->
    +	      #iq{type = error} =
    +		  send_recv(Config, #iq{type = get, sub_els = [SubEl]})
    +      end, [#privacy_query{active = none},
    +	    #privacy_query{default = none},
    +	    #privacy_query{lists = [#privacy_list{name = <<"1">>},
    +				    #privacy_list{name = <<"2">>}]},
    +	    #block{items = [JID]}, #unblock{items = [JID]},
    +	    #block{}, #unblock{}]),
    +    disconnect(Config).
    +
    +malformed_set(Config) ->
    +    lists:foreach(
    +      fun(SubEl) ->
    +	      #iq{type = error} =
    +		  send_recv(Config, #iq{type = set, sub_els = [SubEl]})
    +      end, [#privacy_query{active = none, default = none},
    +	    #privacy_query{lists = [#privacy_list{name = <<"1">>},
    +				    #privacy_list{name = <<"2">>}]},
    +	    #block{},
    +	    #block_list{},
    +	    #block_list{items = [jid:make(randoms:get_string())]}]).
    +
    +malformed_type_value(Config) ->
    +    Item = #privacy_item{order = 0, action = deny},
    +    #stanza_error{reason = 'bad-request'} =
    +	set_items(Config, <<"malformed-jid">>,
    +		  [Item#privacy_item{type = jid, value = <<"@bad">>}]),
    +    #stanza_error{reason = 'bad-request'} =
    +	set_items(Config, <<"malformed-group">>,
    +		  [Item#privacy_item{type = group, value = <<"">>}]),
    +    #stanza_error{reason = 'bad-request'} =
    +	set_items(Config, <<"malformed-subscription">>,
    +		  [Item#privacy_item{type = subscription, value = <<"bad">>}]),
    +    disconnect(Config).
    +
    +set_get_block(Config) ->
    +    J1 = jid:make(randoms:get_string(), randoms:get_string()),
    +    J2 = jid:make(randoms:get_string(), randoms:get_string()),
    +    {ok, ListName} = set_block(Config, [J1, J2]),
    +    JIDs = get_block(Config),
    +    JIDs = lists:sort([J1, J2]),
    +    {ok, ListName} = set_unblock(Config, [J2, J1]),
    +    [] = get_block(Config),
    +    del_privacy(disconnect(Config)).
    +
    +%%%===================================================================
    +%%% Master-slave cases
    +%%%===================================================================
    +master_slave_cases() ->
    +    {privacy_master_slave, [sequence],
    +     [master_slave_test(deny_bare_jid),
    +      master_slave_test(deny_full_jid),
    +      master_slave_test(deny_server_jid),
    +      master_slave_test(deny_group),
    +      master_slave_test(deny_sub_both),
    +      master_slave_test(deny_sub_from),
    +      master_slave_test(deny_sub_to),
    +      master_slave_test(deny_sub_none),
    +      master_slave_test(deny_all),
    +      master_slave_test(deny_offline),
    +      master_slave_test(block),
    +      master_slave_test(unblock),
    +      master_slave_test(unblock_all)]}.
    +
    +deny_bare_jid_master(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    deny_master(Config, {jid, jid:to_string(PeerBareJID)}).
    +
    +deny_bare_jid_slave(Config) ->
    +    deny_slave(Config).
    +
    +deny_full_jid_master(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    deny_master(Config, {jid, jid:to_string(PeerJID)}).
    +
    +deny_full_jid_slave(Config) ->
    +    deny_slave(Config).
    +
    +deny_server_jid_master(Config) ->
    +    {_, Server, _} = jid:tolower(?config(peer, Config)),
    +    deny_master(Config, {jid, Server}).
    +
    +deny_server_jid_slave(Config) ->
    +    deny_slave(Config).
    +
    +deny_group_master(Config) ->
    +    Group = randoms:get_string(),
    +    deny_master(Config, {group, Group}).
    +
    +deny_group_slave(Config) ->
    +    deny_slave(Config).
    +
    +deny_sub_both_master(Config) ->
    +    deny_master(Config, {subscription, <<"both">>}).
    +
    +deny_sub_both_slave(Config) ->
    +    deny_slave(Config).
    +
    +deny_sub_from_master(Config) ->
    +    deny_master(Config, {subscription, <<"from">>}).
    +
    +deny_sub_from_slave(Config) ->
    +    deny_slave(Config).
    +
    +deny_sub_to_master(Config) ->
    +    deny_master(Config, {subscription, <<"to">>}).
    +
    +deny_sub_to_slave(Config) ->
    +    deny_slave(Config).
    +
    +deny_sub_none_master(Config) ->
    +    deny_master(Config, {subscription, <<"none">>}).
    +
    +deny_sub_none_slave(Config) ->
    +    deny_slave(Config).
    +
    +deny_all_master(Config) ->
    +    deny_master(Config, {undefined, <<"">>}).
    +
    +deny_all_slave(Config) ->
    +    deny_slave(Config).
    +
    +deny_master(Config, {Type, Value}) ->
    +    Sub = if Type == subscription ->
    +		  erlang:binary_to_atom(Value, utf8);
    +	     true ->
    +		  both
    +	  end,
    +    Groups = if Type == group -> [Value];
    +		true -> []
    +	     end,
    +    set_roster(Config, Sub, Groups),
    +    lists:foreach(
    +      fun(Opts) ->
    +	      ct:pal("Set list for ~s, ~s, ~w", [Type, Value, Opts]),
    +	      ListName = randoms:get_string(),
    +	      Item = #privacy_item{order = 0,
    +				   action = deny,
    +				   iq = proplists:get_bool(iq, Opts),
    +				   message = proplists:get_bool(message, Opts),
    +				   presence_in = proplists:get_bool(presence_in, Opts),
    +				   presence_out = proplists:get_bool(presence_out, Opts),
    +				   type = Type,
    +				   value = Value},
    +	      ok = set_items(Config, ListName, [Item]),
    +	      ok = set_active(Config, ListName),
    +	      put_event(Config, Opts),
    +	      case is_presence_in_blocked(Opts) of
    +		  true -> ok;
    +		  false -> recv_presences(Config)
    +	      end,
    +	      case is_iq_in_blocked(Opts) of
    +		  true -> ok;
    +		  false -> recv_iqs(Config)
    +	      end,
    +	      case is_message_in_blocked(Opts) of
    +		  true -> ok;
    +		  false -> recv_messages(Config)
    +	      end,
    +	      ct:comment("Waiting for 'send' command from the slave"),
    +	      send = get_event(Config),
    +	      case is_presence_out_blocked(Opts) of
    +		  true -> check_presence_blocked(Config, 'not-acceptable');
    +		  false -> ok
    +	      end,
    +	      case is_iq_out_blocked(Opts) of
    +		  true -> check_iq_blocked(Config, 'not-acceptable');
    +		  false -> send_iqs(Config)
    +	      end,
    +	      case is_message_out_blocked(Opts) of
    +		  true -> check_message_blocked(Config, 'not-acceptable');
    +		  false -> send_messages(Config)
    +	      end,
    +	      case is_other_blocked(Opts) of
    +		  true -> check_other_blocked(Config, 'not-acceptable');
    +		  false -> ok
    +	      end,
    +	      ct:comment("Waiting for slave to finish processing our stanzas"),
    +	      done = get_event(Config)
    +      end,
    +      [[iq], [message], [presence_in], [presence_out],
    +       [iq, message, presence_in, presence_out], []]),
    +    put_event(Config, disconnect),
    +    clean_up(disconnect(Config)).
    +
    +deny_slave(Config) ->
    +    set_roster(Config, both, []),
    +    deny_slave(Config, get_event(Config)).
    +
    +deny_slave(Config, disconnect) ->
    +    clean_up(disconnect(Config));
    +deny_slave(Config, Opts) ->
    +    send_presences(Config),
    +    case is_iq_in_blocked(Opts) of
    +	true -> check_iq_blocked(Config, 'service-unavailable');
    +	false -> send_iqs(Config)
    +    end,
    +    case is_message_in_blocked(Opts) of
    +	true -> check_message_blocked(Config, 'service-unavailable');
    +	false -> send_messages(Config)
    +    end,
    +    put_event(Config, send),
    +    case is_iq_out_blocked(Opts) of
    +	true -> ok;
    +	false -> recv_iqs(Config)
    +    end,
    +    case is_message_out_blocked(Opts) of
    +	true -> ok;
    +	false -> recv_messages(Config)
    +    end,
    +    put_event(Config, done),
    +    deny_slave(Config, get_event(Config)).
    +
    +deny_offline_master(Config) ->
    +    set_roster(Config, both, []),
    +    ListName = <<"deny-offline">>,
    +    Item = #privacy_item{order = 0, action = deny},
    +    ok = set_items(Config, ListName, [Item]),
    +    ok = set_default(Config, ListName),
    +    NewConfig = disconnect(Config),
    +    put_event(NewConfig, send),
    +    ct:comment("Waiting for the slave to finish"),
    +    done = get_event(NewConfig),
    +    clean_up(NewConfig).
    +
    +deny_offline_slave(Config) ->
    +    set_roster(Config, both, []),
    +    ct:comment("Waiting for 'send' command from the master"),
    +    send = get_event(Config),
    +    send_presences(Config),
    +    check_iq_blocked(Config, 'service-unavailable'),
    +    check_message_blocked(Config, 'service-unavailable'),
    +    put_event(Config, done),
    +    clean_up(disconnect(Config)).
    +
    +block_master(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    set_roster(Config, both, []),
    +    {ok, _} = set_block(Config, [PeerJID]),
    +    check_presence_blocked(Config, 'not-acceptable'),
    +    check_iq_blocked(Config, 'not-acceptable'),
    +    check_message_blocked(Config, 'not-acceptable'),
    +    check_other_blocked(Config, 'not-acceptable'),
    +    %% We should always be able to communicate with our home server
    +    server_send_iqs(Config),
    +    server_recv_iqs(Config),
    +    send_stanzas_to_server_resource(Config),
    +    put_event(Config, send),
    +    done = get_event(Config),
    +    clean_up(disconnect(Config)).
    +
    +block_slave(Config) ->
    +    set_roster(Config, both, []),
    +    ct:comment("Waiting for 'send' command from master"),
    +    send = get_event(Config),
    +    send_presences(Config),
    +    check_iq_blocked(Config, 'service-unavailable'),
    +    check_message_blocked(Config, 'service-unavailable'),
    +    put_event(Config, done),
    +    clean_up(disconnect(Config)).
    +
    +unblock_master(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    set_roster(Config, both, []),
    +    {ok, ListName} = set_block(Config, [PeerJID]),
    +    {ok, ListName} = set_unblock(Config, [PeerJID]),
    +    put_event(Config, send),
    +    recv_presences(Config),
    +    recv_iqs(Config),
    +    recv_messages(Config),
    +    clean_up(disconnect(Config)).
    +
    +unblock_slave(Config) ->
    +    set_roster(Config, both, []),
    +    ct:comment("Waiting for 'send' command from master"),
    +    send = get_event(Config),
    +    send_presences(Config),
    +    send_iqs(Config),
    +    send_messages(Config),
    +    clean_up(disconnect(Config)).
    +
    +unblock_all_master(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    set_roster(Config, both, []),
    +    {ok, ListName} = set_block(Config, [PeerJID]),
    +    {ok, ListName} = set_unblock(Config, []),
    +    put_event(Config, send),
    +    recv_presences(Config),
    +    recv_iqs(Config),
    +    recv_messages(Config),
    +    clean_up(disconnect(Config)).
    +
    +unblock_all_slave(Config) ->
    +    set_roster(Config, both, []),
    +    ct:comment("Waiting for 'send' command from master"),
    +    send = get_event(Config),
    +    send_presences(Config),
    +    send_iqs(Config),
    +    send_messages(Config),
    +    clean_up(disconnect(Config)).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("privacy_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("privacy_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("privacy_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("privacy_" ++ atom_to_list(T) ++ "_slave")]}.
    +
    +set_items(Config, Name, Items) ->
    +    ct:comment("Setting privacy list ~s with items = ~p", [Name, Items]),
    +    case send_recv(
    +	   Config,
    +	   #iq{type = set, sub_els = [#privacy_query{
    +					 lists = [#privacy_list{
    +						     name = Name,
    +						     items = Items}]}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ct:comment("Receiving privacy list push"),
    +	    #iq{type = set, id = ID,
    +		sub_els = [#privacy_query{lists = [#privacy_list{
    +						      name = Name}]}]} =
    +		recv_iq(Config),
    +	    send(Config, #iq{type = result, id = ID}),
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +get_list(Config, Name) ->
    +    ct:comment("Requesting privacy list ~s", [Name]),
    +    case send_recv(Config,
    +		   #iq{type = get,
    +		       sub_els = [#privacy_query{
    +				     lists = [#privacy_list{name = Name}]}]}) of
    +	#iq{type = result, sub_els = [#privacy_query{lists = [List]}]} ->
    +	    List;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +get_lists(Config) ->
    +    ct:comment("Requesting privacy lists"),
    +    case send_recv(Config, #iq{type = get, sub_els = [#privacy_query{}]}) of
    +	#iq{type = result, sub_els = [SubEl]} ->
    +	    SubEl;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +del_list(Config, Name) ->
    +    case send_recv(
    +	   Config,
    +	   #iq{type = set, sub_els = [#privacy_query{
    +					 lists = [#privacy_list{
    +						     name = Name}]}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ct:comment("Receiving privacy list push"),
    +	    #iq{type = set, id = ID,
    +		sub_els = [#privacy_query{lists = [#privacy_list{
    +						      name = Name}]}]} =
    +		recv_iq(Config),
    +	    send(Config, #iq{type = result, id = ID}),
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +set_active(Config, Name) ->
    +    ct:comment("Setting active privacy list ~s", [Name]),
    +    case send_recv(
    +	   Config,
    +	   #iq{type = set, sub_els = [#privacy_query{active = Name}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +set_default(Config, Name) ->
    +    ct:comment("Setting default privacy list ~s", [Name]),
    +    case send_recv(
    +	   Config,
    +	   #iq{type = set, sub_els = [#privacy_query{default = Name}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +get_block(Config) ->
    +    case send_recv(Config, #iq{type = get, sub_els = [#block_list{}]}) of
    +	#iq{type = result, sub_els = [#block_list{items = JIDs}]} ->
    +	    lists:sort(JIDs);
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +set_block(Config, JIDs) ->
    +    case send_recv(Config, #iq{type = set,
    +			       sub_els = [#block{items = JIDs}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    {#iq{id = I1, sub_els = [#block{items = Items}]},
    +	     #iq{id = I2, sub_els = [#privacy_query{lists = Lists}]}} =
    +		?recv2(#iq{type = set, sub_els = [#block{}]},
    +		       #iq{type = set, sub_els = [#privacy_query{}]}),
    +	    send(Config, #iq{type = result, id = I1}),
    +	    send(Config, #iq{type = result, id = I2}),
    +	    ct:comment("Checking if all JIDs present in the push"),
    +	    true = lists:sort(JIDs) == lists:sort(Items),
    +	    ct:comment("Getting name of the corresponding privacy list"),
    +	    [#privacy_list{name = Name}] = Lists,
    +	    {ok, Name};
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +set_unblock(Config, JIDs) ->
    +    ct:comment("Unblocking ~p", [JIDs]),
    +    case send_recv(Config, #iq{type = set,
    +			       sub_els = [#unblock{items = JIDs}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    {#iq{id = I1, sub_els = [#unblock{items = Items}]},
    +	     #iq{id = I2, sub_els = [#privacy_query{lists = Lists}]}} =
    +		?recv2(#iq{type = set, sub_els = [#unblock{}]},
    +		       #iq{type = set, sub_els = [#privacy_query{}]}),
    +	    send(Config, #iq{type = result, id = I1}),
    +	    send(Config, #iq{type = result, id = I2}),
    +	    ct:comment("Checking if all JIDs present in the push"),
    +	    true = lists:sort(JIDs) == lists:sort(Items),
    +	    ct:comment("Getting name of the corresponding privacy list"),
    +	    [#privacy_list{name = Name}] = Lists,
    +	    {ok, Name};
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +del_privacy(Config) ->
    +    {U, S, _} = jid:tolower(my_jid(Config)),
    +    ct:comment("Removing all privacy data"),
    +    mod_privacy:remove_user(U, S),
    +    Config.
    +
    +clean_up(Config) ->
    +    del_privacy(del_roster(Config)).
    +
    +check_iq_blocked(Config, Reason) ->
    +    PeerJID = ?config(peer, Config),
    +    ct:comment("Checking if all IQs are blocked"),
    +    lists:foreach(
    +      fun(Type) ->
    +	      send(Config, #iq{type = Type, to = PeerJID})
    +      end, [error, result]),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #iq{type = error} = Err =
    +		  send_recv(Config, #iq{type = Type, to = PeerJID,
    +					sub_els = [#ping{}]}),
    +	      #stanza_error{reason = Reason} = xmpp:get_error(Err)
    +      end, [set, get]).
    +
    +check_message_blocked(Config, Reason) ->
    +    PeerJID = ?config(peer, Config),
    +    ct:comment("Checking if all messages are blocked"),
    +    %% TODO: do something with headline and groupchat.
    +    %% The hack from 64d96778b452aad72349b21d2ac94e744617b07a
    +    %% screws this up.
    +    lists:foreach(
    +      fun(Type) ->
    +	      send(Config, #message{type = Type, to = PeerJID})
    +      end, [error]),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #message{type = error} = Err =
    +		  send_recv(Config, #message{type = Type, to = PeerJID}),
    +	      #stanza_error{reason = Reason} = xmpp:get_error(Err)
    +      end, [chat, normal]).
    +
    +check_presence_blocked(Config, Reason) ->
    +    PeerJID = ?config(peer, Config),
    +    ct:comment("Checking if all presences are blocked"),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #presence{type = error} = Err =
    +		  send_recv(Config, #presence{type = Type, to = PeerJID}),
    +	      #stanza_error{reason = Reason} = xmpp:get_error(Err)
    +      end, [available, unavailable]).
    +
    +check_other_blocked(Config, Reason) ->
    +    PeerJID = ?config(peer, Config),
    +    ct:comment("Checking if subscriptions and presence-errors are blocked"),
    +    send(Config, #presence{type = error, to = PeerJID}),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #presence{type = error} = Err =
    +		  send_recv(Config, #presence{type = Type, to = PeerJID}),
    +	      #stanza_error{reason = Reason} = xmpp:get_error(Err)
    +      end, [subscribe, subscribed, unsubscribe, unsubscribed]).
    +
    +send_presences(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    ct:comment("Sending all types of presences to the peer"),
    +    lists:foreach(
    +      fun(Type) ->
    +	      send(Config, #presence{type = Type, to = PeerJID})
    +      end, [available, unavailable]).
    +
    +send_iqs(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    ct:comment("Sending all types of IQs to the peer"),
    +    lists:foreach(
    +      fun(Type) ->
    +	      send(Config, #iq{type = Type, to = PeerJID})
    +      end, [set, get, error, result]).
    +
    +send_messages(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    ct:comment("Sending all types of messages to the peer"),
    +    lists:foreach(
    +      fun(Type) ->
    +	      send(Config, #message{type = Type, to = PeerJID})
    +      end, [chat, error, groupchat, headline, normal]).
    +
    +recv_presences(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #presence{type = Type, from = PeerJID} =
    +		  recv_presence(Config)
    +      end, [available, unavailable]).
    +
    +recv_iqs(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #iq{type = Type, from = PeerJID} = recv_iq(Config)
    +      end, [set, get, error, result]).
    +
    +recv_messages(Config) ->
    +    PeerJID = ?config(peer, Config),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #message{type = Type, from = PeerJID} = recv_message(Config)
    +      end, [chat, error, groupchat, headline, normal]).
    +
    +match_all(Opts) ->
    +    IQ = proplists:get_bool(iq, Opts),
    +    Message = proplists:get_bool(message, Opts),
    +    PresenceIn = proplists:get_bool(presence_in, Opts),
    +    PresenceOut = proplists:get_bool(presence_out, Opts),
    +    not (IQ or Message or PresenceIn or PresenceOut).
    +
    +is_message_in_blocked(Opts) ->
    +    proplists:get_bool(message, Opts) or match_all(Opts).
    +
    +is_message_out_blocked(Opts) ->
    +    match_all(Opts).
    +
    +is_iq_in_blocked(Opts) ->    
    +    proplists:get_bool(iq, Opts) or match_all(Opts).
    +
    +is_iq_out_blocked(Opts) ->
    +    match_all(Opts).
    +
    +is_presence_in_blocked(Opts) ->
    +    proplists:get_bool(presence_in, Opts) or match_all(Opts).
    +
    +is_presence_out_blocked(Opts) ->
    +    proplists:get_bool(presence_out, Opts) or match_all(Opts).
    +
    +is_other_blocked(Opts) ->
    +    %% 'other' means subscriptions and presence-errors
    +    match_all(Opts).
    +
    +server_send_iqs(Config) ->
    +    ServerJID = server_jid(Config),
    +    MyJID = my_jid(Config),
    +    ct:comment("Sending IQs from ~s to ~s",
    +	       [jid:to_string(ServerJID), jid:to_string(MyJID)]),
    +    lists:foreach(
    +      fun(Type) ->
    +	      ejabberd_router:route(
    +		ServerJID, MyJID, #iq{type = Type})
    +      end, [error, result]),
    +    lists:foreach(
    +      fun(Type) ->
    +	      ejabberd_local:route_iq(
    +		ServerJID, MyJID, #iq{type = Type},
    +		fun(#iq{type = result, sub_els = []}) -> ok;
    +		   (IQ) -> ct:fail({unexpected_iq_result, IQ})
    +		end)
    +      end, [set, get]).
    +
    +server_recv_iqs(Config) ->
    +    ServerJID = server_jid(Config),
    +    ct:comment("Receiving IQs from ~s", [jid:to_string(ServerJID)]),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #iq{type = Type, from = ServerJID} = recv_iq(Config)
    +      end, [error, result]),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #iq{type = Type, from = ServerJID, id = I} = recv_iq(Config),
    +	      send(Config, #iq{to = ServerJID, type = result, id = I})
    +      end, [set, get]).
    +
    +send_stanzas_to_server_resource(Config) ->
    +    ServerJID = server_jid(Config),
    +    ServerJIDResource = jid:replace_resource(ServerJID, <<"resource">>),
    +    %% All stanzas sent should be handled by local_send_to_resource_hook
    +    %% and should be bounced with item-not-found error
    +    ct:comment("Sending IQs to ~s", [jid:to_string(ServerJIDResource)]),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #iq{type = error} = Err =
    +		  send_recv(Config, #iq{type = Type, to = ServerJIDResource}),
    +		  #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
    +      end, [set, get]),
    +    ct:comment("Sending messages to ~s", [jid:to_string(ServerJIDResource)]),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #message{type = error} = Err =
    +		  send_recv(Config, #message{type = Type, to = ServerJIDResource}),
    +	      #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
    +      end, [normal, chat, groupchat, headline]),
    +    ct:comment("Sending presences to ~s", [jid:to_string(ServerJIDResource)]),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #presence{type = error} = Err =
    +		  send_recv(Config, #presence{type = Type, to = ServerJIDResource}),
    +	      #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
    +      end, [available, unavailable]).
    diff --git a/test/proxy65_tests.erl b/test/proxy65_tests.erl
    new file mode 100644
    index 000000000..49e195d38
    --- /dev/null
    +++ b/test/proxy65_tests.erl
    @@ -0,0 +1,113 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(proxy65_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [disconnect/1, is_feature_advertised/3, proxy_jid/1,
    +		my_jid/1, wait_for_slave/1, wait_for_master/1,
    +		send_recv/2, put_event/2, get_event/1]).
    +
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {proxy65_single, [sequence],
    +     [single_test(feature_enabled),
    +      single_test(service_vcard)]}.
    +
    +feature_enabled(Config) ->
    +    true = is_feature_advertised(Config, ?NS_BYTESTREAMS, proxy_jid(Config)),
    +    disconnect(Config).
    +
    +service_vcard(Config) ->
    +    JID = proxy_jid(Config),
    +    ct:comment("Retreiving vCard from ~s", [jid:to_string(JID)]),
    +    #iq{type = result, sub_els = [#vcard_temp{}]} =
    +	send_recv(Config, #iq{type = get, to = JID, sub_els = [#vcard_temp{}]}),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {proxy65_master_slave, [sequence],
    +     [master_slave_test(all)]}.
    +
    +all_master(Config) ->
    +    Proxy = proxy_jid(Config),
    +    MyJID = my_jid(Config),
    +    Peer = ?config(slave, Config),
    +    wait_for_slave(Config),
    +    #presence{} = send_recv(Config, #presence{}),
    +    #iq{type = result, sub_els = [#bytestreams{hosts = [StreamHost]}]} =
    +        send_recv(
    +          Config,
    +          #iq{type = get, sub_els = [#bytestreams{}], to = Proxy}),
    +    SID = randoms:get_string(),
    +    Data = randoms:bytes(1024),
    +    put_event(Config, {StreamHost, SID, Data}),
    +    Socks5 = socks5_connect(StreamHost, {SID, MyJID, Peer}),
    +    wait_for_slave(Config),
    +    #iq{type = result, sub_els = []} =
    +        send_recv(Config,
    +                  #iq{type = set, to = Proxy,
    +                      sub_els = [#bytestreams{activate = Peer, sid = SID}]}),
    +    socks5_send(Socks5, Data),
    +    disconnect(Config).
    +
    +all_slave(Config) ->
    +    MyJID = my_jid(Config),
    +    Peer = ?config(master, Config),
    +    #presence{} = send_recv(Config, #presence{}),
    +    wait_for_master(Config),
    +    {StreamHost, SID, Data} = get_event(Config),
    +    Socks5 = socks5_connect(StreamHost, {SID, Peer, MyJID}),
    +    wait_for_master(Config),
    +    socks5_recv(Socks5, Data),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("proxy65_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("proxy65_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("proxy65_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("proxy65_" ++ atom_to_list(T) ++ "_slave")]}.
    +
    +socks5_connect(#streamhost{host = Host, port = Port},
    +               {SID, JID1, JID2}) ->
    +    Hash = p1_sha:sha([SID, jid:to_string(JID1), jid:to_string(JID2)]),
    +    {ok, Sock} = gen_tcp:connect(binary_to_list(Host), Port,
    +                                 [binary, {active, false}]),
    +    Init = <>,
    +    InitAck = <>,
    +    Req = <>,
    +    Resp = <>,
    +    gen_tcp:send(Sock, Init),
    +    {ok, InitAck} = gen_tcp:recv(Sock, size(InitAck)),
    +    gen_tcp:send(Sock, Req),
    +    {ok, Resp} = gen_tcp:recv(Sock, size(Resp)),
    +    Sock.
    +
    +socks5_send(Sock, Data) ->
    +    ok = gen_tcp:send(Sock, Data).
    +
    +socks5_recv(Sock, Data) ->
    +    {ok, Data} = gen_tcp:recv(Sock, size(Data)).
    diff --git a/test/pubsub_tests.erl b/test/pubsub_tests.erl
    new file mode 100644
    index 000000000..daffc29ec
    --- /dev/null
    +++ b/test/pubsub_tests.erl
    @@ -0,0 +1,737 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(pubsub_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [pubsub_jid/1, send_recv/2, get_features/2, disconnect/1,
    +		put_event/2, get_event/1, wait_for_master/1, wait_for_slave/1,
    +		recv_message/1, my_jid/1, send/2, recv_presence/1, recv/1]).
    +
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {pubsub_single, [sequence],
    +     [single_test(test_features),
    +      single_test(test_vcard),
    +      single_test(test_create),
    +      single_test(test_configure),
    +      single_test(test_delete),
    +      single_test(test_get_affiliations),
    +      single_test(test_get_subscriptions),
    +      single_test(test_create_instant),
    +      single_test(test_default),
    +      single_test(test_create_configure),
    +      single_test(test_publish),
    +      single_test(test_auto_create),
    +      single_test(test_get_items),
    +      single_test(test_delete_item),
    +      single_test(test_purge),
    +      single_test(test_subscribe),
    +      single_test(test_unsubscribe)]}.
    +
    +test_features(Config) ->
    +    PJID = pubsub_jid(Config),
    +    AllFeatures = sets:from_list(get_features(Config, PJID)),
    +    NeededFeatures = sets:from_list(
    +		       [?NS_PUBSUB,
    +			?PUBSUB("access-open"),
    +			?PUBSUB("access-authorize"),
    +			?PUBSUB("create-nodes"),
    +			?PUBSUB("instant-nodes"),
    +			?PUBSUB("config-node"),
    +			?PUBSUB("retrieve-default"),
    +			?PUBSUB("create-and-configure"),
    +			?PUBSUB("publish"),
    +			?PUBSUB("auto-create"),
    +			?PUBSUB("retrieve-items"),
    +			?PUBSUB("delete-items"),
    +			?PUBSUB("subscribe"),
    +			?PUBSUB("retrieve-affiliations"),
    +			?PUBSUB("modify-affiliations"),
    +			?PUBSUB("retrieve-subscriptions"),
    +			?PUBSUB("manage-subscriptions"),
    +			?PUBSUB("purge-nodes"),
    +			?PUBSUB("delete-nodes")]),
    +    true = sets:is_subset(NeededFeatures, AllFeatures),
    +    disconnect(Config).
    +
    +test_vcard(Config) ->
    +    JID = pubsub_jid(Config),
    +    ct:comment("Retreiving vCard from ~s", [jid:to_string(JID)]),
    +    #iq{type = result, sub_els = [#vcard_temp{}]} =
    +	send_recv(Config, #iq{type = get, to = JID, sub_els = [#vcard_temp{}]}),
    +    disconnect(Config).
    +
    +test_create(Config) ->
    +    Node = ?config(pubsub_node, Config),
    +    Node = create_node(Config, Node),
    +    disconnect(Config).
    +
    +test_create_instant(Config) ->
    +    Node = create_node(Config, <<>>),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +test_configure(Config) ->
    +    Node = ?config(pubsub_node, Config),
    +    NodeTitle = ?config(pubsub_node_title, Config),
    +    NodeConfig = get_node_config(Config, Node),
    +    MyNodeConfig = set_opts(NodeConfig,
    +			    [{title, NodeTitle}]),
    +    set_node_config(Config, Node, MyNodeConfig),
    +    NewNodeConfig = get_node_config(Config, Node),
    +    NodeTitle = proplists:get_value(title, NewNodeConfig),
    +    disconnect(Config).
    +
    +test_default(Config) ->
    +    get_default_node_config(Config),
    +    disconnect(Config).
    +
    +test_create_configure(Config) ->
    +    NodeTitle = ?config(pubsub_node_title, Config),
    +    DefaultNodeConfig = get_default_node_config(Config),
    +    CustomNodeConfig = set_opts(DefaultNodeConfig,
    +				[{title, NodeTitle}]),
    +    Node = create_node(Config, <<>>, CustomNodeConfig),
    +    NodeConfig = get_node_config(Config, Node),
    +    NodeTitle = proplists:get_value(title, NodeConfig),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +test_publish(Config) ->
    +    Node = create_node(Config, <<>>),
    +    publish_item(Config, Node),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +test_auto_create(Config) ->
    +    Node = randoms:get_string(),
    +    publish_item(Config, Node),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +test_get_items(Config) ->
    +    Node = create_node(Config, <<>>),
    +    ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)],
    +    ItemsOut = get_items(Config, Node),
    +    true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)]
    +	== [I || #ps_item{id = I} <- lists:sort(ItemsOut)],
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +test_delete_item(Config) ->
    +    Node = create_node(Config, <<>>),
    +    #ps_item{id = I} = publish_item(Config, Node),
    +    [#ps_item{id = I}] = get_items(Config, Node),
    +    delete_item(Config, Node, I),
    +    [] = get_items(Config, Node),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +test_subscribe(Config) ->
    +    Node = create_node(Config, <<>>),
    +    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
    +    [#ps_subscription{node = Node}] = get_subscriptions(Config),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +test_unsubscribe(Config) ->
    +    Node = create_node(Config, <<>>),
    +    subscribe_node(Config, Node),
    +    [#ps_subscription{node = Node}] = get_subscriptions(Config),
    +    unsubscribe_node(Config, Node),
    +    [] = get_subscriptions(Config),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +test_get_affiliations(Config) ->
    +    Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]),
    +    Affs = get_affiliations(Config),
    +    Nodes = lists:sort([Node || #ps_affiliation{node = Node,
    +						type = owner} <- Affs]),
    +    [delete_node(Config, Node) || Node <- Nodes],
    +    disconnect(Config).
    +
    +test_get_subscriptions(Config) ->
    +    Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]),
    +    [subscribe_node(Config, Node) || Node <- Nodes],
    +    Subs = get_subscriptions(Config),
    +    Nodes = lists:sort([Node || #ps_subscription{node = Node} <- Subs]),
    +    [delete_node(Config, Node) || Node <- Nodes],
    +    disconnect(Config).
    +
    +test_purge(Config) ->
    +    Node = create_node(Config, <<>>),
    +    ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)],
    +    ItemsOut = get_items(Config, Node),
    +    true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)]
    +	== [I || #ps_item{id = I} <- lists:sort(ItemsOut)],
    +    purge_node(Config, Node),
    +    [] = get_items(Config, Node),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +test_delete(Config) ->
    +    Node = ?config(pubsub_node, Config),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {pubsub_master_slave, [sequence],
    +     [master_slave_test(publish),
    +      master_slave_test(subscriptions),
    +      master_slave_test(affiliations),
    +      master_slave_test(authorize)]}.
    +
    +publish_master(Config) ->
    +    Node = create_node(Config, <<>>),
    +    put_event(Config, Node),
    +    wait_for_slave(Config),
    +    #ps_item{id = ID} = publish_item(Config, Node),
    +    #ps_item{id = ID} = get_event(Config),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +publish_slave(Config) ->
    +    Node = get_event(Config),
    +    subscribe_node(Config, Node),
    +    wait_for_master(Config),
    +    #message{
    +       sub_els =
    +	   [#ps_event{
    +	       items = #ps_items{node = Node,
    +				 items = [Item]}}]} = recv_message(Config),
    +    put_event(Config, Item),
    +    disconnect(Config).
    +
    +subscriptions_master(Config) ->
    +    Peer = ?config(slave, Config),
    +    Node = ?config(pubsub_node, Config),
    +    Node = create_node(Config, Node),
    +    [] = get_subscriptions(Config, Node),
    +    wait_for_slave(Config),
    +    lists:foreach(
    +      fun(Type) ->
    +	      ok = set_subscriptions(Config, Node, [{Peer, Type}]),
    +	      #ps_item{} = publish_item(Config, Node),
    +	      case get_subscriptions(Config, Node) of
    +		  [] when Type == none; Type == pending ->
    +		      ok;
    +		  [#ps_subscription{jid = Peer, type = Type}] ->
    +		      ok
    +	      end
    +      end, [subscribed, unconfigured, pending, none]),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +subscriptions_slave(Config) ->
    +    wait_for_master(Config),
    +    MyJID = my_jid(Config),
    +    Node = ?config(pubsub_node, Config),
    +    lists:foreach(
    +      fun(subscribed = Type) ->
    +	      ?recv2(#message{
    +			sub_els =
    +			    [#ps_event{
    +				subscription = #ps_subscription{
    +						  node = Node,
    +						  jid = MyJID,
    +						  type = Type}}]},
    +		     #message{sub_els = [#ps_event{}]});
    +	 (Type) ->
    +	      #message{
    +		 sub_els =
    +		     [#ps_event{
    +			 subscription = #ps_subscription{
    +					   node = Node,
    +					   jid = MyJID,
    +					   type = Type}}]} =
    +		  recv_message(Config)
    +      end, [subscribed, unconfigured, pending, none]),
    +    disconnect(Config).
    +
    +affiliations_master(Config) ->
    +    Peer = ?config(slave, Config),
    +    BarePeer = jid:remove_resource(Peer),
    +    lists:foreach(
    +      fun(Aff) ->
    +	      Node = <<(atom_to_binary(Aff, utf8))/binary,
    +		       $-, (randoms:get_string())/binary>>,
    +	      create_node(Config, Node, default_node_config(Config)),
    +	      #ps_item{id = I} = publish_item(Config, Node),
    +	      ok = set_affiliations(Config, Node, [{Peer, Aff}]),
    +	      Affs = get_affiliations(Config, Node),
    +	      case lists:keyfind(BarePeer, #ps_affiliation.jid, Affs) of
    +		  false when Aff == none ->
    +		      ok;
    +		  #ps_affiliation{type = Aff} ->
    +		      ok
    +	      end,
    +	      put_event(Config, {Aff, Node, I}),
    +	      wait_for_slave(Config),
    +	      delete_node(Config, Node)
    +      end, [outcast, none, member, publish_only, publisher, owner]),
    +    put_event(Config, disconnect),
    +    disconnect(Config).
    +
    +affiliations_slave(Config) ->
    +    affiliations_slave(Config, get_event(Config)).
    +
    +affiliations_slave(Config, {outcast, Node, ItemID}) ->
    +    #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node),
    +    #stanza_error{} = unsubscribe_node(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = get_items(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
    +    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_node_config(Config, Node, default_node_config(Config)),
    +    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
    +    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_affiliations(Config, Node, [{?config(master, Config), outcast},
    +					{my_jid(Config), owner}]),
    +    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
    +    wait_for_master(Config),
    +    affiliations_slave(Config, get_event(Config));
    +affiliations_slave(Config, {none, Node, ItemID}) ->
    +    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
    +    ok = unsubscribe_node(Config, Node),
    +    %% This violates the affiliation char from section 4.1
    +    [_|_] = get_items(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
    +    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_node_config(Config, Node, default_node_config(Config)),
    +    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
    +    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_affiliations(Config, Node, [{?config(master, Config), outcast},
    +					{my_jid(Config), owner}]),
    +    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
    +    wait_for_master(Config),
    +    affiliations_slave(Config, get_event(Config));
    +affiliations_slave(Config, {member, Node, ItemID}) ->
    +    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
    +    ok = unsubscribe_node(Config, Node),
    +    [_|_] = get_items(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
    +    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_node_config(Config, Node, default_node_config(Config)),
    +    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
    +    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_affiliations(Config, Node, [{?config(master, Config), outcast},
    +					{my_jid(Config), owner}]),
    +    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
    +    wait_for_master(Config),
    +    affiliations_slave(Config, get_event(Config));
    +affiliations_slave(Config, {publish_only, Node, ItemID}) ->
    +    #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node),
    +    #stanza_error{} = unsubscribe_node(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = get_items(Config, Node),
    +    #ps_item{id = _MyItemID} = publish_item(Config, Node),
    +    %% BUG: This should be fixed
    +    %% ?match(ok, delete_item(Config, Node, MyItemID)),
    +    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
    +    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_node_config(Config, Node, default_node_config(Config)),
    +    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
    +    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_affiliations(Config, Node, [{?config(master, Config), outcast},
    +					{my_jid(Config), owner}]),
    +    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
    +    wait_for_master(Config),
    +    affiliations_slave(Config, get_event(Config));
    +affiliations_slave(Config, {publisher, Node, _ItemID}) ->
    +    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
    +    ok = unsubscribe_node(Config, Node),
    +    [_|_] = get_items(Config, Node),
    +    #ps_item{id = MyItemID} = publish_item(Config, Node),
    +    ok = delete_item(Config, Node, MyItemID),
    +    %% BUG: this should be fixed
    +    %% #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
    +    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
    +    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_node_config(Config, Node, default_node_config(Config)),
    +    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
    +    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
    +    #stanza_error{reason = 'forbidden'} =
    +	set_affiliations(Config, Node, [{?config(master, Config), outcast},
    +					{my_jid(Config), owner}]),
    +    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
    +    wait_for_master(Config),
    +    affiliations_slave(Config, get_event(Config));
    +affiliations_slave(Config, {owner, Node, ItemID}) ->
    +    MyJID = my_jid(Config),
    +    Peer = ?config(master, Config),
    +    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
    +    ok = unsubscribe_node(Config, Node),
    +    [_|_] = get_items(Config, Node),
    +    #ps_item{id = MyItemID} = publish_item(Config, Node),
    +    ok = delete_item(Config, Node, MyItemID),
    +    ok = delete_item(Config, Node, ItemID),
    +    ok = purge_node(Config, Node),
    +    [_|_] = get_node_config(Config, Node),
    +    ok = set_node_config(Config, Node, default_node_config(Config)),
    +    ok = set_subscriptions(Config, Node, []),
    +    [] = get_subscriptions(Config, Node),
    +    ok = set_affiliations(Config, Node, [{Peer, outcast}, {MyJID, owner}]),
    +    [_, _] = get_affiliations(Config, Node),
    +    ok = delete_node(Config, Node),
    +    wait_for_master(Config),
    +    affiliations_slave(Config, get_event(Config));
    +affiliations_slave(Config, disconnect) ->
    +    disconnect(Config).
    +
    +authorize_master(Config) ->
    +    send(Config, #presence{}),
    +    #presence{} = recv_presence(Config),
    +    Peer = ?config(slave, Config),
    +    PJID = pubsub_jid(Config),
    +    NodeConfig = set_opts(default_node_config(Config),
    +			  [{access_model, authorize}]),
    +    Node = ?config(pubsub_node, Config),
    +    Node = create_node(Config, Node, NodeConfig),
    +    wait_for_slave(Config),
    +    #message{sub_els = [#xdata{fields = F1}]} = recv_message(Config),
    +    C1 = pubsub_subscribe_authorization:decode(F1),
    +    Node = proplists:get_value(node, C1),
    +    Peer = proplists:get_value(subscriber_jid, C1),
    +    %% Deny it at first
    +    Deny = #xdata{type = submit,
    +		  fields = pubsub_subscribe_authorization:encode(
    +			     [{node, Node},
    +			      {subscriber_jid, Peer},
    +			      {allow, false}])},
    +    send(Config, #message{to = PJID, sub_els = [Deny]}),
    +    %% We should not have any subscriptions
    +    [] = get_subscriptions(Config, Node),
    +    wait_for_slave(Config),
    +    #message{sub_els = [#xdata{fields = F2}]} = recv_message(Config),
    +    C2 = pubsub_subscribe_authorization:decode(F2),
    +    Node = proplists:get_value(node, C2),
    +    Peer = proplists:get_value(subscriber_jid, C2),
    +    %% Now we accept is as the peer is very insisting ;)
    +    Approve = #xdata{type = submit,
    +		     fields = pubsub_subscribe_authorization:encode(
    +				[{node, Node},
    +				 {subscriber_jid, Peer},
    +				 {allow, true}])},
    +    send(Config, #message{to = PJID, sub_els = [Approve]}),
    +    wait_for_slave(Config),
    +    delete_node(Config, Node),
    +    disconnect(Config).
    +
    +authorize_slave(Config) ->
    +    Node = ?config(pubsub_node, Config),
    +    MyJID = my_jid(Config),
    +    wait_for_master(Config),
    +    #ps_subscription{type = pending} = subscribe_node(Config, Node),
    +    %% We're denied at first
    +    #message{
    +       sub_els =
    +	   [#ps_event{
    +	       subscription = #ps_subscription{type = none,
    +					       jid = MyJID}}]} =
    +	recv_message(Config),
    +    wait_for_master(Config),
    +    #ps_subscription{type = pending} = subscribe_node(Config, Node),
    +    %% Now much better!
    +    #message{
    +       sub_els =
    +	   [#ps_event{
    +	       subscription = #ps_subscription{type = subscribed,
    +					       jid = MyJID}}]} =
    +	recv_message(Config),
    +    wait_for_master(Config),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("pubsub_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("pubsub_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("pubsub_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("pubsub_" ++ atom_to_list(T) ++ "_slave")]}.
    +
    +set_opts(Config, Options) ->
    +    lists:foldl(
    +      fun({Opt, Val}, Acc) ->
    +	      lists:keystore(Opt, 1, Acc, {Opt, Val})
    +      end, Config, Options).
    +
    +create_node(Config, Node) ->
    +    create_node(Config, Node, undefined).
    +
    +create_node(Config, Node, Options) ->
    +    PJID = pubsub_jid(Config),
    +    NodeConfig = if is_list(Options) ->
    +			 #xdata{type = submit,
    +				fields = pubsub_node_config:encode(Options)};
    +		    true ->
    +			 undefined
    +		 end,
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub{create = Node,
    +					  configure = {<<>>, NodeConfig}}]}) of
    +	#iq{type = result, sub_els = [#pubsub{create = NewNode}]} ->
    +	    NewNode;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +delete_node(Config, Node) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub_owner{delete = {Node, <<>>}}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +purge_node(Config, Node) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub_owner{purge = Node}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +get_default_node_config(Config) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = get, to = PJID,
    +		       sub_els = [#pubsub_owner{default = {<<>>, undefined}}]}) of
    +	#iq{type = result,
    +	    sub_els = [#pubsub_owner{default = {<<>>, NodeConfig}}]} ->
    +	    pubsub_node_config:decode(NodeConfig#xdata.fields);
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +get_node_config(Config, Node) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = get, to = PJID,
    +		       sub_els = [#pubsub_owner{configure = {Node, undefined}}]}) of
    +	#iq{type = result,
    +	    sub_els = [#pubsub_owner{configure = {Node, NodeConfig}}]} ->
    +	    pubsub_node_config:decode(NodeConfig#xdata.fields);
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +set_node_config(Config, Node, Options) ->
    +    PJID = pubsub_jid(Config),
    +    NodeConfig = #xdata{type = submit,
    +			fields = pubsub_node_config:encode(Options)},
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub_owner{configure =
    +						    {Node, NodeConfig}}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +publish_item(Config, Node) ->
    +    PJID = pubsub_jid(Config),
    +    ItemID = randoms:get_string(),
    +    Item = #ps_item{id = ItemID, xml_els = [xmpp:encode(#presence{id = ItemID})]},
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub{publish = #ps_publish{
    +						       node = Node,
    +						       items = [Item]}}]}) of
    +	#iq{type = result,
    +	    sub_els = [#pubsub{publish = #ps_publish{
    +					    node = Node,
    +					    items = [#ps_item{id = ItemID}]}}]} ->
    +	    Item;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +get_items(Config, Node) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = get, to = PJID,
    +		       sub_els = [#pubsub{items = #ps_items{node = Node}}]}) of
    +	#iq{type = result,
    +	    sub_els = [#pubsub{items = #ps_items{node = Node, items = Items}}]} ->
    +	    Items;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +delete_item(Config, Node, I) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub{retract =
    +					      #ps_retract{
    +						 node = Node,
    +						 items = [#ps_item{id = I}]}}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +subscribe_node(Config, Node) ->
    +    PJID = pubsub_jid(Config),
    +    MyJID = my_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub{subscribe = #ps_subscribe{
    +							 node = Node,
    +							 jid = MyJID}}]}) of
    +	#iq{type = result,
    +	    sub_els = [#pubsub{
    +			  subscription = #ps_subscription{
    +					    node = Node,
    +					    jid = MyJID} = Sub}]} ->
    +	    Sub;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +unsubscribe_node(Config, Node) ->
    +    PJID = pubsub_jid(Config),
    +    MyJID = my_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub{
    +				     unsubscribe = #ps_unsubscribe{
    +						      node = Node,
    +						      jid = MyJID}}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +get_affiliations(Config) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = get, to = PJID,
    +		       sub_els = [#pubsub{affiliations = {<<>>, []}}]}) of
    +	#iq{type = result,
    +	    sub_els = [#pubsub{affiliations = {<<>>, Affs}}]} ->
    +	    Affs;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +get_affiliations(Config, Node) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = get, to = PJID,
    +		       sub_els = [#pubsub_owner{affiliations = {Node, []}}]}) of
    +	#iq{type = result,
    +	    sub_els = [#pubsub_owner{affiliations = {Node, Affs}}]} ->
    +	    Affs;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +set_affiliations(Config, Node, JTs) ->
    +    PJID = pubsub_jid(Config),
    +    Affs = [#ps_affiliation{jid = J, type = T} || {J, T} <- JTs],
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub_owner{affiliations =
    +						    {Node, Affs}}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +get_subscriptions(Config) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = get, to = PJID,
    +		       sub_els = [#pubsub{subscriptions = {<<>>, []}}]}) of
    +	#iq{type = result, sub_els = [#pubsub{subscriptions = {<<>>, Subs}}]} ->
    +	    Subs;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +get_subscriptions(Config, Node) ->
    +    PJID = pubsub_jid(Config),
    +    case send_recv(Config,
    +		   #iq{type = get, to = PJID,
    +		       sub_els = [#pubsub_owner{subscriptions = {Node, []}}]}) of
    +	#iq{type = result,
    +	    sub_els = [#pubsub_owner{subscriptions = {Node, Subs}}]} ->
    +	    Subs;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +set_subscriptions(Config, Node, JTs) ->
    +    PJID = pubsub_jid(Config),
    +    Subs = [#ps_subscription{jid = J, type = T} || {J, T} <- JTs],
    +    case send_recv(Config,
    +		   #iq{type = set, to = PJID,
    +		       sub_els = [#pubsub_owner{subscriptions =
    +						    {Node, Subs}}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    ok;
    +	#iq{type = error} = IQ ->
    +	    xmpp:get_subtag(IQ, #stanza_error{})
    +    end.
    +
    +default_node_config(Config) ->
    +    [{title, ?config(pubsub_node_title, Config)},
    +     {notify_delete, false},
    +     {send_last_published_item, never}].
    diff --git a/test/replaced_tests.erl b/test/replaced_tests.erl
    new file mode 100644
    index 000000000..e50c27f05
    --- /dev/null
    +++ b/test/replaced_tests.erl
    @@ -0,0 +1,57 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(replaced_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [bind/1, wait_for_slave/1, wait_for_master/1, recv/1,
    +		close_socket/1, disconnect/1]).
    +
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {replaced_single, [sequence], []}.
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {replaced_master_slave, [sequence], []}.
    +%% Disable tests for now due to a race condition
    +%% because ejabberd_sm:sid() is generated in ejabberd_s2s:init()
    +%%[master_slave_test(conflict)]}.
    +
    +conflict_master(Config0) ->
    +    Config = bind(Config0),
    +    wait_for_slave(Config),
    +    #stream_error{reason = conflict} = recv(Config),
    +    {xmlstreamend, <<"stream:stream">>} = recv(Config),
    +    close_socket(Config).
    +
    +conflict_slave(Config0) ->
    +    wait_for_master(Config0),
    +    Config = bind(Config0),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("replaced_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("replaced_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("replaced_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("replaced_" ++ atom_to_list(T) ++ "_slave")]}.
    diff --git a/test/roster_tests.erl b/test/roster_tests.erl
    new file mode 100644
    index 000000000..4aa06b953
    --- /dev/null
    +++ b/test/roster_tests.erl
    @@ -0,0 +1,527 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 22 Oct 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(roster_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [send_recv/2, recv_iq/1, send/2, disconnect/1, del_roster/1,
    +		del_roster/2, make_iq_result/1, wait_for_slave/1,
    +		wait_for_master/1, recv_presence/1, self_presence/2,
    +		put_event/2, get_event/1, match_failure/2, get_roster/1]).
    +-include("suite.hrl").
    +-include("mod_roster.hrl").
    +
    +-record(state, {subscription = none :: none | from | to | both,
    +		peer_available = false,
    +		pending_in = false :: boolean(),
    +		pending_out = false :: boolean()}).
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +init(_TestCase, Config) ->
    +    Config.
    +
    +stop(_TestCase, Config) ->
    +    Config.
    +
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {roster_single, [sequence],
    +     [single_test(feature_enabled),
    +      single_test(iq_set_many_items),
    +      single_test(iq_set_duplicated_groups),
    +      single_test(iq_get_item),
    +      single_test(iq_unexpected_element),
    +      single_test(iq_set_ask),
    +      single_test(set_item),
    +      single_test(version)]}.
    +
    +feature_enabled(Config) ->
    +    ct:comment("Checking if roster versioning stream feature is set"),
    +    true = ?config(rosterver, Config),
    +    disconnect(Config).
    +
    +set_item(Config) ->
    +    JID = jid:from_string(<<"nurse@example.com">>),
    +    Item = #roster_item{jid = JID},
    +    {V1, Item} = set_items(Config, [Item]),
    +    {V1, [Item]} = get_items(Config),
    +    ItemWithGroups = Item#roster_item{groups = [<<"G1">>, <<"G2">>]},
    +    {V2, ItemWithGroups} = set_items(Config, [ItemWithGroups]),
    +    {V2, [ItemWithGroups]} = get_items(Config),
    +    {V3, Item} = set_items(Config, [Item]),
    +    {V3, [Item]} = get_items(Config),
    +    ItemWithName = Item#roster_item{name = <<"some name">>},
    +    {V4, ItemWithName} = set_items(Config, [ItemWithName]),
    +    {V4, [ItemWithName]} = get_items(Config),
    +    ItemRemoved = Item#roster_item{subscription = remove},
    +    {V5, ItemRemoved} = set_items(Config, [ItemRemoved]),
    +    {V5, []} = get_items(Config),
    +    del_roster(disconnect(Config), JID).
    +
    +iq_set_many_items(Config) ->
    +    J1 = jid:from_string(<<"nurse1@example.com">>),
    +    J2 = jid:from_string(<<"nurse2@example.com">>),
    +    ct:comment("Trying to send roster-set with many  elements"),
    +    Items = [#roster_item{jid = J1}, #roster_item{jid = J2}],
    +    #stanza_error{reason = 'bad-request'} = set_items(Config, Items),
    +    disconnect(Config).
    +
    +iq_set_duplicated_groups(Config) ->
    +    JID = jid:from_string(<<"nurse@example.com">>),
    +    G = randoms:get_string(),
    +    ct:comment("Trying to send roster-set with duplicated groups"),
    +    Item = #roster_item{jid = JID, groups = [G, G]},
    +    #stanza_error{reason = 'bad-request'} = set_items(Config, [Item]),
    +    disconnect(Config).
    +
    +iq_set_ask(Config) ->
    +    JID = jid:from_string(<<"nurse@example.com">>),
    +    ct:comment("Trying to send roster-set with 'ask' included"),
    +    Item = #roster_item{jid = JID, ask = subscribe},
    +    #stanza_error{reason = 'bad-request'} = set_items(Config, [Item]),
    +    disconnect(Config).
    +
    +iq_get_item(Config) ->
    +    JID = jid:from_string(<<"nurse@example.com">>),
    +    ct:comment("Trying to send roster-get with  element"),
    +    #iq{type = error} = Err3 =
    +	send_recv(Config, #iq{type = get,
    +			      sub_els = [#roster_query{
    +					    items = [#roster_item{jid = JID}]}]}),
    +    #stanza_error{reason = 'bad-request'} = xmpp:get_error(Err3),
    +    disconnect(Config).
    +
    +iq_unexpected_element(Config) ->
    +    JID = jid:from_string(<<"nurse@example.com">>),
    +    ct:comment("Trying to send IQs with unexpected element"),
    +    lists:foreach(
    +      fun(Type) ->
    +	      #iq{type = error} = Err4 =
    +		  send_recv(Config, #iq{type = Type,
    +					sub_els = [#roster_item{jid = JID}]}),
    +	      #stanza_error{reason = 'service-unavailable'} = xmpp:get_error(Err4)
    +      end, [get, set]),
    +    disconnect(Config).
    +
    +version(Config) ->
    +    JID = jid:from_string(<<"nurse@example.com">>),
    +    ct:comment("Requesting roster"),
    +    {InitialVersion, _} = get_items(Config, <<"">>),
    +    ct:comment("Requesting roster with initial version"),
    +    {empty, []} = get_items(Config, InitialVersion),
    +    ct:comment("Adding JID to the roster"),
    +    {NewVersion, _} = set_items(Config, [#roster_item{jid = JID}]),
    +    ct:comment("Requesting roster with initial version"),
    +    {NewVersion, _} = get_items(Config, InitialVersion),
    +    ct:comment("Requesting roster with new version"),
    +    {empty, []} = get_items(Config, NewVersion),
    +    del_roster(disconnect(Config), JID).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {roster_master_slave, [sequence],
    +     [master_slave_test(subscribe)]}.
    +
    +subscribe_master(Config) ->
    +    Actions = actions(),
    +    process_subscriptions_master(Config, Actions),
    +    del_roster(disconnect(Config)).
    +
    +subscribe_slave(Config) ->
    +    process_subscriptions_slave(Config),
    +    del_roster(disconnect(Config)).
    +
    +process_subscriptions_master(Config, Actions) ->
    +    EnumeratedActions = lists:zip(lists:seq(1, length(Actions)), Actions),
    +    self_presence(Config, available),
    +    lists:foldl(
    +      fun({N, {Dir, Type}}, State) ->
    +	      timer:sleep(100),
    +	      if Dir == out -> put_event(Config, {N, in, Type});
    +		 Dir == in -> put_event(Config, {N, out, Type})
    +	      end,
    +	      wait_for_slave(Config),
    +	      ct:pal("Performing ~s-~s (#~p) "
    +		     "in state:~n~s~nwith roster:~n~s",
    +		     [Dir, Type, N, pp(State),
    +		      pp(get_roster(Config))]),
    +	      transition(Config, Dir, Type, State)
    +      end, #state{}, EnumeratedActions),
    +    put_event(Config, done),
    +    wait_for_slave(Config),
    +    Config.
    +
    +process_subscriptions_slave(Config) ->
    +    self_presence(Config, available),
    +    process_subscriptions_slave(Config, get_event(Config), #state{}).
    +
    +process_subscriptions_slave(Config, done, _State) ->
    +    wait_for_master(Config),
    +    Config;
    +process_subscriptions_slave(Config, {N, Dir, Type}, State) ->
    +    wait_for_master(Config),
    +    ct:pal("Performing ~s-~s (#~p) "
    +	   "in state:~n~s~nwith roster:~n~s",
    +	   [Dir, Type, N, pp(State), pp(get_roster(Config))]),
    +    NewState = transition(Config, Dir, Type, State),
    +    process_subscriptions_slave(Config, get_event(Config), NewState).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("roster_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("roster_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("roster_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("roster_" ++ atom_to_list(T) ++ "_slave")]}.
    +
    +get_items(Config) ->
    +    get_items(Config, <<"">>).
    +
    +get_items(Config, Version) ->
    +    case send_recv(Config, #iq{type = get,
    +			       sub_els = [#roster_query{ver = Version}]}) of
    +	#iq{type = result,
    +	    sub_els = [#roster_query{ver = NewVersion, items = Items}]} ->
    +	    {NewVersion, Items};
    +	#iq{type = result, sub_els = []} ->
    +	    {empty, []};
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +get_item(Config, JID) ->
    +    case get_items(Config) of
    +	{_Ver, Items} when is_list(Items) ->
    +	    lists:keyfind(JID, #roster_item.jid, Items);
    +	_ ->
    +	    false
    +    end.
    +
    +set_items(Config, Items) ->
    +    case send_recv(Config, #iq{type = set,
    +			       sub_els = [#roster_query{items = Items}]}) of
    +	#iq{type = result, sub_els = []} ->
    +	    recv_push(Config);
    +	#iq{type = error} = Err ->
    +	    xmpp:get_error(Err)
    +    end.
    +
    +recv_push(Config) ->
    +    ct:comment("Receiving roster push"),
    +    Push = #iq{type = set,
    +	       sub_els = [#roster_query{ver = Ver, items = [PushItem]}]}
    +	= recv_iq(Config),
    +    send(Config, make_iq_result(Push)),
    +    {Ver, PushItem}.
    +
    +recv_push(Config, Subscription, Ask) ->
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    Match = #roster_item{jid = PeerBareJID,
    +			 subscription = Subscription,
    +			 ask = Ask,
    +			 groups = [],
    +			 name = <<"">>},
    +    ct:comment("Receiving roster push"),
    +    Push = #iq{type = set, sub_els = [#roster_query{items = [Item]}]} =
    +	recv_iq(Config),
    +    case Item of
    +	Match -> send(Config, make_iq_result(Push));
    +	_ -> match_failure(Item, Match)
    +    end.
    +
    +recv_presence(Config, Type) ->
    +    PeerJID = ?config(peer, Config),
    +    case recv_presence(Config) of
    +	#presence{from = PeerJID, type = Type} -> ok;
    +	Pres -> match_failure(Pres, #presence{from = PeerJID, type = Type})
    +    end.
    +
    +recv_subscription(Config, Type) ->
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    case recv_presence(Config) of
    +	#presence{from = PeerBareJID, type = Type} -> ok;
    +	Pres -> match_failure(Pres, #presence{from = PeerBareJID, type = Type})
    +    end.
    +
    +pp(Term) ->
    +    io_lib_pretty:print(Term, fun pp/2).
    +
    +pp(state, N) ->
    +    Fs = record_info(fields, state),
    +    try N = length(Fs), Fs
    +    catch _:_ -> no end;
    +pp(roster, N) ->
    +    Fs = record_info(fields, roster),
    +    try N = length(Fs), Fs
    +    catch _:_ -> no end;
    +pp(_, _) -> no.
    +
    +%% RFC6121, A.2.1
    +transition(Config, out, subscribe,
    +	   #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    send(Config, #presence{to = PeerBareJID, type = subscribe}),
    +    case {Sub, Out, In} of
    +	{none, false, _} ->
    +	    recv_push(Config, none, subscribe),
    +	    State#state{pending_out = true};
    +	{none, true, false} ->
    +	    %% BUG: we should not receive roster push here
    +	    recv_push(Config, none, subscribe),
    +	    State;
    +	{from, false, false} ->
    +	    recv_push(Config, from, subscribe),
    +	    State#state{pending_out = true};
    +	_ ->
    +	    State
    +    end;
    +%% RFC6121, A.2.2
    +transition(Config, out, unsubscribe,
    +	   #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    send(Config, #presence{to = PeerBareJID, type = unsubscribe}),
    +    case {Sub, Out, In} of
    +	{none, true, _} ->
    +	    recv_push(Config, none, undefined),
    +	    State#state{pending_out = false};
    +	{to, false, _} ->
    +	    recv_push(Config, none, undefined),
    +	    recv_presence(Config, unavailable),
    +	    State#state{subscription = none, peer_available = false};
    +	{from, true, false} ->
    +	    recv_push(Config, from, undefined),
    +	    State#state{pending_out = false};
    +	{both, false, false} ->
    +	    recv_push(Config, from, undefined),
    +	    recv_presence(Config, unavailable),
    +	    State#state{subscription = from, peer_available = false};
    +	_ ->
    +	    State
    +    end;
    +%% RFC6121, A.2.3
    +transition(Config, out, subscribed,
    +	    #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    send(Config, #presence{to = PeerBareJID, type = subscribed}),
    +    case {Sub, Out, In} of
    +	{none, false, true} ->
    +	    recv_push(Config, from, undefined),
    +	    State#state{subscription = from, pending_in = false};
    +	{none, true, true} ->
    +	    recv_push(Config, from, subscribe),
    +	    State#state{subscription = from, pending_in = false};
    +	{to, false, true} ->
    +	    recv_push(Config, both, undefined),
    +	    State#state{subscription = both, pending_in = false};
    +	{to, false, _} ->
    +	    %% BUG: we should not transition to 'both' state
    +	    recv_push(Config, both, undefined),
    +	    State#state{subscription = both};
    +	_ ->
    +	    State
    +    end;
    +%% RFC6121, A.2.4
    +transition(Config, out, unsubscribed,
    +	   #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    send(Config, #presence{to = PeerBareJID, type = unsubscribed}),
    +    case {Sub, Out, In} of
    +	{none, false, true} ->
    +	    State#state{subscription = none, pending_in = false};
    +	{none, true, true} ->
    +	    recv_push(Config, none, subscribe),
    +	    State#state{subscription = none, pending_in = false};
    +	{to, _, true} ->
    +	    State#state{pending_in = false};
    +	{from, false, _} ->
    +	    recv_push(Config, none, undefined),
    +	    State#state{subscription = none};
    +	{from, true, _} ->
    +	    recv_push(Config, none, subscribe),
    +	    State#state{subscription = none};
    +	{both, _, _} ->
    +	    recv_push(Config, to, undefined),
    +	    State#state{subscription = to};
    +	_ ->
    +	    State
    +    end;
    +%% RFC6121, A.3.1
    +transition(Config, in, subscribe = Type,
    +	   #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
    +    case {Sub, Out, In} of
    +	{none, false, false} ->
    +	    recv_subscription(Config, Type),
    +	    State#state{pending_in = true};
    +	{none, true, false} ->
    +	    recv_push(Config, none, subscribe),
    +	    recv_subscription(Config, Type),
    +	    State#state{pending_in = true};
    +	{to, false, false} ->
    +	    %% BUG: we should not receive roster push in this state!
    +	    recv_push(Config, to, undefined),
    +	    recv_subscription(Config, Type),
    +	    State#state{pending_in = true};
    +	_ ->
    +	    State
    +    end;
    +%% RFC6121, A.3.2
    +transition(Config, in, unsubscribe = Type,
    +	   #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
    +    case {Sub, Out, In} of
    +	{none, _, true} ->
    +	    State#state{pending_in = false};
    +	{to, _, true} ->
    +	    recv_push(Config, to, undefined),
    +	    recv_subscription(Config, Type),
    +	    State#state{pending_in = false};
    +	{from, false, _} ->
    +	    recv_push(Config, none, undefined),
    +	    recv_subscription(Config, Type),
    +	    State#state{subscription = none};
    +	{from, true, _} ->
    +	    recv_push(Config, none, subscribe),
    +	    recv_subscription(Config, Type),
    +	    State#state{subscription = none};
    +	{both, _, _} ->
    +	    recv_push(Config, to, undefined),
    +	    recv_subscription(Config, Type),
    +	    State#state{subscription = to};
    +	_ ->
    +	    State
    +    end;
    +%% RFC6121, A.3.3
    +transition(Config, in, subscribed = Type,
    +	   #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
    +    case {Sub, Out, In} of
    +	{none, true, _} ->
    +	    recv_push(Config, to, undefined),
    +	    recv_subscription(Config, Type),
    +	    recv_presence(Config, available),
    +	    State#state{subscription = to, pending_out = false, peer_available = true};
    +	{from, true, _} ->
    +	    recv_push(Config, both, undefined),
    +	    recv_subscription(Config, Type),
    +	    recv_presence(Config, available),
    +	    State#state{subscription = both, pending_out = false, peer_available = true};
    +	{from, false, _} ->
    +	    %% BUG: we should not transition to 'both' in this state
    +	    recv_push(Config, both, undefined),
    +	    recv_subscription(Config, Type),
    +	    recv_presence(Config, available),
    +	    State#state{subscription = both, pending_out = false, peer_available = true};
    +	_ ->
    +	    State
    +    end;
    +%% RFC6121, A.3.4
    +transition(Config, in, unsubscribed = Type,
    +	   #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
    +    case {Sub, Out, In} of
    +	{none, true, true} ->
    +	    %% BUG: we should receive roster push in this state!
    +	    recv_subscription(Config, Type),
    +	    State#state{subscription = none, pending_out = false};
    +	{none, true, false} ->
    +	    recv_push(Config, none, undefined),
    +	    recv_subscription(Config, Type),
    +	    State#state{subscription = none, pending_out = false};
    +	{none, false, false} ->
    +	    State;
    +	{to, false, _} ->
    +	    recv_push(Config, none, undefined),
    +	    recv_subscription(Config, Type),
    +	    recv_presence(Config, unavailable),
    +	    State#state{subscription = none, peer_available = false};
    +	{from, true, false} ->
    +	    recv_push(Config, from, undefined),
    +	    recv_subscription(Config, Type),
    +	    State#state{subscription = from, pending_out = false};
    +	{both, _, _} ->
    +	    recv_push(Config, from, undefined),
    +	    recv_subscription(Config, Type),
    +	    recv_presence(Config, unavailable),
    +	    State#state{subscription = from, peer_available = false};
    +	_ ->
    +	    State
    +    end;
    +%% Outgoing roster remove
    +transition(Config, out, remove,
    +	   #state{subscription = Sub, pending_in = In, pending_out = Out}) ->
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    Item = #roster_item{jid = PeerBareJID, subscription = remove},
    +    #iq{type = result, sub_els = []} =
    +	send_recv(Config, #iq{type = set,
    +			      sub_els = [#roster_query{items = [Item]}]}),
    +    recv_push(Config, remove, undefined),
    +    case {Sub, Out, In} of
    +	{to, _, _} ->
    +	    recv_presence(Config, unavailable);
    +	{both, _, _} ->
    +	    recv_presence(Config, unavailable);
    +	_ ->
    +	    ok
    +    end,
    +    #state{};
    +%% Incoming roster remove
    +transition(Config, in, remove,
    +	   #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
    +    case {Sub, Out, In} of
    +	{none, true, _} ->
    +	    ok;
    +	{from, false, _} ->
    +	    recv_push(Config, none, undefined),
    +	    recv_subscription(Config, unsubscribe);
    +	{from, true, _} ->
    +	    recv_push(Config, none, subscribe),
    +	    recv_subscription(Config, unsubscribe);
    +	{to, false, _} ->
    +	    %% BUG: we should receive push here
    +	    %% recv_push(Config, none, undefined),
    +	    recv_presence(Config, unavailable),
    +	    recv_subscription(Config, unsubscribed);
    +	{both, _, _} ->
    +	    recv_presence(Config, unavailable),
    +	    recv_push(Config, to, undefined),
    +	    recv_subscription(Config, unsubscribe),
    +	    recv_push(Config, none, undefined),
    +	    recv_subscription(Config, unsubscribed);
    +	_ ->
    +	    ok
    +    end,
    +    State#state{subscription = none}.
    +
    +actions() ->
    +    States = [{Dir, Type} || Dir <- [out, in],
    +			     Type <- [subscribe, subscribed,
    +				      unsubscribe, unsubscribed,
    +				      remove]],
    +    Actions = lists:flatten([[X, Y] || X <- States, Y <- States]),
    +    remove_dups(Actions, []).
    +
    +remove_dups([X|T], [X,X|_] = Acc) ->
    +    remove_dups(T, Acc);
    +remove_dups([X|T], Acc) ->
    +    remove_dups(T, [X|Acc]);
    +remove_dups([], Acc) ->
    +    lists:reverse(Acc).
    diff --git a/test/sm_tests.erl b/test/sm_tests.erl
    new file mode 100644
    index 000000000..0a74d392a
    --- /dev/null
    +++ b/test/sm_tests.erl
    @@ -0,0 +1,99 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(sm_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [send/2, recv/1, close_socket/1, set_opt/3, my_jid/1,
    +		recv_message/1, disconnect/1]).
    +
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {sm_single, [sequence],
    +     [single_test(feature_enabled),
    +      single_test(enable),
    +      single_test(resume),
    +      single_test(resume_failed)]}.
    +
    +feature_enabled(Config) ->
    +    true = ?config(sm, Config),
    +    disconnect(Config).
    +
    +enable(Config) ->
    +    Server = ?config(server, Config),
    +    ServerJID = jid:make(<<"">>, Server, <<"">>),
    +    %% Send messages of type 'headline' so the server discards them silently
    +    Msg = #message{to = ServerJID, type = headline,
    +		   body = [#text{data = <<"body">>}]},
    +    %% Enable the session management with resumption enabled
    +    send(Config, #sm_enable{resume = true, xmlns = ?NS_STREAM_MGMT_3}),
    +    #sm_enabled{id = ID, resume = true} = recv(Config),
    +    %% Initial request; 'h' should be 0.
    +    send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
    +    #sm_a{h = 0} = recv(Config),
    +    %% sending two messages and requesting again; 'h' should be 3.
    +    send(Config, Msg),
    +    send(Config, Msg),
    +    send(Config, Msg),
    +    send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
    +    #sm_a{h = 3} = recv(Config),
    +    close_socket(Config),
    +    {save_config, set_opt(sm_previd, ID, Config)}.
    +
    +resume(Config) ->
    +    {_, SMConfig} = ?config(saved_config, Config),
    +    ID = ?config(sm_previd, SMConfig),
    +    Server = ?config(server, Config),
    +    ServerJID = jid:make(<<"">>, Server, <<"">>),
    +    MyJID = my_jid(Config),
    +    Txt = #text{data = <<"body">>},
    +    Msg = #message{from = ServerJID, to = MyJID, body = [Txt]},
    +    %% Route message. The message should be queued by the C2S process.
    +    ejabberd_router:route(ServerJID, MyJID, Msg),
    +    send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}),
    +    #sm_resumed{previd = ID, h = 3} = recv(Config),
    +    #message{from = ServerJID, to = MyJID, body = [Txt]} = recv_message(Config),
    +    #sm_r{} = recv(Config),
    +    send(Config, #sm_a{h = 1, xmlns = ?NS_STREAM_MGMT_3}),
    +    %% Send another stanza to increment the server's 'h' for sm_resume_failed.
    +    send(Config, #presence{to = ServerJID}),
    +    close_socket(Config),
    +    {save_config, set_opt(sm_previd, ID, Config)}.
    +
    +resume_failed(Config) ->
    +    {_, SMConfig} = ?config(saved_config, Config),
    +    ID = ?config(sm_previd, SMConfig),
    +    ct:sleep(5000), % Wait for session to time out.
    +    send(Config, #sm_resume{previd = ID, h = 1, xmlns = ?NS_STREAM_MGMT_3}),
    +    #sm_failed{reason = 'item-not-found', h = 4} = recv(Config),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {sm_master_slave, [sequence], []}.
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("sm_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("sm_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("sm_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("sm_" ++ atom_to_list(T) ++ "_slave")]}.
    diff --git a/test/suite.erl b/test/suite.erl
    index c5593c4cf..3d832dd59 100644
    --- a/test/suite.erl
    +++ b/test/suite.erl
    @@ -13,6 +13,7 @@
     
     -include("suite.hrl").
     -include_lib("kernel/include/file.hrl").
    +-include("mod_roster.hrl").
     
     %%%===================================================================
     %%% API
    @@ -27,14 +28,22 @@ init_config(Config) ->
         SASLPath = filename:join([PrivDir, "sasl.log"]),
         MnesiaDir = filename:join([PrivDir, "mnesia"]),
         CertFile = filename:join([DataDir, "cert.pem"]),
    +    SelfSignedCertFile = filename:join([DataDir, "self-signed-cert.pem"]),
    +    CAFile = filename:join([DataDir, "ca.pem"]),
         {ok, CWD} = file:get_cwd(),
         {ok, _} = file:copy(CertFile, filename:join([CWD, "cert.pem"])),
    +    {ok, _} = file:copy(SelfSignedCertFile,
    +			filename:join([CWD, "self-signed-cert.pem"])),
    +    {ok, _} = file:copy(CAFile, filename:join([CWD, "ca.pem"])),
         {ok, CfgContentTpl} = file:read_file(ConfigPathTpl),
    +    Password = <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>,
         CfgContent = process_config_tpl(CfgContentTpl, [
                                                         {c2s_port, 5222},
                                                         {loglevel, 4},
                                                         {s2s_port, 5269},
    +						    {component_port, 5270},
                                                         {web_port, 5280},
    +						    {password, Password},
                                                         {mysql_server, <<"localhost">>},
                                                         {mysql_port, 3306},
                                                         {mysql_db, <<"ejabberd_test">>},
    @@ -58,17 +67,35 @@ init_config(Config) ->
         application:set_env(mnesia, dir, MnesiaDir),
         [{server_port, ct:get_config(c2s_port, 5222)},
          {server_host, "localhost"},
    +     {component_port, ct:get_config(component_port, 5270)},
    +     {s2s_port, ct:get_config(s2s_port, 5269)},
          {server, ?COMMON_VHOST},
          {user, <<"test_single!#$%^*()`~+-;_=[]{}|\\">>},
    +     {nick, <<"nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
          {master_nick, <<"master_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
          {slave_nick, <<"slave_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
          {room_subject, <<"hello, world!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
          {certfile, CertFile},
    +     {persistent_room, true},
    +     {anonymous, false},
    +     {type, client},
    +     {xmlns, ?NS_CLIENT},
    +     {ns_stream, ?NS_STREAM},
    +     {stream_version, {1, 0}},
    +     {stream_id, <<"">>},
    +     {stream_from, <<"">>},
    +     {db_xmlns, <<"">>},
    +     {mechs, []},
    +     {rosterver, false},
    +     {lang, <<"en">>},
          {base_dir, BaseDir},
    +     {socket, undefined},
    +     {pubsub_node, <<"node!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
    +     {pubsub_node_title, <<"title!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
          {resource, <<"resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
          {master_resource, <<"master_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
          {slave_resource, <<"slave_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
    -     {password, <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
    +     {password, Password},
          {backends, get_config_backends()}
          |Config].
     
    @@ -115,47 +142,104 @@ process_config_tpl(Content, [{Name, DefaultValue} | Rest]) ->
                   V3 ->
                       V3
               end,
    -    NewContent = binary:replace(Content, <<"@@",(atom_to_binary(Name, latin1))/binary, "@@">>, Val),
    +    NewContent = binary:replace(Content,
    +				<<"@@",(atom_to_binary(Name,latin1))/binary, "@@">>,
    +				Val, [global]),
         process_config_tpl(NewContent, Rest).
     
    +stream_header(Config) ->
    +    To = case ?config(server, Config) of
    +	     <<"">> -> undefined;
    +	     Server -> jid:make(Server)
    +	 end,
    +    From = case ?config(stream_from, Config) of
    +	       <<"">> -> undefined;
    +	       Frm -> jid:make(Frm)
    +	   end,
    +    #stream_start{to = To,
    +		  from = From,
    +		  lang = ?config(lang, Config),
    +		  version = ?config(stream_version, Config),
    +		  xmlns = ?config(xmlns, Config),
    +		  db_xmlns = ?config(db_xmlns, Config),
    +		  stream_xmlns = ?config(ns_stream, Config)}.
     
     connect(Config) ->
    -    {ok, Sock} = ejabberd_socket:connect(
    -                   ?config(server_host, Config),
    -                   ?config(server_port, Config),
    -                   [binary, {packet, 0}, {active, false}]),
    -    init_stream(set_opt(socket, Sock, Config)).
    +    NewConfig = init_stream(Config),
    +    case ?config(type, NewConfig) of
    +	client -> process_stream_features(NewConfig);
    +	server -> process_stream_features(NewConfig);
    +	component -> NewConfig
    +    end.
    +
    +tcp_connect(Config) ->
    +    case ?config(socket, Config) of
    +	undefined ->
    +	    Owner = self(),
    +	    NS = case ?config(type, Config) of
    +		     client -> ?NS_CLIENT;
    +		     server -> ?NS_SERVER;
    +		     component -> ?NS_COMPONENT
    +		 end,
    +	    ReceiverPid = spawn(fun() -> receiver(NS, Owner) end),
    +	    {ok, Sock} = ejabberd_socket:connect(
    +			   ?config(server_host, Config),
    +			   ?config(server_port, Config),
    +			   [binary, {packet, 0}, {active, false}],
    +			   infinity, ReceiverPid),
    +	    set_opt(socket, Sock, Config);
    +	_ ->
    +	    Config
    +    end.
     
     init_stream(Config) ->
    -    ok = send_text(Config, io_lib:format(?STREAM_HEADER,
    -                                          [?config(server, Config)])),
    -    {xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
    -    <<"jabber:client">> = fxml:get_attr_s(<<"xmlns">>, Attrs),
    -    <<"1.0">> = fxml:get_attr_s(<<"version">>, Attrs),
    -    #stream_features{sub_els = Fs} = recv(),
    -    Mechs = lists:flatmap(
    -              fun(#sasl_mechanisms{list = Ms}) ->
    -                      Ms;
    -                 (_) ->
    -                      []
    -              end, Fs),
    -    lists:foldl(
    -      fun(#feature_register{}, Acc) ->
    -              set_opt(register, true, Acc);
    -         (#starttls{}, Acc) ->
    -              set_opt(starttls, true, Acc);
    -         (#compression{methods = Ms}, Acc) ->
    -              set_opt(compression, Ms, Acc);
    -         (_, Acc) ->
    -              Acc
    -      end, set_opt(mechs, Mechs, Config), Fs).
    +    Version = ?config(stream_version, Config),
    +    NewConfig = tcp_connect(Config),
    +    send(NewConfig, stream_header(NewConfig)),
    +    XMLNS = case ?config(type, Config) of
    +		client -> ?NS_CLIENT;
    +		component -> ?NS_COMPONENT;
    +		server -> ?NS_SERVER
    +	    end,
    +    receive
    +	#stream_start{id = ID, xmlns = XMLNS, version = Version} ->
    +	    set_opt(stream_id, ID, NewConfig)
    +    end.
    +
    +process_stream_features(Config) ->
    +    receive
    +	#stream_features{sub_els = Fs} ->
    +	    Mechs = lists:flatmap(
    +		      fun(#sasl_mechanisms{list = Ms}) ->
    +			      Ms;
    +			 (_) ->
    +			      []
    +		      end, Fs),
    +	    lists:foldl(
    +	      fun(#feature_register{}, Acc) ->
    +		      set_opt(register, true, Acc);
    +		 (#starttls{}, Acc) ->
    +		      set_opt(starttls, true, Acc);
    +		 (#compression{methods = Ms}, Acc) ->
    +		      set_opt(compression, Ms, Acc);
    +		 (_, Acc) ->
    +		      Acc
    +	      end, set_opt(mechs, Mechs, Config), Fs)
    +    end.
     
     disconnect(Config) ->
    +    ct:comment("Disconnecting"),
         Socket = ?config(socket, Config),
    -    ok = ejabberd_socket:send(Socket, ?STREAM_TRAILER),
    -    {xmlstreamend, <<"stream:stream">>} = recv(),
    +    try
    +	ok = send_text(Config, ?STREAM_TRAILER)
    +    catch exit:normal ->
    +	    ok
    +    end,
    +    receive {xmlstreamend, <<"stream:stream">>} -> ok end,
    +    flush(Config),
         ejabberd_socket:close(Socket),
    -    Config.
    +    ct:comment("Disconnected"),
    +    set_opt(socket, undefined, Config).
     
     close_socket(Config) ->
         Socket = ?config(socket, Config),
    @@ -163,76 +247,199 @@ close_socket(Config) ->
         Config.
     
     starttls(Config) ->
    +    starttls(Config, false).
    +
    +starttls(Config, ShouldFail) ->
         send(Config, #starttls{}),
    -    #starttls_proceed{} = recv(),
    -    TLSSocket = ejabberd_socket:starttls(
    -                  ?config(socket, Config),
    -                  [{certfile, ?config(certfile, Config)},
    -                   connect]),
    -    init_stream(set_opt(socket, TLSSocket, Config)).
    +    receive
    +	#starttls_proceed{} when ShouldFail ->
    +	    ct:fail(starttls_should_have_failed);
    +	#starttls_failure{} when ShouldFail ->
    +	    Config;
    +	#starttls_failure{} ->
    +	    ct:fail(starttls_failed);
    +	#starttls_proceed{} ->
    +	    TLSSocket = ejabberd_socket:starttls(
    +			  ?config(socket, Config),
    +			  [{certfile, ?config(certfile, Config)},
    +			   connect]),
    +	    set_opt(socket, TLSSocket, Config)
    +    end.
     
     zlib(Config) ->
         send(Config, #compress{methods = [<<"zlib">>]}),
    -    #compressed{} = recv(),
    +    receive #compressed{} -> ok end,
         ZlibSocket = ejabberd_socket:compress(?config(socket, Config)),
    -    init_stream(set_opt(socket, ZlibSocket, Config)).
    +    process_stream_features(init_stream(set_opt(socket, ZlibSocket, Config))).
     
     auth(Config) ->
    +    auth(Config, false).
    +
    +auth(Config, ShouldFail) ->
    +    Type = ?config(type, Config),
    +    IsAnonymous = ?config(anonymous, Config),
         Mechs = ?config(mechs, Config),
         HaveMD5 = lists:member(<<"DIGEST-MD5">>, Mechs),
         HavePLAIN = lists:member(<<"PLAIN">>, Mechs),
    -    if HavePLAIN ->
    -            auth_SASL(<<"PLAIN">>, Config);
    +    HaveExternal = lists:member(<<"EXTERNAL">>, Mechs),
    +    HaveAnonymous = lists:member(<<"ANONYMOUS">>, Mechs),
    +    if HaveAnonymous and IsAnonymous ->
    +	    auth_SASL(<<"ANONYMOUS">>, Config, ShouldFail);
    +       HavePLAIN ->
    +            auth_SASL(<<"PLAIN">>, Config, ShouldFail);
            HaveMD5 ->
    -            auth_SASL(<<"DIGEST-MD5">>, Config);
    +            auth_SASL(<<"DIGEST-MD5">>, Config, ShouldFail);
    +       HaveExternal andalso Type == server ->
    +	    auth_SASL(<<"EXTERNAL">>, Config, ShouldFail);
    +       Type == client ->
    +	    auth_legacy(Config, false, ShouldFail);
    +       Type == component ->
    +	    auth_component(Config, ShouldFail);
            true ->
    -            ct:fail(no_sasl_mechanisms_available)
    +	    ct:fail(no_known_sasl_mechanism_available)
         end.
     
     bind(Config) ->
    -    #iq{type = result, sub_els = [#bind{}]} =
    -        send_recv(
    -          Config,
    -          #iq{type = set,
    -              sub_els = [#bind{resource = ?config(resource, Config)}]}),
    -    Config.
    +    U = ?config(user, Config),
    +    S = ?config(server, Config),
    +    R = ?config(resource, Config),
    +    case ?config(type, Config) of
    +	client ->
    +	    #iq{type = result, sub_els = [#bind{jid = JID}]} =
    +		send_recv(
    +		  Config, #iq{type = set, sub_els = [#bind{resource = R}]}),
    +	    case ?config(anonymous, Config) of
    +		false ->
    +		    {U, S, R} = jid:tolower(JID),
    +		    Config;
    +		true ->
    +		    {User, S, Resource} = jid:tolower(JID),
    +		    set_opt(user, User, set_opt(resource, Resource, Config))
    +	    end;
    +	component ->
    +	    Config
    +    end.
     
     open_session(Config) ->
    -    #iq{type = result, sub_els = []} =
    -        send_recv(Config, #iq{type = set, sub_els = [#session{}]}),
    +    open_session(Config, false).
    +
    +open_session(Config, Force) ->
    +    if Force ->
    +	    #iq{type = result, sub_els = []} =
    +		send_recv(Config, #iq{type = set, sub_els = [#xmpp_session{}]});
    +       true ->
    +	    ok
    +    end,
         Config.
     
    +auth_legacy(Config, IsDigest) ->
    +    auth_legacy(Config, IsDigest, false).
    +
    +auth_legacy(Config, IsDigest, ShouldFail) ->
    +    ServerJID = server_jid(Config),
    +    U = ?config(user, Config),
    +    R = ?config(resource, Config),
    +    P = ?config(password, Config),
    +    #iq{type = result,
    +	from = ServerJID,
    +	sub_els = [#legacy_auth{username = <<"">>,
    +				password = <<"">>,
    +				resource = <<"">>} = Auth]} =
    +	send_recv(Config,
    +		  #iq{to = ServerJID, type = get,
    +		      sub_els = [#legacy_auth{}]}),
    +    Res = case Auth#legacy_auth.digest of
    +	      <<"">> when IsDigest ->
    +		  StreamID = ?config(stream_id, Config),
    +		  D = p1_sha:sha(<>),
    +		  send_recv(Config, #iq{to = ServerJID, type = set,
    +					sub_els = [#legacy_auth{username = U,
    +								resource = R,
    +								digest = D}]});
    +	      _ when not IsDigest ->
    +		  send_recv(Config, #iq{to = ServerJID, type = set,
    +					sub_els = [#legacy_auth{username = U,
    +								resource = R,
    +								password = P}]})
    +	  end,
    +    case Res of
    +	#iq{from = ServerJID, type = result, sub_els = []} ->
    +	    if ShouldFail ->
    +		    ct:fail(legacy_auth_should_have_failed);
    +	       true ->
    +		    Config
    +	    end;
    +	#iq{from = ServerJID, type = error} ->
    +	    if ShouldFail ->
    +		    Config;
    +	       true ->
    +		    ct:fail(legacy_auth_failed)
    +	    end
    +    end.
    +
    +auth_component(Config, ShouldFail) ->
    +    StreamID = ?config(stream_id, Config),
    +    Password = ?config(password, Config),
    +    Digest = p1_sha:sha(<>),
    +    send(Config, #handshake{data = Digest}),
    +    receive
    +	#handshake{} when ShouldFail ->
    +	    ct:fail(component_auth_should_have_failed);
    +	#handshake{} ->
    +	    Config;
    +	#stream_error{reason = 'not-authorized'} when ShouldFail ->
    +	    Config;
    +	#stream_error{reason = 'not-authorized'} ->
    +	    ct:fail(component_auth_failed)
    +    end.
    +
     auth_SASL(Mech, Config) ->
    +    auth_SASL(Mech, Config, false).
    +
    +auth_SASL(Mech, Config, ShouldFail) ->
         {Response, SASL} = sasl_new(Mech,
                                     ?config(user, Config),
                                     ?config(server, Config),
                                     ?config(password, Config)),
         send(Config, #sasl_auth{mechanism = Mech, text = Response}),
    -    wait_auth_SASL_result(set_opt(sasl, SASL, Config)).
    +    wait_auth_SASL_result(set_opt(sasl, SASL, Config), ShouldFail).
     
    -wait_auth_SASL_result(Config) ->
    -    case recv() of
    +wait_auth_SASL_result(Config, ShouldFail) ->
    +    receive
    +	#sasl_success{} when ShouldFail ->
    +	    ct:fail(sasl_auth_should_have_failed);
             #sasl_success{} ->
                 ejabberd_socket:reset_stream(?config(socket, Config)),
    -            send_text(Config,
    -                      io_lib:format(?STREAM_HEADER,
    -                                    [?config(server, Config)])),
    -            {xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
    -            <<"jabber:client">> = fxml:get_attr_s(<<"xmlns">>, Attrs),
    -            <<"1.0">> = fxml:get_attr_s(<<"version">>, Attrs),
    -            #stream_features{sub_els = Fs} = recv(),
    -	    lists:foldl(
    -	      fun(#feature_sm{}, ConfigAcc) ->
    -		      set_opt(sm, true, ConfigAcc);
    -		 (#feature_csi{}, ConfigAcc) ->
    -		      set_opt(csi, true, ConfigAcc);
    -		 (_, ConfigAcc) ->
    -		      ConfigAcc
    -	      end, Config, Fs);
    +            send(Config, stream_header(Config)),
    +	    Type = ?config(type, Config),
    +	    NS = if Type == client -> ?NS_CLIENT;
    +		    Type == server -> ?NS_SERVER
    +		 end,
    +	    receive #stream_start{xmlns = NS, version = {1,0}} -> ok end,
    +            receive #stream_features{sub_els = Fs} ->
    +		    if Type == client ->
    +			    #xmpp_session{optional = true} =
    +				lists:keyfind(xmpp_session, 1, Fs);
    +		       true ->
    +			    ok
    +		    end,
    +		    lists:foldl(
    +		      fun(#feature_sm{}, ConfigAcc) ->
    +			      set_opt(sm, true, ConfigAcc);
    +			 (#feature_csi{}, ConfigAcc) ->
    +			      set_opt(csi, true, ConfigAcc);
    +			 (#rosterver_feature{}, ConfigAcc) ->
    +			      set_opt(rosterver, true, ConfigAcc);
    +			 (_, ConfigAcc) ->
    +			      ConfigAcc
    +		      end, Config, Fs)
    +	    end;
             #sasl_challenge{text = ClientIn} ->
                 {Response, SASL} = (?config(sasl, Config))(ClientIn),
                 send(Config, #sasl_response{text = Response}),
    -            wait_auth_SASL_result(set_opt(sasl, SASL, Config));
    +            wait_auth_SASL_result(set_opt(sasl, SASL, Config), ShouldFail);
    +	#sasl_failure{} when ShouldFail ->
    +	    Config;
             #sasl_failure{} ->
                 ct:fail(sasl_auth_failed)
         end.
    @@ -249,28 +456,44 @@ match_failure(Received, [Match]) when is_list(Match)->
     match_failure(Received, Matches) ->
         ct:fail("Received input:~n~n~p~n~ndon't match expected patterns:~n~n~p", [Received, Matches]).
     
    -recv() ->
    +recv(_Config) ->
         receive
    -        {'$gen_event', {xmlstreamelement, El}} ->
    -            Pkt = xmpp_codec:decode(fix_ns(El)),
    -            ct:pal("recv: ~p ->~n~s", [El, xmpp_codec:pp(Pkt)]),
    -            Pkt;
    -        {'$gen_event', Event} ->
    -            Event
    +	{fail, El, Why} ->
    +	    ct:fail("recv failed: ~p->~n~s",
    +		    [El, xmpp:format_error(Why)]);
    +	Event ->
    +	    Event
         end.
     
    -fix_ns(#xmlel{name = Tag, attrs = Attrs} = El)
    -  when Tag == <<"stream:features">>; Tag == <<"stream:error">> ->
    -    NewAttrs = [{<<"xmlns">>, <<"http://etherx.jabber.org/streams">>}
    -                |lists:keydelete(<<"xmlns">>, 1, Attrs)],
    -    El#xmlel{attrs = NewAttrs};
    -fix_ns(#xmlel{name = Tag, attrs = Attrs} = El)
    -  when Tag == <<"message">>; Tag == <<"iq">>; Tag == <<"presence">> ->
    -    NewAttrs = [{<<"xmlns">>, <<"jabber:client">>}
    -                |lists:keydelete(<<"xmlns">>, 1, Attrs)],
    -    El#xmlel{attrs = NewAttrs};
    -fix_ns(El) ->
    -    El.
    +recv_iq(_Config) ->
    +    receive #iq{} = IQ -> IQ end.
    +
    +recv_presence(_Config) ->
    +    receive #presence{} = Pres -> Pres end.
    +
    +recv_message(_Config) ->
    +    receive #message{} = Msg -> Msg end.
    +
    +decode_stream_element(NS, El) ->
    +    decode(El, NS, []).
    +
    +format_element(El) ->
    +    case erlang:function_exported(ct, log, 5) of
    +	true -> ejabberd_web_admin:pretty_print_xml(El);
    +	false -> io_lib:format("~p~n", [El])
    +    end.
    +
    +decode(El, NS, Opts) ->
    +    try
    +	Pkt = xmpp:decode(El, NS, Opts),
    +	ct:pal("RECV:~n~s~n~s",
    +	       [format_element(El), xmpp:pp(Pkt)]),
    +	Pkt
    +    catch _:{xmpp_codec, Why} ->
    +	    ct:pal("recv failed: ~p->~n~s",
    +		   [El, xmpp:format_error(Why)]),
    +	    erlang:error({xmpp_codec, Why})
    +    end.
     
     send_text(Config, Text) ->
         ejabberd_socket:send(?config(socket, Config), Text).
    @@ -289,18 +512,35 @@ send(State, Pkt) ->
                               _ ->
                                   {undefined, Pkt}
                           end,
    -    El = xmpp_codec:encode(NewPkt),
    -    ct:pal("sent: ~p <-~n~s", [El, xmpp_codec:pp(NewPkt)]),
    -    ok = send_text(State, fxml:element_to_binary(El)),
    +    El = xmpp:encode(NewPkt),
    +    ct:pal("SENT:~n~s~n~s",
    +	   [format_element(El), xmpp:pp(NewPkt)]),
    +    Data = case NewPkt of
    +	       #stream_start{} -> fxml:element_to_header(El);
    +	       _ -> fxml:element_to_binary(El)
    +	   end,
    +    ok = send_text(State, Data),
         NewID.
     
    -send_recv(State, IQ) ->
    +send_recv(State, #message{} = Msg) ->
    +    ID = send(State, Msg),
    +    receive #message{id = ID} = Result -> Result end;
    +send_recv(State, #presence{} = Pres) ->
    +    ID = send(State, Pres),
    +    receive #presence{id = ID} = Result -> Result end;
    +send_recv(State, #iq{} = IQ) ->
         ID = send(State, IQ),
    -    #iq{id = ID} = recv().
    +    receive #iq{id = ID} = Result -> Result end.
     
     sasl_new(<<"PLAIN">>, User, Server, Password) ->
         {<>,
          fun (_) -> {error, <<"Invalid SASL challenge">>} end};
    +sasl_new(<<"EXTERNAL">>, _User, _Server, _Password) ->
    +    {<<"">>,
    +     fun(_) -> ct:fail(sasl_challenge_is_not_expected) end};
    +sasl_new(<<"ANONYMOUS">>, _User, _Server, _Password) ->
    +    {<<"">>,
    +     fun(_) -> ct:fail(sasl_challenge_is_not_expected) end};
     sasl_new(<<"DIGEST-MD5">>, User, Server, Password) ->
         {<<"">>,
          fun (ServerIn) ->
    @@ -395,6 +635,20 @@ muc_room_jid(Config) ->
         Server = ?config(server, Config),
         jid:make(<<"test">>, <<"conference.", Server/binary>>, <<>>).
     
    +my_muc_jid(Config) ->
    +    Nick = ?config(nick, Config),
    +    RoomJID = muc_room_jid(Config),
    +    jid:replace_resource(RoomJID, Nick).
    +
    +peer_muc_jid(Config) ->
    +    PeerNick = ?config(peer_nick, Config),
    +    RoomJID = muc_room_jid(Config),
    +    jid:replace_resource(RoomJID, PeerNick).
    +
    +alt_room_jid(Config) ->
    +    Server = ?config(server, Config),
    +    jid:make(<<"alt">>, <<"conference.", Server/binary>>, <<>>).
    +
     mix_jid(Config) ->
         Server = ?config(server, Config),
         jid:make(<<>>, <<"mix.", Server/binary>>, <<>>).
    @@ -404,9 +658,9 @@ mix_room_jid(Config) ->
         jid:make(<<"test">>, <<"mix.", Server/binary>>, <<>>).
     
     id() ->
    -    id(undefined).
    +    id(<<>>).
     
    -id(undefined) ->
    +id(<<>>) ->
         randoms:get_string();
     id(ID) ->
         ID.
    @@ -415,6 +669,7 @@ get_features(Config) ->
         get_features(Config, server_jid(Config)).
     
     get_features(Config, To) ->
    +    ct:comment("Getting features of ~s", [jid:to_string(To)]),
         #iq{type = result, sub_els = [#disco_info{features = Features}]} =
             send_recv(Config, #iq{type = get, sub_els = [#disco_info{}], to = To}),
         Features.
    @@ -430,16 +685,82 @@ set_opt(Opt, Val, Config) ->
         [{Opt, Val}|lists:keydelete(Opt, 1, Config)].
     
     wait_for_master(Config) ->
    -    put_event(Config, slave_ready),
    -    master_ready = get_event(Config).
    +    put_event(Config, peer_ready),
    +    case get_event(Config) of
    +	peer_ready ->
    +	    ok;
    +	Other ->
    +	    suite:match_failure(Other, peer_ready)
    +    end.
     
     wait_for_slave(Config) ->
    -    put_event(Config, master_ready),
    -    slave_ready = get_event(Config).
    +    put_event(Config, peer_ready),
    +    case get_event(Config) of
    +	peer_ready ->
    +	    ok;
    +	Other ->
    +	    suite:match_failure(Other, peer_ready)
    +    end.
     
     make_iq_result(#iq{from = From} = IQ) ->
         IQ#iq{type = result, to = From, from = undefined, sub_els = []}.
     
    +self_presence(Config, Type) ->
    +    MyJID = my_jid(Config),
    +    ct:comment("Sending self-presence"),
    +    #presence{type = Type, from = MyJID} =
    +	send_recv(Config, #presence{type = Type}).
    +
    +set_roster(Config, Subscription, Groups) ->
    +    MyJID = my_jid(Config),
    +    {U, S, _} = jid:tolower(MyJID),
    +    PeerJID = ?config(peer, Config),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    PeerLJID = jid:tolower(PeerBareJID),
    +    ct:comment("Adding ~s to roster with subscription '~s' in groups ~p",
    +	       [jid:to_string(PeerBareJID), Subscription, Groups]),
    +    {atomic, _} = mod_roster:set_roster(#roster{usj = {U, S, PeerLJID},
    +						us = {U, S},
    +						jid = PeerLJID,
    +						subscription = Subscription,
    +						groups = Groups}),
    +    Config.
    +
    +del_roster(Config) ->
    +    del_roster(Config, ?config(peer, Config)).
    +
    +del_roster(Config, PeerJID) ->
    +    MyJID = my_jid(Config),
    +    {U, S, _} = jid:tolower(MyJID),
    +    PeerBareJID = jid:remove_resource(PeerJID),
    +    PeerLJID = jid:tolower(PeerBareJID),
    +    ct:comment("Removing ~s from roster", [jid:to_string(PeerBareJID)]),
    +    {atomic, _} = mod_roster:del_roster(U, S, PeerLJID),
    +    Config.
    +
    +get_roster(Config) ->
    +    {LUser, LServer, _} = jid:tolower(my_jid(Config)),
    +    mod_roster:get_roster(LUser, LServer).
    +
    +receiver(NS, Owner) ->
    +    MRef = erlang:monitor(process, Owner),
    +    receiver(NS, Owner, MRef).
    +
    +receiver(NS, Owner, MRef) ->
    +    receive
    +        {'$gen_event', {xmlstreamelement, El}} ->
    +	    Owner ! decode_stream_element(NS, El),
    +	    receiver(NS, Owner, MRef);
    +	{'$gen_event', {xmlstreamstart, Name, Attrs}} ->
    +	    Owner ! decode(#xmlel{name = Name, attrs = Attrs}, <<>>, []),
    +	    receiver(NS, Owner, MRef);
    +	{'$gen_event', Event} ->
    +            Owner ! Event,
    +	    receiver(NS, Owner, MRef);
    +	{'DOWN', MRef, process, Owner, _} ->
    +	    ok
    +    end.
    +
     %%%===================================================================
     %%% Clients puts and gets events via this relay.
     %%%===================================================================
    @@ -456,6 +777,7 @@ event_relay() ->
     event_relay(Events, Subscribers) ->
         receive
             {subscribe, From} ->
    +	    erlang:monitor(process, From),
                 From ! {ok, self()},
                 lists:foreach(
                   fun(Event) -> From ! {event, Event, self()}
    @@ -469,7 +791,19 @@ event_relay(Events, Subscribers) ->
                      (_) ->
                           ok
                   end, Subscribers),
    -            event_relay([Event|Events], Subscribers)
    +            event_relay([Event|Events], Subscribers);
    +	{'DOWN', _MRef, process, Pid, _Info} ->
    +	    case lists:member(Pid, Subscribers) of
    +		true ->
    +		    NewSubscribers = lists:delete(Pid, Subscribers),
    +		    lists:foreach(
    +		      fun(Subscriber) ->
    +			      Subscriber ! {event, peer_down, self()}
    +		      end, NewSubscribers),
    +		    event_relay(Events, NewSubscribers);
    +		false ->
    +		    event_relay(Events, Subscribers)
    +	    end
         end.
     
     subscribe_to_events(Config) ->
    @@ -494,3 +828,12 @@ get_event(Config) ->
             {event, Event, Relay} ->
                 Event
         end.
    +
    +flush(Config) ->
    +    receive
    +	{event, peer_down, _} -> flush(Config);
    +	closed -> flush(Config);
    +	Msg -> ct:fail({unexpected_msg, Msg})
    +    after 0 ->
    +	    ok
    +    end.
    diff --git a/test/suite.hrl b/test/suite.hrl
    index fb6b4f3ac..00239f8cf 100644
    --- a/test/suite.hrl
    +++ b/test/suite.hrl
    @@ -5,12 +5,6 @@
     -include("mod_proxy65.hrl").
     -include("xmpp_codec.hrl").
     
    --define(STREAM_HEADER,
    -	<<"">>).
    -
     -define(STREAM_TRAILER, <<"">>).
     
     -define(PUBSUB(Node), <<(?NS_PUBSUB)/binary, "#", Node>>).
    @@ -19,7 +13,7 @@
     
     -define(recv1(P1),
             P1 = (fun() ->
    -                 V = recv(),
    +                 V = recv(Config),
                      case V of
                          P1 -> V;
                          _ -> suite:match_failure([V], [??P1])
    @@ -28,7 +22,7 @@
     
     -define(recv2(P1, P2),
             (fun() ->
    -                 case {R1 = recv(), R2 = recv()} of
    +                 case {R1 = recv(Config), R2 = recv(Config)} of
                          {P1, P2} -> {R1, R2};
                          {P2, P1} -> {R2, R1};
                          {P1, V1} -> suite:match_failure([V1], [P2]);
    @@ -41,7 +35,7 @@
     
     -define(recv3(P1, P2, P3),
             (fun() ->
    -                 case R3 = recv() of
    +                 case R3 = recv(Config) of
                          P1 -> insert(R3, 1, ?recv2(P2, P3));
                          P2 -> insert(R3, 2, ?recv2(P1, P3));
                          P3 -> insert(R3, 3, ?recv2(P1, P2));
    @@ -51,7 +45,7 @@
     
     -define(recv4(P1, P2, P3, P4),
             (fun() ->
    -                 case R4 = recv() of
    +                 case R4 = recv(Config) of
                          P1 -> insert(R4, 1, ?recv3(P2, P3, P4));
                          P2 -> insert(R4, 2, ?recv3(P1, P3, P4));
                          P3 -> insert(R4, 3, ?recv3(P1, P2, P4));
    @@ -62,7 +56,7 @@
     
     -define(recv5(P1, P2, P3, P4, P5),
             (fun() ->
    -                 case R5 = recv() of
    +                 case R5 = recv(Config) of
                          P1 -> insert(R5, 1, ?recv4(P2, P3, P4, P5));
                          P2 -> insert(R5, 2, ?recv4(P1, P3, P4, P5));
                          P3 -> insert(R5, 3, ?recv4(P1, P2, P4, P5));
    @@ -72,6 +66,14 @@
                      end
              end)()).
     
    +-define(match(Pattern, Result),
    +	case Result of
    +	    Pattern ->
    +		Pattern;
    +	    Mismatch ->
    +		suite:match_failure([Mismatch], [??Pattern])
    +	end).
    +
     -define(COMMON_VHOST, <<"localhost">>).
     -define(MNESIA_VHOST, <<"mnesia.localhost">>).
     -define(REDIS_VHOST, <<"redis.localhost">>).
    @@ -81,6 +83,7 @@
     -define(LDAP_VHOST, <<"ldap.localhost">>).
     -define(EXTAUTH_VHOST, <<"extauth.localhost">>).
     -define(RIAK_VHOST, <<"riak.localhost">>).
    +-define(S2S_VHOST, <<"s2s.localhost">>).
     
     insert(Val, N, Tuple) ->
         L = tuple_to_list(Tuple),
    diff --git a/test/vcard_tests.erl b/test/vcard_tests.erl
    new file mode 100644
    index 000000000..26cfdc92b
    --- /dev/null
    +++ b/test/vcard_tests.erl
    @@ -0,0 +1,133 @@
    +%%%-------------------------------------------------------------------
    +%%% @author Evgeny Khramtsov 
    +%%% @copyright (C) 2016, Evgeny Khramtsov
    +%%% @doc
    +%%%
    +%%% @end
    +%%% Created : 16 Nov 2016 by Evgeny Khramtsov 
    +%%%-------------------------------------------------------------------
    +-module(vcard_tests).
    +
    +%% API
    +-compile(export_all).
    +-import(suite, [send_recv/2, disconnect/1, is_feature_advertised/2,
    +		is_feature_advertised/3, server_jid/1,
    +		my_jid/1, wait_for_slave/1, wait_for_master/1,
    +		recv_presence/1, recv/1]).
    +
    +-include("suite.hrl").
    +
    +%%%===================================================================
    +%%% API
    +%%%===================================================================
    +%%%===================================================================
    +%%% Single user tests
    +%%%===================================================================
    +single_cases() ->
    +    {vcard_single, [sequence],
    +     [single_test(feature_enabled),
    +      single_test(get_set),
    +      single_test(service_vcard)]}.
    +
    +feature_enabled(Config) ->
    +    BareMyJID = jid:remove_resource(my_jid(Config)),
    +    true = is_feature_advertised(Config, ?NS_VCARD),
    +    true = is_feature_advertised(Config, ?NS_VCARD, BareMyJID),
    +    disconnect(Config).
    +
    +get_set(Config) ->
    +    VCard =
    +        #vcard_temp{fn = <<"Peter Saint-Andre">>,
    +		    n = #vcard_name{family = <<"Saint-Andre">>,
    +				    given = <<"Peter">>},
    +		    nickname = <<"stpeter">>,
    +		    bday = <<"1966-08-06">>,
    +		    adr = [#vcard_adr{work = true,
    +				      extadd = <<"Suite 600">>,
    +				      street = <<"1899 Wynkoop Street">>,
    +				      locality = <<"Denver">>,
    +				      region = <<"CO">>,
    +				      pcode = <<"80202">>,
    +				      ctry = <<"USA">>},
    +			   #vcard_adr{home = true,
    +				      locality = <<"Denver">>,
    +				      region = <<"CO">>,
    +				      pcode = <<"80209">>,
    +				      ctry = <<"USA">>}],
    +		    tel = [#vcard_tel{work = true,voice = true,
    +				      number = <<"303-308-3282">>},
    +			   #vcard_tel{home = true,voice = true,
    +				      number = <<"303-555-1212">>}],
    +		    email = [#vcard_email{internet = true,pref = true,
    +					  userid = <<"stpeter@jabber.org">>}],
    +		    jabberid = <<"stpeter@jabber.org">>,
    +		    title = <<"Executive Director">>,role = <<"Patron Saint">>,
    +		    org = #vcard_org{name = <<"XMPP Standards Foundation">>},
    +		    url = <<"http://www.xmpp.org/xsf/people/stpeter.shtml">>,
    +		    desc = <<"More information about me is located on my "
    +			     "personal website: http://www.saint-andre.com/">>},
    +    #iq{type = result, sub_els = []} =
    +        send_recv(Config, #iq{type = set, sub_els = [VCard]}),
    +    %% TODO: check if VCard == VCard1.
    +    #iq{type = result, sub_els = [_VCard1]} =
    +        send_recv(Config, #iq{type = get, sub_els = [#vcard_temp{}]}),
    +    disconnect(Config).
    +
    +service_vcard(Config) ->
    +    JID = server_jid(Config),
    +    ct:comment("Retreiving vCard from ~s", [jid:to_string(JID)]),
    +    #iq{type = result, sub_els = [#vcard_temp{}]} =
    +	send_recv(Config, #iq{type = get, to = JID, sub_els = [#vcard_temp{}]}),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Master-slave tests
    +%%%===================================================================
    +master_slave_cases() ->
    +    {vcard_master_slave, [sequence], []}.
    +   %%[master_slave_test(xupdate)]}.
    +
    +xupdate_master(Config) ->
    +    Img = <<137, "PNG\r\n", 26, $\n>>,
    +    ImgHash = p1_sha:sha(Img),
    +    MyJID = my_jid(Config),
    +    Peer = ?config(slave, Config),
    +    wait_for_slave(Config),
    +    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
    +    #presence{from = Peer, type = available} = recv_presence(Config),
    +    VCard = #vcard_temp{photo = #vcard_photo{type = <<"image/png">>, binval = Img}},
    +    #iq{type = result, sub_els = []} =
    +	send_recv(Config, #iq{type = set, sub_els = [VCard]}),
    +    #presence{from = MyJID, type = available,
    +	      sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config),
    +    #iq{type = result, sub_els = []} =
    +	send_recv(Config, #iq{type = set, sub_els = [#vcard_temp{}]}),
    +    ?recv2(#presence{from = MyJID, type = available,
    +		     sub_els = [#vcard_xupdate{hash = undefined}]},
    +	   #presence{from = Peer, type = unavailable}),
    +    disconnect(Config).
    +
    +xupdate_slave(Config) ->
    +    Img = <<137, "PNG\r\n", 26, $\n>>,
    +    ImgHash = p1_sha:sha(Img),
    +    MyJID = my_jid(Config),
    +    Peer = ?config(master, Config),
    +    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
    +    wait_for_master(Config),
    +    #presence{from = Peer, type = available} = recv_presence(Config),
    +    #presence{from = Peer, type = available,
    +	      sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config),
    +    #presence{from = Peer, type = available,
    +	      sub_els = [#vcard_xupdate{hash = undefined}]} = recv_presence(Config),
    +    disconnect(Config).
    +
    +%%%===================================================================
    +%%% Internal functions
    +%%%===================================================================
    +single_test(T) ->
    +    list_to_atom("vcard_" ++ atom_to_list(T)).
    +
    +master_slave_test(T) ->
    +    {list_to_atom("vcard_" ++ atom_to_list(T)), [parallel],
    +     [list_to_atom("vcard_" ++ atom_to_list(T) ++ "_master"),
    +      list_to_atom("vcard_" ++ atom_to_list(T) ++ "_slave")]}.
    diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl
    deleted file mode 100644
    index ef1421962..000000000
    --- a/tools/xmpp_codec.erl
    +++ /dev/null
    @@ -1,20275 +0,0 @@
    -%% Created automatically by XML generator (fxml_gen.erl)
    -%% Source: xmpp_codec.spec
    -
    --module(xmpp_codec).
    -
    --compile({nowarn_unused_function,
    -	  [{dec_int, 3}, {dec_int, 1}, {dec_enum, 2},
    -	   {enc_int, 1}, {get_attr, 2}, {enc_enum, 1}]}).
    -
    --export([pp/1, format_error/1, decode/1, decode/2,
    -	 is_known_tag/1, encode/1, get_ns/1]).
    -
    -decode(_el) -> decode(_el, []).
    -
    -decode({xmlel, _name, _attrs, _} = _el, Opts) ->
    -    IgnoreEls = proplists:get_bool(ignore_els, Opts),
    -    case {_name, get_attr(<<"xmlns">>, _attrs)} of
    -      {<<"participant">>, <<"urn:xmpp:mix:0">>} ->
    -	  decode_mix_participant(<<"urn:xmpp:mix:0">>, IgnoreEls,
    -				 _el);
    -      {<<"leave">>, <<"urn:xmpp:mix:0">>} ->
    -	  decode_mix_leave(<<"urn:xmpp:mix:0">>, IgnoreEls, _el);
    -      {<<"join">>, <<"urn:xmpp:mix:0">>} ->
    -	  decode_mix_join(<<"urn:xmpp:mix:0">>, IgnoreEls, _el);
    -      {<<"subscribe">>, <<"urn:xmpp:mix:0">>} ->
    -	  decode_mix_subscribe(<<"urn:xmpp:mix:0">>, IgnoreEls,
    -			       _el);
    -      {<<"offline">>,
    -       <<"http://jabber.org/protocol/offline">>} ->
    -	  decode_offline(<<"http://jabber.org/protocol/offline">>,
    -			 IgnoreEls, _el);
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/offline">>} ->
    -	  decode_offline_item(<<"http://jabber.org/protocol/offline">>,
    -			      IgnoreEls, _el);
    -      {<<"fetch">>,
    -       <<"http://jabber.org/protocol/offline">>} ->
    -	  decode_offline_fetch(<<"http://jabber.org/protocol/offline">>,
    -			       IgnoreEls, _el);
    -      {<<"purge">>,
    -       <<"http://jabber.org/protocol/offline">>} ->
    -	  decode_offline_purge(<<"http://jabber.org/protocol/offline">>,
    -			       IgnoreEls, _el);
    -      {<<"failed">>, <<"urn:xmpp:sm:2">>} ->
    -	  decode_sm_failed(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
    -      {<<"failed">>, <<"urn:xmpp:sm:3">>} ->
    -	  decode_sm_failed(<<"urn:xmpp:sm:3">>, IgnoreEls, _el);
    -      {<<"a">>, <<"urn:xmpp:sm:2">>} ->
    -	  decode_sm_a(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
    -      {<<"a">>, <<"urn:xmpp:sm:3">>} ->
    -	  decode_sm_a(<<"urn:xmpp:sm:3">>, IgnoreEls, _el);
    -      {<<"r">>, <<"urn:xmpp:sm:2">>} ->
    -	  decode_sm_r(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
    -      {<<"r">>, <<"urn:xmpp:sm:3">>} ->
    -	  decode_sm_r(<<"urn:xmpp:sm:3">>, IgnoreEls, _el);
    -      {<<"resumed">>, <<"urn:xmpp:sm:2">>} ->
    -	  decode_sm_resumed(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
    -      {<<"resumed">>, <<"urn:xmpp:sm:3">>} ->
    -	  decode_sm_resumed(<<"urn:xmpp:sm:3">>, IgnoreEls, _el);
    -      {<<"resume">>, <<"urn:xmpp:sm:2">>} ->
    -	  decode_sm_resume(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
    -      {<<"resume">>, <<"urn:xmpp:sm:3">>} ->
    -	  decode_sm_resume(<<"urn:xmpp:sm:3">>, IgnoreEls, _el);
    -      {<<"enabled">>, <<"urn:xmpp:sm:2">>} ->
    -	  decode_sm_enabled(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
    -      {<<"enabled">>, <<"urn:xmpp:sm:3">>} ->
    -	  decode_sm_enabled(<<"urn:xmpp:sm:3">>, IgnoreEls, _el);
    -      {<<"enable">>, <<"urn:xmpp:sm:2">>} ->
    -	  decode_sm_enable(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
    -      {<<"enable">>, <<"urn:xmpp:sm:3">>} ->
    -	  decode_sm_enable(<<"urn:xmpp:sm:3">>, IgnoreEls, _el);
    -      {<<"sm">>, <<"urn:xmpp:sm:2">>} ->
    -	  decode_feature_sm(<<"urn:xmpp:sm:2">>, IgnoreEls, _el);
    -      {<<"sm">>, <<"urn:xmpp:sm:3">>} ->
    -	  decode_feature_sm(<<"urn:xmpp:sm:3">>, IgnoreEls, _el);
    -      {<<"inactive">>, <<"urn:xmpp:csi:0">>} ->
    -	  decode_csi_inactive(<<"urn:xmpp:csi:0">>, IgnoreEls,
    -			      _el);
    -      {<<"active">>, <<"urn:xmpp:csi:0">>} ->
    -	  decode_csi_active(<<"urn:xmpp:csi:0">>, IgnoreEls, _el);
    -      {<<"csi">>, <<"urn:xmpp:csi:0">>} ->
    -	  decode_feature_csi(<<"urn:xmpp:csi:0">>, IgnoreEls,
    -			     _el);
    -      {<<"sent">>, <<"urn:xmpp:carbons:2">>} ->
    -	  decode_carbons_sent(<<"urn:xmpp:carbons:2">>, IgnoreEls,
    -			      _el);
    -      {<<"received">>, <<"urn:xmpp:carbons:2">>} ->
    -	  decode_carbons_received(<<"urn:xmpp:carbons:2">>,
    -				  IgnoreEls, _el);
    -      {<<"private">>, <<"urn:xmpp:carbons:2">>} ->
    -	  decode_carbons_private(<<"urn:xmpp:carbons:2">>,
    -				 IgnoreEls, _el);
    -      {<<"enable">>, <<"urn:xmpp:carbons:2">>} ->
    -	  decode_carbons_enable(<<"urn:xmpp:carbons:2">>,
    -				IgnoreEls, _el);
    -      {<<"disable">>, <<"urn:xmpp:carbons:2">>} ->
    -	  decode_carbons_disable(<<"urn:xmpp:carbons:2">>,
    -				 IgnoreEls, _el);
    -      {<<"forwarded">>, <<"urn:xmpp:forward:0">>} ->
    -	  decode_forwarded(<<"urn:xmpp:forward:0">>, IgnoreEls,
    -			   _el);
    -      {<<"fin">>, <<"urn:xmpp:mam:0">>} ->
    -	  decode_mam_fin(<<"urn:xmpp:mam:0">>, IgnoreEls, _el);
    -      {<<"prefs">>, <<"urn:xmpp:mam:0">>} ->
    -	  decode_mam_prefs(<<"urn:xmpp:mam:0">>, IgnoreEls, _el);
    -      {<<"prefs">>, <<"urn:xmpp:mam:1">>} ->
    -	  decode_mam_prefs(<<"urn:xmpp:mam:1">>, IgnoreEls, _el);
    -      {<<"prefs">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_prefs(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
    -			   _el);
    -      {<<"always">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_always(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
    -			    _el);
    -      {<<"never">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_never(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
    -			   _el);
    -      {<<"jid">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_jid(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el);
    -      {<<"result">>, <<"urn:xmpp:mam:0">>} ->
    -	  decode_mam_result(<<"urn:xmpp:mam:0">>, IgnoreEls, _el);
    -      {<<"result">>, <<"urn:xmpp:mam:1">>} ->
    -	  decode_mam_result(<<"urn:xmpp:mam:1">>, IgnoreEls, _el);
    -      {<<"result">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_result(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
    -			    _el);
    -      {<<"archived">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_archived(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
    -			      _el);
    -      {<<"query">>, <<"urn:xmpp:mam:0">>} ->
    -	  decode_mam_query(<<"urn:xmpp:mam:0">>, IgnoreEls, _el);
    -      {<<"query">>, <<"urn:xmpp:mam:1">>} ->
    -	  decode_mam_query(<<"urn:xmpp:mam:1">>, IgnoreEls, _el);
    -      {<<"query">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_query(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
    -			   _el);
    -      {<<"with">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_with(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el);
    -      {<<"end">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_end(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el);
    -      {<<"start">>, <<"urn:xmpp:mam:tmp">>} ->
    -	  decode_mam_start(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
    -			   _el);
    -      {<<"set">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  decode_rsm_set(<<"http://jabber.org/protocol/rsm">>,
    -			 IgnoreEls, _el);
    -      {<<"first">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  decode_rsm_first(<<"http://jabber.org/protocol/rsm">>,
    -			   IgnoreEls, _el);
    -      {<<"max">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  decode_rsm_max(<<"http://jabber.org/protocol/rsm">>,
    -			 IgnoreEls, _el);
    -      {<<"index">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  decode_rsm_index(<<"http://jabber.org/protocol/rsm">>,
    -			   IgnoreEls, _el);
    -      {<<"count">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  decode_rsm_count(<<"http://jabber.org/protocol/rsm">>,
    -			   IgnoreEls, _el);
    -      {<<"last">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  decode_rsm_last(<<"http://jabber.org/protocol/rsm">>,
    -			  IgnoreEls, _el);
    -      {<<"before">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  decode_rsm_before(<<"http://jabber.org/protocol/rsm">>,
    -			    IgnoreEls, _el);
    -      {<<"after">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  decode_rsm_after(<<"http://jabber.org/protocol/rsm">>,
    -			   IgnoreEls, _el);
    -      {<<"x">>, <<"http://jabber.org/protocol/muc">>} ->
    -	  decode_muc(<<"http://jabber.org/protocol/muc">>,
    -		     IgnoreEls, _el);
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  decode_muc_admin(<<"http://jabber.org/protocol/muc#admin">>,
    -			   IgnoreEls, _el);
    -      {<<"reason">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  decode_muc_admin_reason(<<"http://jabber.org/protocol/muc#admin">>,
    -				  IgnoreEls, _el);
    -      {<<"continue">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  decode_muc_admin_continue(<<"http://jabber.org/protocol/muc#admin">>,
    -				    IgnoreEls, _el);
    -      {<<"actor">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  decode_muc_admin_actor(<<"http://jabber.org/protocol/muc#admin">>,
    -				 IgnoreEls, _el);
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  decode_muc_admin_item(<<"http://jabber.org/protocol/muc#admin">>,
    -				IgnoreEls, _el);
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/muc#owner">>} ->
    -	  decode_muc_owner(<<"http://jabber.org/protocol/muc#owner">>,
    -			   IgnoreEls, _el);
    -      {<<"destroy">>,
    -       <<"http://jabber.org/protocol/muc#owner">>} ->
    -	  decode_muc_owner_destroy(<<"http://jabber.org/protocol/muc#owner">>,
    -				   IgnoreEls, _el);
    -      {<<"reason">>,
    -       <<"http://jabber.org/protocol/muc#owner">>} ->
    -	  decode_muc_owner_reason(<<"http://jabber.org/protocol/muc#owner">>,
    -				  IgnoreEls, _el);
    -      {<<"password">>,
    -       <<"http://jabber.org/protocol/muc#owner">>} ->
    -	  decode_muc_owner_password(<<"http://jabber.org/protocol/muc#owner">>,
    -				    IgnoreEls, _el);
    -      {<<"x">>, <<"http://jabber.org/protocol/muc#user">>} ->
    -	  decode_muc_user(<<"http://jabber.org/protocol/muc#user">>,
    -			  IgnoreEls, _el);
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  decode_muc_user_item(<<"http://jabber.org/protocol/muc#user">>,
    -			       IgnoreEls, _el);
    -      {<<"status">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  decode_muc_user_status(<<"http://jabber.org/protocol/muc#user">>,
    -				 IgnoreEls, _el);
    -      {<<"continue">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  decode_muc_user_continue(<<"http://jabber.org/protocol/muc#user">>,
    -				   IgnoreEls, _el);
    -      {<<"actor">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  decode_muc_user_actor(<<"http://jabber.org/protocol/muc#user">>,
    -				IgnoreEls, _el);
    -      {<<"invite">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  decode_muc_user_invite(<<"http://jabber.org/protocol/muc#user">>,
    -				 IgnoreEls, _el);
    -      {<<"destroy">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  decode_muc_user_destroy(<<"http://jabber.org/protocol/muc#user">>,
    -				  IgnoreEls, _el);
    -      {<<"decline">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  decode_muc_user_decline(<<"http://jabber.org/protocol/muc#user">>,
    -				  IgnoreEls, _el);
    -      {<<"reason">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>,
    -				 IgnoreEls, _el);
    -      {<<"history">>, <<"http://jabber.org/protocol/muc">>} ->
    -	  decode_muc_history(<<"http://jabber.org/protocol/muc">>,
    -			     IgnoreEls, _el);
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/bytestreams">>} ->
    -	  decode_bytestreams(<<"http://jabber.org/protocol/bytestreams">>,
    -			     IgnoreEls, _el);
    -      {<<"activate">>,
    -       <<"http://jabber.org/protocol/bytestreams">>} ->
    -	  decode_bytestreams_activate(<<"http://jabber.org/protocol/bytestreams">>,
    -				      IgnoreEls, _el);
    -      {<<"streamhost-used">>,
    -       <<"http://jabber.org/protocol/bytestreams">>} ->
    -	  decode_bytestreams_streamhost_used(<<"http://jabber.org/protocol/bytestreams">>,
    -					     IgnoreEls, _el);
    -      {<<"streamhost">>,
    -       <<"http://jabber.org/protocol/bytestreams">>} ->
    -	  decode_bytestreams_streamhost(<<"http://jabber.org/protocol/bytestreams">>,
    -					IgnoreEls, _el);
    -      {<<"delay">>, <<"urn:xmpp:delay">>} ->
    -	  decode_delay(<<"urn:xmpp:delay">>, IgnoreEls, _el);
    -      {<<"paused">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  decode_chatstate_paused(<<"http://jabber.org/protocol/chatstates">>,
    -				  IgnoreEls, _el);
    -      {<<"inactive">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  decode_chatstate_inactive(<<"http://jabber.org/protocol/chatstates">>,
    -				    IgnoreEls, _el);
    -      {<<"gone">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  decode_chatstate_gone(<<"http://jabber.org/protocol/chatstates">>,
    -				IgnoreEls, _el);
    -      {<<"composing">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  decode_chatstate_composing(<<"http://jabber.org/protocol/chatstates">>,
    -				     IgnoreEls, _el);
    -      {<<"active">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  decode_chatstate_active(<<"http://jabber.org/protocol/chatstates">>,
    -				  IgnoreEls, _el);
    -      {<<"headers">>,
    -       <<"http://jabber.org/protocol/shim">>} ->
    -	  decode_shim_headers(<<"http://jabber.org/protocol/shim">>,
    -			      IgnoreEls, _el);
    -      {<<"header">>, <<"http://jabber.org/protocol/shim">>} ->
    -	  decode_shim_header(<<"http://jabber.org/protocol/shim">>,
    -			     IgnoreEls, _el);
    -      {<<"pubsub">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub(<<"http://jabber.org/protocol/pubsub">>,
    -			IgnoreEls, _el);
    -      {<<"retract">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_retract(<<"http://jabber.org/protocol/pubsub">>,
    -				IgnoreEls, _el);
    -      {<<"options">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_options(<<"http://jabber.org/protocol/pubsub">>,
    -				IgnoreEls, _el);
    -      {<<"publish">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_publish(<<"http://jabber.org/protocol/pubsub">>,
    -				IgnoreEls, _el);
    -      {<<"unsubscribe">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_unsubscribe(<<"http://jabber.org/protocol/pubsub">>,
    -				    IgnoreEls, _el);
    -      {<<"subscribe">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_subscribe(<<"http://jabber.org/protocol/pubsub">>,
    -				  IgnoreEls, _el);
    -      {<<"affiliations">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_affiliations(<<"http://jabber.org/protocol/pubsub">>,
    -				     IgnoreEls, _el);
    -      {<<"subscriptions">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_subscriptions(<<"http://jabber.org/protocol/pubsub">>,
    -				      IgnoreEls, _el);
    -      {<<"event">>,
    -       <<"http://jabber.org/protocol/pubsub#event">>} ->
    -	  decode_pubsub_event(<<"http://jabber.org/protocol/pubsub#event">>,
    -			      IgnoreEls, _el);
    -      {<<"items">>,
    -       <<"http://jabber.org/protocol/pubsub#event">>} ->
    -	  decode_pubsub_event_items(<<"http://jabber.org/protocol/pubsub#event">>,
    -				    IgnoreEls, _el);
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/pubsub#event">>} ->
    -	  decode_pubsub_event_item(<<"http://jabber.org/protocol/pubsub#event">>,
    -				   IgnoreEls, _el);
    -      {<<"retract">>,
    -       <<"http://jabber.org/protocol/pubsub#event">>} ->
    -	  decode_pubsub_event_retract(<<"http://jabber.org/protocol/pubsub#event">>,
    -				      IgnoreEls, _el);
    -      {<<"items">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_items(<<"http://jabber.org/protocol/pubsub">>,
    -			      IgnoreEls, _el);
    -      {<<"item">>, <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_item(<<"http://jabber.org/protocol/pubsub">>,
    -			     IgnoreEls, _el);
    -      {<<"affiliation">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_affiliation(<<"http://jabber.org/protocol/pubsub">>,
    -				    IgnoreEls, _el);
    -      {<<"subscription">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  decode_pubsub_subscription(<<"http://jabber.org/protocol/pubsub">>,
    -				     IgnoreEls, _el);
    -      {<<"x">>, <<"jabber:x:data">>} ->
    -	  decode_xdata(<<"jabber:x:data">>, IgnoreEls, _el);
    -      {<<"item">>, <<"jabber:x:data">>} ->
    -	  decode_xdata_item(<<"jabber:x:data">>, IgnoreEls, _el);
    -      {<<"reported">>, <<"jabber:x:data">>} ->
    -	  decode_xdata_reported(<<"jabber:x:data">>, IgnoreEls,
    -				_el);
    -      {<<"title">>, <<"jabber:x:data">>} ->
    -	  decode_xdata_title(<<"jabber:x:data">>, IgnoreEls, _el);
    -      {<<"instructions">>, <<"jabber:x:data">>} ->
    -	  decode_xdata_instructions(<<"jabber:x:data">>,
    -				    IgnoreEls, _el);
    -      {<<"field">>, <<"jabber:x:data">>} ->
    -	  decode_xdata_field(<<"jabber:x:data">>, IgnoreEls, _el);
    -      {<<"option">>, <<"jabber:x:data">>} ->
    -	  decode_xdata_field_option(<<"jabber:x:data">>,
    -				    IgnoreEls, _el);
    -      {<<"value">>, <<"jabber:x:data">>} ->
    -	  decode_xdata_field_value(<<"jabber:x:data">>, IgnoreEls,
    -				   _el);
    -      {<<"desc">>, <<"jabber:x:data">>} ->
    -	  decode_xdata_field_desc(<<"jabber:x:data">>, IgnoreEls,
    -				  _el);
    -      {<<"required">>, <<"jabber:x:data">>} ->
    -	  decode_xdata_field_required(<<"jabber:x:data">>,
    -				      IgnoreEls, _el);
    -      {<<"x">>, <<"vcard-temp:x:update">>} ->
    -	  decode_vcard_xupdate(<<"vcard-temp:x:update">>,
    -			       IgnoreEls, _el);
    -      {<<"photo">>, <<"vcard-temp:x:update">>} ->
    -	  decode_vcard_xupdate_photo(<<"vcard-temp:x:update">>,
    -				     IgnoreEls, _el);
    -      {<<"vCard">>, <<"vcard-temp">>} ->
    -	  decode_vcard(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"CLASS">>, <<"vcard-temp">>} ->
    -	  decode_vcard_CLASS(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"CATEGORIES">>, <<"vcard-temp">>} ->
    -	  decode_vcard_CATEGORIES(<<"vcard-temp">>, IgnoreEls,
    -				  _el);
    -      {<<"KEY">>, <<"vcard-temp">>} ->
    -	  decode_vcard_KEY(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"SOUND">>, <<"vcard-temp">>} ->
    -	  decode_vcard_SOUND(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"ORG">>, <<"vcard-temp">>} ->
    -	  decode_vcard_ORG(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PHOTO">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PHOTO(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"LOGO">>, <<"vcard-temp">>} ->
    -	  decode_vcard_LOGO(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"BINVAL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_BINVAL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"GEO">>, <<"vcard-temp">>} ->
    -	  decode_vcard_GEO(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"EMAIL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_EMAIL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"TEL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_TEL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"LABEL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_LABEL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"ADR">>, <<"vcard-temp">>} ->
    -	  decode_vcard_ADR(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"N">>, <<"vcard-temp">>} ->
    -	  decode_vcard_N(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"CONFIDENTIAL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_CONFIDENTIAL(<<"vcard-temp">>, IgnoreEls,
    -				    _el);
    -      {<<"PRIVATE">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PRIVATE(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PUBLIC">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PUBLIC(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"EXTVAL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_EXTVAL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"TYPE">>, <<"vcard-temp">>} ->
    -	  decode_vcard_TYPE(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"DESC">>, <<"vcard-temp">>} ->
    -	  decode_vcard_DESC(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"URL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_URL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"UID">>, <<"vcard-temp">>} ->
    -	  decode_vcard_UID(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"SORT-STRING">>, <<"vcard-temp">>} ->
    -	  decode_vcard_SORT_STRING(<<"vcard-temp">>, IgnoreEls,
    -				   _el);
    -      {<<"REV">>, <<"vcard-temp">>} ->
    -	  decode_vcard_REV(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PRODID">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PRODID(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"NOTE">>, <<"vcard-temp">>} ->
    -	  decode_vcard_NOTE(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"KEYWORD">>, <<"vcard-temp">>} ->
    -	  decode_vcard_KEYWORD(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"ROLE">>, <<"vcard-temp">>} ->
    -	  decode_vcard_ROLE(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"TITLE">>, <<"vcard-temp">>} ->
    -	  decode_vcard_TITLE(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"TZ">>, <<"vcard-temp">>} ->
    -	  decode_vcard_TZ(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"MAILER">>, <<"vcard-temp">>} ->
    -	  decode_vcard_MAILER(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"JABBERID">>, <<"vcard-temp">>} ->
    -	  decode_vcard_JABBERID(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"BDAY">>, <<"vcard-temp">>} ->
    -	  decode_vcard_BDAY(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"NICKNAME">>, <<"vcard-temp">>} ->
    -	  decode_vcard_NICKNAME(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"FN">>, <<"vcard-temp">>} ->
    -	  decode_vcard_FN(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"VERSION">>, <<"vcard-temp">>} ->
    -	  decode_vcard_VERSION(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"CRED">>, <<"vcard-temp">>} ->
    -	  decode_vcard_CRED(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PHONETIC">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PHONETIC(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"ORGUNIT">>, <<"vcard-temp">>} ->
    -	  decode_vcard_ORGUNIT(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"ORGNAME">>, <<"vcard-temp">>} ->
    -	  decode_vcard_ORGNAME(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"LON">>, <<"vcard-temp">>} ->
    -	  decode_vcard_LON(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"LAT">>, <<"vcard-temp">>} ->
    -	  decode_vcard_LAT(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"USERID">>, <<"vcard-temp">>} ->
    -	  decode_vcard_USERID(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"NUMBER">>, <<"vcard-temp">>} ->
    -	  decode_vcard_NUMBER(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"LINE">>, <<"vcard-temp">>} ->
    -	  decode_vcard_LINE(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"CTRY">>, <<"vcard-temp">>} ->
    -	  decode_vcard_CTRY(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PCODE">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PCODE(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"REGION">>, <<"vcard-temp">>} ->
    -	  decode_vcard_REGION(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"LOCALITY">>, <<"vcard-temp">>} ->
    -	  decode_vcard_LOCALITY(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"STREET">>, <<"vcard-temp">>} ->
    -	  decode_vcard_STREET(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"EXTADD">>, <<"vcard-temp">>} ->
    -	  decode_vcard_EXTADD(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"POBOX">>, <<"vcard-temp">>} ->
    -	  decode_vcard_POBOX(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"SUFFIX">>, <<"vcard-temp">>} ->
    -	  decode_vcard_SUFFIX(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PREFIX">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PREFIX(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"MIDDLE">>, <<"vcard-temp">>} ->
    -	  decode_vcard_MIDDLE(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"GIVEN">>, <<"vcard-temp">>} ->
    -	  decode_vcard_GIVEN(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"FAMILY">>, <<"vcard-temp">>} ->
    -	  decode_vcard_FAMILY(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"X400">>, <<"vcard-temp">>} ->
    -	  decode_vcard_X400(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"INTERNET">>, <<"vcard-temp">>} ->
    -	  decode_vcard_INTERNET(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PREF">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PREF(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"INTL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_INTL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"DOM">>, <<"vcard-temp">>} ->
    -	  decode_vcard_DOM(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PARCEL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PARCEL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"POSTAL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_POSTAL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PCS">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PCS(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"ISDN">>, <<"vcard-temp">>} ->
    -	  decode_vcard_ISDN(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"MODEM">>, <<"vcard-temp">>} ->
    -	  decode_vcard_MODEM(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"BBS">>, <<"vcard-temp">>} ->
    -	  decode_vcard_BBS(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"VIDEO">>, <<"vcard-temp">>} ->
    -	  decode_vcard_VIDEO(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"CELL">>, <<"vcard-temp">>} ->
    -	  decode_vcard_CELL(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"MSG">>, <<"vcard-temp">>} ->
    -	  decode_vcard_MSG(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"PAGER">>, <<"vcard-temp">>} ->
    -	  decode_vcard_PAGER(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"FAX">>, <<"vcard-temp">>} ->
    -	  decode_vcard_FAX(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"VOICE">>, <<"vcard-temp">>} ->
    -	  decode_vcard_VOICE(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"WORK">>, <<"vcard-temp">>} ->
    -	  decode_vcard_WORK(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"HOME">>, <<"vcard-temp">>} ->
    -	  decode_vcard_HOME(<<"vcard-temp">>, IgnoreEls, _el);
    -      {<<"stream:error">>,
    -       <<"http://etherx.jabber.org/streams">>} ->
    -	  decode_stream_error(<<"http://etherx.jabber.org/streams">>,
    -			      IgnoreEls, _el);
    -      {<<"unsupported-version">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_unsupported_version(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						  IgnoreEls, _el);
    -      {<<"unsupported-stanza-type">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_unsupported_stanza_type(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						      IgnoreEls, _el);
    -      {<<"unsupported-encoding">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_unsupported_encoding(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						   IgnoreEls, _el);
    -      {<<"undefined-condition">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_undefined_condition(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						  IgnoreEls, _el);
    -      {<<"system-shutdown">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_system_shutdown(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					      IgnoreEls, _el);
    -      {<<"see-other-host">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_see_other_host(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					     IgnoreEls, _el);
    -      {<<"restricted-xml">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_restricted_xml(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					     IgnoreEls, _el);
    -      {<<"resource-constraint">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_resource_constraint(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						  IgnoreEls, _el);
    -      {<<"reset">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_reset(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -				    IgnoreEls, _el);
    -      {<<"remote-connection-failed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_remote_connection_failed(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						       IgnoreEls, _el);
    -      {<<"policy-violation">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_policy_violation(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					       IgnoreEls, _el);
    -      {<<"not-well-formed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_not_well_formed(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					      IgnoreEls, _el);
    -      {<<"not-authorized">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_not_authorized(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					     IgnoreEls, _el);
    -      {<<"invalid-xml">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_invalid_xml(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					  IgnoreEls, _el);
    -      {<<"invalid-namespace">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_invalid_namespace(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						IgnoreEls, _el);
    -      {<<"invalid-id">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_invalid_id(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					 IgnoreEls, _el);
    -      {<<"invalid-from">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_invalid_from(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					   IgnoreEls, _el);
    -      {<<"internal-server-error">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_internal_server_error(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						    IgnoreEls, _el);
    -      {<<"improper-addressing">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_improper_addressing(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						  IgnoreEls, _el);
    -      {<<"host-unknown">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_host_unknown(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					   IgnoreEls, _el);
    -      {<<"host-gone">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_host_gone(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					IgnoreEls, _el);
    -      {<<"connection-timeout">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_connection_timeout(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						 IgnoreEls, _el);
    -      {<<"conflict">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_conflict(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -				       IgnoreEls, _el);
    -      {<<"bad-namespace-prefix">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_bad_namespace_prefix(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -						   IgnoreEls, _el);
    -      {<<"bad-format">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_bad_format(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -					 IgnoreEls, _el);
    -      {<<"text">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  decode_stream_error_text(<<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -				   IgnoreEls, _el);
    -      {<<"time">>, <<"urn:xmpp:time">>} ->
    -	  decode_time(<<"urn:xmpp:time">>, IgnoreEls, _el);
    -      {<<"tzo">>, <<"urn:xmpp:time">>} ->
    -	  decode_time_tzo(<<"urn:xmpp:time">>, IgnoreEls, _el);
    -      {<<"utc">>, <<"urn:xmpp:time">>} ->
    -	  decode_time_utc(<<"urn:xmpp:time">>, IgnoreEls, _el);
    -      {<<"ping">>, <<"urn:xmpp:ping">>} ->
    -	  decode_ping(<<"urn:xmpp:ping">>, IgnoreEls, _el);
    -      {<<"session">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-session">>} ->
    -	  decode_session(<<"urn:ietf:params:xml:ns:xmpp-session">>,
    -			 IgnoreEls, _el);
    -      {<<"query">>, <<"jabber:iq:register">>} ->
    -	  decode_register(<<"jabber:iq:register">>, IgnoreEls,
    -			  _el);
    -      {<<"key">>, <<"jabber:iq:register">>} ->
    -	  decode_register_key(<<"jabber:iq:register">>, IgnoreEls,
    -			      _el);
    -      {<<"text">>, <<"jabber:iq:register">>} ->
    -	  decode_register_text(<<"jabber:iq:register">>,
    -			       IgnoreEls, _el);
    -      {<<"misc">>, <<"jabber:iq:register">>} ->
    -	  decode_register_misc(<<"jabber:iq:register">>,
    -			       IgnoreEls, _el);
    -      {<<"date">>, <<"jabber:iq:register">>} ->
    -	  decode_register_date(<<"jabber:iq:register">>,
    -			       IgnoreEls, _el);
    -      {<<"url">>, <<"jabber:iq:register">>} ->
    -	  decode_register_url(<<"jabber:iq:register">>, IgnoreEls,
    -			      _el);
    -      {<<"phone">>, <<"jabber:iq:register">>} ->
    -	  decode_register_phone(<<"jabber:iq:register">>,
    -				IgnoreEls, _el);
    -      {<<"zip">>, <<"jabber:iq:register">>} ->
    -	  decode_register_zip(<<"jabber:iq:register">>, IgnoreEls,
    -			      _el);
    -      {<<"state">>, <<"jabber:iq:register">>} ->
    -	  decode_register_state(<<"jabber:iq:register">>,
    -				IgnoreEls, _el);
    -      {<<"city">>, <<"jabber:iq:register">>} ->
    -	  decode_register_city(<<"jabber:iq:register">>,
    -			       IgnoreEls, _el);
    -      {<<"address">>, <<"jabber:iq:register">>} ->
    -	  decode_register_address(<<"jabber:iq:register">>,
    -				  IgnoreEls, _el);
    -      {<<"email">>, <<"jabber:iq:register">>} ->
    -	  decode_register_email(<<"jabber:iq:register">>,
    -				IgnoreEls, _el);
    -      {<<"last">>, <<"jabber:iq:register">>} ->
    -	  decode_register_last(<<"jabber:iq:register">>,
    -			       IgnoreEls, _el);
    -      {<<"first">>, <<"jabber:iq:register">>} ->
    -	  decode_register_first(<<"jabber:iq:register">>,
    -				IgnoreEls, _el);
    -      {<<"name">>, <<"jabber:iq:register">>} ->
    -	  decode_register_name(<<"jabber:iq:register">>,
    -			       IgnoreEls, _el);
    -      {<<"password">>, <<"jabber:iq:register">>} ->
    -	  decode_register_password(<<"jabber:iq:register">>,
    -				   IgnoreEls, _el);
    -      {<<"nick">>, <<"jabber:iq:register">>} ->
    -	  decode_register_nick(<<"jabber:iq:register">>,
    -			       IgnoreEls, _el);
    -      {<<"username">>, <<"jabber:iq:register">>} ->
    -	  decode_register_username(<<"jabber:iq:register">>,
    -				   IgnoreEls, _el);
    -      {<<"instructions">>, <<"jabber:iq:register">>} ->
    -	  decode_register_instructions(<<"jabber:iq:register">>,
    -				       IgnoreEls, _el);
    -      {<<"remove">>, <<"jabber:iq:register">>} ->
    -	  decode_register_remove(<<"jabber:iq:register">>,
    -				 IgnoreEls, _el);
    -      {<<"registered">>, <<"jabber:iq:register">>} ->
    -	  decode_register_registered(<<"jabber:iq:register">>,
    -				     IgnoreEls, _el);
    -      {<<"register">>,
    -       <<"http://jabber.org/features/iq-register">>} ->
    -	  decode_feature_register(<<"http://jabber.org/features/iq-register">>,
    -				  IgnoreEls, _el);
    -      {<<"c">>, <<"http://jabber.org/protocol/caps">>} ->
    -	  decode_caps(<<"http://jabber.org/protocol/caps">>,
    -		      IgnoreEls, _el);
    -      {<<"ack">>, <<"p1:ack">>} ->
    -	  decode_p1_ack(<<"p1:ack">>, IgnoreEls, _el);
    -      {<<"rebind">>, <<"p1:rebind">>} ->
    -	  decode_p1_rebind(<<"p1:rebind">>, IgnoreEls, _el);
    -      {<<"push">>, <<"p1:push">>} ->
    -	  decode_p1_push(<<"p1:push">>, IgnoreEls, _el);
    -      {<<"stream:features">>,
    -       <<"http://etherx.jabber.org/streams">>} ->
    -	  decode_stream_features(<<"http://etherx.jabber.org/streams">>,
    -				 IgnoreEls, _el);
    -      {<<"compression">>,
    -       <<"http://jabber.org/features/compress">>} ->
    -	  decode_compression(<<"http://jabber.org/features/compress">>,
    -			     IgnoreEls, _el);
    -      {<<"method">>,
    -       <<"http://jabber.org/features/compress">>} ->
    -	  decode_compression_method(<<"http://jabber.org/features/compress">>,
    -				    IgnoreEls, _el);
    -      {<<"compressed">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  decode_compressed(<<"http://jabber.org/protocol/compress">>,
    -			    IgnoreEls, _el);
    -      {<<"compress">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  decode_compress(<<"http://jabber.org/protocol/compress">>,
    -			  IgnoreEls, _el);
    -      {<<"method">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  decode_compress_method(<<"http://jabber.org/protocol/compress">>,
    -				 IgnoreEls, _el);
    -      {<<"failure">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  decode_compress_failure(<<"http://jabber.org/protocol/compress">>,
    -				  IgnoreEls, _el);
    -      {<<"unsupported-method">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  decode_compress_failure_unsupported_method(<<"http://jabber.org/protocol/compress">>,
    -						     IgnoreEls, _el);
    -      {<<"processing-failed">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  decode_compress_failure_processing_failed(<<"http://jabber.org/protocol/compress">>,
    -						    IgnoreEls, _el);
    -      {<<"setup-failed">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  decode_compress_failure_setup_failed(<<"http://jabber.org/protocol/compress">>,
    -					       IgnoreEls, _el);
    -      {<<"failure">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-tls">>} ->
    -	  decode_starttls_failure(<<"urn:ietf:params:xml:ns:xmpp-tls">>,
    -				  IgnoreEls, _el);
    -      {<<"proceed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-tls">>} ->
    -	  decode_starttls_proceed(<<"urn:ietf:params:xml:ns:xmpp-tls">>,
    -				  IgnoreEls, _el);
    -      {<<"starttls">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-tls">>} ->
    -	  decode_starttls(<<"urn:ietf:params:xml:ns:xmpp-tls">>,
    -			  IgnoreEls, _el);
    -      {<<"required">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-tls">>} ->
    -	  decode_starttls_required(<<"urn:ietf:params:xml:ns:xmpp-tls">>,
    -				   IgnoreEls, _el);
    -      {<<"mechanisms">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_mechanisms(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -				 IgnoreEls, _el);
    -      {<<"mechanism">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_mechanism(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -				IgnoreEls, _el);
    -      {<<"failure">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -			      IgnoreEls, _el);
    -      {<<"temporary-auth-failure">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_temporary_auth_failure(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -						     IgnoreEls, _el);
    -      {<<"not-authorized">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_not_authorized(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -					     IgnoreEls, _el);
    -      {<<"mechanism-too-weak">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_mechanism_too_weak(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -						 IgnoreEls, _el);
    -      {<<"malformed-request">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_malformed_request(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -						IgnoreEls, _el);
    -      {<<"invalid-mechanism">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_invalid_mechanism(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -						IgnoreEls, _el);
    -      {<<"invalid-authzid">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_invalid_authzid(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -					      IgnoreEls, _el);
    -      {<<"incorrect-encoding">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_incorrect_encoding(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -						 IgnoreEls, _el);
    -      {<<"encryption-required">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_encryption_required(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -						  IgnoreEls, _el);
    -      {<<"credentials-expired">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_credentials_expired(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -						  IgnoreEls, _el);
    -      {<<"account-disabled">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_account_disabled(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -					       IgnoreEls, _el);
    -      {<<"aborted">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_aborted(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -				      IgnoreEls, _el);
    -      {<<"text">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_failure_text(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -				   IgnoreEls, _el);
    -      {<<"success">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_success(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -			      IgnoreEls, _el);
    -      {<<"response">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_response(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -			       IgnoreEls, _el);
    -      {<<"challenge">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_challenge(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -				IgnoreEls, _el);
    -      {<<"abort">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_abort(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -			    IgnoreEls, _el);
    -      {<<"auth">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  decode_sasl_auth(<<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -			   IgnoreEls, _el);
    -      {<<"bind">>, <<"urn:ietf:params:xml:ns:xmpp-bind">>} ->
    -	  decode_bind(<<"urn:ietf:params:xml:ns:xmpp-bind">>,
    -		      IgnoreEls, _el);
    -      {<<"resource">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-bind">>} ->
    -	  decode_bind_resource(<<"urn:ietf:params:xml:ns:xmpp-bind">>,
    -			       IgnoreEls, _el);
    -      {<<"jid">>, <<"urn:ietf:params:xml:ns:xmpp-bind">>} ->
    -	  decode_bind_jid(<<"urn:ietf:params:xml:ns:xmpp-bind">>,
    -			  IgnoreEls, _el);
    -      {<<"error">>, <<"jabber:client">>} ->
    -	  decode_error(<<"jabber:client">>, IgnoreEls, _el);
    -      {<<"text">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_text(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -			    IgnoreEls, _el);
    -      {<<"unexpected-request">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_unexpected_request(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					  IgnoreEls, _el);
    -      {<<"undefined-condition">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_undefined_condition(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					   IgnoreEls, _el);
    -      {<<"subscription-required">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_subscription_required(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					     IgnoreEls, _el);
    -      {<<"service-unavailable">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_service_unavailable(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					   IgnoreEls, _el);
    -      {<<"resource-constraint">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_resource_constraint(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					   IgnoreEls, _el);
    -      {<<"remote-server-timeout">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_remote_server_timeout(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					     IgnoreEls, _el);
    -      {<<"remote-server-not-found">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_remote_server_not_found(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					       IgnoreEls, _el);
    -      {<<"registration-required">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_registration_required(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					     IgnoreEls, _el);
    -      {<<"redirect">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_redirect(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -				IgnoreEls, _el);
    -      {<<"recipient-unavailable">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_recipient_unavailable(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					     IgnoreEls, _el);
    -      {<<"policy-violation">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_policy_violation(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					IgnoreEls, _el);
    -      {<<"not-authorized">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_not_authorized(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -				      IgnoreEls, _el);
    -      {<<"not-allowed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_not_allowed(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -				   IgnoreEls, _el);
    -      {<<"not-acceptable">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_not_acceptable(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -				      IgnoreEls, _el);
    -      {<<"jid-malformed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_jid_malformed(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -				     IgnoreEls, _el);
    -      {<<"item-not-found">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_item_not_found(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -				      IgnoreEls, _el);
    -      {<<"internal-server-error">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_internal_server_error(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					     IgnoreEls, _el);
    -      {<<"gone">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_gone(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -			    IgnoreEls, _el);
    -      {<<"forbidden">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_forbidden(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -				 IgnoreEls, _el);
    -      {<<"feature-not-implemented">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_feature_not_implemented(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -					       IgnoreEls, _el);
    -      {<<"conflict">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_conflict(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -				IgnoreEls, _el);
    -      {<<"bad-request">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  decode_error_bad_request(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -				   IgnoreEls, _el);
    -      {<<"presence">>, <<"jabber:client">>} ->
    -	  decode_presence(<<"jabber:client">>, IgnoreEls, _el);
    -      {<<"priority">>, <<"jabber:client">>} ->
    -	  decode_presence_priority(<<"jabber:client">>, IgnoreEls,
    -				   _el);
    -      {<<"status">>, <<"jabber:client">>} ->
    -	  decode_presence_status(<<"jabber:client">>, IgnoreEls,
    -				 _el);
    -      {<<"show">>, <<"jabber:client">>} ->
    -	  decode_presence_show(<<"jabber:client">>, IgnoreEls,
    -			       _el);
    -      {<<"message">>, <<"jabber:client">>} ->
    -	  decode_message(<<"jabber:client">>, IgnoreEls, _el);
    -      {<<"thread">>, <<"jabber:client">>} ->
    -	  decode_message_thread(<<"jabber:client">>, IgnoreEls,
    -				_el);
    -      {<<"body">>, <<"jabber:client">>} ->
    -	  decode_message_body(<<"jabber:client">>, IgnoreEls,
    -			      _el);
    -      {<<"subject">>, <<"jabber:client">>} ->
    -	  decode_message_subject(<<"jabber:client">>, IgnoreEls,
    -				 _el);
    -      {<<"iq">>, <<"jabber:client">>} ->
    -	  decode_iq(<<"jabber:client">>, IgnoreEls, _el);
    -      {<<"query">>, <<"http://jabber.org/protocol/stats">>} ->
    -	  decode_stats(<<"http://jabber.org/protocol/stats">>,
    -		       IgnoreEls, _el);
    -      {<<"stat">>, <<"http://jabber.org/protocol/stats">>} ->
    -	  decode_stat(<<"http://jabber.org/protocol/stats">>,
    -		      IgnoreEls, _el);
    -      {<<"error">>, <<"http://jabber.org/protocol/stats">>} ->
    -	  decode_stat_error(<<"http://jabber.org/protocol/stats">>,
    -			    IgnoreEls, _el);
    -      {<<"storage">>, <<"storage:bookmarks">>} ->
    -	  decode_bookmarks_storage(<<"storage:bookmarks">>,
    -				   IgnoreEls, _el);
    -      {<<"url">>, <<"storage:bookmarks">>} ->
    -	  decode_bookmark_url(<<"storage:bookmarks">>, IgnoreEls,
    -			      _el);
    -      {<<"conference">>, <<"storage:bookmarks">>} ->
    -	  decode_bookmark_conference(<<"storage:bookmarks">>,
    -				     IgnoreEls, _el);
    -      {<<"password">>, <<"storage:bookmarks">>} ->
    -	  decode_conference_password(<<"storage:bookmarks">>,
    -				     IgnoreEls, _el);
    -      {<<"nick">>, <<"storage:bookmarks">>} ->
    -	  decode_conference_nick(<<"storage:bookmarks">>,
    -				 IgnoreEls, _el);
    -      {<<"query">>, <<"jabber:iq:private">>} ->
    -	  decode_private(<<"jabber:iq:private">>, IgnoreEls, _el);
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/disco#items">>} ->
    -	  decode_disco_items(<<"http://jabber.org/protocol/disco#items">>,
    -			     IgnoreEls, _el);
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/disco#items">>} ->
    -	  decode_disco_item(<<"http://jabber.org/protocol/disco#items">>,
    -			    IgnoreEls, _el);
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/disco#info">>} ->
    -	  decode_disco_info(<<"http://jabber.org/protocol/disco#info">>,
    -			    IgnoreEls, _el);
    -      {<<"feature">>,
    -       <<"http://jabber.org/protocol/disco#info">>} ->
    -	  decode_disco_feature(<<"http://jabber.org/protocol/disco#info">>,
    -			       IgnoreEls, _el);
    -      {<<"identity">>,
    -       <<"http://jabber.org/protocol/disco#info">>} ->
    -	  decode_disco_identity(<<"http://jabber.org/protocol/disco#info">>,
    -				IgnoreEls, _el);
    -      {<<"blocklist">>, <<"urn:xmpp:blocking">>} ->
    -	  decode_block_list(<<"urn:xmpp:blocking">>, IgnoreEls,
    -			    _el);
    -      {<<"unblock">>, <<"urn:xmpp:blocking">>} ->
    -	  decode_unblock(<<"urn:xmpp:blocking">>, IgnoreEls, _el);
    -      {<<"block">>, <<"urn:xmpp:blocking">>} ->
    -	  decode_block(<<"urn:xmpp:blocking">>, IgnoreEls, _el);
    -      {<<"item">>, <<"urn:xmpp:blocking">>} ->
    -	  decode_block_item(<<"urn:xmpp:blocking">>, IgnoreEls,
    -			    _el);
    -      {<<"query">>, <<"jabber:iq:privacy">>} ->
    -	  decode_privacy(<<"jabber:iq:privacy">>, IgnoreEls, _el);
    -      {<<"active">>, <<"jabber:iq:privacy">>} ->
    -	  decode_privacy_active_list(<<"jabber:iq:privacy">>,
    -				     IgnoreEls, _el);
    -      {<<"default">>, <<"jabber:iq:privacy">>} ->
    -	  decode_privacy_default_list(<<"jabber:iq:privacy">>,
    -				      IgnoreEls, _el);
    -      {<<"list">>, <<"jabber:iq:privacy">>} ->
    -	  decode_privacy_list(<<"jabber:iq:privacy">>, IgnoreEls,
    -			      _el);
    -      {<<"item">>, <<"jabber:iq:privacy">>} ->
    -	  decode_privacy_item(<<"jabber:iq:privacy">>, IgnoreEls,
    -			      _el);
    -      {<<"presence-out">>, <<"jabber:iq:privacy">>} ->
    -	  decode_privacy_presence_out(<<"jabber:iq:privacy">>,
    -				      IgnoreEls, _el);
    -      {<<"presence-in">>, <<"jabber:iq:privacy">>} ->
    -	  decode_privacy_presence_in(<<"jabber:iq:privacy">>,
    -				     IgnoreEls, _el);
    -      {<<"iq">>, <<"jabber:iq:privacy">>} ->
    -	  decode_privacy_iq(<<"jabber:iq:privacy">>, IgnoreEls,
    -			    _el);
    -      {<<"message">>, <<"jabber:iq:privacy">>} ->
    -	  decode_privacy_message(<<"jabber:iq:privacy">>,
    -				 IgnoreEls, _el);
    -      {<<"query">>, <<"jabber:iq:roster">>} ->
    -	  decode_roster(<<"jabber:iq:roster">>, IgnoreEls, _el);
    -      {<<"item">>, <<"jabber:iq:roster">>} ->
    -	  decode_roster_item(<<"jabber:iq:roster">>, IgnoreEls,
    -			     _el);
    -      {<<"group">>, <<"jabber:iq:roster">>} ->
    -	  decode_roster_group(<<"jabber:iq:roster">>, IgnoreEls,
    -			      _el);
    -      {<<"query">>, <<"jabber:iq:version">>} ->
    -	  decode_version(<<"jabber:iq:version">>, IgnoreEls, _el);
    -      {<<"os">>, <<"jabber:iq:version">>} ->
    -	  decode_version_os(<<"jabber:iq:version">>, IgnoreEls,
    -			    _el);
    -      {<<"version">>, <<"jabber:iq:version">>} ->
    -	  decode_version_ver(<<"jabber:iq:version">>, IgnoreEls,
    -			     _el);
    -      {<<"name">>, <<"jabber:iq:version">>} ->
    -	  decode_version_name(<<"jabber:iq:version">>, IgnoreEls,
    -			      _el);
    -      {<<"query">>, <<"jabber:iq:last">>} ->
    -	  decode_last(<<"jabber:iq:last">>, IgnoreEls, _el);
    -      {_name, _xmlns} ->
    -	  erlang:error({xmpp_codec, {unknown_tag, _name, _xmlns}})
    -    end.
    -
    -is_known_tag({xmlel, _name, _attrs, _} = _el) ->
    -    case {_name, get_attr(<<"xmlns">>, _attrs)} of
    -      {<<"participant">>, <<"urn:xmpp:mix:0">>} -> true;
    -      {<<"leave">>, <<"urn:xmpp:mix:0">>} -> true;
    -      {<<"join">>, <<"urn:xmpp:mix:0">>} -> true;
    -      {<<"subscribe">>, <<"urn:xmpp:mix:0">>} -> true;
    -      {<<"offline">>,
    -       <<"http://jabber.org/protocol/offline">>} ->
    -	  true;
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/offline">>} ->
    -	  true;
    -      {<<"fetch">>,
    -       <<"http://jabber.org/protocol/offline">>} ->
    -	  true;
    -      {<<"purge">>,
    -       <<"http://jabber.org/protocol/offline">>} ->
    -	  true;
    -      {<<"failed">>, <<"urn:xmpp:sm:2">>} -> true;
    -      {<<"failed">>, <<"urn:xmpp:sm:3">>} -> true;
    -      {<<"a">>, <<"urn:xmpp:sm:2">>} -> true;
    -      {<<"a">>, <<"urn:xmpp:sm:3">>} -> true;
    -      {<<"r">>, <<"urn:xmpp:sm:2">>} -> true;
    -      {<<"r">>, <<"urn:xmpp:sm:3">>} -> true;
    -      {<<"resumed">>, <<"urn:xmpp:sm:2">>} -> true;
    -      {<<"resumed">>, <<"urn:xmpp:sm:3">>} -> true;
    -      {<<"resume">>, <<"urn:xmpp:sm:2">>} -> true;
    -      {<<"resume">>, <<"urn:xmpp:sm:3">>} -> true;
    -      {<<"enabled">>, <<"urn:xmpp:sm:2">>} -> true;
    -      {<<"enabled">>, <<"urn:xmpp:sm:3">>} -> true;
    -      {<<"enable">>, <<"urn:xmpp:sm:2">>} -> true;
    -      {<<"enable">>, <<"urn:xmpp:sm:3">>} -> true;
    -      {<<"sm">>, <<"urn:xmpp:sm:2">>} -> true;
    -      {<<"sm">>, <<"urn:xmpp:sm:3">>} -> true;
    -      {<<"inactive">>, <<"urn:xmpp:csi:0">>} -> true;
    -      {<<"active">>, <<"urn:xmpp:csi:0">>} -> true;
    -      {<<"csi">>, <<"urn:xmpp:csi:0">>} -> true;
    -      {<<"sent">>, <<"urn:xmpp:carbons:2">>} -> true;
    -      {<<"received">>, <<"urn:xmpp:carbons:2">>} -> true;
    -      {<<"private">>, <<"urn:xmpp:carbons:2">>} -> true;
    -      {<<"enable">>, <<"urn:xmpp:carbons:2">>} -> true;
    -      {<<"disable">>, <<"urn:xmpp:carbons:2">>} -> true;
    -      {<<"forwarded">>, <<"urn:xmpp:forward:0">>} -> true;
    -      {<<"fin">>, <<"urn:xmpp:mam:0">>} -> true;
    -      {<<"prefs">>, <<"urn:xmpp:mam:0">>} -> true;
    -      {<<"prefs">>, <<"urn:xmpp:mam:1">>} -> true;
    -      {<<"prefs">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"always">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"never">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"jid">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"result">>, <<"urn:xmpp:mam:0">>} -> true;
    -      {<<"result">>, <<"urn:xmpp:mam:1">>} -> true;
    -      {<<"result">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"archived">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"query">>, <<"urn:xmpp:mam:0">>} -> true;
    -      {<<"query">>, <<"urn:xmpp:mam:1">>} -> true;
    -      {<<"query">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"with">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"end">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"start">>, <<"urn:xmpp:mam:tmp">>} -> true;
    -      {<<"set">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  true;
    -      {<<"first">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  true;
    -      {<<"max">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  true;
    -      {<<"index">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  true;
    -      {<<"count">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  true;
    -      {<<"last">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  true;
    -      {<<"before">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  true;
    -      {<<"after">>, <<"http://jabber.org/protocol/rsm">>} ->
    -	  true;
    -      {<<"x">>, <<"http://jabber.org/protocol/muc">>} -> true;
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  true;
    -      {<<"reason">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  true;
    -      {<<"continue">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  true;
    -      {<<"actor">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  true;
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/muc#admin">>} ->
    -	  true;
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/muc#owner">>} ->
    -	  true;
    -      {<<"destroy">>,
    -       <<"http://jabber.org/protocol/muc#owner">>} ->
    -	  true;
    -      {<<"reason">>,
    -       <<"http://jabber.org/protocol/muc#owner">>} ->
    -	  true;
    -      {<<"password">>,
    -       <<"http://jabber.org/protocol/muc#owner">>} ->
    -	  true;
    -      {<<"x">>, <<"http://jabber.org/protocol/muc#user">>} ->
    -	  true;
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  true;
    -      {<<"status">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  true;
    -      {<<"continue">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  true;
    -      {<<"actor">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  true;
    -      {<<"invite">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  true;
    -      {<<"destroy">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  true;
    -      {<<"decline">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  true;
    -      {<<"reason">>,
    -       <<"http://jabber.org/protocol/muc#user">>} ->
    -	  true;
    -      {<<"history">>, <<"http://jabber.org/protocol/muc">>} ->
    -	  true;
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/bytestreams">>} ->
    -	  true;
    -      {<<"activate">>,
    -       <<"http://jabber.org/protocol/bytestreams">>} ->
    -	  true;
    -      {<<"streamhost-used">>,
    -       <<"http://jabber.org/protocol/bytestreams">>} ->
    -	  true;
    -      {<<"streamhost">>,
    -       <<"http://jabber.org/protocol/bytestreams">>} ->
    -	  true;
    -      {<<"delay">>, <<"urn:xmpp:delay">>} -> true;
    -      {<<"paused">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  true;
    -      {<<"inactive">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  true;
    -      {<<"gone">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  true;
    -      {<<"composing">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  true;
    -      {<<"active">>,
    -       <<"http://jabber.org/protocol/chatstates">>} ->
    -	  true;
    -      {<<"headers">>,
    -       <<"http://jabber.org/protocol/shim">>} ->
    -	  true;
    -      {<<"header">>, <<"http://jabber.org/protocol/shim">>} ->
    -	  true;
    -      {<<"pubsub">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"retract">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"options">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"publish">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"unsubscribe">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"subscribe">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"affiliations">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"subscriptions">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"event">>,
    -       <<"http://jabber.org/protocol/pubsub#event">>} ->
    -	  true;
    -      {<<"items">>,
    -       <<"http://jabber.org/protocol/pubsub#event">>} ->
    -	  true;
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/pubsub#event">>} ->
    -	  true;
    -      {<<"retract">>,
    -       <<"http://jabber.org/protocol/pubsub#event">>} ->
    -	  true;
    -      {<<"items">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"item">>, <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"affiliation">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"subscription">>,
    -       <<"http://jabber.org/protocol/pubsub">>} ->
    -	  true;
    -      {<<"x">>, <<"jabber:x:data">>} -> true;
    -      {<<"item">>, <<"jabber:x:data">>} -> true;
    -      {<<"reported">>, <<"jabber:x:data">>} -> true;
    -      {<<"title">>, <<"jabber:x:data">>} -> true;
    -      {<<"instructions">>, <<"jabber:x:data">>} -> true;
    -      {<<"field">>, <<"jabber:x:data">>} -> true;
    -      {<<"option">>, <<"jabber:x:data">>} -> true;
    -      {<<"value">>, <<"jabber:x:data">>} -> true;
    -      {<<"desc">>, <<"jabber:x:data">>} -> true;
    -      {<<"required">>, <<"jabber:x:data">>} -> true;
    -      {<<"x">>, <<"vcard-temp:x:update">>} -> true;
    -      {<<"photo">>, <<"vcard-temp:x:update">>} -> true;
    -      {<<"vCard">>, <<"vcard-temp">>} -> true;
    -      {<<"CLASS">>, <<"vcard-temp">>} -> true;
    -      {<<"CATEGORIES">>, <<"vcard-temp">>} -> true;
    -      {<<"KEY">>, <<"vcard-temp">>} -> true;
    -      {<<"SOUND">>, <<"vcard-temp">>} -> true;
    -      {<<"ORG">>, <<"vcard-temp">>} -> true;
    -      {<<"PHOTO">>, <<"vcard-temp">>} -> true;
    -      {<<"LOGO">>, <<"vcard-temp">>} -> true;
    -      {<<"BINVAL">>, <<"vcard-temp">>} -> true;
    -      {<<"GEO">>, <<"vcard-temp">>} -> true;
    -      {<<"EMAIL">>, <<"vcard-temp">>} -> true;
    -      {<<"TEL">>, <<"vcard-temp">>} -> true;
    -      {<<"LABEL">>, <<"vcard-temp">>} -> true;
    -      {<<"ADR">>, <<"vcard-temp">>} -> true;
    -      {<<"N">>, <<"vcard-temp">>} -> true;
    -      {<<"CONFIDENTIAL">>, <<"vcard-temp">>} -> true;
    -      {<<"PRIVATE">>, <<"vcard-temp">>} -> true;
    -      {<<"PUBLIC">>, <<"vcard-temp">>} -> true;
    -      {<<"EXTVAL">>, <<"vcard-temp">>} -> true;
    -      {<<"TYPE">>, <<"vcard-temp">>} -> true;
    -      {<<"DESC">>, <<"vcard-temp">>} -> true;
    -      {<<"URL">>, <<"vcard-temp">>} -> true;
    -      {<<"UID">>, <<"vcard-temp">>} -> true;
    -      {<<"SORT-STRING">>, <<"vcard-temp">>} -> true;
    -      {<<"REV">>, <<"vcard-temp">>} -> true;
    -      {<<"PRODID">>, <<"vcard-temp">>} -> true;
    -      {<<"NOTE">>, <<"vcard-temp">>} -> true;
    -      {<<"KEYWORD">>, <<"vcard-temp">>} -> true;
    -      {<<"ROLE">>, <<"vcard-temp">>} -> true;
    -      {<<"TITLE">>, <<"vcard-temp">>} -> true;
    -      {<<"TZ">>, <<"vcard-temp">>} -> true;
    -      {<<"MAILER">>, <<"vcard-temp">>} -> true;
    -      {<<"JABBERID">>, <<"vcard-temp">>} -> true;
    -      {<<"BDAY">>, <<"vcard-temp">>} -> true;
    -      {<<"NICKNAME">>, <<"vcard-temp">>} -> true;
    -      {<<"FN">>, <<"vcard-temp">>} -> true;
    -      {<<"VERSION">>, <<"vcard-temp">>} -> true;
    -      {<<"CRED">>, <<"vcard-temp">>} -> true;
    -      {<<"PHONETIC">>, <<"vcard-temp">>} -> true;
    -      {<<"ORGUNIT">>, <<"vcard-temp">>} -> true;
    -      {<<"ORGNAME">>, <<"vcard-temp">>} -> true;
    -      {<<"LON">>, <<"vcard-temp">>} -> true;
    -      {<<"LAT">>, <<"vcard-temp">>} -> true;
    -      {<<"USERID">>, <<"vcard-temp">>} -> true;
    -      {<<"NUMBER">>, <<"vcard-temp">>} -> true;
    -      {<<"LINE">>, <<"vcard-temp">>} -> true;
    -      {<<"CTRY">>, <<"vcard-temp">>} -> true;
    -      {<<"PCODE">>, <<"vcard-temp">>} -> true;
    -      {<<"REGION">>, <<"vcard-temp">>} -> true;
    -      {<<"LOCALITY">>, <<"vcard-temp">>} -> true;
    -      {<<"STREET">>, <<"vcard-temp">>} -> true;
    -      {<<"EXTADD">>, <<"vcard-temp">>} -> true;
    -      {<<"POBOX">>, <<"vcard-temp">>} -> true;
    -      {<<"SUFFIX">>, <<"vcard-temp">>} -> true;
    -      {<<"PREFIX">>, <<"vcard-temp">>} -> true;
    -      {<<"MIDDLE">>, <<"vcard-temp">>} -> true;
    -      {<<"GIVEN">>, <<"vcard-temp">>} -> true;
    -      {<<"FAMILY">>, <<"vcard-temp">>} -> true;
    -      {<<"X400">>, <<"vcard-temp">>} -> true;
    -      {<<"INTERNET">>, <<"vcard-temp">>} -> true;
    -      {<<"PREF">>, <<"vcard-temp">>} -> true;
    -      {<<"INTL">>, <<"vcard-temp">>} -> true;
    -      {<<"DOM">>, <<"vcard-temp">>} -> true;
    -      {<<"PARCEL">>, <<"vcard-temp">>} -> true;
    -      {<<"POSTAL">>, <<"vcard-temp">>} -> true;
    -      {<<"PCS">>, <<"vcard-temp">>} -> true;
    -      {<<"ISDN">>, <<"vcard-temp">>} -> true;
    -      {<<"MODEM">>, <<"vcard-temp">>} -> true;
    -      {<<"BBS">>, <<"vcard-temp">>} -> true;
    -      {<<"VIDEO">>, <<"vcard-temp">>} -> true;
    -      {<<"CELL">>, <<"vcard-temp">>} -> true;
    -      {<<"MSG">>, <<"vcard-temp">>} -> true;
    -      {<<"PAGER">>, <<"vcard-temp">>} -> true;
    -      {<<"FAX">>, <<"vcard-temp">>} -> true;
    -      {<<"VOICE">>, <<"vcard-temp">>} -> true;
    -      {<<"WORK">>, <<"vcard-temp">>} -> true;
    -      {<<"HOME">>, <<"vcard-temp">>} -> true;
    -      {<<"stream:error">>,
    -       <<"http://etherx.jabber.org/streams">>} ->
    -	  true;
    -      {<<"unsupported-version">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"unsupported-stanza-type">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"unsupported-encoding">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"undefined-condition">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"system-shutdown">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"see-other-host">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"restricted-xml">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"resource-constraint">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"reset">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"remote-connection-failed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"policy-violation">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"not-well-formed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"not-authorized">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"invalid-xml">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"invalid-namespace">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"invalid-id">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"invalid-from">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"internal-server-error">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"improper-addressing">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"host-unknown">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"host-gone">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"connection-timeout">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"conflict">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"bad-namespace-prefix">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"bad-format">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"text">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-streams">>} ->
    -	  true;
    -      {<<"time">>, <<"urn:xmpp:time">>} -> true;
    -      {<<"tzo">>, <<"urn:xmpp:time">>} -> true;
    -      {<<"utc">>, <<"urn:xmpp:time">>} -> true;
    -      {<<"ping">>, <<"urn:xmpp:ping">>} -> true;
    -      {<<"session">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-session">>} ->
    -	  true;
    -      {<<"query">>, <<"jabber:iq:register">>} -> true;
    -      {<<"key">>, <<"jabber:iq:register">>} -> true;
    -      {<<"text">>, <<"jabber:iq:register">>} -> true;
    -      {<<"misc">>, <<"jabber:iq:register">>} -> true;
    -      {<<"date">>, <<"jabber:iq:register">>} -> true;
    -      {<<"url">>, <<"jabber:iq:register">>} -> true;
    -      {<<"phone">>, <<"jabber:iq:register">>} -> true;
    -      {<<"zip">>, <<"jabber:iq:register">>} -> true;
    -      {<<"state">>, <<"jabber:iq:register">>} -> true;
    -      {<<"city">>, <<"jabber:iq:register">>} -> true;
    -      {<<"address">>, <<"jabber:iq:register">>} -> true;
    -      {<<"email">>, <<"jabber:iq:register">>} -> true;
    -      {<<"last">>, <<"jabber:iq:register">>} -> true;
    -      {<<"first">>, <<"jabber:iq:register">>} -> true;
    -      {<<"name">>, <<"jabber:iq:register">>} -> true;
    -      {<<"password">>, <<"jabber:iq:register">>} -> true;
    -      {<<"nick">>, <<"jabber:iq:register">>} -> true;
    -      {<<"username">>, <<"jabber:iq:register">>} -> true;
    -      {<<"instructions">>, <<"jabber:iq:register">>} -> true;
    -      {<<"remove">>, <<"jabber:iq:register">>} -> true;
    -      {<<"registered">>, <<"jabber:iq:register">>} -> true;
    -      {<<"register">>,
    -       <<"http://jabber.org/features/iq-register">>} ->
    -	  true;
    -      {<<"c">>, <<"http://jabber.org/protocol/caps">>} ->
    -	  true;
    -      {<<"ack">>, <<"p1:ack">>} -> true;
    -      {<<"rebind">>, <<"p1:rebind">>} -> true;
    -      {<<"push">>, <<"p1:push">>} -> true;
    -      {<<"stream:features">>,
    -       <<"http://etherx.jabber.org/streams">>} ->
    -	  true;
    -      {<<"compression">>,
    -       <<"http://jabber.org/features/compress">>} ->
    -	  true;
    -      {<<"method">>,
    -       <<"http://jabber.org/features/compress">>} ->
    -	  true;
    -      {<<"compressed">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  true;
    -      {<<"compress">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  true;
    -      {<<"method">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  true;
    -      {<<"failure">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  true;
    -      {<<"unsupported-method">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  true;
    -      {<<"processing-failed">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  true;
    -      {<<"setup-failed">>,
    -       <<"http://jabber.org/protocol/compress">>} ->
    -	  true;
    -      {<<"failure">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-tls">>} ->
    -	  true;
    -      {<<"proceed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-tls">>} ->
    -	  true;
    -      {<<"starttls">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-tls">>} ->
    -	  true;
    -      {<<"required">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-tls">>} ->
    -	  true;
    -      {<<"mechanisms">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"mechanism">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"failure">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"temporary-auth-failure">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"not-authorized">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"mechanism-too-weak">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"malformed-request">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"invalid-mechanism">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"invalid-authzid">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"incorrect-encoding">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"encryption-required">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"credentials-expired">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"account-disabled">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"aborted">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"text">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"success">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"response">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"challenge">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"abort">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"auth">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>} ->
    -	  true;
    -      {<<"bind">>, <<"urn:ietf:params:xml:ns:xmpp-bind">>} ->
    -	  true;
    -      {<<"resource">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-bind">>} ->
    -	  true;
    -      {<<"jid">>, <<"urn:ietf:params:xml:ns:xmpp-bind">>} ->
    -	  true;
    -      {<<"error">>, <<"jabber:client">>} -> true;
    -      {<<"text">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"unexpected-request">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"undefined-condition">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"subscription-required">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"service-unavailable">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"resource-constraint">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"remote-server-timeout">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"remote-server-not-found">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"registration-required">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"redirect">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"recipient-unavailable">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"policy-violation">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"not-authorized">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"not-allowed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"not-acceptable">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"jid-malformed">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"item-not-found">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"internal-server-error">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"gone">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"forbidden">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"feature-not-implemented">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"conflict">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"bad-request">>,
    -       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
    -	  true;
    -      {<<"presence">>, <<"jabber:client">>} -> true;
    -      {<<"priority">>, <<"jabber:client">>} -> true;
    -      {<<"status">>, <<"jabber:client">>} -> true;
    -      {<<"show">>, <<"jabber:client">>} -> true;
    -      {<<"message">>, <<"jabber:client">>} -> true;
    -      {<<"thread">>, <<"jabber:client">>} -> true;
    -      {<<"body">>, <<"jabber:client">>} -> true;
    -      {<<"subject">>, <<"jabber:client">>} -> true;
    -      {<<"iq">>, <<"jabber:client">>} -> true;
    -      {<<"query">>, <<"http://jabber.org/protocol/stats">>} ->
    -	  true;
    -      {<<"stat">>, <<"http://jabber.org/protocol/stats">>} ->
    -	  true;
    -      {<<"error">>, <<"http://jabber.org/protocol/stats">>} ->
    -	  true;
    -      {<<"storage">>, <<"storage:bookmarks">>} -> true;
    -      {<<"url">>, <<"storage:bookmarks">>} -> true;
    -      {<<"conference">>, <<"storage:bookmarks">>} -> true;
    -      {<<"password">>, <<"storage:bookmarks">>} -> true;
    -      {<<"nick">>, <<"storage:bookmarks">>} -> true;
    -      {<<"query">>, <<"jabber:iq:private">>} -> true;
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/disco#items">>} ->
    -	  true;
    -      {<<"item">>,
    -       <<"http://jabber.org/protocol/disco#items">>} ->
    -	  true;
    -      {<<"query">>,
    -       <<"http://jabber.org/protocol/disco#info">>} ->
    -	  true;
    -      {<<"feature">>,
    -       <<"http://jabber.org/protocol/disco#info">>} ->
    -	  true;
    -      {<<"identity">>,
    -       <<"http://jabber.org/protocol/disco#info">>} ->
    -	  true;
    -      {<<"blocklist">>, <<"urn:xmpp:blocking">>} -> true;
    -      {<<"unblock">>, <<"urn:xmpp:blocking">>} -> true;
    -      {<<"block">>, <<"urn:xmpp:blocking">>} -> true;
    -      {<<"item">>, <<"urn:xmpp:blocking">>} -> true;
    -      {<<"query">>, <<"jabber:iq:privacy">>} -> true;
    -      {<<"active">>, <<"jabber:iq:privacy">>} -> true;
    -      {<<"default">>, <<"jabber:iq:privacy">>} -> true;
    -      {<<"list">>, <<"jabber:iq:privacy">>} -> true;
    -      {<<"item">>, <<"jabber:iq:privacy">>} -> true;
    -      {<<"presence-out">>, <<"jabber:iq:privacy">>} -> true;
    -      {<<"presence-in">>, <<"jabber:iq:privacy">>} -> true;
    -      {<<"iq">>, <<"jabber:iq:privacy">>} -> true;
    -      {<<"message">>, <<"jabber:iq:privacy">>} -> true;
    -      {<<"query">>, <<"jabber:iq:roster">>} -> true;
    -      {<<"item">>, <<"jabber:iq:roster">>} -> true;
    -      {<<"group">>, <<"jabber:iq:roster">>} -> true;
    -      {<<"query">>, <<"jabber:iq:version">>} -> true;
    -      {<<"os">>, <<"jabber:iq:version">>} -> true;
    -      {<<"version">>, <<"jabber:iq:version">>} -> true;
    -      {<<"name">>, <<"jabber:iq:version">>} -> true;
    -      {<<"query">>, <<"jabber:iq:last">>} -> true;
    -      _ -> false
    -    end.
    -
    -encode({xmlel, _, _, _} = El) -> El;
    -encode({last, _, _} = Query) ->
    -    encode_last(Query,
    -		[{<<"xmlns">>, <<"jabber:iq:last">>}]);
    -encode({version, _, _, _} = Query) ->
    -    encode_version(Query,
    -		   [{<<"xmlns">>, <<"jabber:iq:version">>}]);
    -encode({roster_item, _, _, _, _, _} = Item) ->
    -    encode_roster_item(Item,
    -		       [{<<"xmlns">>, <<"jabber:iq:roster">>}]);
    -encode({roster, _, _} = Query) ->
    -    encode_roster(Query,
    -		  [{<<"xmlns">>, <<"jabber:iq:roster">>}]);
    -encode({privacy_item, _, _, _, _, _} = Item) ->
    -    encode_privacy_item(Item,
    -			[{<<"xmlns">>, <<"jabber:iq:privacy">>}]);
    -encode({privacy_list, _, _} = List) ->
    -    encode_privacy_list(List,
    -			[{<<"xmlns">>, <<"jabber:iq:privacy">>}]);
    -encode({privacy, _, _, _} = Query) ->
    -    encode_privacy(Query,
    -		   [{<<"xmlns">>, <<"jabber:iq:privacy">>}]);
    -encode({block, _} = Block) ->
    -    encode_block(Block,
    -		 [{<<"xmlns">>, <<"urn:xmpp:blocking">>}]);
    -encode({unblock, _} = Unblock) ->
    -    encode_unblock(Unblock,
    -		   [{<<"xmlns">>, <<"urn:xmpp:blocking">>}]);
    -encode({block_list} = Blocklist) ->
    -    encode_block_list(Blocklist,
    -		      [{<<"xmlns">>, <<"urn:xmpp:blocking">>}]);
    -encode({identity, _, _, _, _} = Identity) ->
    -    encode_disco_identity(Identity,
    -			  [{<<"xmlns">>,
    -			    <<"http://jabber.org/protocol/disco#info">>}]);
    -encode({disco_info, _, _, _, _} = Query) ->
    -    encode_disco_info(Query,
    -		      [{<<"xmlns">>,
    -			<<"http://jabber.org/protocol/disco#info">>}]);
    -encode({disco_item, _, _, _} = Item) ->
    -    encode_disco_item(Item,
    -		      [{<<"xmlns">>,
    -			<<"http://jabber.org/protocol/disco#items">>}]);
    -encode({disco_items, _, _} = Query) ->
    -    encode_disco_items(Query,
    -		       [{<<"xmlns">>,
    -			 <<"http://jabber.org/protocol/disco#items">>}]);
    -encode({private, _} = Query) ->
    -    encode_private(Query,
    -		   [{<<"xmlns">>, <<"jabber:iq:private">>}]);
    -encode({bookmark_conference, _, _, _, _, _} =
    -	   Conference) ->
    -    encode_bookmark_conference(Conference,
    -			       [{<<"xmlns">>, <<"storage:bookmarks">>}]);
    -encode({bookmark_url, _, _} = Url) ->
    -    encode_bookmark_url(Url,
    -			[{<<"xmlns">>, <<"storage:bookmarks">>}]);
    -encode({bookmark_storage, _, _} = Storage) ->
    -    encode_bookmarks_storage(Storage,
    -			     [{<<"xmlns">>, <<"storage:bookmarks">>}]);
    -encode({stat, _, _, _, _} = Stat) ->
    -    encode_stat(Stat,
    -		[{<<"xmlns">>,
    -		  <<"http://jabber.org/protocol/stats">>}]);
    -encode({stats, _} = Query) ->
    -    encode_stats(Query,
    -		 [{<<"xmlns">>,
    -		   <<"http://jabber.org/protocol/stats">>}]);
    -encode({iq, _, _, _, _, _, _, _} = Iq) ->
    -    encode_iq(Iq, [{<<"xmlns">>, <<"jabber:client">>}]);
    -encode({message, _, _, _, _, _, _, _, _, _, _} =
    -	   Message) ->
    -    encode_message(Message,
    -		   [{<<"xmlns">>, <<"jabber:client">>}]);
    -encode({presence, _, _, _, _, _, _, _, _, _, _} =
    -	   Presence) ->
    -    encode_presence(Presence,
    -		    [{<<"xmlns">>, <<"jabber:client">>}]);
    -encode({gone, _} = Gone) ->
    -    encode_error_gone(Gone,
    -		      [{<<"xmlns">>,
    -			<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}]);
    -encode({redirect, _} = Redirect) ->
    -    encode_error_redirect(Redirect,
    -			  [{<<"xmlns">>,
    -			    <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}]);
    -encode({error, _, _, _, _} = Error) ->
    -    encode_error(Error,
    -		 [{<<"xmlns">>, <<"jabber:client">>}]);
    -encode({bind, _, _} = Bind) ->
    -    encode_bind(Bind,
    -		[{<<"xmlns">>,
    -		  <<"urn:ietf:params:xml:ns:xmpp-bind">>}]);
    -encode({sasl_auth, _, _} = Auth) ->
    -    encode_sasl_auth(Auth,
    -		     [{<<"xmlns">>,
    -		       <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]);
    -encode({sasl_abort} = Abort) ->
    -    encode_sasl_abort(Abort,
    -		      [{<<"xmlns">>,
    -			<<"urn:ietf:params:xml:ns:xmpp-sasl">>}]);
    -encode({sasl_challenge, _} = Challenge) ->
    -    encode_sasl_challenge(Challenge,
    -			  [{<<"xmlns">>,
    -			    <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]);
    -encode({sasl_response, _} = Response) ->
    -    encode_sasl_response(Response,
    -			 [{<<"xmlns">>,
    -			   <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]);
    -encode({sasl_success, _} = Success) ->
    -    encode_sasl_success(Success,
    -			[{<<"xmlns">>,
    -			  <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]);
    -encode({sasl_failure, _, _} = Failure) ->
    -    encode_sasl_failure(Failure,
    -			[{<<"xmlns">>,
    -			  <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]);
    -encode({sasl_mechanisms, _} = Mechanisms) ->
    -    encode_sasl_mechanisms(Mechanisms,
    -			   [{<<"xmlns">>,
    -			     <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]);
    -encode({starttls, _} = Starttls) ->
    -    encode_starttls(Starttls,
    -		    [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-tls">>}]);
    -encode({starttls_proceed} = Proceed) ->
    -    encode_starttls_proceed(Proceed,
    -			    [{<<"xmlns">>,
    -			      <<"urn:ietf:params:xml:ns:xmpp-tls">>}]);
    -encode({starttls_failure} = Failure) ->
    -    encode_starttls_failure(Failure,
    -			    [{<<"xmlns">>,
    -			      <<"urn:ietf:params:xml:ns:xmpp-tls">>}]);
    -encode({compress_failure, _} = Failure) ->
    -    encode_compress_failure(Failure,
    -			    [{<<"xmlns">>,
    -			      <<"http://jabber.org/protocol/compress">>}]);
    -encode({compress, _} = Compress) ->
    -    encode_compress(Compress,
    -		    [{<<"xmlns">>,
    -		      <<"http://jabber.org/protocol/compress">>}]);
    -encode({compressed} = Compressed) ->
    -    encode_compressed(Compressed,
    -		      [{<<"xmlns">>,
    -			<<"http://jabber.org/protocol/compress">>}]);
    -encode({compression, _} = Compression) ->
    -    encode_compression(Compression,
    -		       [{<<"xmlns">>,
    -			 <<"http://jabber.org/features/compress">>}]);
    -encode({stream_features, _} = Stream_features) ->
    -    encode_stream_features(Stream_features,
    -			   [{<<"xmlns">>,
    -			     <<"http://etherx.jabber.org/streams">>}]);
    -encode({p1_push} = Push) ->
    -    encode_p1_push(Push, [{<<"xmlns">>, <<"p1:push">>}]);
    -encode({p1_rebind} = Rebind) ->
    -    encode_p1_rebind(Rebind,
    -		     [{<<"xmlns">>, <<"p1:rebind">>}]);
    -encode({p1_ack} = Ack) ->
    -    encode_p1_ack(Ack, [{<<"xmlns">>, <<"p1:ack">>}]);
    -encode({caps, _, _, _} = C) ->
    -    encode_caps(C,
    -		[{<<"xmlns">>, <<"http://jabber.org/protocol/caps">>}]);
    -encode({feature_register} = Register) ->
    -    encode_feature_register(Register,
    -			    [{<<"xmlns">>,
    -			      <<"http://jabber.org/features/iq-register">>}]);
    -encode({register, _, _, _, _, _, _, _, _, _, _, _, _, _,
    -	_, _, _, _, _, _, _, _} =
    -	   Query) ->
    -    encode_register(Query,
    -		    [{<<"xmlns">>, <<"jabber:iq:register">>}]);
    -encode({session} = Session) ->
    -    encode_session(Session,
    -		   [{<<"xmlns">>,
    -		     <<"urn:ietf:params:xml:ns:xmpp-session">>}]);
    -encode({ping} = Ping) ->
    -    encode_ping(Ping, [{<<"xmlns">>, <<"urn:xmpp:ping">>}]);
    -encode({time, _, _} = Time) ->
    -    encode_time(Time, [{<<"xmlns">>, <<"urn:xmpp:time">>}]);
    -encode({text, _, _} = Text) ->
    -    encode_stream_error_text(Text, []);
    -encode({'see-other-host', _} = See_other_host) ->
    -    encode_stream_error_see_other_host(See_other_host,
    -				       [{<<"xmlns">>,
    -					 <<"urn:ietf:params:xml:ns:xmpp-streams">>}]);
    -encode({stream_error, _, _} = Stream_error) ->
    -    encode_stream_error(Stream_error,
    -			[{<<"xmlns">>,
    -			  <<"http://etherx.jabber.org/streams">>}]);
    -encode({vcard_name, _, _, _, _, _} = N) ->
    -    encode_vcard_N(N, [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_adr, _, _, _, _, _, _, _, _, _, _, _, _,
    -	_, _} =
    -	   Adr) ->
    -    encode_vcard_ADR(Adr,
    -		     [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_label, _, _, _, _, _, _, _, _} = Label) ->
    -    encode_vcard_LABEL(Label,
    -		       [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_tel, _, _, _, _, _, _, _, _, _, _, _, _,
    -	_, _} =
    -	   Tel) ->
    -    encode_vcard_TEL(Tel,
    -		     [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_email, _, _, _, _, _, _} = Email) ->
    -    encode_vcard_EMAIL(Email,
    -		       [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_geo, _, _} = Geo) ->
    -    encode_vcard_GEO(Geo,
    -		     [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_logo, _, _, _} = Logo) ->
    -    encode_vcard_LOGO(Logo,
    -		      [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_photo, _, _, _} = Photo) ->
    -    encode_vcard_PHOTO(Photo,
    -		       [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_org, _, _} = Org) ->
    -    encode_vcard_ORG(Org,
    -		     [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_sound, _, _, _} = Sound) ->
    -    encode_vcard_SOUND(Sound,
    -		       [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_key, _, _} = Key) ->
    -    encode_vcard_KEY(Key,
    -		     [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
    -	_, _, _, _, _, _, _, _, _, _, _, _, _, _, _} =
    -	   Vcard) ->
    -    encode_vcard(Vcard, [{<<"xmlns">>, <<"vcard-temp">>}]);
    -encode({vcard_xupdate, _} = X) ->
    -    encode_vcard_xupdate(X,
    -			 [{<<"xmlns">>, <<"vcard-temp:x:update">>}]);
    -encode({xdata_field, _, _, _, _, _, _, _} = Field) ->
    -    encode_xdata_field(Field,
    -		       [{<<"xmlns">>, <<"jabber:x:data">>}]);
    -encode({xdata, _, _, _, _, _, _} = X) ->
    -    encode_xdata(X, [{<<"xmlns">>, <<"jabber:x:data">>}]);
    -encode({pubsub_subscription, _, _, _, _} =
    -	   Subscription) ->
    -    encode_pubsub_subscription(Subscription,
    -			       [{<<"xmlns">>,
    -				 <<"http://jabber.org/protocol/pubsub">>}]);
    -encode({pubsub_affiliation, _, _} = Affiliation) ->
    -    encode_pubsub_affiliation(Affiliation,
    -			      [{<<"xmlns">>,
    -				<<"http://jabber.org/protocol/pubsub">>}]);
    -encode({pubsub_item, _, _} = Item) ->
    -    encode_pubsub_item(Item,
    -		       [{<<"xmlns">>,
    -			 <<"http://jabber.org/protocol/pubsub">>}]);
    -encode({pubsub_items, _, _, _, _} = Items) ->
    -    encode_pubsub_items(Items,
    -			[{<<"xmlns">>,
    -			  <<"http://jabber.org/protocol/pubsub">>}]);
    -encode({pubsub_event_item, _, _, _, _} = Item) ->
    -    encode_pubsub_event_item(Item,
    -			     [{<<"xmlns">>,
    -			       <<"http://jabber.org/protocol/pubsub#event">>}]);
    -encode({pubsub_event_items, _, _, _} = Items) ->
    -    encode_pubsub_event_items(Items,
    -			      [{<<"xmlns">>,
    -				<<"http://jabber.org/protocol/pubsub#event">>}]);
    -encode({pubsub_event, _} = Event) ->
    -    encode_pubsub_event(Event,
    -			[{<<"xmlns">>,
    -			  <<"http://jabber.org/protocol/pubsub#event">>}]);
    -encode({pubsub_subscribe, _, _} = Subscribe) ->
    -    encode_pubsub_subscribe(Subscribe,
    -			    [{<<"xmlns">>,
    -			      <<"http://jabber.org/protocol/pubsub">>}]);
    -encode({pubsub_unsubscribe, _, _, _} = Unsubscribe) ->
    -    encode_pubsub_unsubscribe(Unsubscribe,
    -			      [{<<"xmlns">>,
    -				<<"http://jabber.org/protocol/pubsub">>}]);
    -encode({pubsub_publish, _, _} = Publish) ->
    -    encode_pubsub_publish(Publish,
    -			  [{<<"xmlns">>,
    -			    <<"http://jabber.org/protocol/pubsub">>}]);
    -encode({pubsub_options, _, _, _, _} = Options) ->
    -    encode_pubsub_options(Options,
    -			  [{<<"xmlns">>,
    -			    <<"http://jabber.org/protocol/pubsub">>}]);
    -encode({pubsub_retract, _, _, _} = Retract) ->
    -    encode_pubsub_retract(Retract,
    -			  [{<<"xmlns">>,
    -			    <<"http://jabber.org/protocol/pubsub">>}]);
    -encode({pubsub, _, _, _, _, _, _, _, _} = Pubsub) ->
    -    encode_pubsub(Pubsub,
    -		  [{<<"xmlns">>,
    -		    <<"http://jabber.org/protocol/pubsub">>}]);
    -encode({shim, _} = Headers) ->
    -    encode_shim_headers(Headers,
    -			[{<<"xmlns">>, <<"http://jabber.org/protocol/shim">>}]);
    -encode({chatstate, active} = Active) ->
    -    encode_chatstate_active(Active,
    -			    [{<<"xmlns">>,
    -			      <<"http://jabber.org/protocol/chatstates">>}]);
    -encode({chatstate, composing} = Composing) ->
    -    encode_chatstate_composing(Composing,
    -			       [{<<"xmlns">>,
    -				 <<"http://jabber.org/protocol/chatstates">>}]);
    -encode({chatstate, gone} = Gone) ->
    -    encode_chatstate_gone(Gone,
    -			  [{<<"xmlns">>,
    -			    <<"http://jabber.org/protocol/chatstates">>}]);
    -encode({chatstate, inactive} = Inactive) ->
    -    encode_chatstate_inactive(Inactive,
    -			      [{<<"xmlns">>,
    -				<<"http://jabber.org/protocol/chatstates">>}]);
    -encode({chatstate, paused} = Paused) ->
    -    encode_chatstate_paused(Paused,
    -			    [{<<"xmlns">>,
    -			      <<"http://jabber.org/protocol/chatstates">>}]);
    -encode({delay, _, _} = Delay) ->
    -    encode_delay(Delay,
    -		 [{<<"xmlns">>, <<"urn:xmpp:delay">>}]);
    -encode({streamhost, _, _, _} = Streamhost) ->
    -    encode_bytestreams_streamhost(Streamhost,
    -				  [{<<"xmlns">>,
    -				    <<"http://jabber.org/protocol/bytestreams">>}]);
    -encode({bytestreams, _, _, _, _, _, _} = Query) ->
    -    encode_bytestreams(Query,
    -		       [{<<"xmlns">>,
    -			 <<"http://jabber.org/protocol/bytestreams">>}]);
    -encode({muc_history, _, _, _, _} = History) ->
    -    encode_muc_history(History,
    -		       [{<<"xmlns">>, <<"http://jabber.org/protocol/muc">>}]);
    -encode({muc_decline, _, _, _} = Decline) ->
    -    encode_muc_user_decline(Decline,
    -			    [{<<"xmlns">>,
    -			      <<"http://jabber.org/protocol/muc#user">>}]);
    -encode({muc_user_destroy, _, _} = Destroy) ->
    -    encode_muc_user_destroy(Destroy,
    -			    [{<<"xmlns">>,
    -			      <<"http://jabber.org/protocol/muc#user">>}]);
    -encode({muc_invite, _, _, _} = Invite) ->
    -    encode_muc_user_invite(Invite,
    -			   [{<<"xmlns">>,
    -			     <<"http://jabber.org/protocol/muc#user">>}]);
    -encode({muc_user, _, _, _, _, _, _} = X) ->
    -    encode_muc_user(X,
    -		    [{<<"xmlns">>,
    -		      <<"http://jabber.org/protocol/muc#user">>}]);
    -encode({muc_owner_destroy, _, _, _} = Destroy) ->
    -    encode_muc_owner_destroy(Destroy,
    -			     [{<<"xmlns">>,
    -			       <<"http://jabber.org/protocol/muc#owner">>}]);
    -encode({muc_owner, _, _} = Query) ->
    -    encode_muc_owner(Query,
    -		     [{<<"xmlns">>,
    -		       <<"http://jabber.org/protocol/muc#owner">>}]);
    -encode({muc_item, _, _, _, _, _, _, _} = Item) ->
    -    encode_muc_admin_item(Item, []);
    -encode({muc_actor, _, _} = Actor) ->
    -    encode_muc_admin_actor(Actor, []);
    -encode({muc_admin, _} = Query) ->
    -    encode_muc_admin(Query,
    -		     [{<<"xmlns">>,
    -		       <<"http://jabber.org/protocol/muc#admin">>}]);
    -encode({muc, _, _} = X) ->
    -    encode_muc(X,
    -	       [{<<"xmlns">>, <<"http://jabber.org/protocol/muc">>}]);
    -encode({rsm_first, _, _} = First) ->
    -    encode_rsm_first(First,
    -		     [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]);
    -encode({rsm_set, _, _, _, _, _, _, _} = Set) ->
    -    encode_rsm_set(Set,
    -		   [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]);
    -encode({mam_query, _, _, _, _, _, _, _} = Query) ->
    -    encode_mam_query(Query, []);
    -encode({mam_archived, _, _} = Archived) ->
    -    encode_mam_archived(Archived,
    -			[{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}]);
    -encode({mam_result, _, _, _, _} = Result) ->
    -    encode_mam_result(Result, []);
    -encode({mam_prefs, _, _, _, _} = Prefs) ->
    -    encode_mam_prefs(Prefs, []);
    -encode({mam_fin, _, _, _, _} = Fin) ->
    -    encode_mam_fin(Fin,
    -		   [{<<"xmlns">>, <<"urn:xmpp:mam:0">>}]);
    -encode({forwarded, _, _} = Forwarded) ->
    -    encode_forwarded(Forwarded,
    -		     [{<<"xmlns">>, <<"urn:xmpp:forward:0">>}]);
    -encode({carbons_disable} = Disable) ->
    -    encode_carbons_disable(Disable,
    -			   [{<<"xmlns">>, <<"urn:xmpp:carbons:2">>}]);
    -encode({carbons_enable} = Enable) ->
    -    encode_carbons_enable(Enable,
    -			  [{<<"xmlns">>, <<"urn:xmpp:carbons:2">>}]);
    -encode({carbons_private} = Private) ->
    -    encode_carbons_private(Private,
    -			   [{<<"xmlns">>, <<"urn:xmpp:carbons:2">>}]);
    -encode({carbons_received, _} = Received) ->
    -    encode_carbons_received(Received,
    -			    [{<<"xmlns">>, <<"urn:xmpp:carbons:2">>}]);
    -encode({carbons_sent, _} = Sent) ->
    -    encode_carbons_sent(Sent,
    -			[{<<"xmlns">>, <<"urn:xmpp:carbons:2">>}]);
    -encode({feature_csi, _} = Csi) ->
    -    encode_feature_csi(Csi, []);
    -encode({csi, active} = Active) ->
    -    encode_csi_active(Active,
    -		      [{<<"xmlns">>, <<"urn:xmpp:csi:0">>}]);
    -encode({csi, inactive} = Inactive) ->
    -    encode_csi_inactive(Inactive,
    -			[{<<"xmlns">>, <<"urn:xmpp:csi:0">>}]);
    -encode({feature_sm, _} = Sm) ->
    -    encode_feature_sm(Sm, []);
    -encode({sm_enable, _, _, _} = Enable) ->
    -    encode_sm_enable(Enable, []);
    -encode({sm_enabled, _, _, _, _, _} = Enabled) ->
    -    encode_sm_enabled(Enabled, []);
    -encode({sm_resume, _, _, _} = Resume) ->
    -    encode_sm_resume(Resume, []);
    -encode({sm_resumed, _, _, _} = Resumed) ->
    -    encode_sm_resumed(Resumed, []);
    -encode({sm_r, _} = R) -> encode_sm_r(R, []);
    -encode({sm_a, _, _} = A) -> encode_sm_a(A, []);
    -encode({sm_failed, _, _, _} = Failed) ->
    -    encode_sm_failed(Failed, []);
    -encode({offline_item, _, _} = Item) ->
    -    encode_offline_item(Item,
    -			[{<<"xmlns">>,
    -			  <<"http://jabber.org/protocol/offline">>}]);
    -encode({offline, _, _, _} = Offline) ->
    -    encode_offline(Offline,
    -		   [{<<"xmlns">>,
    -		     <<"http://jabber.org/protocol/offline">>}]);
    -encode({mix_join, _, _} = Join) ->
    -    encode_mix_join(Join,
    -		    [{<<"xmlns">>, <<"urn:xmpp:mix:0">>}]);
    -encode({mix_leave} = Leave) ->
    -    encode_mix_leave(Leave,
    -		     [{<<"xmlns">>, <<"urn:xmpp:mix:0">>}]);
    -encode({mix_participant, _, _} = Participant) ->
    -    encode_mix_participant(Participant,
    -			   [{<<"xmlns">>, <<"urn:xmpp:mix:0">>}]).
    -
    -get_ns({last, _, _}) -> <<"jabber:iq:last">>;
    -get_ns({version, _, _, _}) -> <<"jabber:iq:version">>;
    -get_ns({roster_item, _, _, _, _, _}) ->
    -    <<"jabber:iq:roster">>;
    -get_ns({roster, _, _}) -> <<"jabber:iq:roster">>;
    -get_ns({privacy_item, _, _, _, _, _}) ->
    -    <<"jabber:iq:privacy">>;
    -get_ns({privacy_list, _, _}) -> <<"jabber:iq:privacy">>;
    -get_ns({privacy, _, _, _}) -> <<"jabber:iq:privacy">>;
    -get_ns({block, _}) -> <<"urn:xmpp:blocking">>;
    -get_ns({unblock, _}) -> <<"urn:xmpp:blocking">>;
    -get_ns({block_list}) -> <<"urn:xmpp:blocking">>;
    -get_ns({identity, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/disco#info">>;
    -get_ns({disco_info, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/disco#info">>;
    -get_ns({disco_item, _, _, _}) ->
    -    <<"http://jabber.org/protocol/disco#items">>;
    -get_ns({disco_items, _, _}) ->
    -    <<"http://jabber.org/protocol/disco#items">>;
    -get_ns({private, _}) -> <<"jabber:iq:private">>;
    -get_ns({bookmark_conference, _, _, _, _, _}) ->
    -    <<"storage:bookmarks">>;
    -get_ns({bookmark_url, _, _}) -> <<"storage:bookmarks">>;
    -get_ns({bookmark_storage, _, _}) ->
    -    <<"storage:bookmarks">>;
    -get_ns({stat, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/stats">>;
    -get_ns({stats, _}) ->
    -    <<"http://jabber.org/protocol/stats">>;
    -get_ns({iq, _, _, _, _, _, _, _}) ->
    -    <<"jabber:client">>;
    -get_ns({message, _, _, _, _, _, _, _, _, _, _}) ->
    -    <<"jabber:client">>;
    -get_ns({presence, _, _, _, _, _, _, _, _, _, _}) ->
    -    <<"jabber:client">>;
    -get_ns({gone, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-stanzas">>;
    -get_ns({redirect, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-stanzas">>;
    -get_ns({error, _, _, _, _}) -> <<"jabber:client">>;
    -get_ns({bind, _, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-bind">>;
    -get_ns({sasl_auth, _, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-sasl">>;
    -get_ns({sasl_abort}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-sasl">>;
    -get_ns({sasl_challenge, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-sasl">>;
    -get_ns({sasl_response, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-sasl">>;
    -get_ns({sasl_success, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-sasl">>;
    -get_ns({sasl_failure, _, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-sasl">>;
    -get_ns({sasl_mechanisms, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-sasl">>;
    -get_ns({starttls, _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-tls">>;
    -get_ns({starttls_proceed}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-tls">>;
    -get_ns({starttls_failure}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-tls">>;
    -get_ns({compress_failure, _}) ->
    -    <<"http://jabber.org/protocol/compress">>;
    -get_ns({compress, _}) ->
    -    <<"http://jabber.org/protocol/compress">>;
    -get_ns({compressed}) ->
    -    <<"http://jabber.org/protocol/compress">>;
    -get_ns({compression, _}) ->
    -    <<"http://jabber.org/features/compress">>;
    -get_ns({stream_features, _}) ->
    -    <<"http://etherx.jabber.org/streams">>;
    -get_ns({p1_push}) -> <<"p1:push">>;
    -get_ns({p1_rebind}) -> <<"p1:rebind">>;
    -get_ns({p1_ack}) -> <<"p1:ack">>;
    -get_ns({caps, _, _, _}) ->
    -    <<"http://jabber.org/protocol/caps">>;
    -get_ns({feature_register}) ->
    -    <<"http://jabber.org/features/iq-register">>;
    -get_ns({register, _, _, _, _, _, _, _, _, _, _, _, _, _,
    -	_, _, _, _, _, _, _, _}) ->
    -    <<"jabber:iq:register">>;
    -get_ns({session}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-session">>;
    -get_ns({ping}) -> <<"urn:xmpp:ping">>;
    -get_ns({time, _, _}) -> <<"urn:xmpp:time">>;
    -get_ns({'see-other-host', _}) ->
    -    <<"urn:ietf:params:xml:ns:xmpp-streams">>;
    -get_ns({stream_error, _, _}) ->
    -    <<"http://etherx.jabber.org/streams">>;
    -get_ns({vcard_name, _, _, _, _, _}) -> <<"vcard-temp">>;
    -get_ns({vcard_adr, _, _, _, _, _, _, _, _, _, _, _, _,
    -	_, _}) ->
    -    <<"vcard-temp">>;
    -get_ns({vcard_label, _, _, _, _, _, _, _, _}) ->
    -    <<"vcard-temp">>;
    -get_ns({vcard_tel, _, _, _, _, _, _, _, _, _, _, _, _,
    -	_, _}) ->
    -    <<"vcard-temp">>;
    -get_ns({vcard_email, _, _, _, _, _, _}) ->
    -    <<"vcard-temp">>;
    -get_ns({vcard_geo, _, _}) -> <<"vcard-temp">>;
    -get_ns({vcard_logo, _, _, _}) -> <<"vcard-temp">>;
    -get_ns({vcard_photo, _, _, _}) -> <<"vcard-temp">>;
    -get_ns({vcard_org, _, _}) -> <<"vcard-temp">>;
    -get_ns({vcard_sound, _, _, _}) -> <<"vcard-temp">>;
    -get_ns({vcard_key, _, _}) -> <<"vcard-temp">>;
    -get_ns({vcard, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
    -	_, _, _, _, _, _, _, _, _, _, _, _, _, _, _}) ->
    -    <<"vcard-temp">>;
    -get_ns({vcard_xupdate, _}) -> <<"vcard-temp:x:update">>;
    -get_ns({xdata_field, _, _, _, _, _, _, _}) ->
    -    <<"jabber:x:data">>;
    -get_ns({xdata, _, _, _, _, _, _}) ->
    -    <<"jabber:x:data">>;
    -get_ns({pubsub_subscription, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({pubsub_affiliation, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({pubsub_item, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({pubsub_items, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({pubsub_event_item, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub#event">>;
    -get_ns({pubsub_event_items, _, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub#event">>;
    -get_ns({pubsub_event, _}) ->
    -    <<"http://jabber.org/protocol/pubsub#event">>;
    -get_ns({pubsub_subscribe, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({pubsub_unsubscribe, _, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({pubsub_publish, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({pubsub_options, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({pubsub_retract, _, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({pubsub, _, _, _, _, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/pubsub">>;
    -get_ns({shim, _}) ->
    -    <<"http://jabber.org/protocol/shim">>;
    -get_ns({chatstate, active}) ->
    -    <<"http://jabber.org/protocol/chatstates">>;
    -get_ns({chatstate, composing}) ->
    -    <<"http://jabber.org/protocol/chatstates">>;
    -get_ns({chatstate, gone}) ->
    -    <<"http://jabber.org/protocol/chatstates">>;
    -get_ns({chatstate, inactive}) ->
    -    <<"http://jabber.org/protocol/chatstates">>;
    -get_ns({chatstate, paused}) ->
    -    <<"http://jabber.org/protocol/chatstates">>;
    -get_ns({delay, _, _}) -> <<"urn:xmpp:delay">>;
    -get_ns({streamhost, _, _, _}) ->
    -    <<"http://jabber.org/protocol/bytestreams">>;
    -get_ns({bytestreams, _, _, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/bytestreams">>;
    -get_ns({muc_history, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/muc">>;
    -get_ns({muc_decline, _, _, _}) ->
    -    <<"http://jabber.org/protocol/muc#user">>;
    -get_ns({muc_user_destroy, _, _}) ->
    -    <<"http://jabber.org/protocol/muc#user">>;
    -get_ns({muc_invite, _, _, _}) ->
    -    <<"http://jabber.org/protocol/muc#user">>;
    -get_ns({muc_user, _, _, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/muc#user">>;
    -get_ns({muc_owner_destroy, _, _, _}) ->
    -    <<"http://jabber.org/protocol/muc#owner">>;
    -get_ns({muc_owner, _, _}) ->
    -    <<"http://jabber.org/protocol/muc#owner">>;
    -get_ns({muc_admin, _}) ->
    -    <<"http://jabber.org/protocol/muc#admin">>;
    -get_ns({muc, _, _}) ->
    -    <<"http://jabber.org/protocol/muc">>;
    -get_ns({rsm_first, _, _}) ->
    -    <<"http://jabber.org/protocol/rsm">>;
    -get_ns({rsm_set, _, _, _, _, _, _, _}) ->
    -    <<"http://jabber.org/protocol/rsm">>;
    -get_ns({mam_archived, _, _}) -> <<"urn:xmpp:mam:tmp">>;
    -get_ns({mam_fin, _, _, _, _}) -> <<"urn:xmpp:mam:0">>;
    -get_ns({forwarded, _, _}) -> <<"urn:xmpp:forward:0">>;
    -get_ns({carbons_disable}) -> <<"urn:xmpp:carbons:2">>;
    -get_ns({carbons_enable}) -> <<"urn:xmpp:carbons:2">>;
    -get_ns({carbons_private}) -> <<"urn:xmpp:carbons:2">>;
    -get_ns({carbons_received, _}) ->
    -    <<"urn:xmpp:carbons:2">>;
    -get_ns({carbons_sent, _}) -> <<"urn:xmpp:carbons:2">>;
    -get_ns({feature_csi, _}) -> <<"urn:xmpp:csi:0">>;
    -get_ns({csi, active}) -> <<"urn:xmpp:csi:0">>;
    -get_ns({csi, inactive}) -> <<"urn:xmpp:csi:0">>;
    -get_ns({offline_item, _, _}) ->
    -    <<"http://jabber.org/protocol/offline">>;
    -get_ns({offline, _, _, _}) ->
    -    <<"http://jabber.org/protocol/offline">>;
    -get_ns({mix_join, _, _}) -> <<"urn:xmpp:mix:0">>;
    -get_ns({mix_leave}) -> <<"urn:xmpp:mix:0">>;
    -get_ns({mix_participant, _, _}) -> <<"urn:xmpp:mix:0">>;
    -get_ns(_) -> <<>>.
    -
    -dec_int(Val) -> dec_int(Val, infinity, infinity).
    -
    -dec_int(Val, Min, Max) ->
    -    case list_to_integer(binary_to_list(Val)) of
    -      Int when Int =< Max, Min == infinity -> Int;
    -      Int when Int =< Max, Int >= Min -> Int
    -    end.
    -
    -enc_int(Int) -> list_to_binary(integer_to_list(Int)).
    -
    -dec_enum(Val, Enums) ->
    -    AtomVal = erlang:binary_to_existing_atom(Val, utf8),
    -    case lists:member(AtomVal, Enums) of
    -      true -> AtomVal
    -    end.
    -
    -enc_enum(Atom) -> erlang:atom_to_binary(Atom, utf8).
    -
    -format_error({bad_attr_value, Attr, Tag, XMLNS}) ->
    -    <<"Bad value of attribute '", Attr/binary, "' in tag <",
    -      Tag/binary, "/> qualified by namespace '", XMLNS/binary,
    -      "'">>;
    -format_error({bad_cdata_value, <<>>, Tag, XMLNS}) ->
    -    <<"Bad value of cdata in tag <", Tag/binary,
    -      "/> qualified by namespace '", XMLNS/binary, "'">>;
    -format_error({missing_tag, Tag, XMLNS}) ->
    -    <<"Missing tag <", Tag/binary,
    -      "/> qualified by namespace '", XMLNS/binary, "'">>;
    -format_error({missing_attr, Attr, Tag, XMLNS}) ->
    -    <<"Missing attribute '", Attr/binary, "' in tag <",
    -      Tag/binary, "/> qualified by namespace '", XMLNS/binary,
    -      "'">>;
    -format_error({missing_cdata, <<>>, Tag, XMLNS}) ->
    -    <<"Missing cdata in tag <", Tag/binary,
    -      "/> qualified by namespace '", XMLNS/binary, "'">>;
    -format_error({unknown_tag, Tag, XMLNS}) ->
    -    <<"Unknown tag <", Tag/binary,
    -      "/> qualified by namespace '", XMLNS/binary, "'">>.
    -
    -get_attr(Attr, Attrs) ->
    -    case lists:keyfind(Attr, 1, Attrs) of
    -      {_, Val} -> Val;
    -      false -> <<>>
    -    end.
    -
    -pp(Term) -> io_lib_pretty:print(Term, fun pp/2).
    -
    -pp(last, 2) -> [seconds, text];
    -pp(version, 3) -> [name, ver, os];
    -pp(roster_item, 5) ->
    -    [jid, name, groups, subscription, ask];
    -pp(roster, 2) -> [items, ver];
    -pp(privacy_item, 5) ->
    -    [order, action, type, value, kinds];
    -pp(privacy_list, 2) -> [name, items];
    -pp(privacy, 3) -> [lists, default, active];
    -pp(block, 1) -> [items];
    -pp(unblock, 1) -> [items];
    -pp(block_list, 0) -> [];
    -pp(identity, 4) -> [category, type, lang, name];
    -pp(disco_info, 4) ->
    -    [node, identities, features, xdata];
    -pp(disco_item, 3) -> [jid, name, node];
    -pp(disco_items, 2) -> [node, items];
    -pp(private, 1) -> [xml_els];
    -pp(bookmark_conference, 5) ->
    -    [name, jid, autojoin, nick, password];
    -pp(bookmark_url, 2) -> [name, url];
    -pp(bookmark_storage, 2) -> [conference, url];
    -pp(stat, 4) -> [name, units, value, error];
    -pp(stats, 1) -> [stat];
    -pp(iq, 7) -> [id, type, lang, from, to, error, sub_els];
    -pp(message, 10) ->
    -    [id, type, lang, from, to, subject, body, thread, error,
    -     sub_els];
    -pp(presence, 10) ->
    -    [id, type, lang, from, to, show, status, priority,
    -     error, sub_els];
    -pp(gone, 1) -> [uri];
    -pp(redirect, 1) -> [uri];
    -pp(error, 4) -> [type, by, reason, text];
    -pp(bind, 2) -> [jid, resource];
    -pp(sasl_auth, 2) -> [mechanism, text];
    -pp(sasl_abort, 0) -> [];
    -pp(sasl_challenge, 1) -> [text];
    -pp(sasl_response, 1) -> [text];
    -pp(sasl_success, 1) -> [text];
    -pp(sasl_failure, 2) -> [reason, text];
    -pp(sasl_mechanisms, 1) -> [list];
    -pp(starttls, 1) -> [required];
    -pp(starttls_proceed, 0) -> [];
    -pp(starttls_failure, 0) -> [];
    -pp(compress_failure, 1) -> [reason];
    -pp(compress, 1) -> [methods];
    -pp(compressed, 0) -> [];
    -pp(compression, 1) -> [methods];
    -pp(stream_features, 1) -> [sub_els];
    -pp(p1_push, 0) -> [];
    -pp(p1_rebind, 0) -> [];
    -pp(p1_ack, 0) -> [];
    -pp(caps, 3) -> [hash, node, ver];
    -pp(feature_register, 0) -> [];
    -pp(register, 21) ->
    -    [registered, remove, instructions, username, nick,
    -     password, name, first, last, email, address, city,
    -     state, zip, phone, url, date, misc, text, key, xdata];
    -pp(session, 0) -> [];
    -pp(ping, 0) -> [];
    -pp(time, 2) -> [tzo, utc];
    -pp(text, 2) -> [lang, data];
    -pp('see-other-host', 1) -> [host];
    -pp(stream_error, 2) -> [reason, text];
    -pp(vcard_name, 5) ->
    -    [family, given, middle, prefix, suffix];
    -pp(vcard_adr, 14) ->
    -    [home, work, postal, parcel, dom, intl, pref, pobox,
    -     extadd, street, locality, region, pcode, ctry];
    -pp(vcard_label, 8) ->
    -    [home, work, postal, parcel, dom, intl, pref, line];
    -pp(vcard_tel, 14) ->
    -    [home, work, voice, fax, pager, msg, cell, video, bbs,
    -     modem, isdn, pcs, pref, number];
    -pp(vcard_email, 6) ->
    -    [home, work, internet, pref, x400, userid];
    -pp(vcard_geo, 2) -> [lat, lon];
    -pp(vcard_logo, 3) -> [type, binval, extval];
    -pp(vcard_photo, 3) -> [type, binval, extval];
    -pp(vcard_org, 2) -> [name, units];
    -pp(vcard_sound, 3) -> [phonetic, binval, extval];
    -pp(vcard_key, 2) -> [type, cred];
    -pp(vcard, 29) ->
    -    [version, fn, n, nickname, photo, bday, adr, label, tel,
    -     email, jabberid, mailer, tz, geo, title, role, logo,
    -     org, categories, note, prodid, rev, sort_string, sound,
    -     uid, url, class, key, desc];
    -pp(vcard_xupdate, 1) -> [photo];
    -pp(xdata_field, 7) ->
    -    [label, type, var, required, desc, values, options];
    -pp(xdata, 6) ->
    -    [type, instructions, title, reported, items, fields];
    -pp(pubsub_subscription, 4) -> [jid, node, subid, type];
    -pp(pubsub_affiliation, 2) -> [node, type];
    -pp(pubsub_item, 2) -> [id, xml_els];
    -pp(pubsub_items, 4) -> [node, max_items, subid, items];
    -pp(pubsub_event_item, 4) ->
    -    [id, node, publisher, xml_els];
    -pp(pubsub_event_items, 3) -> [node, retract, items];
    -pp(pubsub_event, 1) -> [items];
    -pp(pubsub_subscribe, 2) -> [node, jid];
    -pp(pubsub_unsubscribe, 3) -> [node, jid, subid];
    -pp(pubsub_publish, 2) -> [node, items];
    -pp(pubsub_options, 4) -> [node, jid, subid, xdata];
    -pp(pubsub_retract, 3) -> [node, notify, items];
    -pp(pubsub, 8) ->
    -    [subscriptions, affiliations, publish, subscribe,
    -     unsubscribe, options, items, retract];
    -pp(shim, 1) -> [headers];
    -pp(chatstate, 1) -> [type];
    -pp(delay, 2) -> [stamp, from];
    -pp(streamhost, 3) -> [jid, host, port];
    -pp(bytestreams, 6) ->
    -    [hosts, used, activate, dstaddr, mode, sid];
    -pp(muc_history, 4) ->
    -    [maxchars, maxstanzas, seconds, since];
    -pp(muc_decline, 3) -> [reason, from, to];
    -pp(muc_user_destroy, 2) -> [reason, jid];
    -pp(muc_invite, 3) -> [reason, from, to];
    -pp(muc_user, 6) ->
    -    [decline, destroy, invites, items, status_codes,
    -     password];
    -pp(muc_owner_destroy, 3) -> [jid, reason, password];
    -pp(muc_owner, 2) -> [destroy, config];
    -pp(muc_item, 7) ->
    -    [actor, continue, reason, affiliation, role, jid, nick];
    -pp(muc_actor, 2) -> [jid, nick];
    -pp(muc_admin, 1) -> [items];
    -pp(muc, 2) -> [history, password];
    -pp(rsm_first, 2) -> [index, data];
    -pp(rsm_set, 7) ->
    -    ['after', before, count, first, index, last, max];
    -pp(mam_query, 7) ->
    -    [xmlns, id, start, 'end', with, rsm, xdata];
    -pp(mam_archived, 2) -> [by, id];
    -pp(mam_result, 4) -> [xmlns, queryid, id, sub_els];
    -pp(mam_prefs, 4) -> [xmlns, default, always, never];
    -pp(mam_fin, 4) -> [id, rsm, stable, complete];
    -pp(forwarded, 2) -> [delay, sub_els];
    -pp(carbons_disable, 0) -> [];
    -pp(carbons_enable, 0) -> [];
    -pp(carbons_private, 0) -> [];
    -pp(carbons_received, 1) -> [forwarded];
    -pp(carbons_sent, 1) -> [forwarded];
    -pp(feature_csi, 1) -> [xmlns];
    -pp(csi, 1) -> [type];
    -pp(feature_sm, 1) -> [xmlns];
    -pp(sm_enable, 3) -> [max, resume, xmlns];
    -pp(sm_enabled, 5) -> [id, location, max, resume, xmlns];
    -pp(sm_resume, 3) -> [h, previd, xmlns];
    -pp(sm_resumed, 3) -> [h, previd, xmlns];
    -pp(sm_r, 1) -> [xmlns];
    -pp(sm_a, 2) -> [h, xmlns];
    -pp(sm_failed, 3) -> [reason, h, xmlns];
    -pp(offline_item, 2) -> [node, action];
    -pp(offline, 3) -> [items, purge, fetch];
    -pp(mix_join, 2) -> [jid, subscribe];
    -pp(mix_leave, 0) -> [];
    -pp(mix_participant, 2) -> [jid, nick];
    -pp(_, _) -> no.
    -
    -enc_bool(false) -> <<"false">>;
    -enc_bool(true) -> <<"true">>.
    -
    -dec_bool(<<"false">>) -> false;
    -dec_bool(<<"0">>) -> false;
    -dec_bool(<<"true">>) -> true;
    -dec_bool(<<"1">>) -> true.
    -
    -resourceprep(R) ->
    -    case jid:resourceprep(R) of
    -      error -> erlang:error(badarg);
    -      R1 -> R1
    -    end.
    -
    -enc_jid(J) -> jid:to_string(J).
    -
    -dec_jid(Val) ->
    -    case jid:from_string(Val) of
    -      error -> erlang:error(badarg);
    -      J -> J
    -    end.
    -
    -enc_utc(Val) -> jlib:now_to_utc_string(Val).
    -
    -dec_utc(Val) ->
    -    {_, _, _} = jlib:datetime_string_to_timestamp(Val).
    -
    -enc_tzo({H, M}) ->
    -    Sign = if H >= 0 -> <<>>;
    -	      true -> <<"-">>
    -	   end,
    -    list_to_binary([Sign,
    -		    io_lib:format("~2..0w:~2..0w", [H, M])]).
    -
    -dec_tzo(Val) ->
    -    [H1, M1] = str:tokens(Val, <<":">>),
    -    H = jlib:binary_to_integer(H1),
    -    M = jlib:binary_to_integer(M1),
    -    if H >= -12, H =< 12, M >= 0, M < 60 -> {H, M} end.
    -
    -decode_mix_participant(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"participant">>, _attrs, _els}) ->
    -    {Jid, Nick} = decode_mix_participant_attrs(__TopXMLNS,
    -					       _attrs, undefined, undefined),
    -    {mix_participant, Jid, Nick}.
    -
    -decode_mix_participant_attrs(__TopXMLNS,
    -			     [{<<"jid">>, _val} | _attrs], _Jid, Nick) ->
    -    decode_mix_participant_attrs(__TopXMLNS, _attrs, _val,
    -				 Nick);
    -decode_mix_participant_attrs(__TopXMLNS,
    -			     [{<<"nick">>, _val} | _attrs], Jid, _Nick) ->
    -    decode_mix_participant_attrs(__TopXMLNS, _attrs, Jid,
    -				 _val);
    -decode_mix_participant_attrs(__TopXMLNS, [_ | _attrs],
    -			     Jid, Nick) ->
    -    decode_mix_participant_attrs(__TopXMLNS, _attrs, Jid,
    -				 Nick);
    -decode_mix_participant_attrs(__TopXMLNS, [], Jid,
    -			     Nick) ->
    -    {decode_mix_participant_attr_jid(__TopXMLNS, Jid),
    -     decode_mix_participant_attr_nick(__TopXMLNS, Nick)}.
    -
    -encode_mix_participant({mix_participant, Jid, Nick},
    -		       _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_mix_participant_attr_nick(Nick,
    -					      encode_mix_participant_attr_jid(Jid,
    -									      _xmlns_attrs)),
    -    {xmlel, <<"participant">>, _attrs, _els}.
    -
    -decode_mix_participant_attr_jid(__TopXMLNS,
    -				undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"participant">>,
    -		   __TopXMLNS}});
    -decode_mix_participant_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"participant">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mix_participant_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_mix_participant_attr_nick(__TopXMLNS,
    -				 undefined) ->
    -    undefined;
    -decode_mix_participant_attr_nick(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_mix_participant_attr_nick(undefined, _acc) ->
    -    _acc;
    -encode_mix_participant_attr_nick(_val, _acc) ->
    -    [{<<"nick">>, _val} | _acc].
    -
    -decode_mix_leave(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"leave">>, _attrs, _els}) ->
    -    {mix_leave}.
    -
    -encode_mix_leave({mix_leave}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"leave">>, _attrs, _els}.
    -
    -decode_mix_join(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"join">>, _attrs, _els}) ->
    -    Subscribe = decode_mix_join_els(__TopXMLNS, __IgnoreEls,
    -				    _els, []),
    -    Jid = decode_mix_join_attrs(__TopXMLNS, _attrs,
    -				undefined),
    -    {mix_join, Jid, Subscribe}.
    -
    -decode_mix_join_els(__TopXMLNS, __IgnoreEls, [],
    -		    Subscribe) ->
    -    lists:reverse(Subscribe);
    -decode_mix_join_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"subscribe">>, _attrs, _} = _el | _els],
    -		    Subscribe) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_mix_join_els(__TopXMLNS, __IgnoreEls, _els,
    -			       [decode_mix_subscribe(__TopXMLNS, __IgnoreEls,
    -						     _el)
    -				| Subscribe]);
    -       true ->
    -	   decode_mix_join_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Subscribe)
    -    end;
    -decode_mix_join_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Subscribe) ->
    -    decode_mix_join_els(__TopXMLNS, __IgnoreEls, _els,
    -			Subscribe).
    -
    -decode_mix_join_attrs(__TopXMLNS,
    -		      [{<<"jid">>, _val} | _attrs], _Jid) ->
    -    decode_mix_join_attrs(__TopXMLNS, _attrs, _val);
    -decode_mix_join_attrs(__TopXMLNS, [_ | _attrs], Jid) ->
    -    decode_mix_join_attrs(__TopXMLNS, _attrs, Jid);
    -decode_mix_join_attrs(__TopXMLNS, [], Jid) ->
    -    decode_mix_join_attr_jid(__TopXMLNS, Jid).
    -
    -encode_mix_join({mix_join, Jid, Subscribe},
    -		_xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_mix_join_$subscribe'(Subscribe,
    -						   [])),
    -    _attrs = encode_mix_join_attr_jid(Jid, _xmlns_attrs),
    -    {xmlel, <<"join">>, _attrs, _els}.
    -
    -'encode_mix_join_$subscribe'([], _acc) -> _acc;
    -'encode_mix_join_$subscribe'([Subscribe | _els],
    -			     _acc) ->
    -    'encode_mix_join_$subscribe'(_els,
    -				 [encode_mix_subscribe(Subscribe, []) | _acc]).
    -
    -decode_mix_join_attr_jid(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mix_join_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"join">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mix_join_attr_jid(undefined, _acc) -> _acc;
    -encode_mix_join_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_mix_subscribe(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"subscribe">>, _attrs, _els}) ->
    -    Node = decode_mix_subscribe_attrs(__TopXMLNS, _attrs,
    -				      undefined),
    -    Node.
    -
    -decode_mix_subscribe_attrs(__TopXMLNS,
    -			   [{<<"node">>, _val} | _attrs], _Node) ->
    -    decode_mix_subscribe_attrs(__TopXMLNS, _attrs, _val);
    -decode_mix_subscribe_attrs(__TopXMLNS, [_ | _attrs],
    -			   Node) ->
    -    decode_mix_subscribe_attrs(__TopXMLNS, _attrs, Node);
    -decode_mix_subscribe_attrs(__TopXMLNS, [], Node) ->
    -    decode_mix_subscribe_attr_node(__TopXMLNS, Node).
    -
    -encode_mix_subscribe(Node, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_mix_subscribe_attr_node(Node,
    -					    _xmlns_attrs),
    -    {xmlel, <<"subscribe">>, _attrs, _els}.
    -
    -decode_mix_subscribe_attr_node(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"node">>, <<"subscribe">>,
    -		   __TopXMLNS}});
    -decode_mix_subscribe_attr_node(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_mix_subscribe_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_offline(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"offline">>, _attrs, _els}) ->
    -    {Items, Purge, Fetch} = decode_offline_els(__TopXMLNS,
    -					       __IgnoreEls, _els, [], false,
    -					       false),
    -    {offline, Items, Purge, Fetch}.
    -
    -decode_offline_els(__TopXMLNS, __IgnoreEls, [], Items,
    -		   Purge, Fetch) ->
    -    {lists:reverse(Items), Purge, Fetch};
    -decode_offline_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"purge">>, _attrs, _} = _el | _els], Items,
    -		   Purge, Fetch) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			      decode_offline_purge(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -			      Fetch);
    -       true ->
    -	   decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			      Purge, Fetch)
    -    end;
    -decode_offline_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"fetch">>, _attrs, _} = _el | _els], Items,
    -		   Purge, Fetch) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			      Purge,
    -			      decode_offline_fetch(__TopXMLNS, __IgnoreEls,
    -						   _el));
    -       true ->
    -	   decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			      Purge, Fetch)
    -    end;
    -decode_offline_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items,
    -		   Purge, Fetch) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_offline_els(__TopXMLNS, __IgnoreEls, _els,
    -			      [decode_offline_item(__TopXMLNS, __IgnoreEls, _el)
    -			       | Items],
    -			      Purge, Fetch);
    -       true ->
    -	   decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			      Purge, Fetch)
    -    end;
    -decode_offline_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Items, Purge, Fetch) ->
    -    decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -		       Purge, Fetch).
    -
    -encode_offline({offline, Items, Purge, Fetch},
    -	       _xmlns_attrs) ->
    -    _els = lists:reverse('encode_offline_$items'(Items,
    -						 'encode_offline_$purge'(Purge,
    -									 'encode_offline_$fetch'(Fetch,
    -												 [])))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"offline">>, _attrs, _els}.
    -
    -'encode_offline_$items'([], _acc) -> _acc;
    -'encode_offline_$items'([Items | _els], _acc) ->
    -    'encode_offline_$items'(_els,
    -			    [encode_offline_item(Items, []) | _acc]).
    -
    -'encode_offline_$purge'(false, _acc) -> _acc;
    -'encode_offline_$purge'(Purge, _acc) ->
    -    [encode_offline_purge(Purge, []) | _acc].
    -
    -'encode_offline_$fetch'(false, _acc) -> _acc;
    -'encode_offline_$fetch'(Fetch, _acc) ->
    -    [encode_offline_fetch(Fetch, []) | _acc].
    -
    -decode_offline_item(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"item">>, _attrs, _els}) ->
    -    {Node, Action} = decode_offline_item_attrs(__TopXMLNS,
    -					       _attrs, undefined, undefined),
    -    {offline_item, Node, Action}.
    -
    -decode_offline_item_attrs(__TopXMLNS,
    -			  [{<<"node">>, _val} | _attrs], _Node, Action) ->
    -    decode_offline_item_attrs(__TopXMLNS, _attrs, _val,
    -			      Action);
    -decode_offline_item_attrs(__TopXMLNS,
    -			  [{<<"action">>, _val} | _attrs], Node, _Action) ->
    -    decode_offline_item_attrs(__TopXMLNS, _attrs, Node,
    -			      _val);
    -decode_offline_item_attrs(__TopXMLNS, [_ | _attrs],
    -			  Node, Action) ->
    -    decode_offline_item_attrs(__TopXMLNS, _attrs, Node,
    -			      Action);
    -decode_offline_item_attrs(__TopXMLNS, [], Node,
    -			  Action) ->
    -    {decode_offline_item_attr_node(__TopXMLNS, Node),
    -     decode_offline_item_attr_action(__TopXMLNS, Action)}.
    -
    -encode_offline_item({offline_item, Node, Action},
    -		    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_offline_item_attr_action(Action,
    -					     encode_offline_item_attr_node(Node,
    -									   _xmlns_attrs)),
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -decode_offline_item_attr_node(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_offline_item_attr_node(__TopXMLNS, _val) -> _val.
    -
    -encode_offline_item_attr_node(undefined, _acc) -> _acc;
    -encode_offline_item_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_offline_item_attr_action(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_offline_item_attr_action(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val, [view, remove]) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"action">>, <<"item">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_offline_item_attr_action(undefined, _acc) ->
    -    _acc;
    -encode_offline_item_attr_action(_val, _acc) ->
    -    [{<<"action">>, enc_enum(_val)} | _acc].
    -
    -decode_offline_fetch(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"fetch">>, _attrs, _els}) ->
    -    true.
    -
    -encode_offline_fetch(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"fetch">>, _attrs, _els}.
    -
    -decode_offline_purge(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"purge">>, _attrs, _els}) ->
    -    true.
    -
    -encode_offline_purge(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"purge">>, _attrs, _els}.
    -
    -decode_sm_failed(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"failed">>, _attrs, _els}) ->
    -    Reason = decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -				  _els, undefined),
    -    {H, Xmlns} = decode_sm_failed_attrs(__TopXMLNS, _attrs,
    -					undefined, undefined),
    -    {sm_failed, Reason, H, Xmlns}.
    -
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls, [],
    -		     Reason) ->
    -    Reason;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"bad-request">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_bad_request(_xmlns, __IgnoreEls,
    -							 _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"conflict">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_conflict(_xmlns, __IgnoreEls,
    -						      _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"feature-not-implemented">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_feature_not_implemented(_xmlns,
    -								     __IgnoreEls,
    -								     _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"forbidden">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_forbidden(_xmlns, __IgnoreEls,
    -						       _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"gone">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_gone(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"internal-server-error">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_internal_server_error(_xmlns,
    -								   __IgnoreEls,
    -								   _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"item-not-found">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_item_not_found(_xmlns, __IgnoreEls,
    -							    _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"jid-malformed">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_jid_malformed(_xmlns, __IgnoreEls,
    -							   _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"not-acceptable">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_not_acceptable(_xmlns, __IgnoreEls,
    -							    _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"not-allowed">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_not_allowed(_xmlns, __IgnoreEls,
    -							 _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"not-authorized">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_not_authorized(_xmlns, __IgnoreEls,
    -							    _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"policy-violation">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_policy_violation(_xmlns,
    -							      __IgnoreEls,
    -							      _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"recipient-unavailable">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_recipient_unavailable(_xmlns,
    -								   __IgnoreEls,
    -								   _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"redirect">>, _attrs, _} = _el | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_redirect(_xmlns, __IgnoreEls,
    -						      _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"registration-required">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_registration_required(_xmlns,
    -								   __IgnoreEls,
    -								   _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"remote-server-not-found">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_remote_server_not_found(_xmlns,
    -								     __IgnoreEls,
    -								     _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"remote-server-timeout">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_remote_server_timeout(_xmlns,
    -								   __IgnoreEls,
    -								   _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"resource-constraint">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_resource_constraint(_xmlns,
    -								 __IgnoreEls,
    -								 _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"service-unavailable">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_service_unavailable(_xmlns,
    -								 __IgnoreEls,
    -								 _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"subscription-required">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_subscription_required(_xmlns,
    -								   __IgnoreEls,
    -								   _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"undefined-condition">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_undefined_condition(_xmlns,
    -								 __IgnoreEls,
    -								 _el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"unexpected-request">>, _attrs, _} = _el
    -		      | _els],
    -		     Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_error_unexpected_request(_xmlns,
    -								__IgnoreEls,
    -								_el));
    -       true ->
    -	   decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -				Reason)
    -    end;
    -decode_sm_failed_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Reason) ->
    -    decode_sm_failed_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Reason).
    -
    -decode_sm_failed_attrs(__TopXMLNS,
    -		       [{<<"h">>, _val} | _attrs], _H, Xmlns) ->
    -    decode_sm_failed_attrs(__TopXMLNS, _attrs, _val, Xmlns);
    -decode_sm_failed_attrs(__TopXMLNS,
    -		       [{<<"xmlns">>, _val} | _attrs], H, _Xmlns) ->
    -    decode_sm_failed_attrs(__TopXMLNS, _attrs, H, _val);
    -decode_sm_failed_attrs(__TopXMLNS, [_ | _attrs], H,
    -		       Xmlns) ->
    -    decode_sm_failed_attrs(__TopXMLNS, _attrs, H, Xmlns);
    -decode_sm_failed_attrs(__TopXMLNS, [], H, Xmlns) ->
    -    {decode_sm_failed_attr_h(__TopXMLNS, H),
    -     decode_sm_failed_attr_xmlns(__TopXMLNS, Xmlns)}.
    -
    -encode_sm_failed({sm_failed, Reason, H, Xmlns},
    -		 _xmlns_attrs) ->
    -    _els = lists:reverse('encode_sm_failed_$reason'(Reason,
    -						    [])),
    -    _attrs = encode_sm_failed_attr_xmlns(Xmlns,
    -					 encode_sm_failed_attr_h(H,
    -								 _xmlns_attrs)),
    -    {xmlel, <<"failed">>, _attrs, _els}.
    -
    -'encode_sm_failed_$reason'(undefined, _acc) -> _acc;
    -'encode_sm_failed_$reason'('bad-request' = Reason,
    -			   _acc) ->
    -    [encode_error_bad_request(Reason,
    -			      [{<<"xmlns">>,
    -				<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'(conflict = Reason, _acc) ->
    -    [encode_error_conflict(Reason,
    -			   [{<<"xmlns">>,
    -			     <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('feature-not-implemented' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_feature_not_implemented(Reason,
    -					  [{<<"xmlns">>,
    -					    <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'(forbidden = Reason, _acc) ->
    -    [encode_error_forbidden(Reason,
    -			    [{<<"xmlns">>,
    -			      <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'({gone, _} = Reason, _acc) ->
    -    [encode_error_gone(Reason,
    -		       [{<<"xmlns">>,
    -			 <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('internal-server-error' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_internal_server_error(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('item-not-found' = Reason,
    -			   _acc) ->
    -    [encode_error_item_not_found(Reason,
    -				 [{<<"xmlns">>,
    -				   <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('jid-malformed' = Reason,
    -			   _acc) ->
    -    [encode_error_jid_malformed(Reason,
    -				[{<<"xmlns">>,
    -				  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('not-acceptable' = Reason,
    -			   _acc) ->
    -    [encode_error_not_acceptable(Reason,
    -				 [{<<"xmlns">>,
    -				   <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('not-allowed' = Reason,
    -			   _acc) ->
    -    [encode_error_not_allowed(Reason,
    -			      [{<<"xmlns">>,
    -				<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('not-authorized' = Reason,
    -			   _acc) ->
    -    [encode_error_not_authorized(Reason,
    -				 [{<<"xmlns">>,
    -				   <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('policy-violation' = Reason,
    -			   _acc) ->
    -    [encode_error_policy_violation(Reason,
    -				   [{<<"xmlns">>,
    -				     <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('recipient-unavailable' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_recipient_unavailable(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'({redirect, _} = Reason,
    -			   _acc) ->
    -    [encode_error_redirect(Reason,
    -			   [{<<"xmlns">>,
    -			     <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('registration-required' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_registration_required(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('remote-server-not-found' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_remote_server_not_found(Reason,
    -					  [{<<"xmlns">>,
    -					    <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('remote-server-timeout' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_remote_server_timeout(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('resource-constraint' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_resource_constraint(Reason,
    -				      [{<<"xmlns">>,
    -					<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('service-unavailable' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_service_unavailable(Reason,
    -				      [{<<"xmlns">>,
    -					<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('subscription-required' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_subscription_required(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('undefined-condition' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_undefined_condition(Reason,
    -				      [{<<"xmlns">>,
    -					<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_sm_failed_$reason'('unexpected-request' =
    -			       Reason,
    -			   _acc) ->
    -    [encode_error_unexpected_request(Reason,
    -				     [{<<"xmlns">>,
    -				       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc].
    -
    -decode_sm_failed_attr_h(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_failed_attr_h(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"h">>, <<"failed">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sm_failed_attr_h(undefined, _acc) -> _acc;
    -encode_sm_failed_attr_h(_val, _acc) ->
    -    [{<<"h">>, enc_int(_val)} | _acc].
    -
    -decode_sm_failed_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_failed_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_failed_attr_xmlns(undefined, _acc) -> _acc;
    -encode_sm_failed_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_sm_a(__TopXMLNS, __IgnoreEls,
    -	    {xmlel, <<"a">>, _attrs, _els}) ->
    -    {H, Xmlns} = decode_sm_a_attrs(__TopXMLNS, _attrs,
    -				   undefined, undefined),
    -    {sm_a, H, Xmlns}.
    -
    -decode_sm_a_attrs(__TopXMLNS,
    -		  [{<<"h">>, _val} | _attrs], _H, Xmlns) ->
    -    decode_sm_a_attrs(__TopXMLNS, _attrs, _val, Xmlns);
    -decode_sm_a_attrs(__TopXMLNS,
    -		  [{<<"xmlns">>, _val} | _attrs], H, _Xmlns) ->
    -    decode_sm_a_attrs(__TopXMLNS, _attrs, H, _val);
    -decode_sm_a_attrs(__TopXMLNS, [_ | _attrs], H, Xmlns) ->
    -    decode_sm_a_attrs(__TopXMLNS, _attrs, H, Xmlns);
    -decode_sm_a_attrs(__TopXMLNS, [], H, Xmlns) ->
    -    {decode_sm_a_attr_h(__TopXMLNS, H),
    -     decode_sm_a_attr_xmlns(__TopXMLNS, Xmlns)}.
    -
    -encode_sm_a({sm_a, H, Xmlns}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_sm_a_attr_xmlns(Xmlns,
    -				    encode_sm_a_attr_h(H, _xmlns_attrs)),
    -    {xmlel, <<"a">>, _attrs, _els}.
    -
    -decode_sm_a_attr_h(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"h">>, <<"a">>, __TopXMLNS}});
    -decode_sm_a_attr_h(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"h">>, <<"a">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sm_a_attr_h(_val, _acc) ->
    -    [{<<"h">>, enc_int(_val)} | _acc].
    -
    -decode_sm_a_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_a_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_a_attr_xmlns(undefined, _acc) -> _acc;
    -encode_sm_a_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_sm_r(__TopXMLNS, __IgnoreEls,
    -	    {xmlel, <<"r">>, _attrs, _els}) ->
    -    Xmlns = decode_sm_r_attrs(__TopXMLNS, _attrs,
    -			      undefined),
    -    {sm_r, Xmlns}.
    -
    -decode_sm_r_attrs(__TopXMLNS,
    -		  [{<<"xmlns">>, _val} | _attrs], _Xmlns) ->
    -    decode_sm_r_attrs(__TopXMLNS, _attrs, _val);
    -decode_sm_r_attrs(__TopXMLNS, [_ | _attrs], Xmlns) ->
    -    decode_sm_r_attrs(__TopXMLNS, _attrs, Xmlns);
    -decode_sm_r_attrs(__TopXMLNS, [], Xmlns) ->
    -    decode_sm_r_attr_xmlns(__TopXMLNS, Xmlns).
    -
    -encode_sm_r({sm_r, Xmlns}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_sm_r_attr_xmlns(Xmlns, _xmlns_attrs),
    -    {xmlel, <<"r">>, _attrs, _els}.
    -
    -decode_sm_r_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_r_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_r_attr_xmlns(undefined, _acc) -> _acc;
    -encode_sm_r_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_sm_resumed(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"resumed">>, _attrs, _els}) ->
    -    {H, Xmlns, Previd} = decode_sm_resumed_attrs(__TopXMLNS,
    -						 _attrs, undefined, undefined,
    -						 undefined),
    -    {sm_resumed, H, Previd, Xmlns}.
    -
    -decode_sm_resumed_attrs(__TopXMLNS,
    -			[{<<"h">>, _val} | _attrs], _H, Xmlns, Previd) ->
    -    decode_sm_resumed_attrs(__TopXMLNS, _attrs, _val, Xmlns,
    -			    Previd);
    -decode_sm_resumed_attrs(__TopXMLNS,
    -			[{<<"xmlns">>, _val} | _attrs], H, _Xmlns, Previd) ->
    -    decode_sm_resumed_attrs(__TopXMLNS, _attrs, H, _val,
    -			    Previd);
    -decode_sm_resumed_attrs(__TopXMLNS,
    -			[{<<"previd">>, _val} | _attrs], H, Xmlns, _Previd) ->
    -    decode_sm_resumed_attrs(__TopXMLNS, _attrs, H, Xmlns,
    -			    _val);
    -decode_sm_resumed_attrs(__TopXMLNS, [_ | _attrs], H,
    -			Xmlns, Previd) ->
    -    decode_sm_resumed_attrs(__TopXMLNS, _attrs, H, Xmlns,
    -			    Previd);
    -decode_sm_resumed_attrs(__TopXMLNS, [], H, Xmlns,
    -			Previd) ->
    -    {decode_sm_resumed_attr_h(__TopXMLNS, H),
    -     decode_sm_resumed_attr_xmlns(__TopXMLNS, Xmlns),
    -     decode_sm_resumed_attr_previd(__TopXMLNS, Previd)}.
    -
    -encode_sm_resumed({sm_resumed, H, Previd, Xmlns},
    -		  _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_sm_resumed_attr_previd(Previd,
    -					   encode_sm_resumed_attr_xmlns(Xmlns,
    -									encode_sm_resumed_attr_h(H,
    -												 _xmlns_attrs))),
    -    {xmlel, <<"resumed">>, _attrs, _els}.
    -
    -decode_sm_resumed_attr_h(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"h">>, <<"resumed">>, __TopXMLNS}});
    -decode_sm_resumed_attr_h(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"h">>, <<"resumed">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sm_resumed_attr_h(_val, _acc) ->
    -    [{<<"h">>, enc_int(_val)} | _acc].
    -
    -decode_sm_resumed_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_resumed_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_resumed_attr_xmlns(undefined, _acc) -> _acc;
    -encode_sm_resumed_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_sm_resumed_attr_previd(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"previd">>, <<"resumed">>,
    -		   __TopXMLNS}});
    -decode_sm_resumed_attr_previd(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_resumed_attr_previd(_val, _acc) ->
    -    [{<<"previd">>, _val} | _acc].
    -
    -decode_sm_resume(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"resume">>, _attrs, _els}) ->
    -    {H, Xmlns, Previd} = decode_sm_resume_attrs(__TopXMLNS,
    -						_attrs, undefined, undefined,
    -						undefined),
    -    {sm_resume, H, Previd, Xmlns}.
    -
    -decode_sm_resume_attrs(__TopXMLNS,
    -		       [{<<"h">>, _val} | _attrs], _H, Xmlns, Previd) ->
    -    decode_sm_resume_attrs(__TopXMLNS, _attrs, _val, Xmlns,
    -			   Previd);
    -decode_sm_resume_attrs(__TopXMLNS,
    -		       [{<<"xmlns">>, _val} | _attrs], H, _Xmlns, Previd) ->
    -    decode_sm_resume_attrs(__TopXMLNS, _attrs, H, _val,
    -			   Previd);
    -decode_sm_resume_attrs(__TopXMLNS,
    -		       [{<<"previd">>, _val} | _attrs], H, Xmlns, _Previd) ->
    -    decode_sm_resume_attrs(__TopXMLNS, _attrs, H, Xmlns,
    -			   _val);
    -decode_sm_resume_attrs(__TopXMLNS, [_ | _attrs], H,
    -		       Xmlns, Previd) ->
    -    decode_sm_resume_attrs(__TopXMLNS, _attrs, H, Xmlns,
    -			   Previd);
    -decode_sm_resume_attrs(__TopXMLNS, [], H, Xmlns,
    -		       Previd) ->
    -    {decode_sm_resume_attr_h(__TopXMLNS, H),
    -     decode_sm_resume_attr_xmlns(__TopXMLNS, Xmlns),
    -     decode_sm_resume_attr_previd(__TopXMLNS, Previd)}.
    -
    -encode_sm_resume({sm_resume, H, Previd, Xmlns},
    -		 _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_sm_resume_attr_previd(Previd,
    -					  encode_sm_resume_attr_xmlns(Xmlns,
    -								      encode_sm_resume_attr_h(H,
    -											      _xmlns_attrs))),
    -    {xmlel, <<"resume">>, _attrs, _els}.
    -
    -decode_sm_resume_attr_h(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"h">>, <<"resume">>, __TopXMLNS}});
    -decode_sm_resume_attr_h(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"h">>, <<"resume">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sm_resume_attr_h(_val, _acc) ->
    -    [{<<"h">>, enc_int(_val)} | _acc].
    -
    -decode_sm_resume_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_resume_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_resume_attr_xmlns(undefined, _acc) -> _acc;
    -encode_sm_resume_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_sm_resume_attr_previd(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"previd">>, <<"resume">>,
    -		   __TopXMLNS}});
    -decode_sm_resume_attr_previd(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_resume_attr_previd(_val, _acc) ->
    -    [{<<"previd">>, _val} | _acc].
    -
    -decode_sm_enabled(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"enabled">>, _attrs, _els}) ->
    -    {Id, Location, Xmlns, Max, Resume} =
    -	decode_sm_enabled_attrs(__TopXMLNS, _attrs, undefined,
    -				undefined, undefined, undefined, undefined),
    -    {sm_enabled, Id, Location, Max, Resume, Xmlns}.
    -
    -decode_sm_enabled_attrs(__TopXMLNS,
    -			[{<<"id">>, _val} | _attrs], _Id, Location, Xmlns, Max,
    -			Resume) ->
    -    decode_sm_enabled_attrs(__TopXMLNS, _attrs, _val,
    -			    Location, Xmlns, Max, Resume);
    -decode_sm_enabled_attrs(__TopXMLNS,
    -			[{<<"location">>, _val} | _attrs], Id, _Location, Xmlns,
    -			Max, Resume) ->
    -    decode_sm_enabled_attrs(__TopXMLNS, _attrs, Id, _val,
    -			    Xmlns, Max, Resume);
    -decode_sm_enabled_attrs(__TopXMLNS,
    -			[{<<"xmlns">>, _val} | _attrs], Id, Location, _Xmlns,
    -			Max, Resume) ->
    -    decode_sm_enabled_attrs(__TopXMLNS, _attrs, Id,
    -			    Location, _val, Max, Resume);
    -decode_sm_enabled_attrs(__TopXMLNS,
    -			[{<<"max">>, _val} | _attrs], Id, Location, Xmlns, _Max,
    -			Resume) ->
    -    decode_sm_enabled_attrs(__TopXMLNS, _attrs, Id,
    -			    Location, Xmlns, _val, Resume);
    -decode_sm_enabled_attrs(__TopXMLNS,
    -			[{<<"resume">>, _val} | _attrs], Id, Location, Xmlns,
    -			Max, _Resume) ->
    -    decode_sm_enabled_attrs(__TopXMLNS, _attrs, Id,
    -			    Location, Xmlns, Max, _val);
    -decode_sm_enabled_attrs(__TopXMLNS, [_ | _attrs], Id,
    -			Location, Xmlns, Max, Resume) ->
    -    decode_sm_enabled_attrs(__TopXMLNS, _attrs, Id,
    -			    Location, Xmlns, Max, Resume);
    -decode_sm_enabled_attrs(__TopXMLNS, [], Id, Location,
    -			Xmlns, Max, Resume) ->
    -    {decode_sm_enabled_attr_id(__TopXMLNS, Id),
    -     decode_sm_enabled_attr_location(__TopXMLNS, Location),
    -     decode_sm_enabled_attr_xmlns(__TopXMLNS, Xmlns),
    -     decode_sm_enabled_attr_max(__TopXMLNS, Max),
    -     decode_sm_enabled_attr_resume(__TopXMLNS, Resume)}.
    -
    -encode_sm_enabled({sm_enabled, Id, Location, Max,
    -		   Resume, Xmlns},
    -		  _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_sm_enabled_attr_resume(Resume,
    -					   encode_sm_enabled_attr_max(Max,
    -								      encode_sm_enabled_attr_xmlns(Xmlns,
    -												   encode_sm_enabled_attr_location(Location,
    -																   encode_sm_enabled_attr_id(Id,
    -																			     _xmlns_attrs))))),
    -    {xmlel, <<"enabled">>, _attrs, _els}.
    -
    -decode_sm_enabled_attr_id(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_enabled_attr_id(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_enabled_attr_id(undefined, _acc) -> _acc;
    -encode_sm_enabled_attr_id(_val, _acc) ->
    -    [{<<"id">>, _val} | _acc].
    -
    -decode_sm_enabled_attr_location(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_sm_enabled_attr_location(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_sm_enabled_attr_location(undefined, _acc) ->
    -    _acc;
    -encode_sm_enabled_attr_location(_val, _acc) ->
    -    [{<<"location">>, _val} | _acc].
    -
    -decode_sm_enabled_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_enabled_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_enabled_attr_xmlns(undefined, _acc) -> _acc;
    -encode_sm_enabled_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_sm_enabled_attr_max(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_enabled_attr_max(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"max">>, <<"enabled">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sm_enabled_attr_max(undefined, _acc) -> _acc;
    -encode_sm_enabled_attr_max(_val, _acc) ->
    -    [{<<"max">>, enc_int(_val)} | _acc].
    -
    -decode_sm_enabled_attr_resume(__TopXMLNS, undefined) ->
    -    false;
    -decode_sm_enabled_attr_resume(__TopXMLNS, _val) ->
    -    case catch dec_bool(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"resume">>, <<"enabled">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sm_enabled_attr_resume(false, _acc) -> _acc;
    -encode_sm_enabled_attr_resume(_val, _acc) ->
    -    [{<<"resume">>, enc_bool(_val)} | _acc].
    -
    -decode_sm_enable(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"enable">>, _attrs, _els}) ->
    -    {Max, Xmlns, Resume} =
    -	decode_sm_enable_attrs(__TopXMLNS, _attrs, undefined,
    -			       undefined, undefined),
    -    {sm_enable, Max, Resume, Xmlns}.
    -
    -decode_sm_enable_attrs(__TopXMLNS,
    -		       [{<<"max">>, _val} | _attrs], _Max, Xmlns, Resume) ->
    -    decode_sm_enable_attrs(__TopXMLNS, _attrs, _val, Xmlns,
    -			   Resume);
    -decode_sm_enable_attrs(__TopXMLNS,
    -		       [{<<"xmlns">>, _val} | _attrs], Max, _Xmlns, Resume) ->
    -    decode_sm_enable_attrs(__TopXMLNS, _attrs, Max, _val,
    -			   Resume);
    -decode_sm_enable_attrs(__TopXMLNS,
    -		       [{<<"resume">>, _val} | _attrs], Max, Xmlns, _Resume) ->
    -    decode_sm_enable_attrs(__TopXMLNS, _attrs, Max, Xmlns,
    -			   _val);
    -decode_sm_enable_attrs(__TopXMLNS, [_ | _attrs], Max,
    -		       Xmlns, Resume) ->
    -    decode_sm_enable_attrs(__TopXMLNS, _attrs, Max, Xmlns,
    -			   Resume);
    -decode_sm_enable_attrs(__TopXMLNS, [], Max, Xmlns,
    -		       Resume) ->
    -    {decode_sm_enable_attr_max(__TopXMLNS, Max),
    -     decode_sm_enable_attr_xmlns(__TopXMLNS, Xmlns),
    -     decode_sm_enable_attr_resume(__TopXMLNS, Resume)}.
    -
    -encode_sm_enable({sm_enable, Max, Resume, Xmlns},
    -		 _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_sm_enable_attr_resume(Resume,
    -					  encode_sm_enable_attr_xmlns(Xmlns,
    -								      encode_sm_enable_attr_max(Max,
    -												_xmlns_attrs))),
    -    {xmlel, <<"enable">>, _attrs, _els}.
    -
    -decode_sm_enable_attr_max(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_enable_attr_max(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"max">>, <<"enable">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sm_enable_attr_max(undefined, _acc) -> _acc;
    -encode_sm_enable_attr_max(_val, _acc) ->
    -    [{<<"max">>, enc_int(_val)} | _acc].
    -
    -decode_sm_enable_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_sm_enable_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_sm_enable_attr_xmlns(undefined, _acc) -> _acc;
    -encode_sm_enable_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_sm_enable_attr_resume(__TopXMLNS, undefined) ->
    -    false;
    -decode_sm_enable_attr_resume(__TopXMLNS, _val) ->
    -    case catch dec_bool(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"resume">>, <<"enable">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sm_enable_attr_resume(false, _acc) -> _acc;
    -encode_sm_enable_attr_resume(_val, _acc) ->
    -    [{<<"resume">>, enc_bool(_val)} | _acc].
    -
    -decode_feature_sm(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"sm">>, _attrs, _els}) ->
    -    Xmlns = decode_feature_sm_attrs(__TopXMLNS, _attrs,
    -				    undefined),
    -    {feature_sm, Xmlns}.
    -
    -decode_feature_sm_attrs(__TopXMLNS,
    -			[{<<"xmlns">>, _val} | _attrs], _Xmlns) ->
    -    decode_feature_sm_attrs(__TopXMLNS, _attrs, _val);
    -decode_feature_sm_attrs(__TopXMLNS, [_ | _attrs],
    -			Xmlns) ->
    -    decode_feature_sm_attrs(__TopXMLNS, _attrs, Xmlns);
    -decode_feature_sm_attrs(__TopXMLNS, [], Xmlns) ->
    -    decode_feature_sm_attr_xmlns(__TopXMLNS, Xmlns).
    -
    -encode_feature_sm({feature_sm, Xmlns}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_feature_sm_attr_xmlns(Xmlns,
    -					  _xmlns_attrs),
    -    {xmlel, <<"sm">>, _attrs, _els}.
    -
    -decode_feature_sm_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_feature_sm_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_feature_sm_attr_xmlns(undefined, _acc) -> _acc;
    -encode_feature_sm_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_csi_inactive(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"inactive">>, _attrs, _els}) ->
    -    {csi, inactive}.
    -
    -encode_csi_inactive({csi, inactive}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"inactive">>, _attrs, _els}.
    -
    -decode_csi_active(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"active">>, _attrs, _els}) ->
    -    {csi, active}.
    -
    -encode_csi_active({csi, active}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"active">>, _attrs, _els}.
    -
    -decode_feature_csi(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"csi">>, _attrs, _els}) ->
    -    Xmlns = decode_feature_csi_attrs(__TopXMLNS, _attrs,
    -				     undefined),
    -    {feature_csi, Xmlns}.
    -
    -decode_feature_csi_attrs(__TopXMLNS,
    -			 [{<<"xmlns">>, _val} | _attrs], _Xmlns) ->
    -    decode_feature_csi_attrs(__TopXMLNS, _attrs, _val);
    -decode_feature_csi_attrs(__TopXMLNS, [_ | _attrs],
    -			 Xmlns) ->
    -    decode_feature_csi_attrs(__TopXMLNS, _attrs, Xmlns);
    -decode_feature_csi_attrs(__TopXMLNS, [], Xmlns) ->
    -    decode_feature_csi_attr_xmlns(__TopXMLNS, Xmlns).
    -
    -encode_feature_csi({feature_csi, Xmlns},
    -		   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_feature_csi_attr_xmlns(Xmlns,
    -					   _xmlns_attrs),
    -    {xmlel, <<"csi">>, _attrs, _els}.
    -
    -decode_feature_csi_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_feature_csi_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_feature_csi_attr_xmlns(undefined, _acc) -> _acc;
    -encode_feature_csi_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_carbons_sent(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"sent">>, _attrs, _els}) ->
    -    Forwarded = decode_carbons_sent_els(__TopXMLNS,
    -					__IgnoreEls, _els, error),
    -    {carbons_sent, Forwarded}.
    -
    -decode_carbons_sent_els(__TopXMLNS, __IgnoreEls, [],
    -			Forwarded) ->
    -    case Forwarded of
    -      error ->
    -	  erlang:error({xmpp_codec,
    -			{missing_tag, <<"forwarded">>, __TopXMLNS}});
    -      {value, Forwarded1} -> Forwarded1
    -    end;
    -decode_carbons_sent_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"forwarded">>, _attrs, _} = _el | _els],
    -			Forwarded) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"urn:xmpp:forward:0">> ->
    -	   decode_carbons_sent_els(__TopXMLNS, __IgnoreEls, _els,
    -				   {value,
    -				    decode_forwarded(_xmlns, __IgnoreEls,
    -						     _el)});
    -       true ->
    -	   decode_carbons_sent_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Forwarded)
    -    end;
    -decode_carbons_sent_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Forwarded) ->
    -    decode_carbons_sent_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Forwarded).
    -
    -encode_carbons_sent({carbons_sent, Forwarded},
    -		    _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_carbons_sent_$forwarded'(Forwarded,
    -						       [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"sent">>, _attrs, _els}.
    -
    -'encode_carbons_sent_$forwarded'(Forwarded, _acc) ->
    -    [encode_forwarded(Forwarded,
    -		      [{<<"xmlns">>, <<"urn:xmpp:forward:0">>}])
    -     | _acc].
    -
    -decode_carbons_received(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"received">>, _attrs, _els}) ->
    -    Forwarded = decode_carbons_received_els(__TopXMLNS,
    -					    __IgnoreEls, _els, error),
    -    {carbons_received, Forwarded}.
    -
    -decode_carbons_received_els(__TopXMLNS, __IgnoreEls, [],
    -			    Forwarded) ->
    -    case Forwarded of
    -      error ->
    -	  erlang:error({xmpp_codec,
    -			{missing_tag, <<"forwarded">>, __TopXMLNS}});
    -      {value, Forwarded1} -> Forwarded1
    -    end;
    -decode_carbons_received_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlel, <<"forwarded">>, _attrs, _} = _el | _els],
    -			    Forwarded) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"urn:xmpp:forward:0">> ->
    -	   decode_carbons_received_els(__TopXMLNS, __IgnoreEls,
    -				       _els,
    -				       {value,
    -					decode_forwarded(_xmlns, __IgnoreEls,
    -							 _el)});
    -       true ->
    -	   decode_carbons_received_els(__TopXMLNS, __IgnoreEls,
    -				       _els, Forwarded)
    -    end;
    -decode_carbons_received_els(__TopXMLNS, __IgnoreEls,
    -			    [_ | _els], Forwarded) ->
    -    decode_carbons_received_els(__TopXMLNS, __IgnoreEls,
    -				_els, Forwarded).
    -
    -encode_carbons_received({carbons_received, Forwarded},
    -			_xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_carbons_received_$forwarded'(Forwarded,
    -							   [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"received">>, _attrs, _els}.
    -
    -'encode_carbons_received_$forwarded'(Forwarded, _acc) ->
    -    [encode_forwarded(Forwarded,
    -		      [{<<"xmlns">>, <<"urn:xmpp:forward:0">>}])
    -     | _acc].
    -
    -decode_carbons_private(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"private">>, _attrs, _els}) ->
    -    {carbons_private}.
    -
    -encode_carbons_private({carbons_private},
    -		       _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"private">>, _attrs, _els}.
    -
    -decode_carbons_enable(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"enable">>, _attrs, _els}) ->
    -    {carbons_enable}.
    -
    -encode_carbons_enable({carbons_enable}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"enable">>, _attrs, _els}.
    -
    -decode_carbons_disable(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"disable">>, _attrs, _els}) ->
    -    {carbons_disable}.
    -
    -encode_carbons_disable({carbons_disable},
    -		       _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"disable">>, _attrs, _els}.
    -
    -decode_forwarded(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"forwarded">>, _attrs, _els}) ->
    -    {Delay, __Els} = decode_forwarded_els(__TopXMLNS,
    -					  __IgnoreEls, _els, undefined, []),
    -    {forwarded, Delay, __Els}.
    -
    -decode_forwarded_els(__TopXMLNS, __IgnoreEls, [], Delay,
    -		     __Els) ->
    -    {Delay, lists:reverse(__Els)};
    -decode_forwarded_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"delay">>, _attrs, _} = _el | _els], Delay,
    -		     __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"urn:xmpp:delay">> ->
    -	   decode_forwarded_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_delay(_xmlns, __IgnoreEls, _el), __Els);
    -       true ->
    -	   decode_forwarded_els(__TopXMLNS, __IgnoreEls, _els,
    -				Delay, __Els)
    -    end;
    -decode_forwarded_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, _, _, _} = _el | _els], Delay, __Els) ->
    -    if __IgnoreEls ->
    -	   decode_forwarded_els(__TopXMLNS, __IgnoreEls, _els,
    -				Delay, [_el | __Els]);
    -       true ->
    -	   case is_known_tag(_el) of
    -	     true ->
    -		 decode_forwarded_els(__TopXMLNS, __IgnoreEls, _els,
    -				      Delay, [decode(_el) | __Els]);
    -	     false ->
    -		 decode_forwarded_els(__TopXMLNS, __IgnoreEls, _els,
    -				      Delay, __Els)
    -	   end
    -    end;
    -decode_forwarded_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Delay, __Els) ->
    -    decode_forwarded_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Delay, __Els).
    -
    -encode_forwarded({forwarded, Delay, __Els},
    -		 _xmlns_attrs) ->
    -    _els = [encode(_el) || _el <- __Els] ++
    -	     lists:reverse('encode_forwarded_$delay'(Delay, [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"forwarded">>, _attrs, _els}.
    -
    -'encode_forwarded_$delay'(undefined, _acc) -> _acc;
    -'encode_forwarded_$delay'(Delay, _acc) ->
    -    [encode_delay(Delay,
    -		  [{<<"xmlns">>, <<"urn:xmpp:delay">>}])
    -     | _acc].
    -
    -decode_mam_fin(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"fin">>, _attrs, _els}) ->
    -    Rsm = decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els,
    -			     undefined),
    -    {Id, Stable, Complete} =
    -	decode_mam_fin_attrs(__TopXMLNS, _attrs, undefined,
    -			     undefined, undefined),
    -    {mam_fin, Id, Rsm, Stable, Complete}.
    -
    -decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [], Rsm) ->
    -    Rsm;
    -decode_mam_fin_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"set">>, _attrs, _} = _el | _els], Rsm) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"http://jabber.org/protocol/rsm">> ->
    -	   decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els,
    -			      decode_rsm_set(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, Rsm)
    -    end;
    -decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Rsm) ->
    -    decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, Rsm).
    -
    -decode_mam_fin_attrs(__TopXMLNS,
    -		     [{<<"queryid">>, _val} | _attrs], _Id, Stable,
    -		     Complete) ->
    -    decode_mam_fin_attrs(__TopXMLNS, _attrs, _val, Stable,
    -			 Complete);
    -decode_mam_fin_attrs(__TopXMLNS,
    -		     [{<<"stable">>, _val} | _attrs], Id, _Stable,
    -		     Complete) ->
    -    decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, _val,
    -			 Complete);
    -decode_mam_fin_attrs(__TopXMLNS,
    -		     [{<<"complete">>, _val} | _attrs], Id, Stable,
    -		     _Complete) ->
    -    decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable,
    -			 _val);
    -decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id,
    -		     Stable, Complete) ->
    -    decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable,
    -			 Complete);
    -decode_mam_fin_attrs(__TopXMLNS, [], Id, Stable,
    -		     Complete) ->
    -    {decode_mam_fin_attr_queryid(__TopXMLNS, Id),
    -     decode_mam_fin_attr_stable(__TopXMLNS, Stable),
    -     decode_mam_fin_attr_complete(__TopXMLNS, Complete)}.
    -
    -encode_mam_fin({mam_fin, Id, Rsm, Stable, Complete},
    -	       _xmlns_attrs) ->
    -    _els = lists:reverse('encode_mam_fin_$rsm'(Rsm, [])),
    -    _attrs = encode_mam_fin_attr_complete(Complete,
    -					  encode_mam_fin_attr_stable(Stable,
    -								     encode_mam_fin_attr_queryid(Id,
    -												 _xmlns_attrs))),
    -    {xmlel, <<"fin">>, _attrs, _els}.
    -
    -'encode_mam_fin_$rsm'(undefined, _acc) -> _acc;
    -'encode_mam_fin_$rsm'(Rsm, _acc) ->
    -    [encode_rsm_set(Rsm,
    -		    [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}])
    -     | _acc].
    -
    -decode_mam_fin_attr_queryid(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_fin_attr_queryid(__TopXMLNS, _val) -> _val.
    -
    -encode_mam_fin_attr_queryid(undefined, _acc) -> _acc;
    -encode_mam_fin_attr_queryid(_val, _acc) ->
    -    [{<<"queryid">>, _val} | _acc].
    -
    -decode_mam_fin_attr_stable(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_fin_attr_stable(__TopXMLNS, _val) ->
    -    case catch dec_bool(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"stable">>, <<"fin">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mam_fin_attr_stable(undefined, _acc) -> _acc;
    -encode_mam_fin_attr_stable(_val, _acc) ->
    -    [{<<"stable">>, enc_bool(_val)} | _acc].
    -
    -decode_mam_fin_attr_complete(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_fin_attr_complete(__TopXMLNS, _val) ->
    -    case catch dec_bool(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"complete">>, <<"fin">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mam_fin_attr_complete(undefined, _acc) -> _acc;
    -encode_mam_fin_attr_complete(_val, _acc) ->
    -    [{<<"complete">>, enc_bool(_val)} | _acc].
    -
    -decode_mam_prefs(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"prefs">>, _attrs, _els}) ->
    -    {Never, Always} = decode_mam_prefs_els(__TopXMLNS,
    -					   __IgnoreEls, _els, [], []),
    -    {Default, Xmlns} = decode_mam_prefs_attrs(__TopXMLNS,
    -					      _attrs, undefined, undefined),
    -    {mam_prefs, Xmlns, Default, Always, Never}.
    -
    -decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, [], Never,
    -		     Always) ->
    -    {Never, Always};
    -decode_mam_prefs_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"always">>, _attrs, _} = _el | _els], Never,
    -		     Always) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
    -				Never,
    -				decode_mam_always(__TopXMLNS, __IgnoreEls,
    -						  _el));
    -       true ->
    -	   decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
    -				Never, Always)
    -    end;
    -decode_mam_prefs_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"never">>, _attrs, _} = _el | _els], Never,
    -		     Always) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_mam_never(__TopXMLNS, __IgnoreEls, _el),
    -				Always);
    -       true ->
    -	   decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
    -				Never, Always)
    -    end;
    -decode_mam_prefs_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Never, Always) ->
    -    decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Never, Always).
    -
    -decode_mam_prefs_attrs(__TopXMLNS,
    -		       [{<<"default">>, _val} | _attrs], _Default, Xmlns) ->
    -    decode_mam_prefs_attrs(__TopXMLNS, _attrs, _val, Xmlns);
    -decode_mam_prefs_attrs(__TopXMLNS,
    -		       [{<<"xmlns">>, _val} | _attrs], Default, _Xmlns) ->
    -    decode_mam_prefs_attrs(__TopXMLNS, _attrs, Default,
    -			   _val);
    -decode_mam_prefs_attrs(__TopXMLNS, [_ | _attrs],
    -		       Default, Xmlns) ->
    -    decode_mam_prefs_attrs(__TopXMLNS, _attrs, Default,
    -			   Xmlns);
    -decode_mam_prefs_attrs(__TopXMLNS, [], Default,
    -		       Xmlns) ->
    -    {decode_mam_prefs_attr_default(__TopXMLNS, Default),
    -     decode_mam_prefs_attr_xmlns(__TopXMLNS, Xmlns)}.
    -
    -encode_mam_prefs({mam_prefs, Xmlns, Default, Always,
    -		  Never},
    -		 _xmlns_attrs) ->
    -    _els = lists:reverse('encode_mam_prefs_$never'(Never,
    -						   'encode_mam_prefs_$always'(Always,
    -									      []))),
    -    _attrs = encode_mam_prefs_attr_xmlns(Xmlns,
    -					 encode_mam_prefs_attr_default(Default,
    -								       _xmlns_attrs)),
    -    {xmlel, <<"prefs">>, _attrs, _els}.
    -
    -'encode_mam_prefs_$never'([], _acc) -> _acc;
    -'encode_mam_prefs_$never'(Never, _acc) ->
    -    [encode_mam_never(Never,
    -		      [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}])
    -     | _acc].
    -
    -'encode_mam_prefs_$always'([], _acc) -> _acc;
    -'encode_mam_prefs_$always'(Always, _acc) ->
    -    [encode_mam_always(Always,
    -		       [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}])
    -     | _acc].
    -
    -decode_mam_prefs_attr_default(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_prefs_attr_default(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val, [always, never, roster]) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"default">>, <<"prefs">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mam_prefs_attr_default(undefined, _acc) -> _acc;
    -encode_mam_prefs_attr_default(_val, _acc) ->
    -    [{<<"default">>, enc_enum(_val)} | _acc].
    -
    -decode_mam_prefs_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_prefs_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_mam_prefs_attr_xmlns(undefined, _acc) -> _acc;
    -encode_mam_prefs_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_mam_always(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"always">>, _attrs, _els}) ->
    -    Jids = decode_mam_always_els(__TopXMLNS, __IgnoreEls,
    -				 _els, []),
    -    Jids.
    -
    -decode_mam_always_els(__TopXMLNS, __IgnoreEls, [],
    -		      Jids) ->
    -    lists:reverse(Jids);
    -decode_mam_always_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlel, <<"jid">>, _attrs, _} = _el | _els], Jids) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els,
    -				 case decode_mam_jid(__TopXMLNS, __IgnoreEls,
    -						     _el)
    -				     of
    -				   [] -> Jids;
    -				   _new_el -> [_new_el | Jids]
    -				 end);
    -       true ->
    -	   decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Jids)
    -    end;
    -decode_mam_always_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Jids) ->
    -    decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Jids).
    -
    -encode_mam_always(Jids, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_mam_always_$jids'(Jids,
    -						   [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"always">>, _attrs, _els}.
    -
    -'encode_mam_always_$jids'([], _acc) -> _acc;
    -'encode_mam_always_$jids'([Jids | _els], _acc) ->
    -    'encode_mam_always_$jids'(_els,
    -			      [encode_mam_jid(Jids, []) | _acc]).
    -
    -decode_mam_never(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"never">>, _attrs, _els}) ->
    -    Jids = decode_mam_never_els(__TopXMLNS, __IgnoreEls,
    -				_els, []),
    -    Jids.
    -
    -decode_mam_never_els(__TopXMLNS, __IgnoreEls, [],
    -		     Jids) ->
    -    lists:reverse(Jids);
    -decode_mam_never_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"jid">>, _attrs, _} = _el | _els], Jids) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els,
    -				case decode_mam_jid(__TopXMLNS, __IgnoreEls,
    -						    _el)
    -				    of
    -				  [] -> Jids;
    -				  _new_el -> [_new_el | Jids]
    -				end);
    -       true ->
    -	   decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els,
    -				Jids)
    -    end;
    -decode_mam_never_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Jids) ->
    -    decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Jids).
    -
    -encode_mam_never(Jids, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_mam_never_$jids'(Jids,
    -						  [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"never">>, _attrs, _els}.
    -
    -'encode_mam_never_$jids'([], _acc) -> _acc;
    -'encode_mam_never_$jids'([Jids | _els], _acc) ->
    -    'encode_mam_never_$jids'(_els,
    -			     [encode_mam_jid(Jids, []) | _acc]).
    -
    -decode_mam_jid(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"jid">>, _attrs, _els}) ->
    -    Cdata = decode_mam_jid_els(__TopXMLNS, __IgnoreEls,
    -			       _els, <<>>),
    -    Cdata.
    -
    -decode_mam_jid_els(__TopXMLNS, __IgnoreEls, [],
    -		   Cdata) ->
    -    decode_mam_jid_cdata(__TopXMLNS, Cdata);
    -decode_mam_jid_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_mam_jid_els(__TopXMLNS, __IgnoreEls, _els,
    -		       <>);
    -decode_mam_jid_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Cdata) ->
    -    decode_mam_jid_els(__TopXMLNS, __IgnoreEls, _els,
    -		       Cdata).
    -
    -encode_mam_jid(Cdata, _xmlns_attrs) ->
    -    _els = encode_mam_jid_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"jid">>, _attrs, _els}.
    -
    -decode_mam_jid_cdata(__TopXMLNS, <<>>) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_cdata, <<>>, <<"jid">>, __TopXMLNS}});
    -decode_mam_jid_cdata(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"jid">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mam_jid_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_jid(_val)} | _acc].
    -
    -decode_mam_result(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"result">>, _attrs, _els}) ->
    -    __Els = decode_mam_result_els(__TopXMLNS, __IgnoreEls,
    -				  _els, []),
    -    {Queryid, Xmlns, Id} =
    -	decode_mam_result_attrs(__TopXMLNS, _attrs, undefined,
    -				undefined, undefined),
    -    {mam_result, Xmlns, Queryid, Id, __Els}.
    -
    -decode_mam_result_els(__TopXMLNS, __IgnoreEls, [],
    -		      __Els) ->
    -    lists:reverse(__Els);
    -decode_mam_result_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlel, _, _, _} = _el | _els], __Els) ->
    -    if __IgnoreEls ->
    -	   decode_mam_result_els(__TopXMLNS, __IgnoreEls, _els,
    -				 [_el | __Els]);
    -       true ->
    -	   case is_known_tag(_el) of
    -	     true ->
    -		 decode_mam_result_els(__TopXMLNS, __IgnoreEls, _els,
    -				       [decode(_el) | __Els]);
    -	     false ->
    -		 decode_mam_result_els(__TopXMLNS, __IgnoreEls, _els,
    -				       __Els)
    -	   end
    -    end;
    -decode_mam_result_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], __Els) ->
    -    decode_mam_result_els(__TopXMLNS, __IgnoreEls, _els,
    -			  __Els).
    -
    -decode_mam_result_attrs(__TopXMLNS,
    -			[{<<"queryid">>, _val} | _attrs], _Queryid, Xmlns,
    -			Id) ->
    -    decode_mam_result_attrs(__TopXMLNS, _attrs, _val, Xmlns,
    -			    Id);
    -decode_mam_result_attrs(__TopXMLNS,
    -			[{<<"xmlns">>, _val} | _attrs], Queryid, _Xmlns, Id) ->
    -    decode_mam_result_attrs(__TopXMLNS, _attrs, Queryid,
    -			    _val, Id);
    -decode_mam_result_attrs(__TopXMLNS,
    -			[{<<"id">>, _val} | _attrs], Queryid, Xmlns, _Id) ->
    -    decode_mam_result_attrs(__TopXMLNS, _attrs, Queryid,
    -			    Xmlns, _val);
    -decode_mam_result_attrs(__TopXMLNS, [_ | _attrs],
    -			Queryid, Xmlns, Id) ->
    -    decode_mam_result_attrs(__TopXMLNS, _attrs, Queryid,
    -			    Xmlns, Id);
    -decode_mam_result_attrs(__TopXMLNS, [], Queryid, Xmlns,
    -			Id) ->
    -    {decode_mam_result_attr_queryid(__TopXMLNS, Queryid),
    -     decode_mam_result_attr_xmlns(__TopXMLNS, Xmlns),
    -     decode_mam_result_attr_id(__TopXMLNS, Id)}.
    -
    -encode_mam_result({mam_result, Xmlns, Queryid, Id,
    -		   __Els},
    -		  _xmlns_attrs) ->
    -    _els = [encode(_el) || _el <- __Els],
    -    _attrs = encode_mam_result_attr_id(Id,
    -				       encode_mam_result_attr_xmlns(Xmlns,
    -								    encode_mam_result_attr_queryid(Queryid,
    -												   _xmlns_attrs))),
    -    {xmlel, <<"result">>, _attrs, _els}.
    -
    -decode_mam_result_attr_queryid(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_result_attr_queryid(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_mam_result_attr_queryid(undefined, _acc) -> _acc;
    -encode_mam_result_attr_queryid(_val, _acc) ->
    -    [{<<"queryid">>, _val} | _acc].
    -
    -decode_mam_result_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_result_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_mam_result_attr_xmlns(undefined, _acc) -> _acc;
    -encode_mam_result_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_mam_result_attr_id(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_result_attr_id(__TopXMLNS, _val) -> _val.
    -
    -encode_mam_result_attr_id(undefined, _acc) -> _acc;
    -encode_mam_result_attr_id(_val, _acc) ->
    -    [{<<"id">>, _val} | _acc].
    -
    -decode_mam_archived(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"archived">>, _attrs, _els}) ->
    -    {Id, By} = decode_mam_archived_attrs(__TopXMLNS, _attrs,
    -					 undefined, undefined),
    -    {mam_archived, By, Id}.
    -
    -decode_mam_archived_attrs(__TopXMLNS,
    -			  [{<<"id">>, _val} | _attrs], _Id, By) ->
    -    decode_mam_archived_attrs(__TopXMLNS, _attrs, _val, By);
    -decode_mam_archived_attrs(__TopXMLNS,
    -			  [{<<"by">>, _val} | _attrs], Id, _By) ->
    -    decode_mam_archived_attrs(__TopXMLNS, _attrs, Id, _val);
    -decode_mam_archived_attrs(__TopXMLNS, [_ | _attrs], Id,
    -			  By) ->
    -    decode_mam_archived_attrs(__TopXMLNS, _attrs, Id, By);
    -decode_mam_archived_attrs(__TopXMLNS, [], Id, By) ->
    -    {decode_mam_archived_attr_id(__TopXMLNS, Id),
    -     decode_mam_archived_attr_by(__TopXMLNS, By)}.
    -
    -encode_mam_archived({mam_archived, By, Id},
    -		    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_mam_archived_attr_by(By,
    -					 encode_mam_archived_attr_id(Id,
    -								     _xmlns_attrs)),
    -    {xmlel, <<"archived">>, _attrs, _els}.
    -
    -decode_mam_archived_attr_id(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_archived_attr_id(__TopXMLNS, _val) -> _val.
    -
    -encode_mam_archived_attr_id(undefined, _acc) -> _acc;
    -encode_mam_archived_attr_id(_val, _acc) ->
    -    [{<<"id">>, _val} | _acc].
    -
    -decode_mam_archived_attr_by(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"by">>, <<"archived">>, __TopXMLNS}});
    -decode_mam_archived_attr_by(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"by">>, <<"archived">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mam_archived_attr_by(_val, _acc) ->
    -    [{<<"by">>, enc_jid(_val)} | _acc].
    -
    -decode_mam_query(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"query">>, _attrs, _els}) ->
    -    {Xdata, End, Start, With, Rsm} =
    -	decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -			     undefined, undefined, undefined, undefined,
    -			     undefined),
    -    {Id, Xmlns} = decode_mam_query_attrs(__TopXMLNS, _attrs,
    -					 undefined, undefined),
    -    {mam_query, Xmlns, Id, Start, End, With, Rsm, Xdata}.
    -
    -decode_mam_query_els(__TopXMLNS, __IgnoreEls, [], Xdata,
    -		     End, Start, With, Rsm) ->
    -    {Xdata, End, Start, With, Rsm};
    -decode_mam_query_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"start">>, _attrs, _} = _el | _els], Xdata,
    -		     End, Start, With, Rsm) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				Xdata, End,
    -				decode_mam_start(__TopXMLNS, __IgnoreEls, _el),
    -				With, Rsm);
    -       true ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				Xdata, End, Start, With, Rsm)
    -    end;
    -decode_mam_query_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"end">>, _attrs, _} = _el | _els], Xdata,
    -		     End, Start, With, Rsm) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				Xdata,
    -				decode_mam_end(__TopXMLNS, __IgnoreEls, _el),
    -				Start, With, Rsm);
    -       true ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				Xdata, End, Start, With, Rsm)
    -    end;
    -decode_mam_query_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"with">>, _attrs, _} = _el | _els], Xdata,
    -		     End, Start, With, Rsm) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				Xdata, End, Start,
    -				decode_mam_with(__TopXMLNS, __IgnoreEls, _el),
    -				Rsm);
    -       true ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				Xdata, End, Start, With, Rsm)
    -    end;
    -decode_mam_query_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"set">>, _attrs, _} = _el | _els], Xdata,
    -		     End, Start, With, Rsm) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"http://jabber.org/protocol/rsm">> ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				Xdata, End, Start, With,
    -				decode_rsm_set(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				Xdata, End, Start, With, Rsm)
    -    end;
    -decode_mam_query_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"x">>, _attrs, _} = _el | _els], Xdata, End,
    -		     Start, With, Rsm) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"jabber:x:data">> ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_xdata(_xmlns, __IgnoreEls, _el), End,
    -				Start, With, Rsm);
    -       true ->
    -	   decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -				Xdata, End, Start, With, Rsm)
    -    end;
    -decode_mam_query_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Xdata, End, Start, With, Rsm) ->
    -    decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Xdata, End, Start, With, Rsm).
    -
    -decode_mam_query_attrs(__TopXMLNS,
    -		       [{<<"queryid">>, _val} | _attrs], _Id, Xmlns) ->
    -    decode_mam_query_attrs(__TopXMLNS, _attrs, _val, Xmlns);
    -decode_mam_query_attrs(__TopXMLNS,
    -		       [{<<"xmlns">>, _val} | _attrs], Id, _Xmlns) ->
    -    decode_mam_query_attrs(__TopXMLNS, _attrs, Id, _val);
    -decode_mam_query_attrs(__TopXMLNS, [_ | _attrs], Id,
    -		       Xmlns) ->
    -    decode_mam_query_attrs(__TopXMLNS, _attrs, Id, Xmlns);
    -decode_mam_query_attrs(__TopXMLNS, [], Id, Xmlns) ->
    -    {decode_mam_query_attr_queryid(__TopXMLNS, Id),
    -     decode_mam_query_attr_xmlns(__TopXMLNS, Xmlns)}.
    -
    -encode_mam_query({mam_query, Xmlns, Id, Start, End,
    -		  With, Rsm, Xdata},
    -		 _xmlns_attrs) ->
    -    _els = lists:reverse('encode_mam_query_$xdata'(Xdata,
    -						   'encode_mam_query_$end'(End,
    -									   'encode_mam_query_$start'(Start,
    -												     'encode_mam_query_$with'(With,
    -															      'encode_mam_query_$rsm'(Rsm,
    -																		      [])))))),
    -    _attrs = encode_mam_query_attr_xmlns(Xmlns,
    -					 encode_mam_query_attr_queryid(Id,
    -								       _xmlns_attrs)),
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_mam_query_$xdata'(undefined, _acc) -> _acc;
    -'encode_mam_query_$xdata'(Xdata, _acc) ->
    -    [encode_xdata(Xdata,
    -		  [{<<"xmlns">>, <<"jabber:x:data">>}])
    -     | _acc].
    -
    -'encode_mam_query_$end'(undefined, _acc) -> _acc;
    -'encode_mam_query_$end'(End, _acc) ->
    -    [encode_mam_end(End,
    -		    [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}])
    -     | _acc].
    -
    -'encode_mam_query_$start'(undefined, _acc) -> _acc;
    -'encode_mam_query_$start'(Start, _acc) ->
    -    [encode_mam_start(Start,
    -		      [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}])
    -     | _acc].
    -
    -'encode_mam_query_$with'(undefined, _acc) -> _acc;
    -'encode_mam_query_$with'(With, _acc) ->
    -    [encode_mam_with(With,
    -		     [{<<"xmlns">>, <<"urn:xmpp:mam:tmp">>}])
    -     | _acc].
    -
    -'encode_mam_query_$rsm'(undefined, _acc) -> _acc;
    -'encode_mam_query_$rsm'(Rsm, _acc) ->
    -    [encode_rsm_set(Rsm,
    -		    [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}])
    -     | _acc].
    -
    -decode_mam_query_attr_queryid(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_query_attr_queryid(__TopXMLNS, _val) -> _val.
    -
    -encode_mam_query_attr_queryid(undefined, _acc) -> _acc;
    -encode_mam_query_attr_queryid(_val, _acc) ->
    -    [{<<"queryid">>, _val} | _acc].
    -
    -decode_mam_query_attr_xmlns(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_mam_query_attr_xmlns(__TopXMLNS, _val) -> _val.
    -
    -encode_mam_query_attr_xmlns(undefined, _acc) -> _acc;
    -encode_mam_query_attr_xmlns(_val, _acc) ->
    -    [{<<"xmlns">>, _val} | _acc].
    -
    -decode_mam_with(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"with">>, _attrs, _els}) ->
    -    Cdata = decode_mam_with_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    Cdata.
    -
    -decode_mam_with_els(__TopXMLNS, __IgnoreEls, [],
    -		    Cdata) ->
    -    decode_mam_with_cdata(__TopXMLNS, Cdata);
    -decode_mam_with_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_mam_with_els(__TopXMLNS, __IgnoreEls, _els,
    -			<>);
    -decode_mam_with_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Cdata) ->
    -    decode_mam_with_els(__TopXMLNS, __IgnoreEls, _els,
    -			Cdata).
    -
    -encode_mam_with(Cdata, _xmlns_attrs) ->
    -    _els = encode_mam_with_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"with">>, _attrs, _els}.
    -
    -decode_mam_with_cdata(__TopXMLNS, <<>>) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_cdata, <<>>, <<"with">>, __TopXMLNS}});
    -decode_mam_with_cdata(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"with">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mam_with_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_jid(_val)} | _acc].
    -
    -decode_mam_end(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"end">>, _attrs, _els}) ->
    -    Cdata = decode_mam_end_els(__TopXMLNS, __IgnoreEls,
    -			       _els, <<>>),
    -    Cdata.
    -
    -decode_mam_end_els(__TopXMLNS, __IgnoreEls, [],
    -		   Cdata) ->
    -    decode_mam_end_cdata(__TopXMLNS, Cdata);
    -decode_mam_end_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_mam_end_els(__TopXMLNS, __IgnoreEls, _els,
    -		       <>);
    -decode_mam_end_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Cdata) ->
    -    decode_mam_end_els(__TopXMLNS, __IgnoreEls, _els,
    -		       Cdata).
    -
    -encode_mam_end(Cdata, _xmlns_attrs) ->
    -    _els = encode_mam_end_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"end">>, _attrs, _els}.
    -
    -decode_mam_end_cdata(__TopXMLNS, <<>>) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_cdata, <<>>, <<"end">>, __TopXMLNS}});
    -decode_mam_end_cdata(__TopXMLNS, _val) ->
    -    case catch dec_utc(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"end">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mam_end_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_utc(_val)} | _acc].
    -
    -decode_mam_start(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"start">>, _attrs, _els}) ->
    -    Cdata = decode_mam_start_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Cdata.
    -
    -decode_mam_start_els(__TopXMLNS, __IgnoreEls, [],
    -		     Cdata) ->
    -    decode_mam_start_cdata(__TopXMLNS, Cdata);
    -decode_mam_start_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_mam_start_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_mam_start_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cdata) ->
    -    decode_mam_start_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cdata).
    -
    -encode_mam_start(Cdata, _xmlns_attrs) ->
    -    _els = encode_mam_start_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"start">>, _attrs, _els}.
    -
    -decode_mam_start_cdata(__TopXMLNS, <<>>) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_cdata, <<>>, <<"start">>, __TopXMLNS}});
    -decode_mam_start_cdata(__TopXMLNS, _val) ->
    -    case catch dec_utc(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"start">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_mam_start_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_utc(_val)} | _acc].
    -
    -decode_rsm_set(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"set">>, _attrs, _els}) ->
    -    {After, Last, First, Count, Before, Max, Index} =
    -	decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els,
    -			   undefined, undefined, undefined, undefined,
    -			   undefined, undefined, undefined),
    -    {rsm_set, After, Before, Count, First, Index, Last,
    -     Max}.
    -
    -decode_rsm_set_els(__TopXMLNS, __IgnoreEls, [], After,
    -		   Last, First, Count, Before, Max, Index) ->
    -    {After, Last, First, Count, Before, Max, Index};
    -decode_rsm_set_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"after">>, _attrs, _} = _el | _els], After,
    -		   Last, First, Count, Before, Max, Index) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els,
    -			      decode_rsm_after(__TopXMLNS, __IgnoreEls, _el),
    -			      Last, First, Count, Before, Max, Index);
    -       true ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count, Before, Max, Index)
    -    end;
    -decode_rsm_set_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"before">>, _attrs, _} = _el | _els], After,
    -		   Last, First, Count, Before, Max, Index) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count,
    -			      decode_rsm_before(__TopXMLNS, __IgnoreEls, _el),
    -			      Max, Index);
    -       true ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count, Before, Max, Index)
    -    end;
    -decode_rsm_set_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"count">>, _attrs, _} = _el | _els], After,
    -		   Last, First, Count, Before, Max, Index) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First,
    -			      decode_rsm_count(__TopXMLNS, __IgnoreEls, _el),
    -			      Before, Max, Index);
    -       true ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count, Before, Max, Index)
    -    end;
    -decode_rsm_set_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"first">>, _attrs, _} = _el | _els], After,
    -		   Last, First, Count, Before, Max, Index) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last,
    -			      decode_rsm_first(__TopXMLNS, __IgnoreEls, _el),
    -			      Count, Before, Max, Index);
    -       true ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count, Before, Max, Index)
    -    end;
    -decode_rsm_set_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"index">>, _attrs, _} = _el | _els], After,
    -		   Last, First, Count, Before, Max, Index) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count, Before, Max,
    -			      decode_rsm_index(__TopXMLNS, __IgnoreEls, _el));
    -       true ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count, Before, Max, Index)
    -    end;
    -decode_rsm_set_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"last">>, _attrs, _} = _el | _els], After,
    -		   Last, First, Count, Before, Max, Index) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      decode_rsm_last(__TopXMLNS, __IgnoreEls, _el),
    -			      First, Count, Before, Max, Index);
    -       true ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count, Before, Max, Index)
    -    end;
    -decode_rsm_set_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"max">>, _attrs, _} = _el | _els], After,
    -		   Last, First, Count, Before, Max, Index) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count, Before,
    -			      decode_rsm_max(__TopXMLNS, __IgnoreEls, _el),
    -			      Index);
    -       true ->
    -	   decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -			      Last, First, Count, Before, Max, Index)
    -    end;
    -decode_rsm_set_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   After, Last, First, Count, Before, Max, Index) ->
    -    decode_rsm_set_els(__TopXMLNS, __IgnoreEls, _els, After,
    -		       Last, First, Count, Before, Max, Index).
    -
    -encode_rsm_set({rsm_set, After, Before, Count, First,
    -		Index, Last, Max},
    -	       _xmlns_attrs) ->
    -    _els = lists:reverse('encode_rsm_set_$after'(After,
    -						 'encode_rsm_set_$last'(Last,
    -									'encode_rsm_set_$first'(First,
    -												'encode_rsm_set_$count'(Count,
    -															'encode_rsm_set_$before'(Before,
    -																		 'encode_rsm_set_$max'(Max,
    -																				       'encode_rsm_set_$index'(Index,
    -																							       [])))))))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"set">>, _attrs, _els}.
    -
    -'encode_rsm_set_$after'(undefined, _acc) -> _acc;
    -'encode_rsm_set_$after'(After, _acc) ->
    -    [encode_rsm_after(After, []) | _acc].
    -
    -'encode_rsm_set_$last'(undefined, _acc) -> _acc;
    -'encode_rsm_set_$last'(Last, _acc) ->
    -    [encode_rsm_last(Last, []) | _acc].
    -
    -'encode_rsm_set_$first'(undefined, _acc) -> _acc;
    -'encode_rsm_set_$first'(First, _acc) ->
    -    [encode_rsm_first(First, []) | _acc].
    -
    -'encode_rsm_set_$count'(undefined, _acc) -> _acc;
    -'encode_rsm_set_$count'(Count, _acc) ->
    -    [encode_rsm_count(Count, []) | _acc].
    -
    -'encode_rsm_set_$before'(undefined, _acc) -> _acc;
    -'encode_rsm_set_$before'(Before, _acc) ->
    -    [encode_rsm_before(Before, []) | _acc].
    -
    -'encode_rsm_set_$max'(undefined, _acc) -> _acc;
    -'encode_rsm_set_$max'(Max, _acc) ->
    -    [encode_rsm_max(Max, []) | _acc].
    -
    -'encode_rsm_set_$index'(undefined, _acc) -> _acc;
    -'encode_rsm_set_$index'(Index, _acc) ->
    -    [encode_rsm_index(Index, []) | _acc].
    -
    -decode_rsm_first(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"first">>, _attrs, _els}) ->
    -    Data = decode_rsm_first_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    Index = decode_rsm_first_attrs(__TopXMLNS, _attrs,
    -				   undefined),
    -    {rsm_first, Index, Data}.
    -
    -decode_rsm_first_els(__TopXMLNS, __IgnoreEls, [],
    -		     Data) ->
    -    decode_rsm_first_cdata(__TopXMLNS, Data);
    -decode_rsm_first_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Data) ->
    -    decode_rsm_first_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_rsm_first_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Data) ->
    -    decode_rsm_first_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Data).
    -
    -decode_rsm_first_attrs(__TopXMLNS,
    -		       [{<<"index">>, _val} | _attrs], _Index) ->
    -    decode_rsm_first_attrs(__TopXMLNS, _attrs, _val);
    -decode_rsm_first_attrs(__TopXMLNS, [_ | _attrs],
    -		       Index) ->
    -    decode_rsm_first_attrs(__TopXMLNS, _attrs, Index);
    -decode_rsm_first_attrs(__TopXMLNS, [], Index) ->
    -    decode_rsm_first_attr_index(__TopXMLNS, Index).
    -
    -encode_rsm_first({rsm_first, Index, Data},
    -		 _xmlns_attrs) ->
    -    _els = encode_rsm_first_cdata(Data, []),
    -    _attrs = encode_rsm_first_attr_index(Index,
    -					 _xmlns_attrs),
    -    {xmlel, <<"first">>, _attrs, _els}.
    -
    -decode_rsm_first_attr_index(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_rsm_first_attr_index(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"index">>, <<"first">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_rsm_first_attr_index(undefined, _acc) -> _acc;
    -encode_rsm_first_attr_index(_val, _acc) ->
    -    [{<<"index">>, enc_int(_val)} | _acc].
    -
    -decode_rsm_first_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_rsm_first_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_rsm_first_cdata(undefined, _acc) -> _acc;
    -encode_rsm_first_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_rsm_max(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"max">>, _attrs, _els}) ->
    -    Cdata = decode_rsm_max_els(__TopXMLNS, __IgnoreEls,
    -			       _els, <<>>),
    -    Cdata.
    -
    -decode_rsm_max_els(__TopXMLNS, __IgnoreEls, [],
    -		   Cdata) ->
    -    decode_rsm_max_cdata(__TopXMLNS, Cdata);
    -decode_rsm_max_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_rsm_max_els(__TopXMLNS, __IgnoreEls, _els,
    -		       <>);
    -decode_rsm_max_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Cdata) ->
    -    decode_rsm_max_els(__TopXMLNS, __IgnoreEls, _els,
    -		       Cdata).
    -
    -encode_rsm_max(Cdata, _xmlns_attrs) ->
    -    _els = encode_rsm_max_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"max">>, _attrs, _els}.
    -
    -decode_rsm_max_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_rsm_max_cdata(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"max">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_rsm_max_cdata(undefined, _acc) -> _acc;
    -encode_rsm_max_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_int(_val)} | _acc].
    -
    -decode_rsm_index(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"index">>, _attrs, _els}) ->
    -    Cdata = decode_rsm_index_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Cdata.
    -
    -decode_rsm_index_els(__TopXMLNS, __IgnoreEls, [],
    -		     Cdata) ->
    -    decode_rsm_index_cdata(__TopXMLNS, Cdata);
    -decode_rsm_index_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_rsm_index_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_rsm_index_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cdata) ->
    -    decode_rsm_index_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cdata).
    -
    -encode_rsm_index(Cdata, _xmlns_attrs) ->
    -    _els = encode_rsm_index_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"index">>, _attrs, _els}.
    -
    -decode_rsm_index_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_rsm_index_cdata(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"index">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_rsm_index_cdata(undefined, _acc) -> _acc;
    -encode_rsm_index_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_int(_val)} | _acc].
    -
    -decode_rsm_count(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"count">>, _attrs, _els}) ->
    -    Cdata = decode_rsm_count_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Cdata.
    -
    -decode_rsm_count_els(__TopXMLNS, __IgnoreEls, [],
    -		     Cdata) ->
    -    decode_rsm_count_cdata(__TopXMLNS, Cdata);
    -decode_rsm_count_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_rsm_count_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_rsm_count_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cdata) ->
    -    decode_rsm_count_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cdata).
    -
    -encode_rsm_count(Cdata, _xmlns_attrs) ->
    -    _els = encode_rsm_count_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"count">>, _attrs, _els}.
    -
    -decode_rsm_count_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_rsm_count_cdata(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"count">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_rsm_count_cdata(undefined, _acc) -> _acc;
    -encode_rsm_count_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_int(_val)} | _acc].
    -
    -decode_rsm_last(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"last">>, _attrs, _els}) ->
    -    Cdata = decode_rsm_last_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    Cdata.
    -
    -decode_rsm_last_els(__TopXMLNS, __IgnoreEls, [],
    -		    Cdata) ->
    -    decode_rsm_last_cdata(__TopXMLNS, Cdata);
    -decode_rsm_last_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_rsm_last_els(__TopXMLNS, __IgnoreEls, _els,
    -			<>);
    -decode_rsm_last_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Cdata) ->
    -    decode_rsm_last_els(__TopXMLNS, __IgnoreEls, _els,
    -			Cdata).
    -
    -encode_rsm_last(Cdata, _xmlns_attrs) ->
    -    _els = encode_rsm_last_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"last">>, _attrs, _els}.
    -
    -decode_rsm_last_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_rsm_last_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_rsm_last_cdata(undefined, _acc) -> _acc;
    -encode_rsm_last_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_rsm_before(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"before">>, _attrs, _els}) ->
    -    Cdata = decode_rsm_before_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_rsm_before_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_rsm_before_cdata(__TopXMLNS, Cdata);
    -decode_rsm_before_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_rsm_before_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_rsm_before_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_rsm_before_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_rsm_before(Cdata, _xmlns_attrs) ->
    -    _els = encode_rsm_before_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"before">>, _attrs, _els}.
    -
    -decode_rsm_before_cdata(__TopXMLNS, <<>>) -> none;
    -decode_rsm_before_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_rsm_before_cdata(none, _acc) -> _acc;
    -encode_rsm_before_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_rsm_after(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"after">>, _attrs, _els}) ->
    -    Cdata = decode_rsm_after_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Cdata.
    -
    -decode_rsm_after_els(__TopXMLNS, __IgnoreEls, [],
    -		     Cdata) ->
    -    decode_rsm_after_cdata(__TopXMLNS, Cdata);
    -decode_rsm_after_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_rsm_after_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_rsm_after_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cdata) ->
    -    decode_rsm_after_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cdata).
    -
    -encode_rsm_after(Cdata, _xmlns_attrs) ->
    -    _els = encode_rsm_after_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"after">>, _attrs, _els}.
    -
    -decode_rsm_after_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_rsm_after_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_rsm_after_cdata(undefined, _acc) -> _acc;
    -encode_rsm_after_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_muc(__TopXMLNS, __IgnoreEls,
    -	   {xmlel, <<"x">>, _attrs, _els}) ->
    -    History = decode_muc_els(__TopXMLNS, __IgnoreEls, _els,
    -			     undefined),
    -    Password = decode_muc_attrs(__TopXMLNS, _attrs,
    -				undefined),
    -    {muc, History, Password}.
    -
    -decode_muc_els(__TopXMLNS, __IgnoreEls, [], History) ->
    -    History;
    -decode_muc_els(__TopXMLNS, __IgnoreEls,
    -	       [{xmlel, <<"history">>, _attrs, _} = _el | _els],
    -	       History) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_els(__TopXMLNS, __IgnoreEls, _els,
    -			  decode_muc_history(__TopXMLNS, __IgnoreEls, _el));
    -       true ->
    -	   decode_muc_els(__TopXMLNS, __IgnoreEls, _els, History)
    -    end;
    -decode_muc_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -	       History) ->
    -    decode_muc_els(__TopXMLNS, __IgnoreEls, _els, History).
    -
    -decode_muc_attrs(__TopXMLNS,
    -		 [{<<"password">>, _val} | _attrs], _Password) ->
    -    decode_muc_attrs(__TopXMLNS, _attrs, _val);
    -decode_muc_attrs(__TopXMLNS, [_ | _attrs], Password) ->
    -    decode_muc_attrs(__TopXMLNS, _attrs, Password);
    -decode_muc_attrs(__TopXMLNS, [], Password) ->
    -    decode_muc_attr_password(__TopXMLNS, Password).
    -
    -encode_muc({muc, History, Password}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_muc_$history'(History,
    -					       [])),
    -    _attrs = encode_muc_attr_password(Password,
    -				      _xmlns_attrs),
    -    {xmlel, <<"x">>, _attrs, _els}.
    -
    -'encode_muc_$history'(undefined, _acc) -> _acc;
    -'encode_muc_$history'(History, _acc) ->
    -    [encode_muc_history(History, []) | _acc].
    -
    -decode_muc_attr_password(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_muc_attr_password(__TopXMLNS, _val) -> _val.
    -
    -encode_muc_attr_password(undefined, _acc) -> _acc;
    -encode_muc_attr_password(_val, _acc) ->
    -    [{<<"password">>, _val} | _acc].
    -
    -decode_muc_admin(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"query">>, _attrs, _els}) ->
    -    Items = decode_muc_admin_els(__TopXMLNS, __IgnoreEls,
    -				 _els, []),
    -    {muc_admin, Items}.
    -
    -decode_muc_admin_els(__TopXMLNS, __IgnoreEls, [],
    -		     Items) ->
    -    lists:reverse(Items);
    -decode_muc_admin_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_admin_els(__TopXMLNS, __IgnoreEls, _els,
    -				[decode_muc_admin_item(__TopXMLNS, __IgnoreEls,
    -						       _el)
    -				 | Items]);
    -       true ->
    -	   decode_muc_admin_els(__TopXMLNS, __IgnoreEls, _els,
    -				Items)
    -    end;
    -decode_muc_admin_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Items) ->
    -    decode_muc_admin_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Items).
    -
    -encode_muc_admin({muc_admin, Items}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_muc_admin_$items'(Items,
    -						   [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_muc_admin_$items'([], _acc) -> _acc;
    -'encode_muc_admin_$items'([Items | _els], _acc) ->
    -    'encode_muc_admin_$items'(_els,
    -			      [encode_muc_admin_item(Items, []) | _acc]).
    -
    -decode_muc_admin_reason(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"reason">>, _attrs, _els}) ->
    -    Cdata = decode_muc_admin_reason_els(__TopXMLNS,
    -					__IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls, [],
    -			    Cdata) ->
    -    decode_muc_admin_reason_cdata(__TopXMLNS, Cdata);
    -decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls,
    -				_els, <>);
    -decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls,
    -			    [_ | _els], Cdata) ->
    -    decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls,
    -				_els, Cdata).
    -
    -encode_muc_admin_reason(Cdata, _xmlns_attrs) ->
    -    _els = encode_muc_admin_reason_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"reason">>, _attrs, _els}.
    -
    -decode_muc_admin_reason_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_muc_admin_reason_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_muc_admin_reason_cdata(undefined, _acc) -> _acc;
    -encode_muc_admin_reason_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_muc_admin_continue(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"continue">>, _attrs, _els}) ->
    -    Thread = decode_muc_admin_continue_attrs(__TopXMLNS,
    -					     _attrs, undefined),
    -    Thread.
    -
    -decode_muc_admin_continue_attrs(__TopXMLNS,
    -				[{<<"thread">>, _val} | _attrs], _Thread) ->
    -    decode_muc_admin_continue_attrs(__TopXMLNS, _attrs,
    -				    _val);
    -decode_muc_admin_continue_attrs(__TopXMLNS,
    -				[_ | _attrs], Thread) ->
    -    decode_muc_admin_continue_attrs(__TopXMLNS, _attrs,
    -				    Thread);
    -decode_muc_admin_continue_attrs(__TopXMLNS, [],
    -				Thread) ->
    -    decode_muc_admin_continue_attr_thread(__TopXMLNS,
    -					  Thread).
    -
    -encode_muc_admin_continue(Thread, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_muc_admin_continue_attr_thread(Thread,
    -						   _xmlns_attrs),
    -    {xmlel, <<"continue">>, _attrs, _els}.
    -
    -decode_muc_admin_continue_attr_thread(__TopXMLNS,
    -				      undefined) ->
    -    undefined;
    -decode_muc_admin_continue_attr_thread(__TopXMLNS,
    -				      _val) ->
    -    _val.
    -
    -encode_muc_admin_continue_attr_thread(undefined,
    -				      _acc) ->
    -    _acc;
    -encode_muc_admin_continue_attr_thread(_val, _acc) ->
    -    [{<<"thread">>, _val} | _acc].
    -
    -decode_muc_admin_actor(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"actor">>, _attrs, _els}) ->
    -    {Jid, Nick} = decode_muc_admin_actor_attrs(__TopXMLNS,
    -					       _attrs, undefined, undefined),
    -    {muc_actor, Jid, Nick}.
    -
    -decode_muc_admin_actor_attrs(__TopXMLNS,
    -			     [{<<"jid">>, _val} | _attrs], _Jid, Nick) ->
    -    decode_muc_admin_actor_attrs(__TopXMLNS, _attrs, _val,
    -				 Nick);
    -decode_muc_admin_actor_attrs(__TopXMLNS,
    -			     [{<<"nick">>, _val} | _attrs], Jid, _Nick) ->
    -    decode_muc_admin_actor_attrs(__TopXMLNS, _attrs, Jid,
    -				 _val);
    -decode_muc_admin_actor_attrs(__TopXMLNS, [_ | _attrs],
    -			     Jid, Nick) ->
    -    decode_muc_admin_actor_attrs(__TopXMLNS, _attrs, Jid,
    -				 Nick);
    -decode_muc_admin_actor_attrs(__TopXMLNS, [], Jid,
    -			     Nick) ->
    -    {decode_muc_admin_actor_attr_jid(__TopXMLNS, Jid),
    -     decode_muc_admin_actor_attr_nick(__TopXMLNS, Nick)}.
    -
    -encode_muc_admin_actor({muc_actor, Jid, Nick},
    -		       _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_muc_admin_actor_attr_nick(Nick,
    -					      encode_muc_admin_actor_attr_jid(Jid,
    -									      _xmlns_attrs)),
    -    {xmlel, <<"actor">>, _attrs, _els}.
    -
    -decode_muc_admin_actor_attr_jid(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_muc_admin_actor_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"actor">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_admin_actor_attr_jid(undefined, _acc) ->
    -    _acc;
    -encode_muc_admin_actor_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_admin_actor_attr_nick(__TopXMLNS,
    -				 undefined) ->
    -    undefined;
    -decode_muc_admin_actor_attr_nick(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_muc_admin_actor_attr_nick(undefined, _acc) ->
    -    _acc;
    -encode_muc_admin_actor_attr_nick(_val, _acc) ->
    -    [{<<"nick">>, _val} | _acc].
    -
    -decode_muc_admin_item(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"item">>, _attrs, _els}) ->
    -    {Actor, Continue, Reason} =
    -	decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				  undefined, undefined, undefined),
    -    {Affiliation, Role, Jid, Nick} =
    -	decode_muc_admin_item_attrs(__TopXMLNS, _attrs,
    -				    undefined, undefined, undefined, undefined),
    -    {muc_item, Actor, Continue, Reason, Affiliation, Role,
    -     Jid, Nick}.
    -
    -decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, [],
    -			  Actor, Continue, Reason) ->
    -    {Actor, Continue, Reason};
    -decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlel, <<"actor">>, _attrs, _} = _el | _els], Actor,
    -			  Continue, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				     decode_muc_admin_actor(__TopXMLNS,
    -							    __IgnoreEls, _el),
    -				     Continue, Reason);
    -       true ->
    -	   decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Actor, Continue, Reason)
    -    end;
    -decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlel, <<"continue">>, _attrs, _} = _el | _els],
    -			  Actor, Continue, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Actor,
    -				     decode_muc_admin_continue(__TopXMLNS,
    -							       __IgnoreEls,
    -							       _el),
    -				     Reason);
    -       true ->
    -	   decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Actor, Continue, Reason)
    -    end;
    -decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
    -			  Actor, Continue, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Actor, Continue,
    -				     decode_muc_admin_reason(__TopXMLNS,
    -							     __IgnoreEls, _el));
    -       true ->
    -	   decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Actor, Continue, Reason)
    -    end;
    -decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Actor, Continue, Reason) ->
    -    decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Actor, Continue, Reason).
    -
    -decode_muc_admin_item_attrs(__TopXMLNS,
    -			    [{<<"affiliation">>, _val} | _attrs], _Affiliation,
    -			    Role, Jid, Nick) ->
    -    decode_muc_admin_item_attrs(__TopXMLNS, _attrs, _val,
    -				Role, Jid, Nick);
    -decode_muc_admin_item_attrs(__TopXMLNS,
    -			    [{<<"role">>, _val} | _attrs], Affiliation, _Role,
    -			    Jid, Nick) ->
    -    decode_muc_admin_item_attrs(__TopXMLNS, _attrs,
    -				Affiliation, _val, Jid, Nick);
    -decode_muc_admin_item_attrs(__TopXMLNS,
    -			    [{<<"jid">>, _val} | _attrs], Affiliation, Role,
    -			    _Jid, Nick) ->
    -    decode_muc_admin_item_attrs(__TopXMLNS, _attrs,
    -				Affiliation, Role, _val, Nick);
    -decode_muc_admin_item_attrs(__TopXMLNS,
    -			    [{<<"nick">>, _val} | _attrs], Affiliation, Role,
    -			    Jid, _Nick) ->
    -    decode_muc_admin_item_attrs(__TopXMLNS, _attrs,
    -				Affiliation, Role, Jid, _val);
    -decode_muc_admin_item_attrs(__TopXMLNS, [_ | _attrs],
    -			    Affiliation, Role, Jid, Nick) ->
    -    decode_muc_admin_item_attrs(__TopXMLNS, _attrs,
    -				Affiliation, Role, Jid, Nick);
    -decode_muc_admin_item_attrs(__TopXMLNS, [], Affiliation,
    -			    Role, Jid, Nick) ->
    -    {decode_muc_admin_item_attr_affiliation(__TopXMLNS,
    -					    Affiliation),
    -     decode_muc_admin_item_attr_role(__TopXMLNS, Role),
    -     decode_muc_admin_item_attr_jid(__TopXMLNS, Jid),
    -     decode_muc_admin_item_attr_nick(__TopXMLNS, Nick)}.
    -
    -encode_muc_admin_item({muc_item, Actor, Continue,
    -		       Reason, Affiliation, Role, Jid, Nick},
    -		      _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_muc_admin_item_$actor'(Actor,
    -						     'encode_muc_admin_item_$continue'(Continue,
    -										       'encode_muc_admin_item_$reason'(Reason,
    -														       [])))),
    -    _attrs = encode_muc_admin_item_attr_nick(Nick,
    -					     encode_muc_admin_item_attr_jid(Jid,
    -									    encode_muc_admin_item_attr_role(Role,
    -													    encode_muc_admin_item_attr_affiliation(Affiliation,
    -																		   _xmlns_attrs)))),
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -'encode_muc_admin_item_$actor'(undefined, _acc) -> _acc;
    -'encode_muc_admin_item_$actor'(Actor, _acc) ->
    -    [encode_muc_admin_actor(Actor, []) | _acc].
    -
    -'encode_muc_admin_item_$continue'(undefined, _acc) ->
    -    _acc;
    -'encode_muc_admin_item_$continue'(Continue, _acc) ->
    -    [encode_muc_admin_continue(Continue, []) | _acc].
    -
    -'encode_muc_admin_item_$reason'(undefined, _acc) ->
    -    _acc;
    -'encode_muc_admin_item_$reason'(Reason, _acc) ->
    -    [encode_muc_admin_reason(Reason, []) | _acc].
    -
    -decode_muc_admin_item_attr_affiliation(__TopXMLNS,
    -				       undefined) ->
    -    undefined;
    -decode_muc_admin_item_attr_affiliation(__TopXMLNS,
    -				       _val) ->
    -    case catch dec_enum(_val,
    -			[admin, member, none, outcast, owner])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"affiliation">>, <<"item">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_admin_item_attr_affiliation(undefined,
    -				       _acc) ->
    -    _acc;
    -encode_muc_admin_item_attr_affiliation(_val, _acc) ->
    -    [{<<"affiliation">>, enc_enum(_val)} | _acc].
    -
    -decode_muc_admin_item_attr_role(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_muc_admin_item_attr_role(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val,
    -			[moderator, none, participant, visitor])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"role">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_admin_item_attr_role(undefined, _acc) ->
    -    _acc;
    -encode_muc_admin_item_attr_role(_val, _acc) ->
    -    [{<<"role">>, enc_enum(_val)} | _acc].
    -
    -decode_muc_admin_item_attr_jid(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_muc_admin_item_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_admin_item_attr_jid(undefined, _acc) -> _acc;
    -encode_muc_admin_item_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_admin_item_attr_nick(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_muc_admin_item_attr_nick(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_muc_admin_item_attr_nick(undefined, _acc) ->
    -    _acc;
    -encode_muc_admin_item_attr_nick(_val, _acc) ->
    -    [{<<"nick">>, _val} | _acc].
    -
    -decode_muc_owner(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"query">>, _attrs, _els}) ->
    -    {Config, Destroy} = decode_muc_owner_els(__TopXMLNS,
    -					     __IgnoreEls, _els, undefined,
    -					     undefined),
    -    {muc_owner, Destroy, Config}.
    -
    -decode_muc_owner_els(__TopXMLNS, __IgnoreEls, [],
    -		     Config, Destroy) ->
    -    {Config, Destroy};
    -decode_muc_owner_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"destroy">>, _attrs, _} = _el | _els],
    -		     Config, Destroy) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
    -				Config,
    -				decode_muc_owner_destroy(__TopXMLNS,
    -							 __IgnoreEls, _el));
    -       true ->
    -	   decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
    -				Config, Destroy)
    -    end;
    -decode_muc_owner_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"x">>, _attrs, _} = _el | _els], Config,
    -		     Destroy) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"jabber:x:data">> ->
    -	   decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_xdata(_xmlns, __IgnoreEls, _el),
    -				Destroy);
    -       true ->
    -	   decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
    -				Config, Destroy)
    -    end;
    -decode_muc_owner_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Config, Destroy) ->
    -    decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Config, Destroy).
    -
    -encode_muc_owner({muc_owner, Destroy, Config},
    -		 _xmlns_attrs) ->
    -    _els = lists:reverse('encode_muc_owner_$config'(Config,
    -						    'encode_muc_owner_$destroy'(Destroy,
    -										[]))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_muc_owner_$config'(undefined, _acc) -> _acc;
    -'encode_muc_owner_$config'(Config, _acc) ->
    -    [encode_xdata(Config,
    -		  [{<<"xmlns">>, <<"jabber:x:data">>}])
    -     | _acc].
    -
    -'encode_muc_owner_$destroy'(undefined, _acc) -> _acc;
    -'encode_muc_owner_$destroy'(Destroy, _acc) ->
    -    [encode_muc_owner_destroy(Destroy, []) | _acc].
    -
    -decode_muc_owner_destroy(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"destroy">>, _attrs, _els}) ->
    -    {Password, Reason} =
    -	decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -				     _els, undefined, undefined),
    -    Jid = decode_muc_owner_destroy_attrs(__TopXMLNS, _attrs,
    -					 undefined),
    -    {muc_owner_destroy, Jid, Reason, Password}.
    -
    -decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -			     [], Password, Reason) ->
    -    {Password, Reason};
    -decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlel, <<"password">>, _attrs, _} = _el | _els],
    -			     Password, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -					_els,
    -					decode_muc_owner_password(__TopXMLNS,
    -								  __IgnoreEls,
    -								  _el),
    -					Reason);
    -       true ->
    -	   decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -					_els, Password, Reason)
    -    end;
    -decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
    -			     Password, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -					_els, Password,
    -					decode_muc_owner_reason(__TopXMLNS,
    -								__IgnoreEls,
    -								_el));
    -       true ->
    -	   decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -					_els, Password, Reason)
    -    end;
    -decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], Password, Reason) ->
    -    decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
    -				 _els, Password, Reason).
    -
    -decode_muc_owner_destroy_attrs(__TopXMLNS,
    -			       [{<<"jid">>, _val} | _attrs], _Jid) ->
    -    decode_muc_owner_destroy_attrs(__TopXMLNS, _attrs,
    -				   _val);
    -decode_muc_owner_destroy_attrs(__TopXMLNS, [_ | _attrs],
    -			       Jid) ->
    -    decode_muc_owner_destroy_attrs(__TopXMLNS, _attrs, Jid);
    -decode_muc_owner_destroy_attrs(__TopXMLNS, [], Jid) ->
    -    decode_muc_owner_destroy_attr_jid(__TopXMLNS, Jid).
    -
    -encode_muc_owner_destroy({muc_owner_destroy, Jid,
    -			  Reason, Password},
    -			 _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_muc_owner_destroy_$password'(Password,
    -							   'encode_muc_owner_destroy_$reason'(Reason,
    -											      []))),
    -    _attrs = encode_muc_owner_destroy_attr_jid(Jid,
    -					       _xmlns_attrs),
    -    {xmlel, <<"destroy">>, _attrs, _els}.
    -
    -'encode_muc_owner_destroy_$password'(undefined, _acc) ->
    -    _acc;
    -'encode_muc_owner_destroy_$password'(Password, _acc) ->
    -    [encode_muc_owner_password(Password, []) | _acc].
    -
    -'encode_muc_owner_destroy_$reason'(undefined, _acc) ->
    -    _acc;
    -'encode_muc_owner_destroy_$reason'(Reason, _acc) ->
    -    [encode_muc_owner_reason(Reason, []) | _acc].
    -
    -decode_muc_owner_destroy_attr_jid(__TopXMLNS,
    -				  undefined) ->
    -    undefined;
    -decode_muc_owner_destroy_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"destroy">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_owner_destroy_attr_jid(undefined, _acc) ->
    -    _acc;
    -encode_muc_owner_destroy_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_owner_reason(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"reason">>, _attrs, _els}) ->
    -    Cdata = decode_muc_owner_reason_els(__TopXMLNS,
    -					__IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls, [],
    -			    Cdata) ->
    -    decode_muc_owner_reason_cdata(__TopXMLNS, Cdata);
    -decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls,
    -				_els, <>);
    -decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls,
    -			    [_ | _els], Cdata) ->
    -    decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls,
    -				_els, Cdata).
    -
    -encode_muc_owner_reason(Cdata, _xmlns_attrs) ->
    -    _els = encode_muc_owner_reason_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"reason">>, _attrs, _els}.
    -
    -decode_muc_owner_reason_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_muc_owner_reason_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_muc_owner_reason_cdata(undefined, _acc) -> _acc;
    -encode_muc_owner_reason_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_muc_owner_password(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"password">>, _attrs, _els}) ->
    -    Cdata = decode_muc_owner_password_els(__TopXMLNS,
    -					  __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
    -			      [], Cdata) ->
    -    decode_muc_owner_password_cdata(__TopXMLNS, Cdata);
    -decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
    -			      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <>);
    -decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
    -			      [_ | _els], Cdata) ->
    -    decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
    -				  _els, Cdata).
    -
    -encode_muc_owner_password(Cdata, _xmlns_attrs) ->
    -    _els = encode_muc_owner_password_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"password">>, _attrs, _els}.
    -
    -decode_muc_owner_password_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_muc_owner_password_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_muc_owner_password_cdata(undefined, _acc) ->
    -    _acc;
    -encode_muc_owner_password_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_muc_user(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"x">>, _attrs, _els}) ->
    -    {Status_codes, Items, Invites, Decline, Destroy} =
    -	decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, [],
    -			    [], [], undefined, undefined),
    -    Password = decode_muc_user_attrs(__TopXMLNS, _attrs,
    -				     undefined),
    -    {muc_user, Decline, Destroy, Invites, Items,
    -     Status_codes, Password}.
    -
    -decode_muc_user_els(__TopXMLNS, __IgnoreEls, [],
    -		    Status_codes, Items, Invites, Decline, Destroy) ->
    -    {lists:reverse(Status_codes), lists:reverse(Items),
    -     lists:reverse(Invites), Decline, Destroy};
    -decode_muc_user_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"decline">>, _attrs, _} = _el | _els],
    -		    Status_codes, Items, Invites, Decline, Destroy) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Status_codes, Items, Invites,
    -			       decode_muc_user_decline(__TopXMLNS, __IgnoreEls,
    -						       _el),
    -			       Destroy);
    -       true ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Status_codes, Items, Invites, Decline, Destroy)
    -    end;
    -decode_muc_user_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"destroy">>, _attrs, _} = _el | _els],
    -		    Status_codes, Items, Invites, Decline, Destroy) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Status_codes, Items, Invites, Decline,
    -			       decode_muc_user_destroy(__TopXMLNS, __IgnoreEls,
    -						       _el));
    -       true ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Status_codes, Items, Invites, Decline, Destroy)
    -    end;
    -decode_muc_user_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"invite">>, _attrs, _} = _el | _els],
    -		    Status_codes, Items, Invites, Decline, Destroy) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Status_codes, Items,
    -			       [decode_muc_user_invite(__TopXMLNS, __IgnoreEls,
    -						       _el)
    -				| Invites],
    -			       Decline, Destroy);
    -       true ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Status_codes, Items, Invites, Decline, Destroy)
    -    end;
    -decode_muc_user_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"item">>, _attrs, _} = _el | _els],
    -		    Status_codes, Items, Invites, Decline, Destroy) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Status_codes,
    -			       [decode_muc_user_item(__TopXMLNS, __IgnoreEls,
    -						     _el)
    -				| Items],
    -			       Invites, Decline, Destroy);
    -       true ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Status_codes, Items, Invites, Decline, Destroy)
    -    end;
    -decode_muc_user_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"status">>, _attrs, _} = _el | _els],
    -		    Status_codes, Items, Invites, Decline, Destroy) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       case decode_muc_user_status(__TopXMLNS,
    -							   __IgnoreEls, _el)
    -				   of
    -				 undefined -> Status_codes;
    -				 _new_el -> [_new_el | Status_codes]
    -			       end,
    -			       Items, Invites, Decline, Destroy);
    -       true ->
    -	   decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Status_codes, Items, Invites, Decline, Destroy)
    -    end;
    -decode_muc_user_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Status_codes, Items, Invites, Decline, Destroy) ->
    -    decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
    -			Status_codes, Items, Invites, Decline, Destroy).
    -
    -decode_muc_user_attrs(__TopXMLNS,
    -		      [{<<"password">>, _val} | _attrs], _Password) ->
    -    decode_muc_user_attrs(__TopXMLNS, _attrs, _val);
    -decode_muc_user_attrs(__TopXMLNS, [_ | _attrs],
    -		      Password) ->
    -    decode_muc_user_attrs(__TopXMLNS, _attrs, Password);
    -decode_muc_user_attrs(__TopXMLNS, [], Password) ->
    -    decode_muc_user_attr_password(__TopXMLNS, Password).
    -
    -encode_muc_user({muc_user, Decline, Destroy, Invites,
    -		 Items, Status_codes, Password},
    -		_xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_muc_user_$status_codes'(Status_codes,
    -						      'encode_muc_user_$items'(Items,
    -									       'encode_muc_user_$invites'(Invites,
    -													  'encode_muc_user_$decline'(Decline,
    -																     'encode_muc_user_$destroy'(Destroy,
    -																				[])))))),
    -    _attrs = encode_muc_user_attr_password(Password,
    -					   _xmlns_attrs),
    -    {xmlel, <<"x">>, _attrs, _els}.
    -
    -'encode_muc_user_$status_codes'([], _acc) -> _acc;
    -'encode_muc_user_$status_codes'([Status_codes | _els],
    -				_acc) ->
    -    'encode_muc_user_$status_codes'(_els,
    -				    [encode_muc_user_status(Status_codes, [])
    -				     | _acc]).
    -
    -'encode_muc_user_$items'([], _acc) -> _acc;
    -'encode_muc_user_$items'([Items | _els], _acc) ->
    -    'encode_muc_user_$items'(_els,
    -			     [encode_muc_user_item(Items, []) | _acc]).
    -
    -'encode_muc_user_$invites'([], _acc) -> _acc;
    -'encode_muc_user_$invites'([Invites | _els], _acc) ->
    -    'encode_muc_user_$invites'(_els,
    -			       [encode_muc_user_invite(Invites, []) | _acc]).
    -
    -'encode_muc_user_$decline'(undefined, _acc) -> _acc;
    -'encode_muc_user_$decline'(Decline, _acc) ->
    -    [encode_muc_user_decline(Decline, []) | _acc].
    -
    -'encode_muc_user_$destroy'(undefined, _acc) -> _acc;
    -'encode_muc_user_$destroy'(Destroy, _acc) ->
    -    [encode_muc_user_destroy(Destroy, []) | _acc].
    -
    -decode_muc_user_attr_password(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_muc_user_attr_password(__TopXMLNS, _val) -> _val.
    -
    -encode_muc_user_attr_password(undefined, _acc) -> _acc;
    -encode_muc_user_attr_password(_val, _acc) ->
    -    [{<<"password">>, _val} | _acc].
    -
    -decode_muc_user_item(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"item">>, _attrs, _els}) ->
    -    {Actor, Continue, Reason} =
    -	decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				 undefined, undefined, undefined),
    -    {Affiliation, Role, Jid, Nick} =
    -	decode_muc_user_item_attrs(__TopXMLNS, _attrs,
    -				   undefined, undefined, undefined, undefined),
    -    {muc_item, Actor, Continue, Reason, Affiliation, Role,
    -     Jid, Nick}.
    -
    -decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, [],
    -			 Actor, Continue, Reason) ->
    -    {Actor, Continue, Reason};
    -decode_muc_user_item_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlel, <<"actor">>, _attrs, _} = _el | _els], Actor,
    -			 Continue, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				    decode_muc_user_actor(__TopXMLNS,
    -							  __IgnoreEls, _el),
    -				    Continue, Reason);
    -       true ->
    -	   decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				    Actor, Continue, Reason)
    -    end;
    -decode_muc_user_item_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlel, <<"continue">>, _attrs, _} = _el | _els],
    -			 Actor, Continue, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				    Actor,
    -				    decode_muc_user_continue(__TopXMLNS,
    -							     __IgnoreEls, _el),
    -				    Reason);
    -       true ->
    -	   decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				    Actor, Continue, Reason)
    -    end;
    -decode_muc_user_item_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlel, <<"reason">>, _attrs, _} = _el | _els], Actor,
    -			 Continue, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				    Actor, Continue,
    -				    decode_muc_user_reason(__TopXMLNS,
    -							   __IgnoreEls, _el));
    -       true ->
    -	   decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				    Actor, Continue, Reason)
    -    end;
    -decode_muc_user_item_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Actor, Continue, Reason) ->
    -    decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Actor, Continue, Reason).
    -
    -decode_muc_user_item_attrs(__TopXMLNS,
    -			   [{<<"affiliation">>, _val} | _attrs], _Affiliation,
    -			   Role, Jid, Nick) ->
    -    decode_muc_user_item_attrs(__TopXMLNS, _attrs, _val,
    -			       Role, Jid, Nick);
    -decode_muc_user_item_attrs(__TopXMLNS,
    -			   [{<<"role">>, _val} | _attrs], Affiliation, _Role,
    -			   Jid, Nick) ->
    -    decode_muc_user_item_attrs(__TopXMLNS, _attrs,
    -			       Affiliation, _val, Jid, Nick);
    -decode_muc_user_item_attrs(__TopXMLNS,
    -			   [{<<"jid">>, _val} | _attrs], Affiliation, Role,
    -			   _Jid, Nick) ->
    -    decode_muc_user_item_attrs(__TopXMLNS, _attrs,
    -			       Affiliation, Role, _val, Nick);
    -decode_muc_user_item_attrs(__TopXMLNS,
    -			   [{<<"nick">>, _val} | _attrs], Affiliation, Role,
    -			   Jid, _Nick) ->
    -    decode_muc_user_item_attrs(__TopXMLNS, _attrs,
    -			       Affiliation, Role, Jid, _val);
    -decode_muc_user_item_attrs(__TopXMLNS, [_ | _attrs],
    -			   Affiliation, Role, Jid, Nick) ->
    -    decode_muc_user_item_attrs(__TopXMLNS, _attrs,
    -			       Affiliation, Role, Jid, Nick);
    -decode_muc_user_item_attrs(__TopXMLNS, [], Affiliation,
    -			   Role, Jid, Nick) ->
    -    {decode_muc_user_item_attr_affiliation(__TopXMLNS,
    -					   Affiliation),
    -     decode_muc_user_item_attr_role(__TopXMLNS, Role),
    -     decode_muc_user_item_attr_jid(__TopXMLNS, Jid),
    -     decode_muc_user_item_attr_nick(__TopXMLNS, Nick)}.
    -
    -encode_muc_user_item({muc_item, Actor, Continue, Reason,
    -		      Affiliation, Role, Jid, Nick},
    -		     _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_muc_user_item_$actor'(Actor,
    -						    'encode_muc_user_item_$continue'(Continue,
    -										     'encode_muc_user_item_$reason'(Reason,
    -														    [])))),
    -    _attrs = encode_muc_user_item_attr_nick(Nick,
    -					    encode_muc_user_item_attr_jid(Jid,
    -									  encode_muc_user_item_attr_role(Role,
    -													 encode_muc_user_item_attr_affiliation(Affiliation,
    -																	       _xmlns_attrs)))),
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -'encode_muc_user_item_$actor'(undefined, _acc) -> _acc;
    -'encode_muc_user_item_$actor'(Actor, _acc) ->
    -    [encode_muc_user_actor(Actor, []) | _acc].
    -
    -'encode_muc_user_item_$continue'(undefined, _acc) ->
    -    _acc;
    -'encode_muc_user_item_$continue'(Continue, _acc) ->
    -    [encode_muc_user_continue(Continue, []) | _acc].
    -
    -'encode_muc_user_item_$reason'(undefined, _acc) -> _acc;
    -'encode_muc_user_item_$reason'(Reason, _acc) ->
    -    [encode_muc_user_reason(Reason, []) | _acc].
    -
    -decode_muc_user_item_attr_affiliation(__TopXMLNS,
    -				      undefined) ->
    -    undefined;
    -decode_muc_user_item_attr_affiliation(__TopXMLNS,
    -				      _val) ->
    -    case catch dec_enum(_val,
    -			[admin, member, none, outcast, owner])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"affiliation">>, <<"item">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_item_attr_affiliation(undefined,
    -				      _acc) ->
    -    _acc;
    -encode_muc_user_item_attr_affiliation(_val, _acc) ->
    -    [{<<"affiliation">>, enc_enum(_val)} | _acc].
    -
    -decode_muc_user_item_attr_role(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_muc_user_item_attr_role(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val,
    -			[moderator, none, participant, visitor])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"role">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_item_attr_role(undefined, _acc) -> _acc;
    -encode_muc_user_item_attr_role(_val, _acc) ->
    -    [{<<"role">>, enc_enum(_val)} | _acc].
    -
    -decode_muc_user_item_attr_jid(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_muc_user_item_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_item_attr_jid(undefined, _acc) -> _acc;
    -encode_muc_user_item_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_user_item_attr_nick(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_muc_user_item_attr_nick(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_muc_user_item_attr_nick(undefined, _acc) -> _acc;
    -encode_muc_user_item_attr_nick(_val, _acc) ->
    -    [{<<"nick">>, _val} | _acc].
    -
    -decode_muc_user_status(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"status">>, _attrs, _els}) ->
    -    Code = decode_muc_user_status_attrs(__TopXMLNS, _attrs,
    -					undefined),
    -    Code.
    -
    -decode_muc_user_status_attrs(__TopXMLNS,
    -			     [{<<"code">>, _val} | _attrs], _Code) ->
    -    decode_muc_user_status_attrs(__TopXMLNS, _attrs, _val);
    -decode_muc_user_status_attrs(__TopXMLNS, [_ | _attrs],
    -			     Code) ->
    -    decode_muc_user_status_attrs(__TopXMLNS, _attrs, Code);
    -decode_muc_user_status_attrs(__TopXMLNS, [], Code) ->
    -    decode_muc_user_status_attr_code(__TopXMLNS, Code).
    -
    -encode_muc_user_status(Code, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_muc_user_status_attr_code(Code,
    -					      _xmlns_attrs),
    -    {xmlel, <<"status">>, _attrs, _els}.
    -
    -decode_muc_user_status_attr_code(__TopXMLNS,
    -				 undefined) ->
    -    undefined;
    -decode_muc_user_status_attr_code(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 100, 999) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"code">>, <<"status">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_status_attr_code(undefined, _acc) ->
    -    _acc;
    -encode_muc_user_status_attr_code(_val, _acc) ->
    -    [{<<"code">>, enc_int(_val)} | _acc].
    -
    -decode_muc_user_continue(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"continue">>, _attrs, _els}) ->
    -    Thread = decode_muc_user_continue_attrs(__TopXMLNS,
    -					    _attrs, undefined),
    -    Thread.
    -
    -decode_muc_user_continue_attrs(__TopXMLNS,
    -			       [{<<"thread">>, _val} | _attrs], _Thread) ->
    -    decode_muc_user_continue_attrs(__TopXMLNS, _attrs,
    -				   _val);
    -decode_muc_user_continue_attrs(__TopXMLNS, [_ | _attrs],
    -			       Thread) ->
    -    decode_muc_user_continue_attrs(__TopXMLNS, _attrs,
    -				   Thread);
    -decode_muc_user_continue_attrs(__TopXMLNS, [],
    -			       Thread) ->
    -    decode_muc_user_continue_attr_thread(__TopXMLNS,
    -					 Thread).
    -
    -encode_muc_user_continue(Thread, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_muc_user_continue_attr_thread(Thread,
    -						  _xmlns_attrs),
    -    {xmlel, <<"continue">>, _attrs, _els}.
    -
    -decode_muc_user_continue_attr_thread(__TopXMLNS,
    -				     undefined) ->
    -    undefined;
    -decode_muc_user_continue_attr_thread(__TopXMLNS,
    -				     _val) ->
    -    _val.
    -
    -encode_muc_user_continue_attr_thread(undefined, _acc) ->
    -    _acc;
    -encode_muc_user_continue_attr_thread(_val, _acc) ->
    -    [{<<"thread">>, _val} | _acc].
    -
    -decode_muc_user_actor(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"actor">>, _attrs, _els}) ->
    -    {Jid, Nick} = decode_muc_user_actor_attrs(__TopXMLNS,
    -					      _attrs, undefined, undefined),
    -    {muc_actor, Jid, Nick}.
    -
    -decode_muc_user_actor_attrs(__TopXMLNS,
    -			    [{<<"jid">>, _val} | _attrs], _Jid, Nick) ->
    -    decode_muc_user_actor_attrs(__TopXMLNS, _attrs, _val,
    -				Nick);
    -decode_muc_user_actor_attrs(__TopXMLNS,
    -			    [{<<"nick">>, _val} | _attrs], Jid, _Nick) ->
    -    decode_muc_user_actor_attrs(__TopXMLNS, _attrs, Jid,
    -				_val);
    -decode_muc_user_actor_attrs(__TopXMLNS, [_ | _attrs],
    -			    Jid, Nick) ->
    -    decode_muc_user_actor_attrs(__TopXMLNS, _attrs, Jid,
    -				Nick);
    -decode_muc_user_actor_attrs(__TopXMLNS, [], Jid,
    -			    Nick) ->
    -    {decode_muc_user_actor_attr_jid(__TopXMLNS, Jid),
    -     decode_muc_user_actor_attr_nick(__TopXMLNS, Nick)}.
    -
    -encode_muc_user_actor({muc_actor, Jid, Nick},
    -		      _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_muc_user_actor_attr_nick(Nick,
    -					     encode_muc_user_actor_attr_jid(Jid,
    -									    _xmlns_attrs)),
    -    {xmlel, <<"actor">>, _attrs, _els}.
    -
    -decode_muc_user_actor_attr_jid(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_muc_user_actor_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"actor">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_actor_attr_jid(undefined, _acc) -> _acc;
    -encode_muc_user_actor_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_user_actor_attr_nick(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_muc_user_actor_attr_nick(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_muc_user_actor_attr_nick(undefined, _acc) ->
    -    _acc;
    -encode_muc_user_actor_attr_nick(_val, _acc) ->
    -    [{<<"nick">>, _val} | _acc].
    -
    -decode_muc_user_invite(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"invite">>, _attrs, _els}) ->
    -    Reason = decode_muc_user_invite_els(__TopXMLNS,
    -					__IgnoreEls, _els, undefined),
    -    {To, From} = decode_muc_user_invite_attrs(__TopXMLNS,
    -					      _attrs, undefined, undefined),
    -    {muc_invite, Reason, From, To}.
    -
    -decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, [],
    -			   Reason) ->
    -    Reason;
    -decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
    -			   [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
    -			   Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
    -				      _els,
    -				      decode_muc_user_reason(__TopXMLNS,
    -							     __IgnoreEls, _el));
    -       true ->
    -	   decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
    -				      _els, Reason)
    -    end;
    -decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
    -			   [_ | _els], Reason) ->
    -    decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
    -			       _els, Reason).
    -
    -decode_muc_user_invite_attrs(__TopXMLNS,
    -			     [{<<"to">>, _val} | _attrs], _To, From) ->
    -    decode_muc_user_invite_attrs(__TopXMLNS, _attrs, _val,
    -				 From);
    -decode_muc_user_invite_attrs(__TopXMLNS,
    -			     [{<<"from">>, _val} | _attrs], To, _From) ->
    -    decode_muc_user_invite_attrs(__TopXMLNS, _attrs, To,
    -				 _val);
    -decode_muc_user_invite_attrs(__TopXMLNS, [_ | _attrs],
    -			     To, From) ->
    -    decode_muc_user_invite_attrs(__TopXMLNS, _attrs, To,
    -				 From);
    -decode_muc_user_invite_attrs(__TopXMLNS, [], To,
    -			     From) ->
    -    {decode_muc_user_invite_attr_to(__TopXMLNS, To),
    -     decode_muc_user_invite_attr_from(__TopXMLNS, From)}.
    -
    -encode_muc_user_invite({muc_invite, Reason, From, To},
    -		       _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_muc_user_invite_$reason'(Reason,
    -						       [])),
    -    _attrs = encode_muc_user_invite_attr_from(From,
    -					      encode_muc_user_invite_attr_to(To,
    -									     _xmlns_attrs)),
    -    {xmlel, <<"invite">>, _attrs, _els}.
    -
    -'encode_muc_user_invite_$reason'(undefined, _acc) ->
    -    _acc;
    -'encode_muc_user_invite_$reason'(Reason, _acc) ->
    -    [encode_muc_user_reason(Reason, []) | _acc].
    -
    -decode_muc_user_invite_attr_to(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_muc_user_invite_attr_to(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"to">>, <<"invite">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_invite_attr_to(undefined, _acc) -> _acc;
    -encode_muc_user_invite_attr_to(_val, _acc) ->
    -    [{<<"to">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_user_invite_attr_from(__TopXMLNS,
    -				 undefined) ->
    -    undefined;
    -decode_muc_user_invite_attr_from(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"from">>, <<"invite">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_invite_attr_from(undefined, _acc) ->
    -    _acc;
    -encode_muc_user_invite_attr_from(_val, _acc) ->
    -    [{<<"from">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_user_destroy(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"destroy">>, _attrs, _els}) ->
    -    Reason = decode_muc_user_destroy_els(__TopXMLNS,
    -					 __IgnoreEls, _els, undefined),
    -    Jid = decode_muc_user_destroy_attrs(__TopXMLNS, _attrs,
    -					undefined),
    -    {muc_user_destroy, Reason, Jid}.
    -
    -decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls, [],
    -			    Reason) ->
    -    Reason;
    -decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
    -			    Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
    -				       _els,
    -				       decode_muc_user_reason(__TopXMLNS,
    -							      __IgnoreEls,
    -							      _el));
    -       true ->
    -	   decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
    -				       _els, Reason)
    -    end;
    -decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
    -			    [_ | _els], Reason) ->
    -    decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
    -				_els, Reason).
    -
    -decode_muc_user_destroy_attrs(__TopXMLNS,
    -			      [{<<"jid">>, _val} | _attrs], _Jid) ->
    -    decode_muc_user_destroy_attrs(__TopXMLNS, _attrs, _val);
    -decode_muc_user_destroy_attrs(__TopXMLNS, [_ | _attrs],
    -			      Jid) ->
    -    decode_muc_user_destroy_attrs(__TopXMLNS, _attrs, Jid);
    -decode_muc_user_destroy_attrs(__TopXMLNS, [], Jid) ->
    -    decode_muc_user_destroy_attr_jid(__TopXMLNS, Jid).
    -
    -encode_muc_user_destroy({muc_user_destroy, Reason, Jid},
    -			_xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_muc_user_destroy_$reason'(Reason,
    -							[])),
    -    _attrs = encode_muc_user_destroy_attr_jid(Jid,
    -					      _xmlns_attrs),
    -    {xmlel, <<"destroy">>, _attrs, _els}.
    -
    -'encode_muc_user_destroy_$reason'(undefined, _acc) ->
    -    _acc;
    -'encode_muc_user_destroy_$reason'(Reason, _acc) ->
    -    [encode_muc_user_reason(Reason, []) | _acc].
    -
    -decode_muc_user_destroy_attr_jid(__TopXMLNS,
    -				 undefined) ->
    -    undefined;
    -decode_muc_user_destroy_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"destroy">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_destroy_attr_jid(undefined, _acc) ->
    -    _acc;
    -encode_muc_user_destroy_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_user_decline(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"decline">>, _attrs, _els}) ->
    -    Reason = decode_muc_user_decline_els(__TopXMLNS,
    -					 __IgnoreEls, _els, undefined),
    -    {To, From} = decode_muc_user_decline_attrs(__TopXMLNS,
    -					       _attrs, undefined, undefined),
    -    {muc_decline, Reason, From, To}.
    -
    -decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls, [],
    -			    Reason) ->
    -    Reason;
    -decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
    -			    Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
    -				       _els,
    -				       decode_muc_user_reason(__TopXMLNS,
    -							      __IgnoreEls,
    -							      _el));
    -       true ->
    -	   decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
    -				       _els, Reason)
    -    end;
    -decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
    -			    [_ | _els], Reason) ->
    -    decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
    -				_els, Reason).
    -
    -decode_muc_user_decline_attrs(__TopXMLNS,
    -			      [{<<"to">>, _val} | _attrs], _To, From) ->
    -    decode_muc_user_decline_attrs(__TopXMLNS, _attrs, _val,
    -				  From);
    -decode_muc_user_decline_attrs(__TopXMLNS,
    -			      [{<<"from">>, _val} | _attrs], To, _From) ->
    -    decode_muc_user_decline_attrs(__TopXMLNS, _attrs, To,
    -				  _val);
    -decode_muc_user_decline_attrs(__TopXMLNS, [_ | _attrs],
    -			      To, From) ->
    -    decode_muc_user_decline_attrs(__TopXMLNS, _attrs, To,
    -				  From);
    -decode_muc_user_decline_attrs(__TopXMLNS, [], To,
    -			      From) ->
    -    {decode_muc_user_decline_attr_to(__TopXMLNS, To),
    -     decode_muc_user_decline_attr_from(__TopXMLNS, From)}.
    -
    -encode_muc_user_decline({muc_decline, Reason, From, To},
    -			_xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_muc_user_decline_$reason'(Reason,
    -							[])),
    -    _attrs = encode_muc_user_decline_attr_from(From,
    -					       encode_muc_user_decline_attr_to(To,
    -									       _xmlns_attrs)),
    -    {xmlel, <<"decline">>, _attrs, _els}.
    -
    -'encode_muc_user_decline_$reason'(undefined, _acc) ->
    -    _acc;
    -'encode_muc_user_decline_$reason'(Reason, _acc) ->
    -    [encode_muc_user_reason(Reason, []) | _acc].
    -
    -decode_muc_user_decline_attr_to(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_muc_user_decline_attr_to(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"to">>, <<"decline">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_decline_attr_to(undefined, _acc) ->
    -    _acc;
    -encode_muc_user_decline_attr_to(_val, _acc) ->
    -    [{<<"to">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_user_decline_attr_from(__TopXMLNS,
    -				  undefined) ->
    -    undefined;
    -decode_muc_user_decline_attr_from(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"from">>, <<"decline">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_user_decline_attr_from(undefined, _acc) ->
    -    _acc;
    -encode_muc_user_decline_attr_from(_val, _acc) ->
    -    [{<<"from">>, enc_jid(_val)} | _acc].
    -
    -decode_muc_user_reason(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"reason">>, _attrs, _els}) ->
    -    Cdata = decode_muc_user_reason_els(__TopXMLNS,
    -				       __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls, [],
    -			   Cdata) ->
    -    decode_muc_user_reason_cdata(__TopXMLNS, Cdata);
    -decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls,
    -			   [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls,
    -			       _els, <>);
    -decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls,
    -			   [_ | _els], Cdata) ->
    -    decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls,
    -			       _els, Cdata).
    -
    -encode_muc_user_reason(Cdata, _xmlns_attrs) ->
    -    _els = encode_muc_user_reason_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"reason">>, _attrs, _els}.
    -
    -decode_muc_user_reason_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_muc_user_reason_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_muc_user_reason_cdata(undefined, _acc) -> _acc;
    -encode_muc_user_reason_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_muc_history(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"history">>, _attrs, _els}) ->
    -    {Maxchars, Maxstanzas, Seconds, Since} =
    -	decode_muc_history_attrs(__TopXMLNS, _attrs, undefined,
    -				 undefined, undefined, undefined),
    -    {muc_history, Maxchars, Maxstanzas, Seconds, Since}.
    -
    -decode_muc_history_attrs(__TopXMLNS,
    -			 [{<<"maxchars">>, _val} | _attrs], _Maxchars,
    -			 Maxstanzas, Seconds, Since) ->
    -    decode_muc_history_attrs(__TopXMLNS, _attrs, _val,
    -			     Maxstanzas, Seconds, Since);
    -decode_muc_history_attrs(__TopXMLNS,
    -			 [{<<"maxstanzas">>, _val} | _attrs], Maxchars,
    -			 _Maxstanzas, Seconds, Since) ->
    -    decode_muc_history_attrs(__TopXMLNS, _attrs, Maxchars,
    -			     _val, Seconds, Since);
    -decode_muc_history_attrs(__TopXMLNS,
    -			 [{<<"seconds">>, _val} | _attrs], Maxchars, Maxstanzas,
    -			 _Seconds, Since) ->
    -    decode_muc_history_attrs(__TopXMLNS, _attrs, Maxchars,
    -			     Maxstanzas, _val, Since);
    -decode_muc_history_attrs(__TopXMLNS,
    -			 [{<<"since">>, _val} | _attrs], Maxchars, Maxstanzas,
    -			 Seconds, _Since) ->
    -    decode_muc_history_attrs(__TopXMLNS, _attrs, Maxchars,
    -			     Maxstanzas, Seconds, _val);
    -decode_muc_history_attrs(__TopXMLNS, [_ | _attrs],
    -			 Maxchars, Maxstanzas, Seconds, Since) ->
    -    decode_muc_history_attrs(__TopXMLNS, _attrs, Maxchars,
    -			     Maxstanzas, Seconds, Since);
    -decode_muc_history_attrs(__TopXMLNS, [], Maxchars,
    -			 Maxstanzas, Seconds, Since) ->
    -    {decode_muc_history_attr_maxchars(__TopXMLNS, Maxchars),
    -     decode_muc_history_attr_maxstanzas(__TopXMLNS,
    -					Maxstanzas),
    -     decode_muc_history_attr_seconds(__TopXMLNS, Seconds),
    -     decode_muc_history_attr_since(__TopXMLNS, Since)}.
    -
    -encode_muc_history({muc_history, Maxchars, Maxstanzas,
    -		    Seconds, Since},
    -		   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_muc_history_attr_since(Since,
    -					   encode_muc_history_attr_seconds(Seconds,
    -									   encode_muc_history_attr_maxstanzas(Maxstanzas,
    -													      encode_muc_history_attr_maxchars(Maxchars,
    -																	       _xmlns_attrs)))),
    -    {xmlel, <<"history">>, _attrs, _els}.
    -
    -decode_muc_history_attr_maxchars(__TopXMLNS,
    -				 undefined) ->
    -    undefined;
    -decode_muc_history_attr_maxchars(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"maxchars">>, <<"history">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_history_attr_maxchars(undefined, _acc) ->
    -    _acc;
    -encode_muc_history_attr_maxchars(_val, _acc) ->
    -    [{<<"maxchars">>, enc_int(_val)} | _acc].
    -
    -decode_muc_history_attr_maxstanzas(__TopXMLNS,
    -				   undefined) ->
    -    undefined;
    -decode_muc_history_attr_maxstanzas(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"maxstanzas">>, <<"history">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_history_attr_maxstanzas(undefined, _acc) ->
    -    _acc;
    -encode_muc_history_attr_maxstanzas(_val, _acc) ->
    -    [{<<"maxstanzas">>, enc_int(_val)} | _acc].
    -
    -decode_muc_history_attr_seconds(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_muc_history_attr_seconds(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"seconds">>, <<"history">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_history_attr_seconds(undefined, _acc) ->
    -    _acc;
    -encode_muc_history_attr_seconds(_val, _acc) ->
    -    [{<<"seconds">>, enc_int(_val)} | _acc].
    -
    -decode_muc_history_attr_since(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_muc_history_attr_since(__TopXMLNS, _val) ->
    -    case catch dec_utc(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"since">>, <<"history">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_muc_history_attr_since(undefined, _acc) -> _acc;
    -encode_muc_history_attr_since(_val, _acc) ->
    -    [{<<"since">>, enc_utc(_val)} | _acc].
    -
    -decode_bytestreams(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"query">>, _attrs, _els}) ->
    -    {Hosts, Used, Activate} =
    -	decode_bytestreams_els(__TopXMLNS, __IgnoreEls, _els,
    -			       [], undefined, undefined),
    -    {Dstaddr, Sid, Mode} =
    -	decode_bytestreams_attrs(__TopXMLNS, _attrs, undefined,
    -				 undefined, undefined),
    -    {bytestreams, Hosts, Used, Activate, Dstaddr, Mode,
    -     Sid}.
    -
    -decode_bytestreams_els(__TopXMLNS, __IgnoreEls, [],
    -		       Hosts, Used, Activate) ->
    -    {lists:reverse(Hosts), Used, Activate};
    -decode_bytestreams_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"streamhost">>, _attrs, _} = _el | _els],
    -		       Hosts, Used, Activate) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_bytestreams_els(__TopXMLNS, __IgnoreEls, _els,
    -				  [decode_bytestreams_streamhost(__TopXMLNS,
    -								 __IgnoreEls,
    -								 _el)
    -				   | Hosts],
    -				  Used, Activate);
    -       true ->
    -	   decode_bytestreams_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Hosts, Used, Activate)
    -    end;
    -decode_bytestreams_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"streamhost-used">>, _attrs, _} = _el
    -			| _els],
    -		       Hosts, Used, Activate) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_bytestreams_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Hosts,
    -				  decode_bytestreams_streamhost_used(__TopXMLNS,
    -								     __IgnoreEls,
    -								     _el),
    -				  Activate);
    -       true ->
    -	   decode_bytestreams_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Hosts, Used, Activate)
    -    end;
    -decode_bytestreams_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"activate">>, _attrs, _} = _el | _els],
    -		       Hosts, Used, Activate) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_bytestreams_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Hosts, Used,
    -				  decode_bytestreams_activate(__TopXMLNS,
    -							      __IgnoreEls,
    -							      _el));
    -       true ->
    -	   decode_bytestreams_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Hosts, Used, Activate)
    -    end;
    -decode_bytestreams_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Hosts, Used, Activate) ->
    -    decode_bytestreams_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Hosts, Used, Activate).
    -
    -decode_bytestreams_attrs(__TopXMLNS,
    -			 [{<<"dstaddr">>, _val} | _attrs], _Dstaddr, Sid,
    -			 Mode) ->
    -    decode_bytestreams_attrs(__TopXMLNS, _attrs, _val, Sid,
    -			     Mode);
    -decode_bytestreams_attrs(__TopXMLNS,
    -			 [{<<"sid">>, _val} | _attrs], Dstaddr, _Sid, Mode) ->
    -    decode_bytestreams_attrs(__TopXMLNS, _attrs, Dstaddr,
    -			     _val, Mode);
    -decode_bytestreams_attrs(__TopXMLNS,
    -			 [{<<"mode">>, _val} | _attrs], Dstaddr, Sid, _Mode) ->
    -    decode_bytestreams_attrs(__TopXMLNS, _attrs, Dstaddr,
    -			     Sid, _val);
    -decode_bytestreams_attrs(__TopXMLNS, [_ | _attrs],
    -			 Dstaddr, Sid, Mode) ->
    -    decode_bytestreams_attrs(__TopXMLNS, _attrs, Dstaddr,
    -			     Sid, Mode);
    -decode_bytestreams_attrs(__TopXMLNS, [], Dstaddr, Sid,
    -			 Mode) ->
    -    {decode_bytestreams_attr_dstaddr(__TopXMLNS, Dstaddr),
    -     decode_bytestreams_attr_sid(__TopXMLNS, Sid),
    -     decode_bytestreams_attr_mode(__TopXMLNS, Mode)}.
    -
    -encode_bytestreams({bytestreams, Hosts, Used, Activate,
    -		    Dstaddr, Mode, Sid},
    -		   _xmlns_attrs) ->
    -    _els = lists:reverse('encode_bytestreams_$hosts'(Hosts,
    -						     'encode_bytestreams_$used'(Used,
    -										'encode_bytestreams_$activate'(Activate,
    -													       [])))),
    -    _attrs = encode_bytestreams_attr_mode(Mode,
    -					  encode_bytestreams_attr_sid(Sid,
    -								      encode_bytestreams_attr_dstaddr(Dstaddr,
    -												      _xmlns_attrs))),
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_bytestreams_$hosts'([], _acc) -> _acc;
    -'encode_bytestreams_$hosts'([Hosts | _els], _acc) ->
    -    'encode_bytestreams_$hosts'(_els,
    -				[encode_bytestreams_streamhost(Hosts, [])
    -				 | _acc]).
    -
    -'encode_bytestreams_$used'(undefined, _acc) -> _acc;
    -'encode_bytestreams_$used'(Used, _acc) ->
    -    [encode_bytestreams_streamhost_used(Used, []) | _acc].
    -
    -'encode_bytestreams_$activate'(undefined, _acc) -> _acc;
    -'encode_bytestreams_$activate'(Activate, _acc) ->
    -    [encode_bytestreams_activate(Activate, []) | _acc].
    -
    -decode_bytestreams_attr_dstaddr(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_bytestreams_attr_dstaddr(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_bytestreams_attr_dstaddr(undefined, _acc) ->
    -    _acc;
    -encode_bytestreams_attr_dstaddr(_val, _acc) ->
    -    [{<<"dstaddr">>, _val} | _acc].
    -
    -decode_bytestreams_attr_sid(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_bytestreams_attr_sid(__TopXMLNS, _val) -> _val.
    -
    -encode_bytestreams_attr_sid(undefined, _acc) -> _acc;
    -encode_bytestreams_attr_sid(_val, _acc) ->
    -    [{<<"sid">>, _val} | _acc].
    -
    -decode_bytestreams_attr_mode(__TopXMLNS, undefined) ->
    -    tcp;
    -decode_bytestreams_attr_mode(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val, [tcp, udp]) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"mode">>, <<"query">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_bytestreams_attr_mode(tcp, _acc) -> _acc;
    -encode_bytestreams_attr_mode(_val, _acc) ->
    -    [{<<"mode">>, enc_enum(_val)} | _acc].
    -
    -decode_bytestreams_activate(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"activate">>, _attrs, _els}) ->
    -    Cdata = decode_bytestreams_activate_els(__TopXMLNS,
    -					    __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_bytestreams_activate_els(__TopXMLNS, __IgnoreEls,
    -				[], Cdata) ->
    -    decode_bytestreams_activate_cdata(__TopXMLNS, Cdata);
    -decode_bytestreams_activate_els(__TopXMLNS, __IgnoreEls,
    -				[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_bytestreams_activate_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <>);
    -decode_bytestreams_activate_els(__TopXMLNS, __IgnoreEls,
    -				[_ | _els], Cdata) ->
    -    decode_bytestreams_activate_els(__TopXMLNS, __IgnoreEls,
    -				    _els, Cdata).
    -
    -encode_bytestreams_activate(Cdata, _xmlns_attrs) ->
    -    _els = encode_bytestreams_activate_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"activate">>, _attrs, _els}.
    -
    -decode_bytestreams_activate_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_bytestreams_activate_cdata(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"activate">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_bytestreams_activate_cdata(undefined, _acc) ->
    -    _acc;
    -encode_bytestreams_activate_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_jid(_val)} | _acc].
    -
    -decode_bytestreams_streamhost_used(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"streamhost-used">>, _attrs,
    -				    _els}) ->
    -    Jid =
    -	decode_bytestreams_streamhost_used_attrs(__TopXMLNS,
    -						 _attrs, undefined),
    -    Jid.
    -
    -decode_bytestreams_streamhost_used_attrs(__TopXMLNS,
    -					 [{<<"jid">>, _val} | _attrs], _Jid) ->
    -    decode_bytestreams_streamhost_used_attrs(__TopXMLNS,
    -					     _attrs, _val);
    -decode_bytestreams_streamhost_used_attrs(__TopXMLNS,
    -					 [_ | _attrs], Jid) ->
    -    decode_bytestreams_streamhost_used_attrs(__TopXMLNS,
    -					     _attrs, Jid);
    -decode_bytestreams_streamhost_used_attrs(__TopXMLNS, [],
    -					 Jid) ->
    -    decode_bytestreams_streamhost_used_attr_jid(__TopXMLNS,
    -						Jid).
    -
    -encode_bytestreams_streamhost_used(Jid, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs =
    -	encode_bytestreams_streamhost_used_attr_jid(Jid,
    -						    _xmlns_attrs),
    -    {xmlel, <<"streamhost-used">>, _attrs, _els}.
    -
    -decode_bytestreams_streamhost_used_attr_jid(__TopXMLNS,
    -					    undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"streamhost-used">>,
    -		   __TopXMLNS}});
    -decode_bytestreams_streamhost_used_attr_jid(__TopXMLNS,
    -					    _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"streamhost-used">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_bytestreams_streamhost_used_attr_jid(_val,
    -					    _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_bytestreams_streamhost(__TopXMLNS, __IgnoreEls,
    -			      {xmlel, <<"streamhost">>, _attrs, _els}) ->
    -    {Jid, Host, Port} =
    -	decode_bytestreams_streamhost_attrs(__TopXMLNS, _attrs,
    -					    undefined, undefined, undefined),
    -    {streamhost, Jid, Host, Port}.
    -
    -decode_bytestreams_streamhost_attrs(__TopXMLNS,
    -				    [{<<"jid">>, _val} | _attrs], _Jid, Host,
    -				    Port) ->
    -    decode_bytestreams_streamhost_attrs(__TopXMLNS, _attrs,
    -					_val, Host, Port);
    -decode_bytestreams_streamhost_attrs(__TopXMLNS,
    -				    [{<<"host">>, _val} | _attrs], Jid, _Host,
    -				    Port) ->
    -    decode_bytestreams_streamhost_attrs(__TopXMLNS, _attrs,
    -					Jid, _val, Port);
    -decode_bytestreams_streamhost_attrs(__TopXMLNS,
    -				    [{<<"port">>, _val} | _attrs], Jid, Host,
    -				    _Port) ->
    -    decode_bytestreams_streamhost_attrs(__TopXMLNS, _attrs,
    -					Jid, Host, _val);
    -decode_bytestreams_streamhost_attrs(__TopXMLNS,
    -				    [_ | _attrs], Jid, Host, Port) ->
    -    decode_bytestreams_streamhost_attrs(__TopXMLNS, _attrs,
    -					Jid, Host, Port);
    -decode_bytestreams_streamhost_attrs(__TopXMLNS, [], Jid,
    -				    Host, Port) ->
    -    {decode_bytestreams_streamhost_attr_jid(__TopXMLNS,
    -					    Jid),
    -     decode_bytestreams_streamhost_attr_host(__TopXMLNS,
    -					     Host),
    -     decode_bytestreams_streamhost_attr_port(__TopXMLNS,
    -					     Port)}.
    -
    -encode_bytestreams_streamhost({streamhost, Jid, Host,
    -			       Port},
    -			      _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_bytestreams_streamhost_attr_port(Port,
    -						     encode_bytestreams_streamhost_attr_host(Host,
    -											     encode_bytestreams_streamhost_attr_jid(Jid,
    -																    _xmlns_attrs))),
    -    {xmlel, <<"streamhost">>, _attrs, _els}.
    -
    -decode_bytestreams_streamhost_attr_jid(__TopXMLNS,
    -				       undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"streamhost">>,
    -		   __TopXMLNS}});
    -decode_bytestreams_streamhost_attr_jid(__TopXMLNS,
    -				       _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"streamhost">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_bytestreams_streamhost_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_bytestreams_streamhost_attr_host(__TopXMLNS,
    -					undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"host">>, <<"streamhost">>,
    -		   __TopXMLNS}});
    -decode_bytestreams_streamhost_attr_host(__TopXMLNS,
    -					_val) ->
    -    _val.
    -
    -encode_bytestreams_streamhost_attr_host(_val, _acc) ->
    -    [{<<"host">>, _val} | _acc].
    -
    -decode_bytestreams_streamhost_attr_port(__TopXMLNS,
    -					undefined) ->
    -    1080;
    -decode_bytestreams_streamhost_attr_port(__TopXMLNS,
    -					_val) ->
    -    case catch dec_int(_val, 0, 65535) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"port">>, <<"streamhost">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_bytestreams_streamhost_attr_port(1080, _acc) ->
    -    _acc;
    -encode_bytestreams_streamhost_attr_port(_val, _acc) ->
    -    [{<<"port">>, enc_int(_val)} | _acc].
    -
    -decode_delay(__TopXMLNS, __IgnoreEls,
    -	     {xmlel, <<"delay">>, _attrs, _els}) ->
    -    {Stamp, From} = decode_delay_attrs(__TopXMLNS, _attrs,
    -				       undefined, undefined),
    -    {delay, Stamp, From}.
    -
    -decode_delay_attrs(__TopXMLNS,
    -		   [{<<"stamp">>, _val} | _attrs], _Stamp, From) ->
    -    decode_delay_attrs(__TopXMLNS, _attrs, _val, From);
    -decode_delay_attrs(__TopXMLNS,
    -		   [{<<"from">>, _val} | _attrs], Stamp, _From) ->
    -    decode_delay_attrs(__TopXMLNS, _attrs, Stamp, _val);
    -decode_delay_attrs(__TopXMLNS, [_ | _attrs], Stamp,
    -		   From) ->
    -    decode_delay_attrs(__TopXMLNS, _attrs, Stamp, From);
    -decode_delay_attrs(__TopXMLNS, [], Stamp, From) ->
    -    {decode_delay_attr_stamp(__TopXMLNS, Stamp),
    -     decode_delay_attr_from(__TopXMLNS, From)}.
    -
    -encode_delay({delay, Stamp, From}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_delay_attr_from(From,
    -				    encode_delay_attr_stamp(Stamp,
    -							    _xmlns_attrs)),
    -    {xmlel, <<"delay">>, _attrs, _els}.
    -
    -decode_delay_attr_stamp(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"stamp">>, <<"delay">>, __TopXMLNS}});
    -decode_delay_attr_stamp(__TopXMLNS, _val) ->
    -    case catch dec_utc(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"stamp">>, <<"delay">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_delay_attr_stamp(_val, _acc) ->
    -    [{<<"stamp">>, enc_utc(_val)} | _acc].
    -
    -decode_delay_attr_from(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_delay_attr_from(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"from">>, <<"delay">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_delay_attr_from(undefined, _acc) -> _acc;
    -encode_delay_attr_from(_val, _acc) ->
    -    [{<<"from">>, enc_jid(_val)} | _acc].
    -
    -decode_chatstate_paused(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"paused">>, _attrs, _els}) ->
    -    {chatstate, paused}.
    -
    -encode_chatstate_paused({chatstate, paused},
    -			_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"paused">>, _attrs, _els}.
    -
    -decode_chatstate_inactive(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"inactive">>, _attrs, _els}) ->
    -    {chatstate, inactive}.
    -
    -encode_chatstate_inactive({chatstate, inactive},
    -			  _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"inactive">>, _attrs, _els}.
    -
    -decode_chatstate_gone(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"gone">>, _attrs, _els}) ->
    -    {chatstate, gone}.
    -
    -encode_chatstate_gone({chatstate, gone},
    -		      _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"gone">>, _attrs, _els}.
    -
    -decode_chatstate_composing(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"composing">>, _attrs, _els}) ->
    -    {chatstate, composing}.
    -
    -encode_chatstate_composing({chatstate, composing},
    -			   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"composing">>, _attrs, _els}.
    -
    -decode_chatstate_active(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"active">>, _attrs, _els}) ->
    -    {chatstate, active}.
    -
    -encode_chatstate_active({chatstate, active},
    -			_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"active">>, _attrs, _els}.
    -
    -decode_shim_headers(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"headers">>, _attrs, _els}) ->
    -    Headers = decode_shim_headers_els(__TopXMLNS,
    -				      __IgnoreEls, _els, []),
    -    {shim, Headers}.
    -
    -decode_shim_headers_els(__TopXMLNS, __IgnoreEls, [],
    -			Headers) ->
    -    lists:reverse(Headers);
    -decode_shim_headers_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"header">>, _attrs, _} = _el | _els],
    -			Headers) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_shim_headers_els(__TopXMLNS, __IgnoreEls, _els,
    -				   [decode_shim_header(__TopXMLNS, __IgnoreEls,
    -						       _el)
    -				    | Headers]);
    -       true ->
    -	   decode_shim_headers_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Headers)
    -    end;
    -decode_shim_headers_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Headers) ->
    -    decode_shim_headers_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Headers).
    -
    -encode_shim_headers({shim, Headers}, _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_shim_headers_$headers'(Headers,
    -						     [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"headers">>, _attrs, _els}.
    -
    -'encode_shim_headers_$headers'([], _acc) -> _acc;
    -'encode_shim_headers_$headers'([Headers | _els],
    -			       _acc) ->
    -    'encode_shim_headers_$headers'(_els,
    -				   [encode_shim_header(Headers, []) | _acc]).
    -
    -decode_shim_header(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"header">>, _attrs, _els}) ->
    -    Cdata = decode_shim_header_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <<>>),
    -    Name = decode_shim_header_attrs(__TopXMLNS, _attrs,
    -				    undefined),
    -    {Name, Cdata}.
    -
    -decode_shim_header_els(__TopXMLNS, __IgnoreEls, [],
    -		       Cdata) ->
    -    decode_shim_header_cdata(__TopXMLNS, Cdata);
    -decode_shim_header_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_shim_header_els(__TopXMLNS, __IgnoreEls, _els,
    -			   <>);
    -decode_shim_header_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Cdata) ->
    -    decode_shim_header_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Cdata).
    -
    -decode_shim_header_attrs(__TopXMLNS,
    -			 [{<<"name">>, _val} | _attrs], _Name) ->
    -    decode_shim_header_attrs(__TopXMLNS, _attrs, _val);
    -decode_shim_header_attrs(__TopXMLNS, [_ | _attrs],
    -			 Name) ->
    -    decode_shim_header_attrs(__TopXMLNS, _attrs, Name);
    -decode_shim_header_attrs(__TopXMLNS, [], Name) ->
    -    decode_shim_header_attr_name(__TopXMLNS, Name).
    -
    -encode_shim_header({Name, Cdata}, _xmlns_attrs) ->
    -    _els = encode_shim_header_cdata(Cdata, []),
    -    _attrs = encode_shim_header_attr_name(Name,
    -					  _xmlns_attrs),
    -    {xmlel, <<"header">>, _attrs, _els}.
    -
    -decode_shim_header_attr_name(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"name">>, <<"header">>, __TopXMLNS}});
    -decode_shim_header_attr_name(__TopXMLNS, _val) -> _val.
    -
    -encode_shim_header_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_shim_header_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_shim_header_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_shim_header_cdata(undefined, _acc) -> _acc;
    -encode_shim_header_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_pubsub(__TopXMLNS, __IgnoreEls,
    -	      {xmlel, <<"pubsub">>, _attrs, _els}) ->
    -    {Items, Options, Affiliations, Subscriptions, Retract,
    -     Unsubscribe, Subscribe, Publish} =
    -	decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els,
    -			  undefined, undefined, undefined, undefined, undefined,
    -			  undefined, undefined, undefined),
    -    {pubsub, Subscriptions, Affiliations, Publish,
    -     Subscribe, Unsubscribe, Options, Items, Retract}.
    -
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls, [], Items,
    -		  Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    {Items, Options, Affiliations, Subscriptions, Retract,
    -     Unsubscribe, Subscribe, Publish};
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls,
    -		  [{xmlel, <<"subscriptions">>, _attrs, _} = _el | _els],
    -		  Items, Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations,
    -			     decode_pubsub_subscriptions(__TopXMLNS,
    -							 __IgnoreEls, _el),
    -			     Retract, Unsubscribe, Subscribe, Publish);
    -       true ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe, Publish)
    -    end;
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls,
    -		  [{xmlel, <<"affiliations">>, _attrs, _} = _el | _els],
    -		  Items, Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options,
    -			     decode_pubsub_affiliations(__TopXMLNS, __IgnoreEls,
    -							_el),
    -			     Subscriptions, Retract, Unsubscribe, Subscribe,
    -			     Publish);
    -       true ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe, Publish)
    -    end;
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls,
    -		  [{xmlel, <<"subscribe">>, _attrs, _} = _el | _els],
    -		  Items, Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe,
    -			     decode_pubsub_subscribe(__TopXMLNS, __IgnoreEls,
    -						     _el),
    -			     Publish);
    -       true ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe, Publish)
    -    end;
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls,
    -		  [{xmlel, <<"unsubscribe">>, _attrs, _} = _el | _els],
    -		  Items, Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     decode_pubsub_unsubscribe(__TopXMLNS, __IgnoreEls,
    -						       _el),
    -			     Subscribe, Publish);
    -       true ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe, Publish)
    -    end;
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls,
    -		  [{xmlel, <<"options">>, _attrs, _} = _el | _els], Items,
    -		  Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     decode_pubsub_options(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -			     Affiliations, Subscriptions, Retract, Unsubscribe,
    -			     Subscribe, Publish);
    -       true ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe, Publish)
    -    end;
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls,
    -		  [{xmlel, <<"items">>, _attrs, _} = _el | _els], Items,
    -		  Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els,
    -			     decode_pubsub_items(__TopXMLNS, __IgnoreEls, _el),
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe, Publish);
    -       true ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe, Publish)
    -    end;
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls,
    -		  [{xmlel, <<"retract">>, _attrs, _} = _el | _els], Items,
    -		  Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions,
    -			     decode_pubsub_retract(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -			     Unsubscribe, Subscribe, Publish);
    -       true ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe, Publish)
    -    end;
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls,
    -		  [{xmlel, <<"publish">>, _attrs, _} = _el | _els], Items,
    -		  Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe,
    -			     decode_pubsub_publish(__TopXMLNS, __IgnoreEls,
    -						   _el));
    -       true ->
    -	   decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -			     Options, Affiliations, Subscriptions, Retract,
    -			     Unsubscribe, Subscribe, Publish)
    -    end;
    -decode_pubsub_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		  Items, Options, Affiliations, Subscriptions, Retract,
    -		  Unsubscribe, Subscribe, Publish) ->
    -    decode_pubsub_els(__TopXMLNS, __IgnoreEls, _els, Items,
    -		      Options, Affiliations, Subscriptions, Retract,
    -		      Unsubscribe, Subscribe, Publish).
    -
    -encode_pubsub({pubsub, Subscriptions, Affiliations,
    -	       Publish, Subscribe, Unsubscribe, Options, Items,
    -	       Retract},
    -	      _xmlns_attrs) ->
    -    _els = lists:reverse('encode_pubsub_$items'(Items,
    -						'encode_pubsub_$options'(Options,
    -									 'encode_pubsub_$affiliations'(Affiliations,
    -												       'encode_pubsub_$subscriptions'(Subscriptions,
    -																      'encode_pubsub_$retract'(Retract,
    -																			       'encode_pubsub_$unsubscribe'(Unsubscribe,
    -																							    'encode_pubsub_$subscribe'(Subscribe,
    -																										       'encode_pubsub_$publish'(Publish,
    -																														[]))))))))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"pubsub">>, _attrs, _els}.
    -
    -'encode_pubsub_$items'(undefined, _acc) -> _acc;
    -'encode_pubsub_$items'(Items, _acc) ->
    -    [encode_pubsub_items(Items, []) | _acc].
    -
    -'encode_pubsub_$options'(undefined, _acc) -> _acc;
    -'encode_pubsub_$options'(Options, _acc) ->
    -    [encode_pubsub_options(Options, []) | _acc].
    -
    -'encode_pubsub_$affiliations'(undefined, _acc) -> _acc;
    -'encode_pubsub_$affiliations'(Affiliations, _acc) ->
    -    [encode_pubsub_affiliations(Affiliations, []) | _acc].
    -
    -'encode_pubsub_$subscriptions'(undefined, _acc) -> _acc;
    -'encode_pubsub_$subscriptions'(Subscriptions, _acc) ->
    -    [encode_pubsub_subscriptions(Subscriptions, []) | _acc].
    -
    -'encode_pubsub_$retract'(undefined, _acc) -> _acc;
    -'encode_pubsub_$retract'(Retract, _acc) ->
    -    [encode_pubsub_retract(Retract, []) | _acc].
    -
    -'encode_pubsub_$unsubscribe'(undefined, _acc) -> _acc;
    -'encode_pubsub_$unsubscribe'(Unsubscribe, _acc) ->
    -    [encode_pubsub_unsubscribe(Unsubscribe, []) | _acc].
    -
    -'encode_pubsub_$subscribe'(undefined, _acc) -> _acc;
    -'encode_pubsub_$subscribe'(Subscribe, _acc) ->
    -    [encode_pubsub_subscribe(Subscribe, []) | _acc].
    -
    -'encode_pubsub_$publish'(undefined, _acc) -> _acc;
    -'encode_pubsub_$publish'(Publish, _acc) ->
    -    [encode_pubsub_publish(Publish, []) | _acc].
    -
    -decode_pubsub_retract(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"retract">>, _attrs, _els}) ->
    -    Items = decode_pubsub_retract_els(__TopXMLNS,
    -				      __IgnoreEls, _els, []),
    -    {Node, Notify} = decode_pubsub_retract_attrs(__TopXMLNS,
    -						 _attrs, undefined, undefined),
    -    {pubsub_retract, Node, Notify, Items}.
    -
    -decode_pubsub_retract_els(__TopXMLNS, __IgnoreEls, [],
    -			  Items) ->
    -    lists:reverse(Items);
    -decode_pubsub_retract_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlel, <<"item">>, _attrs, _} = _el | _els],
    -			  Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_retract_els(__TopXMLNS, __IgnoreEls, _els,
    -				     [decode_pubsub_item(__TopXMLNS,
    -							 __IgnoreEls, _el)
    -				      | Items]);
    -       true ->
    -	   decode_pubsub_retract_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Items)
    -    end;
    -decode_pubsub_retract_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Items) ->
    -    decode_pubsub_retract_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Items).
    -
    -decode_pubsub_retract_attrs(__TopXMLNS,
    -			    [{<<"node">>, _val} | _attrs], _Node, Notify) ->
    -    decode_pubsub_retract_attrs(__TopXMLNS, _attrs, _val,
    -				Notify);
    -decode_pubsub_retract_attrs(__TopXMLNS,
    -			    [{<<"notify">>, _val} | _attrs], Node, _Notify) ->
    -    decode_pubsub_retract_attrs(__TopXMLNS, _attrs, Node,
    -				_val);
    -decode_pubsub_retract_attrs(__TopXMLNS, [_ | _attrs],
    -			    Node, Notify) ->
    -    decode_pubsub_retract_attrs(__TopXMLNS, _attrs, Node,
    -				Notify);
    -decode_pubsub_retract_attrs(__TopXMLNS, [], Node,
    -			    Notify) ->
    -    {decode_pubsub_retract_attr_node(__TopXMLNS, Node),
    -     decode_pubsub_retract_attr_notify(__TopXMLNS, Notify)}.
    -
    -encode_pubsub_retract({pubsub_retract, Node, Notify,
    -		       Items},
    -		      _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_pubsub_retract_$items'(Items,
    -						     [])),
    -    _attrs = encode_pubsub_retract_attr_notify(Notify,
    -					       encode_pubsub_retract_attr_node(Node,
    -									       _xmlns_attrs)),
    -    {xmlel, <<"retract">>, _attrs, _els}.
    -
    -'encode_pubsub_retract_$items'([], _acc) -> _acc;
    -'encode_pubsub_retract_$items'([Items | _els], _acc) ->
    -    'encode_pubsub_retract_$items'(_els,
    -				   [encode_pubsub_item(Items, []) | _acc]).
    -
    -decode_pubsub_retract_attr_node(__TopXMLNS,
    -				undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"node">>, <<"retract">>, __TopXMLNS}});
    -decode_pubsub_retract_attr_node(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_retract_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_retract_attr_notify(__TopXMLNS,
    -				  undefined) ->
    -    false;
    -decode_pubsub_retract_attr_notify(__TopXMLNS, _val) ->
    -    case catch dec_bool(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"notify">>, <<"retract">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_pubsub_retract_attr_notify(false, _acc) -> _acc;
    -encode_pubsub_retract_attr_notify(_val, _acc) ->
    -    [{<<"notify">>, enc_bool(_val)} | _acc].
    -
    -decode_pubsub_options(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"options">>, _attrs, _els}) ->
    -    Xdata = decode_pubsub_options_els(__TopXMLNS,
    -				      __IgnoreEls, _els, undefined),
    -    {Node, Subid, Jid} =
    -	decode_pubsub_options_attrs(__TopXMLNS, _attrs,
    -				    undefined, undefined, undefined),
    -    {pubsub_options, Node, Jid, Subid, Xdata}.
    -
    -decode_pubsub_options_els(__TopXMLNS, __IgnoreEls, [],
    -			  Xdata) ->
    -    Xdata;
    -decode_pubsub_options_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlel, <<"x">>, _attrs, _} = _el | _els], Xdata) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"jabber:x:data">> ->
    -	   decode_pubsub_options_els(__TopXMLNS, __IgnoreEls, _els,
    -				     decode_xdata(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_pubsub_options_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Xdata)
    -    end;
    -decode_pubsub_options_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Xdata) ->
    -    decode_pubsub_options_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Xdata).
    -
    -decode_pubsub_options_attrs(__TopXMLNS,
    -			    [{<<"node">>, _val} | _attrs], _Node, Subid, Jid) ->
    -    decode_pubsub_options_attrs(__TopXMLNS, _attrs, _val,
    -				Subid, Jid);
    -decode_pubsub_options_attrs(__TopXMLNS,
    -			    [{<<"subid">>, _val} | _attrs], Node, _Subid,
    -			    Jid) ->
    -    decode_pubsub_options_attrs(__TopXMLNS, _attrs, Node,
    -				_val, Jid);
    -decode_pubsub_options_attrs(__TopXMLNS,
    -			    [{<<"jid">>, _val} | _attrs], Node, Subid, _Jid) ->
    -    decode_pubsub_options_attrs(__TopXMLNS, _attrs, Node,
    -				Subid, _val);
    -decode_pubsub_options_attrs(__TopXMLNS, [_ | _attrs],
    -			    Node, Subid, Jid) ->
    -    decode_pubsub_options_attrs(__TopXMLNS, _attrs, Node,
    -				Subid, Jid);
    -decode_pubsub_options_attrs(__TopXMLNS, [], Node, Subid,
    -			    Jid) ->
    -    {decode_pubsub_options_attr_node(__TopXMLNS, Node),
    -     decode_pubsub_options_attr_subid(__TopXMLNS, Subid),
    -     decode_pubsub_options_attr_jid(__TopXMLNS, Jid)}.
    -
    -encode_pubsub_options({pubsub_options, Node, Jid, Subid,
    -		       Xdata},
    -		      _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_pubsub_options_$xdata'(Xdata,
    -						     [])),
    -    _attrs = encode_pubsub_options_attr_jid(Jid,
    -					    encode_pubsub_options_attr_subid(Subid,
    -									     encode_pubsub_options_attr_node(Node,
    -													     _xmlns_attrs))),
    -    {xmlel, <<"options">>, _attrs, _els}.
    -
    -'encode_pubsub_options_$xdata'(undefined, _acc) -> _acc;
    -'encode_pubsub_options_$xdata'(Xdata, _acc) ->
    -    [encode_xdata(Xdata,
    -		  [{<<"xmlns">>, <<"jabber:x:data">>}])
    -     | _acc].
    -
    -decode_pubsub_options_attr_node(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_pubsub_options_attr_node(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_options_attr_node(undefined, _acc) ->
    -    _acc;
    -encode_pubsub_options_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_options_attr_subid(__TopXMLNS,
    -				 undefined) ->
    -    undefined;
    -decode_pubsub_options_attr_subid(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_options_attr_subid(undefined, _acc) ->
    -    _acc;
    -encode_pubsub_options_attr_subid(_val, _acc) ->
    -    [{<<"subid">>, _val} | _acc].
    -
    -decode_pubsub_options_attr_jid(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"options">>, __TopXMLNS}});
    -decode_pubsub_options_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"options">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_pubsub_options_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_pubsub_publish(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"publish">>, _attrs, _els}) ->
    -    Items = decode_pubsub_publish_els(__TopXMLNS,
    -				      __IgnoreEls, _els, []),
    -    Node = decode_pubsub_publish_attrs(__TopXMLNS, _attrs,
    -				       undefined),
    -    {pubsub_publish, Node, Items}.
    -
    -decode_pubsub_publish_els(__TopXMLNS, __IgnoreEls, [],
    -			  Items) ->
    -    lists:reverse(Items);
    -decode_pubsub_publish_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlel, <<"item">>, _attrs, _} = _el | _els],
    -			  Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_publish_els(__TopXMLNS, __IgnoreEls, _els,
    -				     [decode_pubsub_item(__TopXMLNS,
    -							 __IgnoreEls, _el)
    -				      | Items]);
    -       true ->
    -	   decode_pubsub_publish_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Items)
    -    end;
    -decode_pubsub_publish_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Items) ->
    -    decode_pubsub_publish_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Items).
    -
    -decode_pubsub_publish_attrs(__TopXMLNS,
    -			    [{<<"node">>, _val} | _attrs], _Node) ->
    -    decode_pubsub_publish_attrs(__TopXMLNS, _attrs, _val);
    -decode_pubsub_publish_attrs(__TopXMLNS, [_ | _attrs],
    -			    Node) ->
    -    decode_pubsub_publish_attrs(__TopXMLNS, _attrs, Node);
    -decode_pubsub_publish_attrs(__TopXMLNS, [], Node) ->
    -    decode_pubsub_publish_attr_node(__TopXMLNS, Node).
    -
    -encode_pubsub_publish({pubsub_publish, Node, Items},
    -		      _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_pubsub_publish_$items'(Items,
    -						     [])),
    -    _attrs = encode_pubsub_publish_attr_node(Node,
    -					     _xmlns_attrs),
    -    {xmlel, <<"publish">>, _attrs, _els}.
    -
    -'encode_pubsub_publish_$items'([], _acc) -> _acc;
    -'encode_pubsub_publish_$items'([Items | _els], _acc) ->
    -    'encode_pubsub_publish_$items'(_els,
    -				   [encode_pubsub_item(Items, []) | _acc]).
    -
    -decode_pubsub_publish_attr_node(__TopXMLNS,
    -				undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"node">>, <<"publish">>, __TopXMLNS}});
    -decode_pubsub_publish_attr_node(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_publish_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_unsubscribe(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"unsubscribe">>, _attrs, _els}) ->
    -    {Node, Subid, Jid} =
    -	decode_pubsub_unsubscribe_attrs(__TopXMLNS, _attrs,
    -					undefined, undefined, undefined),
    -    {pubsub_unsubscribe, Node, Jid, Subid}.
    -
    -decode_pubsub_unsubscribe_attrs(__TopXMLNS,
    -				[{<<"node">>, _val} | _attrs], _Node, Subid,
    -				Jid) ->
    -    decode_pubsub_unsubscribe_attrs(__TopXMLNS, _attrs,
    -				    _val, Subid, Jid);
    -decode_pubsub_unsubscribe_attrs(__TopXMLNS,
    -				[{<<"subid">>, _val} | _attrs], Node, _Subid,
    -				Jid) ->
    -    decode_pubsub_unsubscribe_attrs(__TopXMLNS, _attrs,
    -				    Node, _val, Jid);
    -decode_pubsub_unsubscribe_attrs(__TopXMLNS,
    -				[{<<"jid">>, _val} | _attrs], Node, Subid,
    -				_Jid) ->
    -    decode_pubsub_unsubscribe_attrs(__TopXMLNS, _attrs,
    -				    Node, Subid, _val);
    -decode_pubsub_unsubscribe_attrs(__TopXMLNS,
    -				[_ | _attrs], Node, Subid, Jid) ->
    -    decode_pubsub_unsubscribe_attrs(__TopXMLNS, _attrs,
    -				    Node, Subid, Jid);
    -decode_pubsub_unsubscribe_attrs(__TopXMLNS, [], Node,
    -				Subid, Jid) ->
    -    {decode_pubsub_unsubscribe_attr_node(__TopXMLNS, Node),
    -     decode_pubsub_unsubscribe_attr_subid(__TopXMLNS, Subid),
    -     decode_pubsub_unsubscribe_attr_jid(__TopXMLNS, Jid)}.
    -
    -encode_pubsub_unsubscribe({pubsub_unsubscribe, Node,
    -			   Jid, Subid},
    -			  _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_pubsub_unsubscribe_attr_jid(Jid,
    -						encode_pubsub_unsubscribe_attr_subid(Subid,
    -										     encode_pubsub_unsubscribe_attr_node(Node,
    -															 _xmlns_attrs))),
    -    {xmlel, <<"unsubscribe">>, _attrs, _els}.
    -
    -decode_pubsub_unsubscribe_attr_node(__TopXMLNS,
    -				    undefined) ->
    -    undefined;
    -decode_pubsub_unsubscribe_attr_node(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_unsubscribe_attr_node(undefined, _acc) ->
    -    _acc;
    -encode_pubsub_unsubscribe_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_unsubscribe_attr_subid(__TopXMLNS,
    -				     undefined) ->
    -    undefined;
    -decode_pubsub_unsubscribe_attr_subid(__TopXMLNS,
    -				     _val) ->
    -    _val.
    -
    -encode_pubsub_unsubscribe_attr_subid(undefined, _acc) ->
    -    _acc;
    -encode_pubsub_unsubscribe_attr_subid(_val, _acc) ->
    -    [{<<"subid">>, _val} | _acc].
    -
    -decode_pubsub_unsubscribe_attr_jid(__TopXMLNS,
    -				   undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"unsubscribe">>,
    -		   __TopXMLNS}});
    -decode_pubsub_unsubscribe_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"unsubscribe">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_pubsub_unsubscribe_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_pubsub_subscribe(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"subscribe">>, _attrs, _els}) ->
    -    {Node, Jid} = decode_pubsub_subscribe_attrs(__TopXMLNS,
    -						_attrs, undefined, undefined),
    -    {pubsub_subscribe, Node, Jid}.
    -
    -decode_pubsub_subscribe_attrs(__TopXMLNS,
    -			      [{<<"node">>, _val} | _attrs], _Node, Jid) ->
    -    decode_pubsub_subscribe_attrs(__TopXMLNS, _attrs, _val,
    -				  Jid);
    -decode_pubsub_subscribe_attrs(__TopXMLNS,
    -			      [{<<"jid">>, _val} | _attrs], Node, _Jid) ->
    -    decode_pubsub_subscribe_attrs(__TopXMLNS, _attrs, Node,
    -				  _val);
    -decode_pubsub_subscribe_attrs(__TopXMLNS, [_ | _attrs],
    -			      Node, Jid) ->
    -    decode_pubsub_subscribe_attrs(__TopXMLNS, _attrs, Node,
    -				  Jid);
    -decode_pubsub_subscribe_attrs(__TopXMLNS, [], Node,
    -			      Jid) ->
    -    {decode_pubsub_subscribe_attr_node(__TopXMLNS, Node),
    -     decode_pubsub_subscribe_attr_jid(__TopXMLNS, Jid)}.
    -
    -encode_pubsub_subscribe({pubsub_subscribe, Node, Jid},
    -			_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_pubsub_subscribe_attr_jid(Jid,
    -					      encode_pubsub_subscribe_attr_node(Node,
    -										_xmlns_attrs)),
    -    {xmlel, <<"subscribe">>, _attrs, _els}.
    -
    -decode_pubsub_subscribe_attr_node(__TopXMLNS,
    -				  undefined) ->
    -    undefined;
    -decode_pubsub_subscribe_attr_node(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_subscribe_attr_node(undefined, _acc) ->
    -    _acc;
    -encode_pubsub_subscribe_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_subscribe_attr_jid(__TopXMLNS,
    -				 undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"subscribe">>,
    -		   __TopXMLNS}});
    -decode_pubsub_subscribe_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"subscribe">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_pubsub_subscribe_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_pubsub_affiliations(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"affiliations">>, _attrs, _els}) ->
    -    Affiliations =
    -	decode_pubsub_affiliations_els(__TopXMLNS, __IgnoreEls,
    -				       _els, []),
    -    Affiliations.
    -
    -decode_pubsub_affiliations_els(__TopXMLNS, __IgnoreEls,
    -			       [], Affiliations) ->
    -    lists:reverse(Affiliations);
    -decode_pubsub_affiliations_els(__TopXMLNS, __IgnoreEls,
    -			       [{xmlel, <<"affiliation">>, _attrs, _} = _el
    -				| _els],
    -			       Affiliations) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_affiliations_els(__TopXMLNS, __IgnoreEls,
    -					  _els,
    -					  [decode_pubsub_affiliation(__TopXMLNS,
    -								     __IgnoreEls,
    -								     _el)
    -					   | Affiliations]);
    -       true ->
    -	   decode_pubsub_affiliations_els(__TopXMLNS, __IgnoreEls,
    -					  _els, Affiliations)
    -    end;
    -decode_pubsub_affiliations_els(__TopXMLNS, __IgnoreEls,
    -			       [_ | _els], Affiliations) ->
    -    decode_pubsub_affiliations_els(__TopXMLNS, __IgnoreEls,
    -				   _els, Affiliations).
    -
    -encode_pubsub_affiliations(Affiliations,
    -			   _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_pubsub_affiliations_$affiliations'(Affiliations,
    -								 [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"affiliations">>, _attrs, _els}.
    -
    -'encode_pubsub_affiliations_$affiliations'([], _acc) ->
    -    _acc;
    -'encode_pubsub_affiliations_$affiliations'([Affiliations
    -					    | _els],
    -					   _acc) ->
    -    'encode_pubsub_affiliations_$affiliations'(_els,
    -					       [encode_pubsub_affiliation(Affiliations,
    -									  [])
    -						| _acc]).
    -
    -decode_pubsub_subscriptions(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"subscriptions">>, _attrs, _els}) ->
    -    Subscriptions =
    -	decode_pubsub_subscriptions_els(__TopXMLNS, __IgnoreEls,
    -					_els, []),
    -    Node = decode_pubsub_subscriptions_attrs(__TopXMLNS,
    -					     _attrs, undefined),
    -    {Node, Subscriptions}.
    -
    -decode_pubsub_subscriptions_els(__TopXMLNS, __IgnoreEls,
    -				[], Subscriptions) ->
    -    lists:reverse(Subscriptions);
    -decode_pubsub_subscriptions_els(__TopXMLNS, __IgnoreEls,
    -				[{xmlel, <<"subscription">>, _attrs, _} = _el
    -				 | _els],
    -				Subscriptions) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_subscriptions_els(__TopXMLNS, __IgnoreEls,
    -					   _els,
    -					   [decode_pubsub_subscription(__TopXMLNS,
    -								       __IgnoreEls,
    -								       _el)
    -					    | Subscriptions]);
    -       true ->
    -	   decode_pubsub_subscriptions_els(__TopXMLNS, __IgnoreEls,
    -					   _els, Subscriptions)
    -    end;
    -decode_pubsub_subscriptions_els(__TopXMLNS, __IgnoreEls,
    -				[_ | _els], Subscriptions) ->
    -    decode_pubsub_subscriptions_els(__TopXMLNS, __IgnoreEls,
    -				    _els, Subscriptions).
    -
    -decode_pubsub_subscriptions_attrs(__TopXMLNS,
    -				  [{<<"node">>, _val} | _attrs], _Node) ->
    -    decode_pubsub_subscriptions_attrs(__TopXMLNS, _attrs,
    -				      _val);
    -decode_pubsub_subscriptions_attrs(__TopXMLNS,
    -				  [_ | _attrs], Node) ->
    -    decode_pubsub_subscriptions_attrs(__TopXMLNS, _attrs,
    -				      Node);
    -decode_pubsub_subscriptions_attrs(__TopXMLNS, [],
    -				  Node) ->
    -    decode_pubsub_subscriptions_attr_node(__TopXMLNS, Node).
    -
    -encode_pubsub_subscriptions({Node, Subscriptions},
    -			    _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_pubsub_subscriptions_$subscriptions'(Subscriptions,
    -								   [])),
    -    _attrs = encode_pubsub_subscriptions_attr_node(Node,
    -						   _xmlns_attrs),
    -    {xmlel, <<"subscriptions">>, _attrs, _els}.
    -
    -'encode_pubsub_subscriptions_$subscriptions'([],
    -					     _acc) ->
    -    _acc;
    -'encode_pubsub_subscriptions_$subscriptions'([Subscriptions
    -					      | _els],
    -					     _acc) ->
    -    'encode_pubsub_subscriptions_$subscriptions'(_els,
    -						 [encode_pubsub_subscription(Subscriptions,
    -									     [])
    -						  | _acc]).
    -
    -decode_pubsub_subscriptions_attr_node(__TopXMLNS,
    -				      undefined) ->
    -    none;
    -decode_pubsub_subscriptions_attr_node(__TopXMLNS,
    -				      _val) ->
    -    _val.
    -
    -encode_pubsub_subscriptions_attr_node(none, _acc) ->
    -    _acc;
    -encode_pubsub_subscriptions_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_event(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"event">>, _attrs, _els}) ->
    -    Items = decode_pubsub_event_els(__TopXMLNS, __IgnoreEls,
    -				    _els, []),
    -    {pubsub_event, Items}.
    -
    -decode_pubsub_event_els(__TopXMLNS, __IgnoreEls, [],
    -			Items) ->
    -    lists:reverse(Items);
    -decode_pubsub_event_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"items">>, _attrs, _} = _el | _els],
    -			Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_event_els(__TopXMLNS, __IgnoreEls, _els,
    -				   [decode_pubsub_event_items(__TopXMLNS,
    -							      __IgnoreEls, _el)
    -				    | Items]);
    -       true ->
    -	   decode_pubsub_event_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Items)
    -    end;
    -decode_pubsub_event_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Items) ->
    -    decode_pubsub_event_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Items).
    -
    -encode_pubsub_event({pubsub_event, Items},
    -		    _xmlns_attrs) ->
    -    _els = lists:reverse('encode_pubsub_event_$items'(Items,
    -						      [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"event">>, _attrs, _els}.
    -
    -'encode_pubsub_event_$items'([], _acc) -> _acc;
    -'encode_pubsub_event_$items'([Items | _els], _acc) ->
    -    'encode_pubsub_event_$items'(_els,
    -				 [encode_pubsub_event_items(Items, []) | _acc]).
    -
    -decode_pubsub_event_items(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"items">>, _attrs, _els}) ->
    -    {Items, Retract} =
    -	decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -				      _els, [], []),
    -    Node = decode_pubsub_event_items_attrs(__TopXMLNS,
    -					   _attrs, undefined),
    -    {pubsub_event_items, Node, Retract, Items}.
    -
    -decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -			      [], Items, Retract) ->
    -    {lists:reverse(Items), lists:reverse(Retract)};
    -decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -			      [{xmlel, <<"retract">>, _attrs, _} = _el | _els],
    -			      Items, Retract) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -					 _els, Items,
    -					 [decode_pubsub_event_retract(__TopXMLNS,
    -								      __IgnoreEls,
    -								      _el)
    -					  | Retract]);
    -       true ->
    -	   decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -					 _els, Items, Retract)
    -    end;
    -decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -			      [{xmlel, <<"item">>, _attrs, _} = _el | _els],
    -			      Items, Retract) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -					 _els,
    -					 [decode_pubsub_event_item(__TopXMLNS,
    -								   __IgnoreEls,
    -								   _el)
    -					  | Items],
    -					 Retract);
    -       true ->
    -	   decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -					 _els, Items, Retract)
    -    end;
    -decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -			      [_ | _els], Items, Retract) ->
    -    decode_pubsub_event_items_els(__TopXMLNS, __IgnoreEls,
    -				  _els, Items, Retract).
    -
    -decode_pubsub_event_items_attrs(__TopXMLNS,
    -				[{<<"node">>, _val} | _attrs], _Node) ->
    -    decode_pubsub_event_items_attrs(__TopXMLNS, _attrs,
    -				    _val);
    -decode_pubsub_event_items_attrs(__TopXMLNS,
    -				[_ | _attrs], Node) ->
    -    decode_pubsub_event_items_attrs(__TopXMLNS, _attrs,
    -				    Node);
    -decode_pubsub_event_items_attrs(__TopXMLNS, [], Node) ->
    -    decode_pubsub_event_items_attr_node(__TopXMLNS, Node).
    -
    -encode_pubsub_event_items({pubsub_event_items, Node,
    -			   Retract, Items},
    -			  _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_pubsub_event_items_$items'(Items,
    -							 'encode_pubsub_event_items_$retract'(Retract,
    -											      []))),
    -    _attrs = encode_pubsub_event_items_attr_node(Node,
    -						 _xmlns_attrs),
    -    {xmlel, <<"items">>, _attrs, _els}.
    -
    -'encode_pubsub_event_items_$items'([], _acc) -> _acc;
    -'encode_pubsub_event_items_$items'([Items | _els],
    -				   _acc) ->
    -    'encode_pubsub_event_items_$items'(_els,
    -				       [encode_pubsub_event_item(Items, [])
    -					| _acc]).
    -
    -'encode_pubsub_event_items_$retract'([], _acc) -> _acc;
    -'encode_pubsub_event_items_$retract'([Retract | _els],
    -				     _acc) ->
    -    'encode_pubsub_event_items_$retract'(_els,
    -					 [encode_pubsub_event_retract(Retract,
    -								      [])
    -					  | _acc]).
    -
    -decode_pubsub_event_items_attr_node(__TopXMLNS,
    -				    undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"node">>, <<"items">>, __TopXMLNS}});
    -decode_pubsub_event_items_attr_node(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_event_items_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_event_item(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"item">>, _attrs, _els}) ->
    -    __Xmls = decode_pubsub_event_item_els(__TopXMLNS,
    -					  __IgnoreEls, _els, []),
    -    {Id, Node, Publisher} =
    -	decode_pubsub_event_item_attrs(__TopXMLNS, _attrs,
    -				       undefined, undefined, undefined),
    -    {pubsub_event_item, Id, Node, Publisher, __Xmls}.
    -
    -decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
    -			     [], __Xmls) ->
    -    lists:reverse(__Xmls);
    -decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlel, _, _, _} = _el | _els], __Xmls) ->
    -    decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
    -				 _els, [_el | __Xmls]);
    -decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], __Xmls) ->
    -    decode_pubsub_event_item_els(__TopXMLNS, __IgnoreEls,
    -				 _els, __Xmls).
    -
    -decode_pubsub_event_item_attrs(__TopXMLNS,
    -			       [{<<"id">>, _val} | _attrs], _Id, Node,
    -			       Publisher) ->
    -    decode_pubsub_event_item_attrs(__TopXMLNS, _attrs, _val,
    -				   Node, Publisher);
    -decode_pubsub_event_item_attrs(__TopXMLNS,
    -			       [{<<"node">>, _val} | _attrs], Id, _Node,
    -			       Publisher) ->
    -    decode_pubsub_event_item_attrs(__TopXMLNS, _attrs, Id,
    -				   _val, Publisher);
    -decode_pubsub_event_item_attrs(__TopXMLNS,
    -			       [{<<"publisher">>, _val} | _attrs], Id, Node,
    -			       _Publisher) ->
    -    decode_pubsub_event_item_attrs(__TopXMLNS, _attrs, Id,
    -				   Node, _val);
    -decode_pubsub_event_item_attrs(__TopXMLNS, [_ | _attrs],
    -			       Id, Node, Publisher) ->
    -    decode_pubsub_event_item_attrs(__TopXMLNS, _attrs, Id,
    -				   Node, Publisher);
    -decode_pubsub_event_item_attrs(__TopXMLNS, [], Id, Node,
    -			       Publisher) ->
    -    {decode_pubsub_event_item_attr_id(__TopXMLNS, Id),
    -     decode_pubsub_event_item_attr_node(__TopXMLNS, Node),
    -     decode_pubsub_event_item_attr_publisher(__TopXMLNS,
    -					     Publisher)}.
    -
    -encode_pubsub_event_item({pubsub_event_item, Id, Node,
    -			  Publisher, __Xmls},
    -			 _xmlns_attrs) ->
    -    _els = __Xmls,
    -    _attrs =
    -	encode_pubsub_event_item_attr_publisher(Publisher,
    -						encode_pubsub_event_item_attr_node(Node,
    -										   encode_pubsub_event_item_attr_id(Id,
    -														    _xmlns_attrs))),
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -decode_pubsub_event_item_attr_id(__TopXMLNS,
    -				 undefined) ->
    -    undefined;
    -decode_pubsub_event_item_attr_id(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_event_item_attr_id(undefined, _acc) ->
    -    _acc;
    -encode_pubsub_event_item_attr_id(_val, _acc) ->
    -    [{<<"id">>, _val} | _acc].
    -
    -decode_pubsub_event_item_attr_node(__TopXMLNS,
    -				   undefined) ->
    -    undefined;
    -decode_pubsub_event_item_attr_node(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_event_item_attr_node(undefined, _acc) ->
    -    _acc;
    -encode_pubsub_event_item_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_event_item_attr_publisher(__TopXMLNS,
    -					undefined) ->
    -    undefined;
    -decode_pubsub_event_item_attr_publisher(__TopXMLNS,
    -					_val) ->
    -    _val.
    -
    -encode_pubsub_event_item_attr_publisher(undefined,
    -					_acc) ->
    -    _acc;
    -encode_pubsub_event_item_attr_publisher(_val, _acc) ->
    -    [{<<"publisher">>, _val} | _acc].
    -
    -decode_pubsub_event_retract(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"retract">>, _attrs, _els}) ->
    -    Id = decode_pubsub_event_retract_attrs(__TopXMLNS,
    -					   _attrs, undefined),
    -    Id.
    -
    -decode_pubsub_event_retract_attrs(__TopXMLNS,
    -				  [{<<"id">>, _val} | _attrs], _Id) ->
    -    decode_pubsub_event_retract_attrs(__TopXMLNS, _attrs,
    -				      _val);
    -decode_pubsub_event_retract_attrs(__TopXMLNS,
    -				  [_ | _attrs], Id) ->
    -    decode_pubsub_event_retract_attrs(__TopXMLNS, _attrs,
    -				      Id);
    -decode_pubsub_event_retract_attrs(__TopXMLNS, [], Id) ->
    -    decode_pubsub_event_retract_attr_id(__TopXMLNS, Id).
    -
    -encode_pubsub_event_retract(Id, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_pubsub_event_retract_attr_id(Id,
    -						 _xmlns_attrs),
    -    {xmlel, <<"retract">>, _attrs, _els}.
    -
    -decode_pubsub_event_retract_attr_id(__TopXMLNS,
    -				    undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"id">>, <<"retract">>, __TopXMLNS}});
    -decode_pubsub_event_retract_attr_id(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_event_retract_attr_id(_val, _acc) ->
    -    [{<<"id">>, _val} | _acc].
    -
    -decode_pubsub_items(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"items">>, _attrs, _els}) ->
    -    Items = decode_pubsub_items_els(__TopXMLNS, __IgnoreEls,
    -				    _els, []),
    -    {Max_items, Node, Subid} =
    -	decode_pubsub_items_attrs(__TopXMLNS, _attrs, undefined,
    -				  undefined, undefined),
    -    {pubsub_items, Node, Max_items, Subid, Items}.
    -
    -decode_pubsub_items_els(__TopXMLNS, __IgnoreEls, [],
    -			Items) ->
    -    lists:reverse(Items);
    -decode_pubsub_items_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"item">>, _attrs, _} = _el | _els], Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_pubsub_items_els(__TopXMLNS, __IgnoreEls, _els,
    -				   [decode_pubsub_item(__TopXMLNS, __IgnoreEls,
    -						       _el)
    -				    | Items]);
    -       true ->
    -	   decode_pubsub_items_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Items)
    -    end;
    -decode_pubsub_items_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Items) ->
    -    decode_pubsub_items_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Items).
    -
    -decode_pubsub_items_attrs(__TopXMLNS,
    -			  [{<<"max_items">>, _val} | _attrs], _Max_items, Node,
    -			  Subid) ->
    -    decode_pubsub_items_attrs(__TopXMLNS, _attrs, _val,
    -			      Node, Subid);
    -decode_pubsub_items_attrs(__TopXMLNS,
    -			  [{<<"node">>, _val} | _attrs], Max_items, _Node,
    -			  Subid) ->
    -    decode_pubsub_items_attrs(__TopXMLNS, _attrs, Max_items,
    -			      _val, Subid);
    -decode_pubsub_items_attrs(__TopXMLNS,
    -			  [{<<"subid">>, _val} | _attrs], Max_items, Node,
    -			  _Subid) ->
    -    decode_pubsub_items_attrs(__TopXMLNS, _attrs, Max_items,
    -			      Node, _val);
    -decode_pubsub_items_attrs(__TopXMLNS, [_ | _attrs],
    -			  Max_items, Node, Subid) ->
    -    decode_pubsub_items_attrs(__TopXMLNS, _attrs, Max_items,
    -			      Node, Subid);
    -decode_pubsub_items_attrs(__TopXMLNS, [], Max_items,
    -			  Node, Subid) ->
    -    {decode_pubsub_items_attr_max_items(__TopXMLNS,
    -					Max_items),
    -     decode_pubsub_items_attr_node(__TopXMLNS, Node),
    -     decode_pubsub_items_attr_subid(__TopXMLNS, Subid)}.
    -
    -encode_pubsub_items({pubsub_items, Node, Max_items,
    -		     Subid, Items},
    -		    _xmlns_attrs) ->
    -    _els = lists:reverse('encode_pubsub_items_$items'(Items,
    -						      [])),
    -    _attrs = encode_pubsub_items_attr_subid(Subid,
    -					    encode_pubsub_items_attr_node(Node,
    -									  encode_pubsub_items_attr_max_items(Max_items,
    -													     _xmlns_attrs))),
    -    {xmlel, <<"items">>, _attrs, _els}.
    -
    -'encode_pubsub_items_$items'([], _acc) -> _acc;
    -'encode_pubsub_items_$items'([Items | _els], _acc) ->
    -    'encode_pubsub_items_$items'(_els,
    -				 [encode_pubsub_item(Items, []) | _acc]).
    -
    -decode_pubsub_items_attr_max_items(__TopXMLNS,
    -				   undefined) ->
    -    undefined;
    -decode_pubsub_items_attr_max_items(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"max_items">>, <<"items">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_pubsub_items_attr_max_items(undefined, _acc) ->
    -    _acc;
    -encode_pubsub_items_attr_max_items(_val, _acc) ->
    -    [{<<"max_items">>, enc_int(_val)} | _acc].
    -
    -decode_pubsub_items_attr_node(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"node">>, <<"items">>, __TopXMLNS}});
    -decode_pubsub_items_attr_node(__TopXMLNS, _val) -> _val.
    -
    -encode_pubsub_items_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_items_attr_subid(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_pubsub_items_attr_subid(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_items_attr_subid(undefined, _acc) -> _acc;
    -encode_pubsub_items_attr_subid(_val, _acc) ->
    -    [{<<"subid">>, _val} | _acc].
    -
    -decode_pubsub_item(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"item">>, _attrs, _els}) ->
    -    __Xmls = decode_pubsub_item_els(__TopXMLNS, __IgnoreEls,
    -				    _els, []),
    -    Id = decode_pubsub_item_attrs(__TopXMLNS, _attrs,
    -				  undefined),
    -    {pubsub_item, Id, __Xmls}.
    -
    -decode_pubsub_item_els(__TopXMLNS, __IgnoreEls, [],
    -		       __Xmls) ->
    -    lists:reverse(__Xmls);
    -decode_pubsub_item_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, _, _, _} = _el | _els], __Xmls) ->
    -    decode_pubsub_item_els(__TopXMLNS, __IgnoreEls, _els,
    -			   [_el | __Xmls]);
    -decode_pubsub_item_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], __Xmls) ->
    -    decode_pubsub_item_els(__TopXMLNS, __IgnoreEls, _els,
    -			   __Xmls).
    -
    -decode_pubsub_item_attrs(__TopXMLNS,
    -			 [{<<"id">>, _val} | _attrs], _Id) ->
    -    decode_pubsub_item_attrs(__TopXMLNS, _attrs, _val);
    -decode_pubsub_item_attrs(__TopXMLNS, [_ | _attrs],
    -			 Id) ->
    -    decode_pubsub_item_attrs(__TopXMLNS, _attrs, Id);
    -decode_pubsub_item_attrs(__TopXMLNS, [], Id) ->
    -    decode_pubsub_item_attr_id(__TopXMLNS, Id).
    -
    -encode_pubsub_item({pubsub_item, Id, __Xmls},
    -		   _xmlns_attrs) ->
    -    _els = __Xmls,
    -    _attrs = encode_pubsub_item_attr_id(Id, _xmlns_attrs),
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -decode_pubsub_item_attr_id(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_pubsub_item_attr_id(__TopXMLNS, _val) -> _val.
    -
    -encode_pubsub_item_attr_id(undefined, _acc) -> _acc;
    -encode_pubsub_item_attr_id(_val, _acc) ->
    -    [{<<"id">>, _val} | _acc].
    -
    -decode_pubsub_affiliation(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"affiliation">>, _attrs, _els}) ->
    -    {Node, Type} =
    -	decode_pubsub_affiliation_attrs(__TopXMLNS, _attrs,
    -					undefined, undefined),
    -    {pubsub_affiliation, Node, Type}.
    -
    -decode_pubsub_affiliation_attrs(__TopXMLNS,
    -				[{<<"node">>, _val} | _attrs], _Node, Type) ->
    -    decode_pubsub_affiliation_attrs(__TopXMLNS, _attrs,
    -				    _val, Type);
    -decode_pubsub_affiliation_attrs(__TopXMLNS,
    -				[{<<"affiliation">>, _val} | _attrs], Node,
    -				_Type) ->
    -    decode_pubsub_affiliation_attrs(__TopXMLNS, _attrs,
    -				    Node, _val);
    -decode_pubsub_affiliation_attrs(__TopXMLNS,
    -				[_ | _attrs], Node, Type) ->
    -    decode_pubsub_affiliation_attrs(__TopXMLNS, _attrs,
    -				    Node, Type);
    -decode_pubsub_affiliation_attrs(__TopXMLNS, [], Node,
    -				Type) ->
    -    {decode_pubsub_affiliation_attr_node(__TopXMLNS, Node),
    -     decode_pubsub_affiliation_attr_affiliation(__TopXMLNS,
    -						Type)}.
    -
    -encode_pubsub_affiliation({pubsub_affiliation, Node,
    -			   Type},
    -			  _xmlns_attrs) ->
    -    _els = [],
    -    _attrs =
    -	encode_pubsub_affiliation_attr_affiliation(Type,
    -						   encode_pubsub_affiliation_attr_node(Node,
    -										       _xmlns_attrs)),
    -    {xmlel, <<"affiliation">>, _attrs, _els}.
    -
    -decode_pubsub_affiliation_attr_node(__TopXMLNS,
    -				    undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"node">>, <<"affiliation">>,
    -		   __TopXMLNS}});
    -decode_pubsub_affiliation_attr_node(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_pubsub_affiliation_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_affiliation_attr_affiliation(__TopXMLNS,
    -					   undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"affiliation">>, <<"affiliation">>,
    -		   __TopXMLNS}});
    -decode_pubsub_affiliation_attr_affiliation(__TopXMLNS,
    -					   _val) ->
    -    case catch dec_enum(_val,
    -			[member, none, outcast, owner, publisher,
    -			 'publish-only'])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"affiliation">>, <<"affiliation">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_pubsub_affiliation_attr_affiliation(_val,
    -					   _acc) ->
    -    [{<<"affiliation">>, enc_enum(_val)} | _acc].
    -
    -decode_pubsub_subscription(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"subscription">>, _attrs, _els}) ->
    -    {Jid, Node, Subid, Type} =
    -	decode_pubsub_subscription_attrs(__TopXMLNS, _attrs,
    -					 undefined, undefined, undefined,
    -					 undefined),
    -    {pubsub_subscription, Jid, Node, Subid, Type}.
    -
    -decode_pubsub_subscription_attrs(__TopXMLNS,
    -				 [{<<"jid">>, _val} | _attrs], _Jid, Node,
    -				 Subid, Type) ->
    -    decode_pubsub_subscription_attrs(__TopXMLNS, _attrs,
    -				     _val, Node, Subid, Type);
    -decode_pubsub_subscription_attrs(__TopXMLNS,
    -				 [{<<"node">>, _val} | _attrs], Jid, _Node,
    -				 Subid, Type) ->
    -    decode_pubsub_subscription_attrs(__TopXMLNS, _attrs,
    -				     Jid, _val, Subid, Type);
    -decode_pubsub_subscription_attrs(__TopXMLNS,
    -				 [{<<"subid">>, _val} | _attrs], Jid, Node,
    -				 _Subid, Type) ->
    -    decode_pubsub_subscription_attrs(__TopXMLNS, _attrs,
    -				     Jid, Node, _val, Type);
    -decode_pubsub_subscription_attrs(__TopXMLNS,
    -				 [{<<"subscription">>, _val} | _attrs], Jid,
    -				 Node, Subid, _Type) ->
    -    decode_pubsub_subscription_attrs(__TopXMLNS, _attrs,
    -				     Jid, Node, Subid, _val);
    -decode_pubsub_subscription_attrs(__TopXMLNS,
    -				 [_ | _attrs], Jid, Node, Subid, Type) ->
    -    decode_pubsub_subscription_attrs(__TopXMLNS, _attrs,
    -				     Jid, Node, Subid, Type);
    -decode_pubsub_subscription_attrs(__TopXMLNS, [], Jid,
    -				 Node, Subid, Type) ->
    -    {decode_pubsub_subscription_attr_jid(__TopXMLNS, Jid),
    -     decode_pubsub_subscription_attr_node(__TopXMLNS, Node),
    -     decode_pubsub_subscription_attr_subid(__TopXMLNS,
    -					   Subid),
    -     decode_pubsub_subscription_attr_subscription(__TopXMLNS,
    -						  Type)}.
    -
    -encode_pubsub_subscription({pubsub_subscription, Jid,
    -			    Node, Subid, Type},
    -			   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs =
    -	encode_pubsub_subscription_attr_subscription(Type,
    -						     encode_pubsub_subscription_attr_subid(Subid,
    -											   encode_pubsub_subscription_attr_node(Node,
    -																encode_pubsub_subscription_attr_jid(Jid,
    -																				    _xmlns_attrs)))),
    -    {xmlel, <<"subscription">>, _attrs, _els}.
    -
    -decode_pubsub_subscription_attr_jid(__TopXMLNS,
    -				    undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"subscription">>,
    -		   __TopXMLNS}});
    -decode_pubsub_subscription_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"subscription">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_pubsub_subscription_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_pubsub_subscription_attr_node(__TopXMLNS,
    -				     undefined) ->
    -    undefined;
    -decode_pubsub_subscription_attr_node(__TopXMLNS,
    -				     _val) ->
    -    _val.
    -
    -encode_pubsub_subscription_attr_node(undefined, _acc) ->
    -    _acc;
    -encode_pubsub_subscription_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_pubsub_subscription_attr_subid(__TopXMLNS,
    -				      undefined) ->
    -    undefined;
    -decode_pubsub_subscription_attr_subid(__TopXMLNS,
    -				      _val) ->
    -    _val.
    -
    -encode_pubsub_subscription_attr_subid(undefined,
    -				      _acc) ->
    -    _acc;
    -encode_pubsub_subscription_attr_subid(_val, _acc) ->
    -    [{<<"subid">>, _val} | _acc].
    -
    -decode_pubsub_subscription_attr_subscription(__TopXMLNS,
    -					     undefined) ->
    -    undefined;
    -decode_pubsub_subscription_attr_subscription(__TopXMLNS,
    -					     _val) ->
    -    case catch dec_enum(_val,
    -			[none, pending, subscribed, unconfigured])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"subscription">>, <<"subscription">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_pubsub_subscription_attr_subscription(undefined,
    -					     _acc) ->
    -    _acc;
    -encode_pubsub_subscription_attr_subscription(_val,
    -					     _acc) ->
    -    [{<<"subscription">>, enc_enum(_val)} | _acc].
    -
    -decode_xdata(__TopXMLNS, __IgnoreEls,
    -	     {xmlel, <<"x">>, _attrs, _els}) ->
    -    {Fields, Items, Instructions, Reported, Title} =
    -	decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, [], [],
    -			 [], undefined, undefined),
    -    Type = decode_xdata_attrs(__TopXMLNS, _attrs,
    -			      undefined),
    -    {xdata, Type, Instructions, Title, Reported, Items,
    -     Fields}.
    -
    -decode_xdata_els(__TopXMLNS, __IgnoreEls, [], Fields,
    -		 Items, Instructions, Reported, Title) ->
    -    {lists:reverse(Fields), lists:reverse(Items),
    -     lists:reverse(Instructions), Reported, Title};
    -decode_xdata_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"instructions">>, _attrs, _} = _el | _els],
    -		 Fields, Items, Instructions, Reported, Title) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -			    Items,
    -			    case decode_xdata_instructions(__TopXMLNS,
    -							   __IgnoreEls, _el)
    -				of
    -			      undefined -> Instructions;
    -			      _new_el -> [_new_el | Instructions]
    -			    end,
    -			    Reported, Title);
    -       true ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -			    Items, Instructions, Reported, Title)
    -    end;
    -decode_xdata_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"title">>, _attrs, _} = _el | _els], Fields,
    -		 Items, Instructions, Reported, Title) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -			    Items, Instructions, Reported,
    -			    decode_xdata_title(__TopXMLNS, __IgnoreEls, _el));
    -       true ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -			    Items, Instructions, Reported, Title)
    -    end;
    -decode_xdata_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"reported">>, _attrs, _} = _el | _els],
    -		 Fields, Items, Instructions, Reported, Title) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -			    Items, Instructions,
    -			    decode_xdata_reported(__TopXMLNS, __IgnoreEls, _el),
    -			    Title);
    -       true ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -			    Items, Instructions, Reported, Title)
    -    end;
    -decode_xdata_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"item">>, _attrs, _} = _el | _els], Fields,
    -		 Items, Instructions, Reported, Title) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -			    [decode_xdata_item(__TopXMLNS, __IgnoreEls, _el)
    -			     | Items],
    -			    Instructions, Reported, Title);
    -       true ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -			    Items, Instructions, Reported, Title)
    -    end;
    -decode_xdata_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"field">>, _attrs, _} = _el | _els], Fields,
    -		 Items, Instructions, Reported, Title) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els,
    -			    [decode_xdata_field(__TopXMLNS, __IgnoreEls, _el)
    -			     | Fields],
    -			    Items, Instructions, Reported, Title);
    -       true ->
    -	   decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -			    Items, Instructions, Reported, Title)
    -    end;
    -decode_xdata_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		 Fields, Items, Instructions, Reported, Title) ->
    -    decode_xdata_els(__TopXMLNS, __IgnoreEls, _els, Fields,
    -		     Items, Instructions, Reported, Title).
    -
    -decode_xdata_attrs(__TopXMLNS,
    -		   [{<<"type">>, _val} | _attrs], _Type) ->
    -    decode_xdata_attrs(__TopXMLNS, _attrs, _val);
    -decode_xdata_attrs(__TopXMLNS, [_ | _attrs], Type) ->
    -    decode_xdata_attrs(__TopXMLNS, _attrs, Type);
    -decode_xdata_attrs(__TopXMLNS, [], Type) ->
    -    decode_xdata_attr_type(__TopXMLNS, Type).
    -
    -encode_xdata({xdata, Type, Instructions, Title,
    -	      Reported, Items, Fields},
    -	     _xmlns_attrs) ->
    -    _els = lists:reverse('encode_xdata_$fields'(Fields,
    -						'encode_xdata_$items'(Items,
    -								      'encode_xdata_$instructions'(Instructions,
    -												   'encode_xdata_$reported'(Reported,
    -															    'encode_xdata_$title'(Title,
    -																		  [])))))),
    -    _attrs = encode_xdata_attr_type(Type, _xmlns_attrs),
    -    {xmlel, <<"x">>, _attrs, _els}.
    -
    -'encode_xdata_$fields'([], _acc) -> _acc;
    -'encode_xdata_$fields'([Fields | _els], _acc) ->
    -    'encode_xdata_$fields'(_els,
    -			   [encode_xdata_field(Fields, []) | _acc]).
    -
    -'encode_xdata_$items'([], _acc) -> _acc;
    -'encode_xdata_$items'([Items | _els], _acc) ->
    -    'encode_xdata_$items'(_els,
    -			  [encode_xdata_item(Items, []) | _acc]).
    -
    -'encode_xdata_$instructions'([], _acc) -> _acc;
    -'encode_xdata_$instructions'([Instructions | _els],
    -			     _acc) ->
    -    'encode_xdata_$instructions'(_els,
    -				 [encode_xdata_instructions(Instructions, [])
    -				  | _acc]).
    -
    -'encode_xdata_$reported'(undefined, _acc) -> _acc;
    -'encode_xdata_$reported'(Reported, _acc) ->
    -    [encode_xdata_reported(Reported, []) | _acc].
    -
    -'encode_xdata_$title'(undefined, _acc) -> _acc;
    -'encode_xdata_$title'(Title, _acc) ->
    -    [encode_xdata_title(Title, []) | _acc].
    -
    -decode_xdata_attr_type(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"type">>, <<"x">>, __TopXMLNS}});
    -decode_xdata_attr_type(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val,
    -			[cancel, form, result, submit])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"type">>, <<"x">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_xdata_attr_type(_val, _acc) ->
    -    [{<<"type">>, enc_enum(_val)} | _acc].
    -
    -decode_xdata_item(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"item">>, _attrs, _els}) ->
    -    Fields = decode_xdata_item_els(__TopXMLNS, __IgnoreEls,
    -				   _els, []),
    -    Fields.
    -
    -decode_xdata_item_els(__TopXMLNS, __IgnoreEls, [],
    -		      Fields) ->
    -    lists:reverse(Fields);
    -decode_xdata_item_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlel, <<"field">>, _attrs, _} = _el | _els],
    -		      Fields) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				 [decode_xdata_field(__TopXMLNS, __IgnoreEls,
    -						     _el)
    -				  | Fields]);
    -       true ->
    -	   decode_xdata_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Fields)
    -    end;
    -decode_xdata_item_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Fields) ->
    -    decode_xdata_item_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Fields).
    -
    -encode_xdata_item(Fields, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_xdata_item_$fields'(Fields,
    -						     [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -'encode_xdata_item_$fields'([], _acc) -> _acc;
    -'encode_xdata_item_$fields'([Fields | _els], _acc) ->
    -    'encode_xdata_item_$fields'(_els,
    -				[encode_xdata_field(Fields, []) | _acc]).
    -
    -decode_xdata_reported(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"reported">>, _attrs, _els}) ->
    -    Fields = decode_xdata_reported_els(__TopXMLNS,
    -				       __IgnoreEls, _els, []),
    -    Fields.
    -
    -decode_xdata_reported_els(__TopXMLNS, __IgnoreEls, [],
    -			  Fields) ->
    -    lists:reverse(Fields);
    -decode_xdata_reported_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlel, <<"field">>, _attrs, _} = _el | _els],
    -			  Fields) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_reported_els(__TopXMLNS, __IgnoreEls, _els,
    -				     [decode_xdata_field(__TopXMLNS,
    -							 __IgnoreEls, _el)
    -				      | Fields]);
    -       true ->
    -	   decode_xdata_reported_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Fields)
    -    end;
    -decode_xdata_reported_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Fields) ->
    -    decode_xdata_reported_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Fields).
    -
    -encode_xdata_reported(Fields, _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_xdata_reported_$fields'(Fields,
    -						      [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"reported">>, _attrs, _els}.
    -
    -'encode_xdata_reported_$fields'([], _acc) -> _acc;
    -'encode_xdata_reported_$fields'([Fields | _els],
    -				_acc) ->
    -    'encode_xdata_reported_$fields'(_els,
    -				    [encode_xdata_field(Fields, []) | _acc]).
    -
    -decode_xdata_title(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"title">>, _attrs, _els}) ->
    -    Cdata = decode_xdata_title_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <<>>),
    -    Cdata.
    -
    -decode_xdata_title_els(__TopXMLNS, __IgnoreEls, [],
    -		       Cdata) ->
    -    decode_xdata_title_cdata(__TopXMLNS, Cdata);
    -decode_xdata_title_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_xdata_title_els(__TopXMLNS, __IgnoreEls, _els,
    -			   <>);
    -decode_xdata_title_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Cdata) ->
    -    decode_xdata_title_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Cdata).
    -
    -encode_xdata_title(Cdata, _xmlns_attrs) ->
    -    _els = encode_xdata_title_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"title">>, _attrs, _els}.
    -
    -decode_xdata_title_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_xdata_title_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_xdata_title_cdata(undefined, _acc) -> _acc;
    -encode_xdata_title_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_xdata_instructions(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"instructions">>, _attrs, _els}) ->
    -    Cdata = decode_xdata_instructions_els(__TopXMLNS,
    -					  __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_xdata_instructions_els(__TopXMLNS, __IgnoreEls,
    -			      [], Cdata) ->
    -    decode_xdata_instructions_cdata(__TopXMLNS, Cdata);
    -decode_xdata_instructions_els(__TopXMLNS, __IgnoreEls,
    -			      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_xdata_instructions_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <>);
    -decode_xdata_instructions_els(__TopXMLNS, __IgnoreEls,
    -			      [_ | _els], Cdata) ->
    -    decode_xdata_instructions_els(__TopXMLNS, __IgnoreEls,
    -				  _els, Cdata).
    -
    -encode_xdata_instructions(Cdata, _xmlns_attrs) ->
    -    _els = encode_xdata_instructions_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"instructions">>, _attrs, _els}.
    -
    -decode_xdata_instructions_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_xdata_instructions_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_xdata_instructions_cdata(undefined, _acc) ->
    -    _acc;
    -encode_xdata_instructions_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_xdata_field(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"field">>, _attrs, _els}) ->
    -    {Options, Values, Desc, Required} =
    -	decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -			       [], [], undefined, false),
    -    {Label, Type, Var} =
    -	decode_xdata_field_attrs(__TopXMLNS, _attrs, undefined,
    -				 undefined, undefined),
    -    {xdata_field, Label, Type, Var, Required, Desc, Values,
    -     Options}.
    -
    -decode_xdata_field_els(__TopXMLNS, __IgnoreEls, [],
    -		       Options, Values, Desc, Required) ->
    -    {lists:reverse(Options), lists:reverse(Values), Desc,
    -     Required};
    -decode_xdata_field_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"required">>, _attrs, _} = _el | _els],
    -		       Options, Values, Desc, Required) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Options, Values, Desc,
    -				  decode_xdata_field_required(__TopXMLNS,
    -							      __IgnoreEls,
    -							      _el));
    -       true ->
    -	   decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Options, Values, Desc, Required)
    -    end;
    -decode_xdata_field_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"desc">>, _attrs, _} = _el | _els], Options,
    -		       Values, Desc, Required) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Options, Values,
    -				  decode_xdata_field_desc(__TopXMLNS,
    -							  __IgnoreEls, _el),
    -				  Required);
    -       true ->
    -	   decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Options, Values, Desc, Required)
    -    end;
    -decode_xdata_field_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"value">>, _attrs, _} = _el | _els], Options,
    -		       Values, Desc, Required) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Options,
    -				  case decode_xdata_field_value(__TopXMLNS,
    -								__IgnoreEls,
    -								_el)
    -				      of
    -				    undefined -> Values;
    -				    _new_el -> [_new_el | Values]
    -				  end,
    -				  Desc, Required);
    -       true ->
    -	   decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Options, Values, Desc, Required)
    -    end;
    -decode_xdata_field_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"option">>, _attrs, _} = _el | _els],
    -		       Options, Values, Desc, Required) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -				  case decode_xdata_field_option(__TopXMLNS,
    -								 __IgnoreEls,
    -								 _el)
    -				      of
    -				    undefined -> Options;
    -				    _new_el -> [_new_el | Options]
    -				  end,
    -				  Values, Desc, Required);
    -       true ->
    -	   decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Options, Values, Desc, Required)
    -    end;
    -decode_xdata_field_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Options, Values, Desc, Required) ->
    -    decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Options, Values, Desc, Required).
    -
    -decode_xdata_field_attrs(__TopXMLNS,
    -			 [{<<"label">>, _val} | _attrs], _Label, Type, Var) ->
    -    decode_xdata_field_attrs(__TopXMLNS, _attrs, _val, Type,
    -			     Var);
    -decode_xdata_field_attrs(__TopXMLNS,
    -			 [{<<"type">>, _val} | _attrs], Label, _Type, Var) ->
    -    decode_xdata_field_attrs(__TopXMLNS, _attrs, Label,
    -			     _val, Var);
    -decode_xdata_field_attrs(__TopXMLNS,
    -			 [{<<"var">>, _val} | _attrs], Label, Type, _Var) ->
    -    decode_xdata_field_attrs(__TopXMLNS, _attrs, Label,
    -			     Type, _val);
    -decode_xdata_field_attrs(__TopXMLNS, [_ | _attrs],
    -			 Label, Type, Var) ->
    -    decode_xdata_field_attrs(__TopXMLNS, _attrs, Label,
    -			     Type, Var);
    -decode_xdata_field_attrs(__TopXMLNS, [], Label, Type,
    -			 Var) ->
    -    {decode_xdata_field_attr_label(__TopXMLNS, Label),
    -     decode_xdata_field_attr_type(__TopXMLNS, Type),
    -     decode_xdata_field_attr_var(__TopXMLNS, Var)}.
    -
    -encode_xdata_field({xdata_field, Label, Type, Var,
    -		    Required, Desc, Values, Options},
    -		   _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_xdata_field_$options'(Options,
    -						    'encode_xdata_field_$values'(Values,
    -										 'encode_xdata_field_$desc'(Desc,
    -													    'encode_xdata_field_$required'(Required,
    -																	   []))))),
    -    _attrs = encode_xdata_field_attr_var(Var,
    -					 encode_xdata_field_attr_type(Type,
    -								      encode_xdata_field_attr_label(Label,
    -												    _xmlns_attrs))),
    -    {xmlel, <<"field">>, _attrs, _els}.
    -
    -'encode_xdata_field_$options'([], _acc) -> _acc;
    -'encode_xdata_field_$options'([Options | _els], _acc) ->
    -    'encode_xdata_field_$options'(_els,
    -				  [encode_xdata_field_option(Options, [])
    -				   | _acc]).
    -
    -'encode_xdata_field_$values'([], _acc) -> _acc;
    -'encode_xdata_field_$values'([Values | _els], _acc) ->
    -    'encode_xdata_field_$values'(_els,
    -				 [encode_xdata_field_value(Values, []) | _acc]).
    -
    -'encode_xdata_field_$desc'(undefined, _acc) -> _acc;
    -'encode_xdata_field_$desc'(Desc, _acc) ->
    -    [encode_xdata_field_desc(Desc, []) | _acc].
    -
    -'encode_xdata_field_$required'(false, _acc) -> _acc;
    -'encode_xdata_field_$required'(Required, _acc) ->
    -    [encode_xdata_field_required(Required, []) | _acc].
    -
    -decode_xdata_field_attr_label(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_xdata_field_attr_label(__TopXMLNS, _val) -> _val.
    -
    -encode_xdata_field_attr_label(undefined, _acc) -> _acc;
    -encode_xdata_field_attr_label(_val, _acc) ->
    -    [{<<"label">>, _val} | _acc].
    -
    -decode_xdata_field_attr_type(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_xdata_field_attr_type(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val,
    -			[boolean, fixed, hidden, 'jid-multi', 'jid-single',
    -			 'list-multi', 'list-single', 'text-multi',
    -			 'text-private', 'text-single'])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"type">>, <<"field">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_xdata_field_attr_type(undefined, _acc) -> _acc;
    -encode_xdata_field_attr_type(_val, _acc) ->
    -    [{<<"type">>, enc_enum(_val)} | _acc].
    -
    -decode_xdata_field_attr_var(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_xdata_field_attr_var(__TopXMLNS, _val) -> _val.
    -
    -encode_xdata_field_attr_var(undefined, _acc) -> _acc;
    -encode_xdata_field_attr_var(_val, _acc) ->
    -    [{<<"var">>, _val} | _acc].
    -
    -decode_xdata_field_option(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"option">>, _attrs, _els}) ->
    -    Value = decode_xdata_field_option_els(__TopXMLNS,
    -					  __IgnoreEls, _els, error),
    -    Value.
    -
    -decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls,
    -			      [], Value) ->
    -    case Value of
    -      error ->
    -	  erlang:error({xmpp_codec,
    -			{missing_tag, <<"value">>, __TopXMLNS}});
    -      {value, Value1} -> Value1
    -    end;
    -decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls,
    -			      [{xmlel, <<"value">>, _attrs, _} = _el | _els],
    -			      Value) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls,
    -					 _els,
    -					 {value,
    -					  decode_xdata_field_value(__TopXMLNS,
    -								   __IgnoreEls,
    -								   _el)});
    -       true ->
    -	   decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls,
    -					 _els, Value)
    -    end;
    -decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls,
    -			      [_ | _els], Value) ->
    -    decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls,
    -				  _els, Value).
    -
    -encode_xdata_field_option(Value, _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_xdata_field_option_$value'(Value,
    -							 [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"option">>, _attrs, _els}.
    -
    -'encode_xdata_field_option_$value'(Value, _acc) ->
    -    [encode_xdata_field_value(Value, []) | _acc].
    -
    -decode_xdata_field_value(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"value">>, _attrs, _els}) ->
    -    Cdata = decode_xdata_field_value_els(__TopXMLNS,
    -					 __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_xdata_field_value_els(__TopXMLNS, __IgnoreEls,
    -			     [], Cdata) ->
    -    decode_xdata_field_value_cdata(__TopXMLNS, Cdata);
    -decode_xdata_field_value_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_xdata_field_value_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <>);
    -decode_xdata_field_value_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], Cdata) ->
    -    decode_xdata_field_value_els(__TopXMLNS, __IgnoreEls,
    -				 _els, Cdata).
    -
    -encode_xdata_field_value(Cdata, _xmlns_attrs) ->
    -    _els = encode_xdata_field_value_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"value">>, _attrs, _els}.
    -
    -decode_xdata_field_value_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_xdata_field_value_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_xdata_field_value_cdata(undefined, _acc) -> _acc;
    -encode_xdata_field_value_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_xdata_field_desc(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"desc">>, _attrs, _els}) ->
    -    Cdata = decode_xdata_field_desc_els(__TopXMLNS,
    -					__IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_xdata_field_desc_els(__TopXMLNS, __IgnoreEls, [],
    -			    Cdata) ->
    -    decode_xdata_field_desc_cdata(__TopXMLNS, Cdata);
    -decode_xdata_field_desc_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_xdata_field_desc_els(__TopXMLNS, __IgnoreEls,
    -				_els, <>);
    -decode_xdata_field_desc_els(__TopXMLNS, __IgnoreEls,
    -			    [_ | _els], Cdata) ->
    -    decode_xdata_field_desc_els(__TopXMLNS, __IgnoreEls,
    -				_els, Cdata).
    -
    -encode_xdata_field_desc(Cdata, _xmlns_attrs) ->
    -    _els = encode_xdata_field_desc_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"desc">>, _attrs, _els}.
    -
    -decode_xdata_field_desc_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_xdata_field_desc_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_xdata_field_desc_cdata(undefined, _acc) -> _acc;
    -encode_xdata_field_desc_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_xdata_field_required(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"required">>, _attrs, _els}) ->
    -    true.
    -
    -encode_xdata_field_required(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"required">>, _attrs, _els}.
    -
    -decode_vcard_xupdate(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"x">>, _attrs, _els}) ->
    -    Photo = decode_vcard_xupdate_els(__TopXMLNS,
    -				     __IgnoreEls, _els, undefined),
    -    {vcard_xupdate, Photo}.
    -
    -decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls, [],
    -			 Photo) ->
    -    Photo;
    -decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlel, <<"photo">>, _attrs, _} = _el | _els],
    -			 Photo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls, _els,
    -				    decode_vcard_xupdate_photo(__TopXMLNS,
    -							       __IgnoreEls,
    -							       _el));
    -       true ->
    -	   decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls, _els,
    -				    Photo)
    -    end;
    -decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Photo) ->
    -    decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Photo).
    -
    -encode_vcard_xupdate({vcard_xupdate, Photo},
    -		     _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_vcard_xupdate_$photo'(Photo, [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"x">>, _attrs, _els}.
    -
    -'encode_vcard_xupdate_$photo'(undefined, _acc) -> _acc;
    -'encode_vcard_xupdate_$photo'(Photo, _acc) ->
    -    [encode_vcard_xupdate_photo(Photo, []) | _acc].
    -
    -decode_vcard_xupdate_photo(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"photo">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_xupdate_photo_els(__TopXMLNS,
    -					   __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
    -			       [], Cdata) ->
    -    decode_vcard_xupdate_photo_cdata(__TopXMLNS, Cdata);
    -decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
    -			       [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <>);
    -decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
    -			       [_ | _els], Cdata) ->
    -    decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
    -				   _els, Cdata).
    -
    -encode_vcard_xupdate_photo(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_xupdate_photo_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"photo">>, _attrs, _els}.
    -
    -decode_vcard_xupdate_photo_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_xupdate_photo_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_vcard_xupdate_photo_cdata(undefined, _acc) ->
    -    _acc;
    -encode_vcard_xupdate_photo_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard(__TopXMLNS, __IgnoreEls,
    -	     {xmlel, <<"vCard">>, _attrs, _els}) ->
    -    {Mailer, Adr, Class, Categories, Desc, Uid, Prodid,
    -     Jabberid, Sound, Note, Role, Title, Nickname, Rev,
    -     Sort_string, Org, Bday, Key, Tz, Url, Email, Tel, Label,
    -     Fn, Version, N, Photo, Logo, Geo} =
    -	decode_vcard_els(__TopXMLNS, __IgnoreEls, _els,
    -			 undefined, [], undefined, [], undefined, undefined,
    -			 undefined, undefined, undefined, undefined, undefined,
    -			 undefined, undefined, undefined, undefined, undefined,
    -			 undefined, undefined, undefined, undefined, [], [], [],
    -			 undefined, undefined, undefined, undefined, undefined,
    -			 undefined),
    -    {vcard, Version, Fn, N, Nickname, Photo, Bday, Adr,
    -     Label, Tel, Email, Jabberid, Mailer, Tz, Geo, Title,
    -     Role, Logo, Org, Categories, Note, Prodid, Rev,
    -     Sort_string, Sound, Uid, Url, Class, Key, Desc}.
    -
    -decode_vcard_els(__TopXMLNS, __IgnoreEls, [], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    {Mailer, lists:reverse(Adr), Class, Categories, Desc,
    -     Uid, Prodid, Jabberid, Sound, Note, Role, Title,
    -     Nickname, Rev, Sort_string, Org, Bday, Key, Tz, Url,
    -     lists:reverse(Email), lists:reverse(Tel),
    -     lists:reverse(Label), Fn, Version, N, Photo, Logo, Geo};
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"N">>, _attrs, _} = _el | _els], Mailer, Adr,
    -		 Class, Categories, Desc, Uid, Prodid, Jabberid, Sound,
    -		 Note, Role, Title, Nickname, Rev, Sort_string, Org,
    -		 Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version, N,
    -		 Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version,
    -			    decode_vcard_N(__TopXMLNS, __IgnoreEls, _el), Photo,
    -			    Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"ADR">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    [decode_vcard_ADR(__TopXMLNS, __IgnoreEls, _el)
    -			     | Adr],
    -			    Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"LABEL">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    [decode_vcard_LABEL(__TopXMLNS, __IgnoreEls, _el)
    -			     | Label],
    -			    Fn, Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"TEL">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email,
    -			    [decode_vcard_TEL(__TopXMLNS, __IgnoreEls, _el)
    -			     | Tel],
    -			    Label, Fn, Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"EMAIL">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url,
    -			    [decode_vcard_EMAIL(__TopXMLNS, __IgnoreEls, _el)
    -			     | Email],
    -			    Tel, Label, Fn, Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"GEO">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo,
    -			    decode_vcard_GEO(__TopXMLNS, __IgnoreEls, _el));
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"LOGO">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo,
    -			    decode_vcard_LOGO(__TopXMLNS, __IgnoreEls, _el),
    -			    Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"PHOTO">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N,
    -			    decode_vcard_PHOTO(__TopXMLNS, __IgnoreEls, _el),
    -			    Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"ORG">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string,
    -			    decode_vcard_ORG(__TopXMLNS, __IgnoreEls, _el),
    -			    Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -			    N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"SOUND">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    decode_vcard_SOUND(__TopXMLNS, __IgnoreEls, _el),
    -			    Note, Role, Title, Nickname, Rev, Sort_string, Org,
    -			    Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -			    N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"KEY">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday,
    -			    decode_vcard_KEY(__TopXMLNS, __IgnoreEls, _el), Tz,
    -			    Url, Email, Tel, Label, Fn, Version, N, Photo, Logo,
    -			    Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"VERSION">>, _attrs, _} = _el | _els],
    -		 Mailer, Adr, Class, Categories, Desc, Uid, Prodid,
    -		 Jabberid, Sound, Note, Role, Title, Nickname, Rev,
    -		 Sort_string, Org, Bday, Key, Tz, Url, Email, Tel, Label,
    -		 Fn, Version, N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn,
    -			    decode_vcard_VERSION(__TopXMLNS, __IgnoreEls, _el),
    -			    N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"FN">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label,
    -			    decode_vcard_FN(__TopXMLNS, __IgnoreEls, _el),
    -			    Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"NICKNAME">>, _attrs, _} = _el | _els],
    -		 Mailer, Adr, Class, Categories, Desc, Uid, Prodid,
    -		 Jabberid, Sound, Note, Role, Title, Nickname, Rev,
    -		 Sort_string, Org, Bday, Key, Tz, Url, Email, Tel, Label,
    -		 Fn, Version, N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title,
    -			    decode_vcard_NICKNAME(__TopXMLNS, __IgnoreEls, _el),
    -			    Rev, Sort_string, Org, Bday, Key, Tz, Url, Email,
    -			    Tel, Label, Fn, Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"BDAY">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org,
    -			    decode_vcard_BDAY(__TopXMLNS, __IgnoreEls, _el),
    -			    Key, Tz, Url, Email, Tel, Label, Fn, Version, N,
    -			    Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"JABBERID">>, _attrs, _} = _el | _els],
    -		 Mailer, Adr, Class, Categories, Desc, Uid, Prodid,
    -		 Jabberid, Sound, Note, Role, Title, Nickname, Rev,
    -		 Sort_string, Org, Bday, Key, Tz, Url, Email, Tel, Label,
    -		 Fn, Version, N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid,
    -			    decode_vcard_JABBERID(__TopXMLNS, __IgnoreEls, _el),
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"MAILER">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els,
    -			    decode_vcard_MAILER(__TopXMLNS, __IgnoreEls, _el),
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"TZ">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key,
    -			    decode_vcard_TZ(__TopXMLNS, __IgnoreEls, _el), Url,
    -			    Email, Tel, Label, Fn, Version, N, Photo, Logo,
    -			    Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"TITLE">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role,
    -			    decode_vcard_TITLE(__TopXMLNS, __IgnoreEls, _el),
    -			    Nickname, Rev, Sort_string, Org, Bday, Key, Tz, Url,
    -			    Email, Tel, Label, Fn, Version, N, Photo, Logo,
    -			    Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"ROLE">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note,
    -			    decode_vcard_ROLE(__TopXMLNS, __IgnoreEls, _el),
    -			    Title, Nickname, Rev, Sort_string, Org, Bday, Key,
    -			    Tz, Url, Email, Tel, Label, Fn, Version, N, Photo,
    -			    Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"NOTE">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound,
    -			    decode_vcard_NOTE(__TopXMLNS, __IgnoreEls, _el),
    -			    Role, Title, Nickname, Rev, Sort_string, Org, Bday,
    -			    Key, Tz, Url, Email, Tel, Label, Fn, Version, N,
    -			    Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"PRODID">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid,
    -			    decode_vcard_PRODID(__TopXMLNS, __IgnoreEls, _el),
    -			    Jabberid, Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"REV">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname,
    -			    decode_vcard_REV(__TopXMLNS, __IgnoreEls, _el),
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"SORT-STRING">>, _attrs, _} = _el | _els],
    -		 Mailer, Adr, Class, Categories, Desc, Uid, Prodid,
    -		 Jabberid, Sound, Note, Role, Title, Nickname, Rev,
    -		 Sort_string, Org, Bday, Key, Tz, Url, Email, Tel, Label,
    -		 Fn, Version, N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    decode_vcard_SORT_STRING(__TopXMLNS, __IgnoreEls,
    -						     _el),
    -			    Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn,
    -			    Version, N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"UID">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc,
    -			    decode_vcard_UID(__TopXMLNS, __IgnoreEls, _el),
    -			    Prodid, Jabberid, Sound, Note, Role, Title,
    -			    Nickname, Rev, Sort_string, Org, Bday, Key, Tz, Url,
    -			    Email, Tel, Label, Fn, Version, N, Photo, Logo,
    -			    Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"URL">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz,
    -			    decode_vcard_URL(__TopXMLNS, __IgnoreEls, _el),
    -			    Email, Tel, Label, Fn, Version, N, Photo, Logo,
    -			    Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"DESC">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories,
    -			    decode_vcard_DESC(__TopXMLNS, __IgnoreEls, _el),
    -			    Uid, Prodid, Jabberid, Sound, Note, Role, Title,
    -			    Nickname, Rev, Sort_string, Org, Bday, Key, Tz, Url,
    -			    Email, Tel, Label, Fn, Version, N, Photo, Logo,
    -			    Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"CATEGORIES">>, _attrs, _} = _el | _els],
    -		 Mailer, Adr, Class, Categories, Desc, Uid, Prodid,
    -		 Jabberid, Sound, Note, Role, Title, Nickname, Rev,
    -		 Sort_string, Org, Bday, Key, Tz, Url, Email, Tel, Label,
    -		 Fn, Version, N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class,
    -			    decode_vcard_CATEGORIES(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			    Desc, Uid, Prodid, Jabberid, Sound, Note, Role,
    -			    Title, Nickname, Rev, Sort_string, Org, Bday, Key,
    -			    Tz, Url, Email, Tel, Label, Fn, Version, N, Photo,
    -			    Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"CLASS">>, _attrs, _} = _el | _els], Mailer,
    -		 Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		 Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		 Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		 N, Photo, Logo, Geo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr,
    -			    decode_vcard_CLASS(__TopXMLNS, __IgnoreEls, _el),
    -			    Categories, Desc, Uid, Prodid, Jabberid, Sound,
    -			    Note, Role, Title, Nickname, Rev, Sort_string, Org,
    -			    Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -			    N, Photo, Logo, Geo);
    -       true ->
    -	   decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -			    Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -			    Sound, Note, Role, Title, Nickname, Rev,
    -			    Sort_string, Org, Bday, Key, Tz, Url, Email, Tel,
    -			    Label, Fn, Version, N, Photo, Logo, Geo)
    -    end;
    -decode_vcard_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		 Mailer, Adr, Class, Categories, Desc, Uid, Prodid,
    -		 Jabberid, Sound, Note, Role, Title, Nickname, Rev,
    -		 Sort_string, Org, Bday, Key, Tz, Url, Email, Tel, Label,
    -		 Fn, Version, N, Photo, Logo, Geo) ->
    -    decode_vcard_els(__TopXMLNS, __IgnoreEls, _els, Mailer,
    -		     Adr, Class, Categories, Desc, Uid, Prodid, Jabberid,
    -		     Sound, Note, Role, Title, Nickname, Rev, Sort_string,
    -		     Org, Bday, Key, Tz, Url, Email, Tel, Label, Fn, Version,
    -		     N, Photo, Logo, Geo).
    -
    -encode_vcard({vcard, Version, Fn, N, Nickname, Photo,
    -	      Bday, Adr, Label, Tel, Email, Jabberid, Mailer, Tz, Geo,
    -	      Title, Role, Logo, Org, Categories, Note, Prodid, Rev,
    -	      Sort_string, Sound, Uid, Url, Class, Key, Desc},
    -	     _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_$mailer'(Mailer,
    -						'encode_vcard_$adr'(Adr,
    -								    'encode_vcard_$class'(Class,
    -											  'encode_vcard_$categories'(Categories,
    -														     'encode_vcard_$desc'(Desc,
    -																	  'encode_vcard_$uid'(Uid,
    -																			      'encode_vcard_$prodid'(Prodid,
    -																						     'encode_vcard_$jabberid'(Jabberid,
    -																									      'encode_vcard_$sound'(Sound,
    -																												    'encode_vcard_$note'(Note,
    -																															 'encode_vcard_$role'(Role,
    -																																	      'encode_vcard_$title'(Title,
    -																																				    'encode_vcard_$nickname'(Nickname,
    -																																							     'encode_vcard_$rev'(Rev,
    -																																										 'encode_vcard_$sort_string'(Sort_string,
    -																																													     'encode_vcard_$org'(Org,
    -																																																 'encode_vcard_$bday'(Bday,
    -																																																		      'encode_vcard_$key'(Key,
    -																																																					  'encode_vcard_$tz'(Tz,
    -																																																							     'encode_vcard_$url'(Url,
    -																																																										 'encode_vcard_$email'(Email,
    -																																																												       'encode_vcard_$tel'(Tel,
    -																																																															   'encode_vcard_$label'(Label,
    -																																																																		 'encode_vcard_$fn'(Fn,
    -																																																																				    'encode_vcard_$version'(Version,
    -																																																																							    'encode_vcard_$n'(N,
    -																																																																									      'encode_vcard_$photo'(Photo,
    -																																																																												    'encode_vcard_$logo'(Logo,
    -																																																																															 'encode_vcard_$geo'(Geo,
    -																																																																																	     [])))))))))))))))))))))))))))))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"vCard">>, _attrs, _els}.
    -
    -'encode_vcard_$mailer'(undefined, _acc) -> _acc;
    -'encode_vcard_$mailer'(Mailer, _acc) ->
    -    [encode_vcard_MAILER(Mailer, []) | _acc].
    -
    -'encode_vcard_$adr'([], _acc) -> _acc;
    -'encode_vcard_$adr'([Adr | _els], _acc) ->
    -    'encode_vcard_$adr'(_els,
    -			[encode_vcard_ADR(Adr, []) | _acc]).
    -
    -'encode_vcard_$class'(undefined, _acc) -> _acc;
    -'encode_vcard_$class'(Class, _acc) ->
    -    [encode_vcard_CLASS(Class, []) | _acc].
    -
    -'encode_vcard_$categories'([], _acc) -> _acc;
    -'encode_vcard_$categories'(Categories, _acc) ->
    -    [encode_vcard_CATEGORIES(Categories, []) | _acc].
    -
    -'encode_vcard_$desc'(undefined, _acc) -> _acc;
    -'encode_vcard_$desc'(Desc, _acc) ->
    -    [encode_vcard_DESC(Desc, []) | _acc].
    -
    -'encode_vcard_$uid'(undefined, _acc) -> _acc;
    -'encode_vcard_$uid'(Uid, _acc) ->
    -    [encode_vcard_UID(Uid, []) | _acc].
    -
    -'encode_vcard_$prodid'(undefined, _acc) -> _acc;
    -'encode_vcard_$prodid'(Prodid, _acc) ->
    -    [encode_vcard_PRODID(Prodid, []) | _acc].
    -
    -'encode_vcard_$jabberid'(undefined, _acc) -> _acc;
    -'encode_vcard_$jabberid'(Jabberid, _acc) ->
    -    [encode_vcard_JABBERID(Jabberid, []) | _acc].
    -
    -'encode_vcard_$sound'(undefined, _acc) -> _acc;
    -'encode_vcard_$sound'(Sound, _acc) ->
    -    [encode_vcard_SOUND(Sound, []) | _acc].
    -
    -'encode_vcard_$note'(undefined, _acc) -> _acc;
    -'encode_vcard_$note'(Note, _acc) ->
    -    [encode_vcard_NOTE(Note, []) | _acc].
    -
    -'encode_vcard_$role'(undefined, _acc) -> _acc;
    -'encode_vcard_$role'(Role, _acc) ->
    -    [encode_vcard_ROLE(Role, []) | _acc].
    -
    -'encode_vcard_$title'(undefined, _acc) -> _acc;
    -'encode_vcard_$title'(Title, _acc) ->
    -    [encode_vcard_TITLE(Title, []) | _acc].
    -
    -'encode_vcard_$nickname'(undefined, _acc) -> _acc;
    -'encode_vcard_$nickname'(Nickname, _acc) ->
    -    [encode_vcard_NICKNAME(Nickname, []) | _acc].
    -
    -'encode_vcard_$rev'(undefined, _acc) -> _acc;
    -'encode_vcard_$rev'(Rev, _acc) ->
    -    [encode_vcard_REV(Rev, []) | _acc].
    -
    -'encode_vcard_$sort_string'(undefined, _acc) -> _acc;
    -'encode_vcard_$sort_string'(Sort_string, _acc) ->
    -    [encode_vcard_SORT_STRING(Sort_string, []) | _acc].
    -
    -'encode_vcard_$org'(undefined, _acc) -> _acc;
    -'encode_vcard_$org'(Org, _acc) ->
    -    [encode_vcard_ORG(Org, []) | _acc].
    -
    -'encode_vcard_$bday'(undefined, _acc) -> _acc;
    -'encode_vcard_$bday'(Bday, _acc) ->
    -    [encode_vcard_BDAY(Bday, []) | _acc].
    -
    -'encode_vcard_$key'(undefined, _acc) -> _acc;
    -'encode_vcard_$key'(Key, _acc) ->
    -    [encode_vcard_KEY(Key, []) | _acc].
    -
    -'encode_vcard_$tz'(undefined, _acc) -> _acc;
    -'encode_vcard_$tz'(Tz, _acc) ->
    -    [encode_vcard_TZ(Tz, []) | _acc].
    -
    -'encode_vcard_$url'(undefined, _acc) -> _acc;
    -'encode_vcard_$url'(Url, _acc) ->
    -    [encode_vcard_URL(Url, []) | _acc].
    -
    -'encode_vcard_$email'([], _acc) -> _acc;
    -'encode_vcard_$email'([Email | _els], _acc) ->
    -    'encode_vcard_$email'(_els,
    -			  [encode_vcard_EMAIL(Email, []) | _acc]).
    -
    -'encode_vcard_$tel'([], _acc) -> _acc;
    -'encode_vcard_$tel'([Tel | _els], _acc) ->
    -    'encode_vcard_$tel'(_els,
    -			[encode_vcard_TEL(Tel, []) | _acc]).
    -
    -'encode_vcard_$label'([], _acc) -> _acc;
    -'encode_vcard_$label'([Label | _els], _acc) ->
    -    'encode_vcard_$label'(_els,
    -			  [encode_vcard_LABEL(Label, []) | _acc]).
    -
    -'encode_vcard_$fn'(undefined, _acc) -> _acc;
    -'encode_vcard_$fn'(Fn, _acc) ->
    -    [encode_vcard_FN(Fn, []) | _acc].
    -
    -'encode_vcard_$version'(undefined, _acc) -> _acc;
    -'encode_vcard_$version'(Version, _acc) ->
    -    [encode_vcard_VERSION(Version, []) | _acc].
    -
    -'encode_vcard_$n'(undefined, _acc) -> _acc;
    -'encode_vcard_$n'(N, _acc) ->
    -    [encode_vcard_N(N, []) | _acc].
    -
    -'encode_vcard_$photo'(undefined, _acc) -> _acc;
    -'encode_vcard_$photo'(Photo, _acc) ->
    -    [encode_vcard_PHOTO(Photo, []) | _acc].
    -
    -'encode_vcard_$logo'(undefined, _acc) -> _acc;
    -'encode_vcard_$logo'(Logo, _acc) ->
    -    [encode_vcard_LOGO(Logo, []) | _acc].
    -
    -'encode_vcard_$geo'(undefined, _acc) -> _acc;
    -'encode_vcard_$geo'(Geo, _acc) ->
    -    [encode_vcard_GEO(Geo, []) | _acc].
    -
    -decode_vcard_CLASS(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"CLASS">>, _attrs, _els}) ->
    -    Class = decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls,
    -				   _els, undefined),
    -    Class.
    -
    -decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls, [],
    -		       Class) ->
    -    Class;
    -decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"PUBLIC">>, _attrs, _} = _el | _els],
    -		       Class) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls, _els,
    -				  decode_vcard_PUBLIC(__TopXMLNS, __IgnoreEls,
    -						      _el));
    -       true ->
    -	   decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Class)
    -    end;
    -decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"PRIVATE">>, _attrs, _} = _el | _els],
    -		       Class) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls, _els,
    -				  decode_vcard_PRIVATE(__TopXMLNS, __IgnoreEls,
    -						       _el));
    -       true ->
    -	   decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Class)
    -    end;
    -decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"CONFIDENTIAL">>, _attrs, _} = _el | _els],
    -		       Class) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls, _els,
    -				  decode_vcard_CONFIDENTIAL(__TopXMLNS,
    -							    __IgnoreEls, _el));
    -       true ->
    -	   decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Class)
    -    end;
    -decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Class) ->
    -    decode_vcard_CLASS_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Class).
    -
    -encode_vcard_CLASS(Class, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_CLASS_$class'(Class,
    -						     [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"CLASS">>, _attrs, _els}.
    -
    -'encode_vcard_CLASS_$class'(undefined, _acc) -> _acc;
    -'encode_vcard_CLASS_$class'(public = Class, _acc) ->
    -    [encode_vcard_PUBLIC(Class, []) | _acc];
    -'encode_vcard_CLASS_$class'(private = Class, _acc) ->
    -    [encode_vcard_PRIVATE(Class, []) | _acc];
    -'encode_vcard_CLASS_$class'(confidential = Class,
    -			    _acc) ->
    -    [encode_vcard_CONFIDENTIAL(Class, []) | _acc].
    -
    -decode_vcard_CATEGORIES(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"CATEGORIES">>, _attrs, _els}) ->
    -    Keywords = decode_vcard_CATEGORIES_els(__TopXMLNS,
    -					   __IgnoreEls, _els, []),
    -    Keywords.
    -
    -decode_vcard_CATEGORIES_els(__TopXMLNS, __IgnoreEls, [],
    -			    Keywords) ->
    -    lists:reverse(Keywords);
    -decode_vcard_CATEGORIES_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlel, <<"KEYWORD">>, _attrs, _} = _el | _els],
    -			    Keywords) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_CATEGORIES_els(__TopXMLNS, __IgnoreEls,
    -				       _els,
    -				       case decode_vcard_KEYWORD(__TopXMLNS,
    -								 __IgnoreEls,
    -								 _el)
    -					   of
    -					 undefined -> Keywords;
    -					 _new_el -> [_new_el | Keywords]
    -				       end);
    -       true ->
    -	   decode_vcard_CATEGORIES_els(__TopXMLNS, __IgnoreEls,
    -				       _els, Keywords)
    -    end;
    -decode_vcard_CATEGORIES_els(__TopXMLNS, __IgnoreEls,
    -			    [_ | _els], Keywords) ->
    -    decode_vcard_CATEGORIES_els(__TopXMLNS, __IgnoreEls,
    -				_els, Keywords).
    -
    -encode_vcard_CATEGORIES(Keywords, _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_vcard_CATEGORIES_$keywords'(Keywords,
    -							  [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"CATEGORIES">>, _attrs, _els}.
    -
    -'encode_vcard_CATEGORIES_$keywords'([], _acc) -> _acc;
    -'encode_vcard_CATEGORIES_$keywords'([Keywords | _els],
    -				    _acc) ->
    -    'encode_vcard_CATEGORIES_$keywords'(_els,
    -					[encode_vcard_KEYWORD(Keywords, [])
    -					 | _acc]).
    -
    -decode_vcard_KEY(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"KEY">>, _attrs, _els}) ->
    -    {Cred, Type} = decode_vcard_KEY_els(__TopXMLNS,
    -					__IgnoreEls, _els, undefined,
    -					undefined),
    -    {vcard_key, Type, Cred}.
    -
    -decode_vcard_KEY_els(__TopXMLNS, __IgnoreEls, [], Cred,
    -		     Type) ->
    -    {Cred, Type};
    -decode_vcard_KEY_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"TYPE">>, _attrs, _} = _el | _els], Cred,
    -		     Type) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_KEY_els(__TopXMLNS, __IgnoreEls, _els,
    -				Cred,
    -				decode_vcard_TYPE(__TopXMLNS, __IgnoreEls,
    -						  _el));
    -       true ->
    -	   decode_vcard_KEY_els(__TopXMLNS, __IgnoreEls, _els,
    -				Cred, Type)
    -    end;
    -decode_vcard_KEY_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"CRED">>, _attrs, _} = _el | _els], Cred,
    -		     Type) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_KEY_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_vcard_CRED(__TopXMLNS, __IgnoreEls, _el),
    -				Type);
    -       true ->
    -	   decode_vcard_KEY_els(__TopXMLNS, __IgnoreEls, _els,
    -				Cred, Type)
    -    end;
    -decode_vcard_KEY_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cred, Type) ->
    -    decode_vcard_KEY_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cred, Type).
    -
    -encode_vcard_KEY({vcard_key, Type, Cred},
    -		 _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_KEY_$cred'(Cred,
    -						  'encode_vcard_KEY_$type'(Type,
    -									   []))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"KEY">>, _attrs, _els}.
    -
    -'encode_vcard_KEY_$cred'(undefined, _acc) -> _acc;
    -'encode_vcard_KEY_$cred'(Cred, _acc) ->
    -    [encode_vcard_CRED(Cred, []) | _acc].
    -
    -'encode_vcard_KEY_$type'(undefined, _acc) -> _acc;
    -'encode_vcard_KEY_$type'(Type, _acc) ->
    -    [encode_vcard_TYPE(Type, []) | _acc].
    -
    -decode_vcard_SOUND(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"SOUND">>, _attrs, _els}) ->
    -    {Phonetic, Extval, Binval} =
    -	decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls, _els,
    -			       undefined, undefined, undefined),
    -    {vcard_sound, Phonetic, Binval, Extval}.
    -
    -decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls, [],
    -		       Phonetic, Extval, Binval) ->
    -    {Phonetic, Extval, Binval};
    -decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"BINVAL">>, _attrs, _} = _el | _els],
    -		       Phonetic, Extval, Binval) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Phonetic, Extval,
    -				  decode_vcard_BINVAL(__TopXMLNS, __IgnoreEls,
    -						      _el));
    -       true ->
    -	   decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Phonetic, Extval, Binval)
    -    end;
    -decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"EXTVAL">>, _attrs, _} = _el | _els],
    -		       Phonetic, Extval, Binval) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Phonetic,
    -				  decode_vcard_EXTVAL(__TopXMLNS, __IgnoreEls,
    -						      _el),
    -				  Binval);
    -       true ->
    -	   decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Phonetic, Extval, Binval)
    -    end;
    -decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"PHONETIC">>, _attrs, _} = _el | _els],
    -		       Phonetic, Extval, Binval) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls, _els,
    -				  decode_vcard_PHONETIC(__TopXMLNS, __IgnoreEls,
    -							_el),
    -				  Extval, Binval);
    -       true ->
    -	   decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Phonetic, Extval, Binval)
    -    end;
    -decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Phonetic, Extval, Binval) ->
    -    decode_vcard_SOUND_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Phonetic, Extval, Binval).
    -
    -encode_vcard_SOUND({vcard_sound, Phonetic, Binval,
    -		    Extval},
    -		   _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_vcard_SOUND_$phonetic'(Phonetic,
    -						     'encode_vcard_SOUND_$extval'(Extval,
    -										  'encode_vcard_SOUND_$binval'(Binval,
    -													       [])))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"SOUND">>, _attrs, _els}.
    -
    -'encode_vcard_SOUND_$phonetic'(undefined, _acc) -> _acc;
    -'encode_vcard_SOUND_$phonetic'(Phonetic, _acc) ->
    -    [encode_vcard_PHONETIC(Phonetic, []) | _acc].
    -
    -'encode_vcard_SOUND_$extval'(undefined, _acc) -> _acc;
    -'encode_vcard_SOUND_$extval'(Extval, _acc) ->
    -    [encode_vcard_EXTVAL(Extval, []) | _acc].
    -
    -'encode_vcard_SOUND_$binval'(undefined, _acc) -> _acc;
    -'encode_vcard_SOUND_$binval'(Binval, _acc) ->
    -    [encode_vcard_BINVAL(Binval, []) | _acc].
    -
    -decode_vcard_ORG(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"ORG">>, _attrs, _els}) ->
    -    {Units, Name} = decode_vcard_ORG_els(__TopXMLNS,
    -					 __IgnoreEls, _els, [], undefined),
    -    {vcard_org, Name, Units}.
    -
    -decode_vcard_ORG_els(__TopXMLNS, __IgnoreEls, [], Units,
    -		     Name) ->
    -    {lists:reverse(Units), Name};
    -decode_vcard_ORG_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"ORGNAME">>, _attrs, _} = _el | _els], Units,
    -		     Name) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ORG_els(__TopXMLNS, __IgnoreEls, _els,
    -				Units,
    -				decode_vcard_ORGNAME(__TopXMLNS, __IgnoreEls,
    -						     _el));
    -       true ->
    -	   decode_vcard_ORG_els(__TopXMLNS, __IgnoreEls, _els,
    -				Units, Name)
    -    end;
    -decode_vcard_ORG_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"ORGUNIT">>, _attrs, _} = _el | _els], Units,
    -		     Name) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ORG_els(__TopXMLNS, __IgnoreEls, _els,
    -				case decode_vcard_ORGUNIT(__TopXMLNS,
    -							  __IgnoreEls, _el)
    -				    of
    -				  undefined -> Units;
    -				  _new_el -> [_new_el | Units]
    -				end,
    -				Name);
    -       true ->
    -	   decode_vcard_ORG_els(__TopXMLNS, __IgnoreEls, _els,
    -				Units, Name)
    -    end;
    -decode_vcard_ORG_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Units, Name) ->
    -    decode_vcard_ORG_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Units, Name).
    -
    -encode_vcard_ORG({vcard_org, Name, Units},
    -		 _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_ORG_$units'(Units,
    -						   'encode_vcard_ORG_$name'(Name,
    -									    []))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"ORG">>, _attrs, _els}.
    -
    -'encode_vcard_ORG_$units'([], _acc) -> _acc;
    -'encode_vcard_ORG_$units'([Units | _els], _acc) ->
    -    'encode_vcard_ORG_$units'(_els,
    -			      [encode_vcard_ORGUNIT(Units, []) | _acc]).
    -
    -'encode_vcard_ORG_$name'(undefined, _acc) -> _acc;
    -'encode_vcard_ORG_$name'(Name, _acc) ->
    -    [encode_vcard_ORGNAME(Name, []) | _acc].
    -
    -decode_vcard_PHOTO(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"PHOTO">>, _attrs, _els}) ->
    -    {Type, Extval, Binval} =
    -	decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls, _els,
    -			       undefined, undefined, undefined),
    -    {vcard_photo, Type, Binval, Extval}.
    -
    -decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls, [],
    -		       Type, Extval, Binval) ->
    -    {Type, Extval, Binval};
    -decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"TYPE">>, _attrs, _} = _el | _els], Type,
    -		       Extval, Binval) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls, _els,
    -				  decode_vcard_TYPE(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				  Extval, Binval);
    -       true ->
    -	   decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Type, Extval, Binval)
    -    end;
    -decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"BINVAL">>, _attrs, _} = _el | _els], Type,
    -		       Extval, Binval) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Type, Extval,
    -				  decode_vcard_BINVAL(__TopXMLNS, __IgnoreEls,
    -						      _el));
    -       true ->
    -	   decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Type, Extval, Binval)
    -    end;
    -decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"EXTVAL">>, _attrs, _} = _el | _els], Type,
    -		       Extval, Binval) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Type,
    -				  decode_vcard_EXTVAL(__TopXMLNS, __IgnoreEls,
    -						      _el),
    -				  Binval);
    -       true ->
    -	   decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Type, Extval, Binval)
    -    end;
    -decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Type, Extval, Binval) ->
    -    decode_vcard_PHOTO_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Type, Extval, Binval).
    -
    -encode_vcard_PHOTO({vcard_photo, Type, Binval, Extval},
    -		   _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_PHOTO_$type'(Type,
    -						    'encode_vcard_PHOTO_$extval'(Extval,
    -										 'encode_vcard_PHOTO_$binval'(Binval,
    -													      [])))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PHOTO">>, _attrs, _els}.
    -
    -'encode_vcard_PHOTO_$type'(undefined, _acc) -> _acc;
    -'encode_vcard_PHOTO_$type'(Type, _acc) ->
    -    [encode_vcard_TYPE(Type, []) | _acc].
    -
    -'encode_vcard_PHOTO_$extval'(undefined, _acc) -> _acc;
    -'encode_vcard_PHOTO_$extval'(Extval, _acc) ->
    -    [encode_vcard_EXTVAL(Extval, []) | _acc].
    -
    -'encode_vcard_PHOTO_$binval'(undefined, _acc) -> _acc;
    -'encode_vcard_PHOTO_$binval'(Binval, _acc) ->
    -    [encode_vcard_BINVAL(Binval, []) | _acc].
    -
    -decode_vcard_LOGO(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"LOGO">>, _attrs, _els}) ->
    -    {Type, Extval, Binval} =
    -	decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls, _els,
    -			      undefined, undefined, undefined),
    -    {vcard_logo, Type, Binval, Extval}.
    -
    -decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls, [], Type,
    -		      Extval, Binval) ->
    -    {Type, Extval, Binval};
    -decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlel, <<"TYPE">>, _attrs, _} = _el | _els], Type,
    -		      Extval, Binval) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls, _els,
    -				 decode_vcard_TYPE(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -				 Extval, Binval);
    -       true ->
    -	   decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Type, Extval, Binval)
    -    end;
    -decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlel, <<"BINVAL">>, _attrs, _} = _el | _els], Type,
    -		      Extval, Binval) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Type, Extval,
    -				 decode_vcard_BINVAL(__TopXMLNS, __IgnoreEls,
    -						     _el));
    -       true ->
    -	   decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Type, Extval, Binval)
    -    end;
    -decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlel, <<"EXTVAL">>, _attrs, _} = _el | _els], Type,
    -		      Extval, Binval) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Type,
    -				 decode_vcard_EXTVAL(__TopXMLNS, __IgnoreEls,
    -						     _el),
    -				 Binval);
    -       true ->
    -	   decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Type, Extval, Binval)
    -    end;
    -decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Type, Extval, Binval) ->
    -    decode_vcard_LOGO_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Type, Extval, Binval).
    -
    -encode_vcard_LOGO({vcard_logo, Type, Binval, Extval},
    -		  _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_LOGO_$type'(Type,
    -						   'encode_vcard_LOGO_$extval'(Extval,
    -									       'encode_vcard_LOGO_$binval'(Binval,
    -													   [])))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"LOGO">>, _attrs, _els}.
    -
    -'encode_vcard_LOGO_$type'(undefined, _acc) -> _acc;
    -'encode_vcard_LOGO_$type'(Type, _acc) ->
    -    [encode_vcard_TYPE(Type, []) | _acc].
    -
    -'encode_vcard_LOGO_$extval'(undefined, _acc) -> _acc;
    -'encode_vcard_LOGO_$extval'(Extval, _acc) ->
    -    [encode_vcard_EXTVAL(Extval, []) | _acc].
    -
    -'encode_vcard_LOGO_$binval'(undefined, _acc) -> _acc;
    -'encode_vcard_LOGO_$binval'(Binval, _acc) ->
    -    [encode_vcard_BINVAL(Binval, []) | _acc].
    -
    -decode_vcard_BINVAL(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"BINVAL">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_BINVAL_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_BINVAL_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_BINVAL_cdata(__TopXMLNS, Cdata);
    -decode_vcard_BINVAL_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_BINVAL_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_BINVAL_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_BINVAL_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_BINVAL(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_BINVAL_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"BINVAL">>, _attrs, _els}.
    -
    -decode_vcard_BINVAL_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_BINVAL_cdata(__TopXMLNS, _val) ->
    -    case catch base64:decode(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"BINVAL">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_vcard_BINVAL_cdata(undefined, _acc) -> _acc;
    -encode_vcard_BINVAL_cdata(_val, _acc) ->
    -    [{xmlcdata, base64:encode(_val)} | _acc].
    -
    -decode_vcard_GEO(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"GEO">>, _attrs, _els}) ->
    -    {Lat, Lon} = decode_vcard_GEO_els(__TopXMLNS,
    -				      __IgnoreEls, _els, undefined, undefined),
    -    {vcard_geo, Lat, Lon}.
    -
    -decode_vcard_GEO_els(__TopXMLNS, __IgnoreEls, [], Lat,
    -		     Lon) ->
    -    {Lat, Lon};
    -decode_vcard_GEO_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"LAT">>, _attrs, _} = _el | _els], Lat,
    -		     Lon) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_GEO_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_vcard_LAT(__TopXMLNS, __IgnoreEls, _el),
    -				Lon);
    -       true ->
    -	   decode_vcard_GEO_els(__TopXMLNS, __IgnoreEls, _els, Lat,
    -				Lon)
    -    end;
    -decode_vcard_GEO_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"LON">>, _attrs, _} = _el | _els], Lat,
    -		     Lon) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_GEO_els(__TopXMLNS, __IgnoreEls, _els, Lat,
    -				decode_vcard_LON(__TopXMLNS, __IgnoreEls, _el));
    -       true ->
    -	   decode_vcard_GEO_els(__TopXMLNS, __IgnoreEls, _els, Lat,
    -				Lon)
    -    end;
    -decode_vcard_GEO_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Lat, Lon) ->
    -    decode_vcard_GEO_els(__TopXMLNS, __IgnoreEls, _els, Lat,
    -			 Lon).
    -
    -encode_vcard_GEO({vcard_geo, Lat, Lon}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_GEO_$lat'(Lat,
    -						 'encode_vcard_GEO_$lon'(Lon,
    -									 []))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"GEO">>, _attrs, _els}.
    -
    -'encode_vcard_GEO_$lat'(undefined, _acc) -> _acc;
    -'encode_vcard_GEO_$lat'(Lat, _acc) ->
    -    [encode_vcard_LAT(Lat, []) | _acc].
    -
    -'encode_vcard_GEO_$lon'(undefined, _acc) -> _acc;
    -'encode_vcard_GEO_$lon'(Lon, _acc) ->
    -    [encode_vcard_LON(Lon, []) | _acc].
    -
    -decode_vcard_EMAIL(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"EMAIL">>, _attrs, _els}) ->
    -    {X400, Userid, Internet, Home, Pref, Work} =
    -	decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -			       false, undefined, false, false, false, false),
    -    {vcard_email, Home, Work, Internet, Pref, X400, Userid}.
    -
    -decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, [],
    -		       X400, Userid, Internet, Home, Pref, Work) ->
    -    {X400, Userid, Internet, Home, Pref, Work};
    -decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"HOME">>, _attrs, _} = _el | _els], X400,
    -		       Userid, Internet, Home, Pref, Work) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid, Internet,
    -				  decode_vcard_HOME(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				  Pref, Work);
    -       true ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid, Internet, Home, Pref, Work)
    -    end;
    -decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"WORK">>, _attrs, _} = _el | _els], X400,
    -		       Userid, Internet, Home, Pref, Work) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid, Internet, Home, Pref,
    -				  decode_vcard_WORK(__TopXMLNS, __IgnoreEls,
    -						    _el));
    -       true ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid, Internet, Home, Pref, Work)
    -    end;
    -decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"INTERNET">>, _attrs, _} = _el | _els], X400,
    -		       Userid, Internet, Home, Pref, Work) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid,
    -				  decode_vcard_INTERNET(__TopXMLNS, __IgnoreEls,
    -							_el),
    -				  Home, Pref, Work);
    -       true ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid, Internet, Home, Pref, Work)
    -    end;
    -decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"PREF">>, _attrs, _} = _el | _els], X400,
    -		       Userid, Internet, Home, Pref, Work) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid, Internet, Home,
    -				  decode_vcard_PREF(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				  Work);
    -       true ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid, Internet, Home, Pref, Work)
    -    end;
    -decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"X400">>, _attrs, _} = _el | _els], X400,
    -		       Userid, Internet, Home, Pref, Work) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  decode_vcard_X400(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				  Userid, Internet, Home, Pref, Work);
    -       true ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid, Internet, Home, Pref, Work)
    -    end;
    -decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"USERID">>, _attrs, _} = _el | _els], X400,
    -		       Userid, Internet, Home, Pref, Work) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400,
    -				  decode_vcard_USERID(__TopXMLNS, __IgnoreEls,
    -						      _el),
    -				  Internet, Home, Pref, Work);
    -       true ->
    -	   decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  X400, Userid, Internet, Home, Pref, Work)
    -    end;
    -decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], X400, Userid, Internet, Home, Pref, Work) ->
    -    decode_vcard_EMAIL_els(__TopXMLNS, __IgnoreEls, _els,
    -			   X400, Userid, Internet, Home, Pref, Work).
    -
    -encode_vcard_EMAIL({vcard_email, Home, Work, Internet,
    -		    Pref, X400, Userid},
    -		   _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_EMAIL_$x400'(X400,
    -						    'encode_vcard_EMAIL_$userid'(Userid,
    -										 'encode_vcard_EMAIL_$internet'(Internet,
    -														'encode_vcard_EMAIL_$home'(Home,
    -																	   'encode_vcard_EMAIL_$pref'(Pref,
    -																				      'encode_vcard_EMAIL_$work'(Work,
    -																								 []))))))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"EMAIL">>, _attrs, _els}.
    -
    -'encode_vcard_EMAIL_$x400'(false, _acc) -> _acc;
    -'encode_vcard_EMAIL_$x400'(X400, _acc) ->
    -    [encode_vcard_X400(X400, []) | _acc].
    -
    -'encode_vcard_EMAIL_$userid'(undefined, _acc) -> _acc;
    -'encode_vcard_EMAIL_$userid'(Userid, _acc) ->
    -    [encode_vcard_USERID(Userid, []) | _acc].
    -
    -'encode_vcard_EMAIL_$internet'(false, _acc) -> _acc;
    -'encode_vcard_EMAIL_$internet'(Internet, _acc) ->
    -    [encode_vcard_INTERNET(Internet, []) | _acc].
    -
    -'encode_vcard_EMAIL_$home'(false, _acc) -> _acc;
    -'encode_vcard_EMAIL_$home'(Home, _acc) ->
    -    [encode_vcard_HOME(Home, []) | _acc].
    -
    -'encode_vcard_EMAIL_$pref'(false, _acc) -> _acc;
    -'encode_vcard_EMAIL_$pref'(Pref, _acc) ->
    -    [encode_vcard_PREF(Pref, []) | _acc].
    -
    -'encode_vcard_EMAIL_$work'(false, _acc) -> _acc;
    -'encode_vcard_EMAIL_$work'(Work, _acc) ->
    -    [encode_vcard_WORK(Work, []) | _acc].
    -
    -decode_vcard_TEL(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"TEL">>, _attrs, _els}) ->
    -    {Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax,
    -     Work, Cell, Modem, Isdn, Video} =
    -	decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -			     undefined, false, false, false, false, false,
    -			     false, false, false, false, false, false, false,
    -			     false),
    -    {vcard_tel, Home, Work, Voice, Fax, Pager, Msg, Cell,
    -     Video, Bbs, Modem, Isdn, Pcs, Pref, Number}.
    -
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, [],
    -		     Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax,
    -		     Work, Cell, Modem, Isdn, Video) ->
    -    {Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax,
    -     Work, Cell, Modem, Isdn, Video};
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"HOME">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice,
    -				decode_vcard_HOME(__TopXMLNS, __IgnoreEls, _el),
    -				Pref, Msg, Fax, Work, Cell, Modem, Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"WORK">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax,
    -				decode_vcard_WORK(__TopXMLNS, __IgnoreEls, _el),
    -				Cell, Modem, Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"VOICE">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs,
    -				decode_vcard_VOICE(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -				Home, Pref, Msg, Fax, Work, Cell, Modem, Isdn,
    -				Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"FAX">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				decode_vcard_FAX(__TopXMLNS, __IgnoreEls, _el),
    -				Work, Cell, Modem, Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"PAGER">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number,
    -				decode_vcard_PAGER(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -				Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -				Cell, Modem, Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"MSG">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref,
    -				decode_vcard_MSG(__TopXMLNS, __IgnoreEls, _el),
    -				Fax, Work, Cell, Modem, Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"CELL">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work,
    -				decode_vcard_CELL(__TopXMLNS, __IgnoreEls, _el),
    -				Modem, Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"VIDEO">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn,
    -				decode_vcard_VIDEO(__TopXMLNS, __IgnoreEls,
    -						   _el));
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"BBS">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs,
    -				decode_vcard_BBS(__TopXMLNS, __IgnoreEls, _el),
    -				Voice, Home, Pref, Msg, Fax, Work, Cell, Modem,
    -				Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"MODEM">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell,
    -				decode_vcard_MODEM(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -				Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"ISDN">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem,
    -				decode_vcard_ISDN(__TopXMLNS, __IgnoreEls, _el),
    -				Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"PCS">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager,
    -				decode_vcard_PCS(__TopXMLNS, __IgnoreEls, _el),
    -				Bbs, Voice, Home, Pref, Msg, Fax, Work, Cell,
    -				Modem, Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"PREF">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home,
    -				decode_vcard_PREF(__TopXMLNS, __IgnoreEls, _el),
    -				Msg, Fax, Work, Cell, Modem, Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"NUMBER">>, _attrs, _} = _el | _els], Number,
    -		     Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax, Work,
    -		     Cell, Modem, Isdn, Video) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_vcard_NUMBER(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax,
    -				Work, Cell, Modem, Isdn, Video);
    -       true ->
    -	   decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg,
    -				Fax, Work, Cell, Modem, Isdn, Video)
    -    end;
    -decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Number, Pager, Pcs, Bbs, Voice, Home, Pref,
    -		     Msg, Fax, Work, Cell, Modem, Isdn, Video) ->
    -    decode_vcard_TEL_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Number, Pager, Pcs, Bbs, Voice, Home, Pref, Msg, Fax,
    -			 Work, Cell, Modem, Isdn, Video).
    -
    -encode_vcard_TEL({vcard_tel, Home, Work, Voice, Fax,
    -		  Pager, Msg, Cell, Video, Bbs, Modem, Isdn, Pcs, Pref,
    -		  Number},
    -		 _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_TEL_$number'(Number,
    -						    'encode_vcard_TEL_$pager'(Pager,
    -									      'encode_vcard_TEL_$pcs'(Pcs,
    -												      'encode_vcard_TEL_$bbs'(Bbs,
    -															      'encode_vcard_TEL_$voice'(Voice,
    -																			'encode_vcard_TEL_$home'(Home,
    -																						 'encode_vcard_TEL_$pref'(Pref,
    -																									  'encode_vcard_TEL_$msg'(Msg,
    -																												  'encode_vcard_TEL_$fax'(Fax,
    -																															  'encode_vcard_TEL_$work'(Work,
    -																																		   'encode_vcard_TEL_$cell'(Cell,
    -																																					    'encode_vcard_TEL_$modem'(Modem,
    -																																								      'encode_vcard_TEL_$isdn'(Isdn,
    -																																											       'encode_vcard_TEL_$video'(Video,
    -																																															 []))))))))))))))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"TEL">>, _attrs, _els}.
    -
    -'encode_vcard_TEL_$number'(undefined, _acc) -> _acc;
    -'encode_vcard_TEL_$number'(Number, _acc) ->
    -    [encode_vcard_NUMBER(Number, []) | _acc].
    -
    -'encode_vcard_TEL_$pager'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$pager'(Pager, _acc) ->
    -    [encode_vcard_PAGER(Pager, []) | _acc].
    -
    -'encode_vcard_TEL_$pcs'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$pcs'(Pcs, _acc) ->
    -    [encode_vcard_PCS(Pcs, []) | _acc].
    -
    -'encode_vcard_TEL_$bbs'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$bbs'(Bbs, _acc) ->
    -    [encode_vcard_BBS(Bbs, []) | _acc].
    -
    -'encode_vcard_TEL_$voice'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$voice'(Voice, _acc) ->
    -    [encode_vcard_VOICE(Voice, []) | _acc].
    -
    -'encode_vcard_TEL_$home'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$home'(Home, _acc) ->
    -    [encode_vcard_HOME(Home, []) | _acc].
    -
    -'encode_vcard_TEL_$pref'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$pref'(Pref, _acc) ->
    -    [encode_vcard_PREF(Pref, []) | _acc].
    -
    -'encode_vcard_TEL_$msg'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$msg'(Msg, _acc) ->
    -    [encode_vcard_MSG(Msg, []) | _acc].
    -
    -'encode_vcard_TEL_$fax'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$fax'(Fax, _acc) ->
    -    [encode_vcard_FAX(Fax, []) | _acc].
    -
    -'encode_vcard_TEL_$work'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$work'(Work, _acc) ->
    -    [encode_vcard_WORK(Work, []) | _acc].
    -
    -'encode_vcard_TEL_$cell'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$cell'(Cell, _acc) ->
    -    [encode_vcard_CELL(Cell, []) | _acc].
    -
    -'encode_vcard_TEL_$modem'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$modem'(Modem, _acc) ->
    -    [encode_vcard_MODEM(Modem, []) | _acc].
    -
    -'encode_vcard_TEL_$isdn'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$isdn'(Isdn, _acc) ->
    -    [encode_vcard_ISDN(Isdn, []) | _acc].
    -
    -'encode_vcard_TEL_$video'(false, _acc) -> _acc;
    -'encode_vcard_TEL_$video'(Video, _acc) ->
    -    [encode_vcard_VIDEO(Video, []) | _acc].
    -
    -decode_vcard_LABEL(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"LABEL">>, _attrs, _els}) ->
    -    {Line, Home, Pref, Work, Intl, Parcel, Postal, Dom} =
    -	decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -			       [], false, false, false, false, false, false,
    -			       false),
    -    {vcard_label, Home, Work, Postal, Parcel, Dom, Intl,
    -     Pref, Line}.
    -
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, [],
    -		       Line, Home, Pref, Work, Intl, Parcel, Postal, Dom) ->
    -    {lists:reverse(Line), Home, Pref, Work, Intl, Parcel,
    -     Postal, Dom};
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"HOME">>, _attrs, _} = _el | _els], Line,
    -		       Home, Pref, Work, Intl, Parcel, Postal, Dom) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line,
    -				  decode_vcard_HOME(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				  Pref, Work, Intl, Parcel, Postal, Dom);
    -       true ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel, Postal,
    -				  Dom)
    -    end;
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"WORK">>, _attrs, _} = _el | _els], Line,
    -		       Home, Pref, Work, Intl, Parcel, Postal, Dom) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref,
    -				  decode_vcard_WORK(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				  Intl, Parcel, Postal, Dom);
    -       true ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel, Postal,
    -				  Dom)
    -    end;
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"POSTAL">>, _attrs, _} = _el | _els], Line,
    -		       Home, Pref, Work, Intl, Parcel, Postal, Dom) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel,
    -				  decode_vcard_POSTAL(__TopXMLNS, __IgnoreEls,
    -						      _el),
    -				  Dom);
    -       true ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel, Postal,
    -				  Dom)
    -    end;
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"PARCEL">>, _attrs, _} = _el | _els], Line,
    -		       Home, Pref, Work, Intl, Parcel, Postal, Dom) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl,
    -				  decode_vcard_PARCEL(__TopXMLNS, __IgnoreEls,
    -						      _el),
    -				  Postal, Dom);
    -       true ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel, Postal,
    -				  Dom)
    -    end;
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"DOM">>, _attrs, _} = _el | _els], Line,
    -		       Home, Pref, Work, Intl, Parcel, Postal, Dom) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel, Postal,
    -				  decode_vcard_DOM(__TopXMLNS, __IgnoreEls,
    -						   _el));
    -       true ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel, Postal,
    -				  Dom)
    -    end;
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"INTL">>, _attrs, _} = _el | _els], Line,
    -		       Home, Pref, Work, Intl, Parcel, Postal, Dom) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work,
    -				  decode_vcard_INTL(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				  Parcel, Postal, Dom);
    -       true ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel, Postal,
    -				  Dom)
    -    end;
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"PREF">>, _attrs, _} = _el | _els], Line,
    -		       Home, Pref, Work, Intl, Parcel, Postal, Dom) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home,
    -				  decode_vcard_PREF(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				  Work, Intl, Parcel, Postal, Dom);
    -       true ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel, Postal,
    -				  Dom)
    -    end;
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"LINE">>, _attrs, _} = _el | _els], Line,
    -		       Home, Pref, Work, Intl, Parcel, Postal, Dom) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  case decode_vcard_LINE(__TopXMLNS,
    -							 __IgnoreEls, _el)
    -				      of
    -				    undefined -> Line;
    -				    _new_el -> [_new_el | Line]
    -				  end,
    -				  Home, Pref, Work, Intl, Parcel, Postal, Dom);
    -       true ->
    -	   decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Line, Home, Pref, Work, Intl, Parcel, Postal,
    -				  Dom)
    -    end;
    -decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Line, Home, Pref, Work, Intl, Parcel,
    -		       Postal, Dom) ->
    -    decode_vcard_LABEL_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Line, Home, Pref, Work, Intl, Parcel, Postal, Dom).
    -
    -encode_vcard_LABEL({vcard_label, Home, Work, Postal,
    -		    Parcel, Dom, Intl, Pref, Line},
    -		   _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_LABEL_$line'(Line,
    -						    'encode_vcard_LABEL_$home'(Home,
    -									       'encode_vcard_LABEL_$pref'(Pref,
    -													  'encode_vcard_LABEL_$work'(Work,
    -																     'encode_vcard_LABEL_$intl'(Intl,
    -																				'encode_vcard_LABEL_$parcel'(Parcel,
    -																							     'encode_vcard_LABEL_$postal'(Postal,
    -																											  'encode_vcard_LABEL_$dom'(Dom,
    -																														    []))))))))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"LABEL">>, _attrs, _els}.
    -
    -'encode_vcard_LABEL_$line'([], _acc) -> _acc;
    -'encode_vcard_LABEL_$line'([Line | _els], _acc) ->
    -    'encode_vcard_LABEL_$line'(_els,
    -			       [encode_vcard_LINE(Line, []) | _acc]).
    -
    -'encode_vcard_LABEL_$home'(false, _acc) -> _acc;
    -'encode_vcard_LABEL_$home'(Home, _acc) ->
    -    [encode_vcard_HOME(Home, []) | _acc].
    -
    -'encode_vcard_LABEL_$pref'(false, _acc) -> _acc;
    -'encode_vcard_LABEL_$pref'(Pref, _acc) ->
    -    [encode_vcard_PREF(Pref, []) | _acc].
    -
    -'encode_vcard_LABEL_$work'(false, _acc) -> _acc;
    -'encode_vcard_LABEL_$work'(Work, _acc) ->
    -    [encode_vcard_WORK(Work, []) | _acc].
    -
    -'encode_vcard_LABEL_$intl'(false, _acc) -> _acc;
    -'encode_vcard_LABEL_$intl'(Intl, _acc) ->
    -    [encode_vcard_INTL(Intl, []) | _acc].
    -
    -'encode_vcard_LABEL_$parcel'(false, _acc) -> _acc;
    -'encode_vcard_LABEL_$parcel'(Parcel, _acc) ->
    -    [encode_vcard_PARCEL(Parcel, []) | _acc].
    -
    -'encode_vcard_LABEL_$postal'(false, _acc) -> _acc;
    -'encode_vcard_LABEL_$postal'(Postal, _acc) ->
    -    [encode_vcard_POSTAL(Postal, []) | _acc].
    -
    -'encode_vcard_LABEL_$dom'(false, _acc) -> _acc;
    -'encode_vcard_LABEL_$dom'(Dom, _acc) ->
    -    [encode_vcard_DOM(Dom, []) | _acc].
    -
    -decode_vcard_ADR(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"ADR">>, _attrs, _els}) ->
    -    {Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -     Locality, Work, Intl, Parcel, Postal, Dom, Region} =
    -	decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -			     undefined, undefined, undefined, false, false,
    -			     undefined, undefined, undefined, false, false,
    -			     false, false, false, undefined),
    -    {vcard_adr, Home, Work, Postal, Parcel, Dom, Intl, Pref,
    -     Pobox, Extadd, Street, Locality, Region, Pcode, Ctry}.
    -
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, [],
    -		     Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -		     Locality, Work, Intl, Parcel, Postal, Dom, Region) ->
    -    {Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -     Locality, Work, Intl, Parcel, Postal, Dom, Region};
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"HOME">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode,
    -				decode_vcard_HOME(__TopXMLNS, __IgnoreEls, _el),
    -				Pref, Pobox, Ctry, Locality, Work, Intl, Parcel,
    -				Postal, Dom, Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"WORK">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality,
    -				decode_vcard_WORK(__TopXMLNS, __IgnoreEls, _el),
    -				Intl, Parcel, Postal, Dom, Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"POSTAL">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel,
    -				decode_vcard_POSTAL(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				Dom, Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"PARCEL">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl,
    -				decode_vcard_PARCEL(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				Postal, Dom, Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"DOM">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal,
    -				decode_vcard_DOM(__TopXMLNS, __IgnoreEls, _el),
    -				Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"INTL">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work,
    -				decode_vcard_INTL(__TopXMLNS, __IgnoreEls, _el),
    -				Parcel, Postal, Dom, Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"PREF">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home,
    -				decode_vcard_PREF(__TopXMLNS, __IgnoreEls, _el),
    -				Pobox, Ctry, Locality, Work, Intl, Parcel,
    -				Postal, Dom, Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"POBOX">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref,
    -				decode_vcard_POBOX(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -				Ctry, Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"EXTADD">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street,
    -				decode_vcard_EXTADD(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -				Intl, Parcel, Postal, Dom, Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"STREET">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				decode_vcard_STREET(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -				Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"LOCALITY">>, _attrs, _} = _el | _els],
    -		     Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -		     Locality, Work, Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				decode_vcard_LOCALITY(__TopXMLNS, __IgnoreEls,
    -						      _el),
    -				Work, Intl, Parcel, Postal, Dom, Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"REGION">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				decode_vcard_REGION(__TopXMLNS, __IgnoreEls,
    -						    _el));
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"PCODE">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd,
    -				decode_vcard_PCODE(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -				Home, Pref, Pobox, Ctry, Locality, Work, Intl,
    -				Parcel, Postal, Dom, Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlel, <<"CTRY">>, _attrs, _} = _el | _els], Street,
    -		     Extadd, Pcode, Home, Pref, Pobox, Ctry, Locality, Work,
    -		     Intl, Parcel, Postal, Dom, Region) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox,
    -				decode_vcard_CTRY(__TopXMLNS, __IgnoreEls, _el),
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region);
    -       true ->
    -	   decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -				Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -				Locality, Work, Intl, Parcel, Postal, Dom,
    -				Region)
    -    end;
    -decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Street, Extadd, Pcode, Home, Pref, Pobox,
    -		     Ctry, Locality, Work, Intl, Parcel, Postal, Dom,
    -		     Region) ->
    -    decode_vcard_ADR_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Street, Extadd, Pcode, Home, Pref, Pobox, Ctry,
    -			 Locality, Work, Intl, Parcel, Postal, Dom, Region).
    -
    -encode_vcard_ADR({vcard_adr, Home, Work, Postal, Parcel,
    -		  Dom, Intl, Pref, Pobox, Extadd, Street, Locality,
    -		  Region, Pcode, Ctry},
    -		 _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_ADR_$street'(Street,
    -						    'encode_vcard_ADR_$extadd'(Extadd,
    -									       'encode_vcard_ADR_$pcode'(Pcode,
    -													 'encode_vcard_ADR_$home'(Home,
    -																  'encode_vcard_ADR_$pref'(Pref,
    -																			   'encode_vcard_ADR_$pobox'(Pobox,
    -																						     'encode_vcard_ADR_$ctry'(Ctry,
    -																									      'encode_vcard_ADR_$locality'(Locality,
    -																													   'encode_vcard_ADR_$work'(Work,
    -																																    'encode_vcard_ADR_$intl'(Intl,
    -																																			     'encode_vcard_ADR_$parcel'(Parcel,
    -																																							'encode_vcard_ADR_$postal'(Postal,
    -																																										   'encode_vcard_ADR_$dom'(Dom,
    -																																													   'encode_vcard_ADR_$region'(Region,
    -																																																      []))))))))))))))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"ADR">>, _attrs, _els}.
    -
    -'encode_vcard_ADR_$street'(undefined, _acc) -> _acc;
    -'encode_vcard_ADR_$street'(Street, _acc) ->
    -    [encode_vcard_STREET(Street, []) | _acc].
    -
    -'encode_vcard_ADR_$extadd'(undefined, _acc) -> _acc;
    -'encode_vcard_ADR_$extadd'(Extadd, _acc) ->
    -    [encode_vcard_EXTADD(Extadd, []) | _acc].
    -
    -'encode_vcard_ADR_$pcode'(undefined, _acc) -> _acc;
    -'encode_vcard_ADR_$pcode'(Pcode, _acc) ->
    -    [encode_vcard_PCODE(Pcode, []) | _acc].
    -
    -'encode_vcard_ADR_$home'(false, _acc) -> _acc;
    -'encode_vcard_ADR_$home'(Home, _acc) ->
    -    [encode_vcard_HOME(Home, []) | _acc].
    -
    -'encode_vcard_ADR_$pref'(false, _acc) -> _acc;
    -'encode_vcard_ADR_$pref'(Pref, _acc) ->
    -    [encode_vcard_PREF(Pref, []) | _acc].
    -
    -'encode_vcard_ADR_$pobox'(undefined, _acc) -> _acc;
    -'encode_vcard_ADR_$pobox'(Pobox, _acc) ->
    -    [encode_vcard_POBOX(Pobox, []) | _acc].
    -
    -'encode_vcard_ADR_$ctry'(undefined, _acc) -> _acc;
    -'encode_vcard_ADR_$ctry'(Ctry, _acc) ->
    -    [encode_vcard_CTRY(Ctry, []) | _acc].
    -
    -'encode_vcard_ADR_$locality'(undefined, _acc) -> _acc;
    -'encode_vcard_ADR_$locality'(Locality, _acc) ->
    -    [encode_vcard_LOCALITY(Locality, []) | _acc].
    -
    -'encode_vcard_ADR_$work'(false, _acc) -> _acc;
    -'encode_vcard_ADR_$work'(Work, _acc) ->
    -    [encode_vcard_WORK(Work, []) | _acc].
    -
    -'encode_vcard_ADR_$intl'(false, _acc) -> _acc;
    -'encode_vcard_ADR_$intl'(Intl, _acc) ->
    -    [encode_vcard_INTL(Intl, []) | _acc].
    -
    -'encode_vcard_ADR_$parcel'(false, _acc) -> _acc;
    -'encode_vcard_ADR_$parcel'(Parcel, _acc) ->
    -    [encode_vcard_PARCEL(Parcel, []) | _acc].
    -
    -'encode_vcard_ADR_$postal'(false, _acc) -> _acc;
    -'encode_vcard_ADR_$postal'(Postal, _acc) ->
    -    [encode_vcard_POSTAL(Postal, []) | _acc].
    -
    -'encode_vcard_ADR_$dom'(false, _acc) -> _acc;
    -'encode_vcard_ADR_$dom'(Dom, _acc) ->
    -    [encode_vcard_DOM(Dom, []) | _acc].
    -
    -'encode_vcard_ADR_$region'(undefined, _acc) -> _acc;
    -'encode_vcard_ADR_$region'(Region, _acc) ->
    -    [encode_vcard_REGION(Region, []) | _acc].
    -
    -decode_vcard_N(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"N">>, _attrs, _els}) ->
    -    {Middle, Suffix, Prefix, Family, Given} =
    -	decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			   undefined, undefined, undefined, undefined,
    -			   undefined),
    -    {vcard_name, Family, Given, Middle, Prefix, Suffix}.
    -
    -decode_vcard_N_els(__TopXMLNS, __IgnoreEls, [], Middle,
    -		   Suffix, Prefix, Family, Given) ->
    -    {Middle, Suffix, Prefix, Family, Given};
    -decode_vcard_N_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"FAMILY">>, _attrs, _} = _el | _els], Middle,
    -		   Suffix, Prefix, Family, Given) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Middle, Suffix, Prefix,
    -			      decode_vcard_FAMILY(__TopXMLNS, __IgnoreEls, _el),
    -			      Given);
    -       true ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Middle, Suffix, Prefix, Family, Given)
    -    end;
    -decode_vcard_N_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"GIVEN">>, _attrs, _} = _el | _els], Middle,
    -		   Suffix, Prefix, Family, Given) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Middle, Suffix, Prefix, Family,
    -			      decode_vcard_GIVEN(__TopXMLNS, __IgnoreEls, _el));
    -       true ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Middle, Suffix, Prefix, Family, Given)
    -    end;
    -decode_vcard_N_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"MIDDLE">>, _attrs, _} = _el | _els], Middle,
    -		   Suffix, Prefix, Family, Given) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      decode_vcard_MIDDLE(__TopXMLNS, __IgnoreEls, _el),
    -			      Suffix, Prefix, Family, Given);
    -       true ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Middle, Suffix, Prefix, Family, Given)
    -    end;
    -decode_vcard_N_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"PREFIX">>, _attrs, _} = _el | _els], Middle,
    -		   Suffix, Prefix, Family, Given) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Middle, Suffix,
    -			      decode_vcard_PREFIX(__TopXMLNS, __IgnoreEls, _el),
    -			      Family, Given);
    -       true ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Middle, Suffix, Prefix, Family, Given)
    -    end;
    -decode_vcard_N_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"SUFFIX">>, _attrs, _} = _el | _els], Middle,
    -		   Suffix, Prefix, Family, Given) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Middle,
    -			      decode_vcard_SUFFIX(__TopXMLNS, __IgnoreEls, _el),
    -			      Prefix, Family, Given);
    -       true ->
    -	   decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Middle, Suffix, Prefix, Family, Given)
    -    end;
    -decode_vcard_N_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Middle, Suffix, Prefix, Family, Given) ->
    -    decode_vcard_N_els(__TopXMLNS, __IgnoreEls, _els,
    -		       Middle, Suffix, Prefix, Family, Given).
    -
    -encode_vcard_N({vcard_name, Family, Given, Middle,
    -		Prefix, Suffix},
    -	       _xmlns_attrs) ->
    -    _els = lists:reverse('encode_vcard_N_$middle'(Middle,
    -						  'encode_vcard_N_$suffix'(Suffix,
    -									   'encode_vcard_N_$prefix'(Prefix,
    -												    'encode_vcard_N_$family'(Family,
    -															     'encode_vcard_N_$given'(Given,
    -																		     [])))))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"N">>, _attrs, _els}.
    -
    -'encode_vcard_N_$middle'(undefined, _acc) -> _acc;
    -'encode_vcard_N_$middle'(Middle, _acc) ->
    -    [encode_vcard_MIDDLE(Middle, []) | _acc].
    -
    -'encode_vcard_N_$suffix'(undefined, _acc) -> _acc;
    -'encode_vcard_N_$suffix'(Suffix, _acc) ->
    -    [encode_vcard_SUFFIX(Suffix, []) | _acc].
    -
    -'encode_vcard_N_$prefix'(undefined, _acc) -> _acc;
    -'encode_vcard_N_$prefix'(Prefix, _acc) ->
    -    [encode_vcard_PREFIX(Prefix, []) | _acc].
    -
    -'encode_vcard_N_$family'(undefined, _acc) -> _acc;
    -'encode_vcard_N_$family'(Family, _acc) ->
    -    [encode_vcard_FAMILY(Family, []) | _acc].
    -
    -'encode_vcard_N_$given'(undefined, _acc) -> _acc;
    -'encode_vcard_N_$given'(Given, _acc) ->
    -    [encode_vcard_GIVEN(Given, []) | _acc].
    -
    -decode_vcard_CONFIDENTIAL(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"CONFIDENTIAL">>, _attrs, _els}) ->
    -    confidential.
    -
    -encode_vcard_CONFIDENTIAL(confidential, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"CONFIDENTIAL">>, _attrs, _els}.
    -
    -decode_vcard_PRIVATE(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"PRIVATE">>, _attrs, _els}) ->
    -    private.
    -
    -encode_vcard_PRIVATE(private, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PRIVATE">>, _attrs, _els}.
    -
    -decode_vcard_PUBLIC(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"PUBLIC">>, _attrs, _els}) ->
    -    public.
    -
    -encode_vcard_PUBLIC(public, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PUBLIC">>, _attrs, _els}.
    -
    -decode_vcard_EXTVAL(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"EXTVAL">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_EXTVAL_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_EXTVAL_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_EXTVAL_cdata(__TopXMLNS, Cdata);
    -decode_vcard_EXTVAL_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_EXTVAL_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_EXTVAL_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_EXTVAL_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_EXTVAL(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_EXTVAL_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"EXTVAL">>, _attrs, _els}.
    -
    -decode_vcard_EXTVAL_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_EXTVAL_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_EXTVAL_cdata(undefined, _acc) -> _acc;
    -encode_vcard_EXTVAL_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_TYPE(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"TYPE">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_TYPE_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_TYPE_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_vcard_TYPE_cdata(__TopXMLNS, Cdata);
    -decode_vcard_TYPE_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_TYPE_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_vcard_TYPE_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_vcard_TYPE_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_vcard_TYPE(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_TYPE_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"TYPE">>, _attrs, _els}.
    -
    -decode_vcard_TYPE_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_TYPE_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_TYPE_cdata(undefined, _acc) -> _acc;
    -encode_vcard_TYPE_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_DESC(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"DESC">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_DESC_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_DESC_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_vcard_DESC_cdata(__TopXMLNS, Cdata);
    -decode_vcard_DESC_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_DESC_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_vcard_DESC_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_vcard_DESC_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_vcard_DESC(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_DESC_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"DESC">>, _attrs, _els}.
    -
    -decode_vcard_DESC_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_DESC_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_DESC_cdata(undefined, _acc) -> _acc;
    -encode_vcard_DESC_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_URL(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"URL">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_URL_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_URL_els(__TopXMLNS, __IgnoreEls, [],
    -		     Cdata) ->
    -    decode_vcard_URL_cdata(__TopXMLNS, Cdata);
    -decode_vcard_URL_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_URL_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_vcard_URL_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cdata) ->
    -    decode_vcard_URL_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cdata).
    -
    -encode_vcard_URL(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_URL_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"URL">>, _attrs, _els}.
    -
    -decode_vcard_URL_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_URL_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_URL_cdata(undefined, _acc) -> _acc;
    -encode_vcard_URL_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_UID(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"UID">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_UID_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_UID_els(__TopXMLNS, __IgnoreEls, [],
    -		     Cdata) ->
    -    decode_vcard_UID_cdata(__TopXMLNS, Cdata);
    -decode_vcard_UID_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_UID_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_vcard_UID_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cdata) ->
    -    decode_vcard_UID_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cdata).
    -
    -encode_vcard_UID(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_UID_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"UID">>, _attrs, _els}.
    -
    -decode_vcard_UID_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_UID_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_UID_cdata(undefined, _acc) -> _acc;
    -encode_vcard_UID_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_SORT_STRING(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"SORT-STRING">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_SORT_STRING_els(__TopXMLNS,
    -					 __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_SORT_STRING_els(__TopXMLNS, __IgnoreEls,
    -			     [], Cdata) ->
    -    decode_vcard_SORT_STRING_cdata(__TopXMLNS, Cdata);
    -decode_vcard_SORT_STRING_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_SORT_STRING_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <>);
    -decode_vcard_SORT_STRING_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], Cdata) ->
    -    decode_vcard_SORT_STRING_els(__TopXMLNS, __IgnoreEls,
    -				 _els, Cdata).
    -
    -encode_vcard_SORT_STRING(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_SORT_STRING_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"SORT-STRING">>, _attrs, _els}.
    -
    -decode_vcard_SORT_STRING_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_SORT_STRING_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_vcard_SORT_STRING_cdata(undefined, _acc) -> _acc;
    -encode_vcard_SORT_STRING_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_REV(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"REV">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_REV_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_REV_els(__TopXMLNS, __IgnoreEls, [],
    -		     Cdata) ->
    -    decode_vcard_REV_cdata(__TopXMLNS, Cdata);
    -decode_vcard_REV_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_REV_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_vcard_REV_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cdata) ->
    -    decode_vcard_REV_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cdata).
    -
    -encode_vcard_REV(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_REV_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"REV">>, _attrs, _els}.
    -
    -decode_vcard_REV_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_REV_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_REV_cdata(undefined, _acc) -> _acc;
    -encode_vcard_REV_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_PRODID(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"PRODID">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_PRODID_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_PRODID_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_PRODID_cdata(__TopXMLNS, Cdata);
    -decode_vcard_PRODID_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_PRODID_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_PRODID_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_PRODID_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_PRODID(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_PRODID_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PRODID">>, _attrs, _els}.
    -
    -decode_vcard_PRODID_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_PRODID_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_PRODID_cdata(undefined, _acc) -> _acc;
    -encode_vcard_PRODID_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_NOTE(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"NOTE">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_NOTE_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_NOTE_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_vcard_NOTE_cdata(__TopXMLNS, Cdata);
    -decode_vcard_NOTE_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_NOTE_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_vcard_NOTE_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_vcard_NOTE_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_vcard_NOTE(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_NOTE_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"NOTE">>, _attrs, _els}.
    -
    -decode_vcard_NOTE_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_NOTE_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_NOTE_cdata(undefined, _acc) -> _acc;
    -encode_vcard_NOTE_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_KEYWORD(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"KEYWORD">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_KEYWORD_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_KEYWORD_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_vcard_KEYWORD_cdata(__TopXMLNS, Cdata);
    -decode_vcard_KEYWORD_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_KEYWORD_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_vcard_KEYWORD_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_vcard_KEYWORD_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_vcard_KEYWORD(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_KEYWORD_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"KEYWORD">>, _attrs, _els}.
    -
    -decode_vcard_KEYWORD_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_KEYWORD_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_KEYWORD_cdata(undefined, _acc) -> _acc;
    -encode_vcard_KEYWORD_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_ROLE(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"ROLE">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_ROLE_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_ROLE_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_vcard_ROLE_cdata(__TopXMLNS, Cdata);
    -decode_vcard_ROLE_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_ROLE_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_vcard_ROLE_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_vcard_ROLE_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_vcard_ROLE(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_ROLE_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"ROLE">>, _attrs, _els}.
    -
    -decode_vcard_ROLE_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_ROLE_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_ROLE_cdata(undefined, _acc) -> _acc;
    -encode_vcard_ROLE_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_TITLE(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"TITLE">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_TITLE_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_TITLE_els(__TopXMLNS, __IgnoreEls, [],
    -		       Cdata) ->
    -    decode_vcard_TITLE_cdata(__TopXMLNS, Cdata);
    -decode_vcard_TITLE_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_TITLE_els(__TopXMLNS, __IgnoreEls, _els,
    -			   <>);
    -decode_vcard_TITLE_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Cdata) ->
    -    decode_vcard_TITLE_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Cdata).
    -
    -encode_vcard_TITLE(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_TITLE_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"TITLE">>, _attrs, _els}.
    -
    -decode_vcard_TITLE_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_TITLE_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_TITLE_cdata(undefined, _acc) -> _acc;
    -encode_vcard_TITLE_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_TZ(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"TZ">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_TZ_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    Cdata.
    -
    -decode_vcard_TZ_els(__TopXMLNS, __IgnoreEls, [],
    -		    Cdata) ->
    -    decode_vcard_TZ_cdata(__TopXMLNS, Cdata);
    -decode_vcard_TZ_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_TZ_els(__TopXMLNS, __IgnoreEls, _els,
    -			<>);
    -decode_vcard_TZ_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Cdata) ->
    -    decode_vcard_TZ_els(__TopXMLNS, __IgnoreEls, _els,
    -			Cdata).
    -
    -encode_vcard_TZ(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_TZ_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"TZ">>, _attrs, _els}.
    -
    -decode_vcard_TZ_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_TZ_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_TZ_cdata(undefined, _acc) -> _acc;
    -encode_vcard_TZ_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_MAILER(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"MAILER">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_MAILER_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_MAILER_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_MAILER_cdata(__TopXMLNS, Cdata);
    -decode_vcard_MAILER_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_MAILER_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_MAILER_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_MAILER_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_MAILER(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_MAILER_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"MAILER">>, _attrs, _els}.
    -
    -decode_vcard_MAILER_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_MAILER_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_MAILER_cdata(undefined, _acc) -> _acc;
    -encode_vcard_MAILER_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_JABBERID(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"JABBERID">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_JABBERID_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_JABBERID_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_vcard_JABBERID_cdata(__TopXMLNS, Cdata);
    -decode_vcard_JABBERID_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_JABBERID_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_vcard_JABBERID_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_vcard_JABBERID_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_vcard_JABBERID(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_JABBERID_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"JABBERID">>, _attrs, _els}.
    -
    -decode_vcard_JABBERID_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_JABBERID_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_JABBERID_cdata(undefined, _acc) -> _acc;
    -encode_vcard_JABBERID_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_BDAY(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"BDAY">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_BDAY_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_BDAY_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_vcard_BDAY_cdata(__TopXMLNS, Cdata);
    -decode_vcard_BDAY_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_BDAY_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_vcard_BDAY_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_vcard_BDAY_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_vcard_BDAY(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_BDAY_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"BDAY">>, _attrs, _els}.
    -
    -decode_vcard_BDAY_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_BDAY_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_BDAY_cdata(undefined, _acc) -> _acc;
    -encode_vcard_BDAY_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_NICKNAME(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"NICKNAME">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_NICKNAME_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_NICKNAME_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_vcard_NICKNAME_cdata(__TopXMLNS, Cdata);
    -decode_vcard_NICKNAME_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_NICKNAME_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_vcard_NICKNAME_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_vcard_NICKNAME_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_vcard_NICKNAME(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_NICKNAME_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"NICKNAME">>, _attrs, _els}.
    -
    -decode_vcard_NICKNAME_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_NICKNAME_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_NICKNAME_cdata(undefined, _acc) -> _acc;
    -encode_vcard_NICKNAME_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_FN(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"FN">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_FN_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    Cdata.
    -
    -decode_vcard_FN_els(__TopXMLNS, __IgnoreEls, [],
    -		    Cdata) ->
    -    decode_vcard_FN_cdata(__TopXMLNS, Cdata);
    -decode_vcard_FN_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_FN_els(__TopXMLNS, __IgnoreEls, _els,
    -			<>);
    -decode_vcard_FN_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Cdata) ->
    -    decode_vcard_FN_els(__TopXMLNS, __IgnoreEls, _els,
    -			Cdata).
    -
    -encode_vcard_FN(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_FN_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"FN">>, _attrs, _els}.
    -
    -decode_vcard_FN_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_FN_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_FN_cdata(undefined, _acc) -> _acc;
    -encode_vcard_FN_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_VERSION(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"VERSION">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_VERSION_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_VERSION_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_vcard_VERSION_cdata(__TopXMLNS, Cdata);
    -decode_vcard_VERSION_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_VERSION_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_vcard_VERSION_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_vcard_VERSION_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_vcard_VERSION(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_VERSION_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"VERSION">>, _attrs, _els}.
    -
    -decode_vcard_VERSION_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_VERSION_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_VERSION_cdata(undefined, _acc) -> _acc;
    -encode_vcard_VERSION_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_CRED(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"CRED">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_CRED_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_CRED_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_vcard_CRED_cdata(__TopXMLNS, Cdata);
    -decode_vcard_CRED_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_CRED_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_vcard_CRED_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_vcard_CRED_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_vcard_CRED(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_CRED_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"CRED">>, _attrs, _els}.
    -
    -decode_vcard_CRED_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_CRED_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_CRED_cdata(undefined, _acc) -> _acc;
    -encode_vcard_CRED_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_PHONETIC(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"PHONETIC">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_PHONETIC_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_PHONETIC_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_vcard_PHONETIC_cdata(__TopXMLNS, Cdata);
    -decode_vcard_PHONETIC_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_PHONETIC_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_vcard_PHONETIC_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_vcard_PHONETIC_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_vcard_PHONETIC(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_PHONETIC_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PHONETIC">>, _attrs, _els}.
    -
    -decode_vcard_PHONETIC_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_PHONETIC_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_PHONETIC_cdata(undefined, _acc) -> _acc;
    -encode_vcard_PHONETIC_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_ORGUNIT(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"ORGUNIT">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_ORGUNIT_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_ORGUNIT_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_vcard_ORGUNIT_cdata(__TopXMLNS, Cdata);
    -decode_vcard_ORGUNIT_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_ORGUNIT_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_vcard_ORGUNIT_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_vcard_ORGUNIT_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_vcard_ORGUNIT(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_ORGUNIT_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"ORGUNIT">>, _attrs, _els}.
    -
    -decode_vcard_ORGUNIT_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_ORGUNIT_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_ORGUNIT_cdata(undefined, _acc) -> _acc;
    -encode_vcard_ORGUNIT_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_ORGNAME(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"ORGNAME">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_ORGNAME_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_ORGNAME_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_vcard_ORGNAME_cdata(__TopXMLNS, Cdata);
    -decode_vcard_ORGNAME_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_ORGNAME_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_vcard_ORGNAME_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_vcard_ORGNAME_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_vcard_ORGNAME(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_ORGNAME_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"ORGNAME">>, _attrs, _els}.
    -
    -decode_vcard_ORGNAME_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_ORGNAME_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_ORGNAME_cdata(undefined, _acc) -> _acc;
    -encode_vcard_ORGNAME_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_LON(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"LON">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_LON_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_LON_els(__TopXMLNS, __IgnoreEls, [],
    -		     Cdata) ->
    -    decode_vcard_LON_cdata(__TopXMLNS, Cdata);
    -decode_vcard_LON_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_LON_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_vcard_LON_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cdata) ->
    -    decode_vcard_LON_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cdata).
    -
    -encode_vcard_LON(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_LON_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"LON">>, _attrs, _els}.
    -
    -decode_vcard_LON_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_LON_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_LON_cdata(undefined, _acc) -> _acc;
    -encode_vcard_LON_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_LAT(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"LAT">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_LAT_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_LAT_els(__TopXMLNS, __IgnoreEls, [],
    -		     Cdata) ->
    -    decode_vcard_LAT_cdata(__TopXMLNS, Cdata);
    -decode_vcard_LAT_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_LAT_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_vcard_LAT_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Cdata) ->
    -    decode_vcard_LAT_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Cdata).
    -
    -encode_vcard_LAT(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_LAT_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"LAT">>, _attrs, _els}.
    -
    -decode_vcard_LAT_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_LAT_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_LAT_cdata(undefined, _acc) -> _acc;
    -encode_vcard_LAT_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_USERID(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"USERID">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_USERID_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_USERID_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_USERID_cdata(__TopXMLNS, Cdata);
    -decode_vcard_USERID_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_USERID_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_USERID_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_USERID_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_USERID(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_USERID_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"USERID">>, _attrs, _els}.
    -
    -decode_vcard_USERID_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_USERID_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_USERID_cdata(undefined, _acc) -> _acc;
    -encode_vcard_USERID_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_NUMBER(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"NUMBER">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_NUMBER_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_NUMBER_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_NUMBER_cdata(__TopXMLNS, Cdata);
    -decode_vcard_NUMBER_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_NUMBER_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_NUMBER_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_NUMBER_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_NUMBER(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_NUMBER_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"NUMBER">>, _attrs, _els}.
    -
    -decode_vcard_NUMBER_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_NUMBER_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_NUMBER_cdata(undefined, _acc) -> _acc;
    -encode_vcard_NUMBER_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_LINE(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"LINE">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_LINE_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_LINE_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_vcard_LINE_cdata(__TopXMLNS, Cdata);
    -decode_vcard_LINE_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_LINE_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_vcard_LINE_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_vcard_LINE_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_vcard_LINE(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_LINE_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"LINE">>, _attrs, _els}.
    -
    -decode_vcard_LINE_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_LINE_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_LINE_cdata(undefined, _acc) -> _acc;
    -encode_vcard_LINE_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_CTRY(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"CTRY">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_CTRY_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_CTRY_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_vcard_CTRY_cdata(__TopXMLNS, Cdata);
    -decode_vcard_CTRY_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_CTRY_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_vcard_CTRY_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_vcard_CTRY_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_vcard_CTRY(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_CTRY_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"CTRY">>, _attrs, _els}.
    -
    -decode_vcard_CTRY_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_CTRY_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_CTRY_cdata(undefined, _acc) -> _acc;
    -encode_vcard_CTRY_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_PCODE(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"PCODE">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_PCODE_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_PCODE_els(__TopXMLNS, __IgnoreEls, [],
    -		       Cdata) ->
    -    decode_vcard_PCODE_cdata(__TopXMLNS, Cdata);
    -decode_vcard_PCODE_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_PCODE_els(__TopXMLNS, __IgnoreEls, _els,
    -			   <>);
    -decode_vcard_PCODE_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Cdata) ->
    -    decode_vcard_PCODE_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Cdata).
    -
    -encode_vcard_PCODE(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_PCODE_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PCODE">>, _attrs, _els}.
    -
    -decode_vcard_PCODE_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_PCODE_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_PCODE_cdata(undefined, _acc) -> _acc;
    -encode_vcard_PCODE_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_REGION(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"REGION">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_REGION_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_REGION_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_REGION_cdata(__TopXMLNS, Cdata);
    -decode_vcard_REGION_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_REGION_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_REGION_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_REGION_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_REGION(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_REGION_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"REGION">>, _attrs, _els}.
    -
    -decode_vcard_REGION_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_REGION_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_REGION_cdata(undefined, _acc) -> _acc;
    -encode_vcard_REGION_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_LOCALITY(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"LOCALITY">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_LOCALITY_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_LOCALITY_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_vcard_LOCALITY_cdata(__TopXMLNS, Cdata);
    -decode_vcard_LOCALITY_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_LOCALITY_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_vcard_LOCALITY_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_vcard_LOCALITY_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_vcard_LOCALITY(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_LOCALITY_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"LOCALITY">>, _attrs, _els}.
    -
    -decode_vcard_LOCALITY_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_LOCALITY_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_LOCALITY_cdata(undefined, _acc) -> _acc;
    -encode_vcard_LOCALITY_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_STREET(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"STREET">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_STREET_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_STREET_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_STREET_cdata(__TopXMLNS, Cdata);
    -decode_vcard_STREET_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_STREET_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_STREET_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_STREET_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_STREET(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_STREET_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"STREET">>, _attrs, _els}.
    -
    -decode_vcard_STREET_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_STREET_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_STREET_cdata(undefined, _acc) -> _acc;
    -encode_vcard_STREET_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_EXTADD(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"EXTADD">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_EXTADD_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_EXTADD_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_EXTADD_cdata(__TopXMLNS, Cdata);
    -decode_vcard_EXTADD_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_EXTADD_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_EXTADD_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_EXTADD_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_EXTADD(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_EXTADD_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"EXTADD">>, _attrs, _els}.
    -
    -decode_vcard_EXTADD_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_EXTADD_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_EXTADD_cdata(undefined, _acc) -> _acc;
    -encode_vcard_EXTADD_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_POBOX(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"POBOX">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_POBOX_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_POBOX_els(__TopXMLNS, __IgnoreEls, [],
    -		       Cdata) ->
    -    decode_vcard_POBOX_cdata(__TopXMLNS, Cdata);
    -decode_vcard_POBOX_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_POBOX_els(__TopXMLNS, __IgnoreEls, _els,
    -			   <>);
    -decode_vcard_POBOX_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Cdata) ->
    -    decode_vcard_POBOX_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Cdata).
    -
    -encode_vcard_POBOX(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_POBOX_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"POBOX">>, _attrs, _els}.
    -
    -decode_vcard_POBOX_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_POBOX_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_POBOX_cdata(undefined, _acc) -> _acc;
    -encode_vcard_POBOX_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_SUFFIX(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"SUFFIX">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_SUFFIX_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_SUFFIX_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_SUFFIX_cdata(__TopXMLNS, Cdata);
    -decode_vcard_SUFFIX_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_SUFFIX_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_SUFFIX_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_SUFFIX_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_SUFFIX(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_SUFFIX_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"SUFFIX">>, _attrs, _els}.
    -
    -decode_vcard_SUFFIX_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_SUFFIX_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_SUFFIX_cdata(undefined, _acc) -> _acc;
    -encode_vcard_SUFFIX_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_PREFIX(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"PREFIX">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_PREFIX_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_PREFIX_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_PREFIX_cdata(__TopXMLNS, Cdata);
    -decode_vcard_PREFIX_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_PREFIX_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_PREFIX_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_PREFIX_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_PREFIX(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_PREFIX_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PREFIX">>, _attrs, _els}.
    -
    -decode_vcard_PREFIX_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_PREFIX_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_PREFIX_cdata(undefined, _acc) -> _acc;
    -encode_vcard_PREFIX_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_MIDDLE(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"MIDDLE">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_MIDDLE_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_MIDDLE_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_MIDDLE_cdata(__TopXMLNS, Cdata);
    -decode_vcard_MIDDLE_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_MIDDLE_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_MIDDLE_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_MIDDLE_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_MIDDLE(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_MIDDLE_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"MIDDLE">>, _attrs, _els}.
    -
    -decode_vcard_MIDDLE_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_MIDDLE_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_MIDDLE_cdata(undefined, _acc) -> _acc;
    -encode_vcard_MIDDLE_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_GIVEN(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"GIVEN">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_GIVEN_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_GIVEN_els(__TopXMLNS, __IgnoreEls, [],
    -		       Cdata) ->
    -    decode_vcard_GIVEN_cdata(__TopXMLNS, Cdata);
    -decode_vcard_GIVEN_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_GIVEN_els(__TopXMLNS, __IgnoreEls, _els,
    -			   <>);
    -decode_vcard_GIVEN_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Cdata) ->
    -    decode_vcard_GIVEN_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Cdata).
    -
    -encode_vcard_GIVEN(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_GIVEN_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"GIVEN">>, _attrs, _els}.
    -
    -decode_vcard_GIVEN_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_vcard_GIVEN_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_GIVEN_cdata(undefined, _acc) -> _acc;
    -encode_vcard_GIVEN_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_FAMILY(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"FAMILY">>, _attrs, _els}) ->
    -    Cdata = decode_vcard_FAMILY_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_vcard_FAMILY_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_vcard_FAMILY_cdata(__TopXMLNS, Cdata);
    -decode_vcard_FAMILY_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_vcard_FAMILY_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_vcard_FAMILY_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_vcard_FAMILY_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_vcard_FAMILY(Cdata, _xmlns_attrs) ->
    -    _els = encode_vcard_FAMILY_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"FAMILY">>, _attrs, _els}.
    -
    -decode_vcard_FAMILY_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_vcard_FAMILY_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_vcard_FAMILY_cdata(undefined, _acc) -> _acc;
    -encode_vcard_FAMILY_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_vcard_X400(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"X400">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_X400(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"X400">>, _attrs, _els}.
    -
    -decode_vcard_INTERNET(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"INTERNET">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_INTERNET(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"INTERNET">>, _attrs, _els}.
    -
    -decode_vcard_PREF(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"PREF">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_PREF(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PREF">>, _attrs, _els}.
    -
    -decode_vcard_INTL(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"INTL">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_INTL(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"INTL">>, _attrs, _els}.
    -
    -decode_vcard_DOM(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"DOM">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_DOM(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"DOM">>, _attrs, _els}.
    -
    -decode_vcard_PARCEL(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"PARCEL">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_PARCEL(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PARCEL">>, _attrs, _els}.
    -
    -decode_vcard_POSTAL(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"POSTAL">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_POSTAL(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"POSTAL">>, _attrs, _els}.
    -
    -decode_vcard_PCS(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"PCS">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_PCS(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PCS">>, _attrs, _els}.
    -
    -decode_vcard_ISDN(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"ISDN">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_ISDN(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"ISDN">>, _attrs, _els}.
    -
    -decode_vcard_MODEM(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"MODEM">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_MODEM(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"MODEM">>, _attrs, _els}.
    -
    -decode_vcard_BBS(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"BBS">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_BBS(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"BBS">>, _attrs, _els}.
    -
    -decode_vcard_VIDEO(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"VIDEO">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_VIDEO(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"VIDEO">>, _attrs, _els}.
    -
    -decode_vcard_CELL(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"CELL">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_CELL(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"CELL">>, _attrs, _els}.
    -
    -decode_vcard_MSG(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"MSG">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_MSG(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"MSG">>, _attrs, _els}.
    -
    -decode_vcard_PAGER(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"PAGER">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_PAGER(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"PAGER">>, _attrs, _els}.
    -
    -decode_vcard_FAX(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"FAX">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_FAX(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"FAX">>, _attrs, _els}.
    -
    -decode_vcard_VOICE(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"VOICE">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_VOICE(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"VOICE">>, _attrs, _els}.
    -
    -decode_vcard_WORK(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"WORK">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_WORK(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"WORK">>, _attrs, _els}.
    -
    -decode_vcard_HOME(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"HOME">>, _attrs, _els}) ->
    -    true.
    -
    -encode_vcard_HOME(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"HOME">>, _attrs, _els}.
    -
    -decode_stream_error(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"stream:error">>, _attrs, _els}) ->
    -    {Text, Reason} = decode_stream_error_els(__TopXMLNS,
    -					     __IgnoreEls, _els, undefined,
    -					     undefined),
    -    {stream_error, Reason, Text}.
    -
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls, [],
    -			Text, Reason) ->
    -    {Text, Reason};
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"text">>, _attrs, _} = _el | _els], Text,
    -			Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   decode_stream_error_text(_xmlns, __IgnoreEls,
    -							    _el),
    -				   Reason);
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"bad-format">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_bad_format(_xmlns,
    -								  __IgnoreEls,
    -								  _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"bad-namespace-prefix">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_bad_namespace_prefix(_xmlns,
    -									    __IgnoreEls,
    -									    _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"conflict">>, _attrs, _} = _el | _els], Text,
    -			Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_conflict(_xmlns,
    -								__IgnoreEls,
    -								_el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"connection-timeout">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_connection_timeout(_xmlns,
    -									  __IgnoreEls,
    -									  _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"host-gone">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_host_gone(_xmlns,
    -								 __IgnoreEls,
    -								 _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"host-unknown">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_host_unknown(_xmlns,
    -								    __IgnoreEls,
    -								    _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"improper-addressing">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_improper_addressing(_xmlns,
    -									   __IgnoreEls,
    -									   _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"internal-server-error">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_internal_server_error(_xmlns,
    -									     __IgnoreEls,
    -									     _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"invalid-from">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_invalid_from(_xmlns,
    -								    __IgnoreEls,
    -								    _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"invalid-id">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_invalid_id(_xmlns,
    -								  __IgnoreEls,
    -								  _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"invalid-namespace">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_invalid_namespace(_xmlns,
    -									 __IgnoreEls,
    -									 _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"invalid-xml">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_invalid_xml(_xmlns,
    -								   __IgnoreEls,
    -								   _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"not-authorized">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_not_authorized(_xmlns,
    -								      __IgnoreEls,
    -								      _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"not-well-formed">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_not_well_formed(_xmlns,
    -								       __IgnoreEls,
    -								       _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"policy-violation">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_policy_violation(_xmlns,
    -									__IgnoreEls,
    -									_el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"remote-connection-failed">>, _attrs, _} =
    -			     _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_remote_connection_failed(_xmlns,
    -										__IgnoreEls,
    -										_el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"reset">>, _attrs, _} = _el | _els], Text,
    -			Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_reset(_xmlns,
    -							     __IgnoreEls, _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"resource-constraint">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_resource_constraint(_xmlns,
    -									   __IgnoreEls,
    -									   _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"restricted-xml">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_restricted_xml(_xmlns,
    -								      __IgnoreEls,
    -								      _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"see-other-host">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_see_other_host(_xmlns,
    -								      __IgnoreEls,
    -								      _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"system-shutdown">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_system_shutdown(_xmlns,
    -								       __IgnoreEls,
    -								       _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"undefined-condition">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_undefined_condition(_xmlns,
    -									   __IgnoreEls,
    -									   _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"unsupported-encoding">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_unsupported_encoding(_xmlns,
    -									    __IgnoreEls,
    -									    _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"unsupported-stanza-type">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_unsupported_stanza_type(_xmlns,
    -									       __IgnoreEls,
    -									       _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"unsupported-version">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-streams">> ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_stream_error_unsupported_version(_xmlns,
    -									   __IgnoreEls,
    -									   _el));
    -       true ->
    -	   decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_stream_error_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Text, Reason) ->
    -    decode_stream_error_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Text, Reason).
    -
    -encode_stream_error({stream_error, Reason, Text},
    -		    _xmlns_attrs) ->
    -    _els = lists:reverse('encode_stream_error_$text'(Text,
    -						     'encode_stream_error_$reason'(Reason,
    -										   []))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"stream:error">>, _attrs, _els}.
    -
    -'encode_stream_error_$text'(undefined, _acc) -> _acc;
    -'encode_stream_error_$text'(Text, _acc) ->
    -    [encode_stream_error_text(Text,
    -			      [{<<"xmlns">>,
    -				<<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc].
    -
    -'encode_stream_error_$reason'(undefined, _acc) -> _acc;
    -'encode_stream_error_$reason'('bad-format' = Reason,
    -			      _acc) ->
    -    [encode_stream_error_bad_format(Reason,
    -				    [{<<"xmlns">>,
    -				      <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('bad-namespace-prefix' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_bad_namespace_prefix(Reason,
    -					      [{<<"xmlns">>,
    -						<<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'(conflict = Reason,
    -			      _acc) ->
    -    [encode_stream_error_conflict(Reason,
    -				  [{<<"xmlns">>,
    -				    <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('connection-timeout' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_connection_timeout(Reason,
    -					    [{<<"xmlns">>,
    -					      <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('host-gone' = Reason,
    -			      _acc) ->
    -    [encode_stream_error_host_gone(Reason,
    -				   [{<<"xmlns">>,
    -				     <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('host-unknown' = Reason,
    -			      _acc) ->
    -    [encode_stream_error_host_unknown(Reason,
    -				      [{<<"xmlns">>,
    -					<<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('improper-addressing' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_improper_addressing(Reason,
    -					     [{<<"xmlns">>,
    -					       <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('internal-server-error' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_internal_server_error(Reason,
    -					       [{<<"xmlns">>,
    -						 <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('invalid-from' = Reason,
    -			      _acc) ->
    -    [encode_stream_error_invalid_from(Reason,
    -				      [{<<"xmlns">>,
    -					<<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('invalid-id' = Reason,
    -			      _acc) ->
    -    [encode_stream_error_invalid_id(Reason,
    -				    [{<<"xmlns">>,
    -				      <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('invalid-namespace' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_invalid_namespace(Reason,
    -					   [{<<"xmlns">>,
    -					     <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('invalid-xml' = Reason,
    -			      _acc) ->
    -    [encode_stream_error_invalid_xml(Reason,
    -				     [{<<"xmlns">>,
    -				       <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('not-authorized' = Reason,
    -			      _acc) ->
    -    [encode_stream_error_not_authorized(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('not-well-formed' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_not_well_formed(Reason,
    -					 [{<<"xmlns">>,
    -					   <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('policy-violation' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_policy_violation(Reason,
    -					  [{<<"xmlns">>,
    -					    <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('remote-connection-failed' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_remote_connection_failed(Reason,
    -						  [{<<"xmlns">>,
    -						    <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'(reset = Reason, _acc) ->
    -    [encode_stream_error_reset(Reason,
    -			       [{<<"xmlns">>,
    -				 <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('resource-constraint' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_resource_constraint(Reason,
    -					     [{<<"xmlns">>,
    -					       <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('restricted-xml' = Reason,
    -			      _acc) ->
    -    [encode_stream_error_restricted_xml(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'({'see-other-host', _} =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_see_other_host(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('system-shutdown' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_system_shutdown(Reason,
    -					 [{<<"xmlns">>,
    -					   <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('undefined-condition' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_undefined_condition(Reason,
    -					     [{<<"xmlns">>,
    -					       <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('unsupported-encoding' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_unsupported_encoding(Reason,
    -					      [{<<"xmlns">>,
    -						<<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('unsupported-stanza-type' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_unsupported_stanza_type(Reason,
    -						 [{<<"xmlns">>,
    -						   <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc];
    -'encode_stream_error_$reason'('unsupported-version' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_stream_error_unsupported_version(Reason,
    -					     [{<<"xmlns">>,
    -					       <<"urn:ietf:params:xml:ns:xmpp-streams">>}])
    -     | _acc].
    -
    -decode_stream_error_unsupported_version(__TopXMLNS,
    -					__IgnoreEls,
    -					{xmlel, <<"unsupported-version">>,
    -					 _attrs, _els}) ->
    -    'unsupported-version'.
    -
    -encode_stream_error_unsupported_version('unsupported-version',
    -					_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"unsupported-version">>, _attrs, _els}.
    -
    -decode_stream_error_unsupported_stanza_type(__TopXMLNS,
    -					    __IgnoreEls,
    -					    {xmlel,
    -					     <<"unsupported-stanza-type">>,
    -					     _attrs, _els}) ->
    -    'unsupported-stanza-type'.
    -
    -encode_stream_error_unsupported_stanza_type('unsupported-stanza-type',
    -					    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"unsupported-stanza-type">>, _attrs, _els}.
    -
    -decode_stream_error_unsupported_encoding(__TopXMLNS,
    -					 __IgnoreEls,
    -					 {xmlel, <<"unsupported-encoding">>,
    -					  _attrs, _els}) ->
    -    'unsupported-encoding'.
    -
    -encode_stream_error_unsupported_encoding('unsupported-encoding',
    -					 _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"unsupported-encoding">>, _attrs, _els}.
    -
    -decode_stream_error_undefined_condition(__TopXMLNS,
    -					__IgnoreEls,
    -					{xmlel, <<"undefined-condition">>,
    -					 _attrs, _els}) ->
    -    'undefined-condition'.
    -
    -encode_stream_error_undefined_condition('undefined-condition',
    -					_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"undefined-condition">>, _attrs, _els}.
    -
    -decode_stream_error_system_shutdown(__TopXMLNS,
    -				    __IgnoreEls,
    -				    {xmlel, <<"system-shutdown">>, _attrs,
    -				     _els}) ->
    -    'system-shutdown'.
    -
    -encode_stream_error_system_shutdown('system-shutdown',
    -				    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"system-shutdown">>, _attrs, _els}.
    -
    -decode_stream_error_see_other_host(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"see-other-host">>, _attrs,
    -				    _els}) ->
    -    Host =
    -	decode_stream_error_see_other_host_els(__TopXMLNS,
    -					       __IgnoreEls, _els, <<>>),
    -    {'see-other-host', Host}.
    -
    -decode_stream_error_see_other_host_els(__TopXMLNS,
    -				       __IgnoreEls, [], Host) ->
    -    decode_stream_error_see_other_host_cdata(__TopXMLNS,
    -					     Host);
    -decode_stream_error_see_other_host_els(__TopXMLNS,
    -				       __IgnoreEls, [{xmlcdata, _data} | _els],
    -				       Host) ->
    -    decode_stream_error_see_other_host_els(__TopXMLNS,
    -					   __IgnoreEls, _els,
    -					   <>);
    -decode_stream_error_see_other_host_els(__TopXMLNS,
    -				       __IgnoreEls, [_ | _els], Host) ->
    -    decode_stream_error_see_other_host_els(__TopXMLNS,
    -					   __IgnoreEls, _els, Host).
    -
    -encode_stream_error_see_other_host({'see-other-host',
    -				    Host},
    -				   _xmlns_attrs) ->
    -    _els = encode_stream_error_see_other_host_cdata(Host,
    -						    []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"see-other-host">>, _attrs, _els}.
    -
    -decode_stream_error_see_other_host_cdata(__TopXMLNS,
    -					 <<>>) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_cdata, <<>>, <<"see-other-host">>,
    -		   __TopXMLNS}});
    -decode_stream_error_see_other_host_cdata(__TopXMLNS,
    -					 _val) ->
    -    _val.
    -
    -encode_stream_error_see_other_host_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_stream_error_restricted_xml(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"restricted-xml">>, _attrs,
    -				    _els}) ->
    -    'restricted-xml'.
    -
    -encode_stream_error_restricted_xml('restricted-xml',
    -				   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"restricted-xml">>, _attrs, _els}.
    -
    -decode_stream_error_resource_constraint(__TopXMLNS,
    -					__IgnoreEls,
    -					{xmlel, <<"resource-constraint">>,
    -					 _attrs, _els}) ->
    -    'resource-constraint'.
    -
    -encode_stream_error_resource_constraint('resource-constraint',
    -					_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"resource-constraint">>, _attrs, _els}.
    -
    -decode_stream_error_reset(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"reset">>, _attrs, _els}) ->
    -    reset.
    -
    -encode_stream_error_reset(reset, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"reset">>, _attrs, _els}.
    -
    -decode_stream_error_remote_connection_failed(__TopXMLNS,
    -					     __IgnoreEls,
    -					     {xmlel,
    -					      <<"remote-connection-failed">>,
    -					      _attrs, _els}) ->
    -    'remote-connection-failed'.
    -
    -encode_stream_error_remote_connection_failed('remote-connection-failed',
    -					     _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"remote-connection-failed">>, _attrs, _els}.
    -
    -decode_stream_error_policy_violation(__TopXMLNS,
    -				     __IgnoreEls,
    -				     {xmlel, <<"policy-violation">>, _attrs,
    -				      _els}) ->
    -    'policy-violation'.
    -
    -encode_stream_error_policy_violation('policy-violation',
    -				     _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"policy-violation">>, _attrs, _els}.
    -
    -decode_stream_error_not_well_formed(__TopXMLNS,
    -				    __IgnoreEls,
    -				    {xmlel, <<"not-well-formed">>, _attrs,
    -				     _els}) ->
    -    'not-well-formed'.
    -
    -encode_stream_error_not_well_formed('not-well-formed',
    -				    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"not-well-formed">>, _attrs, _els}.
    -
    -decode_stream_error_not_authorized(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"not-authorized">>, _attrs,
    -				    _els}) ->
    -    'not-authorized'.
    -
    -encode_stream_error_not_authorized('not-authorized',
    -				   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"not-authorized">>, _attrs, _els}.
    -
    -decode_stream_error_invalid_xml(__TopXMLNS, __IgnoreEls,
    -				{xmlel, <<"invalid-xml">>, _attrs, _els}) ->
    -    'invalid-xml'.
    -
    -encode_stream_error_invalid_xml('invalid-xml',
    -				_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"invalid-xml">>, _attrs, _els}.
    -
    -decode_stream_error_invalid_namespace(__TopXMLNS,
    -				      __IgnoreEls,
    -				      {xmlel, <<"invalid-namespace">>, _attrs,
    -				       _els}) ->
    -    'invalid-namespace'.
    -
    -encode_stream_error_invalid_namespace('invalid-namespace',
    -				      _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"invalid-namespace">>, _attrs, _els}.
    -
    -decode_stream_error_invalid_id(__TopXMLNS, __IgnoreEls,
    -			       {xmlel, <<"invalid-id">>, _attrs, _els}) ->
    -    'invalid-id'.
    -
    -encode_stream_error_invalid_id('invalid-id',
    -			       _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"invalid-id">>, _attrs, _els}.
    -
    -decode_stream_error_invalid_from(__TopXMLNS,
    -				 __IgnoreEls,
    -				 {xmlel, <<"invalid-from">>, _attrs, _els}) ->
    -    'invalid-from'.
    -
    -encode_stream_error_invalid_from('invalid-from',
    -				 _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"invalid-from">>, _attrs, _els}.
    -
    -decode_stream_error_internal_server_error(__TopXMLNS,
    -					  __IgnoreEls,
    -					  {xmlel, <<"internal-server-error">>,
    -					   _attrs, _els}) ->
    -    'internal-server-error'.
    -
    -encode_stream_error_internal_server_error('internal-server-error',
    -					  _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"internal-server-error">>, _attrs, _els}.
    -
    -decode_stream_error_improper_addressing(__TopXMLNS,
    -					__IgnoreEls,
    -					{xmlel, <<"improper-addressing">>,
    -					 _attrs, _els}) ->
    -    'improper-addressing'.
    -
    -encode_stream_error_improper_addressing('improper-addressing',
    -					_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"improper-addressing">>, _attrs, _els}.
    -
    -decode_stream_error_host_unknown(__TopXMLNS,
    -				 __IgnoreEls,
    -				 {xmlel, <<"host-unknown">>, _attrs, _els}) ->
    -    'host-unknown'.
    -
    -encode_stream_error_host_unknown('host-unknown',
    -				 _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"host-unknown">>, _attrs, _els}.
    -
    -decode_stream_error_host_gone(__TopXMLNS, __IgnoreEls,
    -			      {xmlel, <<"host-gone">>, _attrs, _els}) ->
    -    'host-gone'.
    -
    -encode_stream_error_host_gone('host-gone',
    -			      _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"host-gone">>, _attrs, _els}.
    -
    -decode_stream_error_connection_timeout(__TopXMLNS,
    -				       __IgnoreEls,
    -				       {xmlel, <<"connection-timeout">>, _attrs,
    -					_els}) ->
    -    'connection-timeout'.
    -
    -encode_stream_error_connection_timeout('connection-timeout',
    -				       _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"connection-timeout">>, _attrs, _els}.
    -
    -decode_stream_error_conflict(__TopXMLNS, __IgnoreEls,
    -			     {xmlel, <<"conflict">>, _attrs, _els}) ->
    -    conflict.
    -
    -encode_stream_error_conflict(conflict, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"conflict">>, _attrs, _els}.
    -
    -decode_stream_error_bad_namespace_prefix(__TopXMLNS,
    -					 __IgnoreEls,
    -					 {xmlel, <<"bad-namespace-prefix">>,
    -					  _attrs, _els}) ->
    -    'bad-namespace-prefix'.
    -
    -encode_stream_error_bad_namespace_prefix('bad-namespace-prefix',
    -					 _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"bad-namespace-prefix">>, _attrs, _els}.
    -
    -decode_stream_error_bad_format(__TopXMLNS, __IgnoreEls,
    -			       {xmlel, <<"bad-format">>, _attrs, _els}) ->
    -    'bad-format'.
    -
    -encode_stream_error_bad_format('bad-format',
    -			       _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"bad-format">>, _attrs, _els}.
    -
    -decode_stream_error_text(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"text">>, _attrs, _els}) ->
    -    Data = decode_stream_error_text_els(__TopXMLNS,
    -					__IgnoreEls, _els, <<>>),
    -    Lang = decode_stream_error_text_attrs(__TopXMLNS,
    -					  _attrs, undefined),
    -    {text, Lang, Data}.
    -
    -decode_stream_error_text_els(__TopXMLNS, __IgnoreEls,
    -			     [], Data) ->
    -    decode_stream_error_text_cdata(__TopXMLNS, Data);
    -decode_stream_error_text_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlcdata, _data} | _els], Data) ->
    -    decode_stream_error_text_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <>);
    -decode_stream_error_text_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], Data) ->
    -    decode_stream_error_text_els(__TopXMLNS, __IgnoreEls,
    -				 _els, Data).
    -
    -decode_stream_error_text_attrs(__TopXMLNS,
    -			       [{<<"xml:lang">>, _val} | _attrs], _Lang) ->
    -    decode_stream_error_text_attrs(__TopXMLNS, _attrs,
    -				   _val);
    -decode_stream_error_text_attrs(__TopXMLNS, [_ | _attrs],
    -			       Lang) ->
    -    decode_stream_error_text_attrs(__TopXMLNS, _attrs,
    -				   Lang);
    -decode_stream_error_text_attrs(__TopXMLNS, [], Lang) ->
    -    'decode_stream_error_text_attr_xml:lang'(__TopXMLNS,
    -					     Lang).
    -
    -encode_stream_error_text({text, Lang, Data},
    -			 _xmlns_attrs) ->
    -    _els = encode_stream_error_text_cdata(Data, []),
    -    _attrs = 'encode_stream_error_text_attr_xml:lang'(Lang,
    -						      _xmlns_attrs),
    -    {xmlel, <<"text">>, _attrs, _els}.
    -
    -'decode_stream_error_text_attr_xml:lang'(__TopXMLNS,
    -					 undefined) ->
    -    undefined;
    -'decode_stream_error_text_attr_xml:lang'(__TopXMLNS,
    -					 _val) ->
    -    _val.
    -
    -'encode_stream_error_text_attr_xml:lang'(undefined,
    -					 _acc) ->
    -    _acc;
    -'encode_stream_error_text_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_stream_error_text_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_stream_error_text_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_stream_error_text_cdata(undefined, _acc) -> _acc;
    -encode_stream_error_text_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_time(__TopXMLNS, __IgnoreEls,
    -	    {xmlel, <<"time">>, _attrs, _els}) ->
    -    {Utc, Tzo} = decode_time_els(__TopXMLNS, __IgnoreEls,
    -				 _els, undefined, undefined),
    -    {time, Tzo, Utc}.
    -
    -decode_time_els(__TopXMLNS, __IgnoreEls, [], Utc,
    -		Tzo) ->
    -    {Utc, Tzo};
    -decode_time_els(__TopXMLNS, __IgnoreEls,
    -		[{xmlel, <<"tzo">>, _attrs, _} = _el | _els], Utc,
    -		Tzo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_time_els(__TopXMLNS, __IgnoreEls, _els, Utc,
    -			   decode_time_tzo(__TopXMLNS, __IgnoreEls, _el));
    -       true ->
    -	   decode_time_els(__TopXMLNS, __IgnoreEls, _els, Utc, Tzo)
    -    end;
    -decode_time_els(__TopXMLNS, __IgnoreEls,
    -		[{xmlel, <<"utc">>, _attrs, _} = _el | _els], Utc,
    -		Tzo) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_time_els(__TopXMLNS, __IgnoreEls, _els,
    -			   decode_time_utc(__TopXMLNS, __IgnoreEls, _el), Tzo);
    -       true ->
    -	   decode_time_els(__TopXMLNS, __IgnoreEls, _els, Utc, Tzo)
    -    end;
    -decode_time_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		Utc, Tzo) ->
    -    decode_time_els(__TopXMLNS, __IgnoreEls, _els, Utc,
    -		    Tzo).
    -
    -encode_time({time, Tzo, Utc}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_time_$utc'(Utc,
    -					    'encode_time_$tzo'(Tzo, []))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"time">>, _attrs, _els}.
    -
    -'encode_time_$utc'(undefined, _acc) -> _acc;
    -'encode_time_$utc'(Utc, _acc) ->
    -    [encode_time_utc(Utc, []) | _acc].
    -
    -'encode_time_$tzo'(undefined, _acc) -> _acc;
    -'encode_time_$tzo'(Tzo, _acc) ->
    -    [encode_time_tzo(Tzo, []) | _acc].
    -
    -decode_time_tzo(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"tzo">>, _attrs, _els}) ->
    -    Cdata = decode_time_tzo_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    Cdata.
    -
    -decode_time_tzo_els(__TopXMLNS, __IgnoreEls, [],
    -		    Cdata) ->
    -    decode_time_tzo_cdata(__TopXMLNS, Cdata);
    -decode_time_tzo_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_time_tzo_els(__TopXMLNS, __IgnoreEls, _els,
    -			<>);
    -decode_time_tzo_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Cdata) ->
    -    decode_time_tzo_els(__TopXMLNS, __IgnoreEls, _els,
    -			Cdata).
    -
    -encode_time_tzo(Cdata, _xmlns_attrs) ->
    -    _els = encode_time_tzo_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"tzo">>, _attrs, _els}.
    -
    -decode_time_tzo_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_time_tzo_cdata(__TopXMLNS, _val) ->
    -    case catch dec_tzo(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"tzo">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_time_tzo_cdata(undefined, _acc) -> _acc;
    -encode_time_tzo_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_tzo(_val)} | _acc].
    -
    -decode_time_utc(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"utc">>, _attrs, _els}) ->
    -    Cdata = decode_time_utc_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    Cdata.
    -
    -decode_time_utc_els(__TopXMLNS, __IgnoreEls, [],
    -		    Cdata) ->
    -    decode_time_utc_cdata(__TopXMLNS, Cdata);
    -decode_time_utc_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_time_utc_els(__TopXMLNS, __IgnoreEls, _els,
    -			<>);
    -decode_time_utc_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Cdata) ->
    -    decode_time_utc_els(__TopXMLNS, __IgnoreEls, _els,
    -			Cdata).
    -
    -encode_time_utc(Cdata, _xmlns_attrs) ->
    -    _els = encode_time_utc_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"utc">>, _attrs, _els}.
    -
    -decode_time_utc_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_time_utc_cdata(__TopXMLNS, _val) ->
    -    case catch dec_utc(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"utc">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_time_utc_cdata(undefined, _acc) -> _acc;
    -encode_time_utc_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_utc(_val)} | _acc].
    -
    -decode_ping(__TopXMLNS, __IgnoreEls,
    -	    {xmlel, <<"ping">>, _attrs, _els}) ->
    -    {ping}.
    -
    -encode_ping({ping}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"ping">>, _attrs, _els}.
    -
    -decode_session(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"session">>, _attrs, _els}) ->
    -    {session}.
    -
    -encode_session({session}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"session">>, _attrs, _els}.
    -
    -decode_register(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"query">>, _attrs, _els}) ->
    -    {Zip, Xdata, Misc, Address, Instructions, Text, Last,
    -     First, Password, Registered, Date, Phone, State, Name,
    -     Username, Remove, Key, City, Nick, Url, Email} =
    -	decode_register_els(__TopXMLNS, __IgnoreEls, _els,
    -			    undefined, undefined, undefined, undefined,
    -			    undefined, undefined, undefined, undefined,
    -			    undefined, false, undefined, undefined, undefined,
    -			    undefined, undefined, false, undefined, undefined,
    -			    undefined, undefined, undefined),
    -    {register, Registered, Remove, Instructions, Username,
    -     Nick, Password, Name, First, Last, Email, Address, City,
    -     State, Zip, Phone, Url, Date, Misc, Text, Key, Xdata}.
    -
    -decode_register_els(__TopXMLNS, __IgnoreEls, [], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    {Zip, Xdata, Misc, Address, Instructions, Text, Last,
    -     First, Password, Registered, Date, Phone, State, Name,
    -     Username, Remove, Key, City, Nick, Url, Email};
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"x">>, _attrs, _} = _el | _els], Zip, Xdata,
    -		    Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"jabber:x:data">> ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       decode_xdata(_xmlns, __IgnoreEls, _el), Misc,
    -			       Address, Instructions, Text, Last, First,
    -			       Password, Registered, Date, Phone, State, Name,
    -			       Username, Remove, Key, City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"registered">>, _attrs, _} = _el | _els],
    -		    Zip, Xdata, Misc, Address, Instructions, Text, Last,
    -		    First, Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password,
    -			       decode_register_registered(__TopXMLNS,
    -							  __IgnoreEls, _el),
    -			       Date, Phone, State, Name, Username, Remove, Key,
    -			       City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"remove">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username,
    -			       decode_register_remove(__TopXMLNS, __IgnoreEls,
    -						      _el),
    -			       Key, City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"instructions">>, _attrs, _} = _el | _els],
    -		    Zip, Xdata, Misc, Address, Instructions, Text, Last,
    -		    First, Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address,
    -			       decode_register_instructions(__TopXMLNS,
    -							    __IgnoreEls, _el),
    -			       Text, Last, First, Password, Registered, Date,
    -			       Phone, State, Name, Username, Remove, Key, City,
    -			       Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"username">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name,
    -			       decode_register_username(__TopXMLNS, __IgnoreEls,
    -							_el),
    -			       Remove, Key, City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"nick">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City,
    -			       decode_register_nick(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			       Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"password">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First,
    -			       decode_register_password(__TopXMLNS, __IgnoreEls,
    -							_el),
    -			       Registered, Date, Phone, State, Name, Username,
    -			       Remove, Key, City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"name">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       decode_register_name(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			       Username, Remove, Key, City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"first">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       decode_register_first(__TopXMLNS, __IgnoreEls,
    -						     _el),
    -			       Password, Registered, Date, Phone, State, Name,
    -			       Username, Remove, Key, City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"last">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text,
    -			       decode_register_last(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"email">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       decode_register_email(__TopXMLNS, __IgnoreEls,
    -						     _el));
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"address">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc,
    -			       decode_register_address(__TopXMLNS, __IgnoreEls,
    -						       _el),
    -			       Instructions, Text, Last, First, Password,
    -			       Registered, Date, Phone, State, Name, Username,
    -			       Remove, Key, City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"city">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key,
    -			       decode_register_city(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			       Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"state">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone,
    -			       decode_register_state(__TopXMLNS, __IgnoreEls,
    -						     _el),
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"zip">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els,
    -			       decode_register_zip(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"phone">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date,
    -			       decode_register_phone(__TopXMLNS, __IgnoreEls,
    -						     _el),
    -			       State, Name, Username, Remove, Key, City, Nick,
    -			       Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"url">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick,
    -			       decode_register_url(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -			       Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"date">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered,
    -			       decode_register_date(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			       Phone, State, Name, Username, Remove, Key, City,
    -			       Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"misc">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata,
    -			       decode_register_misc(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			       Address, Instructions, Text, Last, First,
    -			       Password, Registered, Date, Phone, State, Name,
    -			       Username, Remove, Key, City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"text">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions,
    -			       decode_register_text(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			       Last, First, Password, Registered, Date, Phone,
    -			       State, Name, Username, Remove, Key, City, Nick,
    -			       Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"key">>, _attrs, _} = _el | _els], Zip,
    -		    Xdata, Misc, Address, Instructions, Text, Last, First,
    -		    Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove,
    -			       decode_register_key(__TopXMLNS, __IgnoreEls,
    -						   _el),
    -			       City, Nick, Url, Email);
    -       true ->
    -	   decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			       Xdata, Misc, Address, Instructions, Text, Last,
    -			       First, Password, Registered, Date, Phone, State,
    -			       Name, Username, Remove, Key, City, Nick, Url,
    -			       Email)
    -    end;
    -decode_register_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Zip, Xdata, Misc, Address, Instructions, Text, Last,
    -		    First, Password, Registered, Date, Phone, State, Name,
    -		    Username, Remove, Key, City, Nick, Url, Email) ->
    -    decode_register_els(__TopXMLNS, __IgnoreEls, _els, Zip,
    -			Xdata, Misc, Address, Instructions, Text, Last, First,
    -			Password, Registered, Date, Phone, State, Name,
    -			Username, Remove, Key, City, Nick, Url, Email).
    -
    -encode_register({register, Registered, Remove,
    -		 Instructions, Username, Nick, Password, Name, First,
    -		 Last, Email, Address, City, State, Zip, Phone, Url,
    -		 Date, Misc, Text, Key, Xdata},
    -		_xmlns_attrs) ->
    -    _els = lists:reverse('encode_register_$zip'(Zip,
    -						'encode_register_$xdata'(Xdata,
    -									 'encode_register_$misc'(Misc,
    -												 'encode_register_$address'(Address,
    -															    'encode_register_$instructions'(Instructions,
    -																			    'encode_register_$text'(Text,
    -																						    'encode_register_$last'(Last,
    -																									    'encode_register_$first'(First,
    -																												     'encode_register_$password'(Password,
    -																																 'encode_register_$registered'(Registered,
    -																																			       'encode_register_$date'(Date,
    -																																						       'encode_register_$phone'(Phone,
    -																																										'encode_register_$state'(State,
    -																																													 'encode_register_$name'(Name,
    -																																																 'encode_register_$username'(Username,
    -																																																			     'encode_register_$remove'(Remove,
    -																																																						       'encode_register_$key'(Key,
    -																																																									      'encode_register_$city'(City,
    -																																																												      'encode_register_$nick'(Nick,
    -																																																															      'encode_register_$url'(Url,
    -																																																																		     'encode_register_$email'(Email,
    -																																																																					      [])))))))))))))))))))))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_register_$zip'(undefined, _acc) -> _acc;
    -'encode_register_$zip'(Zip, _acc) ->
    -    [encode_register_zip(Zip, []) | _acc].
    -
    -'encode_register_$xdata'(undefined, _acc) -> _acc;
    -'encode_register_$xdata'(Xdata, _acc) ->
    -    [encode_xdata(Xdata,
    -		  [{<<"xmlns">>, <<"jabber:x:data">>}])
    -     | _acc].
    -
    -'encode_register_$misc'(undefined, _acc) -> _acc;
    -'encode_register_$misc'(Misc, _acc) ->
    -    [encode_register_misc(Misc, []) | _acc].
    -
    -'encode_register_$address'(undefined, _acc) -> _acc;
    -'encode_register_$address'(Address, _acc) ->
    -    [encode_register_address(Address, []) | _acc].
    -
    -'encode_register_$instructions'(undefined, _acc) ->
    -    _acc;
    -'encode_register_$instructions'(Instructions, _acc) ->
    -    [encode_register_instructions(Instructions, []) | _acc].
    -
    -'encode_register_$text'(undefined, _acc) -> _acc;
    -'encode_register_$text'(Text, _acc) ->
    -    [encode_register_text(Text, []) | _acc].
    -
    -'encode_register_$last'(undefined, _acc) -> _acc;
    -'encode_register_$last'(Last, _acc) ->
    -    [encode_register_last(Last, []) | _acc].
    -
    -'encode_register_$first'(undefined, _acc) -> _acc;
    -'encode_register_$first'(First, _acc) ->
    -    [encode_register_first(First, []) | _acc].
    -
    -'encode_register_$password'(undefined, _acc) -> _acc;
    -'encode_register_$password'(Password, _acc) ->
    -    [encode_register_password(Password, []) | _acc].
    -
    -'encode_register_$registered'(false, _acc) -> _acc;
    -'encode_register_$registered'(Registered, _acc) ->
    -    [encode_register_registered(Registered, []) | _acc].
    -
    -'encode_register_$date'(undefined, _acc) -> _acc;
    -'encode_register_$date'(Date, _acc) ->
    -    [encode_register_date(Date, []) | _acc].
    -
    -'encode_register_$phone'(undefined, _acc) -> _acc;
    -'encode_register_$phone'(Phone, _acc) ->
    -    [encode_register_phone(Phone, []) | _acc].
    -
    -'encode_register_$state'(undefined, _acc) -> _acc;
    -'encode_register_$state'(State, _acc) ->
    -    [encode_register_state(State, []) | _acc].
    -
    -'encode_register_$name'(undefined, _acc) -> _acc;
    -'encode_register_$name'(Name, _acc) ->
    -    [encode_register_name(Name, []) | _acc].
    -
    -'encode_register_$username'(undefined, _acc) -> _acc;
    -'encode_register_$username'(Username, _acc) ->
    -    [encode_register_username(Username, []) | _acc].
    -
    -'encode_register_$remove'(false, _acc) -> _acc;
    -'encode_register_$remove'(Remove, _acc) ->
    -    [encode_register_remove(Remove, []) | _acc].
    -
    -'encode_register_$key'(undefined, _acc) -> _acc;
    -'encode_register_$key'(Key, _acc) ->
    -    [encode_register_key(Key, []) | _acc].
    -
    -'encode_register_$city'(undefined, _acc) -> _acc;
    -'encode_register_$city'(City, _acc) ->
    -    [encode_register_city(City, []) | _acc].
    -
    -'encode_register_$nick'(undefined, _acc) -> _acc;
    -'encode_register_$nick'(Nick, _acc) ->
    -    [encode_register_nick(Nick, []) | _acc].
    -
    -'encode_register_$url'(undefined, _acc) -> _acc;
    -'encode_register_$url'(Url, _acc) ->
    -    [encode_register_url(Url, []) | _acc].
    -
    -'encode_register_$email'(undefined, _acc) -> _acc;
    -'encode_register_$email'(Email, _acc) ->
    -    [encode_register_email(Email, []) | _acc].
    -
    -decode_register_key(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"key">>, _attrs, _els}) ->
    -    Cdata = decode_register_key_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_register_key_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_register_key_cdata(__TopXMLNS, Cdata);
    -decode_register_key_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_key_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_register_key_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_register_key_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_register_key(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_key_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"key">>, _attrs, _els}.
    -
    -decode_register_key_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_key_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_key_cdata(none, _acc) -> _acc;
    -encode_register_key_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_text(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"text">>, _attrs, _els}) ->
    -    Cdata = decode_register_text_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_text_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_register_text_cdata(__TopXMLNS, Cdata);
    -decode_register_text_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_text_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_register_text_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_register_text_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_register_text(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_text_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"text">>, _attrs, _els}.
    -
    -decode_register_text_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_text_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_text_cdata(none, _acc) -> _acc;
    -encode_register_text_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_misc(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"misc">>, _attrs, _els}) ->
    -    Cdata = decode_register_misc_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_misc_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_register_misc_cdata(__TopXMLNS, Cdata);
    -decode_register_misc_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_misc_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_register_misc_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_register_misc_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_register_misc(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_misc_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"misc">>, _attrs, _els}.
    -
    -decode_register_misc_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_misc_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_misc_cdata(none, _acc) -> _acc;
    -encode_register_misc_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_date(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"date">>, _attrs, _els}) ->
    -    Cdata = decode_register_date_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_date_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_register_date_cdata(__TopXMLNS, Cdata);
    -decode_register_date_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_date_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_register_date_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_register_date_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_register_date(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_date_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"date">>, _attrs, _els}.
    -
    -decode_register_date_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_date_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_date_cdata(none, _acc) -> _acc;
    -encode_register_date_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_url(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"url">>, _attrs, _els}) ->
    -    Cdata = decode_register_url_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_register_url_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_register_url_cdata(__TopXMLNS, Cdata);
    -decode_register_url_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_url_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_register_url_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_register_url_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_register_url(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_url_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"url">>, _attrs, _els}.
    -
    -decode_register_url_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_url_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_url_cdata(none, _acc) -> _acc;
    -encode_register_url_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_phone(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"phone">>, _attrs, _els}) ->
    -    Cdata = decode_register_phone_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_phone_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_register_phone_cdata(__TopXMLNS, Cdata);
    -decode_register_phone_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_phone_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_register_phone_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_register_phone_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_register_phone(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_phone_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"phone">>, _attrs, _els}.
    -
    -decode_register_phone_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_phone_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_phone_cdata(none, _acc) -> _acc;
    -encode_register_phone_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_zip(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"zip">>, _attrs, _els}) ->
    -    Cdata = decode_register_zip_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_register_zip_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_register_zip_cdata(__TopXMLNS, Cdata);
    -decode_register_zip_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_zip_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_register_zip_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_register_zip_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_register_zip(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_zip_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"zip">>, _attrs, _els}.
    -
    -decode_register_zip_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_zip_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_zip_cdata(none, _acc) -> _acc;
    -encode_register_zip_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_state(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"state">>, _attrs, _els}) ->
    -    Cdata = decode_register_state_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_state_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_register_state_cdata(__TopXMLNS, Cdata);
    -decode_register_state_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_state_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_register_state_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_register_state_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_register_state(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_state_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"state">>, _attrs, _els}.
    -
    -decode_register_state_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_state_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_state_cdata(none, _acc) -> _acc;
    -encode_register_state_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_city(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"city">>, _attrs, _els}) ->
    -    Cdata = decode_register_city_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_city_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_register_city_cdata(__TopXMLNS, Cdata);
    -decode_register_city_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_city_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_register_city_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_register_city_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_register_city(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_city_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"city">>, _attrs, _els}.
    -
    -decode_register_city_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_city_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_city_cdata(none, _acc) -> _acc;
    -encode_register_city_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_address(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"address">>, _attrs, _els}) ->
    -    Cdata = decode_register_address_els(__TopXMLNS,
    -					__IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_address_els(__TopXMLNS, __IgnoreEls, [],
    -			    Cdata) ->
    -    decode_register_address_cdata(__TopXMLNS, Cdata);
    -decode_register_address_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_address_els(__TopXMLNS, __IgnoreEls,
    -				_els, <>);
    -decode_register_address_els(__TopXMLNS, __IgnoreEls,
    -			    [_ | _els], Cdata) ->
    -    decode_register_address_els(__TopXMLNS, __IgnoreEls,
    -				_els, Cdata).
    -
    -encode_register_address(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_address_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"address">>, _attrs, _els}.
    -
    -decode_register_address_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_address_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_address_cdata(none, _acc) -> _acc;
    -encode_register_address_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_email(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"email">>, _attrs, _els}) ->
    -    Cdata = decode_register_email_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_email_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_register_email_cdata(__TopXMLNS, Cdata);
    -decode_register_email_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_email_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_register_email_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_register_email_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_register_email(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_email_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"email">>, _attrs, _els}.
    -
    -decode_register_email_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_email_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_email_cdata(none, _acc) -> _acc;
    -encode_register_email_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_last(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"last">>, _attrs, _els}) ->
    -    Cdata = decode_register_last_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_last_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_register_last_cdata(__TopXMLNS, Cdata);
    -decode_register_last_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_last_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_register_last_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_register_last_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_register_last(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_last_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"last">>, _attrs, _els}.
    -
    -decode_register_last_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_last_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_last_cdata(none, _acc) -> _acc;
    -encode_register_last_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_first(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"first">>, _attrs, _els}) ->
    -    Cdata = decode_register_first_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_first_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_register_first_cdata(__TopXMLNS, Cdata);
    -decode_register_first_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_first_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_register_first_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_register_first_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_register_first(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_first_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"first">>, _attrs, _els}.
    -
    -decode_register_first_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_first_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_first_cdata(none, _acc) -> _acc;
    -encode_register_first_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_name(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"name">>, _attrs, _els}) ->
    -    Cdata = decode_register_name_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_name_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_register_name_cdata(__TopXMLNS, Cdata);
    -decode_register_name_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_name_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_register_name_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_register_name_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_register_name(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_name_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"name">>, _attrs, _els}.
    -
    -decode_register_name_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_name_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_name_cdata(none, _acc) -> _acc;
    -encode_register_name_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_password(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"password">>, _attrs, _els}) ->
    -    Cdata = decode_register_password_els(__TopXMLNS,
    -					 __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_password_els(__TopXMLNS, __IgnoreEls,
    -			     [], Cdata) ->
    -    decode_register_password_cdata(__TopXMLNS, Cdata);
    -decode_register_password_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_password_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <>);
    -decode_register_password_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], Cdata) ->
    -    decode_register_password_els(__TopXMLNS, __IgnoreEls,
    -				 _els, Cdata).
    -
    -encode_register_password(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_password_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"password">>, _attrs, _els}.
    -
    -decode_register_password_cdata(__TopXMLNS, <<>>) ->
    -    none;
    -decode_register_password_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_register_password_cdata(none, _acc) -> _acc;
    -encode_register_password_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_nick(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"nick">>, _attrs, _els}) ->
    -    Cdata = decode_register_nick_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_nick_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_register_nick_cdata(__TopXMLNS, Cdata);
    -decode_register_nick_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_nick_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_register_nick_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_register_nick_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_register_nick(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_nick_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"nick">>, _attrs, _els}.
    -
    -decode_register_nick_cdata(__TopXMLNS, <<>>) -> none;
    -decode_register_nick_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_register_nick_cdata(none, _acc) -> _acc;
    -encode_register_nick_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_username(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"username">>, _attrs, _els}) ->
    -    Cdata = decode_register_username_els(__TopXMLNS,
    -					 __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_username_els(__TopXMLNS, __IgnoreEls,
    -			     [], Cdata) ->
    -    decode_register_username_cdata(__TopXMLNS, Cdata);
    -decode_register_username_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_register_username_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <>);
    -decode_register_username_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], Cdata) ->
    -    decode_register_username_els(__TopXMLNS, __IgnoreEls,
    -				 _els, Cdata).
    -
    -encode_register_username(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_username_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"username">>, _attrs, _els}.
    -
    -decode_register_username_cdata(__TopXMLNS, <<>>) ->
    -    none;
    -decode_register_username_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_register_username_cdata(none, _acc) -> _acc;
    -encode_register_username_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_instructions(__TopXMLNS, __IgnoreEls,
    -			     {xmlel, <<"instructions">>, _attrs, _els}) ->
    -    Cdata = decode_register_instructions_els(__TopXMLNS,
    -					     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_register_instructions_els(__TopXMLNS,
    -				 __IgnoreEls, [], Cdata) ->
    -    decode_register_instructions_cdata(__TopXMLNS, Cdata);
    -decode_register_instructions_els(__TopXMLNS,
    -				 __IgnoreEls, [{xmlcdata, _data} | _els],
    -				 Cdata) ->
    -    decode_register_instructions_els(__TopXMLNS,
    -				     __IgnoreEls, _els,
    -				     <>);
    -decode_register_instructions_els(__TopXMLNS,
    -				 __IgnoreEls, [_ | _els], Cdata) ->
    -    decode_register_instructions_els(__TopXMLNS,
    -				     __IgnoreEls, _els, Cdata).
    -
    -encode_register_instructions(Cdata, _xmlns_attrs) ->
    -    _els = encode_register_instructions_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"instructions">>, _attrs, _els}.
    -
    -decode_register_instructions_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_register_instructions_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_register_instructions_cdata(undefined, _acc) ->
    -    _acc;
    -encode_register_instructions_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_register_remove(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"remove">>, _attrs, _els}) ->
    -    true.
    -
    -encode_register_remove(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"remove">>, _attrs, _els}.
    -
    -decode_register_registered(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"registered">>, _attrs, _els}) ->
    -    true.
    -
    -encode_register_registered(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"registered">>, _attrs, _els}.
    -
    -decode_feature_register(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"register">>, _attrs, _els}) ->
    -    {feature_register}.
    -
    -encode_feature_register({feature_register},
    -			_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"register">>, _attrs, _els}.
    -
    -decode_caps(__TopXMLNS, __IgnoreEls,
    -	    {xmlel, <<"c">>, _attrs, _els}) ->
    -    {Hash, Node, Ver} = decode_caps_attrs(__TopXMLNS,
    -					  _attrs, undefined, undefined,
    -					  undefined),
    -    {caps, Hash, Node, Ver}.
    -
    -decode_caps_attrs(__TopXMLNS,
    -		  [{<<"hash">>, _val} | _attrs], _Hash, Node, Ver) ->
    -    decode_caps_attrs(__TopXMLNS, _attrs, _val, Node, Ver);
    -decode_caps_attrs(__TopXMLNS,
    -		  [{<<"node">>, _val} | _attrs], Hash, _Node, Ver) ->
    -    decode_caps_attrs(__TopXMLNS, _attrs, Hash, _val, Ver);
    -decode_caps_attrs(__TopXMLNS,
    -		  [{<<"ver">>, _val} | _attrs], Hash, Node, _Ver) ->
    -    decode_caps_attrs(__TopXMLNS, _attrs, Hash, Node, _val);
    -decode_caps_attrs(__TopXMLNS, [_ | _attrs], Hash, Node,
    -		  Ver) ->
    -    decode_caps_attrs(__TopXMLNS, _attrs, Hash, Node, Ver);
    -decode_caps_attrs(__TopXMLNS, [], Hash, Node, Ver) ->
    -    {decode_caps_attr_hash(__TopXMLNS, Hash),
    -     decode_caps_attr_node(__TopXMLNS, Node),
    -     decode_caps_attr_ver(__TopXMLNS, Ver)}.
    -
    -encode_caps({caps, Hash, Node, Ver}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_caps_attr_ver(Ver,
    -				  encode_caps_attr_node(Node,
    -							encode_caps_attr_hash(Hash,
    -									      _xmlns_attrs))),
    -    {xmlel, <<"c">>, _attrs, _els}.
    -
    -decode_caps_attr_hash(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_caps_attr_hash(__TopXMLNS, _val) -> _val.
    -
    -encode_caps_attr_hash(undefined, _acc) -> _acc;
    -encode_caps_attr_hash(_val, _acc) ->
    -    [{<<"hash">>, _val} | _acc].
    -
    -decode_caps_attr_node(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_caps_attr_node(__TopXMLNS, _val) -> _val.
    -
    -encode_caps_attr_node(undefined, _acc) -> _acc;
    -encode_caps_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_caps_attr_ver(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_caps_attr_ver(__TopXMLNS, _val) ->
    -    case catch base64:decode(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"ver">>, <<"c">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_caps_attr_ver(undefined, _acc) -> _acc;
    -encode_caps_attr_ver(_val, _acc) ->
    -    [{<<"ver">>, base64:encode(_val)} | _acc].
    -
    -decode_p1_ack(__TopXMLNS, __IgnoreEls,
    -	      {xmlel, <<"ack">>, _attrs, _els}) ->
    -    {p1_ack}.
    -
    -encode_p1_ack({p1_ack}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"ack">>, _attrs, _els}.
    -
    -decode_p1_rebind(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"rebind">>, _attrs, _els}) ->
    -    {p1_rebind}.
    -
    -encode_p1_rebind({p1_rebind}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"rebind">>, _attrs, _els}.
    -
    -decode_p1_push(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"push">>, _attrs, _els}) ->
    -    {p1_push}.
    -
    -encode_p1_push({p1_push}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"push">>, _attrs, _els}.
    -
    -decode_stream_features(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"stream:features">>, _attrs, _els}) ->
    -    __Els = decode_stream_features_els(__TopXMLNS,
    -				       __IgnoreEls, _els, []),
    -    {stream_features, __Els}.
    -
    -decode_stream_features_els(__TopXMLNS, __IgnoreEls, [],
    -			   __Els) ->
    -    lists:reverse(__Els);
    -decode_stream_features_els(__TopXMLNS, __IgnoreEls,
    -			   [{xmlel, _, _, _} = _el | _els], __Els) ->
    -    if __IgnoreEls ->
    -	   decode_stream_features_els(__TopXMLNS, __IgnoreEls,
    -				      _els, [_el | __Els]);
    -       true ->
    -	   case is_known_tag(_el) of
    -	     true ->
    -		 decode_stream_features_els(__TopXMLNS, __IgnoreEls,
    -					    _els, [decode(_el) | __Els]);
    -	     false ->
    -		 decode_stream_features_els(__TopXMLNS, __IgnoreEls,
    -					    _els, __Els)
    -	   end
    -    end;
    -decode_stream_features_els(__TopXMLNS, __IgnoreEls,
    -			   [_ | _els], __Els) ->
    -    decode_stream_features_els(__TopXMLNS, __IgnoreEls,
    -			       _els, __Els).
    -
    -encode_stream_features({stream_features, __Els},
    -		       _xmlns_attrs) ->
    -    _els = [encode(_el) || _el <- __Els],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"stream:features">>, _attrs, _els}.
    -
    -decode_compression(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"compression">>, _attrs, _els}) ->
    -    Methods = decode_compression_els(__TopXMLNS,
    -				     __IgnoreEls, _els, []),
    -    {compression, Methods}.
    -
    -decode_compression_els(__TopXMLNS, __IgnoreEls, [],
    -		       Methods) ->
    -    lists:reverse(Methods);
    -decode_compression_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"method">>, _attrs, _} = _el | _els],
    -		       Methods) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_compression_els(__TopXMLNS, __IgnoreEls, _els,
    -				  case decode_compression_method(__TopXMLNS,
    -								 __IgnoreEls,
    -								 _el)
    -				      of
    -				    undefined -> Methods;
    -				    _new_el -> [_new_el | Methods]
    -				  end);
    -       true ->
    -	   decode_compression_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Methods)
    -    end;
    -decode_compression_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Methods) ->
    -    decode_compression_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Methods).
    -
    -encode_compression({compression, Methods},
    -		   _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_compression_$methods'(Methods,
    -						    [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"compression">>, _attrs, _els}.
    -
    -'encode_compression_$methods'([], _acc) -> _acc;
    -'encode_compression_$methods'([Methods | _els], _acc) ->
    -    'encode_compression_$methods'(_els,
    -				  [encode_compression_method(Methods, [])
    -				   | _acc]).
    -
    -decode_compression_method(__TopXMLNS, __IgnoreEls,
    -			  {xmlel, <<"method">>, _attrs, _els}) ->
    -    Cdata = decode_compression_method_els(__TopXMLNS,
    -					  __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_compression_method_els(__TopXMLNS, __IgnoreEls,
    -			      [], Cdata) ->
    -    decode_compression_method_cdata(__TopXMLNS, Cdata);
    -decode_compression_method_els(__TopXMLNS, __IgnoreEls,
    -			      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_compression_method_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <>);
    -decode_compression_method_els(__TopXMLNS, __IgnoreEls,
    -			      [_ | _els], Cdata) ->
    -    decode_compression_method_els(__TopXMLNS, __IgnoreEls,
    -				  _els, Cdata).
    -
    -encode_compression_method(Cdata, _xmlns_attrs) ->
    -    _els = encode_compression_method_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"method">>, _attrs, _els}.
    -
    -decode_compression_method_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_compression_method_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_compression_method_cdata(undefined, _acc) ->
    -    _acc;
    -encode_compression_method_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_compressed(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"compressed">>, _attrs, _els}) ->
    -    {compressed}.
    -
    -encode_compressed({compressed}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"compressed">>, _attrs, _els}.
    -
    -decode_compress(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"compress">>, _attrs, _els}) ->
    -    Methods = decode_compress_els(__TopXMLNS, __IgnoreEls,
    -				  _els, []),
    -    {compress, Methods}.
    -
    -decode_compress_els(__TopXMLNS, __IgnoreEls, [],
    -		    Methods) ->
    -    lists:reverse(Methods);
    -decode_compress_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"method">>, _attrs, _} = _el | _els],
    -		    Methods) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_compress_els(__TopXMLNS, __IgnoreEls, _els,
    -			       case decode_compress_method(__TopXMLNS,
    -							   __IgnoreEls, _el)
    -				   of
    -				 undefined -> Methods;
    -				 _new_el -> [_new_el | Methods]
    -			       end);
    -       true ->
    -	   decode_compress_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Methods)
    -    end;
    -decode_compress_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Methods) ->
    -    decode_compress_els(__TopXMLNS, __IgnoreEls, _els,
    -			Methods).
    -
    -encode_compress({compress, Methods}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_compress_$methods'(Methods,
    -						    [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"compress">>, _attrs, _els}.
    -
    -'encode_compress_$methods'([], _acc) -> _acc;
    -'encode_compress_$methods'([Methods | _els], _acc) ->
    -    'encode_compress_$methods'(_els,
    -			       [encode_compress_method(Methods, []) | _acc]).
    -
    -decode_compress_method(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"method">>, _attrs, _els}) ->
    -    Cdata = decode_compress_method_els(__TopXMLNS,
    -				       __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_compress_method_els(__TopXMLNS, __IgnoreEls, [],
    -			   Cdata) ->
    -    decode_compress_method_cdata(__TopXMLNS, Cdata);
    -decode_compress_method_els(__TopXMLNS, __IgnoreEls,
    -			   [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_compress_method_els(__TopXMLNS, __IgnoreEls,
    -			       _els, <>);
    -decode_compress_method_els(__TopXMLNS, __IgnoreEls,
    -			   [_ | _els], Cdata) ->
    -    decode_compress_method_els(__TopXMLNS, __IgnoreEls,
    -			       _els, Cdata).
    -
    -encode_compress_method(Cdata, _xmlns_attrs) ->
    -    _els = encode_compress_method_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"method">>, _attrs, _els}.
    -
    -decode_compress_method_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_compress_method_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_compress_method_cdata(undefined, _acc) -> _acc;
    -encode_compress_method_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_compress_failure(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"failure">>, _attrs, _els}) ->
    -    Reason = decode_compress_failure_els(__TopXMLNS,
    -					 __IgnoreEls, _els, undefined),
    -    {compress_failure, Reason}.
    -
    -decode_compress_failure_els(__TopXMLNS, __IgnoreEls, [],
    -			    Reason) ->
    -    Reason;
    -decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlel, <<"setup-failed">>, _attrs, _} = _el
    -			     | _els],
    -			    Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -				       _els,
    -				       decode_compress_failure_setup_failed(__TopXMLNS,
    -									    __IgnoreEls,
    -									    _el));
    -       true ->
    -	   decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -				       _els, Reason)
    -    end;
    -decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlel, <<"processing-failed">>, _attrs, _} = _el
    -			     | _els],
    -			    Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -				       _els,
    -				       decode_compress_failure_processing_failed(__TopXMLNS,
    -										 __IgnoreEls,
    -										 _el));
    -       true ->
    -	   decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -				       _els, Reason)
    -    end;
    -decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -			    [{xmlel, <<"unsupported-method">>, _attrs, _} = _el
    -			     | _els],
    -			    Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -				       _els,
    -				       decode_compress_failure_unsupported_method(__TopXMLNS,
    -										  __IgnoreEls,
    -										  _el));
    -       true ->
    -	   decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -				       _els, Reason)
    -    end;
    -decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -			    [_ | _els], Reason) ->
    -    decode_compress_failure_els(__TopXMLNS, __IgnoreEls,
    -				_els, Reason).
    -
    -encode_compress_failure({compress_failure, Reason},
    -			_xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_compress_failure_$reason'(Reason,
    -							[])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"failure">>, _attrs, _els}.
    -
    -'encode_compress_failure_$reason'(undefined, _acc) ->
    -    _acc;
    -'encode_compress_failure_$reason'('setup-failed' =
    -				      Reason,
    -				  _acc) ->
    -    [encode_compress_failure_setup_failed(Reason, [])
    -     | _acc];
    -'encode_compress_failure_$reason'('processing-failed' =
    -				      Reason,
    -				  _acc) ->
    -    [encode_compress_failure_processing_failed(Reason, [])
    -     | _acc];
    -'encode_compress_failure_$reason'('unsupported-method' =
    -				      Reason,
    -				  _acc) ->
    -    [encode_compress_failure_unsupported_method(Reason, [])
    -     | _acc].
    -
    -decode_compress_failure_unsupported_method(__TopXMLNS,
    -					   __IgnoreEls,
    -					   {xmlel, <<"unsupported-method">>,
    -					    _attrs, _els}) ->
    -    'unsupported-method'.
    -
    -encode_compress_failure_unsupported_method('unsupported-method',
    -					   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"unsupported-method">>, _attrs, _els}.
    -
    -decode_compress_failure_processing_failed(__TopXMLNS,
    -					  __IgnoreEls,
    -					  {xmlel, <<"processing-failed">>,
    -					   _attrs, _els}) ->
    -    'processing-failed'.
    -
    -encode_compress_failure_processing_failed('processing-failed',
    -					  _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"processing-failed">>, _attrs, _els}.
    -
    -decode_compress_failure_setup_failed(__TopXMLNS,
    -				     __IgnoreEls,
    -				     {xmlel, <<"setup-failed">>, _attrs,
    -				      _els}) ->
    -    'setup-failed'.
    -
    -encode_compress_failure_setup_failed('setup-failed',
    -				     _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"setup-failed">>, _attrs, _els}.
    -
    -decode_starttls_failure(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"failure">>, _attrs, _els}) ->
    -    {starttls_failure}.
    -
    -encode_starttls_failure({starttls_failure},
    -			_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"failure">>, _attrs, _els}.
    -
    -decode_starttls_proceed(__TopXMLNS, __IgnoreEls,
    -			{xmlel, <<"proceed">>, _attrs, _els}) ->
    -    {starttls_proceed}.
    -
    -encode_starttls_proceed({starttls_proceed},
    -			_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"proceed">>, _attrs, _els}.
    -
    -decode_starttls(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"starttls">>, _attrs, _els}) ->
    -    Required = decode_starttls_els(__TopXMLNS, __IgnoreEls,
    -				   _els, false),
    -    {starttls, Required}.
    -
    -decode_starttls_els(__TopXMLNS, __IgnoreEls, [],
    -		    Required) ->
    -    Required;
    -decode_starttls_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"required">>, _attrs, _} = _el | _els],
    -		    Required) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_starttls_els(__TopXMLNS, __IgnoreEls, _els,
    -			       decode_starttls_required(__TopXMLNS, __IgnoreEls,
    -							_el));
    -       true ->
    -	   decode_starttls_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Required)
    -    end;
    -decode_starttls_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Required) ->
    -    decode_starttls_els(__TopXMLNS, __IgnoreEls, _els,
    -			Required).
    -
    -encode_starttls({starttls, Required}, _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_starttls_$required'(Required,
    -						  [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"starttls">>, _attrs, _els}.
    -
    -'encode_starttls_$required'(false, _acc) -> _acc;
    -'encode_starttls_$required'(Required, _acc) ->
    -    [encode_starttls_required(Required, []) | _acc].
    -
    -decode_starttls_required(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"required">>, _attrs, _els}) ->
    -    true.
    -
    -encode_starttls_required(true, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"required">>, _attrs, _els}.
    -
    -decode_sasl_mechanisms(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"mechanisms">>, _attrs, _els}) ->
    -    List = decode_sasl_mechanisms_els(__TopXMLNS,
    -				      __IgnoreEls, _els, []),
    -    {sasl_mechanisms, List}.
    -
    -decode_sasl_mechanisms_els(__TopXMLNS, __IgnoreEls, [],
    -			   List) ->
    -    lists:reverse(List);
    -decode_sasl_mechanisms_els(__TopXMLNS, __IgnoreEls,
    -			   [{xmlel, <<"mechanism">>, _attrs, _} = _el | _els],
    -			   List) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_mechanisms_els(__TopXMLNS, __IgnoreEls,
    -				      _els,
    -				      case decode_sasl_mechanism(__TopXMLNS,
    -								 __IgnoreEls,
    -								 _el)
    -					  of
    -					undefined -> List;
    -					_new_el -> [_new_el | List]
    -				      end);
    -       true ->
    -	   decode_sasl_mechanisms_els(__TopXMLNS, __IgnoreEls,
    -				      _els, List)
    -    end;
    -decode_sasl_mechanisms_els(__TopXMLNS, __IgnoreEls,
    -			   [_ | _els], List) ->
    -    decode_sasl_mechanisms_els(__TopXMLNS, __IgnoreEls,
    -			       _els, List).
    -
    -encode_sasl_mechanisms({sasl_mechanisms, List},
    -		       _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_sasl_mechanisms_$list'(List, [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"mechanisms">>, _attrs, _els}.
    -
    -'encode_sasl_mechanisms_$list'([], _acc) -> _acc;
    -'encode_sasl_mechanisms_$list'([List | _els], _acc) ->
    -    'encode_sasl_mechanisms_$list'(_els,
    -				   [encode_sasl_mechanism(List, []) | _acc]).
    -
    -decode_sasl_mechanism(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"mechanism">>, _attrs, _els}) ->
    -    Cdata = decode_sasl_mechanism_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_sasl_mechanism_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_sasl_mechanism_cdata(__TopXMLNS, Cdata);
    -decode_sasl_mechanism_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_sasl_mechanism_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_sasl_mechanism_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_sasl_mechanism_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_sasl_mechanism(Cdata, _xmlns_attrs) ->
    -    _els = encode_sasl_mechanism_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"mechanism">>, _attrs, _els}.
    -
    -decode_sasl_mechanism_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_sasl_mechanism_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_sasl_mechanism_cdata(undefined, _acc) -> _acc;
    -encode_sasl_mechanism_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_sasl_failure(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"failure">>, _attrs, _els}) ->
    -    {Text, Reason} = decode_sasl_failure_els(__TopXMLNS,
    -					     __IgnoreEls, _els, [], undefined),
    -    {sasl_failure, Reason, Text}.
    -
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, [],
    -			Text, Reason) ->
    -    {lists:reverse(Text), Reason};
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"text">>, _attrs, _} = _el | _els], Text,
    -			Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   [decode_sasl_failure_text(__TopXMLNS,
    -							     __IgnoreEls, _el)
    -				    | Text],
    -				   Reason);
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"aborted">>, _attrs, _} = _el | _els], Text,
    -			Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_aborted(__TopXMLNS,
    -							       __IgnoreEls,
    -							       _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"account-disabled">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_account_disabled(__TopXMLNS,
    -									__IgnoreEls,
    -									_el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"credentials-expired">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_credentials_expired(__TopXMLNS,
    -									   __IgnoreEls,
    -									   _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"encryption-required">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_encryption_required(__TopXMLNS,
    -									   __IgnoreEls,
    -									   _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"incorrect-encoding">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_incorrect_encoding(__TopXMLNS,
    -									  __IgnoreEls,
    -									  _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"invalid-authzid">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_invalid_authzid(__TopXMLNS,
    -								       __IgnoreEls,
    -								       _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"invalid-mechanism">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_invalid_mechanism(__TopXMLNS,
    -									 __IgnoreEls,
    -									 _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"malformed-request">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_malformed_request(__TopXMLNS,
    -									 __IgnoreEls,
    -									 _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"mechanism-too-weak">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_mechanism_too_weak(__TopXMLNS,
    -									  __IgnoreEls,
    -									  _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"not-authorized">>, _attrs, _} = _el | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_not_authorized(__TopXMLNS,
    -								      __IgnoreEls,
    -								      _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"temporary-auth-failure">>, _attrs, _} = _el
    -			 | _els],
    -			Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text,
    -				   decode_sasl_failure_temporary_auth_failure(__TopXMLNS,
    -									      __IgnoreEls,
    -									      _el));
    -       true ->
    -	   decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Text, Reason)
    -    end;
    -decode_sasl_failure_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Text, Reason) ->
    -    decode_sasl_failure_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Text, Reason).
    -
    -encode_sasl_failure({sasl_failure, Reason, Text},
    -		    _xmlns_attrs) ->
    -    _els = lists:reverse('encode_sasl_failure_$text'(Text,
    -						     'encode_sasl_failure_$reason'(Reason,
    -										   []))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"failure">>, _attrs, _els}.
    -
    -'encode_sasl_failure_$text'([], _acc) -> _acc;
    -'encode_sasl_failure_$text'([Text | _els], _acc) ->
    -    'encode_sasl_failure_$text'(_els,
    -				[encode_sasl_failure_text(Text, []) | _acc]).
    -
    -'encode_sasl_failure_$reason'(undefined, _acc) -> _acc;
    -'encode_sasl_failure_$reason'(aborted = Reason, _acc) ->
    -    [encode_sasl_failure_aborted(Reason, []) | _acc];
    -'encode_sasl_failure_$reason'('account-disabled' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_account_disabled(Reason, [])
    -     | _acc];
    -'encode_sasl_failure_$reason'('credentials-expired' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_credentials_expired(Reason, [])
    -     | _acc];
    -'encode_sasl_failure_$reason'('encryption-required' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_encryption_required(Reason, [])
    -     | _acc];
    -'encode_sasl_failure_$reason'('incorrect-encoding' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_incorrect_encoding(Reason, [])
    -     | _acc];
    -'encode_sasl_failure_$reason'('invalid-authzid' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_invalid_authzid(Reason, [])
    -     | _acc];
    -'encode_sasl_failure_$reason'('invalid-mechanism' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_invalid_mechanism(Reason, [])
    -     | _acc];
    -'encode_sasl_failure_$reason'('malformed-request' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_malformed_request(Reason, [])
    -     | _acc];
    -'encode_sasl_failure_$reason'('mechanism-too-weak' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_mechanism_too_weak(Reason, [])
    -     | _acc];
    -'encode_sasl_failure_$reason'('not-authorized' = Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_not_authorized(Reason, []) | _acc];
    -'encode_sasl_failure_$reason'('temporary-auth-failure' =
    -				  Reason,
    -			      _acc) ->
    -    [encode_sasl_failure_temporary_auth_failure(Reason, [])
    -     | _acc].
    -
    -decode_sasl_failure_temporary_auth_failure(__TopXMLNS,
    -					   __IgnoreEls,
    -					   {xmlel, <<"temporary-auth-failure">>,
    -					    _attrs, _els}) ->
    -    'temporary-auth-failure'.
    -
    -encode_sasl_failure_temporary_auth_failure('temporary-auth-failure',
    -					   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"temporary-auth-failure">>, _attrs, _els}.
    -
    -decode_sasl_failure_not_authorized(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"not-authorized">>, _attrs,
    -				    _els}) ->
    -    'not-authorized'.
    -
    -encode_sasl_failure_not_authorized('not-authorized',
    -				   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"not-authorized">>, _attrs, _els}.
    -
    -decode_sasl_failure_mechanism_too_weak(__TopXMLNS,
    -				       __IgnoreEls,
    -				       {xmlel, <<"mechanism-too-weak">>, _attrs,
    -					_els}) ->
    -    'mechanism-too-weak'.
    -
    -encode_sasl_failure_mechanism_too_weak('mechanism-too-weak',
    -				       _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"mechanism-too-weak">>, _attrs, _els}.
    -
    -decode_sasl_failure_malformed_request(__TopXMLNS,
    -				      __IgnoreEls,
    -				      {xmlel, <<"malformed-request">>, _attrs,
    -				       _els}) ->
    -    'malformed-request'.
    -
    -encode_sasl_failure_malformed_request('malformed-request',
    -				      _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"malformed-request">>, _attrs, _els}.
    -
    -decode_sasl_failure_invalid_mechanism(__TopXMLNS,
    -				      __IgnoreEls,
    -				      {xmlel, <<"invalid-mechanism">>, _attrs,
    -				       _els}) ->
    -    'invalid-mechanism'.
    -
    -encode_sasl_failure_invalid_mechanism('invalid-mechanism',
    -				      _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"invalid-mechanism">>, _attrs, _els}.
    -
    -decode_sasl_failure_invalid_authzid(__TopXMLNS,
    -				    __IgnoreEls,
    -				    {xmlel, <<"invalid-authzid">>, _attrs,
    -				     _els}) ->
    -    'invalid-authzid'.
    -
    -encode_sasl_failure_invalid_authzid('invalid-authzid',
    -				    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"invalid-authzid">>, _attrs, _els}.
    -
    -decode_sasl_failure_incorrect_encoding(__TopXMLNS,
    -				       __IgnoreEls,
    -				       {xmlel, <<"incorrect-encoding">>, _attrs,
    -					_els}) ->
    -    'incorrect-encoding'.
    -
    -encode_sasl_failure_incorrect_encoding('incorrect-encoding',
    -				       _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"incorrect-encoding">>, _attrs, _els}.
    -
    -decode_sasl_failure_encryption_required(__TopXMLNS,
    -					__IgnoreEls,
    -					{xmlel, <<"encryption-required">>,
    -					 _attrs, _els}) ->
    -    'encryption-required'.
    -
    -encode_sasl_failure_encryption_required('encryption-required',
    -					_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"encryption-required">>, _attrs, _els}.
    -
    -decode_sasl_failure_credentials_expired(__TopXMLNS,
    -					__IgnoreEls,
    -					{xmlel, <<"credentials-expired">>,
    -					 _attrs, _els}) ->
    -    'credentials-expired'.
    -
    -encode_sasl_failure_credentials_expired('credentials-expired',
    -					_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"credentials-expired">>, _attrs, _els}.
    -
    -decode_sasl_failure_account_disabled(__TopXMLNS,
    -				     __IgnoreEls,
    -				     {xmlel, <<"account-disabled">>, _attrs,
    -				      _els}) ->
    -    'account-disabled'.
    -
    -encode_sasl_failure_account_disabled('account-disabled',
    -				     _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"account-disabled">>, _attrs, _els}.
    -
    -decode_sasl_failure_aborted(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"aborted">>, _attrs, _els}) ->
    -    aborted.
    -
    -encode_sasl_failure_aborted(aborted, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"aborted">>, _attrs, _els}.
    -
    -decode_sasl_failure_text(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"text">>, _attrs, _els}) ->
    -    Data = decode_sasl_failure_text_els(__TopXMLNS,
    -					__IgnoreEls, _els, <<>>),
    -    Lang = decode_sasl_failure_text_attrs(__TopXMLNS,
    -					  _attrs, undefined),
    -    {text, Lang, Data}.
    -
    -decode_sasl_failure_text_els(__TopXMLNS, __IgnoreEls,
    -			     [], Data) ->
    -    decode_sasl_failure_text_cdata(__TopXMLNS, Data);
    -decode_sasl_failure_text_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlcdata, _data} | _els], Data) ->
    -    decode_sasl_failure_text_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <>);
    -decode_sasl_failure_text_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], Data) ->
    -    decode_sasl_failure_text_els(__TopXMLNS, __IgnoreEls,
    -				 _els, Data).
    -
    -decode_sasl_failure_text_attrs(__TopXMLNS,
    -			       [{<<"xml:lang">>, _val} | _attrs], _Lang) ->
    -    decode_sasl_failure_text_attrs(__TopXMLNS, _attrs,
    -				   _val);
    -decode_sasl_failure_text_attrs(__TopXMLNS, [_ | _attrs],
    -			       Lang) ->
    -    decode_sasl_failure_text_attrs(__TopXMLNS, _attrs,
    -				   Lang);
    -decode_sasl_failure_text_attrs(__TopXMLNS, [], Lang) ->
    -    'decode_sasl_failure_text_attr_xml:lang'(__TopXMLNS,
    -					     Lang).
    -
    -encode_sasl_failure_text({text, Lang, Data},
    -			 _xmlns_attrs) ->
    -    _els = encode_sasl_failure_text_cdata(Data, []),
    -    _attrs = 'encode_sasl_failure_text_attr_xml:lang'(Lang,
    -						      _xmlns_attrs),
    -    {xmlel, <<"text">>, _attrs, _els}.
    -
    -'decode_sasl_failure_text_attr_xml:lang'(__TopXMLNS,
    -					 undefined) ->
    -    undefined;
    -'decode_sasl_failure_text_attr_xml:lang'(__TopXMLNS,
    -					 _val) ->
    -    _val.
    -
    -'encode_sasl_failure_text_attr_xml:lang'(undefined,
    -					 _acc) ->
    -    _acc;
    -'encode_sasl_failure_text_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_sasl_failure_text_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_sasl_failure_text_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_sasl_failure_text_cdata(undefined, _acc) -> _acc;
    -encode_sasl_failure_text_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_sasl_success(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"success">>, _attrs, _els}) ->
    -    Text = decode_sasl_success_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <<>>),
    -    {sasl_success, Text}.
    -
    -decode_sasl_success_els(__TopXMLNS, __IgnoreEls, [],
    -			Text) ->
    -    decode_sasl_success_cdata(__TopXMLNS, Text);
    -decode_sasl_success_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Text) ->
    -    decode_sasl_success_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_sasl_success_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Text) ->
    -    decode_sasl_success_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Text).
    -
    -encode_sasl_success({sasl_success, Text},
    -		    _xmlns_attrs) ->
    -    _els = encode_sasl_success_cdata(Text, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"success">>, _attrs, _els}.
    -
    -decode_sasl_success_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_sasl_success_cdata(__TopXMLNS, _val) ->
    -    case catch base64:decode(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"success">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sasl_success_cdata(undefined, _acc) -> _acc;
    -encode_sasl_success_cdata(_val, _acc) ->
    -    [{xmlcdata, base64:encode(_val)} | _acc].
    -
    -decode_sasl_response(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"response">>, _attrs, _els}) ->
    -    Text = decode_sasl_response_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    {sasl_response, Text}.
    -
    -decode_sasl_response_els(__TopXMLNS, __IgnoreEls, [],
    -			 Text) ->
    -    decode_sasl_response_cdata(__TopXMLNS, Text);
    -decode_sasl_response_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Text) ->
    -    decode_sasl_response_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_sasl_response_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Text) ->
    -    decode_sasl_response_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Text).
    -
    -encode_sasl_response({sasl_response, Text},
    -		     _xmlns_attrs) ->
    -    _els = encode_sasl_response_cdata(Text, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"response">>, _attrs, _els}.
    -
    -decode_sasl_response_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_sasl_response_cdata(__TopXMLNS, _val) ->
    -    case catch base64:decode(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"response">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sasl_response_cdata(undefined, _acc) -> _acc;
    -encode_sasl_response_cdata(_val, _acc) ->
    -    [{xmlcdata, base64:encode(_val)} | _acc].
    -
    -decode_sasl_challenge(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"challenge">>, _attrs, _els}) ->
    -    Text = decode_sasl_challenge_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    {sasl_challenge, Text}.
    -
    -decode_sasl_challenge_els(__TopXMLNS, __IgnoreEls, [],
    -			  Text) ->
    -    decode_sasl_challenge_cdata(__TopXMLNS, Text);
    -decode_sasl_challenge_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Text) ->
    -    decode_sasl_challenge_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_sasl_challenge_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Text) ->
    -    decode_sasl_challenge_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Text).
    -
    -encode_sasl_challenge({sasl_challenge, Text},
    -		      _xmlns_attrs) ->
    -    _els = encode_sasl_challenge_cdata(Text, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"challenge">>, _attrs, _els}.
    -
    -decode_sasl_challenge_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_sasl_challenge_cdata(__TopXMLNS, _val) ->
    -    case catch base64:decode(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"challenge">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sasl_challenge_cdata(undefined, _acc) -> _acc;
    -encode_sasl_challenge_cdata(_val, _acc) ->
    -    [{xmlcdata, base64:encode(_val)} | _acc].
    -
    -decode_sasl_abort(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"abort">>, _attrs, _els}) ->
    -    {sasl_abort}.
    -
    -encode_sasl_abort({sasl_abort}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"abort">>, _attrs, _els}.
    -
    -decode_sasl_auth(__TopXMLNS, __IgnoreEls,
    -		 {xmlel, <<"auth">>, _attrs, _els}) ->
    -    Text = decode_sasl_auth_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    Mechanism = decode_sasl_auth_attrs(__TopXMLNS, _attrs,
    -				       undefined),
    -    {sasl_auth, Mechanism, Text}.
    -
    -decode_sasl_auth_els(__TopXMLNS, __IgnoreEls, [],
    -		     Text) ->
    -    decode_sasl_auth_cdata(__TopXMLNS, Text);
    -decode_sasl_auth_els(__TopXMLNS, __IgnoreEls,
    -		     [{xmlcdata, _data} | _els], Text) ->
    -    decode_sasl_auth_els(__TopXMLNS, __IgnoreEls, _els,
    -			 <>);
    -decode_sasl_auth_els(__TopXMLNS, __IgnoreEls,
    -		     [_ | _els], Text) ->
    -    decode_sasl_auth_els(__TopXMLNS, __IgnoreEls, _els,
    -			 Text).
    -
    -decode_sasl_auth_attrs(__TopXMLNS,
    -		       [{<<"mechanism">>, _val} | _attrs], _Mechanism) ->
    -    decode_sasl_auth_attrs(__TopXMLNS, _attrs, _val);
    -decode_sasl_auth_attrs(__TopXMLNS, [_ | _attrs],
    -		       Mechanism) ->
    -    decode_sasl_auth_attrs(__TopXMLNS, _attrs, Mechanism);
    -decode_sasl_auth_attrs(__TopXMLNS, [], Mechanism) ->
    -    decode_sasl_auth_attr_mechanism(__TopXMLNS, Mechanism).
    -
    -encode_sasl_auth({sasl_auth, Mechanism, Text},
    -		 _xmlns_attrs) ->
    -    _els = encode_sasl_auth_cdata(Text, []),
    -    _attrs = encode_sasl_auth_attr_mechanism(Mechanism,
    -					     _xmlns_attrs),
    -    {xmlel, <<"auth">>, _attrs, _els}.
    -
    -decode_sasl_auth_attr_mechanism(__TopXMLNS,
    -				undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"mechanism">>, <<"auth">>,
    -		   __TopXMLNS}});
    -decode_sasl_auth_attr_mechanism(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_sasl_auth_attr_mechanism(_val, _acc) ->
    -    [{<<"mechanism">>, _val} | _acc].
    -
    -decode_sasl_auth_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_sasl_auth_cdata(__TopXMLNS, _val) ->
    -    case catch base64:decode(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"auth">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_sasl_auth_cdata(undefined, _acc) -> _acc;
    -encode_sasl_auth_cdata(_val, _acc) ->
    -    [{xmlcdata, base64:encode(_val)} | _acc].
    -
    -decode_bind(__TopXMLNS, __IgnoreEls,
    -	    {xmlel, <<"bind">>, _attrs, _els}) ->
    -    {Jid, Resource} = decode_bind_els(__TopXMLNS,
    -				      __IgnoreEls, _els, undefined, undefined),
    -    {bind, Jid, Resource}.
    -
    -decode_bind_els(__TopXMLNS, __IgnoreEls, [], Jid,
    -		Resource) ->
    -    {Jid, Resource};
    -decode_bind_els(__TopXMLNS, __IgnoreEls,
    -		[{xmlel, <<"jid">>, _attrs, _} = _el | _els], Jid,
    -		Resource) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_bind_els(__TopXMLNS, __IgnoreEls, _els,
    -			   decode_bind_jid(__TopXMLNS, __IgnoreEls, _el),
    -			   Resource);
    -       true ->
    -	   decode_bind_els(__TopXMLNS, __IgnoreEls, _els, Jid,
    -			   Resource)
    -    end;
    -decode_bind_els(__TopXMLNS, __IgnoreEls,
    -		[{xmlel, <<"resource">>, _attrs, _} = _el | _els], Jid,
    -		Resource) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_bind_els(__TopXMLNS, __IgnoreEls, _els, Jid,
    -			   decode_bind_resource(__TopXMLNS, __IgnoreEls, _el));
    -       true ->
    -	   decode_bind_els(__TopXMLNS, __IgnoreEls, _els, Jid,
    -			   Resource)
    -    end;
    -decode_bind_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		Jid, Resource) ->
    -    decode_bind_els(__TopXMLNS, __IgnoreEls, _els, Jid,
    -		    Resource).
    -
    -encode_bind({bind, Jid, Resource}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_bind_$jid'(Jid,
    -					    'encode_bind_$resource'(Resource,
    -								    []))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"bind">>, _attrs, _els}.
    -
    -'encode_bind_$jid'(undefined, _acc) -> _acc;
    -'encode_bind_$jid'(Jid, _acc) ->
    -    [encode_bind_jid(Jid, []) | _acc].
    -
    -'encode_bind_$resource'(undefined, _acc) -> _acc;
    -'encode_bind_$resource'(Resource, _acc) ->
    -    [encode_bind_resource(Resource, []) | _acc].
    -
    -decode_bind_resource(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"resource">>, _attrs, _els}) ->
    -    Cdata = decode_bind_resource_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_bind_resource_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_bind_resource_cdata(__TopXMLNS, Cdata);
    -decode_bind_resource_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_bind_resource_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_bind_resource_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_bind_resource_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_bind_resource(Cdata, _xmlns_attrs) ->
    -    _els = encode_bind_resource_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"resource">>, _attrs, _els}.
    -
    -decode_bind_resource_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_bind_resource_cdata(__TopXMLNS, _val) ->
    -    case catch resourceprep(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"resource">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_bind_resource_cdata(undefined, _acc) -> _acc;
    -encode_bind_resource_cdata(_val, _acc) ->
    -    [{xmlcdata, resourceprep(_val)} | _acc].
    -
    -decode_bind_jid(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"jid">>, _attrs, _els}) ->
    -    Cdata = decode_bind_jid_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    Cdata.
    -
    -decode_bind_jid_els(__TopXMLNS, __IgnoreEls, [],
    -		    Cdata) ->
    -    decode_bind_jid_cdata(__TopXMLNS, Cdata);
    -decode_bind_jid_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_bind_jid_els(__TopXMLNS, __IgnoreEls, _els,
    -			<>);
    -decode_bind_jid_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Cdata) ->
    -    decode_bind_jid_els(__TopXMLNS, __IgnoreEls, _els,
    -			Cdata).
    -
    -encode_bind_jid(Cdata, _xmlns_attrs) ->
    -    _els = encode_bind_jid_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"jid">>, _attrs, _els}.
    -
    -decode_bind_jid_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_bind_jid_cdata(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"jid">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_bind_jid_cdata(undefined, _acc) -> _acc;
    -encode_bind_jid_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_jid(_val)} | _acc].
    -
    -decode_error(__TopXMLNS, __IgnoreEls,
    -	     {xmlel, <<"error">>, _attrs, _els}) ->
    -    {Text, Reason} = decode_error_els(__TopXMLNS,
    -				      __IgnoreEls, _els, undefined, undefined),
    -    {Type, By} = decode_error_attrs(__TopXMLNS, _attrs,
    -				    undefined, undefined),
    -    {error, Type, By, Reason, Text}.
    -
    -decode_error_els(__TopXMLNS, __IgnoreEls, [], Text,
    -		 Reason) ->
    -    {Text, Reason};
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"text">>, _attrs, _} = _el | _els], Text,
    -		 Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els,
    -			    decode_error_text(_xmlns, __IgnoreEls, _el),
    -			    Reason);
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"bad-request">>, _attrs, _} = _el | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_bad_request(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"conflict">>, _attrs, _} = _el | _els], Text,
    -		 Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_conflict(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"feature-not-implemented">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_feature_not_implemented(_xmlns,
    -								 __IgnoreEls,
    -								 _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"forbidden">>, _attrs, _} = _el | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_forbidden(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"gone">>, _attrs, _} = _el | _els], Text,
    -		 Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_gone(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"internal-server-error">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_internal_server_error(_xmlns,
    -							       __IgnoreEls,
    -							       _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"item-not-found">>, _attrs, _} = _el | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_item_not_found(_xmlns, __IgnoreEls,
    -							_el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"jid-malformed">>, _attrs, _} = _el | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_jid_malformed(_xmlns, __IgnoreEls,
    -						       _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"not-acceptable">>, _attrs, _} = _el | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_not_acceptable(_xmlns, __IgnoreEls,
    -							_el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"not-allowed">>, _attrs, _} = _el | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_not_allowed(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"not-authorized">>, _attrs, _} = _el | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_not_authorized(_xmlns, __IgnoreEls,
    -							_el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"policy-violation">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_policy_violation(_xmlns, __IgnoreEls,
    -							  _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"recipient-unavailable">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_recipient_unavailable(_xmlns,
    -							       __IgnoreEls,
    -							       _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"redirect">>, _attrs, _} = _el | _els], Text,
    -		 Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_redirect(_xmlns, __IgnoreEls, _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"registration-required">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_registration_required(_xmlns,
    -							       __IgnoreEls,
    -							       _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"remote-server-not-found">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_remote_server_not_found(_xmlns,
    -								 __IgnoreEls,
    -								 _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"remote-server-timeout">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_remote_server_timeout(_xmlns,
    -							       __IgnoreEls,
    -							       _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"resource-constraint">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_resource_constraint(_xmlns,
    -							     __IgnoreEls, _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"service-unavailable">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_service_unavailable(_xmlns,
    -							     __IgnoreEls, _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"subscription-required">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_subscription_required(_xmlns,
    -							       __IgnoreEls,
    -							       _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"undefined-condition">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_undefined_condition(_xmlns,
    -							     __IgnoreEls, _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"unexpected-request">>, _attrs, _} = _el
    -		  | _els],
    -		 Text, Reason) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns ==
    -	 <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    decode_error_unexpected_request(_xmlns, __IgnoreEls,
    -							    _el));
    -       true ->
    -	   decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -			    Reason)
    -    end;
    -decode_error_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		 Text, Reason) ->
    -    decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
    -		     Reason).
    -
    -decode_error_attrs(__TopXMLNS,
    -		   [{<<"type">>, _val} | _attrs], _Type, By) ->
    -    decode_error_attrs(__TopXMLNS, _attrs, _val, By);
    -decode_error_attrs(__TopXMLNS,
    -		   [{<<"by">>, _val} | _attrs], Type, _By) ->
    -    decode_error_attrs(__TopXMLNS, _attrs, Type, _val);
    -decode_error_attrs(__TopXMLNS, [_ | _attrs], Type,
    -		   By) ->
    -    decode_error_attrs(__TopXMLNS, _attrs, Type, By);
    -decode_error_attrs(__TopXMLNS, [], Type, By) ->
    -    {decode_error_attr_type(__TopXMLNS, Type),
    -     decode_error_attr_by(__TopXMLNS, By)}.
    -
    -encode_error({error, Type, By, Reason, Text},
    -	     _xmlns_attrs) ->
    -    _els = lists:reverse('encode_error_$text'(Text,
    -					      'encode_error_$reason'(Reason,
    -								     []))),
    -    _attrs = encode_error_attr_by(By,
    -				  encode_error_attr_type(Type, _xmlns_attrs)),
    -    {xmlel, <<"error">>, _attrs, _els}.
    -
    -'encode_error_$text'(undefined, _acc) -> _acc;
    -'encode_error_$text'(Text, _acc) ->
    -    [encode_error_text(Text,
    -		       [{<<"xmlns">>,
    -			 <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc].
    -
    -'encode_error_$reason'(undefined, _acc) -> _acc;
    -'encode_error_$reason'('bad-request' = Reason, _acc) ->
    -    [encode_error_bad_request(Reason,
    -			      [{<<"xmlns">>,
    -				<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'(conflict = Reason, _acc) ->
    -    [encode_error_conflict(Reason,
    -			   [{<<"xmlns">>,
    -			     <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('feature-not-implemented' =
    -			   Reason,
    -		       _acc) ->
    -    [encode_error_feature_not_implemented(Reason,
    -					  [{<<"xmlns">>,
    -					    <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'(forbidden = Reason, _acc) ->
    -    [encode_error_forbidden(Reason,
    -			    [{<<"xmlns">>,
    -			      <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'({gone, _} = Reason, _acc) ->
    -    [encode_error_gone(Reason,
    -		       [{<<"xmlns">>,
    -			 <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('internal-server-error' = Reason,
    -		       _acc) ->
    -    [encode_error_internal_server_error(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('item-not-found' = Reason,
    -		       _acc) ->
    -    [encode_error_item_not_found(Reason,
    -				 [{<<"xmlns">>,
    -				   <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('jid-malformed' = Reason,
    -		       _acc) ->
    -    [encode_error_jid_malformed(Reason,
    -				[{<<"xmlns">>,
    -				  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('not-acceptable' = Reason,
    -		       _acc) ->
    -    [encode_error_not_acceptable(Reason,
    -				 [{<<"xmlns">>,
    -				   <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('not-allowed' = Reason, _acc) ->
    -    [encode_error_not_allowed(Reason,
    -			      [{<<"xmlns">>,
    -				<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('not-authorized' = Reason,
    -		       _acc) ->
    -    [encode_error_not_authorized(Reason,
    -				 [{<<"xmlns">>,
    -				   <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('policy-violation' = Reason,
    -		       _acc) ->
    -    [encode_error_policy_violation(Reason,
    -				   [{<<"xmlns">>,
    -				     <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('recipient-unavailable' = Reason,
    -		       _acc) ->
    -    [encode_error_recipient_unavailable(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'({redirect, _} = Reason, _acc) ->
    -    [encode_error_redirect(Reason,
    -			   [{<<"xmlns">>,
    -			     <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('registration-required' = Reason,
    -		       _acc) ->
    -    [encode_error_registration_required(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('remote-server-not-found' =
    -			   Reason,
    -		       _acc) ->
    -    [encode_error_remote_server_not_found(Reason,
    -					  [{<<"xmlns">>,
    -					    <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('remote-server-timeout' = Reason,
    -		       _acc) ->
    -    [encode_error_remote_server_timeout(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('resource-constraint' = Reason,
    -		       _acc) ->
    -    [encode_error_resource_constraint(Reason,
    -				      [{<<"xmlns">>,
    -					<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('service-unavailable' = Reason,
    -		       _acc) ->
    -    [encode_error_service_unavailable(Reason,
    -				      [{<<"xmlns">>,
    -					<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('subscription-required' = Reason,
    -		       _acc) ->
    -    [encode_error_subscription_required(Reason,
    -					[{<<"xmlns">>,
    -					  <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('undefined-condition' = Reason,
    -		       _acc) ->
    -    [encode_error_undefined_condition(Reason,
    -				      [{<<"xmlns">>,
    -					<<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc];
    -'encode_error_$reason'('unexpected-request' = Reason,
    -		       _acc) ->
    -    [encode_error_unexpected_request(Reason,
    -				     [{<<"xmlns">>,
    -				       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
    -     | _acc].
    -
    -decode_error_attr_type(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"type">>, <<"error">>, __TopXMLNS}});
    -decode_error_attr_type(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val,
    -			[auth, cancel, continue, modify, wait])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"type">>, <<"error">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_error_attr_type(_val, _acc) ->
    -    [{<<"type">>, enc_enum(_val)} | _acc].
    -
    -decode_error_attr_by(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_error_attr_by(__TopXMLNS, _val) -> _val.
    -
    -encode_error_attr_by(undefined, _acc) -> _acc;
    -encode_error_attr_by(_val, _acc) ->
    -    [{<<"by">>, _val} | _acc].
    -
    -decode_error_text(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"text">>, _attrs, _els}) ->
    -    Data = decode_error_text_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <<>>),
    -    Lang = decode_error_text_attrs(__TopXMLNS, _attrs,
    -				   undefined),
    -    {text, Lang, Data}.
    -
    -decode_error_text_els(__TopXMLNS, __IgnoreEls, [],
    -		      Data) ->
    -    decode_error_text_cdata(__TopXMLNS, Data);
    -decode_error_text_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Data) ->
    -    decode_error_text_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_error_text_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Data) ->
    -    decode_error_text_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Data).
    -
    -decode_error_text_attrs(__TopXMLNS,
    -			[{<<"xml:lang">>, _val} | _attrs], _Lang) ->
    -    decode_error_text_attrs(__TopXMLNS, _attrs, _val);
    -decode_error_text_attrs(__TopXMLNS, [_ | _attrs],
    -			Lang) ->
    -    decode_error_text_attrs(__TopXMLNS, _attrs, Lang);
    -decode_error_text_attrs(__TopXMLNS, [], Lang) ->
    -    'decode_error_text_attr_xml:lang'(__TopXMLNS, Lang).
    -
    -encode_error_text({text, Lang, Data}, _xmlns_attrs) ->
    -    _els = encode_error_text_cdata(Data, []),
    -    _attrs = 'encode_error_text_attr_xml:lang'(Lang,
    -					       _xmlns_attrs),
    -    {xmlel, <<"text">>, _attrs, _els}.
    -
    -'decode_error_text_attr_xml:lang'(__TopXMLNS,
    -				  undefined) ->
    -    undefined;
    -'decode_error_text_attr_xml:lang'(__TopXMLNS, _val) ->
    -    _val.
    -
    -'encode_error_text_attr_xml:lang'(undefined, _acc) ->
    -    _acc;
    -'encode_error_text_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_error_text_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_error_text_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_error_text_cdata(undefined, _acc) -> _acc;
    -encode_error_text_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_error_unexpected_request(__TopXMLNS, __IgnoreEls,
    -				{xmlel, <<"unexpected-request">>, _attrs,
    -				 _els}) ->
    -    'unexpected-request'.
    -
    -encode_error_unexpected_request('unexpected-request',
    -				_xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"unexpected-request">>, _attrs, _els}.
    -
    -decode_error_undefined_condition(__TopXMLNS,
    -				 __IgnoreEls,
    -				 {xmlel, <<"undefined-condition">>, _attrs,
    -				  _els}) ->
    -    'undefined-condition'.
    -
    -encode_error_undefined_condition('undefined-condition',
    -				 _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"undefined-condition">>, _attrs, _els}.
    -
    -decode_error_subscription_required(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"subscription-required">>, _attrs,
    -				    _els}) ->
    -    'subscription-required'.
    -
    -encode_error_subscription_required('subscription-required',
    -				   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"subscription-required">>, _attrs, _els}.
    -
    -decode_error_service_unavailable(__TopXMLNS,
    -				 __IgnoreEls,
    -				 {xmlel, <<"service-unavailable">>, _attrs,
    -				  _els}) ->
    -    'service-unavailable'.
    -
    -encode_error_service_unavailable('service-unavailable',
    -				 _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"service-unavailable">>, _attrs, _els}.
    -
    -decode_error_resource_constraint(__TopXMLNS,
    -				 __IgnoreEls,
    -				 {xmlel, <<"resource-constraint">>, _attrs,
    -				  _els}) ->
    -    'resource-constraint'.
    -
    -encode_error_resource_constraint('resource-constraint',
    -				 _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"resource-constraint">>, _attrs, _els}.
    -
    -decode_error_remote_server_timeout(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"remote-server-timeout">>, _attrs,
    -				    _els}) ->
    -    'remote-server-timeout'.
    -
    -encode_error_remote_server_timeout('remote-server-timeout',
    -				   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"remote-server-timeout">>, _attrs, _els}.
    -
    -decode_error_remote_server_not_found(__TopXMLNS,
    -				     __IgnoreEls,
    -				     {xmlel, <<"remote-server-not-found">>,
    -				      _attrs, _els}) ->
    -    'remote-server-not-found'.
    -
    -encode_error_remote_server_not_found('remote-server-not-found',
    -				     _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"remote-server-not-found">>, _attrs, _els}.
    -
    -decode_error_registration_required(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"registration-required">>, _attrs,
    -				    _els}) ->
    -    'registration-required'.
    -
    -encode_error_registration_required('registration-required',
    -				   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"registration-required">>, _attrs, _els}.
    -
    -decode_error_redirect(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"redirect">>, _attrs, _els}) ->
    -    Uri = decode_error_redirect_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    {redirect, Uri}.
    -
    -decode_error_redirect_els(__TopXMLNS, __IgnoreEls, [],
    -			  Uri) ->
    -    decode_error_redirect_cdata(__TopXMLNS, Uri);
    -decode_error_redirect_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Uri) ->
    -    decode_error_redirect_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_error_redirect_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Uri) ->
    -    decode_error_redirect_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Uri).
    -
    -encode_error_redirect({redirect, Uri}, _xmlns_attrs) ->
    -    _els = encode_error_redirect_cdata(Uri, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"redirect">>, _attrs, _els}.
    -
    -decode_error_redirect_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_error_redirect_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_error_redirect_cdata(undefined, _acc) -> _acc;
    -encode_error_redirect_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_error_recipient_unavailable(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"recipient-unavailable">>, _attrs,
    -				    _els}) ->
    -    'recipient-unavailable'.
    -
    -encode_error_recipient_unavailable('recipient-unavailable',
    -				   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"recipient-unavailable">>, _attrs, _els}.
    -
    -decode_error_policy_violation(__TopXMLNS, __IgnoreEls,
    -			      {xmlel, <<"policy-violation">>, _attrs, _els}) ->
    -    'policy-violation'.
    -
    -encode_error_policy_violation('policy-violation',
    -			      _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"policy-violation">>, _attrs, _els}.
    -
    -decode_error_not_authorized(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"not-authorized">>, _attrs, _els}) ->
    -    'not-authorized'.
    -
    -encode_error_not_authorized('not-authorized',
    -			    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"not-authorized">>, _attrs, _els}.
    -
    -decode_error_not_allowed(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"not-allowed">>, _attrs, _els}) ->
    -    'not-allowed'.
    -
    -encode_error_not_allowed('not-allowed', _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"not-allowed">>, _attrs, _els}.
    -
    -decode_error_not_acceptable(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"not-acceptable">>, _attrs, _els}) ->
    -    'not-acceptable'.
    -
    -encode_error_not_acceptable('not-acceptable',
    -			    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"not-acceptable">>, _attrs, _els}.
    -
    -decode_error_jid_malformed(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"jid-malformed">>, _attrs, _els}) ->
    -    'jid-malformed'.
    -
    -encode_error_jid_malformed('jid-malformed',
    -			   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"jid-malformed">>, _attrs, _els}.
    -
    -decode_error_item_not_found(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"item-not-found">>, _attrs, _els}) ->
    -    'item-not-found'.
    -
    -encode_error_item_not_found('item-not-found',
    -			    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"item-not-found">>, _attrs, _els}.
    -
    -decode_error_internal_server_error(__TopXMLNS,
    -				   __IgnoreEls,
    -				   {xmlel, <<"internal-server-error">>, _attrs,
    -				    _els}) ->
    -    'internal-server-error'.
    -
    -encode_error_internal_server_error('internal-server-error',
    -				   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"internal-server-error">>, _attrs, _els}.
    -
    -decode_error_gone(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"gone">>, _attrs, _els}) ->
    -    Uri = decode_error_gone_els(__TopXMLNS, __IgnoreEls,
    -				_els, <<>>),
    -    {gone, Uri}.
    -
    -decode_error_gone_els(__TopXMLNS, __IgnoreEls, [],
    -		      Uri) ->
    -    decode_error_gone_cdata(__TopXMLNS, Uri);
    -decode_error_gone_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Uri) ->
    -    decode_error_gone_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_error_gone_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Uri) ->
    -    decode_error_gone_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Uri).
    -
    -encode_error_gone({gone, Uri}, _xmlns_attrs) ->
    -    _els = encode_error_gone_cdata(Uri, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"gone">>, _attrs, _els}.
    -
    -decode_error_gone_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_error_gone_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_error_gone_cdata(undefined, _acc) -> _acc;
    -encode_error_gone_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_error_forbidden(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"forbidden">>, _attrs, _els}) ->
    -    forbidden.
    -
    -encode_error_forbidden(forbidden, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"forbidden">>, _attrs, _els}.
    -
    -decode_error_feature_not_implemented(__TopXMLNS,
    -				     __IgnoreEls,
    -				     {xmlel, <<"feature-not-implemented">>,
    -				      _attrs, _els}) ->
    -    'feature-not-implemented'.
    -
    -encode_error_feature_not_implemented('feature-not-implemented',
    -				     _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"feature-not-implemented">>, _attrs, _els}.
    -
    -decode_error_conflict(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"conflict">>, _attrs, _els}) ->
    -    conflict.
    -
    -encode_error_conflict(conflict, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"conflict">>, _attrs, _els}.
    -
    -decode_error_bad_request(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"bad-request">>, _attrs, _els}) ->
    -    'bad-request'.
    -
    -encode_error_bad_request('bad-request', _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"bad-request">>, _attrs, _els}.
    -
    -decode_presence(__TopXMLNS, __IgnoreEls,
    -		{xmlel, <<"presence">>, _attrs, _els}) ->
    -    {Error, Status, Show, Priority, __Els} =
    -	decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			    undefined, [], undefined, undefined, []),
    -    {Id, Type, From, To, Lang} =
    -	decode_presence_attrs(__TopXMLNS, _attrs, undefined,
    -			      undefined, undefined, undefined, undefined),
    -    {presence, Id, Type, Lang, From, To, Show, Status,
    -     Priority, Error, __Els}.
    -
    -decode_presence_els(__TopXMLNS, __IgnoreEls, [], Error,
    -		    Status, Show, Priority, __Els) ->
    -    {Error, lists:reverse(Status), Show, Priority,
    -     lists:reverse(__Els)};
    -decode_presence_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"error">>, _attrs, _} = _el | _els], Error,
    -		    Status, Show, Priority, __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			       decode_error(__TopXMLNS, __IgnoreEls, _el),
    -			       Status, Show, Priority, __Els);
    -       true ->
    -	   decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Error, Status, Show, Priority, __Els)
    -    end;
    -decode_presence_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"show">>, _attrs, _} = _el | _els], Error,
    -		    Status, Show, Priority, __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Error, Status,
    -			       decode_presence_show(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			       Priority, __Els);
    -       true ->
    -	   decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Error, Status, Show, Priority, __Els)
    -    end;
    -decode_presence_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"status">>, _attrs, _} = _el | _els], Error,
    -		    Status, Show, Priority, __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Error,
    -			       [decode_presence_status(__TopXMLNS, __IgnoreEls,
    -						       _el)
    -				| Status],
    -			       Show, Priority, __Els);
    -       true ->
    -	   decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Error, Status, Show, Priority, __Els)
    -    end;
    -decode_presence_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, <<"priority">>, _attrs, _} = _el | _els],
    -		    Error, Status, Show, Priority, __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Error, Status, Show,
    -			       decode_presence_priority(__TopXMLNS, __IgnoreEls,
    -							_el),
    -			       __Els);
    -       true ->
    -	   decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Error, Status, Show, Priority, __Els)
    -    end;
    -decode_presence_els(__TopXMLNS, __IgnoreEls,
    -		    [{xmlel, _, _, _} = _el | _els], Error, Status, Show,
    -		    Priority, __Els) ->
    -    if __IgnoreEls ->
    -	   decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			       Error, Status, Show, Priority, [_el | __Els]);
    -       true ->
    -	   case is_known_tag(_el) of
    -	     true ->
    -		 decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Error, Status, Show, Priority,
    -				     [decode(_el) | __Els]);
    -	     false ->
    -		 decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -				     Error, Status, Show, Priority, __Els)
    -	   end
    -    end;
    -decode_presence_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		    Error, Status, Show, Priority, __Els) ->
    -    decode_presence_els(__TopXMLNS, __IgnoreEls, _els,
    -			Error, Status, Show, Priority, __Els).
    -
    -decode_presence_attrs(__TopXMLNS,
    -		      [{<<"id">>, _val} | _attrs], _Id, Type, From, To,
    -		      Lang) ->
    -    decode_presence_attrs(__TopXMLNS, _attrs, _val, Type,
    -			  From, To, Lang);
    -decode_presence_attrs(__TopXMLNS,
    -		      [{<<"type">>, _val} | _attrs], Id, _Type, From, To,
    -		      Lang) ->
    -    decode_presence_attrs(__TopXMLNS, _attrs, Id, _val,
    -			  From, To, Lang);
    -decode_presence_attrs(__TopXMLNS,
    -		      [{<<"from">>, _val} | _attrs], Id, Type, _From, To,
    -		      Lang) ->
    -    decode_presence_attrs(__TopXMLNS, _attrs, Id, Type,
    -			  _val, To, Lang);
    -decode_presence_attrs(__TopXMLNS,
    -		      [{<<"to">>, _val} | _attrs], Id, Type, From, _To,
    -		      Lang) ->
    -    decode_presence_attrs(__TopXMLNS, _attrs, Id, Type,
    -			  From, _val, Lang);
    -decode_presence_attrs(__TopXMLNS,
    -		      [{<<"xml:lang">>, _val} | _attrs], Id, Type, From, To,
    -		      _Lang) ->
    -    decode_presence_attrs(__TopXMLNS, _attrs, Id, Type,
    -			  From, To, _val);
    -decode_presence_attrs(__TopXMLNS, [_ | _attrs], Id,
    -		      Type, From, To, Lang) ->
    -    decode_presence_attrs(__TopXMLNS, _attrs, Id, Type,
    -			  From, To, Lang);
    -decode_presence_attrs(__TopXMLNS, [], Id, Type, From,
    -		      To, Lang) ->
    -    {decode_presence_attr_id(__TopXMLNS, Id),
    -     decode_presence_attr_type(__TopXMLNS, Type),
    -     decode_presence_attr_from(__TopXMLNS, From),
    -     decode_presence_attr_to(__TopXMLNS, To),
    -     'decode_presence_attr_xml:lang'(__TopXMLNS, Lang)}.
    -
    -encode_presence({presence, Id, Type, Lang, From, To,
    -		 Show, Status, Priority, Error, __Els},
    -		_xmlns_attrs) ->
    -    _els = [encode(_el) || _el <- __Els] ++
    -	     lists:reverse('encode_presence_$error'(Error,
    -						    'encode_presence_$status'(Status,
    -									      'encode_presence_$show'(Show,
    -												      'encode_presence_$priority'(Priority,
    -																  []))))),
    -    _attrs = 'encode_presence_attr_xml:lang'(Lang,
    -					     encode_presence_attr_to(To,
    -								     encode_presence_attr_from(From,
    -											       encode_presence_attr_type(Type,
    -															 encode_presence_attr_id(Id,
    -																		 _xmlns_attrs))))),
    -    {xmlel, <<"presence">>, _attrs, _els}.
    -
    -'encode_presence_$error'(undefined, _acc) -> _acc;
    -'encode_presence_$error'(Error, _acc) ->
    -    [encode_error(Error, []) | _acc].
    -
    -'encode_presence_$status'([], _acc) -> _acc;
    -'encode_presence_$status'([Status | _els], _acc) ->
    -    'encode_presence_$status'(_els,
    -			      [encode_presence_status(Status, []) | _acc]).
    -
    -'encode_presence_$show'(undefined, _acc) -> _acc;
    -'encode_presence_$show'(Show, _acc) ->
    -    [encode_presence_show(Show, []) | _acc].
    -
    -'encode_presence_$priority'(undefined, _acc) -> _acc;
    -'encode_presence_$priority'(Priority, _acc) ->
    -    [encode_presence_priority(Priority, []) | _acc].
    -
    -decode_presence_attr_id(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_presence_attr_id(__TopXMLNS, _val) -> _val.
    -
    -encode_presence_attr_id(undefined, _acc) -> _acc;
    -encode_presence_attr_id(_val, _acc) ->
    -    [{<<"id">>, _val} | _acc].
    -
    -decode_presence_attr_type(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_presence_attr_type(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val,
    -			[unavailable, subscribe, subscribed, unsubscribe,
    -			 unsubscribed, probe, error])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"type">>, <<"presence">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_presence_attr_type(undefined, _acc) -> _acc;
    -encode_presence_attr_type(_val, _acc) ->
    -    [{<<"type">>, enc_enum(_val)} | _acc].
    -
    -decode_presence_attr_from(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_presence_attr_from(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"from">>, <<"presence">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_presence_attr_from(undefined, _acc) -> _acc;
    -encode_presence_attr_from(_val, _acc) ->
    -    [{<<"from">>, enc_jid(_val)} | _acc].
    -
    -decode_presence_attr_to(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_presence_attr_to(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"to">>, <<"presence">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_presence_attr_to(undefined, _acc) -> _acc;
    -encode_presence_attr_to(_val, _acc) ->
    -    [{<<"to">>, enc_jid(_val)} | _acc].
    -
    -'decode_presence_attr_xml:lang'(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -'decode_presence_attr_xml:lang'(__TopXMLNS, _val) ->
    -    _val.
    -
    -'encode_presence_attr_xml:lang'(undefined, _acc) ->
    -    _acc;
    -'encode_presence_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_presence_priority(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"priority">>, _attrs, _els}) ->
    -    Cdata = decode_presence_priority_els(__TopXMLNS,
    -					 __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_presence_priority_els(__TopXMLNS, __IgnoreEls,
    -			     [], Cdata) ->
    -    decode_presence_priority_cdata(__TopXMLNS, Cdata);
    -decode_presence_priority_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_presence_priority_els(__TopXMLNS, __IgnoreEls,
    -				 _els, <>);
    -decode_presence_priority_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], Cdata) ->
    -    decode_presence_priority_els(__TopXMLNS, __IgnoreEls,
    -				 _els, Cdata).
    -
    -encode_presence_priority(Cdata, _xmlns_attrs) ->
    -    _els = encode_presence_priority_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"priority">>, _attrs, _els}.
    -
    -decode_presence_priority_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_presence_priority_cdata(__TopXMLNS, _val) ->
    -    case catch dec_int(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"priority">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_presence_priority_cdata(undefined, _acc) -> _acc;
    -encode_presence_priority_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_int(_val)} | _acc].
    -
    -decode_presence_status(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"status">>, _attrs, _els}) ->
    -    Data = decode_presence_status_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Lang = decode_presence_status_attrs(__TopXMLNS, _attrs,
    -					undefined),
    -    {text, Lang, Data}.
    -
    -decode_presence_status_els(__TopXMLNS, __IgnoreEls, [],
    -			   Data) ->
    -    decode_presence_status_cdata(__TopXMLNS, Data);
    -decode_presence_status_els(__TopXMLNS, __IgnoreEls,
    -			   [{xmlcdata, _data} | _els], Data) ->
    -    decode_presence_status_els(__TopXMLNS, __IgnoreEls,
    -			       _els, <>);
    -decode_presence_status_els(__TopXMLNS, __IgnoreEls,
    -			   [_ | _els], Data) ->
    -    decode_presence_status_els(__TopXMLNS, __IgnoreEls,
    -			       _els, Data).
    -
    -decode_presence_status_attrs(__TopXMLNS,
    -			     [{<<"xml:lang">>, _val} | _attrs], _Lang) ->
    -    decode_presence_status_attrs(__TopXMLNS, _attrs, _val);
    -decode_presence_status_attrs(__TopXMLNS, [_ | _attrs],
    -			     Lang) ->
    -    decode_presence_status_attrs(__TopXMLNS, _attrs, Lang);
    -decode_presence_status_attrs(__TopXMLNS, [], Lang) ->
    -    'decode_presence_status_attr_xml:lang'(__TopXMLNS,
    -					   Lang).
    -
    -encode_presence_status({text, Lang, Data},
    -		       _xmlns_attrs) ->
    -    _els = encode_presence_status_cdata(Data, []),
    -    _attrs = 'encode_presence_status_attr_xml:lang'(Lang,
    -						    _xmlns_attrs),
    -    {xmlel, <<"status">>, _attrs, _els}.
    -
    -'decode_presence_status_attr_xml:lang'(__TopXMLNS,
    -				       undefined) ->
    -    undefined;
    -'decode_presence_status_attr_xml:lang'(__TopXMLNS,
    -				       _val) ->
    -    _val.
    -
    -'encode_presence_status_attr_xml:lang'(undefined,
    -				       _acc) ->
    -    _acc;
    -'encode_presence_status_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_presence_status_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_presence_status_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_presence_status_cdata(undefined, _acc) -> _acc;
    -encode_presence_status_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_presence_show(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"show">>, _attrs, _els}) ->
    -    Cdata = decode_presence_show_els(__TopXMLNS,
    -				     __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_presence_show_els(__TopXMLNS, __IgnoreEls, [],
    -			 Cdata) ->
    -    decode_presence_show_cdata(__TopXMLNS, Cdata);
    -decode_presence_show_els(__TopXMLNS, __IgnoreEls,
    -			 [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_presence_show_els(__TopXMLNS, __IgnoreEls, _els,
    -			     <>);
    -decode_presence_show_els(__TopXMLNS, __IgnoreEls,
    -			 [_ | _els], Cdata) ->
    -    decode_presence_show_els(__TopXMLNS, __IgnoreEls, _els,
    -			     Cdata).
    -
    -encode_presence_show(Cdata, _xmlns_attrs) ->
    -    _els = encode_presence_show_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"show">>, _attrs, _els}.
    -
    -decode_presence_show_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_presence_show_cdata(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val, [away, chat, dnd, xa]) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_cdata_value, <<>>, <<"show">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_presence_show_cdata(undefined, _acc) -> _acc;
    -encode_presence_show_cdata(_val, _acc) ->
    -    [{xmlcdata, enc_enum(_val)} | _acc].
    -
    -decode_message(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"message">>, _attrs, _els}) ->
    -    {Error, Thread, Subject, Body, __Els} =
    -	decode_message_els(__TopXMLNS, __IgnoreEls, _els,
    -			   undefined, undefined, [], [], []),
    -    {Id, Type, From, To, Lang} =
    -	decode_message_attrs(__TopXMLNS, _attrs, undefined,
    -			     undefined, undefined, undefined, undefined),
    -    {message, Id, Type, Lang, From, To, Subject, Body,
    -     Thread, Error, __Els}.
    -
    -decode_message_els(__TopXMLNS, __IgnoreEls, [], Error,
    -		   Thread, Subject, Body, __Els) ->
    -    {Error, Thread, lists:reverse(Subject),
    -     lists:reverse(Body), lists:reverse(__Els)};
    -decode_message_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"error">>, _attrs, _} = _el | _els], Error,
    -		   Thread, Subject, Body, __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_message_els(__TopXMLNS, __IgnoreEls, _els,
    -			      decode_error(__TopXMLNS, __IgnoreEls, _el),
    -			      Thread, Subject, Body, __Els);
    -       true ->
    -	   decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			      Thread, Subject, Body, __Els)
    -    end;
    -decode_message_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"subject">>, _attrs, _} = _el | _els], Error,
    -		   Thread, Subject, Body, __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			      Thread,
    -			      [decode_message_subject(__TopXMLNS, __IgnoreEls,
    -						      _el)
    -			       | Subject],
    -			      Body, __Els);
    -       true ->
    -	   decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			      Thread, Subject, Body, __Els)
    -    end;
    -decode_message_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"thread">>, _attrs, _} = _el | _els], Error,
    -		   Thread, Subject, Body, __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			      decode_message_thread(__TopXMLNS, __IgnoreEls,
    -						    _el),
    -			      Subject, Body, __Els);
    -       true ->
    -	   decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			      Thread, Subject, Body, __Els)
    -    end;
    -decode_message_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"body">>, _attrs, _} = _el | _els], Error,
    -		   Thread, Subject, Body, __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			      Thread, Subject,
    -			      [decode_message_body(__TopXMLNS, __IgnoreEls, _el)
    -			       | Body],
    -			      __Els);
    -       true ->
    -	   decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			      Thread, Subject, Body, __Els)
    -    end;
    -decode_message_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, _, _, _} = _el | _els], Error, Thread, Subject,
    -		   Body, __Els) ->
    -    if __IgnoreEls ->
    -	   decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			      Thread, Subject, Body, [_el | __Els]);
    -       true ->
    -	   case is_known_tag(_el) of
    -	     true ->
    -		 decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -				    Thread, Subject, Body,
    -				    [decode(_el) | __Els]);
    -	     false ->
    -		 decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -				    Thread, Subject, Body, __Els)
    -	   end
    -    end;
    -decode_message_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Error, Thread, Subject, Body, __Els) ->
    -    decode_message_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -		       Thread, Subject, Body, __Els).
    -
    -decode_message_attrs(__TopXMLNS,
    -		     [{<<"id">>, _val} | _attrs], _Id, Type, From, To,
    -		     Lang) ->
    -    decode_message_attrs(__TopXMLNS, _attrs, _val, Type,
    -			 From, To, Lang);
    -decode_message_attrs(__TopXMLNS,
    -		     [{<<"type">>, _val} | _attrs], Id, _Type, From, To,
    -		     Lang) ->
    -    decode_message_attrs(__TopXMLNS, _attrs, Id, _val, From,
    -			 To, Lang);
    -decode_message_attrs(__TopXMLNS,
    -		     [{<<"from">>, _val} | _attrs], Id, Type, _From, To,
    -		     Lang) ->
    -    decode_message_attrs(__TopXMLNS, _attrs, Id, Type, _val,
    -			 To, Lang);
    -decode_message_attrs(__TopXMLNS,
    -		     [{<<"to">>, _val} | _attrs], Id, Type, From, _To,
    -		     Lang) ->
    -    decode_message_attrs(__TopXMLNS, _attrs, Id, Type, From,
    -			 _val, Lang);
    -decode_message_attrs(__TopXMLNS,
    -		     [{<<"xml:lang">>, _val} | _attrs], Id, Type, From, To,
    -		     _Lang) ->
    -    decode_message_attrs(__TopXMLNS, _attrs, Id, Type, From,
    -			 To, _val);
    -decode_message_attrs(__TopXMLNS, [_ | _attrs], Id, Type,
    -		     From, To, Lang) ->
    -    decode_message_attrs(__TopXMLNS, _attrs, Id, Type, From,
    -			 To, Lang);
    -decode_message_attrs(__TopXMLNS, [], Id, Type, From, To,
    -		     Lang) ->
    -    {decode_message_attr_id(__TopXMLNS, Id),
    -     decode_message_attr_type(__TopXMLNS, Type),
    -     decode_message_attr_from(__TopXMLNS, From),
    -     decode_message_attr_to(__TopXMLNS, To),
    -     'decode_message_attr_xml:lang'(__TopXMLNS, Lang)}.
    -
    -encode_message({message, Id, Type, Lang, From, To,
    -		Subject, Body, Thread, Error, __Els},
    -	       _xmlns_attrs) ->
    -    _els = [encode(_el) || _el <- __Els] ++
    -	     lists:reverse('encode_message_$error'(Error,
    -						   'encode_message_$thread'(Thread,
    -									    'encode_message_$subject'(Subject,
    -												      'encode_message_$body'(Body,
    -															     []))))),
    -    _attrs = 'encode_message_attr_xml:lang'(Lang,
    -					    encode_message_attr_to(To,
    -								   encode_message_attr_from(From,
    -											    encode_message_attr_type(Type,
    -														     encode_message_attr_id(Id,
    -																	    _xmlns_attrs))))),
    -    {xmlel, <<"message">>, _attrs, _els}.
    -
    -'encode_message_$error'(undefined, _acc) -> _acc;
    -'encode_message_$error'(Error, _acc) ->
    -    [encode_error(Error, []) | _acc].
    -
    -'encode_message_$thread'(undefined, _acc) -> _acc;
    -'encode_message_$thread'(Thread, _acc) ->
    -    [encode_message_thread(Thread, []) | _acc].
    -
    -'encode_message_$subject'([], _acc) -> _acc;
    -'encode_message_$subject'([Subject | _els], _acc) ->
    -    'encode_message_$subject'(_els,
    -			      [encode_message_subject(Subject, []) | _acc]).
    -
    -'encode_message_$body'([], _acc) -> _acc;
    -'encode_message_$body'([Body | _els], _acc) ->
    -    'encode_message_$body'(_els,
    -			   [encode_message_body(Body, []) | _acc]).
    -
    -decode_message_attr_id(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_message_attr_id(__TopXMLNS, _val) -> _val.
    -
    -encode_message_attr_id(undefined, _acc) -> _acc;
    -encode_message_attr_id(_val, _acc) ->
    -    [{<<"id">>, _val} | _acc].
    -
    -decode_message_attr_type(__TopXMLNS, undefined) ->
    -    normal;
    -decode_message_attr_type(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val,
    -			[chat, normal, groupchat, headline, error])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"type">>, <<"message">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_message_attr_type(normal, _acc) -> _acc;
    -encode_message_attr_type(_val, _acc) ->
    -    [{<<"type">>, enc_enum(_val)} | _acc].
    -
    -decode_message_attr_from(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_message_attr_from(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"from">>, <<"message">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_message_attr_from(undefined, _acc) -> _acc;
    -encode_message_attr_from(_val, _acc) ->
    -    [{<<"from">>, enc_jid(_val)} | _acc].
    -
    -decode_message_attr_to(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_message_attr_to(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"to">>, <<"message">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_message_attr_to(undefined, _acc) -> _acc;
    -encode_message_attr_to(_val, _acc) ->
    -    [{<<"to">>, enc_jid(_val)} | _acc].
    -
    -'decode_message_attr_xml:lang'(__TopXMLNS, undefined) ->
    -    undefined;
    -'decode_message_attr_xml:lang'(__TopXMLNS, _val) ->
    -    _val.
    -
    -'encode_message_attr_xml:lang'(undefined, _acc) -> _acc;
    -'encode_message_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_message_thread(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"thread">>, _attrs, _els}) ->
    -    Cdata = decode_message_thread_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_message_thread_els(__TopXMLNS, __IgnoreEls, [],
    -			  Cdata) ->
    -    decode_message_thread_cdata(__TopXMLNS, Cdata);
    -decode_message_thread_els(__TopXMLNS, __IgnoreEls,
    -			  [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_message_thread_els(__TopXMLNS, __IgnoreEls, _els,
    -			      <>);
    -decode_message_thread_els(__TopXMLNS, __IgnoreEls,
    -			  [_ | _els], Cdata) ->
    -    decode_message_thread_els(__TopXMLNS, __IgnoreEls, _els,
    -			      Cdata).
    -
    -encode_message_thread(Cdata, _xmlns_attrs) ->
    -    _els = encode_message_thread_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"thread">>, _attrs, _els}.
    -
    -decode_message_thread_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_message_thread_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_message_thread_cdata(undefined, _acc) -> _acc;
    -encode_message_thread_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_message_body(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"body">>, _attrs, _els}) ->
    -    Data = decode_message_body_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <<>>),
    -    Lang = decode_message_body_attrs(__TopXMLNS, _attrs,
    -				     undefined),
    -    {text, Lang, Data}.
    -
    -decode_message_body_els(__TopXMLNS, __IgnoreEls, [],
    -			Data) ->
    -    decode_message_body_cdata(__TopXMLNS, Data);
    -decode_message_body_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Data) ->
    -    decode_message_body_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_message_body_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Data) ->
    -    decode_message_body_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Data).
    -
    -decode_message_body_attrs(__TopXMLNS,
    -			  [{<<"xml:lang">>, _val} | _attrs], _Lang) ->
    -    decode_message_body_attrs(__TopXMLNS, _attrs, _val);
    -decode_message_body_attrs(__TopXMLNS, [_ | _attrs],
    -			  Lang) ->
    -    decode_message_body_attrs(__TopXMLNS, _attrs, Lang);
    -decode_message_body_attrs(__TopXMLNS, [], Lang) ->
    -    'decode_message_body_attr_xml:lang'(__TopXMLNS, Lang).
    -
    -encode_message_body({text, Lang, Data}, _xmlns_attrs) ->
    -    _els = encode_message_body_cdata(Data, []),
    -    _attrs = 'encode_message_body_attr_xml:lang'(Lang,
    -						 _xmlns_attrs),
    -    {xmlel, <<"body">>, _attrs, _els}.
    -
    -'decode_message_body_attr_xml:lang'(__TopXMLNS,
    -				    undefined) ->
    -    undefined;
    -'decode_message_body_attr_xml:lang'(__TopXMLNS, _val) ->
    -    _val.
    -
    -'encode_message_body_attr_xml:lang'(undefined, _acc) ->
    -    _acc;
    -'encode_message_body_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_message_body_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_message_body_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_message_body_cdata(undefined, _acc) -> _acc;
    -encode_message_body_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_message_subject(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"subject">>, _attrs, _els}) ->
    -    Data = decode_message_subject_els(__TopXMLNS,
    -				      __IgnoreEls, _els, <<>>),
    -    Lang = decode_message_subject_attrs(__TopXMLNS, _attrs,
    -					undefined),
    -    {text, Lang, Data}.
    -
    -decode_message_subject_els(__TopXMLNS, __IgnoreEls, [],
    -			   Data) ->
    -    decode_message_subject_cdata(__TopXMLNS, Data);
    -decode_message_subject_els(__TopXMLNS, __IgnoreEls,
    -			   [{xmlcdata, _data} | _els], Data) ->
    -    decode_message_subject_els(__TopXMLNS, __IgnoreEls,
    -			       _els, <>);
    -decode_message_subject_els(__TopXMLNS, __IgnoreEls,
    -			   [_ | _els], Data) ->
    -    decode_message_subject_els(__TopXMLNS, __IgnoreEls,
    -			       _els, Data).
    -
    -decode_message_subject_attrs(__TopXMLNS,
    -			     [{<<"xml:lang">>, _val} | _attrs], _Lang) ->
    -    decode_message_subject_attrs(__TopXMLNS, _attrs, _val);
    -decode_message_subject_attrs(__TopXMLNS, [_ | _attrs],
    -			     Lang) ->
    -    decode_message_subject_attrs(__TopXMLNS, _attrs, Lang);
    -decode_message_subject_attrs(__TopXMLNS, [], Lang) ->
    -    'decode_message_subject_attr_xml:lang'(__TopXMLNS,
    -					   Lang).
    -
    -encode_message_subject({text, Lang, Data},
    -		       _xmlns_attrs) ->
    -    _els = encode_message_subject_cdata(Data, []),
    -    _attrs = 'encode_message_subject_attr_xml:lang'(Lang,
    -						    _xmlns_attrs),
    -    {xmlel, <<"subject">>, _attrs, _els}.
    -
    -'decode_message_subject_attr_xml:lang'(__TopXMLNS,
    -				       undefined) ->
    -    undefined;
    -'decode_message_subject_attr_xml:lang'(__TopXMLNS,
    -				       _val) ->
    -    _val.
    -
    -'encode_message_subject_attr_xml:lang'(undefined,
    -				       _acc) ->
    -    _acc;
    -'encode_message_subject_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_message_subject_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_message_subject_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_message_subject_cdata(undefined, _acc) -> _acc;
    -encode_message_subject_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_iq(__TopXMLNS, __IgnoreEls,
    -	  {xmlel, <<"iq">>, _attrs, _els}) ->
    -    {Error, __Els} = decode_iq_els(__TopXMLNS, __IgnoreEls,
    -				   _els, undefined, []),
    -    {Id, Type, From, To, Lang} = decode_iq_attrs(__TopXMLNS,
    -						 _attrs, undefined, undefined,
    -						 undefined, undefined,
    -						 undefined),
    -    {iq, Id, Type, Lang, From, To, Error, __Els}.
    -
    -decode_iq_els(__TopXMLNS, __IgnoreEls, [], Error,
    -	      __Els) ->
    -    {Error, lists:reverse(__Els)};
    -decode_iq_els(__TopXMLNS, __IgnoreEls,
    -	      [{xmlel, <<"error">>, _attrs, _} = _el | _els], Error,
    -	      __Els) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_iq_els(__TopXMLNS, __IgnoreEls, _els,
    -			 decode_error(__TopXMLNS, __IgnoreEls, _el), __Els);
    -       true ->
    -	   decode_iq_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			 __Els)
    -    end;
    -decode_iq_els(__TopXMLNS, __IgnoreEls,
    -	      [{xmlel, _, _, _} = _el | _els], Error, __Els) ->
    -    if __IgnoreEls ->
    -	   decode_iq_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			 [_el | __Els]);
    -       true ->
    -	   case is_known_tag(_el) of
    -	     true ->
    -		 decode_iq_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			       [decode(_el) | __Els]);
    -	     false ->
    -		 decode_iq_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -			       __Els)
    -	   end
    -    end;
    -decode_iq_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -	      Error, __Els) ->
    -    decode_iq_els(__TopXMLNS, __IgnoreEls, _els, Error,
    -		  __Els).
    -
    -decode_iq_attrs(__TopXMLNS, [{<<"id">>, _val} | _attrs],
    -		_Id, Type, From, To, Lang) ->
    -    decode_iq_attrs(__TopXMLNS, _attrs, _val, Type, From,
    -		    To, Lang);
    -decode_iq_attrs(__TopXMLNS,
    -		[{<<"type">>, _val} | _attrs], Id, _Type, From, To,
    -		Lang) ->
    -    decode_iq_attrs(__TopXMLNS, _attrs, Id, _val, From, To,
    -		    Lang);
    -decode_iq_attrs(__TopXMLNS,
    -		[{<<"from">>, _val} | _attrs], Id, Type, _From, To,
    -		Lang) ->
    -    decode_iq_attrs(__TopXMLNS, _attrs, Id, Type, _val, To,
    -		    Lang);
    -decode_iq_attrs(__TopXMLNS, [{<<"to">>, _val} | _attrs],
    -		Id, Type, From, _To, Lang) ->
    -    decode_iq_attrs(__TopXMLNS, _attrs, Id, Type, From,
    -		    _val, Lang);
    -decode_iq_attrs(__TopXMLNS,
    -		[{<<"xml:lang">>, _val} | _attrs], Id, Type, From, To,
    -		_Lang) ->
    -    decode_iq_attrs(__TopXMLNS, _attrs, Id, Type, From, To,
    -		    _val);
    -decode_iq_attrs(__TopXMLNS, [_ | _attrs], Id, Type,
    -		From, To, Lang) ->
    -    decode_iq_attrs(__TopXMLNS, _attrs, Id, Type, From, To,
    -		    Lang);
    -decode_iq_attrs(__TopXMLNS, [], Id, Type, From, To,
    -		Lang) ->
    -    {decode_iq_attr_id(__TopXMLNS, Id),
    -     decode_iq_attr_type(__TopXMLNS, Type),
    -     decode_iq_attr_from(__TopXMLNS, From),
    -     decode_iq_attr_to(__TopXMLNS, To),
    -     'decode_iq_attr_xml:lang'(__TopXMLNS, Lang)}.
    -
    -encode_iq({iq, Id, Type, Lang, From, To, Error, __Els},
    -	  _xmlns_attrs) ->
    -    _els = [encode(_el) || _el <- __Els] ++
    -	     lists:reverse('encode_iq_$error'(Error, [])),
    -    _attrs = 'encode_iq_attr_xml:lang'(Lang,
    -				       encode_iq_attr_to(To,
    -							 encode_iq_attr_from(From,
    -									     encode_iq_attr_type(Type,
    -												 encode_iq_attr_id(Id,
    -														   _xmlns_attrs))))),
    -    {xmlel, <<"iq">>, _attrs, _els}.
    -
    -'encode_iq_$error'(undefined, _acc) -> _acc;
    -'encode_iq_$error'(Error, _acc) ->
    -    [encode_error(Error, []) | _acc].
    -
    -decode_iq_attr_id(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"id">>, <<"iq">>, __TopXMLNS}});
    -decode_iq_attr_id(__TopXMLNS, _val) -> _val.
    -
    -encode_iq_attr_id(_val, _acc) ->
    -    [{<<"id">>, _val} | _acc].
    -
    -decode_iq_attr_type(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"type">>, <<"iq">>, __TopXMLNS}});
    -decode_iq_attr_type(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val, [get, set, result, error]) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"type">>, <<"iq">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_iq_attr_type(_val, _acc) ->
    -    [{<<"type">>, enc_enum(_val)} | _acc].
    -
    -decode_iq_attr_from(__TopXMLNS, undefined) -> undefined;
    -decode_iq_attr_from(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"from">>, <<"iq">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_iq_attr_from(undefined, _acc) -> _acc;
    -encode_iq_attr_from(_val, _acc) ->
    -    [{<<"from">>, enc_jid(_val)} | _acc].
    -
    -decode_iq_attr_to(__TopXMLNS, undefined) -> undefined;
    -decode_iq_attr_to(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"to">>, <<"iq">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_iq_attr_to(undefined, _acc) -> _acc;
    -encode_iq_attr_to(_val, _acc) ->
    -    [{<<"to">>, enc_jid(_val)} | _acc].
    -
    -'decode_iq_attr_xml:lang'(__TopXMLNS, undefined) ->
    -    undefined;
    -'decode_iq_attr_xml:lang'(__TopXMLNS, _val) -> _val.
    -
    -'encode_iq_attr_xml:lang'(undefined, _acc) -> _acc;
    -'encode_iq_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_stats(__TopXMLNS, __IgnoreEls,
    -	     {xmlel, <<"query">>, _attrs, _els}) ->
    -    Stat = decode_stats_els(__TopXMLNS, __IgnoreEls, _els,
    -			    []),
    -    {stats, Stat}.
    -
    -decode_stats_els(__TopXMLNS, __IgnoreEls, [], Stat) ->
    -    lists:reverse(Stat);
    -decode_stats_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"stat">>, _attrs, _} = _el | _els], Stat) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_stats_els(__TopXMLNS, __IgnoreEls, _els,
    -			    [decode_stat(__TopXMLNS, __IgnoreEls, _el) | Stat]);
    -       true ->
    -	   decode_stats_els(__TopXMLNS, __IgnoreEls, _els, Stat)
    -    end;
    -decode_stats_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		 Stat) ->
    -    decode_stats_els(__TopXMLNS, __IgnoreEls, _els, Stat).
    -
    -encode_stats({stats, Stat}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_stats_$stat'(Stat, [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_stats_$stat'([], _acc) -> _acc;
    -'encode_stats_$stat'([Stat | _els], _acc) ->
    -    'encode_stats_$stat'(_els,
    -			 [encode_stat(Stat, []) | _acc]).
    -
    -decode_stat(__TopXMLNS, __IgnoreEls,
    -	    {xmlel, <<"stat">>, _attrs, _els}) ->
    -    Error = decode_stat_els(__TopXMLNS, __IgnoreEls, _els,
    -			    []),
    -    {Name, Units, Value} = decode_stat_attrs(__TopXMLNS,
    -					     _attrs, undefined, undefined,
    -					     undefined),
    -    {stat, Name, Units, Value, Error}.
    -
    -decode_stat_els(__TopXMLNS, __IgnoreEls, [], Error) ->
    -    lists:reverse(Error);
    -decode_stat_els(__TopXMLNS, __IgnoreEls,
    -		[{xmlel, <<"error">>, _attrs, _} = _el | _els],
    -		Error) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_stat_els(__TopXMLNS, __IgnoreEls, _els,
    -			   [decode_stat_error(__TopXMLNS, __IgnoreEls, _el)
    -			    | Error]);
    -       true ->
    -	   decode_stat_els(__TopXMLNS, __IgnoreEls, _els, Error)
    -    end;
    -decode_stat_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		Error) ->
    -    decode_stat_els(__TopXMLNS, __IgnoreEls, _els, Error).
    -
    -decode_stat_attrs(__TopXMLNS,
    -		  [{<<"name">>, _val} | _attrs], _Name, Units, Value) ->
    -    decode_stat_attrs(__TopXMLNS, _attrs, _val, Units,
    -		      Value);
    -decode_stat_attrs(__TopXMLNS,
    -		  [{<<"units">>, _val} | _attrs], Name, _Units, Value) ->
    -    decode_stat_attrs(__TopXMLNS, _attrs, Name, _val,
    -		      Value);
    -decode_stat_attrs(__TopXMLNS,
    -		  [{<<"value">>, _val} | _attrs], Name, Units, _Value) ->
    -    decode_stat_attrs(__TopXMLNS, _attrs, Name, Units,
    -		      _val);
    -decode_stat_attrs(__TopXMLNS, [_ | _attrs], Name, Units,
    -		  Value) ->
    -    decode_stat_attrs(__TopXMLNS, _attrs, Name, Units,
    -		      Value);
    -decode_stat_attrs(__TopXMLNS, [], Name, Units, Value) ->
    -    {decode_stat_attr_name(__TopXMLNS, Name),
    -     decode_stat_attr_units(__TopXMLNS, Units),
    -     decode_stat_attr_value(__TopXMLNS, Value)}.
    -
    -encode_stat({stat, Name, Units, Value, Error},
    -	    _xmlns_attrs) ->
    -    _els = lists:reverse('encode_stat_$error'(Error, [])),
    -    _attrs = encode_stat_attr_value(Value,
    -				    encode_stat_attr_units(Units,
    -							   encode_stat_attr_name(Name,
    -										 _xmlns_attrs))),
    -    {xmlel, <<"stat">>, _attrs, _els}.
    -
    -'encode_stat_$error'([], _acc) -> _acc;
    -'encode_stat_$error'([Error | _els], _acc) ->
    -    'encode_stat_$error'(_els,
    -			 [encode_stat_error(Error, []) | _acc]).
    -
    -decode_stat_attr_name(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"name">>, <<"stat">>, __TopXMLNS}});
    -decode_stat_attr_name(__TopXMLNS, _val) -> _val.
    -
    -encode_stat_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_stat_attr_units(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_stat_attr_units(__TopXMLNS, _val) -> _val.
    -
    -encode_stat_attr_units(undefined, _acc) -> _acc;
    -encode_stat_attr_units(_val, _acc) ->
    -    [{<<"units">>, _val} | _acc].
    -
    -decode_stat_attr_value(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_stat_attr_value(__TopXMLNS, _val) -> _val.
    -
    -encode_stat_attr_value(undefined, _acc) -> _acc;
    -encode_stat_attr_value(_val, _acc) ->
    -    [{<<"value">>, _val} | _acc].
    -
    -decode_stat_error(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"error">>, _attrs, _els}) ->
    -    Cdata = decode_stat_error_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Code = decode_stat_error_attrs(__TopXMLNS, _attrs,
    -				   undefined),
    -    {Code, Cdata}.
    -
    -decode_stat_error_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_stat_error_cdata(__TopXMLNS, Cdata);
    -decode_stat_error_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_stat_error_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_stat_error_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_stat_error_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -decode_stat_error_attrs(__TopXMLNS,
    -			[{<<"code">>, _val} | _attrs], _Code) ->
    -    decode_stat_error_attrs(__TopXMLNS, _attrs, _val);
    -decode_stat_error_attrs(__TopXMLNS, [_ | _attrs],
    -			Code) ->
    -    decode_stat_error_attrs(__TopXMLNS, _attrs, Code);
    -decode_stat_error_attrs(__TopXMLNS, [], Code) ->
    -    decode_stat_error_attr_code(__TopXMLNS, Code).
    -
    -encode_stat_error({Code, Cdata}, _xmlns_attrs) ->
    -    _els = encode_stat_error_cdata(Cdata, []),
    -    _attrs = encode_stat_error_attr_code(Code,
    -					 _xmlns_attrs),
    -    {xmlel, <<"error">>, _attrs, _els}.
    -
    -decode_stat_error_attr_code(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"code">>, <<"error">>, __TopXMLNS}});
    -decode_stat_error_attr_code(__TopXMLNS, _val) ->
    -    case catch dec_int(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"code">>, <<"error">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_stat_error_attr_code(_val, _acc) ->
    -    [{<<"code">>, enc_int(_val)} | _acc].
    -
    -decode_stat_error_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_stat_error_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_stat_error_cdata(undefined, _acc) -> _acc;
    -encode_stat_error_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_bookmarks_storage(__TopXMLNS, __IgnoreEls,
    -			 {xmlel, <<"storage">>, _attrs, _els}) ->
    -    {Conference, Url} =
    -	decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -				     _els, [], []),
    -    {bookmark_storage, Conference, Url}.
    -
    -decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -			     [], Conference, Url) ->
    -    {lists:reverse(Conference), lists:reverse(Url)};
    -decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlel, <<"conference">>, _attrs, _} = _el
    -			      | _els],
    -			     Conference, Url) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -					_els,
    -					[decode_bookmark_conference(__TopXMLNS,
    -								    __IgnoreEls,
    -								    _el)
    -					 | Conference],
    -					Url);
    -       true ->
    -	   decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -					_els, Conference, Url)
    -    end;
    -decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -			     [{xmlel, <<"url">>, _attrs, _} = _el | _els],
    -			     Conference, Url) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -					_els, Conference,
    -					[decode_bookmark_url(__TopXMLNS,
    -							     __IgnoreEls, _el)
    -					 | Url]);
    -       true ->
    -	   decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -					_els, Conference, Url)
    -    end;
    -decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -			     [_ | _els], Conference, Url) ->
    -    decode_bookmarks_storage_els(__TopXMLNS, __IgnoreEls,
    -				 _els, Conference, Url).
    -
    -encode_bookmarks_storage({bookmark_storage, Conference,
    -			  Url},
    -			 _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_bookmarks_storage_$conference'(Conference,
    -							     'encode_bookmarks_storage_$url'(Url,
    -											     []))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"storage">>, _attrs, _els}.
    -
    -'encode_bookmarks_storage_$conference'([], _acc) ->
    -    _acc;
    -'encode_bookmarks_storage_$conference'([Conference
    -					| _els],
    -				       _acc) ->
    -    'encode_bookmarks_storage_$conference'(_els,
    -					   [encode_bookmark_conference(Conference,
    -								       [])
    -					    | _acc]).
    -
    -'encode_bookmarks_storage_$url'([], _acc) -> _acc;
    -'encode_bookmarks_storage_$url'([Url | _els], _acc) ->
    -    'encode_bookmarks_storage_$url'(_els,
    -				    [encode_bookmark_url(Url, []) | _acc]).
    -
    -decode_bookmark_url(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"url">>, _attrs, _els}) ->
    -    {Name, Url} = decode_bookmark_url_attrs(__TopXMLNS,
    -					    _attrs, undefined, undefined),
    -    {bookmark_url, Name, Url}.
    -
    -decode_bookmark_url_attrs(__TopXMLNS,
    -			  [{<<"name">>, _val} | _attrs], _Name, Url) ->
    -    decode_bookmark_url_attrs(__TopXMLNS, _attrs, _val,
    -			      Url);
    -decode_bookmark_url_attrs(__TopXMLNS,
    -			  [{<<"url">>, _val} | _attrs], Name, _Url) ->
    -    decode_bookmark_url_attrs(__TopXMLNS, _attrs, Name,
    -			      _val);
    -decode_bookmark_url_attrs(__TopXMLNS, [_ | _attrs],
    -			  Name, Url) ->
    -    decode_bookmark_url_attrs(__TopXMLNS, _attrs, Name,
    -			      Url);
    -decode_bookmark_url_attrs(__TopXMLNS, [], Name, Url) ->
    -    {decode_bookmark_url_attr_name(__TopXMLNS, Name),
    -     decode_bookmark_url_attr_url(__TopXMLNS, Url)}.
    -
    -encode_bookmark_url({bookmark_url, Name, Url},
    -		    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_bookmark_url_attr_url(Url,
    -					  encode_bookmark_url_attr_name(Name,
    -									_xmlns_attrs)),
    -    {xmlel, <<"url">>, _attrs, _els}.
    -
    -decode_bookmark_url_attr_name(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"name">>, <<"url">>, __TopXMLNS}});
    -decode_bookmark_url_attr_name(__TopXMLNS, _val) -> _val.
    -
    -encode_bookmark_url_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_bookmark_url_attr_url(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"url">>, <<"url">>, __TopXMLNS}});
    -decode_bookmark_url_attr_url(__TopXMLNS, _val) -> _val.
    -
    -encode_bookmark_url_attr_url(_val, _acc) ->
    -    [{<<"url">>, _val} | _acc].
    -
    -decode_bookmark_conference(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"conference">>, _attrs, _els}) ->
    -    {Password, Nick} =
    -	decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -				       _els, undefined, undefined),
    -    {Name, Jid, Autojoin} =
    -	decode_bookmark_conference_attrs(__TopXMLNS, _attrs,
    -					 undefined, undefined, undefined),
    -    {bookmark_conference, Name, Jid, Autojoin, Nick,
    -     Password}.
    -
    -decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -			       [], Password, Nick) ->
    -    {Password, Nick};
    -decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -			       [{xmlel, <<"nick">>, _attrs, _} = _el | _els],
    -			       Password, Nick) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -					  _els, Password,
    -					  decode_conference_nick(__TopXMLNS,
    -								 __IgnoreEls,
    -								 _el));
    -       true ->
    -	   decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -					  _els, Password, Nick)
    -    end;
    -decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -			       [{xmlel, <<"password">>, _attrs, _} = _el
    -				| _els],
    -			       Password, Nick) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -					  _els,
    -					  decode_conference_password(__TopXMLNS,
    -								     __IgnoreEls,
    -								     _el),
    -					  Nick);
    -       true ->
    -	   decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -					  _els, Password, Nick)
    -    end;
    -decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -			       [_ | _els], Password, Nick) ->
    -    decode_bookmark_conference_els(__TopXMLNS, __IgnoreEls,
    -				   _els, Password, Nick).
    -
    -decode_bookmark_conference_attrs(__TopXMLNS,
    -				 [{<<"name">>, _val} | _attrs], _Name, Jid,
    -				 Autojoin) ->
    -    decode_bookmark_conference_attrs(__TopXMLNS, _attrs,
    -				     _val, Jid, Autojoin);
    -decode_bookmark_conference_attrs(__TopXMLNS,
    -				 [{<<"jid">>, _val} | _attrs], Name, _Jid,
    -				 Autojoin) ->
    -    decode_bookmark_conference_attrs(__TopXMLNS, _attrs,
    -				     Name, _val, Autojoin);
    -decode_bookmark_conference_attrs(__TopXMLNS,
    -				 [{<<"autojoin">>, _val} | _attrs], Name, Jid,
    -				 _Autojoin) ->
    -    decode_bookmark_conference_attrs(__TopXMLNS, _attrs,
    -				     Name, Jid, _val);
    -decode_bookmark_conference_attrs(__TopXMLNS,
    -				 [_ | _attrs], Name, Jid, Autojoin) ->
    -    decode_bookmark_conference_attrs(__TopXMLNS, _attrs,
    -				     Name, Jid, Autojoin);
    -decode_bookmark_conference_attrs(__TopXMLNS, [], Name,
    -				 Jid, Autojoin) ->
    -    {decode_bookmark_conference_attr_name(__TopXMLNS, Name),
    -     decode_bookmark_conference_attr_jid(__TopXMLNS, Jid),
    -     decode_bookmark_conference_attr_autojoin(__TopXMLNS,
    -					      Autojoin)}.
    -
    -encode_bookmark_conference({bookmark_conference, Name,
    -			    Jid, Autojoin, Nick, Password},
    -			   _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_bookmark_conference_$password'(Password,
    -							     'encode_bookmark_conference_$nick'(Nick,
    -												[]))),
    -    _attrs =
    -	encode_bookmark_conference_attr_autojoin(Autojoin,
    -						 encode_bookmark_conference_attr_jid(Jid,
    -										     encode_bookmark_conference_attr_name(Name,
    -															  _xmlns_attrs))),
    -    {xmlel, <<"conference">>, _attrs, _els}.
    -
    -'encode_bookmark_conference_$password'(undefined,
    -				       _acc) ->
    -    _acc;
    -'encode_bookmark_conference_$password'(Password,
    -				       _acc) ->
    -    [encode_conference_password(Password, []) | _acc].
    -
    -'encode_bookmark_conference_$nick'(undefined, _acc) ->
    -    _acc;
    -'encode_bookmark_conference_$nick'(Nick, _acc) ->
    -    [encode_conference_nick(Nick, []) | _acc].
    -
    -decode_bookmark_conference_attr_name(__TopXMLNS,
    -				     undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"name">>, <<"conference">>,
    -		   __TopXMLNS}});
    -decode_bookmark_conference_attr_name(__TopXMLNS,
    -				     _val) ->
    -    _val.
    -
    -encode_bookmark_conference_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_bookmark_conference_attr_jid(__TopXMLNS,
    -				    undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"conference">>,
    -		   __TopXMLNS}});
    -decode_bookmark_conference_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"conference">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_bookmark_conference_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_bookmark_conference_attr_autojoin(__TopXMLNS,
    -					 undefined) ->
    -    false;
    -decode_bookmark_conference_attr_autojoin(__TopXMLNS,
    -					 _val) ->
    -    case catch dec_bool(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"autojoin">>, <<"conference">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_bookmark_conference_attr_autojoin(false, _acc) ->
    -    _acc;
    -encode_bookmark_conference_attr_autojoin(_val, _acc) ->
    -    [{<<"autojoin">>, enc_bool(_val)} | _acc].
    -
    -decode_conference_password(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"password">>, _attrs, _els}) ->
    -    Cdata = decode_conference_password_els(__TopXMLNS,
    -					   __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_conference_password_els(__TopXMLNS, __IgnoreEls,
    -			       [], Cdata) ->
    -    decode_conference_password_cdata(__TopXMLNS, Cdata);
    -decode_conference_password_els(__TopXMLNS, __IgnoreEls,
    -			       [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_conference_password_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <>);
    -decode_conference_password_els(__TopXMLNS, __IgnoreEls,
    -			       [_ | _els], Cdata) ->
    -    decode_conference_password_els(__TopXMLNS, __IgnoreEls,
    -				   _els, Cdata).
    -
    -encode_conference_password(Cdata, _xmlns_attrs) ->
    -    _els = encode_conference_password_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"password">>, _attrs, _els}.
    -
    -decode_conference_password_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_conference_password_cdata(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_conference_password_cdata(undefined, _acc) ->
    -    _acc;
    -encode_conference_password_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_conference_nick(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"nick">>, _attrs, _els}) ->
    -    Cdata = decode_conference_nick_els(__TopXMLNS,
    -				       __IgnoreEls, _els, <<>>),
    -    Cdata.
    -
    -decode_conference_nick_els(__TopXMLNS, __IgnoreEls, [],
    -			   Cdata) ->
    -    decode_conference_nick_cdata(__TopXMLNS, Cdata);
    -decode_conference_nick_els(__TopXMLNS, __IgnoreEls,
    -			   [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_conference_nick_els(__TopXMLNS, __IgnoreEls,
    -			       _els, <>);
    -decode_conference_nick_els(__TopXMLNS, __IgnoreEls,
    -			   [_ | _els], Cdata) ->
    -    decode_conference_nick_els(__TopXMLNS, __IgnoreEls,
    -			       _els, Cdata).
    -
    -encode_conference_nick(Cdata, _xmlns_attrs) ->
    -    _els = encode_conference_nick_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"nick">>, _attrs, _els}.
    -
    -decode_conference_nick_cdata(__TopXMLNS, <<>>) ->
    -    undefined;
    -decode_conference_nick_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_conference_nick_cdata(undefined, _acc) -> _acc;
    -encode_conference_nick_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_private(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"query">>, _attrs, _els}) ->
    -    __Xmls = decode_private_els(__TopXMLNS, __IgnoreEls,
    -				_els, []),
    -    {private, __Xmls}.
    -
    -decode_private_els(__TopXMLNS, __IgnoreEls, [],
    -		   __Xmls) ->
    -    lists:reverse(__Xmls);
    -decode_private_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, _, _, _} = _el | _els], __Xmls) ->
    -    decode_private_els(__TopXMLNS, __IgnoreEls, _els,
    -		       [_el | __Xmls]);
    -decode_private_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   __Xmls) ->
    -    decode_private_els(__TopXMLNS, __IgnoreEls, _els,
    -		       __Xmls).
    -
    -encode_private({private, __Xmls}, _xmlns_attrs) ->
    -    _els = __Xmls,
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -decode_disco_items(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"query">>, _attrs, _els}) ->
    -    Items = decode_disco_items_els(__TopXMLNS, __IgnoreEls,
    -				   _els, []),
    -    Node = decode_disco_items_attrs(__TopXMLNS, _attrs,
    -				    undefined),
    -    {disco_items, Node, Items}.
    -
    -decode_disco_items_els(__TopXMLNS, __IgnoreEls, [],
    -		       Items) ->
    -    lists:reverse(Items);
    -decode_disco_items_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els,
    -				  [decode_disco_item(__TopXMLNS, __IgnoreEls,
    -						     _el)
    -				   | Items]);
    -       true ->
    -	   decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Items)
    -    end;
    -decode_disco_items_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Items) ->
    -    decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Items).
    -
    -decode_disco_items_attrs(__TopXMLNS,
    -			 [{<<"node">>, _val} | _attrs], _Node) ->
    -    decode_disco_items_attrs(__TopXMLNS, _attrs, _val);
    -decode_disco_items_attrs(__TopXMLNS, [_ | _attrs],
    -			 Node) ->
    -    decode_disco_items_attrs(__TopXMLNS, _attrs, Node);
    -decode_disco_items_attrs(__TopXMLNS, [], Node) ->
    -    decode_disco_items_attr_node(__TopXMLNS, Node).
    -
    -encode_disco_items({disco_items, Node, Items},
    -		   _xmlns_attrs) ->
    -    _els = lists:reverse('encode_disco_items_$items'(Items,
    -						     [])),
    -    _attrs = encode_disco_items_attr_node(Node,
    -					  _xmlns_attrs),
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_disco_items_$items'([], _acc) -> _acc;
    -'encode_disco_items_$items'([Items | _els], _acc) ->
    -    'encode_disco_items_$items'(_els,
    -				[encode_disco_item(Items, []) | _acc]).
    -
    -decode_disco_items_attr_node(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_disco_items_attr_node(__TopXMLNS, _val) -> _val.
    -
    -encode_disco_items_attr_node(undefined, _acc) -> _acc;
    -encode_disco_items_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_disco_item(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"item">>, _attrs, _els}) ->
    -    {Jid, Name, Node} = decode_disco_item_attrs(__TopXMLNS,
    -						_attrs, undefined, undefined,
    -						undefined),
    -    {disco_item, Jid, Name, Node}.
    -
    -decode_disco_item_attrs(__TopXMLNS,
    -			[{<<"jid">>, _val} | _attrs], _Jid, Name, Node) ->
    -    decode_disco_item_attrs(__TopXMLNS, _attrs, _val, Name,
    -			    Node);
    -decode_disco_item_attrs(__TopXMLNS,
    -			[{<<"name">>, _val} | _attrs], Jid, _Name, Node) ->
    -    decode_disco_item_attrs(__TopXMLNS, _attrs, Jid, _val,
    -			    Node);
    -decode_disco_item_attrs(__TopXMLNS,
    -			[{<<"node">>, _val} | _attrs], Jid, Name, _Node) ->
    -    decode_disco_item_attrs(__TopXMLNS, _attrs, Jid, Name,
    -			    _val);
    -decode_disco_item_attrs(__TopXMLNS, [_ | _attrs], Jid,
    -			Name, Node) ->
    -    decode_disco_item_attrs(__TopXMLNS, _attrs, Jid, Name,
    -			    Node);
    -decode_disco_item_attrs(__TopXMLNS, [], Jid, Name,
    -			Node) ->
    -    {decode_disco_item_attr_jid(__TopXMLNS, Jid),
    -     decode_disco_item_attr_name(__TopXMLNS, Name),
    -     decode_disco_item_attr_node(__TopXMLNS, Node)}.
    -
    -encode_disco_item({disco_item, Jid, Name, Node},
    -		  _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_disco_item_attr_node(Node,
    -					 encode_disco_item_attr_name(Name,
    -								     encode_disco_item_attr_jid(Jid,
    -												_xmlns_attrs))),
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -decode_disco_item_attr_jid(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"item">>, __TopXMLNS}});
    -decode_disco_item_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_disco_item_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_disco_item_attr_name(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_disco_item_attr_name(__TopXMLNS, _val) -> _val.
    -
    -encode_disco_item_attr_name(undefined, _acc) -> _acc;
    -encode_disco_item_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_disco_item_attr_node(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_disco_item_attr_node(__TopXMLNS, _val) -> _val.
    -
    -encode_disco_item_attr_node(undefined, _acc) -> _acc;
    -encode_disco_item_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_disco_info(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"query">>, _attrs, _els}) ->
    -    {Xdata, Features, Identities} =
    -	decode_disco_info_els(__TopXMLNS, __IgnoreEls, _els, [],
    -			      [], []),
    -    Node = decode_disco_info_attrs(__TopXMLNS, _attrs,
    -				   undefined),
    -    {disco_info, Node, Identities, Features, Xdata}.
    -
    -decode_disco_info_els(__TopXMLNS, __IgnoreEls, [],
    -		      Xdata, Features, Identities) ->
    -    {lists:reverse(Xdata), lists:reverse(Features),
    -     lists:reverse(Identities)};
    -decode_disco_info_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlel, <<"identity">>, _attrs, _} = _el | _els],
    -		      Xdata, Features, Identities) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_disco_info_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Xdata, Features,
    -				 [decode_disco_identity(__TopXMLNS, __IgnoreEls,
    -							_el)
    -				  | Identities]);
    -       true ->
    -	   decode_disco_info_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Xdata, Features, Identities)
    -    end;
    -decode_disco_info_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlel, <<"feature">>, _attrs, _} = _el | _els], Xdata,
    -		      Features, Identities) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_disco_info_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Xdata,
    -				 [decode_disco_feature(__TopXMLNS, __IgnoreEls,
    -						       _el)
    -				  | Features],
    -				 Identities);
    -       true ->
    -	   decode_disco_info_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Xdata, Features, Identities)
    -    end;
    -decode_disco_info_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlel, <<"x">>, _attrs, _} = _el | _els], Xdata,
    -		      Features, Identities) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<"jabber:x:data">> ->
    -	   decode_disco_info_els(__TopXMLNS, __IgnoreEls, _els,
    -				 [decode_xdata(_xmlns, __IgnoreEls, _el)
    -				  | Xdata],
    -				 Features, Identities);
    -       true ->
    -	   decode_disco_info_els(__TopXMLNS, __IgnoreEls, _els,
    -				 Xdata, Features, Identities)
    -    end;
    -decode_disco_info_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Xdata, Features, Identities) ->
    -    decode_disco_info_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Xdata, Features, Identities).
    -
    -decode_disco_info_attrs(__TopXMLNS,
    -			[{<<"node">>, _val} | _attrs], _Node) ->
    -    decode_disco_info_attrs(__TopXMLNS, _attrs, _val);
    -decode_disco_info_attrs(__TopXMLNS, [_ | _attrs],
    -			Node) ->
    -    decode_disco_info_attrs(__TopXMLNS, _attrs, Node);
    -decode_disco_info_attrs(__TopXMLNS, [], Node) ->
    -    decode_disco_info_attr_node(__TopXMLNS, Node).
    -
    -encode_disco_info({disco_info, Node, Identities,
    -		   Features, Xdata},
    -		  _xmlns_attrs) ->
    -    _els = lists:reverse('encode_disco_info_$xdata'(Xdata,
    -						    'encode_disco_info_$features'(Features,
    -										  'encode_disco_info_$identities'(Identities,
    -														  [])))),
    -    _attrs = encode_disco_info_attr_node(Node,
    -					 _xmlns_attrs),
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_disco_info_$xdata'([], _acc) -> _acc;
    -'encode_disco_info_$xdata'([Xdata | _els], _acc) ->
    -    'encode_disco_info_$xdata'(_els,
    -			       [encode_xdata(Xdata,
    -					     [{<<"xmlns">>,
    -					       <<"jabber:x:data">>}])
    -				| _acc]).
    -
    -'encode_disco_info_$features'([], _acc) -> _acc;
    -'encode_disco_info_$features'([Features | _els],
    -			      _acc) ->
    -    'encode_disco_info_$features'(_els,
    -				  [encode_disco_feature(Features, []) | _acc]).
    -
    -'encode_disco_info_$identities'([], _acc) -> _acc;
    -'encode_disco_info_$identities'([Identities | _els],
    -				_acc) ->
    -    'encode_disco_info_$identities'(_els,
    -				    [encode_disco_identity(Identities, [])
    -				     | _acc]).
    -
    -decode_disco_info_attr_node(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_disco_info_attr_node(__TopXMLNS, _val) -> _val.
    -
    -encode_disco_info_attr_node(undefined, _acc) -> _acc;
    -encode_disco_info_attr_node(_val, _acc) ->
    -    [{<<"node">>, _val} | _acc].
    -
    -decode_disco_feature(__TopXMLNS, __IgnoreEls,
    -		     {xmlel, <<"feature">>, _attrs, _els}) ->
    -    Var = decode_disco_feature_attrs(__TopXMLNS, _attrs,
    -				     undefined),
    -    Var.
    -
    -decode_disco_feature_attrs(__TopXMLNS,
    -			   [{<<"var">>, _val} | _attrs], _Var) ->
    -    decode_disco_feature_attrs(__TopXMLNS, _attrs, _val);
    -decode_disco_feature_attrs(__TopXMLNS, [_ | _attrs],
    -			   Var) ->
    -    decode_disco_feature_attrs(__TopXMLNS, _attrs, Var);
    -decode_disco_feature_attrs(__TopXMLNS, [], Var) ->
    -    decode_disco_feature_attr_var(__TopXMLNS, Var).
    -
    -encode_disco_feature(Var, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_disco_feature_attr_var(Var,
    -					   _xmlns_attrs),
    -    {xmlel, <<"feature">>, _attrs, _els}.
    -
    -decode_disco_feature_attr_var(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"var">>, <<"feature">>, __TopXMLNS}});
    -decode_disco_feature_attr_var(__TopXMLNS, _val) -> _val.
    -
    -encode_disco_feature_attr_var(_val, _acc) ->
    -    [{<<"var">>, _val} | _acc].
    -
    -decode_disco_identity(__TopXMLNS, __IgnoreEls,
    -		      {xmlel, <<"identity">>, _attrs, _els}) ->
    -    {Category, Type, Lang, Name} =
    -	decode_disco_identity_attrs(__TopXMLNS, _attrs,
    -				    undefined, undefined, undefined, undefined),
    -    {identity, Category, Type, Lang, Name}.
    -
    -decode_disco_identity_attrs(__TopXMLNS,
    -			    [{<<"category">>, _val} | _attrs], _Category, Type,
    -			    Lang, Name) ->
    -    decode_disco_identity_attrs(__TopXMLNS, _attrs, _val,
    -				Type, Lang, Name);
    -decode_disco_identity_attrs(__TopXMLNS,
    -			    [{<<"type">>, _val} | _attrs], Category, _Type,
    -			    Lang, Name) ->
    -    decode_disco_identity_attrs(__TopXMLNS, _attrs,
    -				Category, _val, Lang, Name);
    -decode_disco_identity_attrs(__TopXMLNS,
    -			    [{<<"xml:lang">>, _val} | _attrs], Category, Type,
    -			    _Lang, Name) ->
    -    decode_disco_identity_attrs(__TopXMLNS, _attrs,
    -				Category, Type, _val, Name);
    -decode_disco_identity_attrs(__TopXMLNS,
    -			    [{<<"name">>, _val} | _attrs], Category, Type, Lang,
    -			    _Name) ->
    -    decode_disco_identity_attrs(__TopXMLNS, _attrs,
    -				Category, Type, Lang, _val);
    -decode_disco_identity_attrs(__TopXMLNS, [_ | _attrs],
    -			    Category, Type, Lang, Name) ->
    -    decode_disco_identity_attrs(__TopXMLNS, _attrs,
    -				Category, Type, Lang, Name);
    -decode_disco_identity_attrs(__TopXMLNS, [], Category,
    -			    Type, Lang, Name) ->
    -    {decode_disco_identity_attr_category(__TopXMLNS,
    -					 Category),
    -     decode_disco_identity_attr_type(__TopXMLNS, Type),
    -     'decode_disco_identity_attr_xml:lang'(__TopXMLNS, Lang),
    -     decode_disco_identity_attr_name(__TopXMLNS, Name)}.
    -
    -encode_disco_identity({identity, Category, Type, Lang,
    -		       Name},
    -		      _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_disco_identity_attr_name(Name,
    -					     'encode_disco_identity_attr_xml:lang'(Lang,
    -										   encode_disco_identity_attr_type(Type,
    -														   encode_disco_identity_attr_category(Category,
    -																		       _xmlns_attrs)))),
    -    {xmlel, <<"identity">>, _attrs, _els}.
    -
    -decode_disco_identity_attr_category(__TopXMLNS,
    -				    undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"category">>, <<"identity">>,
    -		   __TopXMLNS}});
    -decode_disco_identity_attr_category(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_disco_identity_attr_category(_val, _acc) ->
    -    [{<<"category">>, _val} | _acc].
    -
    -decode_disco_identity_attr_type(__TopXMLNS,
    -				undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"type">>, <<"identity">>,
    -		   __TopXMLNS}});
    -decode_disco_identity_attr_type(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_disco_identity_attr_type(_val, _acc) ->
    -    [{<<"type">>, _val} | _acc].
    -
    -'decode_disco_identity_attr_xml:lang'(__TopXMLNS,
    -				      undefined) ->
    -    undefined;
    -'decode_disco_identity_attr_xml:lang'(__TopXMLNS,
    -				      _val) ->
    -    _val.
    -
    -'encode_disco_identity_attr_xml:lang'(undefined,
    -				      _acc) ->
    -    _acc;
    -'encode_disco_identity_attr_xml:lang'(_val, _acc) ->
    -    [{<<"xml:lang">>, _val} | _acc].
    -
    -decode_disco_identity_attr_name(__TopXMLNS,
    -				undefined) ->
    -    undefined;
    -decode_disco_identity_attr_name(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_disco_identity_attr_name(undefined, _acc) ->
    -    _acc;
    -encode_disco_identity_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_block_list(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"blocklist">>, _attrs, _els}) ->
    -    {block_list}.
    -
    -encode_block_list({block_list}, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"blocklist">>, _attrs, _els}.
    -
    -decode_unblock(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"unblock">>, _attrs, _els}) ->
    -    Items = decode_unblock_els(__TopXMLNS, __IgnoreEls,
    -			       _els, []),
    -    {unblock, Items}.
    -
    -decode_unblock_els(__TopXMLNS, __IgnoreEls, [],
    -		   Items) ->
    -    lists:reverse(Items);
    -decode_unblock_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_unblock_els(__TopXMLNS, __IgnoreEls, _els,
    -			      case decode_block_item(__TopXMLNS, __IgnoreEls,
    -						     _el)
    -				  of
    -				undefined -> Items;
    -				_new_el -> [_new_el | Items]
    -			      end);
    -       true ->
    -	   decode_unblock_els(__TopXMLNS, __IgnoreEls, _els, Items)
    -    end;
    -decode_unblock_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Items) ->
    -    decode_unblock_els(__TopXMLNS, __IgnoreEls, _els,
    -		       Items).
    -
    -encode_unblock({unblock, Items}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_unblock_$items'(Items,
    -						 [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"unblock">>, _attrs, _els}.
    -
    -'encode_unblock_$items'([], _acc) -> _acc;
    -'encode_unblock_$items'([Items | _els], _acc) ->
    -    'encode_unblock_$items'(_els,
    -			    [encode_block_item(Items, []) | _acc]).
    -
    -decode_block(__TopXMLNS, __IgnoreEls,
    -	     {xmlel, <<"block">>, _attrs, _els}) ->
    -    Items = decode_block_els(__TopXMLNS, __IgnoreEls, _els,
    -			     []),
    -    {block, Items}.
    -
    -decode_block_els(__TopXMLNS, __IgnoreEls, [], Items) ->
    -    lists:reverse(Items);
    -decode_block_els(__TopXMLNS, __IgnoreEls,
    -		 [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_block_els(__TopXMLNS, __IgnoreEls, _els,
    -			    case decode_block_item(__TopXMLNS, __IgnoreEls, _el)
    -				of
    -			      undefined -> Items;
    -			      _new_el -> [_new_el | Items]
    -			    end);
    -       true ->
    -	   decode_block_els(__TopXMLNS, __IgnoreEls, _els, Items)
    -    end;
    -decode_block_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		 Items) ->
    -    decode_block_els(__TopXMLNS, __IgnoreEls, _els, Items).
    -
    -encode_block({block, Items}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_block_$items'(Items, [])),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"block">>, _attrs, _els}.
    -
    -'encode_block_$items'([], _acc) -> _acc;
    -'encode_block_$items'([Items | _els], _acc) ->
    -    'encode_block_$items'(_els,
    -			  [encode_block_item(Items, []) | _acc]).
    -
    -decode_block_item(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"item">>, _attrs, _els}) ->
    -    Jid = decode_block_item_attrs(__TopXMLNS, _attrs,
    -				  undefined),
    -    Jid.
    -
    -decode_block_item_attrs(__TopXMLNS,
    -			[{<<"jid">>, _val} | _attrs], _Jid) ->
    -    decode_block_item_attrs(__TopXMLNS, _attrs, _val);
    -decode_block_item_attrs(__TopXMLNS, [_ | _attrs],
    -			Jid) ->
    -    decode_block_item_attrs(__TopXMLNS, _attrs, Jid);
    -decode_block_item_attrs(__TopXMLNS, [], Jid) ->
    -    decode_block_item_attr_jid(__TopXMLNS, Jid).
    -
    -encode_block_item(Jid, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_block_item_attr_jid(Jid, _xmlns_attrs),
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -decode_block_item_attr_jid(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"item">>, __TopXMLNS}});
    -decode_block_item_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_block_item_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_privacy(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"query">>, _attrs, _els}) ->
    -    {Lists, Default, Active} =
    -	decode_privacy_els(__TopXMLNS, __IgnoreEls, _els, [],
    -			   undefined, undefined),
    -    {privacy, Lists, Default, Active}.
    -
    -decode_privacy_els(__TopXMLNS, __IgnoreEls, [], Lists,
    -		   Default, Active) ->
    -    {lists:reverse(Lists), Default, Active};
    -decode_privacy_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"list">>, _attrs, _} = _el | _els], Lists,
    -		   Default, Active) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_privacy_els(__TopXMLNS, __IgnoreEls, _els,
    -			      [decode_privacy_list(__TopXMLNS, __IgnoreEls, _el)
    -			       | Lists],
    -			      Default, Active);
    -       true ->
    -	   decode_privacy_els(__TopXMLNS, __IgnoreEls, _els, Lists,
    -			      Default, Active)
    -    end;
    -decode_privacy_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"default">>, _attrs, _} = _el | _els], Lists,
    -		   Default, Active) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_privacy_els(__TopXMLNS, __IgnoreEls, _els, Lists,
    -			      decode_privacy_default_list(__TopXMLNS,
    -							  __IgnoreEls, _el),
    -			      Active);
    -       true ->
    -	   decode_privacy_els(__TopXMLNS, __IgnoreEls, _els, Lists,
    -			      Default, Active)
    -    end;
    -decode_privacy_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"active">>, _attrs, _} = _el | _els], Lists,
    -		   Default, Active) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_privacy_els(__TopXMLNS, __IgnoreEls, _els, Lists,
    -			      Default,
    -			      decode_privacy_active_list(__TopXMLNS,
    -							 __IgnoreEls, _el));
    -       true ->
    -	   decode_privacy_els(__TopXMLNS, __IgnoreEls, _els, Lists,
    -			      Default, Active)
    -    end;
    -decode_privacy_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Lists, Default, Active) ->
    -    decode_privacy_els(__TopXMLNS, __IgnoreEls, _els, Lists,
    -		       Default, Active).
    -
    -encode_privacy({privacy, Lists, Default, Active},
    -	       _xmlns_attrs) ->
    -    _els = lists:reverse('encode_privacy_$lists'(Lists,
    -						 'encode_privacy_$default'(Default,
    -									   'encode_privacy_$active'(Active,
    -												    [])))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_privacy_$lists'([], _acc) -> _acc;
    -'encode_privacy_$lists'([Lists | _els], _acc) ->
    -    'encode_privacy_$lists'(_els,
    -			    [encode_privacy_list(Lists, []) | _acc]).
    -
    -'encode_privacy_$default'(undefined, _acc) -> _acc;
    -'encode_privacy_$default'(Default, _acc) ->
    -    [encode_privacy_default_list(Default, []) | _acc].
    -
    -'encode_privacy_$active'(undefined, _acc) -> _acc;
    -'encode_privacy_$active'(Active, _acc) ->
    -    [encode_privacy_active_list(Active, []) | _acc].
    -
    -decode_privacy_active_list(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"active">>, _attrs, _els}) ->
    -    Name = decode_privacy_active_list_attrs(__TopXMLNS,
    -					    _attrs, undefined),
    -    Name.
    -
    -decode_privacy_active_list_attrs(__TopXMLNS,
    -				 [{<<"name">>, _val} | _attrs], _Name) ->
    -    decode_privacy_active_list_attrs(__TopXMLNS, _attrs,
    -				     _val);
    -decode_privacy_active_list_attrs(__TopXMLNS,
    -				 [_ | _attrs], Name) ->
    -    decode_privacy_active_list_attrs(__TopXMLNS, _attrs,
    -				     Name);
    -decode_privacy_active_list_attrs(__TopXMLNS, [],
    -				 Name) ->
    -    decode_privacy_active_list_attr_name(__TopXMLNS, Name).
    -
    -encode_privacy_active_list(Name, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_privacy_active_list_attr_name(Name,
    -						  _xmlns_attrs),
    -    {xmlel, <<"active">>, _attrs, _els}.
    -
    -decode_privacy_active_list_attr_name(__TopXMLNS,
    -				     undefined) ->
    -    none;
    -decode_privacy_active_list_attr_name(__TopXMLNS,
    -				     _val) ->
    -    _val.
    -
    -encode_privacy_active_list_attr_name(none, _acc) ->
    -    _acc;
    -encode_privacy_active_list_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_privacy_default_list(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"default">>, _attrs, _els}) ->
    -    Name = decode_privacy_default_list_attrs(__TopXMLNS,
    -					     _attrs, undefined),
    -    Name.
    -
    -decode_privacy_default_list_attrs(__TopXMLNS,
    -				  [{<<"name">>, _val} | _attrs], _Name) ->
    -    decode_privacy_default_list_attrs(__TopXMLNS, _attrs,
    -				      _val);
    -decode_privacy_default_list_attrs(__TopXMLNS,
    -				  [_ | _attrs], Name) ->
    -    decode_privacy_default_list_attrs(__TopXMLNS, _attrs,
    -				      Name);
    -decode_privacy_default_list_attrs(__TopXMLNS, [],
    -				  Name) ->
    -    decode_privacy_default_list_attr_name(__TopXMLNS, Name).
    -
    -encode_privacy_default_list(Name, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = encode_privacy_default_list_attr_name(Name,
    -						   _xmlns_attrs),
    -    {xmlel, <<"default">>, _attrs, _els}.
    -
    -decode_privacy_default_list_attr_name(__TopXMLNS,
    -				      undefined) ->
    -    none;
    -decode_privacy_default_list_attr_name(__TopXMLNS,
    -				      _val) ->
    -    _val.
    -
    -encode_privacy_default_list_attr_name(none, _acc) ->
    -    _acc;
    -encode_privacy_default_list_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_privacy_list(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"list">>, _attrs, _els}) ->
    -    Items = decode_privacy_list_els(__TopXMLNS, __IgnoreEls,
    -				    _els, []),
    -    Name = decode_privacy_list_attrs(__TopXMLNS, _attrs,
    -				     undefined),
    -    {privacy_list, Name, Items}.
    -
    -decode_privacy_list_els(__TopXMLNS, __IgnoreEls, [],
    -			Items) ->
    -    lists:reverse(Items);
    -decode_privacy_list_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"item">>, _attrs, _} = _el | _els], Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_privacy_list_els(__TopXMLNS, __IgnoreEls, _els,
    -				   [decode_privacy_item(__TopXMLNS, __IgnoreEls,
    -							_el)
    -				    | Items]);
    -       true ->
    -	   decode_privacy_list_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Items)
    -    end;
    -decode_privacy_list_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Items) ->
    -    decode_privacy_list_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Items).
    -
    -decode_privacy_list_attrs(__TopXMLNS,
    -			  [{<<"name">>, _val} | _attrs], _Name) ->
    -    decode_privacy_list_attrs(__TopXMLNS, _attrs, _val);
    -decode_privacy_list_attrs(__TopXMLNS, [_ | _attrs],
    -			  Name) ->
    -    decode_privacy_list_attrs(__TopXMLNS, _attrs, Name);
    -decode_privacy_list_attrs(__TopXMLNS, [], Name) ->
    -    decode_privacy_list_attr_name(__TopXMLNS, Name).
    -
    -encode_privacy_list({privacy_list, Name, Items},
    -		    _xmlns_attrs) ->
    -    _els = lists:reverse('encode_privacy_list_$items'(Items,
    -						      [])),
    -    _attrs = encode_privacy_list_attr_name(Name,
    -					   _xmlns_attrs),
    -    {xmlel, <<"list">>, _attrs, _els}.
    -
    -'encode_privacy_list_$items'([], _acc) -> _acc;
    -'encode_privacy_list_$items'([Items | _els], _acc) ->
    -    'encode_privacy_list_$items'(_els,
    -				 [encode_privacy_item(Items, []) | _acc]).
    -
    -decode_privacy_list_attr_name(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"name">>, <<"list">>, __TopXMLNS}});
    -decode_privacy_list_attr_name(__TopXMLNS, _val) -> _val.
    -
    -encode_privacy_list_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_privacy_item(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"item">>, _attrs, _els}) ->
    -    Kinds = decode_privacy_item_els(__TopXMLNS, __IgnoreEls,
    -				    _els, []),
    -    {Action, Order, Type, Value} =
    -	decode_privacy_item_attrs(__TopXMLNS, _attrs, undefined,
    -				  undefined, undefined, undefined),
    -    {privacy_item, Order, Action, Type, Value, Kinds}.
    -
    -decode_privacy_item_els(__TopXMLNS, __IgnoreEls, [],
    -			Kinds) ->
    -    lists:reverse(Kinds);
    -decode_privacy_item_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"message">>, _attrs, _} = _el | _els],
    -			Kinds) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_privacy_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Kinds);
    -       true ->
    -	   decode_privacy_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Kinds)
    -    end;
    -decode_privacy_item_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"iq">>, _attrs, _} = _el | _els], Kinds) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_privacy_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Kinds);
    -       true ->
    -	   decode_privacy_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Kinds)
    -    end;
    -decode_privacy_item_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"presence-in">>, _attrs, _} = _el | _els],
    -			Kinds) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_privacy_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Kinds);
    -       true ->
    -	   decode_privacy_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Kinds)
    -    end;
    -decode_privacy_item_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlel, <<"presence-out">>, _attrs, _} = _el | _els],
    -			Kinds) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_privacy_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Kinds);
    -       true ->
    -	   decode_privacy_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				   Kinds)
    -    end;
    -decode_privacy_item_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Kinds) ->
    -    decode_privacy_item_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Kinds).
    -
    -decode_privacy_item_attrs(__TopXMLNS,
    -			  [{<<"action">>, _val} | _attrs], _Action, Order, Type,
    -			  Value) ->
    -    decode_privacy_item_attrs(__TopXMLNS, _attrs, _val,
    -			      Order, Type, Value);
    -decode_privacy_item_attrs(__TopXMLNS,
    -			  [{<<"order">>, _val} | _attrs], Action, _Order, Type,
    -			  Value) ->
    -    decode_privacy_item_attrs(__TopXMLNS, _attrs, Action,
    -			      _val, Type, Value);
    -decode_privacy_item_attrs(__TopXMLNS,
    -			  [{<<"type">>, _val} | _attrs], Action, Order, _Type,
    -			  Value) ->
    -    decode_privacy_item_attrs(__TopXMLNS, _attrs, Action,
    -			      Order, _val, Value);
    -decode_privacy_item_attrs(__TopXMLNS,
    -			  [{<<"value">>, _val} | _attrs], Action, Order, Type,
    -			  _Value) ->
    -    decode_privacy_item_attrs(__TopXMLNS, _attrs, Action,
    -			      Order, Type, _val);
    -decode_privacy_item_attrs(__TopXMLNS, [_ | _attrs],
    -			  Action, Order, Type, Value) ->
    -    decode_privacy_item_attrs(__TopXMLNS, _attrs, Action,
    -			      Order, Type, Value);
    -decode_privacy_item_attrs(__TopXMLNS, [], Action, Order,
    -			  Type, Value) ->
    -    {decode_privacy_item_attr_action(__TopXMLNS, Action),
    -     decode_privacy_item_attr_order(__TopXMLNS, Order),
    -     decode_privacy_item_attr_type(__TopXMLNS, Type),
    -     decode_privacy_item_attr_value(__TopXMLNS, Value)}.
    -
    -encode_privacy_item({privacy_item, Order, Action, Type,
    -		     Value, Kinds},
    -		    _xmlns_attrs) ->
    -    _els = lists:reverse('encode_privacy_item_$kinds'(Kinds,
    -						      [])),
    -    _attrs = encode_privacy_item_attr_value(Value,
    -					    encode_privacy_item_attr_type(Type,
    -									  encode_privacy_item_attr_order(Order,
    -													 encode_privacy_item_attr_action(Action,
    -																	 _xmlns_attrs)))),
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -'encode_privacy_item_$kinds'([], _acc) -> _acc;
    -'encode_privacy_item_$kinds'([message = Kinds | _els],
    -			     _acc) ->
    -    'encode_privacy_item_$kinds'(_els,
    -				 [encode_privacy_message(Kinds, []) | _acc]);
    -'encode_privacy_item_$kinds'([iq = Kinds | _els],
    -			     _acc) ->
    -    'encode_privacy_item_$kinds'(_els,
    -				 [encode_privacy_iq(Kinds, []) | _acc]);
    -'encode_privacy_item_$kinds'(['presence-in' = Kinds
    -			      | _els],
    -			     _acc) ->
    -    'encode_privacy_item_$kinds'(_els,
    -				 [encode_privacy_presence_in(Kinds, [])
    -				  | _acc]);
    -'encode_privacy_item_$kinds'(['presence-out' = Kinds
    -			      | _els],
    -			     _acc) ->
    -    'encode_privacy_item_$kinds'(_els,
    -				 [encode_privacy_presence_out(Kinds, [])
    -				  | _acc]).
    -
    -decode_privacy_item_attr_action(__TopXMLNS,
    -				undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"action">>, <<"item">>, __TopXMLNS}});
    -decode_privacy_item_attr_action(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val, [allow, deny]) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"action">>, <<"item">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_privacy_item_attr_action(_val, _acc) ->
    -    [{<<"action">>, enc_enum(_val)} | _acc].
    -
    -decode_privacy_item_attr_order(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"order">>, <<"item">>, __TopXMLNS}});
    -decode_privacy_item_attr_order(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"order">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_privacy_item_attr_order(_val, _acc) ->
    -    [{<<"order">>, enc_int(_val)} | _acc].
    -
    -decode_privacy_item_attr_type(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_privacy_item_attr_type(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val, [group, jid, subscription]) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"type">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_privacy_item_attr_type(undefined, _acc) -> _acc;
    -encode_privacy_item_attr_type(_val, _acc) ->
    -    [{<<"type">>, enc_enum(_val)} | _acc].
    -
    -decode_privacy_item_attr_value(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_privacy_item_attr_value(__TopXMLNS, _val) ->
    -    _val.
    -
    -encode_privacy_item_attr_value(undefined, _acc) -> _acc;
    -encode_privacy_item_attr_value(_val, _acc) ->
    -    [{<<"value">>, _val} | _acc].
    -
    -decode_privacy_presence_out(__TopXMLNS, __IgnoreEls,
    -			    {xmlel, <<"presence-out">>, _attrs, _els}) ->
    -    'presence-out'.
    -
    -encode_privacy_presence_out('presence-out',
    -			    _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"presence-out">>, _attrs, _els}.
    -
    -decode_privacy_presence_in(__TopXMLNS, __IgnoreEls,
    -			   {xmlel, <<"presence-in">>, _attrs, _els}) ->
    -    'presence-in'.
    -
    -encode_privacy_presence_in('presence-in',
    -			   _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"presence-in">>, _attrs, _els}.
    -
    -decode_privacy_iq(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"iq">>, _attrs, _els}) ->
    -    iq.
    -
    -encode_privacy_iq(iq, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"iq">>, _attrs, _els}.
    -
    -decode_privacy_message(__TopXMLNS, __IgnoreEls,
    -		       {xmlel, <<"message">>, _attrs, _els}) ->
    -    message.
    -
    -encode_privacy_message(message, _xmlns_attrs) ->
    -    _els = [],
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"message">>, _attrs, _els}.
    -
    -decode_roster(__TopXMLNS, __IgnoreEls,
    -	      {xmlel, <<"query">>, _attrs, _els}) ->
    -    Items = decode_roster_els(__TopXMLNS, __IgnoreEls, _els,
    -			      []),
    -    Ver = decode_roster_attrs(__TopXMLNS, _attrs,
    -			      undefined),
    -    {roster, Items, Ver}.
    -
    -decode_roster_els(__TopXMLNS, __IgnoreEls, [], Items) ->
    -    lists:reverse(Items);
    -decode_roster_els(__TopXMLNS, __IgnoreEls,
    -		  [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_roster_els(__TopXMLNS, __IgnoreEls, _els,
    -			     [decode_roster_item(__TopXMLNS, __IgnoreEls, _el)
    -			      | Items]);
    -       true ->
    -	   decode_roster_els(__TopXMLNS, __IgnoreEls, _els, Items)
    -    end;
    -decode_roster_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		  Items) ->
    -    decode_roster_els(__TopXMLNS, __IgnoreEls, _els, Items).
    -
    -decode_roster_attrs(__TopXMLNS,
    -		    [{<<"ver">>, _val} | _attrs], _Ver) ->
    -    decode_roster_attrs(__TopXMLNS, _attrs, _val);
    -decode_roster_attrs(__TopXMLNS, [_ | _attrs], Ver) ->
    -    decode_roster_attrs(__TopXMLNS, _attrs, Ver);
    -decode_roster_attrs(__TopXMLNS, [], Ver) ->
    -    decode_roster_attr_ver(__TopXMLNS, Ver).
    -
    -encode_roster({roster, Items, Ver}, _xmlns_attrs) ->
    -    _els = lists:reverse('encode_roster_$items'(Items, [])),
    -    _attrs = encode_roster_attr_ver(Ver, _xmlns_attrs),
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_roster_$items'([], _acc) -> _acc;
    -'encode_roster_$items'([Items | _els], _acc) ->
    -    'encode_roster_$items'(_els,
    -			   [encode_roster_item(Items, []) | _acc]).
    -
    -decode_roster_attr_ver(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_roster_attr_ver(__TopXMLNS, _val) -> _val.
    -
    -encode_roster_attr_ver(undefined, _acc) -> _acc;
    -encode_roster_attr_ver(_val, _acc) ->
    -    [{<<"ver">>, _val} | _acc].
    -
    -decode_roster_item(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"item">>, _attrs, _els}) ->
    -    Groups = decode_roster_item_els(__TopXMLNS, __IgnoreEls,
    -				    _els, []),
    -    {Jid, Name, Subscription, Ask} =
    -	decode_roster_item_attrs(__TopXMLNS, _attrs, undefined,
    -				 undefined, undefined, undefined),
    -    {roster_item, Jid, Name, Groups, Subscription, Ask}.
    -
    -decode_roster_item_els(__TopXMLNS, __IgnoreEls, [],
    -		       Groups) ->
    -    lists:reverse(Groups);
    -decode_roster_item_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlel, <<"group">>, _attrs, _} = _el | _els],
    -		       Groups) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_roster_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				  [decode_roster_group(__TopXMLNS, __IgnoreEls,
    -						       _el)
    -				   | Groups]);
    -       true ->
    -	   decode_roster_item_els(__TopXMLNS, __IgnoreEls, _els,
    -				  Groups)
    -    end;
    -decode_roster_item_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Groups) ->
    -    decode_roster_item_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Groups).
    -
    -decode_roster_item_attrs(__TopXMLNS,
    -			 [{<<"jid">>, _val} | _attrs], _Jid, Name, Subscription,
    -			 Ask) ->
    -    decode_roster_item_attrs(__TopXMLNS, _attrs, _val, Name,
    -			     Subscription, Ask);
    -decode_roster_item_attrs(__TopXMLNS,
    -			 [{<<"name">>, _val} | _attrs], Jid, _Name,
    -			 Subscription, Ask) ->
    -    decode_roster_item_attrs(__TopXMLNS, _attrs, Jid, _val,
    -			     Subscription, Ask);
    -decode_roster_item_attrs(__TopXMLNS,
    -			 [{<<"subscription">>, _val} | _attrs], Jid, Name,
    -			 _Subscription, Ask) ->
    -    decode_roster_item_attrs(__TopXMLNS, _attrs, Jid, Name,
    -			     _val, Ask);
    -decode_roster_item_attrs(__TopXMLNS,
    -			 [{<<"ask">>, _val} | _attrs], Jid, Name, Subscription,
    -			 _Ask) ->
    -    decode_roster_item_attrs(__TopXMLNS, _attrs, Jid, Name,
    -			     Subscription, _val);
    -decode_roster_item_attrs(__TopXMLNS, [_ | _attrs], Jid,
    -			 Name, Subscription, Ask) ->
    -    decode_roster_item_attrs(__TopXMLNS, _attrs, Jid, Name,
    -			     Subscription, Ask);
    -decode_roster_item_attrs(__TopXMLNS, [], Jid, Name,
    -			 Subscription, Ask) ->
    -    {decode_roster_item_attr_jid(__TopXMLNS, Jid),
    -     decode_roster_item_attr_name(__TopXMLNS, Name),
    -     decode_roster_item_attr_subscription(__TopXMLNS,
    -					  Subscription),
    -     decode_roster_item_attr_ask(__TopXMLNS, Ask)}.
    -
    -encode_roster_item({roster_item, Jid, Name, Groups,
    -		    Subscription, Ask},
    -		   _xmlns_attrs) ->
    -    _els =
    -	lists:reverse('encode_roster_item_$groups'(Groups, [])),
    -    _attrs = encode_roster_item_attr_ask(Ask,
    -					 encode_roster_item_attr_subscription(Subscription,
    -									      encode_roster_item_attr_name(Name,
    -													   encode_roster_item_attr_jid(Jid,
    -																       _xmlns_attrs)))),
    -    {xmlel, <<"item">>, _attrs, _els}.
    -
    -'encode_roster_item_$groups'([], _acc) -> _acc;
    -'encode_roster_item_$groups'([Groups | _els], _acc) ->
    -    'encode_roster_item_$groups'(_els,
    -				 [encode_roster_group(Groups, []) | _acc]).
    -
    -decode_roster_item_attr_jid(__TopXMLNS, undefined) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_attr, <<"jid">>, <<"item">>, __TopXMLNS}});
    -decode_roster_item_attr_jid(__TopXMLNS, _val) ->
    -    case catch dec_jid(_val) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"jid">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_roster_item_attr_jid(_val, _acc) ->
    -    [{<<"jid">>, enc_jid(_val)} | _acc].
    -
    -decode_roster_item_attr_name(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_roster_item_attr_name(__TopXMLNS, _val) -> _val.
    -
    -encode_roster_item_attr_name(undefined, _acc) -> _acc;
    -encode_roster_item_attr_name(_val, _acc) ->
    -    [{<<"name">>, _val} | _acc].
    -
    -decode_roster_item_attr_subscription(__TopXMLNS,
    -				     undefined) ->
    -    none;
    -decode_roster_item_attr_subscription(__TopXMLNS,
    -				     _val) ->
    -    case catch dec_enum(_val,
    -			[none, to, from, both, remove])
    -	of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"subscription">>, <<"item">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_roster_item_attr_subscription(none, _acc) ->
    -    _acc;
    -encode_roster_item_attr_subscription(_val, _acc) ->
    -    [{<<"subscription">>, enc_enum(_val)} | _acc].
    -
    -decode_roster_item_attr_ask(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_roster_item_attr_ask(__TopXMLNS, _val) ->
    -    case catch dec_enum(_val, [subscribe]) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"ask">>, <<"item">>, __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_roster_item_attr_ask(undefined, _acc) -> _acc;
    -encode_roster_item_attr_ask(_val, _acc) ->
    -    [{<<"ask">>, enc_enum(_val)} | _acc].
    -
    -decode_roster_group(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"group">>, _attrs, _els}) ->
    -    Cdata = decode_roster_group_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_roster_group_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_roster_group_cdata(__TopXMLNS, Cdata);
    -decode_roster_group_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_roster_group_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_roster_group_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_roster_group_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_roster_group(Cdata, _xmlns_attrs) ->
    -    _els = encode_roster_group_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"group">>, _attrs, _els}.
    -
    -decode_roster_group_cdata(__TopXMLNS, <<>>) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_cdata, <<>>, <<"group">>, __TopXMLNS}});
    -decode_roster_group_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_roster_group_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_version(__TopXMLNS, __IgnoreEls,
    -	       {xmlel, <<"query">>, _attrs, _els}) ->
    -    {Ver, Os, Name} = decode_version_els(__TopXMLNS,
    -					 __IgnoreEls, _els, undefined,
    -					 undefined, undefined),
    -    {version, Name, Ver, Os}.
    -
    -decode_version_els(__TopXMLNS, __IgnoreEls, [], Ver, Os,
    -		   Name) ->
    -    {Ver, Os, Name};
    -decode_version_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"name">>, _attrs, _} = _el | _els], Ver, Os,
    -		   Name) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_version_els(__TopXMLNS, __IgnoreEls, _els, Ver,
    -			      Os,
    -			      decode_version_name(__TopXMLNS, __IgnoreEls,
    -						  _el));
    -       true ->
    -	   decode_version_els(__TopXMLNS, __IgnoreEls, _els, Ver,
    -			      Os, Name)
    -    end;
    -decode_version_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"version">>, _attrs, _} = _el | _els], Ver,
    -		   Os, Name) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_version_els(__TopXMLNS, __IgnoreEls, _els,
    -			      decode_version_ver(__TopXMLNS, __IgnoreEls, _el),
    -			      Os, Name);
    -       true ->
    -	   decode_version_els(__TopXMLNS, __IgnoreEls, _els, Ver,
    -			      Os, Name)
    -    end;
    -decode_version_els(__TopXMLNS, __IgnoreEls,
    -		   [{xmlel, <<"os">>, _attrs, _} = _el | _els], Ver, Os,
    -		   Name) ->
    -    _xmlns = get_attr(<<"xmlns">>, _attrs),
    -    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
    -	   decode_version_els(__TopXMLNS, __IgnoreEls, _els, Ver,
    -			      decode_version_os(__TopXMLNS, __IgnoreEls, _el),
    -			      Name);
    -       true ->
    -	   decode_version_els(__TopXMLNS, __IgnoreEls, _els, Ver,
    -			      Os, Name)
    -    end;
    -decode_version_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		   Ver, Os, Name) ->
    -    decode_version_els(__TopXMLNS, __IgnoreEls, _els, Ver,
    -		       Os, Name).
    -
    -encode_version({version, Name, Ver, Os},
    -	       _xmlns_attrs) ->
    -    _els = lists:reverse('encode_version_$ver'(Ver,
    -					       'encode_version_$os'(Os,
    -								    'encode_version_$name'(Name,
    -											   [])))),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -'encode_version_$ver'(undefined, _acc) -> _acc;
    -'encode_version_$ver'(Ver, _acc) ->
    -    [encode_version_ver(Ver, []) | _acc].
    -
    -'encode_version_$os'(undefined, _acc) -> _acc;
    -'encode_version_$os'(Os, _acc) ->
    -    [encode_version_os(Os, []) | _acc].
    -
    -'encode_version_$name'(undefined, _acc) -> _acc;
    -'encode_version_$name'(Name, _acc) ->
    -    [encode_version_name(Name, []) | _acc].
    -
    -decode_version_os(__TopXMLNS, __IgnoreEls,
    -		  {xmlel, <<"os">>, _attrs, _els}) ->
    -    Cdata = decode_version_os_els(__TopXMLNS, __IgnoreEls,
    -				  _els, <<>>),
    -    Cdata.
    -
    -decode_version_os_els(__TopXMLNS, __IgnoreEls, [],
    -		      Cdata) ->
    -    decode_version_os_cdata(__TopXMLNS, Cdata);
    -decode_version_os_els(__TopXMLNS, __IgnoreEls,
    -		      [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_version_os_els(__TopXMLNS, __IgnoreEls, _els,
    -			  <>);
    -decode_version_os_els(__TopXMLNS, __IgnoreEls,
    -		      [_ | _els], Cdata) ->
    -    decode_version_os_els(__TopXMLNS, __IgnoreEls, _els,
    -			  Cdata).
    -
    -encode_version_os(Cdata, _xmlns_attrs) ->
    -    _els = encode_version_os_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"os">>, _attrs, _els}.
    -
    -decode_version_os_cdata(__TopXMLNS, <<>>) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_cdata, <<>>, <<"os">>, __TopXMLNS}});
    -decode_version_os_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_version_os_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_version_ver(__TopXMLNS, __IgnoreEls,
    -		   {xmlel, <<"version">>, _attrs, _els}) ->
    -    Cdata = decode_version_ver_els(__TopXMLNS, __IgnoreEls,
    -				   _els, <<>>),
    -    Cdata.
    -
    -decode_version_ver_els(__TopXMLNS, __IgnoreEls, [],
    -		       Cdata) ->
    -    decode_version_ver_cdata(__TopXMLNS, Cdata);
    -decode_version_ver_els(__TopXMLNS, __IgnoreEls,
    -		       [{xmlcdata, _data} | _els], Cdata) ->
    -    decode_version_ver_els(__TopXMLNS, __IgnoreEls, _els,
    -			   <>);
    -decode_version_ver_els(__TopXMLNS, __IgnoreEls,
    -		       [_ | _els], Cdata) ->
    -    decode_version_ver_els(__TopXMLNS, __IgnoreEls, _els,
    -			   Cdata).
    -
    -encode_version_ver(Cdata, _xmlns_attrs) ->
    -    _els = encode_version_ver_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"version">>, _attrs, _els}.
    -
    -decode_version_ver_cdata(__TopXMLNS, <<>>) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_cdata, <<>>, <<"version">>, __TopXMLNS}});
    -decode_version_ver_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_version_ver_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_version_name(__TopXMLNS, __IgnoreEls,
    -		    {xmlel, <<"name">>, _attrs, _els}) ->
    -    Cdata = decode_version_name_els(__TopXMLNS, __IgnoreEls,
    -				    _els, <<>>),
    -    Cdata.
    -
    -decode_version_name_els(__TopXMLNS, __IgnoreEls, [],
    -			Cdata) ->
    -    decode_version_name_cdata(__TopXMLNS, Cdata);
    -decode_version_name_els(__TopXMLNS, __IgnoreEls,
    -			[{xmlcdata, _data} | _els], Cdata) ->
    -    decode_version_name_els(__TopXMLNS, __IgnoreEls, _els,
    -			    <>);
    -decode_version_name_els(__TopXMLNS, __IgnoreEls,
    -			[_ | _els], Cdata) ->
    -    decode_version_name_els(__TopXMLNS, __IgnoreEls, _els,
    -			    Cdata).
    -
    -encode_version_name(Cdata, _xmlns_attrs) ->
    -    _els = encode_version_name_cdata(Cdata, []),
    -    _attrs = _xmlns_attrs,
    -    {xmlel, <<"name">>, _attrs, _els}.
    -
    -decode_version_name_cdata(__TopXMLNS, <<>>) ->
    -    erlang:error({xmpp_codec,
    -		  {missing_cdata, <<>>, <<"name">>, __TopXMLNS}});
    -decode_version_name_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_version_name_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    -
    -decode_last(__TopXMLNS, __IgnoreEls,
    -	    {xmlel, <<"query">>, _attrs, _els}) ->
    -    Text = decode_last_els(__TopXMLNS, __IgnoreEls, _els,
    -			   <<>>),
    -    Seconds = decode_last_attrs(__TopXMLNS, _attrs,
    -				undefined),
    -    {last, Seconds, Text}.
    -
    -decode_last_els(__TopXMLNS, __IgnoreEls, [], Text) ->
    -    decode_last_cdata(__TopXMLNS, Text);
    -decode_last_els(__TopXMLNS, __IgnoreEls,
    -		[{xmlcdata, _data} | _els], Text) ->
    -    decode_last_els(__TopXMLNS, __IgnoreEls, _els,
    -		    <>);
    -decode_last_els(__TopXMLNS, __IgnoreEls, [_ | _els],
    -		Text) ->
    -    decode_last_els(__TopXMLNS, __IgnoreEls, _els, Text).
    -
    -decode_last_attrs(__TopXMLNS,
    -		  [{<<"seconds">>, _val} | _attrs], _Seconds) ->
    -    decode_last_attrs(__TopXMLNS, _attrs, _val);
    -decode_last_attrs(__TopXMLNS, [_ | _attrs], Seconds) ->
    -    decode_last_attrs(__TopXMLNS, _attrs, Seconds);
    -decode_last_attrs(__TopXMLNS, [], Seconds) ->
    -    decode_last_attr_seconds(__TopXMLNS, Seconds).
    -
    -encode_last({last, Seconds, Text}, _xmlns_attrs) ->
    -    _els = encode_last_cdata(Text, []),
    -    _attrs = encode_last_attr_seconds(Seconds,
    -				      _xmlns_attrs),
    -    {xmlel, <<"query">>, _attrs, _els}.
    -
    -decode_last_attr_seconds(__TopXMLNS, undefined) ->
    -    undefined;
    -decode_last_attr_seconds(__TopXMLNS, _val) ->
    -    case catch dec_int(_val, 0, infinity) of
    -      {'EXIT', _} ->
    -	  erlang:error({xmpp_codec,
    -			{bad_attr_value, <<"seconds">>, <<"query">>,
    -			 __TopXMLNS}});
    -      _res -> _res
    -    end.
    -
    -encode_last_attr_seconds(undefined, _acc) -> _acc;
    -encode_last_attr_seconds(_val, _acc) ->
    -    [{<<"seconds">>, enc_int(_val)} | _acc].
    -
    -decode_last_cdata(__TopXMLNS, <<>>) -> undefined;
    -decode_last_cdata(__TopXMLNS, _val) -> _val.
    -
    -encode_last_cdata(undefined, _acc) -> _acc;
    -encode_last_cdata(_val, _acc) ->
    -    [{xmlcdata, _val} | _acc].
    diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl
    deleted file mode 100644
    index 6d4b750b6..000000000
    --- a/tools/xmpp_codec.hrl
    +++ /dev/null
    @@ -1,537 +0,0 @@
    -%% Created automatically by XML generator (fxml_gen.erl)
    -%% Source: xmpp_codec.spec
    -
    --record(chatstate, {type :: active | composing | gone | inactive | paused}).
    -
    --record(csi, {type :: active | inactive}).
    -
    --record(feature_register, {}).
    -
    --record(sasl_success, {text :: any()}).
    -
    --record(mam_result, {xmlns :: binary(),
    -                     queryid :: binary(),
    -                     id :: binary(),
    -                     sub_els = [] :: [any()]}).
    -
    --record(rsm_first, {index :: non_neg_integer(),
    -                    data :: binary()}).
    -
    --record(text, {lang :: binary(),
    -               data :: binary()}).
    -
    --record(streamhost, {jid :: any(),
    -                     host :: binary(),
    -                     port = 1080 :: non_neg_integer()}).
    -
    --record(sm_resume, {h :: non_neg_integer(),
    -                    previd :: binary(),
    -                    xmlns :: binary()}).
    -
    --record(carbons_enable, {}).
    -
    --record(carbons_private, {}).
    -
    --record(pubsub_unsubscribe, {node :: binary(),
    -                             jid :: any(),
    -                             subid :: binary()}).
    -
    --record(mix_leave, {}).
    -
    --record(ping, {}).
    -
    --record(delay, {stamp :: any(),
    -                from :: any()}).
    -
    --record(muc_history, {maxchars :: non_neg_integer(),
    -                      maxstanzas :: non_neg_integer(),
    -                      seconds :: non_neg_integer(),
    -                      since :: any()}).
    -
    --record(pubsub_affiliation, {node :: binary(),
    -                             type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}).
    -
    --record(muc_decline, {reason :: binary(),
    -                      from :: any(),
    -                      to :: any()}).
    -
    --record(sm_a, {h :: non_neg_integer(),
    -               xmlns :: binary()}).
    -
    --record(starttls_proceed, {}).
    -
    --record(sm_resumed, {h :: non_neg_integer(),
    -                     previd :: binary(),
    -                     xmlns :: binary()}).
    -
    --record(forwarded, {delay :: #delay{},
    -                    sub_els = [] :: [any()]}).
    -
    --record(sm_enable, {max :: non_neg_integer(),
    -                    resume = false :: any(),
    -                    xmlns :: binary()}).
    -
    --record(starttls_failure, {}).
    -
    --record(sasl_challenge, {text :: any()}).
    -
    --record(gone, {uri :: binary()}).
    -
    --record(private, {xml_els = [] :: [any()]}).
    -
    --record(p1_ack, {}).
    -
    --record(feature_sm, {xmlns :: binary()}).
    -
    --record(pubsub_item, {id :: binary(),
    -                      xml_els = [] :: [any()]}).
    -
    --record(pubsub_publish, {node :: binary(),
    -                         items = [] :: [#pubsub_item{}]}).
    -
    --record(roster_item, {jid :: any(),
    -                      name :: binary(),
    -                      groups = [] :: [binary()],
    -                      subscription = none :: 'both' | 'from' | 'none' | 'remove' | 'to',
    -                      ask :: 'subscribe'}).
    -
    --record(roster, {items = [] :: [#roster_item{}],
    -                 ver :: binary()}).
    -
    --record(pubsub_event_item, {id :: binary(),
    -                            node :: binary(),
    -                            publisher :: binary(),
    -                            xml_els = [] :: [any()]}).
    -
    --record(sm_r, {xmlns :: binary()}).
    -
    --record(muc_actor, {jid :: any(),
    -                    nick :: binary()}).
    -
    --record(stat, {name :: binary(),
    -               units :: binary(),
    -               value :: binary(),
    -               error = [] :: [{integer(),'undefined' | binary()}]}).
    -
    --record('see-other-host', {host :: binary()}).
    -
    --record(compress, {methods = [] :: [binary()]}).
    -
    --record(starttls, {required = false :: boolean()}).
    -
    --record(last, {seconds :: non_neg_integer(),
    -               text :: binary()}).
    -
    --record(redirect, {uri :: binary()}).
    -
    --record(sm_enabled, {id :: binary(),
    -                     location :: binary(),
    -                     max :: non_neg_integer(),
    -                     resume = false :: any(),
    -                     xmlns :: binary()}).
    -
    --record(pubsub_event_items, {node :: binary(),
    -                             retract = [] :: [binary()],
    -                             items = [] :: [#pubsub_event_item{}]}).
    -
    --record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
    -
    --record(sasl_response, {text :: any()}).
    -
    --record(pubsub_subscribe, {node :: binary(),
    -                           jid :: any()}).
    -
    --record(sasl_auth, {mechanism :: binary(),
    -                    text :: any()}).
    -
    --record(p1_push, {}).
    -
    --record(feature_csi, {xmlns :: binary()}).
    -
    --record(muc_user_destroy, {reason :: binary(),
    -                           jid :: any()}).
    -
    --record(disco_item, {jid :: any(),
    -                     name :: binary(),
    -                     node :: binary()}).
    -
    --record(disco_items, {node :: binary(),
    -                      items = [] :: [#disco_item{}]}).
    -
    --record(unblock, {items = [] :: [any()]}).
    -
    --record(block, {items = [] :: [any()]}).
    -
    --record(session, {}).
    -
    --record(compression, {methods = [] :: [binary()]}).
    -
    --record(muc_owner_destroy, {jid :: any(),
    -                            reason :: binary(),
    -                            password :: binary()}).
    -
    --record(pubsub_subscription, {jid :: any(),
    -                              node :: binary(),
    -                              subid :: binary(),
    -                              type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}).
    -
    --record(muc_item, {actor :: #muc_actor{},
    -                   continue :: binary(),
    -                   reason :: binary(),
    -                   affiliation :: 'admin' | 'member' | 'none' | 'outcast' | 'owner',
    -                   role :: 'moderator' | 'none' | 'participant' | 'visitor',
    -                   jid :: any(),
    -                   nick :: binary()}).
    -
    --record(muc_admin, {items = [] :: [#muc_item{}]}).
    -
    --record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}).
    -
    --record(mam_prefs, {xmlns :: binary(),
    -                    default :: 'always' | 'never' | 'roster',
    -                    always = [] :: [any()],
    -                    never = [] :: [any()]}).
    -
    --record(caps, {hash :: binary(),
    -               node :: binary(),
    -               ver :: any()}).
    -
    --record(muc, {history :: #muc_history{},
    -              password :: binary()}).
    -
    --record(stream_features, {sub_els = [] :: [any()]}).
    -
    --record(stats, {stat = [] :: [#stat{}]}).
    -
    --record(pubsub_items, {node :: binary(),
    -                       max_items :: non_neg_integer(),
    -                       subid :: binary(),
    -                       items = [] :: [#pubsub_item{}]}).
    -
    --record(carbons_sent, {forwarded :: #forwarded{}}).
    -
    --record(mam_archived, {by :: any(),
    -                       id :: binary()}).
    -
    --record(p1_rebind, {}).
    -
    --record(compress_failure, {reason :: 'processing-failed' | 'setup-failed' | 'unsupported-method'}).
    -
    --record(sasl_abort, {}).
    -
    --record(vcard_email, {home = false :: boolean(),
    -                      work = false :: boolean(),
    -                      internet = false :: boolean(),
    -                      pref = false :: boolean(),
    -                      x400 = false :: boolean(),
    -                      userid :: binary()}).
    -
    --record(carbons_received, {forwarded :: #forwarded{}}).
    -
    --record(pubsub_retract, {node :: binary(),
    -                         notify = false :: any(),
    -                         items = [] :: [#pubsub_item{}]}).
    -
    --record(mix_participant, {jid :: any(),
    -                          nick :: binary()}).
    -
    --record(vcard_geo, {lat :: binary(),
    -                    lon :: binary()}).
    -
    --record(compressed, {}).
    -
    --record(sasl_failure, {reason :: 'aborted' | 'account-disabled' | 'credentials-expired' | 'encryption-required' | 'incorrect-encoding' | 'invalid-authzid' | 'invalid-mechanism' | 'malformed-request' | 'mechanism-too-weak' | 'not-authorized' | 'temporary-auth-failure',
    -                       text = [] :: [#text{}]}).
    -
    --record(block_list, {}).
    -
    --record(xdata_field, {label :: binary(),
    -                      type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
    -                      var :: binary(),
    -                      required = false :: boolean(),
    -                      desc :: binary(),
    -                      values = [] :: [binary()],
    -                      options = [] :: [binary()]}).
    -
    --record(version, {name :: binary(),
    -                  ver :: binary(),
    -                  os :: binary()}).
    -
    --record(muc_invite, {reason :: binary(),
    -                     from :: any(),
    -                     to :: any()}).
    -
    --record(bind, {jid :: any(),
    -               resource :: any()}).
    -
    --record(muc_user, {decline :: #muc_decline{},
    -                   destroy :: #muc_user_destroy{},
    -                   invites = [] :: [#muc_invite{}],
    -                   items = [] :: [#muc_item{}],
    -                   status_codes = [] :: [pos_integer()],
    -                   password :: binary()}).
    -
    --record(vcard_xupdate, {photo :: binary()}).
    -
    --record(carbons_disable, {}).
    -
    --record(bytestreams, {hosts = [] :: [#streamhost{}],
    -                      used :: any(),
    -                      activate :: any(),
    -                      dstaddr :: binary(),
    -                      mode = tcp :: 'tcp' | 'udp',
    -                      sid :: binary()}).
    -
    --record(vcard_org, {name :: binary(),
    -                    units = [] :: [binary()]}).
    -
    --record(rsm_set, {'after' :: binary(),
    -                  before :: 'none' | binary(),
    -                  count :: non_neg_integer(),
    -                  first :: #rsm_first{},
    -                  index :: non_neg_integer(),
    -                  last :: binary(),
    -                  max :: non_neg_integer()}).
    -
    --record(mam_fin, {id :: binary(),
    -                  rsm :: #rsm_set{},
    -                  stable :: any(),
    -                  complete :: any()}).
    -
    --record(vcard_tel, {home = false :: boolean(),
    -                    work = false :: boolean(),
    -                    voice = false :: boolean(),
    -                    fax = false :: boolean(),
    -                    pager = false :: boolean(),
    -                    msg = false :: boolean(),
    -                    cell = false :: boolean(),
    -                    video = false :: boolean(),
    -                    bbs = false :: boolean(),
    -                    modem = false :: boolean(),
    -                    isdn = false :: boolean(),
    -                    pcs = false :: boolean(),
    -                    pref = false :: boolean(),
    -                    number :: binary()}).
    -
    --record(vcard_key, {type :: binary(),
    -                    cred :: binary()}).
    -
    --record(vcard_name, {family :: binary(),
    -                     given :: binary(),
    -                     middle :: binary(),
    -                     prefix :: binary(),
    -                     suffix :: binary()}).
    -
    --record(identity, {category :: binary(),
    -                   type :: binary(),
    -                   lang :: binary(),
    -                   name :: binary()}).
    -
    --record(bookmark_conference, {name :: binary(),
    -                              jid :: any(),
    -                              autojoin = false :: any(),
    -                              nick :: binary(),
    -                              password :: binary()}).
    -
    --record(bookmark_url, {name :: binary(),
    -                       url :: binary()}).
    -
    --record(bookmark_storage, {conference = [] :: [#bookmark_conference{}],
    -                           url = [] :: [#bookmark_url{}]}).
    -
    --record(vcard_sound, {phonetic :: binary(),
    -                      binval :: any(),
    -                      extval :: binary()}).
    -
    --record(vcard_photo, {type :: binary(),
    -                      binval :: any(),
    -                      extval :: binary()}).
    -
    --record(vcard_label, {home = false :: boolean(),
    -                      work = false :: boolean(),
    -                      postal = false :: boolean(),
    -                      parcel = false :: boolean(),
    -                      dom = false :: boolean(),
    -                      intl = false :: boolean(),
    -                      pref = false :: boolean(),
    -                      line = [] :: [binary()]}).
    -
    --record(vcard_adr, {home = false :: boolean(),
    -                    work = false :: boolean(),
    -                    postal = false :: boolean(),
    -                    parcel = false :: boolean(),
    -                    dom = false :: boolean(),
    -                    intl = false :: boolean(),
    -                    pref = false :: boolean(),
    -                    pobox :: binary(),
    -                    extadd :: binary(),
    -                    street :: binary(),
    -                    locality :: binary(),
    -                    region :: binary(),
    -                    pcode :: binary(),
    -                    ctry :: binary()}).
    -
    --record(xdata, {type :: 'cancel' | 'form' | 'result' | 'submit',
    -                instructions = [] :: [binary()],
    -                title :: binary(),
    -                reported :: [#xdata_field{}],
    -                items = [] :: [[#xdata_field{}]],
    -                fields = [] :: [#xdata_field{}]}).
    -
    --record(mam_query, {xmlns :: binary(),
    -                    id :: binary(),
    -                    start :: any(),
    -                    'end' :: any(),
    -                    with :: any(),
    -                    rsm :: #rsm_set{},
    -                    xdata :: #xdata{}}).
    -
    --record(muc_owner, {destroy :: #muc_owner_destroy{},
    -                    config :: #xdata{}}).
    -
    --record(pubsub_options, {node :: binary(),
    -                         jid :: any(),
    -                         subid :: binary(),
    -                         xdata :: #xdata{}}).
    -
    --record(pubsub, {subscriptions :: {'none' | binary(),[#pubsub_subscription{}]},
    -                 affiliations :: [#pubsub_affiliation{}],
    -                 publish :: #pubsub_publish{},
    -                 subscribe :: #pubsub_subscribe{},
    -                 unsubscribe :: #pubsub_unsubscribe{},
    -                 options :: #pubsub_options{},
    -                 items :: #pubsub_items{},
    -                 retract :: #pubsub_retract{}}).
    -
    --record(register, {registered = false :: boolean(),
    -                   remove = false :: boolean(),
    -                   instructions :: binary(),
    -                   username :: 'none' | binary(),
    -                   nick :: 'none' | binary(),
    -                   password :: 'none' | binary(),
    -                   name :: 'none' | binary(),
    -                   first :: 'none' | binary(),
    -                   last :: 'none' | binary(),
    -                   email :: 'none' | binary(),
    -                   address :: 'none' | binary(),
    -                   city :: 'none' | binary(),
    -                   state :: 'none' | binary(),
    -                   zip :: 'none' | binary(),
    -                   phone :: 'none' | binary(),
    -                   url :: 'none' | binary(),
    -                   date :: 'none' | binary(),
    -                   misc :: 'none' | binary(),
    -                   text :: 'none' | binary(),
    -                   key :: 'none' | binary(),
    -                   xdata :: #xdata{}}).
    -
    --record(disco_info, {node :: binary(),
    -                     identities = [] :: [#identity{}],
    -                     features = [] :: [binary()],
    -                     xdata = [] :: [#xdata{}]}).
    -
    --record(offline_item, {node :: binary(),
    -                       action :: 'remove' | 'view'}).
    -
    --record(offline, {items = [] :: [#offline_item{}],
    -                  purge = false :: boolean(),
    -                  fetch = false :: boolean()}).
    -
    --record(sasl_mechanisms, {list = [] :: [binary()]}).
    -
    --record(sm_failed, {reason :: atom() | #gone{} | #redirect{},
    -                    h :: non_neg_integer(),
    -                    xmlns :: binary()}).
    -
    --record(error, {type :: 'auth' | 'cancel' | 'continue' | 'modify' | 'wait',
    -                by :: binary(),
    -                reason :: atom() | #gone{} | #redirect{},
    -                text :: #text{}}).
    -
    --record(presence, {id :: binary(),
    -                   type :: 'error' | 'probe' | 'subscribe' | 'subscribed' | 'unavailable' | 'unsubscribe' | 'unsubscribed',
    -                   lang :: binary(),
    -                   from :: any(),
    -                   to :: any(),
    -                   show :: 'away' | 'chat' | 'dnd' | 'xa',
    -                   status = [] :: [#text{}],
    -                   priority :: integer(),
    -                   error :: #error{},
    -                   sub_els = [] :: [any()]}).
    -
    --record(message, {id :: binary(),
    -                  type = normal :: 'chat' | 'error' | 'groupchat' | 'headline' | 'normal',
    -                  lang :: binary(),
    -                  from :: any(),
    -                  to :: any(),
    -                  subject = [] :: [#text{}],
    -                  body = [] :: [#text{}],
    -                  thread :: binary(),
    -                  error :: #error{},
    -                  sub_els = [] :: [any()]}).
    -
    --record(iq, {id :: binary(),
    -             type :: 'error' | 'get' | 'result' | 'set',
    -             lang :: binary(),
    -             from :: any(),
    -             to :: any(),
    -             error :: #error{},
    -             sub_els = [] :: [any()]}).
    -
    --record(mix_join, {jid :: any(),
    -                   subscribe = [] :: [binary()]}).
    -
    --record(privacy_item, {order :: non_neg_integer(),
    -                       action :: 'allow' | 'deny',
    -                       type :: 'group' | 'jid' | 'subscription',
    -                       value :: binary(),
    -                       kinds = [] :: ['iq' | 'message' | 'presence-in' | 'presence-out']}).
    -
    --record(privacy_list, {name :: binary(),
    -                       items = [] :: [#privacy_item{}]}).
    -
    --record(privacy, {lists = [] :: [#privacy_list{}],
    -                  default :: 'none' | binary(),
    -                  active :: 'none' | binary()}).
    -
    --record(stream_error, {reason :: atom() | #'see-other-host'{},
    -                       text :: #text{}}).
    -
    --record(vcard_logo, {type :: binary(),
    -                     binval :: any(),
    -                     extval :: binary()}).
    -
    --record(vcard, {version :: binary(),
    -                fn :: binary(),
    -                n :: #vcard_name{},
    -                nickname :: binary(),
    -                photo :: #vcard_photo{},
    -                bday :: binary(),
    -                adr = [] :: [#vcard_adr{}],
    -                label = [] :: [#vcard_label{}],
    -                tel = [] :: [#vcard_tel{}],
    -                email = [] :: [#vcard_email{}],
    -                jabberid :: binary(),
    -                mailer :: binary(),
    -                tz :: binary(),
    -                geo :: #vcard_geo{},
    -                title :: binary(),
    -                role :: binary(),
    -                logo :: #vcard_logo{},
    -                org :: #vcard_org{},
    -                categories = [] :: [binary()],
    -                note :: binary(),
    -                prodid :: binary(),
    -                rev :: binary(),
    -                sort_string :: binary(),
    -                sound :: #vcard_sound{},
    -                uid :: binary(),
    -                url :: binary(),
    -                class :: 'confidential' | 'private' | 'public',
    -                key :: #vcard_key{},
    -                desc :: binary()}).
    -
    --record(time, {tzo :: any(),
    -               utc :: any()}).
    -
    -
    diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec
    deleted file mode 100644
    index acf9c3bb7..000000000
    --- a/tools/xmpp_codec.spec
    +++ /dev/null
    @@ -1,2520 +0,0 @@
    --xml(last,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"jabber:iq:last">>,
    -           result = {last, '$seconds', '$text'},
    -           attrs = [#attr{name = <<"seconds">>,
    -                          enc = {enc_int, []},
    -                          dec = {dec_int, [0, infinity]}}],
    -           cdata = #cdata{label = '$text'}}).
    -
    --xml(version_name,
    -     #elem{name = <<"name">>,
    -           xmlns = <<"jabber:iq:version">>,
    -           result = '$cdata',
    -           cdata = #cdata{label = '$cdata', required = true}}).
    -
    --xml(version_ver,
    -     #elem{name = <<"version">>,
    -           xmlns = <<"jabber:iq:version">>,
    -           result = '$cdata',
    -           cdata = #cdata{label = '$cdata', required = true}}).
    -
    --xml(version_os,
    -     #elem{name = <<"os">>,
    -           xmlns = <<"jabber:iq:version">>,
    -           result = '$cdata',
    -           cdata = #cdata{label = '$cdata', required = true}}).
    -
    --xml(version,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"jabber:iq:version">>,
    -           result = {version, '$name', '$ver', '$os'},
    -           refs = [#ref{name = version_name,
    -                        label = '$name',
    -                        min = 0, max = 1},
    -                   #ref{name = version_ver,
    -                        label = '$ver',
    -                        min = 0, max = 1},
    -                   #ref{name = version_os,
    -                        label = '$os',
    -                        min = 0, max = 1}]}).
    -
    --xml(roster_group,
    -     #elem{name = <<"group">>,
    -           xmlns = <<"jabber:iq:roster">>,
    -           result = '$cdata',
    -           cdata = #cdata{required = true, label = '$cdata'}}).
    -
    --xml(roster_item,
    -     #elem{name = <<"item">>,
    -           xmlns = <<"jabber:iq:roster">>,
    -           result = {roster_item, '$jid', '$name',
    -                     '$groups', '$subscription', '$ask'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"name">>},
    -                    #attr{name = <<"subscription">>,
    -                          default = none,
    -                          enc = {enc_enum, []},
    -                          dec = {dec_enum, [[none,to,from,both,remove]]}},
    -                    #attr{name = <<"ask">>,
    -                          enc = {enc_enum, []},
    -                          dec = {dec_enum, [[subscribe]]}}],
    -           refs = [#ref{name = roster_group, label = '$groups'}]}).
    -
    --xml(roster,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"jabber:iq:roster">>,
    -           result = {roster, '$items', '$ver'},
    -           attrs = [#attr{name = <<"ver">>}],
    -           refs = [#ref{name = roster_item, label = '$items'}]}).
    -
    --xml(privacy_message, #elem{name = <<"message">>, xmlns = <<"jabber:iq:privacy">>,
    -                            result = message}).
    --xml(privacy_iq, #elem{name = <<"iq">>, xmlns = <<"jabber:iq:privacy">>,
    -                       result = iq}).
    --xml(privacy_presence_in, #elem{name = <<"presence-in">>,
    -                                xmlns = <<"jabber:iq:privacy">>,
    -                                result = 'presence-in'}).
    --xml(privacy_presence_out, #elem{name = <<"presence-out">>,
    -                                 xmlns = <<"jabber:iq:privacy">>,
    -                                 result = 'presence-out'}).
    -
    --xml(privacy_item,
    -     #elem{name = <<"item">>,
    -           xmlns = <<"jabber:iq:privacy">>,
    -           result = {privacy_item, '$order', '$action', '$type',
    -                     '$value', '$kinds'},
    -           attrs = [#attr{name = <<"action">>,
    -                          required = true,
    -                          dec = {dec_enum, [[allow, deny]]},
    -                          enc = {enc_enum, []}},
    -                    #attr{name = <<"order">>,
    -                          required = true,
    -                          dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -                    #attr{name = <<"type">>,
    -                          dec = {dec_enum, [[group, jid, subscription]]},
    -                          enc = {enc_enum, []}},
    -                    #attr{name = <<"value">>}],
    -           refs = [#ref{name = privacy_message,
    -                        label = '$kinds'},
    -                   #ref{name = privacy_iq,
    -                        label = '$kinds'},
    -                   #ref{name = privacy_presence_in,
    -                        label = '$kinds'},
    -                   #ref{name = privacy_presence_out,
    -                        label = '$kinds'}]}).
    -
    --xml(privacy_list,
    -     #elem{name = <<"list">>,
    -           xmlns = <<"jabber:iq:privacy">>,
    -           result = {privacy_list, '$name', '$items'},
    -           attrs = [#attr{name = <<"name">>,
    -                          required = true}],
    -           refs = [#ref{name = privacy_item,
    -                        label = '$items'}]}).
    -
    --xml(privacy_default_list,
    -     #elem{name = <<"default">>,
    -           xmlns = <<"jabber:iq:privacy">>,
    -           result = '$name',
    -           attrs = [#attr{name = <<"name">>,
    -                          default = none}]}).
    -
    --xml(privacy_active_list,
    -     #elem{name = <<"active">>,
    -           xmlns = <<"jabber:iq:privacy">>,
    -           result = '$name',
    -           attrs = [#attr{name = <<"name">>,
    -                          default = none}]}).
    -
    --xml(privacy,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"jabber:iq:privacy">>,
    -           result = {privacy, '$lists', '$default', '$active'},
    -           refs = [#ref{name = privacy_list,
    -                        label = '$lists'},
    -                   #ref{name = privacy_default_list,
    -                        min = 0, max = 1,
    -                        label = '$default'},
    -                   #ref{name = privacy_active_list,
    -                        min = 0, max = 1,
    -                        label = '$active'}]}).
    -
    --xml(block_item,
    -     #elem{name = <<"item">>,
    -           xmlns = <<"urn:xmpp:blocking">>,
    -           result = '$jid',
    -           attrs = [#attr{name = <<"jid">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}]}).
    -
    --xml(block,
    -     #elem{name = <<"block">>,
    -           xmlns = <<"urn:xmpp:blocking">>,
    -           result = {block, '$items'},
    -           refs = [#ref{name = block_item,
    -                        label = '$items'}]}).
    -
    --xml(unblock,
    -     #elem{name = <<"unblock">>,
    -           xmlns = <<"urn:xmpp:blocking">>,
    -           result = {unblock, '$items'},
    -           refs = [#ref{name = block_item,
    -                        label = '$items'}]}).
    -
    --xml(block_list,
    -     #elem{name = <<"blocklist">>,
    -           xmlns = <<"urn:xmpp:blocking">>,
    -           result = {block_list}}).
    -
    --xml(disco_identity,
    -     #elem{name = <<"identity">>,
    -           xmlns = <<"http://jabber.org/protocol/disco#info">>,
    -           result = {identity, '$category', '$type', '$lang', '$name'},
    -           attrs = [#attr{name = <<"category">>,
    -                          required = true},
    -                    #attr{name = <<"type">>,
    -                          required = true},
    -                    #attr{name = <<"xml:lang">>,
    -                          label = '$lang'},
    -                    #attr{name = <<"name">>}]}).
    -
    --xml(disco_feature,
    -     #elem{name = <<"feature">>,
    -           xmlns = <<"http://jabber.org/protocol/disco#info">>,
    -           result = '$var',
    -           attrs = [#attr{name = <<"var">>,
    -                          required = true}]}).
    -
    --xml(disco_info,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"http://jabber.org/protocol/disco#info">>,
    -           result = {disco_info, '$node', '$identities', '$features', '$xdata'},
    -           attrs = [#attr{name = <<"node">>}],
    -           refs = [#ref{name = disco_identity,
    -                        label = '$identities'},
    -                   #ref{name = disco_feature,
    -                        label = '$features'},
    -                   #ref{name = xdata,
    -                        label = '$xdata'}]}).
    -
    --xml(disco_item,
    -     #elem{name = <<"item">>,
    -           xmlns = <<"http://jabber.org/protocol/disco#items">>,
    -           result = {disco_item, '$jid', '$name', '$node'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []},
    -                          required = true},
    -                    #attr{name = <<"name">>},
    -                    #attr{name = <<"node">>}]}).
    --xml(disco_items,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"http://jabber.org/protocol/disco#items">>,
    -           result = {disco_items, '$node', '$items'},
    -           attrs = [#attr{name = <<"node">>}],
    -           refs = [#ref{name = disco_item,
    -                        label = '$items'}]}).
    -
    --xml(private,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"jabber:iq:private">>,
    -           result = {private, '$_xmls'}}).
    -
    --xml(conference_nick,
    -     #elem{name = <<"nick">>,
    -           xmlns = <<"storage:bookmarks">>,
    -           result = '$cdata'}).
    -
    --xml(conference_password,
    -     #elem{name = <<"password">>,
    -           xmlns = <<"storage:bookmarks">>,
    -           result = '$cdata'}).
    -
    --xml(bookmark_conference,
    -     #elem{name = <<"conference">>,
    -           xmlns = <<"storage:bookmarks">>,
    -           result = {bookmark_conference, '$name', '$jid',
    -                     '$autojoin', '$nick', '$password'},
    -           attrs = [#attr{name = <<"name">>,
    -                          required = true},
    -                    #attr{name = <<"jid">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"autojoin">>,
    -                          default = false,
    -                          dec = {dec_bool, []},
    -                          enc = {enc_bool, []}}],
    -           refs = [#ref{name = conference_nick,
    -                        label = '$nick',
    -                        min = 0, max = 1},
    -                   #ref{name = conference_password,
    -                        label = '$password',
    -                        min = 0, max = 1}]}).
    -
    --xml(bookmark_url,
    -     #elem{name = <<"url">>,
    -           xmlns = <<"storage:bookmarks">>,
    -           result = {bookmark_url, '$name', '$url'},
    -           attrs = [#attr{name = <<"name">>,
    -                          required = true},
    -                    #attr{name = <<"url">>,
    -                          required = true}]}).
    -
    --xml(bookmarks_storage,
    -     #elem{name = <<"storage">>,
    -           xmlns = <<"storage:bookmarks">>,
    -           result = {bookmark_storage, '$conference', '$url'},
    -           refs = [#ref{name = bookmark_conference,
    -                        label = '$conference'},
    -                   #ref{name = bookmark_url,
    -                        label = '$url'}]}).
    -
    --xml(stat_error,
    -     #elem{name = <<"error">>,
    -           xmlns = <<"http://jabber.org/protocol/stats">>,
    -           result = {'$code', '$cdata'},
    -           attrs = [#attr{name = <<"code">>,
    -                          required = true,
    -                          enc = {enc_int, []},
    -                          dec = {dec_int, []}}]}).
    -
    --xml(stat,
    -     #elem{name = <<"stat">>,
    -           xmlns = <<"http://jabber.org/protocol/stats">>,
    -           result = {stat, '$name', '$units', '$value', '$error'},
    -           attrs = [#attr{name = <<"name">>,
    -                          required = true},
    -                    #attr{name = <<"units">>},
    -                    #attr{name = <<"value">>}],
    -           refs = [#ref{name = stat_error,
    -                        label = '$error'}]}).
    -
    --xml(stats,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"http://jabber.org/protocol/stats">>,
    -           result = {stats, '$stat'},
    -           refs = [#ref{name = stat,
    -                        label = '$stat'}]}).
    -
    --xml(iq,
    -     #elem{name = <<"iq">>,
    -           xmlns = <<"jabber:client">>,
    -           result = {iq, '$id', '$type', '$lang', '$from', '$to',
    -                     '$error', '$_els'},
    -           attrs = [#attr{name = <<"id">>,
    -                          required = true},
    -                    #attr{name = <<"type">>,
    -                          required = true,
    -                          enc = {enc_enum, []},
    -                          dec = {dec_enum, [[get, set, result, error]]}},
    -                    #attr{name = <<"from">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"to">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"xml:lang">>,
    -                          label = '$lang'}],
    -           refs = [#ref{name = error, min = 0, max = 1, label = '$error'}]}).
    -
    --xml(message_subject,
    -     #elem{name = <<"subject">>,
    -           xmlns = <<"jabber:client">>,
    -           result = {text, '$lang', '$data'},
    -           cdata = #cdata{label = '$data'},
    -           attrs = [#attr{name = <<"xml:lang">>, label = '$lang'}]}).
    -
    --xml(message_body,
    -     #elem{name = <<"body">>,
    -           xmlns = <<"jabber:client">>,
    -           result = {text, '$lang', '$data'},
    -           cdata = #cdata{label = '$data'},
    -           attrs = [#attr{name = <<"xml:lang">>, label = '$lang'}]}).
    -
    --xml(message_thread,
    -     #elem{name = <<"thread">>,
    -           xmlns = <<"jabber:client">>,
    -           result = '$cdata'}).
    -
    --xml(message,
    -     #elem{name = <<"message">>,
    -           xmlns = <<"jabber:client">>,
    -           result = {message, '$id', '$type', '$lang', '$from', '$to',
    -                     '$subject', '$body', '$thread', '$error', '$_els'},
    -           attrs = [#attr{name = <<"id">>},
    -                    #attr{name = <<"type">>,
    -                          default = normal,
    -                          enc = {enc_enum, []},
    -                          dec = {dec_enum, [[chat, normal, groupchat,
    -                                             headline, error]]}},
    -                    #attr{name = <<"from">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"to">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"xml:lang">>,
    -                          label = '$lang'}],
    -           refs = [#ref{name = error, min = 0, max = 1, label = '$error'},
    -                   #ref{name = message_subject, label = '$subject'},
    -                   #ref{name = message_thread, min = 0, max = 1, label = '$thread'},
    -                   #ref{name = message_body, label = '$body'}]}).
    -
    --xml(presence_show,
    -     #elem{name = <<"show">>,
    -           xmlns = <<"jabber:client">>,
    -           result = '$cdata',
    -           cdata = #cdata{enc = {enc_enum, []},
    -                          dec = {dec_enum, [[away, chat, dnd, xa]]}}}).
    -
    --xml(presence_status,
    -     #elem{name = <<"status">>,
    -           xmlns = <<"jabber:client">>,
    -           result = {text, '$lang', '$data'},
    -           cdata = #cdata{label = '$data'},
    -           attrs = [#attr{name = <<"xml:lang">>,
    -                          label = '$lang'}]}).
    -
    --xml(presence_priority,
    -     #elem{name = <<"priority">>,
    -           xmlns = <<"jabber:client">>,
    -           result = '$cdata',
    -           cdata = #cdata{enc = {enc_int, []},
    -                          dec = {dec_int, []}}}).
    -
    --xml(presence,
    -     #elem{name = <<"presence">>,
    -           xmlns = <<"jabber:client">>,
    -           result = {presence, '$id', '$type', '$lang', '$from', '$to',
    -                     '$show', '$status', '$priority', '$error', '$_els'},
    -           attrs = [#attr{name = <<"id">>},
    -                    #attr{name = <<"type">>,
    -                          enc = {enc_enum, []},
    -                          dec = {dec_enum, [[unavailable, subscribe, subscribed,
    -                                             unsubscribe, unsubscribed,
    -                                             probe, error]]}},
    -                    #attr{name = <<"from">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"to">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"xml:lang">>,
    -                          label = '$lang'}],
    -           refs = [#ref{name = error, min = 0, max = 1, label = '$error'},
    -                   #ref{name = presence_show, min = 0, max = 1, label = '$show'},
    -                   #ref{name = presence_status, label = '$status'},
    -                   #ref{name = presence_priority, min = 0, max = 1,
    -                        label = '$priority'}]}).
    -
    --xml(error_bad_request,
    -     #elem{name = <<"bad-request">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'bad-request'}).
    --xml(error_conflict,
    -     #elem{name = <<"conflict">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'conflict'}).
    --xml(error_feature_not_implemented,
    -     #elem{name = <<"feature-not-implemented">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'feature-not-implemented'}).
    --xml(error_forbidden,
    -     #elem{name = <<"forbidden">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'forbidden'}).
    --xml(error_gone,
    -     #elem{name = <<"gone">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           cdata = #cdata{label = '$uri'},
    -           result = {'gone', '$uri'}}).
    --xml(error_internal_server_error,
    -     #elem{name = <<"internal-server-error">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'internal-server-error'}).
    --xml(error_item_not_found,
    -     #elem{name = <<"item-not-found">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'item-not-found'}).
    --xml(error_jid_malformed,
    -     #elem{name = <<"jid-malformed">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'jid-malformed'}).
    --xml(error_not_acceptable,
    -     #elem{name = <<"not-acceptable">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'not-acceptable'}).
    --xml(error_not_allowed,
    -     #elem{name = <<"not-allowed">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'not-allowed'}).
    --xml(error_not_authorized,
    -     #elem{name = <<"not-authorized">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'not-authorized'}).
    --xml(error_policy_violation,
    -     #elem{name = <<"policy-violation">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'policy-violation'}).
    --xml(error_recipient_unavailable,
    -     #elem{name = <<"recipient-unavailable">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'recipient-unavailable'}).
    --xml(error_redirect,
    -     #elem{name = <<"redirect">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           cdata = #cdata{label = '$uri'},
    -           result = {'redirect', '$uri'}}).
    --xml(error_registration_required,
    -     #elem{name = <<"registration-required">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'registration-required'}).
    --xml(error_remote_server_not_found,
    -     #elem{name = <<"remote-server-not-found">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'remote-server-not-found'}).
    --xml(error_remote_server_timeout,
    -     #elem{name = <<"remote-server-timeout">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'remote-server-timeout'}).
    --xml(error_resource_constraint,
    -     #elem{name = <<"resource-constraint">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'resource-constraint'}).
    --xml(error_service_unavailable,
    -     #elem{name = <<"service-unavailable">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'service-unavailable'}).
    --xml(error_subscription_required,
    -     #elem{name = <<"subscription-required">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'subscription-required'}).
    --xml(error_undefined_condition,
    -     #elem{name = <<"undefined-condition">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'undefined-condition'}).
    --xml(error_unexpected_request,
    -     #elem{name = <<"unexpected-request">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           result = 'unexpected-request'}).
    -
    --xml(error_text,
    -     #elem{name = <<"text">>,
    -           result = {text, '$lang', '$data'},
    -           cdata = #cdata{label = '$data'},
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
    -           attrs = [#attr{name = <<"xml:lang">>,
    -                          label = '$lang'}]}).
    -
    --xml(error,
    -     #elem{name = <<"error">>,
    -           xmlns = <<"jabber:client">>,
    -           result = {error, '$type', '$by', '$reason', '$text'},
    -           attrs = [#attr{name = <<"type">>,
    -                          label = '$type',
    -                          required = true,
    -                          dec = {dec_enum, [[auth, cancel, continue,
    -                                             modify, wait]]},
    -                          enc = {enc_enum, []}},
    -                    #attr{name = <<"by">>}],
    -           refs = [#ref{name = error_text,
    -                        min = 0, max = 1, label = '$text'},
    -                   #ref{name = error_bad_request,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_conflict,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_feature_not_implemented,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_forbidden,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_gone,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_internal_server_error,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_item_not_found,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_jid_malformed,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_not_acceptable,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_not_allowed,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_not_authorized,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_policy_violation,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_recipient_unavailable,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_redirect,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_registration_required,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_remote_server_not_found,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_remote_server_timeout,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_resource_constraint,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_service_unavailable,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_subscription_required,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_undefined_condition,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_unexpected_request,
    -                        min = 0, max = 1, label = '$reason'}]}).
    -
    --xml(bind_jid,
    -     #elem{name = <<"jid">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-bind">>,
    -           result = '$cdata',
    -           cdata = #cdata{dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}}).
    -
    --xml(bind_resource,
    -     #elem{name = <<"resource">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-bind">>,
    -           result = '$cdata',
    -           cdata = #cdata{dec = {resourceprep, []},
    -                          enc = {resourceprep, []}}}).
    -
    --xml(bind, #elem{name = <<"bind">>,
    -                 xmlns = <<"urn:ietf:params:xml:ns:xmpp-bind">>,
    -                 result = {bind, '$jid', '$resource'},
    -                 refs = [#ref{name = bind_jid,
    -                              label = '$jid',
    -                              min = 0, max = 1},
    -                         #ref{name = bind_resource,
    -                              min = 0, max = 1,
    -                              label = '$resource'}]}).
    -
    --xml(sasl_auth,
    -     #elem{name = <<"auth">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -           cdata = #cdata{label = '$text',
    -                          dec = {base64, decode, []},
    -                          enc = {base64, encode, []}},
    -           result = {sasl_auth, '$mechanism', '$text'},
    -           attrs = [#attr{name = <<"mechanism">>,
    -                          required = true}]}).
    -
    --xml(sasl_abort,
    -     #elem{name = <<"abort">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -           result = {sasl_abort}}).
    -
    --xml(sasl_challenge,
    -     #elem{name = <<"challenge">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -           cdata = #cdata{label = '$text',
    -                          dec = {base64, decode, []},
    -                          enc = {base64, encode, []}},
    -           result = {sasl_challenge, '$text'}}).
    -
    --xml(sasl_response,
    -     #elem{name = <<"response">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -           cdata = #cdata{label = '$text',
    -                          dec = {base64, decode, []},
    -                          enc = {base64, encode, []}},
    -           result = {sasl_response, '$text'}}).
    -
    --xml(sasl_success,
    -     #elem{name = <<"success">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -           cdata = #cdata{label = '$text',
    -                          dec = {base64, decode, []},
    -                          enc = {base64, encode, []}},
    -           result = {sasl_success, '$text'}}).
    -
    --xml(sasl_failure_text,
    -     #elem{name = <<"text">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -           result = {text, '$lang', '$data'},
    -           cdata = #cdata{label = '$data'},
    -           attrs = [#attr{name = <<"xml:lang">>,
    -                          label = '$lang'}]}).
    -
    --xml(sasl_failure_aborted,
    -     #elem{name = <<"aborted">>,
    -           result = 'aborted',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_account_disabled,
    -     #elem{name = <<"account-disabled">>,
    -           result = 'account-disabled',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_credentials_expired,
    -     #elem{name = <<"credentials-expired">>,
    -           result = 'credentials-expired',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_encryption_required,
    -     #elem{name = <<"encryption-required">>,
    -           result = 'encryption-required',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_incorrect_encoding,
    -     #elem{name = <<"incorrect-encoding">>,
    -           result = 'incorrect-encoding',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_invalid_authzid,
    -     #elem{name = <<"invalid-authzid">>,
    -           result = 'invalid-authzid',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_invalid_mechanism,
    -     #elem{name = <<"invalid-mechanism">>,
    -           result = 'invalid-mechanism',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_malformed_request,
    -     #elem{name = <<"malformed-request">>,
    -           result = 'malformed-request',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_mechanism_too_weak,
    -     #elem{name = <<"mechanism-too-weak">>,
    -           result = 'mechanism-too-weak',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_not_authorized,
    -     #elem{name = <<"not-authorized">>,
    -           result = 'not-authorized',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    --xml(sasl_failure_temporary_auth_failure,
    -     #elem{name = <<"temporary-auth-failure">>,
    -           result = 'temporary-auth-failure',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
    -
    --xml(sasl_failure,
    -     #elem{name = <<"failure">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -           result = {sasl_failure, '$reason', '$text'},
    -           refs = [#ref{name = sasl_failure_text,
    -                        label = '$text'},
    -                   #ref{name = sasl_failure_aborted,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_account_disabled,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_credentials_expired,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_encryption_required,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_incorrect_encoding,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_invalid_authzid,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_invalid_mechanism,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_malformed_request,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_mechanism_too_weak,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_not_authorized,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = sasl_failure_temporary_auth_failure,
    -                        min = 0, max = 1, label = '$reason'}]}).
    -
    --xml(sasl_mechanism,
    -     #elem{name = <<"mechanism">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -           result = '$cdata'}).
    -
    --xml(sasl_mechanisms,
    -     #elem{name = <<"mechanisms">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
    -           result = {sasl_mechanisms, '$list'},
    -           refs = [#ref{name = sasl_mechanism,
    -                        label = '$list'}]}).
    -
    --xml(starttls_required,
    -     #elem{name = <<"required">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-tls">>,
    -           result = true}).
    -
    --xml(starttls,
    -     #elem{name = <<"starttls">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-tls">>,
    -           result = {starttls, '$required'},
    -           refs = [#ref{name = starttls_required,
    -                        label = '$required',
    -                        min = 0, max = 1,
    -                        default = false}]}).
    -
    --xml(starttls_proceed,
    -     #elem{name = <<"proceed">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-tls">>,
    -           result = {starttls_proceed}}).
    -
    --xml(starttls_failure,
    -     #elem{name = <<"failure">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-tls">>,
    -           result = {starttls_failure}}).
    -
    --xml(compress_failure_setup_failed,
    -     #elem{name = <<"setup-failed">>,
    -           xmlns = <<"http://jabber.org/protocol/compress">>,
    -           result = 'setup-failed'}).
    --xml(compress_failure_processing_failed,
    -     #elem{name = <<"processing-failed">>,
    -           xmlns = <<"http://jabber.org/protocol/compress">>,
    -           result = 'processing-failed'}).
    --xml(compress_failure_unsupported_method,
    -     #elem{name = <<"unsupported-method">>,
    -           xmlns = <<"http://jabber.org/protocol/compress">>,
    -           result = 'unsupported-method'}).
    -
    --xml(compress_failure,
    -     #elem{name = <<"failure">>,
    -           xmlns = <<"http://jabber.org/protocol/compress">>,
    -           result = {compress_failure, '$reason'},
    -           refs = [#ref{name = compress_failure_setup_failed,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = compress_failure_processing_failed,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = compress_failure_unsupported_method,
    -                        min = 0, max = 1, label = '$reason'}]}).
    -
    --xml(compress_method,
    -     #elem{name = <<"method">>,
    -           xmlns = <<"http://jabber.org/protocol/compress">>,
    -           result = '$cdata'}).
    -
    --xml(compress,
    -     #elem{name = <<"compress">>,
    -           xmlns = <<"http://jabber.org/protocol/compress">>,
    -           result = {compress, '$methods'},
    -           refs = [#ref{name = compress_method,
    -                        label = '$methods'}]}).
    -
    --xml(compressed,
    -     #elem{name = <<"compressed">>,
    -           xmlns = <<"http://jabber.org/protocol/compress">>,
    -           result = {compressed}}).
    -
    --xml(compression_method,
    -     #elem{name = <<"method">>,
    -           xmlns = <<"http://jabber.org/features/compress">>,
    -           result = '$cdata'}).
    -
    --xml(compression,
    -     #elem{name = <<"compression">>,
    -           xmlns = <<"http://jabber.org/features/compress">>,
    -           result = {compression, '$methods'},
    -           refs = [#ref{name = compression_method, label = '$methods'}]}).
    -
    --xml(stream_features,
    -     #elem{name = <<"stream:features">>,
    -           xmlns = <<"http://etherx.jabber.org/streams">>,
    -           result = {stream_features, '$_els'}}).
    -
    --xml(p1_push,
    -     #elem{name = <<"push">>,
    -           result = {p1_push},
    -           xmlns = <<"p1:push">>}).
    -
    --xml(p1_rebind,
    -     #elem{name = <<"rebind">>,
    -           result = {p1_rebind},
    -           xmlns = <<"p1:rebind">>}).
    -
    --xml(p1_ack,
    -     #elem{name = <<"ack">>,
    -           result = {p1_ack},
    -           xmlns = <<"p1:ack">>}).
    -
    --xml(caps,
    -     #elem{name = <<"c">>,
    -           xmlns = <<"http://jabber.org/protocol/caps">>,
    -           result = {caps, '$hash', '$node', '$ver'},
    -           attrs = [#attr{name = <<"hash">>},
    -                    #attr{name = <<"node">>},
    -                    #attr{name = <<"ver">>,
    -                          enc = {base64, encode, []},
    -                          dec = {base64, decode, []}}]}).
    -
    --xml(feature_register,
    -     #elem{name = <<"register">>,
    -           xmlns = <<"http://jabber.org/features/iq-register">>,
    -           result = {feature_register}}).
    -
    --xml(register_registered,
    -     #elem{name = <<"registered">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           result = true}).
    --xml(register_remove,
    -     #elem{name = <<"remove">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           result = true}).
    --xml(register_instructions,
    -     #elem{name = <<"instructions">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           result = '$cdata'}).
    --xml(register_username,
    -     #elem{name = <<"username">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_nick,
    -     #elem{name = <<"nick">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_password,
    -     #elem{name = <<"password">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_name,
    -     #elem{name = <<"name">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_first,
    -     #elem{name = <<"first">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_last,
    -     #elem{name = <<"last">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_email,
    -     #elem{name = <<"email">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_address,
    -     #elem{name = <<"address">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_city,
    -     #elem{name = <<"city">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_state,
    -     #elem{name = <<"state">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_zip,
    -     #elem{name = <<"zip">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_phone,
    -     #elem{name = <<"phone">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_url,
    -     #elem{name = <<"url">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_date,
    -     #elem{name = <<"date">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_misc,
    -     #elem{name = <<"misc">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_text,
    -     #elem{name = <<"text">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    --xml(register_key,
    -     #elem{name = <<"key">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           cdata = #cdata{default = none},
    -           result = '$cdata'}).
    -
    --xml(register,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"jabber:iq:register">>,
    -           result = {register, '$registered', '$remove', '$instructions',
    -                     '$username', '$nick', '$password', '$name',
    -                     '$first', '$last', '$email', '$address',
    -                     '$city', '$state', '$zip', '$phone', '$url',
    -                     '$date', '$misc', '$text', '$key', '$xdata'},
    -           refs = [#ref{name = xdata, min = 0, max = 1,
    -			label = '$xdata'},
    -		   #ref{name = register_registered, min = 0, max = 1,
    -                        default = false, label = '$registered'},
    -                   #ref{name = register_remove, min = 0, max = 1,
    -                        default = false, label = '$remove'},
    -                   #ref{name = register_instructions, min = 0, max = 1,
    -                        label = '$instructions'},
    -                   #ref{name = register_username, min = 0, max = 1,
    -                        label = '$username'},
    -                   #ref{name = register_nick, min = 0, max = 1,
    -                        label = '$nick'},
    -                   #ref{name = register_password, min = 0, max = 1,
    -                        label = '$password'},
    -                   #ref{name = register_name, min = 0, max = 1,
    -                        label = '$name'},
    -                   #ref{name = register_first, min = 0, max = 1,
    -                        label = '$first'},
    -                   #ref{name = register_last, min = 0, max = 1,
    -                        label = '$last'},
    -                   #ref{name = register_email, min = 0, max = 1,
    -                        label = '$email'},
    -                   #ref{name = register_address, min = 0, max = 1,
    -                        label = '$address'},
    -                   #ref{name = register_city, min = 0, max = 1,
    -                        label = '$city'},
    -                   #ref{name = register_state, min = 0, max = 1,
    -                        label = '$state'},
    -                   #ref{name = register_zip, min = 0, max = 1,
    -                        label = '$zip'},
    -                   #ref{name = register_phone, min = 0, max = 1,
    -                        label = '$phone'},
    -                   #ref{name = register_url, min = 0, max = 1,
    -                        label = '$url'},
    -                   #ref{name = register_date, min = 0, max = 1,
    -                        label = '$date'},
    -                   #ref{name = register_misc, min = 0, max = 1,
    -                        label = '$misc'},
    -                   #ref{name = register_text, min = 0, max = 1,
    -                        label = '$text'},
    -                   #ref{name = register_key, min = 0, max = 1,
    -                        label = '$key'}]}).
    -
    --xml(session,
    -     #elem{name = <<"session">>,
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-session">>,
    -           result = {session}}).
    -
    --xml(ping,
    -     #elem{name = <<"ping">>,
    -           xmlns = <<"urn:xmpp:ping">>,
    -           result = {ping}}).
    -
    --xml(time_utc,
    -     #elem{name = <<"utc">>,
    -           xmlns = <<"urn:xmpp:time">>,
    -           result = '$cdata',
    -           cdata = #cdata{dec = {dec_utc, []},
    -                          enc = {enc_utc, []}}}).
    -
    --xml(time_tzo,
    -     #elem{name = <<"tzo">>,
    -           xmlns = <<"urn:xmpp:time">>,
    -           result = '$cdata',
    -           cdata = #cdata{dec = {dec_tzo, []},
    -                          enc = {enc_tzo, []}}}).
    -
    --xml(time,
    -     #elem{name = <<"time">>,
    -           xmlns = <<"urn:xmpp:time">>,
    -           result = {time, '$tzo', '$utc'},
    -           refs = [#ref{name = time_tzo,
    -                        label = '$tzo',
    -                        min = 0, max = 1},
    -                   #ref{name = time_utc,
    -                        label = '$utc',
    -                        min = 0, max = 1}]}).
    -
    --xml(stream_error_text,
    -     #elem{name = <<"text">>,
    -           result = {text, '$lang', '$data'},
    -           cdata = #cdata{label = '$data'},
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>,
    -           attrs = [#attr{name = <<"xml:lang">>,
    -                          label = '$lang'}]}).
    -
    --xml(stream_error_bad_format,
    -     #elem{name = <<"bad-format">>,
    -           result = 'bad-format',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).       
    --xml(stream_error_bad_namespace_prefix,
    -     #elem{name = <<"bad-namespace-prefix">>,
    -           result = 'bad-namespace-prefix',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_conflict,
    -     #elem{name = <<"conflict">>,
    -           result = 'conflict',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_connection_timeout,
    -     #elem{name = <<"connection-timeout">>,
    -           result = 'connection-timeout',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_host_gone,
    -     #elem{name = <<"host-gone">>,
    -           result = 'host-gone',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_host_unknown,
    -     #elem{name = <<"host-unknown">>,
    -           result = 'host-unknown',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_improper_addressing,
    -     #elem{name = <<"improper-addressing">>,
    -           result = 'improper-addressing',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_internal_server_error,
    -     #elem{name = <<"internal-server-error">>,
    -           result = 'internal-server-error',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_invalid_from,
    -     #elem{name = <<"invalid-from">>,
    -           result = 'invalid-from',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_invalid_id,
    -     #elem{name = <<"invalid-id">>,
    -           result = 'invalid-id',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_invalid_namespace,
    -     #elem{name = <<"invalid-namespace">>,
    -           result = 'invalid-namespace',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_invalid_xml,
    -     #elem{name = <<"invalid-xml">>,
    -           result = 'invalid-xml',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_not_authorized,
    -     #elem{name = <<"not-authorized">>,
    -           result = 'not-authorized',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_not_well_formed,
    -     #elem{name = <<"not-well-formed">>,
    -           result = 'not-well-formed',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_policy_violation,
    -     #elem{name = <<"policy-violation">>,
    -           result = 'policy-violation',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_remote_connection_failed,
    -     #elem{name = <<"remote-connection-failed">>,
    -           result = 'remote-connection-failed',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_reset,
    -     #elem{name = <<"reset">>,
    -           result = 'reset',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_resource_constraint,
    -     #elem{name = <<"resource-constraint">>,
    -           result = 'resource-constraint',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_restricted_xml,
    -     #elem{name = <<"restricted-xml">>,
    -           result = 'restricted-xml',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_see_other_host,
    -     #elem{name = <<"see-other-host">>,
    -           cdata = #cdata{required = true, label = '$host'},
    -           result = {'see-other-host', '$host'},
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_system_shutdown,
    -     #elem{name = <<"system-shutdown">>,
    -           result = 'system-shutdown',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_undefined_condition,
    -     #elem{name = <<"undefined-condition">>,
    -           result = 'undefined-condition',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_unsupported_encoding,
    -     #elem{name = <<"unsupported-encoding">>,
    -           result = 'unsupported-encoding',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_unsupported_stanza_type,
    -     #elem{name = <<"unsupported-stanza-type">>,
    -           result = 'unsupported-stanza-type',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    --xml(stream_error_unsupported_version,
    -     #elem{name = <<"unsupported-version">>,
    -           result = 'unsupported-version',
    -           xmlns = <<"urn:ietf:params:xml:ns:xmpp-streams">>}).
    -
    --xml(stream_error,
    -     #elem{name = <<"stream:error">>,
    -           xmlns = <<"http://etherx.jabber.org/streams">>,
    -           result = {stream_error, '$reason', '$text'},
    -           refs = [#ref{name = stream_error_text,
    -                        label = '$text',
    -                        min = 0, max = 1},
    -                   #ref{name = stream_error_bad_format,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_bad_namespace_prefix,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_conflict,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_connection_timeout,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_host_gone,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_host_unknown,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_improper_addressing,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_internal_server_error,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_invalid_from,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_invalid_id,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_invalid_namespace,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_invalid_xml,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_not_authorized,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_not_well_formed,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_policy_violation,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_remote_connection_failed,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_reset,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_resource_constraint,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_restricted_xml,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_see_other_host,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_system_shutdown,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_undefined_condition,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_unsupported_encoding,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_unsupported_stanza_type,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = stream_error_unsupported_version,
    -                        min = 0, max = 1, label = '$reason'}
    -                  ]}).
    -
    --xml(vcard_HOME, #elem{name = <<"HOME">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_WORK, #elem{name = <<"WORK">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_VOICE, #elem{name = <<"VOICE">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_FAX, #elem{name = <<"FAX">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_PAGER, #elem{name = <<"PAGER">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_MSG, #elem{name = <<"MSG">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_CELL, #elem{name = <<"CELL">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_VIDEO, #elem{name = <<"VIDEO">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_BBS, #elem{name = <<"BBS">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_MODEM, #elem{name = <<"MODEM">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_ISDN, #elem{name = <<"ISDN">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_PCS, #elem{name = <<"PCS">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_POSTAL, #elem{name = <<"POSTAL">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_PARCEL, #elem{name = <<"PARCEL">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_DOM, #elem{name = <<"DOM">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_INTL, #elem{name = <<"INTL">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_PREF, #elem{name = <<"PREF">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_INTERNET, #elem{name = <<"INTERNET">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_X400, #elem{name = <<"X400">>, xmlns = <<"vcard-temp">>, result = true}).
    --xml(vcard_FAMILY, #elem{name = <<"FAMILY">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_GIVEN, #elem{name = <<"GIVEN">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_MIDDLE, #elem{name = <<"MIDDLE">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_PREFIX, #elem{name = <<"PREFIX">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_SUFFIX, #elem{name = <<"SUFFIX">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_POBOX, #elem{name = <<"POBOX">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_EXTADD, #elem{name = <<"EXTADD">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_STREET, #elem{name = <<"STREET">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_LOCALITY, #elem{name = <<"LOCALITY">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_REGION, #elem{name = <<"REGION">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_PCODE, #elem{name = <<"PCODE">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_CTRY, #elem{name = <<"CTRY">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_LINE, #elem{name = <<"LINE">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_NUMBER, #elem{name = <<"NUMBER">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_USERID, #elem{name = <<"USERID">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_LAT, #elem{name = <<"LAT">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_LON, #elem{name = <<"LON">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_ORGNAME, #elem{name = <<"ORGNAME">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_ORGUNIT, #elem{name = <<"ORGUNIT">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_PHONETIC, #elem{name = <<"PHONETIC">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_CRED, #elem{name = <<"CRED">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_VERSION, #elem{name = <<"VERSION">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_FN, #elem{name = <<"FN">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_NICKNAME, #elem{name = <<"NICKNAME">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_BDAY, #elem{name = <<"BDAY">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_JABBERID, #elem{name = <<"JABBERID">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_MAILER, #elem{name = <<"MAILER">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_TZ, #elem{name = <<"TZ">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_TITLE, #elem{name = <<"TITLE">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_ROLE, #elem{name = <<"ROLE">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_KEYWORD, #elem{name = <<"KEYWORD">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_NOTE, #elem{name = <<"NOTE">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_PRODID, #elem{name = <<"PRODID">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_REV, #elem{name = <<"REV">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_SORT_STRING, #elem{name = <<"SORT-STRING">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_UID, #elem{name = <<"UID">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_URL, #elem{name = <<"URL">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_DESC, #elem{name = <<"DESC">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_TYPE, #elem{name = <<"TYPE">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_EXTVAL, #elem{name = <<"EXTVAL">>, xmlns = <<"vcard-temp">>, result = '$cdata'}).
    --xml(vcard_PUBLIC, #elem{name = <<"PUBLIC">>, xmlns = <<"vcard-temp">>, result = public}).
    --xml(vcard_PRIVATE, #elem{name = <<"PRIVATE">>, xmlns = <<"vcard-temp">>, result = private}).
    --xml(vcard_CONFIDENTIAL, #elem{name = <<"CONFIDENTIAL">>, xmlns = <<"vcard-temp">>, result = confidential}).
    -
    --xml(vcard_N,
    -     #elem{name = <<"N">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_name, '$family', '$given', '$middle',
    -                     '$prefix', '$suffix'},
    -           refs = [#ref{name = vcard_FAMILY, min = 0, max = 1, label = '$family'},
    -                   #ref{name = vcard_GIVEN, min = 0, max = 1, label = '$given'},
    -                   #ref{name = vcard_MIDDLE, min = 0, max = 1, label = '$middle'},
    -                   #ref{name = vcard_PREFIX, min = 0, max = 1, label = '$prefix'},
    -                   #ref{name = vcard_SUFFIX, min = 0, max = 1, label = '$suffix'}]}).
    -
    --xml(vcard_ADR,
    -     #elem{name = <<"ADR">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_adr, '$home', '$work', '$postal', '$parcel',
    -                     '$dom', '$intl', '$pref', '$pobox', '$extadd', '$street',
    -                     '$locality', '$region', '$pcode', '$ctry'},
    -           refs = [#ref{name = vcard_HOME, default = false,
    -                        min = 0, max = 1, label = '$home'},
    -                   #ref{name = vcard_WORK, default = false,
    -                        min = 0, max = 1, label = '$work'},
    -                   #ref{name = vcard_POSTAL, default = false,
    -                        min = 0, max = 1, label = '$postal'},
    -                   #ref{name = vcard_PARCEL, default = false,
    -                        min = 0, max = 1, label = '$parcel'},
    -                   #ref{name = vcard_DOM, default = false,
    -                        min = 0, max = 1, label = '$dom'},
    -                   #ref{name = vcard_INTL, default = false,
    -                        min = 0, max = 1, label = '$intl'},
    -                   #ref{name = vcard_PREF, default = false,
    -                        min = 0, max = 1, label = '$pref'},
    -                   #ref{name = vcard_POBOX, min = 0, max = 1, label = '$pobox'},
    -                   #ref{name = vcard_EXTADD, min = 0, max = 1, label = '$extadd'},
    -                   #ref{name = vcard_STREET, min = 0, max = 1, label = '$street'},
    -                   #ref{name = vcard_LOCALITY, min = 0, max = 1, label = '$locality'},
    -                   #ref{name = vcard_REGION, min = 0, max = 1, label = '$region'},
    -                   #ref{name = vcard_PCODE, min = 0, max = 1, label = '$pcode'},
    -                   #ref{name = vcard_CTRY, min = 0, max = 1, label = '$ctry'}]}).
    -
    --xml(vcard_LABEL,
    -     #elem{name = <<"LABEL">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_label, '$home', '$work', '$postal', '$parcel',
    -                     '$dom', '$intl', '$pref', '$line'},
    -           refs = [#ref{name = vcard_HOME, default = false,
    -                        min = 0, max = 1, label = '$home'},
    -                   #ref{name = vcard_WORK, default = false,
    -                        min = 0, max = 1, label = '$work'},
    -                   #ref{name = vcard_POSTAL, default = false,
    -                        min = 0, max = 1, label = '$postal'},
    -                   #ref{name = vcard_PARCEL, default = false,
    -                        min = 0, max = 1, label = '$parcel'},
    -                   #ref{name = vcard_DOM, default = false,
    -                        min = 0, max = 1, label = '$dom'},
    -                   #ref{name = vcard_INTL, default = false,
    -                        min = 0, max = 1, label = '$intl'},
    -                   #ref{name = vcard_PREF, default = false,
    -                        min = 0, max = 1, label = '$pref'},
    -                   #ref{name = vcard_LINE, label = '$line'}]}).
    -
    --xml(vcard_TEL,
    -     #elem{name = <<"TEL">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_tel, '$home', '$work', '$voice', '$fax',
    -                     '$pager', '$msg', '$cell', '$video', '$bbs',
    -                     '$modem', '$isdn', '$pcs', '$pref', '$number'},
    -           refs = [#ref{name = vcard_HOME, default = false,
    -                        min = 0, max = 1, label = '$home'},
    -                   #ref{name = vcard_WORK, default = false,
    -                        min = 0, max = 1, label = '$work'},
    -                   #ref{name = vcard_VOICE, default = false,
    -                        min = 0, max = 1, label = '$voice'},
    -                   #ref{name = vcard_FAX, default = false,
    -                        min = 0, max = 1, label = '$fax'},
    -                   #ref{name = vcard_PAGER, default = false,
    -                        min = 0, max = 1, label = '$pager'},
    -                   #ref{name = vcard_MSG, default = false,
    -                        min = 0, max = 1, label = '$msg'},
    -                   #ref{name = vcard_CELL, default = false,
    -                        min = 0, max = 1, label = '$cell'},
    -                   #ref{name = vcard_VIDEO, default = false,
    -                        min = 0, max = 1, label = '$video'},
    -                   #ref{name = vcard_BBS, default = false,
    -                        min = 0, max = 1, label = '$bbs'},
    -                   #ref{name = vcard_MODEM, default = false,
    -                        min = 0, max = 1, label = '$modem'},
    -                   #ref{name = vcard_ISDN, default = false,
    -                        min = 0, max = 1, label = '$isdn'},
    -                   #ref{name = vcard_PCS, default = false,
    -                        min = 0, max = 1, label = '$pcs'},
    -                   #ref{name = vcard_PREF, default = false,
    -                        min = 0, max = 1, label = '$pref'},
    -                   #ref{name = vcard_NUMBER,
    -                        min = 0, max = 1, label = '$number'}]}).
    -
    --xml(vcard_EMAIL,
    -     #elem{name = <<"EMAIL">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_email, '$home', '$work',
    -                     '$internet', '$pref', '$x400', '$userid'},
    -           refs = [#ref{name = vcard_HOME, default = false,
    -                        min = 0, max = 1, label = '$home'},
    -                   #ref{name = vcard_WORK, default = false,
    -                        min = 0, max = 1, label = '$work'},
    -                   #ref{name = vcard_INTERNET, default = false,
    -                        min = 0, max = 1, label = '$internet'},
    -                   #ref{name = vcard_PREF, default = false,
    -                        min = 0, max = 1, label = '$pref'},
    -                   #ref{name = vcard_X400, default = false,
    -                        min = 0, max = 1, label = '$x400'},
    -                   #ref{name = vcard_USERID,
    -                        min = 0, max = 1, label = '$userid'}]}).
    -
    --xml(vcard_GEO,
    -     #elem{name = <<"GEO">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_geo, '$lat', '$lon'},
    -           refs = [#ref{name = vcard_LAT, min = 0, max = 1, label = '$lat'},
    -                   #ref{name = vcard_LON, min = 0, max = 1, label = '$lon'}]}).
    -
    --xml(vcard_BINVAL,
    -     #elem{name = <<"BINVAL">>,
    -           xmlns = <<"vcard-temp">>,
    -           cdata = #cdata{dec = {base64, decode, []},
    -                          enc = {base64, encode, []}},
    -           result = '$cdata'}).
    -
    --xml(vcard_LOGO,
    -     #elem{name = <<"LOGO">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_logo, '$type', '$binval', '$extval'},
    -           refs = [#ref{name = vcard_TYPE, min = 0, max = 1, label = '$type'},
    -                   #ref{name = vcard_BINVAL, min = 0, max = 1, label = '$binval'},
    -                   #ref{name = vcard_EXTVAL, min = 0, max = 1, label = '$extval'}]}).
    -
    --xml(vcard_PHOTO,
    -     #elem{name = <<"PHOTO">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_photo, '$type', '$binval', '$extval'},
    -           refs = [#ref{name = vcard_TYPE, min = 0, max = 1, label = '$type'},
    -                   #ref{name = vcard_BINVAL, min = 0, max = 1, label = '$binval'},
    -                   #ref{name = vcard_EXTVAL, min = 0, max = 1, label = '$extval'}]}).
    -
    --xml(vcard_ORG,
    -     #elem{name = <<"ORG">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_org, '$name', '$units'},
    -           refs = [#ref{name = vcard_ORGNAME,
    -                        label = '$name',
    -                        min = 0, max = 1},
    -                   #ref{name = vcard_ORGUNIT,
    -                        label = '$units'}]}).
    -
    --xml(vcard_SOUND,
    -     #elem{name = <<"SOUND">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_sound, '$phonetic', '$binval', '$extval'},
    -           refs = [#ref{name = vcard_BINVAL, min = 0, max = 1, label = '$binval'},
    -                   #ref{name = vcard_EXTVAL, min = 0, max = 1, label = '$extval'},
    -                   #ref{name = vcard_PHONETIC, min = 0, max = 1, label = '$phonetic'}]}).
    -
    --xml(vcard_KEY,
    -     #elem{name = <<"KEY">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard_key, '$type', '$cred'},
    -           refs = [#ref{name = vcard_TYPE, min = 0, max = 1, label = '$type'},
    -                   #ref{name = vcard_CRED, min = 0, max = 1, label = '$cred'}]}).
    -
    --xml(vcard_CATEGORIES,
    -     #elem{name = <<"CATEGORIES">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = '$keywords',
    -           refs = [#ref{name = vcard_KEYWORD, label = '$keywords'}]}).
    -
    --xml(vcard_CLASS,
    -     #elem{name = <<"CLASS">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = '$class',
    -           refs = [#ref{name = vcard_PUBLIC, min = 0, max = 1, label = '$class'},
    -                   #ref{name = vcard_PRIVATE, min = 0, max = 1, label = '$class'},
    -                   #ref{name = vcard_CONFIDENTIAL, min = 0, max = 1, label = '$class'}]}).
    -
    -%% {vcard_AGENT,
    -%%  #elem{name = <<"AGENT">>,
    -%%        xmlns = <<"vcard-temp">>,
    -%%        result = {vcard_agent, '$vcard', '$extval'},
    -%%        refs = [#ref{name = vcard, min = 0, max = 1, label = '$vcard'},
    -%%                #ref{name = vcard_EXTVAL, min = 0, max = 1, label = '$extval'}]}).
    -
    --xml(vcard,
    -     #elem{name = <<"vCard">>,
    -           xmlns = <<"vcard-temp">>,
    -           result = {vcard, '$version', '$fn', '$n', '$nickname', '$photo',
    -                     '$bday', '$adr', '$label', '$tel', '$email', '$jabberid',
    -                     '$mailer', '$tz', '$geo', '$title', '$role', '$logo',
    -                     '$org', '$categories', '$note', '$prodid', %% '$agent',
    -                     '$rev', '$sort_string', '$sound', '$uid', '$url', '$class',
    -                     '$key', '$desc'},
    -           refs = [#ref{name = vcard_N, min = 0, max = 1, label = '$n'},
    -                   #ref{name = vcard_ADR, label = '$adr'},
    -                   #ref{name = vcard_LABEL, label = '$label'},
    -                   #ref{name = vcard_TEL, label = '$tel'},
    -                   #ref{name = vcard_EMAIL, label = '$email'},
    -                   #ref{name = vcard_GEO, min = 0, max = 1, label = '$geo'},
    -                   #ref{name = vcard_LOGO, min = 0, max = 1, label = '$logo'},
    -                   #ref{name = vcard_PHOTO, min = 0, max = 1, label = '$photo'},
    -                   #ref{name = vcard_ORG, min = 0, max = 1, label = '$org'},
    -                   #ref{name = vcard_SOUND, min = 0, max = 1, label = '$sound'},
    -                   #ref{name = vcard_KEY, min = 0, max = 1, label = '$key'},
    -                   #ref{name = vcard_VERSION, min = 0, max = 1, label = '$version'},
    -                   #ref{name = vcard_FN, min = 0, max = 1, label = '$fn'},
    -                   #ref{name = vcard_NICKNAME, min = 0, max = 1, label = '$nickname'},
    -                   #ref{name = vcard_BDAY, min = 0, max = 1, label = '$bday'},
    -                   #ref{name = vcard_JABBERID, min = 0, max = 1, label = '$jabberid'},
    -                   #ref{name = vcard_MAILER, min = 0, max = 1, label = '$mailer'},
    -                   #ref{name = vcard_TZ, min = 0, max = 1, label = '$tz'},
    -                   #ref{name = vcard_TITLE, min = 0, max = 1, label = '$title'},
    -                   #ref{name = vcard_ROLE, min = 0, max = 1, label = '$role'},
    -                   #ref{name = vcard_NOTE, min = 0, max = 1, label = '$note'},
    -                   #ref{name = vcard_PRODID, min = 0, max = 1, label = '$prodid'},
    -                   #ref{name = vcard_REV, min = 0, max = 1, label = '$rev'},
    -                   %%#ref{name = vcard_AGENT, min = 0, max = 1, label = '$agent'},
    -                   #ref{name = vcard_SORT_STRING, min = 0, max = 1,
    -                        label = '$sort_string'},
    -                   #ref{name = vcard_UID, min = 0, max = 1, label = '$uid'},
    -                   #ref{name = vcard_URL, min = 0, max = 1, label = '$url'},
    -                   #ref{name = vcard_DESC, min = 0, max = 1, label = '$desc'},
    -                   #ref{name = vcard_CATEGORIES, default = [], min = 0, max = 1,
    -                        label = '$categories'},
    -                   #ref{name = vcard_CLASS, min = 0, max = 1, label = '$class'}]}).
    -
    --xml(vcard_xupdate_photo,
    -     #elem{name = <<"photo">>,
    -	   xmlns = <<"vcard-temp:x:update">>,
    -	   result = '$cdata'}).
    -
    --xml(vcard_xupdate,
    -     #elem{name = <<"x">>,
    -	   xmlns = <<"vcard-temp:x:update">>,
    -	   result = {vcard_xupdate, '$photo'},
    -	   refs = [#ref{name = vcard_xupdate_photo, min = 0, max = 1,
    -			label = '$photo'}]}).
    -
    --xml(xdata_field_required,
    -     #elem{name = <<"required">>,
    -           xmlns = <<"jabber:x:data">>,
    -           result = true}).
    -
    --xml(xdata_field_desc,
    -     #elem{name = <<"desc">>, xmlns = <<"jabber:x:data">>, result = '$cdata'}).
    -
    --xml(xdata_field_value,
    -     #elem{name = <<"value">>, xmlns = <<"jabber:x:data">>, result = '$cdata'}).
    -
    --xml(xdata_field_option,
    -     #elem{name = <<"option">>,
    -           xmlns = <<"jabber:x:data">>,
    -           result = '$value',
    -           refs = [#ref{name = xdata_field_value,
    -                        label = '$value',
    -                        min = 1, max = 1}]}).
    -
    --xml(xdata_field,
    -     #elem{name = <<"field">>,
    -           xmlns = <<"jabber:x:data">>,
    -           result = {xdata_field, '$label', '$type', '$var',
    -                     '$required', '$desc', '$values', '$options'},
    -           attrs = [#attr{name = <<"label">>},
    -                    #attr{name = <<"type">>,
    -                          enc = {enc_enum, []},
    -                          dec = {dec_enum, [['boolean',
    -                                             'fixed',
    -                                             'hidden',
    -                                             'jid-multi',
    -                                             'jid-single',
    -                                             'list-multi',
    -                                             'list-single',
    -                                             'text-multi',
    -                                             'text-private',
    -                                             'text-single']]}},
    -                    #attr{name = <<"var">>}],
    -           refs = [#ref{name = xdata_field_required,
    -                        label = '$required',
    -                        default = false,
    -                        min = 0, max = 1},
    -                   #ref{name = xdata_field_desc,
    -                        label = '$desc',
    -                        min = 0, max = 1},
    -                   #ref{name = xdata_field_value,
    -                        label = '$values'},
    -                   #ref{name = xdata_field_option,
    -                        label = '$options'}]}).
    -
    --xml(xdata_instructions,  #elem{name = <<"instructions">>,
    -                                xmlns = <<"jabber:x:data">>,
    -                                result = '$cdata'}).
    --xml(xdata_title, #elem{name = <<"title">>,
    -                        xmlns = <<"jabber:x:data">>,
    -                        result = '$cdata'}).
    --xml(xdata_reported, #elem{name = <<"reported">>,
    -                           xmlns = <<"jabber:x:data">>,
    -                           result = '$fields',
    -                           refs = [#ref{name = xdata_field,
    -                                        label = '$fields'}]}).
    --xml(xdata_item,  #elem{name = <<"item">>,
    -                        xmlns = <<"jabber:x:data">>,
    -                        result = '$fields',
    -                        refs = [#ref{name = xdata_field,
    -                                     label = '$fields'}]}).
    -
    --xml(xdata,
    -     #elem{name = <<"x">>,
    -           xmlns = <<"jabber:x:data">>,
    -           result = {xdata, '$type', '$instructions', '$title',
    -                     '$reported', '$items', '$fields'},
    -           attrs = [#attr{name = <<"type">>,
    -                          required = true,
    -                          dec = {dec_enum, [[cancel, form, result, submit]]},
    -                          enc = {enc_enum, []}}],
    -           refs = [#ref{name = xdata_instructions,
    -                        label = '$instructions'},
    -                   #ref{name = xdata_title,
    -                        label = '$title',
    -                        min = 0, max = 1},
    -                   #ref{name = xdata_reported,
    -                        label = '$reported',
    -                        min = 0, max = 1},
    -                   #ref{name = xdata_item,
    -                        label = '$items'},
    -                   #ref{name = xdata_field,
    -                        label = '$fields'}]}).
    -
    --xml(pubsub_subscription,
    -     #elem{name = <<"subscription">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub_subscription, '$jid', '$node', '$subid',
    -                     '$type'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"node">>},
    -                    #attr{name = <<"subid">>},
    -                    #attr{name = <<"subscription">>,
    -                          label = '$type',
    -                          dec = {dec_enum, [[none, pending, subscribed,
    -                                             unconfigured]]},
    -                          enc = {enc_enum, []}}]}).
    -
    --xml(pubsub_affiliation,
    -     #elem{name = <<"affiliation">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub_affiliation, '$node', '$type'},
    -           attrs = [#attr{name = <<"node">>,
    -                          required = true},
    -                    #attr{name = <<"affiliation">>,
    -                          label = '$type',
    -                          required = true,
    -                          dec = {dec_enum, [[member, none, outcast, owner,
    -                                             publisher, 'publish-only']]},
    -                          enc = {enc_enum, []}}]}).
    -
    --xml(pubsub_item,
    -     #elem{name = <<"item">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub_item, '$id', '$_xmls'},
    -           attrs = [#attr{name = <<"id">>}]}).
    -
    --xml(pubsub_items,
    -     #elem{name = <<"items">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub_items, '$node', '$max_items',
    -                     '$subid', '$items'},
    -           attrs = [#attr{name = <<"max_items">>,
    -                          dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -                    #attr{name = <<"node">>,
    -                          required = true},
    -                    #attr{name = <<"subid">>}],
    -           refs = [#ref{name = pubsub_item, label = '$items'}]}).
    -
    --xml(pubsub_event_retract,
    -     #elem{name = <<"retract">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub#event">>,
    -           result = '$id',
    -           attrs = [#attr{name = <<"id">>, required = true}]}).
    -
    --xml(pubsub_event_item,
    -     #elem{name = <<"item">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub#event">>,
    -           result = {pubsub_event_item, '$id', '$node', '$publisher', '$_xmls'},
    -           attrs = [#attr{name = <<"id">>},
    -                    #attr{name = <<"node">>},
    -                    #attr{name = <<"publisher">>}]}).
    -
    --xml(pubsub_event_items,
    -     #elem{name = <<"items">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub#event">>,
    -           result = {pubsub_event_items, '$node', '$retract', '$items'},
    -           attrs = [#attr{name = <<"node">>,
    -                          required = true}],
    -           refs = [#ref{name = pubsub_event_retract, label = '$retract'},
    -                   #ref{name = pubsub_event_item, label = '$items'}]}).
    -
    --xml(pubsub_event,
    -     #elem{name = <<"event">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub#event">>,
    -           result = {pubsub_event, '$items'},
    -           refs = [#ref{name = pubsub_event_items, label = '$items'}]}).
    -
    --xml(pubsub_subscriptions,
    -     #elem{name = <<"subscriptions">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {'$node', '$subscriptions'},
    -           attrs = [#attr{name = <<"node">>,
    -                          default = none}],
    -           refs = [#ref{name = pubsub_subscription, label = '$subscriptions'}]}).
    -
    --xml(pubsub_affiliations,
    -     #elem{name = <<"affiliations">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = '$affiliations',
    -           refs = [#ref{name = pubsub_affiliation, label = '$affiliations'}]}).
    -
    --xml(pubsub_subscribe,
    -     #elem{name = <<"subscribe">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub_subscribe, '$node', '$jid'},
    -           attrs = [#attr{name = <<"node">>},
    -                    #attr{name = <<"jid">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}]}).
    -
    --xml(pubsub_unsubscribe,
    -     #elem{name = <<"unsubscribe">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub_unsubscribe, '$node', '$jid', '$subid'},
    -           attrs = [#attr{name = <<"node">>},
    -                    #attr{name = <<"subid">>},
    -                    #attr{name = <<"jid">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}]}).
    -
    --xml(pubsub_publish,
    -     #elem{name = <<"publish">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub_publish, '$node', '$items'},
    -           attrs = [#attr{name = <<"node">>,
    -                          required = true}],
    -           refs = [#ref{name = pubsub_item, label = '$items'}]}).
    -
    --xml(pubsub_options,
    -     #elem{name = <<"options">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub_options, '$node', '$jid', '$subid', '$xdata'},
    -           attrs = [#attr{name = <<"node">>},
    -                    #attr{name = <<"subid">>},
    -                    #attr{name = <<"jid">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}],
    -           refs = [#ref{name = xdata, min = 0, max = 1,
    -                        label = '$xdata'}]}).
    -
    --xml(pubsub_retract,
    -     #elem{name = <<"retract">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub_retract, '$node', '$notify', '$items'},
    -           attrs = [#attr{name = <<"node">>,
    -                          required = true},
    -                    #attr{name = <<"notify">>,
    -                          default = false,
    -                          dec = {dec_bool, []},
    -                          enc = {enc_bool, []}}],
    -           refs = [#ref{name = pubsub_item, label = '$items'}]}).
    -
    --xml(pubsub,
    -     #elem{name = <<"pubsub">>,
    -           xmlns = <<"http://jabber.org/protocol/pubsub">>,
    -           result = {pubsub, '$subscriptions', '$affiliations', '$publish',
    -                     '$subscribe', '$unsubscribe', '$options', '$items',
    -                     '$retract'},
    -           refs = [#ref{name = pubsub_subscriptions, label = '$subscriptions',
    -                        min = 0, max = 1},
    -                   #ref{name = pubsub_affiliations, label = '$affiliations',
    -                        min = 0, max = 1},
    -                   #ref{name = pubsub_subscribe, label = '$subscribe',
    -                        min = 0, max = 1},
    -                   #ref{name = pubsub_unsubscribe, label = '$unsubscribe',
    -                        min = 0, max = 1},
    -                   #ref{name = pubsub_options, label = '$options',
    -                        min = 0, max = 1},
    -                   #ref{name = pubsub_items, label = '$items',
    -                        min = 0, max = 1},
    -                   #ref{name = pubsub_retract, label = '$retract',
    -                        min = 0, max = 1},
    -                   #ref{name = pubsub_publish, label = '$publish',
    -                        min = 0, max = 1}]}).
    -
    --xml(shim_header,
    -     #elem{name = <<"header">>,
    -           xmlns = <<"http://jabber.org/protocol/shim">>,
    -           result = {'$name', '$cdata'},
    -           attrs = [#attr{name = <<"name">>,
    -                          required = true}]}).
    -
    --xml(shim_headers,
    -     #elem{name = <<"headers">>,
    -           xmlns = <<"http://jabber.org/protocol/shim">>,
    -           result = {shim, '$headers'},
    -           refs = [#ref{name = shim_header, label = '$headers'}]}).
    -
    --record(chatstate, {type :: active | composing | gone | inactive | paused}).
    -
    --xml(chatstate_active,
    -     #elem{name = <<"active">>,
    -           xmlns = <<"http://jabber.org/protocol/chatstates">>,
    -           result = {chatstate, active}}).
    -
    --xml(chatstate_composing,
    -     #elem{name = <<"composing">>,
    -           xmlns = <<"http://jabber.org/protocol/chatstates">>,
    -           result = {chatstate, composing}}).
    -
    --xml(chatstate_gone,
    -     #elem{name = <<"gone">>,
    -           xmlns = <<"http://jabber.org/protocol/chatstates">>,
    -           result = {chatstate, gone}}).
    -
    --xml(chatstate_inactive,
    -     #elem{name = <<"inactive">>,
    -           xmlns = <<"http://jabber.org/protocol/chatstates">>,
    -           result = {chatstate, inactive}}).
    -
    --xml(chatstate_paused,
    -     #elem{name = <<"paused">>,
    -           xmlns = <<"http://jabber.org/protocol/chatstates">>,
    -           result = {chatstate, paused}}).
    -
    --xml(delay,
    -     #elem{name = <<"delay">>,
    -           xmlns = <<"urn:xmpp:delay">>,
    -           result = {delay, '$stamp', '$from'},
    -           attrs = [#attr{name = <<"stamp">>,
    -                          required = true,
    -                          dec = {dec_utc, []},
    -                          enc = {enc_utc, []}},
    -                    #attr{name = <<"from">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}]}).
    -
    --xml(bytestreams_streamhost,
    -     #elem{name = <<"streamhost">>,
    -           xmlns = <<"http://jabber.org/protocol/bytestreams">>,
    -           result = {streamhost, '$jid', '$host', '$port'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"host">>,
    -                          required = true},
    -                    #attr{name = <<"port">>,
    -                          default = 1080,
    -                          dec = {dec_int, [0, 65535]},
    -                          enc = {enc_int, []}}]}).
    -
    --xml(bytestreams_streamhost_used,
    -     #elem{name = <<"streamhost-used">>,
    -           xmlns = <<"http://jabber.org/protocol/bytestreams">>,
    -           result = '$jid',
    -           attrs = [#attr{name = <<"jid">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}]}).
    -
    --xml(bytestreams_activate,
    -     #elem{name = <<"activate">>,
    -           xmlns = <<"http://jabber.org/protocol/bytestreams">>,
    -           cdata = #cdata{enc = {enc_jid, []}, dec = {dec_jid, []}},
    -           result = '$cdata'}).
    -
    --xml(bytestreams,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"http://jabber.org/protocol/bytestreams">>,
    -           result = {bytestreams, '$hosts', '$used', '$activate',
    -                     '$dstaddr', '$mode', '$sid'},
    -           attrs = [#attr{name = <<"dstaddr">>},
    -                    #attr{name = <<"sid">>},
    -                    #attr{name = <<"mode">>,
    -                          default = tcp,
    -                          dec = {dec_enum, [[tcp, udp]]},
    -                          enc = {enc_enum, []}}],
    -           refs = [#ref{name = bytestreams_streamhost, label = '$hosts'},
    -                   #ref{name = bytestreams_streamhost_used,
    -                        min = 0, max = 1, label = '$used'},
    -                   #ref{name = bytestreams_activate,
    -                        min = 0, max = 1, label = '$activate'}]}).
    -
    --xml(muc_history,
    -     #elem{name = <<"history">>,
    -           xmlns = <<"http://jabber.org/protocol/muc">>,
    -           result = {muc_history, '$maxchars', '$maxstanzas',
    -                     '$seconds', '$since'},
    -           attrs = [#attr{name = <<"maxchars">>,
    -                          dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -                    #attr{name = <<"maxstanzas">>,
    -                          dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -                    #attr{name = <<"seconds">>,
    -                          dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -                    #attr{name = <<"since">>,
    -                          dec = {dec_utc, []},
    -                          enc = {enc_utc, []}}]}).
    -
    --xml(muc_user_reason,
    -     #elem{name = <<"reason">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#user">>,
    -           result = '$cdata'}).
    -
    --xml(muc_user_decline,
    -     #elem{name = <<"decline">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#user">>,
    -           result = {muc_decline, '$reason', '$from', '$to'},
    -           attrs = [#attr{name = <<"to">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"from">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}],
    -           refs = [#ref{name = muc_user_reason, min = 0,
    -                        max = 1, label = '$reason'}]}).
    -
    --xml(muc_user_destroy,
    -     #elem{name = <<"destroy">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#user">>,
    -           result = {muc_user_destroy, '$reason', '$jid'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}],
    -           refs = [#ref{name = muc_user_reason, min = 0,
    -                        max = 1, label = '$reason'}]}).
    -
    --xml(muc_user_invite,
    -     #elem{name = <<"invite">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#user">>,
    -           result = {muc_invite, '$reason', '$from', '$to'},
    -           attrs = [#attr{name = <<"to">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"from">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}],
    -           refs = [#ref{name = muc_user_reason, min = 0,
    -                        max = 1, label = '$reason'}]}).
    -
    --xml(muc_user_actor,
    -     #elem{name = <<"actor">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#user">>,
    -           result = {muc_actor, '$jid', '$nick'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"nick">>}]}).
    -
    --xml(muc_user_continue,
    -     #elem{name = <<"continue">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#user">>,
    -           result = '$thread',
    -           attrs = [#attr{name = <<"thread">>}]}).
    -
    --xml(muc_user_status,
    -     #elem{name = <<"status">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#user">>,
    -           result = '$code',
    -           attrs = [#attr{name = <<"code">>,
    -                          dec = {dec_int, [100, 999]},
    -                          enc = {enc_int, []}}]}).
    -
    --xml(muc_user_item,
    -     #elem{name = <<"item">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#user">>,
    -           result = {muc_item, '$actor', '$continue', '$reason',
    -                     '$affiliation', '$role', '$jid', '$nick'},
    -           refs = [#ref{name = muc_user_actor,
    -                        min = 0, max = 1, label = '$actor'},
    -                   #ref{name = muc_user_continue,
    -                        min = 0, max = 1, label = '$continue'},
    -                   #ref{name = muc_user_reason,
    -                        min = 0, max = 1, label = '$reason'}],
    -           attrs = [#attr{name = <<"affiliation">>,
    -                          dec = {dec_enum, [[admin, member, none,
    -                                             outcast, owner]]},
    -                          enc = {enc_enum, []}},
    -                    #attr{name = <<"role">>,
    -                          dec = {dec_enum, [[moderator, none,
    -                                             participant, visitor]]},
    -                          enc = {enc_enum, []}},
    -                    #attr{name = <<"jid">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"nick">>}]}).
    -
    --xml(muc_user,
    -     #elem{name = <<"x">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#user">>,
    -           result = {muc_user, '$decline', '$destroy', '$invites',
    -                     '$items', '$status_codes', '$password'},
    -           attrs = [#attr{name = <<"password">>}],
    -           refs = [#ref{name = muc_user_decline, min = 0,
    -                        max = 1, label = '$decline'},
    -                   #ref{name = muc_user_destroy, min = 0, max = 1,
    -                        label = '$destroy'},
    -                   #ref{name = muc_user_invite, label = '$invites'},
    -                   #ref{name = muc_user_item, label = '$items'},
    -                   #ref{name = muc_user_status, label = '$status_codes'}]}).
    -
    --xml(muc_owner_password,
    -     #elem{name = <<"password">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#owner">>,
    -           result = '$cdata'}).
    -
    --xml(muc_owner_reason,
    -     #elem{name = <<"reason">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#owner">>,
    -           result = '$cdata'}).
    -
    --xml(muc_owner_destroy,
    -     #elem{name = <<"destroy">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#owner">>,
    -           result = {muc_owner_destroy, '$jid', '$reason', '$password'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}],
    -           refs = [#ref{name = muc_owner_password, min = 0, max = 1,
    -                        label = '$password'},
    -                   #ref{name = muc_owner_reason, min = 0, max = 1,
    -                        label = '$reason'}]}).
    -
    --xml(muc_owner,
    -     #elem{name = <<"query">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#owner">>,
    -           result = {muc_owner, '$destroy', '$config'},
    -           refs = [#ref{name = muc_owner_destroy, min = 0, max = 1,
    -                        label = '$destroy'},
    -                   #ref{name = xdata, min = 0, max = 1, label = '$config'}]}).
    -
    --xml(muc_admin_item,
    -     #elem{name = <<"item">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#admin">>,
    -           result = {muc_item, '$actor', '$continue', '$reason',
    -                     '$affiliation', '$role', '$jid', '$nick'},
    -           refs = [#ref{name = muc_admin_actor,
    -                        min = 0, max = 1, label = '$actor'},
    -                   #ref{name = muc_admin_continue,
    -                        min = 0, max = 1, label = '$continue'},
    -                   #ref{name = muc_admin_reason,
    -                        min = 0, max = 1, label = '$reason'}],
    -           attrs = [#attr{name = <<"affiliation">>,
    -                          dec = {dec_enum, [[admin, member, none,
    -                                             outcast, owner]]},
    -                          enc = {enc_enum, []}},
    -                    #attr{name = <<"role">>,
    -                          dec = {dec_enum, [[moderator, none,
    -                                             participant, visitor]]},
    -                          enc = {enc_enum, []}},
    -                    #attr{name = <<"jid">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"nick">>}]}).
    -
    --xml(muc_admin_actor,
    -     #elem{name = <<"actor">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#admin">>,
    -           result = {muc_actor, '$jid', '$nick'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"nick">>}]}).
    -
    --xml(muc_admin_continue,
    -     #elem{name = <<"continue">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#admin">>,
    -           result = '$thread',
    -           attrs = [#attr{name = <<"thread">>}]}).
    -
    --xml(muc_admin_reason,
    -     #elem{name = <<"reason">>,
    -           xmlns = <<"http://jabber.org/protocol/muc#admin">>,
    -           result = '$cdata'}).
    -
    --xml(muc_admin,
    -     #elem{name = <<"query">>,
    -	   xmlns = <<"http://jabber.org/protocol/muc#admin">>,
    -	   result = {muc_admin, '$items'},
    -	   refs = [#ref{name = muc_admin_item, label = '$items'}]}).
    -
    --xml(muc,
    -     #elem{name = <<"x">>,
    -           xmlns = <<"http://jabber.org/protocol/muc">>,
    -           result = {muc, '$history', '$password'},
    -           attrs = [#attr{name = <<"password">>}],
    -           refs = [#ref{name = muc_history, min = 0, max = 1,
    -                        label = '$history'}]}).
    -
    --xml(rsm_after,
    -     #elem{name = <<"after">>,
    -           xmlns = <<"http://jabber.org/protocol/rsm">>,
    -           result = '$cdata'}).
    -
    --xml(rsm_before,
    -     #elem{name = <<"before">>,
    -           xmlns = <<"http://jabber.org/protocol/rsm">>,
    -	   cdata = #cdata{default = none},
    -           result = '$cdata'}).
    -
    --xml(rsm_last,
    -     #elem{name = <<"last">>,
    -           xmlns = <<"http://jabber.org/protocol/rsm">>,
    -           result = '$cdata'}).
    -
    --xml(rsm_count,
    -     #elem{name = <<"count">>, result = '$cdata',
    -           xmlns = <<"http://jabber.org/protocol/rsm">>,
    -           cdata = #cdata{dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}}}).
    -
    --xml(rsm_index,
    -     #elem{name = <<"index">>, result = '$cdata',
    -           xmlns = <<"http://jabber.org/protocol/rsm">>,
    -           cdata = #cdata{dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}}}).
    -
    --xml(rsm_max,
    -     #elem{name = <<"max">>, result = '$cdata',
    -           xmlns = <<"http://jabber.org/protocol/rsm">>,
    -           cdata = #cdata{dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}}}).
    -
    --xml(rsm_first,
    -     #elem{name = <<"first">>,
    -           xmlns = <<"http://jabber.org/protocol/rsm">>,
    -           result = {rsm_first, '$index', '$data'},
    -           cdata = #cdata{label = '$data'},
    -           attrs = [#attr{name = <<"index">>,
    -                          dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}}]}).
    -
    --xml(rsm_set,
    -     #elem{name = <<"set">>,
    -           xmlns = <<"http://jabber.org/protocol/rsm">>,
    -           result = {rsm_set, '$after', '$before', '$count',
    -                     '$first', '$index', '$last', '$max'},
    -           refs = [#ref{name = rsm_after, label = '$after', min = 0, max = 1},
    -                   #ref{name = rsm_before, label = '$before', min = 0, max = 1},
    -                   #ref{name = rsm_count, label = '$count', min = 0, max = 1},
    -                   #ref{name = rsm_first, label = '$first', min = 0, max = 1},
    -                   #ref{name = rsm_index, label = '$index', min = 0, max = 1},
    -                   #ref{name = rsm_last, label = '$last', min = 0, max = 1},
    -                   #ref{name = rsm_max, label = '$max', min = 0, max = 1}]}).
    -
    --xml(mam_start,
    -     #elem{name = <<"start">>,
    -           xmlns = <<"urn:xmpp:mam:tmp">>,
    -           result = '$cdata',
    -           cdata = #cdata{required = true,
    -                          dec = {dec_utc, []},
    -                          enc = {enc_utc, []}}}).
    -
    --xml(mam_end,
    -     #elem{name = <<"end">>,
    -           xmlns = <<"urn:xmpp:mam:tmp">>,
    -           result = '$cdata',
    -           cdata = #cdata{required = true,
    -                          dec = {dec_utc, []},
    -                          enc = {enc_utc, []}}}).
    -
    --xml(mam_with,
    -     #elem{name = <<"with">>,
    -           xmlns = <<"urn:xmpp:mam:tmp">>,
    -           result = '$cdata',
    -           cdata = #cdata{required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}}).
    -
    --xml(mam_query,
    -     #elem{name = <<"query">>,
    -           xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
    -           result = {mam_query, '$xmlns', '$id', '$start', '$end', '$with',
    -		     '$rsm', '$xdata'},
    -           attrs = [#attr{name = <<"queryid">>, label = '$id'},
    -		    #attr{name = <<"xmlns">>}],
    -           refs = [#ref{name = mam_start, min = 0, max = 1, label = '$start'},
    -                   #ref{name = mam_end, min = 0, max = 1, label = '$end'},
    -                   #ref{name = mam_with, min = 0, max = 1, label = '$with'},
    -                   #ref{name = rsm_set, min = 0, max = 1, label = '$rsm'},
    -		   #ref{name = xdata, min = 0, max = 1, label = '$xdata'}]}).
    -
    --xml(mam_archived,
    -     #elem{name = <<"archived">>,
    -           xmlns = <<"urn:xmpp:mam:tmp">>,
    -           result = {mam_archived, '$by', '$id'},
    -           attrs = [#attr{name = <<"id">>},
    -                    #attr{name = <<"by">>,
    -                          required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}]}).
    -
    --xml(mam_result,
    -     #elem{name = <<"result">>,
    -           xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
    -           result = {mam_result, '$xmlns', '$queryid', '$id', '$_els'},
    -           attrs = [#attr{name = <<"queryid">>},
    -		    #attr{name = <<"xmlns">>},
    -                    #attr{name = <<"id">>}]}).
    -
    --xml(mam_jid,
    -     #elem{name = <<"jid">>,
    -           xmlns = <<"urn:xmpp:mam:tmp">>,
    -           result = '$cdata',
    -           cdata = #cdata{required = true,
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}}).
    -
    --xml(mam_never,
    -     #elem{name = <<"never">>,
    -           xmlns = <<"urn:xmpp:mam:tmp">>,
    -           result = '$jids',
    -           refs = [#ref{name = mam_jid, label = '$jids', default = []}]}).
    -
    --xml(mam_always,
    -     #elem{name = <<"always">>,
    -           xmlns = <<"urn:xmpp:mam:tmp">>,
    -           result = '$jids',
    -           refs = [#ref{name = mam_jid, label = '$jids', default = []}]}).
    -
    --xml(mam_prefs,
    -     #elem{name = <<"prefs">>,
    -           xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
    -           result = {mam_prefs, '$xmlns', '$default', '$always', '$never'},
    -           attrs = [#attr{name = <<"default">>,
    -                          dec = {dec_enum, [[always, never, roster]]},
    -                          enc = {enc_enum, []}},
    -		    #attr{name = <<"xmlns">>}],
    -           refs = [#ref{name = mam_always, label = '$always',
    -                        min = 0, max = 1, default = []},
    -                   #ref{name = mam_never, label = '$never',
    -                        min = 0, max = 1, default = []}]}).
    -
    --xml(mam_fin,
    -     #elem{name = <<"fin">>,
    -	   xmlns = <<"urn:xmpp:mam:0">>,
    -	   result = {mam_fin, '$id', '$rsm', '$stable', '$complete'},
    -	   attrs = [#attr{name = <<"queryid">>, label = '$id'},
    -		    #attr{name = <<"stable">>, label = '$stable',
    -			  dec = {dec_bool, []},
    -			  enc = {enc_bool, []}},
    -		    #attr{name = <<"complete">>, label = '$complete',
    -			  dec = {dec_bool, []},
    -			  enc = {enc_bool, []}}],
    -	   refs = [#ref{name = rsm_set, min = 0, max = 1, label = '$rsm'}]}).
    -
    --xml(forwarded,
    -     #elem{name = <<"forwarded">>,
    -           xmlns = <<"urn:xmpp:forward:0">>,
    -           result = {forwarded, '$delay', '$_els'},
    -           refs = [#ref{name = delay, min = 0,
    -                        max = 1, label = '$delay'}]}).
    -
    --xml(carbons_disable,
    -     #elem{name = <<"disable">>,
    -           xmlns = <<"urn:xmpp:carbons:2">>,
    -           result = {carbons_disable}}).
    -
    --xml(carbons_enable,
    -     #elem{name = <<"enable">>,
    -	   xmlns = <<"urn:xmpp:carbons:2">>,
    -	   result = {carbons_enable}}).
    -
    --xml(carbons_private,
    -     #elem{name = <<"private">>,
    -	   xmlns = <<"urn:xmpp:carbons:2">>,
    -	   result = {carbons_private}}).
    -
    --xml(carbons_received,
    -     #elem{name = <<"received">>,
    -	   xmlns = <<"urn:xmpp:carbons:2">>,
    -	   result = {carbons_received, '$forwarded'},
    -	   refs = [#ref{name = forwarded, min = 1,
    -                        max = 1, label = '$forwarded'}]}).
    -
    --xml(carbons_sent,
    -     #elem{name = <<"sent">>,
    -	   xmlns = <<"urn:xmpp:carbons:2">>,
    -	   result = {carbons_sent, '$forwarded'},
    -	   refs = [#ref{name = forwarded, min = 1,
    -                        max = 1, label = '$forwarded'}]}).
    -
    --xml(feature_csi,
    -     #elem{name = <<"csi">>,
    -	   xmlns = <<"urn:xmpp:csi:0">>,
    -	   result = {feature_csi, '$xmlns'},
    -	   attrs = [#attr{name = <<"xmlns">>}]}).
    -
    --record(csi, {type :: active | inactive}).
    -
    --xml(csi_active,
    -     #elem{name = <<"active">>,
    -	   xmlns = <<"urn:xmpp:csi:0">>,
    -	   result = {csi, active}}).
    -
    --xml(csi_inactive,
    -     #elem{name = <<"inactive">>,
    -	   xmlns = <<"urn:xmpp:csi:0">>,
    -	   result = {csi, inactive}}).
    -
    --xml(feature_sm,
    -     #elem{name = <<"sm">>,
    -	   xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
    -	   result = {feature_sm, '$xmlns'},
    -	   attrs = [#attr{name = <<"xmlns">>}]}).
    -
    --xml(sm_enable,
    -     #elem{name = <<"enable">>,
    -	   xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
    -	   result = {sm_enable, '$max', '$resume', '$xmlns'},
    -	   attrs = [#attr{name = <<"max">>,
    -			  dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -		    #attr{name = <<"xmlns">>},
    -		    #attr{name = <<"resume">>,
    -			  default = false,
    -			  dec = {dec_bool, []},
    -                          enc = {enc_bool, []}}]}).
    -
    --xml(sm_enabled,
    -     #elem{name = <<"enabled">>,
    -	   xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
    -	   result = {sm_enabled, '$id', '$location', '$max', '$resume', '$xmlns'},
    -	   attrs = [#attr{name = <<"id">>},
    -		    #attr{name = <<"location">>},
    -		    #attr{name = <<"xmlns">>},
    -		    #attr{name = <<"max">>,
    -			  dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -		    #attr{name = <<"resume">>,
    -			  default = false,
    -			  dec = {dec_bool, []},
    -                          enc = {enc_bool, []}}]}).
    -
    --xml(sm_resume,
    -     #elem{name = <<"resume">>,
    -	   xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
    -	   result = {sm_resume, '$h', '$previd', '$xmlns'},
    -	   attrs = [#attr{name = <<"h">>,
    -			  required = true,
    -			  dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -		    #attr{name = <<"xmlns">>},
    -		    #attr{name = <<"previd">>,
    -			  required = true}]}).
    -
    --xml(sm_resumed,
    -     #elem{name = <<"resumed">>,
    -	   xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
    -	   result = {sm_resumed, '$h', '$previd', '$xmlns'},
    -	   attrs = [#attr{name = <<"h">>,
    -			  required = true,
    -			  dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -		    #attr{name = <<"xmlns">>},
    -		    #attr{name = <<"previd">>,
    -			  required = true}]}).
    -
    --xml(sm_r,
    -     #elem{name = <<"r">>,
    -	   xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
    -	   result = {sm_r, '$xmlns'},
    -	   attrs = [#attr{name = <<"xmlns">>}]}).
    -
    --xml(sm_a,
    -     #elem{name = <<"a">>,
    -	   xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
    -	   result = {sm_a, '$h', '$xmlns'},
    -	   attrs = [#attr{name = <<"h">>,
    -			  required = true,
    -			  dec = {dec_int, [0, infinity]},
    -                          enc = {enc_int, []}},
    -		    #attr{name = <<"xmlns">>}]}).
    -
    --xml(sm_failed,
    -     #elem{name = <<"failed">>,
    -	   xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
    -	   result = {sm_failed, '$reason', '$h', '$xmlns'},
    -	   attrs = [#attr{name = <<"h">>,
    -			  dec = {dec_int, [0, infinity]},
    -			  enc = {enc_int, []}},
    -		    #attr{name = <<"xmlns">>}],
    -	   refs = [#ref{name = error_bad_request,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_conflict,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_feature_not_implemented,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_forbidden,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_gone,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_internal_server_error,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_item_not_found,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_jid_malformed,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_not_acceptable,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_not_allowed,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_not_authorized,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_policy_violation,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_recipient_unavailable,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_redirect,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_registration_required,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_remote_server_not_found,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_remote_server_timeout,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_resource_constraint,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_service_unavailable,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_subscription_required,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_undefined_condition,
    -                        min = 0, max = 1, label = '$reason'},
    -                   #ref{name = error_unexpected_request,
    -                        min = 0, max = 1, label = '$reason'}]}).
    -
    --xml(offline_purge,
    -     #elem{name = <<"purge">>,
    -	   xmlns = <<"http://jabber.org/protocol/offline">>,
    -	   result = true}).
    -
    --xml(offline_fetch,
    -     #elem{name = <<"fetch">>,
    -	   xmlns = <<"http://jabber.org/protocol/offline">>,
    -	   result = true}).
    -
    --xml(offline_item,
    -     #elem{name = <<"item">>,
    -	   xmlns = <<"http://jabber.org/protocol/offline">>,
    -	   result = {offline_item, '$node', '$action'},
    -	   attrs = [#attr{name = <<"node">>},
    -		    #attr{name = <<"action">>,
    -			  dec = {dec_enum, [[view, remove]]},
    -                          enc = {enc_enum, []}}]}).
    -
    --xml(offline,
    -     #elem{name = <<"offline">>,
    -	   xmlns = <<"http://jabber.org/protocol/offline">>,
    -	   result = {offline, '$items', '$purge', '$fetch'},
    -	   refs = [#ref{name = offline_purge, min = 0, max = 1,
    -			label = '$purge', default = false},
    -		   #ref{name = offline_fetch, min = 0, max = 1,
    -			label = '$fetch', default = false},
    -		   #ref{name = offline_item, min = 0, label = '$items'}]}).
    -
    --xml(mix_subscribe,
    -     #elem{name = <<"subscribe">>,
    -           xmlns = <<"urn:xmpp:mix:0">>,
    -           result = '$node',
    -           attrs = [#attr{name = <<"node">>,
    -                          required = true,
    -                          label = '$node'}]}).
    -
    --xml(mix_join,
    -     #elem{name = <<"join">>,
    -           xmlns = <<"urn:xmpp:mix:0">>,
    -           result = {mix_join, '$jid', '$subscribe'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          label = '$jid',
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}}],
    -           refs = [#ref{name = mix_subscribe, min = 0, label = '$subscribe'}]}).
    -
    --xml(mix_leave,
    -     #elem{name = <<"leave">>,
    -           xmlns = <<"urn:xmpp:mix:0">>,
    -           result = {mix_leave}}).
    -
    --xml(mix_participant,
    -     #elem{name = <<"participant">>,
    -           xmlns = <<"urn:xmpp:mix:0">>,
    -           result = {mix_participant, '$jid', '$nick'},
    -           attrs = [#attr{name = <<"jid">>,
    -                          required = true,
    -                          label = '$jid',
    -                          dec = {dec_jid, []},
    -                          enc = {enc_jid, []}},
    -                    #attr{name = <<"nick">>,
    -                          label = '$nick'}]}).
    -
    -dec_tzo(Val) ->
    -    [H1, M1] = str:tokens(Val, <<":">>),
    -    H = jlib:binary_to_integer(H1),
    -    M = jlib:binary_to_integer(M1),
    -    if H >= -12, H =< 12, M >= 0, M < 60  ->
    -            {H, M}
    -    end.
    -
    -enc_tzo({H, M}) ->
    -    Sign = if H >= 0 ->
    -                   <<>>;
    -              true ->
    -                   <<"-">>
    -           end,
    -    list_to_binary([Sign, io_lib:format("~2..0w:~2..0w", [H, M])]).
    -
    -dec_utc(Val) ->
    -    {_, _, _} = jlib:datetime_string_to_timestamp(Val).
    -
    -enc_utc(Val) ->
    -    jlib:now_to_utc_string(Val).
    -
    -dec_jid(Val) ->
    -    case jid:from_string(Val) of
    -        error ->
    -            erlang:error(badarg);
    -        J ->
    -            J
    -    end.
    -
    -enc_jid(J) ->            
    -    jid:to_string(J).
    -
    -resourceprep(R) ->
    -    case jid:resourceprep(R) of
    -        error ->
    -            erlang:error(badarg);
    -        R1 ->
    -            R1
    -    end.
    -
    -dec_bool(<<"false">>) -> false;
    -dec_bool(<<"0">>) -> false;
    -dec_bool(<<"true">>) -> true;
    -dec_bool(<<"1">>) -> true.
    -
    -enc_bool(false) -> <<"false">>;
    -enc_bool(true) -> <<"true">>.
    -
    -%% Local Variables:
    -%% mode: erlang
    -%% End:
    -%% vim: set filetype=erlang tabstop=8: