Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 724d09a510 | |||
| fba6a64648 | |||
| 0539637d30 | |||
| 4a7d42647f | |||
| b56663ef07 | |||
| 25597a4326 | |||
| 56c8f6b280 | |||
| abc3260e75 | |||
| 24a11fc8e8 | |||
| 6eb2f07274 | |||
| 9cd47d7085 | |||
| 69d1d62add | |||
| 3db9459591 | |||
| 6320dfd34e | |||
| 055fe744d3 | |||
| d7c696f97c | |||
| fc444ce503 | |||
| 88f392721b | |||
| c55e7b8499 | |||
| 1f7ca91670 | |||
| d9131c854d | |||
| 09a87f5a0c | |||
| 1db70edcf8 | |||
| 1a3533e4a2 | |||
| 3db9de26e9 | |||
| 78f0439e78 | |||
| b124e911d3 | |||
| c836dc66a8 | |||
| b1b3c4cdcf | |||
| b0f95975c2 | |||
| 2e48c24638 | |||
| 7359eb6246 | |||
| 99d21bca49 | |||
| 1b98084918 | |||
| d311eaf8f3 | |||
| 0355e15a42 | |||
| 101f7a6d63 | |||
| 4aa85c538c | |||
| 22980ed8a5 | |||
| cb1c0a3188 | |||
| 0705695e02 | |||
| c11922e2a2 | |||
| 37226dd41f | |||
| cd0b65f4d5 | |||
| b7c088d4b0 | |||
| e197b25e82 | |||
| b02506eaaf | |||
| 8694517c34 | |||
| 2febd1c220 | |||
| aa0ed37034 | |||
| da18245d9a | |||
| 5cc9a1fe44 | |||
| de0aead1cd | |||
| 624ba7e94f | |||
| 9bb3aee0e2 | |||
| 16585713f8 | |||
| e01e528235 | |||
| eac7e3488c | |||
| 762486d199 | |||
| 23493ce239 | |||
| 510ab53341 | |||
| 220cf73318 | |||
| f6d102f5e2 | |||
| 05b68764cc | |||
| 4e51e82ccf | |||
| 116fa8e9ca | |||
| ce6fd654a0 | |||
| 5ee2f48aea | |||
| ccb47a67c4 | |||
| a2e6d8bb6b | |||
| 1bd560f3f2 | |||
| 222bb1d55d | |||
| a5ea3fa282 | |||
| 6c52438128 | |||
| 0508dce2ed | |||
| 87dda1b638 | |||
| 5ec214386e | |||
| 73ba38ae35 | |||
| 1ffa9a0cf5 | |||
| 633b362577 | |||
| 150b7e7219 | |||
| e6065bf08f | |||
| c2aa5f77bf | |||
| 7caec56e96 | |||
| 97354426cf | |||
| 63e3fb92d1 | |||
| a2d1ffffe6 | |||
| f17d4c0adc | |||
| 92a09fdb71 | |||
| 039d786e1f | |||
| 2d707cc0d2 | |||
| 0a88d03dc9 | |||
| f12ee28660 | |||
| 260c289d34 | |||
| d8899ca9ac | |||
| 1e456065f6 | |||
| 82074190fb | |||
| 6fe7c5cac5 | |||
| e9d1201ea8 | |||
| 9a89b360c0 | |||
| b39a1e2d74 | |||
| 151b818af4 | |||
| df47e2a93f | |||
| d0e93f9219 | |||
| 47c5aba1e5 | |||
| 00abf5d42c | |||
| 9c25d1024a | |||
| 248cc2d013 | |||
| 8eccbade56 |
+1
-1
@@ -2,7 +2,7 @@ language: erlang
|
||||
|
||||
otp_release:
|
||||
- 19.3
|
||||
- 22.2
|
||||
- 22.3
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"},
|
||||
}
|
||||
|
||||
@@ -243,7 +243,6 @@ p[dir=ltr] a {
|
||||
|
||||
background: #3eaffa;
|
||||
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75em;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
+18
-16
@@ -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
File diff suppressed because it is too large
Load Diff
+15
-25
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+15
-25
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 ''
|
||||
|
||||
@@ -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}},
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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"].
|
||||
|
||||
@@ -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
@@ -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
@@ -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 =>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
@@ -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
@@ -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").
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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) ->
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"]}]}.
|
||||
|
||||
@@ -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
@@ -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 =>
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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(_, _) ->
|
||||
[].
|
||||
|
||||
@@ -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
@@ -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>'")]}.
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
|
||||
@@ -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
@@ -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
@@ -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).
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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
@@ -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",
|
||||
" ..."]}
|
||||
]}.
|
||||
|
||||
@@ -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
@@ -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
@@ -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"))];
|
||||
|
||||
@@ -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
@@ -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"
|
||||
]}
|
||||
]}.
|
||||
|
||||
@@ -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]]}.
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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}.
|
||||
@@ -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).
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user