Compare commits

...

109 Commits

Author SHA1 Message Date
Jerome Sautret 724d09a510 Set ejabberd version to 20.04 2020-04-29 16:29:59 +02:00
Badlop fba6a64648 Fix English typos in configure.ac 2020-04-29 12:07:35 +02:00
Jérôme Sautret 0539637d30 Merge pull request #3232 from weiss/enable-stun
Enable STUN/TURN support by default
2020-04-29 10:29:05 +02:00
Badlop 4a7d42647f Rewrite sentences in modules options examples, to not break Docs indentation 2020-04-28 21:31:35 +02:00
Paweł Chmielowski b56663ef07 Update dependences in mix 2020-04-28 17:23:36 +02:00
Paweł Chmielowski 25597a4326 Run tests for mssql only if configured with --enable-mssql 2020-04-28 16:52:08 +02:00
Paweł Chmielowski 56c8f6b280 Update deps 2020-04-28 16:24:40 +02:00
ChaosKid42 abc3260e75 enable tests with mssql-backend (#3136) 2020-04-28 16:22:42 +02:00
Jérôme Sautret 24a11fc8e8 Merge pull request #3235 from weiss/xep-0215
Support STUN/TURN service discovery
2020-04-28 16:03:21 +02:00
Holger Weiss 6eb2f07274 ejabberd_stun: Tone down 'auth_realm' warning
These days, STUN/TURN authentication can be performed with ephemeral
credentials, where the REALM is irrelevant. Therefore, just log an
[info] message rather than a [warning] in the case where no
authentication REALM is configured but multiple virtual domains exist.
2020-04-28 10:34:43 +02:00
Holger Weiss 9cd47d7085 Add tests for mod_stun_disco 2020-04-28 10:34:43 +02:00
Holger Weiss 69d1d62add Support XEP-0215: External Service Discovery
Add the 'mod_stun_disco' module, which allows XMPP clients to discover
STUN/TURN services and to obtain temporary credentials for using them as
per XEP-0215: External Service Discovery.  The temporary credentials
handed out to clients have the format described in:

https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00

Also add the new module to the example configuration file.

Closes #2947.
2020-04-28 10:34:43 +02:00
Badlop 3db9459591 Don't offer X-OAUTH2 if the only auth method enabled is Anonymous (#3209) 2020-04-27 20:03:21 +02:00
Paweł Chmielowski 6320dfd34e Don't store caps information for direct presences of muc room
We really don't need those, and thanks to each individual room having
different hash (as one of hashed data is room description) we end with
lot of data that we really don't need.
2020-04-27 13:17:51 +02:00
Badlop 055fe744d3 Clean some unused functions in ejabberd_ctl, this makes "make hooks" happy 2020-04-24 20:36:24 +02:00
Christoph Scholz d7c696f97c mix.exs: Update 'xmpp' and 'stun' 2020-04-23 20:05:40 +02:00
Holger Weiss fc444ce503 rebar.config: Update 'xmpp' and 'stun'
Use the current versions of 'xmpp' (to get XEP-0215 support) and 'stun'
(to fix TURN issues).
2020-04-23 20:05:40 +02:00
Holger Weiss 88f392721b gen_mod: Reload modules after reloading listeners
Make sure modules won't be reloaded before listeners.  This is necessary
to allow the (not yet committed) 'mod_stun_disco' module to parse the
listener configuration after configuration reloads.
2020-04-23 20:05:40 +02:00
Holger Weiss c55e7b8499 ejabberd_stun: Fix 'turn_ip' fallback
The 'turn_ip' option validator doesn't accept an inet:ip4_address()
tuple.

While at it, change the logic to only perform the fallback address
lookup if no 'turn_ip' is configured (analogous to the fallback
mechanism for the case where the 'auth_realm' is undefined).
2020-04-23 18:32:40 +02:00
ChaosKid42 1f7ca91670 use dsn-less config for mssql (#3131) 2020-04-23 13:56:41 +02:00
Licaon_Kter d9131c854d Bump jiffy so it compiles on older GCC (#3218)
* Update jiffy

* And here
2020-04-23 13:29:33 +02:00
Holger Weiss 09a87f5a0c ejabberd_stun: Handle hashed passwords gracefully
Don't crash when STUN/TURN authentication is performed against a
SCRAM-hashed password.
2020-04-22 00:16:03 +02:00
Holger Weiss 1db70edcf8 ejabberd_stun: Add 'stun_get_password' hook
Add a hook that allows modules to offer a password for STUN/TURN
authentication.
2020-04-22 00:09:42 +02:00
Badlop 1a3533e4a2 Fix some English typos 2020-04-21 20:58:01 +02:00
Badlop 3db9de26e9 Rephrase mod_admin_extra doc, a2x screwed the format when building Docs 2020-04-21 20:11:39 +02:00
Badlop 78f0439e78 Make a few more strings translatable in MUC and Shared Roster WebAdmin 2020-04-20 20:24:56 +02:00
Badlop b124e911d3 Update some translated strings where only print chars had changed 2020-04-20 20:24:50 +02:00
Holger Weiss c836dc66a8 ejabberd_stun: Set a default 'turn_ip'
Try to resolve the local hostname, use the result as the default
'turn_ip', and only log a warning if that fails.  Using the local
hostname's address by default is analogous to mod_proxy65's behavior.
2020-04-20 08:42:32 +02:00
Holger Weiss b1b3c4cdcf Enable STUN/TURN by default
Build ejabberd with STUN/TURN support by default, and add a STUN/TURN
listener to the example configuration file.
2020-04-20 00:37:41 +02:00
Holger Weiss b0f95975c2 Travis CI: Test against Erlang/OTP 22.3 2020-04-19 15:39:34 +02:00
Badlop 2e48c24638 Updated Spanish translation 2020-04-17 19:29:17 +02:00
Badlop 7359eb6246 Updated Catalan translation 2020-04-17 19:23:21 +02:00
Badlop 99d21bca49 Don't extract for translation strings from man pages, at least for now 2020-04-17 19:23:18 +02:00
Badlop 1b98084918 Fix previous commit 2020-04-17 19:23:13 +02:00
Badlop d311eaf8f3 Log messages generated by msgmerge and display unexpected ones 2020-04-17 17:28:43 +02:00
Badlop 0355e15a42 Fix doc content in mod_admin_extra so it can be extracted by make translations 2020-04-17 17:28:39 +02:00
Badlop 101f7a6d63 Check if mod_muc_log is enabled before setting logging option (#3215) 2020-04-17 16:19:58 +02:00
Badlop 4aa85c538c When rescode is some unexpected, probably error message, print it 2020-04-17 16:19:55 +02:00
Paweł Chmielowski 22980ed8a5 Restart offline pop_messages when there is mismatch between select and delete
When another connection is inserting something to spool at this same time
as we do pop_messages, it's possible that insert will happen between we
fetch messages and delete them, so we effectively will delete it without
delivering it to client. This change catch this situation and restart
transaction, so we should always have consistent results.
2020-04-17 15:30:28 +02:00
Paweł Chmielowski cb1c0a3188 Update mysql driver to get rid of warning 2020-04-16 18:26:13 +02:00
Badlop 0705695e02 Update documentation of mod_shared_roster (#3214) 2020-04-16 13:12:32 +02:00
Paweł Chmielowski c11922e2a2 Make session iq response have from be set to server jid
It looks like old version of Smack don't accept request that are have
from sent to sender jid, but are only working when jid is set to server
address. This is also how it looks in old xmpp rfc examples.
2020-04-16 13:05:42 +02:00
Paweł Chmielowski 37226dd41f Resending unacked stanzas should send even archived msgs if mod_offline is enabled
Messages that are received when no c2s is active will be stored in offline,
even when mam archived them, so i don't think we should be doing something
different in this case.
2020-04-16 13:04:12 +02:00
Badlop cd0b65f4d5 Fix unused variables from previous commit 2020-04-14 15:00:45 +02:00
Badlop b7c088d4b0 Update links to the ejabberd Docs page in WebAdmin 2020-04-14 13:59:11 +02:00
Badlop e197b25e82 Rename opts->name to label, to avoid confusion with the group name (#3214)
Also updated WebAdmin to show more meaningful explanations
Also fixed a bug that break support for group@host in Displayed
2020-04-14 13:58:53 +02:00
Jérôme Sautret b02506eaaf Merge pull request #3132 from area-42/publisher_mssql_text_to_varchar
change PubSub publisher from text to varchar for mssql
2020-04-10 16:20:43 +02:00
Badlop 8694517c34 Minor fixes in doc 2020-04-09 16:30:21 +02:00
Badlop 2febd1c220 Copy more option explanations from ejabberd Docs site 2020-04-08 18:49:41 +02:00
Badlop aa0ed37034 Add ejabberd version number to man pages 2020-04-08 18:48:09 +02:00
Badlop da18245d9a Indicate which ejabberd version is used to produce the page 2020-04-08 18:47:50 +02:00
Badlop 5cc9a1fe44 Don't make commands subsections, so Docs TOC plage is cleaner 2020-04-08 18:45:29 +02:00
Badlop de0aead1cd Fix set_loglevel example argument documentation 2020-04-08 18:44:09 +02:00
Badlop 624ba7e94f Improve formatting of mod_announce doc 2020-04-08 18:42:45 +02:00
Paweł Chmielowski 9bb3aee0e2 Make resumed sessions try to deliver possibly queued messages to new session
Between receiving resume request and being closed by new session, it's
possible (even if not very likely) that new messages would arrive to
process that is resumed. In that case try to reroute messages that were
received after we sent resume reply to new process.
2020-04-07 14:51:49 +02:00
Paweł Chmielowski 16585713f8 Log errors that happen when retrieving http headers in ejabberd_http
It seems that ssl errors can be generated here, so lets have abily to show
them instead of swallowing them silently.
2020-04-07 13:50:01 +02:00
Holger Weiss e01e528235 mod_carboncopy: Bump supported XEP revision
Since mod_carboncopy supports "urn:xmpp:carbons:rules:0", it implements
version 0.13.x of XEP-0280.
2020-04-05 22:52:55 +02:00
Paweł Chmielowski eac7e3488c Remove bash-izm from ejabberdctl.template introduced recently 2020-04-03 17:28:27 +02:00
Jerome Sautret 762486d199 Limit number of atoms used by ejabberdctl ( #2977) 2020-04-02 15:51:16 +02:00
Badlop 23493ce239 Document mod_shared_roster_ldap options 2020-04-02 12:56:43 +02:00
Badlop 510ab53341 Add ejabberd_auth_http auth_opts brief description 2020-04-02 12:56:40 +02:00
Badlop 220cf73318 Document sql_prepared_statements 2020-04-02 12:56:38 +02:00
Badlop f6d102f5e2 Quick document with forward link api_permissions 2020-04-02 12:56:36 +02:00
Badlop 05b68764cc Document some global options 2020-04-02 12:56:33 +02:00
Badlop 4e51e82ccf Add three missing mod_bosh options 2020-04-02 12:56:31 +02:00
Badlop 116fa8e9ca add missing mod_mam options 2020-04-02 12:56:28 +02:00
Badlop ce6fd654a0 Fix mod_pubsub indentation 2020-04-02 12:56:26 +02:00
Badlop 5ee2f48aea Add mod_pubsub configuration documentation 2020-04-02 12:56:23 +02:00
Paweł Chmielowski ccb47a67c4 Don't replace %25 in webadmin test on older erlangs
It seems that is a bug in R21+ httpc, so let's try to keep that test
working with older versions
2020-04-01 17:11:19 +02:00
Paweł Chmielowski a2e6d8bb6b Make stop_ejabberd test also work without receiving </stream:stream> 2020-04-01 15:34:06 +02:00
Paweł Chmielowski 1bd560f3f2 Fix potential message loss in terminating c2s sessions
Calling sync version of xmpp_stream_in/out:stop could lead to messages
never being processed by c2s process if they were queued in p1_server.

This could be reproduced by when having messages in offline storage,
starting sessions, enabling stream_mgmt, sending initial presence, and then immediately
</stream:stream>, messages that mod_offline would send process would not
be bounced back by stream_mgmt.
2020-04-01 14:36:01 +02:00
Badlop 222bb1d55d Use different username than other tests, but still include the test chars 2020-04-01 12:50:52 +02:00
Paweł Chmielowski a5ea3fa282 Better error reporting in pubsub tests 2020-04-01 11:45:01 +02:00
Paweł Chmielowski 6c52438128 Make webadmin tests use different user for changepassword/unregister
Using username that is shared with other tests causes login problems in
other places.
2020-04-01 11:44:38 +02:00
Badlop 0508dce2ed Add more webadmin tests 2020-03-31 19:28:36 +02:00
Boris Chernov 87dda1b638 sql_type should be taken for LServer, not LHost (#3202)
sql_type option should be retrieved for the main domain, not the MUC subdomain
2020-03-30 09:47:36 +02:00
Paweł Chmielowski 5ec214386e Make webadmin redirect to page that end with / 2020-03-26 14:17:48 +01:00
Paweł Chmielowski 73ba38ae35 Revert "Pass base path instead of level to support URL missing slash (#3177)"
This reverts commit e9d1201ea8.
2020-03-26 13:43:24 +01:00
Paweł Chmielowski 1ffa9a0cf5 Do not change to attribute of sent messages from bare to full jid
This is not correct per xmpp spec
2020-03-25 16:00:16 +01:00
Jerome Sautret 633b362577 Increase version to 20.3.0 2020-03-25 11:35:38 +01:00
Badlop 150b7e7219 Fix unsubscribe command result, handle_sync_event result, and dialyzer
How to reproduce the problems fixed by this commit:
Create temporary room, other account subscribes, and owner leaves
Unsubscribe that account with the command: ejabberdctl unsubscribe_room ...
2020-03-24 11:44:22 +01:00
Paweł Chmielowski e6065bf08f Update changelog 2020-03-23 17:55:11 +01:00
Paweł Chmielowski c2aa5f77bf Update mix.lock 2020-03-23 17:42:57 +01:00
Paweł Chmielowski 7caec56e96 Make bounce_message_queue wait for 100ms for incoming messages
There is possibilty that between c2s process unregistering itself from sm
and terminating, some other process could try to send something to c2s,
which could result in messages to triggering mam/offline hooks, and causing
them not to be stored in any way.
2020-03-23 15:16:33 +01:00
Paweł Chmielowski 97354426cf Make mod_muc_room:unsubscirbe handle that unsubscribe may stop room 2020-03-23 13:16:48 +01:00
Paweł Chmielowski 63e3fb92d1 Better handling of xml parse errors in send_stanza 2020-03-23 12:59:30 +01:00
Paweł Chmielowski a2d1ffffe6 Add baisc tests for webadmin 2020-03-23 10:52:07 +01:00
Paweł Chmielowski f17d4c0adc Update deps 2020-03-19 12:11:46 +01:00
Paweł Chmielowski 92a09fdb71 Also add "escape '\'" to prepared statements in pgsqllike_escape 2020-03-18 14:36:17 +01:00
Paweł Chmielowski 039d786e1f Add escape '\' to like expression in pgsql to fix problem cockroachdb
Cockroachdb doesn't properly handle escaping of _ in like expressions,
having "like ... escape '\'" makes it work, by disabling optimization that
causes this broken behaviour
2020-03-18 14:31:13 +01:00
Paweł Chmielowski 2d707cc0d2 Fix type of computed field in node_flat sql query 2020-03-18 14:05:05 +01:00
Paweł Chmielowski 0a88d03dc9 Use correct type for seconds field in mod_last sql queries 2020-03-18 14:05:05 +01:00
Paweł Chmielowski f12ee28660 Change conversion of boolean values for cockroachdb 2020-03-18 14:05:05 +01:00
Badlop 260c289d34 Fix Dialyzer warning that jid can't be 'undefined' 2020-03-18 12:02:16 +01:00
Paweł Chmielowski d8899ca9ac Add cache to mod_shared_roster
This should help with excessive queries that sql backend generates
Should fix issue #3158.
2020-03-17 14:35:43 +01:00
Paweł Chmielowski 1e456065f6 Fix issue with family field on cockroachdb 2020-03-17 11:35:54 +01:00
Paweł Chmielowski 82074190fb Replace mod_shared_roster:X call with just X inside that module 2020-03-16 14:57:55 +01:00
Paweł Chmielowski 6fe7c5cac5 Try to limit calls to groups_with_opts in mod_shared_roster 2020-03-16 13:29:05 +01:00
Badlop e9d1201ea8 Pass base path instead of level to support URL missing slash (#3177) 2020-03-11 16:26:33 +01:00
Paweł Chmielowski 9a89b360c0 fix command rooms_empty_destroy (#3183) 2020-03-05 11:41:51 +01:00
Paweł Chmielowski b39a1e2d74 Add reload handler to ejabberd_auth_ldap
This will restart ldap process with new options, and should made it
recognize new values.

This fixes issue #3181
2020-03-04 13:19:41 +01:00
Paweł Chmielowski 151b818af4 Use compilation flags used during build to compile modules in ext_mod
This fixes issue #3178
2020-03-03 11:25:17 +01:00
Paweł Chmielowski df47e2a93f Fix list parameters in sql queries on pgsql 2020-02-27 11:10:30 +01:00
Mickaël Rémond d0e93f9219 Merge pull request #3125 from area-42/enable_odbc_in_mix
enable odbc in mix build
2020-02-26 14:45:01 +01:00
Badlop 47c5aba1e5 Allow mod_register_web to be accessed from now-served vhosts (#3173) 2020-02-26 13:57:19 +01:00
Badlop 00abf5d42c Fix handle of 'http' atom in Headers, problem introduced in 357e7e11 2020-02-26 13:56:38 +01:00
Paweł Chmielowski 9c25d1024a Support ssl connection on mysql 2020-02-26 10:54:04 +01:00
Christoph Scholz 248cc2d013 change publisher from text to varchar for mssql 2019-12-28 15:49:37 +01:00
Christoph Scholz 8eccbade56 enable odbc in mix build 2019-12-25 00:38:48 +01:00
116 changed files with 3835 additions and 1815 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ language: erlang
otp_release:
- 19.3
- 22.2
- 22.3
services:
- redis-server
+20
View File
@@ -1,3 +1,23 @@
# Version 20.03
* Changes in this version
- Add support of ssl connection when connection to mysql
database (configured with `sql_ssl: true` option)
- Experimental support for cockroachdb when configured
with postgres connector
- Add cache and optimize queries issued by `mod_shared_roster`,
this should greatly improve performance of this module when
used with `sql` backend
- Fix problem with accessing webadmin
- Make webadmin work even when url is missing trailing slash
- When compiling external modules with ext_mod, use flags
that were detected during compilation of ejabberd
- Make config changed to ldap options be updated when issued
`reload_config` command
- Fix `room_empty_destory` command
- Fix reporting errors in `send_stanza` command when xml
passed to it couldn't be passed correctly
# Version 20.02
* Changes in this version
+7 -7
View File
@@ -103,8 +103,8 @@ esac],[full_xml=false])
AC_ARG_ENABLE(mssql,
[AC_HELP_STRING([--enable-mssql], [use Microsoft SQL Server database (default: no, requires --enable-odbc)])],
[case "${enableval}" in
yes) db_type=mssql ;;
no) db_type=generic ;;
yes) db_type=mssql; mssql=true ;;
no) db_type=generic; mssql=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-mssql) ;;
esac],[db_type=generic])
@@ -197,7 +197,7 @@ AC_ARG_ENABLE(debug,
esac],[if test "x$debug" = "x"; then debug=true; fi])
AC_ARG_ENABLE(latest_deps,
[AC_HELP_STRING([--enable-latest-deps], [makes rebar use latest commits for dependences instead of tagged versions (default: no)])],
[AC_HELP_STRING([--enable-latest-deps], [makes rebar use latest commits for dependencies instead of tagged versions (default: no)])],
[case "${enableval}" in
yes) latest_deps=true ;;
no) latest_deps=false ;;
@@ -205,7 +205,7 @@ AC_ARG_ENABLE(latest_deps,
esac],[if test "x$latest_deps" = "x"; then latest_deps=false; fi])
AC_ARG_ENABLE(system_deps,
[AC_HELP_STRING([--enable-system-deps], [makes rebar use localy installed dependences instead of downloading them (default: no)])],
[AC_HELP_STRING([--enable-system-deps], [makes rebar use locally installed dependencies instead of downloading them (default: no)])],
[case "${enableval}" in
yes) system_deps=true ;;
no) system_deps=false ;;
@@ -213,12 +213,12 @@ AC_ARG_ENABLE(system_deps,
esac],[if test "x$system_deps" = "x"; then system_deps=false; fi])
AC_ARG_ENABLE(stun,
[AC_HELP_STRING([--enable-stun], [enable STUN/TURN support (default: no)])],
[AC_HELP_STRING([--enable-stun], [enable STUN/TURN support (default: yes)])],
[case "${enableval}" in
yes) stun=true ;;
no) stun=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-stun) ;;
esac],[if test "x$stun" = "x"; then stun=false; fi])
esac],[if test "x$stun" = "x"; then stun=true; fi])
AC_ARG_ENABLE(sip,
[AC_HELP_STRING([--enable-sip], [enable SIP support (default: no)])],
@@ -267,7 +267,7 @@ if test "$sqlite" = "true"; then
fi
enabled_backends=""
for backend in odbc mysql pgsql sqlite redis; do
for backend in odbc mysql pgsql sqlite redis mssql; do
if eval test x\${$backend} = xtrue; then
if test "x$enabled_backends" = "x"; then
enabled_backends=$backend
+8
View File
@@ -57,6 +57,13 @@ listen:
request_handlers:
/admin: ejabberd_web_admin
/.well-known/acme-challenge: ejabberd_acme
-
port: 3478
transport: udp
module: ejabberd_stun
use_turn: true
## The server's public IPv4 address:
# turn_ip: 203.0.113.3
-
port: 1883
ip: "::"
@@ -203,6 +210,7 @@ modules:
mod_shared_roster: {}
mod_stream_mgmt:
resend_on_timeout: if_offline
mod_stun_disco: {}
mod_vcard: {}
mod_vcard_xupdate: {}
mod_version:
+1 -1
View File
@@ -198,7 +198,7 @@ uid()
uuid=$(uuidgen 2>/dev/null)
[ -z "$uuid" ] && [ -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
[ -z "$uuid" ] && uuid=$(printf "%X" "${RANDOM:-$$}$(date +%M%S)")
uuid=${uuid%%-*}
uuid=$(printf '%s' $uuid | sed 's/^\(...\).*$/\1/')
[ $# -eq 0 ] && echo "${uuid}-${ERLANG_NODE}"
[ $# -eq 1 ] && echo "${uuid}-${1}-${ERLANG_NODE}"
[ $# -eq 2 ] && echo "${uuid}-${1}@${2}"
+1
View File
@@ -21,6 +21,7 @@
-record(request,
{method :: method(),
path = [] :: [binary()],
raw_path :: binary(),
q = [] :: [{binary() | nokey, binary()}],
us = {<<>>, <<>>} :: {binary(), binary()},
auth :: {binary(), binary()} | {oauth, binary(), []} | undefined | invalid,
+2 -2
View File
@@ -93,9 +93,9 @@
-define(GL(Ref, Title),
?XAE(<<"div">>, [{<<"class">>, <<"guidelink">>}],
[?XAE(<<"a">>,
[{<<"href">>, <<"https://docs.ejabberd.im/admin/configuration/#", Ref/binary>>},
[{<<"href">>, <<"https://docs.ejabberd.im/admin/configuration/", Ref/binary>>},
{<<"target">>, <<"_blank">>}],
[?C(<<"[Guide: ", Title/binary, "]">>)])])).
[?C(<<"docs: ", Title/binary>>)])])).
%% h1 with a Guide Link
-define(H1GL(Name, Ref, Title),
+21 -7
View File
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "20.2.0",
version: "20.4.0",
description: description(),
elixir: "~> 1.4",
elixirc_paths: ["lib"],
@@ -51,12 +51,25 @@ defmodule Ejabberd.Mixfile do
end
end
defp if_version_below(ver, okResult) do
if :erlang.system_info(:otp_release) < ver do
okResult
else
[]
end
end
defp erlc_options do
# Use our own includes + includes from all dependencies
includes = ["include"] ++ deps_include(["fast_xml", "xmpp", "p1_utils"])
[:debug_info, {:d, :ELIXIR_ENABLED}] ++ cond_options() ++ Enum.map(includes, fn(path) -> {:i, path} end) ++
if_version_above('20', [{:d, :DEPRECATED_GET_STACKTRACE}]) ++
if_function_exported(:erl_error, :format_exception, 6, [{:d, :HAVE_ERL_ERROR}])
result = [:debug_info, {:d, :ELIXIR_ENABLED}] ++
cond_options() ++
Enum.map(includes, fn (path) -> {:i, path} end) ++
if_version_above('20', [{:d, :DEPRECATED_GET_STACKTRACE}]) ++
if_version_below('22', [{:d, :LAGER}]) ++
if_function_exported(:erl_error, :format_exception, 6, [{:d, :HAVE_ERL_ERROR}])
defines = for {:d, value} <- result, do: {:d, value}
result ++ [{:d, :ALL_DEFS, defines}]
end
defp cond_options do
@@ -72,17 +85,17 @@ defmodule Ejabberd.Mixfile do
[{:lager, "~> 3.6.0"},
{:p1_utils, "~> 1.0"},
{:fast_xml, "~> 1.1"},
{:xmpp, "~> 1.4"},
{:xmpp, ">= 1.4.6"},
{:cache_tab, "~> 1.0"},
{:stringprep, "~> 1.0"},
{:fast_yaml, "~> 1.0"},
{:fast_tls, "~> 1.1"},
{:stun, "~> 1.0"},
{:esip, "~> 1.0"},
{:esip, "~> 1.0.32"},
{:p1_mysql, "~> 1.0"},
{:mqtree, "~> 1.0"},
{:p1_pgsql, "~> 1.1"},
{:jiffy, "~> 1.0"},
{:jiffy, "~> 1.0.4"},
{:p1_oauth2, "~> 0.6.1"},
{:distillery, "~> 2.0"},
{:pkix, "~> 1.0"},
@@ -120,6 +133,7 @@ defmodule Ejabberd.Mixfile do
defp cond_apps do
for {:true, app} <- [{config(:redis), :eredis},
{config(:mysql), :p1_mysql},
{config(:odbc), :odbc},
{config(:pgsql), :p1_pgsql},
{config(:sqlite), :sqlite3},
{config(:zlib), :ezlib}], do:
+35 -35
View File
@@ -1,38 +1,38 @@
%{
"artificery": {:hex, :artificery, "0.4.2", "3ded6e29e13113af52811c72f414d1e88f711410cac1b619ab3a2666bbd7efd4", [:mix], [], "hexpm"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"cache_tab": {:hex, :cache_tab, "1.0.22", "ad16577e7f26709cacdcb86e6a4960c8d158cab9d189cdf49cc1e2dc33106a70", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"distillery": {:hex, :distillery, "2.1.1", "f9332afc2eec8a1a2b86f22429e068ef35f84a93ea1718265e740d90dd367814", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm"},
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"epam": {:hex, :epam, "1.0.6", "6e57e1f5a330fa02a08ee0d4b16d9161f95177351e48c6dfede2f89b7e2f589f", [:rebar3], [], "hexpm"},
"artificery": {:hex, :artificery, "0.4.2", "3ded6e29e13113af52811c72f414d1e88f711410cac1b619ab3a2666bbd7efd4", [:mix], [], "hexpm", "514586f4312ef3709a3ccbd8e55f69455add235c1729656687bb781d10d0afdb"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"},
"cache_tab": {:hex, :cache_tab, "1.0.22", "ad16577e7f26709cacdcb86e6a4960c8d158cab9d189cdf49cc1e2dc33106a70", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "53ed75e7c289434953a4407eb0a40b8675c5046b057313f97cf10e196efd8d79"},
"distillery": {:hex, :distillery, "2.1.1", "f9332afc2eec8a1a2b86f22429e068ef35f84a93ea1718265e740d90dd367814", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm", "bbc7008b0161a6f130d8d903b5b3232351fccc9c31a991f8fcbf2a12ace22995"},
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
"epam": {:hex, :epam, "1.0.7", "55889bbfdc5ab9f2e785a710229f34e550784c5ead1960d7839ea77514aef44d", [:rebar3], [], "hexpm", "6b029ebd2b244bc339cbf5cb5908d0f2d50e43f33a6e7f70818912ea5d3fd596"},
"eredis": {:hex, :eredis, "1.2.0", "0b8e9cfc2c00fa1374cd107ea63b49be08d933df2cf175e6a89b73dd9c380de4", [:rebar3], [], "hexpm"},
"esip": {:hex, :esip, "1.0.32", "b6d5d9eb8342b86509de02ac79e6a9a772dab011e936092441d4e92a7986ca29", [:rebar3], [{:fast_tls, "1.1.4", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.31", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ezlib": {:hex, :ezlib, "1.0.7", "c8adffd32e66831df77955d163d705cdcf0a3d66762e6f68f8123012e714bf05", [:rebar3], [], "hexpm"},
"fast_tls": {:hex, :fast_tls, "1.1.4", "a0320baf14be72fc9f99211543e411bb98077bf72c42e2d86fc4e2c10d60c258", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"fast_xml": {:hex, :fast_xml, "1.1.39", "687080c0190a8c45d564a3576201f1a89f31ae413dd700a2def0821736f98d4d", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"fast_yaml": {:hex, :fast_yaml, "1.0.23", "0c74d6274c232609467bf55563066840c265e70081ee0c23215d1f3ca2624dfc", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jiffy": {:hex, :jiffy, "1.0.1", "4f25639772ca41202f41ba9c8f6ca0933554283dd4742c90651e03471c55e341", [:rebar3], [], "hexpm"},
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
"lager": {:hex, :lager, "3.6.10", "6172b43ab720ac33914ccd0aeb21fdbdf88213847707d4b91e6af57b2ae5c4d2", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
"luerl": {:hex, :luerl, "0.3.1", "5412807630aac1aaf59ffe5a1bc09259c447b4faeb1d3fe2d4ef41b87676cb04", [:rebar3], [], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"mqtree": {:hex, :mqtree, "1.0.7", "0d8f6101eb2bb6a6e27f0e5a60cfad04b27dd552e75f30294e565605ce7cd0d2", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"},
"p1_acme": {:hex, :p1_acme, "1.0.4", "2d118dbc38e7bc8eda34f4c5bf7afa6bce1345affc022bba514f42e194818820", [:rebar3], [{:idna, "~>6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "~>1.0.1", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "~>1.9.0", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "~>1.0.3", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm"},
"p1_mysql": {:hex, :p1_mysql, "1.0.13", "6a17bfd7a33d035673d633572e93370bdd2fcf4077362ed13b1a8fd8176a1643", [:rebar3], [], "hexpm"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.6", "b17053bd7a34621f9a1a7327285a3e37abd38eb1d176afccc8cfc39882ff0a44", [:rebar3], [], "hexpm"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.9", "07ff9b037954dec06b4e30e33a82ac69a5a513e2860d2e59b7f6f4af23493c45", [:rebar3], [], "hexpm"},
"p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm"},
"pkix": {:hex, :pkix, "1.0.5", "407c02c70191d0791cd9b422ac2380df5f7f8304ec26a6d3b06e0e02be688fca", [:rebar3], [], "hexpm"},
"sqlite3": {:hex, :sqlite3, "1.1.6", "4ea71af0b45908b5f02c9b09e4c87177039ef404f20accb35049cd8924cc417c", [:rebar3], [], "hexpm"},
"stringprep": {:hex, :stringprep, "1.0.19", "79761de42960a625fb0cd6d31686f6118aef30540a7abb884b92f72861b6adde", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"stun": {:hex, :stun, "1.0.31", "577d845d4b77b155bad234598c2056f6e182f178468727de083bedf275dc83a1", [:rebar3], [{:fast_tls, "1.1.4", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"xmpp": {:hex, :xmpp, "1.4.5", "b226baa9ad960e8de041289b94bbcb6148a7980acc0c1ec58dfc8f24acded3ad", [:rebar3], [{:ezlib, "1.0.7", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.4", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.39", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.19", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm"},
"yconf": {:hex, :yconf, "1.0.3", "7f71d0fe0e95ecb0f4004acee7b7db46c13e38c216d0bd03ef2a595a898d21a3", [:rebar3], [{:fast_yaml, "1.0.23", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm"},
"esip": {:hex, :esip, "1.0.33", "d3c78bfb291f52e11d6955ecb29cb194a55eb0c4ce7ecf407619698005b815e3", [:rebar3], [{:fast_tls, "1.1.5", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.32", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "d09addd003dbe078832a2af6d72367b374a6c495f35fbe54b09bff338f4803be"},
"ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"},
"ezlib": {:hex, :ezlib, "1.0.7", "c8adffd32e66831df77955d163d705cdcf0a3d66762e6f68f8123012e714bf05", [:rebar3], [], "hexpm", "5634b9f7112837f9338a61da1993601f4ab81615de84ff0baddcdc5a3fe940dc"},
"fast_tls": {:hex, :fast_tls, "1.1.5", "e1f60d8b415aa36cae1fc405e14c3f5ff069bb30f04f298287e8a8aa25efe01c", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "b4edad6a10b30827f819238cc10c1f82839797256f5791ab4b41bfe3e362f144"},
"fast_xml": {:hex, :fast_xml, "1.1.40", "1e44357f9862d86cee4e0a9b9892463096092c0b8b5ee295822309fabbceb063", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d74864613e479fd5b0e9ebf8cf17f745e3133aa5314dd722d5e617850f473ac6"},
"fast_yaml": {:hex, :fast_yaml, "1.0.24", "d304799e6b961a21a509449830193154870b2b526cfc2e7046e9953ad413765f", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "71f4d5f868a2cfd4794e6bbd89495c4e4c54c45c6cb65b7f12d96271d6c02c84"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm", "99cb4128cffcb3227581e5d4d803d5413fa643f4eb96523f77d9e6937d994ceb"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"jiffy": {:hex, :jiffy, "1.0.4", "72adeff75c52a2ff07de738f0813768abe7ce158026cc1115a170340259c0caa", [:rebar3], [], "hexpm", "113e5299ee4e6b9f40204256d7bbbd1caf646edeaef31ef0f7f5f842c0dad39e"},
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm", "6429c4fee52b2dda7861ee19a4f09c8c1ffa213bee3a1ec187828fde95d447ed"},
"lager": {:hex, :lager, "3.6.10", "6172b43ab720ac33914ccd0aeb21fdbdf88213847707d4b91e6af57b2ae5c4d2", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm", "5d10499461826b79c5abee18bb594b3949cbdf76d9d9fd7e66d0a558137c21c9"},
"luerl": {:hex, :luerl, "0.3.1", "5412807630aac1aaf59ffe5a1bc09259c447b4faeb1d3fe2d4ef41b87676cb04", [:rebar3], [], "hexpm", "1bc011c7297e43aec762e53b17ecb15b0ff29f9546cd153110b343cf5b043f5f"},
"makeup": {:hex, :makeup, "1.0.1", "82f332e461dc6c79dbd82fbe2a9c10d48ed07146f0a478286e590c83c52010b5", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "49736fe5b66a08d8575bf5321d716bac5da20c8e6b97714fec2bcd6febcfa1f8"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
"mqtree": {:hex, :mqtree, "1.0.7", "0d8f6101eb2bb6a6e27f0e5a60cfad04b27dd552e75f30294e565605ce7cd0d2", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "317db0349a8d9695bc89ef7062e9654e93347cd9576f827739650e26482825bb"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
"p1_acme": {:hex, :p1_acme, "1.0.5", "de54353100ed82d0c820fbc011b7a7ad54f65af052eb8112922ad8be8eadf8f1", [:rebar3], [{:idna, "~>6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "~>1.0.1", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "~>1.9.0", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "~>1.0.4", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "cbecfc70ce11d37d679875117c685aa2a7bc2502bfa51722c8e619602c92a0c0"},
"p1_mysql": {:hex, :p1_mysql, "1.0.15", "d24ac3cc154012733801ff4f7781e7ab7843dc85cbad61e757fad601a5d0b511", [:rebar3], [], "hexpm", "4a97e0c93a8bd61acad9a6f7894a6cc31881309cb87540a4734e4c78be41df9c"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.6", "b17053bd7a34621f9a1a7327285a3e37abd38eb1d176afccc8cfc39882ff0a44", [:rebar3], [], "hexpm", "8a5fd16fc581a50e62176ab8b78b83b6e7cc6f76f7f59f75f58d713b7c1ca7b2"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.9", "07ff9b037954dec06b4e30e33a82ac69a5a513e2860d2e59b7f6f4af23493c45", [:rebar3], [], "hexpm", "81aab8cff0203250dd3d9cc77a0232dc9f8e56c99fd742abbaedc51a0fd633a7"},
"p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
"pkix": {:hex, :pkix, "1.0.5", "407c02c70191d0791cd9b422ac2380df5f7f8304ec26a6d3b06e0e02be688fca", [:rebar3], [], "hexpm", "b86aed212afaf019ac97bf56857366e5f01c3003f38ee050af8ba16455e13719"},
"sqlite3": {:hex, :sqlite3, "1.1.6", "4ea71af0b45908b5f02c9b09e4c87177039ef404f20accb35049cd8924cc417c", [:rebar3], [], "hexpm", "cf9fa59c5b27de0d5d94a2ef464521379e23d8c6e9fa939abf8415c767f514bb"},
"stringprep": {:hex, :stringprep, "1.0.19", "79761de42960a625fb0cd6d31686f6118aef30540a7abb884b92f72861b6adde", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "3f77edbcb530899faffe95d57b6e2bac704a5a6ea1ead5957387f9e4ed8c5f07"},
"stun": {:hex, :stun, "1.0.32", "c1bf6c3ef4b6304c423541b2734adcfa46e265d96119b14f2a390da7119d0a42", [:rebar3], [{:fast_tls, "1.1.5", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "4178cf7514dd1df05502199b6d68ed8dc568d8cfa9dbad36a890843431190aac"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
"xmpp": {:hex, :xmpp, "1.4.6", "99b24010ed9ba6423887c65a8686566f4e5408f0c7a75ef624aea1ac612034af", [:rebar3], [{:ezlib, "1.0.7", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.5", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.40", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.19", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "355d2d9eac87fc75c5290a94386a8c7b25b2874b3889e80e0b6af40bd0bb8adf"},
"yconf": {:hex, :yconf, "1.0.4", "f08dcc2ad041f68580e98753f70453976d256f2c1a40a29a985465ab16d489a6", [:rebar3], [{:fast_yaml, "1.0.24", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "44570111ad224ee4eec6e2bffa1e7223ef4b76f91f9dd2f768eee214d2dcabe2"},
}
-1
View File
@@ -243,7 +243,6 @@ p[dir=ltr] a {
background: #3eaffa;
text-transform: uppercase;
font-size: 0.75em;
color: #fff;
}
+18 -16
View File
@@ -60,7 +60,7 @@
{"Client acknowledged more stanzas than sent by server","El client ha reconegut més paquets dels que ha enviat el servidor"}.
{"Commands","Comandaments"}.
{"Conference room does not exist","La sala de conferències no existeix"}.
{"Configuration of room ~ts","Configuració de la sala ~ts"}.
{"Configuration of room ~s","Configuració de la sala ~s"}.
{"Configuration","Configuració"}.
{"Connected Resources:","Recursos connectats:"}.
{"Country","Pais"}.
@@ -79,7 +79,8 @@
{"Delete User","Eliminar Usuari"}.
{"Description:","Descripció:"}.
{"Disc only copy","Còpia sols en disc"}.
{"Displayed Groups:","Mostrar grups:"}.
{"'Displayed groups' not added (they do not exist!): ","'Mostrats' no afegits (no existeixen!): "}.
{"Displayed:","Mostrats:"}.
{"Don't tell your password to anybody, not even the administrators of the Jabber server.","No li donis la teva contrasenya a ningú, ni tan sols als administradors del servidor Jabber."}.
{"Dump Backup to Text File at ","Exporta còpia de seguretat a fitxer de text en "}.
{"Dump to Text File","Exportar a fitxer de text"}.
@@ -116,7 +117,7 @@
{"Failed to extract JID from your voice request approval","No s'ha pogut extraure el JID de la teva aprovació de petició de veu"}.
{"Failed to map delegated namespace to external component","Ha fallat mapejar la delegació de l'espai de noms al component extern"}.
{"Failed to parse HTTP response","Ha fallat el processat de la resposta HTTP"}.
{"Failed to process option '~ts'","Ha fallat el processat de la opció '~ts'"}.
{"Failed to process option '~s'","Ha fallat el processat de la opció '~s'"}.
{"Family Name","Cognom"}.
{"February","Febrer"}.
{"File larger than ~w bytes","El fitxer es més gran que ~w bytes"}.
@@ -132,7 +133,7 @@
{"Get User Password","Obtenir Contrasenya d'usuari"}.
{"Get User Statistics","Obtenir Estadístiques d'Usuari"}.
{"Given Name","Nom propi"}.
{"Group ","Grup "}.
{"Group","Grup"}.
{"Groups","Grups"}.
{"has been banned","ha sigut bloquejat"}.
{"has been kicked because of a system shutdown","ha sigut expulsat perquè el sistema va a apagar-se"}.
@@ -169,7 +170,7 @@
{"Invitations are not allowed in this conference","Les invitacions no estan permeses en aquesta sala de conferència"}.
{"IP addresses","Adreça IP"}.
{"is now known as","ara es conegut com"}.
{"It is not allowed to send error messages to the room. The participant (~ts) has sent an error message (~ts) and got kicked from the room","No està permés enviar missatges d'error a la sala. El participant (~ts) ha enviat un missatge d'error (~ts) i ha sigut expulsat de la sala"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","No està permés enviar missatges d'error a la sala. El participant (~s) ha enviat un missatge d'error (~s) i ha sigut expulsat de la sala"}.
{"It is not allowed to send private messages of type \"groupchat\"","No està permés enviar missatges del tipus \"groupchat\""}.
{"It is not allowed to send private messages to the conference","No està permès l'enviament de missatges privats a la sala"}.
{"It is not allowed to send private messages","No està permés enviar missatges privats"}.
@@ -181,6 +182,7 @@
{"joins the room","entra a la sala"}.
{"July","Juliol"}.
{"June","Juny"}.
{"Label:","Etiqueta:"}.
{"Last Activity","Última activitat"}.
{"Last login","Últim login"}.
{"Last month","Últim mes"}.
@@ -200,7 +202,7 @@
{"March","Març"}.
{"Maximum Number of Occupants","Número màxim d'ocupants"}.
{"May","Maig"}.
{"Members not added (inexistent vhost): ","Membres no afegits (perque el vhost no existeix): "}.
{"Members not added (inexistent vhost!): ","Membres no afegits (perquè el vhost no existeix): "}.
{"Membership is required to enter this room","Necessites ser membre d'aquesta sala per a poder entrar"}.
{"Members:","Membre:"}.
{"Memorize your password, or write it in a paper placed in a safe place. In Jabber there isn't an automated way to recover your password if you forget it.","Memoritza la teva contrasenya, o escriu-la en un paper guardat a un lloc segur.A Jabber no hi ha una forma automatitzada de recuperar la teva contrasenya si la oblides."}.
@@ -224,7 +226,7 @@
{"New Password:","Nova Contrasenya:"}.
{"Nickname can't be empty","El sobrenom no pot estar buit"}.
{"Nickname Registration at ","Registre del sobrenom en "}.
{"Nickname ~ts does not exist in the room","El sobrenom ~ts no existeix a la sala"}.
{"Nickname ~s does not exist in the room","El sobrenom ~s no existeix a la sala"}.
{"Nickname","Sobrenom"}.
{"No address elements found","No s'han trobat elements d'adreces ('address')"}.
{"No addresses element found","No s'ha trobat l'element d'adreces ('addresses')"}.
@@ -349,6 +351,7 @@
{"Roster","Llista de contactes"}.
{"RPC Call Error","Error de cridada RPC"}.
{"Running Nodes","Nodes funcionant"}.
{"~s invites you to the room ~s","~s et convida a la sala ~s"}.
{"Saturday","Dissabte"}.
{"Script check","Comprovar script"}.
{"Search Results for ","Resultats de la búsqueda "}.
@@ -409,12 +412,12 @@
{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Aquesta pàgina permet crear un compte Jabber en aquest servidor Jabber. El teu JID (Jabber IDentifier; Identificador Jabber) tindrà aquesta forma: usuari@servidor. Si us plau, llegeix amb cura les instruccions per emplenar correctament els camps."}.
{"This page allows to unregister a Jabber account in this Jabber server.","Aquesta pàgina permet anul·lar el registre d'un compte Jabber en aquest servidor Jabber."}.
{"This room is not anonymous","Aquesta sala no és anònima"}.
{"This service can not process the address: ~ts","Este servei no pot processar la direcció: ~ts"}.
{"This service can not process the address: ~s","Este servei no pot processar la direcció: ~s"}.
{"Thursday","Dijous"}.
{"Time delay","Temps de retard"}.
{"Timed out waiting for stream resumption","Massa temps esperant que es resumisca la connexió"}.
{"Time","Data"}.
{"To register, visit ~ts","Per a registrar-te, visita ~ts"}.
{"To register, visit ~s","Per a registrar-te, visita ~s"}.
{"To ~ts","A ~ts"}.
{"Token TTL","Token TTL"}.
{"Too many active bytestreams","N'hi ha massa Bytestreams actius"}.
@@ -422,7 +425,7 @@
{"Too many child elements","N'hi ha massa subelements"}.
{"Too many <item/> elements","N'hi ha massa elements <item/>"}.
{"Too many <list/> elements","N'hi ha massa elements <list/>"}.
{"Too many (~p) failed authentications from this IP address (~ts). The address will be unblocked at ~ts UTC","Massa autenticacions (~p) han fallat des d'aquesta adreça IP (~ts). L'adreça serà desbloquejada en ~ts UTC"}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Massa autenticacions (~p) han fallat des d'aquesta adreça IP (~s). L'adreça serà desbloquejada en ~s UTC"}.
{"Too many receiver fields were specified","S'han especificat massa camps de receptors"}.
{"Too many unacked stanzas","Massa missatges sense haver reconegut la seva recepció"}.
{"Too many users in this conference","N'hi ha massa usuaris en esta sala de conferència"}.
@@ -433,7 +436,6 @@
{"Transactions Committed:","Transaccions Realitzades:"}.
{"Transactions Logged:","Transaccions registrades:"}.
{"Transactions Restarted:","Transaccions reiniciades:"}.
{"~ts invites you to the room ~ts","~ts et convida a la sala ~ts"}.
{"~ts's Offline Messages Queue","~ts's cua de missatges offline"}.
{"Tuesday","Dimarts"}.
{"Unable to generate a CAPTCHA","No s'ha pogut generar un CAPTCHA"}.
@@ -467,12 +469,11 @@
{"User","Usuari"}.
{"Validate","Validar"}.
{"Value 'get' of 'type' attribute is not allowed","El valor 'get' a l'atribut 'type' no és permès"}.
{"Value of '~ts' should be boolean","El valor de '~ts' deuria ser booleà"}.
{"Value of '~ts' should be datetime string","El valor de '~ts' deuria ser una data"}.
{"Value of '~ts' should be integer","El valor de '~ts' deuria ser un numero enter"}.
{"Value of '~s' should be boolean","El valor de '~s' deuria ser booleà"}.
{"Value of '~s' should be datetime string","El valor de '~s' deuria ser una data"}.
{"Value of '~s' should be integer","El valor de '~s' deuria ser un numero enter"}.
{"Value 'set' of 'type' attribute is not allowed","El valor 'set' a l'atribut 'type' no és permès"}.
{"vCard User Search","vCard recerca d'usuari"}.
{"Virtual Hosting","Hosts virtuals"}.
{"Virtual Hosts","Hosts virtuals"}.
{"Visitors are not allowed to change their nicknames in this room","Els visitants no tenen permés canviar el seus Nicknames en esta sala"}.
{"Visitors are not allowed to send messages to all occupants","Els visitants no poden enviar missatges a tots els ocupants"}.
@@ -481,6 +482,7 @@
{"Wednesday","Dimecres"}.
{"Wrong parameters in the web formulary","Paràmetres incorrectes en el formulari web"}.
{"Wrong xmlns","El xmlns ès incorrecte"}.
{"XMPP Domains","Dominis XMPP"}.
{"You are being removed from the room because of a system shutdown","Has sigut expulsat de la sala perquè el sistema va a apagar-se"}.
{"You are not joined to the channel","No t'has unit al canal"}.
{"You can later change your password using a Jabber client.","Podràs canviar la teva contrasenya més endavant utilitzant un client Jabber."}.
@@ -494,5 +496,5 @@
{"Your contact offline message queue is full. The message has been discarded.","La teua cua de missatges offline és plena. El missatge ha sigut descartat."}.
{"Your Jabber account was successfully created.","El teu compte Jabber ha sigut creat correctament."}.
{"Your Jabber account was successfully deleted.","El teu compte Jabber ha sigut esborrat correctament."}.
{"Your subscription request and/or messages to ~ts have been blocked. To unblock your subscription request, visit ~ts","La teua petició de subscripció i/o missatges a ~ts han sigut bloquejats. Per a desbloquejar-los, visita ~ts"}.
{"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","La teua petició de subscripció i/o missatges a ~s han sigut bloquejats. Per a desbloquejar-los, visita ~s"}.
{"You're not allowed to create nodes","No tens permís per a crear nodes"}.
+366 -355
View File
File diff suppressed because it is too large Load Diff
+15 -25
View File
@@ -291,8 +291,7 @@ msgid "Configuration"
msgstr "Konfigurace"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Konfigurace místnosti ~s"
#: ejabberd_web_admin.erl:937
@@ -495,8 +494,7 @@ msgid "Failed to parse HTTP response"
msgstr "Chyba parsování HTTP odpovědi"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgid "Failed to process option '~s'"
msgstr "Chyba při zpracování možnosti '~s'"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
@@ -565,8 +563,8 @@ msgid "Given Name"
msgstr "Křestní jméno"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Skupina "
msgid "Group"
msgstr "Skupina"
#: mod_roster.erl:956
msgid "Groups"
@@ -694,10 +692,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "Pozvánky nejsou povoleny v této místnosti"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Není povoleno posílat chybové zprávy do místnosti. Účastník (~s) odeslal "
"chybovou zprávu (~s) a byl vyhozen z místnosti"
@@ -933,8 +930,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Přezdívka ~s v místnosti neexistuje"
#: mod_muc_room.erl:3396
@@ -1767,8 +1763,7 @@ msgid "To"
msgstr "Pro"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgid "To register, visit ~s"
msgstr "Pokud se chcete zaregistrovat, navštivte ~s"
#: mod_configure.erl:666
@@ -1781,10 +1776,9 @@ msgid "Token TTL"
msgstr "Token TTL"
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Příliš mnoho (~p) chybných pokusů o přihlášení z této IP adresy (~s). Adresa "
"bude zablokována do ~s UTC"
@@ -1996,19 +1990,16 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "Hodnota 'set' atrubutu 'type' není povolena"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgid "Value of '~s' should be boolean"
msgstr "Hodnota '~s' by měla být boolean"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgid "Value of '~s' should be datetime string"
msgstr "Hodnota '~s' by měla být datetime řetězec"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgid "Value of '~s' should be integer"
msgstr "Hodnota '~s' by měla být celé číslo"
#: ejabberd_web_admin.erl:433
@@ -2109,10 +2100,9 @@ msgid ""
msgstr "Fronta offline zpráv pro váš kontakt je plná. Zpráva byla zahozena."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "Nesmíte posílat zprávy na ~s. Pro povolení navštivte ~s"
#: mod_disco.erl:438
+15 -25
View File
@@ -300,8 +300,7 @@ msgid "Configuration"
msgstr "Konfiguration"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Konfiguration für Raum ~s"
#: ejabberd_web_admin.erl:937
@@ -507,8 +506,7 @@ msgid "Failed to parse HTTP response"
msgstr "Konnte HTTP-Antwort nicht parsen"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgid "Failed to process option '~s'"
msgstr "Konnte Option '~s' nicht verarbeiten"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
@@ -578,8 +576,8 @@ msgid "Given Name"
msgstr "Vorname"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Gruppe "
msgid "Group"
msgstr "Gruppe"
#: mod_roster.erl:956
msgid "Groups"
@@ -708,10 +706,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "Einladungen sind in dieser Konferenz nicht erlaubt"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Es ist nicht erlaubt Fehlermeldungen an den Raum zu senden. Der Teilnehmer "
"(~s) hat eine Fehlermeldung (~s) gesendet und wurde aus dem Raum gekickt"
@@ -949,8 +946,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Der Benutzername ~s existiert im Raum nicht"
#: mod_muc_room.erl:3396
@@ -1790,8 +1786,7 @@ msgid "To"
msgstr "An"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgid "To register, visit ~s"
msgstr "Um sich anzumelden, besuchen Sie ~s"
#: mod_configure.erl:666
@@ -1804,10 +1799,9 @@ msgid "Token TTL"
msgstr "Token TTL"
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Zu viele (~p) fehlgeschlagene Anmeldeversuche von dieser IP Adresse (~s). "
"Die Adresse wird bis ~s UTC blockiert."
@@ -2019,19 +2013,16 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "Wert 'set' des 'type'-Attributs ist nicht erlaubt"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgid "Value of '~s' should be boolean"
msgstr "Wert von '~s' sollte boolesch sein"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgid "Value of '~s' should be datetime string"
msgstr "Wert von '~s' sollte datetime-String sein"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgid "Value of '~s' should be integer"
msgstr "Wert von '~s' sollte eine Ganzzahl sein"
#: ejabberd_web_admin.erl:433
@@ -2140,10 +2131,9 @@ msgstr ""
"verworfen."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"Ihre Nachrichten an ~s werden blockiert. Um dies zu ändern, besuchen Sie ~s"
+20 -29
View File
@@ -294,8 +294,7 @@ msgid "Configuration"
msgstr "Διαμόρφωση"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Διαμόρφωση Αίθουσας σύνεδριασης ~s"
#: ejabberd_web_admin.erl:937
@@ -505,9 +504,8 @@ msgid "Failed to parse HTTP response"
msgstr "Αποτυχία ανάλυσης της απόκρισης HTTP"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgstr "Αποτυχία επεξεργασίας της επιλογής '~ s'"
msgid "Failed to process option '~s'"
msgstr "Αποτυχία επεξεργασίας της επιλογής '~s'"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
#: mod_vcard_sql.erl:174 mod_vcard_ldap.erl:330 mod_vcard_ldap.erl:343
@@ -576,8 +574,8 @@ msgid "Given Name"
msgstr "Ονομα"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Ομάδα "
msgid "Group"
msgstr "Ομάδα"
#: mod_roster.erl:956
msgid "Groups"
@@ -705,13 +703,12 @@ msgid "Invitations are not allowed in this conference"
msgstr "Οι προσκλήσεις δεν επιτρέπονται σε αυτή τη διάσκεψη"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Δεν επιτρέπεται η αποστολή μηνυμάτων σφάλματος στο δωμάτιο. Ο συμμετέχων (~ "
"s) έχει στείλει ένα μήνυμα σφάλματος (~ s) και έχει πέταχτεί έξω από την "
"Δεν επιτρέπεται η αποστολή μηνυμάτων σφάλματος στο δωμάτιο. Ο συμμετέχων (~s"
") έχει στείλει ένα μήνυμα σφάλματος (~s) και έχει πέταχτεί έξω από την "
"αίθουσα"
#: mod_muc_room.erl:564 mod_muc_room.erl:575
@@ -946,8 +943,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Ψευδώνυμο ~s δεν υπάρχει σε αυτή την αίθουσα"
#: mod_muc_room.erl:3396
@@ -1797,9 +1793,8 @@ msgid "To"
msgstr "Πρώς"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgstr "Για να εγγραφείτε, επισκεφθείτε το ~ s"
msgid "To register, visit ~s"
msgstr "Για να εγγραφείτε, επισκεφθείτε το ~s"
#: mod_configure.erl:666
#, fuzzy
@@ -2025,20 +2020,17 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "Δεν επιτρέπεται η παράμετρος 'set' του 'type'"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgstr "Η τιμή του '~ s' πρέπει να είναι boolean"
msgid "Value of '~s' should be boolean"
msgstr "Η τιμή του '~s' πρέπει να είναι boolean"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgstr "Η τιμή του '~ s' θα πρέπει να είναι χρονοσειρά"
msgid "Value of '~s' should be datetime string"
msgstr "Η τιμή του '~s' θα πρέπει να είναι χρονοσειρά"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgstr "Η τιμή του '~ s' θα πρέπει να είναι ακέραιος"
msgid "Value of '~s' should be integer"
msgstr "Η τιμή του '~s' θα πρέπει να είναι ακέραιος"
#: ejabberd_web_admin.erl:433
#, fuzzy
@@ -2146,10 +2138,9 @@ msgstr ""
"Η μνήμη χωρίς σύνδεση μήνυματών είναι πλήρης. Το μήνυμα έχει απορριφθεί."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"Τα μηνύματά σας πρως ~s είναι αποκλεισμένα. Για αποδεσμεύση, επισκεφθείτε ~s"
+8 -12
View File
@@ -290,8 +290,7 @@ msgid "Configuration"
msgstr "Agordo"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Agordo de babilejo ~s"
#: ejabberd_web_admin.erl:937
@@ -566,8 +565,8 @@ msgid "Given Name"
msgstr "Meza Nomo"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Grupo "
msgid "Group"
msgstr "Grupo"
#: mod_roster.erl:956
msgid "Groups"
@@ -936,8 +935,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Kaŝnomo ~s ne ekzistas en la babilejo"
#: mod_muc_room.erl:3396
@@ -1782,10 +1780,9 @@ msgid "Token TTL"
msgstr ""
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Tro da malsukcesaj aŭtentprovoj (~p) de ĉi tiu IP-adreso (~s). La adreso "
"estos malbarata je ~s UTC."
@@ -2113,10 +2110,9 @@ msgstr ""
"forĵetita"
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "Viaj mesaĝoj al ~s estas blokata. Por malbloki ilin, iru al ~s"
#: mod_disco.erl:438
+18 -16
View File
@@ -60,7 +60,7 @@
{"Client acknowledged more stanzas than sent by server","El cliente ha reconocido más paquetes de los que el servidor ha enviado"}.
{"Commands","Comandos"}.
{"Conference room does not exist","La sala de conferencias no existe"}.
{"Configuration of room ~ts","Configuración para la sala ~ts"}.
{"Configuration of room ~s","Configuración para la sala ~s"}.
{"Configuration","Configuración"}.
{"Connected Resources:","Recursos conectados:"}.
{"Country","País"}.
@@ -79,7 +79,8 @@
{"Delete User","Borrar usuario"}.
{"Description:","Descripción:"}.
{"Disc only copy","Copia en disco solamente"}.
{"Displayed Groups:","Mostrar grupos:"}.
{"'Displayed groups' not added (they do not exist!): ","'Mostrados' que no han sido añadidos (¡no existen!): "}.
{"Displayed:","Mostrados:"}.
{"Don't tell your password to anybody, not even the administrators of the Jabber server.","No le digas tu contraseña a nadie, ni siquiera a los administradores del servidor Jabber."}.
{"Dump Backup to Text File at ","Exporta copia de seguridad a fichero de texto en "}.
{"Dump to Text File","Exportar a fichero de texto"}.
@@ -116,7 +117,7 @@
{"Failed to extract JID from your voice request approval","Fallo al extraer el Jabber ID de tu aprobación de petición de voz"}.
{"Failed to map delegated namespace to external component","Falló el mapeo de espacio de nombres delegado al componente externo"}.
{"Failed to parse HTTP response","Falló la comprensión de la respuesta HTTP"}.
{"Failed to process option '~ts'","Falló el procesado de la opción '~ts'"}.
{"Failed to process option '~s'","Falló el procesado de la opción '~s'"}.
{"Family Name","Apellido"}.
{"February","febrero"}.
{"File larger than ~w bytes","El fichero es más grande que ~w bytes"}.
@@ -132,7 +133,7 @@
{"Get User Password","Ver contraseña de usuario"}.
{"Get User Statistics","Ver estadísticas de usuario"}.
{"Given Name","Nombre"}.
{"Group ","Grupo "}.
{"Group","Grupo"}.
{"Groups","Grupos"}.
{"has been banned","ha sido bloqueado"}.
{"has been kicked because of a system shutdown","ha sido expulsado porque el sistema se va a detener"}.
@@ -169,7 +170,7 @@
{"Invitations are not allowed in this conference","Las invitaciones no están permitidas en esta sala"}.
{"IP addresses","Direcciones IP"}.
{"is now known as","se cambia el nombre a"}.
{"It is not allowed to send error messages to the room. The participant (~ts) has sent an error message (~ts) and got kicked from the room","No está permitido enviar mensajes de error a la sala. Este participante (~ts) ha enviado un mensaje de error (~ts) y fue expulsado de la sala"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","No está permitido enviar mensajes de error a la sala. Este participante (~s) ha enviado un mensaje de error (~s) y fue expulsado de la sala"}.
{"It is not allowed to send private messages of type \"groupchat\"","No está permitido enviar mensajes privados del tipo \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Impedir el envio de mensajes privados a la sala"}.
{"It is not allowed to send private messages","No está permitido enviar mensajes privados"}.
@@ -181,6 +182,7 @@
{"joins the room","entra en la sala"}.
{"July","julio"}.
{"June","junio"}.
{"Label:","Etiqueta:"}.
{"Last Activity","Última actividad"}.
{"Last login","Última conexión"}.
{"Last month","Último mes"}.
@@ -200,7 +202,7 @@
{"March","marzo"}.
{"Maximum Number of Occupants","Número máximo de ocupantes"}.
{"May","mayo"}.
{"Members not added (inexistent vhost): ","Miembros no añadidos (el vhost no existe): "}.
{"Members not added (inexistent vhost!): ","Miembros no añadidos (el vhost no existe): "}.
{"Membership is required to enter this room","Necesitas ser miembro de esta sala para poder entrar"}.
{"Members:","Miembros:"}.
{"Memorize your password, or write it in a paper placed in a safe place. In Jabber there isn't an automated way to recover your password if you forget it.","Memoriza tu contraseña, o apúntala en un papel en un lugar seguro. En Jabber no hay un método automatizado para recuperar la contraseña si la olvidas."}.
@@ -224,7 +226,7 @@
{"New Password:","Nueva contraseña:"}.
{"Nickname can't be empty","El apodo no puede estar vacío"}.
{"Nickname Registration at ","Registro del apodo en "}.
{"Nickname ~ts does not exist in the room","El apodo ~ts no existe en la sala"}.
{"Nickname ~s does not exist in the room","El apodo ~s no existe en la sala"}.
{"Nickname","Apodo"}.
{"No address elements found","No se encontraron elementos de dirección ('address')"}.
{"No addresses element found","No se encontró elemento de direcciones ('addresses')"}.
@@ -349,6 +351,7 @@
{"Roster","Lista de contactos"}.
{"RPC Call Error","Error en la llamada RPC"}.
{"Running Nodes","Nodos funcionando"}.
{"~s invites you to the room ~s","~s te invita a la sala ~s"}.
{"Saturday","sábado"}.
{"Script check","Comprobación de script"}.
{"Search Results for ","Buscar resultados por "}.
@@ -409,12 +412,12 @@
{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Esta página te permite crear una cuenta Jabber este servidor Jabber. Tu JID (Jabber IDentificador) será de la forma: nombredeusuario@servidor. Por favor lee detenidamente las instrucciones para rellenar correctamente los campos."}.
{"This page allows to unregister a Jabber account in this Jabber server.","Esta página te permite borrar tu cuenta Jabber en este servidor Jabber."}.
{"This room is not anonymous","Sala no anónima"}.
{"This service can not process the address: ~ts","Este servicio no puede procesar la dirección: ~ts"}.
{"This service can not process the address: ~s","Este servicio no puede procesar la dirección: ~s"}.
{"Thursday","jueves"}.
{"Time delay","Retraso temporal"}.
{"Timed out waiting for stream resumption","Ha pasado demasiado tiempo esperando que la conexión se restablezca"}.
{"Time","Fecha"}.
{"To register, visit ~ts","Para registrarte, visita ~ts"}.
{"To register, visit ~s","Para registrarte, visita ~s"}.
{"To ~ts","A ~ts"}.
{"Token TTL","Token TTL"}.
{"Too many active bytestreams","Demasiados bytestreams activos"}.
@@ -422,7 +425,7 @@
{"Too many child elements","Demasiados subelementos"}.
{"Too many <item/> elements","Demasiados elementos <item/>"}.
{"Too many <list/> elements","Demasiados elementos <list/>"}.
{"Too many (~p) failed authentications from this IP address (~ts). The address will be unblocked at ~ts UTC","Demasiadas (~p) autenticaciones fallidas de esta dirección IP (~ts). La dirección será desbloqueada en ~ts UTC"}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Demasiadas (~p) autenticaciones fallidas de esta dirección IP (~s). La dirección será desbloqueada en ~s UTC"}.
{"Too many receiver fields were specified","Se han especificado demasiados campos de destinatario"}.
{"Too many unacked stanzas","Demasiados mensajes sin haber reconocido recibirlos"}.
{"Too many users in this conference","Demasiados usuarios en esta sala"}.
@@ -433,7 +436,6 @@
{"Transactions Committed:","Transacciones finalizadas:"}.
{"Transactions Logged:","Transacciones registradas:"}.
{"Transactions Restarted:","Transacciones reiniciadas:"}.
{"~ts invites you to the room ~ts","~ts te invita a la sala ~ts"}.
{"~ts's Offline Messages Queue","Cola de mensajes diferidos de ~ts"}.
{"Tuesday","martes"}.
{"Unable to generate a CAPTCHA","No se pudo generar un CAPTCHA"}.
@@ -467,12 +469,11 @@
{"User","Usuario"}.
{"Validate","Validar"}.
{"Value 'get' of 'type' attribute is not allowed","El valor 'get' del atributo 'type' no está permitido"}.
{"Value of '~ts' should be boolean","El valor de '~ts' debería ser booleano"}.
{"Value of '~ts' should be datetime string","El valor de '~ts' debería ser una fecha"}.
{"Value of '~ts' should be integer","El valor de '~ts' debería ser un entero"}.
{"Value of '~s' should be boolean","El valor de '~s' debería ser booleano"}.
{"Value of '~s' should be datetime string","El valor de '~s' debería ser una fecha"}.
{"Value of '~s' should be integer","El valor de '~s' debería ser un entero"}.
{"Value 'set' of 'type' attribute is not allowed","El valor 'set' del atributo 'type' no está permitido"}.
{"vCard User Search","Buscar vCard de usuario"}.
{"Virtual Hosting","Dominios Virtuales"}.
{"Virtual Hosts","Dominios Virtuales"}.
{"Visitors are not allowed to change their nicknames in this room","Los visitantes no tienen permitido cambiar sus apodos en esta sala"}.
{"Visitors are not allowed to send messages to all occupants","Los visitantes no pueden enviar mensajes a todos los ocupantes"}.
@@ -481,6 +482,7 @@
{"Wednesday","miércoles"}.
{"Wrong parameters in the web formulary","Parámetros incorrectos en el formulario web"}.
{"Wrong xmlns","xmlns incorrecto"}.
{"XMPP Domains","Dominios XMPP"}.
{"You are being removed from the room because of a system shutdown","Estás siendo expulsado de la sala porque el sistema se va a detener"}.
{"You are not joined to the channel","No has entrado en el canal"}.
{"You can later change your password using a Jabber client.","Puedes cambiar tu contraseña después, usando un cliente Jabber."}.
@@ -494,5 +496,5 @@
{"Your contact offline message queue is full. The message has been discarded.","Tu cola de mensajes diferidos de contactos está llena. El mensaje se ha descartado."}.
{"Your Jabber account was successfully created.","Tu cuenta Jabber se ha creado correctamente."}.
{"Your Jabber account was successfully deleted.","Tu cuenta Jabber se ha borrado correctamente."}.
{"Your subscription request and/or messages to ~ts have been blocked. To unblock your subscription request, visit ~ts","Tu petición de suscripción y/o mensajes a ~ts ha sido bloqueado. Para desbloquear tu petición de suscripción visita ~ts"}.
{"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","Tu petición de suscripción y/o mensajes a ~s ha sido bloqueado. Para desbloquear tu petición de suscripción visita ~s"}.
{"You're not allowed to create nodes","No tienes permitido crear nodos"}.
+422 -412
View File
File diff suppressed because it is too large Load Diff
+15 -25
View File
@@ -293,8 +293,7 @@ msgid "Configuration"
msgstr "Configuration"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Configuration pour le salon ~s"
#: ejabberd_web_admin.erl:937
@@ -501,8 +500,7 @@ msgid "Failed to parse HTTP response"
msgstr "Echec de lecture de la réponse HTTP"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgid "Failed to process option '~s'"
msgstr "Echec de traitement de l'option '~s'"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
@@ -571,8 +569,8 @@ msgid "Given Name"
msgstr "Nom"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Groupe "
msgid "Group"
msgstr "Groupe"
#: mod_roster.erl:956
msgid "Groups"
@@ -701,10 +699,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "Les invitations ne sont pas autorisées dans ce salon"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"L'envoyer de messages d'erreur au salon n'est pas autorisé. Le participant "
"(~s) à envoyé un message d'erreur (~s) et à été expulsé du salon"
@@ -942,8 +939,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Le pseudo ~s n'existe pas dans ce salon"
#: mod_muc_room.erl:3396
@@ -1784,8 +1780,7 @@ msgid "To"
msgstr "A"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgid "To register, visit ~s"
msgstr "Pour vous enregistrer, visitez ~s"
#: mod_configure.erl:666
@@ -1798,10 +1793,9 @@ msgid "Token TTL"
msgstr "Jeton TTL"
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Trop (~p) d'authentification ont échoué pour cette adresse IP (~s). "
"L'adresse sera débloquée à ~s UTC"
@@ -2016,19 +2010,16 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "La valeur de l'attribut 'type' ne peut être 'set'"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgid "Value of '~s' should be boolean"
msgstr "La valeur de '~s' ne peut être booléen"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgid "Value of '~s' should be datetime string"
msgstr "La valeur de '~s' doit être une chaine datetime"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgid "Value of '~s' should be integer"
msgstr "La valeur de '~s' doit être un entier"
#: ejabberd_web_admin.erl:433
@@ -2139,10 +2130,9 @@ msgstr ""
"été détruit."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"Vos messages pour ~s sont bloqués. Pour les débloquer, veuillez visiter ~s"
+15 -25
View File
@@ -291,8 +291,7 @@ msgid "Configuration"
msgstr "Configuración"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Configuración para a sala ~s"
#: ejabberd_web_admin.erl:937
@@ -498,8 +497,7 @@ msgid "Failed to parse HTTP response"
msgstr "Non se puido analizar a resposta HTTP"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgid "Failed to process option '~s'"
msgstr "Fallo ao procesar a opción '~s'"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
@@ -568,8 +566,8 @@ msgid "Given Name"
msgstr "Nome"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Grupo "
msgid "Group"
msgstr "Grupo"
#: mod_roster.erl:956
msgid "Groups"
@@ -697,10 +695,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "As invitacións non están permitidas nesta sala"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Non está permitido enviar mensaxes de erro á sala. Este participante (~s) "
"enviou unha mensaxe de erro (~s) e foi expulsado da sala"
@@ -937,8 +934,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "O alcume ~s non existe na sala"
#: mod_muc_room.erl:3396
@@ -1776,8 +1772,7 @@ msgid "To"
msgstr "Para"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgid "To register, visit ~s"
msgstr "Para rexistrarse, visita ~s"
#: mod_configure.erl:666
@@ -1790,10 +1785,9 @@ msgid "Token TTL"
msgstr "Token TTL"
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Demasiados (~p) fallou autenticaciones desde esta dirección IP (~s). A "
"dirección será desbloqueada as ~s UTC"
@@ -2005,19 +1999,16 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "O valor \"set\" do atributo 'type' non está permitido"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgid "Value of '~s' should be boolean"
msgstr "O valor de '~s' debería ser booleano"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgid "Value of '~s' should be datetime string"
msgstr "O valor de '~s' debería ser unha data"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgid "Value of '~s' should be integer"
msgstr "O valor de '~s' debería ser un enteiro"
#: ejabberd_web_admin.erl:433
@@ -2122,10 +2113,9 @@ msgstr ""
"descartouse."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"As súas mensaxes a ~s encóntranse bloqueadas. Para desbloquear, visite ~s"
+14 -23
View File
@@ -295,8 +295,7 @@ msgstr "תצורה"
# תצורה של חדר
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "תצורת חדר ~s"
#: ejabberd_web_admin.erl:937
@@ -500,8 +499,7 @@ msgid "Failed to parse HTTP response"
msgstr "נכשל לפענח תגובת HTTP"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgid "Failed to process option '~s'"
msgstr "נכשל לעבד אפשרות '~s'"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
@@ -572,8 +570,8 @@ msgid "Given Name"
msgstr "שם פרטי"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "קבוצה "
msgid "Group"
msgstr "קבוצה"
#: mod_roster.erl:956
msgid "Groups"
@@ -702,10 +700,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "הזמנות אינן מותרות בועידה זו"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"אין זה מותר לשלוח הודעות שגיאה לחדר. משתתף זה (~s) שלח הודעת שגיאה (~s) "
"ונבעט מתוך החדר"
@@ -1784,8 +1781,7 @@ msgid "To"
msgstr "לכבוד"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgid "To register, visit ~s"
msgstr "כדי להירשם, בקרו ~s"
#: mod_configure.erl:666
@@ -1798,10 +1794,9 @@ msgid "Token TTL"
msgstr "סימן TTL"
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"יותר מדי (~p) אימותים כושלים מתוך כתובת IP זו (~s). הכתובת תורשה לקבל גישה "
"בשעה ~s UTC"
@@ -2015,19 +2010,16 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr ""
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgid "Value of '~s' should be boolean"
msgstr "ערך של '~s' צריך להיות boolean"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgid "Value of '~s' should be datetime string"
msgstr "ערך של '~s' צריך להיות מחרוזת datetime"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgid "Value of '~s' should be integer"
msgstr "ערך של '~s' צריך להיות integer"
# וירטואליים
@@ -2132,10 +2124,9 @@ msgid ""
msgstr "תור הודעות קשר לא מקוונות הינו מלא. ההודעה סולקה."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "ההודעות שלך לערוץ ~s הינן חסומות. כדי לבטל את חסימתן, בקר בכתובת ~s"
#: mod_disco.erl:438
+24 -25
View File
@@ -295,8 +295,8 @@ msgid "Configuration"
msgstr "Beállítás"
#: mod_muc_room.erl:3489
msgid "Configuration of room ~ts"
msgstr "A(z) ~ts szoba beállítása"
msgid "Configuration of room ~s"
msgstr "A(z) ~s szoba beállítása"
#: ejabberd_web_admin.erl:937
msgid "Connected Resources:"
@@ -567,8 +567,8 @@ msgid "Given Name"
msgstr "Keresztnév"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Csoport "
msgid "Group"
msgstr "Csoport"
#: mod_roster.erl:956
msgid "Groups"
@@ -694,11 +694,11 @@ msgstr "Meghívások nem engedélyezettek ebben a konferenciában"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Nem engedélyezett hibaüzeneteket küldeni a szobába. A résztvevő (~ts) "
"hibaüzenetet (~ts) küldött, és ki lett rúgva a szobából"
"Nem engedélyezett hibaüzeneteket küldeni a szobába. A résztvevő (~s) "
"hibaüzenetet (~s) küldött, és ki lett rúgva a szobából"
#: mod_muc_room.erl:564 mod_muc_room.erl:575
msgid "It is not allowed to send private messages"
@@ -1734,8 +1734,8 @@ msgid "This room is not anonymous"
msgstr "Ez a szoba nem névtelen"
#: mod_multicast.erl:498
msgid "This service can not process the address: ~ts"
msgstr "Ez a szolgáltatás nem tudja feldolgozni a címet: ~ts"
msgid "This service can not process the address: ~s"
msgstr "Ez a szolgáltatás nem tudja feldolgozni a címet: ~s"
#: mod_muc_log.erl:470
msgid "Thursday"
@@ -1758,8 +1758,8 @@ msgid "To"
msgstr "Címzett"
#: mod_register.erl:226
msgid "To register, visit ~ts"
msgstr "Regisztráláshoz látogassa meg ezt az oldalt: ~ts"
msgid "To register, visit ~s"
msgstr "Regisztráláshoz látogassa meg ezt az oldalt: ~s"
#: mod_configure.erl:666
msgid "To ~ts"
@@ -1769,10 +1769,9 @@ msgstr "Címzett: ~ts"
msgid "Token TTL"
msgstr "Token élettartama"
#: mod_fail2ban.erl:219
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Túl sok (~p) sikertelen hitelesítés erről az IP-címről (~ts) A cím ~ts-kor "
"lesz feloldva UTC szerint"
@@ -1981,17 +1980,17 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "A „type” attribútum „set” értéke nem engedélyezett"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
msgid "Value of '~ts' should be boolean"
msgstr "A(z) „~ts” értéke csak logikai lehet"
msgid "Value of '~s' should be boolean"
msgstr "A(z) „~s” értéke csak logikai lehet"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
msgid "Value of '~ts' should be datetime string"
msgstr "A(z) „~ts” értéke csak dátum és idő karakterlánc lehet"
msgid "Value of '~s' should be datetime string"
msgstr "A(z) „~s” értéke csak dátum és idő karakterlánc lehet"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
msgid "Value of '~ts' should be integer"
msgstr "A(z) „~ts” értéke csak egész szám lehet"
msgid "Value of '~s' should be integer"
msgstr "A(z) „~s” értéke csak egész szám lehet"
#: ejabberd_web_admin.erl:433
msgid "Virtual Hosting"
@@ -2097,12 +2096,12 @@ msgstr ""
#: ejabberd_captcha.erl:97
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"A feliratkozási kérelme és/vagy ~ts számára küldött üzenetei blokkolva "
"A feliratkozási kérelme és/vagy ~s számára küldött üzenetei blokkolva "
"lettek. A feliratkozási kérelmének feloldásához látogassa meg ezt az oldalt: "
"~ts"
"~s"
#: mod_disco.erl:438
msgid "ejabberd"
+6 -8
View File
@@ -291,8 +291,7 @@ msgid "Configuration"
msgstr "Pengaturan"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Pengaturan ruangan ~s"
#: ejabberd_web_admin.erl:937
@@ -568,10 +567,11 @@ msgid "Given Name"
msgstr "Nama Tengah"
#: mod_shared_roster.erl:879
msgid "Group "
msgid "Group"
msgstr "Grup"
#: mod_roster.erl:956
#, fuzzy
msgid "Groups"
msgstr "Grup"
@@ -940,8 +940,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Nama Julukan ~s tidak berada di dalam ruangan"
#: mod_muc_room.erl:3396
@@ -2126,10 +2125,9 @@ msgstr ""
"Kontak offline Anda pada antrian pesan sudah penuh. Pesan telah dibuang."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"Pesan Anda untuk ~s sedang diblokir. Untuk membuka blokir tersebut, kunjungi "
"~s"
+6 -9
View File
@@ -298,8 +298,7 @@ msgid "Configuration"
msgstr "Configurazione"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Configurazione per la stanza ~s"
#: ejabberd_web_admin.erl:937
@@ -577,8 +576,8 @@ msgid "Given Name"
msgstr "Altro nome"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Gruppo "
msgid "Group"
msgstr "Gruppo"
#: mod_roster.erl:956
msgid "Groups"
@@ -947,8 +946,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Il nickname ~s non esiste nella stanza"
#: mod_muc_room.erl:3396
@@ -2135,10 +2133,9 @@ msgstr ""
"scartato"
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "I messaggi verso ~s sono bloccati. Per sbloccarli, visitare ~s"
#: mod_disco.erl:438
+11 -14
View File
@@ -288,8 +288,7 @@ msgid "Configuration"
msgstr "設定"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "チャットルーム ~s の設定"
#: ejabberd_web_admin.erl:937
@@ -565,10 +564,12 @@ msgid "Given Name"
msgstr "ミドルネーム"
#: mod_shared_roster.erl:879
msgid "Group "
#, fuzzy
msgid "Group"
msgstr "グループ"
#: mod_roster.erl:956
#, fuzzy
msgid "Groups"
msgstr "グループ"
@@ -697,10 +698,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "この会議では、発言権の要求はできません"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"このルームにエラーメッセージを送ることは許可されていません。参加者(~s)はエ"
"ラーメッセージを(~s)を送信してルームからキックされました。"
@@ -939,8 +939,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "ニックネーム ~s はこのチャットルームにいません"
#: mod_muc_room.erl:3396
@@ -1783,10 +1782,9 @@ msgid "Token TTL"
msgstr ""
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"~p回の認証に失敗しました。このIPアドレス(~s)は~s UTCまでブロックされます。"
@@ -2109,10 +2107,9 @@ msgstr ""
"相手先のオフラインメッセージキューが一杯です。このメッセージは破棄されます。"
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"~s 宛のメッセージはブロックされています。解除するにはこちらを見てください ~s"
+8 -12
View File
@@ -292,8 +292,7 @@ msgid "Configuration"
msgstr "Instellingen"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Instellingen van chatruimte ~s"
#: ejabberd_web_admin.erl:937
@@ -571,8 +570,8 @@ msgid "Given Name"
msgstr "Tussennaam"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Groep "
msgid "Group"
msgstr "Groep"
#: mod_roster.erl:956
msgid "Groups"
@@ -946,8 +945,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "De bijnaam ~s bestaat niet in deze chatruimte"
#: mod_muc_room.erl:3396
@@ -1807,10 +1805,9 @@ msgid "Token TTL"
msgstr ""
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Te veel (~p) mislukte authenticatie-pogingen van dit IP-adres (~s). Dit "
"adres zal worden gedeblokkeerd om ~s UTC"
@@ -2138,10 +2135,9 @@ msgstr ""
"opgeslagen."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"Uw berichten aan ~s worden geblokkeerd. Om ze te deblokkeren, ga naar ~s"
+6 -9
View File
@@ -290,8 +290,7 @@ msgid "Configuration"
msgstr "Konfigurasjon"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Konfigurasjon for rom ~s"
#: ejabberd_web_admin.erl:937
@@ -566,8 +565,8 @@ msgid "Given Name"
msgstr "Mellomnavn"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Gruppe "
msgid "Group"
msgstr "Gruppe"
#: mod_roster.erl:956
msgid "Groups"
@@ -936,8 +935,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Kallenavn ~s eksisterer ikke i dette rommet"
#: mod_muc_room.erl:3396
@@ -2108,10 +2106,9 @@ msgid ""
msgstr "Kontaktens frakoblede meldingskø er full. Meldingen har blitt kassert."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "Dine meldinger til ~s blir blokkert. For å åpne igjen, besøk ~s"
#: mod_disco.erl:438
+14 -23
View File
@@ -294,8 +294,7 @@ msgid "Configuration"
msgstr "Konfiguracja"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Konfiguracja pokoju ~s"
#: ejabberd_web_admin.erl:937
@@ -500,8 +499,7 @@ msgid "Failed to parse HTTP response"
msgstr "Nie udało się zanalizować odpowiedzi HTTP"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgid "Failed to process option '~s'"
msgstr "Nie udało się przetworzyć opcji '~s'"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
@@ -570,8 +568,8 @@ msgid "Given Name"
msgstr "Imię"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Grupa "
msgid "Group"
msgstr "Grupa"
#: mod_roster.erl:956
msgid "Groups"
@@ -699,10 +697,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "Zaproszenia są wyłączone w tym pokoju"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Użytkownik nie może wysyłać wiadomości o błędach do pokoju. Użytkownik (~s) "
"wysłał błąd (~s) i został wyrzucony z pokoju"
@@ -1776,8 +1773,7 @@ msgid "To"
msgstr "Do"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgid "To register, visit ~s"
msgstr "Żeby się zarejestrować odwiedź ~s"
#: mod_configure.erl:666
@@ -1790,10 +1786,9 @@ msgid "Token TTL"
msgstr "Limit czasu tokenu"
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Zbyt wiele (~p) nieudanych prób logowanie z tego adresu IP (~s). Ten adres "
"zostanie odblokowany o ~s UTC"
@@ -2005,19 +2000,16 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "Wartość 'set' dla atrybutu 'type' jest niedozwolona"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgid "Value of '~s' should be boolean"
msgstr "Wartość '~s' powinna być typu logicznego"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgid "Value of '~s' should be datetime string"
msgstr "Wartość '~s' powinna być typu daty"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgid "Value of '~s' should be integer"
msgstr "Wartość '~s' powinna być liczbą"
#: ejabberd_web_admin.erl:433
@@ -2118,10 +2110,9 @@ msgstr ""
"Kolejka wiadomości offline adresata jest pełna. Wiadomość została odrzucona."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "Twoje wiadomości do ~s są blokowane. Aby je odblokować, odwiedź ~s"
#: mod_disco.erl:438
+15 -25
View File
@@ -291,8 +291,7 @@ msgid "Configuration"
msgstr "Configuração"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Configuração para ~s"
#: ejabberd_web_admin.erl:937
@@ -499,8 +498,7 @@ msgid "Failed to parse HTTP response"
msgstr "Falha ao analisar resposta HTTP"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgid "Failed to process option '~s'"
msgstr "Falha ao processar opção '~s'"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
@@ -570,8 +568,8 @@ msgid "Given Name"
msgstr "Nome do meio"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Grupo "
msgid "Group"
msgstr "Grupo"
#: mod_roster.erl:956
msgid "Groups"
@@ -701,10 +699,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "Convites estão desabilitados nesta sala de conferência"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Não é permitido o envio de mensagens de erro a esta sala. O membro (~s) "
"enviou uma mensagem de erro (~s) e foi desconectado (\"kicked\")."
@@ -942,8 +939,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "O apelido ~s não existe na sala"
#: mod_muc_room.erl:3396
@@ -1784,8 +1780,7 @@ msgid "To"
msgstr "Para"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgid "To register, visit ~s"
msgstr "Para registrar, visite ~s"
#: mod_configure.erl:666
@@ -1798,10 +1793,9 @@ msgid "Token TTL"
msgstr "Token TTL"
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Número excessivo (~p) de tentativas falhas de autenticação (~s). O endereço "
"será desbloqueado às ~s UTC"
@@ -2016,19 +2010,16 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "Valor 'set' não permitido para atributo 'type'"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgid "Value of '~s' should be boolean"
msgstr "Value de '~s' deveria ser um booleano"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgid "Value of '~s' should be datetime string"
msgstr "Valor de '~s' deveria ser data e hora"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgid "Value of '~s' should be integer"
msgstr "Valor de '~s' deveria ser um inteiro"
#: ejabberd_web_admin.erl:433
@@ -2132,10 +2123,9 @@ msgid ""
msgstr "Sua fila de mensagens offline esta cheia. Sua mensagem foi descartada"
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"Suas mensagens para ~s estão bloqueadas. Para desbloqueá-las, visite: ~s"
+3 -5
View File
@@ -300,7 +300,7 @@ msgstr "Configuração"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Configuração para "
#: ejabberd_web_admin.erl:937
@@ -585,9 +585,8 @@ msgid "Given Name"
msgstr "Segundo nome"
#: mod_shared_roster.erl:879
#, fuzzy
msgid "Group "
msgstr "Grupos"
msgstr ""
#: mod_roster.erl:956
msgid "Groups"
@@ -969,8 +968,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "A alcunha ~s não existe na sala"
#: mod_muc_room.erl:3396
+16 -27
View File
@@ -289,8 +289,7 @@ msgid "Configuration"
msgstr "Конфигурация"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Конфигурация комнаты ~s"
#: ejabberd_web_admin.erl:937
@@ -491,8 +490,7 @@ msgid "Failed to parse HTTP response"
msgstr "Ошибка разбора HTTP ответа"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgid "Failed to process option '~s'"
msgstr "Ошибка обработки опции '~s'"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
@@ -559,8 +557,8 @@ msgid "Given Name"
msgstr "Имя"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Группа "
msgid "Group"
msgstr "Группа"
#: mod_roster.erl:956
msgid "Groups"
@@ -685,10 +683,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "Рассылка приглашений отключена в этой конференции"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Запрещено посылать сообщения об ошибках в эту комнату. Участник (~s) послал "
"сообщение об ошибке (~s) и был выкинут из комнаты"
@@ -924,8 +921,7 @@ msgid "Nickname can't be empty"
msgstr "Псевдоним не может быть пустым значением"
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Псевдоним ~s в комнате отсутствует"
#: mod_muc_room.erl:3396
@@ -1726,8 +1722,7 @@ msgid "This room is not anonymous"
msgstr "Эта комната не анонимная"
#: mod_multicast.erl:498
#, fuzzy
msgid "This service can not process the address: ~ts"
msgid "This service can not process the address: ~s"
msgstr "Сервер не может обработать адрес: ~s"
#: mod_muc_log.erl:470
@@ -1751,8 +1746,7 @@ msgid "To"
msgstr "Кому"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgid "To register, visit ~s"
msgstr "Для регистрации посетите ~s"
#: mod_configure.erl:666
@@ -1765,10 +1759,9 @@ msgid "Token TTL"
msgstr "Токен TTL"
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Слишком много (~p) неудачных попыток аутентификации с этого IP-адреса (~s). "
"Адрес будет разблокирован в ~s UTC"
@@ -1977,19 +1970,16 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "Значение 'set' атрибута 'type' недопустимо"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgid "Value of '~s' should be boolean"
msgstr "Значение '~s' должно быть булевым"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgid "Value of '~s' should be datetime string"
msgstr "Значение '~s' должно быть датой"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgid "Value of '~s' should be integer"
msgstr "Значение '~s' должно быть целочисленным"
#: ejabberd_web_admin.erl:433
@@ -2089,10 +2079,9 @@ msgstr ""
"было сохранено."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"Ваши запросы на добавление в контакт-лист, а также сообщения к ~s "
"блокируются. Для снятия блокировки перейдите по ссылке ~s"
+6 -9
View File
@@ -291,8 +291,7 @@ msgid "Configuration"
msgstr "Konfigurácia"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Konfigurácia miestnosti ~s"
#: ejabberd_web_admin.erl:937
@@ -567,8 +566,8 @@ msgid "Given Name"
msgstr "Prostredné meno: "
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Skupina "
msgid "Group"
msgstr "Skupina"
#: mod_roster.erl:956
msgid "Groups"
@@ -936,8 +935,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Prezývka ~s v miestnosti neexistuje"
#: mod_muc_room.erl:3396
@@ -2113,10 +2111,9 @@ msgid ""
msgstr "Fronta offline správ tohoto kontaktu je plná. Správa bola zahodená."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "Správa určená pre ~s bola zablokovaná. Oblokovať ju môžete na ~s"
#: mod_disco.erl:438
+6 -9
View File
@@ -295,8 +295,7 @@ msgid "Configuration"
msgstr "Konfiguration"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Konfiguration för ~s"
#: ejabberd_web_admin.erl:937
@@ -570,8 +569,8 @@ msgid "Given Name"
msgstr "Mellannamn"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Grupp "
msgid "Group"
msgstr "Grupp"
#: mod_roster.erl:956
msgid "Groups"
@@ -942,8 +941,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Smeknamnet ~s existerar inte i det här rummet"
#: mod_muc_room.erl:3396
@@ -2121,10 +2119,9 @@ msgid ""
msgstr "Din kontaktkö for offlinekontakter ar full"
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"Dina meddelanden till ~s är blockerade. För att avblockera dem, gå till ~s"
+2 -2
View File
@@ -569,8 +569,8 @@ msgid "Given Name"
msgstr "ชื่อกลาง"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "กลุ่"
msgid "Group"
msgstr "กลุ่"
#: mod_roster.erl:956
msgid "Groups"
+5 -8
View File
@@ -294,8 +294,7 @@ msgid "Configuration"
msgstr "Ayarlar"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "~s odasının ayarları"
#: ejabberd_web_admin.erl:937
@@ -573,7 +572,7 @@ msgstr "Ortanca İsim"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Group "
msgstr ""
#: mod_roster.erl:956
msgid "Groups"
@@ -943,8 +942,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "~s takma ismi odada yok"
#: mod_muc_room.erl:3396
@@ -2128,10 +2126,9 @@ msgid ""
msgstr "Çevirim-dışı mesaj kuyruğunuz dolu. Mesajını dikkate alınmadı."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
"~s kullanıcısına mesajlarınız engelleniyor. Durumu düzeltmek için ~s "
"adresini ziyaret ediniz."
+10 -15
View File
@@ -295,8 +295,7 @@ msgid "Configuration"
msgstr "Конфігурація"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Конфігурація кімнати ~s"
#: ejabberd_web_admin.erl:937
@@ -568,8 +567,8 @@ msgid "Given Name"
msgstr "По-батькові"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Група "
msgid "Group"
msgstr "Група"
#: mod_roster.erl:956
msgid "Groups"
@@ -699,10 +698,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "Голосові запити відключені в цій конференції"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"Не дозволяється відправляти помилкові повідомлення в кімнату. Учасник (~s) "
"відправив помилкове повідомлення (~s), та був виганий з кімнати"
@@ -940,8 +938,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Псевдонім ~s в кімнаті відсутній"
#: mod_muc_room.erl:3396
@@ -1794,10 +1791,9 @@ msgid "Token TTL"
msgstr ""
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"Забагато (~p) помилок авторизації з цієї IP адреси (~s). Адресу буде "
"розблоковано о ~s UTC"
@@ -2126,10 +2122,9 @@ msgstr ""
"збережено."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "Ваші повідомлення до ~s блокуються. Для розблокування відвідайте ~s"
#: mod_disco.erl:438
+5 -7
View File
@@ -571,8 +571,8 @@ msgid "Given Name"
msgstr "Họ Đệm"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Nhóm "
msgid "Group"
msgstr "Nhóm"
#: mod_roster.erl:956
msgid "Groups"
@@ -946,8 +946,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Bí danh ~s không tồn tại trong phòng này"
#: mod_muc_room.erl:3396
@@ -2140,10 +2139,9 @@ msgid ""
msgstr ""
"Danh sách chờ thư liên lạc ngoại tuyến của bạn đã đầy. Thư này đã bị loại bỏ."
#: ejabberd_captcha.erl:97
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr ""
#: mod_disco.erl:438
+10 -15
View File
@@ -293,8 +293,7 @@ msgid "Configuration"
msgstr "Apontiaedjes"
#: mod_muc_room.erl:3489
#, fuzzy
msgid "Configuration of room ~ts"
msgid "Configuration of room ~s"
msgstr "Apontiaedje del såle ~s"
#: ejabberd_web_admin.erl:937
@@ -571,8 +570,8 @@ msgid "Given Name"
msgstr "No do mitan"
#: mod_shared_roster.erl:879
msgid "Group "
msgstr "Groupe "
msgid "Group"
msgstr "Groupe"
#: mod_roster.erl:956
msgid "Groups"
@@ -702,10 +701,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "Les dmandes di vwès sont dismetowes e cisse conferince ci"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"On n' pout nén evoyî des messaedjes d' aroke sol såle. Li pårticipan (~s) a-"
"st evoyî on messaedje d' aroke (~s) ey a stî tapé foû."
@@ -943,8 +941,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "Li metou no ~s n' egzistêye nén dins l' såle"
#: mod_muc_room.erl:3396
@@ -1798,10 +1795,9 @@ msgid "Token TTL"
msgstr ""
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr ""
"I gn a-st avou pår trop (~p) d' otintifiaedjes k' ont fwait berwete vinant "
"di ciste adresse IP la (~s). L' adresse serè disblokêye a ~s UTC"
@@ -2129,10 +2125,9 @@ msgstr ""
"messaedje a stî tapé å diale."
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "Vos messaedjes po ~s sont blokés. Po les disbloker, alez vey ~s"
#: mod_disco.erl:438
+13 -22
View File
@@ -491,8 +491,7 @@ msgid "Failed to parse HTTP response"
msgstr "HTTP响应解析失败"
#: mod_muc_room.erl:3632
#, fuzzy
msgid "Failed to process option '~ts'"
msgid "Failed to process option '~s'"
msgstr "选项'~s'处理失败"
#: mod_vcard_mnesia.erl:103 mod_vcard_mnesia.erl:117 mod_vcard_sql.erl:160
@@ -561,7 +560,7 @@ msgid "Given Name"
msgstr "中间名"
#: mod_shared_roster.erl:879
msgid "Group "
msgid "Group"
msgstr "组"
#: mod_roster.erl:956
@@ -690,10 +689,9 @@ msgid "Invitations are not allowed in this conference"
msgstr "此会议不允许邀请"
#: mod_muc_room.erl:373 mod_muc_room.erl:519 mod_muc_room.erl:1293
#, fuzzy
msgid ""
"It is not allowed to send error messages to the room. The participant (~ts) "
"has sent an error message (~ts) and got kicked from the room"
"It is not allowed to send error messages to the room. The participant (~s) "
"has sent an error message (~s) and got kicked from the room"
msgstr ""
"不允许将错误消息发送到该房间. 参与者(~s)已发送过一条消息(~s)并已被踢出房间"
@@ -928,8 +926,7 @@ msgid "Nickname can't be empty"
msgstr ""
#: mod_muc_room.erl:2994
#, fuzzy
msgid "Nickname ~ts does not exist in the room"
msgid "Nickname ~s does not exist in the room"
msgstr "昵称~s不在该房间"
#: mod_muc_room.erl:3396
@@ -1754,8 +1751,7 @@ msgid "To"
msgstr "到"
#: mod_register.erl:226
#, fuzzy
msgid "To register, visit ~ts"
msgid "To register, visit ~s"
msgstr "要注册,请访问 ~s"
#: mod_configure.erl:666
@@ -1768,10 +1764,9 @@ msgid "Token TTL"
msgstr "TTL令牌"
#: mod_fail2ban.erl:219
#, fuzzy
msgid ""
"Too many (~p) failed authentications from this IP address (~ts). The address "
"will be unblocked at ~ts UTC"
"Too many (~p) failed authentications from this IP address (~s). The address "
"will be unblocked at ~s UTC"
msgstr "来自IP地址(~p)的(~s)失败认证太多. 该地址将在UTC时间~s被禁用."
#: mod_muc_room.erl:2768 mod_muc_room.erl:3402
@@ -1981,19 +1976,16 @@ msgid "Value 'set' of 'type' attribute is not allowed"
msgstr "不允许 'type' 属性的 'set' 值"
#: pubsub_subscription.erl:237 pubsub_subscription_sql.erl:202
#, fuzzy
msgid "Value of '~ts' should be boolean"
msgid "Value of '~s' should be boolean"
msgstr "'~s' 的值应为布尔型"
#: pubsub_subscription.erl:215 pubsub_subscription_sql.erl:180
#, fuzzy
msgid "Value of '~ts' should be datetime string"
msgid "Value of '~s' should be datetime string"
msgstr "'~s' 的值应为日期时间字符串"
#: pubsub_subscription.erl:209 pubsub_subscription.erl:227
#: pubsub_subscription_sql.erl:174 pubsub_subscription_sql.erl:192
#, fuzzy
msgid "Value of '~ts' should be integer"
msgid "Value of '~s' should be integer"
msgstr "'~s' 的值应为整数"
#: ejabberd_web_admin.erl:433
@@ -2093,10 +2085,9 @@ msgid ""
msgstr "您的联系人离线消息队列已满. 消息已被丢弃"
#: ejabberd_captcha.erl:97
#, fuzzy
msgid ""
"Your subscription request and/or messages to ~ts have been blocked. To "
"unblock your subscription request, visit ~ts"
"Your subscription request and/or messages to ~s have been blocked. To "
"unblock your subscription request, visit ~s"
msgstr "您发送给~s的消息已被阻止. 要解除阻止, 请访问 ~s"
#: mod_disco.erl:438
+10 -10
View File
@@ -21,25 +21,25 @@
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager", "3.6.10"}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.18"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.22"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.4"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.5"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.19"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.39"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.40"}}},
{idna, ".*", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.4.5"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.23"}}},
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.3"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "1.0.1"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.4.6"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.24"}}},
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.4"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "1.0.4"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.6"}}},
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.5"}}},
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.9.0"}}},
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.14"}}},
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.7"}}},
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme.git", {tag, "1.0.4"}}},
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme.git", {tag, "1.0.5"}}},
{base64url, ".*", {git, "https://github.com/dvv/base64url.git", {tag, "v1.0"}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.31"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.32"}}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.32"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.33"}}}},
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
{tag, "1.0.13"}}}},
{tag, "1.0.15"}}}},
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
{tag, "1.1.9"}}}},
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
+4 -2
View File
@@ -304,12 +304,14 @@ fun(Hooks) ->
end,
ProcessErlOpt = fun(Vals) ->
lists:map(
R = lists:map(
fun({i, Path}) ->
{i, ResolveDepPath(Path)};
(ErlOpt) ->
ErlOpt
end, Vals)
end, Vals),
M = lists:filter(fun({d, M}) -> true; (_) -> false end, R),
[{d, 'ALL_DEFS', M} | R]
end,
ProcssXrefExclusions = fun(Items) ->
+1 -1
View File
@@ -209,7 +209,7 @@ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW
CREATE TABLE [dbo].[pubsub_item] (
[nodeid] [bigint] NULL,
[itemid] [varchar] (255) NOT NULL,
[publisher] [text] NOT NULL,
[publisher] [varchar] (250) NOT NULL,
[creation] [varchar] (32) NOT NULL,
[modification] [varchar] (32) NOT NULL,
[payload] [text] NOT NULL DEFAULT ''
+1 -1
View File
@@ -151,7 +151,7 @@ get_commands_spec() ->
module = ?MODULE, function = set_loglevel,
args_desc = ["Desired logging level: none | emergency | alert | critical "
"| error | warning | notice | info | debug"],
args_example = [debug],
args_example = ["debug"],
args = [{loglevel, string}],
result = {res, rescode}},
+6 -1
View File
@@ -37,7 +37,8 @@
-export([start/1, stop/1, start_link/1, set_password/3,
check_password/4, user_exists/2,
get_users/2, count_users/2,
store_type/1, plain_password_required/1]).
store_type/1, plain_password_required/1,
reload/1]).
-include("logger.hrl").
@@ -109,6 +110,10 @@ init(Host) ->
State#state.password, State#state.tls_options),
{ok, State}.
reload(Host) ->
stop(Host),
start(Host).
plain_password_required(_) -> true.
store_type(_) -> external.
+18 -10
View File
@@ -43,7 +43,7 @@
process_closed/2, process_terminated/2, process_info/2]).
%% API
-export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2,
open_session/1, call/3, cast/2, send/2, close/1, close/2, stop/1,
open_session/1, call/3, cast/2, send/2, close/1, close/2, stop_async/1,
reply/2, copy_state/2, set_timeout/2, route/2, format_reason/2,
host_up/1, host_down/1, send_ws_ping/1, bounce_message_queue/2]).
@@ -110,10 +110,9 @@ close(Ref) ->
close(Ref, Reason) ->
xmpp_stream_in:close(Ref, Reason).
-spec stop(pid()) -> ok;
(state()) -> no_return().
stop(Ref) ->
xmpp_stream_in:stop(Ref).
-spec stop_async(pid()) -> ok.
stop_async(Pid) ->
xmpp_stream_in:stop_async(Pid).
-spec send(pid(), xmpp_element()) -> ok;
(state(), xmpp_element()) -> state().
@@ -285,7 +284,8 @@ process_auth_result(#{sasl_mech := Mech,
State.
process_closed(State, Reason) ->
stop(State#{stop_reason => Reason}).
stop_async(self()),
State#{stop_reason => Reason}.
process_terminated(#{sid := SID, socket := Socket,
jid := JID, user := U, server := S, resource := R} = State,
@@ -386,7 +386,7 @@ sasl_mechanisms(Mechs, #{lserver := LServer} = State) ->
(<<"DIGEST-MD5">>) -> Type == plain;
(<<"SCRAM-SHA-1">>) -> Type /= external;
(<<"PLAIN">>) -> true;
(<<"X-OAUTH2">>) -> true;
(<<"X-OAUTH2">>) -> [ejabberd_auth_anonymous] /= ejabberd_auth:auth_modules(LServer);
(<<"EXTERNAL">>) -> maps:get(tls_verify, State, false);
(_) -> false
end, Mechs -- Mechs1).
@@ -491,7 +491,11 @@ handle_authenticated_packet(Pkt, #{lserver := LServer, jid := JID,
#iq{type = set, sub_els = [_]} ->
try xmpp:try_subtag(Pkt2, #xmpp_session{}) of
#xmpp_session{} ->
send(State2, xmpp:make_iq_result(Pkt2));
% It seems that some client are expecting to have response
% to session request be sent from server jid, let's make
% sure it is that.
Pkt3 = xmpp:set_to(Pkt2, jid:make(<<>>, LServer, <<>>)),
send(State2, xmpp:make_iq_result(Pkt3));
_ ->
check_privacy_then_route(State2, Pkt2)
catch _:{xmpp_codec, Why} ->
@@ -902,7 +906,7 @@ bounce_message_queue({_, Pid} = SID, JID) ->
receive {route, Pkt} ->
ejabberd_router:route(Pkt),
bounce_message_queue(SID, JID)
after 0 ->
after 100 ->
ok
end
end.
@@ -937,7 +941,11 @@ fix_from_to(Pkt, #{jid := JID}) when ?is_stanza(Pkt) ->
{U, S, _} -> jid:replace_resource(JID, From#jid.resource);
_ -> From
end,
xmpp:set_from_to(Pkt, From1, JID)
To1 = case xmpp:get_to(Pkt) of
#jid{lresource = <<>>} = To2 -> To2;
_ -> JID
end,
xmpp:set_from_to(Pkt, From1, To1)
end;
fix_from_to(Pkt, _State) ->
Pkt.
+3 -2
View File
@@ -80,7 +80,7 @@ md_tag(p, V) ->
md_tag(h1, V) ->
[<<"\n\n## ">>, V, <<"\n">>];
md_tag(h2, V) ->
[<<"\n\n### ">>, V, <<"\n">>];
[<<"\n__">>, V, <<"__\n\n">>];
md_tag(strong, V) ->
[<<"*">>, V, <<"*">>];
md_tag(_, V) ->
@@ -460,7 +460,8 @@ generate_md_output(File, RegExp, Languages) ->
Cmds4 = [maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3],
Langs = binary:split(Languages, <<",">>, [global]),
Header = <<"---\ntitle: Administration API reference\ntoc: true\nmenu: Administration API\norder: 40\n"
"// Autogenerated with 'ejabberdctl gen_markdown_doc_for_commands'\n---">>,
"// Autogenerated with 'ejabberdctl gen_markdown_doc_for_commands'\n---\n\n"
"This page documents API of ejabberd version ", (ejabberd_option:version())/binary>>,
Out = lists:map(fun(C) -> gen_doc(C, false, Langs) end, Cmds4),
{ok, Fh} = file:open(File, [write]),
io:format(Fh, "~ts~ts", [Header, Out]),
+5 -61
View File
@@ -23,33 +23,14 @@
%%%
%%%----------------------------------------------------------------------
%%% @headerfile "ejabberd_ctl.hrl"
%%% @doc Management of ejabberdctl commands and frontend to ejabberd commands.
%%%
%%% An ejabberdctl command is an abstract function identified by a
%%% name, with a defined number of calling arguments, that can be
%%% defined in any Erlang module and executed using ejabberdctl
%%% administration script.
%%%
%%% Note: strings cannot have blankspaces
%%%
%%% Does not support commands that have arguments with ctypes: list, tuple
%%%
%%% TODO: Update the guide
%%% TODO: Mention this in the release notes
%%% Note: the commands with several words use now the underline: _
%%% It is still possible to call the commands with dash: -
%%% but this is deprecated, and may be removed in a future version.
-module(ejabberd_ctl).
-behaviour(gen_server).
-author('alexey@process-one.net').
-export([start/0, start_link/0, process/1, process2/2,
register_commands/3, unregister_commands/3]).
-export([start/0, start_link/0, process/1, process2/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
@@ -111,8 +92,6 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init([]) ->
ets:new(ejabberd_ctl_cmds, [named_table, set, public]),
ets:new(ejabberd_ctl_host_cmds, [named_table, set, public]),
{ok, #state{}}.
handle_call(Request, From, State) ->
@@ -133,30 +112,10 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%-----------------------------
%% ejabberdctl Command management
%%-----------------------------
register_commands(CmdDescs, Module, Function) ->
ets:insert(ejabberd_ctl_cmds, CmdDescs),
ejabberd_hooks:add(ejabberd_ctl_process,
Module, Function, 50),
ok.
unregister_commands(CmdDescs, Module, Function) ->
lists:foreach(fun(CmdDesc) ->
ets:delete_object(ejabberd_ctl_cmds, CmdDesc)
end, CmdDescs),
ejabberd_hooks:delete(ejabberd_ctl_process,
Module, Function, 50),
ok.
%%-----------------------------
%% Process
%%-----------------------------
-spec process([string()]) -> non_neg_integer().
process(Args) ->
process(Args, ?DEFAULT_VERSION).
@@ -472,7 +431,9 @@ make_status(ok) -> ?STATUS_SUCCESS;
make_status(true) -> ?STATUS_SUCCESS;
make_status(Code) when is_integer(Code), Code > 255 -> ?STATUS_ERROR;
make_status(Code) when is_integer(Code), Code > 0 -> Code;
make_status(_Error) -> ?STATUS_ERROR.
make_status(Error) ->
io:format("Error: ~p~n", [Error]),
?STATUS_ERROR.
get_list_commands(Version) ->
try ejabberd_commands:list_commands(Version) of
@@ -507,13 +468,6 @@ is_supported_args(Args) ->
end,
Args).
get_list_ctls() ->
case catch ets:tab2list(ejabberd_ctl_cmds) of
{'EXIT', _} -> [];
Cs -> [{NameArgs, [], Desc} || {NameArgs, Desc} <- Cs]
end.
%%-----------------------------
%% Print help
%%-----------------------------
@@ -539,8 +493,7 @@ print_usage(HelpMode, MaxC, ShCode, Version) ->
{"restart", [], "Restart ejabberd"},
{"help", ["[--tags [tag] | com?*]"], "Show help (try: ejabberdctl help help)"},
{"mnesia", ["[info]"], "show information of Mnesia system"}] ++
get_list_commands(Version) ++
get_list_ctls(),
get_list_commands(Version),
print(
["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--version ", ?U("api_version"), "] ",
@@ -872,12 +825,3 @@ disable_logging() ->
disable_logging() ->
logger:set_primary_config(level, none).
-endif.
%%-----------------------------
%% Command management
%%-----------------------------
%%+++
%% Struct(Integer res) create_account(Struct(String user, String server, String password))
%%format_usage_xmlrpc(ArgsDef, ResultDef) ->
%% ["aaaa bbb ccc"].
+4 -3
View File
@@ -69,10 +69,11 @@ man(Lang) ->
catch _:undef -> []
end
end, ejabberd_config:callback_modules(all)),
Version = binary_to_list(ejabberd_option:version()),
Options =
["TOP LEVEL OPTIONS",
"-----------------",
tr(Lang, ?T("This section describes top level options of ejabberd.")),
"This section describes top level options of ejabberd "++Version,
io_lib:nl()] ++
lists:flatmap(
fun(Opt) ->
@@ -92,7 +93,7 @@ man(Lang) ->
"MODULES",
"-------",
"[[modules]]",
tr(Lang, ?T("This section describes options of all ejabberd modules.")),
"This section describes options of all modules in ejabberd "++Version,
io_lib:nl()] ++
lists:flatmap(
fun({M, Descr, DocOpts, Backends, Example}) ->
@@ -111,7 +112,7 @@ man(Lang) ->
"LISTENERS",
"-------",
"[[listeners]]",
tr(Lang, ?T("This section describes options of all ejabberd listeners.")),
"This section describes options of all listeners in ejabberd "++Version,
io_lib:nl(),
"TODO"],
AsciiData =
+15 -7
View File
@@ -193,7 +193,10 @@ receive_headers(#state{trail = Trail} = State) ->
Socket = State#state.socket,
Data = SockMod:recv(Socket, 0, 300000),
case Data of
{error, _} -> ok;
{error, Error} ->
?DEBUG("Error when retrieving http headers ~p: ~p",
[State#state.sockmod, Error]),
ok;
{ok, D} ->
parse_headers(State#state{trail = <<Trail/binary, D/binary>>})
end.
@@ -386,7 +389,7 @@ extract_path_query(#state{request_method = Method,
{'EXIT', _Reason} -> [];
LQ -> LQ
end,
{State, {LPath, LQuery, <<"">>}}
{State, {LPath, LQuery, <<"">>, Path}}
end;
extract_path_query(#state{request_method = Method,
request_path = {abs_path, Path},
@@ -402,7 +405,7 @@ extract_path_query(#state{request_method = Method,
{LPath, _Query} ->
case Method of
'PUT' ->
{State, {LPath, [], Trail}};
{State, {LPath, [], Trail, Path}};
'POST' ->
case recv_data(State) of
{ok, Data} ->
@@ -410,7 +413,7 @@ extract_path_query(#state{request_method = Method,
{'EXIT', _Reason} -> [];
LQ -> LQ
end,
{State, {LPath, LQuery, Data}};
{State, {LPath, LQuery, Data, Path}};
error ->
{State, false}
end
@@ -451,7 +454,7 @@ process_request(#state{request_method = Method,
case extract_path_query(State) of
{State2, false} ->
{State2, make_bad_request(State)};
{State2, {LPath, LQuery, Data}} ->
{State2, {LPath, LQuery, Data, RawPath}} ->
PeerName = case SockPeer of
none ->
case SockMod of
@@ -471,6 +474,7 @@ process_request(#state{request_method = Method,
IP = analyze_ip_xff(IPHere, XFF),
Request = #request{method = Method,
path = LPath,
raw_path = RawPath,
q = LQuery,
auth = Auth,
length = Length,
@@ -857,9 +861,13 @@ parse_urlencoded(<<>>, Last, Cur, _State) ->
parse_urlencoded(undefined, _, _, _) -> [].
apply_custom_headers(Headers, CustomHeaders) ->
M = maps:merge(maps:from_list(Headers),
{Doctype, Headers2} = case Headers -- [html] of
Headers -> {[], Headers};
Other -> {[html], Other}
end,
M = maps:merge(maps:from_list(Headers2),
maps:from_list(CustomHeaders)),
maps:to_list(M).
Doctype ++ maps:to_list(M).
% The following code is mostly taken from yaws_ssl.erl
+113 -23
View File
@@ -36,14 +36,14 @@ doc() ->
{listen,
#{value => "[Options, ...]",
desc =>
?T("The option for listeners configuration. See "
"<<listeners,Listeners>> section of this document "
?T("The option for listeners configuration. See the "
"http://../listen/[Listen Modules] section "
"for details.")}},
{modules,
#{value => "{Module: Options}",
desc =>
?T("The option for modules configuration. See "
"<<modules,Modules>> section of this document "
"http://../modules/[Modules] section "
"for details.")}},
{loglevel,
#{value =>
@@ -60,32 +60,49 @@ doc() ->
#{value => "timeout()",
desc =>
?T("The time of a cached item to keep in cache. "
"Once it's expired, the corresponding item is "
"erased from cache. The default value is 'one hour'.")}},
"Once it's expired, the corresponding item is "
"erased from cache. The default value is 'one hour'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_cache_life_time', 'oauth_cache_life_time', "
"'router_cache_life_time', and 'sm_cache_life_time'.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Whether or not to cache missed lookups. When there is "
"an attempt to lookup for a value in a database and "
"this value is not found and the option is set to 'true', "
"this attempt will be cached and no attempts will be "
"performed until the cache expires (see 'cache_life_time'). "
"Usually you don't want to change it. Default is 'true'.")}},
"an attempt to lookup for a value in a database and "
"this value is not found and the option is set to 'true', "
"this attempt will be cached and no attempts will be "
"performed until the cache expires (see 'cache_life_time'). "
"Usually you don't want to change it. Default is 'true'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_cache_missed', 'oauth_cache_missed', "
"'router_cache_missed', and 'sm_cache_missed'.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("A maximum number of items (not memory!) in cache. "
"The rule of thumb, for all tables except rosters, "
"you should set it to the number of maximum online "
"users you expect. For roster multiply this number "
"by 20 or so. If the cache size reaches this threshold, "
"it's fully cleared, i.e. all items are deleted, and "
"the corresponding warning is logged. You should avoid "
"frequent cache clearance, because this degrades "
"performance. The default value is '1000'.")}},
?T("A maximum number of items (not memory!) in cache. "
"The rule of thumb, for all tables except rosters, "
"you should set it to the number of maximum online "
"users you expect. For roster multiply this number "
"by 20 or so. If the cache size reaches this threshold, "
"it's fully cleared, i.e. all items are deleted, and "
"the corresponding warning is logged. You should avoid "
"frequent cache clearance, because this degrades "
"performance. The default value is '1000'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_cache_size', 'oauth_cache_size', "
"'router_cache_size', and 'sm_cache_size'.")}},
{use_cache,
#{value => "true | false",
desc => ?T("Enable or disable cache. The default is 'true'.")}},
desc =>
?T("Enable or disable cache. The default is 'true'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_use_cache', 'oauth_use_cache', 'router_use_cache', "
"and 'sm_use_cache'.")}},
{default_db,
#{value => "mnesia | sql",
desc =>
@@ -300,6 +317,13 @@ doc() ->
"'sasl_anon' means that the SASL Anonymous method will be used. "
"'both' means that SASL Anonymous and login anonymous are both "
"enabled. The default value is 'sasl_anon'.")}},
{api_permissions,
#{value => "[Permission, ...]",
desc =>
?T("Define the permissions for API access. Please consult the "
"ejabberd Docs web -> For Developers -> ejabberd ReST API -> "
"https://docs.ejabberd.im/developer/ejabberd-api/permissions/"
"[API Permissions].")}},
{append_host_config,
#{value => "{Host: Options}",
desc =>
@@ -330,6 +354,13 @@ doc() ->
"considered successful as long as authentication of "
"at least one of the methods succeeds. "
"The default value is '[mnesia]'.")}},
{auth_opts,
#{value => "[Option, ...]",
desc =>
?T("This is used by the contributed module "
"'ejabberd_auth_http' that can be installed from the "
"'ejabberd-contrib' Git repository. Please refer to that "
"module's README file for details.")}},
{auth_password_format,
#{value => "plain | scram",
desc =>
@@ -415,7 +446,8 @@ doc() ->
"any given JID. The option is intended to protect the server "
"from CAPTCHA DoS. The default value is 'infinity'.")}},
{captcha_host,
#{desc => ?T("Deprecated. Use 'captcha_url' instead.")}},
#{value => "String",
desc => ?T("Deprecated. Use 'captcha_url' instead.")}},
{captcha_url,
#{value => ?T("URL"),
desc =>
@@ -453,7 +485,7 @@ doc() ->
desc =>
?T("A list of Erlang nodes to connect on ejabberd startup. "
"This option is mostly intended for ejabberd customization "
"and sofisticated setups. The default value is an empty list.")}},
"and sophisticated setups. The default value is an empty list.")}},
{define_macro,
#{value => "{MacroName: MacroValue}",
desc =>
@@ -516,6 +548,11 @@ doc() ->
#{value => "2..1000",
desc =>
?T("The number of components to balance.")}}]},
{extauth_pool_name,
#{value => ?T("Name"),
desc =>
?T("Define the pool name appendix, so the full pool name will be "
"'extauth_pool_Name'. The default value is the hostname.")}},
{extauth_pool_size,
#{value => ?T("Size"),
desc =>
@@ -527,6 +564,28 @@ doc() ->
desc =>
?T("Indicate in this option the full path to the external "
"authentication script. The script must be executable by ejabberd.")}},
{ext_api_headers,
#{value => "Headers",
desc =>
?T("String of headers (separated with commas ',') that will be "
"provided by ejabberd when sending ReST requests. "
"The default value is an empty string of headers: '\"\"'.")}},
{ext_api_http_pool_size,
#{value => "pos_integer()",
desc =>
?T("Define the size of the HTTP pool, that is, the maximum number "
"of sessions that the ejabberd ReST service will handle "
"simultaneously. The default value is: '100'.")}},
{ext_api_path_oauth,
#{value => "Path",
desc =>
?T("Define the base URI path when performing OAUTH ReST requests. "
"The default value is: '\"/oauth\"'.")}},
{ext_api_url,
#{value => "URL",
desc =>
?T("Define the base URI when performing ReST requests. "
"The default value is: '\"http://localhost/api\"'.")}},
{fqdn,
#{value => ?T("Domain"),
desc =>
@@ -577,6 +636,26 @@ doc() ->
"file 'Filename'. The options that do not match this "
"criteria are not accepted. The default value is to include "
"all options.")}}]},
{jwt_auth_only_rule,
#{value => ?T("AccessName"),
desc =>
?T("This ACL rule defines accounts that can use only this auth "
"method, even if others are also defined in the ejabberd "
"configuration file. In other words: if there are several auth "
"methods enabled for this host (JWT, SQL, ...), users that "
"match this rule can only use JWT. "
"The default value is 'none'.")}},
{jwt_jid_field,
#{value => ?T("FieldName"),
desc =>
?T("By default, the JID is defined in the '\"jid\"' JWT field. "
"This option allows to specify other JWT field name "
"where the JID is defined.")}},
{jwt_key,
#{value => ?T("FilePath"),
desc =>
?T("Path to the file that contains the JWK Key. "
"The default value is 'undefined'.")}},
{language,
#{value => ?T("Language"),
desc =>
@@ -748,7 +827,7 @@ doc() ->
desc =>
{?T("Whether to use 'new' SQL schema. All schemas are located "
"at <https://github.com/processone/ejabberd/tree/~s/sql>. "
"There are two schemas available. The default lecacy schema "
"There are two schemas available. The default legacy schema "
"allows to store one XMPP domain into one ejabberd database. "
"The 'new' schema allows to handle several XMPP domains in a "
"single ejabberd database. Using this 'new' schema is best when "
@@ -780,6 +859,12 @@ doc() ->
desc =>
?T("Same as 'cache_size', but applied to OAuth cache "
"only. If not set, the value from 'cache_size' will be used.")}},
{oauth_client_id_check,
#{value => "allow | db | deny",
desc =>
?T("Define whether the client authentication is always allowed, "
"denied, or it will depend if the client ID is present in the "
"database. The default value is 'allow'.")}},
{oauth_use_cache,
#{value => "true | false",
desc =>
@@ -1144,6 +1229,11 @@ doc() ->
?T("The port where the SQL server is accepting connections. "
"The default is '3306' for MySQL, '5432' for PostgreSQL and "
"'1433' for MSSQL. The option has no effect for SQLite.")}},
{sql_prepared_statements,
#{value => "true | false",
desc =>
?T("This option is 'true' by default, and is useful to disable "
"prepared statements. The option is valid for PostgreSQL.")}},
{sql_query_timeout,
#{value => "timeout()",
desc =>
+5 -5
View File
@@ -319,7 +319,7 @@ host_down(Host) ->
case ejabberd_router:host_of_route(From) of
Host ->
ejabberd_s2s_out:send(Pid, Err),
ejabberd_s2s_out:stop(Pid);
ejabberd_s2s_out:stop_async(Pid);
_ ->
ok
end;
@@ -473,14 +473,14 @@ new_connection(MyServer, Server, From, FromTo,
if Pid1 == Pid ->
ejabberd_s2s_out:connect(Pid);
true ->
ejabberd_s2s_out:stop(Pid)
ejabberd_s2s_out:stop_async(Pid)
end,
[Pid1];
{aborted, Reason} ->
?ERROR_MSG("Failed to register s2s connection ~ts -> ~ts: "
"Mnesia failure: ~p",
[MyServer, Server, Reason]),
ejabberd_s2s_out:stop(Pid),
ejabberd_s2s_out:stop_async(Pid),
[]
end.
@@ -553,13 +553,13 @@ stop_s2s_connections(Err) ->
lists:foreach(
fun({_Id, Pid, _Type, _Module}) ->
ejabberd_s2s_in:send(Pid, Err),
ejabberd_s2s_in:stop(Pid),
ejabberd_s2s_in:stop_async(Pid),
supervisor:terminate_child(ejabberd_s2s_in_sup, Pid)
end, supervisor:which_children(ejabberd_s2s_in_sup)),
lists:foreach(
fun({_Id, Pid, _Type, _Module}) ->
ejabberd_s2s_out:send(Pid, Err),
ejabberd_s2s_out:stop(Pid),
ejabberd_s2s_out:stop_async(Pid),
supervisor:terminate_child(ejabberd_s2s_out_sup, Pid)
end, supervisor:which_children(ejabberd_s2s_out_sup)),
_ = mnesia:clear_table(s2s),
+6 -4
View File
@@ -38,7 +38,7 @@
-export([handle_unexpected_info/2, handle_unexpected_cast/2,
reject_unauthenticated_packet/2, process_closed/2]).
%% API
-export([stop/1, close/1, close/2, send/2, update_state/2, establish/1,
-export([stop_async/1, close/1, close/2, send/2, update_state/2, establish/1,
host_up/1, host_down/1]).
-include("xmpp.hrl").
@@ -64,8 +64,9 @@ close(Ref) ->
close(Ref, Reason) ->
xmpp_stream_in:close(Ref, Reason).
stop(Ref) ->
xmpp_stream_in:stop(Ref).
-spec stop_async(pid()) -> ok.
stop_async(Pid) ->
xmpp_stream_in:stop_async(Pid).
accept(Ref) ->
xmpp_stream_in:accept(Ref).
@@ -130,7 +131,8 @@ process_closed(#{server := LServer} = State, Reason) ->
end,
?INFO_MSG("Closing inbound s2s connection ~ts -> ~ts: ~ts",
[RServer, LServer, xmpp_stream_out:format_error(Reason)]),
stop(State).
stop_async(self()),
State.
%%%===================================================================
%%% xmpp_stream_in callbacks
+9 -7
View File
@@ -36,7 +36,7 @@
-export([process_auth_result/2, process_closed/2, handle_unexpected_info/2,
handle_unexpected_cast/2, process_downgraded/2]).
%% API
-export([start/3, start_link/3, connect/1, close/1, close/2, stop/1, send/2,
-export([start/3, start_link/3, connect/1, close/1, close/2, stop_async/1, send/2,
route/2, establish/1, update_state/2, host_up/1, host_down/1]).
-include("xmpp.hrl").
@@ -79,10 +79,9 @@ close(Ref) ->
close(Ref, Reason) ->
xmpp_stream_out:close(Ref, Reason).
-spec stop(pid()) -> ok;
(state()) -> no_return().
stop(Ref) ->
xmpp_stream_out:stop(Ref).
-spec stop_async(pid()) -> ok.
stop_async(Pid) ->
xmpp_stream_out:stop_async(Pid).
-spec send(pid(), xmpp_element()) -> ok;
(state(), xmpp_element()) -> state().
@@ -150,7 +149,8 @@ process_closed(#{server := LServer, remote_server := RServer,
Reason) ->
?INFO_MSG("Closing outbound s2s connection ~ts -> ~ts: ~ts",
[LServer, RServer, format_error(Reason)]),
stop(State);
stop_async(self()),
State;
process_closed(#{server := LServer, remote_server := RServer} = State,
Reason) ->
Delay = get_delay(),
@@ -248,7 +248,9 @@ handle_send(El, Pkt, #{server_host := ServerHost} = State) ->
handle_timeout(#{on_route := Action, lang := Lang} = State) ->
case Action of
bounce -> stop(State);
bounce ->
stop_async(self()),
State;
_ ->
Txt = ?T("Idle connection"),
send(State, xmpp:serr_connection_timeout(Txt, Lang))
+5 -6
View File
@@ -33,7 +33,7 @@
-export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4,
handle_authenticated_packet/2, get_password_fun/1, tls_options/1]).
%% API
-export([send/2, close/1, close/2, stop/1]).
-export([send/2, close/1, close/2, stop_async/1]).
-include("xmpp.hrl").
-include("logger.hrl").
@@ -59,7 +59,7 @@ stop() ->
lists:foreach(
fun({_Id, Pid, _Type, _Module}) ->
send(Pid, Err),
stop(Pid),
stop_async(Pid),
supervisor:terminate_child(ejabberd_service_sup, Pid)
end, supervisor:which_children(ejabberd_service_sup)),
_ = supervisor:terminate_child(ejabberd_sup, ejabberd_service_sup),
@@ -83,10 +83,9 @@ close(Ref) ->
close(Ref, Reason) ->
xmpp_stream_in:close(Ref, Reason).
-spec stop(pid()) -> ok;
(state()) -> no_return().
stop(Ref) ->
xmpp_stream_in:stop(Ref).
-spec stop_async(pid()) -> ok.
stop_async(Pid) ->
xmpp_stream_in:stop_async(Pid).
%%%===================================================================
%%% xmpp_stream_in callbacks
+1 -1
View File
@@ -538,7 +538,7 @@ host_down(Host) ->
lists:foreach(
fun(#session{sid = {_, Pid}}) when node(Pid) == node() ->
ejabberd_c2s:send(Pid, Err),
ejabberd_c2s:stop(Pid);
ejabberd_c2s:stop_async(Pid);
(_) ->
ok
end, get_sessions(Mod, Host)),
+29 -55
View File
@@ -51,10 +51,8 @@
sqlite_file/1,
encode_term/1,
decode_term/1,
odbc_config/0,
freetds_config/0,
odbcinst_config/0,
init_mssql/1,
init_mssql/0,
keep_alive/2,
to_list/2,
to_array/2]).
@@ -253,7 +251,7 @@ to_list(EscapeFun, Val) ->
to_array(EscapeFun, Val) ->
Escaped = lists:join(<<",">>, lists:map(EscapeFun, Val)),
[<<"{">>, Escaped, <<"}">>].
lists:flatten([<<"{">>, Escaped, <<"}">>]).
to_string_literal(odbc, S) ->
<<"'", (escape(S))/binary, "'">>;
@@ -736,11 +734,11 @@ pgsql_sql_query_format(SQLQuery) ->
pgsql_escape() ->
#sql_escape{string = fun(X) -> <<"E'", (escape(X))/binary, "'">> end,
integer = fun(X) -> misc:i2l(X) end,
boolean = fun(true) -> <<"1">>;
(false) -> <<"0">>
boolean = fun(true) -> <<"'t'">>;
(false) -> <<"'f'">>
end,
in_array_string = fun(X) -> <<"E'", (escape(X))/binary, "'">> end,
like_escape = fun() -> <<"">> end
like_escape = fun() -> <<"ESCAPE E'\\\\'">> end
}.
sqlite_sql_query(SQLQuery) ->
@@ -776,11 +774,13 @@ pgsql_prepare(SQLQuery, State) ->
like_escape = fun() -> escape end},
{RArgs, _} =
lists:foldl(
fun(arg, {Acc, I}) ->
{[<<$$, (integer_to_binary(I))/binary>> | Acc], I + 1};
(escape, {Acc, I}) ->
{[<<"">> | Acc], I}
end, {[], 1}, (SQLQuery#sql_query.args)(Escape)),
fun(arg, {Acc, I}) ->
{[<<$$, (integer_to_binary(I))/binary>> | Acc], I + 1};
(escape, {Acc, I}) ->
{[<<"ESCAPE E'\\\\'">> | Acc], I};
(List, {Acc, I}) when is_list(List) ->
{[<<$$, (integer_to_binary(I))/binary>> | Acc], I + 1}
end, {[], 1}, (SQLQuery#sql_query.args)(Escape)),
Args = lists:reverse(RArgs),
%N = length((SQLQuery#sql_query.args)(Escape)),
%Args = [<<$$, (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)],
@@ -1001,12 +1001,18 @@ pgsql_execute_to_odbc(_) -> {updated, undefined}.
%% part of init/1
%% Open a database connection to MySQL
mysql_connect(Server, Port, DB, Username, Password, ConnectTimeout, _, _) ->
mysql_connect(Server, Port, DB, Username, Password, ConnectTimeout, Transport, _) ->
SSLOpts = case Transport of
ssl ->
[ssl_required];
_ ->
[]
end,
case p1_mysql_conn:start(binary_to_list(Server), Port,
binary_to_list(Username),
binary_to_list(Password),
binary_to_list(DB),
ConnectTimeout, fun log/3)
ConnectTimeout, fun log/3, SSLOpts)
of
{ok, Ref} ->
p1_mysql_conn:fetch(
@@ -1101,8 +1107,9 @@ db_opts(Host) ->
SSLOpts = get_ssl_opts(Transport, Host),
case Type of
mssql ->
[mssql, <<"DSN=", Host/binary, ";UID=", User/binary,
";PWD=", Pass/binary>>, Timeout];
[mssql, <<"DRIVER=FreeTDS;SERVER=", Server/binary, ";UID=", User/binary,
";DATABASE=", DB/binary ,";PWD=", Pass/binary,
";PORT=", (integer_to_binary(Port))/binary ,";CLIENT_CHARSET=UTF-8;">>, Timeout];
_ ->
[Type, Server, Port, DB, User, Pass, Timeout, Transport, SSLOpts]
end
@@ -1112,6 +1119,8 @@ warn_if_ssl_unsupported(tcp, _) ->
ok;
warn_if_ssl_unsupported(ssl, pgsql) ->
ok;
warn_if_ssl_unsupported(ssl, mysql) ->
ok;
warn_if_ssl_unsupported(ssl, Type) ->
?WARNING_MSG("SSL connection is not supported for ~ts", [Type]).
@@ -1142,44 +1151,15 @@ get_ssl_opts(ssl, Host) ->
get_ssl_opts(tcp, _) ->
[].
init_mssql(Host) ->
Server = ejabberd_option:sql_server(Host),
Port = ejabberd_option:sql_port(Host),
DB = case ejabberd_option:sql_database(Host) of
undefined -> <<"ejabberd">>;
D -> D
end,
FreeTDS = io_lib:fwrite("[~ts]~n"
"\thost = ~ts~n"
"\tport = ~p~n"
"\tclient charset = UTF-8~n"
"\ttds version = 7.1~n",
[Host, Server, Port]),
ODBCINST = io_lib:fwrite("[freetds]~n"
"Description = MSSQL connection~n"
"Driver = libtdsodbc.so~n"
"Setup = libtdsS.so~n"
"UsageCount = 1~n"
"FileUsage = 1~n", []),
ODBCINI = io_lib:fwrite("[~ts]~n"
"Description = MS SQL~n"
"Driver = freetds~n"
"Servername = ~ts~n"
"Database = ~ts~n"
"Port = ~p~n",
[Host, Host, DB, Port]),
?DEBUG("~ts:~n~ts", [freetds_config(), FreeTDS]),
init_mssql() ->
ODBCINST = io_lib:fwrite("[FreeTDS]~n"
"Driver = libtdsodbc.so~n", []),
?DEBUG("~ts:~n~ts", [odbcinst_config(), ODBCINST]),
?DEBUG("~ts:~n~ts", [odbc_config(), ODBCINI]),
case filelib:ensure_dir(freetds_config()) of
case filelib:ensure_dir(odbcinst_config()) of
ok ->
try
ok = write_file_if_new(freetds_config(), FreeTDS),
ok = write_file_if_new(odbcinst_config(), ODBCINST),
ok = write_file_if_new(odbc_config(), ODBCINI),
os:putenv("ODBCSYSINI", tmp_dir()),
os:putenv("FREETDS", freetds_config()),
os:putenv("FREETDSCONF", freetds_config()),
ok
catch error:{badmatch, {error, Reason} = Err} ->
?ERROR_MSG("Failed to create temporary files in ~ts: ~ts",
@@ -1204,12 +1184,6 @@ tmp_dir() ->
_ -> filename:join(["/tmp", "ejabberd"])
end.
odbc_config() ->
filename:join(tmp_dir(), "odbc.ini").
freetds_config() ->
filename:join(tmp_dir(), "freetds.conf").
odbcinst_config() ->
filename:join(tmp_dir(), "odbcinst.ini").
+5 -1
View File
@@ -648,7 +648,11 @@ make_sql_upsert_insert(Table, ParseRes) ->
]),
State.
make_sql_upsert_pgsql901(Table, ParseRes) ->
make_sql_upsert_pgsql901(Table, ParseRes0) ->
ParseRes = lists:map(
fun({"family", A2, A3}) -> {"\"family\"", A2, A3};
(Other) -> Other
end, ParseRes0),
Update = make_sql_upsert_update(Table, ParseRes),
Vals =
lists:map(
+1 -3
View File
@@ -84,8 +84,6 @@ stop() ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 20).
init([]) ->
file:delete(ejabberd_sql:freetds_config()),
file:delete(ejabberd_sql:odbc_config()),
file:delete(ejabberd_sql:odbcinst_config()),
ejabberd_hooks:add(host_up, ?MODULE, start, 20),
ejabberd_hooks:add(host_down, ?MODULE, stop, 90),
@@ -98,7 +96,7 @@ init([Host]) ->
sqlite ->
check_sqlite_db(Host);
mssql ->
ejabberd_sql:init_mssql(Host);
ejabberd_sql:init_mssql();
_ ->
ok
end,
+44 -19
View File
@@ -46,7 +46,8 @@ start_link(_, _, _) ->
fail().
-else.
-export([tcp_init/2, udp_init/2, udp_recv/5, start/3,
start_link/3, accept/1, listen_opt_type/1, listen_options/0]).
start_link/3, accept/1, listen_opt_type/1, listen_options/0,
get_password/2]).
-include("logger.hrl").
@@ -74,6 +75,21 @@ start_link(_SockMod, Socket, Opts) ->
accept(_Pid) ->
ok.
get_password(User, Realm) ->
case ejabberd_hooks:run_fold(stun_get_password, <<>>, [User, Realm]) of
Password when byte_size(Password) > 0 ->
Password;
<<>> ->
case ejabberd_auth:get_password_s(User, Realm) of
Password when is_binary(Password) ->
Password;
_ ->
?INFO_MSG("Cannot use hashed password of ~s@~s for "
"STUN/TURN authentication", [User, Realm]),
<<>>
end
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -85,27 +101,36 @@ prepare_turn_opts(Opts, _UseTurn = false) ->
set_certfile(Opts);
prepare_turn_opts(Opts, _UseTurn = true) ->
NumberOfMyHosts = length(ejabberd_option:hosts()),
case proplists:get_value(turn_ip, Opts) of
undefined ->
?WARNING_MSG("Option 'turn_ip' is undefined, "
"most likely the TURN relay won't be working "
"properly", []);
_ ->
ok
end,
AuthFun = fun ejabberd_auth:get_password_s/2,
TurnIP = case proplists:get_value(turn_ip, Opts) of
undefined ->
MyIP = misc:get_my_ip(),
case MyIP of
{127, _, _, _} ->
?WARNING_MSG("Option 'turn_ip' is undefined and "
"the server's hostname doesn't "
"resolve to a public IPv4 address, "
"most likely the TURN relay won't be "
"working properly", []);
_ ->
ok
end,
[{turn_ip, MyIP}];
_ ->
[]
end,
AuthFun = fun ejabberd_stun:get_password/2,
Shaper = proplists:get_value(shaper, Opts, none),
AuthType = proplists:get_value(auth_type, Opts, user),
Realm = case proplists:get_value(auth_realm, Opts) of
undefined when AuthType == user ->
if NumberOfMyHosts > 1 ->
?WARNING_MSG("You have several virtual "
"hosts configured, but option "
"'auth_realm' is undefined and "
"'auth_type' is set to 'user', "
"most likely the TURN relay won't "
"be working properly. Using ~ts as "
"a fallback", [ejabberd_config:get_myname()]);
?INFO_MSG("You have several virtual hosts "
"configured, but option 'auth_realm' is "
"undefined and 'auth_type' is set to "
"'user', so the TURN relay might not be "
"working properly. Using ~ts as a "
"fallback",
[ejabberd_config:get_myname()]);
true ->
ok
end,
@@ -114,8 +139,8 @@ prepare_turn_opts(Opts, _UseTurn = true) ->
[]
end,
MaxRate = ejabberd_shaper:get_max_rate(Shaper),
Opts1 = Realm ++ [{auth_fun, AuthFun},{shaper, MaxRate} |
lists:keydelete(shaper, 1, Opts)],
Opts1 = TurnIP ++ Realm ++ [{auth_fun, AuthFun},{shaper, MaxRate} |
lists:keydelete(shaper, 1, Opts)],
set_certfile(Opts1).
set_certfile(Opts) ->
+19 -5
View File
@@ -150,7 +150,21 @@ url_to_path(URL) -> str:tokens(URL, <<"/">>).
%%%==================================
%%%% process/2
process([<<"server">>, SHost | RPath] = Path,
process(Path, #request{raw_path = RawPath} = Request) ->
Continue = case Path of
[E] ->
binary:match(E, <<".">>) /= nomatch;
_ ->
false
end,
case Continue orelse binary:at(RawPath, size(RawPath) - 1) == $/ of
true ->
process2(Path, Request);
_ ->
{301, [{<<"Location">>, <<RawPath/binary, "/">>}], <<>>}
end.
process2([<<"server">>, SHost | RPath] = Path,
#request{auth = Auth, lang = Lang, host = HostHTTP,
method = Method} =
Request) ->
@@ -185,7 +199,7 @@ process([<<"server">>, SHost | RPath] = Path,
end;
false -> ejabberd_web:error(not_found)
end;
process(RPath,
process2(RPath,
#request{auth = Auth, lang = Lang, host = HostHTTP,
method = Method} =
Request) ->
@@ -430,7 +444,7 @@ process_admin(_Host, #request{path = [<<"additions.js">>]}, _) ->
process_admin(global, #request{path = [<<"vhosts">>], lang = Lang}, AJID) ->
Res = list_vhosts(Lang, AJID),
make_xhtml((?H1GL((translate:translate(Lang, ?T("Virtual Hosts"))),
<<"virtual-hosting">>, ?T("Virtual Hosting")))
<<"basic/#xmpp-domains">>, ?T("XMPP Domains")))
++ Res,
global, Lang, AJID, 1);
process_admin(Host, #request{path = [<<"users">>], q = Query,
@@ -465,7 +479,7 @@ process_admin(Host, #request{path = [<<"last-activity">>],
list_last_activity(Host, Lang, false, Month);
_ -> list_last_activity(Host, Lang, true, Month)
end,
PageH1 = ?H1GL(translate:translate(Lang, ?T("Users Last Activity")), <<"mod-last">>, <<"mod_last">>),
PageH1 = ?H1GL(translate:translate(Lang, ?T("Users Last Activity")), <<"modules/#mod-last">>, <<"mod_last">>),
make_xhtml(PageH1 ++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
@@ -496,7 +510,7 @@ process_admin(Host, #request{path = [<<"last-activity">>],
Host, Lang, AJID, 3);
process_admin(Host, #request{path = [<<"stats">>], lang = Lang}, AJID) ->
Res = get_stats(Host, Lang),
PageH1 = ?H1GL(translate:translate(Lang, ?T("Statistics")), <<"mod-stats">>, <<"mod_stats">>),
PageH1 = ?H1GL(translate:translate(Lang, ?T("Statistics")), <<"modules/#mod-stats">>, <<"mod_stats">>),
Level = case Host of
global -> 1;
_ -> 3
+1 -1
View File
@@ -570,7 +570,7 @@ compile_result(Results) ->
end.
compile_options() ->
[verbose, report_errors, report_warnings]
[verbose, report_errors, report_warnings, ?ALL_DEFS]
++ [{i, filename:join(app_dir(App), "include")}
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]]
++ [{i, filename:join(mod_dir(Mod), "include")}
+2 -2
View File
@@ -86,7 +86,7 @@ start_link() ->
end.
init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 60),
ejabberd_hooks:add(host_up, ?MODULE, start_modules, 40),
ejabberd_hooks:add(host_down, ?MODULE, stop_modules, 70),
ets:new(ejabberd_modules,
@@ -97,7 +97,7 @@ init([]) ->
-spec stop() -> ok.
stop() ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50),
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 60),
ejabberd_hooks:delete(host_up, ?MODULE, start_modules, 40),
ejabberd_hooks:delete(host_down, ?MODULE, stop_modules, 70),
stop_modules(),
+10 -2
View File
@@ -40,8 +40,8 @@
read_css/1, read_img/1, read_js/1, read_lua/1, try_url/1,
intersection/2, format_val/1, cancel_timer/1, unique_timestamp/0,
is_mucsub_message/1, best_match/2, pmap/2, peach/2, format_exception/4,
parse_ip_mask/1, match_ip_mask/3, format_hosts_list/1, format_cycle/1,
delete_dir/1]).
get_my_ip/0, parse_ip_mask/1, match_ip_mask/3, format_hosts_list/1,
format_cycle/1, delete_dir/1]).
%% Deprecated functions
-export([decode_base64/1, encode_base64/1]).
@@ -509,6 +509,14 @@ format_exception(Level, Class, Reason, Stacktrace) ->
end).
-endif.
-spec get_my_ip() -> inet:ip_address().
get_my_ip() ->
{ok, MyHostName} = inet:gethostname(),
case inet:getaddr(MyHostName, inet) of
{ok, Addr} -> Addr;
{error, _} -> {127, 0, 0, 1}
end.
-spec parse_ip_mask(binary()) -> {ok, {inet:ip4_address(), 0..32}} |
{ok, {inet:ip6_address(), 0..128}} |
error.
+1 -1
View File
@@ -288,5 +288,5 @@ mod_doc() ->
[{report_commands_node,
#{value => "true | false",
desc =>
?T("Provide the Commands item in the Service Disvocery. "
?T("Provide the Commands item in the Service Discovery. "
"Default value: 'false'.")}}]}.
+31 -29
View File
@@ -1513,6 +1513,9 @@ send_stanza(FromString, ToString, Stanza) ->
catch _:{xmpp_codec, Why} ->
io:format("incorrect stanza: ~ts~n", [xmpp:format_error(Why)]),
{error, Why};
_:{badmatch, {error, {Code, Why}}} when is_integer(Code) ->
io:format("invalid xml: ~p~n", [Why]),
{error, Why};
_:{badmatch, {error, Why}} ->
io:format("invalid xml: ~p~n", [Why]),
{error, Why};
@@ -1596,41 +1599,33 @@ mod_doc() ->
[?T("This module provides additional administrative commands."), "",
?T("Details for some commands:"), "",
?T("- 'ban-acount':"),
?T("This command kicks all the connected sessions of the
account from the server. It also changes their password to
a randomly generated one, so they can't login anymore
unless a server administrator changes their password
again. 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
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_rooms'"),
?T("This command kicks all the connected sessions of the account "
"from the server. It also changes their password to a randomly "
"generated one, so they can't login anymore unless a server "
"administrator changes their password again. 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. See an example below."),
?T("- 'pushroster' (and 'pushroster-all'):"),
?T("The roster file must be placed, if using Windows, on
the directory where you installed ejabberd: C:/Program
Files/ejabberd or similar. If you use other Operating
System, place the file on the same directory where the
.beam files are installed. See below an example roster
file."),
?T("The roster file must be placed, if using Windows, on the "
"directory where you installed ejabberd: "
"C:/Program Files/ejabberd or similar. If you use other "
"Operating System, place the file on the same directory where "
"the .beam files are installed. See below an example roster file."),
?T("- 'srg-create':"),
?T("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'")],
?T("If you want to put a group Name with blankspaces, use the "
"characters \"\' and \'\" to define when the Name starts and "
"ends. See an example below.")],
opts =>
[{module_resource,
#{value => ?T("Resource"),
desc =>
?T("Indicate the resource that the XMPP stanzas must
use in the FROM or TO JIDs. This is only useful in
the 'get_vcard*' and 'set_vcard*' commands. The
default value is 'mod_admin_extra'.")}}],
?T("Indicate the resource that the XMPP stanzas must use "
"in the FROM or TO JIDs. This is only useful in the "
"'get_vcard*' and 'set_vcard*' commands. The default "
"value is 'mod_admin_extra'.")}}],
example =>
[{?T("With this configuration, vCards can only be modified
with mod_admin_extra commands:"),
[{?T("With this configuration, vCards can only be modified with "
"mod_admin_extra commands:"),
["acl:",
" adminextraresource:",
" - resource: \"modadminextraf8x,31ad\"",
@@ -1645,4 +1640,11 @@ mod_doc() ->
{?T("Content of roster file for 'pushroster' command:"),
["[{<<\"bob\">>, <<\"example.org\">>, <<\"workers\">>, <<\"Bob\">>},",
"{<<\"mart\">>, <<\"example.org\">>, <<\"workers\">>, <<\"Mart\">>},",
"{<<\"Rich\">>, <<\"example.org\">>, <<\"bosses\">>, <<\"Rich\">>}]."]}]}.
"{<<\"Rich\">>, <<\"example.org\">>, <<\"bosses\">>, <<\"Rich\">>}]."]},
{?T("With this call, the sessions of the local account which JID is "
"boby@example.org will be kicked, and its password will be set "
"to something like "
"'BANNED_ACCOUNT--20080425T21:45:07--2176635--Spammed_rooms'"),
["ejabberdctl vhost example.org ban-account boby \"Spammed rooms\""]},
{?T("Call to srg-create using double-quotes and single-quotes:"),
["ejabberdctl srg-create g1 example.org \"\'Group number 1\'\" this_is_g1 g1"]}]}.
+9 -5
View File
@@ -925,31 +925,35 @@ mod_doc() ->
"Configured users can perform these actions with an XMPP "
"client either using Ad-hoc Commands or sending messages "
"to specific JIDs."), "",
?T("Note that this module can be resource intensive on large "
"deployments as it may broadcast a lot of messages. This module "
"should be disabled for instances of ejabberd with hundreds of "
"thousands users."), "",
?T("The Ad-hoc Commands are listed in the Server Discovery. "
"For this feature to work, 'mod_adhoc' must be enabled."), "",
?T("The specific JIDs where messages can be sent are listed below. "
"The first JID in each entry will apply only to the specified "
"virtual host example.org, while the JID between brackets "
"will apply to all virtual hosts in ejabberd:"), "",
"example.org/announce/all (example.org/announce/all-hosts/all)::",
"- example.org/announce/all (example.org/announce/all-hosts/all)::",
?T("The message is sent to all registered users. If the user is "
"online and connected to several resources, only the resource "
"with the highest priority will receive the message. "
"If the registered user is not connected, the message will be "
"stored offline in assumption that offline storage (see 'mod_offline') "
"is enabled."),
"example.org/announce/online (example.org/announce/all-hosts/online)::",
"- example.org/announce/online (example.org/announce/all-hosts/online)::",
?T("The message is sent to all connected users. If the user is "
"online and connected to several resources, all resources will "
"receive the message."),
"example.org/announce/motd (example.org/announce/all-hosts/motd)::",
"- example.org/announce/motd (example.org/announce/all-hosts/motd)::",
?T("The message is set as the message of the day (MOTD) and is sent "
"to users when they login. In addition the message is sent to all "
"connected users (similar to announce/online)."),
"example.org/announce/motd/update (example.org/announce/all-hosts/motd/update)::",
"- example.org/announce/motd/update (example.org/announce/all-hosts/motd/update)::",
?T("The message is set as message of the day (MOTD) and is sent to users "
"when they login. The message is not sent to any currently connected user."),
"example.org/announce/motd/delete (example.org/announce/all-hosts/motd/delete)::",
"- example.org/announce/motd/delete (example.org/announce/all-hosts/motd/delete)::",
?T("Any message sent to this JID removes the existing message of the day (MOTD).")],
opts =>
[{access,
+1 -4
View File
@@ -481,12 +481,9 @@ mod_doc() ->
"the list of supported formats is detected at compile time "
"depending on the image libraries installed in the system."),
example =>
[{?T("In this example avatars in WebP format are "
"converted to JPEG, all other formats are "
"converted to PNG:"),
["convert:",
" webp: jpg",
" default: png"]}]}},
" default: png"]}},
{rate_limit,
#{value => ?T("Number"),
desc =>
+19
View File
@@ -213,11 +213,30 @@ mod_doc() ->
[{json,
#{value => "true | false",
desc => ?T("This option has no effect.")}},
{max_concat,
#{value => "pos_integer() | infinity",
desc =>
?T("This option limits the number of stanzas that the server "
"will send in a single bosh request. "
"The default value is 'unlimited'.")}},
{max_inactivity,
#{value => "timeout()",
desc =>
?T("The option defines the maximum inactivity period. "
"The default value is '30' seconds.")}},
{max_pause,
#{value => "pos_integer()",
desc =>
?T("Indicate the maximum length of a temporary session pause "
"(in seconds) that a client can request. "
"The default value is '120'.")}},
{prebind,
#{value => "true | false",
desc =>
?T("If enabled, the client can create the session without "
"going through authentication. Basically, it creates a "
"new session with anonymous authentication. "
"The default value is 'false'.")}},
{queue_type,
#{value => "ram | file",
desc =>
+13 -1
View File
@@ -143,7 +143,19 @@ user_send_packet(Acc) ->
-spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
user_receive_packet({#presence{from = From, type = available} = Pkt,
#{lserver := LServer, jid := To} = State}) ->
IsRemote = not ejabberd_router:is_my_host(From#jid.lserver),
IsRemote = case From#jid.lresource of
% Don't store caps for presences sent by our muc rooms
<<>> ->
try ejabberd_router:host_of_route(From#jid.lserver) of
MaybeMuc ->
not lists:member(From#jid.lserver,
gen_mod:get_module_opt_hosts(MaybeMuc, mod_muc))
catch error:{unregistered_route, _} ->
true
end;
_ ->
not ejabberd_router:is_my_host(From#jid.lserver)
end,
if IsRemote ->
case read_caps(Pkt) of
nothing -> ok;
+2 -2
View File
@@ -27,7 +27,7 @@
-module (mod_carboncopy).
-author ('ecestari@process-one.net').
-protocol({xep, 280, '0.8'}).
-protocol({xep, 280, '0.13.2'}).
-behaviour(gen_mod).
@@ -268,7 +268,7 @@ is_received_muc_invite(Msg, received) ->
#muc_user{invites = [_|_]} ->
true;
_ ->
xmpp:has_subtag(Msg, #x_conference{})
xmpp:has_subtag(Msg, #x_conference{jid = jid:make(<<"">>)})
end.
-spec is_received_muc_pm(jid(), message(), direction()) -> boolean().
+12 -3
View File
@@ -85,13 +85,18 @@ mod_options(_Host) ->
mod_doc() ->
#{desc =>
?T("This module is an implementation of "
[?T("This module is an implementation of "
"https://xmpp.org/extensions/xep-0355.html"
"[XEP-0355: Namespace Delegation]. "
"Only admin mode has been implemented by now. "
"Namespace delegation allows external services to "
"handle IQ using specific namespace. This may be applied "
"for external PEP service."),
"for external PEP service."), "",
?T("WARNING: Security issue: Namespace delegation gives components "
"access to sensitive data, so permission should be granted "
"carefully, only if you trust the component."), "",
?T("NOTE: This module is complementary to 'mod_privilege' but can "
"also be used separately.")],
opts =>
[{namespaces,
#{value => "{Namespace: Options}",
@@ -109,6 +114,10 @@ mod_doc() ->
?T("The option defines which components are allowed "
"for namespace delegation. The default value is 'none'.")}}]}],
example =>
[{?T("Make sure you do not delegate the same namespace to several "
"services at the same time. As in the example provided later, "
"to have the 'sat-pubsub.example.org' component perform "
"correctly disable the 'mod_pubsub' module."),
["access_rules:",
" external_pubsub:",
" allow: external_component",
@@ -126,7 +135,7 @@ mod_doc() ->
" urn:xmpp:mam:1:",
" access: external_mam",
" http://jabber.org/protocol/pubsub:",
" access: external_pubsub"]}.
" access: external_pubsub"]}]}.
depends(_, _) ->
[].
+7 -1
View File
@@ -263,7 +263,13 @@ mod_doc() ->
"record of authentication failures after some time since the "
"first failure or on a successful authentication. "
"It also does not simply block network traffic, but "
"provides the client with a descriptive error message.")],
"provides the client with a descriptive error message."), "",
?T("WARNING: You should not use this module behind a proxy or load "
"balancer. ejabberd will see the failures as coming from the "
"load balancer and, when the threshold of auth failures is "
"reached, will reject all connections coming from the load "
"balancer. You can lock all your user base out of ejabberd "
"when using this module behind a proxy.")],
opts =>
[{access,
#{value => ?T("AccessName"),
+10 -2
View File
@@ -524,5 +524,13 @@ mod_options(_) ->
mod_doc() ->
#{desc =>
?T("This module provides a ReST API to call "
"ejabberd commands using JSON data.")}.
[?T("This module provides a ReST API to call ejabberd commands "
"using JSON data."), "",
?T("To use this module, in addition to adding it to the 'modules' "
"section, you must also add it to 'request_handlers' of some "
"listener."), "",
?T("To use a specific API version N, when defining the URL path "
"in the request_handlers, add a 'vN'. "
"For example: '/api/v2: mod_http_api'"), "",
?T("To run a command, send a POST request to the corresponding "
"URL: 'http://localhost:5280/api/<command_name>'")]}.
+3 -4
View File
@@ -522,12 +522,11 @@ mod_doc() ->
?T("Specify mappings of extension to content type. "
"There are several content types already defined. "
"With this option you can add new definitions "
"or modify existing ones."),
"or modify existing ones. The default values are:"),
example =>
[{?T("The default value is shown in the example below:"),
["content_types:"|
["content_types:"|
[" " ++ binary_to_list(E) ++ ": " ++ binary_to_list(T)
|| {E, T} <- ?DEFAULT_CONTENT_TYPES]]}]}},
|| {E, T} <- ?DEFAULT_CONTENT_TYPES]]}},
{default_content_type,
#{value => ?T("Type"),
desc =>
+5 -1
View File
@@ -130,6 +130,10 @@ mod_doc() ->
"the specified soft quota (see 'access_soft_quota'). "
"The default value is 'hard_upload_quota'.")}}],
example =>
[{?T("Please note that it's not necessary to specify the "
"'access_hard_quota' and 'access_soft_quota' options in order "
"to use the quota feature. You can stick to the default names "
"and just specify access rules such as those in this example:"),
["shaper_rules:",
" ...",
" soft_upload_quota:",
@@ -143,7 +147,7 @@ mod_doc() ->
" mod_http_upload: {}",
" mod_http_upload_quota:",
" max_days: 100",
" ..."]}.
" ..."]}]}.
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
+4 -2
View File
@@ -55,10 +55,11 @@ get_last(LUser, LServer) ->
end.
store_last_info(LUser, LServer, TimeStamp, Status) ->
TS = integer_to_binary(TimeStamp),
case ?SQL_UPSERT(LServer, "last",
["!username=%(LUser)s",
"!server_host=%(LServer)s",
"seconds=%(TimeStamp)d",
"seconds=%(TS)s",
"state=%(Status)s"]) of
ok ->
ok;
@@ -76,11 +77,12 @@ export(_Server) ->
fun(Host, #last_activity{us = {LUser, LServer},
timestamp = TimeStamp, status = Status})
when LServer == Host ->
TS = integer_to_binary(TimeStamp),
[?SQL("delete from last where username=%(LUser)s and %(LServer)H;"),
?SQL_INSERT("last",
["username=%(LUser)s",
"server_host=%(LServer)s",
"seconds=%(TimeStamp)d",
"seconds=%(TS)s",
"state=%(Status)s"])];
(_Host, _R) ->
[]
+15 -2
View File
@@ -1435,7 +1435,12 @@ mod_doc() ->
"Compatible XMPP clients can use it to store their "
"chat history on the server."),
opts =>
[{assume_mam_usage,
[{access_preferences,
#{value => ?T("AccessName"),
desc =>
?T("This access rule defines who is allowed to modify the "
"MAM preferences. The default value is 'all'.")}},
{assume_mam_usage,
#{value => "true | false",
desc =>
?T("This option determines how ejabberd's "
@@ -1499,4 +1504,12 @@ mod_doc() ->
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}},
{user_mucsub_from_muc_archive,
#{value => "true | false",
desc =>
?T("When this option is disabled, for each individual "
"subscriber a separa mucsub message is stored. With this "
"option enabled, when a user fetches archive virtual "
"mucsub, messages are generated from muc archives. "
"The default value is 'false'.")}}]}.
+1 -1
View File
@@ -105,7 +105,7 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, TS) ->
jid:tolower(Peer)),
Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
SType = misc:atom_to_binary(Type),
SqlType = ejabberd_option:sql_type(LHost),
SqlType = ejabberd_option:sql_type(LServer),
XML = case mod_mam_opt:compress_xml(LServer) of
true ->
J1 = case Type of
+4
View File
@@ -107,6 +107,10 @@ mod_doc() ->
"yet ready to use in production. It's asserted that "
"the MIX protocol is going to replace the MUC protocol "
"in the future (see 'mod_muc')."), "",
?T("To learn more about how to use that feature, you can refer to "
"our tutorial: https://docs.ejabberd.im/tutorials/mix-010/"
"[Getting started with XEP-0369: Mediated Information "
"eXchange (MIX) v0.1]."), "",
?T("The module depends on 'mod_mam'.")],
opts =>
[{access_create,
+2 -1
View File
@@ -267,7 +267,8 @@ listen_options() ->
mod_doc() ->
#{desc =>
?T("This module adds support for the MQTT protocol "
"version '3.1.1' and '5.0'."),
"version '3.1.1' and '5.0'. Remember to configure "
"'mod_mqtt' in 'modules' and 'listen' sections."),
opts =>
[{access_subscribe,
#{value => "{TopicFilter: AccessName}",
+13 -2
View File
@@ -1280,9 +1280,20 @@ mod_options(Host) ->
mod_doc() ->
#{desc =>
?T("This module provides support for https://xmpp.org/extensions/xep-0045.html"
[?T("This module provides support for https://xmpp.org/extensions/xep-0045.html"
"[XEP-0045: Multi-User Chat]. Users can discover existing rooms, "
"join or create them. Occupants of a room can chat in public or have private chats."),
"join or create them. Occupants of a room can chat in public or have private chats."), "",
?T("The MUC service allows any Jabber ID to register a nickname, so "
"nobody else can use that nickname in any room in the MUC "
"service. To register a nickname, open the Service Discovery in "
"your XMPP client and register in the MUC service."), "",
?T("This module supports clustering and load balancing. One module "
"can be started per cluster node. Rooms are distributed at "
"creation time on all available MUC module instances. The "
"multi-user chat module is clustered but the rooms themselves "
"are not clustered nor fault-tolerant: if the node managing a "
"set of rooms goes down, the rooms disappear and they will be "
"recreated on an available node on first connection attempt.")],
opts =>
[{access,
#{value => ?T("AccessName"),
+20 -11
View File
@@ -459,7 +459,7 @@ web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
Acc + mod_muc:count_online_rooms(Host)
end, 0, find_hosts(global)),
PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
Res = ?H1GL(PageTitle, <<"mod-muc">>, <<"mod_muc">>) ++
Res = ?H1GL(PageTitle, <<"modules/#mod-muc">>, <<"mod_muc">>) ++
[?XCT(<<"h3">>, ?T("Statistics")),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [?TDTD(?T("Total rooms"), OnlineRoomsNumber)
@@ -511,14 +511,14 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
fun(Room) ->
?XE(<<"tr">>, [?XC(<<"td">>, E) || E <- Room])
end, Rooms_prepared),
Titles = [<<"Jabber ID">>,
<<"# participants">>,
<<"Last message">>,
<<"Public">>,
<<"Persistent">>,
<<"Logging">>,
<<"Just created">>,
<<"Room title">>],
Titles = [?T("Jabber ID"),
?T("# participants"),
?T("Last message"),
?T("Public"),
?T("Persistent"),
?T("Logging"),
?T("Just created"),
?T("Room title")],
{Titles_TR, _} =
lists:mapfoldl(
fun(Title, Num_column) ->
@@ -533,7 +533,7 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
1,
Titles),
PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
?H1GL(PageTitle, <<"mod-muc">>, <<"mod_muc">>) ++
?H1GL(PageTitle, <<"modules/#mod-muc">>, <<"mod_muc">>) ++
[?XCT(<<"h2">>, ?T("Chatrooms")),
?XE(<<"table">>,
[?XE(<<"thead">>,
@@ -805,7 +805,7 @@ get_rooms(ServiceArg) ->
Hosts = find_services(ServiceArg),
lists:flatmap(
fun(Host) ->
[{RoomName, RoomHost, Host, Pid}
[{RoomName, RoomHost, ejabberd_router:host_of_route(Host), Pid}
|| {RoomName, RoomHost, Pid} <- mod_muc:get_online_rooms(Host)]
end, Hosts).
@@ -995,6 +995,15 @@ change_room_option(Name, Service, OptionString, ValueString) ->
room_not_found;
Pid ->
{Option, Value} = format_room_option(OptionString, ValueString),
change_room_option(Pid, Option, Value)
end.
change_room_option(Pid, Option, Value) ->
case {Option,
gen_mod:is_loaded((get_room_state(Pid))#state.server_host, mod_muc_log)} of
{logging, false} ->
mod_muc_log_not_enabled;
_ ->
Config = get_room_config(Pid),
Config2 = change_option(Option, Value, Config),
{ok, _} = mod_muc_room:set_config(Pid, Config2),
+7 -2
View File
@@ -217,6 +217,8 @@ unsubscribe(Pid, JID) ->
try p1_fsm:sync_send_all_state_event(Pid, {muc_unsubscribe, JID})
catch _:{timeout, {p1_fsm, _, _}} ->
{error, ?T("Request has timed out")};
exit:{normal, {p1_fsm, _, _}} ->
ok;
_:{_, {p1_fsm, _, _}} ->
{error, ?T("Conference room does not exist")}
end.
@@ -741,10 +743,13 @@ handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
{error, Err} ->
{reply, {error, get_error_text(Err)}, StateName, StateData}
end;
handle_sync_event({muc_unsubscribe, From}, _From, StateName, StateData) ->
handle_sync_event({muc_unsubscribe, From}, _From, StateName,
#state{config = Conf} = StateData) ->
IQ = #iq{type = set, id = p1_rand:get_string(),
from = From, sub_els = [#muc_unsubscribe{}]},
case process_iq_mucsub(From, IQ, StateData) of
{result, _, stop} ->
{stop, normal, StateData#state{config = Conf#config{persistent = false}}};
{result, _, NewState} ->
{reply, ok, StateName, NewState};
{ignore, NewState} ->
@@ -4198,7 +4203,7 @@ process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [Pkt]},
-spec process_iq_mucsub(jid(), iq(), state()) ->
{error, stanza_error()} |
{result, undefined | muc_subscribe() | muc_subscriptions(), state()} |
{result, undefined | muc_subscribe() | muc_subscriptions(), stop | state()} |
{ignore, state()}.
process_iq_mucsub(_From, #iq{type = set, lang = Lang,
sub_els = [#muc_subscribe{}]},
+3 -3
View File
@@ -989,7 +989,7 @@ user_queue(User, Server, Query, Lang) ->
Hdrs = get_messages_subset(User, Server, HdrsAll),
FMsgs = format_user_queue(Hdrs),
PageTitle = str:format(translate:translate(Lang, ?T("~ts's Offline Messages Queue")), [us_to_list(US)]),
(?H1GL(PageTitle, <<"mod-offline">>, <<"mod_offline">>))
(?H1GL(PageTitle, <<"modules/#mod-offline">>, <<"mod_offline">>))
++ [?XREST(?T("Submitted"))] ++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
@@ -1249,7 +1249,7 @@ mod_doc() ->
"is considered offline if no session presence priority > 0 "
"are currently open."), "",
?T("NOTE: 'ejabberdctl' has a command to "
"delete expired messages (see chapter"
"delete expired messages (see chapter "
"https://docs.ejabberd.im/admin/guide/managing"
"[Managing an ejabberd server] in online documentation.")],
opts =>
@@ -1281,7 +1281,7 @@ mod_doc() ->
{use_mam_for_storage,
#{value => "true | false",
desc =>
?T("This is an experimetal option. Enabling this option "
?T("This is an experimental option. Enabling this option "
"will make 'mod_offline' not use the former spool "
"table for storing MucSub offline messages, but will "
"use the archive table instead. This use of the archive "
+10 -4
View File
@@ -267,9 +267,15 @@ get_and_del_spool_msg_t(LServer, LUser) ->
ejabberd_sql:sql_query_t(
?SQL("select @(username)s, @(xml)s from spool where "
"username=%(LUser)s and %(LServer)H order by seq;")),
ejabberd_sql:sql_query_t(
?SQL("delete from spool where"
" username=%(LUser)s and %(LServer)H;")),
Result
DResult =
ejabberd_sql:sql_query_t(
?SQL("delete from spool where"
" username=%(LUser)s and %(LServer)H;")),
case {Result, DResult} of
{{selected, Rs}, {updated, DC}} when length(Rs) /= DC ->
ejabberd_sql:restart(concurent_insert);
_ ->
Result
end
end,
ejabberd_sql:sql_transaction(LServer, F).
+6
View File
@@ -100,6 +100,12 @@ mod_doc() ->
"It is worth noting that the permissions grant access to "
"the component to a specific data type for all users of "
"the virtual host on which 'mod_privilege' is loaded."), "",
?T("Make sure you have a listener configured to connect your "
"component. Check the section about listening ports for more "
"information."), "",
?T("WARNING: Security issue: Privileged access gives components "
"access to sensitive data, so permission should be granted "
"carefully, only if you trust a component."), "",
?T("NOTE: This module is complementary to 'mod_delegation', "
"but can also be used separately.")],
opts =>
+1 -9
View File
@@ -266,19 +266,11 @@ get_streamhost(Host, ServerHost) ->
get_endpoint(Host) ->
Port = mod_proxy65_opt:port(Host),
IP = case mod_proxy65_opt:ip(Host) of
undefined -> get_my_ip();
undefined -> misc:get_my_ip();
Addr -> Addr
end,
{Port, IP, tcp}.
-spec get_my_ip() -> inet:ip_address().
get_my_ip() ->
{ok, MyHostName} = inet:gethostname(),
case inet:getaddr(MyHostName, inet) of
{ok, Addr} -> Addr;
{error, _} -> {127, 0, 0, 1}
end.
max_connections(ServerHost) ->
mod_proxy65_opt:max_connections(ServerHost).
+206 -1
View File
@@ -90,7 +90,7 @@
%% API and gen_server callbacks
-export([start/2, stop/1, init/1,
handle_call/3, handle_cast/2, handle_info/2,
handle_call/3, handle_cast/2, handle_info/2, mod_doc/0,
terminate/2, code_change/3, depends/2, mod_opt_type/1, mod_options/1]).
-export([route/1]).
@@ -4180,3 +4180,208 @@ mod_options(Host) ->
{max_subscriptions_node, undefined},
{default_node_config, []},
{force_node_config, []}].
mod_doc() ->
#{desc =>
[?T("This module offers a service for "
"https://xmpp.org/extensions/xep-0060.html"
"[XEP-0060: Publish-Subscribe]. The functionality in "
"'mod_pubsub' can be extended using plugins. "
"The plugin that implements PEP "
"(https://xmpp.org/extensions/xep-0163.html"
"[XEP-0163: Personal Eventing via Pubsub]) "
"is enabled in the default ejabberd configuration file, "
"and it requires 'mod_caps'.")],
opts =>
[{access_createnode,
#{value => "AccessName",
desc =>
?T("This option restricts which users are allowed to "
"create pubsub nodes using 'acl' and 'access'. "
"By default any account in the local ejabberd server "
"is allowed to create pubsub nodes. "
"The default value is: 'all'.")}},
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to "
"this module only.")}},
{default_node_config,
#{value => "List of Key:Value",
desc =>
?T("To override default node configuration, regardless "
"of node plugin. Value is a list of key-value "
"definition. Node configuration still uses default "
"configuration defined by node plugin, and overrides "
"any items by value defined in this configurable list.")}},
{force_node_config,
#{value => "List of Node and the list of its Key:Value",
desc =>
?T("Define the configuration for given nodes. "
"The default value is: '[]'."),
example =>
["force_node_config:",
" ## Avoid buggy clients to make their bookmarks public",
" storage:bookmarks:",
" access_model: whitelist"]}},
{host,
#{desc => ?T("Deprecated. Use 'hosts' instead.")}},
{hosts,
#{value => ?T("[Host, ...]"),
desc =>
?T("This option defines the Jabber IDs of the service. "
"If the 'hosts' option is not specified, the only Jabber "
"ID will be the hostname of the virtual host with the "
"prefix \"vjud.\". The keyword '@HOST@' is replaced with "
"the real virtual host name.")}},
{ignore_pep_from_offline,
#{value => "false | true",
desc =>
?T("To specify whether or not we should get last "
"published PEP items from users in our roster which "
"are offline when we connect. Value is 'true' or "
"'false'. If not defined, pubsub assumes true so we "
"only get last items of online contacts.")}},
{last_item_cache,
#{value => "false | true",
desc =>
?T("To specify whether or not pubsub should cache last "
"items. Value is 'true' or 'false'. If not defined, "
"pubsub does not cache last items. On systems with not"
" so many nodes, caching last items speeds up pubsub "
"and allows to raise user connection rate. The cost "
"is memory usage, as every item is stored in memory.")}},
{max_items_node,
#{value => "MaxItems",
desc =>
?T("Define the maximum number of items that can be "
"stored in a node. Default value is: '10'.")}},
{max_nodes_discoitems,
#{value => "pos_integer() | infinity",
desc =>
?T("The maximum number of nodes to return in a "
"discoitem response. The default value is: '100'.")}},
{max_subscriptions_node,
#{value => "MaxSubs",
desc =>
?T("Define the maximum number of subscriptions managed "
"by a node. "
"Default value is no limitation: 'undefined'.")}},
{name,
#{value => ?T("Name"),
desc =>
?T("The value of the service name. This name is only visible "
"in some clients that support "
"https://xmpp.org/extensions/xep-0030.html"
"[XEP-0030: Service Discovery]. "
"The default is 'vCard User Search'.")}},
{nodetree,
#{value => "Nodetree",
desc =>
[?T("To specify which nodetree to use. If not defined, the "
"default pubsub nodetree is used: 'tree'. Only one "
"nodetree can be used per host, and is shared by all "
"node plugins."),
?T("- 'tree' nodetree store node configuration and "
"relations on the database. 'flat' nodes are stored "
"without any relationship, and 'hometree' nodes can "
"have child nodes."),
?T("- 'virtual' nodetree does not store nodes on database. "
"This saves resources on systems with tons of nodes. "
"If using the 'virtual' nodetree, you can only enable "
"those node plugins: '[flat, pep]' or '[flat]'; any "
"other plugins configuration will not work. Also, all "
"nodes will have the default configuration, and this "
"can not be changed. Using 'virtual' nodetree requires "
"to start from a clean database, it will not work if "
"you used the default 'tree' nodetree before."),
?T("- 'dag' nodetree provides experimental support for "
"PubSub Collection Nodes (XEP-0248). In that case you "
"should also add 'dag' node plugin as default, for "
"example: 'plugins: [flat,pep]'")]}},
{pep_mapping,
#{value => "List of Key:Value",
desc =>
?T("This allows to define a list of key-value to choose "
"defined node plugins on given PEP namespace."),
example =>
[{?T("The following example will use 'node_tune' instead of "
"'node_pep' for every PEP node with the tune namespace:"),
["modules:",
" ...",
" mod_pubsub:",
" pep_mapping:",
" http://jabber.org/protocol/tune: tune",
" ..."]
}]}},
{plugins,
#{value => "[Plugin, ...]",
desc => [?T("To specify which pubsub node plugins to use. "
"The first one in the list is used by default. "
"If this option is not defined, the default plugins "
"list is: '[flat]'. PubSub clients can define which "
"plugin to use when creating a node: "
"add 'type=\'plugin-name\'' attribute "
"to the 'create' stanza element."),
?T("- 'flat' plugin handles the default behaviour and "
"follows standard XEP-0060 implementation."),
?T("- 'pep' plugin adds extention to handle Personal "
"Eventing Protocol (XEP-0163) to the PubSub engine. "
"Adding pep allows to handle PEP automatically.")]}},
{vcard,
#{value => ?T("vCard"),
desc =>
?T("A custom vCard of the server that will be displayed by "
"some XMPP clients in Service Discovery. The value of "
"'vCard' is a YAML map constructed from an XML "
"representation of vCard. Since the representation has "
"no attributes, the mapping is straightforward."),
example =>
[{?T("The following XML representation of vCard:"),
["<vCard xmlns='vcard-temp'>",
" <FN>PubSub Service</FN>",
" <ADR>",
" <WORK/>",
" <STREET>Elm Street</STREET>",
" </ADR>",
"</vCard>"]},
{?T("will be translated to:"),
["vcard:",
" fn: PubSub Service",
" adr:",
" -",
" work: true",
" street: Elm Street"]}]}}
],
example =>
[{?T("Example of configuration that uses flat nodes as default, "
"and allows use of flat, hometree and pep nodes:"),
["modules:",
" ...",
" mod_pubsub:",
" access_createnode: pubsub_createnode",
" max_subscriptions_node: 100",
" default_node_config:",
" notification_type: normal",
" notify_retract: false",
" max_items: 4",
" plugins:",
" - flat",
" - pep",
" ..."]},
{?T("Using relational database requires using mod_pubsub with "
"db_type 'sql'. Only flat, hometree and pep plugins supports "
"SQL. The following example shows previous configuration "
"with SQL usage:"),
["modules:",
" ...",
" mod_pubsub:",
" db_type: sql",
" access_createnode: pubsub_createnode",
" ignore_pep_from_offline: true",
" last_item_cache: false",
" plugins:",
" - flat",
" - pep",
" ..."]}
]}.
+4 -1
View File
@@ -628,7 +628,10 @@ mod_doc() ->
"This protocol enables end users to use a XMPP client to:"), "",
?T("* Register a new account on the server."), "",
?T("* Change the password from an existing account on the server."), "",
?T("* Delete an existing account on the server.")],
?T("* Delete an existing account on the server."), "",
?T("This module reads also another option defined globally for the "
"server: 'registration_timeout'. Please check that option "
"documentation in the section with top-level options.")],
opts =>
[{access,
#{value => ?T("AccessName"),
+19 -22
View File
@@ -93,31 +93,20 @@ process([], #request{method = 'GET', lang = Lang}) ->
process([<<"register.css">>],
#request{method = 'GET'}) ->
serve_css();
process([<<"new">>],
process([Section],
#request{method = 'GET', lang = Lang, host = Host,
ip = IP}) ->
case ejabberd_router:is_my_host(Host) of
ip = {Addr, _Port}}) ->
Host2 = case ejabberd_router:is_my_host(Host) of
true ->
{Addr, _Port} = IP,
form_new_get(Host, Lang, Addr);
Host;
false ->
{400, [], <<"Host not served">>}
end;
process([<<"delete">>],
#request{method = 'GET', lang = Lang, host = Host}) ->
case ejabberd_router:is_my_host(Host) of
true ->
form_del_get(Host, Lang);
false ->
{400, [], <<"Host not served">>}
end;
process([<<"change_password">>],
#request{method = 'GET', lang = Lang, host = Host}) ->
case ejabberd_router:is_my_host(Host) of
true ->
form_changepass_get(Host, Lang);
false ->
{400, [], <<"Host not served">>}
<<"">>
end,
case Section of
<<"new">> -> form_new_get(Host2, Lang, Addr);
<<"delete">> -> form_del_get(Host2, Lang);
<<"change_password">> -> form_changepass_get(Host2, Lang);
_ -> {404, [], "Not Found"}
end;
process([<<"new">>],
#request{method = 'POST', q = Q, ip = {Ip, _Port},
@@ -636,5 +625,13 @@ mod_doc() ->
?T("- Register a new account on the server."), "",
?T("- Change the password from an existing account on the server."), "",
?T("- Delete an existing account on the server."), "",
?T("This module supports CAPTCHA image to register a new account. "
"To enable this feature, configure the options 'captcha\_cmd' "
"and 'captcha\_url', which are documented in the section with "
"top-level options."), "",
?T("As an example usage, the users of the host 'example.org' can "
"visit the page: 'https://example.org:5281/register/' It is "
"important to include the last / character in the URL, "
"otherwise the subpages URL will be incorrect."), "",
?T("The module depends on 'mod_register' where all the configuration "
"is performed.")]}.
+1 -1
View File
@@ -1005,7 +1005,7 @@ user_roster(User, Server, Query, Lang) ->
SItems)))])]
end,
PageTitle = str:format(translate:translate(Lang, ?T("Roster of ~ts")), [us_to_list(US)]),
(?H1GL(PageTitle, <<"mod-roster">>, <<"mod_roster">>))
(?H1GL(PageTitle, <<"modules/#mod-roster">>, <<"mod_roster">>))
++
case Res of
ok -> [?XREST(?T("Submitted"))];
+2 -1
View File
@@ -265,7 +265,8 @@ s2s_out_packet(#{server := LServer,
ejabberd_s2s_in:update_state(
Pid, fun(S) -> send_db_result(S, Response) end),
%% At this point the connection is no longer needed and we can terminate it
ejabberd_s2s_out:stop(State);
ejabberd_s2s_out:stop_async(self()),
State;
s2s_out_packet(#{server := LServer, remote_server := RServer} = State,
#db_result{to = LServer, from = RServer,
type = Type} = Result) when Type /= undefined ->
+342 -144
View File
@@ -71,10 +71,20 @@
-callback is_user_in_group({binary(), binary()}, binary(), binary()) -> boolean().
-callback add_user_to_group(binary(), {binary(), binary()}, binary()) -> any().
-callback remove_user_from_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}.
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-optional_callbacks([use_cache/1, cache_nodes/1]).
-define(GROUP_OPTS_CACHE, shared_roster_group_opts_cache).
-define(USER_GROUPS_CACHE, shared_roster_user_groups_cache).
-define(GROUP_EXPLICIT_USERS_CACHE, shared_roster_group_explicit_cache).
-define(SPECIAL_GROUPS_CACHE, shared_roster_special_groups_cache).
start(Host, Opts) ->
Mod = gen_mod:db_mod(Opts, ?MODULE),
Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE,
webadmin_menu, 70),
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE,
@@ -136,17 +146,54 @@ reload(Host, NewOpts, OldOpts) ->
depends(_Host, _Opts) ->
[].
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
init_cache(Mod, Host, Opts) ->
case use_cache(Mod, Host) of
true ->
CacheOpts = cache_opts(Opts),
ets_cache:new(?GROUP_OPTS_CACHE, CacheOpts),
ets_cache:new(?USER_GROUPS_CACHE, CacheOpts),
ets_cache:new(?GROUP_EXPLICIT_USERS_CACHE, CacheOpts),
ets_cache:new(?SPECIAL_GROUPS_CACHE, CacheOpts);
false ->
ets_cache:delete(?GROUP_OPTS_CACHE),
ets_cache:delete(?USER_GROUPS_CACHE),
ets_cache:delete(?GROUP_EXPLICIT_USERS_CACHE),
ets_cache:delete(?SPECIAL_GROUPS_CACHE)
end.
-spec cache_opts(gen_mod:opts()) -> [proplists:property()].
cache_opts(Opts) ->
MaxSize = mod_private_opt:cache_size(Opts),
CacheMissed = mod_private_opt:cache_missed(Opts),
LifeTime = mod_private_opt:cache_life_time(Opts),
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec use_cache(module(), binary()) -> boolean().
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(Host);
false -> mod_shared_roster_opt:use_cache(Host)
end.
-spec cache_nodes(module(), binary()) -> [node()].
cache_nodes(Mod, Host) ->
case erlang:function_exported(Mod, cache_nodes, 1) of
true -> Mod:cache_nodes(Host);
false -> ejabberd_cluster:get_nodes()
end.
-spec get_user_roster([#roster{}], {binary(), binary()}) -> [#roster{}].
get_user_roster(Items, US) ->
{U, S} = US,
DisplayedGroups = get_user_displayed_groups(US),
SRUsers = lists:foldl(fun (Group, Acc1) ->
GroupName = get_group_name(S, Group),
GroupLabel = get_group_label(S, Group), %++
lists:foldl(fun (User, Acc2) ->
if User == US -> Acc2;
true ->
dict:append(User,
GroupName,
GroupLabel,
Acc2)
end
end,
@@ -161,12 +208,12 @@ get_user_roster(Items, US) ->
case dict:find(US1,
SRUsers1)
of
{ok, GroupNames} ->
{ok, GroupLabels} ->
{Item#roster{subscription
=
both,
groups =
Item#roster.groups ++ GroupNames,
Item#roster.groups ++ GroupLabels,
ask =
none},
dict:erase(US1,
@@ -179,8 +226,8 @@ get_user_roster(Items, US) ->
SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}},
us = US, jid = {U1, S1, <<"">>},
name = get_rosteritem_name(U1, S1),
subscription = both, ask = none, groups = GroupNames}
|| {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
subscription = both, ask = none, groups = GroupLabels}
|| {{U1, S1}, GroupLabels} <- dict:to_list(SRUsersRest)],
SRItems ++ NewItems1.
get_rosteritem_name(U, S) ->
@@ -221,12 +268,12 @@ process_item(RosterItem, Host) ->
[] -> RosterItem;
%% Roster item cannot be removed: We simply reset the original groups:
_ when RosterItem#roster.subscription == remove ->
GroupNames = lists:map(fun (Group) ->
get_group_name(Host, Group)
GroupLabels = lists:map(fun (Group) ->
get_group_label(Host, Group)
end,
CommonGroups),
RosterItem#roster{subscription = both, ask = none,
groups = GroupNames};
groups = GroupLabels};
%% Both users have at least a common shared group,
%% So each user can see the other
_ ->
@@ -305,10 +352,10 @@ get_jid_info({Subscription, Ask, Groups}, User, Server,
US1 = {U1, S1},
DisplayedGroups = get_user_displayed_groups(US),
SRUsers = lists:foldl(fun (Group, Acc1) ->
GroupName = get_group_name(LServer, Group),
GroupLabel = get_group_label(LServer, Group), %++
lists:foldl(fun (User1, Acc2) ->
dict:append(User1,
GroupName,
GroupLabel,
Acc2)
end,
Acc1,
@@ -316,8 +363,8 @@ get_jid_info({Subscription, Ask, Groups}, User, Server,
end,
dict:new(), DisplayedGroups),
case dict:find(US1, SRUsers) of
{ok, GroupNames} ->
NewGroups = if Groups == [] -> GroupNames;
{ok, GroupLabels} ->
NewGroups = if Groups == [] -> GroupLabels;
true -> Groups
end,
{both, none, NewGroups};
@@ -376,24 +423,70 @@ create_group(Host, Group) ->
create_group(Host, Group, Opts) ->
Mod = gen_mod:db_mod(Host, ?MODULE),
case use_cache(Mod, Host) of
true ->
ets_cache:insert(?GROUP_OPTS_CACHE, {Host, Group}, Opts, cache_nodes(Mod, Host)),
ets_cache:clear(?SPECIAL_GROUPS_CACHE, cache_nodes(Mod, Host));
_ ->
ok
end,
Mod:create_group(Host, Group, Opts).
delete_group(Host, Group) ->
Mod = gen_mod:db_mod(Host, ?MODULE),
case use_cache(Mod, Host) of
true ->
ets_cache:delete(?GROUP_OPTS_CACHE, {Host, Group}, cache_nodes(Mod, Host)),
ets_cache:clear(?USER_GROUPS_CACHE, cache_nodes(Mod, Host)),
ets_cache:clear(?GROUP_EXPLICIT_USERS_CACHE, cache_nodes(Mod, Host)),
ets_cache:clear(?SPECIAL_GROUPS_CACHE, cache_nodes(Mod, Host));
_ ->
ok
end,
Mod:delete_group(Host, Group).
get_group_opts(Host, Group) ->
get_group_opts(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:get_group_opts(Host, Group).
case use_cache(Mod, Host) of
true ->
ets_cache:lookup(
?GROUP_OPTS_CACHE, {Host, Group},
fun() ->
case Mod:get_group_opts(Host, Group) of
error -> error;
V -> {cache, V}
end
end);
false ->
Mod:get_group_opts(Host, Group)
end.
set_group_opts(Host, Group, Opts) ->
Mod = gen_mod:db_mod(Host, ?MODULE),
case use_cache(Mod, Host) of
true ->
ets_cache:insert(?GROUP_OPTS_CACHE, {Host, Group}, Opts, cache_nodes(Mod, Host)),
ets_cache:clear(?SPECIAL_GROUPS_CACHE, cache_nodes(Mod, Host));
_ ->
ok
end,
Mod:set_group_opts(Host, Group, Opts).
get_user_groups(US) ->
Host = element(2, US),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:get_user_groups(US, Host) ++ get_special_users_groups(Host).
UG = case use_cache(Mod, Host) of
true ->
ets_cache:lookup(
?USER_GROUPS_CACHE, {Host, US},
fun() ->
{cache, Mod:get_user_groups(US, Host)}
end);
false ->
Mod:get_user_groups(US, Host)
end,
UG ++ get_special_users_groups(Host).
is_group_enabled(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
@@ -419,16 +512,7 @@ get_online_users(Host) ->
get_group_users(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
case get_group_opt(Host, Group, all_users, false) of
true -> ejabberd_auth:get_users(Host);
false -> []
end
++
case get_group_opt(Host, Group, online_users, false) of
true -> get_online_users(Host);
false -> []
end
++ get_group_explicit_users(Host, Group).
get_group_users(Host, Group, get_group_opts(Host, Group)).
get_group_users(Host, Group, GroupOpts) ->
case proplists:get_value(all_users, GroupOpts, false) of
@@ -445,32 +529,72 @@ get_group_users(Host, Group, GroupOpts) ->
get_group_explicit_users(Host, Group) ->
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:get_group_explicit_users(Host, Group).
case use_cache(Mod, Host) of
true ->
ets_cache:lookup(
?GROUP_EXPLICIT_USERS_CACHE, {Host, Group},
fun() ->
{cache, Mod:get_group_explicit_users(Host, Group)}
end);
false ->
Mod:get_group_explicit_users(Host, Group)
end.
get_group_name(Host1, Group1) ->
get_group_label(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
get_group_opt(Host, Group, name, Group).
get_group_opt(Host, Group, label, Group).
%% Get list of names of groups that have @all@/@online@/etc in the memberlist
get_special_users_groups(Host) ->
lists:filtermap(fun ({Group, Opts}) ->
case proplists:get_value(all_users, Opts, false) orelse
proplists:get_value(online_users, Opts, false) of
true -> {true, Group};
false -> false
end
end,
groups_with_opts(Host)).
Extract =
fun() ->
lists:filtermap(
fun({Group, Opts}) ->
case proplists:get_value(all_users, Opts, false) orelse
proplists:get_value(online_users, Opts, false) of
true -> {true, Group};
false -> false
end
end,
groups_with_opts(Host))
end,
Mod = gen_mod:db_mod(Host, ?MODULE),
case use_cache(Mod, Host) of
true ->
ets_cache:lookup(
?SPECIAL_GROUPS_CACHE, {Host, false},
fun() ->
{cache, Extract()}
end);
false ->
Extract()
end.
%% Get list of names of groups that have @online@ in the memberlist
get_special_users_groups_online(Host) ->
lists:filtermap(fun ({Group, Opts}) ->
case proplists:get_value(online_users, Opts, false) of
true -> {true, Group};
false -> false
end
end,
groups_with_opts(Host)).
Extract =
fun() ->
lists:filtermap(
fun({Group, Opts}) ->
case proplists:get_value(online_users, Opts, false) of
true -> {true, Group};
false -> false
end
end,
groups_with_opts(Host))
end,
Mod = gen_mod:db_mod(Host, ?MODULE),
case use_cache(Mod, Host) of
true ->
ets_cache:lookup(
?SPECIAL_GROUPS_CACHE, {Host, true},
fun() ->
{cache, Extract()}
end);
false ->
Extract()
end.
%% Given two lists of groupnames and their options,
%% return the list of displayed groups to the second list
@@ -555,27 +679,32 @@ add_user_to_group2(Host, US, Group) ->
{LUser, LServer} = US,
case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of
match ->
GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
GroupOpts = get_group_opts(Host, Group),
MoreGroupOpts = case LUser of
<<"@all@">> -> [{all_users, true}];
<<"@online@">> -> [{online_users, true}];
_ -> []
end,
mod_shared_roster:set_group_opts(Host, Group,
GroupOpts ++ MoreGroupOpts);
set_group_opts(Host, Group,
GroupOpts ++ MoreGroupOpts);
nomatch ->
DisplayedToGroups = displayed_to_groups(Group, Host),
DisplayedGroups = get_displayed_groups(Group, LServer),
push_user_to_displayed(LUser, LServer, Group, Host, both, DisplayedToGroups),
push_displayed_to_user(LUser, LServer, Host, both, DisplayedGroups),
Mod = gen_mod:db_mod(Host, ?MODULE),
case use_cache(Mod, Host) of
true ->
ets_cache:delete(?USER_GROUPS_CACHE, {Host, US}, cache_nodes(Mod, Host)),
ets_cache:delete(?GROUP_EXPLICIT_USERS_CACHE, {Host, Group}, cache_nodes(Mod, Host));
false ->
ok
end,
Mod:add_user_to_group(Host, US, Group)
end.
get_displayed_groups(Group, LServer) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
proplists:get_value(displayed_groups, GroupOpts, []).
get_group_opt(LServer, Group, displayed_groups, []).
push_displayed_to_user(LUser, LServer, Host, Subscription, DisplayedGroups) ->
[push_members_to_user(LUser, LServer, DGroup, Host,
@@ -586,7 +715,7 @@ remove_user_from_group(Host, US, Group) ->
{LUser, LServer} = US,
case ejabberd_regexp:run(LUser, <<"^@.+@\$">>) of
match ->
GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
GroupOpts = get_group_opts(Host, Group),
NewGroupOpts = case LUser of
<<"@all@">> ->
lists:filter(fun (X) -> X /= {all_users, true}
@@ -597,9 +726,16 @@ remove_user_from_group(Host, US, Group) ->
end,
GroupOpts)
end,
mod_shared_roster:set_group_opts(Host, Group, NewGroupOpts);
set_group_opts(Host, Group, NewGroupOpts);
nomatch ->
Mod = gen_mod:db_mod(Host, ?MODULE),
case use_cache(Mod, Host) of
true ->
ets_cache:delete(?USER_GROUPS_CACHE, {Host, US}, cache_nodes(Mod, Host)),
ets_cache:delete(?GROUP_EXPLICIT_USERS_CACHE, {Host, Group}, cache_nodes(Mod, Host));
false ->
ok
end,
Result = Mod:remove_user_from_group(Host, US, Group),
DisplayedToGroups = displayed_to_groups(Group, Host),
DisplayedGroups = get_displayed_groups(Group, LServer),
@@ -610,12 +746,11 @@ remove_user_from_group(Host, US, Group) ->
push_members_to_user(LUser, LServer, Group, Host,
Subscription) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
GroupName = proplists:get_value(name, GroupOpts, Group),
GroupOpts = get_group_opts(LServer, Group),
GroupLabel = proplists:get_value(label, GroupOpts, Group), %++
Members = get_group_users(Host, Group),
lists:foreach(fun ({U, S}) ->
push_roster_item(LUser, LServer, U, S, GroupName,
push_roster_item(LUser, LServer, U, S, GroupLabel,
Subscription)
end,
Members).
@@ -645,12 +780,12 @@ push_user_to_members(User, Server, Subscription) ->
Group),
GroupOpts = proplists:get_value(Group, GroupsOpts,
[]),
GroupName = proplists:get_value(name, GroupOpts,
GroupLabel = proplists:get_value(label, GroupOpts,
Group),
lists:foreach(fun ({U, S}) ->
push_roster_item(U, S, LUser,
LServer,
GroupName,
GroupLabel,
Subscription)
end,
get_group_users(LServer, Group,
@@ -659,22 +794,20 @@ push_user_to_members(User, Server, Subscription) ->
lists:usort(SpecialGroups ++ UserGroups)).
push_user_to_displayed(LUser, LServer, Group, Host, Subscription, DisplayedToGroupsOpts) ->
GroupsOpts = groups_with_opts(Host),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
GroupName = proplists:get_value(name, GroupOpts, Group),
GroupLabel = get_group_opt(Host, Group, label, Group), %++
[push_user_to_group(LUser, LServer, GroupD, Host,
GroupName, Subscription)
GroupLabel, Subscription)
|| GroupD <- DisplayedToGroupsOpts].
push_user_to_group(LUser, LServer, Group, Host,
GroupName, Subscription) ->
GroupLabel, Subscription) ->
lists:foreach(fun ({U, S})
when (U == LUser) and (S == LServer) ->
ok;
({U, S}) ->
case lists:member(S, ejabberd_option:hosts()) of
true ->
push_roster_item(U, S, LUser, LServer, GroupName,
push_roster_item(U, S, LUser, LServer, GroupLabel,
Subscription);
_ ->
ok
@@ -699,12 +832,12 @@ push_item(User, Server, Item) ->
Item).
push_roster_item(User, Server, ContactU, ContactS,
GroupName, Subscription) ->
GroupLabel, Subscription) ->
Item = #roster{usj =
{User, Server, {ContactU, ContactS, <<"">>}},
us = {User, Server}, jid = {ContactU, ContactS, <<"">>},
name = <<"">>, subscription = Subscription, ask = none,
groups = [GroupName]},
groups = [GroupLabel]},
push_item(User, Server, Item).
-spec c2s_self_presence({presence(), ejabberd_c2s:state()})
@@ -722,13 +855,15 @@ unset_presence(LUser, LServer, Resource, Status) ->
case length(Resources) of
0 ->
OnlineGroups = get_special_users_groups_online(LServer),
lists:foreach(fun (OG) ->
push_user_to_displayed(LUser, LServer, OG,
LServer, remove, displayed_to_groups(OG, LServer)),
push_displayed_to_user(LUser, LServer,
LServer, remove, displayed_to_groups(OG, LServer))
end,
OnlineGroups);
lists:foreach(
fun(OG) ->
DisplayedToGroups = displayed_to_groups(OG, LServer),
push_user_to_displayed(LUser, LServer, OG,
LServer, remove, DisplayedToGroups),
push_displayed_to_user(LUser, LServer,
LServer, remove, DisplayedToGroups)
end,
OnlineGroups);
_ -> ok
end.
@@ -756,9 +891,13 @@ webadmin_page(Acc, _, _) -> Acc.
list_shared_roster_groups(Host, Query, Lang) ->
Res = list_sr_groups_parse_query(Host, Query),
SRGroups = mod_shared_roster:list_groups(Host),
SRGroups = list_groups(Host),
FGroups = (?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
[?X(<<"td">>),
?XE(<<"td">>, [?CT(?T("Name:"))])
])]++
(lists:map(fun (Group) ->
?XE(<<"tr">>,
[?XE(<<"td">>,
@@ -775,12 +914,12 @@ list_shared_roster_groups(Host, Query, Lang) ->
[?X(<<"td">>),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"namenew">>,
<<"">>)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>, <<"addnew">>,
<<"">>),
?C(<<" ">>),
?INPUTT(<<"submit">>, <<"addnew">>,
?T("Add New"))])])]))])),
(?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))),
<<"mod-shared-roster">>, <<"mod_shared_roster">>))
<<"modules/#mod-shared-roster">>, <<"mod_shared_roster">>))
++
case Res of
ok -> [?XREST(?T("Submitted"))];
@@ -807,15 +946,17 @@ list_sr_groups_parse_query(Host, Query) ->
list_sr_groups_parse_addnew(Host, Query) ->
case lists:keysearch(<<"namenew">>, 1, Query) of
{value, {_, Group}} when Group /= <<"">> ->
mod_shared_roster:create_group(Host, Group), ok;
_ -> error
create_group(Host, Group),
ok;
_ ->
error
end.
list_sr_groups_parse_delete(Host, Query) ->
SRGroups = mod_shared_roster:list_groups(Host),
SRGroups = list_groups(Host),
lists:foreach(fun (Group) ->
case lists:member({<<"selected">>, Group}, Query) of
true -> mod_shared_roster:delete_group(Host, Group);
true -> delete_group(Host, Group);
_ -> ok
end
end,
@@ -825,15 +966,15 @@ list_sr_groups_parse_delete(Host, Query) ->
shared_roster_group(Host, Group, Query, Lang) ->
Res = shared_roster_group_parse_query(Host, Group,
Query),
GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
Name = get_opt(GroupOpts, name, <<"">>),
GroupOpts = get_group_opts(Host, Group),
Label = get_opt(GroupOpts, label, <<"">>), %%++
Description = get_opt(GroupOpts, description, <<"">>),
AllUsers = get_opt(GroupOpts, all_users, false),
OnlineUsers = get_opt(GroupOpts, online_users, false),
DisplayedGroups = get_opt(GroupOpts, displayed_groups,
[]),
Members = mod_shared_roster:get_group_explicit_users(Host,
Group),
Members = get_group_explicit_users(Host,
Group),
FMembers = iolist_to_binary(
[if AllUsers -> <<"@all@\n">>;
true -> <<"">>
@@ -850,41 +991,51 @@ shared_roster_group(Host, Group, Query, Lang) ->
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Name:")),
?XE(<<"td">>, [?C(Group)]),
?XE(<<"td">>, [?C(<<"">>)])]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Label:")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"name">>, Name)])]),
[?INPUT(<<"text">>, <<"label">>, Label)]),
?XE(<<"td">>, [?CT(?T("Name in the rosters where this group will be displayed"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Description:")),
?XE(<<"td">>,
[?TEXTAREA(<<"description">>,
integer_to_binary(lists:max([3,
DescNL])),
<<"20">>, Description)])]),
<<"20">>, Description)]),
?XE(<<"td">>, [?CT(?T("Only admins can see this"))])
]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Members:")),
?XE(<<"td">>,
[?TEXTAREA(<<"members">>,
integer_to_binary(lists:max([3,
length(Members)+3])),
<<"20">>, FMembers)])]),
<<"20">>, FMembers)]),
?XE(<<"td">>, [?C(<<"JIDs, @all@, @online@">>)])
]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Displayed Groups:")),
[?XCT(<<"td">>, ?T("Displayed:")),
?XE(<<"td">>,
[?TEXTAREA(<<"dispgroups">>,
integer_to_binary(lists:max([3, length(FDisplayedGroups)])),
<<"20">>,
list_to_binary(FDisplayedGroups))])])])])),
list_to_binary(FDisplayedGroups))]),
?XE(<<"td">>, [?CT(?T("Groups that will be displayed to the members"))])
])])])),
(?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))),
<<"mod-shared-roster">>, <<"mod_shared_roster">>))
<<"modules/#mod-shared-roster">>, <<"mod_shared_roster">>))
++
[?XC(<<"h2">>, <<(translate:translate(Lang, ?T("Group ")))/binary, Group/binary>>)] ++
[?XC(<<"h2">>, translate:translate(Lang, ?T("Group")))] ++
case Res of
ok -> [?XREST(?T("Submitted"))];
{error_jids, NonAddedList1} ->
NonAddedList2 = [jid:encode({U,S,<<>>}) || {U,S} <- NonAddedList1],
NonAddedList3 = str:join(NonAddedList2, <<", ">>),
NonAddedText1 = translate:translate(Lang, ?T("Members not added (inexistent vhost): ")),
NonAddedText2 = str:concat(NonAddedText1, NonAddedList3),
[?XRES(NonAddedText2)];
{error_elements, NonAddedList1, NG1} ->
make_error_el(Lang,
?T("Members not added (inexistent vhost!): "),
[jid:encode({U,S,<<>>}) || {U,S} <- NonAddedList1])
++ make_error_el(Lang, ?T("'Displayed groups' not added (they do not exist!): "), NG1);
error -> [?XREST(?T("Bad format"))];
nothing -> []
end
@@ -894,28 +1045,37 @@ shared_roster_group(Host, Group, Query, Lang) ->
[FGroup, ?BR,
?INPUTT(<<"submit">>, <<"submit">>, ?T("Submit"))])].
make_error_el(_, _, []) ->
[];
make_error_el(Lang, Message, BinList) ->
NG2 = str:join(BinList, <<", ">>),
NG3 = translate:translate(Lang, Message),
NG4 = str:concat(NG3, NG2),
[?XRES(NG4)].
shared_roster_group_parse_query(Host, Group, Query) ->
case lists:keysearch(<<"submit">>, 1, Query) of
{value, _} ->
{value, {_, Name}} = lists:keysearch(<<"name">>, 1,
Query),
{value, {_, Label}} = lists:keysearch(<<"label">>, 1,
Query), %++
{value, {_, Description}} =
lists:keysearch(<<"description">>, 1, Query),
{value, {_, SMembers}} = lists:keysearch(<<"members">>,
1, Query),
{value, {_, SDispGroups}} =
lists:keysearch(<<"dispgroups">>, 1, Query),
NameOpt = if Name == <<"">> -> [];
true -> [{name, Name}]
LabelOpt = if Label == <<"">> -> [];
true -> [{label, Label}] %++
end,
DescriptionOpt = if Description == <<"">> -> [];
true -> [{description, Description}]
end,
DispGroups = str:tokens(SDispGroups, <<"\r\n">>),
DispGroups1 = str:tokens(SDispGroups, <<"\r\n">>),
{DispGroups, WrongDispGroups} = filter_groups_existence(Host, DispGroups1),
DispGroupsOpt = if DispGroups == [] -> [];
true -> [{displayed_groups, DispGroups}]
end,
OldMembers = mod_shared_roster:get_group_explicit_users(Host,
OldMembers = get_group_explicit_users(Host,
Group),
SJIDs = str:tokens(SMembers, <<", \r\n">>),
NewMembers = lists:foldl(fun (_SJID, error) -> error;
@@ -950,29 +1110,31 @@ shared_roster_group_parse_query(Host, Group, Query) ->
RemovedDisplayedGroups = CurrentDisplayedGroups -- DispGroups,
displayed_groups_update(OldMembers, RemovedDisplayedGroups, remove),
displayed_groups_update(OldMembers, AddedDisplayedGroups, both),
mod_shared_roster:set_group_opts(Host, Group,
NameOpt ++
DispGroupsOpt ++
DescriptionOpt ++
AllUsersOpt ++ OnlineUsersOpt),
set_group_opts(Host, Group,
LabelOpt ++
DispGroupsOpt ++
DescriptionOpt ++
AllUsersOpt ++ OnlineUsersOpt),
if NewMembers == error -> error;
true ->
AddedMembers = NewMembers -- OldMembers,
RemovedMembers = OldMembers -- NewMembers,
lists:foreach(fun (US) ->
mod_shared_roster:remove_user_from_group(Host,
US,
Group)
end,
RemovedMembers),
NonAddedMembers = lists:filter(fun (US) ->
error == mod_shared_roster:add_user_to_group(Host, US,
Group)
end,
AddedMembers),
case NonAddedMembers of
[] -> ok;
_ -> {error_jids, NonAddedMembers}
lists:foreach(
fun(US) ->
remove_user_from_group(Host,
US,
Group)
end,
RemovedMembers),
NonAddedMembers = lists:filter(
fun(US) ->
error == add_user_to_group(Host, US,
Group)
end,
AddedMembers),
case (NonAddedMembers /= []) or (WrongDispGroups /= []) of
true -> {error_elements, NonAddedMembers, WrongDispGroups};
false -> ok
end
end;
_ -> nothing
@@ -993,6 +1155,11 @@ split_grouphost(Host, Group) ->
[_] -> {Host, Group}
end.
filter_groups_existence(Host, Groups) ->
lists:partition(
fun(Group) -> error /= get_group_opts(Host, Group) end,
Groups).
displayed_groups_update(Members, DisplayedGroups, Subscription) ->
lists:foreach(
fun({U, S}) ->
@@ -1001,8 +1168,10 @@ displayed_groups_update(Members, DisplayedGroups, Subscription) ->
opts_to_binary(Opts) ->
lists:map(
fun({name, Name}) ->
{name, iolist_to_binary(Name)};
fun({label, Label}) ->
{label, iolist_to_binary(Label)};
({name, Label}) -> % For SQL backwards compat with ejabberd 20.03 and older
{label, iolist_to_binary(Label)};
({description, Desc}) ->
{description, iolist_to_binary(Desc)};
({displayed_groups, Gs}) ->
@@ -1027,10 +1196,22 @@ import(LServer, {sql, _}, DBType, Tab, L) ->
Mod:import(LServer, Tab, L).
mod_opt_type(db_type) ->
econf:db_type(?MODULE).
econf:db_type(?MODULE);
mod_opt_type(use_cache) ->
econf:bool();
mod_opt_type(cache_size) ->
econf:pos_int(infinity);
mod_opt_type(cache_missed) ->
econf:bool();
mod_opt_type(cache_life_time) ->
econf:timeout(second, infinity).
mod_options(Host) ->
[{db_type, ejabberd_config:default_db(Host, ?MODULE)}].
[{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{use_cache, ejabberd_option:use_cache(Host)},
{cache_size, ejabberd_option:cache_size(Host)},
{cache_missed, ejabberd_option:cache_missed(Host)},
{cache_life_time, ejabberd_option:cache_life_time(Host)}].
mod_doc() ->
#{desc =>
@@ -1047,9 +1228,10 @@ mod_doc() ->
"not replace the standard roster. Instead, the shared roster "
"contacts are merged to the relevant users at retrieval time. "
"The standard user rosters thus stay unmodified."), "",
?T("Shared roster groups can be edited only via the Web Admin. "
"Each group has unique identification and those parameters:"), "",
?T("- Name: The group's name will be displayed in the roster."), "",
?T("Shared roster groups can be edited via the Web Admin, "
"and some API commands called 'srg_*'. "
"Each group has a unique name and those parameters:"), "",
?T("- Label: Used in the rosters where this group is displayed."),"",
?T("- Description: of the group, which has no effect."), "",
?T("- Members: A list of JIDs of group members, entered one per "
"line in the Web Admin. The special member directive '@all@' "
@@ -1059,7 +1241,7 @@ mod_doc() ->
"represents the online users in the virtual host. With those "
"two directives, the actual list of members in those shared "
"rosters is generated dynamically at retrieval time."), "",
?T("- Displayed groups: A list of groups that will be in the "
?T("- Displayed: A list of groups that will be in the "
"rosters of this group's members. A group of other vhost can "
"be identified with 'groupid@vhost'."), "",
?T("This module depends on 'mod_roster'. "
@@ -1072,13 +1254,29 @@ mod_doc() ->
"the tables and store user information. The default is "
"the storage defined by the global option 'default_db', "
"or 'mnesia' if omitted. If 'sql' value is defined, "
"make sure you have defined the database.")}}],
"make sure you have defined the database.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}],
example =>
[{?T("Take the case of a computer club that wants all its members "
"seeing each other in their rosters. To achieve this, they "
"need to create a shared roster group similar to this one:"),
["Identification: club_members",
"Name: Club Members",
["Name: club_members",
"Label: Club Members",
"Description: Members from the computer club",
"Members: member1@example.org, member2@example.org, member3@example.org",
"Displayed Groups: club_members"]},
@@ -1090,24 +1288,24 @@ mod_doc() ->
"should see all managers. This scenario can be achieved by "
"creating shared roster groups as shown in the following lists:"),
["First list:",
"Identification: management",
"Name: Management",
"Name: management",
"Label: Management",
"Description: Management",
"Members: manager1@example.org, manager2@example.org",
"Displayed Groups: management, marketing, sales",
"Displayed: management, marketing, sales",
"",
"Second list:",
"Identification: marketing",
"Name: Marketing",
"Name: marketing",
"Label: Marketing",
"Description: Marketing",
"Members: marketeer1@example.org, marketeer2@example.org, marketeer3@example.org",
"Displayed Groups: management, marketing",
"Displayed: management, marketing",
"",
"Third list:",
"Identification: sales",
"Name: Sales",
"Name: sales",
"Label: Sales",
"Description: Sales",
"Members: salesman1@example.org, salesman2@example.org, salesman3@example.org",
"Displayed Groups: management, sales"
"Displayed: management, sales"
]}
]}.
+139 -1
View File
@@ -40,12 +40,13 @@
-export([get_user_roster/2,
get_jid_info/4, process_item/2, in_subscription/2,
out_subscription/1, mod_opt_type/1, mod_options/1,
depends/2]).
depends/2, mod_doc/0]).
-include("logger.hrl").
-include("xmpp.hrl").
-include("mod_roster.hrl").
-include("eldap.hrl").
-include("translate.hrl").
-define(USER_CACHE, shared_roster_ldap_user_cache).
-define(GROUP_CACHE, shared_roster_ldap_group_cache).
@@ -624,3 +625,140 @@ mod_options(Host) ->
{cache_size, ejabberd_option:cache_size(Host)},
{cache_missed, ejabberd_option:cache_missed(Host)},
{cache_life_time, ejabberd_option:cache_life_time(Host)}].
mod_doc() ->
#{desc =>
[?T("This module lets the server administrator automatically "
"populate users' rosters (contact lists) with entries based on "
"users and groups defined in an LDAP-based directory."), "",
?T("NOTE: 'mod_shared_roster_ldap' depends on 'mod_roster' being "
"enabled. Roster queries will return '503' errors if "
"'mod_roster' is not enabled."), "",
?T("The module accepts many configuration options. Some of them, "
"if unspecified, default to the values specified for the top "
"level of configuration. This lets you avoid specifying, for "
"example, the bind password in multiple places."), "",
?T("- Filters: 'ldap_rfilter', 'ldap_ufilter', 'ldap_gfilter', "
"'ldap_filter'. These options specify LDAP filters used to "
"query for shared roster information. All of them are run "
"against the ldap_base."),
?T("- Attributes: 'ldap_groupattr', 'ldap_groupdesc', "
"'ldap_memberattr', 'ldap_userdesc', 'ldap_useruid'. These "
"options specify the names of the attributes which hold "
"interesting data in the entries returned by running filters "
"specified with the filter options."),
?T("- Control parameters: 'ldap_auth_check', "
"'ldap_group_cache_validity', 'ldap_memberattr_format', "
"'ldap_memberattr_format_re', 'ldap_user_cache_validity'. "
"These parameters control the behaviour of the module."),
?T("- Connection parameters: The module also accepts the "
"connection parameters, all of which default to the top-level "
"parameter of the same name, if unspecified. "
"See http://../database-ldap/#ldap-connection[LDAP Connection] "
"section for more information about them."), "",
?T("Check also the http://../database-ldap/#configuration-examples"
"[Configuration examples] section to get details about "
"retrieving the roster, "
"and configuration examples including Flat DIT and Deep DIT.")],
opts =>
[
%% Filters:
{ldap_rfilter,
#{desc =>
?T("So called \"Roster Filter\". Used to find names of "
"all \"shared roster\" groups. See also the "
"'ldap_groupattr' parameter. If unspecified, defaults to "
"the top-level parameter of the same name. You must "
"specify it in some place in the configuration, there is "
"no default.")}},
{ldap_gfilter,
#{desc =>
?T("\"Group Filter\", used when retrieving human-readable "
"name (a.k.a. \"Display Name\") and the members of a "
"group. See also the parameters 'ldap_groupattr', "
"'ldap_groupdesc' and 'ldap_memberattr'. If unspecified, "
"defaults to the top-level parameter of the same name. "
"If that one also is unspecified, then the filter is "
"constructed exactly like \"User Filter\".")}},
{ldap_ufilter,
#{desc =>
?T("\"User Filter\", used for retrieving the human-readable "
"name of roster entries (usually full names of people in "
"the roster). See also the parameters 'ldap_userdesc' and "
"'ldap_useruid'. For more information check the LDAP "
"http://../database-ldap/#filters[Filters] section.")}},
{ldap_filter,
#{desc =>
?T("Additional filter which is AND-ed together "
"with \"User Filter\" and \"Group Filter\". "
"For more information check the LDAP "
"http://../database-ldap/#filters[Filters] section.")}},
%% Attributes:
{ldap_groupattr,
#{desc =>
?T("The name of the attribute that holds the group name, and "
"that is used to differentiate between them. Retrieved "
"from results of the \"Roster Filter\" "
"and \"Group Filter\". Defaults to 'cn'.")}},
{ldap_groupdesc,
#{desc =>
?T("The name of the attribute which holds the human-readable "
"group name in the objects you use to represent groups. "
"Retrieved from results of the \"Group Filter\". "
"Defaults to whatever 'ldap_groupattr' is set.")}},
{ldap_memberattr,
#{desc =>
?T("The name of the attribute which holds the IDs of the "
"members of a group. Retrieved from results of the "
"\"Group Filter\". Defaults to 'memberUid'. The name of "
"the attribute differs depending on the objectClass you "
"use for your group objects, for example: "
"'posixGroup' -> 'memberUid'; 'groupOfNames' -> 'member'; "
"'groupOfUniqueNames' -> 'uniqueMember'.")}},
{ldap_userdesc,
#{desc =>
?T("The name of the attribute which holds the human-readable "
"user name. Retrieved from results of the "
"\"User Filter\". Defaults to 'cn'.")}},
{ldap_useruid,
#{desc =>
?T("The name of the attribute which holds the ID of a roster "
"item. Value of this attribute in the roster item objects "
"needs to match the ID retrieved from the "
"'ldap_memberattr' attribute of a group object. "
"Retrieved from results of the \"User Filter\". "
"Defaults to 'cn'.")}},
%% Control parameters:
{ldap_memberattr_format,
#{desc =>
?T("A globbing format for extracting user ID from the value "
"of the attribute named by 'ldap_memberattr'. Defaults "
"to '%u', which means that the whole value is the member "
"ID. If you change it to something different, you may "
"also need to specify the User and Group Filters "
"manually; see section Filters.")}},
{ldap_memberattr_format_re,
#{desc =>
?T("A regex for extracting user ID from the value of the "
"attribute named by 'ldap_memberattr'. Check the LDAP "
"http://../database-ldap/#control-parameters"
"[Control Parameters] section.")}},
{ldap_auth_check,
#{value => "true | false",
desc =>
?T("Whether the module should check (via the ejabberd "
"authentication subsystem) for existence of each user in "
"the shared LDAP roster. Set to 'false' if you want to "
"disable the check. Default value is 'true'.")}}] ++
[{Opt,
#{desc =>
{?T("Same as top-level '~s' option, but "
"applied to this module only."), [Opt]}}}
|| Opt <- [ldap_backups, ldap_base, ldap_uids, ldap_deref_aliases,
ldap_encrypt, ldap_password, ldap_port, ldap_rootdn,
ldap_servers, ldap_tls_certfile, ldap_tls_cacertfile,
ldap_tls_depth, ldap_tls_verify, use_cache, cache_size,
cache_missed, cache_life_time]]}.
+15 -1
View File
@@ -32,7 +32,7 @@
get_user_groups/2, get_group_explicit_users/2,
get_user_displayed_groups/3, is_user_in_group/3,
add_user_to_group/3, remove_user_from_group/3, import/3]).
-export([need_transform/1, transform/1]).
-export([need_transform/1, transform/1, use_cache/1]).
-include("mod_roster.hrl").
-include("mod_shared_roster.hrl").
@@ -56,6 +56,10 @@ list_groups(Host) ->
[{#sr_group{group_host = {'$1', '$2'}, _ = '_'},
[{'==', '$2', Host}], ['$1']}]).
-spec use_cache(binary()) -> boolean().
use_cache(_Host) ->
false.
groups_with_opts(Host) ->
Gs = mnesia:dirty_select(sr_group,
[{#sr_group{group_host = {'$1', Host}, opts = '$2',
@@ -152,9 +156,19 @@ need_transform({sr_user, {U, S}, {G, H}})
when is_list(U) orelse is_list(S) orelse is_list(G) orelse is_list(H) ->
?INFO_MSG("Mnesia table 'sr_user' will be converted to binary", []),
true;
need_transform({sr_group, {_, _}, [{name, _} | _]}) ->
?INFO_MSG("Mnesia table 'sr_group' will be converted from option Name to Label", []),
true;
need_transform(_) ->
false.
transform(#sr_group{group_host = {G, _H}, opts = Opts} = R)
when is_binary(G) ->
Opts2 = case proplists:get_value(name, Opts, false) of
false -> Opts;
Name -> [{label, Name} | proplists:delete(name, Opts)]
end,
R#sr_group{opts = Opts2};
transform(#sr_group{group_host = {G, H}, opts = Opts} = R) ->
R#sr_group{group_host = {iolist_to_binary(G), iolist_to_binary(H)},
opts = mod_shared_roster:opts_to_binary(Opts)};
+28
View File
@@ -3,7 +3,29 @@
-module(mod_shared_roster_opt).
-export([cache_life_time/1]).
-export([cache_missed/1]).
-export([cache_size/1]).
-export([db_type/1]).
-export([use_cache/1]).
-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer().
cache_life_time(Opts) when is_map(Opts) ->
gen_mod:get_opt(cache_life_time, Opts);
cache_life_time(Host) ->
gen_mod:get_module_opt(Host, mod_shared_roster, cache_life_time).
-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean().
cache_missed(Opts) when is_map(Opts) ->
gen_mod:get_opt(cache_missed, Opts);
cache_missed(Host) ->
gen_mod:get_module_opt(Host, mod_shared_roster, cache_missed).
-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer().
cache_size(Opts) when is_map(Opts) ->
gen_mod:get_opt(cache_size, Opts);
cache_size(Host) ->
gen_mod:get_module_opt(Host, mod_shared_roster, cache_size).
-spec db_type(gen_mod:opts() | global | binary()) -> atom().
db_type(Opts) when is_map(Opts) ->
@@ -11,3 +33,9 @@ db_type(Opts) when is_map(Opts) ->
db_type(Host) ->
gen_mod:get_module_opt(Host, mod_shared_roster, db_type).
-spec use_cache(gen_mod:opts() | global | binary()) -> boolean().
use_cache(Opts) when is_map(Opts) ->
gen_mod:get_opt(use_cache, Opts);
use_cache(Host) ->
gen_mod:get_module_opt(Host, mod_shared_roster, use_cache).
+42 -22
View File
@@ -192,7 +192,7 @@ c2s_handle_recv(State, _, _) ->
c2s_handle_send(#{mgmt_state := MgmtState, mod := Mod,
lang := Lang} = State, Pkt, SendResult)
when MgmtState == pending; MgmtState == active ->
when MgmtState == pending; MgmtState == active; MgmtState == resumed ->
IsStanza = xmpp:is_stanza(Pkt),
case Pkt of
_ when IsStanza ->
@@ -214,10 +214,13 @@ c2s_handle_send(#{mgmt_state := MgmtState, mod := Mod,
end;
#stream_error{} ->
case MgmtState of
resumed ->
State;
active ->
State;
pending ->
Mod:stop(State#{stop_reason => {stream, {out, Pkt}}})
Mod:stop_async(self()),
{stop, State#{stop_reason => {stream, {out, Pkt}}}}
end;
_ ->
State
@@ -229,7 +232,7 @@ c2s_handle_call(#{sid := {Time, _}, mod := Mod, mgmt_queue := Queue} = State,
{resume_session, Time}, From) ->
State1 = State#{mgmt_queue => p1_queue:file_to_ram(Queue)},
Mod:reply(From, {resume, State1}),
{stop, State#{mgmt_state => resumed}};
{stop, State#{mgmt_state => resumed, mgmt_queue => p1_queue:clear(Queue)}};
c2s_handle_call(#{mod := Mod} = State, {resume_session, _}, From) ->
Mod:reply(From, {error, session_not_found}),
{stop, State};
@@ -250,8 +253,9 @@ c2s_handle_info(#{mgmt_state := pending, lang := Lang,
[jid:encode(JID)]),
Txt = ?T("Timed out waiting for stream resumption"),
Err = xmpp:serr_connection_timeout(Txt, Lang),
Mod:stop(State#{mgmt_state => timeout,
stop_reason => {stream, {out, Err}}});
Mod:stop_async(self()),
{stop, State#{mgmt_state => timeout,
stop_reason => {stream, {out, Err}}}};
c2s_handle_info(State, {_Ref, {resume, #{jid := JID} = OldState}}) ->
%% This happens if the resume_session/1 request timed out; the new session
%% now receives the late response.
@@ -280,6 +284,7 @@ c2s_terminated(#{mgmt_state := resumed, sid := SID, jid := JID} = State, _Reason
[jid:encode(JID)]),
{U, S, R} = jid:tolower(JID),
ejabberd_sm:close_session(SID, U, S, R),
route_late_queue_after_resume(State),
ejabberd_c2s:bounce_message_queue(SID, JID),
{stop, State};
c2s_terminated(#{mgmt_state := MgmtState, mgmt_stanzas_in := In,
@@ -444,7 +449,8 @@ handle_resume(#{user := User, lserver := LServer,
-spec transition_to_pending(state(), _) -> state().
transition_to_pending(#{mgmt_state := active, mod := Mod,
mgmt_timeout := 0} = State, _Reason) ->
Mod:stop(State);
Mod:stop_async(self()),
State;
transition_to_pending(#{mgmt_state := active, jid := JID, socket := Socket,
lserver := LServer, mgmt_timeout := Timeout} = State,
Reason) ->
@@ -541,6 +547,18 @@ check_queue_length(#{mgmt_queue := Queue, mgmt_max_queue := Limit} = State) ->
State
end.
-spec route_late_queue_after_resume(state()) -> ok.
route_late_queue_after_resume(#{mgmt_queue := Queue, jid := JID})
when ?qlen(Queue) > 0 ->
?DEBUG("Re-routing ~B late queued packets to ~ts",
[p1_queue:len(Queue), jid:encode(JID)]),
p1_queue:foreach(
fun({_, _Time, Pkt}) ->
ejabberd_router:route(Pkt)
end, Queue);
route_late_queue_after_resume(_State) ->
ok.
-spec resend_unacked_stanzas(state()) -> state().
resend_unacked_stanzas(#{mgmt_state := MgmtState,
mgmt_queue := Queue,
@@ -587,6 +605,7 @@ route_unacked_stanzas(#{mgmt_state := MgmtState,
end,
?DEBUG("Re-routing ~B unacknowledged stanza(s) to ~ts",
[p1_queue:len(Queue), jid:encode(JID)]),
ModOfflineEnabled = gen_mod:is_loaded(LServer, mod_offline),
p1_queue:foreach(
fun({_, _Time, #presence{from = From}}) ->
?DEBUG("Dropping presence stanza from ~ts", [jid:encode(From)]);
@@ -603,20 +622,21 @@ route_unacked_stanzas(#{mgmt_state := MgmtState,
?DEBUG("Dropping forwarded message stanza from ~ts",
[jid:encode(From)]);
({_, Time, #message{} = Msg}) ->
case ejabberd_hooks:run_fold(message_is_archived,
LServer, false,
[State, Msg]) of
true ->
?DEBUG("Dropping archived message stanza from ~ts",
[jid:encode(xmpp:get_from(Msg))]);
false when ResendOnTimeout ->
NewEl = add_resent_delay_info(State, Msg, Time),
ejabberd_router:route(NewEl);
false ->
Txt = ?T("User session terminated"),
ejabberd_router:route_error(
Msg, xmpp:err_service_unavailable(Txt, Lang))
end;
case {ModOfflineEnabled, ResendOnTimeout,
xmpp:get_meta(Msg, mam_archived, false)} of
Val when Val == {true, true, false};
Val == {true, true, true};
Val == {false, true, false} ->
NewEl = add_resent_delay_info(State, Msg, Time),
ejabberd_router:route(NewEl);
{_, _, true} ->
?DEBUG("Dropping archived message stanza from ~s",
[jid:encode(xmpp:get_from(Msg))]);
_ ->
Txt = ?T("User session terminated"),
ejabberd_router:route_error(
Msg, xmpp:err_service_unavailable(Txt, Lang))
end;
({_, _Time, El}) ->
%% Raw element of type 'error' resulting from a validation error
%% We cannot pass it to the router, it will generate an error
@@ -660,7 +680,7 @@ inherit_session_state(#{user := U, server := S,
mgmt_stanzas_out => NumStanzasOut,
mgmt_state => active},
State3 = ejabberd_c2s:open_session(State2),
ejabberd_c2s:stop(OldPID),
ejabberd_c2s:stop_async(OldPID),
{ok, State3};
{error, Msg} ->
{error, Msg}
@@ -674,7 +694,7 @@ inherit_session_state(#{user := U, server := S,
{error, session_was_killed};
exit:{timeout, _} ->
ejabberd_sm:close_session(OldSID, U, S, R),
ejabberd_c2s:stop(OldPID),
ejabberd_c2s:stop_async(OldPID),
{error, session_copy_timed_out}
end
end;
+670
View File
@@ -0,0 +1,670 @@
%%%----------------------------------------------------------------------
%%% File : mod_stun_disco.erl
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Purpose : External Service Discovery (XEP-0215)
%%% Created : 18 Apr 2020 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
%%% ejabberd, Copyright (C) 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_stun_disco).
-author('holger@zedat.fu-berlin.de').
-protocol({xep, 215, '0.7'}).
-behaviour(gen_server).
-behaviour(gen_mod).
%% gen_mod callbacks.
-export([start/2,
stop/1,
reload/3,
mod_opt_type/1,
mod_options/1,
depends/2]).
-export([mod_doc/0]).
%% gen_server callbacks.
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
%% ejabberd_hooks callbacks.
-export([disco_local_features/5, stun_get_password/3]).
%% gen_iq_handler callback.
-export([process_iq/1]).
-include("logger.hrl").
-include("translate.hrl").
-include("xmpp.hrl").
-define(STUN_MODULE, ejabberd_stun).
-type host_or_hash() :: binary() | {hash, binary()}.
-type service_type() :: stun | stuns | turn | turns | undefined.
-record(request,
{host :: binary() | inet:ip_address() | undefined,
port :: 0..65535 | undefined,
transport :: udp | tcp | undefined,
type :: service_type(),
restricted :: true | undefined}).
-record(state,
{host :: binary(),
services :: [service()],
secret :: binary(),
ttl :: non_neg_integer()}).
-type request() :: #request{}.
-type state() :: #state{}.
%%--------------------------------------------------------------------
%% gen_mod callbacks.
%%--------------------------------------------------------------------
-spec start(binary(), gen_mod:opts()) -> {ok, pid()} | {error, any()}.
start(Host, Opts) ->
Proc = get_proc_name(Host),
gen_mod:start_child(?MODULE, Host, Opts, Proc).
-spec stop(binary()) -> ok | {error, any()}.
stop(Host) ->
Proc = get_proc_name(Host),
gen_mod:stop_child(Proc).
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
reload(Host, NewOpts, OldOpts) ->
cast(Host, {reload, NewOpts, OldOpts}).
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].
-spec mod_opt_type(atom()) -> econf:validator().
mod_opt_type(access) ->
econf:acl();
mod_opt_type(credentials_lifetime) ->
econf:timeout(second);
mod_opt_type(offer_local_services) ->
econf:bool();
mod_opt_type(secret) ->
econf:binary();
mod_opt_type(services) ->
econf:list(
econf:and_then(
econf:options(
#{host => econf:either(econf:ip(), econf:binary()),
port => econf:port(),
type => econf:enum([stun, turn, stuns, turns]),
transport => econf:enum([tcp, udp]),
restricted => econf:bool()},
[{required, [host]}]),
fun(Opts) ->
DefPort = fun(stun) -> 3478;
(turn) -> 3478;
(stuns) -> 5349;
(turns) -> 5349
end,
DefTrns = fun(stun) -> udp;
(turn) -> udp;
(stuns) -> tcp;
(turns) -> tcp
end,
DefRstr = fun(stun) -> false;
(turn) -> true;
(stuns) -> false;
(turns) -> true
end,
Host = proplists:get_value(host, Opts),
Type = proplists:get_value(type, Opts, stun),
Port = proplists:get_value(port, Opts, DefPort(Type)),
Trns = proplists:get_value(transport, Opts, DefTrns(Type)),
Rstr = proplists:get_value(restricted, Opts, DefRstr(Type)),
#service{host = Host,
port = Port,
type = Type,
transport = Trns,
restricted = Rstr}
end)).
-spec mod_options(binary()) -> [{services, [tuple()]} | {atom(), any()}].
mod_options(_Host) ->
[{access, local},
{credentials_lifetime, timer:minutes(10)},
{offer_local_services, true},
{secret, undefined},
{services, []}].
mod_doc() ->
#{desc =>
?T("This module allows XMPP clients to discover STUN/TURN services "
"and to obtain temporary credentials for using them as per "
"https://xmpp.org/extensions/xep-0215.html"
"[XEP-0215: External Service Discovery]."),
opts =>
[{access,
#{value => ?T("AccessName"),
desc =>
?T("This option defines which access rule will be used to "
"control who is allowed to discover STUN/TURN services "
"and to request temporary credentials. The default value "
"is 'local'.")}},
{credentials_lifetime,
#{value => "timeout()",
desc =>
?T("The lifetime of temporary credentails offered to "
"clients. If a lifetime longer than the default value of "
"'10' minutes is specified, it's strongly recommended to "
"also specify a 'secret' (see below).")}},
{offer_local_services,
#{value => "true | false",
desc =>
?T("This option specifies whether local STUN/TURN services "
"configured as ejabberd listeners should be announced "
"automatically. Note that this will not include "
"TLS-enabled services, which must be configured manually "
"using the 'services' option (see below). For "
"non-anonymous TURN services, temporary credentials will "
"be offered to the client. The default value is "
"'true'.")}},
{secret,
#{value => ?T("Text"),
desc =>
?T("The secret used for generating temporary credentials. If "
"this option isn't specified, a secret will be "
"auto-generated. However, a secret must be specified if "
"non-anonymous TURN services running on other ejabberd "
"nodes and/or external TURN 'services' are configured. "
"Also note that auto-generated secrets are lost when the "
"node is restarted, which invalidates any credentials "
"offered before the restart. Therefore, the "
"'credentials_lifetime' should not exceed a few minutes "
"if no 'secret' is specified.")}},
{services,
#{value => "[Service, ...]",
example =>
["services:",
" -",
" host: 203.0.113.3",
" port: 3478",
" type: stun",
" transport: udp",
" restricted: false",
" -",
" host: 203.0.113.3",
" port: 3478",
" type: turn",
" transport: udp",
" restricted: true",
" -",
" host: 203.0.113.3",
" port: 3478",
" type: stun",
" transport: tcp",
" restricted: false",
" -",
" host: 203.0.113.3",
" port: 3478",
" type: turn",
" transport: tcp",
" restricted: true",
" -",
" host: server.example.com",
" port: 5349",
" type: stuns",
" transport: tcp",
" restricted: false",
" -",
" host: server.example.com",
" port: 5349",
" type: turns",
" transport: tcp",
" restricted: true"],
desc =>
?T("The list of services offered to clients. This list can "
"include STUN/TURN services running on any ejabberd node "
"and/or external services. However, if any listed TURN "
"service not running on the local ejabberd node requires "
"authentication, a 'secret' must be specified explicitly, "
"and must be shared with that service. This will only "
"work with ejabberd's built-in STUN/TURN server and with "
"external servers that support the same "
"https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00"
"[REST API For Access To TURN Services]. Unless the "
"'offer_local_services' is set to 'false', the explicitly "
"listed services will be offered in addition to those "
"announced automatically.")},
[{host,
#{value => ?T("Host"),
desc =>
?T("The host name or IPv4 address the STUN/TURN service is "
"listening on. For non-TLS services, it's recommended "
"to specify an IPv4 address (to avoid additional DNS "
"lookup latency on the client side). For TLS services, "
"the host name (or possible IPv4 address) should match "
"the certificate. Specifying the 'host' option is "
"mandatory.")}},
{port,
#{value => "1..65535",
desc =>
?T("The port number the STUN/TURN service is listening "
"on. The default port number is 3478 for non-TLS "
"services and 5349 for TLS services.")}},
{type,
#{value => "stun | turn | stuns | turns",
desc =>
?T("The type of service. Must be 'stun' or 'turn' for "
"non-TLS services, 'stuns' or 'turns' for TLS services. "
"The default type is 'stun'.")}},
{transport,
#{value => "tcp | udp",
desc =>
?T("The transport protocol supported by the service. The "
"default is 'udp' for non-TLS services and 'tcp' for "
"TLS services.")}},
{restricted,
#{value => "true | false",
desc =>
?T("This option determines whether temporary credentials "
"for accessing the service are offered. The default is "
"'false' for STUN/STUNS services and 'true' for "
"TURN/TURNS services.")}}]}]}.
%%--------------------------------------------------------------------
%% gen_server callbacks.
%%--------------------------------------------------------------------
-spec init(list()) -> {ok, state()}.
init([Host, Opts]) ->
process_flag(trap_exit, true),
Services = get_configured_services(Opts),
Secret = get_configured_secret(Opts),
TTL = get_configured_ttl(Opts),
register_iq_handlers(Host),
register_hooks(Host),
{ok, #state{host = Host, services = Services, secret = Secret, ttl = TTL}}.
-spec handle_call(term(), {pid(), term()}, state())
-> {reply, {turn_disco, [service()] | binary()}, state()} |
{noreply, state()}.
handle_call({get_services, JID, #request{host = ReqHost,
port = ReqPort,
type = ReqType,
transport = ReqTrns,
restricted = ReqRstr}}, _From,
#state{host = Host,
services = List0,
secret = Secret,
ttl = TTL} = State) ->
?DEBUG("Getting STUN/TURN service list for ~ts", [jid:encode(JID)]),
Hash = <<(hash(jid:encode(JID)))/binary, (hash(Host))/binary>>,
List = lists:filtermap(
fun(#service{host = H, port = P, type = T, restricted = R})
when (ReqHost /= undefined) and (H /= ReqHost);
(ReqPort /= undefined) and (P /= ReqPort);
(ReqType /= undefined) and (T /= ReqType);
(ReqTrns /= undefined) and (T /= ReqTrns);
(ReqRstr /= undefined) and (R /= ReqRstr) ->
false;
(#service{restricted = false}) ->
true;
(#service{restricted = true} = Service) ->
{true, add_credentials(Service, Hash, Secret, TTL)}
end, List0),
?INFO_MSG("Offering STUN/TURN services to ~ts (~s)",
[jid:encode(JID), Hash]),
{reply, {turn_disco, List}, State};
handle_call({get_password, Username}, _From, #state{secret = Secret} = State) ->
?DEBUG("Getting STUN/TURN password for ~ts", [Username]),
Password = make_password(Username, Secret),
{reply, {turn_disco, Password}, State};
handle_call(Request, From, State) ->
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
{noreply, State}.
-spec handle_cast(term(), state()) -> {noreply, state()}.
handle_cast({reload, NewOpts, _OldOpts}, #state{host = Host} = State) ->
?DEBUG("Reloading STUN/TURN discovery configuration for ~ts", [Host]),
Services = get_configured_services(NewOpts),
Secret = get_configured_secret(NewOpts),
TTL = get_configured_ttl(NewOpts),
{noreply, State#state{services = Services, secret = Secret, ttl = TTL}};
handle_cast(Request, State) ->
?ERROR_MSG("Got unexpected request from: ~p", [Request]),
{noreply, State}.
-spec handle_info(term(), state()) -> {noreply, state()}.
handle_info(Info, State) ->
?ERROR_MSG("Got unexpected info: ~p", [Info]),
{noreply, State}.
-spec terminate(normal | shutdown | {shutdown, term()} | term(), state()) -> ok.
terminate(Reason, #state{host = Host}) ->
?DEBUG("Stopping STUN/TURN discovery process for ~ts: ~p",
[Host, Reason]),
unregister_hooks(Host),
unregister_iq_handlers(Host).
-spec code_change({down, term()} | term(), state(), term()) -> {ok, state()}.
code_change(_OldVsn, #state{host = Host} = State, _Extra) ->
?DEBUG("Updating STUN/TURN discovery process for ~ts", [Host]),
{ok, State}.
%%--------------------------------------------------------------------
%% Register/unregister hooks.
%%--------------------------------------------------------------------
-spec register_hooks(binary()) -> ok.
register_hooks(Host) ->
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_local_features, 50),
ejabberd_hooks:add(stun_get_password, ?MODULE,
stun_get_password, 50).
-spec unregister_hooks(binary()) -> ok.
unregister_hooks(Host) ->
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
disco_local_features, 50),
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_hooks:delete(stun_get_password, ?MODULE,
stun_get_password, 50);
true ->
ok
end.
%%--------------------------------------------------------------------
%% Hook callbacks.
%%--------------------------------------------------------------------
-spec disco_local_features(mod_disco:features_acc(), jid(), jid(), binary(),
binary()) -> mod_disco:features_acc().
disco_local_features(empty, From, To, Node, Lang) ->
disco_local_features({result, []}, From, To, Node, Lang);
disco_local_features({result, OtherFeatures} = Acc, From,
#jid{lserver = LServer}, <<"">>, _Lang) ->
Access = mod_stun_disco_opt:access(LServer),
case acl:match_rule(LServer, Access, From) of
allow ->
?DEBUG("Announcing feature to ~ts", [jid:encode(From)]),
{result, [?NS_EXTDISCO_2 | OtherFeatures]};
deny ->
?DEBUG("Not announcing feature to ~ts", [jid:encode(From)]),
Acc
end;
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
-spec stun_get_password(any(), binary(), binary())
-> binary() | {stop, binary()}.
stun_get_password(<<>>, Username, _Realm) ->
case binary:split(Username, <<$:>>) of
[Expiration, <<_UserHash:8/binary, HostHash:8/binary>>] ->
try binary_to_integer(Expiration) of
ExpireTime ->
case erlang:system_time(second) of
Now when Now < ExpireTime ->
?DEBUG("Looking up password for: ~ts", [Username]),
{stop, get_password(Username, HostHash)};
Now when Now >= ExpireTime ->
?INFO_MSG("Credentials expired: ~ts", [Username]),
{stop, <<>>}
end
catch _:badarg ->
?DEBUG("Non-numeric expiration field: ~ts", [Username]),
<<>>
end;
_ ->
?DEBUG("Not an ephemeral username: ~ts", [Username]),
<<>>
end;
stun_get_password(Acc, _Username, _Realm) ->
Acc.
%%--------------------------------------------------------------------
%% IQ handlers.
%%--------------------------------------------------------------------
-spec register_iq_handlers(binary()) -> ok.
register_iq_handlers(Host) ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_EXTDISCO_2, ?MODULE, process_iq).
-spec unregister_iq_handlers(binary()) -> ok.
unregister_iq_handlers(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_EXTDISCO_2).
-spec process_iq(iq()) -> iq().
process_iq(#iq{type = get,
sub_els = [#services{type = ReqType}]} = IQ) ->
Request = #request{type = ReqType},
process_iq_get(IQ, Request);
process_iq(#iq{type = get,
sub_els = [#credentials{
services = [#service{
host = ReqHost,
port = ReqPort,
type = ReqType,
transport = ReqTrns,
name = <<>>,
username = <<>>,
password = <<>>,
expires = undefined,
restricted = undefined,
action = undefined,
xdata = undefined}]}]} = IQ) ->
% Accepting the 'transport' request attribute is an ejabberd extension.
Request = #request{host = ReqHost,
port = ReqPort,
type = ReqType,
transport = ReqTrns,
restricted = true},
process_iq_get(IQ, Request);
process_iq(#iq{type = set, lang = Lang} = IQ) ->
Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_iq(#iq{lang = Lang} = IQ) ->
Txt = ?T("No module is handling this query"),
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
-spec process_iq_get(iq(), request()) -> iq().
process_iq_get(#iq{from = From, to = #jid{lserver = Host}, lang = Lang} = IQ,
Request) ->
Access = mod_stun_disco_opt:access(Host),
case acl:match_rule(Host, Access, From) of
allow ->
?DEBUG("Performing external service discovery for ~ts",
[jid:encode(From)]),
case get_services(Host, From, Request) of
{ok, Services} ->
xmpp:make_iq_result(IQ, #services{list = Services});
{error, timeout} -> % Has been logged already.
Txt = ?T("Service list retrieval timed out"),
Err = xmpp:err_internal_server_error(Txt, Lang),
xmpp:make_error(IQ, Err)
end;
deny ->
?DEBUG("Won't perform external service discovery for ~ts",
[jid:encode(From)]),
Txt = ?T("Access denied by service policy"),
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
end.
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
-spec get_configured_services(gen_mod:opts()) -> [service()].
get_configured_services(Opts) ->
LocalServices = case mod_stun_disco_opt:offer_local_services(Opts) of
true ->
?DEBUG("Discovering local services", []),
find_local_services();
false ->
?DEBUG("Won't discover local services", []),
[]
end,
dedup(LocalServices ++ mod_stun_disco_opt:services(Opts)).
-spec get_configured_secret(gen_mod:opts()) -> binary().
get_configured_secret(Opts) ->
case mod_stun_disco_opt:secret(Opts) of
undefined ->
?DEBUG("Auto-generating secret", []),
new_secret();
Secret ->
?DEBUG("Using configured secret", []),
Secret
end.
-spec get_configured_ttl(gen_mod:opts()) -> non_neg_integer().
get_configured_ttl(Opts) ->
mod_stun_disco_opt:credentials_lifetime(Opts) div 1000.
-spec new_secret() -> binary().
new_secret() ->
p1_rand:bytes(20).
-spec add_credentials(service(), binary(), binary(), non_neg_integer())
-> service().
add_credentials(Service, Hash, Secret, TTL) ->
ExpireAt = erlang:system_time(second) + TTL,
Username = make_username(ExpireAt, Hash),
Password = make_password(Username, Secret),
?DEBUG("Created ephemeral credentials: ~s | ~s", [Username, Password]),
Service#service{username = Username,
password = Password,
expires = seconds_to_timestamp(ExpireAt)}.
-spec make_username(non_neg_integer(), binary()) -> binary().
make_username(ExpireAt, Hash) ->
<<(integer_to_binary(ExpireAt))/binary, $:, Hash/binary>>.
-spec make_password(binary(), binary()) -> binary().
make_password(Username, Secret) ->
base64:encode(crypto:hmac(sha, Secret, Username)).
-spec get_password(binary(), binary()) -> binary().
get_password(Username, HostHash) ->
try call({hash, HostHash}, {get_password, Username}) of
{turn_disco, Password} ->
Password
catch
exit:{timeout, _} ->
?ERROR_MSG("Asking ~ts for password timed out", [HostHash]),
<<>>;
exit:{noproc, _} -> % Can be triggered by bogus Username.
?DEBUG("Cannot retrieve password for ~ts", [Username]),
<<>>
end.
-spec get_services(binary(), jid(), request())
-> {ok, [service()]} | {error, timeout}.
get_services(Host, JID, Request) ->
try call(Host, {get_services, JID, Request}) of
{turn_disco, Services} ->
{ok, Services}
catch
exit:{timeout, _} ->
?ERROR_MSG("Asking ~ts for services timed out", [Host]),
{error, timeout}
end.
-spec find_local_services() -> [service()].
find_local_services() ->
ParseListener = fun(Listener) -> parse_listener(Listener) end,
lists:flatmap(ParseListener, ejabberd_option:listen()).
-spec parse_listener(ejabberd_listener:listener()) -> [service()].
parse_listener({_EndPoint, ?STUN_MODULE, #{tls := true}}) ->
?DEBUG("Ignoring TLS-enabled STUN/TURN listener", []),
[]; % Avoid certificate hostname issues.
parse_listener({{Port, _Addr, Transport}, ?STUN_MODULE, Opts}) ->
case get_listener_ip(Opts) of
{127, _, _, _} = Addr ->
?INFO_MSG("Won't auto-announce STUN/TURN service with loopback "
"address: ~s:~B (~s), please specify a public 'turn_ip'",
[misc:ip_to_list(Addr), Port, Transport]),
[];
Addr ->
StunService = #service{host = Addr,
port = Port,
transport = Transport,
restricted = false,
type = stun},
case Opts of
#{use_turn := true} ->
?DEBUG("Found STUN/TURN listener: ~s:~B (~s)",
[misc:ip_to_list(Addr), Port, Transport]),
[StunService, #service{host = Addr,
port = Port,
transport = Transport,
restricted = is_restricted(Opts),
type = turn}];
#{use_turn := false} ->
?DEBUG("Found STUN listener: ~s:~B (~s)",
[misc:ip_to_list(Addr), Port, Transport]),
[StunService]
end
end;
parse_listener({_EndPoint, Module, _Opts}) ->
?DEBUG("Ignoring ~s listener", [Module]),
[].
-spec get_listener_ip(map()) -> inet:ip_address().
get_listener_ip(#{ip := { 0, 0, 0, 0}} = Opts) -> get_turn_ip(Opts);
get_listener_ip(#{ip := {127, _, _, _}} = Opts) -> get_turn_ip(Opts);
get_listener_ip(#{ip := { 10, _, _, _}} = Opts) -> get_turn_ip(Opts);
get_listener_ip(#{ip := {172, 16, _, _}} = Opts) -> get_turn_ip(Opts);
get_listener_ip(#{ip := {192, 168, _, _}} = Opts) -> get_turn_ip(Opts);
get_listener_ip(#{ip := IP}) -> IP.
-spec get_turn_ip(map()) -> inet:ip_address().
get_turn_ip(#{turn_ip := {_, _, _, _} = TurnIP}) -> TurnIP;
get_turn_ip(#{turn_ip := undefined}) -> misc:get_my_ip().
-spec is_restricted(map()) -> boolean().
is_restricted(#{auth_type := user}) -> true;
is_restricted(#{auth_type := anonymous}) -> false.
-spec call(host_or_hash(), term()) -> term().
call(Host, Request) ->
Proc = get_proc_name(Host),
gen_server:call(Proc, Request, timer:seconds(15)).
-spec cast(host_or_hash(), term()) -> ok.
cast(Host, Request) ->
Proc = get_proc_name(Host),
gen_server:cast(Proc, Request).
-spec get_proc_name(host_or_hash()) -> atom().
get_proc_name(Host) when is_binary(Host) ->
get_proc_name({hash, hash(Host)});
get_proc_name({hash, HostHash}) ->
gen_mod:get_module_proc(HostHash, ?MODULE).
-spec hash(binary()) -> binary().
hash(Host) ->
str:to_hexlist(binary_part(crypto:hash(sha, Host), 0, 4)).
-spec dedup(list()) -> list().
dedup([]) -> [];
dedup([H | T]) -> [H | [E || E <- dedup(T), E /= H]].
-spec seconds_to_timestamp(non_neg_integer()) -> erlang:timestamp().
seconds_to_timestamp(Seconds) ->
{Seconds div 1000000, Seconds rem 1000000, 0}.
+41
View File
@@ -0,0 +1,41 @@
%% Generated automatically
%% DO NOT EDIT: run `make options` instead
-module(mod_stun_disco_opt).
-export([access/1]).
-export([credentials_lifetime/1]).
-export([offer_local_services/1]).
-export([secret/1]).
-export([services/1]).
-spec access(gen_mod:opts() | global | binary()) -> 'local' | acl:acl().
access(Opts) when is_map(Opts) ->
gen_mod:get_opt(access, Opts);
access(Host) ->
gen_mod:get_module_opt(Host, mod_stun_disco, access).
-spec credentials_lifetime(gen_mod:opts() | global | binary()) -> pos_integer().
credentials_lifetime(Opts) when is_map(Opts) ->
gen_mod:get_opt(credentials_lifetime, Opts);
credentials_lifetime(Host) ->
gen_mod:get_module_opt(Host, mod_stun_disco, credentials_lifetime).
-spec offer_local_services(gen_mod:opts() | global | binary()) -> boolean().
offer_local_services(Opts) when is_map(Opts) ->
gen_mod:get_opt(offer_local_services, Opts);
offer_local_services(Host) ->
gen_mod:get_module_opt(Host, mod_stun_disco, offer_local_services).
-spec secret(gen_mod:opts() | global | binary()) -> 'undefined' | binary().
secret(Opts) when is_map(Opts) ->
gen_mod:get_opt(secret, Opts);
secret(Host) ->
gen_mod:get_module_opt(Host, mod_stun_disco, secret).
-spec services(gen_mod:opts() | global | binary()) -> [tuple()].
services(Opts) when is_map(Opts) ->
gen_mod:get_opt(services, Opts);
services(Host) ->
gen_mod:get_module_opt(Host, mod_stun_disco, services).
+4 -5
View File
@@ -896,14 +896,13 @@ select_affiliation_subscriptions(Nidx, JID, JID) ->
select_affiliation_subscriptions(Nidx, GenKey, SubKey) ->
GJ = encode_jid(GenKey),
SJ = encode_jid(SubKey),
case catch
ejabberd_sql:sql_query_t(
?SQL("select jid = %(GJ)s as @(G)d, @(affiliation)s, @(subscriptions)s from "
" pubsub_state where nodeid=%(Nidx)d and jid in (%(GJ)s, %(SJ)s)"))
case catch ejabberd_sql:sql_query_t(
?SQL("select jid = %(GJ)s as @(G)b, @(affiliation)s, @(subscriptions)s from "
" pubsub_state where nodeid=%(Nidx)d and jid in (%(GJ)s, %(SJ)s)"))
of
{selected, Res} ->
lists:foldr(
fun({1, A, S}, {_, Subs}) ->
fun({true, A, S}, {_, Subs}) ->
{decode_affiliation(A), Subs ++ decode_subscriptions(S)};
({_, _, S}, {Aff, Subs}) ->
{Aff, Subs ++ decode_subscriptions(S)}

Some files were not shown because too many files have changed in this diff Show More