Compare commits
21 Commits
master
...
obsolete-m
Author | SHA1 | Date |
---|---|---|
Holger Weiss | 8e43a20f12 | |
Holger Weiss | 91e4c56e67 | |
Holger Weiss | d7fd0f5d68 | |
Holger Weiss | f2652f613f | |
Holger Weiss | 3e683a7621 | |
Holger Weiss | 92a44dfcda | |
Holger Weiss | 4feba7a5e6 | |
Holger Weiss | 7697756fb4 | |
Holger Weiss | 7e2eb9a578 | |
Holger Weiss | 1477a92b8a | |
Badlop | 51ab9b2467 | |
Holger Weiss | 81d926142d | |
Holger Weiss | 1b726e3d9c | |
Holger Weiss | 315d66868f | |
Holger Weiss | f35d3021f6 | |
Holger Weiss | 1bc92c83d5 | |
Holger Weiss | 9c17da232f | |
Holger Weiss | e5d24089d2 | |
Holger Weiss | e0557b9e26 | |
Holger Weiss | 316bac7b8a | |
Holger Weiss | 7c55651da7 |
|
@ -1,162 +0,0 @@
|
||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- '**.spec'
|
|
||||||
- '**.txt'
|
|
||||||
- '*/conf/*.yml'
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- '**.spec'
|
|
||||||
- '**.txt'
|
|
||||||
- '*/conf/*.yml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
tests:
|
|
||||||
name: Tests
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
otp: ['21.3', '25.3']
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: erlang:${{ matrix.otp }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: Checkout ejabberd
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
repository: processone/ejabberd
|
|
||||||
|
|
||||||
- name: Checkout ejabberd-contrib
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
path: .ejabberd-modules/sources/ejabberd-contrib
|
|
||||||
|
|
||||||
- name: Get a compatible Rebar3
|
|
||||||
if: matrix.otp <= '21.3'
|
|
||||||
run: |
|
|
||||||
rm rebar3
|
|
||||||
wget https://github.com/processone/ejabberd/raw/21.12/rebar3
|
|
||||||
chmod +x rebar3
|
|
||||||
|
|
||||||
- name: Prepare libraries
|
|
||||||
run: |
|
|
||||||
apt-get -qq update
|
|
||||||
apt-get -y purge libgd3 nginx
|
|
||||||
apt-get -qq install libexpat1-dev libgd-dev libpam0g-dev \
|
|
||||||
libsqlite3-dev libwebp-dev libyaml-dev
|
|
||||||
|
|
||||||
- name: Prepare rebar
|
|
||||||
id: rebar
|
|
||||||
run: |
|
|
||||||
echo '{xref_ignores, [{eldap_filter_yecc, return_error, 2},
|
|
||||||
{fusco_lib, split_credentials, 1},
|
|
||||||
{http_uri, encode, 1},
|
|
||||||
{http_uri, decode, 1}
|
|
||||||
]}.' >>rebar.config
|
|
||||||
echo '{xref_checks, [deprecated_function_calls, deprecated_functions,
|
|
||||||
locals_not_used, undefined_function_calls, undefined_functions]}.
|
|
||||||
% Disabled: exports_not_used,' >>rebar.config
|
|
||||||
echo '{dialyzer, [{get_warnings, true}, {plt_extra_apps, [cache_tab,
|
|
||||||
eimp, epam, esip, ezlib, fast_tls, fast_xml, fast_yaml,
|
|
||||||
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
|
|
||||||
sqlite3, stringprep, stun, xmpp, yconf]} ]}.' >>rebar.config
|
|
||||||
echo '{ct_extra_params, "-verbosity 20"}.' >>rebar.config
|
|
||||||
|
|
||||||
- name: Remove syntax_tools from release
|
|
||||||
run: sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
|
|
||||||
|
|
||||||
- name: Cache rebar
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/rebar3/
|
|
||||||
key: ${{matrix.otp}}-${{hashFiles('rebar.config')}}
|
|
||||||
|
|
||||||
- name: Compile
|
|
||||||
run: |
|
|
||||||
./autogen.sh
|
|
||||||
./configure --with-rebar=./rebar3 \
|
|
||||||
--prefix=/tmp/ejabberd \
|
|
||||||
--enable-all \
|
|
||||||
--disable-elixir \
|
|
||||||
--disable-mssql \
|
|
||||||
--disable-odbc
|
|
||||||
make update
|
|
||||||
make
|
|
||||||
|
|
||||||
- name: Start ejabberd
|
|
||||||
run: |
|
|
||||||
echo "CONTRIB_MODULES_PATH=`pwd`/.ejabberd-modules" >> ejabberdctl.cfg.example
|
|
||||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
|
||||||
make dev
|
|
||||||
$CTL start
|
|
||||||
$CTL started
|
|
||||||
|
|
||||||
- name: Enable mod_muc_log
|
|
||||||
run: |
|
|
||||||
echo ' mod_muc_log: {}' >>.ejabberd-modules/sources/ejabberd-contrib/mod_muc_log_http/conf/mod_muc_log_http.yml
|
|
||||||
|
|
||||||
- name: Get list of available modules
|
|
||||||
run: |
|
|
||||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
|
||||||
$CTL modules_available | awk '{print $1}' | grep -v mod_captcha_rust >modules_available.txt
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: |
|
|
||||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
|
||||||
for i in `cat modules_available.txt` ; do
|
|
||||||
echo "Installing $i"
|
|
||||||
$CTL module_install $i
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Copy modules
|
|
||||||
run: |
|
|
||||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
|
||||||
for i in `cat modules_available.txt` ; do
|
|
||||||
echo "Copying from $i"
|
|
||||||
find .ejabberd-modules/sources/ejabberd-contrib/ -wholename "*/ejabberd-contrib/$i/include/*.hrl" -exec 'cp' '{}' 'include/' ';'
|
|
||||||
find .ejabberd-modules/sources/ejabberd-contrib/ -wholename "*/ejabberd-contrib/$i/src/*.erl" -exec 'cp' '{}' 'src/' ';'
|
|
||||||
find .ejabberd-modules/sources/ejabberd-contrib/ -wholename "*/ejabberd-contrib/$i/deps/*/ebin/*.beam" -exec 'cp' '{}' '_build/default/lib/ejabberd/ebin/' ';'
|
|
||||||
find .ejabberd-modules/sources/ejabberd-contrib/ -wholename "*/ejabberd-contrib/$i/deps/*/include/*.hrl" -exec 'cp' '{}' 'include/' ';'
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Uninstall modules
|
|
||||||
run: |
|
|
||||||
CTL=_build/dev/rel/ejabberd/bin/ejabberdctl
|
|
||||||
for i in `cat modules_available.txt` ; do
|
|
||||||
echo "Uninstalling $i"
|
|
||||||
$CTL module_uninstall $i
|
|
||||||
done
|
|
||||||
|
|
||||||
# This doesn't work right now, because epmd is in another path
|
|
||||||
# - run: ./ejabberdctl stop && ./ejabberdctl stopped
|
|
||||||
|
|
||||||
- run: make
|
|
||||||
- run: make hooks
|
|
||||||
- run: make options
|
|
||||||
- run: make xref
|
|
||||||
|
|
||||||
- name: Run Dialyzer
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
rm -rf _build/default/lib/ejabberd/ebin/fusco*
|
|
||||||
rm -rf _build/default/lib/ejabberd/ebin/observer_cli*
|
|
||||||
make dialyzer # Too many errors... first fix them, then enable this
|
|
||||||
|
|
||||||
- name: View logs dir
|
|
||||||
if: always()
|
|
||||||
run: ls -la _build/dev/rel/ejabberd/logs
|
|
||||||
- name: View ejabberd.log
|
|
||||||
if: always()
|
|
||||||
run: cat _build/dev/rel/ejabberd/logs/ejabberd.log
|
|
||||||
- name: View error.log
|
|
||||||
if: always()
|
|
||||||
run: cat _build/dev/rel/ejabberd/logs/error.log
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
This is the list of modules that are known to be broken with latest ejabberd master branch.
|
||||||
|
|
||||||
|
If you feel they are worth it, your help to fix them is welcome:
|
||||||
|
|
||||||
|
- atom_pubsub: "The atom_pubsub module provides access to all PEP nodes via an
|
||||||
|
AtomPub interface."
|
||||||
|
- ircd: "This is an IRC server frontend to ejabberd."
|
||||||
|
- mod_archive: "Message Archiving (XEP-0136)"
|
||||||
|
- mod_openid: "Transform the Jabber Server in an openid provider."
|
||||||
|
- mod_profile: "User Profile (XEP-0154) in Mnesia table"
|
88
README.md
88
README.md
|
@ -4,29 +4,34 @@ ejabberd-contrib
|
||||||
This is a collaborative development area for ejabberd module developers
|
This is a collaborative development area for ejabberd module developers
|
||||||
and users.
|
and users.
|
||||||
|
|
||||||
Those modules are not officially supported by ProcessOne.
|
|
||||||
|
|
||||||
For users
|
For users
|
||||||
---------
|
---------
|
||||||
|
|
||||||
To use an ejabberd module coming from this repository:
|
To use an ejabberd module coming from this repository:
|
||||||
|
|
||||||
- You need to have ejabberd installed.
|
- You need to have Erlang installed.
|
||||||
|
|
||||||
- If you have not already done it, run `ejabberdctl modules_update_specs`
|
- Read the module-specific `README.txt` file to see if special steps are
|
||||||
to retrieve the list of available modules.
|
required to deploy it.
|
||||||
|
|
||||||
- Run `ejabberdctl module_install <module>` to get the source code and to
|
- Run `./build.sh` or `build.bat` in the root directory of the desired
|
||||||
compile and install the `beam` file into ejabberd's module search path.
|
module.
|
||||||
This path is either `~/.ejabberd-modules` or defined by the
|
|
||||||
`CONTRIB_MODULES_PATH` setting in `ejabberdctl.cfg`.
|
|
||||||
|
|
||||||
- Edit the configuration file provided in the `conf` directory of the
|
- Copy generated `.beam` files from the `ebin` directory to the directory
|
||||||
installed module and update it to your needs. Or, if you prefer so,
|
where your ejabberd `.beam` files are.
|
||||||
configure it in your main ejabberd configuration file.
|
|
||||||
|
|
||||||
- Run `ejabberdctl module_uninstall <module>` to remove a module from
|
- Use the configuration file examples provided in the `conf` dir to update
|
||||||
ejabberd.
|
your `ejabberd.cfg` or `ejabberd.yml` configuration file.
|
||||||
|
|
||||||
|
If during compilation of a module you get an error like:
|
||||||
|
|
||||||
|
{"init terminating in do_boot",{undef,[{make,all,[]},...
|
||||||
|
|
||||||
|
it means Erlang couldn't find its `make.beam` file. In Debian and other
|
||||||
|
distributions you can try to install packages like:
|
||||||
|
|
||||||
|
erlang-dev erlang-nox erlang-tools
|
||||||
|
|
||||||
|
|
||||||
For developers
|
For developers
|
||||||
|
@ -34,52 +39,23 @@ For developers
|
||||||
|
|
||||||
The following organization has been set up for the development:
|
The following organization has been set up for the development:
|
||||||
|
|
||||||
- Development and compilation of modules is done by ejabberd. You need
|
- Development and compilation of modules should be possible without the
|
||||||
ejabberd installed. Use `ejabberdctl module_check <module>` to ensure it
|
ejabberd source code, as the `ejabberd-dev` helper module contains the
|
||||||
compiles correctly before committing your work. The sources of your
|
include files necessary to make compilation possible.
|
||||||
module must be located in `$CONTRIB_MODULES_PATH/sources/<module>`.
|
|
||||||
|
|
||||||
- Compilation can by done manually (if you know what you are doing) so you
|
|
||||||
don't need ejabberd running:
|
|
||||||
```
|
|
||||||
cd /path/of/module
|
|
||||||
mkdir ebin
|
|
||||||
/path/of/ejabberd's/erlc \
|
|
||||||
-o ebin \
|
|
||||||
-I include -I /path/of/ejabberd/lib/ejabberd-XX.YY/include \
|
|
||||||
-DLAGER -DNO_EXT_LIB \
|
|
||||||
src/*erl
|
|
||||||
```
|
|
||||||
|
|
||||||
- The module directory structure is usually the following:
|
- The module directory structure is usually the following:
|
||||||
* `README.txt`: Module description.
|
* `README.txt`: Module description.
|
||||||
* `COPYING`: License for the module.
|
* `LICENSE.txt`: License for the module.
|
||||||
|
* `Emakefile`: Erlang Makefile to build the module (preferred way, if
|
||||||
|
no dependencies on C code, as build will thus work on Windows).
|
||||||
* `doc/`: Documentation directory.
|
* `doc/`: Documentation directory.
|
||||||
* `src/`: Erlang source directory.
|
* `src/`: Source directory.
|
||||||
* `lib/`: Elixir source directory.
|
* `src/msgs/`: Directory with translation files (pot, po and msg).
|
||||||
* `priv/msgs/`: Directory with translation files (pot, po and msg).
|
* `ebin/`: Empty (target directory for the build).
|
||||||
* `conf/<module>.yml`: Configuration for your module.
|
* `conf/`: Directory containing example configuration for your module.
|
||||||
* `<module>.spec`: Yaml description file for your module.
|
* `build.sh`: Unix/Linux build script.
|
||||||
|
* `build.bat`: Windows build script.
|
||||||
|
|
||||||
- Module developers should note in the `README.txt` file whether the
|
- Module developers should note in the `README.txt` file whether the
|
||||||
module has requirements or known incompatibilities with other modules.
|
module has requirements or known incompatibilities with other modules
|
||||||
|
(for example, by modifying the same main ejabberd modules).
|
||||||
- If your module project contains several erlang modules, you should export a
|
|
||||||
function pre_uninstall/0 in the main one listing the other ones.
|
|
||||||
See mod_statsdx as an example.
|
|
||||||
|
|
||||||
|
|
||||||
Broken modules
|
|
||||||
--------------
|
|
||||||
|
|
||||||
This is the list of modules that are known to be broken with latest ejabberd master branch.
|
|
||||||
|
|
||||||
If you feel they are worth it, your help to fix them is welcome:
|
|
||||||
|
|
||||||
- atom_pubsub: "Provides access to all PEP nodes via an AtomPub interface."
|
|
||||||
- ircd: "This is an IRC server frontend to ejabberd."
|
|
||||||
- mod_archive: "Message Archiving (XEP-0136)."
|
|
||||||
- mod_irc: "IRC transport."
|
|
||||||
- mod_mam_mnesia: This feature got included in ejabberd 15.06
|
|
||||||
- mod_openid: "Transform the Jabber Server in an openid provider."
|
|
||||||
- mod_profile: "User Profile (XEP-0154) in Mnesia table."
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/atom_microblog', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/atom_pubsub', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/mod_couch', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -1,20 +1,10 @@
|
||||||
|
|
||||||
|
|
||||||
***************
|
|
||||||
PLEASE NOTE
|
|
||||||
***************
|
|
||||||
|
|
||||||
This module does NOT work
|
|
||||||
with ejabberd 13 or newer.
|
|
||||||
|
|
||||||
***************
|
|
||||||
|
|
||||||
|
|
||||||
atom_pubsub - the Atom PubSub tunnel
|
atom_pubsub - the Atom PubSub tunnel
|
||||||
|
|
||||||
Author: Eric Cestari <eric@ohmforce.com> http://www.cestari.info/
|
Author: Eric Cestari <eric@ohmforce.com> http://www.cestari.info/
|
||||||
Licensed under the same terms as ejabberd (GPL 2)
|
Licensed under the same terms as ejabberd (GPL 2)
|
||||||
Requires: ejabberd 2.0.3 or newer.
|
Requires: ejabberd 2.0.3 or newer.
|
||||||
|
Does NOT work with ejabberd 13 or newer.
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
|
@ -33,6 +23,28 @@ http://www.cestari.info/2008/6/19/atom-pubsub-module-for-ejabberd
|
||||||
http://www.cestari.info/2008/9/12/atom_pubsub-dead-long-live-atom_microblog
|
http://www.cestari.info/2008/9/12/atom_pubsub-dead-long-live-atom_microblog
|
||||||
|
|
||||||
|
|
||||||
|
INSTALL
|
||||||
|
-------
|
||||||
|
|
||||||
|
1. Compile the files executing: ./build.sh
|
||||||
|
|
||||||
|
2. Copy beam files from ebin/ into your ejabberd installation.
|
||||||
|
|
||||||
|
3. Edit ejabberd.cfg and add a request handler in ejabberd_http listener:
|
||||||
|
{listen, [
|
||||||
|
...
|
||||||
|
{8080, ejabberd_http, [
|
||||||
|
http_poll,
|
||||||
|
web_admin,
|
||||||
|
{request_handlers, [{["pep"], atom_microblog}, % Make sure it's "pep", or change it in atom_microblog.erl
|
||||||
|
{["pubsub"], atom_pubsub}]} % Make sure it's "pubsub", or change it in atom_pubsub.erl
|
||||||
|
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
4. Restart ejabberd.
|
||||||
|
|
||||||
|
|
||||||
USAGE
|
USAGE
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Eric Cestari <eric at ohmforce.com>"
|
|
||||||
category: "pubsub"
|
|
||||||
summary: "Provides access to all PEP nodes via an AtomPub interface"
|
|
||||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/atom_pubsub"
|
|
||||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
|
@ -0,0 +1 @@
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -1,7 +0,0 @@
|
||||||
listen:
|
|
||||||
-
|
|
||||||
port: 8080
|
|
||||||
module: ejabberd_http
|
|
||||||
request_handlers:
|
|
||||||
"pep": atom_microblog
|
|
||||||
"pubsub": atom_pubsub
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
ejabberd-dev is an helper module containing include files from the ejabberd
|
||||||
|
project.
|
||||||
|
|
||||||
|
This should allow to develop or build ejabberd modules without a complete
|
||||||
|
development version of ejabberd (for example with a binary version of ejabberd).
|
|
@ -0,0 +1,44 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||||
|
%%%
|
||||||
|
%%% This program is free software; you can redistribute it and/or
|
||||||
|
%%% modify it under the terms of the GNU General Public License as
|
||||||
|
%%% published by the Free Software Foundation; either version 2 of the
|
||||||
|
%%% License, or (at your option) any later version.
|
||||||
|
%%%
|
||||||
|
%%% This program is distributed in the hope that it will be useful,
|
||||||
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
%%% General Public License for more details.
|
||||||
|
%%%
|
||||||
|
%%% You should have received a copy of the GNU General Public License along
|
||||||
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-record(adhoc_request,
|
||||||
|
{
|
||||||
|
lang = <<"">> :: binary(),
|
||||||
|
node = <<"">> :: binary(),
|
||||||
|
sessionid = <<"">> :: binary(),
|
||||||
|
action = <<"">> :: binary(),
|
||||||
|
xdata = false :: false | xmlel(),
|
||||||
|
others = [] :: [xmlel()]
|
||||||
|
}).
|
||||||
|
|
||||||
|
-record(adhoc_response,
|
||||||
|
{
|
||||||
|
lang = <<"">> :: binary(),
|
||||||
|
node = <<"">> :: binary(),
|
||||||
|
sessionid = <<"">> :: binary(),
|
||||||
|
status :: atom(),
|
||||||
|
defaultaction = <<"">> :: binary(),
|
||||||
|
actions = [] :: [binary()],
|
||||||
|
notes = [] :: [{binary(), binary()}],
|
||||||
|
elements = [] :: [xmlel()]
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type adhoc_request() :: #adhoc_request{}.
|
||||||
|
-type adhoc_response() :: #adhoc_response{}.
|
|
@ -0,0 +1,51 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 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 macro returns a string of the ejabberd version running, e.g. "2.3.4"
|
||||||
|
%% If the ejabberd application description isn't loaded, returns atom: undefined
|
||||||
|
-define(VERSION, ejabberd_config:get_version()).
|
||||||
|
|
||||||
|
-define(MYHOSTS, ejabberd_config:get_myhosts()).
|
||||||
|
|
||||||
|
-define(MYNAME, hd(ejabberd_config:get_myhosts())).
|
||||||
|
|
||||||
|
-define(MYLANG, ejabberd_config:get_mylang()).
|
||||||
|
|
||||||
|
-define(MSGS_DIR, filename:join(["priv", "msgs"])).
|
||||||
|
|
||||||
|
-define(CONFIG_PATH, <<"ejabberd.cfg">>).
|
||||||
|
|
||||||
|
-define(LOG_PATH, <<"ejabberd.log">>).
|
||||||
|
|
||||||
|
-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>).
|
||||||
|
|
||||||
|
-define(S2STIMEOUT, 600000).
|
||||||
|
|
||||||
|
%%-define(DBGFSM, true).
|
||||||
|
|
||||||
|
-record(scram,
|
||||||
|
{storedkey = <<"">>,
|
||||||
|
serverkey = <<"">>,
|
||||||
|
salt = <<"">>,
|
||||||
|
iterationcount = 0 :: integer()}).
|
||||||
|
|
||||||
|
-type scram() :: #scram{}.
|
||||||
|
|
||||||
|
-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
|
|
@ -0,0 +1,74 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 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.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-type aterm() :: {atom(), atype()}.
|
||||||
|
-type atype() :: integer | string | binary |
|
||||||
|
{tuple, [aterm()]} | {list, aterm()}.
|
||||||
|
-type rterm() :: {atom(), rtype()}.
|
||||||
|
-type rtype() :: integer | string | atom |
|
||||||
|
{tuple, [rterm()]} | {list, rterm()} |
|
||||||
|
rescode | restuple.
|
||||||
|
|
||||||
|
-record(ejabberd_commands,
|
||||||
|
{name :: atom(),
|
||||||
|
tags = [] :: [atom()] | '_' | '$2',
|
||||||
|
desc = "" :: string() | '_' | '$3',
|
||||||
|
longdesc = "" :: string() | '_',
|
||||||
|
module :: atom(),
|
||||||
|
function :: atom(),
|
||||||
|
args = [] :: [aterm()] | '_' | '$1' | '$2',
|
||||||
|
result = {res, rescode} :: rterm() | '_' | '$2'}).
|
||||||
|
|
||||||
|
-type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
|
||||||
|
tags :: [atom()],
|
||||||
|
desc :: string(),
|
||||||
|
longdesc :: string(),
|
||||||
|
module :: atom(),
|
||||||
|
function :: atom(),
|
||||||
|
args :: [aterm()],
|
||||||
|
result :: rterm()}.
|
||||||
|
|
||||||
|
%% @type ejabberd_commands() = #ejabberd_commands{
|
||||||
|
%% name = atom(),
|
||||||
|
%% tags = [atom()],
|
||||||
|
%% desc = string(),
|
||||||
|
%% longdesc = string(),
|
||||||
|
%% module = atom(),
|
||||||
|
%% function = atom(),
|
||||||
|
%% args = [aterm()],
|
||||||
|
%% result = rterm()
|
||||||
|
%% }.
|
||||||
|
%% desc: Description of the command
|
||||||
|
%% args: Describe the accepted arguments.
|
||||||
|
%% This way the function that calls the command can format the
|
||||||
|
%% arguments before calling.
|
||||||
|
|
||||||
|
%% @type atype() = integer | string | {tuple, [aterm()]} | {list, aterm()}.
|
||||||
|
%% Allowed types for arguments are integer, string, tuple and list.
|
||||||
|
|
||||||
|
%% @type rtype() = integer | string | atom | {tuple, [rterm()]} | {list, rterm()} | rescode | restuple.
|
||||||
|
%% A rtype is either an atom or a tuple with two elements.
|
||||||
|
|
||||||
|
%% @type aterm() = {Name::atom(), Type::atype()}.
|
||||||
|
%% An argument term is a tuple with the term name and the term type.
|
||||||
|
|
||||||
|
%% @type rterm() = {Name::atom(), Type::rtype()}.
|
||||||
|
%% A result term is a tuple with the term name and the term type.
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% File : s3_util.erl
|
|
||||||
%%% Usage : S3 URL Generation and Signing
|
|
||||||
%%% Author : Roman Hargrave <roman@hargrave.info>
|
|
||||||
%%% Purpose : Signing AWS Requests. Intended for S3-CS use.
|
|
||||||
%%% Created : 24 Aug 2022 by Roman Hargrave <roman@hargrave.info>
|
|
||||||
%%%
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||||
%%%
|
%%%
|
||||||
%%% This program is free software; you can redistribute it and/or
|
%%% This program is free software; you can redistribute it and/or
|
||||||
%%% modify it under the terms of the GNU General Public License as
|
%%% modify it under the terms of the GNU General Public License as
|
||||||
|
@ -19,10 +15,16 @@
|
||||||
%%% You should have received a copy of the GNU General Public License along
|
%%% 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.,
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
%%%
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
-record(aws_auth, {access_key_id :: binary(),
|
-record(local_config, {key :: any(), value :: any()}).
|
||||||
access_key :: binary(),
|
|
||||||
region :: binary()}).
|
|
||||||
|
|
||||||
-define(AWS_SERVICE_S3, <<"s3">>).
|
-type local_config() :: #local_config{}.
|
||||||
|
|
||||||
|
-record(state,
|
||||||
|
{opts = [] :: [acl:acl() | local_config()],
|
||||||
|
hosts = [] :: [binary()],
|
||||||
|
override_local = false :: boolean(),
|
||||||
|
override_global = false :: boolean(),
|
||||||
|
override_acls = false :: boolean()}).
|
|
@ -1,6 +1,6 @@
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%%
|
%%%
|
||||||
%%% ejabberd, Copyright (C) 2002-2020 ProcessOne
|
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||||
%%%
|
%%%
|
||||||
%%% This program is free software; you can redistribute it and/or
|
%%% This program is free software; you can redistribute it and/or
|
||||||
%%% modify it under the terms of the GNU General Public License as
|
%%% modify it under the terms of the GNU General Public License as
|
||||||
|
@ -18,18 +18,10 @@
|
||||||
%%%
|
%%%
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
|
-define(STATUS_SUCCESS, 0).
|
||||||
{binary(), binary(), inet:port_number()} |
|
|
||||||
{binary(), binary()} |
|
|
||||||
{binary()}.
|
|
||||||
|
|
||||||
-type irc_data() :: [{username, binary()} | {connections_params, [conn_param()]}].
|
-define(STATUS_ERROR, 1).
|
||||||
|
|
||||||
-record(irc_connection,
|
-define(STATUS_USAGE, 2).
|
||||||
{jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
|
|
||||||
pid = self() :: pid()}).
|
|
||||||
|
|
||||||
-record(irc_custom,
|
-define(STATUS_BADRPC, 3).
|
||||||
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
|
|
||||||
binary()},
|
|
||||||
data = [] :: irc_data()}).
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||||
|
%%%
|
||||||
|
%%% This program is free software; you can redistribute it and/or
|
||||||
|
%%% modify it under the terms of the GNU General Public License as
|
||||||
|
%%% published by the Free Software Foundation; either version 2 of the
|
||||||
|
%%% License, or (at your option) any later version.
|
||||||
|
%%%
|
||||||
|
%%% This program is distributed in the hope that it will be useful,
|
||||||
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
%%% General Public License for more details.
|
||||||
|
%%%
|
||||||
|
%%% You should have received a copy of the GNU General Public License along
|
||||||
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-record(request,
|
||||||
|
{method, % :: method(),
|
||||||
|
path = [] :: [binary()],
|
||||||
|
q = [] :: [{binary() | nokey, binary()}],
|
||||||
|
us = {<<>>, <<>>} :: {binary(), binary()},
|
||||||
|
auth :: {binary(), binary()} |
|
||||||
|
{auth_jid, {binary(), binary()}, jlib:jid()},
|
||||||
|
lang = <<"">> :: binary(),
|
||||||
|
data = <<"">> :: binary(),
|
||||||
|
ip :: {inet:ip_address(), inet:port_number()},
|
||||||
|
host = <<"">> :: binary(),
|
||||||
|
port = 5280 :: inet:port_number(),
|
||||||
|
tp = http, % :: protocol(),
|
||||||
|
opts = [] :: list(),
|
||||||
|
headers = [] :: [{atom() | binary(), binary()}]}).
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 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(X(Name),
|
||||||
|
#xmlel{name = Name, attrs = [], children = []}).
|
||||||
|
|
||||||
|
-define(XA(Name, Attrs),
|
||||||
|
#xmlel{name = Name, attrs = Attrs, children = []}).
|
||||||
|
|
||||||
|
-define(XE(Name, Els),
|
||||||
|
#xmlel{name = Name, attrs = [], children = Els}).
|
||||||
|
|
||||||
|
-define(XAE(Name, Attrs, Els),
|
||||||
|
#xmlel{name = Name, attrs = Attrs, children = Els}).
|
||||||
|
|
||||||
|
-define(C(Text), {xmlcdata, Text}).
|
||||||
|
|
||||||
|
-define(XC(Name, Text), ?XE(Name, [?C(Text)])).
|
||||||
|
|
||||||
|
-define(XAC(Name, Attrs, Text),
|
||||||
|
?XAE(Name, Attrs, [?C(Text)])).
|
||||||
|
|
||||||
|
-define(T(Text), translate:translate(Lang, Text)).
|
||||||
|
|
||||||
|
-define(CT(Text), ?C((?T(Text)))).
|
||||||
|
|
||||||
|
-define(XCT(Name, Text), ?XC(Name, (?T(Text)))).
|
||||||
|
|
||||||
|
-define(XACT(Name, Attrs, Text),
|
||||||
|
?XAC(Name, Attrs, (?T(Text)))).
|
||||||
|
|
||||||
|
-define(LI(Els), ?XE(<<"li">>, Els)).
|
||||||
|
|
||||||
|
-define(A(URL, Els),
|
||||||
|
?XAE(<<"a">>, [{<<"href">>, URL}], Els)).
|
||||||
|
|
||||||
|
-define(AC(URL, Text), ?A(URL, [?C(Text)])).
|
||||||
|
|
||||||
|
-define(ACT(URL, Text), ?AC(URL, (?T(Text)))).
|
||||||
|
|
||||||
|
-define(P, ?X(<<"p">>)).
|
||||||
|
|
||||||
|
-define(BR, ?X(<<"br">>)).
|
||||||
|
|
||||||
|
-define(INPUT(Type, Name, Value),
|
||||||
|
?XA(<<"input">>,
|
||||||
|
[{<<"type">>, Type}, {<<"name">>, Name},
|
||||||
|
{<<"value">>, Value}])).
|
||||||
|
|
||||||
|
-define(INPUTT(Type, Name, Value),
|
||||||
|
?INPUT(Type, Name, (?T(Value)))).
|
||||||
|
|
||||||
|
-define(INPUTS(Type, Name, Value, Size),
|
||||||
|
?XA(<<"input">>,
|
||||||
|
[{<<"type">>, Type}, {<<"name">>, Name},
|
||||||
|
{<<"value">>, Value}, {<<"size">>, Size}])).
|
||||||
|
|
||||||
|
-define(INPUTST(Type, Name, Value, Size),
|
||||||
|
?INPUT(Type, Name, (?T(Value)), Size)).
|
||||||
|
|
||||||
|
-define(ACLINPUT(Text),
|
||||||
|
?XE(<<"td">>,
|
||||||
|
[?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])).
|
||||||
|
|
||||||
|
-define(TEXTAREA(Name, Rows, Cols, Value),
|
||||||
|
?XAC(<<"textarea">>,
|
||||||
|
[{<<"name">>, Name}, {<<"rows">>, Rows},
|
||||||
|
{<<"cols">>, Cols}],
|
||||||
|
Value)).
|
||||||
|
|
||||||
|
%% Build an xmlelement for result
|
||||||
|
-define(XRES(Text),
|
||||||
|
?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)).
|
||||||
|
|
||||||
|
%% Guide Link
|
||||||
|
-define(XREST(Text), ?XRES((?T(Text)))).
|
||||||
|
|
||||||
|
-define(GL(Ref, Title),
|
||||||
|
?XAE(<<"div">>, [{<<"class">>, <<"guidelink">>}],
|
||||||
|
[?XAE(<<"a">>,
|
||||||
|
[{<<"href">>, <<"/admin/doc/guide.html#", Ref/binary>>},
|
||||||
|
{<<"target">>, <<"_blank">>}],
|
||||||
|
[?C(<<"[Guide: ", Title/binary, "]">>)])])).
|
||||||
|
|
||||||
|
%% h1 with a Guide Link
|
||||||
|
-define(H1GL(Name, Ref, Title),
|
||||||
|
[?XC(<<"h1">>, Name), ?GL(Ref, Title)]).
|
|
@ -0,0 +1,501 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 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.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-include("ns.hrl").
|
||||||
|
-include("xml.hrl").
|
||||||
|
|
||||||
|
-define(STANZA_ERROR(Code, Type, Condition),
|
||||||
|
#xmlel{name = <<"error">>,
|
||||||
|
attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
|
||||||
|
children =
|
||||||
|
[#xmlel{name = Condition,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
|
||||||
|
children = []}]}).
|
||||||
|
|
||||||
|
-define(ERR_BAD_FORMAT,
|
||||||
|
?STANZA_ERROR(<<"406">>, <<"modify">>,
|
||||||
|
<<"bad-format">>)).
|
||||||
|
|
||||||
|
-define(ERR_BAD_REQUEST,
|
||||||
|
?STANZA_ERROR(<<"400">>, <<"modify">>,
|
||||||
|
<<"bad-request">>)).
|
||||||
|
|
||||||
|
-define(ERR_CONFLICT,
|
||||||
|
?STANZA_ERROR(<<"409">>, <<"cancel">>, <<"conflict">>)).
|
||||||
|
|
||||||
|
-define(ERR_FEATURE_NOT_IMPLEMENTED,
|
||||||
|
?STANZA_ERROR(<<"501">>, <<"cancel">>,
|
||||||
|
<<"feature-not-implemented">>)).
|
||||||
|
|
||||||
|
-define(ERR_FORBIDDEN,
|
||||||
|
?STANZA_ERROR(<<"403">>, <<"auth">>, <<"forbidden">>)).
|
||||||
|
|
||||||
|
-define(ERR_GONE,
|
||||||
|
?STANZA_ERROR(<<"302">>, <<"modify">>, <<"gone">>)).
|
||||||
|
|
||||||
|
-define(ERR_INTERNAL_SERVER_ERROR,
|
||||||
|
?STANZA_ERROR(<<"500">>, <<"wait">>,
|
||||||
|
<<"internal-server-error">>)).
|
||||||
|
|
||||||
|
-define(ERR_ITEM_NOT_FOUND,
|
||||||
|
?STANZA_ERROR(<<"404">>, <<"cancel">>,
|
||||||
|
<<"item-not-found">>)).
|
||||||
|
|
||||||
|
-define(ERR_JID_MALFORMED,
|
||||||
|
?STANZA_ERROR(<<"400">>, <<"modify">>,
|
||||||
|
<<"jid-malformed">>)).
|
||||||
|
|
||||||
|
-define(ERR_NOT_ACCEPTABLE,
|
||||||
|
?STANZA_ERROR(<<"406">>, <<"modify">>,
|
||||||
|
<<"not-acceptable">>)).
|
||||||
|
|
||||||
|
-define(ERR_NOT_ALLOWED,
|
||||||
|
?STANZA_ERROR(<<"405">>, <<"cancel">>,
|
||||||
|
<<"not-allowed">>)).
|
||||||
|
|
||||||
|
-define(ERR_NOT_AUTHORIZED,
|
||||||
|
?STANZA_ERROR(<<"401">>, <<"auth">>,
|
||||||
|
<<"not-authorized">>)).
|
||||||
|
|
||||||
|
-define(ERR_PAYMENT_REQUIRED,
|
||||||
|
?STANZA_ERROR(<<"402">>, <<"auth">>,
|
||||||
|
<<"payment-required">>)).
|
||||||
|
|
||||||
|
-define(ERR_RECIPIENT_UNAVAILABLE,
|
||||||
|
?STANZA_ERROR(<<"404">>, <<"wait">>,
|
||||||
|
<<"recipient-unavailable">>)).
|
||||||
|
|
||||||
|
-define(ERR_REDIRECT,
|
||||||
|
?STANZA_ERROR(<<"302">>, <<"modify">>, <<"redirect">>)).
|
||||||
|
|
||||||
|
-define(ERR_REGISTRATION_REQUIRED,
|
||||||
|
?STANZA_ERROR(<<"407">>, <<"auth">>,
|
||||||
|
<<"registration-required">>)).
|
||||||
|
|
||||||
|
-define(ERR_REMOTE_SERVER_NOT_FOUND,
|
||||||
|
?STANZA_ERROR(<<"404">>, <<"cancel">>,
|
||||||
|
<<"remote-server-not-found">>)).
|
||||||
|
|
||||||
|
-define(ERR_REMOTE_SERVER_TIMEOUT,
|
||||||
|
?STANZA_ERROR(<<"504">>, <<"wait">>,
|
||||||
|
<<"remote-server-timeout">>)).
|
||||||
|
|
||||||
|
-define(ERR_RESOURCE_CONSTRAINT,
|
||||||
|
?STANZA_ERROR(<<"500">>, <<"wait">>,
|
||||||
|
<<"resource-constraint">>)).
|
||||||
|
|
||||||
|
-define(ERR_SERVICE_UNAVAILABLE,
|
||||||
|
?STANZA_ERROR(<<"503">>, <<"cancel">>,
|
||||||
|
<<"service-unavailable">>)).
|
||||||
|
|
||||||
|
-define(ERR_SUBSCRIPTION_REQUIRED,
|
||||||
|
?STANZA_ERROR(<<"407">>, <<"auth">>,
|
||||||
|
<<"subscription-required">>)).
|
||||||
|
|
||||||
|
-define(ERR_UNEXPECTED_REQUEST,
|
||||||
|
?STANZA_ERROR(<<"400">>, <<"wait">>,
|
||||||
|
<<"unexpected-request">>)).
|
||||||
|
|
||||||
|
-define(ERR_UNEXPECTED_REQUEST_CANCEL,
|
||||||
|
?STANZA_ERROR(<<"401">>, <<"cancel">>,
|
||||||
|
<<"unexpected-request">>)).
|
||||||
|
|
||||||
|
%-define(ERR_,
|
||||||
|
% ?STANZA_ERROR("", "", "")).
|
||||||
|
|
||||||
|
-define(STANZA_ERRORT(Code, Type, Condition, Lang,
|
||||||
|
Text),
|
||||||
|
#xmlel{name = <<"error">>,
|
||||||
|
attrs = [{<<"code">>, Code}, {<<"type">>, Type}],
|
||||||
|
children =
|
||||||
|
[#xmlel{name = Condition,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []},
|
||||||
|
#xmlel{name = <<"text">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_STANZAS}],
|
||||||
|
children =
|
||||||
|
[{xmlcdata,
|
||||||
|
translate:translate(Lang, Text)}]}]}).
|
||||||
|
|
||||||
|
-define(ERRT_BAD_FORMAT(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"406">>, <<"modify">>,
|
||||||
|
<<"bad-format">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_BAD_REQUEST(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"400">>, <<"modify">>,
|
||||||
|
<<"bad-request">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_CONFLICT(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"409">>, <<"cancel">>, <<"conflict">>,
|
||||||
|
Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"501">>, <<"cancel">>,
|
||||||
|
<<"feature-not-implemented">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_FORBIDDEN(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"403">>, <<"auth">>, <<"forbidden">>,
|
||||||
|
Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_GONE(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"gone">>,
|
||||||
|
Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_INTERNAL_SERVER_ERROR(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"500">>, <<"wait">>,
|
||||||
|
<<"internal-server-error">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_ITEM_NOT_FOUND(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"404">>, <<"cancel">>,
|
||||||
|
<<"item-not-found">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_JID_MALFORMED(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"400">>, <<"modify">>,
|
||||||
|
<<"jid-malformed">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_NOT_ACCEPTABLE(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"406">>, <<"modify">>,
|
||||||
|
<<"not-acceptable">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_NOT_ALLOWED(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"405">>, <<"cancel">>,
|
||||||
|
<<"not-allowed">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_NOT_AUTHORIZED(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"401">>, <<"auth">>,
|
||||||
|
<<"not-authorized">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_PAYMENT_REQUIRED(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"402">>, <<"auth">>,
|
||||||
|
<<"payment-required">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_RECIPIENT_UNAVAILABLE(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"404">>, <<"wait">>,
|
||||||
|
<<"recipient-unavailable">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_REDIRECT(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"302">>, <<"modify">>, <<"redirect">>,
|
||||||
|
Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_REGISTRATION_REQUIRED(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"407">>, <<"auth">>,
|
||||||
|
<<"registration-required">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_REMOTE_SERVER_NOT_FOUND(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"404">>, <<"cancel">>,
|
||||||
|
<<"remote-server-not-found">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_REMOTE_SERVER_TIMEOUT(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"504">>, <<"wait">>,
|
||||||
|
<<"remote-server-timeout">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_RESOURCE_CONSTRAINT(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"500">>, <<"wait">>,
|
||||||
|
<<"resource-constraint">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_SERVICE_UNAVAILABLE(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"503">>, <<"cancel">>,
|
||||||
|
<<"service-unavailable">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_SUBSCRIPTION_REQUIRED(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"407">>, <<"auth">>,
|
||||||
|
<<"subscription-required">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERRT_UNEXPECTED_REQUEST(Lang, Text),
|
||||||
|
?STANZA_ERRORT(<<"400">>, <<"wait">>,
|
||||||
|
<<"unexpected-request">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(ERR_AUTH_NO_RESOURCE_PROVIDED(Lang),
|
||||||
|
?ERRT_NOT_ACCEPTABLE(Lang, <<"No resource provided">>)).
|
||||||
|
|
||||||
|
-define(ERR_AUTH_BAD_RESOURCE_FORMAT(Lang),
|
||||||
|
?ERRT_NOT_ACCEPTABLE(Lang,
|
||||||
|
<<"Illegal resource format">>)).
|
||||||
|
|
||||||
|
-define(ERR_AUTH_RESOURCE_CONFLICT(Lang),
|
||||||
|
?ERRT_CONFLICT(Lang, <<"Resource conflict">>)).
|
||||||
|
|
||||||
|
-define(STREAM_ERROR(Condition, Cdata),
|
||||||
|
#xmlel{name = <<"stream:error">>, attrs = [],
|
||||||
|
children =
|
||||||
|
[#xmlel{name = Condition,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_STREAMS}],
|
||||||
|
children = [{xmlcdata, Cdata}]}]}).
|
||||||
|
|
||||||
|
-define(SERR_BAD_FORMAT,
|
||||||
|
?STREAM_ERROR(<<"bad-format">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_BAD_NAMESPACE_PREFIX,
|
||||||
|
?STREAM_ERROR(<<"bad-namespace-prefix">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_CONFLICT,
|
||||||
|
?STREAM_ERROR(<<"conflict">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_CONNECTION_TIMEOUT,
|
||||||
|
?STREAM_ERROR(<<"connection-timeout">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_HOST_GONE,
|
||||||
|
?STREAM_ERROR(<<"host-gone">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_HOST_UNKNOWN,
|
||||||
|
?STREAM_ERROR(<<"host-unknown">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_IMPROPER_ADDRESSING,
|
||||||
|
?STREAM_ERROR(<<"improper-addressing">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_INTERNAL_SERVER_ERROR,
|
||||||
|
?STREAM_ERROR(<<"internal-server-error">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_INVALID_FROM,
|
||||||
|
?STREAM_ERROR(<<"invalid-from">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_INVALID_ID,
|
||||||
|
?STREAM_ERROR(<<"invalid-id">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_INVALID_NAMESPACE,
|
||||||
|
?STREAM_ERROR(<<"invalid-namespace">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_INVALID_XML,
|
||||||
|
?STREAM_ERROR(<<"invalid-xml">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_NOT_AUTHORIZED,
|
||||||
|
?STREAM_ERROR(<<"not-authorized">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_POLICY_VIOLATION,
|
||||||
|
?STREAM_ERROR(<<"policy-violation">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_REMOTE_CONNECTION_FAILED,
|
||||||
|
?STREAM_ERROR(<<"remote-connection-failed">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_RESOURSE_CONSTRAINT,
|
||||||
|
?STREAM_ERROR(<<"resource-constraint">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_RESTRICTED_XML,
|
||||||
|
?STREAM_ERROR(<<"restricted-xml">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_SEE_OTHER_HOST(Host),
|
||||||
|
?STREAM_ERROR(<<"see-other-host">>, Host)).
|
||||||
|
|
||||||
|
-define(SERR_SYSTEM_SHUTDOWN,
|
||||||
|
?STREAM_ERROR(<<"system-shutdown">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_UNSUPPORTED_ENCODING,
|
||||||
|
?STREAM_ERROR(<<"unsupported-encoding">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_UNSUPPORTED_STANZA_TYPE,
|
||||||
|
?STREAM_ERROR(<<"unsupported-stanza-type">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_UNSUPPORTED_VERSION,
|
||||||
|
?STREAM_ERROR(<<"unsupported-version">>, <<"">>)).
|
||||||
|
|
||||||
|
-define(SERR_XML_NOT_WELL_FORMED,
|
||||||
|
?STREAM_ERROR(<<"xml-not-well-formed">>, <<"">>)).
|
||||||
|
|
||||||
|
%-define(SERR_,
|
||||||
|
% ?STREAM_ERROR("", "")).
|
||||||
|
|
||||||
|
-define(STREAM_ERRORT(Condition, Cdata, Lang, Text),
|
||||||
|
#xmlel{name = <<"stream:error">>, attrs = [],
|
||||||
|
children =
|
||||||
|
[#xmlel{name = Condition,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_STREAMS}],
|
||||||
|
children = [{xmlcdata, Cdata}]},
|
||||||
|
#xmlel{name = <<"text">>,
|
||||||
|
attrs =
|
||||||
|
[{<<"xml:lang">>, Lang},
|
||||||
|
{<<"xmlns">>, ?NS_STREAMS}],
|
||||||
|
children =
|
||||||
|
[{xmlcdata,
|
||||||
|
translate:translate(Lang, Text)}]}]}).
|
||||||
|
|
||||||
|
-define(SERRT_BAD_FORMAT(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"bad-format">>, <<"">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_BAD_NAMESPACE_PREFIX(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"bad-namespace-prefix">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_CONFLICT(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"conflict">>, <<"">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_CONNECTION_TIMEOUT(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"connection-timeout">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_HOST_GONE(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"host-gone">>, <<"">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_HOST_UNKNOWN(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"host-unknown">>, <<"">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_IMPROPER_ADDRESSING(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"improper-addressing">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_INTERNAL_SERVER_ERROR(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"internal-server-error">>, <<"">>,
|
||||||
|
Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_INVALID_FROM(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"invalid-from">>, <<"">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_INVALID_ID(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"invalid-id">>, <<"">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_INVALID_NAMESPACE(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"invalid-namespace">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_INVALID_XML(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"invalid-xml">>, <<"">>, Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_NOT_AUTHORIZED(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"not-authorized">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_POLICY_VIOLATION(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"policy-violation">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_REMOTE_CONNECTION_FAILED(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"remote-connection-failed">>, <<"">>,
|
||||||
|
Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_RESOURSE_CONSTRAINT(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"resource-constraint">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_RESTRICTED_XML(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"restricted-xml">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_SEE_OTHER_HOST(Host, Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"see-other-host">>, Host, Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_SYSTEM_SHUTDOWN(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"system-shutdown">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_UNSUPPORTED_ENCODING(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"unsupported-encoding">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_UNSUPPORTED_STANZA_TYPE(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"unsupported-stanza-type">>, <<"">>,
|
||||||
|
Lang, Text)).
|
||||||
|
|
||||||
|
-define(SERRT_UNSUPPORTED_VERSION(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"unsupported-version">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-define(SERRT_XML_NOT_WELL_FORMED(Lang, Text),
|
||||||
|
?STREAM_ERRORT(<<"xml-not-well-formed">>, <<"">>, Lang,
|
||||||
|
Text)).
|
||||||
|
|
||||||
|
-record(jid, {user = <<"">> :: binary(),
|
||||||
|
server = <<"">> :: binary(),
|
||||||
|
resource = <<"">> :: binary(),
|
||||||
|
luser = <<"">> :: binary(),
|
||||||
|
lserver = <<"">> :: binary(),
|
||||||
|
lresource = <<"">> :: binary()}).
|
||||||
|
|
||||||
|
-type(jid() :: #jid{}).
|
||||||
|
|
||||||
|
-type(ljid() :: {binary(), binary(), binary()}).
|
||||||
|
|
||||||
|
-record(iq, {id = <<"">> :: binary(),
|
||||||
|
type = get :: get | set | result | error,
|
||||||
|
xmlns = <<"">> :: binary(),
|
||||||
|
lang = <<"">> :: binary(),
|
||||||
|
sub_el = #xmlel{} :: xmlel() | [xmlel()]}).
|
||||||
|
|
||||||
|
-type(iq_get()
|
||||||
|
:: #iq{
|
||||||
|
id :: binary(),
|
||||||
|
type :: get,
|
||||||
|
xmlns :: binary(),
|
||||||
|
lang :: binary(),
|
||||||
|
sub_el :: xmlel()
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
|
-type(iq_set()
|
||||||
|
:: #iq{
|
||||||
|
id :: binary(),
|
||||||
|
type :: set,
|
||||||
|
xmlns :: binary(),
|
||||||
|
lang :: binary(),
|
||||||
|
sub_el :: xmlel()
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
|
-type iq_request() :: iq_get() | iq_set().
|
||||||
|
|
||||||
|
-type(iq_result()
|
||||||
|
:: #iq{
|
||||||
|
id :: binary(),
|
||||||
|
type :: result,
|
||||||
|
xmlns :: binary(),
|
||||||
|
lang :: binary(),
|
||||||
|
sub_el :: [xmlel()]
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
|
-type(iq_error()
|
||||||
|
:: #iq{
|
||||||
|
id :: binary(),
|
||||||
|
type :: error,
|
||||||
|
xmlns :: binary(),
|
||||||
|
lang :: binary(),
|
||||||
|
sub_el :: [xmlel()]
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
|
-type iq_reply() :: iq_result() | iq_error() .
|
||||||
|
|
||||||
|
-type(iq() :: iq_request() | iq_reply()).
|
||||||
|
|
||||||
|
-record(rsm_in, {max :: integer() | error,
|
||||||
|
direction :: before | aft,
|
||||||
|
id :: binary(),
|
||||||
|
index :: integer() | error}).
|
||||||
|
|
||||||
|
-record(rsm_out, {count :: integer(),
|
||||||
|
index :: integer(),
|
||||||
|
first :: binary(),
|
||||||
|
last :: binary()}).
|
||||||
|
|
||||||
|
-type(rsm_in() :: #rsm_in{}).
|
||||||
|
|
||||||
|
-type(rsm_out() :: #rsm_out{}).
|
||||||
|
|
||||||
|
-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
|
||||||
|
|
||||||
|
-record(xmlelement, {name = "" :: string(),
|
||||||
|
attrs = [] :: [{string(), string()}],
|
||||||
|
children = [] :: [{xmlcdata, iodata()} | xmlelement()]}).
|
||||||
|
|
||||||
|
-type xmlelement() :: #xmlelement{}.
|
|
@ -0,0 +1,102 @@
|
||||||
|
%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% This file is provided to you under the Apache License,
|
||||||
|
%% Version 2.0 (the "License"); you may not use this file
|
||||||
|
%% except in compliance with the License. You may obtain
|
||||||
|
%% a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing,
|
||||||
|
%% software distributed under the License is distributed on an
|
||||||
|
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
%% KIND, either express or implied. See the License for the
|
||||||
|
%% specific language governing permissions and limitations
|
||||||
|
%% under the License.
|
||||||
|
|
||||||
|
|
||||||
|
-define(DEFAULT_TRUNCATION, 4096).
|
||||||
|
-define(DEFAULT_TRACER, lager_default_tracer).
|
||||||
|
|
||||||
|
-define(LEVELS,
|
||||||
|
[debug, info, notice, warning, error, critical, alert, emergency, none]).
|
||||||
|
|
||||||
|
-define(DEBUG, 128).
|
||||||
|
-define(INFO, 64).
|
||||||
|
-define(NOTICE, 32).
|
||||||
|
-define(WARNING, 16).
|
||||||
|
-define(ERROR, 8).
|
||||||
|
-define(CRITICAL, 4).
|
||||||
|
-define(ALERT, 2).
|
||||||
|
-define(EMERGENCY, 1).
|
||||||
|
-define(LOG_NONE, 0).
|
||||||
|
|
||||||
|
-define(LEVEL2NUM(Level),
|
||||||
|
case Level of
|
||||||
|
debug -> ?DEBUG;
|
||||||
|
info -> ?INFO;
|
||||||
|
notice -> ?NOTICE;
|
||||||
|
warning -> ?WARNING;
|
||||||
|
error -> ?ERROR;
|
||||||
|
critical -> ?CRITICAL;
|
||||||
|
alert -> ?ALERT;
|
||||||
|
emergency -> ?EMERGENCY
|
||||||
|
end).
|
||||||
|
|
||||||
|
-define(NUM2LEVEL(Num),
|
||||||
|
case Num of
|
||||||
|
?DEBUG -> debug;
|
||||||
|
?INFO -> info;
|
||||||
|
?NOTICE -> notice;
|
||||||
|
?WARNING -> warning;
|
||||||
|
?ERROR -> error;
|
||||||
|
?CRITICAL -> critical;
|
||||||
|
?ALERT -> alert;
|
||||||
|
?EMERGENCY -> emergency
|
||||||
|
end).
|
||||||
|
|
||||||
|
-define(SHOULD_LOG(Level),
|
||||||
|
(lager_util:level_to_num(Level) band element(1, lager_config:get(loglevel, {?LOG_NONE, []}))) /= 0).
|
||||||
|
|
||||||
|
-define(NOTIFY(Level, Pid, Format, Args),
|
||||||
|
gen_event:notify(lager_event, {log, lager_msg:new(io_lib:format(Format, Args),
|
||||||
|
Level,
|
||||||
|
[{pid,Pid},{line,?LINE},{file,?FILE},{module,?MODULE}],
|
||||||
|
[])}
|
||||||
|
)).
|
||||||
|
|
||||||
|
%% FOR INTERNAL USE ONLY
|
||||||
|
%% internal non-blocking logging call
|
||||||
|
%% there's some special handing for when we try to log (usually errors) while
|
||||||
|
%% lager is still starting.
|
||||||
|
-ifdef(TEST).
|
||||||
|
-define(INT_LOG(Level, Format, Args),
|
||||||
|
case ?SHOULD_LOG(Level) of
|
||||||
|
true ->
|
||||||
|
?NOTIFY(Level, self(), Format, Args);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end).
|
||||||
|
-else.
|
||||||
|
-define(INT_LOG(Level, Format, Args),
|
||||||
|
Self = self(),
|
||||||
|
%% do this in a spawn so we don't cause a deadlock calling gen_event:which_handlers
|
||||||
|
%% from a gen_event handler
|
||||||
|
spawn(fun() ->
|
||||||
|
case catch(gen_event:which_handlers(lager_event)) of
|
||||||
|
X when X == []; X == {'EXIT', noproc}; X == [lager_backend_throttle] ->
|
||||||
|
%% there's no handlers yet or lager isn't running, try again
|
||||||
|
%% in half a second.
|
||||||
|
timer:sleep(500),
|
||||||
|
?NOTIFY(Level, Self, Format, Args);
|
||||||
|
_ ->
|
||||||
|
case ?SHOULD_LOG(Level) of
|
||||||
|
true ->
|
||||||
|
?NOTIFY(Level, Self, Format, Args);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)).
|
||||||
|
-endif.
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 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(PRINT(Format, Args), io:format(Format, Args)).
|
||||||
|
|
||||||
|
-ifdef(LAGER).
|
||||||
|
-compile([{parse_transform, lager_transform}]).
|
||||||
|
|
||||||
|
-define(DEBUG(Format, Args),
|
||||||
|
lager:debug(Format, Args)).
|
||||||
|
|
||||||
|
-define(INFO_MSG(Format, Args),
|
||||||
|
lager:info(Format, Args)).
|
||||||
|
|
||||||
|
-define(WARNING_MSG(Format, Args),
|
||||||
|
lager:warning(Format, Args)).
|
||||||
|
|
||||||
|
-define(ERROR_MSG(Format, Args),
|
||||||
|
lager:error(Format, Args)).
|
||||||
|
|
||||||
|
-define(CRITICAL_MSG(Format, Args),
|
||||||
|
lager:critical(Format, Args)).
|
||||||
|
|
||||||
|
-else.
|
||||||
|
|
||||||
|
-define(DEBUG(Format, Args),
|
||||||
|
p1_logger:debug_msg(?MODULE, ?LINE, Format, Args)).
|
||||||
|
|
||||||
|
-define(INFO_MSG(Format, Args),
|
||||||
|
p1_logger:info_msg(?MODULE, ?LINE, Format, Args)).
|
||||||
|
|
||||||
|
-define(WARNING_MSG(Format, Args),
|
||||||
|
p1_logger:warning_msg(?MODULE, ?LINE, Format, Args)).
|
||||||
|
|
||||||
|
-define(ERROR_MSG(Format, Args),
|
||||||
|
p1_logger:error_msg(?MODULE, ?LINE, Format, Args)).
|
||||||
|
|
||||||
|
-define(CRITICAL_MSG(Format, Args),
|
||||||
|
p1_logger:critical_msg(?MODULE, ?LINE, Format, Args)).
|
||||||
|
-endif.
|
|
@ -0,0 +1,116 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 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(MAX_USERS_DEFAULT, 200).
|
||||||
|
|
||||||
|
-define(SETS, gb_sets).
|
||||||
|
|
||||||
|
-define(DICT, dict).
|
||||||
|
|
||||||
|
-record(lqueue,
|
||||||
|
{
|
||||||
|
queue :: queue(),
|
||||||
|
len :: integer(),
|
||||||
|
max :: integer()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type lqueue() :: #lqueue{}.
|
||||||
|
|
||||||
|
-record(config,
|
||||||
|
{
|
||||||
|
title = <<"">> :: binary(),
|
||||||
|
description = <<"">> :: binary(),
|
||||||
|
allow_change_subj = true :: boolean(),
|
||||||
|
allow_query_users = true :: boolean(),
|
||||||
|
allow_private_messages = true :: boolean(),
|
||||||
|
allow_private_messages_from_visitors = anyone :: anyone | moderators | nobody ,
|
||||||
|
allow_visitor_status = true :: boolean(),
|
||||||
|
allow_visitor_nickchange = true :: boolean(),
|
||||||
|
public = true :: boolean(),
|
||||||
|
public_list = true :: boolean(),
|
||||||
|
persistent = false :: boolean(),
|
||||||
|
moderated = true :: boolean(),
|
||||||
|
captcha_protected = false :: boolean(),
|
||||||
|
members_by_default = true :: boolean(),
|
||||||
|
members_only = false :: boolean(),
|
||||||
|
allow_user_invites = false :: boolean(),
|
||||||
|
password_protected = false :: boolean(),
|
||||||
|
password = <<"">> :: binary(),
|
||||||
|
anonymous = true :: boolean(),
|
||||||
|
allow_voice_requests = true :: boolean(),
|
||||||
|
voice_request_min_interval = 1800 :: non_neg_integer(),
|
||||||
|
max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none,
|
||||||
|
logging = false :: boolean(),
|
||||||
|
vcard = <<"">> :: boolean(),
|
||||||
|
captcha_whitelist = (?SETS):empty() :: gb_set()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type config() :: #config{}.
|
||||||
|
|
||||||
|
-type role() :: moderator | participant | visitor | none.
|
||||||
|
|
||||||
|
-record(user,
|
||||||
|
{
|
||||||
|
jid :: jid(),
|
||||||
|
nick :: binary(),
|
||||||
|
role :: role(),
|
||||||
|
last_presence :: xmlel()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-record(activity,
|
||||||
|
{
|
||||||
|
message_time = 0 :: integer(),
|
||||||
|
presence_time = 0 :: integer(),
|
||||||
|
message_shaper :: shaper:shaper(),
|
||||||
|
presence_shaper :: shaper:shaper(),
|
||||||
|
message :: xmlel(),
|
||||||
|
presence :: {binary(), xmlel()}
|
||||||
|
}).
|
||||||
|
|
||||||
|
-record(state,
|
||||||
|
{
|
||||||
|
room = <<"">> :: binary(),
|
||||||
|
host = <<"">> :: binary(),
|
||||||
|
server_host = <<"">> :: binary(),
|
||||||
|
access = {none,none,none,none} :: {atom(), atom(), atom(), atom()},
|
||||||
|
jid = #jid{} :: jid(),
|
||||||
|
config = #config{} :: config(),
|
||||||
|
users = (?DICT):new() :: dict(),
|
||||||
|
last_voice_request_time = treap:empty() :: treap:treap(),
|
||||||
|
robots = (?DICT):new() :: dict(),
|
||||||
|
nicks = (?DICT):new() :: dict(),
|
||||||
|
affiliations = (?DICT):new() :: dict(),
|
||||||
|
history :: lqueue(),
|
||||||
|
subject = <<"">> :: binary(),
|
||||||
|
subject_author = <<"">> :: binary(),
|
||||||
|
just_created = false :: boolean(),
|
||||||
|
activity = treap:empty() :: treap:treap(),
|
||||||
|
room_shaper = none :: shaper:shaper(),
|
||||||
|
room_queue = queue:new() :: queue()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-record(muc_online_users, {us = {<<>>, <<>>} :: {binary(), binary()},
|
||||||
|
resource = <<>> :: binary() | '_',
|
||||||
|
room = <<>> :: binary() | '_',
|
||||||
|
host = <<>> :: binary() | '_'}).
|
||||||
|
|
||||||
|
-type muc_online_users() :: #muc_online_users{}.
|
||||||
|
|
||||||
|
-type muc_room_state() :: #state{}.
|
|
@ -0,0 +1,43 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2014 ProcessOne
|
||||||
|
%%%
|
||||||
|
%%% This program is free software; you can redistribute it and/or
|
||||||
|
%%% modify it under the terms of the GNU General Public License as
|
||||||
|
%%% published by the Free Software Foundation; either version 2 of the
|
||||||
|
%%% License, or (at your option) any later version.
|
||||||
|
%%%
|
||||||
|
%%% This program is distributed in the hope that it will be useful,
|
||||||
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
%%% General Public License for more details.
|
||||||
|
%%%
|
||||||
|
%%% You should have received a copy of the GNU General Public License along
|
||||||
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-record(privacy, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||||
|
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,
|
||||||
|
order = 0 :: integer(),
|
||||||
|
match_all = false :: boolean(),
|
||||||
|
match_iq = false :: boolean(),
|
||||||
|
match_message = false :: boolean(),
|
||||||
|
match_presence_in = false :: boolean(),
|
||||||
|
match_presence_out = false :: boolean()}).
|
||||||
|
|
||||||
|
-type listitem() :: #listitem{}.
|
||||||
|
|
||||||
|
-record(userlist, {name = none :: none | binary(),
|
||||||
|
list = [] :: [listitem()],
|
||||||
|
needdb = false :: boolean()}).
|
||||||
|
|
||||||
|
-type userlist() :: #userlist{}.
|
||||||
|
|
||||||
|
-export_type([userlist/0]).
|
|
@ -0,0 +1,41 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 ProcessOne
|
||||||
|
%%%
|
||||||
|
%%% This program is free software; you can redistribute it and/or
|
||||||
|
%%% modify it under the terms of the GNU General Public License as
|
||||||
|
%%% published by the Free Software Foundation; either version 2 of the
|
||||||
|
%%% License, or (at your option) any later version.
|
||||||
|
%%%
|
||||||
|
%%% This program is distributed in the hope that it will be useful,
|
||||||
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
%%% General Public License for more details.
|
||||||
|
%%%
|
||||||
|
%%% You should have received a copy of the GNU General Public License along
|
||||||
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-record(roster,
|
||||||
|
{
|
||||||
|
usj = {<<>>, <<>>, {<<>>, <<>>, <<>>}} :: {binary(), binary(), ljid()} | '_',
|
||||||
|
us = {<<>>, <<>>} :: {binary(), binary()} | '_',
|
||||||
|
jid = {<<>>, <<>>, <<>>} :: ljid(),
|
||||||
|
name = <<>> :: binary() | '_',
|
||||||
|
subscription = none :: subscription() | '_',
|
||||||
|
ask = none :: ask() | '_',
|
||||||
|
groups = [] :: [binary()] | '_',
|
||||||
|
askmessage = <<"">> :: binary() | '_',
|
||||||
|
xs = [] :: [xmlel()] | '_'
|
||||||
|
}).
|
||||||
|
|
||||||
|
-record(roster_version,
|
||||||
|
{
|
||||||
|
us = {<<>>, <<>>} :: {binary(), binary()},
|
||||||
|
version = <<>> :: binary()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type ask() :: none | in | out | both | subscribe | unsubscribe.
|
||||||
|
-type subscription() :: none | both | from | to | remove.
|
|
@ -0,0 +1,152 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 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_TIME90, <<"jabber:iq:time">>).
|
||||||
|
-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_DELAY91, <<"jabber:x:delay">>).
|
||||||
|
-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_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_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">>).
|
|
@ -0,0 +1,257 @@
|
||||||
|
%%% ====================================================================
|
||||||
|
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||||
|
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||||
|
%%% compliance with the License. You should have received a copy of the
|
||||||
|
%%% Erlang Public License along with this software. If not, it can be
|
||||||
|
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% Software distributed under the License is distributed on an "AS IS"
|
||||||
|
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||||
|
%%% the License for the specific language governing rights and limitations
|
||||||
|
%%% under the License.
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% The Initial Developer of the Original Code is ProcessOne.
|
||||||
|
%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
|
||||||
|
%%% All Rights Reserved.''
|
||||||
|
%%% This software is copyright 2006-2015, ProcessOne.
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% copyright 2006-2015 ProcessOne
|
||||||
|
%%%
|
||||||
|
%%% This file contains pubsub types definition.
|
||||||
|
%%% ====================================================================
|
||||||
|
|
||||||
|
%% -------------------------------
|
||||||
|
%% Pubsub constants
|
||||||
|
-define(ERR_EXTENDED(E, C),
|
||||||
|
mod_pubsub:extended_error(E, C)).
|
||||||
|
|
||||||
|
%% The actual limit can be configured with mod_pubsub's option max_items_node
|
||||||
|
-define(MAXITEMS, 10).
|
||||||
|
|
||||||
|
%% this is currently a hard limit.
|
||||||
|
%% Would be nice to have it configurable.
|
||||||
|
-define(MAX_PAYLOAD_SIZE, 60000).
|
||||||
|
|
||||||
|
%% -------------------------------
|
||||||
|
%% Pubsub types
|
||||||
|
|
||||||
|
%% @type hostPubsub() = string().
|
||||||
|
-type(hostPubsub() :: binary()).
|
||||||
|
%% <p><tt>hostPubsub</tt> is the name of the PubSub service. For example, it can be
|
||||||
|
%% <tt>"pubsub.localhost"</tt>.</p>
|
||||||
|
|
||||||
|
-type(hostPEP() :: {binary(), binary(), <<>>}).
|
||||||
|
%% @type hostPEP() = {User, Server, Resource}
|
||||||
|
%% User = string()
|
||||||
|
%% Server = string()
|
||||||
|
%% Resource = [].
|
||||||
|
%% <p>For example, it can be :
|
||||||
|
%% ```{"bob", "example.org", []}'''.</p>
|
||||||
|
|
||||||
|
-type(host() :: hostPubsub() | hostPEP()).
|
||||||
|
%% @type host() = hostPubsub() | hostPEP().
|
||||||
|
|
||||||
|
-type(nodeId() :: binary()).
|
||||||
|
%% @type nodeId() = binary().
|
||||||
|
%% <p>A node is defined by a list of its ancestors. The last element is the name
|
||||||
|
%% of the current node. For example:
|
||||||
|
%% of the current node. For example:
|
||||||
|
%% ```<<"/home/localhost/user">>'''</p>
|
||||||
|
|
||||||
|
-type(nodeIdx() :: pos_integer()).
|
||||||
|
%% @type nodeIdx() = integer().
|
||||||
|
|
||||||
|
-type(itemId() :: binary()).
|
||||||
|
%% @type itemId() = string().
|
||||||
|
|
||||||
|
-type(subId() :: binary()).
|
||||||
|
%% @type subId() = string().
|
||||||
|
|
||||||
|
|
||||||
|
%% @type payload() = [#xmlelement{} | #xmlcdata{}].
|
||||||
|
|
||||||
|
%% @type stanzaError() = #xmlelement{}.
|
||||||
|
%% Example:
|
||||||
|
%% Example:
|
||||||
|
%% ```{xmlelement, "error",
|
||||||
|
%% [{"code", Code}, {"type", Type}],
|
||||||
|
%% [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}'''
|
||||||
|
%% @type pubsubIQResponse() = #xmlelement{}.
|
||||||
|
%% Example:
|
||||||
|
%% ```{xmlelement, "pubsub",
|
||||||
|
%% [{"xmlns", ?NS_PUBSUB_EVENT}],
|
||||||
|
%% [{xmlelement, "affiliations", [],
|
||||||
|
%% []}]}'''
|
||||||
|
|
||||||
|
-type(nodeOption() ::
|
||||||
|
{Option::atom(),
|
||||||
|
Value::binary() | [binary()] | boolean() | non_neg_integer()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type(nodeOptions() :: [NodeOption::mod_pubsub:nodeOption(),...]).
|
||||||
|
|
||||||
|
%% @type nodeOption() = {Option, Value}
|
||||||
|
%% Option = atom()
|
||||||
|
%% Value = term().
|
||||||
|
%% Example:
|
||||||
|
%% ```{deliver_payloads, true}'''
|
||||||
|
|
||||||
|
-type(subOption() ::
|
||||||
|
{Option::atom(),
|
||||||
|
Value::binary() | [binary()] | boolean()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type(subOptions() :: [SubOption::mod_pubsub:subOption(),...]).
|
||||||
|
|
||||||
|
%% @type nodeType() = string().
|
||||||
|
%% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub
|
||||||
|
%% plugin to use to manage a given node. For example, it can be
|
||||||
|
%% <tt>"flat"</tt>, <tt>"hometree"</tt> or <tt>"blog"</tt>.</p>
|
||||||
|
|
||||||
|
%% @type jid() = {jid, User, Server, Resource, LUser, LServer, LResource}
|
||||||
|
%% User = string()
|
||||||
|
%% Server = string()
|
||||||
|
%% Resource = string()
|
||||||
|
%% LUser = string()
|
||||||
|
%% LServer = string()
|
||||||
|
%% LResource = string().
|
||||||
|
|
||||||
|
%-type(ljid() :: {binary(), binary(), binary()}).
|
||||||
|
%% @type ljid() = {User, Server, Resource}
|
||||||
|
%% User = string()
|
||||||
|
%% Server = string()
|
||||||
|
%% Resource = string().
|
||||||
|
|
||||||
|
-type(affiliation() :: 'none'
|
||||||
|
| 'owner'
|
||||||
|
| 'publisher'
|
||||||
|
%| 'publish-only'
|
||||||
|
| 'member'
|
||||||
|
| 'outcast'
|
||||||
|
).
|
||||||
|
%% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'.
|
||||||
|
|
||||||
|
-type(subscription() :: 'none'
|
||||||
|
| 'pending'
|
||||||
|
| 'unconfigured'
|
||||||
|
| 'subscribed'
|
||||||
|
).
|
||||||
|
%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
|
||||||
|
|
||||||
|
-type(accessModel() :: 'open'
|
||||||
|
| 'presence'
|
||||||
|
| 'roster'
|
||||||
|
| 'authorize'
|
||||||
|
| 'whitelist'
|
||||||
|
).
|
||||||
|
%% @type accessModel() = 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist'.
|
||||||
|
|
||||||
|
%% @type pubsubIndex() = {pubsub_index, Index, Last, Free}
|
||||||
|
%% Index = atom()
|
||||||
|
%% Last = integer()
|
||||||
|
%% Free = [integer()].
|
||||||
|
%% internal pubsub index table
|
||||||
|
-type(publishModel() :: 'publishers'
|
||||||
|
| 'subscribers'
|
||||||
|
| 'open'
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
-record(pubsub_index,
|
||||||
|
{
|
||||||
|
index :: atom(),
|
||||||
|
last :: mod_pubsub:nodeIdx(),
|
||||||
|
free :: [mod_pubsub:nodeIdx()]
|
||||||
|
}).
|
||||||
|
|
||||||
|
%% @type pubsubNode() = {pubsub_node, NodeId, Id, Parents, Type, Owners, Options}
|
||||||
|
%% NodeId = {host() | ljid(), nodeId()}
|
||||||
|
%% Id = nodeIdx()
|
||||||
|
%% Parents = [nodeId()]
|
||||||
|
%% Type = nodeType()
|
||||||
|
%% Owners = [ljid()]
|
||||||
|
%% Options = [nodeOption()].
|
||||||
|
%% <p>This is the format of the <tt>nodes</tt> table. The type of the table
|
||||||
|
%% is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
|
||||||
|
%% <p>The <tt>Parents</tt> and <tt>type</tt> fields are indexed.</p>
|
||||||
|
%% <tt>id</tt> can be anything you want.
|
||||||
|
-record(pubsub_node,
|
||||||
|
{
|
||||||
|
nodeid ,%:: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()},
|
||||||
|
id ,%:: mod_pubsub:nodeIdx(),
|
||||||
|
parents = [] ,%:: [Parent_NodeId::mod_pubsub:nodeId()],
|
||||||
|
type = <<"flat">> ,%:: binary(),
|
||||||
|
owners = [] ,%:: [Owner::ljid(),...],
|
||||||
|
options = [] %:: mod_pubsub:nodeOptions()
|
||||||
|
}).
|
||||||
|
|
||||||
|
%% @type pubsubState() = {pubsub_state, StateId, Items, Affiliation, Subscriptions}
|
||||||
|
%% StateId = {ljid(), nodeIdx()}
|
||||||
|
%% Items = [itemId()]
|
||||||
|
%% Affiliation = affiliation()
|
||||||
|
%% Subscriptions = [{subscription(), subId()}].
|
||||||
|
%% <p>This is the format of the <tt>affiliations</tt> table. The type of the
|
||||||
|
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
|
||||||
|
|
||||||
|
%-record(pubsub_state,
|
||||||
|
% {stateid, items = [], affiliation = none,
|
||||||
|
% subscriptions = []}).
|
||||||
|
-record(pubsub_state,
|
||||||
|
{
|
||||||
|
stateid ,%:: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()},
|
||||||
|
items = [] ,%:: [ItemId::mod_pubsub:itemId()],
|
||||||
|
affiliation = 'none' ,%:: mod_pubsub:affiliation(),
|
||||||
|
subscriptions = [] %:: [{mod_pubsub:subscription(), mod_pubsub:subId()}]
|
||||||
|
}).
|
||||||
|
|
||||||
|
%% @type pubsubItem() = {pubsub_item, ItemId, Creation, Modification, Payload}
|
||||||
|
%% ItemId = {itemId(), nodeIdx()}
|
||||||
|
%% Creation = {now(), ljid()}
|
||||||
|
%% Modification = {now(), ljid()}
|
||||||
|
%% Payload = payload().
|
||||||
|
%% <p>This is the format of the <tt>published items</tt> table. The type of the
|
||||||
|
%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p>
|
||||||
|
%-record(pubsub_item,
|
||||||
|
% {itemid, creation = {unknown, unknown},
|
||||||
|
% modification = {unknown, unknown}, payload = []}).
|
||||||
|
|
||||||
|
-record(pubsub_item,
|
||||||
|
{
|
||||||
|
itemid ,%:: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()},
|
||||||
|
creation = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()},
|
||||||
|
modification = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()},
|
||||||
|
payload = [] %:: mod_pubsub:payload()
|
||||||
|
}).
|
||||||
|
|
||||||
|
%% @type pubsubSubscription() = {pubsub_subscription, SubId, Options}
|
||||||
|
%% SubId = subId()
|
||||||
|
%% Options = [nodeOption()].
|
||||||
|
%% <p>This is the format of the <tt>subscriptions</tt> table. The type of the
|
||||||
|
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
|
||||||
|
%-record(pubsub_subscription, {subid, options}).
|
||||||
|
-record(pubsub_subscription,
|
||||||
|
{
|
||||||
|
subid ,%:: mod_pubsub:subId(),
|
||||||
|
options %:: [] | mod_pubsub:subOptions()
|
||||||
|
}).
|
||||||
|
|
||||||
|
%% @type pubsubLastItem() = {pubsub_last_item, NodeId, ItemId, Creation, Payload}
|
||||||
|
%% NodeId = nodeIdx()
|
||||||
|
%% ItemId = itemId()
|
||||||
|
%% Creation = {now(),ljid()}
|
||||||
|
%% Payload = payload().
|
||||||
|
%% <p>This is the format of the <tt>last items</tt> table. it stores last item payload
|
||||||
|
%% for every node</p>
|
||||||
|
%-record(pubsub_last_item,
|
||||||
|
% {nodeid, itemid, creation, payload}).
|
||||||
|
|
||||||
|
-record(pubsub_last_item,
|
||||||
|
{
|
||||||
|
nodeid ,%:: mod_pubsub:nodeIdx(),
|
||||||
|
itemid ,%:: mod_pubsub:itemId(),
|
||||||
|
creation ,%:: {erlang:timestamp(), ljid()},
|
||||||
|
payload %:: mod_pubsub:payload()
|
||||||
|
}).
|
|
@ -0,0 +1,38 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% File : xml.hrl
|
||||||
|
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%% Purpose : XML utils
|
||||||
|
%%% Created : 1 May 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% xml, Copyright (C) 2002-2015 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., 59 Temple Place, Suite 330, Boston, MA
|
||||||
|
%%% 02111-1307 USA
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-record(xmlel,
|
||||||
|
{
|
||||||
|
name = <<"">> :: binary(),
|
||||||
|
attrs = [] :: [attr()],
|
||||||
|
children = [] :: [xmlel() | cdata()]
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type(cdata() :: {xmlcdata, CData::binary()}).
|
||||||
|
|
||||||
|
-type(attr() :: {Name::binary(), Value::binary()}).
|
||||||
|
|
||||||
|
-type(xmlel() :: #xmlel{}).
|
|
@ -0,0 +1,278 @@
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% File : gen_mod.erl
|
||||||
|
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||||
|
%%% Purpose :
|
||||||
|
%%% Purpose :
|
||||||
|
%%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2015 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(gen_mod).
|
||||||
|
|
||||||
|
-author('alexey@process-one.net').
|
||||||
|
|
||||||
|
-export([start/0, start_module/2, start_module/3, stop_module/2,
|
||||||
|
stop_module_keep_config/2, get_opt/3, get_opt/4,
|
||||||
|
get_opt_host/3, db_type/1, db_type/2, get_module_opt/5,
|
||||||
|
get_module_opt_host/3, loaded_modules/1,
|
||||||
|
loaded_modules_with_opts/1, get_hosts/2,
|
||||||
|
get_module_proc/2, is_loaded/2]).
|
||||||
|
|
||||||
|
%%-export([behaviour_info/1]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
|
||||||
|
-record(ejabberd_module,
|
||||||
|
{module_host = {undefined, <<"">>} :: {atom(), binary()},
|
||||||
|
opts = [] :: opts() | '_' | '$2'}).
|
||||||
|
|
||||||
|
-type opts() :: [{atom(), any()}].
|
||||||
|
|
||||||
|
-callback start(binary(), opts()) -> any().
|
||||||
|
-callback stop(binary()) -> any().
|
||||||
|
|
||||||
|
-export_type([opts/0]).
|
||||||
|
|
||||||
|
%%behaviour_info(callbacks) -> [{start, 2}, {stop, 1}];
|
||||||
|
%%behaviour_info(_Other) -> undefined.
|
||||||
|
|
||||||
|
start() ->
|
||||||
|
ets:new(ejabberd_modules,
|
||||||
|
[named_table, public,
|
||||||
|
{keypos, #ejabberd_module.module_host}]),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
-spec start_module(binary(), atom()) -> any().
|
||||||
|
|
||||||
|
start_module(Host, Module) ->
|
||||||
|
Modules = ejabberd_config:get_option(
|
||||||
|
{modules, Host},
|
||||||
|
fun(L) when is_list(L) -> L end, []),
|
||||||
|
case lists:keyfind(Module, 1, Modules) of
|
||||||
|
{_, Opts} ->
|
||||||
|
start_module(Host, Module, Opts);
|
||||||
|
false ->
|
||||||
|
{error, not_found_in_config}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec start_module(binary(), atom(), opts()) -> any().
|
||||||
|
|
||||||
|
start_module(Host, Module, Opts) ->
|
||||||
|
ets:insert(ejabberd_modules,
|
||||||
|
#ejabberd_module{module_host = {Module, Host},
|
||||||
|
opts = Opts}),
|
||||||
|
try Module:start(Host, Opts) catch
|
||||||
|
Class:Reason ->
|
||||||
|
ets:delete(ejabberd_modules, {Module, Host}),
|
||||||
|
ErrorText =
|
||||||
|
io_lib:format("Problem starting the module ~p for host "
|
||||||
|
"~p ~n options: ~p~n ~p: ~p~n~p",
|
||||||
|
[Module, Host, Opts, Class, Reason,
|
||||||
|
erlang:get_stacktrace()]),
|
||||||
|
?CRITICAL_MSG(ErrorText, []),
|
||||||
|
case is_app_running(ejabberd) of
|
||||||
|
true ->
|
||||||
|
erlang:raise(Class, Reason, erlang:get_stacktrace());
|
||||||
|
false ->
|
||||||
|
?CRITICAL_MSG("ejabberd initialization was aborted "
|
||||||
|
"because a module start failed.",
|
||||||
|
[]),
|
||||||
|
timer:sleep(3000),
|
||||||
|
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
is_app_running(AppName) ->
|
||||||
|
Timeout = 15000,
|
||||||
|
lists:keymember(AppName, 1,
|
||||||
|
application:which_applications(Timeout)).
|
||||||
|
|
||||||
|
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
|
||||||
|
|
||||||
|
%% @doc Stop the module in a host, and forget its configuration.
|
||||||
|
stop_module(Host, Module) ->
|
||||||
|
case stop_module_keep_config(Host, Module) of
|
||||||
|
error -> error;
|
||||||
|
ok -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @doc Stop the module in a host, but keep its configuration.
|
||||||
|
%% As the module configuration is kept in the Mnesia local_config table,
|
||||||
|
%% when ejabberd is restarted the module will be started again.
|
||||||
|
%% This function is useful when ejabberd is being stopped
|
||||||
|
%% and it stops all modules.
|
||||||
|
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
|
||||||
|
|
||||||
|
stop_module_keep_config(Host, Module) ->
|
||||||
|
case catch Module:stop(Host) of
|
||||||
|
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), error;
|
||||||
|
{wait, ProcList} when is_list(ProcList) ->
|
||||||
|
lists:foreach(fun wait_for_process/1, ProcList),
|
||||||
|
ets:delete(ejabberd_modules, {Module, Host}),
|
||||||
|
ok;
|
||||||
|
{wait, Process} ->
|
||||||
|
wait_for_process(Process),
|
||||||
|
ets:delete(ejabberd_modules, {Module, Host}),
|
||||||
|
ok;
|
||||||
|
_ -> ets:delete(ejabberd_modules, {Module, Host}), ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
wait_for_process(Process) ->
|
||||||
|
MonitorReference = erlang:monitor(process, Process),
|
||||||
|
wait_for_stop(Process, MonitorReference).
|
||||||
|
|
||||||
|
wait_for_stop(Process, MonitorReference) ->
|
||||||
|
receive
|
||||||
|
{'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
|
||||||
|
after 5000 ->
|
||||||
|
catch exit(whereis(Process), kill),
|
||||||
|
wait_for_stop1(MonitorReference)
|
||||||
|
end.
|
||||||
|
|
||||||
|
wait_for_stop1(MonitorReference) ->
|
||||||
|
receive
|
||||||
|
{'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
|
||||||
|
after 5000 -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
|
||||||
|
|
||||||
|
-spec get_opt(atom(), opts(), check_fun()) -> any().
|
||||||
|
|
||||||
|
get_opt(Opt, Opts, F) ->
|
||||||
|
get_opt(Opt, Opts, F, undefined).
|
||||||
|
|
||||||
|
-spec get_opt(atom(), opts(), check_fun(), any()) -> any().
|
||||||
|
|
||||||
|
get_opt(Opt, Opts, F, Default) ->
|
||||||
|
case lists:keysearch(Opt, 1, Opts) of
|
||||||
|
false ->
|
||||||
|
Default;
|
||||||
|
{value, {_, Val}} ->
|
||||||
|
ejabberd_config:prepare_opt_val(Opt, Val, F, Default)
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any().
|
||||||
|
|
||||||
|
get_module_opt(global, Module, Opt, F, Default) ->
|
||||||
|
Hosts = (?MYHOSTS),
|
||||||
|
[Value | Values] = lists:map(fun (Host) ->
|
||||||
|
get_module_opt(Host, Module, Opt,
|
||||||
|
F, Default)
|
||||||
|
end,
|
||||||
|
Hosts),
|
||||||
|
Same_all = lists:all(fun (Other_value) ->
|
||||||
|
Other_value == Value
|
||||||
|
end,
|
||||||
|
Values),
|
||||||
|
case Same_all of
|
||||||
|
true -> Value;
|
||||||
|
false -> Default
|
||||||
|
end;
|
||||||
|
get_module_opt(Host, Module, Opt, F, Default) ->
|
||||||
|
OptsList = ets:lookup(ejabberd_modules, {Module, Host}),
|
||||||
|
case OptsList of
|
||||||
|
[] -> Default;
|
||||||
|
[#ejabberd_module{opts = Opts} | _] ->
|
||||||
|
get_opt(Opt, Opts, F, Default)
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec get_module_opt_host(global | binary(), atom(), binary()) -> binary().
|
||||||
|
|
||||||
|
get_module_opt_host(Host, Module, Default) ->
|
||||||
|
Val = get_module_opt(Host, Module, host,
|
||||||
|
fun iolist_to_binary/1,
|
||||||
|
Default),
|
||||||
|
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||||
|
|
||||||
|
-spec get_opt_host(binary(), opts(), binary()) -> binary().
|
||||||
|
|
||||||
|
get_opt_host(Host, Opts, Default) ->
|
||||||
|
Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
|
||||||
|
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||||
|
|
||||||
|
-spec db_type(opts()) -> odbc | mnesia | riak.
|
||||||
|
|
||||||
|
db_type(Opts) ->
|
||||||
|
get_opt(db_type, Opts,
|
||||||
|
fun(odbc) -> odbc;
|
||||||
|
(internal) -> mnesia;
|
||||||
|
(mnesia) -> mnesia;
|
||||||
|
(riak) -> riak
|
||||||
|
end,
|
||||||
|
mnesia).
|
||||||
|
|
||||||
|
-spec db_type(binary(), atom()) -> odbc | mnesia | riak.
|
||||||
|
|
||||||
|
db_type(Host, Module) ->
|
||||||
|
get_module_opt(Host, Module, db_type,
|
||||||
|
fun(odbc) -> odbc;
|
||||||
|
(internal) -> mnesia;
|
||||||
|
(mnesia) -> mnesia;
|
||||||
|
(riak) -> riak
|
||||||
|
end,
|
||||||
|
mnesia).
|
||||||
|
|
||||||
|
-spec loaded_modules(binary()) -> [atom()].
|
||||||
|
|
||||||
|
loaded_modules(Host) ->
|
||||||
|
ets:select(ejabberd_modules,
|
||||||
|
[{#ejabberd_module{_ = '_', module_host = {'$1', Host}},
|
||||||
|
[], ['$1']}]).
|
||||||
|
|
||||||
|
-spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}].
|
||||||
|
|
||||||
|
loaded_modules_with_opts(Host) ->
|
||||||
|
ets:select(ejabberd_modules,
|
||||||
|
[{#ejabberd_module{_ = '_', module_host = {'$1', Host},
|
||||||
|
opts = '$2'},
|
||||||
|
[], [{{'$1', '$2'}}]}]).
|
||||||
|
|
||||||
|
-spec get_hosts(opts(), binary()) -> [binary()].
|
||||||
|
|
||||||
|
get_hosts(Opts, Prefix) ->
|
||||||
|
case get_opt(hosts, Opts,
|
||||||
|
fun(Hs) -> [iolist_to_binary(H) || H <- Hs] end) of
|
||||||
|
undefined ->
|
||||||
|
case get_opt(host, Opts,
|
||||||
|
fun iolist_to_binary/1) of
|
||||||
|
undefined ->
|
||||||
|
[<<Prefix/binary, Host/binary>> || Host <- ?MYHOSTS];
|
||||||
|
Host ->
|
||||||
|
[Host]
|
||||||
|
end;
|
||||||
|
Hosts ->
|
||||||
|
Hosts
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec get_module_proc(binary(), {frontend, atom()} | atom()) -> atom().
|
||||||
|
|
||||||
|
get_module_proc(Host, {frontend, Base}) ->
|
||||||
|
get_module_proc(<<"frontend_", Host/binary>>, Base);
|
||||||
|
get_module_proc(Host, Base) ->
|
||||||
|
binary_to_atom(
|
||||||
|
<<(erlang:atom_to_binary(Base, latin1))/binary, "_", Host/binary>>,
|
||||||
|
latin1).
|
||||||
|
|
||||||
|
-spec is_loaded(binary(), atom()) -> boolean().
|
||||||
|
|
||||||
|
is_loaded(Host, Module) ->
|
||||||
|
ets:member(ejabberd_modules, {Module, Host}).
|
|
@ -0,0 +1,279 @@
|
||||||
|
%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% This file is provided to you under the Apache License,
|
||||||
|
%% Version 2.0 (the "License"); you may not use this file
|
||||||
|
%% except in compliance with the License. You may obtain
|
||||||
|
%% a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing,
|
||||||
|
%% software distributed under the License is distributed on an
|
||||||
|
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
%% KIND, either express or implied. See the License for the
|
||||||
|
%% specific language governing permissions and limitations
|
||||||
|
%% under the License.
|
||||||
|
|
||||||
|
%% @doc The parse transform used for lager messages.
|
||||||
|
%% This parse transform rewrites functions calls to lager:Severity/1,2 into
|
||||||
|
%% a more complicated function that captures module, function, line, pid and
|
||||||
|
%% time as well. The entire function call is then wrapped in a case that
|
||||||
|
%% checks the lager_config 'loglevel' value, so the code isn't executed if
|
||||||
|
%% nothing wishes to consume the message.
|
||||||
|
|
||||||
|
-module(lager_transform).
|
||||||
|
|
||||||
|
-include("lager.hrl").
|
||||||
|
|
||||||
|
-export([parse_transform/2]).
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
parse_transform(AST, Options) ->
|
||||||
|
TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION),
|
||||||
|
Enable = proplists:get_value(lager_print_records_flag, Options, true),
|
||||||
|
put(print_records_flag, Enable),
|
||||||
|
put(truncation_size, TruncSize),
|
||||||
|
erlang:put(records, []),
|
||||||
|
%% .app file should either be in the outdir, or the same dir as the source file
|
||||||
|
guess_application(proplists:get_value(outdir, Options), hd(AST)),
|
||||||
|
walk_ast([], AST).
|
||||||
|
|
||||||
|
walk_ast(Acc, []) ->
|
||||||
|
case get(print_records_flag) of
|
||||||
|
true ->
|
||||||
|
insert_record_attribute(Acc);
|
||||||
|
false ->
|
||||||
|
lists:reverse(Acc)
|
||||||
|
end;
|
||||||
|
walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) ->
|
||||||
|
%% A wild parameterized module appears!
|
||||||
|
put(module, Module),
|
||||||
|
walk_ast([H|Acc], T);
|
||||||
|
walk_ast(Acc, [{attribute, _, module, Module}=H|T]) ->
|
||||||
|
put(module, Module),
|
||||||
|
walk_ast([H|Acc], T);
|
||||||
|
walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) ->
|
||||||
|
put(function, Name),
|
||||||
|
walk_ast([{function, Line, Name, Arity,
|
||||||
|
walk_clauses([], Clauses)}|Acc], T);
|
||||||
|
walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) ->
|
||||||
|
FieldNames = lists:map(fun({record_field, _, {atom, _, FieldName}}) ->
|
||||||
|
FieldName;
|
||||||
|
({record_field, _, {atom, _, FieldName}, _Default}) ->
|
||||||
|
FieldName
|
||||||
|
end, Fields),
|
||||||
|
stash_record({Name, FieldNames}),
|
||||||
|
walk_ast([H|Acc], T);
|
||||||
|
walk_ast(Acc, [H|T]) ->
|
||||||
|
walk_ast([H|Acc], T).
|
||||||
|
|
||||||
|
walk_clauses(Acc, []) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
|
||||||
|
walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T).
|
||||||
|
|
||||||
|
walk_body(Acc, []) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
walk_body(Acc, [H|T]) ->
|
||||||
|
walk_body([transform_statement(H)|Acc], T).
|
||||||
|
|
||||||
|
transform_statement({call, Line, {remote, _Line1, {atom, _Line2, lager},
|
||||||
|
{atom, _Line3, Severity}}, Arguments0} = Stmt) ->
|
||||||
|
case lists:member(Severity, ?LEVELS) of
|
||||||
|
true ->
|
||||||
|
SeverityAsInt=lager_util:level_to_num(Severity),
|
||||||
|
DefaultAttrs0 = {cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, module}, {atom, Line, get(module)}]},
|
||||||
|
{cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, function}, {atom, Line, get(function)}]},
|
||||||
|
{cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, line},
|
||||||
|
{integer, Line, Line}]},
|
||||||
|
{cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, pid},
|
||||||
|
{call, Line, {atom, Line, pid_to_list}, [
|
||||||
|
{call, Line, {atom, Line ,self}, []}]}]},
|
||||||
|
{cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, node},
|
||||||
|
{call, Line, {atom, Line, node}, []}]},
|
||||||
|
%% get the metadata with lager:md(), this will always return a list so we can use it as the tail here
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}},
|
||||||
|
%{nil, Line}}}}}}},
|
||||||
|
DefaultAttrs = case erlang:get(application) of
|
||||||
|
undefined ->
|
||||||
|
DefaultAttrs0;
|
||||||
|
App ->
|
||||||
|
%% stick the application in the attribute list
|
||||||
|
concat_lists({cons, Line, {tuple, Line, [
|
||||||
|
{atom, Line, application},
|
||||||
|
{atom, Line, App}]},
|
||||||
|
{nil, Line}}, DefaultAttrs0)
|
||||||
|
end,
|
||||||
|
{Traces, Message, Arguments} = case Arguments0 of
|
||||||
|
[Format] ->
|
||||||
|
{DefaultAttrs, Format, {atom, Line, none}};
|
||||||
|
[Arg1, Arg2] ->
|
||||||
|
%% some ambiguity here, figure out if these arguments are
|
||||||
|
%% [Format, Args] or [Attr, Format].
|
||||||
|
%% The trace attributes will be a list of tuples, so check
|
||||||
|
%% for that.
|
||||||
|
case {element(1, Arg1), Arg1} of
|
||||||
|
{_, {cons, _, {tuple, _, _}, _}} ->
|
||||||
|
{concat_lists(Arg1, DefaultAttrs),
|
||||||
|
Arg2, {atom, Line, none}};
|
||||||
|
{Type, _} when Type == var;
|
||||||
|
Type == lc;
|
||||||
|
Type == call;
|
||||||
|
Type == record_field ->
|
||||||
|
%% crap, its not a literal. look at the second
|
||||||
|
%% argument to see if it is a string
|
||||||
|
case Arg2 of
|
||||||
|
{string, _, _} ->
|
||||||
|
{concat_lists(Arg1, DefaultAttrs),
|
||||||
|
Arg2, {atom, Line, none}};
|
||||||
|
_ ->
|
||||||
|
%% not a string, going to have to guess
|
||||||
|
%% it's the argument list
|
||||||
|
{DefaultAttrs, Arg1, Arg2}
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{DefaultAttrs, Arg1, Arg2}
|
||||||
|
end;
|
||||||
|
[Attrs, Format, Args] ->
|
||||||
|
{concat_lists(Attrs, DefaultAttrs), Format, Args}
|
||||||
|
end,
|
||||||
|
%% Generate some unique variable names so we don't accidentaly export from case clauses.
|
||||||
|
%% Note that these are not actual atoms, but the AST treats variable names as atoms.
|
||||||
|
LevelVar = make_varname("__Level", Line),
|
||||||
|
TracesVar = make_varname("__Traces", Line),
|
||||||
|
PidVar = make_varname("__Pid", Line),
|
||||||
|
%% Wrap the call to lager_dispatch log in a case that will avoid doing any work if this message is not elegible for logging
|
||||||
|
%% case {whereis(lager_event(lager_event), lager_config:get(loglevel, {?LOG_NONE, []})} of
|
||||||
|
{'case', Line,
|
||||||
|
{tuple, Line,
|
||||||
|
[{call, Line, {atom, Line, whereis}, [{atom, Line, lager_event}]},
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lager_config}, {atom, Line, get}}, [{atom, Line, loglevel}, {tuple, Line, [{integer, Line, 0},{nil, Line}]}]}]},
|
||||||
|
[
|
||||||
|
%% {undefined, _} -> {error, lager_not_running}
|
||||||
|
{clause, Line,
|
||||||
|
[{tuple, Line, [{atom, Line, undefined}, {var, Line, '_'}]}],
|
||||||
|
[],
|
||||||
|
%% trick the linter into avoiding a 'term constructed by not used' error:
|
||||||
|
%% (fun() -> {error, lager_not_running} end)();
|
||||||
|
[{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}]},
|
||||||
|
%% If we care about the loglevel, or there's any traces installed, we have do more checking
|
||||||
|
%% {Level, Traces} when (Level band SeverityAsInt) /= 0 orelse Traces /= [] ->
|
||||||
|
{clause, Line,
|
||||||
|
[{tuple, Line, [{var, Line, PidVar}, {tuple, Line, [{var, Line, LevelVar}, {var, Line, TracesVar}]}]}],
|
||||||
|
[[{op, Line, 'orelse',
|
||||||
|
{op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}},
|
||||||
|
{op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]],
|
||||||
|
[
|
||||||
|
%% do the call to lager:dispatch_log
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, do_log}},
|
||||||
|
[
|
||||||
|
{atom,Line,Severity},
|
||||||
|
Traces,
|
||||||
|
Message,
|
||||||
|
Arguments,
|
||||||
|
{integer, Line, get(truncation_size)},
|
||||||
|
{integer, Line, SeverityAsInt},
|
||||||
|
{var, Line, LevelVar},
|
||||||
|
{var, Line, TracesVar},
|
||||||
|
{var, Line, PidVar}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]},
|
||||||
|
%% otherwise, do nothing
|
||||||
|
%% _ -> ok
|
||||||
|
{clause, Line, [{var, Line, '_'}],[],[{atom, Line, ok}]}
|
||||||
|
]};
|
||||||
|
false ->
|
||||||
|
Stmt
|
||||||
|
end;
|
||||||
|
transform_statement({call, Line, {remote, Line1, {atom, Line2, boston_lager},
|
||||||
|
{atom, Line3, Severity}}, Arguments}) ->
|
||||||
|
NewArgs = case Arguments of
|
||||||
|
[{string, L, Msg}] -> [{string, L, re:replace(Msg, "r", "h", [{return, list}, global])}];
|
||||||
|
[{string, L, Format}, Args] -> [{string, L, re:replace(Format, "r", "h", [{return, list}, global])}, Args];
|
||||||
|
Other -> Other
|
||||||
|
end,
|
||||||
|
transform_statement({call, Line, {remote, Line1, {atom, Line2, lager},
|
||||||
|
{atom, Line3, Severity}}, NewArgs});
|
||||||
|
transform_statement(Stmt) when is_tuple(Stmt) ->
|
||||||
|
list_to_tuple(transform_statement(tuple_to_list(Stmt)));
|
||||||
|
transform_statement(Stmt) when is_list(Stmt) ->
|
||||||
|
[transform_statement(S) || S <- Stmt];
|
||||||
|
transform_statement(Stmt) ->
|
||||||
|
Stmt.
|
||||||
|
|
||||||
|
make_varname(Prefix, Line) ->
|
||||||
|
list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)).
|
||||||
|
|
||||||
|
%% concat 2 list ASTs by replacing the terminating [] in A with the contents of B
|
||||||
|
concat_lists({var, Line, _Name}=Var, B) ->
|
||||||
|
%% concatenating a var with a cons
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||||
|
[{cons, Line, Var, B}]};
|
||||||
|
concat_lists({lc, Line, _Body, _Generator} = LC, B) ->
|
||||||
|
%% concatenating a LC with a cons
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||||
|
[{cons, Line, LC, B}]};
|
||||||
|
concat_lists({call, Line, _Function, _Args} = Call, B) ->
|
||||||
|
%% concatenating a call with a cons
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||||
|
[{cons, Line, Call, B}]};
|
||||||
|
concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) ->
|
||||||
|
%% concatenating a record_field with a cons
|
||||||
|
{call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}},
|
||||||
|
[{cons, Line, Rec, B}]};
|
||||||
|
concat_lists({nil, _Line}, B) ->
|
||||||
|
B;
|
||||||
|
concat_lists({cons, Line, Element, Tail}, B) ->
|
||||||
|
{cons, Line, Element, concat_lists(Tail, B)}.
|
||||||
|
|
||||||
|
stash_record(Record) ->
|
||||||
|
Records = case erlang:get(records) of
|
||||||
|
undefined ->
|
||||||
|
[];
|
||||||
|
R ->
|
||||||
|
R
|
||||||
|
end,
|
||||||
|
erlang:put(records, [Record|Records]).
|
||||||
|
|
||||||
|
insert_record_attribute(AST) ->
|
||||||
|
lists:foldl(fun({attribute, Line, module, _}=E, Acc) ->
|
||||||
|
[E, {attribute, Line, lager_records, erlang:get(records)}|Acc];
|
||||||
|
(E, Acc) ->
|
||||||
|
[E|Acc]
|
||||||
|
end, [], AST).
|
||||||
|
|
||||||
|
guess_application(Dirname, Attr) when Dirname /= undefined ->
|
||||||
|
case find_app_file(Dirname) of
|
||||||
|
no_idea ->
|
||||||
|
%% try it based on source file directory (app.src most likely)
|
||||||
|
guess_application(undefined, Attr);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
guess_application(undefined, {attribute, _, file, {Filename, _}}) ->
|
||||||
|
Dir = filename:dirname(Filename),
|
||||||
|
find_app_file(Dir);
|
||||||
|
guess_application(_, _) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
find_app_file(Dir) ->
|
||||||
|
case filelib:wildcard(Dir++"/*.{app,app.src}") of
|
||||||
|
[] ->
|
||||||
|
no_idea;
|
||||||
|
[File] ->
|
||||||
|
case file:consult(File) of
|
||||||
|
{ok, [{application, Appname, _Attributes}|_]} ->
|
||||||
|
erlang:put(application, Appname);
|
||||||
|
_ ->
|
||||||
|
no_idea
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
%% multiple files, uh oh
|
||||||
|
no_idea
|
||||||
|
end.
|
|
@ -0,0 +1,710 @@
|
||||||
|
%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% This file is provided to you under the Apache License,
|
||||||
|
%% Version 2.0 (the "License"); you may not use this file
|
||||||
|
%% except in compliance with the License. You may obtain
|
||||||
|
%% a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing,
|
||||||
|
%% software distributed under the License is distributed on an
|
||||||
|
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
%% KIND, either express or implied. See the License for the
|
||||||
|
%% specific language governing permissions and limitations
|
||||||
|
%% under the License.
|
||||||
|
|
||||||
|
-module(lager_util).
|
||||||
|
|
||||||
|
-include_lib("kernel/include/file.hrl").
|
||||||
|
|
||||||
|
-export([levels/0, level_to_num/1, num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1,
|
||||||
|
open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
|
||||||
|
localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/1,
|
||||||
|
calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3,
|
||||||
|
trace_filter/1, trace_filter/2]).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
-include("lager.hrl").
|
||||||
|
|
||||||
|
levels() ->
|
||||||
|
[debug, info, notice, warning, error, critical, alert, emergency, none].
|
||||||
|
|
||||||
|
level_to_num(debug) -> ?DEBUG;
|
||||||
|
level_to_num(info) -> ?INFO;
|
||||||
|
level_to_num(notice) -> ?NOTICE;
|
||||||
|
level_to_num(warning) -> ?WARNING;
|
||||||
|
level_to_num(error) -> ?ERROR;
|
||||||
|
level_to_num(critical) -> ?CRITICAL;
|
||||||
|
level_to_num(alert) -> ?ALERT;
|
||||||
|
level_to_num(emergency) -> ?EMERGENCY;
|
||||||
|
level_to_num(none) -> ?LOG_NONE.
|
||||||
|
|
||||||
|
num_to_level(?DEBUG) -> debug;
|
||||||
|
num_to_level(?INFO) -> info;
|
||||||
|
num_to_level(?NOTICE) -> notice;
|
||||||
|
num_to_level(?WARNING) -> warning;
|
||||||
|
num_to_level(?ERROR) -> error;
|
||||||
|
num_to_level(?CRITICAL) -> critical;
|
||||||
|
num_to_level(?ALERT) -> alert;
|
||||||
|
num_to_level(?EMERGENCY) -> emergency;
|
||||||
|
num_to_level(?LOG_NONE) -> none.
|
||||||
|
|
||||||
|
-spec config_to_mask(atom()|string()) -> {'mask', integer()}.
|
||||||
|
config_to_mask(Conf) ->
|
||||||
|
Levels = config_to_levels(Conf),
|
||||||
|
{mask, lists:foldl(fun(Level, Acc) ->
|
||||||
|
level_to_num(Level) bor Acc
|
||||||
|
end, 0, Levels)}.
|
||||||
|
|
||||||
|
-spec mask_to_levels(non_neg_integer()) -> [lager:log_level()].
|
||||||
|
mask_to_levels(Mask) ->
|
||||||
|
mask_to_levels(Mask, levels(), []).
|
||||||
|
|
||||||
|
mask_to_levels(_Mask, [], Acc) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
mask_to_levels(Mask, [Level|Levels], Acc) ->
|
||||||
|
NewAcc = case (level_to_num(Level) band Mask) /= 0 of
|
||||||
|
true ->
|
||||||
|
[Level|Acc];
|
||||||
|
false ->
|
||||||
|
Acc
|
||||||
|
end,
|
||||||
|
mask_to_levels(Mask, Levels, NewAcc).
|
||||||
|
|
||||||
|
-spec config_to_levels(atom()|string()) -> [lager:log_level()].
|
||||||
|
config_to_levels(Conf) when is_atom(Conf) ->
|
||||||
|
config_to_levels(atom_to_list(Conf));
|
||||||
|
config_to_levels([$! | Rest]) ->
|
||||||
|
levels() -- config_to_levels(Rest);
|
||||||
|
config_to_levels([$=, $< | Rest]) ->
|
||||||
|
[_|Levels] = config_to_levels_int(Rest),
|
||||||
|
lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
|
||||||
|
config_to_levels([$<, $= | Rest]) ->
|
||||||
|
[_|Levels] = config_to_levels_int(Rest),
|
||||||
|
lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
|
||||||
|
config_to_levels([$>, $= | Rest]) ->
|
||||||
|
config_to_levels_int(Rest);
|
||||||
|
config_to_levels([$=, $> | Rest]) ->
|
||||||
|
config_to_levels_int(Rest);
|
||||||
|
config_to_levels([$= | Rest]) ->
|
||||||
|
[level_to_atom(Rest)];
|
||||||
|
config_to_levels([$< | Rest]) ->
|
||||||
|
Levels = config_to_levels_int(Rest),
|
||||||
|
lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
|
||||||
|
config_to_levels([$> | Rest]) ->
|
||||||
|
[_|Levels] = config_to_levels_int(Rest),
|
||||||
|
lists:filter(fun(E) -> lists:member(E, Levels) end, levels());
|
||||||
|
config_to_levels(Conf) ->
|
||||||
|
config_to_levels_int(Conf).
|
||||||
|
|
||||||
|
%% internal function to break the recursion loop
|
||||||
|
config_to_levels_int(Conf) ->
|
||||||
|
Level = level_to_atom(Conf),
|
||||||
|
lists:dropwhile(fun(E) -> E /= Level end, levels()).
|
||||||
|
|
||||||
|
level_to_atom(String) ->
|
||||||
|
Levels = levels(),
|
||||||
|
try list_to_existing_atom(String) of
|
||||||
|
Atom ->
|
||||||
|
case lists:member(Atom, Levels) of
|
||||||
|
true ->
|
||||||
|
Atom;
|
||||||
|
false ->
|
||||||
|
erlang:error(badarg)
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
erlang:error(badarg)
|
||||||
|
end.
|
||||||
|
|
||||||
|
open_logfile(Name, Buffer) ->
|
||||||
|
case filelib:ensure_dir(Name) of
|
||||||
|
ok ->
|
||||||
|
Options = [append, raw] ++
|
||||||
|
case Buffer of
|
||||||
|
{Size, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size), Size >= 0 ->
|
||||||
|
[{delayed_write, Size, Interval}];
|
||||||
|
_ -> []
|
||||||
|
end,
|
||||||
|
case file:open(Name, Options) of
|
||||||
|
{ok, FD} ->
|
||||||
|
case file:read_file_info(Name) of
|
||||||
|
{ok, FInfo} ->
|
||||||
|
Inode = FInfo#file_info.inode,
|
||||||
|
{ok, {FD, Inode, FInfo#file_info.size}};
|
||||||
|
X -> X
|
||||||
|
end;
|
||||||
|
Y -> Y
|
||||||
|
end;
|
||||||
|
Z -> Z
|
||||||
|
end.
|
||||||
|
|
||||||
|
ensure_logfile(Name, FD, Inode, Buffer) ->
|
||||||
|
case file:read_file_info(Name) of
|
||||||
|
{ok, FInfo} ->
|
||||||
|
Inode2 = FInfo#file_info.inode,
|
||||||
|
case Inode == Inode2 of
|
||||||
|
true ->
|
||||||
|
{ok, {FD, Inode, FInfo#file_info.size}};
|
||||||
|
false ->
|
||||||
|
%% delayed write can cause file:close not to do a close
|
||||||
|
_ = file:close(FD),
|
||||||
|
_ = file:close(FD),
|
||||||
|
case open_logfile(Name, Buffer) of
|
||||||
|
{ok, {FD2, Inode3, Size}} ->
|
||||||
|
%% inode changed, file was probably moved and
|
||||||
|
%% recreated
|
||||||
|
{ok, {FD2, Inode3, Size}};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
%% delayed write can cause file:close not to do a close
|
||||||
|
_ = file:close(FD),
|
||||||
|
_ = file:close(FD),
|
||||||
|
case open_logfile(Name, Buffer) of
|
||||||
|
{ok, {FD2, Inode3, Size}} ->
|
||||||
|
%% file was removed
|
||||||
|
{ok, {FD2, Inode3, Size}};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% returns localtime with milliseconds included
|
||||||
|
localtime_ms() ->
|
||||||
|
Now = os:timestamp(),
|
||||||
|
localtime_ms(Now).
|
||||||
|
|
||||||
|
localtime_ms(Now) ->
|
||||||
|
{_, _, Micro} = Now,
|
||||||
|
{Date, {Hours, Minutes, Seconds}} = calendar:now_to_local_time(Now),
|
||||||
|
{Date, {Hours, Minutes, Seconds, Micro div 1000 rem 1000}}.
|
||||||
|
|
||||||
|
|
||||||
|
maybe_utc({Date, {H, M, S, Ms}}) ->
|
||||||
|
case lager_stdlib:maybe_utc({Date, {H, M, S}}) of
|
||||||
|
{utc, {Date1, {H1, M1, S1}}} ->
|
||||||
|
{utc, {Date1, {H1, M1, S1, Ms}}};
|
||||||
|
{Date1, {H1, M1, S1}} ->
|
||||||
|
{Date1, {H1, M1, S1, Ms}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% renames failing are OK
|
||||||
|
rotate_logfile(File, 0) ->
|
||||||
|
file:delete(File);
|
||||||
|
rotate_logfile(File, 1) ->
|
||||||
|
case file:rename(File, File++".0") of
|
||||||
|
ok ->
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
rotate_logfile(File, 0)
|
||||||
|
end;
|
||||||
|
rotate_logfile(File, Count) ->
|
||||||
|
_ = file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++ integer_to_list(Count - 1)),
|
||||||
|
rotate_logfile(File, Count - 1).
|
||||||
|
|
||||||
|
format_time() ->
|
||||||
|
format_time(maybe_utc(localtime_ms())).
|
||||||
|
|
||||||
|
format_time({utc, {{Y, M, D}, {H, Mi, S, Ms}}}) ->
|
||||||
|
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
|
||||||
|
[i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms), $ , $U, $T, $C]};
|
||||||
|
format_time({{Y, M, D}, {H, Mi, S, Ms}}) ->
|
||||||
|
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
|
||||||
|
[i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms)]};
|
||||||
|
format_time({utc, {{Y, M, D}, {H, Mi, S}}}) ->
|
||||||
|
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
|
||||||
|
[i2l(H), $:, i2l(Mi), $:, i2l(S), $ , $U, $T, $C]};
|
||||||
|
format_time({{Y, M, D}, {H, Mi, S}}) ->
|
||||||
|
{[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
|
||||||
|
[i2l(H), $:, i2l(Mi), $:, i2l(S)]}.
|
||||||
|
|
||||||
|
parse_rotation_day_spec([], Res) ->
|
||||||
|
{ok, Res ++ [{hour, 0}]};
|
||||||
|
parse_rotation_day_spec([$D, D1, D2], Res) ->
|
||||||
|
case list_to_integer([D1, D2]) of
|
||||||
|
X when X >= 0, X =< 23 ->
|
||||||
|
{ok, Res ++ [{hour, X}]};
|
||||||
|
_ ->
|
||||||
|
{error, invalid_date_spec}
|
||||||
|
end;
|
||||||
|
parse_rotation_day_spec([$D, D], Res) when D >= $0, D =< $9 ->
|
||||||
|
{ok, Res ++ [{hour, D - 48}]};
|
||||||
|
parse_rotation_day_spec(_, _) ->
|
||||||
|
{error, invalid_date_spec}.
|
||||||
|
|
||||||
|
parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 ->
|
||||||
|
Week = W - 48,
|
||||||
|
parse_rotation_day_spec(T, [{day, Week}]);
|
||||||
|
parse_rotation_date_spec([$$, $M, L|T]) when L == $L; L == $l ->
|
||||||
|
%% last day in month.
|
||||||
|
parse_rotation_day_spec(T, [{date, last}]);
|
||||||
|
parse_rotation_date_spec([$$, $M, M1, M2|[$D|_]=T]) ->
|
||||||
|
case list_to_integer([M1, M2]) of
|
||||||
|
X when X >= 1, X =< 31 ->
|
||||||
|
parse_rotation_day_spec(T, [{date, X}]);
|
||||||
|
_ ->
|
||||||
|
{error, invalid_date_spec}
|
||||||
|
end;
|
||||||
|
parse_rotation_date_spec([$$, $M, M|[$D|_]=T]) ->
|
||||||
|
parse_rotation_day_spec(T, [{date, M - 48}]);
|
||||||
|
parse_rotation_date_spec([$$, $M, M1, M2]) ->
|
||||||
|
case list_to_integer([M1, M2]) of
|
||||||
|
X when X >= 1, X =< 31 ->
|
||||||
|
{ok, [{date, X}, {hour, 0}]};
|
||||||
|
_ ->
|
||||||
|
{error, invalid_date_spec}
|
||||||
|
end;
|
||||||
|
parse_rotation_date_spec([$$, $M, M]) ->
|
||||||
|
{ok, [{date, M - 48}, {hour, 0}]};
|
||||||
|
parse_rotation_date_spec([$$|X]) when X /= [] ->
|
||||||
|
parse_rotation_day_spec(X, []);
|
||||||
|
parse_rotation_date_spec(_) ->
|
||||||
|
{error, invalid_date_spec}.
|
||||||
|
|
||||||
|
calculate_next_rotation(Spec) ->
|
||||||
|
Now = calendar:local_time(),
|
||||||
|
Later = calculate_next_rotation(Spec, Now),
|
||||||
|
calendar:datetime_to_gregorian_seconds(Later) -
|
||||||
|
calendar:datetime_to_gregorian_seconds(Now).
|
||||||
|
|
||||||
|
calculate_next_rotation([], Now) ->
|
||||||
|
Now;
|
||||||
|
calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X ->
|
||||||
|
%% rotation is today, sometime
|
||||||
|
NewNow = setelement(2, Now, {X, 0, 0}),
|
||||||
|
calculate_next_rotation(T, NewNow);
|
||||||
|
calculate_next_rotation([{hour, X}|T], {{_, _, _}, _} = Now) ->
|
||||||
|
%% rotation is not today
|
||||||
|
Seconds = calendar:datetime_to_gregorian_seconds(Now) + 86400,
|
||||||
|
DateTime = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||||
|
NewNow = setelement(2, DateTime, {X, 0, 0}),
|
||||||
|
calculate_next_rotation(T, NewNow);
|
||||||
|
calculate_next_rotation([{day, Day}|T], {Date, _Time} = Now) ->
|
||||||
|
DoW = calendar:day_of_the_week(Date),
|
||||||
|
AdjustedDay = case Day of
|
||||||
|
0 -> 7;
|
||||||
|
X -> X
|
||||||
|
end,
|
||||||
|
case AdjustedDay of
|
||||||
|
DoW -> %% rotation is today
|
||||||
|
OldDate = element(1, Now),
|
||||||
|
case calculate_next_rotation(T, Now) of
|
||||||
|
{OldDate, _} = NewNow -> NewNow;
|
||||||
|
{NewDate, _} ->
|
||||||
|
%% rotation *isn't* today! rerun the calculation
|
||||||
|
NewNow = {NewDate, {0, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, Day}|T], NewNow)
|
||||||
|
end;
|
||||||
|
Y when Y > DoW -> %% rotation is later this week
|
||||||
|
PlusDays = Y - DoW,
|
||||||
|
Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
|
||||||
|
{NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||||
|
NewNow = {NewDate, {0, 0, 0}},
|
||||||
|
calculate_next_rotation(T, NewNow);
|
||||||
|
Y when Y < DoW -> %% rotation is next week
|
||||||
|
PlusDays = ((7 - DoW) + Y),
|
||||||
|
Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
|
||||||
|
{NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||||
|
NewNow = {NewDate, {0, 0, 0}},
|
||||||
|
calculate_next_rotation(T, NewNow)
|
||||||
|
end;
|
||||||
|
calculate_next_rotation([{date, last}|T], {{Year, Month, Day}, _} = Now) ->
|
||||||
|
Last = calendar:last_day_of_the_month(Year, Month),
|
||||||
|
case Last == Day of
|
||||||
|
true -> %% doing rotation today
|
||||||
|
OldDate = element(1, Now),
|
||||||
|
case calculate_next_rotation(T, Now) of
|
||||||
|
{OldDate, _} = NewNow -> NewNow;
|
||||||
|
{NewDate, _} ->
|
||||||
|
%% rotation *isn't* today! rerun the calculation
|
||||||
|
NewNow = {NewDate, {0, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}|T], NewNow)
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
NewNow = setelement(1, Now, {Year, Month, Last}),
|
||||||
|
calculate_next_rotation(T, NewNow)
|
||||||
|
end;
|
||||||
|
calculate_next_rotation([{date, Date}|T], {{_, _, Date}, _} = Now) ->
|
||||||
|
%% rotation is today
|
||||||
|
OldDate = element(1, Now),
|
||||||
|
case calculate_next_rotation(T, Now) of
|
||||||
|
{OldDate, _} = NewNow -> NewNow;
|
||||||
|
{NewDate, _} ->
|
||||||
|
%% rotation *isn't* today! rerun the calculation
|
||||||
|
NewNow = setelement(1, Now, NewDate),
|
||||||
|
calculate_next_rotation([{date, Date}|T], NewNow)
|
||||||
|
end;
|
||||||
|
calculate_next_rotation([{date, Date}|T], {{Year, Month, Day}, _} = Now) ->
|
||||||
|
PlusDays = case Date of
|
||||||
|
X when X < Day -> %% rotation is next month
|
||||||
|
Last = calendar:last_day_of_the_month(Year, Month),
|
||||||
|
(Last - Day);
|
||||||
|
X when X > Day -> %% rotation is later this month
|
||||||
|
X - Day
|
||||||
|
end,
|
||||||
|
Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
|
||||||
|
NewNow = calendar:gregorian_seconds_to_datetime(Seconds),
|
||||||
|
calculate_next_rotation(T, NewNow).
|
||||||
|
|
||||||
|
-spec trace_filter(Query :: 'none' | [tuple()]) -> {ok, any()}.
|
||||||
|
trace_filter(Query) ->
|
||||||
|
trace_filter(?DEFAULT_TRACER, Query).
|
||||||
|
|
||||||
|
%% TODO: Support multiple trace modules
|
||||||
|
%-spec trace_filter(Module :: atom(), Query :: 'none' | [tuple()]) -> {ok, any()}.
|
||||||
|
trace_filter(Module, Query) when Query == none; Query == [] ->
|
||||||
|
{ok, _} = glc:compile(Module, glc:null(false));
|
||||||
|
trace_filter(Module, Query) when is_list(Query) ->
|
||||||
|
{ok, _} = glc:compile(Module, glc_lib:reduce(trace_any(Query))).
|
||||||
|
|
||||||
|
validate_trace({Filter, Level, {Destination, ID}}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
|
||||||
|
case validate_trace({Filter, Level, Destination}) of
|
||||||
|
{ok, {F, L, D}} ->
|
||||||
|
{ok, {F, L, {D, ID}}};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end;
|
||||||
|
validate_trace({Filter, Level, Destination}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
|
||||||
|
ValidFilter = validate_trace_filter(Filter),
|
||||||
|
try config_to_mask(Level) of
|
||||||
|
_ when not ValidFilter ->
|
||||||
|
{error, invalid_trace};
|
||||||
|
L when is_list(Filter) ->
|
||||||
|
{ok, {trace_all(Filter), L, Destination}};
|
||||||
|
L ->
|
||||||
|
{ok, {Filter, L, Destination}}
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
{error, invalid_level}
|
||||||
|
end;
|
||||||
|
validate_trace(_) ->
|
||||||
|
{error, invalid_trace}.
|
||||||
|
|
||||||
|
validate_trace_filter(Filter) when is_tuple(Filter), is_atom(element(1, Filter)) =:= false ->
|
||||||
|
false;
|
||||||
|
validate_trace_filter(Filter) ->
|
||||||
|
case lists:all(fun({Key, '*'}) when is_atom(Key) -> true;
|
||||||
|
({Key, '!'}) when is_atom(Key) -> true;
|
||||||
|
({Key, _Value}) when is_atom(Key) -> true;
|
||||||
|
({Key, '=', _Value}) when is_atom(Key) -> true;
|
||||||
|
({Key, '<', _Value}) when is_atom(Key) -> true;
|
||||||
|
({Key, '>', _Value}) when is_atom(Key) -> true;
|
||||||
|
(_) -> false end, Filter) of
|
||||||
|
true ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
trace_all(Query) ->
|
||||||
|
glc:all(trace_acc(Query)).
|
||||||
|
|
||||||
|
trace_any(Query) ->
|
||||||
|
glc:any(Query).
|
||||||
|
|
||||||
|
trace_acc(Query) ->
|
||||||
|
trace_acc(Query, []).
|
||||||
|
|
||||||
|
trace_acc([], Acc) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
trace_acc([{Key, '*'}|T], Acc) ->
|
||||||
|
trace_acc(T, [glc:wc(Key)|Acc]);
|
||||||
|
trace_acc([{Key, '!'}|T], Acc) ->
|
||||||
|
trace_acc(T, [glc:nf(Key)|Acc]);
|
||||||
|
trace_acc([{Key, Val}|T], Acc) ->
|
||||||
|
trace_acc(T, [glc:eq(Key, Val)|Acc]);
|
||||||
|
trace_acc([{Key, '=', Val}|T], Acc) ->
|
||||||
|
trace_acc(T, [glc:eq(Key, Val)|Acc]);
|
||||||
|
trace_acc([{Key, '>', Val}|T], Acc) ->
|
||||||
|
trace_acc(T, [glc:gt(Key, Val)|Acc]);
|
||||||
|
trace_acc([{Key, '<', Val}|T], Acc) ->
|
||||||
|
trace_acc(T, [glc:lt(Key, Val)|Acc]).
|
||||||
|
|
||||||
|
|
||||||
|
check_traces(_, _, [], Acc) ->
|
||||||
|
lists:flatten(Acc);
|
||||||
|
check_traces(Attrs, Level, [{_, {mask, FilterLevel}, _}|Flows], Acc) when (Level band FilterLevel) == 0 ->
|
||||||
|
check_traces(Attrs, Level, Flows, Acc);
|
||||||
|
check_traces(Attrs, Level, [{Filter, _, _}|Flows], Acc) when length(Attrs) < length(Filter) ->
|
||||||
|
check_traces(Attrs, Level, Flows, Acc);
|
||||||
|
check_traces(Attrs, Level, [Flow|Flows], Acc) ->
|
||||||
|
check_traces(Attrs, Level, Flows, [check_trace(Attrs, Flow)|Acc]).
|
||||||
|
|
||||||
|
check_trace(Attrs, {Filter, _Level, Dest}) when is_list(Filter) ->
|
||||||
|
check_trace(Attrs, {trace_all(Filter), _Level, Dest});
|
||||||
|
|
||||||
|
check_trace(Attrs, {Filter, _Level, Dest}) when is_tuple(Filter) ->
|
||||||
|
Made = gre:make(Attrs, [list]),
|
||||||
|
glc:handle(?DEFAULT_TRACER, Made),
|
||||||
|
Match = glc_lib:matches(Filter, Made),
|
||||||
|
case Match of
|
||||||
|
true ->
|
||||||
|
Dest;
|
||||||
|
false ->
|
||||||
|
[]
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec is_loggable(lager_msg:lager_msg(), non_neg_integer()|{'mask', non_neg_integer()}, term()) -> boolean().
|
||||||
|
is_loggable(Msg, {mask, Mask}, MyName) ->
|
||||||
|
%% using syslog style comparison flags
|
||||||
|
%S = lager_msg:severity_as_int(Msg),
|
||||||
|
%?debugFmt("comparing masks ~.2B and ~.2B -> ~p~n", [S, Mask, S band Mask]),
|
||||||
|
(lager_msg:severity_as_int(Msg) band Mask) /= 0 orelse
|
||||||
|
lists:member(MyName, lager_msg:destinations(Msg));
|
||||||
|
is_loggable(Msg ,SeverityThreshold,MyName) ->
|
||||||
|
lager_msg:severity_as_int(Msg) =< SeverityThreshold orelse
|
||||||
|
lists:member(MyName, lager_msg:destinations(Msg)).
|
||||||
|
|
||||||
|
i2l(I) when I < 10 -> [$0, $0+I];
|
||||||
|
i2l(I) -> integer_to_list(I).
|
||||||
|
i3l(I) when I < 100 -> [$0 | i2l(I)];
|
||||||
|
i3l(I) -> integer_to_list(I).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
|
||||||
|
parse_test() ->
|
||||||
|
?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")),
|
||||||
|
?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")),
|
||||||
|
?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")),
|
||||||
|
?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")),
|
||||||
|
?assertEqual({ok, [{date, 1}, {hour, 0}]}, parse_rotation_date_spec("$M1D0")),
|
||||||
|
?assertEqual({ok, [{date, 5}, {hour, 6}]}, parse_rotation_date_spec("$M5D6")),
|
||||||
|
?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")),
|
||||||
|
?assertEqual({ok, [{date, 31}, {hour, 0}]}, parse_rotation_date_spec("$M31")),
|
||||||
|
?assertEqual({ok, [{date, 31}, {hour, 1}]}, parse_rotation_date_spec("$M31D1")),
|
||||||
|
?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$ML")),
|
||||||
|
?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$Ml")),
|
||||||
|
?assertEqual({ok, [{day, 5}, {hour, 0}]}, parse_rotation_date_spec("$W5")),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
parse_fail_test() ->
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D24")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7D1")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32D1")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D15M5")),
|
||||||
|
?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M5W5")),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
rotation_calculation_test() ->
|
||||||
|
?assertMatch({{2000, 1, 2}, {0, 0, 0}},
|
||||||
|
calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 2}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 15}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 2}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 1}, {12, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 31}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 1}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 15}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, 15}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 31}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 31}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 29}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2001, 2, 28}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{date, last}, {hour, 16}], {{2001, 1, 31}, {17, 34, 43}})),
|
||||||
|
|
||||||
|
?assertMatch({{2000, 1, 1}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 8}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 7}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 3}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 1}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 2}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 1, 9}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 2}, {17, 34, 43}})),
|
||||||
|
?assertMatch({{2000, 2, 3}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 4}, {hour, 16}], {{2000, 1, 29}, {17, 34, 43}})),
|
||||||
|
|
||||||
|
?assertMatch({{2000, 1, 7}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 3}, {17, 34, 43}})),
|
||||||
|
|
||||||
|
?assertMatch({{2000, 1, 3}, {16, 0, 0}},
|
||||||
|
calculate_next_rotation([{day, 1}, {hour, 16}], {{1999, 12, 28}, {17, 34, 43}})),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
rotate_file_test() ->
|
||||||
|
file:delete("rotation.log"),
|
||||||
|
[file:delete(["rotation.log.", integer_to_list(N)]) || N <- lists:seq(0, 9)],
|
||||||
|
[begin
|
||||||
|
file:write_file("rotation.log", integer_to_list(N)),
|
||||||
|
Count = case N > 10 of
|
||||||
|
true -> 10;
|
||||||
|
_ -> N
|
||||||
|
end,
|
||||||
|
[begin
|
||||||
|
FileName = ["rotation.log.", integer_to_list(M)],
|
||||||
|
?assert(filelib:is_regular(FileName)),
|
||||||
|
%% check the expected value is in the file
|
||||||
|
Number = list_to_binary(integer_to_list(N - M - 1)),
|
||||||
|
?assertEqual({ok, Number}, file:read_file(FileName))
|
||||||
|
end
|
||||||
|
|| M <- lists:seq(0, Count-1)],
|
||||||
|
rotate_logfile("rotation.log", 10)
|
||||||
|
end || N <- lists:seq(0, 20)].
|
||||||
|
|
||||||
|
rotate_file_fail_test() ->
|
||||||
|
%% make sure the directory exists
|
||||||
|
?assertEqual(ok, filelib:ensure_dir("rotation/rotation.log")),
|
||||||
|
%% fix the permissions on it
|
||||||
|
os:cmd("chown -R u+rwx rotation"),
|
||||||
|
%% delete any old files
|
||||||
|
[ok = file:delete(F) || F <- filelib:wildcard("rotation/*")],
|
||||||
|
%% write a file
|
||||||
|
file:write_file("rotation/rotation.log", "hello"),
|
||||||
|
%% hose up the permissions
|
||||||
|
os:cmd("chown u-w rotation"),
|
||||||
|
?assertMatch({error, _}, rotate_logfile("rotation.log", 10)),
|
||||||
|
?assert(filelib:is_regular("rotation/rotation.log")),
|
||||||
|
os:cmd("chown u+w rotation"),
|
||||||
|
?assertMatch(ok, rotate_logfile("rotation/rotation.log", 10)),
|
||||||
|
?assert(filelib:is_regular("rotation/rotation.log.0")),
|
||||||
|
?assertEqual(false, filelib:is_regular("rotation/rotation.log")),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
check_trace_test() ->
|
||||||
|
lager:start(),
|
||||||
|
trace_filter(none),
|
||||||
|
%% match by module
|
||||||
|
?assertEqual([foo], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||||
|
{[{module, ?MODULE}], config_to_mask(emergency), foo},
|
||||||
|
{[{module, test}], config_to_mask(emergency), bar}], [])),
|
||||||
|
%% match by module, but other unsatisfyable attribute
|
||||||
|
?assertEqual([], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||||
|
{[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
|
||||||
|
{[{module, test}], config_to_mask(emergency), bar}], [])),
|
||||||
|
%% match by wildcard module
|
||||||
|
?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||||
|
{[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
|
||||||
|
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||||
|
%% wildcard module, one trace with unsatisfyable attribute
|
||||||
|
?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||||
|
{[{module, '*'}, {foo, bar}], config_to_mask(emergency), foo},
|
||||||
|
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||||
|
%% wildcard but not present custom trace attribute
|
||||||
|
?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
|
||||||
|
{[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
|
||||||
|
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||||
|
%% wildcarding a custom attribute works when it is present
|
||||||
|
?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], ?EMERGENCY, [
|
||||||
|
{[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
|
||||||
|
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||||
|
%% denied by level
|
||||||
|
?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
|
||||||
|
{[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
|
||||||
|
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||||
|
%% allowed by level
|
||||||
|
?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
|
||||||
|
{[{module, '*'}, {foo, '*'}], config_to_mask(debug), foo},
|
||||||
|
{[{module, '*'}], config_to_mask(emergency), bar}], [])),
|
||||||
|
?assertEqual([anythingbutnotice, infoandbelow, infoonly], check_traces([{module, ?MODULE}], ?INFO, [
|
||||||
|
{[{module, '*'}], config_to_mask('=debug'), debugonly},
|
||||||
|
{[{module, '*'}], config_to_mask('=info'), infoonly},
|
||||||
|
{[{module, '*'}], config_to_mask('<=info'), infoandbelow},
|
||||||
|
{[{module, '*'}], config_to_mask('!=info'), anythingbutinfo},
|
||||||
|
{[{module, '*'}], config_to_mask('!=notice'), anythingbutnotice}
|
||||||
|
], [])),
|
||||||
|
application:stop(lager),
|
||||||
|
application:stop(goldrush),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
is_loggable_test_() ->
|
||||||
|
[
|
||||||
|
{"Loggable by severity only", ?_assert(is_loggable(lager_msg:new("", alert, [], []),2,me))},
|
||||||
|
{"Not loggable by severity only", ?_assertNot(is_loggable(lager_msg:new("", critical, [], []),1,me))},
|
||||||
|
{"Loggable by severity with destination", ?_assert(is_loggable(lager_msg:new("", alert, [], [you]),2,me))},
|
||||||
|
{"Not loggable by severity with destination", ?_assertNot(is_loggable(lager_msg:new("", critical, [], [you]),1,me))},
|
||||||
|
{"Loggable by destination overriding severity", ?_assert(is_loggable(lager_msg:new("", critical, [], [me]),1,me))}
|
||||||
|
].
|
||||||
|
|
||||||
|
format_time_test_() ->
|
||||||
|
[
|
||||||
|
?_assertEqual("2012-10-04 11:16:23.002",
|
||||||
|
begin
|
||||||
|
{D, T} = format_time({{2012,10,04},{11,16,23,2}}),
|
||||||
|
lists:flatten([D,$ ,T])
|
||||||
|
end),
|
||||||
|
?_assertEqual("2012-10-04 11:16:23.999",
|
||||||
|
begin
|
||||||
|
{D, T} = format_time({{2012,10,04},{11,16,23,999}}),
|
||||||
|
lists:flatten([D,$ ,T])
|
||||||
|
end),
|
||||||
|
?_assertEqual("2012-10-04 11:16:23",
|
||||||
|
begin
|
||||||
|
{D, T} = format_time({{2012,10,04},{11,16,23}}),
|
||||||
|
lists:flatten([D,$ ,T])
|
||||||
|
end),
|
||||||
|
?_assertEqual("2012-10-04 00:16:23.092 UTC",
|
||||||
|
begin
|
||||||
|
{D, T} = format_time({utc, {{2012,10,04},{0,16,23,92}}}),
|
||||||
|
lists:flatten([D,$ ,T])
|
||||||
|
end),
|
||||||
|
?_assertEqual("2012-10-04 11:16:23 UTC",
|
||||||
|
begin
|
||||||
|
{D, T} = format_time({utc, {{2012,10,04},{11,16,23}}}),
|
||||||
|
lists:flatten([D,$ ,T])
|
||||||
|
end)
|
||||||
|
].
|
||||||
|
|
||||||
|
config_to_levels_test() ->
|
||||||
|
?assertEqual([none], config_to_levels('none')),
|
||||||
|
?assertEqual({mask, 0}, config_to_mask('none')),
|
||||||
|
?assertEqual([debug], config_to_levels('=debug')),
|
||||||
|
?assertEqual([debug], config_to_levels('<info')),
|
||||||
|
?assertEqual(levels() -- [debug], config_to_levels('!=debug')),
|
||||||
|
?assertEqual(levels() -- [debug], config_to_levels('>debug')),
|
||||||
|
?assertEqual(levels() -- [debug], config_to_levels('>=info')),
|
||||||
|
?assertEqual(levels() -- [debug], config_to_levels('=>info')),
|
||||||
|
?assertEqual([debug, info, notice], config_to_levels('<=notice')),
|
||||||
|
?assertEqual([debug, info, notice], config_to_levels('=<notice')),
|
||||||
|
?assertEqual([debug], config_to_levels('<info')),
|
||||||
|
?assertEqual([debug], config_to_levels('!info')),
|
||||||
|
?assertError(badarg, config_to_levels(ok)),
|
||||||
|
?assertError(badarg, config_to_levels('<=>info')),
|
||||||
|
?assertError(badarg, config_to_levels('=<=info')),
|
||||||
|
?assertError(badarg, config_to_levels('<==>=<=>info')),
|
||||||
|
%% double negatives DO work, however
|
||||||
|
?assertEqual([debug], config_to_levels('!!=debug')),
|
||||||
|
?assertEqual(levels() -- [debug], config_to_levels('!!!=debug')),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
config_to_mask_test() ->
|
||||||
|
?assertEqual({mask, 0}, config_to_mask('none')),
|
||||||
|
?assertEqual({mask, ?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('debug')),
|
||||||
|
?assertEqual({mask, ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('warning')),
|
||||||
|
?assertEqual({mask, ?DEBUG bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('!=info')),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
mask_to_levels_test() ->
|
||||||
|
?assertEqual([], mask_to_levels(0)),
|
||||||
|
?assertEqual([debug], mask_to_levels(2#10000000)),
|
||||||
|
?assertEqual([debug, info], mask_to_levels(2#11000000)),
|
||||||
|
?assertEqual([debug, info, emergency], mask_to_levels(2#11000001)),
|
||||||
|
?assertEqual([debug, notice, error], mask_to_levels(?DEBUG bor ?NOTICE bor ?ERROR)),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
-endif.
|
|
@ -0,0 +1,2 @@
|
||||||
|
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/ejabberd_auth_http', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -16,8 +16,6 @@ central user database, which is shared with other services. It fits
|
||||||
perfectly when client application uses custom authentication token and
|
perfectly when client application uses custom authentication token and
|
||||||
ejabberd has to validate it externally.
|
ejabberd has to validate it externally.
|
||||||
|
|
||||||
This module requires ejabberd 20.02 or higher.
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### How to enable
|
### How to enable
|
||||||
|
@ -49,17 +47,6 @@ properly. The following options can be set in `auth_opts` in
|
||||||
* `path_prefix` (optional, default: `"/"`) - a path prefix to be
|
* `path_prefix` (optional, default: `"/"`) - a path prefix to be
|
||||||
inserted between `host` and method name; must be terminated with `/`
|
inserted between `host` and method name; must be terminated with `/`
|
||||||
|
|
||||||
Example configuration:
|
|
||||||
```
|
|
||||||
auth_method: http
|
|
||||||
auth_opts:
|
|
||||||
host: "http://localhost:12000"
|
|
||||||
connection_pool_size: 10
|
|
||||||
connection_opts: []
|
|
||||||
basic_auth: ""
|
|
||||||
path_prefix: "/"
|
|
||||||
```
|
|
||||||
|
|
||||||
## SCRAM support
|
## SCRAM support
|
||||||
|
|
||||||
`ejabberd_auth_http` can use the SCRAM method. When SCRAM is enabled,
|
`ejabberd_auth_http` can use the SCRAM method. When SCRAM is enabled,
|
||||||
|
@ -184,27 +171,23 @@ configuration along with use cases.
|
||||||
An Auth token is provided as a password.
|
An Auth token is provided as a password.
|
||||||
|
|
||||||
* **Service implements:** `check_password`, `user_exists`
|
* **Service implements:** `check_password`, `user_exists`
|
||||||
* **ejabberd config:** `auth_password format`: `plain`, `mod_register` disabled
|
* **ejabberd config:** `password format`: `plain`, `mod_register` disabled
|
||||||
* **Client side:** MUST NOT use `DIGEST-MD5` mechanism; use `PLAIN`
|
* **Client side:** MUST NOT use `DIGEST-MD5` mechanism; use `PLAIN`
|
||||||
|
|
||||||
### Central database of plaintext passwords
|
### Central database of plaintext passwords
|
||||||
|
|
||||||
* **Service implements:** `check_password`, `get_password`, `user_exists`
|
* **Service implements:** `check_password`, `get_password`, `user_exists`
|
||||||
* **ejabberd config:** `auth_password_format`: `plain`, `mod_register` disabled
|
* **ejabberd config:** `password format`: `plain`, `mod_register` disabled
|
||||||
* **Client side:** May use any available auth method
|
* **Client side:** May use any available auth method
|
||||||
|
|
||||||
### Central database able to process SCRAM
|
### Central database able to process SCRAM
|
||||||
|
|
||||||
* **Service implements:** `get_password`, `user_exists`
|
* **Service implements:** `get_password`, `user_exists`
|
||||||
* **ejabberd config:** `auth_password_format`: `scram`, `mod_register` disabled
|
* **ejabberd config:** `password format`: `scram`, `mod_register` disabled
|
||||||
* **Client side:** May use any available auth method
|
* **Client side:** May use any available auth method
|
||||||
|
|
||||||
### All-included
|
### All-included
|
||||||
|
|
||||||
* **Service implements:** all methods
|
* **Service implements:** all methods
|
||||||
* **ejabberd config:** `auth_password_format`: `scram` (recommended) or `plain`, `mod_register` enabled
|
* **ejabberd config:** `password format`: `scram` (recommended) or `plain`, `mod_register` enabled
|
||||||
* **Client side:** May use any available auth method
|
* **Client side:** May use any available auth method
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the service supports multiple passwords per user you need to disable authentication caching with `auth_use_cache: false`. See [https://github.com/processone/ejabberd-contrib/issues/288](https://github.com/processone/ejabberd-contrib/issues/288).
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Piotr Nosek <piotr.nosek at erlang-solutions.com>"
|
|
||||||
category: "auth"
|
|
||||||
summary: "Authentication via HTTP request"
|
|
||||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
|
||||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
|
@ -1,4 +0,0 @@
|
||||||
{deps, [
|
|
||||||
{cuesport, ".*", {git, "https://github.com/goj/cuesport"}},
|
|
||||||
{fusco, ".*", {git, "https://github.com/esl/fusco"}}
|
|
||||||
]}.
|
|
|
@ -8,26 +8,27 @@
|
||||||
-module(ejabberd_auth_http).
|
-module(ejabberd_auth_http).
|
||||||
-author('piotr.nosek@erlang-solutions.com').
|
-author('piotr.nosek@erlang-solutions.com').
|
||||||
|
|
||||||
-behaviour(ejabberd_auth).
|
|
||||||
|
|
||||||
%% External exports
|
%% External exports
|
||||||
-export([start/1,
|
-export([start/1,
|
||||||
set_password/3,
|
set_password/3,
|
||||||
check_password/4,
|
check_password/3,
|
||||||
check_password/6,
|
check_password/5,
|
||||||
try_register/3,
|
try_register/3,
|
||||||
|
dirty_get_registered_users/0,
|
||||||
|
get_vh_registered_users/1,
|
||||||
|
get_vh_registered_users/2,
|
||||||
|
get_vh_registered_users_number/1,
|
||||||
|
get_vh_registered_users_number/2,
|
||||||
get_password/2,
|
get_password/2,
|
||||||
get_password_s/2,
|
get_password_s/2,
|
||||||
user_exists/2,
|
is_user_exists/2,
|
||||||
remove_user/2,
|
remove_user/2,
|
||||||
remove_user/3,
|
remove_user/3,
|
||||||
plain_password_required/1,
|
plain_password_required/0,
|
||||||
store_type/1,
|
store_type/1
|
||||||
login/2,
|
]).
|
||||||
get_password/3,
|
|
||||||
stop/1]).
|
|
||||||
|
|
||||||
-include_lib("xmpp/include/scram.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
@ -36,143 +37,180 @@
|
||||||
|
|
||||||
-spec start(binary()) -> ok.
|
-spec start(binary()) -> ok.
|
||||||
start(Host) ->
|
start(Host) ->
|
||||||
AuthOpts = ejabberd_config:get_option({auth_opts, Host}),
|
AuthOpts = ejabberd_config:get_local_option(auth_opts, Host),
|
||||||
{_, AuthHost} = lists:keyfind(host, 1, AuthOpts),
|
{_, AuthHost} = lists:keyfind(host, 1, AuthOpts),
|
||||||
PoolSize = proplists:get_value(connection_pool_size, AuthOpts, 10),
|
PoolSize = proplists:get_value(connection_pool_size, AuthOpts, 10),
|
||||||
Opts = proplists:get_value(connection_opts, AuthOpts, []),
|
Opts = proplists:get_value(connection_opts, AuthOpts, []),
|
||||||
ChildMods = [fusco],
|
ChildMods = [fusco],
|
||||||
ChildMFA = {fusco, start_link, [binary_to_list(AuthHost), Opts]},
|
ChildMFA = {fusco, start_link, [AuthHost, Opts]},
|
||||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
|
||||||
ChildSpec = {Proc, {cuesport, start_link,
|
{ok, _} = supervisor:start_child(ejabberd_sup,
|
||||||
[pool_name(Host), PoolSize, ChildMods, ChildMFA]},
|
{{ejabberd_auth_http_sup, Host},
|
||||||
transient, 2000, supervisor, [cuesport | ChildMods]},
|
{cuesport, start_link,
|
||||||
supervisor:start_child(ejabberd_backend_sup, ChildSpec),
|
[pool_name(Host), PoolSize, ChildMods, ChildMFA]},
|
||||||
|
transient, 2000, supervisor, [cuesport | ChildMods]}),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec plain_password_required(binary()) -> false.
|
-spec plain_password_required() -> false.
|
||||||
plain_password_required(_Server) ->
|
plain_password_required() ->
|
||||||
false.
|
false.
|
||||||
|
|
||||||
-spec store_type(binary()) -> external.
|
-spec store_type(binary()) -> plain | scram.
|
||||||
store_type(_) ->
|
store_type(Server) ->
|
||||||
external.
|
case scram:enabled(Server) of
|
||||||
|
false -> plain;
|
||||||
-spec check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean()}.
|
true -> scram
|
||||||
check_password(LUser, _AuthzId, LServer, Password) ->
|
|
||||||
case scram2:enabled(LServer) of
|
|
||||||
false ->
|
|
||||||
case make_req(get, <<"check_password">>, LUser, LServer, Password) of
|
|
||||||
{ok, <<"true">>} -> {cache, true};
|
|
||||||
_ -> {nocache, false}
|
|
||||||
end;
|
|
||||||
true ->
|
|
||||||
case verify_scram_password(LUser, LServer, Password) of
|
|
||||||
{ok, true} -> {cache, true};
|
|
||||||
_ -> {nocache, false}
|
|
||||||
end
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec check_password(binary(), binary(), binary(), binary(), binary(), fun()) -> boolean().
|
-spec check_password(binary(), binary(), binary()) -> boolean().
|
||||||
check_password(LUser, _AuthzId, LServer, Password, Digest, DigestGen) ->
|
check_password(User, Server, Password) ->
|
||||||
|
{LUser, LServer} = stringprep(User, Server),
|
||||||
|
case scram:enabled(Server) of
|
||||||
|
false ->
|
||||||
|
case make_req(get, <<"check_password">>, LUser, LServer, Password) of
|
||||||
|
{ok, <<"true">>} -> true;
|
||||||
|
_ -> false
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
{ok, true} =:= verify_scram_password(LUser, LServer, Password)
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec check_password(binary(), binary(), binary(), binary(), fun()) -> boolean().
|
||||||
|
check_password(User, Server, Password, Digest, DigestGen) ->
|
||||||
|
{LUser, LServer} = stringprep(User, Server),
|
||||||
case make_req(get, <<"get_password">>, LUser, LServer, <<"">>) of
|
case make_req(get, <<"get_password">>, LUser, LServer, <<"">>) of
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
false;
|
false;
|
||||||
{ok, GotPasswd} ->
|
{ok, GotPasswd} ->
|
||||||
case scram2:enabled(LServer) of
|
case scram:enabled(LServer) of
|
||||||
true ->
|
true ->
|
||||||
case scram2:deserialize(GotPasswd) of
|
case scram:deserialize(GotPasswd) of
|
||||||
{ok, #scram{} = Scram} ->
|
{ok, #scram{storedkey = StoredKey}} ->
|
||||||
scram2:check_digest(Scram, Digest, DigestGen, Password);
|
Passwd = base64:decode(StoredKey),
|
||||||
|
ejabberd_auth:check_digest(Digest, DigestGen, Password, Passwd);
|
||||||
_ ->
|
_ ->
|
||||||
false
|
false
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
check_digest(Digest, DigestGen, Password, GotPasswd)
|
ejabberd_auth:check_digest(Digest, DigestGen, Password, GotPasswd)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec check_digest(binary(), fun(), binary(), binary()) -> boolean().
|
-spec set_password(binary(), binary(), binary()) -> ok | {error, term()}.
|
||||||
check_digest(Digest, DigestGen, Password, Passwd) ->
|
set_password(User, Server, Password) ->
|
||||||
DigRes = if
|
{LUser, LServer} = stringprep(User, Server),
|
||||||
Digest /= <<>> ->
|
PasswordFinal = case scram:enabled(LServer) of
|
||||||
Digest == DigestGen(Passwd);
|
true -> scram:serialize(scram:password_to_scram(
|
||||||
true ->
|
Password, scram:iterations(Server)));
|
||||||
false
|
|
||||||
end,
|
|
||||||
if DigRes ->
|
|
||||||
true;
|
|
||||||
true ->
|
|
||||||
(Passwd == Password) and (Password /= <<>>)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
-spec set_password(binary(), binary(), binary()) -> {ets_cache:tag(), {ok, binary()} | {error, not_allowed}}.
|
|
||||||
set_password(LUser, LServer, Password) ->
|
|
||||||
PasswordFinal = case scram2:enabled(LServer) of
|
|
||||||
true -> scram2:serialize(scram2:password_to_scram(
|
|
||||||
Password, scram2:iterations(LServer)));
|
|
||||||
false -> Password
|
false -> Password
|
||||||
end,
|
end,
|
||||||
case make_req(post, <<"set_password">>, LUser, LServer, PasswordFinal) of
|
case make_req(post, <<"set_password">>, LUser, LServer, PasswordFinal) of
|
||||||
{error, _Error} -> {nocache, {error, not_allowed}};
|
{error, _} = Err -> Err;
|
||||||
{ok, _} -> {cache, {ok, Password}}
|
_ -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec try_register(binary(), binary(), binary()) -> {ets_cache:tag(), {ok, binary()} | {error, exists | not_allowed}}.
|
-spec try_register(binary(), binary(), binary()) -> {atomic, ok | exists} | {error, term()}.
|
||||||
try_register(LUser, LServer, Password) ->
|
try_register(User, Server, Password) ->
|
||||||
PasswordFinal = case scram2:enabled(LServer) of
|
{LUser, LServer} = stringprep(User, Server),
|
||||||
true -> scram2:serialize(scram2:password_to_scram(
|
PasswordFinal = case scram:enabled(LServer) of
|
||||||
Password, scram2:iterations(LServer)));
|
true -> scram:serialize(scram:password_to_scram(
|
||||||
|
Password, scram:iterations(Server)));
|
||||||
false -> Password
|
false -> Password
|
||||||
end,
|
end,
|
||||||
case make_req(post, <<"register">>, LUser, LServer, PasswordFinal) of
|
case make_req(post, <<"register">>, LUser, LServer, PasswordFinal) of
|
||||||
{ok, <<"created">>} -> {cache, {ok, Password}};
|
{ok, created} -> {atomic, ok};
|
||||||
{error, conflict} -> {nocache, {error, exists}};
|
{error, conflict} -> {atomic, exists};
|
||||||
_Error -> {nocache, {error, not_allowed}}
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_password(binary(), binary()) -> {cache, error}.
|
-spec dirty_get_registered_users() -> [].
|
||||||
get_password(_, _) ->
|
dirty_get_registered_users() ->
|
||||||
{cache, error}.
|
[].
|
||||||
|
|
||||||
-spec get_password_s(binary(), binary()) -> {cache, error}.
|
-spec get_vh_registered_users(binary()) -> [].
|
||||||
get_password_s(_User, _Server) ->
|
get_vh_registered_users(_Server) ->
|
||||||
{cache, error}.
|
[].
|
||||||
|
|
||||||
-spec user_exists(binary(), binary()) -> {ets_cache:tag(), boolean()}.
|
-spec get_vh_registered_users(binary(), list()) -> [].
|
||||||
user_exists(LUser, LServer) ->
|
get_vh_registered_users(_Server, _Opts) ->
|
||||||
|
[].
|
||||||
|
|
||||||
|
-spec get_vh_registered_users_number(binary()) -> 0.
|
||||||
|
get_vh_registered_users_number(_Server) ->
|
||||||
|
0.
|
||||||
|
|
||||||
|
-spec get_vh_registered_users_number(binary(), list()) -> 0.
|
||||||
|
get_vh_registered_users_number(_Server, _Opts) ->
|
||||||
|
0.
|
||||||
|
|
||||||
|
-spec get_password(binary(), binary()) -> false | binary() |
|
||||||
|
{binary(), binary(), binary(), integer()}.
|
||||||
|
get_password(User, Server) ->
|
||||||
|
{LUser, LServer} = stringprep(User, Server),
|
||||||
|
case make_req(get, <<"get_password">>, LUser, LServer, <<"">>) of
|
||||||
|
{error, _} ->
|
||||||
|
false;
|
||||||
|
{ok, Password} ->
|
||||||
|
case scram:enabled(LServer) of
|
||||||
|
true ->
|
||||||
|
case scram:deserialize(Password) of
|
||||||
|
{ok, #scram{} = Scram} ->
|
||||||
|
{base64:decode(Scram#scram.storedkey),
|
||||||
|
base64:decode(Scram#scram.serverkey),
|
||||||
|
base64:decode(Scram#scram.salt),
|
||||||
|
Scram#scram.iterationcount};
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
Password
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec get_password_s(binary(), binary()) -> binary().
|
||||||
|
get_password_s(User, Server) ->
|
||||||
|
case get_password(User, Server) of
|
||||||
|
Pass when is_binary(Pass) -> Pass;
|
||||||
|
_ -> <<>>
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec is_user_exists(binary(), binary()) -> boolean().
|
||||||
|
is_user_exists(User, Server) ->
|
||||||
|
{LUser, LServer} = stringprep(User, Server),
|
||||||
case make_req(get, <<"user_exists">>, LUser, LServer, <<"">>) of
|
case make_req(get, <<"user_exists">>, LUser, LServer, <<"">>) of
|
||||||
{ok, <<"true">>} -> {cache, true};
|
{ok, <<"true">>} -> true;
|
||||||
_ -> {nocache, false}
|
_ -> false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec remove_user(binary(), binary()) -> ok | {error, db_failure | not_allowed}.
|
-spec remove_user(binary(), binary()) -> ok | not_exists | not_allowed | bad_request.
|
||||||
remove_user(LUser, LServer) ->
|
remove_user(User, Server) ->
|
||||||
|
{LUser, LServer} = stringprep(User, Server),
|
||||||
remove_user_req(LUser, LServer, <<"">>, <<"remove_user">>).
|
remove_user_req(LUser, LServer, <<"">>, <<"remove_user">>).
|
||||||
|
|
||||||
-spec remove_user(binary(), binary(), binary()) -> ok | {error, db_failure | not_allowed}.
|
-spec remove_user(binary(), binary(), binary()) -> ok | not_exists | not_allowed | bad_request.
|
||||||
remove_user(LUser, LServer, Password) ->
|
remove_user(User, Server, Password) ->
|
||||||
case scram2:enabled(LServer) of
|
{LUser, LServer} = stringprep(User, Server),
|
||||||
|
case scram:enabled(Server) of
|
||||||
false ->
|
false ->
|
||||||
remove_user_req(LUser, LServer, Password, <<"remove_user_validate">>);
|
remove_user_req(LUser, LServer, Password, <<"remove_user_validate">>);
|
||||||
true ->
|
true ->
|
||||||
case verify_scram_password(LUser, LServer, Password) of
|
case verify_scram_password(LUser, LServer, Password) of
|
||||||
{ok, false} ->
|
{ok, false} ->
|
||||||
{error, not_allowed};
|
not_allowed;
|
||||||
{ok, true} ->
|
{ok, true} ->
|
||||||
remove_user_req(LUser, LServer, <<"">>, <<"remove_user">>);
|
remove_user_req(LUser, LServer, <<"">>, <<"remove_user">>);
|
||||||
{error, _Error} ->
|
{error, Error} ->
|
||||||
{error, db_failure}
|
Error
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec remove_user_req(binary(), binary(), binary(), binary()) ->
|
-spec remove_user_req(binary(), binary(), binary(), binary()) ->
|
||||||
ok | {error, not_allowed | db_failure}.
|
ok | not_exists | not_allowed | bad_request.
|
||||||
remove_user_req(LUser, LServer, Password, Method) ->
|
remove_user_req(LUser, LServer, Password, Method) ->
|
||||||
case make_req(post, Method, LUser, LServer, Password) of
|
case make_req(post, Method, LUser, LServer, Password) of
|
||||||
{error, not_allowed} -> {error, not_allowed};
|
{error, not_allowed} -> not_allowed;
|
||||||
{error, not_found} -> {error, db_failure};
|
{error, not_found} -> not_exists;
|
||||||
{error, _} -> {error, db_failure};
|
{error, _} -> bad_request;
|
||||||
_ -> ok
|
_ -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -180,55 +218,36 @@ remove_user_req(LUser, LServer, Password, Method) ->
|
||||||
%%% Request maker
|
%%% Request maker
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
-ifdef(OTP_BELOW_25).
|
|
||||||
-dialyzer({no_missing_calls, [uri_quote/1]}).
|
|
||||||
uri_quote(URL) ->
|
|
||||||
http_uri:encode(URL).
|
|
||||||
-else.
|
|
||||||
uri_quote(URL) ->
|
|
||||||
uri_string:quote(URL). % Available since OTP 25.0
|
|
||||||
-endif.
|
|
||||||
|
|
||||||
-spec make_req(post | get, binary(), binary(), binary(), binary()) ->
|
-spec make_req(post | get, binary(), binary(), binary(), binary()) ->
|
||||||
{ok, Body :: binary()} | {error, term()}.
|
{ok, Body :: binary()} | {error, term()}.
|
||||||
make_req(_, _, LUser, LServer, _) when LUser == error orelse LServer == error ->
|
make_req(_, _, LUser, LServer, _) when LUser == error orelse LServer == error ->
|
||||||
{error, {prep_failed, LUser, LServer}};
|
{error, {prep_failed, LUser, LServer}};
|
||||||
make_req(Method, Path, LUser, LServer, Password) ->
|
make_req(Method, Path, LUser, LServer, Password) ->
|
||||||
AuthOpts = ejabberd_config:get_option({auth_opts, LServer}),
|
AuthOpts = ejabberd_config:get_local_option(auth_opts, LServer),
|
||||||
BasicAuth = case lists:keyfind(basic_auth, 1, AuthOpts) of
|
BasicAuth = case lists:keyfind(basic_auth, 1, AuthOpts) of
|
||||||
{_, BasicAuth0} -> BasicAuth0;
|
{_, BasicAuth0} -> BasicAuth0;
|
||||||
_ -> ""
|
_ -> ""
|
||||||
end,
|
end,
|
||||||
PathPrefix = case lists:keyfind(path_prefix, 1, AuthOpts) of
|
PathPrefix = case lists:keyfind(path_prefix, 1, AuthOpts) of
|
||||||
{_, Prefix} -> Prefix;
|
{_, Prefix} -> ejabberd_binary:string_to_binary(Prefix);
|
||||||
false -> <<"/">>
|
false -> <<"/">>
|
||||||
end,
|
end,
|
||||||
BasicAuth64 = base64:encode(BasicAuth),
|
BasicAuth64 = base64:encode(BasicAuth),
|
||||||
LUserE = list_to_binary(uri_quote(binary_to_list(LUser))),
|
LUserE = list_to_binary(http_uri:encode(binary_to_list(LUser))),
|
||||||
LServerE = list_to_binary(uri_quote(binary_to_list(LServer))),
|
LServerE = list_to_binary(http_uri:encode(binary_to_list(LServer))),
|
||||||
PasswordE = list_to_binary(uri_quote(binary_to_list(Password))),
|
PasswordE = list_to_binary(http_uri:encode(binary_to_list(Password))),
|
||||||
Query = <<"user=", LUserE/binary, "&server=", LServerE/binary, "&pass=", PasswordE/binary>>,
|
Query = <<"user=", LUserE/binary, "&server=", LServerE/binary, "&pass=", PasswordE/binary>>,
|
||||||
Header = [{<<"Authorization">>, <<"Basic ", BasicAuth64/binary>>}],
|
Header = [{<<"Authorization">>, <<"Basic ", BasicAuth64/binary>>}],
|
||||||
ContentType = {<<"Content-Type">>, <<"application/x-www-form-urlencoded">>},
|
|
||||||
Connection = cuesport:get_worker(existing_pool_name(LServer)),
|
Connection = cuesport:get_worker(existing_pool_name(LServer)),
|
||||||
|
|
||||||
?DEBUG("Making request '~s' for user ~s@~s...", [Path, LUser, LServer]),
|
?DEBUG("Making request '~s' for user ~s@~s...", [Path, LUser, LServer]),
|
||||||
{Url, MethodStr, Headers, Query2} =
|
{ok, {{Code, _Reason}, _RespHeaders, RespBody, _, _}} = case Method of
|
||||||
case Method of
|
get -> fusco:request(Connection, <<PathPrefix/binary, Path/binary, "?", Query/binary>>,
|
||||||
get -> {<<PathPrefix/binary, Path/binary, "?", Query/binary>>,
|
"GET", Header, "", 2, 5000);
|
||||||
"GET",
|
post -> fusco:request(Connection, <<PathPrefix/binary, Path/binary>>,
|
||||||
Header,
|
"POST", Header, Query, 2, 5000)
|
||||||
""};
|
end,
|
||||||
post -> {<<PathPrefix/binary, Path/binary>>,
|
|
||||||
"POST",
|
|
||||||
[ContentType|Header],
|
|
||||||
Query}
|
|
||||||
end,
|
|
||||||
http_request(Connection, Url, MethodStr, Headers, Query2, 0).
|
|
||||||
|
|
||||||
http_request(Connection, Url, MethodStr, Headers, Query, RedirectCounter) ->
|
|
||||||
{ok, {{Code, _Reason}, RespHeaders, RespBody, _, _}} =
|
|
||||||
fusco:request(Connection, Url, MethodStr, Headers, Query, 2, 5000),
|
|
||||||
?DEBUG("Request result: ~s: ~p", [Code, RespBody]),
|
?DEBUG("Request result: ~s: ~p", [Code, RespBody]),
|
||||||
case Code of
|
case Code of
|
||||||
<<"409">> -> {error, conflict};
|
<<"409">> -> {error, conflict};
|
||||||
|
@ -236,25 +255,17 @@ http_request(Connection, Url, MethodStr, Headers, Query, RedirectCounter) ->
|
||||||
<<"401">> -> {error, not_authorized};
|
<<"401">> -> {error, not_authorized};
|
||||||
<<"403">> -> {error, not_allowed};
|
<<"403">> -> {error, not_allowed};
|
||||||
<<"400">> -> {error, RespBody};
|
<<"400">> -> {error, RespBody};
|
||||||
<<"503">> -> {error, RespBody};
|
|
||||||
<<"204">> -> {ok, <<"">>};
|
<<"204">> -> {ok, <<"">>};
|
||||||
<<"201">> -> {ok, <<"created">>};
|
<<"201">> -> {ok, created};
|
||||||
<<"200">> -> {ok, RespBody};
|
<<"200">> -> {ok, RespBody}
|
||||||
R when (R==<<"301">>) or (R==<<"307">>) or (R==<<"308">>) ->
|
|
||||||
handle_redirect(RespHeaders, Connection, MethodStr, Headers, Query, RedirectCounter+1);
|
|
||||||
_ -> {error, RespBody}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle_redirect(RespHeaders, Connection, MethodStr, Headers, Query, RedirectCounter)
|
|
||||||
when RedirectCounter < 5 ->
|
|
||||||
{_, Location} = lists:keyfind(<<"location">>, 1, RespHeaders),
|
|
||||||
http_request(Connection, Location, MethodStr, Headers, Query, RedirectCounter);
|
|
||||||
handle_redirect(_, _, _, _, _, _) ->
|
|
||||||
{error, redirect_loop}.
|
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% Other internal functions
|
%%% Other internal functions
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
stringprep(User, Server) -> {jlib:nodeprep(User), jlib:nameprep(Server)}.
|
||||||
|
|
||||||
-spec pool_name(binary()) -> atom().
|
-spec pool_name(binary()) -> atom().
|
||||||
pool_name(Host) ->
|
pool_name(Host) ->
|
||||||
list_to_atom("ejabberd_auth_http_" ++ binary_to_list(Host)).
|
list_to_atom("ejabberd_auth_http_" ++ binary_to_list(Host)).
|
||||||
|
@ -268,9 +279,9 @@ existing_pool_name(Host) ->
|
||||||
verify_scram_password(LUser, LServer, Password) ->
|
verify_scram_password(LUser, LServer, Password) ->
|
||||||
case make_req(get, <<"get_password">>, LUser, LServer, <<"">>) of
|
case make_req(get, <<"get_password">>, LUser, LServer, <<"">>) of
|
||||||
{ok, RawPassword} ->
|
{ok, RawPassword} ->
|
||||||
case scram2:deserialize(RawPassword) of
|
case scram:deserialize(RawPassword) of
|
||||||
{ok, #scram{} = ScramRecord} ->
|
{ok, #scram{} = ScramRecord} ->
|
||||||
{ok, scram2:check_password(Password, ScramRecord)};
|
{ok, scram:check_password(Password, ScramRecord)};
|
||||||
_ ->
|
_ ->
|
||||||
{error, bad_request}
|
{error, bad_request}
|
||||||
end;
|
end;
|
||||||
|
@ -278,13 +289,3 @@ verify_scram_password(LUser, LServer, Password) ->
|
||||||
{error, not_exists}
|
{error, not_exists}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
login(_User, _Server) ->
|
|
||||||
erlang:error(not_implemented).
|
|
||||||
|
|
||||||
get_password(_User, _Server, _DefaultValue) ->
|
|
||||||
erlang:error(not_implemented).
|
|
||||||
|
|
||||||
stop(Host) ->
|
|
||||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
|
||||||
supervisor:terminate_child(ejabberd_backend_sup, Proc),
|
|
||||||
supervisor:delete_child(ejabberd_backend_sup, Proc).
|
|
||||||
|
|
|
@ -1,192 +0,0 @@
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
%%% File : scram.erl
|
|
||||||
%%% Author : Stephen Röttger <stephen.roettger@googlemail.com>
|
|
||||||
%%% Purpose : SCRAM (RFC 5802)
|
|
||||||
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
|
|
||||||
%%%
|
|
||||||
%%%
|
|
||||||
%%% ejabberd, Copyright (C) 2002-2020 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., 59 Temple Place, Suite 330, Boston, MA
|
|
||||||
%%% 02111-1307 USA
|
|
||||||
%%%
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(scram2).
|
|
||||||
|
|
||||||
-author('stephen.roettger@googlemail.com').
|
|
||||||
|
|
||||||
-include_lib("xmpp/include/scram.hrl").
|
|
||||||
-include("logger.hrl").
|
|
||||||
|
|
||||||
%% External exports
|
|
||||||
%% ejabberd doesn't implement SASLPREP, so we use the similar RESOURCEPREP instead
|
|
||||||
-export([ % Core SCRAM functions
|
|
||||||
salted_password/3,
|
|
||||||
stored_key/1,
|
|
||||||
server_key/1,
|
|
||||||
server_signature/2,
|
|
||||||
client_signature/2,
|
|
||||||
client_key/1,
|
|
||||||
client_key/2]).
|
|
||||||
|
|
||||||
-export([
|
|
||||||
enabled/1,
|
|
||||||
iterations/0,
|
|
||||||
iterations/1,
|
|
||||||
password_to_scram/1,
|
|
||||||
password_to_scram/2,
|
|
||||||
check_password/2,
|
|
||||||
check_digest/4
|
|
||||||
]).
|
|
||||||
|
|
||||||
-export([serialize/1, deserialize/1]).
|
|
||||||
|
|
||||||
-export([scram_to_tuple/1]).
|
|
||||||
|
|
||||||
-define(SALT_LENGTH, 16).
|
|
||||||
-define(SCRAM_SERIAL_PREFIX, "==SCRAM==,").
|
|
||||||
|
|
||||||
-spec salted_password(binary(), binary(), non_neg_integer()) -> binary().
|
|
||||||
salted_password(Password, Salt, IterationCount) ->
|
|
||||||
hi(jid:resourceprep(Password), Salt, IterationCount).
|
|
||||||
|
|
||||||
-spec client_key(binary()) -> binary().
|
|
||||||
client_key(SaltedPassword) ->
|
|
||||||
crypto_hmac(sha, SaltedPassword, <<"Client Key">>).
|
|
||||||
|
|
||||||
-spec stored_key(binary()) -> binary().
|
|
||||||
stored_key(ClientKey) -> crypto:hash(sha, ClientKey).
|
|
||||||
|
|
||||||
-spec server_key(binary()) -> binary().
|
|
||||||
server_key(SaltedPassword) ->
|
|
||||||
crypto_hmac(sha, SaltedPassword, <<"Server Key">>).
|
|
||||||
|
|
||||||
-spec client_signature(binary(), binary()) -> binary().
|
|
||||||
client_signature(StoredKey, AuthMessage) ->
|
|
||||||
crypto_hmac(sha, StoredKey, AuthMessage).
|
|
||||||
|
|
||||||
-spec client_key(binary(), binary()) -> binary().
|
|
||||||
client_key(ClientProof, ClientSignature) ->
|
|
||||||
list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
|
|
||||||
binary_to_list(ClientProof),
|
|
||||||
binary_to_list(ClientSignature))).
|
|
||||||
|
|
||||||
-spec server_signature(binary(), binary()) -> binary().
|
|
||||||
server_signature(ServerKey, AuthMessage) ->
|
|
||||||
crypto_hmac(sha, ServerKey, AuthMessage).
|
|
||||||
|
|
||||||
hi(Password, Salt, IterationCount) ->
|
|
||||||
U1 = crypto_hmac(sha, Password, <<Salt/binary, 0, 0, 0, 1>>),
|
|
||||||
list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
|
|
||||||
binary_to_list(U1),
|
|
||||||
binary_to_list(hi_round(Password, U1,
|
|
||||||
IterationCount - 1)))).
|
|
||||||
|
|
||||||
hi_round(Password, UPrev, 1) ->
|
|
||||||
crypto_hmac(sha, Password, UPrev);
|
|
||||||
hi_round(Password, UPrev, IterationCount) ->
|
|
||||||
U = crypto_hmac(sha, Password, UPrev),
|
|
||||||
list_to_binary(lists:zipwith(fun (X, Y) -> X bxor Y end,
|
|
||||||
binary_to_list(U),
|
|
||||||
binary_to_list(hi_round(Password, U,
|
|
||||||
IterationCount - 1)))).
|
|
||||||
|
|
||||||
|
|
||||||
enabled(Host) ->
|
|
||||||
case ejabberd_config:get_option({auth_opts, Host}) of
|
|
||||||
undefined ->
|
|
||||||
false;
|
|
||||||
AuthOpts ->
|
|
||||||
{password_format, scram} == lists:keyfind(password_format, 1, AuthOpts)
|
|
||||||
end.
|
|
||||||
|
|
||||||
iterations() -> ?SCRAM_DEFAULT_ITERATION_COUNT.
|
|
||||||
|
|
||||||
iterations(Host) ->
|
|
||||||
case ejabberd_config:get_option({auth_opts, Host}) of
|
|
||||||
undefined ->
|
|
||||||
iterations();
|
|
||||||
AuthOpts ->
|
|
||||||
case lists:keyfind(scram_iterations, 1, AuthOpts) of
|
|
||||||
false -> iterations();
|
|
||||||
{_, Iterations} -> Iterations
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
password_to_scram(Password) ->
|
|
||||||
password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
|
|
||||||
|
|
||||||
password_to_scram(#scram{} = Password, _) ->
|
|
||||||
Password;
|
|
||||||
password_to_scram(Password, IterationCount) ->
|
|
||||||
Salt = p1_rand:bytes(?SALT_LENGTH),
|
|
||||||
SaltedPassword = salted_password(Password, Salt, IterationCount),
|
|
||||||
StoredKey = stored_key(scram2:client_key(SaltedPassword)),
|
|
||||||
ServerKey = server_key(SaltedPassword),
|
|
||||||
#scram{storedkey = base64:encode(StoredKey),
|
|
||||||
serverkey = base64:encode(ServerKey),
|
|
||||||
salt = base64:encode(Salt),
|
|
||||||
iterationcount = IterationCount}.
|
|
||||||
|
|
||||||
check_password(Password, Scram) ->
|
|
||||||
IterationCount = Scram#scram.iterationcount,
|
|
||||||
Salt = base64:decode(Scram#scram.salt),
|
|
||||||
SaltedPassword = salted_password(Password, Salt, IterationCount),
|
|
||||||
StoredKey = stored_key(client_key(SaltedPassword)),
|
|
||||||
(base64:decode(Scram#scram.storedkey) == StoredKey).
|
|
||||||
|
|
||||||
serialize(#scram{storedkey = StoredKey, serverkey = ServerKey,
|
|
||||||
salt = Salt, iterationcount = IterationCount})->
|
|
||||||
IterationCountBin = integer_to_binary(IterationCount),
|
|
||||||
<< <<?SCRAM_SERIAL_PREFIX>>/binary,
|
|
||||||
StoredKey/binary,$,,ServerKey/binary,
|
|
||||||
$,,Salt/binary,$,,IterationCountBin/binary>>.
|
|
||||||
|
|
||||||
deserialize(<<?SCRAM_SERIAL_PREFIX, Serialized/binary>>) ->
|
|
||||||
case catch binary:split(Serialized, <<",">>, [global]) of
|
|
||||||
[StoredKey, ServerKey,Salt,IterationCount] ->
|
|
||||||
{ok, #scram{storedkey = StoredKey,
|
|
||||||
serverkey = ServerKey,
|
|
||||||
salt = Salt,
|
|
||||||
iterationcount = binary_to_integer(IterationCount)}};
|
|
||||||
_ ->
|
|
||||||
?WARNING_MSG("Incorrect serialized SCRAM: ~p, ~p", [Serialized]),
|
|
||||||
{error, incorrect_scram}
|
|
||||||
end;
|
|
||||||
deserialize(Bin) ->
|
|
||||||
?WARNING_MSG("Corrupted serialized SCRAM: ~p, ~p", [Bin]),
|
|
||||||
{error, corrupted_scram}.
|
|
||||||
|
|
||||||
-spec scram_to_tuple(scram()) -> {binary(), binary(), binary(), non_neg_integer()}.
|
|
||||||
scram_to_tuple(Scram) ->
|
|
||||||
{base64:decode(Scram#scram.storedkey),
|
|
||||||
base64:decode(Scram#scram.serverkey),
|
|
||||||
base64:decode(Scram#scram.salt),
|
|
||||||
Scram#scram.iterationcount}.
|
|
||||||
|
|
||||||
-spec check_digest(scram(), binary(), fun(), binary()) -> boolean().
|
|
||||||
check_digest(#scram{storedkey = StoredKey}, Digest, DigestGen, Password) ->
|
|
||||||
Passwd = base64:decode(StoredKey),
|
|
||||||
DigRes = if Digest /= <<"">> ->
|
|
||||||
Digest == DigestGen(Passwd);
|
|
||||||
true -> false
|
|
||||||
end,
|
|
||||||
if DigRes -> true;
|
|
||||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
|
||||||
end.
|
|
||||||
|
|
||||||
crypto_hmac(sha, Key, Data) ->
|
|
||||||
misc:crypto_hmac(sha, Key, Data).
|
|
|
@ -1,41 +0,0 @@
|
||||||
ejabberd observer_cli plugins
|
|
||||||
=============================
|
|
||||||
|
|
||||||
[observer_cli][oc] is an erlang tool to
|
|
||||||
visualize Erlang node statistics on the command line.
|
|
||||||
|
|
||||||
This directory contains several plugins
|
|
||||||
specifically written to view statistics of [ejabberd][ej].
|
|
||||||
|
|
||||||
To install this, just run:
|
|
||||||
```bash
|
|
||||||
ejabberdctl module_install ejabberd_observer_cli
|
|
||||||
```
|
|
||||||
It will automatically download, compile and install the required dependencies:
|
|
||||||
[observer_cli][oc], [recon][recon],
|
|
||||||
and the additional plugin [os_stats][os].
|
|
||||||
|
|
||||||
Then, start an erlang shell attached to your ejabberd node, for example:
|
|
||||||
```bash
|
|
||||||
ejabberdctl debug
|
|
||||||
```
|
|
||||||
in that erlang shell execute:
|
|
||||||
```erlang
|
|
||||||
ejabberd_observer_cli:start().
|
|
||||||
```
|
|
||||||
|
|
||||||
If using Elixir (for example when started with `ejabberdctl iexdebug`, run:
|
|
||||||
```elixir
|
|
||||||
:ejabberd_observer_cli.start()
|
|
||||||
```
|
|
||||||
|
|
||||||
To sort columns or change between the different pages,
|
|
||||||
type the corresponding letter and hit Enter.
|
|
||||||
For example, to view MUC statistics, type: `M and then Enter`.
|
|
||||||
|
|
||||||
There is no configuration required.
|
|
||||||
|
|
||||||
[oc]: https://github.com/zhongwencool/observer_cli
|
|
||||||
[os]: https://github.com/zhongwencool/os_stats
|
|
||||||
[recon]: https://github.com/ferd/recon
|
|
||||||
[ej]: https://www.ejabberd.im/
|
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Badlop <badlop at process-one.net>"
|
|
||||||
category: "stats"
|
|
||||||
summary: "Observer CLI plugins for ejabberd"
|
|
||||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
|
||||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
|
@ -1,5 +0,0 @@
|
||||||
{deps, [
|
|
||||||
{observer_cli, ".*", {git, "https://github.com/zhongwencool/observer_cli"}},
|
|
||||||
{os_stats, ".*", {git, "https://github.com/zhongwencool/os_stats"}},
|
|
||||||
{recon, ".*", {git, "https://github.com/ferd/recon"}}
|
|
||||||
]}.
|
|
|
@ -1,44 +0,0 @@
|
||||||
-module(ejabberd_observer_cli).
|
|
||||||
|
|
||||||
-export([start/0, mod_status/0]).
|
|
||||||
|
|
||||||
start() ->
|
|
||||||
application:set_env(observer_cli, plugins, plugins(), [{persistent, true}]),
|
|
||||||
observer_cli:start_plugin().
|
|
||||||
|
|
||||||
mod_status() ->
|
|
||||||
"In an erlang shell run: ejabberd_observer_cli:start().".
|
|
||||||
|
|
||||||
plugins() ->
|
|
||||||
[
|
|
||||||
#{
|
|
||||||
module => ejabberd_observer_cli_vhosts,
|
|
||||||
title => "VHosts",
|
|
||||||
interval => 1600,
|
|
||||||
shortcut => "V",
|
|
||||||
sort_column => 2
|
|
||||||
},
|
|
||||||
#{
|
|
||||||
module => ejabberd_observer_cli_users,
|
|
||||||
title => "Users",
|
|
||||||
interval => 1600,
|
|
||||||
shortcut => "U",
|
|
||||||
sort_column => 2
|
|
||||||
},
|
|
||||||
%% #{module => ejabberd_observer_cli_userstophost, title => "Users Top Vhost",
|
|
||||||
%% interval => 1600, shortcut => "T", sort_column => 2},
|
|
||||||
#{
|
|
||||||
module => ejabberd_observer_cli_muc,
|
|
||||||
title => "MUC",
|
|
||||||
interval => 1600,
|
|
||||||
shortcut => "M",
|
|
||||||
sort_column => 2
|
|
||||||
},
|
|
||||||
#{
|
|
||||||
module => os_stats_plug,
|
|
||||||
title => "OS",
|
|
||||||
interval => 2000,
|
|
||||||
shortcut => "O",
|
|
||||||
sort_column => 2
|
|
||||||
}
|
|
||||||
].
|
|
|
@ -1,48 +0,0 @@
|
||||||
-module(ejabberd_observer_cli_muc).
|
|
||||||
|
|
||||||
%% observer_cli_plugin Callback API
|
|
||||||
-export([attributes/1, sheet_header/0, sheet_body/1]).
|
|
||||||
|
|
||||||
attributes(PrevState) ->
|
|
||||||
OnlineRoomsNumber = lists:foldl(
|
|
||||||
fun(Host, Acc) ->
|
|
||||||
Acc + mod_muc:count_online_rooms(Host)
|
|
||||||
end,
|
|
||||||
0,
|
|
||||||
mod_muc_admin:find_hosts(global)
|
|
||||||
),
|
|
||||||
|
|
||||||
Attrs = [
|
|
||||||
[
|
|
||||||
#{content => "MUC Rooms", width => 25},
|
|
||||||
#{content => OnlineRoomsNumber, width => 8}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
NewState = PrevState,
|
|
||||||
{Attrs, NewState}.
|
|
||||||
|
|
||||||
sheet_header() ->
|
|
||||||
[
|
|
||||||
#{title => "Room Name", width => 20, shortcut => "n"},
|
|
||||||
#{title => "MUC Service", width => 30, shortcut => "s"},
|
|
||||||
#{title => "Occupants", width => 12, shortcut => "o"},
|
|
||||||
#{title => "Subscribers", width => 13, shortcut => "r"}
|
|
||||||
].
|
|
||||||
|
|
||||||
sheet_body(PrevState) ->
|
|
||||||
Body = [
|
|
||||||
begin
|
|
||||||
{Name, Service, _} = jid:split(jid:decode(RoomStr)),
|
|
||||||
OccupantsNumber = mod_muc_admin:get_room_occupants_number(Name, Service),
|
|
||||||
SubsNumber = length(mod_muc_admin:get_subscribers(Name, Service)),
|
|
||||||
[
|
|
||||||
Name,
|
|
||||||
Service,
|
|
||||||
OccupantsNumber,
|
|
||||||
SubsNumber
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|| RoomStr <- mod_muc_admin:muc_online_rooms(global)
|
|
||||||
],
|
|
||||||
NewState = PrevState,
|
|
||||||
{Body, NewState}.
|
|
|
@ -1,95 +0,0 @@
|
||||||
-module(ejabberd_observer_cli_users).
|
|
||||||
|
|
||||||
%% observer_cli_plugin Callback API
|
|
||||||
-export([attributes/1, sheet_header/0, sheet_body/1]).
|
|
||||||
|
|
||||||
attributes(PrevState) ->
|
|
||||||
Hosts = length(ejabberd_option:hosts()),
|
|
||||||
RegisteredUsers =
|
|
||||||
lists:foldl(
|
|
||||||
fun(Host, Sum) ->
|
|
||||||
ejabberd_auth:count_users(Host) + Sum
|
|
||||||
end,
|
|
||||||
0,
|
|
||||||
ejabberd_option:hosts()
|
|
||||||
),
|
|
||||||
Sessions = length(ejabberd_sm:dirty_get_sessions_list()),
|
|
||||||
SessionsThisNode = length(ejabberd_sm:dirty_get_my_sessions_list()),
|
|
||||||
|
|
||||||
Attrs = [
|
|
||||||
[
|
|
||||||
#{content => "Virtual Hosts", width => 18},
|
|
||||||
#{content => Hosts, width => 8},
|
|
||||||
#{content => "Sessions Total", width => 18},
|
|
||||||
#{content => Sessions, width => 8}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
#{content => "Accounts Total", width => 18},
|
|
||||||
#{content => RegisteredUsers, width => 8},
|
|
||||||
#{content => "Sessions This Node", width => 18},
|
|
||||||
#{content => SessionsThisNode, width => 8}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
NewState = PrevState,
|
|
||||||
{Attrs, NewState}.
|
|
||||||
|
|
||||||
sheet_header() ->
|
|
||||||
[
|
|
||||||
#{title => "Username", width => 25, shortcut => "u"},
|
|
||||||
#{title => "Host", width => 25, shortcut => "h"},
|
|
||||||
#{title => "Sessions", width => 11, shortcut => "s"},
|
|
||||||
#{title => "Roster", width => 9, shortcut => "r"},
|
|
||||||
#{title => "Offline", width => 10, shortcut => "o"},
|
|
||||||
#{title => "Last Activity", width => 20, shortcut => "l"}
|
|
||||||
].
|
|
||||||
|
|
||||||
sheet_body(PrevState) ->
|
|
||||||
Body = [
|
|
||||||
begin
|
|
||||||
[
|
|
||||||
Username,
|
|
||||||
Host,
|
|
||||||
length(ejabberd_sm:get_user_resources(Username, Host)),
|
|
||||||
length(mod_roster:get_roster(Username, Host)),
|
|
||||||
mod_offline:count_offline_messages(Username, Host),
|
|
||||||
get_last_activity(Username, Host)
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|| {Username, Host} <- lists:sort(ejabberd_auth:get_users())
|
|
||||||
],
|
|
||||||
NewState = PrevState,
|
|
||||||
{Body, NewState}.
|
|
||||||
|
|
||||||
%% Code copied from ejabberd_web_admin.erl
|
|
||||||
get_last_activity(User, Server) ->
|
|
||||||
case ejabberd_sm:get_user_resources(User, Server) of
|
|
||||||
[] ->
|
|
||||||
case get_last_info(User, Server) of
|
|
||||||
not_found ->
|
|
||||||
"Never";
|
|
||||||
{ok, Shift, _Status} ->
|
|
||||||
TimeStamp = {Shift div 1000000, Shift rem 1000000, 0},
|
|
||||||
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
|
||||||
calendar:now_to_local_time(TimeStamp),
|
|
||||||
(io_lib:format(
|
|
||||||
"~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
|
||||||
[
|
|
||||||
Year,
|
|
||||||
Month,
|
|
||||||
Day,
|
|
||||||
Hour,
|
|
||||||
Minute,
|
|
||||||
Second
|
|
||||||
]
|
|
||||||
))
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
"Online"
|
|
||||||
end.
|
|
||||||
get_last_info(User, Server) ->
|
|
||||||
case gen_mod:is_loaded(Server, mod_last) of
|
|
||||||
true ->
|
|
||||||
mod_last:get_last_info(User, Server);
|
|
||||||
false ->
|
|
||||||
not_found
|
|
||||||
end.
|
|
|
@ -1,61 +0,0 @@
|
||||||
-module(ejabberd_observer_cli_userstophost).
|
|
||||||
|
|
||||||
%% observer_cli_plugin Callback API
|
|
||||||
-export([attributes/1, sheet_header/0, sheet_body/1]).
|
|
||||||
|
|
||||||
get_top_host() ->
|
|
||||||
lists:foldl(
|
|
||||||
fun(Host, {HostRelativeMax, CountRelativeMax}) ->
|
|
||||||
case ejabberd_auth:count_users(Host) of
|
|
||||||
Count when Count > CountRelativeMax ->
|
|
||||||
{Host, Count};
|
|
||||||
_ ->
|
|
||||||
{HostRelativeMax, CountRelativeMax}
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
{unknown, -1},
|
|
||||||
ejabberd_option:hosts()
|
|
||||||
).
|
|
||||||
|
|
||||||
attributes(PrevState) ->
|
|
||||||
{Host, _} = get_top_host(),
|
|
||||||
RegisteredUsers = ejabberd_auth:count_users(Host),
|
|
||||||
Sessions = length(ejabberd_sm:dirty_get_sessions_list()),
|
|
||||||
SessionsThisNode = length(ejabberd_sm:dirty_get_my_sessions_list()),
|
|
||||||
|
|
||||||
Attrs = [
|
|
||||||
[
|
|
||||||
#{content => "Virtual Host", width => 12},
|
|
||||||
#{content => Host, width => 14},
|
|
||||||
#{content => "Sessions Total", width => 18},
|
|
||||||
#{content => Sessions, width => 8}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
#{content => "Accounts Total", width => 12},
|
|
||||||
#{content => RegisteredUsers, width => 14},
|
|
||||||
#{content => "Sessions This Node", width => 18},
|
|
||||||
#{content => SessionsThisNode, width => 8}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
NewState = PrevState,
|
|
||||||
{Attrs, NewState}.
|
|
||||||
|
|
||||||
sheet_header() ->
|
|
||||||
[
|
|
||||||
#{title => "Username", width => 25, shortcut => "u"},
|
|
||||||
#{title => "Resources", width => 15, shortcut => "r"}
|
|
||||||
].
|
|
||||||
|
|
||||||
sheet_body(PrevState) ->
|
|
||||||
{Host, _} = get_top_host(),
|
|
||||||
Body = [
|
|
||||||
begin
|
|
||||||
[
|
|
||||||
Username,
|
|
||||||
length(ejabberd_sm:get_user_resources(Username, Host))
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|| {Username, _} <- lists:reverse(lists:sort(ejabberd_auth:get_users(Host)))
|
|
||||||
],
|
|
||||||
NewState = PrevState,
|
|
||||||
{Body, NewState}.
|
|
|
@ -1,78 +0,0 @@
|
||||||
-module(ejabberd_observer_cli_vhosts).
|
|
||||||
|
|
||||||
%% observer_cli_plugin Callback API
|
|
||||||
-export([attributes/1, sheet_header/0, sheet_body/1]).
|
|
||||||
|
|
||||||
attributes(PrevState) ->
|
|
||||||
Hosts = length(ejabberd_option:hosts()),
|
|
||||||
RegisteredUsers =
|
|
||||||
lists:foldl(
|
|
||||||
fun(Host, Sum) ->
|
|
||||||
ejabberd_auth:count_users(Host) + Sum
|
|
||||||
end,
|
|
||||||
0,
|
|
||||||
ejabberd_option:hosts()
|
|
||||||
),
|
|
||||||
Sessions = length(ejabberd_sm:dirty_get_sessions_list()),
|
|
||||||
SessionsThisNode = length(ejabberd_sm:dirty_get_my_sessions_list()),
|
|
||||||
|
|
||||||
OnlineRoomsNumber = lists:foldl(
|
|
||||||
fun(Host, Acc) ->
|
|
||||||
Acc + mod_muc:count_online_rooms(Host)
|
|
||||||
end,
|
|
||||||
0,
|
|
||||||
mod_muc_admin:find_hosts(global)
|
|
||||||
),
|
|
||||||
|
|
||||||
Attrs = [
|
|
||||||
[
|
|
||||||
#{content => "Virtual Hosts", width => 25},
|
|
||||||
#{content => Hosts, width => 8},
|
|
||||||
#{content => "Sessions Total", width => 25},
|
|
||||||
#{content => Sessions, width => 8}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
#{content => "Accounts Total", width => 25},
|
|
||||||
#{content => RegisteredUsers, width => 8},
|
|
||||||
#{content => "Sessions This Node", width => 25},
|
|
||||||
#{content => SessionsThisNode, width => 8}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
#{content => "MUC Rooms", width => 25},
|
|
||||||
#{content => OnlineRoomsNumber, width => 8}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
NewState = PrevState,
|
|
||||||
{Attrs, NewState}.
|
|
||||||
|
|
||||||
sheet_header() ->
|
|
||||||
[
|
|
||||||
#{title => "Virtual Host", width => 38, shortcut => "v"},
|
|
||||||
#{title => "Accounts", width => 11, shortcut => "a"},
|
|
||||||
#{title => "Sessions", width => 11, shortcut => "s"},
|
|
||||||
#{title => "Rooms", width => 8, shortcut => "r"}
|
|
||||||
].
|
|
||||||
|
|
||||||
sheet_body(PrevState) ->
|
|
||||||
Body = [
|
|
||||||
begin
|
|
||||||
RegisteredUsers = ejabberd_auth:count_users(Host),
|
|
||||||
Sessions = ejabberd_sm:get_vh_session_number(Host),
|
|
||||||
OnlineRoomsNumber = lists:foldl(
|
|
||||||
fun(Host1, Acc) ->
|
|
||||||
Acc + mod_muc:count_online_rooms(Host1)
|
|
||||||
end,
|
|
||||||
0,
|
|
||||||
mod_muc_admin:find_hosts(Host)
|
|
||||||
),
|
|
||||||
[
|
|
||||||
Host,
|
|
||||||
RegisteredUsers,
|
|
||||||
Sessions,
|
|
||||||
OnlineRoomsNumber
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|| Host <- lists:reverse(lists:sort(ejabberd_option:hosts()))
|
|
||||||
],
|
|
||||||
NewState = PrevState,
|
|
||||||
{Body, NewState}.
|
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Gregor Uhlenheuer <kongo2002 at gmail.com>"
|
|
||||||
category: "archive"
|
|
||||||
summary: "Message Archive Management (XEP-0313)"
|
|
||||||
home: "https://github.com/kongo2002/ejabberd-mod-mam/tree/master/"
|
|
||||||
url: "git@github.com:kongo2002/ejabberd-mod-mam.git"
|
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Lavrin"
|
|
||||||
category: "admin"
|
|
||||||
summary: "Easy tracing of connections made to ejabberd"
|
|
||||||
home: "https://github.com/lavrin/ejabberd-trace/tree/master/"
|
|
||||||
url: "git@github.com:lavrin/ejabberd-trace.git"
|
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Rael Max"
|
|
||||||
category: "http"
|
|
||||||
summary: "POST offline messages to a web"
|
|
||||||
home: "https://github.com/raelmax/mod_http_offline/tree/master/"
|
|
||||||
url: "git@github.com:raelmax/mod_http_offline.git"
|
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Jonas Ã…dahl <jadahl at gmail.com>"
|
|
||||||
category: "http"
|
|
||||||
summary: "RESTful API for ejabberd"
|
|
||||||
home: "https://github.com/jadahl/mod_restful/tree/master/"
|
|
||||||
url: "git@github.com:jadahl/mod_restful.git"
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
MODULES=`pwd`
|
||||||
|
|
||||||
|
# Put here the path to ejabberd source
|
||||||
|
EJADIR=$MODULES/../git/ejabberd/
|
||||||
|
|
||||||
|
# TODO: Support extraction of multiple modules
|
||||||
|
#MODULE=mod_webpresence
|
||||||
|
MODULE=mod_register_web
|
||||||
|
|
||||||
|
RUNDIR=$MODULES/$MODULE/trunk/
|
||||||
|
PREPARESCRIPT=$EJADIR/contrib/extract_translations/prepare-translation.sh
|
||||||
|
|
||||||
|
# 1. Create the directory $MODULE/msgs/
|
||||||
|
|
||||||
|
# 2. Create the $MODULE.pot
|
||||||
|
#$PREPARESCRIPT -rundir $RUNDIR -ejadir $EJADIR -project $MODULE -src2pot
|
||||||
|
|
||||||
|
# 3. Create a language
|
||||||
|
# cp $MODULE.pot $LANG.$MODULE.po
|
||||||
|
# echo "" > $LANG.$MODULE.msg
|
||||||
|
|
||||||
|
# 3.b Convert msg to po. But it doesn't work! :(
|
||||||
|
#$PREPARESCRIPT -rundir $RUNDIR -ejadir $EJADIR -project $MODULE -srcmsg2po we
|
||||||
|
|
||||||
|
# 4. Update strings
|
||||||
|
$PREPARESCRIPT -rundir $RUNDIR -ejadir $EJADIR -project $MODULE -updateall
|
|
@ -0,0 +1 @@
|
||||||
|
{'src/ejabberd_ircd', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -1,15 +1,4 @@
|
||||||
|
|
||||||
|
|
||||||
***************
|
|
||||||
PLEASE NOTE
|
|
||||||
***************
|
|
||||||
|
|
||||||
This module does NOT work
|
|
||||||
with ejabberd 13 or newer.
|
|
||||||
|
|
||||||
***************
|
|
||||||
|
|
||||||
|
|
||||||
ircd - IRC-to-XMPP interface
|
ircd - IRC-to-XMPP interface
|
||||||
|
|
||||||
Author:
|
Author:
|
||||||
|
@ -20,6 +9,7 @@
|
||||||
http://www.dtek.chalmers.se/~henoch/text/ejabberd-ircd.html
|
http://www.dtek.chalmers.se/~henoch/text/ejabberd-ircd.html
|
||||||
Requirements:
|
Requirements:
|
||||||
ejabberd trunk SVN 1631 or newer
|
ejabberd trunk SVN 1631 or newer
|
||||||
|
Does NOT work with ejabberd 13 or newer
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Magnus Henoch <henoch at dtek.chalmers.se>"
|
|
||||||
category: "listener"
|
|
||||||
summary: "IRC server frontend to ejabberd"
|
|
||||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
|
||||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
|
@ -84,7 +84,7 @@ socket_type() ->
|
||||||
%% {stop, StopReason}
|
%% {stop, StopReason}
|
||||||
%%----------------------------------------------------------------------
|
%%----------------------------------------------------------------------
|
||||||
init([{SockMod, Socket}, Opts]) ->
|
init([{SockMod, Socket}, Opts]) ->
|
||||||
%iconv:start(),
|
iconv:start(),
|
||||||
Access = case lists:keysearch(access, 1, Opts) of
|
Access = case lists:keysearch(access, 1, Opts) of
|
||||||
{value, {_, A}} -> A;
|
{value, {_, A}} -> A;
|
||||||
_ -> all
|
_ -> all
|
||||||
|
@ -134,8 +134,7 @@ init([{SockMod, Socket}, Opts]) ->
|
||||||
}}.
|
}}.
|
||||||
|
|
||||||
handle_info({tcp, _Socket, Line}, StateName, StateData) ->
|
handle_info({tcp, _Socket, Line}, StateName, StateData) ->
|
||||||
%DecodedLine = iconv:convert(StateData#state.encoding, "utf-8", Line),
|
DecodedLine = iconv:convert(StateData#state.encoding, "utf-8", Line),
|
||||||
DecodedLine = Line,
|
|
||||||
Parsed = parse_line(DecodedLine),
|
Parsed = parse_line(DecodedLine),
|
||||||
?MODULE:StateName({line, Parsed}, StateData);
|
?MODULE:StateName({line, Parsed}, StateData);
|
||||||
handle_info({tcp_closed, _}, _StateName, StateData) ->
|
handle_info({tcp_closed, _}, _StateName, StateData) ->
|
||||||
|
@ -164,8 +163,8 @@ terminate(_Reason, _StateName, #state{socket = Socket, sockmod = SockMod,
|
||||||
none ->
|
none ->
|
||||||
ok;
|
ok;
|
||||||
_ ->
|
_ ->
|
||||||
Packet = {xmlel, <<"presence">>,
|
Packet = {xmlelement, "presence",
|
||||||
[{<<"type">>, <<"unavailable">>}], []},
|
[{"type", "unavailable"}], []},
|
||||||
FromJID = user_jid(State),
|
FromJID = user_jid(State),
|
||||||
?DICT:map(fun(ChannelJID, _ChannelData) ->
|
?DICT:map(fun(ChannelJID, _ChannelData) ->
|
||||||
ejabberd_router:route(FromJID, ChannelJID, Packet)
|
ejabberd_router:route(FromJID, ChannelJID, Packet)
|
||||||
|
@ -187,10 +186,8 @@ wait_for_nick({line, #line{command = "NICK", params = Params}}, State) ->
|
||||||
Nick = hd(Params),
|
Nick = hd(Params),
|
||||||
Pass = State#state.pass,
|
Pass = State#state.pass,
|
||||||
Server = State#state.host,
|
Server = State#state.host,
|
||||||
?DEBUG("user=~p server=~p", [Nick, Server]),
|
|
||||||
|
|
||||||
JID = jlib:make_jid(list_to_binary(Nick), Server, <<"irc">>),
|
JID = jlib:make_jid(Nick, Server, "irc"),
|
||||||
?DEBUG("JID=~p", [JID]),
|
|
||||||
case JID of
|
case JID of
|
||||||
error ->
|
error ->
|
||||||
?DEBUG("invalid nick '~p'", [Nick]),
|
?DEBUG("invalid nick '~p'", [Nick]),
|
||||||
|
@ -203,7 +200,7 @@ wait_for_nick({line, #line{command = "NICK", params = Params}}, State) ->
|
||||||
send_reply('ERR_NICKCOLLISION', [Nick, "Nickname collision"], State),
|
send_reply('ERR_NICKCOLLISION', [Nick, "Nickname collision"], State),
|
||||||
{next_state, wait_for_nick, State};
|
{next_state, wait_for_nick, State};
|
||||||
allow ->
|
allow ->
|
||||||
case ejabberd_auth:check_password(list_to_binary(Nick), Server, list_to_binary(Pass)) of
|
case ejabberd_auth:check_password(Nick, Server, Pass) of
|
||||||
false ->
|
false ->
|
||||||
?DEBUG("auth failed for '~p'", [Nick]),
|
?DEBUG("auth failed for '~p'", [Nick]),
|
||||||
send_reply('ERR_NICKCOLLISION', [Nick, "Authentication failed"], State),
|
send_reply('ERR_NICKCOLLISION', [Nick, "Authentication failed"], State),
|
||||||
|
@ -212,15 +209,15 @@ wait_for_nick({line, #line{command = "NICK", params = Params}}, State) ->
|
||||||
?DEBUG("good nickname '~p'", [Nick]),
|
?DEBUG("good nickname '~p'", [Nick]),
|
||||||
SID = {now(), self()},
|
SID = {now(), self()},
|
||||||
ejabberd_sm:open_session(
|
ejabberd_sm:open_session(
|
||||||
SID, list_to_binary(Nick), Server, <<"irc">>, peerip(gen_tcp, State#state.socket)),
|
SID, Nick, Server, "irc", peerip(gen_tcp, State#state.socket)),
|
||||||
ejabberd_sm:set_presence(SID, list_to_binary(Nick), Server, <<"irc">>,
|
ejabberd_sm:set_presence(SID, Nick, Server, "irc",
|
||||||
3, "undefined",
|
3, "undefined",
|
||||||
[{'ip', peerip(gen_tcp, State#state.socket)}, {'conn','c2s'}, {'state',"+"}]),
|
[{'ip', peerip(gen_tcp, State#state.socket)}, {'conn','c2s'}, {'state',"+"}]),
|
||||||
send_text_command("", "001", [Nick, "IRC interface of ejabberd server "++Server], State),
|
send_text_command("", "001", [Nick, "IRC interface of ejabberd server "++Server], State),
|
||||||
send_reply('RPL_MOTDSTART', [Nick, "- "++binary_to_list(Server)++" Message of the day - "], State),
|
send_reply('RPL_MOTDSTART', [Nick, "- "++Server++" Message of the day - "], State),
|
||||||
send_reply('RPL_MOTD', [Nick, "- This is the IRC interface of the ejabberd server "++binary_to_list(Server)++"."], State),
|
send_reply('RPL_MOTD', [Nick, "- This is the IRC interface of the ejabberd server "++Server++"."], State),
|
||||||
send_reply('RPL_MOTD', [Nick, "- Your full JID is "++Nick++"@"++binary_to_list(Server)++"/irc."], State),
|
send_reply('RPL_MOTD', [Nick, "- Your full JID is "++Nick++"@"++Server++"/irc."], State),
|
||||||
send_reply('RPL_MOTD', [Nick, "- Channel #whatever corresponds to MUC room whatever@"++binary_to_list(State#state.muc_host)++"."], State),
|
send_reply('RPL_MOTD', [Nick, "- Channel #whatever corresponds to MUC room whatever@"++State#state.muc_host++"."], State),
|
||||||
send_reply('RPL_MOTD', [Nick, "- This IRC interface is quite immature. You will probably find bugs."], State),
|
send_reply('RPL_MOTD', [Nick, "- This IRC interface is quite immature. You will probably find bugs."], State),
|
||||||
send_reply('RPL_MOTD', [Nick, "- Have a good time!"], State),
|
send_reply('RPL_MOTD', [Nick, "- Have a good time!"], State),
|
||||||
send_reply('RPL_ENDOFMOTD', [Nick, "End of /MOTD command"], State),
|
send_reply('RPL_ENDOFMOTD', [Nick, "End of /MOTD command"], State),
|
||||||
|
@ -247,7 +244,6 @@ wait_for_cmd({line, #line{command = "USER", params = [_Username, _Hostname, _Ser
|
||||||
%% Yeah, like we care.
|
%% Yeah, like we care.
|
||||||
{next_state, wait_for_cmd, State};
|
{next_state, wait_for_cmd, State};
|
||||||
wait_for_cmd({line, #line{command = "JOIN", params = Params}}, State) ->
|
wait_for_cmd({line, #line{command = "JOIN", params = Params}}, State) ->
|
||||||
?DEBUG("received JOIN ~p", [Params]),
|
|
||||||
{ChannelsString, KeysString} =
|
{ChannelsString, KeysString} =
|
||||||
case Params of
|
case Params of
|
||||||
[C, K] ->
|
[C, K] ->
|
||||||
|
@ -257,9 +253,7 @@ wait_for_cmd({line, #line{command = "JOIN", params = Params}}, State) ->
|
||||||
end,
|
end,
|
||||||
Channels = string:tokens(ChannelsString, ","),
|
Channels = string:tokens(ChannelsString, ","),
|
||||||
Keys = string:tokens(KeysString, ","),
|
Keys = string:tokens(KeysString, ","),
|
||||||
?DEBUG("joining channels ~p", [Channels]),
|
|
||||||
NewState = join_channels(Channels, Keys, State),
|
NewState = join_channels(Channels, Keys, State),
|
||||||
?DEBUG("joined channels ~p", [Channels]),
|
|
||||||
{next_state, wait_for_cmd, NewState};
|
{next_state, wait_for_cmd, NewState};
|
||||||
|
|
||||||
%% USERHOST command
|
%% USERHOST command
|
||||||
|
@ -300,18 +294,18 @@ wait_for_cmd({line, #line{command = "PRIVMSG", params = [To, Text]}}, State) ->
|
||||||
fun(Rcpt) ->
|
fun(Rcpt) ->
|
||||||
case Rcpt of
|
case Rcpt of
|
||||||
[$# | Roomname] ->
|
[$# | Roomname] ->
|
||||||
Packet = {xmlel, <<"message">>,
|
Packet = {xmlelement, "message",
|
||||||
[{<<"type">>, <<"groupchat">>}],
|
[{"type", "groupchat"}],
|
||||||
[{xmlel, <<"body">>, [],
|
[{xmlelement, "body", [],
|
||||||
filter_cdata(translate_action(Text))}]},
|
filter_cdata(translate_action(Text))}]},
|
||||||
ToJID = channel_to_jid(Roomname, State),
|
ToJID = channel_to_jid(Roomname, State),
|
||||||
ejabberd_router:route(FromJID, ToJID, Packet);
|
ejabberd_router:route(FromJID, ToJID, Packet);
|
||||||
_ ->
|
_ ->
|
||||||
case string:tokens(Rcpt, "#") of
|
case string:tokens(Rcpt, "#") of
|
||||||
[Nick, Channel] ->
|
[Nick, Channel] ->
|
||||||
Packet = {xmlel, <<"message">>,
|
Packet = {xmlelement, "message",
|
||||||
[{<<"type">>, <<"chat">>}],
|
[{"type", "chat"}],
|
||||||
[{xmlel, <<"body">>, [],
|
[{xmlelement, "body", [],
|
||||||
filter_cdata(translate_action(Text))}]},
|
filter_cdata(translate_action(Text))}]},
|
||||||
ToJID = channel_nick_to_jid(Nick, Channel, State),
|
ToJID = channel_nick_to_jid(Nick, Channel, State),
|
||||||
ejabberd_router:route(FromJID, ToJID, Packet);
|
ejabberd_router:route(FromJID, ToJID, Packet);
|
||||||
|
@ -363,9 +357,9 @@ wait_for_cmd({line, #line{command = "TOPIC", params = Params}}, State) ->
|
||||||
end;
|
end;
|
||||||
[Channel, NewTopic] ->
|
[Channel, NewTopic] ->
|
||||||
Packet =
|
Packet =
|
||||||
{xmlel, <<"message">>,
|
{xmlelement, "message",
|
||||||
[{<<"type">>, <<"groupchat">>}],
|
[{"type", "groupchat"}],
|
||||||
[{xmlel, <<"subject">>, [], filter_cdata(NewTopic)}]},
|
[{xmlelement, "subject", [], filter_cdata(NewTopic)}]},
|
||||||
FromJID = user_jid(State),
|
FromJID = user_jid(State),
|
||||||
ToJID = channel_to_jid(Channel, State),
|
ToJID = channel_to_jid(Channel, State),
|
||||||
ejabberd_router:route(FromJID, ToJID, Packet)
|
ejabberd_router:route(FromJID, ToJID, Packet)
|
||||||
|
@ -417,11 +411,10 @@ wait_for_cmd({line, #line{command = Unknown, params = Params} = Line}, State) ->
|
||||||
Unknown ++ "/" ++ integer_to_list(length(Params))], State),
|
Unknown ++ "/" ++ integer_to_list(length(Params))], State),
|
||||||
{next_state, wait_for_cmd, State};
|
{next_state, wait_for_cmd, State};
|
||||||
|
|
||||||
wait_for_cmd({route, From, _To, {xmlel, <<"presence">>, Attrs, Els} = El}, State) ->
|
wait_for_cmd({route, From, _To, {xmlelement, "presence", Attrs, Els} = El}, State) ->
|
||||||
?DEBUG("Received a Presence ~p ~p ~p", [From, _To, El]),
|
|
||||||
Type = xml:get_attr_s("type", Attrs),
|
Type = xml:get_attr_s("type", Attrs),
|
||||||
FromRoom = jlib:jid_remove_resource(From),
|
FromRoom = jlib:jid_remove_resource(From),
|
||||||
FromNick = binary_to_list(From#jid.resource),
|
FromNick = From#jid.resource,
|
||||||
|
|
||||||
Channel = jid_to_channel(From, State),
|
Channel = jid_to_channel(From, State),
|
||||||
MyNick = State#state.nick,
|
MyNick = State#state.nick,
|
||||||
|
@ -429,29 +422,24 @@ wait_for_cmd({route, From, _To, {xmlel, <<"presence">>, Attrs, Els} = El}, State
|
||||||
|
|
||||||
Joining = ?DICT:find(FromRoom, State#state.joining),
|
Joining = ?DICT:find(FromRoom, State#state.joining),
|
||||||
Joined = ?DICT:find(FromRoom, State#state.joined),
|
Joined = ?DICT:find(FromRoom, State#state.joined),
|
||||||
?DEBUG("JoinState ~p ~p ~p", [Joining, Joined, Type]),
|
|
||||||
case {Joining, Joined, Type} of
|
case {Joining, Joined, Type} of
|
||||||
{{ok, BufferedNicks}, _, <<"">>} ->
|
{{ok, BufferedNicks}, _, ""} ->
|
||||||
?DEBUG("BufferedNicks ~p", [BufferedNicks]),
|
|
||||||
case BufferedNicks of
|
case BufferedNicks of
|
||||||
[] ->
|
[] ->
|
||||||
%% If this is the first presence, tell the
|
%% If this is the first presence, tell the
|
||||||
%% client that it's joining.
|
%% client that it's joining.
|
||||||
?DEBUG("Sending Command ~p ~p", [IRCSender, Channel]),
|
send_command(make_irc_sender(MyNick, FromRoom, State),
|
||||||
send_command(IRCSender, "JOIN", [Channel], State),
|
"JOIN", [Channel], State);
|
||||||
?DEBUG("Command Sent", []);
|
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
|
|
||||||
?DEBUG("Getting NewRole", []),
|
|
||||||
NewRole = case find_el("x", ?NS_MUC_USER, Els) of
|
NewRole = case find_el("x", ?NS_MUC_USER, Els) of
|
||||||
nothing ->
|
nothing ->
|
||||||
"";
|
"";
|
||||||
XMucEl ->
|
XMucEl ->
|
||||||
xml:get_path_s(XMucEl, [{elem, "item"}, {attr, "role"}])
|
xml:get_path_s(XMucEl, [{elem, "item"}, {attr, "role"}])
|
||||||
end,
|
end,
|
||||||
?DEBUG("NewRole ~p", [NewRole]),
|
|
||||||
NewBufferedNicks = [{FromNick, NewRole} | BufferedNicks],
|
NewBufferedNicks = [{FromNick, NewRole} | BufferedNicks],
|
||||||
?DEBUG("~s is present in ~s. we now have ~p.",
|
?DEBUG("~s is present in ~s. we now have ~p.",
|
||||||
[FromNick, Channel, NewBufferedNicks]),
|
[FromNick, Channel, NewBufferedNicks]),
|
||||||
|
@ -491,14 +479,14 @@ wait_for_cmd({route, From, _To, {xmlel, <<"presence">>, Attrs, Els} = El}, State
|
||||||
State#state{joining = NewJoining}
|
State#state{joining = NewJoining}
|
||||||
end,
|
end,
|
||||||
{next_state, wait_for_cmd, NewState};
|
{next_state, wait_for_cmd, NewState};
|
||||||
{{ok, _BufferedNicks}, _, <<"error">>} ->
|
{{ok, _BufferedNicks}, _, "error"} ->
|
||||||
NewState =
|
NewState =
|
||||||
case FromNick of
|
case FromNick of
|
||||||
MyNick ->
|
MyNick ->
|
||||||
%% we couldn't join the room
|
%% we couldn't join the room
|
||||||
{ReplyCode, ErrorDescription} =
|
{ReplyCode, ErrorDescription} =
|
||||||
case xml:get_subtag(El, "error") of
|
case xml:get_subtag(El, "error") of
|
||||||
{xmlel, _, _, _} = ErrorEl ->
|
{xmlelement, _, _, _} = ErrorEl ->
|
||||||
{ErrorName, ErrorText} = parse_error(ErrorEl),
|
{ErrorName, ErrorText} = parse_error(ErrorEl),
|
||||||
{case ErrorName of
|
{case ErrorName of
|
||||||
"forbidden" -> 'ERR_INVITEONLYCHAN';
|
"forbidden" -> 'ERR_INVITEONLYCHAN';
|
||||||
|
@ -523,7 +511,7 @@ wait_for_cmd({route, From, _To, {xmlel, <<"presence">>, Attrs, Els} = El}, State
|
||||||
end,
|
end,
|
||||||
{next_state, wait_for_cmd, NewState};
|
{next_state, wait_for_cmd, NewState};
|
||||||
%% Presence in a channel we have already joined
|
%% Presence in a channel we have already joined
|
||||||
{_, {ok, _}, <<"">>} ->
|
{_, {ok, _}, ""} ->
|
||||||
%% Someone enters
|
%% Someone enters
|
||||||
send_command(IRCSender, "JOIN", [Channel], State),
|
send_command(IRCSender, "JOIN", [Channel], State),
|
||||||
{next_state, wait_for_cmd, State};
|
{next_state, wait_for_cmd, State};
|
||||||
|
@ -536,37 +524,29 @@ wait_for_cmd({route, From, _To, {xmlel, <<"presence">>, Attrs, Els} = El}, State
|
||||||
{next_state, wait_for_cmd, State}
|
{next_state, wait_for_cmd, State}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
wait_for_cmd({route, From, _To, {xmlel, <<"message">>, Attrs, Els} = El}, State) ->
|
wait_for_cmd({route, From, _To, {xmlelement, "message", Attrs, Els} = El}, State) ->
|
||||||
?DEBUG("Got a Message! ~p ~p ~p", [From, _To, El]),
|
Type = xml:get_attr_s("type", Attrs),
|
||||||
Type = xml:get_attr_s(<<"type">>, Attrs),
|
|
||||||
case Type of
|
case Type of
|
||||||
<<"groupchat">> ->
|
"groupchat" ->
|
||||||
?DEBUG("It's a groupchat", []),
|
|
||||||
ChannelJID = jlib:jid_remove_resource(From),
|
ChannelJID = jlib:jid_remove_resource(From),
|
||||||
case ?DICT:find(ChannelJID, State#state.joined) of
|
case ?DICT:find(ChannelJID, State#state.joined) of
|
||||||
{ok, #channel{} = ChannelData} ->
|
{ok, #channel{} = ChannelData} ->
|
||||||
FromChannel = jid_to_channel(From, State),
|
FromChannel = jid_to_channel(From, State),
|
||||||
FromNick = binary_to_list(From#jid.resource),
|
FromNick = From#jid.resource,
|
||||||
Subject = xml:get_path_s(El, [{elem, <<"subject">>}, cdata]),
|
Subject = xml:get_path_s(El, [{elem, "subject"}, cdata]),
|
||||||
Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
|
Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
|
||||||
?DEBUG("Message Data ~p ~p", [Subject, Body]),
|
XDelay = lists:any(fun({xmlelement, "x", XAttrs, _}) ->
|
||||||
XDelay = lists:any(fun({xmlel, <<"x">>, XAttrs, _}) ->
|
xml:get_attr_s("xmlns", XAttrs) == ?NS_DELAY;
|
||||||
xml:get_attr_s(<<"xmlns">>, XAttrs) == ?NS_DELAY;
|
|
||||||
(_) ->
|
(_) ->
|
||||||
false
|
false
|
||||||
end, Els),
|
end, Els),
|
||||||
?DEBUG("XDelay ~p", [XDelay]),
|
|
||||||
if
|
if
|
||||||
Subject /= <<"">> ->
|
Subject /= "" ->
|
||||||
?DEBUG("Cleaning Subject!", []),
|
|
||||||
CleanSubject = lists:map(fun($\n) ->
|
CleanSubject = lists:map(fun($\n) ->
|
||||||
$\ ;
|
$\ ;
|
||||||
(C) -> C
|
(C) -> C
|
||||||
end, binary_to_list(Subject)),
|
end, Subject),
|
||||||
?DEBUG("CleanSubject ~p", [CleanSubject]),
|
send_text_command(make_irc_sender(From, State),
|
||||||
IRCSender = make_irc_sender(From, State),
|
|
||||||
?DEBUG("IRCSender ~p", [IRCSender]),
|
|
||||||
send_text_command(IRCSender,
|
|
||||||
"TOPIC", [FromChannel, CleanSubject], State),
|
"TOPIC", [FromChannel, CleanSubject], State),
|
||||||
NewChannelData = ChannelData#channel{topic = CleanSubject},
|
NewChannelData = ChannelData#channel{topic = CleanSubject},
|
||||||
NewState = State#state{joined = ?DICT:store(jlib:jid_remove_resource(From), NewChannelData, State#state.joined)},
|
NewState = State#state{joined = ?DICT:store(jlib:jid_remove_resource(From), NewChannelData, State#state.joined)},
|
||||||
|
@ -574,11 +554,9 @@ wait_for_cmd({route, From, _To, {xmlel, <<"message">>, Attrs, Els} = El}, State)
|
||||||
not XDelay, FromNick == State#state.nick ->
|
not XDelay, FromNick == State#state.nick ->
|
||||||
%% there is no message echo in IRC.
|
%% there is no message echo in IRC.
|
||||||
%% we let the backlog through, though.
|
%% we let the backlog through, though.
|
||||||
?DEBUG("Don't care about it", []),
|
|
||||||
{next_state, wait_for_cmd, State};
|
{next_state, wait_for_cmd, State};
|
||||||
true ->
|
true ->
|
||||||
?DEBUG("Send it to someone!", []),
|
BodyLines = string:tokens(Body, "\n"),
|
||||||
BodyLines = string:tokens(binary_to_list(Body), "\n"),
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Line) ->
|
fun(Line) ->
|
||||||
Line1 =
|
Line1 =
|
||||||
|
@ -598,7 +576,7 @@ wait_for_cmd({route, From, _To, {xmlel, <<"message">>, Attrs, Els} = El}, State)
|
||||||
[jlib:jid_to_string(ChannelJID)]),
|
[jlib:jid_to_string(ChannelJID)]),
|
||||||
{next_state, wait_for_cmd, State}
|
{next_state, wait_for_cmd, State}
|
||||||
end;
|
end;
|
||||||
<<"error">> ->
|
"error" ->
|
||||||
MucHost = State#state.muc_host,
|
MucHost = State#state.muc_host,
|
||||||
ErrorFrom =
|
ErrorFrom =
|
||||||
case From of
|
case From of
|
||||||
|
@ -617,7 +595,7 @@ wait_for_cmd({route, From, _To, {xmlel, <<"message">>, Attrs, Els} = El}, State)
|
||||||
%% I think this should cover all possible combinations of
|
%% I think this should cover all possible combinations of
|
||||||
%% XMPP and non-XMPP error messages...
|
%% XMPP and non-XMPP error messages...
|
||||||
ErrorText =
|
ErrorText =
|
||||||
error_to_string(xml:get_subtag(El, <<"error">>)),
|
error_to_string(xml:get_subtag(El, "error")),
|
||||||
send_text_command("", "NOTICE", [State#state.nick,
|
send_text_command("", "NOTICE", [State#state.nick,
|
||||||
"Message to "++ErrorFrom++" bounced: "++
|
"Message to "++ErrorFrom++" bounced: "++
|
||||||
ErrorText], State),
|
ErrorText], State),
|
||||||
|
@ -626,8 +604,8 @@ wait_for_cmd({route, From, _To, {xmlel, <<"message">>, Attrs, Els} = El}, State)
|
||||||
ChannelJID = jlib:jid_remove_resource(From),
|
ChannelJID = jlib:jid_remove_resource(From),
|
||||||
case ?DICT:find(ChannelJID, State#state.joined) of
|
case ?DICT:find(ChannelJID, State#state.joined) of
|
||||||
{ok, #channel{}} ->
|
{ok, #channel{}} ->
|
||||||
FromNick = binary_to_list(From#jid.lresource)++jid_to_channel(From, State),
|
FromNick = From#jid.lresource++jid_to_channel(From, State),
|
||||||
Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
|
Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
|
||||||
BodyLines = string:tokens(Body, "\n"),
|
BodyLines = string:tokens(Body, "\n"),
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Line) ->
|
fun(Line) ->
|
||||||
|
@ -658,36 +636,30 @@ join_channels(Channels, [], State) ->
|
||||||
join_channels([Channel | Channels], [Key | Keys],
|
join_channels([Channel | Channels], [Key | Keys],
|
||||||
#state{nick = Nick} = State) ->
|
#state{nick = Nick} = State) ->
|
||||||
Packet =
|
Packet =
|
||||||
{xmlel, <<"presence">>, [],
|
{xmlelement, "presence", [],
|
||||||
[{xmlel, <<"x">>, [{<<"xmlns">>, ?NS_MUC}],
|
[{xmlelement, "x", [{"xmlns", ?NS_MUC}],
|
||||||
case Key of
|
case Key of
|
||||||
none ->
|
none ->
|
||||||
[];
|
[];
|
||||||
_ ->
|
_ ->
|
||||||
[{xmlel, <<"password">>, [], filter_cdata(Key)}]
|
[{xmlelement, "password", [], filter_cdata(Key)}]
|
||||||
end}]},
|
end}]},
|
||||||
?DEBUG("joining channel nick=~p channel=~p state=~p", [Nick, Channel, State]),
|
|
||||||
From = user_jid(State),
|
From = user_jid(State),
|
||||||
?DEBUG("1 ~p", [From]),
|
|
||||||
To = channel_nick_to_jid(Nick, Channel, State),
|
To = channel_nick_to_jid(Nick, Channel, State),
|
||||||
?DEBUG("2 ~p", [To]),
|
|
||||||
Room = jlib:jid_remove_resource(To),
|
Room = jlib:jid_remove_resource(To),
|
||||||
?DEBUG("3 ~p", [Room]),
|
|
||||||
ejabberd_router:route(From, To, Packet),
|
ejabberd_router:route(From, To, Packet),
|
||||||
?DEBUG("4", []),
|
|
||||||
NewState = State#state{joining = ?DICT:store(Room, [], State#state.joining)},
|
NewState = State#state{joining = ?DICT:store(Room, [], State#state.joining)},
|
||||||
?DEBUG("5 ~p", [NewState]),
|
|
||||||
join_channels(Channels, Keys, NewState).
|
join_channels(Channels, Keys, NewState).
|
||||||
|
|
||||||
part_channels([], State, _Message) ->
|
part_channels([], State, _Message) ->
|
||||||
State;
|
State;
|
||||||
part_channels([Channel | Channels], State, Message) ->
|
part_channels([Channel | Channels], State, Message) ->
|
||||||
Packet =
|
Packet =
|
||||||
{xmlel, <<"presence">>,
|
{xmlelement, "presence",
|
||||||
[{<<"type">>, <<"unavailable">>}],
|
[{"type", "unavailable"}],
|
||||||
case Message of
|
case Message of
|
||||||
nothing -> [];
|
nothing -> [];
|
||||||
_ -> [{xmlel, <<"status">>, [],
|
_ -> [{xmlelement, "status", [],
|
||||||
[{xmlcdata, Message}]}]
|
[{xmlcdata, Message}]}]
|
||||||
end},
|
end},
|
||||||
From = user_jid(State),
|
From = user_jid(State),
|
||||||
|
@ -732,8 +704,7 @@ upcase([C|String]) ->
|
||||||
send_line(Line, #state{sockmod = SockMod, socket = Socket, encoding = Encoding}) ->
|
send_line(Line, #state{sockmod = SockMod, socket = Socket, encoding = Encoding}) ->
|
||||||
?DEBUG("sending ~s", [Line]),
|
?DEBUG("sending ~s", [Line]),
|
||||||
gen_tcp = SockMod,
|
gen_tcp = SockMod,
|
||||||
%EncodedLine = iconv:convert("utf-8", Encoding, Line),
|
EncodedLine = iconv:convert("utf-8", Encoding, Line),
|
||||||
EncodedLine = Line,
|
|
||||||
ok = gen_tcp:send(Socket, [EncodedLine, 13, 10]).
|
ok = gen_tcp:send(Socket, [EncodedLine, 13, 10]).
|
||||||
|
|
||||||
send_command(Sender, Command, Params, State) ->
|
send_command(Sender, Command, Params, State) ->
|
||||||
|
@ -745,10 +716,9 @@ send_text_command(Sender, Command, Params, State) ->
|
||||||
send_command(Sender, Command, Params, State, true).
|
send_command(Sender, Command, Params, State, true).
|
||||||
|
|
||||||
send_command(Sender, Command, Params, State, AlwaysQuote) ->
|
send_command(Sender, Command, Params, State, AlwaysQuote) ->
|
||||||
?DEBUG("SendCommand ~p ~p ~p", [Sender, Command, Params]),
|
|
||||||
Prefix = case Sender of
|
Prefix = case Sender of
|
||||||
"" ->
|
"" ->
|
||||||
[$: | binary_to_list(State#state.host)];
|
[$: | State#state.host];
|
||||||
_ ->
|
_ ->
|
||||||
[$: | Sender]
|
[$: | Sender]
|
||||||
end,
|
end,
|
||||||
|
@ -814,7 +784,7 @@ make_param_string([Param | Params], AlwaysQuote) ->
|
||||||
" " ++ Param ++ make_param_string(Params, AlwaysQuote)
|
" " ++ Param ++ make_param_string(Params, AlwaysQuote)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
find_el(Name, NS, [{xmlel, N, Attrs, _} = El|Els]) ->
|
find_el(Name, NS, [{xmlelement, N, Attrs, _} = El|Els]) ->
|
||||||
XMLNS = xml:get_attr_s("xmlns", Attrs),
|
XMLNS = xml:get_attr_s("xmlns", Attrs),
|
||||||
case {Name, NS} of
|
case {Name, NS} of
|
||||||
{N, XMLNS} ->
|
{N, XMLNS} ->
|
||||||
|
@ -831,7 +801,7 @@ channel_to_jid(Channel, #state{muc_host = MucHost,
|
||||||
channels_to_jids = ChannelsToJids}) ->
|
channels_to_jids = ChannelsToJids}) ->
|
||||||
case ?DICT:find(Channel, ChannelsToJids) of
|
case ?DICT:find(Channel, ChannelsToJids) of
|
||||||
{ok, RoomJID} -> RoomJID;
|
{ok, RoomJID} -> RoomJID;
|
||||||
_ -> jlib:make_jid(list_to_binary(Channel), MucHost, <<"">>)
|
_ -> jlib:make_jid(Channel, MucHost, "")
|
||||||
end.
|
end.
|
||||||
|
|
||||||
channel_nick_to_jid(Nick, [$#|Channel], State) ->
|
channel_nick_to_jid(Nick, [$#|Channel], State) ->
|
||||||
|
@ -839,28 +809,28 @@ channel_nick_to_jid(Nick, [$#|Channel], State) ->
|
||||||
channel_nick_to_jid(Nick, Channel, #state{muc_host = MucHost,
|
channel_nick_to_jid(Nick, Channel, #state{muc_host = MucHost,
|
||||||
channels_to_jids = ChannelsToJids}) ->
|
channels_to_jids = ChannelsToJids}) ->
|
||||||
case ?DICT:find(Channel, ChannelsToJids) of
|
case ?DICT:find(Channel, ChannelsToJids) of
|
||||||
{ok, RoomJID} -> jlib:jid_replace_resource(RoomJID, list_to_binary(Nick));
|
{ok, RoomJID} -> jlib:jid_replace_resource(RoomJID, Nick);
|
||||||
_ -> jlib:make_jid(list_to_binary(Channel), MucHost, list_to_binary(Nick))
|
_ -> jlib:make_jid(Channel, MucHost, Nick)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
jid_to_channel(#jid{user = Room} = RoomJID,
|
jid_to_channel(#jid{user = Room} = RoomJID,
|
||||||
#state{jids_to_channels = JidsToChannels}) ->
|
#state{jids_to_channels = JidsToChannels}) ->
|
||||||
case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
|
case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
|
||||||
{ok, Channel} -> [$#|binary_to_list(Channel)];
|
{ok, Channel} -> [$#|Channel];
|
||||||
_ -> [$#|binary_to_list(Room)]
|
_ -> [$#|Room]
|
||||||
end.
|
end.
|
||||||
|
|
||||||
make_irc_sender(Nick, #jid{luser = Room} = RoomJID,
|
make_irc_sender(Nick, #jid{luser = Room} = RoomJID,
|
||||||
#state{jids_to_channels = JidsToChannels}) ->
|
#state{jids_to_channels = JidsToChannels}) ->
|
||||||
case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
|
case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
|
||||||
{ok, Channel} -> Nick++"!"++Nick++"@"++binary_to_list(Channel);
|
{ok, Channel} -> Nick++"!"++Nick++"@"++Channel;
|
||||||
_ -> Nick++"!"++Nick++"@"++binary_to_list(Room)
|
_ -> Nick++"!"++Nick++"@"++Room
|
||||||
end.
|
end.
|
||||||
make_irc_sender(#jid{lresource = Nick} = JID, State) ->
|
make_irc_sender(#jid{lresource = Nick} = JID, State) ->
|
||||||
make_irc_sender(binary_to_list(Nick), JID, State).
|
make_irc_sender(Nick, JID, State).
|
||||||
|
|
||||||
user_jid(#state{nick = Nick, host = Host}) ->
|
user_jid(#state{nick = Nick, host = Host}) ->
|
||||||
jlib:make_jid(list_to_binary(Nick), Host, <<"irc">>).
|
jlib:make_jid(Nick, Host, "irc").
|
||||||
|
|
||||||
filter_cdata(Msg) ->
|
filter_cdata(Msg) ->
|
||||||
[{xmlcdata, filter_message(Msg)}].
|
[{xmlcdata, filter_message(Msg)}].
|
||||||
|
@ -887,25 +857,25 @@ translate_action(Msg) ->
|
||||||
Msg
|
Msg
|
||||||
end.
|
end.
|
||||||
|
|
||||||
parse_error({xmlel, "error", _ErrorAttrs, ErrorEls} = ErrorEl) ->
|
parse_error({xmlelement, "error", _ErrorAttrs, ErrorEls} = ErrorEl) ->
|
||||||
ErrorTextEl = xml:get_subtag(ErrorEl, "text"),
|
ErrorTextEl = xml:get_subtag(ErrorEl, "text"),
|
||||||
ErrorName =
|
ErrorName =
|
||||||
case ErrorEls -- [ErrorTextEl] of
|
case ErrorEls -- [ErrorTextEl] of
|
||||||
[{xmlel, ErrorReason, _, _}] ->
|
[{xmlelement, ErrorReason, _, _}] ->
|
||||||
ErrorReason;
|
ErrorReason;
|
||||||
_ ->
|
_ ->
|
||||||
"unknown error"
|
"unknown error"
|
||||||
end,
|
end,
|
||||||
ErrorText =
|
ErrorText =
|
||||||
case ErrorTextEl of
|
case ErrorTextEl of
|
||||||
{xmlel, _, _, _} ->
|
{xmlelement, _, _, _} ->
|
||||||
xml:get_tag_cdata(ErrorTextEl);
|
xml:get_tag_cdata(ErrorTextEl);
|
||||||
_ ->
|
_ ->
|
||||||
nothing
|
nothing
|
||||||
end,
|
end,
|
||||||
{ErrorName, ErrorText}.
|
{ErrorName, ErrorText}.
|
||||||
|
|
||||||
error_to_string({xmlel, "error", _ErrorAttrs, _ErrorEls} = ErrorEl) ->
|
error_to_string({xmlelement, "error", _ErrorAttrs, _ErrorEls} = ErrorEl) ->
|
||||||
case parse_error(ErrorEl) of
|
case parse_error(ErrorEl) of
|
||||||
{ErrorName, ErrorText} when is_list(ErrorText) ->
|
{ErrorName, ErrorText} when is_list(ErrorText) ->
|
||||||
ErrorName ++ ": " ++ ErrorText;
|
ErrorName ++ ": " ++ ErrorText;
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
2009-06-15 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_admin_extra.erl: Added command to check the password by
|
||||||
|
providing a MD5 or SHA hash.
|
||||||
|
|
||||||
|
2009-02-16 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_admin_extra.erl: Fix srg_user_add arguments
|
||||||
|
|
||||||
|
2009-02-09 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_admin_extra.erl: Calculation of 'stats registeredusers'
|
||||||
|
works also with non-internal authentication methods (thanks to
|
||||||
|
Francois de Metz)(EJAB-864)
|
||||||
|
|
||||||
|
2009-01-27 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* README.txt: Explicitely mention that this module does not work
|
||||||
|
with any ejabberd 2.0.x or older
|
||||||
|
|
||||||
|
* src/mod_admin_extra.erl: In the srg_create command, allow to
|
||||||
|
separate group identifiers in the Display argument with escaped
|
||||||
|
newline character. Explain this possibility in the command long
|
||||||
|
description.
|
||||||
|
|
||||||
|
2008-11-10 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_admin_extra.erl: Renamed command send_message to
|
||||||
|
send_message_headline. New command send_message_chat. (thanks to
|
||||||
|
Xyu)
|
||||||
|
|
||||||
|
2008-10-31 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_admin_extra.erl: Deleted the duplicated command
|
||||||
|
delete_older_messages (EJAB-814). Rename the other delete_older_*
|
||||||
|
commands.
|
||||||
|
|
||||||
|
2008-10-12 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* README.txt: It requires ejabberd trunk SVN 1635 or newer
|
||||||
|
|
||||||
|
* src/mod_admin_extra.erl: Renamed mod_ctlextra to
|
||||||
|
mod_admin_extra, as it now implements ejabberd commands instead of
|
||||||
|
ejabberdctl commands
|
||||||
|
|
||||||
|
2008-04-26 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: New command: ban-account
|
||||||
|
* README.txt: Documented new command
|
||||||
|
|
||||||
|
2008-04-04 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Added new command
|
||||||
|
rosteritem-purge [options]
|
||||||
|
|
||||||
|
2008-03-19 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Added two new commands for administration
|
||||||
|
of Shared Roster Groups: "srg-list-groups host" and "srg-get-info
|
||||||
|
group host" (thanks to Taylor Laramie)
|
||||||
|
|
||||||
|
2008-03-09 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Show the result of each table export
|
||||||
|
|
||||||
|
2008-03-04 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: New command 'stats
|
||||||
|
onlineusersnode'. Removed old unusued code.
|
||||||
|
|
||||||
|
* README.txt: Updated module page
|
||||||
|
|
||||||
|
2008-01-20 Badlop <badlop@process-one.net>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Allow to define group name with spaces
|
||||||
|
* README.txt: Likewise
|
||||||
|
|
||||||
|
2007-11-14 Badlop <badlop@ono.com>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Updated to ejabberd SVN.
|
||||||
|
|
||||||
|
2007-09-08 Badlop <badlop@ono.com>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Bugfix: unregister commands when module
|
||||||
|
stops. Removed all MUC-related commands.
|
||||||
|
|
||||||
|
2007-08-29 Badlop <badlop@ono.com>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Added command: stats uptime-seconds.
|
||||||
|
|
||||||
|
2007-08-24 Badlop <badlop@ono.com>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Fixed bug in vcard-set command that
|
||||||
|
forgot second level attributes.
|
||||||
|
|
||||||
|
2007-08-23 Badlop <badlop@ono.com>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Command add-rosteritem now pushes the
|
||||||
|
roster item to the client. New command rem-rosteritem.
|
||||||
|
|
||||||
|
2007-08-19 Badlop <badlop@ono.com>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Now vcard-get and vcard-set are more
|
||||||
|
compatible with mod_vcard_odbc and mod_vcard_ldap.
|
||||||
|
|
||||||
|
2007-08-16 Badlop <badlop@ono.com>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: add-rosteritem only adds an item in a
|
||||||
|
roster.
|
||||||
|
|
||||||
|
2007-08-07 Badlop <badlop@ono.com>
|
||||||
|
|
||||||
|
* src/mod_ctlextra.erl: Fixed indentation.
|
||||||
|
|
||||||
|
* ChangeLog: New file to track changes.
|
||||||
|
|
||||||
|
* README.txt: Removed Changelog section.
|
|
@ -0,0 +1,2 @@
|
||||||
|
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/mod_admin_extra', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -0,0 +1,81 @@
|
||||||
|
|
||||||
|
|
||||||
|
mod_admin_extra - Additional ejabberd commands
|
||||||
|
|
||||||
|
Author: Badlop
|
||||||
|
Homepage: http://www.ejabberd.im/mod_admin_extra
|
||||||
|
|
||||||
|
|
||||||
|
CONFIGURATION
|
||||||
|
=============
|
||||||
|
|
||||||
|
Add the module to your ejabberd.yml, on the modules section:
|
||||||
|
modules:
|
||||||
|
mod_admin_extra: {}
|
||||||
|
|
||||||
|
The configurable options are:
|
||||||
|
- module_resource:
|
||||||
|
Indicate the resource that the XMPP stanzas must use in the FROM or TO JIDs.
|
||||||
|
This is only useful in the vcard set and get commands.
|
||||||
|
The default value is "mod_admin_extra".
|
||||||
|
|
||||||
|
In this example configuration, the users vcards can only be modified
|
||||||
|
by executing mod_admin_extra commands.
|
||||||
|
Notice that this needs the patch
|
||||||
|
https://support.process-one.net/browse/EJAB-797
|
||||||
|
acl:
|
||||||
|
adminextraresource:
|
||||||
|
resource: "modadminextraf8x,31ad"
|
||||||
|
access:
|
||||||
|
vcard_set:
|
||||||
|
adminextraresource: allow
|
||||||
|
all: deny
|
||||||
|
modules:
|
||||||
|
mod_admin_extra:
|
||||||
|
module_resource: "modadminextraf8x,31ad"
|
||||||
|
mod_vcard:
|
||||||
|
access_set: vcard_set
|
||||||
|
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
=====
|
||||||
|
|
||||||
|
Now you have several new commands in ejabberdctl.
|
||||||
|
|
||||||
|
Description of some commands:
|
||||||
|
|
||||||
|
- vcard-*
|
||||||
|
Example: ejabberdctl vcard-get joe myjab.net email
|
||||||
|
|
||||||
|
- pushroster*
|
||||||
|
The file used by 'pushroster' and 'pushroster-all' must be placed:
|
||||||
|
- Windows: on the directory were you installed ejabberd:
|
||||||
|
'C:/Program Files/ejabberd'
|
||||||
|
- Other OS: on the same directory where the .beam files are.
|
||||||
|
Example content for the roster file:
|
||||||
|
[{<<"bob">>, <<"example.org">>, <<"workers">>, <<"Bob">>},
|
||||||
|
{<<"mart">>, <<"example.org">>, <<"workers">>, <<"Mart">>},
|
||||||
|
{<<"Rich">>, <<"example.org">>, <<"bosses">>, <<"Rich">>}].
|
||||||
|
|
||||||
|
- srg-create
|
||||||
|
If you want to put a group Name with blankspaces, use the characters
|
||||||
|
"' and '" to define when the Name starts and ends.
|
||||||
|
For example:
|
||||||
|
ejabberdctl srg-create g1 example.org "'Group number 1'" this_is_g1 g1
|
||||||
|
|
||||||
|
- ban-account
|
||||||
|
|
||||||
|
This command kicks all the connected sessions of the account from the
|
||||||
|
server. It also changes his password to another randomly
|
||||||
|
generated, so he can't login anymore unless a server administrator
|
||||||
|
changes him again the password.
|
||||||
|
|
||||||
|
It is possible to define the reason of the ban. The new password
|
||||||
|
also includes the reason and the date and time of the ban.
|
||||||
|
|
||||||
|
For example, if this command is called:
|
||||||
|
ejabberdctl vhost example.org ban-account boby Spammed several MUC rooms
|
||||||
|
then the sessions of the local account which JID is boby@example.org
|
||||||
|
will be kicked, and its password will be set to something like this:
|
||||||
|
BANNED_ACCOUNT--20080425T21:45:07--2176635--Spammed_several_MUC_rooms
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,426 @@
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% File : mod_ecomm_test.erl
|
||||||
|
%%% Author : Badlop <badlop@process-one.net>
|
||||||
|
%%% Purpose : Simple commands for testing
|
||||||
|
%%% Created : 10 Aug 2008 by Badlop <badlop@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2008 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_ecomm_test).
|
||||||
|
-author('badlop@process-one.net').
|
||||||
|
|
||||||
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
|
-export([start/2, stop/1,
|
||||||
|
%% Take: test arguments
|
||||||
|
take_integer/1,
|
||||||
|
take_string/1,
|
||||||
|
take_integer_string/2,
|
||||||
|
take_tuple_2integer/1,
|
||||||
|
take_tuple_2string/1,
|
||||||
|
take_list_integer/1,
|
||||||
|
take_list_string/1,
|
||||||
|
%% Echo: test arguments and result
|
||||||
|
echo_integer/1,
|
||||||
|
echo_string/1,
|
||||||
|
echo_integer_string/2,
|
||||||
|
echo_list_integer/1,
|
||||||
|
echo_list_string/1,
|
||||||
|
echo_integer_list_string/2,
|
||||||
|
echo_isatils/4,
|
||||||
|
%% Tell: test result
|
||||||
|
tell_atom/1,
|
||||||
|
tell_rescode/1,
|
||||||
|
tell_restuple/1,
|
||||||
|
tell_tuple_3integer/0,
|
||||||
|
tell_tuple_3string/0,
|
||||||
|
tell_tuple_3atom/0,
|
||||||
|
tell_tuple_3list/0,
|
||||||
|
tell_list_3integer/0,
|
||||||
|
tell_list_3string/0,
|
||||||
|
tell_list_3atom/0,
|
||||||
|
tell_list_3tuple/0,
|
||||||
|
%% Realistic
|
||||||
|
this_crashes/1,
|
||||||
|
this_wrong_args/1,
|
||||||
|
this_wrong_return/0,
|
||||||
|
pow/2, seq/2, substrs/1, splitjid/1, splitjids/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("ejabberd_commands.hrl").
|
||||||
|
-include("jlib.hrl").
|
||||||
|
|
||||||
|
start(_Host, _Opts) ->
|
||||||
|
ejabberd_commands:register_commands(commands()).
|
||||||
|
|
||||||
|
stop(_Host) ->
|
||||||
|
ejabberd_commands:unregister_commands(commands()).
|
||||||
|
|
||||||
|
%%%
|
||||||
|
%%% ejabberd commands
|
||||||
|
%%%
|
||||||
|
|
||||||
|
commands() ->
|
||||||
|
[
|
||||||
|
|
||||||
|
#ejabberd_commands{name = take_integer, tags = [test],
|
||||||
|
desc = "Take Integer in args, give Integer zero",
|
||||||
|
module = ?MODULE, function = take_integer,
|
||||||
|
args = [{thisinteger, integer}],
|
||||||
|
result = {zero, integer}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = take_string, tags = [test],
|
||||||
|
desc = "Take String, give Integer zero",
|
||||||
|
module = ?MODULE, function = take_string,
|
||||||
|
args = [{thisstring, string}],
|
||||||
|
result = {zero, integer}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = take_integer_string, tags = [test],
|
||||||
|
desc = "Take integer and string, give Integer zero",
|
||||||
|
module = ?MODULE, function = take_integer_string,
|
||||||
|
args = [{thisinteger, integer}, {thisstring, string}],
|
||||||
|
result = {zero, integer}},
|
||||||
|
|
||||||
|
%% Not supported by ejabberd_ctl
|
||||||
|
#ejabberd_commands{name = take_tuple_2integer, tags = [test],
|
||||||
|
desc = "Take Tuple of two integers, give Integer zero",
|
||||||
|
module = ?MODULE, function = take_tuple_2integer,
|
||||||
|
args = [{thistuple, {tuple, [{thisinteger1, integer}, {thisinteger2, integer}]}}],
|
||||||
|
result = {zero, integer}},
|
||||||
|
%% Not supported by ejabberd_ctl
|
||||||
|
#ejabberd_commands{name = take_tuple_2string, tags = [test],
|
||||||
|
desc = "Take Tuple of two strings, give Integer zero",
|
||||||
|
module = ?MODULE, function = take_tuple_2string,
|
||||||
|
args = [{thistuple, {tuple, [{thisstring1, string}, {thisstring2, string}]}}],
|
||||||
|
result = {zero, integer}},
|
||||||
|
%% Not supported by ejabberd_ctl
|
||||||
|
#ejabberd_commands{name = take_list_integer, tags = [test],
|
||||||
|
desc = "Take List of integers, give Integer zero",
|
||||||
|
module = ?MODULE, function = take_list_integer,
|
||||||
|
args = [{thislist, {list, {thisinteger, integer}}}],
|
||||||
|
result = {zero, integer}},
|
||||||
|
%% Not supported by ejabberd_ctl
|
||||||
|
#ejabberd_commands{name = take_list_string, tags = [test],
|
||||||
|
desc = "Take List of strings, give Integer zero",
|
||||||
|
module = ?MODULE, function = take_list_string,
|
||||||
|
args = [{thislist, {list, {thisstring, string}}}],
|
||||||
|
result = {zero, integer}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = echo_integer, tags = [test],
|
||||||
|
desc = "Echo Integer",
|
||||||
|
module = ?MODULE, function = echo_integer,
|
||||||
|
args = [{thisinteger, integer}],
|
||||||
|
result = {thatinteger, integer}},
|
||||||
|
#ejabberd_commands{name = echo_string, tags = [test],
|
||||||
|
desc = "Echo String",
|
||||||
|
module = ?MODULE, function = echo_string,
|
||||||
|
args = [{thisstring, string}],
|
||||||
|
result = {thatstring, string}},
|
||||||
|
#ejabberd_commands{name = echo_integer_string, tags = [test],
|
||||||
|
desc = "Echo integer and string, in result as a tuple",
|
||||||
|
module = ?MODULE, function = echo_integer_string,
|
||||||
|
args = [{thisinteger, integer}, {thisstring, string}],
|
||||||
|
result = {thistuple, {tuple, [{thisinteger, integer}, {thisstring, string}]}}},
|
||||||
|
%% Not supported by ejabberd_ctl
|
||||||
|
#ejabberd_commands{name = echo_list_integer, tags = [test],
|
||||||
|
desc = "Echo List of integers",
|
||||||
|
module = ?MODULE, function = echo_list_integer,
|
||||||
|
args = [{thislist, {list, {thisinteger, integer}}}],
|
||||||
|
result = {thatlist, {list, {thatinteger, integer}}}},
|
||||||
|
%% Not supported by ejabberd_ctl
|
||||||
|
#ejabberd_commands{name = echo_list_string, tags = [test],
|
||||||
|
desc = "Echo List of strings",
|
||||||
|
module = ?MODULE, function = echo_list_string,
|
||||||
|
args = [{thislist, {list, {thisstring, string}}}],
|
||||||
|
result = {thatlist, {list, {thatstring, string}}}},
|
||||||
|
%% Not supported by ejabberd_ctl
|
||||||
|
#ejabberd_commands{name = echo_integer_list_string, tags = [test],
|
||||||
|
desc = "Echo an integer and List of strings",
|
||||||
|
module = ?MODULE, function = echo_integer_list_string,
|
||||||
|
args = [{thisinteger, integer}, {thislist, {list, {thisstring, string}}}],
|
||||||
|
result = {thistuple, {tuple, [{thatinteger, integer}, {thatlist, {list, {thatstring, string}}}]}}},
|
||||||
|
%% Not supported by ejabberd_ctl
|
||||||
|
#ejabberd_commands{name = echo_isatils, tags = [test],
|
||||||
|
desc = "Echo integer, string, atom and tuple of integer and list of strings",
|
||||||
|
module = ?MODULE, function = echo_isatils,
|
||||||
|
args = [{thisinteger, integer},
|
||||||
|
{thisstring, string},
|
||||||
|
{thisatom, atom},
|
||||||
|
{thistuple, {tuple, [
|
||||||
|
{listlen, integer},
|
||||||
|
{thislist, {list, {contentstring, string}}}
|
||||||
|
]}}
|
||||||
|
],
|
||||||
|
result = {results, {tuple, [{thatinteger, integer},
|
||||||
|
{thatstring, string},
|
||||||
|
{thatatom, atom},
|
||||||
|
{thattuple, {tuple, [
|
||||||
|
{listlen, integer},
|
||||||
|
{thatlist, {list, {contentstring, string}}}
|
||||||
|
]}}
|
||||||
|
]}}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = tell_atom, tags = [test],
|
||||||
|
desc = "Tell Atom, give Integer zero",
|
||||||
|
module = ?MODULE, function = tell_atom,
|
||||||
|
args = [{thisinteger, integer}],
|
||||||
|
result = {thisatom, atom}},
|
||||||
|
#ejabberd_commands{name = tell_rescode, tags = [test],
|
||||||
|
desc = "Tell rescode",
|
||||||
|
module = ?MODULE, function = tell_rescode,
|
||||||
|
args = [{thisinteger, integer}],
|
||||||
|
result = {res, rescode}},
|
||||||
|
#ejabberd_commands{name = tell_restuple, tags = [test],
|
||||||
|
desc = "Tell restuple",
|
||||||
|
module = ?MODULE, function = tell_restuple,
|
||||||
|
args = [{thisinteger, integer}],
|
||||||
|
result = {res, restuple}},
|
||||||
|
#ejabberd_commands{name = tell_tuple_3integer, tags = [test],
|
||||||
|
desc = "Tell a tuple with 3 integers",
|
||||||
|
module = ?MODULE, function = tell_tuple_3integer,
|
||||||
|
args = [],
|
||||||
|
result = {thattuple, {tuple, [{first, integer},
|
||||||
|
{second, integer},
|
||||||
|
{third, integer}]}}},
|
||||||
|
#ejabberd_commands{name = tell_tuple_3string, tags = [test],
|
||||||
|
desc = "Tell a tuple with 3 strings",
|
||||||
|
module = ?MODULE, function = tell_tuple_3string,
|
||||||
|
args = [],
|
||||||
|
result = {thattuple, {tuple, [{first, string},
|
||||||
|
{second, string},
|
||||||
|
{third, string}]}}},
|
||||||
|
#ejabberd_commands{name = tell_tuple_3atom, tags = [test],
|
||||||
|
desc = "Tell a tuple with 3 atoms",
|
||||||
|
module = ?MODULE, function = tell_tuple_3atom,
|
||||||
|
args = [],
|
||||||
|
result = {thattuple, {tuple, [{first, atom},
|
||||||
|
{second, atom},
|
||||||
|
{third, atom}]}}},
|
||||||
|
#ejabberd_commands{name = tell_tuple_3list, tags = [test],
|
||||||
|
desc = "Tell a tuple with 3 lists",
|
||||||
|
module = ?MODULE, function = tell_tuple_3list,
|
||||||
|
args = [],
|
||||||
|
result = {thattuple, {tuple,
|
||||||
|
[{first, {list,
|
||||||
|
{thisinteger, integer}}},
|
||||||
|
{second, {list,
|
||||||
|
{thisstring, string}}},
|
||||||
|
{third, {list,
|
||||||
|
{thisatom, atom}}}]}}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = tell_list_3integer, tags = [test],
|
||||||
|
desc = "Tell a list with 3 integers",
|
||||||
|
module = ?MODULE, function = tell_list_3integer,
|
||||||
|
args = [],
|
||||||
|
result = {thatlist, {list, {thisinteger, integer}}}},
|
||||||
|
#ejabberd_commands{name = tell_list_3string, tags = [test],
|
||||||
|
desc = "Tell a list with 3 strings",
|
||||||
|
module = ?MODULE, function = tell_list_3string,
|
||||||
|
args = [],
|
||||||
|
result = {thatlist, {list, {thisstring, string}}}},
|
||||||
|
#ejabberd_commands{name = tell_list_3atom, tags = [test],
|
||||||
|
desc = "Tell a list with 3 atoms",
|
||||||
|
module = ?MODULE, function = tell_list_3atom,
|
||||||
|
args = [],
|
||||||
|
result = {thatlist, {list, {thisatom, atom}}}},
|
||||||
|
#ejabberd_commands{name = tell_list_3tuple, tags = [test],
|
||||||
|
desc = "Tell a list with 3 tuples",
|
||||||
|
module = ?MODULE, function = tell_list_3tuple,
|
||||||
|
args = [],
|
||||||
|
result = {thatlist, {list, {thistuple,
|
||||||
|
{tuple,
|
||||||
|
[{thisinteger, integer},
|
||||||
|
{thistring, string},
|
||||||
|
{thisatom, atom}]}}}}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = this_crashes, tags = [test],
|
||||||
|
desc = "This command crashes: test+5",
|
||||||
|
module = ?MODULE, function = this_crashes,
|
||||||
|
args = [{aninteger, integer}],
|
||||||
|
result = {result, integer}},
|
||||||
|
#ejabberd_commands{name = this_wrong_args, tags = [test],
|
||||||
|
desc = "This problematic command defines 2 arguments but function expects 1",
|
||||||
|
module = ?MODULE, function = this_wrong_args,
|
||||||
|
args = [{a, integer}, {b, integer}],
|
||||||
|
result = {result, integer}},
|
||||||
|
#ejabberd_commands{name = this_wrong_return, tags = [test],
|
||||||
|
desc = "This problematic command doesn't give a proper return",
|
||||||
|
module = ?MODULE, function = this_wrong_return,
|
||||||
|
args = [],
|
||||||
|
result = {result, integer}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = pow, tags = [test],
|
||||||
|
desc = "Return the power of base for exponent",
|
||||||
|
longdesc = "This is an example command. The formula is:\n"
|
||||||
|
" power = base ^ exponent",
|
||||||
|
module = ?MODULE, function = pow,
|
||||||
|
args = [{base, integer}, {exponent, integer}],
|
||||||
|
result = {power, integer}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = seq, tags = [test],
|
||||||
|
desc = "Return list of integers between two integers",
|
||||||
|
module = ?MODULE, function = seq,
|
||||||
|
args = [{from, integer}, {to, integer}],
|
||||||
|
result = {sequence, {list, {intermediate, integer}}}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = substrs, tags = [test],
|
||||||
|
desc = "Return list of substrings of length increasing",
|
||||||
|
module = ?MODULE, function = substrs,
|
||||||
|
args = [{word, string}],
|
||||||
|
result = {substrings, {list, {miniword, string}}}},
|
||||||
|
|
||||||
|
#ejabberd_commands{name = splitjid, tags = [test],
|
||||||
|
desc = "Split JID in parts: user, server, resource",
|
||||||
|
module = ?MODULE, function = splitjid,
|
||||||
|
args = [{jid, string}],
|
||||||
|
result = {jidparts, {tuple, [{user, string},
|
||||||
|
{server, string},
|
||||||
|
{resource, string}]}}},
|
||||||
|
|
||||||
|
%% Not supported by ejabberd_ctl because uses 'list' in the arguments
|
||||||
|
#ejabberd_commands{name = splitjids, tags = [test],
|
||||||
|
desc = "Split JIDs in parts: user, server, resource",
|
||||||
|
module = ?MODULE, function = splitjids,
|
||||||
|
args = [{jids, {list, {jid, string}}}],
|
||||||
|
result = {jidsparts,
|
||||||
|
{list, {jidparts,
|
||||||
|
{tuple, [{user, string},
|
||||||
|
{server, string},
|
||||||
|
{resource, string}]}}}}}
|
||||||
|
|
||||||
|
].
|
||||||
|
|
||||||
|
%%%
|
||||||
|
%%% Take
|
||||||
|
%%%
|
||||||
|
|
||||||
|
take_integer(A) when is_integer(A) -> 0.
|
||||||
|
take_string(A) when is_list(A) -> 0.
|
||||||
|
take_integer_string(A, B)
|
||||||
|
when is_integer(A) and is_list(B) ->
|
||||||
|
0.
|
||||||
|
take_tuple_2integer({A, B})
|
||||||
|
when is_integer(A) and is_integer(B) ->
|
||||||
|
0.
|
||||||
|
take_tuple_2string({A, B})
|
||||||
|
when is_list(A) and is_list(B) ->
|
||||||
|
0.
|
||||||
|
take_list_integer(L)
|
||||||
|
when is_list(L) ->
|
||||||
|
true = lists:all(fun(A) -> is_integer(A) end, L),
|
||||||
|
0.
|
||||||
|
take_list_string(L)
|
||||||
|
when is_list(L) ->
|
||||||
|
true = lists:all(fun(A) -> is_list(A) end, L),
|
||||||
|
0.
|
||||||
|
|
||||||
|
%%%
|
||||||
|
%%% Echo
|
||||||
|
%%%
|
||||||
|
|
||||||
|
echo_integer(A) when is_integer(A) -> A.
|
||||||
|
echo_string(A) when is_list(A) -> A.
|
||||||
|
echo_integer_string(A, B) when is_integer(A) and is_list(B) -> {A, B}.
|
||||||
|
echo_list_integer(L)
|
||||||
|
when is_list(L) ->
|
||||||
|
true = lists:all(fun(A) -> is_integer(A) end, L),
|
||||||
|
L.
|
||||||
|
echo_list_string(L)
|
||||||
|
when is_list(L) ->
|
||||||
|
true = lists:all(fun(A) -> is_list(A) end, L),
|
||||||
|
L.
|
||||||
|
echo_integer_list_string(I, L)
|
||||||
|
when is_integer(I) and is_list(L) ->
|
||||||
|
true = lists:all(fun(A) -> is_list(A) end, L),
|
||||||
|
{I, L}.
|
||||||
|
echo_isatils(I, S, A, {II, L})
|
||||||
|
when is_integer(I) and is_list(S) and is_atom(A) and is_integer(II) and is_list(L) ->
|
||||||
|
true = lists:all(fun(SS) -> is_list(SS) end, L),
|
||||||
|
{I, S, A, {I, L}}.
|
||||||
|
|
||||||
|
|
||||||
|
%%%
|
||||||
|
%%% Tell
|
||||||
|
%%%
|
||||||
|
|
||||||
|
tell_atom(0) -> zero;
|
||||||
|
tell_atom(1) -> one;
|
||||||
|
tell_atom(A) when is_integer(A) -> greater_than_one.
|
||||||
|
|
||||||
|
tell_rescode(0) -> ok;
|
||||||
|
tell_rescode(1) -> true;
|
||||||
|
tell_rescode(2) -> error;
|
||||||
|
tell_rescode(3) -> false;
|
||||||
|
tell_rescode(4) -> whatever.
|
||||||
|
|
||||||
|
tell_restuple(0) -> {ok, "All OK"};
|
||||||
|
tell_restuple(1) -> {true, "Successful result"};
|
||||||
|
tell_restuple(2) -> {error, "This is an error message"}.
|
||||||
|
|
||||||
|
tell_tuple_3integer() -> {123, 456, 789}.
|
||||||
|
tell_tuple_3string() -> {"Tell", "me", "a tuple please"}.
|
||||||
|
tell_tuple_3atom() -> {ok, works, perfectly}.
|
||||||
|
tell_tuple_3list() -> {[1, 23, 456], ["Tell", "me"], [all, is, ok]}.
|
||||||
|
|
||||||
|
tell_list_3integer() -> [123, 456, 789].
|
||||||
|
tell_list_3string() -> ["Tell", "me", "a tuple please"].
|
||||||
|
tell_list_3atom() -> [ok, works, perfectly].
|
||||||
|
tell_list_3tuple() ->
|
||||||
|
[{123, "abcdefghijkl", first},
|
||||||
|
{593, "this string", morning},
|
||||||
|
{999, "Sleeping dog", not_seen}].
|
||||||
|
|
||||||
|
|
||||||
|
%%%
|
||||||
|
%%% Realistic
|
||||||
|
%%%
|
||||||
|
|
||||||
|
%% This function will crash for sure
|
||||||
|
this_crashes(Integer) ->
|
||||||
|
test + Integer.
|
||||||
|
|
||||||
|
this_wrong_args(Integer) ->
|
||||||
|
Integer + 1.
|
||||||
|
|
||||||
|
this_wrong_return() ->
|
||||||
|
"this is a string".
|
||||||
|
|
||||||
|
pow(Base, Exponent) ->
|
||||||
|
PowFloat = math:pow(Base, Exponent),
|
||||||
|
round(PowFloat).
|
||||||
|
|
||||||
|
seq(From, To) ->
|
||||||
|
lists:seq(From, To).
|
||||||
|
|
||||||
|
%% For "stick" returns: s st sti stic stick
|
||||||
|
substrs(Word) ->
|
||||||
|
Lengths = lists:seq(1, string:len(Word)),
|
||||||
|
[string:substr(Word, 1, Length) || Length <- Lengths].
|
||||||
|
|
||||||
|
splitjid(String) ->
|
||||||
|
JID = jlib:string_to_jid(String),
|
||||||
|
{JID#jid.user,
|
||||||
|
JID#jid.server,
|
||||||
|
JID#jid.resource}.
|
||||||
|
|
||||||
|
splitjids(Strings) ->
|
||||||
|
[splitjid(String) || String <- Strings].
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/mod_archive', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/mod_archive_sql', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/mod_archive_odbc', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/mod_archive_webview', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -1,13 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
***************
|
Please note: those modules do NOT work with ejabberd 13 or newer.
|
||||||
PLEASE NOTE
|
|
||||||
***************
|
|
||||||
|
|
||||||
Those modules do NOT work
|
|
||||||
with ejabberd 13 or newer.
|
|
||||||
|
|
||||||
***************
|
|
||||||
|
|
||||||
|
|
||||||
mod_archive - Message Archiving (XEP-0136)
|
mod_archive - Message Archiving (XEP-0136)
|
||||||
|
@ -67,7 +60,7 @@ Based in mod_archive, author: Olivier Goffart <ogoffart at kde.org>
|
||||||
Based in mod_archive_sql, author: Alexey Shchepin
|
Based in mod_archive_sql, author: Alexey Shchepin
|
||||||
|
|
||||||
For a detailed documentation about this module, please refer to
|
For a detailed documentation about this module, please refer to
|
||||||
http://endl.ch/content/mod_archive_odbc-release
|
http://www.ndl.kiev.ua/typo/articles/2007/11/14/mod_archive_odbc-release
|
||||||
|
|
||||||
|
|
||||||
MOD_ARCHIVE_WEBVIEW
|
MOD_ARCHIVE_WEBVIEW
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Olivier Goffart <ogoffart at kde.org>"
|
|
||||||
category: "archive"
|
|
||||||
summary: "Supports almost all the XEP-0136 version 0.6 except otr"
|
|
||||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
|
||||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
|
@ -1,342 +0,0 @@
|
||||||
As a special exception, the authors give permission to link this program
|
|
||||||
with the OpenSSL library and distribute the resulting binary.
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 2, June 1991
|
|
||||||
|
|
||||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The licenses for most software are designed to take away your
|
|
||||||
freedom to share and change it. By contrast, the GNU General Public
|
|
||||||
License is intended to guarantee your freedom to share and change free
|
|
||||||
software--to make sure the software is free for all its users. This
|
|
||||||
General Public License applies to most of the Free Software
|
|
||||||
Foundation's software and to any other program whose authors commit to
|
|
||||||
using it. (Some other Free Software Foundation software is covered by
|
|
||||||
the GNU Lesser General Public License instead.) You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
this service if you wish), that you receive source code or can get it
|
|
||||||
if you want it, that you can change the software or use pieces of it
|
|
||||||
in new free programs; and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to make restrictions that forbid
|
|
||||||
anyone to deny you these rights or to ask you to surrender the rights.
|
|
||||||
These restrictions translate to certain responsibilities for you if you
|
|
||||||
distribute copies of the software, or if you modify it.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must give the recipients all the rights that
|
|
||||||
you have. You must make sure that they, too, receive or can get the
|
|
||||||
source code. And you must show them these terms so they know their
|
|
||||||
rights.
|
|
||||||
|
|
||||||
We protect your rights with two steps: (1) copyright the software, and
|
|
||||||
(2) offer you this license which gives you legal permission to copy,
|
|
||||||
distribute and/or modify the software.
|
|
||||||
|
|
||||||
Also, for each author's protection and ours, we want to make certain
|
|
||||||
that everyone understands that there is no warranty for this free
|
|
||||||
software. If the software is modified by someone else and passed on, we
|
|
||||||
want its recipients to know that what they have is not the original, so
|
|
||||||
that any problems introduced by others will not reflect on the original
|
|
||||||
authors' reputations.
|
|
||||||
|
|
||||||
Finally, any free program is threatened constantly by software
|
|
||||||
patents. We wish to avoid the danger that redistributors of a free
|
|
||||||
program will individually obtain patent licenses, in effect making the
|
|
||||||
program proprietary. To prevent this, we have made it clear that any
|
|
||||||
patent must be licensed for everyone's free use or not licensed at all.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. This License applies to any program or other work which contains
|
|
||||||
a notice placed by the copyright holder saying it may be distributed
|
|
||||||
under the terms of this General Public License. The "Program", below,
|
|
||||||
refers to any such program or work, and a "work based on the Program"
|
|
||||||
means either the Program or any derivative work under copyright law:
|
|
||||||
that is to say, a work containing the Program or a portion of it,
|
|
||||||
either verbatim or with modifications and/or translated into another
|
|
||||||
language. (Hereinafter, translation is included without limitation in
|
|
||||||
the term "modification".) Each licensee is addressed as "you".
|
|
||||||
|
|
||||||
Activities other than copying, distribution and modification are not
|
|
||||||
covered by this License; they are outside its scope. The act of
|
|
||||||
running the Program is not restricted, and the output from the Program
|
|
||||||
is covered only if its contents constitute a work based on the
|
|
||||||
Program (independent of having been made by running the Program).
|
|
||||||
Whether that is true depends on what the Program does.
|
|
||||||
|
|
||||||
1. You may copy and distribute verbatim copies of the Program's
|
|
||||||
source code as you receive it, in any medium, provided that you
|
|
||||||
conspicuously and appropriately publish on each copy an appropriate
|
|
||||||
copyright notice and disclaimer of warranty; keep intact all the
|
|
||||||
notices that refer to this License and to the absence of any warranty;
|
|
||||||
and give any other recipients of the Program a copy of this License
|
|
||||||
along with the Program.
|
|
||||||
|
|
||||||
You may charge a fee for the physical act of transferring a copy, and
|
|
||||||
you may at your option offer warranty protection in exchange for a fee.
|
|
||||||
|
|
||||||
2. You may modify your copy or copies of the Program or any portion
|
|
||||||
of it, thus forming a work based on the Program, and copy and
|
|
||||||
distribute such modifications or work under the terms of Section 1
|
|
||||||
above, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) You must cause the modified files to carry prominent notices
|
|
||||||
stating that you changed the files and the date of any change.
|
|
||||||
|
|
||||||
b) You must cause any work that you distribute or publish, that in
|
|
||||||
whole or in part contains or is derived from the Program or any
|
|
||||||
part thereof, to be licensed as a whole at no charge to all third
|
|
||||||
parties under the terms of this License.
|
|
||||||
|
|
||||||
c) If the modified program normally reads commands interactively
|
|
||||||
when run, you must cause it, when started running for such
|
|
||||||
interactive use in the most ordinary way, to print or display an
|
|
||||||
announcement including an appropriate copyright notice and a
|
|
||||||
notice that there is no warranty (or else, saying that you provide
|
|
||||||
a warranty) and that users may redistribute the program under
|
|
||||||
these conditions, and telling the user how to view a copy of this
|
|
||||||
License. (Exception: if the Program itself is interactive but
|
|
||||||
does not normally print such an announcement, your work based on
|
|
||||||
the Program is not required to print an announcement.)
|
|
||||||
|
|
||||||
These requirements apply to the modified work as a whole. If
|
|
||||||
identifiable sections of that work are not derived from the Program,
|
|
||||||
and can be reasonably considered independent and separate works in
|
|
||||||
themselves, then this License, and its terms, do not apply to those
|
|
||||||
sections when you distribute them as separate works. But when you
|
|
||||||
distribute the same sections as part of a whole which is a work based
|
|
||||||
on the Program, the distribution of the whole must be on the terms of
|
|
||||||
this License, whose permissions for other licensees extend to the
|
|
||||||
entire whole, and thus to each and every part regardless of who wrote it.
|
|
||||||
|
|
||||||
Thus, it is not the intent of this section to claim rights or contest
|
|
||||||
your rights to work written entirely by you; rather, the intent is to
|
|
||||||
exercise the right to control the distribution of derivative or
|
|
||||||
collective works based on the Program.
|
|
||||||
|
|
||||||
In addition, mere aggregation of another work not based on the Program
|
|
||||||
with the Program (or with a work based on the Program) on a volume of
|
|
||||||
a storage or distribution medium does not bring the other work under
|
|
||||||
the scope of this License.
|
|
||||||
|
|
||||||
3. You may copy and distribute the Program (or a work based on it,
|
|
||||||
under Section 2) in object code or executable form under the terms of
|
|
||||||
Sections 1 and 2 above provided that you also do one of the following:
|
|
||||||
|
|
||||||
a) Accompany it with the complete corresponding machine-readable
|
|
||||||
source code, which must be distributed under the terms of Sections
|
|
||||||
1 and 2 above on a medium customarily used for software interchange; or,
|
|
||||||
|
|
||||||
b) Accompany it with a written offer, valid for at least three
|
|
||||||
years, to give any third party, for a charge no more than your
|
|
||||||
cost of physically performing source distribution, a complete
|
|
||||||
machine-readable copy of the corresponding source code, to be
|
|
||||||
distributed under the terms of Sections 1 and 2 above on a medium
|
|
||||||
customarily used for software interchange; or,
|
|
||||||
|
|
||||||
c) Accompany it with the information you received as to the offer
|
|
||||||
to distribute corresponding source code. (This alternative is
|
|
||||||
allowed only for noncommercial distribution and only if you
|
|
||||||
received the program in object code or executable form with such
|
|
||||||
an offer, in accord with Subsection b above.)
|
|
||||||
|
|
||||||
The source code for a work means the preferred form of the work for
|
|
||||||
making modifications to it. For an executable work, complete source
|
|
||||||
code means all the source code for all modules it contains, plus any
|
|
||||||
associated interface definition files, plus the scripts used to
|
|
||||||
control compilation and installation of the executable. However, as a
|
|
||||||
special exception, the source code distributed need not include
|
|
||||||
anything that is normally distributed (in either source or binary
|
|
||||||
form) with the major components (compiler, kernel, and so on) of the
|
|
||||||
operating system on which the executable runs, unless that component
|
|
||||||
itself accompanies the executable.
|
|
||||||
|
|
||||||
If distribution of executable or object code is made by offering
|
|
||||||
access to copy from a designated place, then offering equivalent
|
|
||||||
access to copy the source code from the same place counts as
|
|
||||||
distribution of the source code, even though third parties are not
|
|
||||||
compelled to copy the source along with the object code.
|
|
||||||
|
|
||||||
4. You may not copy, modify, sublicense, or distribute the Program
|
|
||||||
except as expressly provided under this License. Any attempt
|
|
||||||
otherwise to copy, modify, sublicense or distribute the Program is
|
|
||||||
void, and will automatically terminate your rights under this License.
|
|
||||||
However, parties who have received copies, or rights, from you under
|
|
||||||
this License will not have their licenses terminated so long as such
|
|
||||||
parties remain in full compliance.
|
|
||||||
|
|
||||||
5. You are not required to accept this License, since you have not
|
|
||||||
signed it. However, nothing else grants you permission to modify or
|
|
||||||
distribute the Program or its derivative works. These actions are
|
|
||||||
prohibited by law if you do not accept this License. Therefore, by
|
|
||||||
modifying or distributing the Program (or any work based on the
|
|
||||||
Program), you indicate your acceptance of this License to do so, and
|
|
||||||
all its terms and conditions for copying, distributing or modifying
|
|
||||||
the Program or works based on it.
|
|
||||||
|
|
||||||
6. Each time you redistribute the Program (or any work based on the
|
|
||||||
Program), the recipient automatically receives a license from the
|
|
||||||
original licensor to copy, distribute or modify the Program subject to
|
|
||||||
these terms and conditions. You may not impose any further
|
|
||||||
restrictions on the recipients' exercise of the rights granted herein.
|
|
||||||
You are not responsible for enforcing compliance by third parties to
|
|
||||||
this License.
|
|
||||||
|
|
||||||
7. If, as a consequence of a court judgment or allegation of patent
|
|
||||||
infringement or for any other reason (not limited to patent issues),
|
|
||||||
conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot
|
|
||||||
distribute so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you
|
|
||||||
may not distribute the Program at all. For example, if a patent
|
|
||||||
license would not permit royalty-free redistribution of the Program by
|
|
||||||
all those who receive copies directly or indirectly through you, then
|
|
||||||
the only way you could satisfy both it and this License would be to
|
|
||||||
refrain entirely from distribution of the Program.
|
|
||||||
|
|
||||||
If any portion of this section is held invalid or unenforceable under
|
|
||||||
any particular circumstance, the balance of the section is intended to
|
|
||||||
apply and the section as a whole is intended to apply in other
|
|
||||||
circumstances.
|
|
||||||
|
|
||||||
It is not the purpose of this section to induce you to infringe any
|
|
||||||
patents or other property right claims or to contest validity of any
|
|
||||||
such claims; this section has the sole purpose of protecting the
|
|
||||||
integrity of the free software distribution system, which is
|
|
||||||
implemented by public license practices. Many people have made
|
|
||||||
generous contributions to the wide range of software distributed
|
|
||||||
through that system in reliance on consistent application of that
|
|
||||||
system; it is up to the author/donor to decide if he or she is willing
|
|
||||||
to distribute software through any other system and a licensee cannot
|
|
||||||
impose that choice.
|
|
||||||
|
|
||||||
This section is intended to make thoroughly clear what is believed to
|
|
||||||
be a consequence of the rest of this License.
|
|
||||||
|
|
||||||
8. If the distribution and/or use of the Program is restricted in
|
|
||||||
certain countries either by patents or by copyrighted interfaces, the
|
|
||||||
original copyright holder who places the Program under this License
|
|
||||||
may add an explicit geographical distribution limitation excluding
|
|
||||||
those countries, so that distribution is permitted only in or among
|
|
||||||
countries not thus excluded. In such case, this License incorporates
|
|
||||||
the limitation as if written in the body of this License.
|
|
||||||
|
|
||||||
9. The Free Software Foundation may publish revised and/or new versions
|
|
||||||
of the General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the Program
|
|
||||||
specifies a version number of this License which applies to it and "any
|
|
||||||
later version", you have the option of following the terms and conditions
|
|
||||||
either of that version or of any later version published by the Free
|
|
||||||
Software Foundation. If the Program does not specify a version number of
|
|
||||||
this License, you may choose any version ever published by the Free Software
|
|
||||||
Foundation.
|
|
||||||
|
|
||||||
10. If you wish to incorporate parts of the Program into other free
|
|
||||||
programs whose distribution conditions are different, write to the author
|
|
||||||
to ask for permission. For software which is copyrighted by the Free
|
|
||||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
|
||||||
make exceptions for this. Our decision will be guided by the two goals
|
|
||||||
of preserving the free status of all derivatives of our free software and
|
|
||||||
of promoting the sharing and reuse of software generally.
|
|
||||||
|
|
||||||
NO WARRANTY
|
|
||||||
|
|
||||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
|
||||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
|
||||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
|
||||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
|
||||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
|
||||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
|
||||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
|
||||||
REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
|
||||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
|
||||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
|
||||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
|
||||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
|
||||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGES.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
convey the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program is interactive, make it output a short notice like this
|
|
||||||
when it starts in an interactive mode:
|
|
||||||
|
|
||||||
Gnomovision version 69, Copyright (C) year name of author
|
|
||||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, the commands you use may
|
|
||||||
be called something other than `show w' and `show c'; they could even be
|
|
||||||
mouse-clicks or menu items--whatever suits your program.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or your
|
|
||||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
|
||||||
necessary. Here is a sample; alter the names:
|
|
||||||
|
|
||||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
|
||||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
|
||||||
|
|
||||||
<signature of Ty Coon>, 1 April 1989
|
|
||||||
Ty Coon, President of Vice
|
|
||||||
|
|
||||||
This General Public License does not permit incorporating your program into
|
|
||||||
proprietary programs. If your program is a subroutine library, you may
|
|
||||||
consider it more useful to permit linking proprietary applications with the
|
|
||||||
library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License.
|
|
|
@ -1,112 +0,0 @@
|
||||||
mod_captcha_rust - Generate CAPTCHAs using Rust library
|
|
||||||
=======================================================
|
|
||||||
|
|
||||||
Requires:
|
|
||||||
- ejabberd 23.xx or higher compiled with Elixir
|
|
||||||
- Erlang/OTP and Elixir to compile the `captcha_nif` Elixir library
|
|
||||||
- Rust compiler (package `rustc` in some package managers)
|
|
||||||
|
|
||||||
|
|
||||||
This small module generates CAPTCHA images suitable for ejabberd's
|
|
||||||
[CAPTCHA](https://docs.ejabberd.im/admin/configuration/basic/#captcha) feature,
|
|
||||||
thanks to the [captcha](https://github.com/feng19/captcha) Elixir library,
|
|
||||||
which internally uses the [captcha](https://github.com/daniel-e/captcha) Rust library.
|
|
||||||
|
|
||||||
See example CAPTCHA images in the
|
|
||||||
[captcha Rust documentation](https://docs.rs/captcha/latest/captcha/).
|
|
||||||
|
|
||||||
|
|
||||||
Get `captcha_nif` library
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
This module depends on an Elixir library, and there are two ways to get it installed,
|
|
||||||
depending on how you obtained ejabberd:
|
|
||||||
|
|
||||||
### A) ejabberd source code
|
|
||||||
|
|
||||||
If you compile ejabberd from source code, go to the path with your
|
|
||||||
ejabberd source code and apply this small patch:
|
|
||||||
```diff
|
|
||||||
--- a/mix.exs
|
|
||||||
+++ b/mix.exs
|
|
||||||
@@ -99,6 +99,7 @@ defmodule Ejabberd.MixProject do
|
|
||||||
|
|
||||||
defp deps do
|
|
||||||
[{:base64url, "~> 1.0"},
|
|
||||||
+ {:captcha_nif, "~> 0.1", hex: :captcha_nif},
|
|
||||||
{:cache_tab, "~> 1.0"},
|
|
||||||
{:eimp, "~> 1.0"},
|
|
||||||
{:ex_doc, ">= 0.0.0", only: :dev},
|
|
||||||
```
|
|
||||||
|
|
||||||
Then get the new dependencies, and compile as usual:
|
|
||||||
```
|
|
||||||
mix deps.get
|
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
Install ejabberd as usual, restart it
|
|
||||||
and now you can proceed to install `mod_captcha_rust`.
|
|
||||||
|
|
||||||
|
|
||||||
### B) ejabberd with binary installer
|
|
||||||
|
|
||||||
If you installed ejabberd using some kind of binary installer,
|
|
||||||
you can download, compile and install the required library manually:
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/feng19/captcha.git
|
|
||||||
cd captcha/
|
|
||||||
mix deps.get
|
|
||||||
mix
|
|
||||||
mix release
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy all the directories from `_build/dev/rel/captcha/lib`
|
|
||||||
to the path where you have ejabberd lib directories installed.
|
|
||||||
|
|
||||||
Now you can restart ejabberd and proceed to install `mod_captcha_rust`.
|
|
||||||
|
|
||||||
|
|
||||||
Basic Configuration
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
The minimal configuration required to get this module working is:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
captcha_cmd: 'Elixir.ModCaptchaRust'
|
|
||||||
|
|
||||||
captcha_url: http://localhost:5280/captcha
|
|
||||||
|
|
||||||
listen:
|
|
||||||
-
|
|
||||||
port: 5280
|
|
||||||
module: ejabberd_http
|
|
||||||
request_handlers:
|
|
||||||
/captcha: ejabberd_captcha
|
|
||||||
```
|
|
||||||
|
|
||||||
Options
|
|
||||||
-------
|
|
||||||
|
|
||||||
This module supports those configurable options:
|
|
||||||
|
|
||||||
* `difficulty`
|
|
||||||
|
|
||||||
Sets the CAPTCHA difficulty, it can be `easy`, `medium` or `hard`.
|
|
||||||
Default value: `easy`.
|
|
||||||
|
|
||||||
* `color`
|
|
||||||
|
|
||||||
Sets the CAPTCHA color, as a list of RGB integers from 0 to 255.
|
|
||||||
Default value: `[0, 0, 0]`.
|
|
||||||
|
|
||||||
|
|
||||||
Example of module configuration:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
modules:
|
|
||||||
'Elixir.ModCaptchaRust':
|
|
||||||
difficulty: hard
|
|
||||||
color: [255, 0, 0]
|
|
||||||
```
|
|
|
@ -1,13 +0,0 @@
|
||||||
captcha_cmd: 'Elixir.ModCaptchaRust'
|
|
||||||
|
|
||||||
captcha_url: http://localhost:5288/captcha/
|
|
||||||
|
|
||||||
listen:
|
|
||||||
-
|
|
||||||
port: 5288
|
|
||||||
module: ejabberd_http
|
|
||||||
request_handlers:
|
|
||||||
/captcha: ejabberd_captcha
|
|
||||||
|
|
||||||
modules:
|
|
||||||
'Elixir.ModCaptchaRust': {}
|
|
|
@ -1,63 +0,0 @@
|
||||||
defmodule ModCaptchaRust do
|
|
||||||
use Ejabberd.Module
|
|
||||||
|
|
||||||
require Record
|
|
||||||
|
|
||||||
##====================================================================
|
|
||||||
## gen_mod callbacks
|
|
||||||
##====================================================================
|
|
||||||
|
|
||||||
def start(_host, _opts) do
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def stop(_host) do
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def depends(_host, _opts) do
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
|
|
||||||
def mod_opt_type(:color) do
|
|
||||||
:econf.list(:econf.int(0, 255))
|
|
||||||
end
|
|
||||||
|
|
||||||
def mod_opt_type(:difficulty) do
|
|
||||||
:econf.enum([:easy, :medium, :hard])
|
|
||||||
end
|
|
||||||
|
|
||||||
def mod_options(_host) do
|
|
||||||
[
|
|
||||||
{:color, [0, 0, 0]},
|
|
||||||
{:difficulty, :easy},
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
def mod_doc() do
|
|
||||||
%{:desc => 'This is just a demonstration.'}
|
|
||||||
end
|
|
||||||
|
|
||||||
##====================================================================
|
|
||||||
## Captcha Rust
|
|
||||||
##====================================================================
|
|
||||||
|
|
||||||
def create_image(_key) do
|
|
||||||
[r, g, b] = get_opt(:color)
|
|
||||||
color = %{r: r, g: g, b: b}
|
|
||||||
|
|
||||||
{captcha_text, captcha_image} = case get_opt(:difficulty) do
|
|
||||||
:easy -> Captcha.easy(set_color: color)
|
|
||||||
:medium -> Captcha.medium(set_color: color)
|
|
||||||
:hard -> Captcha.hard(set_color: color)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, "image/png", captcha_text, captcha_image}
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_opt(option_name) do
|
|
||||||
host = List.first(:ejabberd_option.hosts())
|
|
||||||
:gen_mod.get_module_opt(host, Elixir.ModCaptchaRust, option_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Badlop <badlop at process-one.net>"
|
|
||||||
category: "captcha"
|
|
||||||
summary: "Generate CAPTCHAs using Rust library"
|
|
||||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
|
||||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}.
|
||||||
|
{'src/mod_cron', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}.
|
|
@ -1,212 +0,0 @@
|
||||||
mod_cron - Execute scheduled tasks
|
|
||||||
==================================
|
|
||||||
|
|
||||||
* Requires: ejabberd 19.08 or higher
|
|
||||||
* http://www.ejabberd.im/mod_cron
|
|
||||||
* Author: Badlop
|
|
||||||
|
|
||||||
|
|
||||||
This module allows advanced ejabberd administrators to schedule tasks for
|
|
||||||
periodic and automatic execution. This module is a similar concept than the
|
|
||||||
Unix's cron program.
|
|
||||||
|
|
||||||
Each time a scheduled task finishes its execution, a message is printed in the
|
|
||||||
ejabberd log file.
|
|
||||||
|
|
||||||
|
|
||||||
Basic Configuration
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Add the module to the `modules` section on the configuration file:
|
|
||||||
```yaml
|
|
||||||
modules:
|
|
||||||
mod_cron: {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, using the `tasks` option, you can add a list of tasks.
|
|
||||||
For each task there are options to define _when_ to run it,
|
|
||||||
and options to define _what_ to run.
|
|
||||||
|
|
||||||
When
|
|
||||||
----
|
|
||||||
|
|
||||||
Those options determine when a task is ran:
|
|
||||||
|
|
||||||
* `time: integer()`
|
|
||||||
|
|
||||||
* `units: seconds | minutes | hours | days`
|
|
||||||
|
|
||||||
Indicates the time unit to use.
|
|
||||||
|
|
||||||
* `timer_type: interval | fixed`
|
|
||||||
|
|
||||||
Default value is `interval`.
|
|
||||||
Fixed timers occur at a fixed time
|
|
||||||
after the [minute|hour|day] e.g. every hour on the 5th minute (1:05PM, 2:05PM etc).
|
|
||||||
Interval timers occur every interval (starting on an even unit) e.g. every 10 minutes
|
|
||||||
starting at 1PM, 1:10PM, 1:20PM etc.
|
|
||||||
Fixed timers are the equivalent of unix cron's comma syntax e.g. `"2 * * *"`
|
|
||||||
and interval timers are the `/` syntax e.g. `"*/5 * * *"`.
|
|
||||||
|
|
||||||
What
|
|
||||||
----
|
|
||||||
|
|
||||||
You can define a task to run some ejabberd API (either in command or in ctl syntax),
|
|
||||||
or any arbitrary erlang function.
|
|
||||||
|
|
||||||
### Command
|
|
||||||
|
|
||||||
Use the option `command` and provide `arguments` in the correct format:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
command: delete_old_mam_messages
|
|
||||||
arguments:
|
|
||||||
- "all"
|
|
||||||
- 0
|
|
||||||
```
|
|
||||||
|
|
||||||
This requires a recent ejabberd version that includes
|
|
||||||
[this commit](https://github.com/processone/ejabberd/commit/10481ed895016893ee9dc3fe23cd937fdc46ded6),
|
|
||||||
and `api_permissions` configured to allow mod_cron, for example:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
api_permissions:
|
|
||||||
"console commands":
|
|
||||||
from:
|
|
||||||
- ejabberd_ctl
|
|
||||||
- mod_cron
|
|
||||||
who: all
|
|
||||||
what: "*"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ctl
|
|
||||||
|
|
||||||
Use the option `ctl` and provide all `arguments` with quotes:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
ctl: delete_old_mam_messages
|
|
||||||
arguments:
|
|
||||||
- "all"
|
|
||||||
- "0"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Erlang Function
|
|
||||||
|
|
||||||
Use `module`, `function`, and provide `arguments` in the correct format:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
module: ejabberd_auth
|
|
||||||
function: try_register
|
|
||||||
arguments:
|
|
||||||
- "user1"
|
|
||||||
- "localhost"
|
|
||||||
- "somepass"
|
|
||||||
```
|
|
||||||
|
|
||||||
Please note the arguments in string format will be converted to binaries.
|
|
||||||
If the function expects strings, you can add the option `args_type: string`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
module: mnesia
|
|
||||||
function: backup
|
|
||||||
args_type: string
|
|
||||||
arguments:
|
|
||||||
- "/var/log/ejabberd/mnesia.backup"
|
|
||||||
```
|
|
||||||
|
|
||||||
Example Tasks
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Those example tasks show how to specify arguments in the basic erlang formats:
|
|
||||||
```yaml
|
|
||||||
modules:
|
|
||||||
mod_cron:
|
|
||||||
tasks:
|
|
||||||
- time: 30
|
|
||||||
units: seconds
|
|
||||||
module: erlang
|
|
||||||
function: is_integer
|
|
||||||
arguments:
|
|
||||||
- 123456
|
|
||||||
- time: 31
|
|
||||||
units: seconds
|
|
||||||
module: erlang
|
|
||||||
function: is_float
|
|
||||||
arguments:
|
|
||||||
- 123.456
|
|
||||||
- time: 32
|
|
||||||
units: seconds
|
|
||||||
module: erlang
|
|
||||||
function: is_atom
|
|
||||||
arguments:
|
|
||||||
- 'this_is_atom'
|
|
||||||
- time: 33
|
|
||||||
units: seconds
|
|
||||||
module: erlang
|
|
||||||
function: is_atom
|
|
||||||
arguments:
|
|
||||||
- this_is_atom_too
|
|
||||||
- time: 34
|
|
||||||
units: seconds
|
|
||||||
module: erlang
|
|
||||||
function: is_binary
|
|
||||||
arguments:
|
|
||||||
- "Keep this as a binary"
|
|
||||||
- time: 35
|
|
||||||
units: seconds
|
|
||||||
module: erlang
|
|
||||||
function: is_list
|
|
||||||
args_type: string
|
|
||||||
arguments:
|
|
||||||
- "Convert this as a string"
|
|
||||||
```
|
|
||||||
|
|
||||||
It is even possible to pass an argument that is a list of elements, see:
|
|
||||||
```yaml
|
|
||||||
modules:
|
|
||||||
mod_cron:
|
|
||||||
tasks:
|
|
||||||
- time: 36
|
|
||||||
units: seconds
|
|
||||||
module: io
|
|
||||||
function: format
|
|
||||||
args_type: string
|
|
||||||
arguments:
|
|
||||||
- "log message, integer: ~p, float: ~p, atom: ~p, binary: ~p~n~n"
|
|
||||||
- - 12345678
|
|
||||||
- 123.456
|
|
||||||
- atom_this_is
|
|
||||||
- "this is a binary"
|
|
||||||
```
|
|
||||||
|
|
||||||
If you don't need to provide arguments at all,
|
|
||||||
you can remove `arguments`, or provide it with an empty list:
|
|
||||||
```yaml
|
|
||||||
modules:
|
|
||||||
mod_cron:
|
|
||||||
tasks:
|
|
||||||
- time: 10
|
|
||||||
units: seconds
|
|
||||||
command: connected_users
|
|
||||||
- time: 15
|
|
||||||
units: seconds
|
|
||||||
ctl: delete_expired_pubsub_items
|
|
||||||
- time: 20
|
|
||||||
units: seconds
|
|
||||||
module: mod_pubsub
|
|
||||||
function: delete_expired_items
|
|
||||||
arguments: []
|
|
||||||
```
|
|
||||||
|
|
||||||
ejabberd Commands
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
This module provides two new commands that can be executed using ejabberdctl:
|
|
||||||
* cron_list: list scheduled tasks
|
|
||||||
* cron_del taskid: delete this task from the schedule
|
|
||||||
|
|
||||||
Web Admin
|
|
||||||
---------
|
|
||||||
|
|
||||||
This module provides a page in the Host section of the Web Admin.
|
|
||||||
Currently that page only allows to view the tasks scheduled for that host.
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
|
||||||
|
mod_cron - Execute scheduled commands
|
||||||
|
|
||||||
|
http://www.ejabberd.im/mod_cron
|
||||||
|
Author: Badlop
|
||||||
|
|
||||||
|
|
||||||
|
This module allows advanced ejabberd administrators to schedule commands for
|
||||||
|
periodic and automatic execution. This module is a similar concept than the
|
||||||
|
*nix's cron program. Obviously, the admin must know in advance which module,
|
||||||
|
function and arguments to use, so this module is not intended for starting
|
||||||
|
administrators.
|
||||||
|
|
||||||
|
Each time a scheduled task finish its execution, a message is printed in the
|
||||||
|
ejabberd log file.
|
||||||
|
|
||||||
|
|
||||||
|
BASIC CONFIGURATION
|
||||||
|
===================
|
||||||
|
|
||||||
|
Add the module to your ejabberd.yml, on the modules section:
|
||||||
|
modules:
|
||||||
|
mod_cron: {}
|
||||||
|
|
||||||
|
|
||||||
|
TASK SYNTAX
|
||||||
|
===========
|
||||||
|
|
||||||
|
Each task is described with five elements:
|
||||||
|
* Time is an integer.
|
||||||
|
* Units indicates the time unit you use. It can be: seconds, minutes, hours, days.
|
||||||
|
* Module and * Function are the exact call you want to schedule.
|
||||||
|
* Arguments is an array. Strings will be converted to binaries.
|
||||||
|
* timer_type is one of 'fixed' or 'interval'. Fixed timers occur at a fixed time
|
||||||
|
after the [minute|hour|day] e.g. every hour on the 5th minute (1:05PM, 2:05PM etc)
|
||||||
|
interval timers occur every interval (starting on an even unit) e.g. every 10 minutes
|
||||||
|
starting at 1PM, 1:10PM, 1:20PM etc.
|
||||||
|
|
||||||
|
Fixed timers are the equivalent of unix cron's comma syntax e.g. "2 * * *" and interval
|
||||||
|
timers are the / syntax e.g. "*/5 * * *".
|
||||||
|
|
||||||
|
Default timer_type is interval.
|
||||||
|
|
||||||
|
EXAMPLE TASKS
|
||||||
|
=============
|
||||||
|
|
||||||
|
Example configuration with some tasks:
|
||||||
|
modules:
|
||||||
|
mod_cron:
|
||||||
|
tasks:
|
||||||
|
- time: 3
|
||||||
|
units: hours
|
||||||
|
module: mnesia
|
||||||
|
function: info
|
||||||
|
arguments: {}
|
||||||
|
timer_type: fixed
|
||||||
|
- time: 10
|
||||||
|
units: seconds
|
||||||
|
module: ejabberd_auth
|
||||||
|
function: try_register
|
||||||
|
arguments:
|
||||||
|
- "user1"
|
||||||
|
- "localhost"
|
||||||
|
- "somepass"
|
||||||
|
timer_type: interval
|
||||||
|
|
||||||
|
|
||||||
|
EJABBERD COMMANDS
|
||||||
|
=================
|
||||||
|
|
||||||
|
This module provides two new commands that can be executed using ejabberdctl:
|
||||||
|
* cron_list: list scheduled tasks
|
||||||
|
* cron_del taskid: delete this task from the schedule
|
||||||
|
|
||||||
|
|
||||||
|
WEB ADMIN
|
||||||
|
=========
|
||||||
|
|
||||||
|
This module provides a page in the Host section of the Web Admin.
|
||||||
|
Currently that page only allows to view the tasks scheduled for that host.
|
|
@ -0,0 +1 @@
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pa ebin -make
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
erl -pa ../ejabberd-dev/ebin -pz ebin -make
|
|
@ -1,2 +0,0 @@
|
||||||
#modules:
|
|
||||||
# mod_cron: {}
|
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Badlop <badlop at process-one.net>"
|
|
||||||
category: "admin"
|
|
||||||
summary: "Execute scheduled commands"
|
|
||||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
|
||||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
|
@ -11,19 +11,21 @@
|
||||||
|
|
||||||
-behaviour(gen_mod).
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
-export([start/2, stop/1, depends/2, mod_options/1, mod_opt_type/1, mod_doc/0]).
|
-export([
|
||||||
-export([cron_list/1, cron_del/1,
|
cron_list/1, cron_del/1,
|
||||||
run_task/3,
|
run_task/3,
|
||||||
web_menu_host/3, web_page_host/3,
|
web_menu_host/3, web_page_host/3,
|
||||||
|
start/2,
|
||||||
apply_interval/3,
|
apply_interval/3,
|
||||||
apply_interval1/3]).
|
apply_interval1/3,
|
||||||
|
stop/1]).
|
||||||
|
|
||||||
-include("ejabberd_commands.hrl").
|
-include("ejabberd_commands.hrl").
|
||||||
|
-include("ejabberd.hrl").
|
||||||
-include("ejabberd_http.hrl").
|
-include("ejabberd_http.hrl").
|
||||||
-include("ejabberd_web_admin.hrl").
|
-include("ejabberd_web_admin.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("translate.hrl").
|
-include("xml.hrl").
|
||||||
-include_lib("xmpp/include/xmpp.hrl").
|
|
||||||
|
|
||||||
-record(task, {taskid, timerref, host, task}).
|
-record(task, {taskid, timerref, host, task}).
|
||||||
|
|
||||||
|
@ -35,30 +37,17 @@ start(Host, Opts) ->
|
||||||
ejabberd_commands:register_commands(commands()),
|
ejabberd_commands:register_commands(commands()),
|
||||||
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
|
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
|
||||||
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
|
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
|
||||||
Tasks = gen_mod:get_opt(tasks, Opts),
|
Tasks = gen_mod:get_opt(tasks, Opts, fun(A) -> A end, []),
|
||||||
catch ets:new(cron_tasks, [ordered_set, named_table, public, {keypos, 2}]),
|
catch ets:new(cron_tasks, [ordered_set, named_table, public, {keypos, 2}]),
|
||||||
[add_task(Host, Task) || Task <- Tasks],
|
[add_task(Host, Task) || Task <- Tasks].
|
||||||
ok.
|
|
||||||
|
|
||||||
stop(Host) ->
|
stop(Host) ->
|
||||||
ejabberd_commands:unregister_commands(commands()),
|
ejabberd_commands:unregister_commands(commands()),
|
||||||
ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
|
ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
|
||||||
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
|
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
|
||||||
%% Delete tasks of this host
|
%% Delete tasks of this host
|
||||||
[delete_task(Task) || Task <- get_tasks(Host)],
|
[delete_task(Task) || Task <- get_tasks(Host)].
|
||||||
ok.
|
|
||||||
|
|
||||||
depends(_Host, _Opts) ->
|
|
||||||
[].
|
|
||||||
|
|
||||||
mod_opt_type(tasks) ->
|
|
||||||
econf:list(econf:any()).
|
|
||||||
|
|
||||||
mod_options(_Host) ->
|
|
||||||
[{tasks, []}].
|
|
||||||
|
|
||||||
mod_doc() ->
|
|
||||||
#{}.
|
|
||||||
|
|
||||||
%% ---------------------
|
%% ---------------------
|
||||||
%% Task management
|
%% Task management
|
||||||
|
@ -73,7 +62,8 @@ time_to_ms(IntervalUnit, IntervalNum) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
time_until_event(IntervalMS) ->
|
time_until_event(IntervalMS) ->
|
||||||
NowMS = p1_time_compat:system_time(micro_seconds),
|
{MegaSecs, Secs, MicroSecs} = erlang:now(),
|
||||||
|
NowMS = (MegaSecs*1000000 + Secs)*1000 + round(MicroSecs/1000),
|
||||||
MSSinceLastEvent = (NowMS rem IntervalMS),
|
MSSinceLastEvent = (NowMS rem IntervalMS),
|
||||||
(IntervalMS - MSSinceLastEvent).
|
(IntervalMS - MSSinceLastEvent).
|
||||||
|
|
||||||
|
@ -139,10 +129,8 @@ update_timer_ref(TaskId, NewTimerRef) ->
|
||||||
|
|
||||||
%% Method to add new task
|
%% Method to add new task
|
||||||
add_task(Host, Task) ->
|
add_task(Host, Task) ->
|
||||||
[TimeNum, TimeUnit, Mod1, Fun1, ArgsType, Args1, InTimerType, Command, Ctl] =
|
[TimeNum, TimeUnit, Mod, Fun, Args, InTimerType] =
|
||||||
[proplists:get_value(Key, Task) || Key <- [time, units, module, function,
|
[proplists:get_value(Key, Task) || Key <- [time, units, module, function, arguments, timer_type]],
|
||||||
args_type, arguments, timer_type,
|
|
||||||
command, ctl]],
|
|
||||||
TimerType = case InTimerType of
|
TimerType = case InTimerType of
|
||||||
<<"fixed">> ->
|
<<"fixed">> ->
|
||||||
fixed;
|
fixed;
|
||||||
|
@ -155,10 +143,6 @@ add_task(Host, Task) ->
|
||||||
%% Get new task identifier
|
%% Get new task identifier
|
||||||
TaskId = get_new_taskid(),
|
TaskId = get_new_taskid(),
|
||||||
|
|
||||||
Args2 = parse_args_type(ArgsType, Args1),
|
|
||||||
|
|
||||||
{Mod, Fun, Args} = prepare_mfa(Mod1, Fun1, Args2, Command, Ctl),
|
|
||||||
|
|
||||||
TimerRef = case TimerType of
|
TimerRef = case TimerType of
|
||||||
interval ->
|
interval ->
|
||||||
begin_interval_timer(TaskId, TimeUnit, TimeNum, [Mod, Fun, Args]);
|
begin_interval_timer(TaskId, TimeUnit, TimeNum, [Mod, Fun, Args]);
|
||||||
|
@ -182,34 +166,6 @@ get_new_taskid() ->
|
||||||
Id -> Id + 1
|
Id -> Id + 1
|
||||||
end.
|
end.
|
||||||
|
|
||||||
parse_args_type(_, undefined) ->
|
|
||||||
[];
|
|
||||||
parse_args_type(string, Args) ->
|
|
||||||
lists:map(fun(Arg) when is_binary(Arg) -> binary_to_list(Arg);
|
|
||||||
(Arg) -> Arg
|
|
||||||
end,
|
|
||||||
Args);
|
|
||||||
parse_args_type(_, Args) ->
|
|
||||||
Args.
|
|
||||||
|
|
||||||
parse_args_ctl(Ctl, Args2) ->
|
|
||||||
[[atom_to_list(Ctl) | Args2]].
|
|
||||||
|
|
||||||
parse_args_command(Command, Args2) ->
|
|
||||||
CI = #{caller_module => ?MODULE},
|
|
||||||
[Command, Args2, CI].
|
|
||||||
|
|
||||||
prepare_mfa(undefined, undefined, Args2, Command, undefined)
|
|
||||||
when Command /= undefined ->
|
|
||||||
{ejabberd_commands, execute_command2,
|
|
||||||
parse_args_command(Command, Args2)};
|
|
||||||
prepare_mfa(undefined, undefined, Args2, undefined, Ctl)
|
|
||||||
when Ctl /= undefined ->
|
|
||||||
{ejabberd_ctl, process,
|
|
||||||
parse_args_ctl(Ctl, parse_args_type(string, Args2))};
|
|
||||||
prepare_mfa(Mod1, Fun1, Args2, undefined, undefined) ->
|
|
||||||
{Mod1, Fun1, Args2}.
|
|
||||||
|
|
||||||
%% Method to run existing task
|
%% Method to run existing task
|
||||||
run_task(Mod, Fun, Args) ->
|
run_task(Mod, Fun, Args) ->
|
||||||
case catch apply(Mod, Fun, Args) of
|
case catch apply(Mod, Fun, Args) of
|
||||||
|
@ -275,7 +231,7 @@ cron_del(TaskId) ->
|
||||||
%% ---------------------
|
%% ---------------------
|
||||||
|
|
||||||
web_menu_host(Acc, _Host, Lang) ->
|
web_menu_host(Acc, _Host, Lang) ->
|
||||||
[{<<"cron">>, translate:translate(Lang, ?T("Cron Tasks"))} | Acc].
|
[{<<"cron">>, ?T(<<"Cron Tasks">>)} | Acc].
|
||||||
|
|
||||||
web_page_host(_, Host,
|
web_page_host(_, Host,
|
||||||
#request{path = [<<"cron">>],
|
#request{path = [<<"cron">>],
|
||||||
|
@ -289,13 +245,9 @@ web_page_host(Acc, _, _) -> Acc.
|
||||||
make_tasks_table(Tasks, Lang) ->
|
make_tasks_table(Tasks, Lang) ->
|
||||||
TList = lists:map(
|
TList = lists:map(
|
||||||
fun(T) ->
|
fun(T) ->
|
||||||
[TimeNum, TimeUnit, Mod, Fun, Args, InTimerType] =
|
{Time_num, Time_unit, Mod, Fun, Args} = T#task.task,
|
||||||
[proplists:get_value(Key, T#task.task)
|
|
||||||
|| Key <- [time, units, module, function, arguments, timer_type]],
|
|
||||||
?XE(<<"tr">>,
|
?XE(<<"tr">>,
|
||||||
[?XC(<<"td">>, list_to_binary(integer_to_list(TimeNum)++" "
|
[?XC(<<"td">>, list_to_binary(integer_to_list(Time_num) ++" " ++ atom_to_list(Time_unit))),
|
||||||
++atom_to_list(TimeUnit)++" "
|
|
||||||
++atom_to_list(InTimerType))),
|
|
||||||
?XC(<<"td">>, list_to_binary(atom_to_list(Mod))),
|
?XC(<<"td">>, list_to_binary(atom_to_list(Mod))),
|
||||||
?XC(<<"td">>, list_to_binary(atom_to_list(Fun))),
|
?XC(<<"td">>, list_to_binary(atom_to_list(Fun))),
|
||||||
?XC(<<"td">>, list_to_binary(io_lib:format("~p", [Args])))])
|
?XC(<<"td">>, list_to_binary(io_lib:format("~p", [Args])))])
|
||||||
|
|
|
@ -1,342 +0,0 @@
|
||||||
As a special exception, the authors give permission to link this program
|
|
||||||
with the OpenSSL library and distribute the resulting binary.
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 2, June 1991
|
|
||||||
|
|
||||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The licenses for most software are designed to take away your
|
|
||||||
freedom to share and change it. By contrast, the GNU General Public
|
|
||||||
License is intended to guarantee your freedom to share and change free
|
|
||||||
software--to make sure the software is free for all its users. This
|
|
||||||
General Public License applies to most of the Free Software
|
|
||||||
Foundation's software and to any other program whose authors commit to
|
|
||||||
using it. (Some other Free Software Foundation software is covered by
|
|
||||||
the GNU Lesser General Public License instead.) You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
this service if you wish), that you receive source code or can get it
|
|
||||||
if you want it, that you can change the software or use pieces of it
|
|
||||||
in new free programs; and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to make restrictions that forbid
|
|
||||||
anyone to deny you these rights or to ask you to surrender the rights.
|
|
||||||
These restrictions translate to certain responsibilities for you if you
|
|
||||||
distribute copies of the software, or if you modify it.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must give the recipients all the rights that
|
|
||||||
you have. You must make sure that they, too, receive or can get the
|
|
||||||
source code. And you must show them these terms so they know their
|
|
||||||
rights.
|
|
||||||
|
|
||||||
We protect your rights with two steps: (1) copyright the software, and
|
|
||||||
(2) offer you this license which gives you legal permission to copy,
|
|
||||||
distribute and/or modify the software.
|
|
||||||
|
|
||||||
Also, for each author's protection and ours, we want to make certain
|
|
||||||
that everyone understands that there is no warranty for this free
|
|
||||||
software. If the software is modified by someone else and passed on, we
|
|
||||||
want its recipients to know that what they have is not the original, so
|
|
||||||
that any problems introduced by others will not reflect on the original
|
|
||||||
authors' reputations.
|
|
||||||
|
|
||||||
Finally, any free program is threatened constantly by software
|
|
||||||
patents. We wish to avoid the danger that redistributors of a free
|
|
||||||
program will individually obtain patent licenses, in effect making the
|
|
||||||
program proprietary. To prevent this, we have made it clear that any
|
|
||||||
patent must be licensed for everyone's free use or not licensed at all.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. This License applies to any program or other work which contains
|
|
||||||
a notice placed by the copyright holder saying it may be distributed
|
|
||||||
under the terms of this General Public License. The "Program", below,
|
|
||||||
refers to any such program or work, and a "work based on the Program"
|
|
||||||
means either the Program or any derivative work under copyright law:
|
|
||||||
that is to say, a work containing the Program or a portion of it,
|
|
||||||
either verbatim or with modifications and/or translated into another
|
|
||||||
language. (Hereinafter, translation is included without limitation in
|
|
||||||
the term "modification".) Each licensee is addressed as "you".
|
|
||||||
|
|
||||||
Activities other than copying, distribution and modification are not
|
|
||||||
covered by this License; they are outside its scope. The act of
|
|
||||||
running the Program is not restricted, and the output from the Program
|
|
||||||
is covered only if its contents constitute a work based on the
|
|
||||||
Program (independent of having been made by running the Program).
|
|
||||||
Whether that is true depends on what the Program does.
|
|
||||||
|
|
||||||
1. You may copy and distribute verbatim copies of the Program's
|
|
||||||
source code as you receive it, in any medium, provided that you
|
|
||||||
conspicuously and appropriately publish on each copy an appropriate
|
|
||||||
copyright notice and disclaimer of warranty; keep intact all the
|
|
||||||
notices that refer to this License and to the absence of any warranty;
|
|
||||||
and give any other recipients of the Program a copy of this License
|
|
||||||
along with the Program.
|
|
||||||
|
|
||||||
You may charge a fee for the physical act of transferring a copy, and
|
|
||||||
you may at your option offer warranty protection in exchange for a fee.
|
|
||||||
|
|
||||||
2. You may modify your copy or copies of the Program or any portion
|
|
||||||
of it, thus forming a work based on the Program, and copy and
|
|
||||||
distribute such modifications or work under the terms of Section 1
|
|
||||||
above, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) You must cause the modified files to carry prominent notices
|
|
||||||
stating that you changed the files and the date of any change.
|
|
||||||
|
|
||||||
b) You must cause any work that you distribute or publish, that in
|
|
||||||
whole or in part contains or is derived from the Program or any
|
|
||||||
part thereof, to be licensed as a whole at no charge to all third
|
|
||||||
parties under the terms of this License.
|
|
||||||
|
|
||||||
c) If the modified program normally reads commands interactively
|
|
||||||
when run, you must cause it, when started running for such
|
|
||||||
interactive use in the most ordinary way, to print or display an
|
|
||||||
announcement including an appropriate copyright notice and a
|
|
||||||
notice that there is no warranty (or else, saying that you provide
|
|
||||||
a warranty) and that users may redistribute the program under
|
|
||||||
these conditions, and telling the user how to view a copy of this
|
|
||||||
License. (Exception: if the Program itself is interactive but
|
|
||||||
does not normally print such an announcement, your work based on
|
|
||||||
the Program is not required to print an announcement.)
|
|
||||||
|
|
||||||
These requirements apply to the modified work as a whole. If
|
|
||||||
identifiable sections of that work are not derived from the Program,
|
|
||||||
and can be reasonably considered independent and separate works in
|
|
||||||
themselves, then this License, and its terms, do not apply to those
|
|
||||||
sections when you distribute them as separate works. But when you
|
|
||||||
distribute the same sections as part of a whole which is a work based
|
|
||||||
on the Program, the distribution of the whole must be on the terms of
|
|
||||||
this License, whose permissions for other licensees extend to the
|
|
||||||
entire whole, and thus to each and every part regardless of who wrote it.
|
|
||||||
|
|
||||||
Thus, it is not the intent of this section to claim rights or contest
|
|
||||||
your rights to work written entirely by you; rather, the intent is to
|
|
||||||
exercise the right to control the distribution of derivative or
|
|
||||||
collective works based on the Program.
|
|
||||||
|
|
||||||
In addition, mere aggregation of another work not based on the Program
|
|
||||||
with the Program (or with a work based on the Program) on a volume of
|
|
||||||
a storage or distribution medium does not bring the other work under
|
|
||||||
the scope of this License.
|
|
||||||
|
|
||||||
3. You may copy and distribute the Program (or a work based on it,
|
|
||||||
under Section 2) in object code or executable form under the terms of
|
|
||||||
Sections 1 and 2 above provided that you also do one of the following:
|
|
||||||
|
|
||||||
a) Accompany it with the complete corresponding machine-readable
|
|
||||||
source code, which must be distributed under the terms of Sections
|
|
||||||
1 and 2 above on a medium customarily used for software interchange; or,
|
|
||||||
|
|
||||||
b) Accompany it with a written offer, valid for at least three
|
|
||||||
years, to give any third party, for a charge no more than your
|
|
||||||
cost of physically performing source distribution, a complete
|
|
||||||
machine-readable copy of the corresponding source code, to be
|
|
||||||
distributed under the terms of Sections 1 and 2 above on a medium
|
|
||||||
customarily used for software interchange; or,
|
|
||||||
|
|
||||||
c) Accompany it with the information you received as to the offer
|
|
||||||
to distribute corresponding source code. (This alternative is
|
|
||||||
allowed only for noncommercial distribution and only if you
|
|
||||||
received the program in object code or executable form with such
|
|
||||||
an offer, in accord with Subsection b above.)
|
|
||||||
|
|
||||||
The source code for a work means the preferred form of the work for
|
|
||||||
making modifications to it. For an executable work, complete source
|
|
||||||
code means all the source code for all modules it contains, plus any
|
|
||||||
associated interface definition files, plus the scripts used to
|
|
||||||
control compilation and installation of the executable. However, as a
|
|
||||||
special exception, the source code distributed need not include
|
|
||||||
anything that is normally distributed (in either source or binary
|
|
||||||
form) with the major components (compiler, kernel, and so on) of the
|
|
||||||
operating system on which the executable runs, unless that component
|
|
||||||
itself accompanies the executable.
|
|
||||||
|
|
||||||
If distribution of executable or object code is made by offering
|
|
||||||
access to copy from a designated place, then offering equivalent
|
|
||||||
access to copy the source code from the same place counts as
|
|
||||||
distribution of the source code, even though third parties are not
|
|
||||||
compelled to copy the source along with the object code.
|
|
||||||
|
|
||||||
4. You may not copy, modify, sublicense, or distribute the Program
|
|
||||||
except as expressly provided under this License. Any attempt
|
|
||||||
otherwise to copy, modify, sublicense or distribute the Program is
|
|
||||||
void, and will automatically terminate your rights under this License.
|
|
||||||
However, parties who have received copies, or rights, from you under
|
|
||||||
this License will not have their licenses terminated so long as such
|
|
||||||
parties remain in full compliance.
|
|
||||||
|
|
||||||
5. You are not required to accept this License, since you have not
|
|
||||||
signed it. However, nothing else grants you permission to modify or
|
|
||||||
distribute the Program or its derivative works. These actions are
|
|
||||||
prohibited by law if you do not accept this License. Therefore, by
|
|
||||||
modifying or distributing the Program (or any work based on the
|
|
||||||
Program), you indicate your acceptance of this License to do so, and
|
|
||||||
all its terms and conditions for copying, distributing or modifying
|
|
||||||
the Program or works based on it.
|
|
||||||
|
|
||||||
6. Each time you redistribute the Program (or any work based on the
|
|
||||||
Program), the recipient automatically receives a license from the
|
|
||||||
original licensor to copy, distribute or modify the Program subject to
|
|
||||||
these terms and conditions. You may not impose any further
|
|
||||||
restrictions on the recipients' exercise of the rights granted herein.
|
|
||||||
You are not responsible for enforcing compliance by third parties to
|
|
||||||
this License.
|
|
||||||
|
|
||||||
7. If, as a consequence of a court judgment or allegation of patent
|
|
||||||
infringement or for any other reason (not limited to patent issues),
|
|
||||||
conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot
|
|
||||||
distribute so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you
|
|
||||||
may not distribute the Program at all. For example, if a patent
|
|
||||||
license would not permit royalty-free redistribution of the Program by
|
|
||||||
all those who receive copies directly or indirectly through you, then
|
|
||||||
the only way you could satisfy both it and this License would be to
|
|
||||||
refrain entirely from distribution of the Program.
|
|
||||||
|
|
||||||
If any portion of this section is held invalid or unenforceable under
|
|
||||||
any particular circumstance, the balance of the section is intended to
|
|
||||||
apply and the section as a whole is intended to apply in other
|
|
||||||
circumstances.
|
|
||||||
|
|
||||||
It is not the purpose of this section to induce you to infringe any
|
|
||||||
patents or other property right claims or to contest validity of any
|
|
||||||
such claims; this section has the sole purpose of protecting the
|
|
||||||
integrity of the free software distribution system, which is
|
|
||||||
implemented by public license practices. Many people have made
|
|
||||||
generous contributions to the wide range of software distributed
|
|
||||||
through that system in reliance on consistent application of that
|
|
||||||
system; it is up to the author/donor to decide if he or she is willing
|
|
||||||
to distribute software through any other system and a licensee cannot
|
|
||||||
impose that choice.
|
|
||||||
|
|
||||||
This section is intended to make thoroughly clear what is believed to
|
|
||||||
be a consequence of the rest of this License.
|
|
||||||
|
|
||||||
8. If the distribution and/or use of the Program is restricted in
|
|
||||||
certain countries either by patents or by copyrighted interfaces, the
|
|
||||||
original copyright holder who places the Program under this License
|
|
||||||
may add an explicit geographical distribution limitation excluding
|
|
||||||
those countries, so that distribution is permitted only in or among
|
|
||||||
countries not thus excluded. In such case, this License incorporates
|
|
||||||
the limitation as if written in the body of this License.
|
|
||||||
|
|
||||||
9. The Free Software Foundation may publish revised and/or new versions
|
|
||||||
of the General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the Program
|
|
||||||
specifies a version number of this License which applies to it and "any
|
|
||||||
later version", you have the option of following the terms and conditions
|
|
||||||
either of that version or of any later version published by the Free
|
|
||||||
Software Foundation. If the Program does not specify a version number of
|
|
||||||
this License, you may choose any version ever published by the Free Software
|
|
||||||
Foundation.
|
|
||||||
|
|
||||||
10. If you wish to incorporate parts of the Program into other free
|
|
||||||
programs whose distribution conditions are different, write to the author
|
|
||||||
to ask for permission. For software which is copyrighted by the Free
|
|
||||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
|
||||||
make exceptions for this. Our decision will be guided by the two goals
|
|
||||||
of preserving the free status of all derivatives of our free software and
|
|
||||||
of promoting the sharing and reuse of software generally.
|
|
||||||
|
|
||||||
NO WARRANTY
|
|
||||||
|
|
||||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
|
||||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
|
||||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
|
||||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
|
||||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
|
||||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
|
||||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
|
||||||
REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
|
||||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
|
||||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
|
||||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
|
||||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
|
||||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGES.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
convey the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program is interactive, make it output a short notice like this
|
|
||||||
when it starts in an interactive mode:
|
|
||||||
|
|
||||||
Gnomovision version 69, Copyright (C) year name of author
|
|
||||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, the commands you use may
|
|
||||||
be called something other than `show w' and `show c'; they could even be
|
|
||||||
mouse-clicks or menu items--whatever suits your program.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or your
|
|
||||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
|
||||||
necessary. Here is a sample; alter the names:
|
|
||||||
|
|
||||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
|
||||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
|
||||||
|
|
||||||
<signature of Ty Coon>, 1 April 1989
|
|
||||||
Ty Coon, President of Vice
|
|
||||||
|
|
||||||
This General Public License does not permit incorporating your program into
|
|
||||||
proprietary programs. If your program is a subroutine library, you may
|
|
||||||
consider it more useful to permit linking proprietary applications with the
|
|
||||||
library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License.
|
|
|
@ -1,41 +0,0 @@
|
||||||
mod_default_contacts - Add roster contact(s) on registration
|
|
||||||
============================================================
|
|
||||||
|
|
||||||
* Author: Holger Weiss <holger@zedat.fu-berlin.de>
|
|
||||||
|
|
||||||
|
|
||||||
Description
|
|
||||||
-----------
|
|
||||||
|
|
||||||
This module allows for specifying one or more contacts that should be
|
|
||||||
added to the user's roster automatically on successful registration (via
|
|
||||||
`mod_register`, or, for example, `ejabberdctl register`). Note that no
|
|
||||||
presence subscription is performed, and the rosters of the contacts aren't
|
|
||||||
modified.
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
In order to use this module, add a configuration snippet such as the
|
|
||||||
following:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
modules:
|
|
||||||
mod_default_contacts:
|
|
||||||
contacts:
|
|
||||||
-
|
|
||||||
name: "Bob Virding"
|
|
||||||
jid: bob@example.com
|
|
||||||
-
|
|
||||||
name: "Alice Armstrong"
|
|
||||||
jid: alice@example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
The configurable `mod_default_contacts` options are:
|
|
||||||
|
|
||||||
- `contacts` (default: `[]`)
|
|
||||||
|
|
||||||
The list of contact JIDs that should be auto-added to the user's roster
|
|
||||||
on account registration. Each list item must specify the `jid:` and
|
|
||||||
optionally the `name:` of the contact.
|
|
|
@ -1,3 +0,0 @@
|
||||||
#modules:
|
|
||||||
# mod_default_contacts:
|
|
||||||
# contacts: []
|
|
|
@ -1,5 +0,0 @@
|
||||||
author: "Holger Weiss <holger at zedat.fu-berlin.de>"
|
|
||||||
category: "roster"
|
|
||||||
summary: "Auto-add roster contacts on registration"
|
|
||||||
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
|
|
||||||
url: "git@github.com:processone/ejabberd-contrib.git"
|
|
|
@ -1,87 +0,0 @@
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
%%% File : mod_default_contacts.erl
|
|
||||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
|
||||||
%%% Purpose : Auto-add contacts on registration
|
|
||||||
%%% Created : 14 May 2019 by Holger Weiss <holger@zedat.fu-berlin.de>
|
|
||||||
%%%
|
|
||||||
%%%
|
|
||||||
%%% ejabberd, Copyright (C) 2019-2020 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_default_contacts).
|
|
||||||
-author('holger@zedat.fu-berlin.de').
|
|
||||||
-behavior(gen_mod).
|
|
||||||
|
|
||||||
%% gen_mod callbacks.
|
|
||||||
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2, mod_options/1,
|
|
||||||
mod_doc/0]).
|
|
||||||
|
|
||||||
%% ejabberd_hooks callbacks.
|
|
||||||
-export([register_user/2]).
|
|
||||||
|
|
||||||
-include("logger.hrl").
|
|
||||||
-include_lib("xmpp/include/xmpp.hrl").
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% gen_mod callbacks.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
-spec start(binary(), gen_mod:opts()) -> ok.
|
|
||||||
start(Host, _Opts) ->
|
|
||||||
ejabberd_hooks:add(register_user, Host, ?MODULE, register_user, 50).
|
|
||||||
|
|
||||||
-spec stop(binary()) -> ok.
|
|
||||||
stop(Host) ->
|
|
||||||
ejabberd_hooks:delete(register_user, Host, ?MODULE, register_user, 50).
|
|
||||||
|
|
||||||
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
|
|
||||||
reload(_Host, _NewOpts, _OldOpts) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
-spec mod_opt_type(atom()) -> econf:validator().
|
|
||||||
mod_opt_type(contacts) ->
|
|
||||||
econf:list(
|
|
||||||
econf:and_then(
|
|
||||||
econf:options(
|
|
||||||
#{jid => econf:jid(),
|
|
||||||
name => econf:binary()},
|
|
||||||
[{required, [jid]}]),
|
|
||||||
fun(Opts) ->
|
|
||||||
Jid = proplists:get_value(jid, Opts),
|
|
||||||
Name = proplists:get_value(name, Opts, <<>>),
|
|
||||||
#roster_item{jid = Jid, name = Name}
|
|
||||||
end)).
|
|
||||||
|
|
||||||
-spec mod_options(binary()) -> [{atom(), any()}].
|
|
||||||
mod_options(_Host) ->
|
|
||||||
[{contacts, []}].
|
|
||||||
|
|
||||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
|
||||||
depends(_Host, _Opts) ->
|
|
||||||
[{mod_roster, hard}].
|
|
||||||
|
|
||||||
mod_doc() ->
|
|
||||||
#{}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% ejabberd_hooks callbacks.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
-spec register_user(binary(), binary()) -> any().
|
|
||||||
register_user(LUser, LServer) ->
|
|
||||||
?DEBUG("Auto-creating roster entries for ~s@~s", [LUser, LServer]),
|
|
||||||
Items = gen_mod:get_module_opt(LServer, ?MODULE, contacts),
|
|
||||||
mod_roster:set_items(LUser, LServer, #roster_query{items = Items}).
|
|
|
@ -1,342 +0,0 @@
|
||||||
As a special exception, the authors give permission to link this program
|
|
||||||
with the OpenSSL library and distribute the resulting binary.
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 2, June 1991
|
|
||||||
|
|
||||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The licenses for most software are designed to take away your
|
|
||||||
freedom to share and change it. By contrast, the GNU General Public
|
|
||||||
License is intended to guarantee your freedom to share and change free
|
|
||||||
software--to make sure the software is free for all its users. This
|
|
||||||
General Public License applies to most of the Free Software
|
|
||||||
Foundation's software and to any other program whose authors commit to
|
|
||||||
using it. (Some other Free Software Foundation software is covered by
|
|
||||||
the GNU Lesser General Public License instead.) You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
this service if you wish), that you receive source code or can get it
|
|
||||||
if you want it, that you can change the software or use pieces of it
|
|
||||||
in new free programs; and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to make restrictions that forbid
|
|
||||||
anyone to deny you these rights or to ask you to surrender the rights.
|
|
||||||
These restrictions translate to certain responsibilities for you if you
|
|
||||||
distribute copies of the software, or if you modify it.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must give the recipients all the rights that
|
|
||||||
you have. You must make sure that they, too, receive or can get the
|
|
||||||
source code. And you must show them these terms so they know their
|
|
||||||
rights.
|
|
||||||
|
|
||||||
We protect your rights with two steps: (1) copyright the software, and
|
|
||||||
(2) offer you this license which gives you legal permission to copy,
|
|
||||||
distribute and/or modify the software.
|
|
||||||
|
|
||||||
Also, for each author's protection and ours, we want to make certain
|
|
||||||
that everyone understands that there is no warranty for this free
|
|
||||||
software. If the software is modified by someone else and passed on, we
|
|
||||||
want its recipients to know that what they have is not the original, so
|
|
||||||
that any problems introduced by others will not reflect on the original
|
|
||||||
authors' reputations.
|
|
||||||
|
|
||||||
Finally, any free program is threatened constantly by software
|
|
||||||
patents. We wish to avoid the danger that redistributors of a free
|
|
||||||
program will individually obtain patent licenses, in effect making the
|
|
||||||
program proprietary. To prevent this, we have made it clear that any
|
|
||||||
patent must be licensed for everyone's free use or not licensed at all.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. This License applies to any program or other work which contains
|
|
||||||
a notice placed by the copyright holder saying it may be distributed
|
|
||||||
under the terms of this General Public License. The "Program", below,
|
|
||||||
refers to any such program or work, and a "work based on the Program"
|
|
||||||
means either the Program or any derivative work under copyright law:
|
|
||||||
that is to say, a work containing the Program or a portion of it,
|
|
||||||
either verbatim or with modifications and/or translated into another
|
|
||||||
language. (Hereinafter, translation is included without limitation in
|
|
||||||
the term "modification".) Each licensee is addressed as "you".
|
|
||||||
|
|
||||||
Activities other than copying, distribution and modification are not
|
|
||||||
covered by this License; they are outside its scope. The act of
|
|
||||||
running the Program is not restricted, and the output from the Program
|
|
||||||
is covered only if its contents constitute a work based on the
|
|
||||||
Program (independent of having been made by running the Program).
|
|
||||||
Whether that is true depends on what the Program does.
|
|
||||||
|
|
||||||
1. You may copy and distribute verbatim copies of the Program's
|
|
||||||
source code as you receive it, in any medium, provided that you
|
|
||||||
conspicuously and appropriately publish on each copy an appropriate
|
|
||||||
copyright notice and disclaimer of warranty; keep intact all the
|
|
||||||
notices that refer to this License and to the absence of any warranty;
|
|
||||||
and give any other recipients of the Program a copy of this License
|
|
||||||
along with the Program.
|
|
||||||
|
|
||||||
You may charge a fee for the physical act of transferring a copy, and
|
|
||||||
you may at your option offer warranty protection in exchange for a fee.
|
|
||||||
|
|
||||||
2. You may modify your copy or copies of the Program or any portion
|
|
||||||
of it, thus forming a work based on the Program, and copy and
|
|
||||||
distribute such modifications or work under the terms of Section 1
|
|
||||||
above, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) You must cause the modified files to carry prominent notices
|
|
||||||
stating that you changed the files and the date of any change.
|
|
||||||
|
|
||||||
b) You must cause any work that you distribute or publish, that in
|
|
||||||
whole or in part contains or is derived from the Program or any
|
|
||||||
part thereof, to be licensed as a whole at no charge to all third
|
|
||||||
parties under the terms of this License.
|
|
||||||
|
|
||||||
c) If the modified program normally reads commands interactively
|
|
||||||
when run, you must cause it, when started running for such
|
|
||||||
interactive use in the most ordinary way, to print or display an
|
|
||||||
announcement including an appropriate copyright notice and a
|
|
||||||
notice that there is no warranty (or else, saying that you provide
|
|
||||||
a warranty) and that users may redistribute the program under
|
|
||||||
these conditions, and telling the user how to view a copy of this
|
|
||||||
License. (Exception: if the Program itself is interactive but
|
|
||||||
does not normally print such an announcement, your work based on
|
|
||||||
the Program is not required to print an announcement.)
|
|
||||||
|
|
||||||
These requirements apply to the modified work as a whole. If
|
|
||||||
identifiable sections of that work are not derived from the Program,
|
|
||||||
and can be reasonably considered independent and separate works in
|
|
||||||
themselves, then this License, and its terms, do not apply to those
|
|
||||||
sections when you distribute them as separate works. But when you
|
|
||||||
distribute the same sections as part of a whole which is a work based
|
|
||||||
on the Program, the distribution of the whole must be on the terms of
|
|
||||||
this License, whose permissions for other licensees extend to the
|
|
||||||
entire whole, and thus to each and every part regardless of who wrote it.
|
|
||||||
|
|
||||||
Thus, it is not the intent of this section to claim rights or contest
|
|
||||||
your rights to work written entirely by you; rather, the intent is to
|
|
||||||
exercise the right to control the distribution of derivative or
|
|
||||||
collective works based on the Program.
|
|
||||||
|
|
||||||
In addition, mere aggregation of another work not based on the Program
|
|
||||||
with the Program (or with a work based on the Program) on a volume of
|
|
||||||
a storage or distribution medium does not bring the other work under
|
|
||||||
the scope of this License.
|
|
||||||
|
|
||||||
3. You may copy and distribute the Program (or a work based on it,
|
|
||||||
under Section 2) in object code or executable form under the terms of
|
|
||||||
Sections 1 and 2 above provided that you also do one of the following:
|
|
||||||
|
|
||||||
a) Accompany it with the complete corresponding machine-readable
|
|
||||||
source code, which must be distributed under the terms of Sections
|
|
||||||
1 and 2 above on a medium customarily used for software interchange; or,
|
|
||||||
|
|
||||||
b) Accompany it with a written offer, valid for at least three
|
|
||||||
years, to give any third party, for a charge no more than your
|
|
||||||
cost of physically performing source distribution, a complete
|
|
||||||
machine-readable copy of the corresponding source code, to be
|
|
||||||
distributed under the terms of Sections 1 and 2 above on a medium
|
|
||||||
customarily used for software interchange; or,
|
|
||||||
|
|
||||||
c) Accompany it with the information you received as to the offer
|
|
||||||
to distribute corresponding source code. (This alternative is
|
|
||||||
allowed only for noncommercial distribution and only if you
|
|
||||||
received the program in object code or executable form with such
|
|
||||||
an offer, in accord with Subsection b above.)
|
|
||||||
|
|
||||||
The source code for a work means the preferred form of the work for
|
|
||||||
making modifications to it. For an executable work, complete source
|
|
||||||
code means all the source code for all modules it contains, plus any
|
|
||||||
associated interface definition files, plus the scripts used to
|
|
||||||
control compilation and installation of the executable. However, as a
|
|
||||||
special exception, the source code distributed need not include
|
|
||||||
anything that is normally distributed (in either source or binary
|
|
||||||
form) with the major components (compiler, kernel, and so on) of the
|
|
||||||
operating system on which the executable runs, unless that component
|
|
||||||
itself accompanies the executable.
|
|
||||||
|
|
||||||
If distribution of executable or object code is made by offering
|
|
||||||
access to copy from a designated place, then offering equivalent
|
|
||||||
access to copy the source code from the same place counts as
|
|
||||||
distribution of the source code, even though third parties are not
|
|
||||||
compelled to copy the source along with the object code.
|
|
||||||
|
|
||||||
4. You may not copy, modify, sublicense, or distribute the Program
|
|
||||||
except as expressly provided under this License. Any attempt
|
|
||||||
otherwise to copy, modify, sublicense or distribute the Program is
|
|
||||||
void, and will automatically terminate your rights under this License.
|
|
||||||
However, parties who have received copies, or rights, from you under
|
|
||||||
this License will not have their licenses terminated so long as such
|
|
||||||
parties remain in full compliance.
|
|
||||||
|
|
||||||
5. You are not required to accept this License, since you have not
|
|
||||||
signed it. However, nothing else grants you permission to modify or
|
|
||||||
distribute the Program or its derivative works. These actions are
|
|
||||||
prohibited by law if you do not accept this License. Therefore, by
|
|
||||||
modifying or distributing the Program (or any work based on the
|
|
||||||
Program), you indicate your acceptance of this License to do so, and
|
|
||||||
all its terms and conditions for copying, distributing or modifying
|
|
||||||
the Program or works based on it.
|
|
||||||
|
|
||||||
6. Each time you redistribute the Program (or any work based on the
|
|
||||||
Program), the recipient automatically receives a license from the
|
|
||||||
original licensor to copy, distribute or modify the Program subject to
|
|
||||||
these terms and conditions. You may not impose any further
|
|
||||||
restrictions on the recipients' exercise of the rights granted herein.
|
|
||||||
You are not responsible for enforcing compliance by third parties to
|
|
||||||
this License.
|
|
||||||
|
|
||||||
7. If, as a consequence of a court judgment or allegation of patent
|
|
||||||
infringement or for any other reason (not limited to patent issues),
|
|
||||||
conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot
|
|
||||||
distribute so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you
|
|
||||||
may not distribute the Program at all. For example, if a patent
|
|
||||||
license would not permit royalty-free redistribution of the Program by
|
|
||||||
all those who receive copies directly or indirectly through you, then
|
|
||||||
the only way you could satisfy both it and this License would be to
|
|
||||||
refrain entirely from distribution of the Program.
|
|
||||||
|
|
||||||
If any portion of this section is held invalid or unenforceable under
|
|
||||||
any particular circumstance, the balance of the section is intended to
|
|
||||||
apply and the section as a whole is intended to apply in other
|
|
||||||
circumstances.
|
|
||||||
|
|
||||||
It is not the purpose of this section to induce you to infringe any
|
|
||||||
patents or other property right claims or to contest validity of any
|
|
||||||
such claims; this section has the sole purpose of protecting the
|
|
||||||
integrity of the free software distribution system, which is
|
|
||||||
implemented by public license practices. Many people have made
|
|
||||||
generous contributions to the wide range of software distributed
|
|
||||||
through that system in reliance on consistent application of that
|
|
||||||
system; it is up to the author/donor to decide if he or she is willing
|
|
||||||
to distribute software through any other system and a licensee cannot
|
|
||||||
impose that choice.
|
|
||||||
|
|
||||||
This section is intended to make thoroughly clear what is believed to
|
|
||||||
be a consequence of the rest of this License.
|
|
||||||
|
|
||||||
8. If the distribution and/or use of the Program is restricted in
|
|
||||||
certain countries either by patents or by copyrighted interfaces, the
|
|
||||||
original copyright holder who places the Program under this License
|
|
||||||
may add an explicit geographical distribution limitation excluding
|
|
||||||
those countries, so that distribution is permitted only in or among
|
|
||||||
countries not thus excluded. In such case, this License incorporates
|
|
||||||
the limitation as if written in the body of this License.
|
|
||||||
|
|
||||||
9. The Free Software Foundation may publish revised and/or new versions
|
|
||||||
of the General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the Program
|
|
||||||
specifies a version number of this License which applies to it and "any
|
|
||||||
later version", you have the option of following the terms and conditions
|
|
||||||
either of that version or of any later version published by the Free
|
|
||||||
Software Foundation. If the Program does not specify a version number of
|
|
||||||
this License, you may choose any version ever published by the Free Software
|
|
||||||
Foundation.
|
|
||||||
|
|
||||||
10. If you wish to incorporate parts of the Program into other free
|
|
||||||
programs whose distribution conditions are different, write to the author
|
|
||||||
to ask for permission. For software which is copyrighted by the Free
|
|
||||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
|
||||||
make exceptions for this. Our decision will be guided by the two goals
|
|
||||||
of preserving the free status of all derivatives of our free software and
|
|
||||||
of promoting the sharing and reuse of software generally.
|
|
||||||
|
|
||||||
NO WARRANTY
|
|
||||||
|
|
||||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
|
||||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
|
||||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
|
||||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
|
||||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
|
||||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
|
||||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
|
||||||
REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
|
||||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
|
||||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
|
||||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
|
||||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
|
||||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGES.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
convey the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program is interactive, make it output a short notice like this
|
|
||||||
when it starts in an interactive mode:
|
|
||||||
|
|
||||||
Gnomovision version 69, Copyright (C) year name of author
|
|
||||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, the commands you use may
|
|
||||||
be called something other than `show w' and `show c'; they could even be
|
|
||||||
mouse-clicks or menu items--whatever suits your program.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or your
|
|
||||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
|
||||||
necessary. Here is a sample; alter the names:
|
|
||||||
|
|
||||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
|
||||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
|
||||||
|
|
||||||
<signature of Ty Coon>, 1 April 1989
|
|
||||||
Ty Coon, President of Vice
|
|
||||||
|
|
||||||
This General Public License does not permit incorporating your program into
|
|
||||||
proprietary programs. If your program is a subroutine library, you may
|
|
||||||
consider it more useful to permit linking proprietary applications with the
|
|
||||||
library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License.
|
|
|
@ -1,39 +0,0 @@
|
||||||
mod_default_rooms - Add MUC bookmark(s) on registration
|
|
||||||
=======================================================
|
|
||||||
|
|
||||||
* Author: Holger Weiss <holger@zedat.fu-berlin.de>
|
|
||||||
|
|
||||||
|
|
||||||
Description
|
|
||||||
-----------
|
|
||||||
|
|
||||||
This module allows for specifying one or more rooms that should be
|
|
||||||
bookmarked automatically on successful user registration (via
|
|
||||||
`mod_register`, or, for example, `ejabberdctl register`).
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
In order to use this module, add a configuration snippet such as the
|
|
||||||
following:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
modules:
|
|
||||||
mod_default_rooms:
|
|
||||||
rooms:
|
|
||||||
- foo@conference.example.net
|
|
||||||
- bar@conference.example.org
|
|
||||||
```
|
|
||||||
|
|
||||||
The configurable `mod_default_rooms` options are:
|
|
||||||
|
|
||||||
- `rooms` (default: `[]`)
|
|
||||||
|
|
||||||
The list of rooms users that should be auto-bookmarked on account
|
|
||||||
registration.
|
|
||||||
|
|
||||||
- `auto_join` (default: `true`)
|
|
||||||
|
|
||||||
This option specifies whether the auto-join flag should be set for the
|
|
||||||
bookmarks created on registration.
|
|
|
@ -1,3 +0,0 @@
|
||||||
#modules:
|
|
||||||
# mod_default_rooms:
|
|
||||||
# rooms: []
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue