aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac6
-rw-r--r--ejabberd.yml.example1
-rw-r--r--mix.exs6
-rw-r--r--mix.lock22
-rw-r--r--priv/msgs/cs.po40
-rw-r--r--priv/msgs/de.po40
-rw-r--r--priv/msgs/el.po49
-rw-r--r--priv/msgs/eo.po20
-rw-r--r--priv/msgs/fr.po40
-rw-r--r--priv/msgs/gl.po40
-rw-r--r--priv/msgs/he.po37
-rw-r--r--priv/msgs/hu.po49
-rw-r--r--priv/msgs/id.po14
-rw-r--r--priv/msgs/it.po15
-rw-r--r--priv/msgs/ja.po25
-rw-r--r--priv/msgs/nl.po20
-rw-r--r--priv/msgs/no.po15
-rw-r--r--priv/msgs/pl.po37
-rw-r--r--priv/msgs/pt-br.po40
-rw-r--r--priv/msgs/pt.po8
-rw-r--r--priv/msgs/ru.po43
-rw-r--r--priv/msgs/sk.po15
-rw-r--r--priv/msgs/sv.po15
-rw-r--r--priv/msgs/th.po4
-rw-r--r--priv/msgs/tr.po13
-rw-r--r--priv/msgs/uk.po25
-rw-r--r--priv/msgs/vi.po12
-rw-r--r--priv/msgs/wa.po25
-rw-r--r--priv/msgs/zh.po35
-rw-r--r--rebar.config14
-rw-r--r--src/ejabberd_c2s.erl2
-rw-r--r--src/ejabberd_ctl.erl62
-rw-r--r--src/ejabberd_options_doc.erl4
-rw-r--r--src/ejabberd_sql.erl52
-rw-r--r--src/ejabberd_sql_sup.erl4
-rw-r--r--src/ejabberd_stun.erl63
-rw-r--r--src/gen_mod.erl4
-rw-r--r--src/misc.erl12
-rw-r--r--src/mod_adhoc.erl2
-rw-r--r--src/mod_admin_extra.erl20
-rw-r--r--src/mod_avatar.erl5
-rw-r--r--src/mod_caps.erl14
-rw-r--r--src/mod_http_fileserver.erl7
-rw-r--r--src/mod_muc_admin.erl16
-rw-r--r--src/mod_offline.erl4
-rw-r--r--src/mod_proxy65_service.erl10
-rw-r--r--src/mod_shared_roster.erl6
-rw-r--r--src/mod_stun_disco.erl670
-rw-r--r--src/mod_stun_disco_opt.erl41
-rw-r--r--test/README25
-rw-r--r--test/docker/README.md14
-rw-r--r--test/docker/db/mssql/initdb/initdb_mssql.sql23
-rw-r--r--test/docker/docker-compose.yml15
-rw-r--r--test/ejabberd_SUITE.erl34
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.cfg22
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.mssql.yml71
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml15
-rw-r--r--test/ejabberd_SUITE_data/macros.yml6
-rw-r--r--test/stundisco_tests.erl192
-rw-r--r--test/suite.erl45
-rw-r--r--test/suite.hrl3
61 files changed, 1553 insertions, 635 deletions
diff --git a/configure.ac b/configure.ac
index 52239313d..d87fcd46a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
@@ -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
diff --git a/ejabberd.yml.example b/ejabberd.yml.example
index eeba1f9c5..8f2a870cb 100644
--- a/ejabberd.yml.example
+++ b/ejabberd.yml.example
@@ -210,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:
diff --git a/mix.exs b/mix.exs
index 24ef640a5..cca7c9c50 100644
--- a/mix.exs
+++ b/mix.exs
@@ -85,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"},
diff --git a/mix.lock b/mix.lock
index 8c6614a8e..26225d521 100644
--- a/mix.lock
+++ b/mix.lock
@@ -5,34 +5,34 @@
"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.6", "6e57e1f5a330fa02a08ee0d4b16d9161f95177351e48c6dfede2f89b7e2f589f", [:rebar3], [], "hexpm"},
+ "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", "ab083aaa144c718d5f1c06b3034947cf6a9f73aedca04015813c983478ad94ed"},
+ "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.4", "a0320baf14be72fc9f99211543e411bb98077bf72c42e2d86fc4e2c10d60c258", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "027fc2a726d6f3ad36c7fa230780000e8d3fc72dc8b397c9bff2018d3f5ff06a"},
- "fast_xml": {:hex, :fast_xml, "1.1.39", "687080c0190a8c45d564a3576201f1a89f31ae413dd700a2def0821736f98d4d", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "2521816ed30b9c76befd85e477b5173c0329bd08fc8429f970b668c14bddb3f9"},
+ "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.1", "4f25639772ca41202f41ba9c8f6ca0933554283dd4742c90651e03471c55e341", [:rebar3], [], "hexpm", "2568927f6f5e383cb775f6fd87bc6e80ea5f7d813418de450aa644b02c7f7112"},
+ "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"},
- "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
+ "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.14", "ea2d58d0551d62fce9882f65fc7e0273ae6d683ab277aad657a661f514411198", [:rebar3], [], "hexpm", "70df0680b71118ace166e59fbf8be19058be3c3918bc3b7ede6e1f297a4a9dbe"},
+ "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"},
+ "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.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", "c7edd93d823c0e1438bfe70adf99c39cf4cbca73cbd3c086c6e461ad00c6f8d4"},
+ "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.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", "7bd21dd10ff3369f65128fcf730595073f1b6288a8b563d165874aecb2220317"},
+ "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"},
}
diff --git a/priv/msgs/cs.po b/priv/msgs/cs.po
index c28b0b65f..135e7e917 100644
--- a/priv/msgs/cs.po
+++ b/priv/msgs/cs.po
@@ -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
diff --git a/priv/msgs/de.po b/priv/msgs/de.po
index 2ad5360ce..3a066d636 100644
--- a/priv/msgs/de.po
+++ b/priv/msgs/de.po
@@ -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"
diff --git a/priv/msgs/el.po b/priv/msgs/el.po
index 38ee66032..aeca8ffd7 100644
--- a/priv/msgs/el.po
+++ b/priv/msgs/el.po
@@ -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"
diff --git a/priv/msgs/eo.po b/priv/msgs/eo.po
index 31146dadb..6c1ad6cf3 100644
--- a/priv/msgs/eo.po
+++ b/priv/msgs/eo.po
@@ -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
diff --git a/priv/msgs/fr.po b/priv/msgs/fr.po
index 8f616457d..fe36401f6 100644
--- a/priv/msgs/fr.po
+++ b/priv/msgs/fr.po
@@ -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"
diff --git a/priv/msgs/gl.po b/priv/msgs/gl.po
index cbb31aa58..7396e63a8 100644
--- a/priv/msgs/gl.po
+++ b/priv/msgs/gl.po
@@ -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"
diff --git a/priv/msgs/he.po b/priv/msgs/he.po
index 8292880ea..0bca2662c 100644
--- a/priv/msgs/he.po
+++ b/priv/msgs/he.po
@@ -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
diff --git a/priv/msgs/hu.po b/priv/msgs/hu.po
index 384f31560..2edf94056 100644
--- a/priv/msgs/hu.po
+++ b/priv/msgs/hu.po
@@ -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"
diff --git a/priv/msgs/id.po b/priv/msgs/id.po
index 8c9fd3538..45c9adcb7 100644
--- a/priv/msgs/id.po
+++ b/priv/msgs/id.po
@@ -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"
diff --git a/priv/msgs/it.po b/priv/msgs/it.po
index 99bdacca6..950a581aa 100644
--- a/priv/msgs/it.po
+++ b/priv/msgs/it.po
@@ -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
diff --git a/priv/msgs/ja.po b/priv/msgs/ja.po
index 8527f5d50..589ae91a1 100644
--- a/priv/msgs/ja.po
+++ b/priv/msgs/ja.po
@@ -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"
diff --git a/priv/msgs/nl.po b/priv/msgs/nl.po
index 4fa0ad0a4..cfeadd5ac 100644
--- a/priv/msgs/nl.po
+++ b/priv/msgs/nl.po
@@ -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"
diff --git a/priv/msgs/no.po b/priv/msgs/no.po
index e556e7f3b..5800da136 100644
--- a/priv/msgs/no.po
+++ b/priv/msgs/no.po
@@ -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
diff --git a/priv/msgs/pl.po b/priv/msgs/pl.po
index 777d21337..c03d28e88 100644
--- a/priv/msgs/pl.po
+++ b/priv/msgs/pl.po
@@ -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
diff --git a/priv/msgs/pt-br.po b/priv/msgs/pt-br.po
index ad56176fb..f14697889 100644
--- a/priv/msgs/pt-br.po
+++ b/priv/msgs/pt-br.po
@@ -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"
diff --git a/priv/msgs/pt.po b/priv/msgs/pt.po
index c7ae0686a..0b2be5579 100644
--- a/priv/msgs/pt.po
+++ b/priv/msgs/pt.po
@@ -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
diff --git a/priv/msgs/ru.po b/priv/msgs/ru.po
index 85ae802ba..fd0363f21 100644
--- a/priv/msgs/ru.po
+++ b/priv/msgs/ru.po
@@ -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"
diff --git a/priv/msgs/sk.po b/priv/msgs/sk.po
index 47c98a164..e832824a9 100644
--- a/priv/msgs/sk.po
+++ b/priv/msgs/sk.po
@@ -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
diff --git a/priv/msgs/sv.po b/priv/msgs/sv.po
index adb38b569..d2be39e66 100644
--- a/priv/msgs/sv.po
+++ b/priv/msgs/sv.po
@@ -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"
diff --git a/priv/msgs/th.po b/priv/msgs/th.po
index 78ea0984f..3e7cccd54 100644
--- a/priv/msgs/th.po
+++ b/priv/msgs/th.po
@@ -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"
diff --git a/priv/msgs/tr.po b/priv/msgs/tr.po
index af916bbd0..679fb8524 100644
--- a/priv/msgs/tr.po
+++ b/priv/msgs/tr.po
@@ -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."
diff --git a/priv/msgs/uk.po b/priv/msgs/uk.po
index 16f57a358..07702761a 100644
--- a/priv/msgs/uk.po
+++ b/priv/msgs/uk.po
@@ -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
diff --git a/priv/msgs/vi.po b/priv/msgs/vi.po
index 3382fde35..4c3443140 100644
--- a/priv/msgs/vi.po
+++ b/priv/msgs/vi.po
@@ -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
diff --git a/priv/msgs/wa.po b/priv/msgs/wa.po
index c931716b0..e29afed32 100644
--- a/priv/msgs/wa.po
+++ b/priv/msgs/wa.po
@@ -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
diff --git a/priv/msgs/zh.po b/priv/msgs/zh.po
index cabb215b5..34cd68e03 100644
--- a/priv/msgs/zh.po
+++ b/priv/msgs/zh.po
@@ -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
diff --git a/rebar.config b/rebar.config
index 778716299..c1d3d9e6c 100644
--- a/rebar.config
+++ b/rebar.config
@@ -21,14 +21,14 @@
{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", "c23e66ebac8fdec4aa08c8926091b0dcf6dacf22"}},
+ {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.1"}}},
+ {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"}}},
@@ -36,10 +36,10 @@
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.7"}}},
{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",
- "604f7b339e845c2fb4219960a19fc5c048f16c0b"}}},
+ {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",
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 9e0274cf8..36a6af195 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -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).
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index 0a394b272..532c6eb7a 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -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) ->
@@ -134,29 +113,9 @@ 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).
@@ -509,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
%%-----------------------------
@@ -541,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"), "] ",
@@ -874,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"].
diff --git a/src/ejabberd_options_doc.erl b/src/ejabberd_options_doc.erl
index 50bccf7a0..20928eae7 100644
--- a/src/ejabberd_options_doc.erl
+++ b/src/ejabberd_options_doc.erl
@@ -485,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 =>
@@ -827,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 "
diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl
index ddcca0459..8b952f4e0 100644
--- a/src/ejabberd_sql.erl
+++ b/src/ejabberd_sql.erl
@@ -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]).
@@ -1109,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
@@ -1152,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",
@@ -1214,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").
diff --git a/src/ejabberd_sql_sup.erl b/src/ejabberd_sql_sup.erl
index 026c3fab6..ee37c7e61 100644
--- a/src/ejabberd_sql_sup.erl
+++ b/src/ejabberd_sql_sup.erl
@@ -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,
diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl
index 347598e6d..92552cc8a 100644
--- a/src/ejabberd_stun.erl
+++ b/src/ejabberd_stun.erl
@@ -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) ->
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index d424f4b5c..fbcc6b8bc 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -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(),
diff --git a/src/misc.erl b/src/misc.erl
index 8476e7cbf..e589c2e22 100644
--- a/src/misc.erl
+++ b/src/misc.erl
@@ -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.
diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl
index 68af99630..040890fcf 100644
--- a/src/mod_adhoc.erl
+++ b/src/mod_adhoc.erl
@@ -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'.")}}]}.
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
index 1dfe42c11..69d240e4a 100644
--- a/src/mod_admin_extra.erl
+++ b/src/mod_admin_extra.erl
@@ -1604,13 +1604,7 @@ mod_doc() ->
"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'"),
+ "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: "
@@ -1620,8 +1614,7 @@ mod_doc() ->
?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'")],
+ "ends. See an example below.")],
opts =>
[{module_resource,
#{value => ?T("Resource"),
@@ -1647,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"]}]}.
diff --git a/src/mod_avatar.erl b/src/mod_avatar.erl
index 10c7e619c..a6c56f968 100644
--- a/src/mod_avatar.erl
+++ b/src/mod_avatar.erl
@@ -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 =>
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index 5e8cc2eda..0b8f034cc 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -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;
diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl
index 287fce624..f1639569c 100644
--- a/src/mod_http_fileserver.erl
+++ b/src/mod_http_fileserver.erl
@@ -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 =>
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index 8e3d483b2..f9708ef59 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -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) ->
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 7c4f8f7e4..1b859b993 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -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 "
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl
index fe07ca72a..5dcb79944 100644
--- a/src/mod_proxy65_service.erl
+++ b/src/mod_proxy65_service.erl
@@ -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).
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index 664dda699..356857d74 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -997,7 +997,7 @@ shared_roster_group(Host, Group, Query, Lang) ->
[?XCT(<<"td">>, ?T("Label:")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"label">>, Label)]),
- ?XE(<<"td">>, [?C(<<"Name in the rosters where this group will be displayed">>)])]),
+ ?XE(<<"td">>, [?CT(?T("Name in the rosters where this group will be displayed"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Description:")),
?XE(<<"td">>,
@@ -1005,7 +1005,7 @@ shared_roster_group(Host, Group, Query, Lang) ->
integer_to_binary(lists:max([3,
DescNL])),
<<"20">>, Description)]),
- ?XE(<<"td">>, [?C(<<"Only admins can see this">>)])
+ ?XE(<<"td">>, [?CT(?T("Only admins can see this"))])
]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Members:")),
@@ -1023,7 +1023,7 @@ shared_roster_group(Host, Group, Query, Lang) ->
integer_to_binary(lists:max([3, length(FDisplayedGroups)])),
<<"20">>,
list_to_binary(FDisplayedGroups))]),
- ?XE(<<"td">>, [?C(<<"Groups that will be displayed to the members">>)])
+ ?XE(<<"td">>, [?CT(?T("Groups that will be displayed to the members"))])
])])])),
(?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))),
<<"modules/#mod-shared-roster">>, <<"mod_shared_roster">>))
diff --git a/src/mod_stun_disco.erl b/src/mod_stun_disco.erl
new file mode 100644
index 000000000..52ced9b28
--- /dev/null
+++ b/src/mod_stun_disco.erl
@@ -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}.
diff --git a/src/mod_stun_disco_opt.erl b/src/mod_stun_disco_opt.erl
new file mode 100644
index 000000000..43b8102e6
--- /dev/null
+++ b/src/mod_stun_disco_opt.erl
@@ -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).
+
diff --git a/test/README b/test/README
index de1a96aa2..68ff183dc 100644
--- a/test/README
+++ b/test/README
@@ -1,9 +1,12 @@
-You need MySQL, PostgreSQL and Redis up and running.
+You need MySQL, MSSQL, PostgreSQL and Redis up and running.
MySQL should be accepting TCP connections on localhost:3306.
+MSSQL should be accepting TCP connections on localhost:1433.
PostgreSQL should be accepting TCP connections on localhost:5432.
Redis should be accepting TCP connections on localhost:6379.
MySQL and PostgreSQL should grant full access to user 'ejabberd_test' with
password 'ejabberd_test' on database 'ejabberd_test'.
+MSSQL should grant full access to user 'ejabberd_test' with
+password 'ejabberd_Test1' on database 'ejabberd_test'.
Here is a quick setup example:
@@ -24,3 +27,23 @@ mysql> CREATE USER 'ejabberd_test'@'localhost' IDENTIFIED BY 'ejabberd_test';
mysql> CREATE DATABASE ejabberd_test;
mysql> GRANT ALL ON ejabberd_test.* TO 'ejabberd_test'@'localhost';
$ mysql ejabberd_test < sql/mysql.sql
+
+-------------------
+ MS SQL Server
+-------------------
+$ sqlcmd -U SA -P ejabberd_Test1 -S localhost
+1> CREATE DATABASE ejabberd_test;
+2> GO
+1> USE ejabberd_test;
+2> GO
+Changed database context to 'ejabberd_test'.
+1> CREATE LOGIN ejabberd_test WITH PASSWORD = 'ejabberd_Test1';
+2> GO
+1> CREATE USER ejabberd_test FOR LOGIN ejabberd_test;
+2> GO
+1> GRANT ALL TO ejabberd_test;
+2> GO
+The ALL permission is deprecated and maintained only for compatibility. It DOES NOT imply ALL permissions defined on the entity.
+1> GRANT CONTROL ON SCHEMA ::dbo TO ejabberd_test;
+2> GO
+$ sqlcmd -U ejabberd_test -P ejabberd_Test1 -S localhost -d ejabberd_test -i sql/mssql.sql
diff --git a/test/docker/README.md b/test/docker/README.md
index 22324a6e0..24c675cab 100644
--- a/test/docker/README.md
+++ b/test/docker/README.md
@@ -4,7 +4,7 @@
You can start the Docker environment with Docker Compose, from ejabberd repository root.
-The following command will launch MySQL, PostgreSQL, Redis and keep the console
+The following command will launch MySQL, MSSQL, PostgreSQL, Redis and keep the console
attached to it.
```
@@ -15,6 +15,17 @@ mkdir test/docker/db/postgres/data
You can stop all the databases with CTRL-C.
+## Creating database for MSSQL
+
+The following commands will create the necessary login, user and database, will grant rights on the database in MSSQL and create the ejabberd schema:
+
+```
+docker cp test/docker/db/mssql/initdb/initdb_mssql.sql ejabberd-mssql:/
+docker exec ejabberd-mssql /opt/mssql-tools/bin/sqlcmd -U SA -P ejabberd_Test1 -S localhost -i /initdb_mssql.sql
+docker cp sql/mssql.sql ejabberd-mssql:/
+docker exec ejabberd-mssql /opt/mssql-tools/bin/sqlcmd -U SA -P ejabberd_Test1 -S localhost -i /mssql.sql
+```
+
## Running tests
Before running the test, you can ensure there is no running instance of Erlang common test tool. You can run the following
@@ -43,4 +54,5 @@ If you want to clean the data, you can remove the data directories after the `do
```
rm -rf test/docker/db/mysql/data
rm -rf test/docker/db/postgres/data
+docker volume rm docker_mssqldata
```
diff --git a/test/docker/db/mssql/initdb/initdb_mssql.sql b/test/docker/db/mssql/initdb/initdb_mssql.sql
new file mode 100644
index 000000000..a9ec5a5a8
--- /dev/null
+++ b/test/docker/db/mssql/initdb/initdb_mssql.sql
@@ -0,0 +1,23 @@
+USE [master]
+GO
+
+IF DB_ID('ejabberd_test') IS NOT NULL
+ set noexec on -- prevent creation when already exists
+
+CREATE DATABASE ejabberd_test;
+GO
+
+USE ejabberd_test;
+GO
+
+CREATE LOGIN ejabberd_test WITH PASSWORD = 'ejabberd_Test1';
+GO
+
+CREATE USER ejabberd_test FOR LOGIN ejabberd_test;
+GO
+
+GRANT ALL TO ejabberd_test;
+GO
+
+GRANT CONTROL ON SCHEMA ::dbo TO ejabberd_test;
+GO
diff --git a/test/docker/docker-compose.yml b/test/docker/docker-compose.yml
index f98612036..c6b800dad 100644
--- a/test/docker/docker-compose.yml
+++ b/test/docker/docker-compose.yml
@@ -17,6 +17,18 @@ services:
MYSQL_USER: ejabberd_test
MYSQL_PASSWORD: ejabberd_test
+ mssql:
+ image: mcr.microsoft.com/mssql/server
+ container_name: ejabberd-mssql
+ volumes:
+ - mssqldata:/var/opt/mssql
+ restart: always
+ ports:
+ - 1433:1433
+ environment:
+ ACCEPT_EULA: Y
+ SA_PASSWORD: ejabberd_Test1
+
postgres:
image: postgres:latest
container_name: ejabberd-postgres
@@ -35,3 +47,6 @@ services:
container_name: ejabberd-redis
ports:
- 6379:6379
+
+volumes:
+ mssqldata:
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
index 6658f6734..63cf864f6 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -77,12 +77,12 @@ init_per_group(Group, Config) ->
do_init_per_group(Group, Config);
Backends ->
%% Skipped backends that were not explicitely enabled
- case lists:member(Group, Backends) of
- true ->
- do_init_per_group(Group, Config);
- false ->
- {skip, {disabled_backend, Group}}
- end
+ case lists:member(Group, Backends) of
+ true ->
+ do_init_per_group(Group, Config);
+ false ->
+ {skip, {disabled_backend, Group}}
+ end
end
end.
@@ -104,6 +104,15 @@ do_init_per_group(mysql, Config) ->
Err ->
{skip, {mysql_not_available, Err}}
end;
+do_init_per_group(mssql, Config) ->
+ case catch ejabberd_sql:sql_query(?MSSQL_VHOST, [<<"select 1;">>]) of
+ {selected, _, _} ->
+ mod_muc:shutdown_rooms(?MSSQL_VHOST),
+ clear_sql_tables(mssql, ?config(base_dir, Config)),
+ set_opt(server, ?MSSQL_VHOST, Config);
+ Err ->
+ {skip, {mssql_not_available, Err}}
+ end;
do_init_per_group(pgsql, Config) ->
case catch ejabberd_sql:sql_query(?PGSQL_VHOST, [<<"select 1;">>]) of
{selected, _, _} ->
@@ -158,6 +167,8 @@ end_per_group(redis, _Config) ->
ok;
end_per_group(mysql, _Config) ->
ok;
+end_per_group(mssql, _Config) ->
+ ok;
end_per_group(pgsql, _Config) ->
ok;
end_per_group(sqlite, _Config) ->
@@ -361,6 +372,7 @@ no_db_tests() ->
muc_tests:master_slave_cases(),
proxy65_tests:single_cases(),
proxy65_tests:master_slave_cases(),
+ stundisco_tests:single_cases(),
replaced_tests:master_slave_cases(),
upload_tests:single_cases(),
carbons_tests:single_cases(),
@@ -485,6 +497,7 @@ groups() ->
{mnesia, [sequence], db_tests(mnesia)},
{redis, [sequence], db_tests(redis)},
{mysql, [sequence], db_tests(mysql)},
+ {mssql, [sequence], db_tests(mssql)},
{pgsql, [sequence], db_tests(pgsql)},
{sqlite, [sequence], db_tests(sqlite)}].
@@ -494,6 +507,7 @@ all() ->
{group, mnesia},
{group, redis},
{group, mysql},
+ {group, mssql},
{group, pgsql},
{group, sqlite},
{group, extauth},
@@ -1012,6 +1026,14 @@ clear_sql_tables(Type, BaseDir) ->
"mysql.sql"
end,
{?MYSQL_VHOST, Path};
+ mssql ->
+ Path = case ejabberd_sql:use_new_schema() of
+ true ->
+ "mssql.new.sql";
+ false ->
+ "mssql.sql"
+ end,
+ {?MSSQL_VHOST, Path};
pgsql ->
Path = case ejabberd_sql:use_new_schema() of
true ->
diff --git a/test/ejabberd_SUITE_data/ejabberd.cfg b/test/ejabberd_SUITE_data/ejabberd.cfg
index a92608b60..f071ffe8b 100644
--- a/test/ejabberd_SUITE_data/ejabberd.cfg
+++ b/test/ejabberd_SUITE_data/ejabberd.cfg
@@ -2,6 +2,7 @@
{hosts, ["localhost",
"mnesia.localhost",
"mysql.localhost",
+ "mssql.localhost",
"pgsql.localhost",
"sqlite.localhost",
"extauth.localhost",
@@ -102,6 +103,27 @@
{mod_roster, [{db_type, odbc}]},
{mod_vcard, [{db_type, odbc}]}]}
]}.
+{host_config, "mssql.localhost",
+ [{auth_method, odbc},
+ {odbc_pool_size, 1},
+ {odbc_server, {mssql, "localhost", "ejabberd_test",
+ "ejabberd_test", "ejabberd_Test1"}},
+ {{add, modules}, [{mod_announce, [{db_type, odbc}]},
+ {mod_blocking, [{db_type, odbc}]},
+ {mod_caps, [{db_type, odbc}]},
+ {mod_last, [{db_type, odbc}]},
+ {mod_muc, [{db_type, odbc}]},
+ {mod_offline, [{db_type, odbc}]},
+ {mod_privacy, [{db_type, odbc}]},
+ {mod_private, [{db_type, odbc}]},
+ {mod_pubsub, [{db_type, odbc},
+ {access_createnode, pubsub_createnode},
+ {ignore_pep_from_offline, true},
+ {last_item_cache, false},
+ {plugins, ["flat", "hometree", "pep"]}]},
+ {mod_roster, [{db_type, odbc}]},
+ {mod_vcard, [{db_type, odbc}]}]}
+ ]}.
{host_config, "pgsql.localhost",
[{auth_method, odbc},
{odbc_pool_size, 1},
diff --git a/test/ejabberd_SUITE_data/ejabberd.mssql.yml b/test/ejabberd_SUITE_data/ejabberd.mssql.yml
new file mode 100644
index 000000000..1ecf77ba8
--- /dev/null
+++ b/test/ejabberd_SUITE_data/ejabberd.mssql.yml
@@ -0,0 +1,71 @@
+define_macro:
+ MSSQL_CONFIG:
+ sql_username: MSSQL_USER
+ sql_type: mssql
+ sql_server: MSSQL_SERVER
+ sql_port: MSSQL_PORT
+ sql_pool_size: 1
+ sql_password: MSSQL_PASS
+ sql_database: MSSQL_DB
+ auth_method: sql
+ sm_db_type: sql
+ modules:
+ mod_announce:
+ db_type: sql
+ access: local
+ mod_blocking: []
+ mod_caps:
+ db_type: sql
+ mod_last:
+ db_type: sql
+ mod_muc:
+ db_type: sql
+ ram_db_type: sql
+ vcard: VCARD
+ mod_offline:
+ use_cache: true
+ db_type: sql
+ mod_privacy:
+ db_type: sql
+ mod_private:
+ db_type: sql
+ mod_pubsub:
+ db_type: sql
+ access_createnode: pubsub_createnode
+ ignore_pep_from_offline: true
+ last_item_cache: false
+ plugins:
+ - "flat"
+ - "pep"
+ vcard: VCARD
+ mod_roster:
+ versioning: true
+ store_current_id: true
+ db_type: sql
+ mod_mam:
+ db_type: sql
+ mod_vcard:
+ db_type: sql
+ vcard: VCARD
+ mod_vcard_xupdate: []
+ mod_adhoc: []
+ mod_configure: []
+ mod_disco: []
+ mod_ping: []
+ mod_proxy65: []
+ mod_push:
+ db_type: sql
+ include_body: false
+ mod_push_keepalive: []
+ mod_s2s_dialback: []
+ mod_stream_mgmt:
+ resume_timeout: 3
+ mod_legacy_auth: []
+ mod_register:
+ welcome_message:
+ subject: "Welcome!"
+ body: "Hi.
+Welcome to this XMPP server."
+ mod_stats: []
+ mod_time: []
+ mod_version: []
diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
index 922325609..8ae915e0b 100644
--- a/test/ejabberd_SUITE_data/ejabberd.yml
+++ b/test/ejabberd_SUITE_data/ejabberd.yml
@@ -4,6 +4,7 @@ include_config_file:
- ejabberd.ldap.yml
- ejabberd.mnesia.yml
- ejabberd.mysql.yml
+ - ejabberd.mssql.yml
- ejabberd.pgsql.yml
- ejabberd.redis.yml
- ejabberd.sqlite.yml
@@ -12,6 +13,7 @@ host_config:
pgsql.localhost: PGSQL_CONFIG
sqlite.localhost: SQLITE_CONFIG
mysql.localhost: MYSQL_CONFIG
+ mssql.localhost: MSSQL_CONFIG
mnesia.localhost: MNESIA_CONFIG
redis.localhost: REDIS_CONFIG
ldap.localhost: LDAP_CONFIG
@@ -26,6 +28,7 @@ hosts:
- mnesia.localhost
- redis.localhost
- mysql.localhost
+ - mssql.localhost
- pgsql.localhost
- extauth.localhost
- ldap.localhost
@@ -91,6 +94,12 @@ listen:
"/upload": mod_http_upload
"/captcha": ejabberd_captcha
-
+ port: STUN_PORT
+ module: ejabberd_stun
+ transport: udp
+ use_turn: true
+ turn_ip: "203.0.113.3"
+ -
port: COMPONENT_PORT
module: ejabberd_service
password: PASSWORD
@@ -124,6 +133,12 @@ Welcome to this XMPP server."
mod_stream_mgmt:
max_ack_queue: 10
resume_timeout: 3
+ mod_stun_disco:
+ secret: "cryptic"
+ services:
+ -
+ host: "example.com"
+ type: turns
mod_time: []
mod_version: []
mod_http_upload:
diff --git a/test/ejabberd_SUITE_data/macros.yml b/test/ejabberd_SUITE_data/macros.yml
index fdd467584..e4270d4c1 100644
--- a/test/ejabberd_SUITE_data/macros.yml
+++ b/test/ejabberd_SUITE_data/macros.yml
@@ -4,6 +4,7 @@ define_macro:
C2S_PORT: @@c2s_port@@
S2S_PORT: @@s2s_port@@
WEB_PORT: @@web_port@@
+ STUN_PORT: @@stun_port@@
COMPONENT_PORT: @@component_port@@
PROXY_PORT: @@proxy_port@@
PASSWORD: >-
@@ -18,6 +19,11 @@ define_macro:
MYSQL_PORT: @@mysql_port@@
MYSQL_PASS: "@@mysql_pass@@"
MYSQL_DB: "@@mysql_db@@"
+ MSSQL_USER: "@@mssql_user@@"
+ MSSQL_SERVER: "@@mssql_server@@"
+ MSSQL_PORT: @@mssql_port@@
+ MSSQL_PASS: "@@mssql_pass@@"
+ MSSQL_DB: "@@mssql_db@@"
PGSQL_USER: "@@pgsql_user@@"
PGSQL_SERVER: "@@pgsql_server@@"
PGSQL_PORT: @@pgsql_port@@
diff --git a/test/stundisco_tests.erl b/test/stundisco_tests.erl
new file mode 100644
index 000000000..8cb026dc0
--- /dev/null
+++ b/test/stundisco_tests.erl
@@ -0,0 +1,192 @@
+%%%-------------------------------------------------------------------
+%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
+%%% Created : 22 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(stundisco_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [send_recv/2, disconnect/1, is_feature_advertised/2,
+ server_jid/1]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+ {stundisco_single, [sequence],
+ [single_test(feature_enabled),
+ single_test(stun_service),
+ single_test(turn_service),
+ single_test(turns_service),
+ single_test(turn_credentials),
+ single_test(turns_credentials)]}.
+
+feature_enabled(Config) ->
+ true = is_feature_advertised(Config, ?NS_EXTDISCO_2),
+ disconnect(Config).
+
+stun_service(Config) ->
+ ServerJID = server_jid(Config),
+ Host = {203, 0, 113, 3},
+ Port = ct:get_config(stun_port, 3478),
+ Type = stun,
+ Transport = udp,
+ Request = #services{type = Type},
+ #iq{type = result,
+ sub_els = [#services{
+ type = undefined,
+ list = [#service{host = Host,
+ port = Port,
+ type = Type,
+ transport = Transport,
+ restricted = false,
+ username = <<>>,
+ password = <<>>,
+ expires = undefined,
+ action = undefined,
+ xdata = undefined}]}]} =
+ send_recv(Config, #iq{type = get, to = ServerJID, sub_els = [Request]}),
+ disconnect(Config).
+
+turn_service(Config) ->
+ ServerJID = server_jid(Config),
+ Host = {203, 0, 113, 3},
+ Port = ct:get_config(stun_port, 3478),
+ Type = turn,
+ Transport = udp,
+ Request = #services{type = Type},
+ #iq{type = result,
+ sub_els = [#services{
+ type = undefined,
+ list = [#service{host = Host,
+ port = Port,
+ type = Type,
+ transport = Transport,
+ restricted = true,
+ username = Username,
+ password = Password,
+ expires = Expires,
+ action = undefined,
+ xdata = undefined}]}]} =
+ send_recv(Config, #iq{type = get, to = ServerJID, sub_els = [Request]}),
+ true = check_password(Username, Password),
+ true = check_expires(Expires),
+ disconnect(Config).
+
+turns_service(Config) ->
+ ServerJID = server_jid(Config),
+ Host = <<"example.com">>,
+ Port = 5349,
+ Type = turns,
+ Transport = tcp,
+ Request = #services{type = Type},
+ #iq{type = result,
+ sub_els = [#services{
+ type = undefined,
+ list = [#service{host = Host,
+ port = Port,
+ type = Type,
+ transport = Transport,
+ restricted = true,
+ username = Username,
+ password = Password,
+ expires = Expires,
+ action = undefined,
+ xdata = undefined}]}]} =
+ send_recv(Config, #iq{type = get, to = ServerJID, sub_els = [Request]}),
+ true = check_password(Username, Password),
+ true = check_expires(Expires),
+ disconnect(Config).
+
+turn_credentials(Config) ->
+ ServerJID = server_jid(Config),
+ Host = {203, 0, 113, 3},
+ Port = ct:get_config(stun_port, 3478),
+ Type = turn,
+ Transport = udp,
+ Request = #credentials{services = [#service{host = Host,
+ port = Port,
+ type = Type}]},
+ #iq{type = result,
+ sub_els = [#services{
+ type = undefined,
+ list = [#service{host = Host,
+ port = Port,
+ type = Type,
+ transport = Transport,
+ restricted = true,
+ username = Username,
+ password = Password,
+ expires = Expires,
+ action = undefined,
+ xdata = undefined}]}]} =
+ send_recv(Config, #iq{type = get, to = ServerJID, sub_els = [Request]}),
+ true = check_password(Username, Password),
+ true = check_expires(Expires),
+ disconnect(Config).
+
+turns_credentials(Config) ->
+ ServerJID = server_jid(Config),
+ Host = <<"example.com">>,
+ Port = 5349,
+ Type = turns,
+ Transport = tcp,
+ Request = #credentials{services = [#service{host = Host,
+ port = Port,
+ type = Type}]},
+ #iq{type = result,
+ sub_els = [#services{
+ type = undefined,
+ list = [#service{host = Host,
+ port = Port,
+ type = Type,
+ transport = Transport,
+ restricted = true,
+ username = Username,
+ password = Password,
+ expires = Expires,
+ action = undefined,
+ xdata = undefined}]}]} =
+ send_recv(Config, #iq{type = get, to = ServerJID, sub_els = [Request]}),
+ true = check_password(Username, Password),
+ true = check_expires(Expires),
+ disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+ list_to_atom("stundisco_" ++ atom_to_list(T)).
+
+check_password(Username, Password) ->
+ Secret = <<"cryptic">>,
+ Password == base64:encode(crypto:hmac(sha, Secret, Username)).
+
+check_expires({_, _, _} = Expires) ->
+ Now = {MegaSecs, Secs, MicroSecs} = erlang:timestamp(),
+ Later = {MegaSecs + 1, Secs, MicroSecs},
+ (Expires > Now) and (Expires < Later).
diff --git a/test/suite.erl b/test/suite.erl
index 78732d00b..883d3a3eb 100644
--- a/test/suite.erl
+++ b/test/suite.erl
@@ -60,6 +60,7 @@ init_config(Config) ->
{loglevel, 4},
{new_schema, false},
{s2s_port, 5269},
+ {stun_port, 3478},
{component_port, 5270},
{web_port, 5280},
{proxy_port, 7777},
@@ -69,6 +70,11 @@ init_config(Config) ->
{mysql_db, <<"ejabberd_test">>},
{mysql_user, <<"ejabberd_test">>},
{mysql_pass, <<"ejabberd_test">>},
+ {mssql_server, <<"localhost">>},
+ {mssql_port, 1433},
+ {mssql_db, <<"ejabberd_test">>},
+ {mssql_user, <<"ejabberd_test">>},
+ {mssql_pass, <<"ejabberd_Test1">>},
{pgsql_server, <<"localhost">>},
{pgsql_port, 5432},
{pgsql_db, <<"ejabberd_test">>},
@@ -132,25 +138,26 @@ init_config(Config) ->
copy_backend_configs(DataDir, CWD, Backends) ->
Files = filelib:wildcard(filename:join([DataDir, "ejabberd.*.yml"])),
lists:foreach(
- fun(Src) ->
- File = filename:basename(Src),
- case string:tokens(File, ".") of
- ["ejabberd", SBackend, "yml"] ->
- Backend = list_to_atom(SBackend),
- Macro = list_to_atom(string:to_upper(SBackend) ++ "_CONFIG"),
- Dst = filename:join([CWD, File]),
- case lists:member(Backend, Backends) of
- true ->
- {ok, _} = file:copy(Src, Dst);
- false ->
- ok = file:write_file(
- Dst, fast_yaml:encode(
- [{define_macro, [{Macro, []}]}]))
- end;
- _ ->
- ok
- end
- end, Files).
+ fun(Src) ->
+ io:format("copying ~p", [Src]),
+ File = filename:basename(Src),
+ case string:tokens(File, ".") of
+ ["ejabberd", SBackend, "yml"] ->
+ Backend = list_to_atom(SBackend),
+ Macro = list_to_atom(string:to_upper(SBackend) ++ "_CONFIG"),
+ Dst = filename:join([CWD, File]),
+ case lists:member(Backend, Backends) of
+ true ->
+ {ok, _} = file:copy(Src, Dst);
+ false ->
+ ok = file:write_file(
+ Dst, fast_yaml:encode(
+ [{define_macro, [{Macro, []}]}]))
+ end;
+ _ ->
+ ok
+ end
+ end, Files).
find_top_dir(Dir) ->
case file:read_file_info(filename:join([Dir, ebin])) of
diff --git a/test/suite.hrl b/test/suite.hrl
index 194c48eb5..b3bab6c12 100644
--- a/test/suite.hrl
+++ b/test/suite.hrl
@@ -92,6 +92,7 @@
-define(MNESIA_VHOST, <<"mnesia.localhost">>).
-define(REDIS_VHOST, <<"redis.localhost">>).
-define(MYSQL_VHOST, <<"mysql.localhost">>).
+-define(MSSQL_VHOST, <<"mssql.localhost">>).
-define(PGSQL_VHOST, <<"pgsql.localhost">>).
-define(SQLITE_VHOST, <<"sqlite.localhost">>).
-define(LDAP_VHOST, <<"ldap.localhost">>).
@@ -99,7 +100,7 @@
-define(S2S_VHOST, <<"s2s.localhost">>).
-define(UPLOAD_VHOST, <<"upload.localhost">>).
--define(BACKENDS, [mnesia, redis, mysql, pgsql, sqlite, ldap, extauth]).
+-define(BACKENDS, [mnesia, redis, mysql, mssql, odbc, pgsql, sqlite, ldap, extauth]).
insert(Val, N, Tuple) ->
L = tuple_to_list(Tuple),