# HG changeset patch
# User Matthew Wild
# Date 1527713709 -3600
# Node ID 7ec098b68042f60687f1002e788b34b06048945d
# Parent 463505cc75d584c5a0b0a735a36d60ec4b18a437# Parent c6b45cac942333b616157095d457cebe61bd8c34
Merge 0.9->0.10
diff -r c6b45cac9423 -r 7ec098b68042 .hgignore
--- a/.hgignore Wed May 30 21:51:15 2018 +0100
+++ b/.hgignore Wed May 30 21:55:09 2018 +0100
@@ -1,5 +1,6 @@
syntax: glob
.hgignore
+.luacheckcache
data
local
www_files
diff -r c6b45cac9423 -r 7ec098b68042 .hgtags
--- a/.hgtags Wed May 30 21:51:15 2018 +0100
+++ b/.hgtags Wed May 30 21:55:09 2018 +0100
@@ -60,5 +60,7 @@
352270bc04393910a567b569ede03358dbb728b5 0.9.10
8613086779fa9276615c2af066d2a10c38d0c86e 0.9.11
2a7b52437167a5c7b6c8a5bc79f4463afe092fd5 0.9.12
+39966cbc29f46d7ae9660edca8683d5121c82edf 0.10.0
082d127286451eb55420c36424dd321e8d9bceee 0.9.13
+4ae8dd415e9431924ad4aa0b57bcee8a4a9272f8 0.10.1
29c6d2681bad9f67d8bd548bb3a7973473bae91e 0.9.14
diff -r c6b45cac9423 -r 7ec098b68042 .luacheckrc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.luacheckrc Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,104 @@
+cache = true
+read_globals = { "prosody", "hosts", "import" }
+globals = { "_M" }
+allow_defined_top = true
+module = true
+unused_secondaries = false
+codes = true
+ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV", "431/log" }
+
+max_line_length = 150
+
+files["core/"] = {
+ read_globals = { "prosody", "hosts" };
+ globals = { "prosody.hosts.?", "hosts.?" };
+}
+files["plugins/"] = {
+ read_globals = {
+ -- Module instance
+ "module.name",
+ "module.host",
+ "module._log",
+ "module.log",
+ "module.event_handlers",
+ "module.reloading",
+ "module.saved_state",
+ "module.global",
+ "module.path",
+
+ -- Module API
+ "module.add_extension",
+ "module.add_feature",
+ "module.add_identity",
+ "module.add_item",
+ "module.add_timer",
+ "module.broadcast",
+ "module.context",
+ "module.depends",
+ "module.fire_event",
+ "module.get_directory",
+ "module.get_host",
+ "module.get_host_items",
+ "module.get_host_type",
+ "module.get_name",
+ "module.get_option",
+ "module.get_option_array",
+ "module.get_option_boolean",
+ "module.get_option_inherited_set",
+ "module.get_option_number",
+ "module.get_option_path",
+ "module.get_option_scalar",
+ "module.get_option_set",
+ "module.get_option_string",
+ "module.handle_items",
+ "module.has_feature",
+ "module.has_identity",
+ "module.hook",
+ "module.hook_global",
+ "module.hook_object_event",
+ "module.hook_tag",
+ "module.load_resource",
+ "module.measure",
+ "module.measure_event",
+ "module.measure_global_event",
+ "module.measure_object_event",
+ "module.open_store",
+ "module.provides",
+ "module.remove_item",
+ "module.require",
+ "module.send",
+ "module.set_global",
+ "module.shared",
+ "module.unhook",
+ "module.unhook_object_event",
+ "module.wrap_event",
+ "module.wrap_global",
+ "module.wrap_object_event",
+ };
+ globals = {
+ "_M",
+
+ -- Methods that can be set on module API
+ "module.unload",
+ "module.add_host",
+ "module.load",
+ "module.add_host",
+ "module.save",
+ "module.restore",
+ "module.command",
+ "module.environment",
+ };
+}
+files["tests/"] = {
+ read_globals = {
+ "testlib_new_env",
+ "assert_equal",
+ "assert_table",
+ "assert_function",
+ "assert_string",
+ "assert_boolean",
+ "assert_is",
+ "assert_is_not",
+ "runtest",
+ };
+}
diff -r c6b45cac9423 -r 7ec098b68042 CHANGES
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/CHANGES Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,25 @@
+0.10.0
+======
+
+**2017-10-02**
+
+New features
+------------
+
+- Rewritten SQL storage module with Archive support
+- SCRAM-SHA-1-PLUS
+- `prosodyctl check`
+- Statistics
+- Improved TLS configuration
+- Lua 5.2 support
+- mod\_blocklist (XEP-0191)
+- mod\_carbons (XEP-0280)
+- Pluggable connection timeout handling
+- mod\_websocket (RFC 7395)
+- mod\_mam (XEP-0313)
+
+Removed
+-------
+
+- mod\_privacy (XEP-0016)
+- mod\_compression (XEP-0138)
diff -r c6b45cac9423 -r 7ec098b68042 Makefile
--- a/Makefile Wed May 30 21:51:15 2018 +0100
+++ b/Makefile Wed May 30 21:55:09 2018 +0100
@@ -13,35 +13,48 @@
INSTALLEDMODULES = $(LIBDIR)/prosody/modules
INSTALLEDDATA = $(DATADIR)
-.PHONY: all clean install
+INSTALL=install -p
+INSTALL_DATA=$(INSTALL) -m644
+INSTALL_EXEC=$(INSTALL) -m755
+MKDIR=install -d
+MKDIR_PRIVATE=$(MKDIR) -m750
+
+.PHONY: all test clean install
all: prosody.install prosodyctl.install prosody.cfg.lua.install prosody.version
$(MAKE) -C util-src install
ifeq ($(EXCERTS),yes)
- $(MAKE) -C certs localhost.crt example.com.crt || true
+ -$(MAKE) -C certs localhost.crt example.com.crt
endif
install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodings.so util/encodings.so util/pposix.so util/signal.so
- install -d $(BIN) $(CONFIG) $(MODULES) $(SOURCE)
- install -m750 -d $(DATA)
- install -d $(MAN)/man1
- install -d $(CONFIG)/certs
- install -d $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util
- install -m755 ./prosody.install $(BIN)/prosody
- install -m755 ./prosodyctl.install $(BIN)/prosodyctl
- install -m644 core/*.lua $(SOURCE)/core
- install -m644 net/*.lua $(SOURCE)/net
- install -d $(SOURCE)/net/http
- install -m644 net/http/*.lua $(SOURCE)/net/http
- install -m644 util/*.lua $(SOURCE)/util
- install -m644 util/*.so $(SOURCE)/util
- install -d $(SOURCE)/util/sasl
- install -m644 util/sasl/* $(SOURCE)/util/sasl
- umask 0022 && cp -r plugins/* $(MODULES)
- install -m644 certs/* $(CONFIG)/certs
- install -m644 man/prosodyctl.man $(MAN)/man1/prosodyctl.1
- test -e $(CONFIG)/prosody.cfg.lua || install -m644 prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
- test -e prosody.version && install -m644 prosody.version $(SOURCE)/prosody.version || true
+ $(MKDIR) $(BIN) $(CONFIG) $(MODULES) $(SOURCE)
+ $(MKDIR_PRIVATE) $(DATA)
+ $(MKDIR) $(MAN)/man1
+ $(MKDIR) $(CONFIG)/certs
+ $(MKDIR) $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util
+ $(INSTALL_EXEC) ./prosody.install $(BIN)/prosody
+ $(INSTALL_EXEC) ./prosodyctl.install $(BIN)/prosodyctl
+ $(INSTALL_DATA) core/*.lua $(SOURCE)/core
+ $(INSTALL_DATA) net/*.lua $(SOURCE)/net
+ $(MKDIR) $(SOURCE)/net/http $(SOURCE)/net/websocket
+ $(INSTALL_DATA) net/http/*.lua $(SOURCE)/net/http
+ $(INSTALL_DATA) net/websocket/*.lua $(SOURCE)/net/websocket
+ $(INSTALL_DATA) util/*.lua $(SOURCE)/util
+ $(INSTALL_DATA) util/*.so $(SOURCE)/util
+ $(MKDIR) $(SOURCE)/util/sasl
+ $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl
+ $(MKDIR) $(MODULES)/mod_s2s $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam
+ $(INSTALL_DATA) plugins/*.lua $(MODULES)
+ $(INSTALL_DATA) plugins/mod_s2s/*.lua $(MODULES)/mod_s2s
+ $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub
+ $(INSTALL_DATA) plugins/adhoc/*.lua $(MODULES)/adhoc
+ $(INSTALL_DATA) plugins/muc/*.lua $(MODULES)/muc
+ $(INSTALL_DATA) plugins/mod_mam/*.lua $(MODULES)/mod_mam
+ $(INSTALL_DATA) certs/* $(CONFIG)/certs
+ $(INSTALL_DATA) man/prosodyctl.man $(MAN)/man1/prosodyctl.1
+ test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
+ -test -f prosody.version && $(INSTALL_DATA) prosody.version $(SOURCE)/prosody.version
$(MAKE) install -C util-src
clean:
@@ -51,6 +64,10 @@
rm -f prosody.version
$(MAKE) clean -C util-src
+test:
+ cd tests && $(RUNWITH) test.lua 0
+ # Skipping: cd tests && RUNWITH=$(RUNWITH) ./test_util_json.sh
+
util/%.so:
$(MAKE) install -C util-src
@@ -64,8 +81,16 @@
prosody.cfg.lua.install: prosody.cfg.lua.dist
sed 's|certs/|$(INSTALLEDCONFIG)/certs/|' $^ > $@
-prosody.version: $(wildcard prosody.release .hg/dirstate)
- test -e .hg/dirstate && \
- hexdump -n6 -e'6/1 "%02x"' .hg/dirstate > $@ || true
- test -f prosody.release && \
- cp prosody.release $@ || true
+%.version: %.release
+ cp $^ $@
+
+%.version: .hg_archival.txt
+ sed -n 's/^node: \(............\).*/\1/p' $^ > $@
+
+%.version: .hg/dirstate
+ hexdump -n6 -e'6/1 "%02x"' $^ > $@
+
+%.version:
+ echo unknown > $@
+
+
diff -r c6b45cac9423 -r 7ec098b68042 certs/Makefile
--- a/certs/Makefile Wed May 30 21:51:15 2018 +0100
+++ b/certs/Makefile Wed May 30 21:55:09 2018 +0100
@@ -15,16 +15,52 @@
# To request a cert
%.csr: %.cnf %.key
- openssl req -new -key $(lastword $^) -out $@ -utf8 -config $(firstword $^)
+ openssl req -new -key $(lastword $^) \
+ -sha256 -utf8 -config $(firstword $^) -out $@
+
+%.csr: %.cnf
+ umask 0077 && touch $*.key
+ openssl req -new -newkey rsa:$(keysize) -nodes -keyout $*.key \
+ -sha256 -utf8 -config $^ -out $@
+ @chmod 400 $*.key
+
+%.csr: %.key
+ openssl req -new -key $^ -utf8 -subj /CN=$* -out $@
+
+%.csr:
+ umask 0077 && touch $*.key
+ openssl req -new -newkey rsa:$(keysize) -nodes -keyout $*.key \
+ -utf8 -subj /CN=$* -out $@
+ @chmod 400 $*.key
# Self signed
%.crt: %.cnf %.key
- openssl req -new -x509 -nodes -key $(lastword $^) -days 365 \
- -sha1 -out $@ -utf8 -config $(firstword $^)
+ openssl req -new -x509 -key $(lastword $^) -days 365 -sha256 -utf8 \
+ -config $(firstword $^) -out $@
+
+%.crt: %.cnf
+ umask 0077 && touch $*.key
+ openssl req -new -x509 -newkey rsa:$(keysize) -nodes -keyout $*.key \
+ -days 365 -sha256 -utf8 -config $(firstword $^) -out $@
+ @chmod 400 $*.key
+%.crt: %.key
+ openssl req -new -x509 -key $^ -days 365 -sha256 -utf8 -subj /CN=$* -out $@
+
+%.crt:
+ umask 0077 && touch $*.key
+ openssl req -new -x509 -newkey rsa:$(keysize) -nodes -keyout $*.key \
+ -days 365 -sha256 -out $@ -utf8 -subj /CN=$*
+ @chmod 400 $*.key
+
+# Generate a config from the example
%.cnf:
sed 's,example\.com,$*,g' openssl.cnf > $@
%.key:
umask 0077 && openssl genrsa -out $@ $(keysize)
@chmod 400 $@
+
+# Generate Diffie-Hellman parameters
+dh-%.pem:
+ openssl dhparam -out $@ $*
diff -r c6b45cac9423 -r 7ec098b68042 certs/localhost.cnf
--- a/certs/localhost.cnf Wed May 30 21:51:15 2018 +0100
+++ b/certs/localhost.cnf Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,5 @@
[v3_extensions]
-extendedKeyUsage = serverAuth,clientAuth
-keyUsage = digitalSignature,keyEncipherment
-basicConstraints = CA:FALSE
+basicConstraints = CA:TRUE
subjectAltName = @subject_alternative_name
[subject_alternative_name]
diff -r c6b45cac9423 -r 7ec098b68042 configure
--- a/configure Wed May 30 21:51:15 2018 +0100
+++ b/configure Wed May 30 21:55:09 2018 +0100
@@ -2,49 +2,57 @@
# Defaults
-PREFIX=/usr/local
-SYSCONFDIR="$PREFIX/etc/prosody"
+APP_NAME="Prosody"
+APP_DIRNAME="prosody"
+PREFIX="/usr/local"
+SYSCONFDIR="$PREFIX/etc/$APP_DIRNAME"
LIBDIR="$PREFIX/lib"
-DATADIR="$PREFIX/var/lib/prosody"
+DATADIR="$PREFIX/var/lib/$APP_DIRNAME"
LUA_SUFFIX=""
LUA_DIR="/usr"
LUA_BINDIR="/usr/bin"
LUA_INCDIR="/usr/include"
LUA_LIBDIR="/usr/lib"
-IDN_LIB=idn
+IDN_LIB="idn"
ICU_FLAGS="-licui18n -licudata -licuuc"
-OPENSSL_LIB=crypto
-CC=gcc
-CXX=g++
-LD=gcc
-RUNWITH=lua
-EXCERTS=yes
+OPENSSL_LIB="crypto"
+CC="gcc"
+LD="gcc"
+RUNWITH="lua"
+EXCERTS="yes"
+PRNG=
+PRNGLIBS=
-CFLAGS="-fPIC -Wall"
+CFLAGS="-fPIC -Wall -pedantic -std=c99"
LDFLAGS="-shared"
-IDN_LIBRARY=idn
+IDN_LIBRARY="idn"
# Help
show_help() {
cat </dev/null`
+ if [ -n "$prog" ]
+ then
+ dirname "$prog"
+ fi
+}
+
+die() {
+ echo "$*"
+ echo
+ echo "configure failed."
+ echo
+ exit 1
+}
+
+find_helper() {
+ explanation="$1"
+ shift
+ tried="$*"
+ while [ -n "$1" ]
do
- value="`echo $1 | sed 's/[^=]*=\(.*\)/\1/'`"
- if echo "$value" | grep -q "~"
+ found=`find_program "$1"`
+ if [ -n "$found" ]
+ then
+ echo "$1 found at $found"
+ HELPER=$1
+ return
+ fi
+ shift
+ done
+ echo "Could not find $explanation. Tried: $tried."
+ die "Make sure one of them is installed and available in your PATH."
+}
+
+case `echo -n x` in
+-n*) echo_n_flag='';;
+*) echo_n_flag='-n';;
+esac
+
+echo_n() {
+ echo $echo_n_flag "$*"
+}
+
+# ----------------------------------------------------------------------------
+# MAIN PROGRAM
+# ----------------------------------------------------------------------------
+
+# Parse options
+
+while [ -n "$1" ]
+do
+ value="`echo $1 | sed 's/[^=]*.\(.*\)/\1/'`"
+ key="`echo $1 | sed 's/=.*//'`"
+ if `echo "$value" | grep "~" >/dev/null 2>/dev/null`
then
echo
echo '*WARNING*: the "~" sign is not expanded in flags.'
echo 'If you mean the home directory, use $HOME instead.'
echo
fi
- case "$1" in
+ case "$key" in
--help)
show_help
exit 0
;;
- --prefix=*)
+ --prefix)
+ [ -n "$value" ] || die "Missing value in flag $key."
PREFIX="$value"
PREFIX_SET=yes
;;
- --sysconfdir=*)
+ --sysconfdir)
+ [ -n "$value" ] || die "Missing value in flag $key."
SYSCONFDIR="$value"
SYSCONFDIR_SET=yes
;;
- --ostype=*)
+ --ostype)
+ # TODO make this a switch?
OSTYPE="$value"
OSTYPE_SET=yes
- if [ "$OSTYPE" = "debian" ]
- then LUA_SUFFIX="5.1";
- LUA_SUFFIX_SET=yes
- RUNWITH="lua5.1"
- LUA_INCDIR=/usr/include/lua5.1;
- LUA_INCDIR_SET=yes
- CFLAGS="$CFLAGS -D_GNU_SOURCE"
- fi
- if [ "$OSTYPE" = "macosx" ]
- then LUA_INCDIR=/usr/local/include;
- LUA_INCDIR_SET=yes
- LUA_LIBDIR=/usr/local/lib
- LUA_LIBDIR_SET=yes
- LDFLAGS="-bundle -undefined dynamic_lookup"
- fi
- if [ "$OSTYPE" = "linux" ]
- then LUA_INCDIR=/usr/local/include;
- LUA_INCDIR_SET=yes
- LUA_LIBDIR=/usr/local/lib
- LUA_LIBDIR_SET=yes
- CFLAGS="-Wall -fPIC"
- CFLAGS="$CFLAGS -D_GNU_SOURCE"
- LDFLAGS="-shared"
- fi
- if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]
- then LUA_INCDIR="/usr/local/include/lua51"
- LUA_INCDIR_SET=yes
- CFLAGS="-Wall -fPIC -I/usr/local/include"
- LDFLAGS="-I/usr/local/include -L/usr/local/lib -shared"
- LUA_SUFFIX="-5.1"
- LUA_SUFFIX_SET=yes
- LUA_DIR=/usr/local
- LUA_DIR_SET=yes
- fi
- if [ "$OSTYPE" = "openbsd" ]
- then LUA_INCDIR="/usr/local/include";
- fi
+ if [ "$OSTYPE" = "debian" ]; then
+ if [ "$LUA_SUFFIX_SET" != "yes" ]; then
+ LUA_SUFFIX="5.1";
+ LUA_SUFFIX_SET=yes
+ fi
+ if [ "$RUNWITH_SET" != "yes" ]; then
+ RUNWITH="lua$LUA_SUFFIX";
+ RUNWITH_SET=yes
+ fi
+ LUA_INCDIR="/usr/include/lua$LUA_SUFFIX"
+ LUA_INCDIR_SET=yes
+ CFLAGS="$CFLAGS -ggdb"
+ fi
+ if [ "$OSTYPE" = "macosx" ]; then
+ LUA_INCDIR=/usr/local/include;
+ LUA_INCDIR_SET=yes
+ LUA_LIBDIR=/usr/local/lib
+ LUA_LIBDIR_SET=yes
+ CFLAGS="$CFLAGS -mmacosx-version-min=10.3"
+ LDFLAGS="-bundle -undefined dynamic_lookup"
+ fi
+ if [ "$OSTYPE" = "linux" ]; then
+ LUA_INCDIR=/usr/local/include;
+ LUA_INCDIR_SET=yes
+ LUA_LIBDIR=/usr/local/lib
+ LUA_LIBDIR_SET=yes
+ CFLAGS="$CFLAGS -ggdb"
+ fi
+ if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]; then
+ LUA_INCDIR="/usr/local/include/lua51"
+ LUA_INCDIR_SET=yes
+ CFLAGS="-Wall -fPIC -I/usr/local/include"
+ LDFLAGS="-I/usr/local/include -L/usr/local/lib -shared"
+ LUA_SUFFIX="51"
+ LUA_SUFFIX_SET=yes
+ LUA_DIR=/usr/local
+ LUA_DIR_SET=yes
+ CC=cc
+ LD=ld
+ fi
+ if [ "$OSTYPE" = "openbsd" ]; then
+ LUA_INCDIR="/usr/local/include";
+ LUA_INCDIR_SET="yes"
+ fi
+ if [ "$OSTYPE" = "netbsd" ]; then
+ LUA_INCDIR="/usr/pkg/include/lua-5.1"
+ LUA_INCDIR_SET=yes
+ LUA_LIBDIR="/usr/pkg/lib/lua/5.1"
+ LUA_LIBDIR_SET=yes
+ CFLAGS="-Wall -fPIC -I/usr/pkg/include"
+ LDFLAGS="-L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib -shared"
+ fi
+ if [ "$OSTYPE" = "pkg-config" ]; then
+ if [ "$LUA_SUFFIX_SET" != "yes" ]; then
+ LUA_SUFFIX="5.1";
+ LUA_SUFFIX_SET=yes
+ fi
+ LUA_CF="$(pkg-config --cflags-only-I lua$LUA_SUFFIX)"
+ LUA_CF="${LUA_CF#*-I}"
+ LUA_CF="${LUA_CF%% *}"
+ if [ "$LUA_CF" != "" ]; then
+ LUA_INCDIR="$LUA_CF"
+ LUA_INCDIR_SET=yes
+ fi
+ CFLAGS="$CFLAGS"
+ fi
;;
- --libdir=*)
+ --libdir)
LIBDIR="$value"
LIBDIR_SET=yes
;;
- --datadir=*)
- DATADIR="$value"
- DATADIR_SET=yes
+ --datadir)
+ DATADIR="$value"
+ DATADIR_SET=yes
;;
- --require-config)
- REQUIRE_CONFIG=yes
- ;;
- --lua-suffix=*)
+ --lua-suffix)
+ [ -n "$value" ] || die "Missing value in flag $key."
LUA_SUFFIX="$value"
LUA_SUFFIX_SET=yes
;;
- --with-lua=*)
+ --lua-version|--with-lua-version)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ LUA_VERSION="$value"
+ [ "$LUA_VERSION" = "5.1" -o "$LUA_VERSION" = "5.2" -o "$LUA_VERSION" = "5.3" ] || die "Invalid Lua version in flag $key."
+ LUA_VERSION_SET=yes
+ ;;
+ --with-lua)
+ [ -n "$value" ] || die "Missing value in flag $key."
LUA_DIR="$value"
LUA_DIR_SET=yes
;;
- --with-lua-include=*)
+ --with-lua-bin)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ LUA_BINDIR="$value"
+ LUA_BINDIR_SET=yes
+ ;;
+ --with-lua-include)
+ [ -n "$value" ] || die "Missing value in flag $key."
LUA_INCDIR="$value"
LUA_INCDIR_SET=yes
;;
- --with-lua-lib=*)
- LUA_LIBDIR="$value" LUA_LIBDIR_SET=yes
+ --with-lua-lib)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ LUA_LIBDIR="$value"
+ LUA_LIBDIR_SET=yes
;;
- --with-idn=*)
+ --with-idn)
IDN_LIB="$value"
;;
- --idn-library=*)
- IDN_LIBRARY="$value"
- ;;
- --with-ssl=*)
+ --idn-library)
+ IDN_LIBRARY="$value"
+ ;;
+ --with-ssl)
OPENSSL_LIB="$value"
;;
- --cflags=*)
+ --with-random)
+ case "$value" in
+ getrandom)
+ PRNG=GETRANDOM
+ ;;
+ openssl)
+ PRNG=OPENSSL
+ ;;
+ arc4random)
+ PRNG=ARC4RANDOM
+ ;;
+ esac
+ ;;
+ --cflags)
CFLAGS="$value"
;;
- --ldflags=*)
+ --add-cflags)
+ CFLAGS="$CFLAGS $value"
+ ;;
+ --ldflags)
LDFLAGS="$value"
;;
- --c-compiler=*)
+ --add-ldflags)
+ LDFLAGS="$LDFLAGS $value"
+ ;;
+ --c-compiler)
CC="$value"
;;
- --linker=*)
+ --linker)
LD="$value"
;;
- --runwith=*)
+ --runwith)
RUNWITH="$value"
+ RUNWITH_SET=yes
;;
--no-example-certs)
EXCERTS=
;;
+ --compiler-wrapper)
+ CC="$value $CC"
+ LD="$value $LD"
+ ;;
*)
- echo "Error: Unknown flag: $1"
- exit 1
+ die "Error: Unknown flag: $1"
;;
esac
shift
@@ -201,16 +338,16 @@
if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ]
then
if [ "$PREFIX" = "/usr" ]
- then SYSCONFDIR=/etc/prosody
- else SYSCONFDIR=$PREFIX/etc/prosody
+ then SYSCONFDIR=/etc/$APP_DIRNAME
+ else SYSCONFDIR=$PREFIX/etc/$APP_DIRNAME
fi
fi
if [ "$PREFIX_SET" = "yes" -a ! "$DATADIR_SET" = "yes" ]
then
if [ "$PREFIX" = "/usr" ]
- then DATADIR=/var/lib/prosody
- else DATADIR=$PREFIX/var/lib/prosody
+ then DATADIR=/var/lib/$APP_DIRNAME
+ else DATADIR=$PREFIX/var/lib/$APP_DIRNAME
fi
fi
@@ -219,160 +356,218 @@
LIBDIR=$PREFIX/lib
fi
-find_program() {
- path="$PATH"
- item="`echo "$path" | sed 's/\([^:]*\):.*/\1/'`"
- path="`echo "$path" | sed -n 's/[^:]*::*\(.*\)/\1/p'`"
- found="no"
- while [ "$item" ]
- do
- if [ -e "$item/$1" ]
+detect_lua_version() {
+ detected_lua=`$1 -e 'print(_VERSION:match(" (5%.[123])$"))' 2> /dev/null`
+ if [ "$detected_lua" != "nil" ]
+ then
+ if [ "$LUA_VERSION_SET" != "yes" ]
then
- found="yes"
- break
+ echo "Lua version detected: $detected_lua"
+ LUA_VERSION=$detected_lua
+ return 0
+ elif [ "$LUA_VERSION" = "$detected_lua" ]
+ then
+ return 0
fi
- item="`echo "$path" | sed 's/\([^:]*\):.*/\1/'`"
- path="`echo "$path" | sed -n 's/[^:]*::*\(.*\)/\1/p'`"
- done
- if [ "$found" = "yes" ]
- then
- echo "$item"
- else
- echo ""
fi
+ return 1
}
+search_interpreter() {
+ suffix="$1"
+ if [ "$LUA_BINDIR_SET" = "yes" ]
+ then
+ find_lua="$LUA_BINDIR"
+ elif [ "$LUA_DIR_SET" = "yes" ]
+ then
+ LUA_BINDIR="$LUA_DIR/bin"
+ if [ -f "$LUA_BINDIR/lua$suffix" ]
+ then
+ find_lua="$LUA_BINDIR"
+ fi
+ else
+ find_lua=`find_program lua$suffix`
+ fi
+ if [ -n "$find_lua" -a -x "$find_lua/lua$suffix" ]
+ then
+ if detect_lua_version "$find_lua/lua$suffix"
+ then
+ echo "Lua interpreter found: $find_lua/lua$suffix..."
+ if [ "$LUA_BINDIR_SET" != "yes" ]
+ then
+ LUA_BINDIR="$find_lua"
+ fi
+ if [ "$LUA_DIR_SET" != "yes" ]
+ then
+ LUA_DIR=`dirname "$find_lua"`
+ fi
+ LUA_SUFFIX="$suffix"
+ return 0
+ fi
+ fi
+ return 1
+}
+
+lua_interp_found=no
if [ "$LUA_SUFFIX_SET" != "yes" ]
then
- for suffix in "5.1" "51" ""
+ if [ "$LUA_VERSION_SET" = "yes" -a "$LUA_VERSION" = "5.1" ]
+ then
+ suffixes="5.1 51 -5.1 -51"
+ elif [ "$LUA_VERSION_SET" = "yes" -a "$LUA_VERSION" = "5.2" ]
+ then
+ suffixes="5.2 52 -5.2 -52"
+ elif [ "$LUA_VERSION_SET" = "yes" -a "$LUA_VERSION" = "5.3" ]
+ then
+ suffixes="5.3 53 -5.3 -53"
+ else
+ suffixes="5.1 51 -5.1 -51 5.2 52 -5.2 -52 5.3 53 -5.3 -53"
+ fi
+ for suffix in "" `echo $suffixes`
do
- LUA_SUFFIX="$suffix"
- if [ "$LUA_DIR_SET" = "yes" ]
- then
- if [ -e "$LUA_DIR/bin/lua$suffix" ]
- then
- find_lua="$LUA_DIR"
- fi
- else
- find_lua=`find_program lua$suffix`
- fi
- if [ "$find_lua" ]
- then
- echo "Lua interpreter found: $find_lua/lua$suffix..."
- break
- fi
- done
+ search_interpreter "$suffix" && {
+ lua_interp_found=yes
+ break
+ }
+done
+else
+ search_interpreter "$LUA_SUFFIX" && {
+ lua_interp_found=yes
+}
fi
-if ! [ "$LUA_DIR_SET" = "yes" ]
+if [ "$lua_interp_found" != "yes" -a "$RUNWITH_SET" != "yes" ]
then
- echo -n "Looking for Lua... "
- if [ ! "$find_lua" ]
+ [ "$LUA_VERSION_SET" ] && { interp="Lua $LUA_VERSION" ;} || { interp="Lua" ;}
+ [ "$LUA_DIR_SET" -o "$LUA_BINDIR_SET" ] && { where="$LUA_BINDIR" ;} || { where="\$PATH" ;}
+ echo "$interp interpreter not found in $where"
+ die "You may want to use the flags --with-lua, --with-lua-bin and/or --lua-suffix. See --help."
+fi
+
+if [ "$LUA_VERSION_SET" = "yes" -a "$RUNWITH_SET" != "yes" ]
+then
+ echo_n "Checking if $LUA_BINDIR/lua$LUA_SUFFIX is Lua version $LUA_VERSION... "
+ if detect_lua_version "$LUA_BINDIR/lua$LUA_SUFFIX"
then
- find_lua=`find_program lua$LUA_SUFFIX`
- echo "lua$LUA_SUFFIX found in \$PATH: $find_lua"
- fi
- if [ "$find_lua" ]
- then
- LUA_DIR=`dirname $find_lua`
- LUA_BINDIR="$find_lua"
+ echo "yes"
else
- echo "lua$LUA_SUFFIX not found in \$PATH."
- echo "You may want to use the flags --with-lua and/or --lua-suffix. See --help."
- exit 1
+ echo "no"
+ die "You may want to use the flags --with-lua, --with-lua-bin and/or --lua-suffix. See --help."
fi
fi
-if ! [ "$LUA_INCDIR_SET" = "yes" ]
+if [ "$LUA_INCDIR_SET" != "yes" ]
then
LUA_INCDIR="$LUA_DIR/include"
fi
-if ! [ "$LUA_LIBDIR_SET" = "yes" ]
+if [ "$LUA_LIBDIR_SET" != "yes" ]
then
LUA_LIBDIR="$LUA_DIR/lib"
fi
-if [ "$LUA_DIR_SET" = "yes" ]
+echo_n "Checking Lua includes... "
+lua_h="$LUA_INCDIR/lua.h"
+if [ -f "$lua_h" ]
then
- LUA_BINDIR="$LUA_DIR/bin"
+ echo "lua.h found in $lua_h"
+else
+ v_dir="$LUA_INCDIR/lua/$LUA_VERSION"
+ lua_h="$v_dir/lua.h"
+ if [ -f "$lua_h" ]
+ then
+ echo "lua.h found in $lua_h"
+ LUA_INCDIR="$v_dir"
+ else
+ d_dir="$LUA_INCDIR/lua$LUA_VERSION"
+ lua_h="$d_dir/lua.h"
+ if [ -f "$lua_h" ]
+ then
+ echo "lua.h found in $lua_h (Debian/Ubuntu)"
+ LUA_INCDIR="$d_dir"
+ else
+ echo "lua.h not found (looked in $LUA_INCDIR, $v_dir, $d_dir)"
+ die "You may want to use the flag --with-lua or --with-lua-include. See --help."
+ fi
+ fi
+fi
+
+if [ "$lua_interp_found" = "yes" ]
+then
+ echo_n "Checking if Lua header version matches that of the interpreter... "
+ header_version=$(sed -n 's/.*LUA_VERSION_NUM.*5.\(.\).*/5.\1/p' "$lua_h")
+ if [ "$header_version" = "$LUA_VERSION" ]
+ then
+ echo "yes"
+ else
+ echo "no"
+ echo "lua.h version mismatch (interpreter: $LUA_VERSION; lua.h: $header_version)."
+ die "You may want to use the flag --with-lua or --with-lua-include. See --help."
+ fi
fi
if [ "$IDN_LIBRARY" = "icu" ]
then
- IDNA_LIBS="$ICU_FLAGS"
- CFLAGS="$CFLAGS -DUSE_STRINGPREP_ICU"
-fi
-if [ "$IDN_LIBRARY" = "idn" ]
-then
- IDNA_LIBS="-l$IDN_LIB"
+ IDNA_LIBS="$ICU_FLAGS"
+ CFLAGS="$CFLAGS -DUSE_STRINGPREP_ICU"
fi
-
-echo -n "Checking Lua includes... "
-lua_h="$LUA_INCDIR/lua.h"
-if [ -e "$lua_h" ]
+if [ "$IDN_LIBRARY" = "idn" ]
then
- echo "lua.h found in $lua_h"
-else
- echo "lua.h not found (looked in $lua_h)"
- echo "You may want to use the flag --with-lua-include. See --help."
- exit 1
+ IDNA_LIBS="-l$IDN_LIB"
fi
-find_helper() {
- explanation="$1"
- shift
- tried="$*"
- while [ "$1" ]
- do
- found=`find_program "$1"`
- if [ "$found" ]
- then
- echo "$1 found at $found"
- HELPER=$1
- return
- fi
- shift
- done
- echo "Could not find a $explanation. Tried: $tried."
- echo "Make sure one of them is installed and available in your PATH."
- exit 1
-}
+if [ -f config.unix ]; then
+ rm -f config.unix
+fi
+
+if [ "$RUNWITH_SET" != yes ]; then
+ RUNWITH="lua$LUA_SUFFIX"
+fi
+
+OPENSSL_LIBS="-l$OPENSSL_LIB"
+
+if [ "$PRNG" = "OPENSSL" ]; then
+ PRNGLIBS=$OPENSSL_LIBS
+fi
# Write config
echo "Writing configuration..."
echo
+rm -f built
cat < config.unix
# This file was automatically generated by the configure script.
# Run "./configure --help" for details.
+LUA_VERSION=$LUA_VERSION
PREFIX=$PREFIX
SYSCONFDIR=$SYSCONFDIR
LIBDIR=$LIBDIR
DATADIR=$DATADIR
LUA_SUFFIX=$LUA_SUFFIX
LUA_DIR=$LUA_DIR
+LUA_DIR_SET=$LUA_DIR_SET
LUA_INCDIR=$LUA_INCDIR
LUA_LIBDIR=$LUA_LIBDIR
LUA_BINDIR=$LUA_BINDIR
-REQUIRE_CONFIG=$REQUIRE_CONFIG
IDN_LIB=$IDN_LIB
IDNA_LIBS=$IDNA_LIBS
-OPENSSL_LIB=$OPENSSL_LIB
+OPENSSL_LIBS=$OPENSSL_LIBS
CFLAGS=$CFLAGS
LDFLAGS=$LDFLAGS
CC=$CC
-CXX=$CXX
LD=$LD
RUNWITH=$RUNWITH
EXCERTS=$EXCERTS
+RANDOM=$PRNG
+RANDOM_LIBS=$PRNGLIBS
+
EOF
echo "Installation prefix: $PREFIX"
-echo "Prosody configuration directory: $SYSCONFDIR"
+echo "$APP_NAME configuration directory: $SYSCONFDIR"
echo "Using Lua from: $LUA_DIR"
make clean > /dev/null 2> /dev/null
diff -r c6b45cac9423 -r 7ec098b68042 core/certmanager.lua
--- a/core/certmanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/certmanager.lua Wed May 30 21:55:09 2018 +0100
@@ -1,97 +1,211 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
+local softreq = require"util.dependencies".softreq;
+local ssl = softreq"ssl";
+if not ssl then
+ return {
+ create_context = function ()
+ return nil, "LuaSec (required for encryption) was not found";
+ end;
+ reload_ssl_config = function () end;
+ }
+end
+
local configmanager = require "core.configmanager";
local log = require "util.logger".init("certmanager");
-local ssl = ssl;
-local ssl_newcontext = ssl and ssl.newcontext;
+local ssl_context = ssl.context or softreq"ssl.context";
+local ssl_x509 = ssl.x509 or softreq"ssl.x509";
+local ssl_newcontext = ssl.newcontext;
+local new_config = require"util.sslconfig".new;
+local stat = require "lfs".attributes;
-local tostring = tostring;
+local tonumber, tostring = tonumber, tostring;
+local pairs = pairs;
+local t_remove = table.remove;
local type = type;
local io_open = io.open;
+local select = select;
local prosody = prosody;
-local resolve_path = configmanager.resolve_relative_path;
-local config_path = prosody.paths.config;
+local resolve_path = require"util.paths".resolve_relative_path;
+local config_path = prosody.paths.config or ".";
-local luasec_has_noticket, luasec_has_verifyext, luasec_has_no_compression;
-if ssl then
- local luasec_major, luasec_minor = ssl._VERSION:match("^(%d+)%.(%d+)");
- luasec_has_noticket = tonumber(luasec_major)>0 or tonumber(luasec_minor)>=4;
- luasec_has_verifyext = tonumber(luasec_major)>0 or tonumber(luasec_minor)>=5;
- luasec_has_no_compression = tonumber(luasec_major)>0 or tonumber(luasec_minor)>=5;
-end
+local luasec_major, luasec_minor = ssl._VERSION:match("^(%d+)%.(%d+)");
+local luasec_version = tonumber(luasec_major) * 100 + tonumber(luasec_minor);
+local luasec_has = softreq"ssl.config" or {
+ algorithms = {
+ ec = luasec_version >= 5;
+ };
+ capabilities = {
+ curves_list = luasec_version >= 7;
+ };
+ options = {
+ cipher_server_preference = luasec_version >= 2;
+ no_ticket = luasec_version >= 4;
+ no_compression = luasec_version >= 5;
+ single_dh_use = luasec_version >= 2;
+ single_ecdh_use = luasec_version >= 2;
+ };
+};
-module "certmanager"
+local _ENV = nil;
-- Global SSL options if not overridden per-host
-local default_ssl_config = configmanager.get("*", "ssl");
-local default_capath = "/etc/ssl/certs";
-local default_verify = (ssl and ssl.x509 and { "peer", "client_once", }) or "none";
-local default_options = { "no_sslv2", "no_sslv3", "cipher_server_preference", luasec_has_noticket and "no_ticket" or nil };
-local default_verifyext = { "lsec_continue", "lsec_ignore_purpose" };
+local global_ssl_config = configmanager.get("*", "ssl");
+
+local global_certificates = configmanager.get("*", "certificates") or "certs";
+
+local crt_try = { "", "/%s.crt", "/%s/fullchain.pem", "/%s.pem", };
+local key_try = { "", "/%s.key", "/%s/privkey.pem", "/%s.pem", };
+
+local function find_cert(user_certs, name)
+ local certs = resolve_path(config_path, user_certs or global_certificates);
+ log("debug", "Searching %s for a key and certificate for %s...", certs, name);
+ for i = 1, #crt_try do
+ local crt_path = certs .. crt_try[i]:format(name);
+ local key_path = certs .. key_try[i]:format(name);
-if ssl and not luasec_has_verifyext and ssl.x509 then
- -- COMPAT mw/luasec-hg
- for i=1,#default_verifyext do -- Remove lsec_ prefix
- default_verify[#default_verify+1] = default_verifyext[i]:sub(6);
+ if stat(crt_path, "mode") == "file" then
+ if key_path:sub(-4) == ".crt" then
+ key_path = key_path:sub(1, -4) .. "key";
+ if stat(key_path, "mode") == "file" then
+ log("debug", "Selecting certificate %s with key %s for %s", crt_path, key_path, name);
+ return { certificate = crt_path, key = key_path };
+ end
+ elseif stat(key_path, "mode") == "file" then
+ log("debug", "Selecting certificate %s with key %s for %s", crt_path, key_path, name);
+ return { certificate = crt_path, key = key_path };
+ end
+ end
end
-end
-if luasec_has_no_compression and configmanager.get("*", "ssl_compression") ~= true then
- default_options[#default_options+1] = "no_compression";
+ log("debug", "No certificate/key found for %s", name);
end
-if luasec_has_no_compression then -- Has no_compression? Then it has these too...
- default_options[#default_options+1] = "single_dh_use";
- default_options[#default_options+1] = "single_ecdh_use";
+local function find_host_cert(host)
+ if not host then return nil; end
+ return find_cert(configmanager.get(host, "certificate"), host) or find_host_cert(host:match("%.(.+)$"));
+end
+
+local function find_service_cert(service, port)
+ local cert_config = configmanager.get("*", service.."_certificate");
+ if type(cert_config) == "table" then
+ cert_config = cert_config[port] or cert_config.default;
+ end
+ return find_cert(cert_config, service);
end
-function create_context(host, mode, user_ssl_config)
- user_ssl_config = user_ssl_config or default_ssl_config;
+-- Built-in defaults
+local core_defaults = {
+ capath = "/etc/ssl/certs";
+ depth = 9;
+ protocol = "tlsv1+";
+ verify = (ssl_x509 and { "peer", "client_once", }) or "none";
+ options = {
+ cipher_server_preference = luasec_has.options.cipher_server_preference;
+ no_ticket = luasec_has.options.no_ticket;
+ no_compression = luasec_has.options.no_compression and configmanager.get("*", "ssl_compression") ~= true;
+ single_dh_use = luasec_has.options.single_dh_use;
+ single_ecdh_use = luasec_has.options.single_ecdh_use;
+ };
+ verifyext = { "lsec_continue", "lsec_ignore_purpose" };
+ curve = luasec_has.algorithms.ec and not luasec_has.capabilities.curves_list and "secp384r1";
+ curveslist = {
+ "X25519",
+ "P-384",
+ "P-256",
+ "P-521",
+ };
+ ciphers = { -- Enabled ciphers in order of preference:
+ "HIGH+kEDH", -- Ephemeral Diffie-Hellman key exchange, if a 'dhparam' file is set
+ "HIGH+kEECDH", -- Ephemeral Elliptic curve Diffie-Hellman key exchange
+ "HIGH", -- Other "High strength" ciphers
+ -- Disabled cipher suites:
+ "!PSK", -- Pre-Shared Key - not used for XMPP
+ "!SRP", -- Secure Remote Password - not used for XMPP
+ "!3DES", -- 3DES - slow and of questionable security
+ "!aNULL", -- Ciphers that does not authenticate the connection
+ };
+}
+
+if luasec_has.curves then
+ for i = #core_defaults.curveslist, 1, -1 do
+ if not luasec_has.curves[ core_defaults.curveslist[i] ] then
+ t_remove(core_defaults.curveslist, i);
+ end
+ end
+else
+ core_defaults.curveslist = nil;
+end
- if not ssl then return nil, "LuaSec (required for encryption) was not found"; end
- if not user_ssl_config then return nil, "No SSL/TLS configuration present for "..host; end
-
- local ssl_config = {
- mode = mode;
- protocol = user_ssl_config.protocol or "sslv23";
- key = resolve_path(config_path, user_ssl_config.key);
- password = user_ssl_config.password or function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end;
- certificate = resolve_path(config_path, user_ssl_config.certificate);
- capath = resolve_path(config_path, user_ssl_config.capath or default_capath);
- cafile = resolve_path(config_path, user_ssl_config.cafile);
- verify = user_ssl_config.verify or default_verify;
- verifyext = user_ssl_config.verifyext or default_verifyext;
- options = user_ssl_config.options or default_options;
- depth = user_ssl_config.depth;
- curve = user_ssl_config.curve or "secp384r1";
- ciphers = user_ssl_config.ciphers or "HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL";
- dhparam = user_ssl_config.dhparam;
- };
+local path_options = { -- These we pass through resolve_path()
+ key = true, certificate = true, cafile = true, capath = true, dhparam = true
+}
+
+if luasec_version < 5 and ssl_x509 then
+ -- COMPAT mw/luasec-hg
+ for i=1,#core_defaults.verifyext do -- Remove lsec_ prefix
+ core_defaults.verify[#core_defaults.verify+1] = core_defaults.verifyext[i]:sub(6);
+ end
+end
+
+local function create_context(host, mode, ...)
+ local cfg = new_config();
+ cfg:apply(core_defaults);
+ local service_name, port = host:match("^(%S+) port (%d+)$");
+ if service_name then
+ cfg:apply(find_service_cert(service_name, tonumber(port)));
+ else
+ cfg:apply(find_host_cert(host));
+ end
+ cfg:apply({
+ mode = mode,
+ -- We can't read the password interactively when daemonized
+ password = function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end;
+ });
+ cfg:apply(global_ssl_config);
+
+ for i = select('#', ...), 1, -1 do
+ cfg:apply(select(i, ...));
+ end
+ local user_ssl_config = cfg:final();
+
+ if mode == "server" then
+ if not user_ssl_config.certificate then return nil, "No certificate present in SSL/TLS configuration for "..host; end
+ if not user_ssl_config.key then return nil, "No key present in SSL/TLS configuration for "..host; end
+ end
+
+ for option in pairs(path_options) do
+ if type(user_ssl_config[option]) == "string" then
+ user_ssl_config[option] = resolve_path(config_path, user_ssl_config[option]);
+ else
+ user_ssl_config[option] = nil;
+ end
+ end
-- LuaSec expects dhparam to be a callback that takes two arguments.
-- We ignore those because it is mostly used for having a separate
-- set of params for EXPORT ciphers, which we don't have by default.
- if type(ssl_config.dhparam) == "string" then
- local f, err = io_open(resolve_path(config_path, ssl_config.dhparam));
+ if type(user_ssl_config.dhparam) == "string" then
+ local f, err = io_open(user_ssl_config.dhparam);
if not f then return nil, "Could not open DH parameters: "..err end
local dhparam = f:read("*a");
f:close();
- ssl_config.dhparam = function() return dhparam; end
+ user_ssl_config.dhparam = function() return dhparam; end
end
- local ctx, err = ssl_newcontext(ssl_config);
+ local ctx, err = ssl_newcontext(user_ssl_config);
- -- COMPAT: LuaSec 0.4.1 ignores the cipher list from the config, so we have to take
- -- care of it ourselves...
- if ctx and ssl_config.ciphers then
+ -- COMPAT Older LuaSec ignores the cipher list from the config, so we have to take care
+ -- of it ourselves (W/A for #x)
+ if ctx and user_ssl_config.ciphers then
local success;
- success, err = ssl.context.setcipher(ctx, ssl_config.ciphers);
+ success, err = ssl_context.setcipher(ctx, user_ssl_config.ciphers);
if not success then ctx = nil; end
end
@@ -99,10 +213,13 @@
err = err or "invalid ssl config"
local file = err:match("^error loading (.-) %(");
if file then
+ local typ;
if file == "private key" then
- file = ssl_config.key or "your private key";
+ typ = file;
+ file = user_ssl_config.key or "your private key";
elseif file == "certificate" then
- file = ssl_config.certificate or "your certificate file";
+ typ = file;
+ file = user_ssl_config.certificate or "your certificate file";
end
local reason = err:match("%((.+)%)$") or "some reason";
if reason == "Permission denied" then
@@ -111,6 +228,8 @@
reason = "Check that the path is correct, and the file exists.";
elseif reason == "system lib" then
reason = "Previous error (see logs), or other system error.";
+ elseif reason == "no start line" then
+ reason = "Check that the file contains a "..(typ or file);
elseif reason == "(null)" or not reason then
reason = "Check that the file exists and the permissions are correct";
else
@@ -121,13 +240,21 @@
log("error", "SSL/TLS: Error initialising for %s: %s", host, err);
end
end
- return ctx, err;
+ return ctx, err, user_ssl_config;
end
-function reload_ssl_config()
- default_ssl_config = configmanager.get("*", "ssl");
+local function reload_ssl_config()
+ global_ssl_config = configmanager.get("*", "ssl");
+ global_certificates = configmanager.get("*", "certificates") or "certs";
+ if luasec_has.options.no_compression then
+ core_defaults.options.no_compression = configmanager.get("*", "ssl_compression") ~= true;
+ end
end
prosody.events.add_handler("config-reloaded", reload_ssl_config);
-return _M;
+return {
+ create_context = create_context;
+ reload_ssl_config = reload_ssl_config;
+ find_cert = find_cert;
+};
diff -r c6b45cac9423 -r 7ec098b68042 core/configmanager.lua
--- a/core/configmanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/configmanager.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -15,26 +15,31 @@
local envload = require"util.envload".envload;
local deps = require"util.dependencies";
+local resolve_relative_path = require"util.paths".resolve_relative_path;
+local glob_to_pattern = require"util.paths".glob_to_pattern;
local path_sep = package.config:sub(1,1);
-local have_encodings, encodings = pcall(require, "util.encodings");
-local nameprep = have_encodings and encodings.stringprep.nameprep or function (host) return host:lower(); end
+local encodings = deps.softreq"util.encodings";
+local nameprep = encodings and encodings.stringprep.nameprep or function (host) return host:lower(); end
-module "configmanager"
+local _M = {};
+local _ENV = nil;
+
+_M.resolve_relative_path = resolve_relative_path; -- COMPAT
local parsers = {};
-local config_mt = { __index = function (t, k) return rawget(t, "*"); end};
+local config_mt = { __index = function (t, _) return rawget(t, "*"); end};
local config = setmetatable({ ["*"] = { } }, config_mt);
-- When host not found, use global
local host_mt = { __index = function(_, k) return config["*"][k] end }
-function getconfig()
+function _M.getconfig()
return config;
end
-function get(host, key, _oldkey)
+function _M.get(host, key, _oldkey)
if key == "core" then
key = _oldkey; -- COMPAT with code that still uses "core"
end
@@ -50,11 +55,11 @@
end
end
-local function set(config, host, key, value)
+local function set(config_table, host, key, value)
if host and key then
- local hostconfig = rawget(config, host);
+ local hostconfig = rawget(config_table, host);
if not hostconfig then
- hostconfig = rawset(config, host, setmetatable({}, host_mt))[host];
+ hostconfig = rawset(config_table, host, setmetatable({}, host_mt))[host];
end
hostconfig[key] = value;
return true;
@@ -69,55 +74,20 @@
return set(config, host, key, value);
end
--- Helper function to resolve relative paths (needed by config)
-do
- function resolve_relative_path(parent_path, path)
- if path then
- -- Some normalization
- parent_path = parent_path:gsub("%"..path_sep.."+$", "");
- path = path:gsub("^%.%"..path_sep.."+", "");
-
- local is_relative;
- if path_sep == "/" and path:sub(1,1) ~= "/" then
- is_relative = true;
- elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and (path:sub(2,3) ~= ":\\" and path:sub(2,3) ~= ":/")) then
- is_relative = true;
- end
- if is_relative then
- return parent_path..path_sep..path;
- end
- end
- return path;
- end
-end
+function _M.load(filename, config_format)
+ config_format = config_format or filename:match("%w+$");
--- Helper function to convert a glob to a Lua pattern
-local function glob_to_pattern(glob)
- return "^"..glob:gsub("[%p*?]", function (c)
- if c == "*" then
- return ".*";
- elseif c == "?" then
- return ".";
- else
- return "%"..c;
- end
- end).."$";
-end
-
-function load(filename, format)
- format = format or filename:match("%w+$");
-
- if parsers[format] and parsers[format].load then
+ if parsers[config_format] and parsers[config_format].load then
local f, err = io.open(filename);
if f then
local new_config = setmetatable({ ["*"] = { } }, config_mt);
- local ok, err = parsers[format].load(f:read("*a"), filename, new_config);
+ local ok, err = parsers[config_format].load(f:read("*a"), filename, new_config);
f:close();
if ok then
config = new_config;
fire_event("config-reloaded", {
filename = filename,
- format = format,
+ format = config_format,
config = config
});
end
@@ -126,98 +96,96 @@
return f, "file", err;
end
- if not format then
+ if not config_format then
return nil, "file", "no parser specified";
else
- return nil, "file", "no parser for "..(format);
+ return nil, "file", "no parser for "..(config_format);
end
end
-function save(filename, format)
-end
-
-function addparser(format, parser)
- if format and parser then
- parsers[format] = parser;
+function _M.addparser(config_format, parser)
+ if config_format and parser then
+ parsers[config_format] = parser;
end
end
-- _M needed to avoid name clash with local 'parsers'
function _M.parsers()
local p = {};
- for format in pairs(parsers) do
- table.insert(p, format);
+ for config_format in pairs(parsers) do
+ table.insert(p, config_format);
end
return p;
end
-- Built-in Lua parser
do
- local pcall, setmetatable = _G.pcall, _G.setmetatable;
- local rawget = _G.rawget;
+ local pcall = _G.pcall;
parsers.lua = {};
- function parsers.lua.load(data, config_file, config)
+ function parsers.lua.load(data, config_file, config_table)
local env;
-- The ' = true' are needed so as not to set off __newindex when we assign the functions below
env = setmetatable({
Host = true, host = true, VirtualHost = true,
Component = true, component = true,
Include = true, include = true, RunScript = true }, {
- __index = function (t, k)
+ __index = function (_, k)
return rawget(_G, k);
end,
- __newindex = function (t, k, v)
- set(config, env.__currenthost or "*", k, v);
+ __newindex = function (_, k, v)
+ set(config_table, env.__currenthost or "*", k, v);
end
});
-
+
rawset(env, "__currenthost", "*") -- Default is global
function env.VirtualHost(name)
name = nameprep(name);
- if rawget(config, name) and rawget(config[name], "component_module") then
+ if rawget(config_table, name) and rawget(config_table[name], "component_module") then
error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
- name, config[name].component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
+ name, config_table[name].component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
end
rawset(env, "__currenthost", name);
-- Needs at least one setting to logically exist :)
- set(config, name or "*", "defined", true);
+ set(config_table, name or "*", "defined", true);
return function (config_options)
rawset(env, "__currenthost", "*"); -- Return to global scope
for option_name, option_value in pairs(config_options) do
- set(config, name or "*", option_name, option_value);
+ set(config_table, name or "*", option_name, option_value);
end
end;
end
env.Host, env.host = env.VirtualHost, env.VirtualHost;
-
+
function env.Component(name)
name = nameprep(name);
- if rawget(config, name) and rawget(config[name], "defined") and not rawget(config[name], "component_module") then
+ if rawget(config_table, name) and rawget(config_table[name], "defined")
+ and not rawget(config_table[name], "component_module") then
error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s",
name, name, name), 0);
end
- set(config, name, "component_module", "component");
+ set(config_table, name, "component_module", "component");
-- Don't load the global modules by default
- set(config, name, "load_global_modules", false);
+ set(config_table, name, "load_global_modules", false);
rawset(env, "__currenthost", name);
local function handle_config_options(config_options)
rawset(env, "__currenthost", "*"); -- Return to global scope
for option_name, option_value in pairs(config_options) do
- set(config, name or "*", option_name, option_value);
+ set(config_table, name or "*", option_name, option_value);
end
end
-
+
return function (module)
if type(module) == "string" then
- set(config, name, "component_module", module);
+ set(config_table, name, "component_module", module);
return handle_config_options;
end
return handle_config_options(module);
end
end
env.component = env.Component;
-
+
function env.Include(file)
+ -- Check whether this is a wildcard Include
if file:match("[*?]") then
local lfs = deps.softreq "lfs";
if not lfs then
@@ -237,38 +205,39 @@
env.Include(path..path_sep..f);
end
end
- else
- local file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file);
- local f, err = io.open(file);
- if f then
- local ret, err = parsers.lua.load(f:read("*a"), file, config);
- if not ret then error(err:gsub("%[string.-%]", file), 0); end
- end
- if not f then error("Error loading included "..file..": "..err, 0); end
- return f, err;
+ return;
end
+ -- Not a wildcard, so resolve (potentially) relative path and run through config parser
+ file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file);
+ local f, err = io.open(file);
+ if f then
+ local ret, err = parsers.lua.load(f:read("*a"), file, config_table);
+ if not ret then error(err:gsub("%[string.-%]", file), 0); end
+ end
+ if not f then error("Error loading included "..file..": "..err, 0); end
+ return f, err;
end
env.include = env.Include;
-
+
function env.RunScript(file)
return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file));
end
-
+
local chunk, err = envload(data, "@"..config_file, env);
-
+
if not chunk then
return nil, err;
end
-
+
local ok, err = pcall(chunk);
-
+
if not ok then
return nil, err;
end
-
+
return true;
end
-
+
end
return _M;
diff -r c6b45cac9423 -r 7ec098b68042 core/hostmanager.lua
--- a/core/hostmanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/hostmanager.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -13,7 +13,6 @@
local NULL = {};
local jid_split = require "util.jid".split;
-local uuid_gen = require "util.uuid".generate;
local log = require "util.logger".init("hostmanager");
@@ -27,15 +26,31 @@
local pairs, select, rawget = pairs, select, rawget;
local tostring, type = tostring, type;
+local setmetatable = setmetatable;
-module "hostmanager"
+local _ENV = nil;
+
+local host_mt = { }
+function host_mt:__tostring()
+ if self.type == "component" then
+ local typ = configmanager.get(self.host, "component_module");
+ if typ == "component" then
+ return ("Component %q"):format(self.host);
+ end
+ return ("Component %q %q"):format(self.host, typ);
+ elseif self.type == "local" then
+ return ("VirtualHost %q"):format(self.host);
+ end
+end
local hosts_loaded_once;
+local activate, deactivate;
+
local function load_enabled_hosts(config)
local defined_hosts = config or configmanager.getconfig();
local activated_any_host;
-
+
for host, host_config in pairs(defined_hosts) do
if host ~= "*" and host_config.enabled ~= false then
if not host_config.component_module then
@@ -44,11 +59,11 @@
activate(host, host_config);
end
end
-
+
if not activated_any_host then
log("error", "No active VirtualHost entries in the config file. This may cause unexpected behaviour as no modules will be loaded.");
end
-
+
prosody_events.fire_event("hosts-activated", defined_hosts);
hosts_loaded_once = true;
end
@@ -56,8 +71,8 @@
prosody_events.add_handler("server-starting", load_enabled_hosts);
local function host_send(stanza)
- local name, type = stanza.name, stanza.attr.type;
- if type == "error" or (name == "iq" and type == "result") then
+ local name, stanza_type = stanza.name, stanza.attr.type;
+ if stanza_type == "error" or (name == "iq" and stanza_type == "result") then
local dest_host_name = select(2, jid_split(stanza.attr.to));
local dest_host = hosts[dest_host_name] or { type = "unknown" };
log("warn", "Unhandled response sent to %s host %s: %s", dest_host.type, dest_host_name, tostring(stanza));
@@ -74,10 +89,13 @@
host = host;
s2sout = {};
events = events_new();
- dialback_secret = configmanager.get(host, "dialback_secret") or uuid_gen();
send = host_send;
modules = {};
};
+ function host_session:close(reason)
+ log("debug", "Attempt to close host session %s with reason: %s", self.host, reason);
+ end
+ setmetatable(host_session, host_mt);
if not host_config.component_module then -- host
host_session.type = "local";
host_session.sessions = {};
@@ -85,7 +103,7 @@
host_session.type = "component";
end
hosts[host] = host_session;
- if not host:match("[@/]") then
+ if not host_config.disco_hidden and not host:match("[@/]") then
disco_items:set(host:match("%.(.*)") or "*", host, host_config.name or true);
end
for option_name in pairs(host_config) do
@@ -93,7 +111,7 @@
log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in the server-wide section instead", host, option_name);
end
end
-
+
log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
prosody_events.fire_event("host-activated", host);
return true;
@@ -104,11 +122,11 @@
if not host_session then return nil, "The host "..tostring(host).." is not activated"; end
log("info", "Deactivating host: %s", host);
prosody_events.fire_event("host-deactivating", { host = host, host_session = host_session, reason = reason });
-
+
if type(reason) ~= "table" then
reason = { condition = "host-gone", text = tostring(reason or "This server has stopped serving "..host) };
end
-
+
-- Disconnect local users, s2s connections
-- TODO: These should move to mod_c2s and mod_s2s (how do they know they're being unloaded and not reloaded?)
if host_session.sessions then
@@ -151,8 +169,12 @@
return true;
end
-function get_children(host)
+local function get_children(host)
return disco_items:get(host) or NULL;
end
-return _M;
+return {
+ activate = activate;
+ deactivate = deactivate;
+ get_children = get_children;
+}
diff -r c6b45cac9423 -r 7ec098b68042 core/loggingmanager.lua
--- a/core/loggingmanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/loggingmanager.lua Wed May 30 21:55:09 2018 +0100
@@ -1,38 +1,34 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-
+-- luacheck: globals log prosody.log
-local format = string.format;
+local format = require "util.format".format;
local setmetatable, rawset, pairs, ipairs, type =
setmetatable, rawset, pairs, ipairs, type;
-local io_open, io_write = io.open, io.write;
+local stdout = io.stdout;
+local io_open = io.open;
local math_max, rep = math.max, string.rep;
local os_date = os.date;
-local getstyle, setstyle = require "util.termcolours".getstyle, require "util.termcolours".setstyle;
-
-if os.getenv("__FLUSH_LOG") then
- local io_flush = io.flush;
- local _io_write = io_write;
- io_write = function(...) _io_write(...); io_flush(); end
-end
+local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
local config = require "core.configmanager";
local logger = require "util.logger";
local prosody = prosody;
_G.log = logger.init("general");
+prosody.log = logger.init("general");
-module "loggingmanager"
+local _ENV = nil;
-- The log config used if none specified in the config file (see reload_logging for initialization)
local default_logging;
local default_file_logging;
-local default_timestamp = "%b %d %H:%M:%S";
+local default_timestamp = "%b %d %H:%M:%S ";
-- The actual config loggingmanager is using
local logging_config;
@@ -45,16 +41,16 @@
-- This function is called automatically when a new sink type is added [see apply_sink_rules()]
local function add_rule(sink_config)
local sink_maker = log_sink_types[sink_config.to];
- if sink_maker then
- -- Create sink
- local sink = sink_maker(sink_config);
-
- -- Set sink for all chosen levels
- for level in pairs(get_levels(sink_config.levels or logging_levels)) do
- logger.add_level_sink(level, sink);
- end
- else
- -- No such sink type
+ if not sink_maker then
+ return; -- No such sink type
+ end
+
+ -- Create sink
+ local sink = sink_maker(sink_config);
+
+ -- Set sink for all chosen levels
+ for level in pairs(get_levels(sink_config.levels or logging_levels)) do
+ logger.add_level_sink(level, sink);
end
end
@@ -63,7 +59,7 @@
-- the log_sink_types table.
function apply_sink_rules(sink_type)
if type(logging_config) == "table" then
-
+
for _, level in ipairs(logging_levels) do
if type(logging_config[level]) == "string" then
local value = logging_config[level];
@@ -82,7 +78,7 @@
end
end
end
-
+
for _, sink_config in ipairs(logging_config) do
if (type(sink_config) == "table" and sink_config.to == sink_type) then
add_rule(sink_config);
@@ -128,7 +124,7 @@
end
end
end
-
+
for _, level in ipairs(criteria) do
set[level] = true;
end
@@ -136,14 +132,14 @@
end
-- Initialize config, etc. --
-function reload_logging()
+local function reload_logging()
local old_sink_types = {};
-
+
for name, sink_maker in pairs(log_sink_types) do
old_sink_types[name] = sink_maker;
log_sink_types[name] = nil;
end
-
+
logger.reset();
local debug_mode = config.get("*", "debug");
@@ -152,15 +148,13 @@
default_file_logging = {
{ to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true }
};
- default_timestamp = "%b %d %H:%M:%S";
logging_config = config.get("*", "log") or default_logging;
-
-
+
for name, sink_maker in pairs(old_sink_types) do
log_sink_types[name] = sink_maker;
end
-
+
prosody.events.fire_event("logging-reloaded");
end
@@ -170,107 +164,90 @@
--- Definition of built-in logging sinks ---
-- Null sink, must enter log_sink_types *first*
-function log_sink_types.nowhere()
+local function log_to_nowhere()
return function () return false; end;
end
-
--- Column width for "source" (used by stdout and console)
-local sourcewidth = 20;
+log_sink_types.nowhere = log_to_nowhere;
-function log_sink_types.stdout(config)
- local timestamps = config.timestamps;
-
- if timestamps == true then
+local function log_to_file(sink_config, logfile)
+ logfile = logfile or io_open(sink_config.filename, "a+");
+ if not logfile then
+ return log_to_nowhere(sink_config);
+ end
+ local write = logfile.write;
+
+ local timestamps = sink_config.timestamps;
+
+ if timestamps == true or timestamps == nil then
timestamps = default_timestamp; -- Default format
+ elseif timestamps then
+ timestamps = timestamps .. " ";
end
-
- return function (name, level, message, ...)
- sourcewidth = math_max(#name+2, sourcewidth);
- local namelen = #name;
- if timestamps then
- io_write(os_date(timestamps), " ");
+
+ if sink_config.buffer_mode ~= false then
+ logfile:setvbuf(sink_config.buffer_mode or "line");
+ end
+
+ -- Column width for "source" (used by stdout and console)
+ local sourcewidth = sink_config.source_width;
+
+ if sourcewidth then
+ return function (name, level, message, ...)
+ sourcewidth = math_max(#name+2, sourcewidth);
+ write(logfile, timestamps and os_date(timestamps) or "", name, rep(" ", sourcewidth-#name), level, "\t", format(message, ...), "\n");
end
- if ... then
- io_write(name, rep(" ", sourcewidth-namelen), level, "\t", format(message, ...), "\n");
- else
- io_write(name, rep(" ", sourcewidth-namelen), level, "\t", message, "\n");
+ else
+ return function (name, level, message, ...)
+ write(logfile, timestamps and os_date(timestamps) or "", name, "\t", level, "\t", format(message, ...), "\n");
end
end
end
+log_sink_types.file = log_to_file;
-do
- local do_pretty_printing = true;
-
- local logstyles = {};
- if do_pretty_printing then
- logstyles["info"] = getstyle("bold");
- logstyles["warn"] = getstyle("bold", "yellow");
- logstyles["error"] = getstyle("bold", "red");
+local function log_to_stdout(sink_config)
+ if not sink_config.timestamps then
+ sink_config.timestamps = false;
end
- function log_sink_types.console(config)
- -- Really if we don't want pretty colours then just use plain stdout
- if not do_pretty_printing then
- return log_sink_types.stdout(config);
- end
-
- local timestamps = config.timestamps;
+ if sink_config.source_width == nil then
+ sink_config.source_width = 20;
+ end
+ return log_to_file(sink_config, stdout);
+end
+log_sink_types.stdout = log_to_stdout;
+
+local do_pretty_printing = true;
- if timestamps == true then
- timestamps = default_timestamp; -- Default format
- end
+local logstyles;
+if do_pretty_printing then
+ logstyles = {};
+ logstyles["info"] = getstyle("bold");
+ logstyles["warn"] = getstyle("bold", "yellow");
+ logstyles["error"] = getstyle("bold", "red");
+end
- return function (name, level, message, ...)
- sourcewidth = math_max(#name+2, sourcewidth);
- local namelen = #name;
-
- if timestamps then
- io_write(os_date(timestamps), " ");
- end
- io_write(name, rep(" ", sourcewidth-namelen));
- setstyle(logstyles[level]);
- io_write(level);
- setstyle();
- if ... then
- io_write("\t", format(message, ...), "\n");
- else
- io_write("\t", message, "\n");
- end
+local function log_to_console(sink_config)
+ -- Really if we don't want pretty colours then just use plain stdout
+ local logstdout = log_to_stdout(sink_config);
+ if not do_pretty_printing then
+ return logstdout;
+ end
+ return function (name, level, message, ...)
+ local logstyle = logstyles[level];
+ if logstyle then
+ level = getstring(logstyle, level);
end
+ return logstdout(name, level, message, ...);
end
end
-
-local empty_function = function () end;
-function log_sink_types.file(config)
- local log = config.filename;
- local logfile = io_open(log, "a+");
- if not logfile then
- return empty_function;
- end
- local write, flush = logfile.write, logfile.flush;
-
- local timestamps = config.timestamps;
+log_sink_types.console = log_to_console;
- if timestamps == nil or timestamps == true then
- timestamps = default_timestamp; -- Default format
- end
-
- return function (name, level, message, ...)
- if timestamps then
- write(logfile, os_date(timestamps), " ");
- end
- if ... then
- write(logfile, name, "\t", level, "\t", format(message, ...), "\n");
- else
- write(logfile, name, "\t" , level, "\t", message, "\n");
- end
- flush(logfile);
- end;
-end
-
-function register_sink_type(name, sink_maker)
+local function register_sink_type(name, sink_maker)
local old_sink_maker = log_sink_types[name];
log_sink_types[name] = sink_maker;
return old_sink_maker;
end
-return _M;
+return {
+ reload_logging = reload_logging;
+ register_sink_type = register_sink_type;
+}
diff -r c6b45cac9423 -r 7ec098b68042 core/moduleapi.lua
--- a/core/moduleapi.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/moduleapi.lua Wed May 30 21:55:09 2018 +0100
@@ -1,23 +1,28 @@
-- Prosody IM
-- Copyright (C) 2008-2012 Matthew Wild
-- Copyright (C) 2008-2012 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local config = require "core.configmanager";
-local modulemanager = require "modulemanager"; -- This is necessary to avoid require loops
local array = require "util.array";
local set = require "util.set";
+local it = require "util.iterators";
local logger = require "util.logger";
local pluginloader = require "util.pluginloader";
local timer = require "util.timer";
+local resolve_relative_path = require"util.paths".resolve_relative_path;
+local measure = require "core.statsmanager".measure;
+local st = require "util.stanza";
local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
local error, setmetatable, type = error, setmetatable, type;
-local ipairs, pairs, select, unpack = ipairs, pairs, select, unpack;
+local ipairs, pairs, select = ipairs, pairs, select;
+local unpack = table.unpack or unpack; --luacheck: ignore 113
local tonumber, tostring = tonumber, tostring;
+local require = require;
local prosody = prosody;
local hosts = prosody.hosts;
@@ -44,14 +49,14 @@
end
function api:get_host_type()
- return self.host ~= "*" and hosts[self.host].type or nil;
+ return (self.host == "*" and "global") or hosts[self.host].type or "local";
end
function api:set_global()
self.host = "*";
-- Update the logger
local _log = logger.init("mod_"..self.name);
- self.log = function (self, ...) return _log(...); end;
+ self.log = function (self, ...) return _log(...); end; --luacheck: ignore self
self._log = _log;
self.global = true;
end
@@ -59,8 +64,8 @@
function api:add_feature(xmlns)
self:add_item("feature", xmlns);
end
-function api:add_identity(category, type, name)
- self:add_item("identity", {category = category, type = type, name = name});
+function api:add_identity(category, identity_type, name)
+ self:add_item("identity", {category = category, type = identity_type, name = name});
end
function api:add_extension(data)
self:add_item("extension", data);
@@ -71,10 +76,10 @@
end
return false;
end
-function api:has_identity(category, type, name)
+function api:has_identity(category, identity_type, name)
for _, id in ipairs(self:get_host_items("identity")) do
- if id.category == category and id.type == type and id.name == name then
- return true;
+ if id.category == category and id.type == identity_type and id.name == name then
+ return true;
end
end
return false;
@@ -90,6 +95,7 @@
end
function api:unhook_object_event(object, event, handler)
+ self.event_handlers:set(object, event, handler, nil);
return object.remove_handler(event, handler);
end
@@ -109,20 +115,35 @@
self:log("warn", "Error: Insufficient parameters to module:hook_stanza()");
return;
end
- return self:hook("stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority);
+ return self:hook("stanza/"..(xmlns and (xmlns..":") or "")..name,
+ function (data) return handler(data.origin, data.stanza, data); end, priority);
end
api.hook_stanza = api.hook_tag; -- COMPAT w/pre-0.9
+function api:unhook(event, handler)
+ return self:unhook_object_event((hosts[self.host] or prosody).events, event, handler);
+end
+
+function api:wrap_object_event(events_object, event, handler)
+ return self:hook_object_event(assert(events_object.wrappers, "no wrappers"), event, handler);
+end
+
+function api:wrap_event(event, handler)
+ return self:wrap_object_event((hosts[self.host] or prosody).events, event, handler);
+end
+
+function api:wrap_global(event, handler)
+ return self:hook_object_event(prosody.events, event, handler);
+end
+
function api:require(lib)
- local f, n = pluginloader.load_code(self.name, lib..".lib.lua", self.environment);
- if not f then
- f, n = pluginloader.load_code(lib, lib..".lib.lua", self.environment);
- end
+ local f, n = pluginloader.load_code_ext(self.name, lib, "lib.lua", self.environment);
if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
return f();
end
function api:depends(name)
+ local modulemanager = require"core.modulemanager";
if not self.dependencies then
self.dependencies = {};
self:hook("module-reloaded", function (event)
@@ -167,7 +188,8 @@
local path = paths[i];
if path:sub(1,1) ~= "/" then -- Prepend default components
local n_components = select(2, path:gsub("/", "%1"));
- path = (n_components<#default_path_components and "/" or "")..t_concat(default_path_components, "/", 1, #default_path_components-n_components).."/"..path;
+ path = (n_components<#default_path_components and "/" or "")
+ ..t_concat(default_path_components, "/", 1, #default_path_components-n_components).."/"..path;
end
local shared = shared_data[path];
if not shared then
@@ -191,7 +213,7 @@
return value;
end
-function api:get_option_string(name, default_value)
+function api:get_option_scalar(name, default_value)
local value = self:get_option(name, default_value);
if type(value) == "table" then
if #value > 1 then
@@ -199,6 +221,11 @@
end
value = value[1];
end
+ return value;
+end
+
+function api:get_option_string(name, default_value)
+ local value = self:get_option_scalar(name, default_value);
if value == nil then
return nil;
end
@@ -206,13 +233,7 @@
end
function api:get_option_number(name, ...)
- local value = self:get_option(name, ...);
- if type(value) == "table" then
- if #value > 1 then
- self:log("error", "Config option '%s' does not take a list, using just the first item", name);
- end
- value = value[1];
- end
+ local value = self:get_option_scalar(name, ...);
local ret = tonumber(value);
if value ~= nil and ret == nil then
self:log("error", "Config option '%s' not understood, expecting a number", name);
@@ -221,13 +242,7 @@
end
function api:get_option_boolean(name, ...)
- local value = self:get_option(name, ...);
- if type(value) == "table" then
- if #value > 1 then
- self:log("error", "Config option '%s' does not take a list, using just the first item", name);
- end
- value = value[1];
- end
+ local value = self:get_option_scalar(name, ...);
if value == nil then
return nil;
end
@@ -252,21 +267,21 @@
if value == nil then
return nil;
end
-
+
if type(value) ~= "table" then
return array{ value }; -- Assume any non-list is a single-item list
end
-
+
return array():append(value); -- Clone
end
function api:get_option_set(name, ...)
local value = self:get_option_array(name, ...);
-
+
if value == nil then
return nil;
end
-
+
return set.new(value);
end
@@ -282,6 +297,20 @@
return value;
end
+function api:get_option_path(name, default, parent)
+ if parent == nil then
+ parent = parent or self:get_directory();
+ elseif prosody.paths[parent] then
+ parent = prosody.paths[parent];
+ end
+ local value = self:get_option_string(name, default);
+ if value == nil then
+ return nil;
+ end
+ return resolve_relative_path(parent, value);
+end
+
+
function api:context(host)
return setmetatable({host=host or "*"}, {__index=self,__newindex=self});
end
@@ -304,15 +333,16 @@
end
function api:get_host_items(key)
+ local modulemanager = require"core.modulemanager";
local result = modulemanager.get_items(key, self.host) or {};
return result;
end
-function api:handle_items(type, added_cb, removed_cb, existing)
- self:hook("item-added/"..type, added_cb);
- self:hook("item-removed/"..type, removed_cb);
+function api:handle_items(item_type, added_cb, removed_cb, existing)
+ self:hook("item-added/"..item_type, added_cb);
+ self:hook("item-removed/"..item_type, removed_cb);
if existing ~= false then
- for _, item in ipairs(self:get_host_items(type)) do
+ for _, item in ipairs(self:get_host_items(item_type)) do
added_cb({ item = item });
end
end
@@ -339,8 +369,16 @@
self:add_item(name.."-provider", item);
end
-function api:send(stanza)
- return core_post_stanza(hosts[self.host], stanza);
+function api:send(stanza, origin)
+ return core_post_stanza(origin or hosts[self.host], stanza);
+end
+
+function api:broadcast(jids, stanza, iter)
+ for jid in (iter or it.values)(jids) do
+ local new_stanza = st.clone(stanza);
+ new_stanza.attr.to = jid;
+ core_post_stanza(hosts[self.host], new_stanza);
+ end
end
function api:add_timer(delay, callback)
@@ -356,12 +394,35 @@
end
function api:load_resource(path, mode)
- path = config.resolve_relative_path(self:get_directory(), path);
+ path = resolve_relative_path(self:get_directory(), path);
return io.open(path, mode);
end
-function api:open_store(name, type)
- return storagemanager.open(self.host, name or self.name, type);
+function api:open_store(name, store_type)
+ return require"core.storagemanager".open(self.host, name or self.name, store_type);
+end
+
+function api:measure(name, stat_type)
+ return measure(stat_type, "/"..self.host.."/mod_"..self.name.."/"..name);
+end
+
+function api:measure_object_event(events_object, event_name, stat_name)
+ local m = self:measure(stat_name or event_name, "times");
+ local function handler(handlers, _event_name, _event_data)
+ local finished = m();
+ local ret = handlers(_event_name, _event_data);
+ finished();
+ return ret;
+ end
+ return self:hook_object_event(events_object, event_name, handler);
+end
+
+function api:measure_event(event_name, stat_name)
+ return self:measure_object_event((hosts[self.host] or prosody).events.wrappers, event_name, stat_name);
+end
+
+function api:measure_global_event(event_name, stat_name)
+ return self:measure_object_event(prosody.events.wrappers, event_name, stat_name);
end
return api;
diff -r c6b45cac9423 -r 7ec098b68042 core/modulemanager.lua
--- a/core/modulemanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/modulemanager.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -13,31 +13,34 @@
local set = require "util.set";
local new_multitable = require "util.multitable".new;
+local api = require "core.moduleapi"; -- Module API container
local hosts = hosts;
local prosody = prosody;
-local pcall, xpcall = pcall, xpcall;
+local xpcall = xpcall;
local setmetatable, rawget = setmetatable, rawget;
local ipairs, pairs, type, tostring, t_insert = ipairs, pairs, type, tostring, table.insert;
local debug_traceback = debug.traceback;
-local unpack, select = unpack, select;
-pcall = function(f, ...)
+local select = select;
+local unpack = table.unpack or unpack; --luacheck: ignore 113
+local pcall = function(f, ...)
local n = select("#", ...);
local params = {...};
return xpcall(function() return f(unpack(params, 1, n)) end, function(e) return tostring(e).."\n"..debug_traceback(); end);
end
-local autoload_modules = {"presence", "message", "iq", "offline", "c2s", "s2s"};
+local autoload_modules = {prosody.platform, "presence", "message", "iq", "offline", "c2s", "s2s", "s2s_auth_certs"};
local component_inheritable_modules = {"tls", "saslauth", "dialback", "iq", "s2s"};
-- We need this to let modules access the real global namespace
local _G = _G;
-module "modulemanager"
+local _ENV = nil;
-local api = _G.require "core.moduleapi"; -- Module API container
+local load_modules_for_host, load, unload, reload, get_module, get_items;
+local get_modules, is_loaded, module_has_method, call_module_method;
-- [host] = { [module] = module_env }
local modulemap = { ["*"] = {} };
@@ -45,28 +48,28 @@
-- Load modules when a host is activated
function load_modules_for_host(host)
local component = config.get(host, "component_module");
-
+
local global_modules_enabled = config.get("*", "modules_enabled");
local global_modules_disabled = config.get("*", "modules_disabled");
local host_modules_enabled = config.get(host, "modules_enabled");
local host_modules_disabled = config.get(host, "modules_disabled");
-
+
if host_modules_enabled == global_modules_enabled then host_modules_enabled = nil; end
if host_modules_disabled == global_modules_disabled then host_modules_disabled = nil; end
-
+
local global_modules = set.new(autoload_modules) + set.new(global_modules_enabled) - set.new(global_modules_disabled);
if component then
global_modules = set.intersection(set.new(component_inheritable_modules), global_modules);
end
local modules = (global_modules + set.new(host_modules_enabled)) - set.new(host_modules_disabled);
-
+
-- COMPAT w/ pre 0.8
if modules:contains("console") then
log("error", "The mod_console plugin has been renamed to mod_admin_telnet. Please update your config.");
modules:remove("console");
modules:add("admin_telnet");
end
-
+
if component then
load(host, component);
end
@@ -84,18 +87,18 @@
local function do_unload_module(host, name)
local mod = get_module(host, name);
if not mod then return nil, "module-not-loaded"; end
-
+
if module_has_method(mod, "unload") then
local ok, err = call_module_method(mod, "unload");
if (not ok) and err then
log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
end
end
-
+
for object, event, handler in mod.module.event_handlers:iter(nil, nil, nil) do
object.remove_handler(event, handler);
end
-
+
if mod.module.items then -- remove items
local events = (host == "*" and prosody.events) or hosts[host].events;
for key,t in pairs(mod.module.items) do
@@ -117,13 +120,15 @@
elseif not hosts[host] and host ~= "*"then
return nil, "unknown-host";
end
-
+
if not modulemap[host] then
modulemap[host] = hosts[host].modules;
end
-
+
if modulemap[host][module_name] then
- log("debug", "%s is already loaded for %s, so not loading again", module_name, host);
+ if not modulemap["*"][module_name] then
+ log("debug", "%s is already loaded for %s, so not loading again", module_name, host);
+ end
return nil, "module-already-loaded";
elseif modulemap["*"][module_name] then
local mod = modulemap["*"][module_name];
@@ -131,7 +136,7 @@
local _log = logger.init(host..":"..module_name);
local host_module_api = setmetatable({
host = host, event_handlers = new_multitable(), items = {};
- _log = _log, log = function (self, ...) return _log(...); end;
+ _log = _log, log = function (self, ...) return _log(...); end; --luacheck: ignore 212/self
},{
__index = modulemap["*"][module_name].module;
});
@@ -147,18 +152,19 @@
end
return nil, "global-module-already-loaded";
end
-
+
local _log = logger.init(host..":"..module_name);
local api_instance = setmetatable({ name = module_name, host = host,
- _log = _log, log = function (self, ...) return _log(...); end, event_handlers = new_multitable(),
- reloading = not not state, saved_state = state~=true and state or nil }
+ _log = _log, log = function (self, ...) return _log(...); end, --luacheck: ignore 212/self
+ event_handlers = new_multitable(), reloading = not not state,
+ saved_state = state~=true and state or nil }
, { __index = api });
local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
api_instance.environment = pluginenv;
-
+
local mod, err = pluginloader.load_code(module_name, nil, pluginenv);
if not mod then
log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil");
@@ -316,4 +322,15 @@
end
end
-return _M;
+return {
+ load_modules_for_host = load_modules_for_host;
+ load = load;
+ unload = unload;
+ reload = reload;
+ get_module = get_module;
+ get_items = get_items;
+ get_modules = get_modules;
+ is_loaded = is_loaded;
+ module_has_method = module_has_method;
+ call_module_method = call_module_method;
+};
diff -r c6b45cac9423 -r 7ec098b68042 core/portmanager.lua
--- a/core/portmanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/portmanager.lua Wed May 30 21:55:09 2018 +0100
@@ -9,12 +9,12 @@
local table = table;
local setmetatable, rawset, rawget = setmetatable, rawset, rawget;
-local type, tonumber, tostring, ipairs, pairs = type, tonumber, tostring, ipairs, pairs;
+local type, tonumber, tostring, ipairs = type, tonumber, tostring, ipairs;
local prosody = prosody;
local fire_event = prosody.events.fire_event;
-module "portmanager";
+local _ENV = nil;
--- Config
@@ -29,7 +29,7 @@
table.insert(default_local_interfaces, "::1");
end
-local default_mode = config.get("*", "network_default_read_size") or "*a";
+local default_mode = config.get("*", "network_default_read_size") or 4096;
--- Private state
@@ -41,7 +41,7 @@
--- Private helpers
-local function error_to_friendly_message(service_name, port, err)
+local function error_to_friendly_message(service_name, port, err) --luacheck: ignore 212/service_name
local friendly_message = err;
if err:match(" in use") then
-- FIXME: Use service_name here
@@ -63,33 +63,14 @@
return friendly_message;
end
-prosody.events.add_handler("item-added/net-provider", function (event)
- local item = event.item;
- register_service(item.name, item);
-end);
-prosody.events.add_handler("item-removed/net-provider", function (event)
- local item = event.item;
- unregister_service(item.name, item);
-end);
-
-local function duplicate_ssl_config(ssl_config)
- local ssl_config = type(ssl_config) == "table" and ssl_config or {};
-
- local _config = {};
- for k, v in pairs(ssl_config) do
- _config[k] = v;
- end
- return _config;
-end
-
--- Public API
-function activate(service_name)
+local function activate(service_name)
local service_info = services[service_name][1];
if not service_info then
return nil, "Unknown service: "..service_name;
end
-
+
local listener = service_info.listener;
local config_prefix = (service_info.config_prefix or service_name).."_";
@@ -105,7 +86,7 @@
or listener.default_interface -- COMPAT w/pre0.9
or default_interfaces
bind_interfaces = set.new(type(bind_interfaces)~="table" and {bind_interfaces} or bind_interfaces);
-
+
local bind_ports = config.get("*", config_prefix.."ports")
or service_info.default_ports
or {service_info.default_port
@@ -115,45 +96,39 @@
local mode, ssl = listener.default_mode or default_mode;
local hooked_ports = {};
-
+
for interface in bind_interfaces do
for port in bind_ports do
local port_number = tonumber(port);
if not port_number then
log("error", "Invalid port number specified for service '%s': %s", service_info.name, tostring(port));
elseif #active_services:search(nil, interface, port_number) > 0 then
- log("error", "Multiple services configured to listen on the same port ([%s]:%d): %s, %s", interface, port, active_services:search(nil, interface, port)[1][1].service.name or "", service_name or "");
+ log("error", "Multiple services configured to listen on the same port ([%s]:%d): %s, %s", interface, port,
+ active_services:search(nil, interface, port)[1][1].service.name or "", service_name or "");
else
local err;
-- Create SSL context for this service/port
if service_info.encryption == "ssl" then
- local ssl_config = duplicate_ssl_config((config.get("*", config_prefix.."ssl") and config.get("*", config_prefix.."ssl")[interface])
- or (config.get("*", config_prefix.."ssl") and config.get("*", config_prefix.."ssl")[port])
- or config.get("*", config_prefix.."ssl")
- or (config.get("*", "ssl") and config.get("*", "ssl")[interface])
- or (config.get("*", "ssl") and config.get("*", "ssl")[port])
- or config.get("*", "ssl"));
- -- add default entries for, or override ssl configuration
- if ssl_config and service_info.ssl_config then
- for key, value in pairs(service_info.ssl_config) do
- if not service_info.ssl_config_override and not ssl_config[key] then
- ssl_config[key] = value;
- elseif service_info.ssl_config_override then
- ssl_config[key] = value;
- end
- end
- end
-
- ssl, err = certmanager.create_context(service_info.name.." port "..port, "server", ssl_config);
+ local global_ssl_config = config.get("*", "ssl") or {};
+ local prefix_ssl_config = config.get("*", config_prefix.."ssl") or global_ssl_config;
+ ssl, err = certmanager.create_context(service_info.name.." port "..port, "server",
+ prefix_ssl_config[interface],
+ prefix_ssl_config[port],
+ prefix_ssl_config,
+ service_info.ssl_config or {},
+ global_ssl_config[interface],
+ global_ssl_config[port]);
if not ssl then
- log("error", "Error binding encrypted port for %s: %s", service_info.name, error_to_friendly_message(service_name, port_number, err) or "unknown error");
+ log("error", "Error binding encrypted port for %s: %s", service_info.name,
+ error_to_friendly_message(service_name, port_number, err) or "unknown error");
end
end
if not err then
-- Start listening on interface+port
local handler, err = server.addserver(interface, port_number, listener, mode, ssl);
if not handler then
- log("error", "Failed to open server port %d on %s, %s", port_number, interface, error_to_friendly_message(service_name, port_number, err));
+ log("error", "Failed to open server port %d on %s, %s", port_number, interface,
+ error_to_friendly_message(service_name, port_number, err));
else
table.insert(hooked_ports, "["..interface.."]:"..port_number);
log("debug", "Added listening service %s to [%s]:%d", service_name, interface, port_number);
@@ -166,12 +141,15 @@
end
end
end
- log("info", "Activated service '%s' on %s", service_name, #hooked_ports == 0 and "no ports" or table.concat(hooked_ports, ", "));
+ log("info", "Activated service '%s' on %s", service_name,
+ #hooked_ports == 0 and "no ports" or table.concat(hooked_ports, ", "));
return true;
end
-function deactivate(service_name, service_info)
- for name, interface, port, n, active_service
+local close; -- forward declaration
+
+local function deactivate(service_name, service_info)
+ for name, interface, port, n, active_service --luacheck: ignore 213/name 213/n
in active_services:iter(service_name or service_info and service_info.name, nil, nil, nil) do
if service_info == nil or active_service.service == service_info then
close(interface, port);
@@ -180,7 +158,7 @@
log("info", "Deactivated service '%s'", service_name or service_info.name);
end
-function register_service(service_name, service_info)
+local function register_service(service_name, service_info)
table.insert(services[service_name], service_info);
if not active_services:get(service_name) then
@@ -190,12 +168,12 @@
log("error", "Failed to activate service '%s': %s", service_name, err or "unknown error");
end
end
-
+
fire_event("service-added", { name = service_name, service = service_info });
return true;
end
-function unregister_service(service_name, service_info)
+local function unregister_service(service_name, service_info)
log("debug", "Unregistering service: %s", service_name);
local service_info_list = services[service_name];
for i, service in ipairs(service_info_list) do
@@ -210,12 +188,14 @@
fire_event("service-removed", { name = service_name, service = service_info });
end
+local get_service_at -- forward declaration
+
function close(interface, port)
- local service, server = get_service_at(interface, port);
+ local service, service_server = get_service_at(interface, port);
if not service then
return false, "port-not-open";
end
- server:close();
+ service_server:close();
active_services:remove(service.name, interface, port);
log("debug", "Removed listening service %s from [%s]:%d", service.name, interface, port);
return true;
@@ -226,16 +206,37 @@
return data.service, data.server;
end
-function get_service(service_name)
+local function get_service(service_name)
return (services[service_name] or {})[1];
end
-function get_active_services(...)
+local function get_active_services()
return active_services;
end
-function get_registered_services()
+local function get_registered_services()
return services;
end
-return _M;
+-- Event handlers
+
+prosody.events.add_handler("item-added/net-provider", function (event)
+ local item = event.item;
+ register_service(item.name, item);
+end);
+prosody.events.add_handler("item-removed/net-provider", function (event)
+ local item = event.item;
+ unregister_service(item.name, item);
+end);
+
+return {
+ activate = activate;
+ deactivate = deactivate;
+ register_service = register_service;
+ unregister_service = unregister_service;
+ close = close;
+ get_service_at = get_service_at;
+ get_service = get_service;
+ get_active_services = get_active_services;
+ get_registered_services = get_registered_services;
+};
diff -r c6b45cac9423 -r 7ec098b68042 core/rostermanager.lua
--- a/core/rostermanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/rostermanager.lua Wed May 30 21:55:09 2018 +0100
@@ -1,11 +1,11 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-
+-- luacheck: globals prosody.bare_sessions.?.roster
@@ -13,21 +13,24 @@
local pairs = pairs;
local tostring = tostring;
+local type = type;
local hosts = hosts;
-local bare_sessions = bare_sessions;
+local bare_sessions = prosody.bare_sessions;
-local datamanager = require "util.datamanager"
local um_user_exists = require "core.usermanager".user_exists;
local st = require "util.stanza";
+local storagemanager = require "core.storagemanager";
-module "rostermanager"
+local _ENV = nil;
-function add_to_roster(session, jid, item)
+local save_roster; -- forward declaration
+
+local function add_to_roster(session, jid, item)
if session.roster then
local old_item = session.roster[jid];
session.roster[jid] = item;
- if save_roster(session.username, session.host) then
+ if save_roster(session.username, session.host, nil, jid) then
return true;
else
session.roster[jid] = old_item;
@@ -38,11 +41,11 @@
end
end
-function remove_from_roster(session, jid)
+local function remove_from_roster(session, jid)
if session.roster then
local old_item = session.roster[jid];
session.roster[jid] = nil;
- if save_roster(session.username, session.host) then
+ if save_roster(session.username, session.host, nil, jid) then
return true;
else
session.roster[jid] = old_item;
@@ -53,8 +56,8 @@
end
end
-function roster_push(username, host, jid)
- local roster = jid and jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
+local function roster_push(username, host, jid)
+ local roster = jid and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
if roster then
local item = hosts[host].sessions[username].roster[jid];
local stanza = st.iq({type="set"});
@@ -72,14 +75,28 @@
-- stanza ready
for _, session in pairs(hosts[host].sessions[username].sessions) do
if session.interested then
- -- FIXME do we need to set stanza.attr.to?
session.send(stanza);
end
end
end
end
-function load_roster(username, host)
+local function roster_metadata(roster, err)
+ local metadata = roster[false];
+ if not metadata then
+ metadata = { broken = err or nil };
+ roster[false] = metadata;
+ end
+ if roster.pending and type(roster.pending.subscription) ~= "string" then
+ metadata.pending = roster.pending;
+ roster.pending = nil;
+ elseif not metadata.pending then
+ metadata.pending = {};
+ end
+ return metadata;
+end
+
+local function load_roster(username, host)
local jid = username.."@"..host;
log("debug", "load_roster: asked for: %s", jid);
local user = bare_sessions[jid];
@@ -87,31 +104,43 @@
if user then
roster = user.roster;
if roster then return roster; end
- log("debug", "load_roster: loading for new user: %s@%s", username, host);
+ log("debug", "load_roster: loading for new user: %s", jid);
else -- Attempt to load roster for non-loaded user
- log("debug", "load_roster: loading for offline user: %s@%s", username, host);
+ log("debug", "load_roster: loading for offline user: %s", jid);
end
- local data, err = datamanager.load(username, host, "roster");
+ local roster_store = storagemanager.open(host, "roster", "keyval");
+ local data, err = roster_store:get(username);
roster = data or {};
if user then user.roster = roster; end
- if not roster[false] then roster[false] = { broken = err or nil }; end
+ local legacy_pending = roster.pending and type(roster.pending.subscription) ~= "string";
+ roster_metadata(roster, err);
+ if legacy_pending then
+ -- Due to map store use, we need to manually delete this entry
+ log("debug", "Removing legacy 'pending' entry");
+ if not save_roster(username, host, roster, "pending") then
+ log("warn", "Could not remove legacy 'pendig' entry");
+ end
+ end
if roster[jid] then
roster[jid] = nil;
- log("warn", "roster for %s has a self-contact", jid);
+ log("debug", "Roster for %s had a self-contact, removing", jid);
+ if not save_roster(username, host, roster, jid) then
+ log("warn", "Could not remove self-contact from roster for %s", jid);
+ end
end
if not err then
- hosts[host].events.fire_event("roster-load", username, host, roster);
+ hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster });
end
return roster, err;
end
-function save_roster(username, host, roster)
+function save_roster(username, host, roster, jid)
if not um_user_exists(username, host) then
log("debug", "not saving roster for %s@%s: the user doesn't exist", username, host);
return nil;
end
- log("debug", "save_roster: saving roster for %s@%s", username, host);
+ log("debug", "save_roster: saving roster for %s@%s, (%s)", username, host, jid or "all contacts");
if not roster then
roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
--if not roster then
@@ -120,22 +149,24 @@
--end
end
if roster then
- local metadata = roster[false];
- if not metadata then
- metadata = {};
- roster[false] = metadata;
- end
+ local metadata = roster_metadata(roster);
if metadata.version ~= true then
metadata.version = (metadata.version or 0) + 1;
end
- if roster[false].broken then return nil, "Not saving broken roster" end
- return datamanager.store(username, host, "roster", roster);
+ if metadata.broken then return nil, "Not saving broken roster" end
+ if jid == nil then
+ local roster_store = storagemanager.open(host, "roster", "keyval");
+ return roster_store:set(username, roster);
+ else
+ local roster_store = storagemanager.open(host, "roster", "map");
+ return roster_store:set_keys(username, { [false] = metadata, [jid] = roster[jid] or roster_store.remove });
+ end
end
log("warn", "save_roster: user had no roster to save");
return nil;
end
-function process_inbound_subscription_approval(username, host, jid)
+local function process_inbound_subscription_approval(username, host, jid)
local roster = load_roster(username, host);
local item = roster[jid];
if item and item.ask then
@@ -145,11 +176,13 @@
item.subscription = "both";
end
item.ask = nil;
- return save_roster(username, host, roster);
+ return save_roster(username, host, roster, jid);
end
end
-function process_inbound_subscription_cancellation(username, host, jid)
+local is_contact_pending_out -- forward declaration
+
+local function process_inbound_subscription_cancellation(username, host, jid)
local roster = load_roster(username, host);
local item = roster[jid];
local changed = nil;
@@ -167,16 +200,18 @@
end
end
if changed then
- return save_roster(username, host, roster);
+ return save_roster(username, host, roster, jid);
end
end
-function process_inbound_unsubscribe(username, host, jid)
+local is_contact_pending_in -- forward declaration
+
+local function process_inbound_unsubscribe(username, host, jid)
local roster = load_roster(username, host);
local item = roster[jid];
local changed = nil;
if is_contact_pending_in(username, host, jid) then
- roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
+ roster[false].pending[jid] = nil;
changed = true;
end
if item then
@@ -189,7 +224,7 @@
end
end
if changed then
- return save_roster(username, host, roster);
+ return save_roster(username, host, roster, jid);
end
end
@@ -198,19 +233,19 @@
local item = user and (user.roster[jidB] or { subscription = "none" });
return item and item.subscription;
end
-function is_contact_subscribed(username, host, jid)
+local function is_contact_subscribed(username, host, jid)
do
local selfjid = username.."@"..host;
- local subscription = _get_online_roster_subscription(selfjid, jid);
- if subscription then return (subscription == "both" or subscription == "from"); end
- local subscription = _get_online_roster_subscription(jid, selfjid);
- if subscription then return (subscription == "both" or subscription == "to"); end
+ local user_subscription = _get_online_roster_subscription(selfjid, jid);
+ if user_subscription then return (user_subscription == "both" or user_subscription == "from"); end
+ local contact_subscription = _get_online_roster_subscription(jid, selfjid);
+ if contact_subscription then return (contact_subscription == "both" or contact_subscription == "to"); end
end
local roster, err = load_roster(username, host);
local item = roster[jid];
return item and (item.subscription == "from" or item.subscription == "both"), err;
end
-function is_user_subscribed(username, host, jid)
+local function is_user_subscribed(username, host, jid)
do
local selfjid = username.."@"..host;
local user_subscription = _get_online_roster_subscription(selfjid, jid);
@@ -225,24 +260,23 @@
function is_contact_pending_in(username, host, jid)
local roster = load_roster(username, host);
- return roster.pending and roster.pending[jid];
+ return roster[false].pending[jid];
end
-function set_contact_pending_in(username, host, jid, pending)
+local function set_contact_pending_in(username, host, jid)
local roster = load_roster(username, host);
local item = roster[jid];
if item and (item.subscription == "from" or item.subscription == "both") then
return; -- false
end
- if not roster.pending then roster.pending = {}; end
- roster.pending[jid] = true;
- return save_roster(username, host, roster);
+ roster[false].pending[jid] = true;
+ return save_roster(username, host, roster, jid);
end
function is_contact_pending_out(username, host, jid)
local roster = load_roster(username, host);
local item = roster[jid];
return item and item.ask;
end
-function set_contact_pending_out(username, host, jid) -- subscribe
+local function set_contact_pending_out(username, host, jid) -- subscribe
local roster = load_roster(username, host);
local item = roster[jid];
if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
@@ -254,9 +288,9 @@
end
item.ask = "subscribe";
log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid);
- return save_roster(username, host, roster);
+ return save_roster(username, host, roster, jid);
end
-function unsubscribe(username, host, jid)
+local function unsubscribe(username, host, jid)
local roster = load_roster(username, host);
local item = roster[jid];
if not item then return false; end
@@ -269,9 +303,9 @@
elseif item.subscription == "to" then
item.subscription = "none";
end
- return save_roster(username, host, roster);
+ return save_roster(username, host, roster, jid);
end
-function subscribed(username, host, jid)
+local function subscribed(username, host, jid)
if is_contact_pending_in(username, host, jid) then
local roster = load_roster(username, host);
local item = roster[jid];
@@ -284,38 +318,37 @@
else -- subscription == to
item.subscription = "both";
end
- roster.pending[jid] = nil;
- -- TODO maybe remove roster.pending if empty
- return save_roster(username, host, roster);
+ roster[false].pending[jid] = nil;
+ return save_roster(username, host, roster, jid);
end -- TODO else implement optional feature pre-approval (ask = subscribed)
end
-function unsubscribed(username, host, jid)
+local function unsubscribed(username, host, jid)
local roster = load_roster(username, host);
local item = roster[jid];
local pending = is_contact_pending_in(username, host, jid);
if pending then
- roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
+ roster[false].pending[jid] = nil;
end
- local subscribed;
+ local is_subscribed;
if item then
if item.subscription == "from" then
item.subscription = "none";
- subscribed = true;
+ is_subscribed = true;
elseif item.subscription == "both" then
item.subscription = "to";
- subscribed = true;
+ is_subscribed = true;
end
end
- local success = (pending or subscribed) and save_roster(username, host, roster);
- return success, pending, subscribed;
+ local success = (pending or is_subscribed) and save_roster(username, host, roster, jid);
+ return success, pending, is_subscribed;
end
-function process_outbound_subscription_request(username, host, jid)
+local function process_outbound_subscription_request(username, host, jid)
local roster = load_roster(username, host);
local item = roster[jid];
if item and (item.subscription == "none" or item.subscription == "from") then
item.ask = "subscribe";
- return save_roster(username, host, roster);
+ return save_roster(username, host, roster, jid);
end
end
@@ -330,4 +363,23 @@
-return _M;
+return {
+ add_to_roster = add_to_roster;
+ remove_from_roster = remove_from_roster;
+ roster_push = roster_push;
+ load_roster = load_roster;
+ save_roster = save_roster;
+ process_inbound_subscription_approval = process_inbound_subscription_approval;
+ process_inbound_subscription_cancellation = process_inbound_subscription_cancellation;
+ process_inbound_unsubscribe = process_inbound_unsubscribe;
+ is_contact_subscribed = is_contact_subscribed;
+ is_user_subscribed = is_user_subscribed;
+ is_contact_pending_in = is_contact_pending_in;
+ set_contact_pending_in = set_contact_pending_in;
+ is_contact_pending_out = is_contact_pending_out;
+ set_contact_pending_out = set_contact_pending_out;
+ unsubscribe = unsubscribe;
+ subscribed = subscribed;
+ unsubscribed = unsubscribed;
+ process_outbound_subscription_request = process_outbound_subscription_request;
+};
diff -r c6b45cac9423 -r 7ec098b68042 core/s2smanager.lua
--- a/core/s2smanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/s2smanager.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -22,16 +22,16 @@
local incoming_s2s = incoming_s2s;
local fire_event = prosody.events.fire_event;
-module "s2smanager"
+local _ENV = nil;
-function new_incoming(conn)
+local function new_incoming(conn)
local session = { conn = conn, type = "s2sin_unauthed", direction = "incoming", hosts = {} };
session.log = logger_init("s2sin"..tostring(session):match("[a-f0-9]+$"));
incoming_s2s[session] = true;
return session;
end
-function new_outgoing(from_host, to_host)
+local function new_outgoing(from_host, to_host)
local host_session = { to_host = to_host, from_host = from_host, host = from_host,
notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
hosts[from_host].s2sout[to_host] = host_session;
@@ -49,11 +49,11 @@
close = function (session)
session.log("debug", "Attempt to close already-closed session");
end;
- filter = function (type, data) return data; end;
+ filter = function (type, data) return data; end; --luacheck: ignore 212/type
}; resting_session.__index = resting_session;
-function retire_session(session, reason)
- local log = session.log or log;
+local function retire_session(session, reason)
+ local log = session.log or log; --luacheck: ignore 431/log
for k in pairs(session) do
if k ~= "log" and k ~= "id" and k ~= "conn" then
session[k] = nil;
@@ -68,17 +68,19 @@
return setmetatable(session, resting_session);
end
-function destroy_session(session, reason)
+local function destroy_session(session, reason)
if session.destroyed then return; end
- (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)..(reason and (": "..reason) or ""));
-
+ (session.log or log)("debug", "Destroying "..tostring(session.direction)
+ .." session "..tostring(session.from_host).."->"..tostring(session.to_host)
+ ..(reason and (": "..reason) or ""));
+
if session.direction == "outgoing" then
hosts[session.from_host].s2sout[session.to_host] = nil;
session:bounce_sendq(reason);
elseif session.direction == "incoming" then
incoming_s2s[session] = nil;
end
-
+
local event_data = { session = session, reason = reason };
if session.type == "s2sout" then
fire_event("s2sout-destroyed", event_data);
@@ -91,9 +93,15 @@
hosts[session.to_host].events.fire_event("s2sin-destroyed", event_data);
end
end
-
+
retire_session(session, reason); -- Clean session until it is GC'd
return true;
end
-return _M;
+return {
+ incoming_s2s = incoming_s2s;
+ new_incoming = new_incoming;
+ new_outgoing = new_outgoing;
+ retire_session = retire_session;
+ destroy_session = destroy_session;
+};
diff -r c6b45cac9423 -r 7ec098b68042 core/sessionmanager.lua
--- a/core/sessionmanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/sessionmanager.lua Wed May 30 21:55:09 2018 +0100
@@ -1,17 +1,18 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
+-- luacheck: globals prosody.full_sessions prosody.bare_sessions
local tostring, setmetatable = tostring, setmetatable;
local pairs, next= pairs, next;
local hosts = hosts;
-local full_sessions = full_sessions;
-local bare_sessions = bare_sessions;
+local full_sessions = prosody.full_sessions;
+local bare_sessions = prosody.bare_sessions;
local logger = require "util.logger";
local log = logger.init("sessionmanager");
@@ -24,9 +25,9 @@
local initialize_filters = require "util.filters".initialize;
local gettime = require "socket".gettime;
-module "sessionmanager"
+local _ENV = nil;
-function new_session(conn)
+local function new_session(conn)
local session = { conn = conn, type = "c2s_unauthed", conntime = gettime() };
local filter = initialize_filters(session);
local w = conn.write;
@@ -39,10 +40,9 @@
if t then
local ret, err = w(conn, t);
if not ret then
- session.log("debug", "Write-error: %s", tostring(err));
- return false;
+ session.log("debug", "Error writing to connection: %s", tostring(err));
+ return false, err;
end
- return true;
end
end
return true;
@@ -50,7 +50,7 @@
session.ip = conn:ip();
local conn_name = "c2s"..tostring(session):match("[a-f0-9]+$");
session.log = logger.init(conn_name);
-
+
return session;
end
@@ -60,11 +60,11 @@
close = function (session)
session.log("debug", "Attempt to close already-closed session");
end;
- filter = function (type, data) return data; end;
+ filter = function (type, data) return data; end; --luacheck: ignore 212/type
}; resting_session.__index = resting_session;
-function retire_session(session)
- local log = session.log or log;
+local function retire_session(session)
+ local log = session.log or log; --luacheck: ignore 431/log
for k in pairs(session) do
if k ~= "log" and k ~= "id" then
session[k] = nil;
@@ -76,22 +76,25 @@
return setmetatable(session, resting_session);
end
-function destroy_session(session, err)
- (session.log or log)("debug", "Destroying session for %s (%s@%s)%s", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)", err and (": "..err) or "");
+local function destroy_session(session, err)
+ (session.log or log)("debug", "Destroying session for %s (%s@%s)%s",
+ session.full_jid or "(unknown)", session.username or "(unknown)",
+ session.host or "(unknown)", err and (": "..err) or "");
+
if session.destroyed then return; end
-
+
-- Remove session/resource from user's session list
if session.full_jid then
local host_session = hosts[session.host];
-
+
-- Allow plugins to prevent session destruction
if host_session.events.fire_event("pre-resource-unbind", {session=session, error=err}) then
return;
end
-
+
host_session.sessions[session.username].sessions[session.resource] = nil;
full_sessions[session.full_jid] = nil;
-
+
if not next(host_session.sessions[session.username].sessions) then
log("debug", "All resources of %s are now offline", session.username);
host_session.sessions[session.username] = nil;
@@ -100,16 +103,16 @@
host_session.events.fire_event("resource-unbind", {session=session, error=err});
end
-
+
retire_session(session);
end
-function make_authenticated(session, username)
+local function make_authenticated(session, username)
username = nodeprep(username);
if not username or #username == 0 then return nil, "Invalid username"; end
session.username = username;
if session.type == "c2s_unauthed" then
- session.type = "c2s";
+ session.type = "c2s_unbound";
end
session.log("info", "Authenticated as %s@%s", username or "(unknown)", session.host or "(unknown)");
return true;
@@ -117,15 +120,25 @@
-- returns true, nil on success
-- returns nil, err_type, err, err_message on failure
-function bind_resource(session, resource)
+local function bind_resource(session, resource)
if not session.username then return nil, "auth", "not-authorized", "Cannot bind resource before authentication"; end
if session.resource then return nil, "cancel", "not-allowed", "Cannot bind multiple resources on a single connection"; end
-- We don't support binding multiple resources
+ local event_payload = { session = session, resource = resource };
+ if hosts[session.host].events.fire_event("pre-resource-bind", event_payload) == false then
+ local err = event_payload.error;
+ if err then return nil, err.type, err.condition, err.text; end
+ return nil, "cancel", "not-allowed";
+ else
+ -- In case a plugin wants to poke at it
+ resource = event_payload.resource;
+ end
+
resource = resourceprep(resource);
resource = resource ~= "" and resource or uuid_generate();
--FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
-
+
if not hosts[session.host].sessions[session.username] then
local sessions = { sessions = {} };
hosts[session.host].sessions[session.username] = sessions;
@@ -162,19 +175,26 @@
end
end
end
-
+
session.resource = resource;
session.full_jid = session.username .. '@' .. session.host .. '/' .. resource;
hosts[session.host].sessions[session.username].sessions[resource] = session;
full_sessions[session.full_jid] = session;
-
+ if session.type == "c2s_unbound" then
+ session.type = "c2s";
+ end
+
local err;
session.roster, err = rm_load_roster(session.username, session.host);
if err then
+ -- FIXME: Why is all this rollback down here, instead of just doing the roster test up above?
full_sessions[session.full_jid] = nil;
hosts[session.host].sessions[session.username].sessions[resource] = nil;
session.full_jid = nil;
session.resource = nil;
+ if session.type == "c2s" then
+ session.type = "c2s_unbound";
+ end
if next(bare_sessions[session.username..'@'..session.host].sessions) == nil then
bare_sessions[session.username..'@'..session.host] = nil;
hosts[session.host].sessions[session.username] = nil;
@@ -182,18 +202,18 @@
session.log("error", "Roster loading failed: %s", err);
return nil, "cancel", "internal-server-error", "Error loading roster";
end
-
+
hosts[session.host].events.fire_event("resource-bind", {session=session});
-
+
return true;
end
-function send_to_available_resources(user, host, stanza)
- local jid = user.."@"..host;
+local function send_to_available_resources(username, host, stanza)
+ local jid = username.."@"..host;
local count = 0;
local user = bare_sessions[jid];
if user then
- for k, session in pairs(user.sessions) do
+ for _, session in pairs(user.sessions) do
if session.presence then
session.send(stanza);
count = count + 1;
@@ -203,12 +223,12 @@
return count;
end
-function send_to_interested_resources(user, host, stanza)
- local jid = user.."@"..host;
+local function send_to_interested_resources(username, host, stanza)
+ local jid = username.."@"..host;
local count = 0;
local user = bare_sessions[jid];
if user then
- for k, session in pairs(user.sessions) do
+ for _, session in pairs(user.sessions) do
if session.interested then
session.send(stanza);
count = count + 1;
@@ -218,4 +238,12 @@
return count;
end
-return _M;
+return {
+ new_session = new_session;
+ retire_session = retire_session;
+ destroy_session = destroy_session;
+ make_authenticated = make_authenticated;
+ bind_resource = bind_resource;
+ send_to_available_resources = send_to_available_resources;
+ send_to_interested_resources = send_to_interested_resources;
+};
diff -r c6b45cac9423 -r 7ec098b68042 core/stanza_router.lua
--- a/core/stanza_router.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/stanza_router.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -30,13 +30,19 @@
deprecated_warning"core_route_stanza";
local valid_stanzas = { message = true, presence = true, iq = true };
-local function handle_unhandled_stanza(host, origin, stanza)
+local function handle_unhandled_stanza(host, origin, stanza) --luacheck: ignore 212/host
local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
if xmlns == "jabber:client" and valid_stanzas[name] then
-- A normal stanza
local st_type = stanza.attr.type;
if st_type == "error" or (name == "iq" and st_type == "result") then
- log("debug", "Discarding %s from %s of type: %s", name, origin_type, st_type or '');
+ if st_type == "error" then
+ local err_type, err_condition, err_message = stanza:get_error();
+ log("debug", "Discarding unhandled error %s (%s, %s) from %s: %s",
+ name, err_type, err_condition or "unknown condition", origin_type, stanza:top_tag());
+ else
+ log("debug", "Discarding %s from %s of type: %s", name, origin_type, st_type or '');
+ end
return;
end
if name == "iq" and (st_type == "get" or st_type == "set") and stanza.tags[1] then
@@ -46,8 +52,9 @@
if origin.send then
origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
end
- elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
- log("warn", "Unhandled %s stream element or stanza: %s; xmlns=%s: %s", origin_type, name, xmlns, tostring(stanza)); -- we didn't handle it
+ else
+ log("warn", "Unhandled %s stream element or stanza: %s; xmlns=%s: %s",
+ origin_type, name, xmlns, tostring(stanza)); -- we didn't handle it
origin:close("unsupported-stanza-type");
end
end
@@ -62,23 +69,18 @@
return handle_unhandled_stanza(origin.host, origin, stanza);
end
if name == "iq" then
- if not stanza.attr.id then stanza.attr.id = ""; end -- COMPAT Jabiru doesn't send the id attribute on roster requests
- if not iq_types[st_type] or ((st_type == "set" or st_type == "get") and (#stanza.tags ~= 1)) then
- origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid IQ type or incorrect number of children"));
+ if not iq_types[st_type] then
+ origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid IQ type"));
+ return;
+ elseif not stanza.attr.id then
+ origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing required 'id' attribute"));
+ return;
+ elseif (st_type == "set" or st_type == "get") and (#stanza.tags ~= 1) then
+ origin.send(st.error_reply(stanza, "modify", "bad-request", "Incorrect number of children for IQ stanza"));
return;
end
end
- if not origin.full_jid
- and not(name == "iq" and st_type == "set" and stanza.tags[1] and stanza.tags[1].name == "bind"
- and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then
- -- authenticated client isn't bound and current stanza is not a bind request
- if stanza.attr.type ~= "result" and stanza.attr.type ~= "error" then
- origin.send(st.error_reply(stanza, "auth", "not-authorized")); -- FIXME maybe allow stanzas to account or server
- end
- return;
- end
-
-- TODO also, stanzas should be returned to their original state before the function ends
stanza.attr.from = origin.full_jid;
end
@@ -138,7 +140,8 @@
if h then
local event;
if xmlns == nil then
- if stanza.name == "iq" and (stanza.attr.type == "set" or stanza.attr.type == "get") then
+ if stanza.name == "iq" and (stanza.attr.type == "set" or stanza.attr.type == "get")
+ and stanza.tags[1] and stanza.tags[1].attr.xmlns then
event = "stanza/iq/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name;
else
event = "stanza/"..stanza.name;
@@ -199,7 +202,7 @@
-- Auto-detect origin if not specified
origin = origin or hosts[from_host];
if not origin then return false; end
-
+
if hosts[host] then
-- old stanza routing code removed
core_post_stanza(origin, stanza);
@@ -211,16 +214,20 @@
else
local xmlns = stanza.attr.xmlns;
stanza.attr.xmlns = nil;
- local routed = host_session.events.fire_event("route/remote", { origin = origin, stanza = stanza, from_host = from_host, to_host = host });
+ local routed = host_session.events.fire_event("route/remote", {
+ origin = origin, stanza = stanza, from_host = from_host, to_host = host });
stanza.attr.xmlns = xmlns; -- reset
if not routed then
log("debug", "... no, just kidding.");
if stanza.attr.type == "error" or (stanza.name == "iq" and stanza.attr.type == "result") then return; end
- core_route_stanza(host_session, st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote domains is not enabled"));
+ core_route_stanza(host_session, st.error_reply(stanza, "cancel", "not-allowed",
+ "Communication with remote domains is not enabled"));
end
end
end
end
+
+--luacheck: ignore 122/prosody
prosody.core_process_stanza = core_process_stanza;
prosody.core_post_stanza = core_post_stanza;
prosody.core_route_stanza = core_route_stanza;
diff -r c6b45cac9423 -r 7ec098b68042 core/statsmanager.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/core/statsmanager.lua Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,117 @@
+
+local config = require "core.configmanager";
+local log = require "util.logger".init("stats");
+local timer = require "util.timer";
+local fire_event = prosody.events.fire_event;
+
+local stats_interval_config = config.get("*", "statistics_interval");
+local stats_interval = tonumber(stats_interval_config);
+if stats_interval_config and not stats_interval then
+ log("error", "Invalid 'statistics_interval' setting, statistics will be disabled");
+end
+
+local stats_provider_name;
+local stats_provider_config = config.get("*", "statistics");
+local stats_provider = stats_provider_config;
+
+if not stats_provider and stats_interval then
+ stats_provider = "internal";
+elseif stats_provider and not stats_interval then
+ stats_interval = 60;
+end
+
+local builtin_providers = {
+ internal = "util.statistics";
+ statsd = "util.statsd";
+};
+
+
+local stats, stats_err = false, nil;
+
+if stats_provider then
+ if stats_provider:sub(1,1) == ":" then
+ stats_provider = stats_provider:sub(2);
+ stats_provider_name = "external "..stats_provider;
+ elseif stats_provider then
+ stats_provider_name = "built-in "..stats_provider;
+ stats_provider = builtin_providers[stats_provider];
+ if not stats_provider then
+ log("error", "Unrecognized statistics provider '%s', statistics will be disabled", stats_provider_config);
+ end
+ end
+
+ local have_stats_provider, stats_lib = pcall(require, stats_provider);
+ if not have_stats_provider then
+ stats, stats_err = nil, stats_lib;
+ else
+ local stats_config = config.get("*", "statistics_config");
+ stats, stats_err = stats_lib.new(stats_config);
+ stats_provider_name = stats_lib._NAME or stats_provider_name;
+ end
+end
+
+if stats == nil then
+ log("error", "Error loading statistics provider '%s': %s", stats_provider, stats_err);
+end
+
+local measure, collect;
+local latest_stats = {};
+local changed_stats = {};
+local stats_extra = {};
+
+if stats then
+ function measure(type, name)
+ local f = assert(stats[type], "unknown stat type: "..type);
+ return f(name);
+ end
+
+ if stats_interval then
+ log("debug", "Statistics enabled using %s provider, collecting every %d seconds", stats_provider_name, stats_interval);
+
+ local mark_collection_start = measure("times", "stats.collection");
+ local mark_processing_start = measure("times", "stats.processing");
+
+ function collect()
+ local mark_collection_done = mark_collection_start();
+ fire_event("stats-update");
+ mark_collection_done();
+
+ if stats.get_stats then
+ changed_stats, stats_extra = {}, {};
+ for stat_name, getter in pairs(stats.get_stats()) do
+ local type, value, extra = getter();
+ local old_value = latest_stats[stat_name];
+ latest_stats[stat_name] = value;
+ if value ~= old_value then
+ changed_stats[stat_name] = value;
+ end
+ if extra then
+ stats_extra[stat_name] = extra;
+ end
+ end
+ local mark_processing_done = mark_processing_start();
+ fire_event("stats-updated", { stats = latest_stats, changed_stats = changed_stats, stats_extra = stats_extra });
+ mark_processing_done();
+ end
+ return stats_interval;
+ end
+ timer.add_task(stats_interval, collect);
+ prosody.events.add_handler("server-started", function () collect() end, -1);
+ else
+ log("debug", "Statistics enabled using %s provider, collection is disabled", stats_provider_name);
+ end
+else
+ log("debug", "Statistics disabled");
+ function measure() return measure; end
+end
+
+
+return {
+ measure = measure;
+ get_stats = function ()
+ return latest_stats, changed_stats, stats_extra;
+ end;
+ get = function (name)
+ return latest_stats[name], stats_extra[name];
+ end;
+};
diff -r c6b45cac9423 -r 7ec098b68042 core/storagemanager.lua
--- a/core/storagemanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/storagemanager.lua Wed May 30 21:55:09 2018 +0100
@@ -1,5 +1,5 @@
-local error, type, pairs = error, type, pairs;
+local type, pairs = type, pairs;
local setmetatable = setmetatable;
local config = require "core.configmanager";
@@ -11,11 +11,10 @@
local prosody = prosody;
-module("storagemanager")
+local _ENV = nil;
local olddm = {}; -- maintain old datamanager, for backwards compatibility
for k,v in pairs(datamanager) do olddm[k] = v; end
-_M.olddm = olddm;
local null_storage_method = function () return false, "no data storage active"; end
local null_storage_driver = setmetatable(
@@ -23,7 +22,7 @@
name = "null",
open = function (self) return self; end
}, {
- __index = function (self, method)
+ __index = function (self, method) --luacheck: ignore 212
return null_storage_method;
end
}
@@ -31,13 +30,13 @@
local stores_available = multitable.new();
-function initialize_host(host)
+local function initialize_host(host)
local host_session = hosts[host];
host_session.events.add_handler("item-added/storage-provider", function (event)
local item = event.item;
stores_available:set(host, item.name, item);
end);
-
+
host_session.events.add_handler("item-removed/storage-provider", function (event)
local item = event.item;
stores_available:set(host, item.name, nil);
@@ -45,7 +44,7 @@
end
prosody.events.add_handler("host-activated", initialize_host, 101);
-function load_driver(host, driver_name)
+local function load_driver(host, driver_name)
if driver_name == "null" then
return null_storage_driver;
end
@@ -58,8 +57,29 @@
return stores_available:get(host, driver_name);
end
-function get_driver(host, store)
- local storage = config.get(host, "storage");
+local function get_storage_config(host)
+ -- COMPAT w/ unreleased Prosody 0.10 and the once-experimental mod_storage_sql2 in peoples' config files
+ local storage_config = config.get(host, "storage");
+ local found_sql2;
+ if storage_config == "sql2" then
+ storage_config, found_sql2 = "sql", true;
+ elseif type(storage_config) == "table" then
+ for store_name, driver_name in pairs(storage_config) do
+ if driver_name == "sql2" then
+ storage_config[store_name] = "sql";
+ found_sql2 = true;
+ end
+ end
+ end
+ if found_sql2 then
+ log("error", "The temporary 'sql2' storage module has now been renamed to 'sql', "
+ .."please update your config file: https://prosody.im/doc/modules/mod_storage_sql2");
+ end
+ return storage_config;
+end
+
+local function get_driver(host, store)
+ local storage = get_storage_config(host);
local driver_name;
local option_type = type(storage);
if option_type == "string" then
@@ -70,7 +90,7 @@
if not driver_name then
driver_name = config.get(host, "default_storage") or "internal";
end
-
+
local driver = load_driver(host, driver_name);
if not driver then
log("warn", "Falling back to null driver for %s storage on %s", store, host);
@@ -80,28 +100,90 @@
return driver, driver_name;
end
+local map_shim_mt = {
+ __index = {
+ get = function(self, username, key)
+ local ret, err = self.keyval_store:get(username);
+ if ret == nil then return nil, err end
+ return ret[key];
+ end;
+ set = function(self, username, key, data)
+ local current, err = self.keyval_store:get(username);
+ if current == nil then
+ if err then
+ return nil, err;
+ else
+ current = {};
+ end
+ end
+ current[key] = data;
+ return self.keyval_store:set(username, current);
+ end;
+ set_keys = function (self, username, keydatas)
+ local current, err = self.keyval_store:get(username);
+ if current == nil then
+ if err then
+ return nil, err;
+ end
+ current = {};
+ end
+ for k,v in pairs(keydatas) do
+ if v == self.remove then v = nil; end
+ current[k] = v;
+ end
+ return self.keyval_store:set(username, current);
+ end;
+ remove = {};
+ };
+}
+
+local open;
+
+local function create_map_shim(host, store)
+ local keyval_store, err = open(host, store, "keyval");
+ if keyval_store == nil then return nil, err end
+ return setmetatable({
+ keyval_store = keyval_store;
+ }, map_shim_mt);
+end
+
function open(host, store, typ)
local driver, driver_name = get_driver(host, store);
local ret, err = driver:open(store, typ);
if not ret then
if err == "unsupported-store" then
- log("debug", "Storage driver %s does not support store %s (%s), falling back to null driver",
- driver_name, store, typ or "");
- ret = null_storage_driver;
- err = nil;
+ if typ == "map" then -- Use shim on top of keyval store
+ log("debug", "map storage driver unavailable, using shim on top of keyval store.");
+ ret, err = create_map_shim(host, store);
+ else
+ log("debug", "Storage driver %s does not support store %s (%s), falling back to null driver",
+ driver_name, store, typ or "");
+ ret, err = null_storage_driver, nil;
+ end
end
end
+ if ret then
+ local event_data = { host = host, store_name = store, store_type = typ, store = ret };
+ hosts[host].events.fire_event("store-opened", event_data);
+ ret, err = event_data.store, event_data.store_err;
+ end
return ret, err;
end
-function purge(user, host)
- local storage = config.get(host, "storage");
+local function purge(user, host)
+ local storage = get_storage_config(host);
if type(storage) == "table" then
-- multiple storage backends in use that we need to purge
local purged = {};
- for store, driver in pairs(storage) do
- if not purged[driver] then
- purged[driver] = get_driver(host, store):purge(user);
+ for store, driver_name in pairs(storage) do
+ if not purged[driver_name] then
+ local driver = get_driver(host, store);
+ if driver.purge then
+ purged[driver_name] = driver:purge(user);
+ else
+ log("warn", "Storage driver %s does not support removing all user data, "
+ .."you may need to delete it manually", driver_name);
+ end
end
end
end
@@ -121,7 +203,7 @@
function datamanager.users(host, datastore, typ)
local driver = open(host, datastore, typ);
if not driver.users then
- return function() log("warn", "storage driver %s does not support listing users", driver.name) end
+ return function() log("warn", "Storage driver %s does not support listing users", driver.name) end
end
return driver:users();
end
@@ -132,4 +214,12 @@
return purge(username, host);
end
-return _M;
+return {
+ initialize_host = initialize_host;
+ load_driver = load_driver;
+ get_driver = get_driver;
+ open = open;
+ purge = purge;
+
+ olddm = olddm;
+};
diff -r c6b45cac9423 -r 7ec098b68042 core/usermanager.lua
--- a/core/usermanager.lua Wed May 30 21:51:15 2018 +0100
+++ b/core/usermanager.lua Wed May 30 21:55:09 2018 +0100
@@ -10,7 +10,6 @@
local log = require "util.logger".init("usermanager");
local type = type;
local ipairs = ipairs;
-local pairs = pairs;
local jid_bare = require "util.jid".bare;
local jid_prep = require "util.jid".prep;
local config = require "core.configmanager";
@@ -24,22 +23,22 @@
local default_provider = "internal_plain";
-module "usermanager"
+local _ENV = nil;
-function new_null_provider()
+local function new_null_provider()
local function dummy() return nil, "method not implemented"; end;
local function dummy_get_sasl_handler() return sasl_new(nil, {}); end
return setmetatable({name = "null", get_sasl_handler = dummy_get_sasl_handler}, {
- __index = function(self, method) return dummy; end
+ __index = function(self, method) return dummy; end --luacheck: ignore 212
});
end
local provider_mt = { __index = new_null_provider() };
-function initialize_host(host)
+local function initialize_host(host)
local host_session = hosts[host];
if host_session.type ~= "local" then return; end
-
+
host_session.events.add_handler("item-added/auth-provider", function (event)
local provider = event.item;
local auth_provider = config.get(host, "authentication") or default_provider;
@@ -51,7 +50,7 @@
host_session.users = setmetatable(provider, provider_mt);
end
if host_session.users ~= nil and host_session.users.name ~= nil then
- log("debug", "host '%s' now set to use user provider '%s'", host, host_session.users.name);
+ log("debug", "Host '%s' now set to use user provider '%s'", host, host_session.users.name);
end
end);
host_session.events.add_handler("item-removed/auth-provider", function (event)
@@ -69,87 +68,102 @@
end;
prosody.events.add_handler("host-activated", initialize_host, 100);
-function test_password(username, host, password)
+local function test_password(username, host, password)
return hosts[host].users.test_password(username, password);
end
-function get_password(username, host)
+local function get_password(username, host)
return hosts[host].users.get_password(username);
end
-function set_password(username, password, host)
- return hosts[host].users.set_password(username, password);
+local function set_password(username, password, host, resource)
+ local ok, err = hosts[host].users.set_password(username, password);
+ if ok then
+ prosody.events.fire_event("user-password-changed", { username = username, host = host, resource = resource });
+ end
+ return ok, err;
end
-function user_exists(username, host)
+local function user_exists(username, host)
+ if hosts[host].sessions[username] then return true; end
return hosts[host].users.user_exists(username);
end
-function create_user(username, password, host)
+local function create_user(username, password, host)
return hosts[host].users.create_user(username, password);
end
-function delete_user(username, host)
+local function delete_user(username, host)
local ok, err = hosts[host].users.delete_user(username);
if not ok then return nil, err; end
prosody.events.fire_event("user-deleted", { username = username, host = host });
return storagemanager.purge(username, host);
end
-function users(host)
+local function users(host)
return hosts[host].users.users();
end
-function get_sasl_handler(host, session)
+local function get_sasl_handler(host, session)
return hosts[host].users.get_sasl_handler(session);
end
-function get_provider(host)
+local function get_provider(host)
return hosts[host].users;
end
-function is_admin(jid, host)
+local function is_admin(jid, host)
if host and not hosts[host] then return false; end
if type(jid) ~= "string" then return false; end
- local is_admin;
jid = jid_bare(jid);
host = host or "*";
-
+
local host_admins = config.get(host, "admins");
local global_admins = config.get("*", "admins");
-
+
if host_admins and host_admins ~= global_admins then
if type(host_admins) == "table" then
for _,admin in ipairs(host_admins) do
if jid_prep(admin) == jid then
- is_admin = true;
- break;
+ return true;
end
end
elseif host_admins then
log("error", "Option 'admins' for host '%s' is not a list", host);
end
end
-
- if not is_admin and global_admins then
+
+ if global_admins then
if type(global_admins) == "table" then
for _,admin in ipairs(global_admins) do
if jid_prep(admin) == jid then
- is_admin = true;
- break;
+ return true;
end
end
elseif global_admins then
log("error", "Global option 'admins' is not a list");
end
end
-
+
-- Still not an admin, check with auth provider
- if not is_admin and host ~= "*" and hosts[host].users and hosts[host].users.is_admin then
- is_admin = hosts[host].users.is_admin(jid);
+ if host ~= "*" and hosts[host].users and hosts[host].users.is_admin then
+ return hosts[host].users.is_admin(jid);
end
- return is_admin or false;
+ return false;
end
-return _M;
+return {
+ new_null_provider = new_null_provider;
+ initialize_host = initialize_host;
+ test_password = test_password;
+ get_password = get_password;
+ set_password = set_password;
+ user_exists = user_exists;
+ create_user = create_user;
+ delete_user = delete_user;
+ users = users;
+ get_sasl_handler = get_sasl_handler;
+ get_provider = get_provider;
+ is_admin = is_admin;
+};
diff -r c6b45cac9423 -r 7ec098b68042 doc/storage.tld
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/storage.tld Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,61 @@
+-- Storage Interface API Description
+--
+-- This is written as a TypedLua description
+
+-- Key-Value stores (the default)
+
+interface keyval_store
+ get : ( self, string? ) -> (any) | (nil, string)
+ set : ( self, string?, any ) -> (boolean) | (nil, string)
+end
+
+-- Map stores (key-key-value stores)
+
+interface map_store
+ get : ( self, string?, any ) -> (any) | (nil, string)
+ set : ( self, string?, any, any ) -> (boolean) | (nil, string)
+ set_keys : ( self, string?, { any : any }) -> (boolean) | (nil, string)
+ remove : {}
+end
+
+-- Archive stores
+
+typealias archive_query = {
+ "start" : number?, -- timestamp
+ "end" : number?, -- timestamp
+ "with" : string?,
+ "after" : string?, -- archive id
+ "before" : string?, -- archive id
+ "total" : boolean?,
+}
+
+interface archive_store
+ -- Optional set of capabilities
+ caps : {
+ -- Optional total count of matching items returned as second return value from :find()
+ "total" : boolean?,
+ }?
+
+ -- Add to the archive
+ append : ( self, string?, string?, any, number?, string? ) -> (string) | (nil, string)
+
+ -- Iterate over archive
+ find : ( self, string?, archive_query? ) -> ( () -> ( string, any, number?, string? ), integer? )
+
+ -- Removal of items. API like find. Optional?
+ delete : ( self, string?, archive_query? ) -> (boolean) | (number) | (nil, string)
+
+ -- Array of dates which do have messages (Optional?)
+ dates : ( self, string? ) -> ({ string }) | (nil, string)
+end
+
+-- This represents moduleapi
+interface module
+ -- If the first string is omitted then the name of the module is used
+ -- The second string is one of "keyval" (default), "map" or "archive"
+ open_store : (self, string?, string?) -> (keyval_store) | (map_store) | (archive_store) | (nil, string)
+
+ -- Other module methods omitted
+end
+
+module : module
diff -r c6b45cac9423 -r 7ec098b68042 fallbacks/bit.lua
--- a/fallbacks/bit.lua Wed May 30 21:51:15 2018 +0100
+++ b/fallbacks/bit.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -68,7 +68,7 @@
end
function rshift(a, i)
local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
- for n = 1,i do _rshift1(t); end
+ for _ = 1, i do _rshift1(t); end
return setmetatable(t, bit_mt);
end
local function _arshift1(t)
@@ -81,7 +81,7 @@
end
function arshift(a, i)
local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
- for n = 1,i do _arshift1(t); end
+ for _ = 1, i do _arshift1(t); end
return setmetatable(t, bit_mt);
end
local function _lshift1(t)
@@ -94,7 +94,7 @@
end
function lshift(a, i)
local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
- for n = 1,i do _lshift1(t); end
+ for _ = 1, i do _lshift1(t); end
return setmetatable(t, bit_mt);
end
diff -r c6b45cac9423 -r 7ec098b68042 fallbacks/lxp.lua
--- a/fallbacks/lxp.lua Wed May 30 21:51:15 2018 +0100
+++ b/fallbacks/lxp.lua Wed May 30 21:55:09 2018 +0100
@@ -61,7 +61,7 @@
while #data == 0 do data = coroutine.yield(); end
return data:sub(1,1);
end
-
+
local ns = { xml = "http://www.w3.org/XML/1998/namespace" };
ns.__index = ns;
local function apply_ns(name, dodefault)
@@ -100,7 +100,7 @@
ns = getmetatable(ns);
return tag;
end
-
+
while true do
if peek() == "<" then
local elem = read_until(">"):sub(2,-2);
diff -r c6b45cac9423 -r 7ec098b68042 man/Makefile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/man/Makefile Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,4 @@
+all: prosodyctl.man
+
+%.man: %.markdown
+ pandoc -s -t man -o $@ $^
diff -r c6b45cac9423 -r 7ec098b68042 man/prosodyctl.man
--- a/man/prosodyctl.man Wed May 30 21:51:15 2018 +0100
+++ b/man/prosodyctl.man Wed May 30 21:55:09 2018 +0100
@@ -1,83 +1,188 @@
-.TH PROSODYCTL 1 "2009-07-02"
-
+.\" Automatically generated by Pandoc 1.19.2.1
+.\"
+.TH "PROSODYCTL" "1" "2017\-09\-02" "" ""
+.hy
.SH NAME
+.PP
prosodyctl \- Manage a Prosody XMPP server
-
.SH SYNOPSIS
-\fBprosodyctl\fP \fIcommand\fP [\fI--help\fP]
-
+.IP
+.nf
+\f[C]
+prosodyctl\ command\ [\-\-help]
+\f[]
+.fi
.SH DESCRIPTION
-\fBprosodyctl\fP is the control tool for the Prosody XMPP server. It may be
-used to control the server daemon and manage users.
-
-\fBprosodyctl\fP needs to be executed with sufficient privileges to perform
-its commands. This typically means executing \fBprosodyctl\fP as the root user.
-If a user named "prosody" is found then \fBprosodyctl\fP will change to that
+.PP
+prosodyctl is the control tool for the Prosody XMPP server.
+It may be used to control the server daemon and manage users.
+.PP
+prosodyctl needs to be executed with sufficient privileges to perform
+its commands.
+This typically means executing prosodyctl as the root user.
+If a user named "prosody" is found then prosodyctl will change to that
user before executing its commands.
-
.SH COMMANDS
.SS User Management
-In the following commands users are identified by a Jabber ID, \fIjid\fP, of the
-usual form: user@domain.
-
-.IP "\fBadduser\fP \fIjid\fP"
-Adds a user with Jabber ID, \fIjid\fP, to the server. You will be
-prompted to enter the user's password.
-
-.IP "\fBpasswd\fP \fIjid\fP"
-Changes the password of an existing user with Jabber ID, \fIjid\fP. You will be
-prompted to enter the user's new password.
-
-.IP "\fBdeluser\fP \fIjid\fP"
-Deletes an existing user with Jabber ID, \fIjid\fP, from the server.
-
+.PP
+In the following commands users are identified by a Jabber ID, jid, of
+the usual form: user\@domain.
+.TP
+.B adduser jid
+Adds a user with Jabber ID, jid, to the server.
+You will be prompted to enter the user\[aq]s password.
+.RS
+.RE
+.TP
+.B passwd jid
+Changes the password of an existing user with Jabber ID, jid.
+You will be prompted to enter the user\[aq]s new password.
+.RS
+.RE
+.TP
+.B deluser jid
+Deletes an existing user with Jabber ID, jid, from the server.
+.RS
+.RE
.SS Daemon Management
-Although \fBprosodyctl\fP has commands to manage the \fBprosody\fP daemon it is
-recommended that you utilize your distributions daemon management features if
-you attained Prosody through a package.
-
-To perform daemon control commands \fBprosodyctl\fP needs a \fIpidfile\fP value
-specified in \fI/etc/prosody/prosody.cfg.lua\fP. Failure to do so will cause
-\fBprosodyctl\fP to complain.
-
-.IP \fBstart\fP
-Starts the \fBprosody\fP server daemon. If run as root \fBprosodyctl\fP will
-attempt to change to a user named "prosody" before executing. This operation
-will block for up to five seconds to wait for the server to execute.
-
-.IP \fBstop\fP
-Stops the \fBprosody\fP server daemon. This operation will block for up to five
-seconds to wait for the server to stop executing.
+.PP
+Although prosodyctl has commands to manage the prosody daemon it is
+recommended that you utilize your distributions daemon management
+features if you attained Prosody through a package.
+.PP
+To perform daemon control commands prosodyctl needs a pidfile value
+specified in \f[C]/etc/prosody/prosody.cfg.lua\f[].
+Failure to do so will cause prosodyctl to complain.
+.TP
+.B start
+Starts the prosody server daemon.
+If run as root prosodyctl will attempt to change to a user named
+"prosody" before executing.
+This operation will block for up to five seconds to wait for the server
+to execute.
+.RS
+.RE
+.TP
+.B stop
+Stops the prosody server daemon.
+This operation will block for up to five seconds to wait for the server
+to stop executing.
+.RS
+.RE
+.TP
+.B restart
+Restarts the prosody server daemon.
+Equivalent to running prosodyctl stop followed by prosodyctl start.
+.RS
+.RE
+.TP
+.B reload
+Signals the prosody server daemon to reload configuration and reopen log
+files.
+.RS
+.RE
+.TP
+.B status
+Prints the current execution status of the prosody server daemon.
+.RS
+.RE
+.SS Certificates
+.PP
+prosodyctl can create self\-signed certificates, certificate requests
+and private keys for use with Prosody.
+Commands are of the form \f[C]prosodyctl\ cert\ subcommand\f[].
+Commands take a list of hosts to be included in the certificate.
+.TP
+.B \f[C]request\ hosts\f[]
+Create a certificate request (CSR) file for submission to a certificate
+authority.
+Multiple hosts can be given, sub\-domains are automatically included.
+.RS
+.RE
+.TP
+.B \f[C]generate\ hosts\f[]
+Generate a self\-signed certificate.
+.RS
+.RE
+.TP
+.B \f[C]key\ host\ [size]\f[]
+Generate a private key of \[aq]size\[aq] bits (defaults to 2048).
+Invoked automatically by \[aq]request\[aq] and \[aq]generate\[aq] if
+needed.
+.RS
+.RE
+.TP
+.B \f[C]config\ hosts\f[]
+Produce a config file for the list of hosts.
+Invoked automatically by \[aq]request\[aq] and \[aq]generate\[aq] if
+needed.
+.RS
+.RE
+.TP
+.B \f[C]import\ hosts\ paths\f[]
+Copy certificates for hosts into the certificate path and reload
+prosody.
+.RS
+.RE
+.SS Debugging
+.PP
+prosodyctl can also show some information about the environment,
+dependencies and such to aid in debugging.
+.TP
+.B \f[C]about\f[]
+Shows environment, various paths used by Prosody and installed
+dependencies.
+.RS
+.RE
+.TP
+.B \f[C]check\ [what]\f[]
+Performs various sanity checks on the configuration, DNS setup and
+configured TLS certificates.
+\f[C]what\f[] can be one of \f[C]config\f[], \f[C]dns\f[] and
+\f[C]certs\f[] to run only that check.
+.RS
+.RE
+.SS Ejabberd Compatibility
+.PP
+ejabberd is another XMPP server which provides a comparable control
+tool, ejabberdctl, to control its server\[aq]s operations.
+prosodyctl implements some commands which are compatible with
+ejabberdctl.
+For details of how these commands work you should see ejabberdctl(8).
+.IP
+.nf
+\f[C]
+register\ user\ server\ password
-.IP \fBrestart\fP
-Restarts the \fBprosody\fP server daemon. Equivalent to running \fBprosodyctl
-stop\fP followed by \fBprosodyctl start\fP.
-
-.IP \fBstatus\fP
-Prints the current execution status of the \fBprosody\fP server daemon.
-
-.SS Ejabberd Compatibility
-\fBejabberd\fP is another XMPP server which provides a comparable control tool,
-\fBejabberdctl\fP, to control its server's operations. \fBprosodyctl\fP
-implements some commands which are compatible with \fBejabberdctl\fP. For
-details of how these commands work you should see
-.BR ejabberdctl (8).
-
-.IP "\fBregister\fP \fIuser server password\fP"
-.IP "\fBunregister\fP \fIuser server\fP"
-
+unregister\ user\ server
+\f[]
+.fi
.SH OPTIONS
-.IP \fI--help\fP
+.TP
+.B \f[C]\-\-config\ filename\f[]
+Use the specified config file instead of the default.
+.RS
+.RE
+.TP
+.B \f[C]\-\-root\f[]
+Don\[aq]t drop root privileges.
+.RS
+.RE
+.TP
+.B \f[C]\-\-help\f[]
Display help text for the specified command.
-
+.RS
+.RE
.SH FILES
-.IP \fI/etc/prosody/prosody.cfg.lua\fP
-The main \fBprosody\fP configuration file. \fBprosodyctl\fP reads this to
-determine the process ID file of the \fBprosody\fP server daemon and to
-determine if a host has been configured.
-
+.TP
+.B \f[C]/etc/prosody/prosody.cfg.lua\f[]
+The main prosody configuration file.
+prosodyctl reads this to determine the process ID file of the prosody
+server daemon and to determine if a host has been configured.
+.RS
+.RE
.SH ONLINE
-More information may be found online at: \fIhttp://prosody.im/\fP
-
+.PP
+More information may be found online at:
.SH AUTHORS
-Dwayne Bent
+Dwayne Bent ; Kim Alvefur.
diff -r c6b45cac9423 -r 7ec098b68042 man/prosodyctl.markdown
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/man/prosodyctl.markdown Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,161 @@
+---
+author:
+- 'Dwayne Bent '
+- Kim Alvefur
+date: '2017-09-02'
+section: 1
+title: PROSODYCTL
+---
+
+NAME
+====
+
+prosodyctl - Manage a Prosody XMPP server
+
+SYNOPSIS
+========
+
+ prosodyctl command [--help]
+
+DESCRIPTION
+===========
+
+prosodyctl is the control tool for the Prosody XMPP server. It may be
+used to control the server daemon and manage users.
+
+prosodyctl needs to be executed with sufficient privileges to perform
+its commands. This typically means executing prosodyctl as the root
+user. If a user named "prosody" is found then prosodyctl will change to
+that user before executing its commands.
+
+COMMANDS
+========
+
+User Management
+---------------
+
+In the following commands users are identified by a Jabber ID, jid, of
+the usual form: user@domain.
+
+adduser jid
+: Adds a user with Jabber ID, jid, to the server. You will be prompted
+ to enter the user's password.
+
+passwd jid
+: Changes the password of an existing user with Jabber ID, jid. You
+ will be prompted to enter the user's new password.
+
+deluser jid
+: Deletes an existing user with Jabber ID, jid, from the server.
+
+Daemon Management
+-----------------
+
+Although prosodyctl has commands to manage the prosody daemon it is
+recommended that you utilize your distributions daemon management
+features if you attained Prosody through a package.
+
+To perform daemon control commands prosodyctl needs a pidfile value
+specified in `/etc/prosody/prosody.cfg.lua`. Failure to do so will cause
+prosodyctl to complain.
+
+start
+: Starts the prosody server daemon. If run as root prosodyctl will
+ attempt to change to a user named "prosody" before executing. This
+ operation will block for up to five seconds to wait for the server
+ to execute.
+
+stop
+: Stops the prosody server daemon. This operation will block for up to
+ five seconds to wait for the server to stop executing.
+
+restart
+: Restarts the prosody server daemon. Equivalent to running prosodyctl
+ stop followed by prosodyctl start.
+
+reload
+: Signals the prosody server daemon to reload configuration and reopen
+ log files.
+
+status
+: Prints the current execution status of the prosody server daemon.
+
+Certificates
+------------
+
+prosodyctl can create self-signed certificates, certificate requests and
+private keys for use with Prosody. Commands are of the form
+`prosodyctl cert subcommand`. Commands take a list of hosts to be
+included in the certificate.
+
+`request hosts`
+: Create a certificate request (CSR) file for submission to a
+ certificate authority. Multiple hosts can be given, sub-domains are
+ automatically included.
+
+`generate hosts`
+: Generate a self-signed certificate.
+
+`key host [size]`
+: Generate a private key of 'size' bits (defaults to 2048). Invoked
+ automatically by 'request' and 'generate' if needed.
+
+`config hosts`
+: Produce a config file for the list of hosts. Invoked automatically
+ by 'request' and 'generate' if needed.
+
+`import hosts paths`
+: Copy certificates for hosts into the certificate path and reload
+ prosody.
+
+Debugging
+---------
+
+prosodyctl can also show some information about the environment,
+dependencies and such to aid in debugging.
+
+`about`
+: Shows environment, various paths used by Prosody and installed
+ dependencies.
+
+`check [what]`
+: Performs various sanity checks on the configuration, DNS setup and
+ configured TLS certificates. `what` can be one of `config`, `dns`
+ and `certs` to run only that check.
+
+Ejabberd Compatibility
+----------------------
+
+ejabberd is another XMPP server which provides a comparable control
+tool, ejabberdctl, to control its server's operations. prosodyctl
+implements some commands which are compatible with ejabberdctl. For
+details of how these commands work you should see ejabberdctl(8).
+
+ register user server password
+
+ unregister user server
+
+OPTIONS
+=======
+
+`--config filename`
+: Use the specified config file instead of the default.
+
+`--root`
+: Don't drop root privileges.
+
+`--help`
+: Display help text for the specified command.
+
+FILES
+=====
+
+`/etc/prosody/prosody.cfg.lua`
+: The main prosody configuration file. prosodyctl reads this to
+ determine the process ID file of the prosody server daemon and to
+ determine if a host has been configured.
+
+ONLINE
+======
+
+More information may be found online at:
diff -r c6b45cac9423 -r 7ec098b68042 net/adns.lua
--- a/net/adns.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/adns.lua Wed May 30 21:55:09 2018 +0100
@@ -1,61 +1,37 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local server = require "net.server";
-local dns = require "net.dns";
+local new_resolver = require "net.dns".resolver;
local log = require "util.logger".init("adns");
-local t_insert, t_remove = table.insert, table.remove;
local coroutine, tostring, pcall = coroutine, tostring, pcall;
+local setmetatable = setmetatable;
local function dummy_send(sock, data, i, j) return (j-i)+1; end
-module "adns"
+local _ENV = nil;
+
+local async_resolver_methods = {};
+local async_resolver_mt = { __index = async_resolver_methods };
-function lookup(handler, qname, qtype, qclass)
- return coroutine.wrap(function (peek)
- if peek then
- log("debug", "Records for %s already cached, using those...", qname);
- handler(peek);
- return;
- end
- log("debug", "Records for %s not in cache, sending query (%s)...", qname, tostring(coroutine.running()));
- local ok, err = dns.query(qname, qtype, qclass);
- if ok then
- coroutine.yield({ qclass or "IN", qtype or "A", qname, coroutine.running()}); -- Wait for reply
- log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running()));
- end
- if ok then
- ok, err = pcall(handler, dns.peek(qname, qtype, qclass));
- else
- log("error", "Error sending DNS query: %s", err);
- ok, err = pcall(handler, nil, err);
- end
- if not ok then
- log("error", "Error in DNS response handler: %s", tostring(err));
- end
- end)(dns.peek(qname, qtype, qclass));
-end
+local query_methods = {};
+local query_mt = { __index = query_methods };
-function cancel(handle, call_handler, reason)
- log("warn", "Cancelling DNS lookup for %s", tostring(handle[3]));
- dns.cancel(handle[1], handle[2], handle[3], handle[4], call_handler);
-end
-
-function new_async_socket(sock, resolver)
+local function new_async_socket(sock, resolver)
local peername = "";
local listener = {};
local handler = {};
local err;
function listener.onincoming(conn, data)
if data then
- dns.feed(handler, data);
+ resolver:feed(handler, data);
end
end
function listener.ondisconnect(conn, err)
@@ -65,7 +41,7 @@
if resolver.socketset[conn] == resolver.best_server and resolver.best_server == #servers then
log("error", "Exhausted all %d configured DNS servers, next lookup will try %s again", #servers, servers[1]);
end
-
+
resolver:servfail(conn); -- Let the magic commence
end
end
@@ -73,7 +49,7 @@
if not handler then
return nil, err;
end
-
+
handler.settimeout = function () end
handler.setsockname = function (_, ...) return sock:setsockname(...); end
handler.setpeername = function (_, ...) peername = (...); local ret, err = sock:setpeername(...); _:set_send(dummy_send); return ret, err; end
@@ -86,6 +62,47 @@
return handler;
end
-dns.socket_wrapper_set(new_async_socket);
+function async_resolver_methods:lookup(handler, qname, qtype, qclass)
+ local resolver = self._resolver;
+ return coroutine.wrap(function (peek)
+ if peek then
+ log("debug", "Records for %s already cached, using those...", qname);
+ handler(peek);
+ return;
+ end
+ log("debug", "Records for %s not in cache, sending query (%s)...", qname, tostring(coroutine.running()));
+ local ok, err = resolver:query(qname, qtype, qclass);
+ if ok then
+ coroutine.yield(setmetatable({ resolver, qclass or "IN", qtype or "A", qname, coroutine.running()}, query_mt)); -- Wait for reply
+ log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running()));
+ end
+ if ok then
+ ok, err = pcall(handler, resolver:peek(qname, qtype, qclass));
+ else
+ log("error", "Error sending DNS query: %s", err);
+ ok, err = pcall(handler, nil, err);
+ end
+ if not ok then
+ log("error", "Error in DNS response handler: %s", tostring(err));
+ end
+ end)(resolver:peek(qname, qtype, qclass));
+end
-return _M;
+function query_methods:cancel(call_handler, reason)
+ log("warn", "Cancelling DNS lookup for %s", tostring(self[4]));
+ self[1].cancel(self[2], self[3], self[4], self[5], call_handler);
+end
+
+local function new_async_resolver()
+ local resolver = new_resolver();
+ resolver:socket_wrapper_set(new_async_socket);
+ return setmetatable({ _resolver = resolver}, async_resolver_mt);
+end
+
+return {
+ lookup = function (...)
+ return new_async_resolver():lookup(...);
+ end;
+ resolver = new_async_resolver;
+ new_async_socket = new_async_socket;
+};
diff -r c6b45cac9423 -r 7ec098b68042 net/connlisteners.lua
--- a/net/connlisteners.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/connlisteners.lua Wed May 30 21:55:09 2018 +0100
@@ -2,14 +2,17 @@
local log = require "util.logger".init("net.connlisteners");
local traceback = debug.traceback;
-module "httpserver"
+local _ENV = nil;
-function fail()
+local function fail()
log("error", "Attempt to use legacy connlisteners API. For more info see http://prosody.im/doc/developers/network");
log("error", "Legacy connlisteners API usage, %s", traceback("", 2));
end
-register, deregister = fail, fail;
-get, start = fail, fail, epic_fail;
-
-return _M;
+return {
+ register = fail;
+ register = fail;
+ get = fail;
+ start = fail;
+ -- epic fail
+};
diff -r c6b45cac9423 -r 7ec098b68042 net/dns.lua
--- a/net/dns.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/dns.lua Wed May 30 21:55:09 2018 +0100
@@ -22,8 +22,8 @@
local coroutine, io, math, string, table =
coroutine, io, math, string, table;
-local ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type=
- ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type;
+local ipairs, next, pairs, print, setmetatable, tostring, assert, error, select, type =
+ ipairs, next, pairs, print, setmetatable, tostring, assert, error, select, type;
local ztact = { -- public domain 20080404 lua@ztact.com
get = function(parent, ...)
@@ -71,8 +71,8 @@
local default_timeout = 15;
-------------------------------------------------- module dns
-module('dns')
-local dns = _M;
+local _ENV = nil;
+local dns = {};
-- dns type & class codes ------------------------------ dns type & class codes
@@ -188,7 +188,7 @@
local rrs_metatable = {}; -- - - - - - - - - - - - - - - - - - rrs_metatable
function rrs_metatable.__tostring(rrs)
local t = {};
- for i,rr in ipairs(rrs) do
+ for _, rr in ipairs(rrs) do
append(t, tostring(rr)..'\n');
end
return table.concat(t);
@@ -211,15 +211,6 @@
end
-function resolver:new() -- - - - - - - - - - - - - - - - - - - - - resolver
- local r = { active = {}, cache = {}, unsorted = {} };
- setmetatable(r, resolver);
- setmetatable(r.cache, cache_metatable);
- setmetatable(r.unsorted, { __mode = 'kv' });
- return r;
-end
-
-
-- packet layer -------------------------------------------------- packet layer
@@ -393,13 +384,13 @@
function resolver:AAAA(rr)
local addr = {};
- for i = 1, rr.rdlength, 2 do
+ for _ = 1, rr.rdlength, 2 do
local b1, b2 = self:byte(2);
table.insert(addr, ("%02x%02x"):format(b1, b2));
end
addr = table.concat(addr, ":"):gsub("%f[%x]0+(%x)","%1");
local zeros = {};
- for item in addr:gmatch(":[0:]+:") do
+ for item in addr:gmatch(":[0:]+:[0:]+:") do
table.insert(zeros, item)
end
if #zeros == 0 then
@@ -513,7 +504,7 @@
rr.ttl = 0x10000*self:word() + self:word();
rr.rdlength = self:word();
- rr.tod = self.time + math.min(rr.ttl, 1);
+ rr.tod = self.time + math.max(rr.ttl, 1);
local remember = self.offset;
local rr_parser = self[dns.type[rr.type]];
@@ -526,7 +517,7 @@
function resolver:rrs (count) -- - - - - - - - - - - - - - - - - - - - - rrs
local rrs = {};
- for i = 1,count do append(rrs, self:rr()); end
+ for _ = 1, count do append(rrs, self:rr()); end
return rrs;
end
@@ -539,7 +530,7 @@
response.question = {};
local offset = self.offset;
- for i = 1,response.header.qdcount do
+ for _ = 1, response.header.qdcount do
append(response.question, self:question());
end
response.question.raw = string.sub(self.packet, offset, self.offset - 1);
@@ -623,7 +614,7 @@
if peer:find(":") then
sock, err = socket.udp6();
else
- sock, err = socket.udp();
+ sock, err = (socket.udp4 or socket.udp)();
end
if sock and self.socket_wrapper then sock, err = self.socket_wrapper(sock, self); end
if not sock then
@@ -844,7 +835,7 @@
rset = rset or self.socket;
local response;
- for i,sock in pairs(rset) do
+ for _, sock in pairs(rset) do
if self.socketset[sock] then
local packet = sock:receive();
@@ -855,7 +846,7 @@
--print('received response');
--self.print(response);
- for j,rr in pairs(response.answer) do
+ for _, rr in pairs(response.answer) do
if rr.name:sub(-#response.question[1].name, -1) == response.question[1].name then
self:remember(rr, response.question[1].type)
end
@@ -897,7 +888,7 @@
--print('received response');
--self.print(response);
- for j,rr in pairs(response.answer) do
+ for _, rr in pairs(response.answer) do
self:remember(rr, response.question[1].type);
end
@@ -1014,7 +1005,7 @@
function resolver.print(response) -- - - - - - - - - - - - - resolver.print
- for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
+ for _, s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do
print( string.format('%-30s', 'header.'..s), response.header[s], hint(response.header, s) );
end
@@ -1027,9 +1018,9 @@
local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 };
local tmp;
- for s,s in pairs({'answer', 'authority', 'additional'}) do
+ for _, s in pairs({'answer', 'authority', 'additional'}) do
for i,rr in pairs(response[s]) do
- for j,t in pairs({ 'name', 'type', 'class', 'ttl', 'rdlength' }) do
+ for _, t in pairs({ 'name', 'type', 'class', 'ttl', 'rdlength' }) do
tmp = string.format('%s[%i].%s', s, i, t);
print(string.format('%-30s', tmp), rr[t], hint(rr, t));
end
@@ -1048,8 +1039,6 @@
function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver
- -- this function seems to be redundant with resolver.new ()
-
local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, best_server = 1 };
setmetatable (r, resolver);
setmetatable (r.cache, cache_metatable);
diff -r c6b45cac9423 -r 7ec098b68042 net/http.lua
--- a/net/http.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/http.lua Wed May 30 21:55:09 2018 +0100
@@ -1,16 +1,17 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-local socket = require "socket"
local b64 = require "util.encodings".base64.encode;
local url = require "socket.url"
local httpstream_new = require "net.http.parser".new;
local util_http = require "util.http";
+local events = require "util.events";
+local verify_identity = require"util.x509".verify_identity;
local ssl_available = pcall(require, "ssl");
@@ -18,26 +19,49 @@
local t_insert, t_concat = table.insert, table.concat;
local pairs = pairs;
-local tonumber, tostring, xpcall, select, traceback =
- tonumber, tostring, xpcall, select, debug.traceback;
-local assert, error = assert, error
+local tonumber, tostring, xpcall, traceback =
+ tonumber, tostring, xpcall, debug.traceback;
+local error = error
+local setmetatable = setmetatable;
local log = require "util.logger".init("http");
-module "http"
+local _ENV = nil;
local requests = {}; -- Open requests
+local function make_id(req) return (tostring(req):match("%x+$")); end
+
local listener = { default_port = 80, default_mode = "*a" };
function listener.onconnect(conn)
local req = requests[conn];
+
+ -- Validate certificate
+ if not req.insecure and conn:ssl() then
+ local sock = conn:socket();
+ local chain_valid = sock.getpeerverification and sock:getpeerverification();
+ if not chain_valid then
+ req.callback("certificate-chain-invalid", 0, req);
+ req.callback = nil;
+ conn:close();
+ return;
+ end
+ local cert = sock.getpeercertificate and sock:getpeercertificate();
+ if not cert or not verify_identity(req.host, false, cert) then
+ req.callback("certificate-verify-failed", 0, req);
+ req.callback = nil;
+ conn:close();
+ return;
+ end
+ end
+
-- Send the request
local request_line = { req.method or "GET", " ", req.path, " HTTP/1.1\r\n" };
if req.query then
t_insert(request_line, 4, "?"..req.query);
end
-
+
conn:write(t_concat(request_line));
local t = { [2] = ": ", [4] = "\r\n" };
for k, v in pairs(req.headers) do
@@ -45,7 +69,7 @@
conn:write(t_concat(t));
end
conn:write("\r\n");
-
+
if req.body then
conn:write(req.body);
end
@@ -67,7 +91,7 @@
function listener.ondisconnect(conn, err)
local request = requests[conn];
if request and request.conn then
- request:reader(nil, err);
+ request:reader(nil, err or "closed");
end
requests[conn] = nil;
end
@@ -76,6 +100,13 @@
requests[conn] = nil;
end
+local function destroy_request(request)
+ if request.conn then
+ request.conn = nil;
+ request.handler:close()
+ end
+end
+
local function request_reader(request, data, err)
if not request.parser then
local function error_cb(reason)
@@ -85,12 +116,12 @@
end
destroy_request(request);
end
-
+
if not data then
error_cb(err);
return;
end
-
+
local function success_cb(r)
if request.callback then
request.callback(r.body, r.code, r, request);
@@ -107,20 +138,39 @@
end
local function handleerr(err) log("error", "Traceback[http]: %s", traceback(tostring(err), 2)); end
-function request(u, ex, callback)
+local function log_if_failed(id, ret, ...)
+ if not ret then
+ log("error", "Request '%s': error in callback: %s", id, tostring((...)));
+ end
+ return ...;
+end
+
+local function request(self, u, ex, callback)
local req = url.parse(u);
-
+ req.url = u;
+
if not (req and req.host) then
- callback(nil, 0, req);
+ callback("invalid-url", 0, req);
return nil, "invalid-url";
end
-
+
if not req.path then
req.path = "/";
end
-
+
+ req.id = ex and ex.id or make_id(req);
+
+ do
+ local event = { http = self, url = u, request = req, options = ex, callback = callback };
+ local ret = self.events.fire_event("pre-request", event);
+ if ret then
+ return ret;
+ end
+ req, u, ex, callback = event.request, event.url, event.options, event.callback;
+ end
+
local method, headers, body;
-
+
local host, port = req.host, req.port;
local host_header = host;
if (port == "80" and req.scheme == "http")
@@ -134,7 +184,7 @@
["Host"] = host_header;
["User-Agent"] = "Prosody XMPP Server";
};
-
+
if req.userinfo then
headers["Authorization"] = "Basic "..b64(req.userinfo);
end
@@ -153,53 +203,79 @@
headers[k] = v;
end
end
+ req.insecure = ex.insecure;
end
-
+
+ log("debug", "Making %s %s request '%s' to %s", req.scheme:upper(), method or "GET", req.id, (ex and ex.suppress_url and host_header) or u);
+
-- Attach to request object
req.method, req.headers, req.body = method, headers, body;
-
+
local using_https = req.scheme == "https";
if using_https and not ssl_available then
error("SSL not available, unable to contact https URL");
end
local port_number = port and tonumber(port) or (using_https and 443 or 80);
-
- -- Connect the socket, and wrap it with net.server
- local conn = socket.tcp();
- conn:settimeout(10);
- local ok, err = conn:connect(host, port_number);
- if not ok and err ~= "timeout" then
- callback(nil, 0, req);
- return nil, err;
- end
-
+
local sslctx = false;
if using_https then
- sslctx = ex and ex.sslctx or { mode = "client", protocol = "sslv23", options = { "no_sslv2", "no_sslv3" } };
+ sslctx = ex and ex.sslctx or self.options and self.options.sslctx;
end
- req.handler, req.conn = assert(server.wrapclient(conn, host, port_number, listener, "*a", sslctx));
+ local handler, conn = server.addclient(host, port_number, listener, "*a", sslctx)
+ if not handler then
+ self.events.fire_event("request-connection-error", { http = self, request = req, url = u, err = conn });
+ callback(conn, 0, req);
+ return nil, conn;
+ end
+ req.handler, req.conn = handler, conn
req.write = function (...) return req.handler:write(...); end
-
- req.callback = function (content, code, request, response) log("debug", "Calling callback, status %s", code or "---"); return select(2, xpcall(function () return callback(content, code, request, response) end, handleerr)); end
+
+ req.callback = function (content, code, response, request)
+ do
+ local event = { http = self, url = u, request = req, response = response, content = content, code = code, callback = callback };
+ self.events.fire_event("response", event);
+ content, code, response = event.content, event.code, event.response;
+ end
+
+ log("debug", "Request '%s': Calling callback, status %s", req.id, code or "---");
+ return log_if_failed(req.id, xpcall(function () return callback(content, code, response, request) end, handleerr));
+ end
req.reader = request_reader;
req.state = "status";
requests[req.handler] = req;
+
+ self.events.fire_event("request", { http = self, request = req, url = u });
return req;
end
-function destroy_request(request)
- if request.conn then
- request.conn = nil;
- request.handler:close()
- end
+local function new(options)
+ local http = {
+ options = options;
+ request = request;
+ new = options and function (new_options)
+ return new(setmetatable(new_options, { __index = options }));
+ end or new;
+ events = events.new();
+ };
+ return http;
end
-local urlencode, urldecode = util_http.urlencode, util_http.urldecode;
-local formencode, formdecode = util_http.formencode, util_http.formdecode;
+local default_http = new({
+ sslctx = { mode = "client", protocol = "sslv23", options = { "no_sslv2", "no_sslv3" } };
+});
-_M.urlencode, _M.urldecode = urlencode, urldecode;
-_M.formencode, _M.formdecode = formencode, formdecode;
-
-return _M;
+return {
+ request = function (u, ex, callback)
+ return default_http:request(u, ex, callback);
+ end;
+ default = default_http;
+ new = new;
+ events = default_http.events;
+ -- COMPAT
+ urlencode = util_http.urlencode;
+ urldecode = util_http.urldecode;
+ formencode = util_http.formencode;
+ formdecode = util_http.formdecode;
+};
diff -r c6b45cac9423 -r 7ec098b68042 net/http/codes.lua
--- a/net/http/codes.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/http/codes.lua Wed May 30 21:55:09 2018 +0100
@@ -25,6 +25,7 @@
[305] = "Use Proxy";
-- The 306 status code was used in a previous version of [RFC2616], is no longer used, and the code is reserved.
[307] = "Temporary Redirect";
+ [308] = "Permanent Redirect";
[400] = "Bad Request";
[401] = "Unauthorized";
@@ -39,17 +40,22 @@
[410] = "Gone";
[411] = "Length Required";
[412] = "Precondition Failed";
- [413] = "Request Entity Too Large";
- [414] = "Request-URI Too Long";
+ [413] = "Payload Too Large";
+ [414] = "URI Too Long";
[415] = "Unsupported Media Type";
- [416] = "Requested Range Not Satisfiable";
+ [416] = "Range Not Satisfiable";
[417] = "Expectation Failed";
[418] = "I'm a teapot";
+ [421] = "Misdirected Request";
[422] = "Unprocessable Entity";
[423] = "Locked";
[424] = "Failed Dependency";
-- The 425 status code is reserved for the WebDAV advanced collections expired proposal [RFC2817]
[426] = "Upgrade Required";
+ [428] = "Precondition Required";
+ [429] = "Too Many Requests";
+ [431] = "Request Header Fields Too Large";
+ [451] = "Unavailable For Legal Reasons";
[500] = "Internal Server Error";
[501] = "Not Implemented";
@@ -61,7 +67,8 @@
[507] = "Insufficient Storage";
[508] = "Loop Detected";
[510] = "Not Extended";
+ [511] = "Network Authentication Required";
};
for k,v in pairs(response_codes) do response_codes[k] = k.." "..v; end
-return setmetatable(response_codes, { __index = function(t, k) return k.." Unassigned"; end })
+return setmetatable(response_codes, { __index = function(_, k) return k.." Unassigned"; end })
diff -r c6b45cac9423 -r 7ec098b68042 net/http/parser.lua
--- a/net/http/parser.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/http/parser.lua Wed May 30 21:55:09 2018 +0100
@@ -38,7 +38,7 @@
local have_body;
local error;
return {
- feed = function(self, data)
+ feed = function(_, data)
if error then return nil, "parse has failed"; end
if not data then -- EOF
if buftable then buf, buftable = t_concat(buf), false; end
@@ -46,7 +46,7 @@
packet.body = buf;
success_cb(packet);
elseif buf ~= "" then -- unexpected EOF
- error = true; return error_cb();
+ error = true; return error_cb("unexpected-eof");
end
return;
end
@@ -134,6 +134,9 @@
if state then -- read body
if client then
if chunked then
+ if chunk_start and buflen - chunk_start - 2 < chunk_size then
+ return;
+ end -- not enough data
if buftable then buf, buftable = t_concat(buf), false; end
if not buf:find("\r\n", nil, true) then
return;
@@ -150,6 +153,7 @@
elseif buflen - chunk_start - 2 >= chunk_size then -- we have a chunk
packet.body = packet.body..buf:sub(chunk_start, chunk_start + (chunk_size-1));
buf = buf:sub(chunk_start + chunk_size + 2);
+ buflen = buflen - (chunk_start + chunk_size + 2 - 1);
chunk_size, chunk_start = nil, nil;
else -- Partial chunk remaining
break;
diff -r c6b45cac9423 -r 7ec098b68042 net/http/server.lua
--- a/net/http/server.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/http/server.lua Wed May 30 21:55:09 2018 +0100
@@ -11,11 +11,14 @@
local xpcall = xpcall;
local traceback = debug.traceback;
local tostring = tostring;
+local cache = require "util.cache";
local codes = require "net.http.codes";
+local blocksize = 2^16;
local _M = {};
local sessions = {};
+local incomplete = {};
local listener = {};
local hosts = {};
local default_host;
@@ -28,7 +31,10 @@
return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1);
end
-local recent_wildcard_events, max_cached_wildcard_events = {}, 10000;
+local _handlers = events._handlers;
+local recent_wildcard_events = cache.new(10000, function (key, value) -- luacheck: ignore 212/value
+ rawset(_handlers, key, nil);
+end);
local event_map = events._event_map;
setmetatable(events._handlers, {
@@ -63,10 +69,7 @@
end
rawset(handlers, curr_event, handlers_array);
if not event_map[curr_event] then -- Only wildcard handlers match, if any
- table.insert(recent_wildcard_events, curr_event);
- if #recent_wildcard_events > max_cached_wildcard_events then
- rawset(handlers, table.remove(recent_wildcard_events, 1), nil);
- end
+ recent_wildcard_events:set(curr_event, true);
end
return handlers_array;
end;
@@ -143,17 +146,26 @@
open_response.finished = true;
open_response:on_destroy();
end
+ incomplete[conn] = nil;
sessions[conn] = nil;
end
function listener.ondetach(conn)
sessions[conn] = nil;
+ incomplete[conn] = nil;
end
function listener.onincoming(conn, data)
sessions[conn]:feed(data);
end
+function listener.ondrain(conn)
+ local response = incomplete[conn];
+ if response and response._send_more then
+ response._send_more();
+ end
+end
+
local headerfix = setmetatable({}, {
__index = function(t, k)
local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": ";
@@ -162,7 +174,7 @@
end
});
-function _M.hijack_response(response, listener)
+function _M.hijack_response(response, listener) -- luacheck: ignore
error("TODO");
end
function handle_request(conn, request, finish_cb)
@@ -193,6 +205,8 @@
persistent = persistent;
conn = conn;
send = _M.send_response;
+ send_file = _M.send_file;
+ done = _M.finish_response;
finish_cb = finish_cb;
};
conn._http_open_response = response;
@@ -212,10 +226,10 @@
err_code, err = 400, "Missing or invalid 'Host' header";
end
end
-
+
if err then
response.status_code = err_code;
- response:send(events.fire_event("http-error", { code = err_code, message = err }));
+ response:send(events.fire_event("http-error", { code = err_code, message = err, response = response }));
return;
end
@@ -230,7 +244,8 @@
if result_type == "number" then
response.status_code = result;
if result >= 400 then
- body = events.fire_event("http-error", { code = result });
+ payload.code = result;
+ body = events.fire_event("http-error", payload);
end
elseif result_type == "string" then
body = result;
@@ -252,26 +267,63 @@
-- if handler not called, return 404
response.status_code = 404;
- response:send(events.fire_event("http-error", { code = 404 }));
+ payload.code = 404;
+ response:send(events.fire_event("http-error", payload));
end
-function _M.send_response(response, body)
- if response.finished then return; end
- response.finished = true;
- response.conn._http_open_response = nil;
-
+local function prepare_header(response)
local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
local headers = response.headers;
- body = body or response.body or "";
- headers.content_length = #body;
-
local output = { status_line };
for k,v in pairs(headers) do
t_insert(output, headerfix[k]..v);
end
t_insert(output, "\r\n\r\n");
+ return output;
+end
+_M.prepare_header = prepare_header;
+function _M.send_response(response, body)
+ if response.finished then return; end
+ body = body or response.body or "";
+ response.headers.content_length = #body;
+ local output = prepare_header(response);
t_insert(output, body);
-
response.conn:write(t_concat(output));
+ response:done();
+end
+function _M.send_file(response, f)
+ if response.finished then return; end
+ local chunked = not response.headers.content_length;
+ if chunked then response.headers.transfer_encoding = "chunked"; end
+ incomplete[response.conn] = response;
+ response._send_more = function ()
+ if response.finished then
+ incomplete[response.conn] = nil;
+ return;
+ end
+ local chunk = f:read(blocksize);
+ if chunk then
+ if chunked then
+ chunk = ("%x\r\n%s\r\n"):format(#chunk, chunk);
+ end
+ -- io.write("."); io.flush();
+ response.conn:write(chunk);
+ else
+ if chunked then
+ response.conn:write("0\r\n\r\n");
+ end
+ -- io.write("\n");
+ if f.close then f:close(); end
+ incomplete[response.conn] = nil;
+ return response:done();
+ end
+ end
+ response.conn:write(t_concat(prepare_header(response)));
+ return true;
+end
+function _M.finish_response(response)
+ if response.finished then return; end
+ response.finished = true;
+ response.conn._http_open_response = nil;
if response.on_destroy then
response:on_destroy();
response.on_destroy = nil;
@@ -290,7 +342,7 @@
end
function _M.listen_on(port, interface, ssl)
- addserver(interface or "*", port, listener, "*a", ssl);
+ return addserver(interface or "*", port, listener, "*a", ssl);
end
function _M.add_host(host)
hosts[host] = true;
diff -r c6b45cac9423 -r 7ec098b68042 net/httpserver.lua
--- a/net/httpserver.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/httpserver.lua Wed May 30 21:55:09 2018 +0100
@@ -2,14 +2,15 @@
local log = require "util.logger".init("net.httpserver");
local traceback = debug.traceback;
-module "httpserver"
+local _ENV = nil;
function fail()
log("error", "Attempt to use legacy HTTP API. For more info see http://prosody.im/doc/developers/legacy_http");
log("error", "Legacy HTTP API usage, %s", traceback("", 2));
end
-new, new_from_config = fail, fail;
-set_default_handler = fail;
-
-return _M;
+return {
+ new = fail;
+ new_from_config = fail;
+ set_default_handler = fail;
+};
diff -r c6b45cac9423 -r 7ec098b68042 net/server.lua
--- a/net/server.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/server.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
diff -r c6b45cac9423 -r 7ec098b68042 net/server_event.lua
--- a/net/server_event.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/server_event.lua Wed May 30 21:55:09 2018 +0100
@@ -11,6 +11,7 @@
-- when using luasec, there are 4 cases of timeout errors: wantread or wantwrite during reading or writing
--]]
+-- luacheck: ignore 212/self 431/err 211/ret
local SCRIPT_NAME = "server_event.lua"
local SCRIPT_VERSION = "0.05"
@@ -29,30 +30,36 @@
WRITE_TIMEOUT = 180, -- timeout in seconds for write data on socket
CONNECT_TIMEOUT = 20, -- timeout in seconds for connection attempts
CLEAR_DELAY = 5, -- seconds to wait for clearing interface list (and calling ondisconnect listeners)
+ READ_RETRY_DELAY = 1e-06, -- if, after reading, there is still data in buffer, wait this long and continue reading
DEBUG = true, -- show debug messages
}
-local function use(x) return rawget(_G, x); end
-local ipairs = use "ipairs"
-local string = use "string"
-local select = use "select"
-local require = use "require"
-local tostring = use "tostring"
-local coroutine = use "coroutine"
-local setmetatable = use "setmetatable"
+local pairs = pairs
+local select = select
+local require = require
+local tostring = tostring
+local setmetatable = setmetatable
local t_insert = table.insert
local t_concat = table.concat
+local s_sub = string.sub
-local ssl = use "ssl"
-local socket = use "socket" or require "socket"
+local coroutine_wrap = coroutine.wrap
+local coroutine_yield = coroutine.yield
+
+local has_luasec, ssl = pcall ( require , "ssl" )
+local socket = require "socket"
+local levent = require "luaevent.core"
+
+local socket_gettime = socket.gettime
+local getaddrinfo = socket.dns.getaddrinfo
local log = require ("util.logger").init("socket")
local function debug(...)
return log("debug", ("%s "):rep(select('#', ...)), ...)
end
-local vdebug = debug;
+-- local vdebug = debug;
local bitor = ( function( ) -- thx Rici Lake
local hasbit = function( x, p )
@@ -72,741 +79,685 @@
end
end )( )
-local event = require "luaevent.core"
-local base = event.new( )
-local EV_READ = event.EV_READ
-local EV_WRITE = event.EV_WRITE
-local EV_TIMEOUT = event.EV_TIMEOUT
-local EV_SIGNAL = event.EV_SIGNAL
+local base = levent.new( )
+local addevent = base.addevent
+local EV_READ = levent.EV_READ
+local EV_WRITE = levent.EV_WRITE
+local EV_TIMEOUT = levent.EV_TIMEOUT
+local EV_SIGNAL = levent.EV_SIGNAL
local EV_READWRITE = bitor( EV_READ, EV_WRITE )
-local interfacelist = ( function( ) -- holds the interfaces for sockets
- local array = { }
- local len = 0
- return function( method, arg )
- if "add" == method then
- len = len + 1
- array[ len ] = arg
- arg:_position( len )
- return len
- elseif "delete" == method then
- if len <= 0 then
- return nil, "array is already empty"
- end
- local position = arg:_position() -- get position in array
- if position ~= len then
- local interface = array[ len ] -- get last interface
- array[ position ] = interface -- copy it into free position
- array[ len ] = nil -- free last position
- interface:_position( position ) -- set new position in array
- else -- free last position
- array[ len ] = nil
- end
- len = len - 1
- return len
- else
- return array
- end
- end
-end )( )
+local interfacelist = { }
-- Client interface methods
-local interface_mt
-do
- interface_mt = {}; interface_mt.__index = interface_mt;
-
- local addevent = base.addevent
- local coroutine_wrap, coroutine_yield = coroutine.wrap,coroutine.yield
-
- -- Private methods
- function interface_mt:_position(new_position)
- self.position = new_position or self.position
- return self.position;
- end
- function interface_mt:_close()
- return self:_destroy();
+local interface_mt = {}; interface_mt.__index = interface_mt;
+
+-- Private methods
+function interface_mt:_close()
+ return self:_destroy();
+end
+
+function interface_mt:_start_connection(plainssl) -- called from wrapclient
+ local callback = function( event )
+ if EV_TIMEOUT == event then -- timeout during connection
+ self.fatalerror = "connection timeout"
+ self:ontimeout() -- call timeout listener
+ self:_close()
+ debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
+ else
+ if plainssl and has_luasec then -- start ssl session
+ self:starttls(self._sslctx, true)
+ else -- normal connection
+ self:_start_session(true)
+ end
+ debug( "new connection established. id:", self.id )
+ end
+ self.eventconnect = nil
+ return -1
end
-
- function interface_mt:_start_connection(plainssl) -- should be called from addclient
- local callback = function( event )
- if EV_TIMEOUT == event then -- timeout during connection
- self.fatalerror = "connection timeout"
- self:ontimeout() -- call timeout listener
- self:_close()
- debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
- else
- if plainssl and ssl then -- start ssl session
- self:starttls(self._sslctx, true)
- else -- normal connection
- self:_start_session(true)
- end
- debug( "new connection established. id:", self.id )
- end
- self.eventconnect = nil
- return -1
+ self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT )
+ return true
+end
+function interface_mt:_start_session(call_onconnect) -- new session, for example after startssl
+ if self.type == "client" then
+ local callback = function( )
+ self:_lock( false, false, false )
+ --vdebug( "start listening on client socket with id:", self.id )
+ self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback
+ if call_onconnect then
+ self:onconnect()
end
- self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT )
- return true
+ self.eventsession = nil
+ return -1
+ end
+ self.eventsession = addevent( base, nil, EV_TIMEOUT, callback, 0 )
+ else
+ self:_lock( false )
+ --vdebug( "start listening on server socket with id:", self.id )
+ self.eventread = addevent( base, self.conn, EV_READ, self.readcallback ) -- register callback
+ end
+ return true
+end
+function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed, therefore we have to close read/write events first
+ --vdebug( "starting ssl session with client id:", self.id )
+ local _
+ _ = self.eventread and self.eventread:close( ) -- close events; this must be called outside of the event callbacks!
+ _ = self.eventwrite and self.eventwrite:close( )
+ self.eventread, self.eventwrite = nil, nil
+ local err
+ self.conn, err = ssl.wrap( self.conn, self._sslctx )
+ if err then
+ self.fatalerror = err
+ self.conn = nil -- cannot be used anymore
+ if call_onconnect then
+ self.ondisconnect = nil -- dont call this when client isnt really connected
+ end
+ self:_close()
+ debug( "fatal error while ssl wrapping:", err )
+ return false
end
- function interface_mt:_start_session(call_onconnect) -- new session, for example after startssl
- if self.type == "client" then
- local callback = function( )
- self:_lock( false, false, false )
- --vdebug( "start listening on client socket with id:", self.id )
- self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback
- if call_onconnect then
- self:onconnect()
+ self.conn:settimeout( 0 ) -- set non blocking
+ local handshakecallback = coroutine_wrap(function( event )
+ local _, err
+ local attempt = 0
+ local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPTS
+ while attempt < maxattempt do -- no endless loop
+ attempt = attempt + 1
+ debug( "ssl handshake of client with id:"..tostring(self)..", attempt:"..attempt )
+ if attempt > maxattempt then
+ self.fatalerror = "max handshake attempts exceeded"
+ elseif EV_TIMEOUT == event then
+ self.fatalerror = "timeout during handshake"
+ else
+ _, err = self.conn:dohandshake( )
+ if not err then
+ self:_lock( false, false, false ) -- unlock the interface; sending, closing etc allowed
+ self.send = self.conn.send -- caching table lookups with new client object
+ self.receive = self.conn.receive
+ if not call_onconnect then -- trigger listener
+ self:onstatus("ssl-handshake-complete");
+ end
+ self:_start_session( call_onconnect )
+ debug( "ssl handshake done" )
+ self.eventhandshake = nil
+ return -1
end
- self.eventsession = nil
- return -1
+ if err == "wantwrite" then
+ event = EV_WRITE
+ elseif err == "wantread" then
+ event = EV_READ
+ else
+ debug( "ssl handshake error:", err )
+ self.fatalerror = err
+ end
end
- self.eventsession = addevent( base, nil, EV_TIMEOUT, callback, 0 )
- else
- self:_lock( false )
- --vdebug( "start listening on server socket with id:", self.id )
- self.eventread = addevent( base, self.conn, EV_READ, self.readcallback ) -- register callback
- end
- return true
- end
- function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed, therefore we have to close read/write events first
- --vdebug( "starting ssl session with client id:", self.id )
- local _
- _ = self.eventread and self.eventread:close( ) -- close events; this must be called outside of the event callbacks!
- _ = self.eventwrite and self.eventwrite:close( )
- self.eventread, self.eventwrite = nil, nil
- local err
- self.conn, err = ssl.wrap( self.conn, self._sslctx )
- if err then
- self.fatalerror = err
- self.conn = nil -- cannot be used anymore
+ if self.fatalerror then
if call_onconnect then
self.ondisconnect = nil -- dont call this when client isnt really connected
end
self:_close()
- debug( "fatal error while ssl wrapping:", err )
- return false
+ debug( "handshake failed because:", self.fatalerror )
+ self.eventhandshake = nil
+ return -1
end
- self.conn:settimeout( 0 ) -- set non blocking
- local handshakecallback = coroutine_wrap(
- function( event )
- local _, err
- local attempt = 0
- local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPTS
- while attempt < maxattempt do -- no endless loop
- attempt = attempt + 1
- debug( "ssl handshake of client with id:"..tostring(self)..", attempt:"..attempt )
- if attempt > maxattempt then
- self.fatalerror = "max handshake attempts exceeded"
- elseif EV_TIMEOUT == event then
- self.fatalerror = "timeout during handshake"
- else
- _, err = self.conn:dohandshake( )
- if not err then
- self:_lock( false, false, false ) -- unlock the interface; sending, closing etc allowed
- self.send = self.conn.send -- caching table lookups with new client object
- self.receive = self.conn.receive
- if not call_onconnect then -- trigger listener
- self:onstatus("ssl-handshake-complete");
- end
- self:_start_session( call_onconnect )
- debug( "ssl handshake done" )
- self.eventhandshake = nil
- return -1
- end
- if err == "wantwrite" then
- event = EV_WRITE
- elseif err == "wantread" then
- event = EV_READ
- else
- debug( "ssl handshake error:", err )
- self.fatalerror = err
- end
- end
- if self.fatalerror then
- if call_onconnect then
- self.ondisconnect = nil -- dont call this when client isnt really connected
- end
- self:_close()
- debug( "handshake failed because:", self.fatalerror )
- self.eventhandshake = nil
- return -1
- end
- event = coroutine_yield( event, cfg.HANDSHAKE_TIMEOUT ) -- yield this monster...
- end
- end
- )
- debug "starting handshake..."
- self:_lock( false, true, true ) -- unlock read/write events, but keep interface locked
- self.eventhandshake = addevent( base, self.conn, EV_READWRITE, handshakecallback, cfg.HANDSHAKE_TIMEOUT )
- return true
- end
- function interface_mt:_destroy() -- close this interface + events and call last listener
- debug( "closing client with id:", self.id, self.fatalerror )
- self:_lock( true, true, true ) -- first of all, lock the interface to avoid further actions
- local _
- _ = self.eventread and self.eventread:close( )
- if self.type == "client" then
- _ = self.eventwrite and self.eventwrite:close( )
- _ = self.eventhandshake and self.eventhandshake:close( )
- _ = self.eventstarthandshake and self.eventstarthandshake:close( )
- _ = self.eventconnect and self.eventconnect:close( )
- _ = self.eventsession and self.eventsession:close( )
- _ = self.eventwritetimeout and self.eventwritetimeout:close( )
- _ = self.eventreadtimeout and self.eventreadtimeout:close( )
- _ = self.ondisconnect and self:ondisconnect( self.fatalerror ~= "client to close" and self.fatalerror) -- call ondisconnect listener (wont be the case if handshake failed on connect)
- _ = self.conn and self.conn:close( ) -- close connection
- _ = self._server and self._server:counter(-1);
- self.eventread, self.eventwrite = nil, nil
- self.eventstarthandshake, self.eventhandshake, self.eventclose = nil, nil, nil
- self.readcallback, self.writecallback = nil, nil
- else
- self.conn:close( )
- self.eventread, self.eventclose = nil, nil
- self.interface, self.readcallback = nil, nil
- end
- interfacelist( "delete", self )
- return true
- end
-
- function interface_mt:_lock(nointerface, noreading, nowriting) -- lock or unlock this interface or events
- self.nointerface, self.noreading, self.nowriting = nointerface, noreading, nowriting
- return nointerface, noreading, nowriting
- end
-
- --TODO: Deprecate
- function interface_mt:lock_read(switch)
- if switch then
- return self:pause();
- else
- return self:resume();
+ event = coroutine_yield( event, cfg.HANDSHAKE_TIMEOUT ) -- yield this monster...
end
end
-
- function interface_mt:pause()
- return self:_lock(self.nointerface, true, self.nowriting);
+ )
+ debug "starting handshake..."
+ self:_lock( false, true, true ) -- unlock read/write events, but keep interface locked
+ self.eventhandshake = addevent( base, self.conn, EV_READWRITE, handshakecallback, cfg.HANDSHAKE_TIMEOUT )
+ return true
+end
+function interface_mt:_destroy() -- close this interface + events and call last listener
+ debug( "closing client with id:", self.id, self.fatalerror )
+ self:_lock( true, true, true ) -- first of all, lock the interface to avoid further actions
+ local _
+ _ = self.eventread and self.eventread:close( )
+ if self.type == "client" then
+ _ = self.eventwrite and self.eventwrite:close( )
+ _ = self.eventhandshake and self.eventhandshake:close( )
+ _ = self.eventstarthandshake and self.eventstarthandshake:close( )
+ _ = self.eventconnect and self.eventconnect:close( )
+ _ = self.eventsession and self.eventsession:close( )
+ _ = self.eventwritetimeout and self.eventwritetimeout:close( )
+ _ = self.eventreadtimeout and self.eventreadtimeout:close( )
+ _ = self.ondisconnect and self:ondisconnect( self.fatalerror ~= "client to close" and self.fatalerror) -- call ondisconnect listener (wont be the case if handshake failed on connect)
+ _ = self.conn and self.conn:close( ) -- close connection
+ _ = self._server and self._server:counter(-1);
+ self.eventread, self.eventwrite = nil, nil
+ self.eventstarthandshake, self.eventhandshake, self.eventclose = nil, nil, nil
+ self.readcallback, self.writecallback = nil, nil
+ else
+ self.conn:close( )
+ self.eventread, self.eventclose = nil, nil
+ self.interface, self.readcallback = nil, nil
end
+ interfacelist[ self ] = nil
+ return true
+end
- function interface_mt:resume()
- self:_lock(self.nointerface, false, self.nowriting);
- if self.readcallback and not self.eventread then
- self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback
- return true;
- end
- end
+function interface_mt:_lock(nointerface, noreading, nowriting) -- lock or unlock this interface or events
+ self.nointerface, self.noreading, self.nowriting = nointerface, noreading, nowriting
+ return nointerface, noreading, nowriting
+end
- function interface_mt:counter(c)
- if c then
- self._connections = self._connections + c
- end
- return self._connections
+--TODO: Deprecate
+function interface_mt:lock_read(switch)
+ if switch then
+ return self:pause();
+ else
+ return self:resume();
end
-
- -- Public methods
- function interface_mt:write(data)
- if self.nowriting then return nil, "locked" end
- --vdebug( "try to send data to client, id/data:", self.id, data )
- data = tostring( data )
- local len = #data
- local total = len + self.writebufferlen
- if total > cfg.MAX_SEND_LENGTH then -- check buffer length
- local err = "send buffer exceeded"
- debug( "error:", err ) -- to much, check your app
- return nil, err
+end
+
+function interface_mt:pause()
+ return self:_lock(self.nointerface, true, self.nowriting);
+end
+
+function interface_mt:resume()
+ self:_lock(self.nointerface, false, self.nowriting);
+ if self.readcallback and not self.eventread then
+ self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback
+ return true;
+ end
+end
+
+function interface_mt:counter(c)
+ if c then
+ self._connections = self._connections + c
+ end
+ return self._connections
+end
+
+-- Public methods
+function interface_mt:write(data)
+ if self.nowriting then return nil, "locked" end
+ --vdebug( "try to send data to client, id/data:", self.id, data )
+ data = tostring( data )
+ local len = #data
+ local total = len + self.writebufferlen
+ if total > cfg.MAX_SEND_LENGTH then -- check buffer length
+ local err = "send buffer exceeded"
+ debug( "error:", err ) -- to much, check your app
+ return nil, err
+ end
+ t_insert(self.writebuffer, data) -- new buffer
+ self.writebufferlen = total
+ if not self.eventwrite then -- register new write event
+ --vdebug( "register new write event" )
+ self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT )
+ end
+ return true
+end
+function interface_mt:close()
+ if self.nointerface then return nil, "locked"; end
+ debug( "try to close client connection with id:", self.id )
+ if self.type == "client" then
+ self.fatalerror = "client to close"
+ if self.eventwrite then -- wait for incomplete write request
+ self:_lock( true, true, false )
+ debug "closing delayed until writebuffer is empty"
+ return nil, "writebuffer not empty, waiting"
+ else -- close now
+ self:_lock( true, true, true )
+ self:_close()
+ return true
end
- t_insert(self.writebuffer, data) -- new buffer
- self.writebufferlen = total
- if not self.eventwrite then -- register new write event
- --vdebug( "register new write event" )
- self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT )
- end
+ else
+ debug( "try to close server with id:", tostring(self.id))
+ self.fatalerror = "server to close"
+ self:_lock( true )
+ self:_close( 0 )
return true
end
- function interface_mt:close()
- if self.nointerface then return nil, "locked"; end
- debug( "try to close client connection with id:", self.id )
- if self.type == "client" then
- self.fatalerror = "client to close"
- if self.eventwrite then -- wait for incomplete write request
- self:_lock( true, true, false )
- debug "closing delayed until writebuffer is empty"
- return nil, "writebuffer not empty, waiting"
- else -- close now
- self:_lock( true, true, true )
- self:_close()
- return true
- end
- else
- debug( "try to close server with id:", tostring(self.id))
- self.fatalerror = "server to close"
- self:_lock( true )
- self:_close( 0 )
- return true
- end
- end
-
- function interface_mt:socket()
- return self.conn
- end
-
- function interface_mt:server()
- return self._server or self;
- end
-
- function interface_mt:port()
- return self._port
+end
+
+function interface_mt:socket()
+ return self.conn
+end
+
+function interface_mt:server()
+ return self._server or self;
+end
+
+function interface_mt:port()
+ return self._port
+end
+
+function interface_mt:serverport()
+ return self._serverport
+end
+
+function interface_mt:ip()
+ return self._ip
+end
+
+function interface_mt:ssl()
+ return self._usingssl
+end
+interface_mt.clientport = interface_mt.port -- COMPAT server_select
+
+function interface_mt:type()
+ return self._type or "client"
+end
+
+function interface_mt:connections()
+ return self._connections
+end
+
+function interface_mt:address()
+ return self.addr
+end
+
+function interface_mt:set_sslctx(sslctx)
+ self._sslctx = sslctx;
+ if sslctx then
+ self.starttls = nil; -- use starttls() of interface_mt
+ else
+ self.starttls = false; -- prevent starttls()
end
-
- function interface_mt:serverport()
- return self._serverport
- end
-
- function interface_mt:ip()
- return self._ip
- end
-
- function interface_mt:ssl()
- return self._usingssl
- end
- interface_mt.clientport = interface_mt.port -- COMPAT server_select
+end
- function interface_mt:type()
- return self._type or "client"
+function interface_mt:set_mode(pattern)
+ if pattern then
+ self._pattern = pattern;
end
-
- function interface_mt:connections()
- return self._connections
- end
-
- function interface_mt:address()
- return self.addr
- end
-
- function interface_mt:set_sslctx(sslctx)
- self._sslctx = sslctx;
- if sslctx then
- self.starttls = nil; -- use starttls() of interface_mt
- else
- self.starttls = false; -- prevent starttls()
- end
- end
+ return self._pattern;
+end
+
+function interface_mt:set_send(new_send) -- luacheck: ignore 212
+ -- No-op, we always use the underlying connection's send
+end
- function interface_mt:set_mode(pattern)
- if pattern then
- self._pattern = pattern;
- end
- return self._pattern;
+function interface_mt:starttls(sslctx, call_onconnect)
+ debug( "try to start ssl at client id:", self.id )
+ local err
+ self._sslctx = sslctx;
+ if self._usingssl then -- startssl was already called
+ err = "ssl already active"
+ end
+ if err then
+ debug( "error:", err )
+ return nil, err
end
-
- function interface_mt:set_send(new_send)
- -- No-op, we always use the underlying connection's send
+ self._usingssl = true
+ self.startsslcallback = function( ) -- we have to start the handshake outside of a read/write event
+ self.startsslcallback = nil
+ self:_start_ssl(call_onconnect);
+ self.eventstarthandshake = nil
+ return -1
+ end
+ if not self.eventwrite then
+ self:_lock( true, true, true ) -- lock the interface, to not disturb the handshake
+ self.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, self.startsslcallback, 0 ) -- add event to start handshake
+ else
+ -- wait until writebuffer is empty
+ self:_lock( true, true, false )
+ debug "ssl session delayed until writebuffer is empty..."
+ end
+ self.starttls = false;
+ return true
+end
+
+function interface_mt:setoption(option, value)
+ if self.conn.setoption then
+ return self.conn:setoption(option, value);
end
-
- function interface_mt:starttls(sslctx, call_onconnect)
- debug( "try to start ssl at client id:", self.id )
- local err
- self._sslctx = sslctx;
- if self._usingssl then -- startssl was already called
- err = "ssl already active"
- end
- if err then
- debug( "error:", err )
- return nil, err
- end
- self._usingssl = true
- self.startsslcallback = function( ) -- we have to start the handshake outside of a read/write event
- self.startsslcallback = nil
- self:_start_ssl(call_onconnect);
- self.eventstarthandshake = nil
- return -1
- end
- if not self.eventwrite then
- self:_lock( true, true, true ) -- lock the interface, to not disturb the handshake
- self.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, self.startsslcallback, 0 ) -- add event to start handshake
- else -- wait until writebuffer is empty
- self:_lock( true, true, false )
- debug "ssl session delayed until writebuffer is empty..."
- end
- self.starttls = false;
- return true
- end
-
- function interface_mt:setoption(option, value)
- if self.conn.setoption then
- return self.conn:setoption(option, value);
- end
- return false, "setoption not implemented";
- end
-
- function interface_mt:setlistener(listener)
- self:ondetach(); -- Notify listener that it is no longer responsible for this connection
- self.onconnect, self.ondisconnect, self.onincoming,
- self.ontimeout, self.onstatus, self.ondetach
- = listener.onconnect, listener.ondisconnect, listener.onincoming,
- listener.ontimeout, listener.onstatus, listener.ondetach;
- end
-
- -- Stub handlers
- function interface_mt:onconnect()
- end
- function interface_mt:onincoming()
- end
- function interface_mt:ondisconnect()
- end
- function interface_mt:ontimeout()
- end
- function interface_mt:ondrain()
- end
- function interface_mt:ondetach()
- end
- function interface_mt:onstatus()
- end
+ return false, "setoption not implemented";
+end
+
+function interface_mt:setlistener(listener)
+ self:ondetach(); -- Notify listener that it is no longer responsible for this connection
+ self.onconnect = listener.onconnect;
+ self.ondisconnect = listener.ondisconnect;
+ self.onincoming = listener.onincoming;
+ self.ontimeout = listener.ontimeout;
+ self.onreadtimeout = listener.onreadtimeout;
+ self.onstatus = listener.onstatus;
+ self.ondetach = listener.ondetach;
+ self.ondrain = listener.ondrain;
+end
+
+-- Stub handlers
+function interface_mt:onconnect()
+end
+function interface_mt:onincoming()
+end
+function interface_mt:ondisconnect()
+end
+function interface_mt:ontimeout()
+end
+function interface_mt:onreadtimeout()
+ self.fatalerror = "timeout during receiving"
+ debug( "connection failed:", self.fatalerror )
+ self:_close()
+ self.eventread = nil
+end
+function interface_mt:ondrain()
+end
+function interface_mt:ondetach()
+end
+function interface_mt:onstatus()
end
-- End of client interface methods
-local handleclient;
-do
- local string_sub = string.sub -- caching table lookups
- local addevent = base.addevent
- local socket_gettime = socket.gettime
- function handleclient( client, ip, port, server, pattern, listener, sslctx ) -- creates an client interface
- --vdebug("creating client interfacce...")
- local interface = {
- type = "client";
- conn = client;
- currenttime = socket_gettime( ); -- safe the origin
- writebuffer = {}; -- writebuffer
- writebufferlen = 0; -- length of writebuffer
- send = client.send; -- caching table lookups
- receive = client.receive;
- onconnect = listener.onconnect; -- will be called when client disconnects
- ondisconnect = listener.ondisconnect; -- will be called when client disconnects
- onincoming = listener.onincoming; -- will be called when client sends data
- ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs
- ondrain = listener.ondrain; -- called when writebuffer is empty
- ondetach = listener.ondetach; -- called when disassociating this listener from this connection
- onstatus = listener.onstatus; -- called for status changes (e.g. of SSL/TLS)
- eventread = false, eventwrite = false, eventclose = false,
- eventhandshake = false, eventstarthandshake = false; -- event handler
- eventconnect = false, eventsession = false; -- more event handler...
- eventwritetimeout = false; -- even more event handler...
- eventreadtimeout = false;
- fatalerror = false; -- error message
- writecallback = false; -- will be called on write events
- readcallback = false; -- will be called on read events
- nointerface = true; -- lock/unlock parameter of this interface
- noreading = false, nowriting = false; -- locks of the read/writecallback
- startsslcallback = false; -- starting handshake callback
- position = false; -- position of client in interfacelist
-
- -- Properties
- _ip = ip, _port = port, _server = server, _pattern = pattern,
- _serverport = (server and server:port() or nil),
- _sslctx = sslctx; -- parameters
- _usingssl = false; -- client is using ssl;
- }
- if not ssl then interface.starttls = false; end
- interface.id = tostring(interface):match("%x+$");
- interface.writecallback = function( event ) -- called on write events
- --vdebug( "new client write event, id/ip/port:", interface, ip, port )
- if interface.nowriting or ( interface.fatalerror and ( "client to close" ~= interface.fatalerror ) ) then -- leave this event
- --vdebug( "leaving this event because:", interface.nowriting or interface.fatalerror )
- interface.eventwrite = false
+local function handleclient( client, ip, port, server, pattern, listener, sslctx ) -- creates an client interface
+ --vdebug("creating client interfacce...")
+ local interface = {
+ type = "client";
+ conn = client;
+ currenttime = socket_gettime( ); -- safe the origin
+ writebuffer = {}; -- writebuffer
+ writebufferlen = 0; -- length of writebuffer
+ send = client.send; -- caching table lookups
+ receive = client.receive;
+ onconnect = listener.onconnect; -- will be called when client disconnects
+ ondisconnect = listener.ondisconnect; -- will be called when client disconnects
+ onincoming = listener.onincoming; -- will be called when client sends data
+ ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs
+ onreadtimeout = listener.onreadtimeout; -- called when socket inactivity timeout occurs
+ ondrain = listener.ondrain; -- called when writebuffer is empty
+ ondetach = listener.ondetach; -- called when disassociating this listener from this connection
+ onstatus = listener.onstatus; -- called for status changes (e.g. of SSL/TLS)
+ eventread = false, eventwrite = false, eventclose = false,
+ eventhandshake = false, eventstarthandshake = false; -- event handler
+ eventconnect = false, eventsession = false; -- more event handler...
+ eventwritetimeout = false; -- even more event handler...
+ eventreadtimeout = false;
+ fatalerror = false; -- error message
+ writecallback = false; -- will be called on write events
+ readcallback = false; -- will be called on read events
+ nointerface = true; -- lock/unlock parameter of this interface
+ noreading = false, nowriting = false; -- locks of the read/writecallback
+ startsslcallback = false; -- starting handshake callback
+ position = false; -- position of client in interfacelist
+
+ -- Properties
+ _ip = ip, _port = port, _server = server, _pattern = pattern,
+ _serverport = (server and server:port() or nil),
+ _sslctx = sslctx; -- parameters
+ _usingssl = false; -- client is using ssl;
+ }
+ if not has_luasec then interface.starttls = false; end
+ interface.id = tostring(interface):match("%x+$");
+ interface.writecallback = function( event ) -- called on write events
+ --vdebug( "new client write event, id/ip/port:", interface, ip, port )
+ if interface.nowriting or ( interface.fatalerror and ( "client to close" ~= interface.fatalerror ) ) then -- leave this event
+ --vdebug( "leaving this event because:", interface.nowriting or interface.fatalerror )
+ interface.eventwrite = false
+ return -1
+ end
+ if EV_TIMEOUT == event then -- took too long to write some data to socket -> disconnect
+ interface.fatalerror = "timeout during writing"
+ debug( "writing failed:", interface.fatalerror )
+ interface:_close()
+ interface.eventwrite = false
+ return -1
+ else -- can write :)
+ if interface._usingssl then -- handle luasec
+ if interface.eventreadtimeout then -- we have to read first
+ local ret = interface.readcallback( ) -- call readcallback
+ --vdebug( "tried to read in writecallback, result:", ret )
+ end
+ if interface.eventwritetimeout then -- luasec only
+ interface.eventwritetimeout:close( ) -- first we have to close timeout event which where regged after a wantread error
+ interface.eventwritetimeout = false
+ end
+ end
+ interface.writebuffer = { t_concat(interface.writebuffer) }
+ local succ, err, byte = interface.conn:send( interface.writebuffer[1], 1, interface.writebufferlen )
+ --vdebug( "write data:", interface.writebuffer, "error:", err, "part:", byte )
+ if succ then -- writing succesful
+ interface.writebuffer[1] = nil
+ interface.writebufferlen = 0
+ interface:ondrain();
+ if interface.fatalerror then
+ debug "closing client after writing"
+ interface:_close() -- close interface if needed
+ elseif interface.startsslcallback then -- start ssl connection if needed
+ debug "starting ssl handshake after writing"
+ interface.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, interface.startsslcallback, 0 )
+ elseif interface.writebufferlen ~= 0 then
+ -- data possibly written from ondrain
+ return EV_WRITE, cfg.WRITE_TIMEOUT
+ elseif interface.eventreadtimeout then
+ return EV_WRITE, cfg.WRITE_TIMEOUT
+ end
+ interface.eventwrite = nil
+ return -1
+ elseif byte and (err == "timeout" or err == "wantwrite") then -- want write again
+ --vdebug( "writebuffer is not empty:", err )
+ interface.writebuffer[1] = s_sub( interface.writebuffer[1], byte + 1, interface.writebufferlen ) -- new buffer
+ interface.writebufferlen = interface.writebufferlen - byte
+ if "wantread" == err then -- happens only with luasec
+ local callback = function( )
+ interface:_close()
+ interface.eventwritetimeout = nil
+ return -1;
+ end
+ interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT ) -- reg a new timeout event
+ debug( "wantread during write attempt, reg it in readcallback but dont know what really happens next..." )
+ -- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent
+ return -1
+ end
+ return EV_WRITE, cfg.WRITE_TIMEOUT
+ else -- connection was closed during writing or fatal error
+ interface.fatalerror = err or "fatal error"
+ debug( "connection failed in write event:", interface.fatalerror )
+ interface:_close()
+ interface.eventwrite = nil
return -1
end
- if EV_TIMEOUT == event then -- took too long to write some data to socket -> disconnect
- interface.fatalerror = "timeout during writing"
- debug( "writing failed:", interface.fatalerror )
- interface:_close()
- interface.eventwrite = false
- return -1
- else -- can write :)
- if interface._usingssl then -- handle luasec
- if interface.eventreadtimeout then -- we have to read first
- local ret = interface.readcallback( ) -- call readcallback
- --vdebug( "tried to read in writecallback, result:", ret )
- end
- if interface.eventwritetimeout then -- luasec only
- interface.eventwritetimeout:close( ) -- first we have to close timeout event which where regged after a wantread error
- interface.eventwritetimeout = false
- end
- end
- interface.writebuffer = { t_concat(interface.writebuffer) }
- local succ, err, byte = interface.conn:send( interface.writebuffer[1], 1, interface.writebufferlen )
- --vdebug( "write data:", interface.writebuffer, "error:", err, "part:", byte )
- if succ then -- writing succesful
- interface.writebuffer[1] = nil
- interface.writebufferlen = 0
- interface:ondrain();
- if interface.fatalerror then
- debug "closing client after writing"
- interface:_close() -- close interface if needed
- elseif interface.startsslcallback then -- start ssl connection if needed
- debug "starting ssl handshake after writing"
- interface.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, interface.startsslcallback, 0 )
- elseif interface.writebufferlen ~= 0 then
- -- data possibly written from ondrain
- return EV_WRITE, cfg.WRITE_TIMEOUT
- elseif interface.eventreadtimeout then
- return EV_WRITE, cfg.WRITE_TIMEOUT
- end
- interface.eventwrite = nil
- return -1
- elseif byte and (err == "timeout" or err == "wantwrite") then -- want write again
- --vdebug( "writebuffer is not empty:", err )
- interface.writebuffer[1] = string_sub( interface.writebuffer[1], byte + 1, interface.writebufferlen ) -- new buffer
- interface.writebufferlen = interface.writebufferlen - byte
- if "wantread" == err then -- happens only with luasec
- local callback = function( )
- interface:_close()
- interface.eventwritetimeout = nil
- return -1;
- end
- interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT ) -- reg a new timeout event
- debug( "wantread during write attempt, reg it in readcallback but dont know what really happens next..." )
- -- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent
- return -1
- end
- return EV_WRITE, cfg.WRITE_TIMEOUT
- else -- connection was closed during writing or fatal error
- interface.fatalerror = err or "fatal error"
- debug( "connection failed in write event:", interface.fatalerror )
- interface:_close()
- interface.eventwrite = nil
- return -1
- end
+ end
+ end
+
+ interface.readcallback = function( event ) -- called on read events
+ --vdebug( "new client read event, id/ip/port:", tostring(interface.id), tostring(ip), tostring(port) )
+ if interface.noreading or interface.fatalerror then -- leave this event
+ --vdebug( "leaving this event because:", tostring(interface.noreading or interface.fatalerror) )
+ interface.eventread = nil
+ return -1
+ end
+ if EV_TIMEOUT == event and not interface.conn:dirty() and interface:onreadtimeout() ~= true then
+ interface.fatalerror = "timeout during receiving"
+ debug( "connection failed:", interface.fatalerror )
+ interface:_close()
+ interface.eventread = nil
+ return -1 -- took too long to get some data from client -> disconnect
+ end
+ if interface._usingssl then -- handle luasec
+ if interface.eventwritetimeout then -- ok, in the past writecallback was regged
+ local ret = interface.writecallback( ) -- call it
+ --vdebug( "tried to write in readcallback, result:", tostring(ret) )
+ end
+ if interface.eventreadtimeout then
+ interface.eventreadtimeout:close( )
+ interface.eventreadtimeout = nil
end
end
-
- interface.readcallback = function( event ) -- called on read events
- --vdebug( "new client read event, id/ip/port:", tostring(interface.id), tostring(ip), tostring(port) )
- if interface.noreading or interface.fatalerror then -- leave this event
- --vdebug( "leaving this event because:", tostring(interface.noreading or interface.fatalerror) )
- interface.eventread = nil
- return -1
- end
- if EV_TIMEOUT == event then -- took too long to get some data from client -> disconnect
- interface.fatalerror = "timeout during receiving"
- debug( "connection failed:", interface.fatalerror )
+ local buffer, err, part = interface.conn:receive( interface._pattern ) -- receive buffer with "pattern"
+ --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )
+ buffer = buffer or part
+ if buffer and #buffer > cfg.MAX_READ_LENGTH then -- check buffer length
+ interface.fatalerror = "receive buffer exceeded"
+ debug( "fatal error:", interface.fatalerror )
+ interface:_close()
+ interface.eventread = nil
+ return -1
+ end
+ if err and ( err ~= "timeout" and err ~= "wantread" ) then
+ if "wantwrite" == err then -- need to read on write event
+ if not interface.eventwrite then -- register new write event if needed
+ interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )
+ end
+ interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT,
+ function( ) interface:_close() end, cfg.READ_TIMEOUT)
+ debug( "wantwrite during read attempt, reg it in writecallback but dont know what really happens next..." )
+ -- to be honest i dont know what happens next, if it is allowed to first read, the write etc...
+ else -- connection was closed or fatal error
+ interface.fatalerror = err
+ debug( "connection failed in read event:", interface.fatalerror )
interface:_close()
interface.eventread = nil
return -1
- else -- can read
- if interface._usingssl then -- handle luasec
- if interface.eventwritetimeout then -- ok, in the past writecallback was regged
- local ret = interface.writecallback( ) -- call it
- --vdebug( "tried to write in readcallback, result:", tostring(ret) )
- end
- if interface.eventreadtimeout then
- interface.eventreadtimeout:close( )
- interface.eventreadtimeout = nil
- end
- end
- local buffer, err, part = interface.conn:receive( interface._pattern ) -- receive buffer with "pattern"
- --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )
- buffer = buffer or part
- if buffer and #buffer > cfg.MAX_READ_LENGTH then -- check buffer length
- interface.fatalerror = "receive buffer exceeded"
- debug( "fatal error:", interface.fatalerror )
- interface:_close()
- interface.eventread = nil
- return -1
- end
- if err and ( err ~= "timeout" and err ~= "wantread" ) then
- if "wantwrite" == err then -- need to read on write event
- if not interface.eventwrite then -- register new write event if needed
- interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )
- end
- interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT,
- function( )
- interface:_close()
- end, cfg.READ_TIMEOUT
- )
- debug( "wantwrite during read attempt, reg it in writecallback but dont know what really happens next..." )
- -- to be honest i dont know what happens next, if it is allowed to first read, the write etc...
- else -- connection was closed or fatal error
- interface.fatalerror = err
- debug( "connection failed in read event:", interface.fatalerror )
- interface:_close()
- interface.eventread = nil
- return -1
- end
- else
- interface.onincoming( interface, buffer, err ) -- send new data to listener
- end
- if interface.noreading then
- interface.eventread = nil;
- return -1;
- end
- return EV_READ, cfg.READ_TIMEOUT
+ end
+ else
+ interface.onincoming( interface, buffer, err ) -- send new data to listener
+ end
+ if interface.noreading then
+ interface.eventread = nil;
+ return -1;
+ end
+ if interface.conn:dirty() then -- still data left in buffer
+ return EV_TIMEOUT, cfg.READ_RETRY_DELAY;
+ end
+ return EV_READ, cfg.READ_TIMEOUT
+ end
+
+ client:settimeout( 0 ) -- set non blocking
+ setmetatable(interface, interface_mt)
+ interfacelist[ interface ] = true -- add to interfacelist
+ return interface
+end
+
+local function handleserver( server, addr, port, pattern, listener, sslctx ) -- creates an server interface
+ debug "creating server interface..."
+ local interface = {
+ _connections = 0;
+
+ type = "server";
+ conn = server;
+ onconnect = listener.onconnect; -- will be called when new client connected
+ eventread = false; -- read event handler
+ eventclose = false; -- close event handler
+ readcallback = false; -- read event callback
+ fatalerror = false; -- error message
+ nointerface = true; -- lock/unlock parameter
+
+ _ip = addr, _port = port, _pattern = pattern,
+ _sslctx = sslctx;
+ }
+ interface.id = tostring(interface):match("%x+$");
+ interface.readcallback = function( event ) -- server handler, called on incoming connections
+ --vdebug( "server can accept, id/addr/port:", interface, addr, port )
+ if interface.fatalerror then
+ --vdebug( "leaving this event because:", self.fatalerror )
+ interface.eventread = nil
+ return -1
+ end
+ local delay = cfg.ACCEPT_DELAY
+ if EV_TIMEOUT == event then
+ if interface._connections >= cfg.MAX_CONNECTIONS then -- check connection count
+ debug( "to many connections, seconds to wait for next accept:", delay )
+ return EV_TIMEOUT, delay -- timeout...
+ else
+ return EV_READ -- accept again
end
end
+ --vdebug("max connection check ok, accepting...")
+ local client, err = server:accept() -- try to accept; TODO: check err
+ while client do
+ if interface._connections >= cfg.MAX_CONNECTIONS then
+ client:close( ) -- refuse connection
+ debug( "maximal connections reached, refuse client connection; accept delay:", delay )
+ return EV_TIMEOUT, delay -- delay for next accept attempt
+ end
+ local client_ip, client_port = client:getpeername( )
+ interface._connections = interface._connections + 1 -- increase connection count
+ local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx )
+ --vdebug( "client id:", clientinterface, "startssl:", startssl )
+ if has_luasec and sslctx then
+ clientinterface:starttls(sslctx, true)
+ else
+ clientinterface:_start_session( true )
+ end
+ debug( "accepted incoming client connection from:", client_ip or "", client_port or "", "to", port or "");
- client:settimeout( 0 ) -- set non blocking
- setmetatable(interface, interface_mt)
- interfacelist( "add", interface ) -- add to interfacelist
- return interface
+ client, err = server:accept() -- try to accept again
+ end
+ return EV_READ
+ end
+
+ server:settimeout( 0 )
+ setmetatable(interface, interface_mt)
+ interfacelist[ interface ] = true
+ interface:_start_session()
+ return interface
+end
+
+local function addserver( addr, port, listener, pattern, sslctx, startssl ) -- TODO: check arguments
+ --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslctx or "nil", startssl or "nil")
+ if sslctx and not has_luasec then
+ debug "fatal error: luasec not found"
+ return nil, "luasec not found"
+ end
+ local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE ) -- create server socket
+ if not server then
+ debug( "creating server socket on "..addr.." port "..port.." failed:", err )
+ return nil, err
+ end
+ local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- new server handler
+ debug( "new server created with id:", tostring(interface))
+ return interface
+end
+
+local function wrapclient( client, ip, port, listeners, pattern, sslctx )
+ local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx )
+ interface:_start_connection(sslctx)
+ return interface, client
+ --function handleclient( client, ip, port, server, pattern, listener, _, sslctx ) -- creates an client interface
+end
+
+local function addclient( addr, serverport, listener, pattern, sslctx, typ )
+ if sslctx and not has_luasec then
+ debug "need luasec, but not available"
+ return nil, "luasec not found"
+ end
+ if not typ then
+ local addrinfo, err = getaddrinfo(addr)
+ if not addrinfo then return nil, err end
+ if addrinfo[1] and addrinfo[1].family == "inet6" then
+ typ = "tcp6"
+ else
+ typ = "tcp"
+ end
+ end
+ local create = socket[typ]
+ if type( create ) ~= "function" then
+ return nil, "invalid socket type"
+ end
+ local client, err = create() -- creating new socket
+ if not client then
+ debug( "cannot create socket:", err )
+ return nil, err
+ end
+ client:settimeout( 0 ) -- set nonblocking
+ local res, err = client:connect( addr, serverport ) -- connect
+ if res or ( err == "timeout" ) then
+ local ip, port = client:getsockname( )
+ local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx )
+ debug( "new connection id:", interface.id )
+ return interface, err
+ else
+ debug( "new connection failed:", err )
+ return nil, err
end
end
-local handleserver
-do
- function handleserver( server, addr, port, pattern, listener, sslctx ) -- creates an server interface
- debug "creating server interface..."
- local interface = {
- _connections = 0;
-
- conn = server;
- onconnect = listener.onconnect; -- will be called when new client connected
- eventread = false; -- read event handler
- eventclose = false; -- close event handler
- readcallback = false; -- read event callback
- fatalerror = false; -- error message
- nointerface = true; -- lock/unlock parameter
-
- _ip = addr, _port = port, _pattern = pattern,
- _sslctx = sslctx;
- }
- interface.id = tostring(interface):match("%x+$");
- interface.readcallback = function( event ) -- server handler, called on incoming connections
- --vdebug( "server can accept, id/addr/port:", interface, addr, port )
- if interface.fatalerror then
- --vdebug( "leaving this event because:", self.fatalerror )
- interface.eventread = nil
- return -1
- end
- local delay = cfg.ACCEPT_DELAY
- if EV_TIMEOUT == event then
- if interface._connections >= cfg.MAX_CONNECTIONS then -- check connection count
- debug( "to many connections, seconds to wait for next accept:", delay )
- return EV_TIMEOUT, delay -- timeout...
- else
- return EV_READ -- accept again
- end
- end
- --vdebug("max connection check ok, accepting...")
- local client, err = server:accept() -- try to accept; TODO: check err
- while client do
- if interface._connections >= cfg.MAX_CONNECTIONS then
- client:close( ) -- refuse connection
- debug( "maximal connections reached, refuse client connection; accept delay:", delay )
- return EV_TIMEOUT, delay -- delay for next accept attempt
- end
- local client_ip, client_port = client:getpeername( )
- interface._connections = interface._connections + 1 -- increase connection count
- local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx )
- --vdebug( "client id:", clientinterface, "startssl:", startssl )
- if ssl and sslctx then
- clientinterface:starttls(sslctx, true)
- else
- clientinterface:_start_session( true )
- end
- debug( "accepted incoming client connection from:", client_ip or "", client_port or "", "to", port or "");
-
- client, err = server:accept() -- try to accept again
- end
- return EV_READ
- end
-
- server:settimeout( 0 )
- setmetatable(interface, interface_mt)
- interfacelist( "add", interface )
- interface:_start_session()
- return interface
- end
-end
-
-local addserver = ( function( )
- return function( addr, port, listener, pattern, sslcfg, startssl ) -- TODO: check arguments
- --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslcfg or "nil", startssl or "nil")
- local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE ) -- create server socket
- if not server then
- debug( "creating server socket on "..addr.." port "..port.." failed:", err )
- return nil, err
- end
- local sslctx
- if sslcfg then
- if not ssl then
- debug "fatal error: luasec not found"
- return nil, "luasec not found"
- end
- sslctx, err = sslcfg
- if err then
- debug( "error while creating new ssl context for server socket:", err )
- return nil, err
- end
- end
- local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- new server handler
- debug( "new server created with id:", tostring(interface))
- return interface
- end
-end )( )
-
-local addclient, wrapclient
-do
- function wrapclient( client, ip, port, listeners, pattern, sslctx )
- local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx )
- interface:_start_connection(sslctx)
- return interface, client
- --function handleclient( client, ip, port, server, pattern, listener, _, sslctx ) -- creates an client interface
- end
-
- function addclient( addr, serverport, listener, pattern, localaddr, localport, sslcfg, startssl )
- local client, err = socket.tcp() -- creating new socket
- if not client then
- debug( "cannot create socket:", err )
- return nil, err
- end
- client:settimeout( 0 ) -- set nonblocking
- if localaddr then
- local res, err = client:bind( localaddr, localport, -1 )
- if not res then
- debug( "cannot bind client:", err )
- return nil, err
- end
- end
- local sslctx
- if sslcfg then -- handle ssl/new context
- if not ssl then
- debug "need luasec, but not available"
- return nil, "luasec not found"
- end
- sslctx, err = sslcfg
- if err then
- debug( "cannot create new ssl context:", err )
- return nil, err
- end
- end
- local res, err = client:connect( addr, serverport ) -- connect
- if res or ( err == "timeout" ) then
- local ip, port = client:getsockname( )
- local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx, startssl )
- interface:_start_connection( startssl )
- debug( "new connection id:", interface.id )
- return interface, err
- else
- debug( "new connection failed:", err )
- return nil, err
- end
- end
-end
-
-
-local loop = function( ) -- starts the event loop
+local function loop( ) -- starts the event loop
base:loop( )
return "quitting";
end
-local newevent = ( function( )
- local add = base.addevent
- return function( ... )
- return add( base, ... )
- end
-end )( )
+local function newevent( ... )
+ return addevent( base, ... )
+end
-local closeallservers = function( arg )
- for _, item in ipairs( interfacelist( ) ) do
+local function closeallservers ( arg )
+ for item in pairs( interfacelist ) do
if item.type == "server" then
item:close( arg )
end
@@ -815,9 +766,9 @@
local function setquitting(yes)
if yes then
- -- Quit now
- closeallservers();
- base:loopexit();
+ -- Quit now
+ closeallservers();
+ base:loopexit();
end
end
@@ -829,7 +780,7 @@
-- being garbage-collected
local signal_events = {}; -- [signal_num] -> event object
local function hook_signal(signal_num, handler)
- local function _handler(event)
+ local function _handler()
local ret = handler();
if ret ~= false then -- Continue handling this signal?
return EV_SIGNAL; -- Yes
@@ -842,14 +793,14 @@
local function link(sender, receiver, buffersize)
local sender_locked;
-
+
function receiver:ondrain()
if sender_locked then
sender:resume();
sender_locked = nil;
end
end
-
+
function sender:onincoming(data)
receiver:write(data);
if receiver.writebufferlen >= buffersize then
@@ -861,12 +812,11 @@
end
return {
-
cfg = cfg,
base = base,
loop = loop,
link = link,
- event = event,
+ event = levent,
event_base = base,
addevent = newevent,
addserver = addserver,
diff -r c6b45cac9423 -r 7ec098b68042 net/server_select.lua
--- a/net/server_select.lua Wed May 30 21:51:15 2018 +0100
+++ b/net/server_select.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
---
+--
-- server.lua by blastbeat of the luadch project
-- Re-used here under the MIT/X Consortium License
---
+--
-- Modifications (C) 2008-2010 Matthew Wild, Waqas Hussain
--
@@ -31,14 +31,12 @@
--// lua libs //--
-local os = use "os"
local table = use "table"
local string = use "string"
local coroutine = use "coroutine"
--// lua lib methods //--
-local os_difftime = os.difftime
local math_min = math.min
local math_huge = math.huge
local table_concat = table.concat
@@ -48,13 +46,14 @@
--// extern libs //--
-local luasec = use "ssl"
+local has_luasec, luasec = pcall ( require , "ssl" )
local luasocket = use "socket" or require "socket"
local luasocket_gettime = luasocket.gettime
+local getaddrinfo = luasocket.dns.getaddrinfo
--// extern lib methods //--
-local ssl_wrap = ( luasec and luasec.wrap )
+local ssl_wrap = ( has_luasec and luasec.wrap )
local socket_bind = luasocket.bind
local socket_sleep = luasocket.sleep
local socket_select = luasocket.select
@@ -149,7 +148,7 @@
_maxsendlen = 51000 * 1024 -- max len of send buffer
_maxreadlen = 25000 * 1024 -- max len of read buffer
-_checkinterval = 1200000 -- interval in secs to check idle clients
+_checkinterval = 30 -- interval in secs to check idle clients
_sendtimeout = 60000 -- allowed send idle time in secs
_readtimeout = 6 * 60 * 60 -- allowed read idle time in secs
@@ -295,6 +294,7 @@
local status = listeners.onstatus
local disconnect = listeners.ondisconnect
local drain = listeners.ondrain
+ local onreadtimeout = listeners.onreadtimeout;
local detach = listeners.ondetach
local bufferqueue = { } -- buffer array
@@ -324,6 +324,8 @@
handler.disconnect = function( )
return disconnect
end
+ handler.onreadtimeout = onreadtimeout;
+
handler.setlistener = function( self, listeners )
if detach then
detach(self) -- Notify listener that it is no longer responsible for this connection
@@ -332,6 +334,7 @@
disconnect = listeners.ondisconnect
status = listeners.onstatus
drain = listeners.ondrain
+ handler.onreadtimeout = listeners.onreadtimeout
detach = listeners.ondetach
end
handler.getstats = function( )
@@ -404,6 +407,9 @@
out_put "server.lua: closed client handler and removed socket from list"
return true
end
+ handler.server = function ( )
+ return server
+ end
handler.ip = function( )
return ip
end
@@ -564,7 +570,7 @@
local read, wrote
handshake = coroutine_wrap( function( client ) -- create handshake coroutine
local err
- for i = 1, _maxsslhandshake do
+ for _ = 1, _maxsslhandshake do
_sendlistlen = ( wrote and removesocket( _sendlist, client, _sendlistlen ) ) or _sendlistlen
_readlistlen = ( read and removesocket( _readlist, client, _readlistlen ) ) or _readlistlen
read, wrote = nil, nil
@@ -576,6 +582,9 @@
_ = status and status( handler, "ssl-handshake-complete" )
if self.autostart_ssl and listeners.onconnect then
listeners.onconnect(self);
+ if bufferqueuelen ~= 0 then
+ _sendlistlen = addsocket(_sendlist, client, _sendlistlen)
+ end
end
_readlistlen = addsocket(_readlist, client, _readlistlen)
return true
@@ -593,13 +602,14 @@
coroutine_yield( ) -- handshake not finished
end
end
- out_put( "server.lua: ssl handshake error: ", tostring(err or "handshake too long") )
- _ = handler and handler:force_close("ssl handshake failed")
+ err = "ssl handshake error: " .. ( err or "handshake too long" );
+ out_put( "server.lua: ", err );
+ _ = handler and handler:force_close(err)
return false, err -- handshake failed
end
)
end
- if luasec then
+ if has_luasec then
handler.starttls = function( self, _sslctx)
if _sslctx then
handler:set_sslctx(_sslctx);
@@ -625,7 +635,7 @@
shutdown = id
_socketlist[ socket ] = handler
_readlistlen = addsocket(_readlist, socket, _readlistlen)
-
+
-- remove traces of the old socket
_readlistlen = removesocket( _readlist, oldsocket, _readlistlen )
_sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )
@@ -652,7 +662,7 @@
_socketlist[ socket ] = handler
_readlistlen = addsocket(_readlist, socket, _readlistlen)
- if sslctx and luasec then
+ if sslctx and has_luasec then
out_put "server.lua: auto-starting ssl negotiation..."
handler.autostart_ssl = true;
local ok, err = handler:starttls(sslctx);
@@ -713,7 +723,7 @@
sender_locked = nil;
end
end
-
+
local _readbuffer = sender.readbuffer;
function sender.readbuffer()
_readbuffer();
@@ -728,22 +738,23 @@
----------------------------------// PUBLIC //--
addserver = function( addr, port, listeners, pattern, sslctx ) -- this function provides a way for other scripts to reg a server
+ addr = addr or "*"
local err
if type( listeners ) ~= "table" then
err = "invalid listener table"
- end
- if type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then
+ elseif type ( addr ) ~= "string" then
+ err = "invalid address"
+ elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then
err = "invalid port"
elseif _server[ addr..":"..port ] then
err = "listeners on '[" .. addr .. "]:" .. port .. "' already exist"
- elseif sslctx and not luasec then
+ elseif sslctx and not has_luasec then
err = "luasec not found"
end
if err then
out_error( "server.lua, [", addr, "]:", port, ": ", err )
return nil, err
end
- addr = addr or "*"
local server, err = socket_bind( addr, port, _tcpbacklog )
if err then
out_error( "server.lua, [", addr, "]:", port, ": ", err )
@@ -853,7 +864,7 @@
local next_timer_time = math_huge;
repeat
local read, write, err = socket_select( _readlist, _sendlist, math_min(_selecttimeout, next_timer_time) )
- for i, socket in ipairs( write ) do -- send data waiting in writequeues
+ for _, socket in ipairs( write ) do -- send data waiting in writequeues
local handler = _socketlist[ socket ]
if handler then
handler.sendbuffer( )
@@ -862,7 +873,7 @@
out_put "server.lua: found no handler and closed socket (writelist)" -- this should not happen
end
end
- for i, socket in ipairs( read ) do -- receive data
+ for _, socket in ipairs( read ) do -- receive data
local handler = _socketlist[ socket ]
if handler then
handler.readbuffer( )
@@ -879,21 +890,22 @@
_currenttime = luasocket_gettime( )
-- Check for socket timeouts
- local difftime = os_difftime( _currenttime - _starttime )
- if difftime > _checkinterval then
+ if _currenttime - _starttime > _checkinterval then
_starttime = _currenttime
for handler, timestamp in pairs( _writetimes ) do
- if os_difftime( _currenttime - timestamp ) > _sendtimeout then
- --_writetimes[ handler ] = nil
+ if _currenttime - timestamp > _sendtimeout then
handler.disconnect( )( handler, "send timeout" )
handler:force_close() -- forced disconnect
end
end
for handler, timestamp in pairs( _readtimes ) do
- if os_difftime( _currenttime - timestamp ) > _readtimeout then
- --_readtimes[ handler ] = nil
- handler.disconnect( )( handler, "read timeout" )
- handler:close( ) -- forced disconnect?
+ if _currenttime - timestamp > _readtimeout then
+ if not(handler.onreadtimeout) or handler:onreadtimeout() ~= true then
+ handler.disconnect( )( handler, "read timeout" )
+ handler:close( ) -- forced disconnect?
+ else
+ _readtimes[ handler ] = _currenttime -- reset timer
+ end
end
end
end
@@ -921,6 +933,7 @@
socket_sleep( _sleeptime )
until quitting;
if once and quitting == "once" then quitting = nil; return; end
+ closeall();
return "quitting"
end
@@ -953,17 +966,46 @@
return handler, socket
end
-local addclient = function( address, port, listeners, pattern, sslctx )
- local client, err = luasocket.tcp( )
+local addclient = function( address, port, listeners, pattern, sslctx, typ )
+ local err
+ if type( listeners ) ~= "table" then
+ err = "invalid listener table"
+ elseif type ( address ) ~= "string" then
+ err = "invalid address"
+ elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then
+ err = "invalid port"
+ elseif sslctx and not has_luasec then
+ err = "luasec not found"
+ end
+ if not typ then
+ local addrinfo, err = getaddrinfo(address)
+ if not addrinfo then return nil, err end
+ if addrinfo[1] and addrinfo[1].family == "inet6" then
+ typ = "tcp6"
+ else
+ typ = "tcp"
+ end
+ end
+ local create = luasocket[typ]
+ if type( create ) ~= "function" then
+ err = "invalid socket type"
+ end
+
+ if err then
+ out_error( "server.lua, addclient: ", err )
+ return nil, err
+ end
+
+ local client, err = create( )
if err then
return nil, err
end
client:settimeout( 0 )
- _, err = client:connect( address, port )
- if err then -- try again
- local handler = wrapclient( client, address, port, listeners )
+ local ok, err = client:connect( address, port )
+ if ok or err == "timeout" then
+ return wrapclient( client, address, port, listeners, pattern, sslctx )
else
- wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx )
+ return nil, err
end
end
@@ -993,7 +1035,7 @@
addclient = addclient,
wrapclient = wrapclient,
-
+
loop = loop,
link = link,
step = step,
diff -r c6b45cac9423 -r 7ec098b68042 net/websocket.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/net/websocket.lua Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,272 @@
+-- Prosody IM
+-- Copyright (C) 2012 Florian Zeitz
+-- Copyright (C) 2014 Daurnimator
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local t_concat = table.concat;
+
+local http = require "net.http";
+local frames = require "net.websocket.frames";
+local base64 = require "util.encodings".base64;
+local sha1 = require "util.hashes".sha1;
+local random_bytes = require "util.random".bytes;
+local timer = require "util.timer";
+local log = require "util.logger".init "websocket";
+
+local close_timeout = 3; -- Seconds to wait after sending close frame until closing connection.
+
+local websockets = {};
+
+local websocket_listeners = {};
+function websocket_listeners.ondisconnect(handler, err)
+ local s = websockets[handler];
+ websockets[handler] = nil;
+ if s.close_timer then
+ timer.stop(s.close_timer);
+ s.close_timer = nil;
+ end
+ s.readyState = 3;
+ if s.close_code == nil and s.onerror then s:onerror(err); end
+ if s.onclose then s:onclose(s.close_code, s.close_message or err); end
+end
+
+function websocket_listeners.ondetach(handler)
+ websockets[handler] = nil;
+end
+
+local function fail(s, code, reason)
+ log("warn", "WebSocket connection failed, closing. %d %s", code, reason);
+ s:close(code, reason);
+ s.handler:close();
+ return false
+end
+
+function websocket_listeners.onincoming(handler, buffer, err) -- luacheck: ignore 212/err
+ local s = websockets[handler];
+ s.readbuffer = s.readbuffer..buffer;
+ while true do
+ local frame, len = frames.parse(s.readbuffer);
+ if frame == nil then break end
+ s.readbuffer = s.readbuffer:sub(len+1);
+
+ log("debug", "Websocket received frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
+
+ -- Error cases
+ if frame.RSV1 or frame.RSV2 or frame.RSV3 then -- Reserved bits non zero
+ return fail(s, 1002, "Reserved bits not zero");
+ end
+
+ if frame.opcode < 0x8 then
+ local databuffer = s.databuffer;
+ if frame.opcode == 0x0 then -- Continuation frames
+ if not databuffer then
+ return fail(s, 1002, "Unexpected continuation frame");
+ end
+ databuffer[#databuffer+1] = frame.data;
+ elseif frame.opcode == 0x1 or frame.opcode == 0x2 then -- Text or Binary frame
+ if databuffer then
+ return fail(s, 1002, "Continuation frame expected");
+ end
+ databuffer = {type=frame.opcode, frame.data};
+ s.databuffer = databuffer;
+ else
+ return fail(s, 1002, "Reserved opcode");
+ end
+ if frame.FIN then
+ s.databuffer = nil;
+ if s.onmessage then
+ s:onmessage(t_concat(databuffer), databuffer.type);
+ end
+ end
+ else -- Control frame
+ if frame.length > 125 then -- Control frame with too much payload
+ return fail(s, 1002, "Payload too large");
+ elseif not frame.FIN then -- Fragmented control frame
+ return fail(s, 1002, "Fragmented control frame");
+ end
+ if frame.opcode == 0x8 then -- Close request
+ if frame.length == 1 then
+ return fail(s, 1002, "Close frame with payload, but too short for status code");
+ end
+ local status_code, message = frames.parse_close(frame.data);
+ if status_code == nil then
+ --[[ RFC 6455 7.4.1
+ 1005 is a reserved value and MUST NOT be set as a status code in a
+ Close control frame by an endpoint. It is designated for use in
+ applications expecting a status code to indicate that no status
+ code was actually present.
+ ]]
+ status_code = 1005
+ elseif status_code < 1000 then
+ return fail(s, 1002, "Closed with invalid status code");
+ elseif ((status_code > 1003 and status_code < 1007) or status_code > 1011) and status_code < 3000 then
+ return fail(s, 1002, "Closed with reserved status code");
+ end
+ s.close_code, s.close_message = status_code, message;
+ s:close(1000);
+ return true;
+ elseif frame.opcode == 0x9 then -- Ping frame
+ frame.opcode = 0xA;
+ frame.MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked
+ handler:write(frames.build(frame));
+ elseif frame.opcode == 0xA then -- Pong frame
+ log("debug", "Received unexpected pong frame: " .. tostring(frame.data));
+ else
+ return fail(s, 1002, "Reserved opcode");
+ end
+ end
+ end
+ return true;
+end
+
+local websocket_methods = {};
+local function close_timeout_cb(now, timerid, s) -- luacheck: ignore 212/now 212/timerid
+ s.close_timer = nil;
+ log("warn", "Close timeout waiting for server to close, closing manually.");
+ s.handler:close();
+end
+function websocket_methods:close(code, reason)
+ if self.readyState < 2 then
+ code = code or 1000;
+ log("debug", "closing WebSocket with code %i: %s" , code , tostring(reason));
+ self.readyState = 2;
+ local handler = self.handler;
+ handler:write(frames.build_close(code, reason, true));
+ -- Do not close socket straight away, wait for acknowledgement from server.
+ self.close_timer = timer.add_task(close_timeout, close_timeout_cb, self);
+ elseif self.readyState == 2 then
+ log("debug", "tried to close a closing WebSocket, closing the raw socket.");
+ -- Stop timer
+ if self.close_timer then
+ timer.stop(self.close_timer);
+ self.close_timer = nil;
+ end
+ local handler = self.handler;
+ handler:close();
+ else
+ log("debug", "tried to close a closed WebSocket, ignoring.");
+ end
+end
+function websocket_methods:send(data, opcode)
+ if self.readyState < 1 then
+ return nil, "WebSocket not open yet, unable to send data.";
+ elseif self.readyState >= 2 then
+ return nil, "WebSocket closed, unable to send data.";
+ end
+ if opcode == "text" or opcode == nil then
+ opcode = 0x1;
+ elseif opcode == "binary" then
+ opcode = 0x2;
+ end
+ local frame = {
+ FIN = true;
+ MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked
+ opcode = opcode;
+ data = tostring(data);
+ };
+ log("debug", "WebSocket sending frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
+ return self.handler:write(frames.build(frame));
+end
+
+local websocket_metatable = {
+ __index = websocket_methods;
+};
+
+local function connect(url, ex, listeners)
+ ex = ex or {};
+
+ --[[RFC 6455 4.1.7:
+ The request MUST include a header field with the name
+ |Sec-WebSocket-Key|. The value of this header field MUST be a
+ nonce consisting of a randomly selected 16-byte value that has
+ been base64-encoded (see Section 4 of [RFC4648]). The nonce
+ MUST be selected randomly for each connection.
+ ]]
+ local key = base64.encode(random_bytes(16));
+
+ -- Either a single protocol string or an array of protocol strings.
+ local protocol = ex.protocol;
+ if type(protocol) == "string" then
+ protocol = { protocol, [protocol] = true };
+ elseif type(protocol) == "table" and protocol[1] then
+ for _, v in ipairs(protocol) do
+ protocol[v] = true;
+ end
+ else
+ protocol = nil;
+ end
+
+ local headers = {
+ ["Upgrade"] = "websocket";
+ ["Connection"] = "Upgrade";
+ ["Sec-WebSocket-Key"] = key;
+ ["Sec-WebSocket-Protocol"] = protocol and t_concat(protocol, ", ");
+ ["Sec-WebSocket-Version"] = "13";
+ ["Sec-WebSocket-Extensions"] = ex.extensions;
+ }
+ if ex.headers then
+ for k,v in pairs(ex.headers) do
+ headers[k] = v;
+ end
+ end
+
+ local s = setmetatable({
+ readbuffer = "";
+ databuffer = nil;
+ handler = nil;
+ close_code = nil;
+ close_message = nil;
+ close_timer = nil;
+ readyState = 0;
+ protocol = nil;
+
+ url = url;
+
+ onopen = listeners.onopen;
+ onclose = listeners.onclose;
+ onmessage = listeners.onmessage;
+ onerror = listeners.onerror;
+ }, websocket_metatable);
+
+ local http_url = url:gsub("^(ws)", "http");
+ local http_req = http.request(http_url, { -- luacheck: ignore 211/http_req
+ method = "GET";
+ headers = headers;
+ sslctx = ex.sslctx;
+ }, function(b, c, r, http_req)
+ if c ~= 101
+ or r.headers["connection"]:lower() ~= "upgrade"
+ or r.headers["upgrade"] ~= "websocket"
+ or r.headers["sec-websocket-accept"] ~= base64.encode(sha1(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
+ or (protocol and not protocol[r.headers["sec-websocket-protocol"]])
+ then
+ s.readyState = 3;
+ log("warn", "WebSocket connection to %s failed: %s", url, tostring(b));
+ if s.onerror then s:onerror("connecting-failed"); end
+ return;
+ end
+
+ s.protocol = r.headers["sec-websocket-protocol"];
+
+ -- Take possession of socket from http
+ http_req.conn = nil;
+ local handler = http_req.handler;
+ s.handler = handler;
+ websockets[handler] = s;
+ handler:setlistener(websocket_listeners);
+
+ log("debug", "WebSocket connected successfully to %s", url);
+ s.readyState = 1;
+ if s.onopen then s:onopen(); end
+ websocket_listeners.onincoming(handler, b);
+ end);
+
+ return s;
+end
+
+return {
+ connect = connect;
+};
diff -r c6b45cac9423 -r 7ec098b68042 net/websocket/frames.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/net/websocket/frames.lua Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,219 @@
+-- Prosody IM
+-- Copyright (C) 2012 Florian Zeitz
+-- Copyright (C) 2014 Daurnimator
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local softreq = require "util.dependencies".softreq;
+local random_bytes = require "util.random".bytes;
+
+local bit = assert(softreq"bit" or softreq"bit32",
+ "No bit module found. See https://prosody.im/doc/depends#bitop");
+local band = bit.band;
+local bor = bit.bor;
+local bxor = bit.bxor;
+local lshift = bit.lshift;
+local rshift = bit.rshift;
+
+local t_concat = table.concat;
+local s_byte = string.byte;
+local s_char= string.char;
+local s_sub = string.sub;
+local s_pack = string.pack;
+local s_unpack = string.unpack;
+
+if not s_pack and softreq"struct" then
+ s_pack = softreq"struct".pack;
+ s_unpack = softreq"struct".unpack;
+end
+
+local function read_uint16be(str, pos)
+ local l1, l2 = s_byte(str, pos, pos+1);
+ return l1*256 + l2;
+end
+-- FIXME: this may lose precision
+local function read_uint64be(str, pos)
+ local l1, l2, l3, l4, l5, l6, l7, l8 = s_byte(str, pos, pos+7);
+ local h = lshift(l1, 24) + lshift(l2, 16) + lshift(l3, 8) + l4;
+ local l = lshift(l5, 24) + lshift(l6, 16) + lshift(l7, 8) + l8;
+ return h * 2^32 + l;
+end
+local function pack_uint16be(x)
+ return s_char(rshift(x, 8), band(x, 0xFF));
+end
+local function get_byte(x, n)
+ return band(rshift(x, n), 0xFF);
+end
+local function pack_uint64be(x)
+ local h = band(x / 2^32, 2^32-1);
+ return s_char(get_byte(h, 24), get_byte(h, 16), get_byte(h, 8), band(h, 0xFF),
+ get_byte(x, 24), get_byte(x, 16), get_byte(x, 8), band(x, 0xFF));
+end
+
+if s_pack then
+ function pack_uint16be(x)
+ return s_pack(">I2", x);
+ end
+ function pack_uint64be(x)
+ return s_pack(">I8", x);
+ end
+end
+
+if s_unpack then
+ function read_uint16be(str, pos)
+ return s_unpack(">I2", str, pos);
+ end
+ function read_uint64be(str, pos)
+ return s_unpack(">I8", str, pos);
+ end
+end
+
+local function parse_frame_header(frame)
+ if #frame < 2 then return; end
+
+ local byte1, byte2 = s_byte(frame, 1, 2);
+ local result = {
+ FIN = band(byte1, 0x80) > 0;
+ RSV1 = band(byte1, 0x40) > 0;
+ RSV2 = band(byte1, 0x20) > 0;
+ RSV3 = band(byte1, 0x10) > 0;
+ opcode = band(byte1, 0x0F);
+
+ MASK = band(byte2, 0x80) > 0;
+ length = band(byte2, 0x7F);
+ };
+
+ local length_bytes = 0;
+ if result.length == 126 then
+ length_bytes = 2;
+ elseif result.length == 127 then
+ length_bytes = 8;
+ end
+
+ local header_length = 2 + length_bytes + (result.MASK and 4 or 0);
+ if #frame < header_length then return; end
+
+ if length_bytes == 2 then
+ result.length = read_uint16be(frame, 3);
+ elseif length_bytes == 8 then
+ result.length = read_uint64be(frame, 3);
+ end
+
+ if result.MASK then
+ result.key = { s_byte(frame, length_bytes+3, length_bytes+6) };
+ end
+
+ return result, header_length;
+end
+
+-- XORs the string `str` with the array of bytes `key`
+-- TODO: optimize
+local function apply_mask(str, key, from, to)
+ from = from or 1
+ if from < 0 then from = #str + from + 1 end -- negative indicies
+ to = to or #str
+ if to < 0 then to = #str + to + 1 end -- negative indicies
+ local key_len = #key
+ local counter = 0;
+ local data = {};
+ for i = from, to do
+ local key_index = counter%key_len + 1;
+ counter = counter + 1;
+ data[counter] = s_char(bxor(key[key_index], s_byte(str, i)));
+ end
+ return t_concat(data);
+end
+
+local function parse_frame_body(frame, header, pos)
+ if header.MASK then
+ return apply_mask(frame, header.key, pos, pos + header.length - 1);
+ else
+ return frame:sub(pos, pos + header.length - 1);
+ end
+end
+
+local function parse_frame(frame)
+ local result, pos = parse_frame_header(frame);
+ if result == nil or #frame < (pos + result.length) then return; end
+ result.data = parse_frame_body(frame, result, pos+1);
+ return result, pos + result.length;
+end
+
+local function build_frame(desc)
+ local data = desc.data or "";
+
+ assert(desc.opcode and desc.opcode >= 0 and desc.opcode <= 0xF, "Invalid WebSocket opcode");
+ if desc.opcode >= 0x8 then
+ -- RFC 6455 5.5
+ assert(#data <= 125, "WebSocket control frames MUST have a payload length of 125 bytes or less.");
+ end
+
+ local b1 = bor(desc.opcode,
+ desc.FIN and 0x80 or 0,
+ desc.RSV1 and 0x40 or 0,
+ desc.RSV2 and 0x20 or 0,
+ desc.RSV3 and 0x10 or 0);
+
+ local b2 = #data;
+ local length_extra;
+ if b2 <= 125 then -- 7-bit length
+ length_extra = "";
+ elseif b2 <= 0xFFFF then -- 2-byte length
+ b2 = 126;
+ length_extra = pack_uint16be(#data);
+ else -- 8-byte length
+ b2 = 127;
+ length_extra = pack_uint64be(#data);
+ end
+
+ local key = ""
+ if desc.MASK then
+ local key_a = desc.key
+ if key_a then
+ key = s_char(unpack(key_a, 1, 4));
+ else
+ key = random_bytes(4);
+ key_a = {key:byte(1,4)};
+ end
+ b2 = bor(b2, 0x80);
+ data = apply_mask(data, key_a);
+ end
+
+ return s_char(b1, b2) .. length_extra .. key .. data
+end
+
+local function parse_close(data)
+ local code, message
+ if #data >= 2 then
+ code = read_uint16be(data, 1);
+ if #data > 2 then
+ message = s_sub(data, 3);
+ end
+ end
+ return code, message
+end
+
+local function build_close(code, message, mask)
+ local data = pack_uint16be(code);
+ if message then
+ assert(#message<=123, "Close reason must be <=123 bytes");
+ data = data .. message;
+ end
+ return build_frame({
+ opcode = 0x8;
+ FIN = true;
+ MASK = mask;
+ data = data;
+ });
+end
+
+return {
+ parse_header = parse_frame_header;
+ parse_body = parse_frame_body;
+ parse = parse_frame;
+ build = build_frame;
+ parse_close = parse_close;
+ build_close = build_close;
+};
diff -r c6b45cac9423 -r 7ec098b68042 plugins/adhoc/adhoc.lib.lua
--- a/plugins/adhoc/adhoc.lib.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/adhoc/adhoc.lib.lua Wed May 30 21:55:09 2018 +0100
@@ -25,12 +25,14 @@
end
function _M.handle_cmd(command, origin, stanza)
- local sessionid = stanza.tags[1].attr.sessionid or uuid.generate();
- local dataIn = {};
- dataIn.to = stanza.attr.to;
- dataIn.from = stanza.attr.from;
- dataIn.action = stanza.tags[1].attr.action or "execute";
- dataIn.form = stanza.tags[1]:child_with_ns("jabber:x:data");
+ local cmdtag = stanza.tags[1]
+ local sessionid = cmdtag.attr.sessionid or uuid.generate();
+ local dataIn = {
+ to = stanza.attr.to;
+ from = stanza.attr.from;
+ action = cmdtag.attr.action or "execute";
+ form = cmdtag:get_child("x", "jabber:x:data");
+ };
local data, state = command:handler(dataIn, states[sessionid]);
states[sessionid] = state;
diff -r c6b45cac9423 -r 7ec098b68042 plugins/adhoc/mod_adhoc.lua
--- a/plugins/adhoc/mod_adhoc.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/adhoc/mod_adhoc.lua Wed May 30 21:55:09 2018 +0100
@@ -6,86 +6,90 @@
--
local st = require "util.stanza";
+local keys = require "util.iterators".keys;
+local array_collect = require "util.array".collect;
local is_admin = require "core.usermanager".is_admin;
+local jid_split = require "util.jid".split;
local adhoc_handle_cmd = module:require "adhoc".handle_cmd;
local xmlns_cmd = "http://jabber.org/protocol/commands";
-local xmlns_disco = "http://jabber.org/protocol/disco";
local commands = {};
module:add_feature(xmlns_cmd);
-module:hook("iq/host/"..xmlns_disco.."#info:query", function (event)
- local origin, stanza = event.origin, event.stanza;
- local node = stanza.tags[1].attr.node;
- if stanza.attr.type == "get" and node then
- if commands[node] then
- local privileged = is_admin(stanza.attr.from, stanza.attr.to);
- if (commands[node].permission == "admin" and privileged)
- or (commands[node].permission == "user") then
- reply = st.reply(stanza);
- reply:tag("query", { xmlns = xmlns_disco.."#info",
- node = node });
- reply:tag("identity", { name = commands[node].name,
- category = "automation", type = "command-node" }):up();
- reply:tag("feature", { var = xmlns_cmd }):up();
- reply:tag("feature", { var = "jabber:x:data" }):up();
- else
- reply = st.error_reply(stanza, "auth", "forbidden", "This item is not available to you");
- end
- origin.send(reply);
+module:hook("host-disco-info-node", function (event)
+ local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node;
+ if commands[node] then
+ local from = stanza.attr.from;
+ local privileged = is_admin(from, stanza.attr.to);
+ local global_admin = is_admin(from);
+ local username, hostname = jid_split(from);
+ local command = commands[node];
+ if (command.permission == "admin" and privileged)
+ or (command.permission == "global_admin" and global_admin)
+ or (command.permission == "local_user" and hostname == module.host)
+ or (command.permission == "user") then
+ reply:tag("identity", { name = command.name,
+ category = "automation", type = "command-node" }):up();
+ reply:tag("feature", { var = xmlns_cmd }):up();
+ reply:tag("feature", { var = "jabber:x:data" }):up();
+ event.exists = true;
+ else
+ origin.send(st.error_reply(stanza, "auth", "forbidden", "This item is not available to you"));
return true;
- elseif node == xmlns_cmd then
- reply = st.reply(stanza);
- reply:tag("query", { xmlns = xmlns_disco.."#info",
- node = node });
- reply:tag("identity", { name = "Ad-Hoc Commands",
- category = "automation", type = "command-list" }):up();
- origin.send(reply);
- return true;
-
end
+ elseif node == xmlns_cmd then
+ reply:tag("identity", { name = "Ad-Hoc Commands",
+ category = "automation", type = "command-list" }):up();
+ event.exists = true;
end
end);
-module:hook("iq/host/"..xmlns_disco.."#items:query", function (event)
- local origin, stanza = event.origin, event.stanza;
- if stanza.attr.type == "get" and stanza.tags[1].attr.node
- and stanza.tags[1].attr.node == xmlns_cmd then
- local admin = is_admin(stanza.attr.from, stanza.attr.to);
- local global_admin = is_admin(stanza.attr.from);
- reply = st.reply(stanza);
- reply:tag("query", { xmlns = xmlns_disco.."#items",
- node = xmlns_cmd });
- for node, command in pairs(commands) do
- if (command.permission == "admin" and admin)
- or (command.permission == "global_admin" and global_admin)
- or (command.permission == "user") then
- reply:tag("item", { name = command.name,
- node = node, jid = module:get_host() });
- reply:up();
- end
+module:hook("host-disco-items-node", function (event)
+ local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node;
+ if node ~= xmlns_cmd then
+ return;
+ end
+
+ local from = stanza.attr.from;
+ local admin = is_admin(from, stanza.attr.to);
+ local global_admin = is_admin(from);
+ local username, hostname = jid_split(from);
+ local nodes = array_collect(keys(commands)):sort();
+ for _, node in ipairs(nodes) do
+ local command = commands[node];
+ if (command.permission == "admin" and admin)
+ or (command.permission == "global_admin" and global_admin)
+ or (command.permission == "local_user" and hostname == module.host)
+ or (command.permission == "user") then
+ reply:tag("item", { name = command.name,
+ node = node, jid = module:get_host() });
+ reply:up();
end
- origin.send(reply);
- return true;
end
-end, 500);
+ event.exists = true;
+end);
module:hook("iq/host/"..xmlns_cmd..":command", function (event)
local origin, stanza = event.origin, event.stanza;
if stanza.attr.type == "set" then
local node = stanza.tags[1].attr.node
- if commands[node] then
- local admin = is_admin(stanza.attr.from, stanza.attr.to);
- local global_admin = is_admin(stanza.attr.from);
- if (commands[node].permission == "admin" and not admin)
- or (commands[node].permission == "global_admin" and not global_admin) then
+ local command = commands[node];
+ if command then
+ local from = stanza.attr.from;
+ local admin = is_admin(from, stanza.attr.to);
+ local global_admin = is_admin(from);
+ local username, hostname = jid_split(from);
+ if (command.permission == "admin" and not admin)
+ or (command.permission == "global_admin" and not global_admin)
+ or (command.permission == "local_user" and hostname ~= module.host) then
origin.send(st.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up()
:add_child(commands[node]:cmdtag("canceled")
:tag("note", {type="error"}):text("You don't have permission to execute this command")));
return true
end
-- User has permission now execute the command
- return adhoc_handle_cmd(commands[node], origin, stanza);
+ adhoc_handle_cmd(commands[node], origin, stanza);
+ return true;
end
end
end, 500);
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_admin_adhoc.lua
--- a/plugins/mod_admin_adhoc.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_admin_adhoc.lua Wed May 30 21:55:09 2018 +0100
@@ -9,6 +9,7 @@
local prosody = _G.prosody;
local hosts = prosody.hosts;
local t_concat = table.concat;
+local t_sort = table.sort;
local module_host = module:get_host();
@@ -25,10 +26,11 @@
local timer_add_task = require "util.timer".add_task;
local dataforms_new = require "util.dataforms".new;
local array = require "util.array";
-local modulemanager = require "modulemanager";
+local modulemanager = require "core.modulemanager";
local core_post_stanza = prosody.core_post_stanza;
local adhoc_simple = require "util.adhoc".new_simple_form;
local adhoc_initial = require "util.adhoc".new_initial_data_form;
+local set = require"util.set";
module:depends("adhoc");
local adhoc_new = module:require "adhoc".new;
@@ -95,7 +97,7 @@
if module_host ~= host then
return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. module_host}};
end
- if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then
+ if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host, nil) then
return { status = "completed", info = "Password successfully changed" };
else
return { status = "completed", error = { message = "User does not exist" } };
@@ -245,7 +247,7 @@
local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
for jid in pairs(roster) do
- if jid ~= "pending" and jid then
+ if jid then
query:tag("item", {
jid = jid,
subscription = roster[jid].subscription,
@@ -298,7 +300,7 @@
local IPs = "";
local resources = "";
for jid in pairs(roster) do
- if jid ~= "pending" and jid then
+ if jid then
rostersize = rostersize + 1;
end
end
@@ -345,7 +347,7 @@
count = count + 1;
if fields.details then
for resource, session in pairs(user.sessions or {}) do
- local status, priority = "unavailable", tostring(session.priority or "-");
+ local status, priority, ip = "unavailable", tostring(session.priority or "-"), session.ip or "";
if session.presence then
status = session.presence:child_with_name("show");
if status then
@@ -354,13 +356,92 @@
status = "available";
end
end
- users[#users+1] = " - "..resource..": "..status.."("..priority..")";
+ users[#users+1] = " - "..resource..": "..status.."("..priority.."), IP: ["..ip.."]";
end
end
end
return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} };
end);
+-- Getting a list of S2S connections (this host)
+local list_s2s_this_result = dataforms_new {
+ title = "List of S2S connections on this host";
+
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/s2s#list" };
+ { name = "sessions", type = "text-multi", label = "Connections:" };
+ { name = "num_in", type = "text-single", label = "#incomming connections:" };
+ { name = "num_out", type = "text-single", label = "#outgoing connections:" };
+};
+
+local function session_flags(session, line)
+ line = line or {};
+
+ if session.id then
+ line[#line+1] = "["..session.id.."]"
+ else
+ line[#line+1] = "["..session.type..(tostring(session):match("%x*$")).."]"
+ end
+
+ local flags = {};
+ if session.cert_identity_status == "valid" then
+ flags[#flags+1] = "authenticated";
+ end
+ if session.secure then
+ flags[#flags+1] = "encrypted";
+ end
+ if session.compressed then
+ flags[#flags+1] = "compressed";
+ end
+ if session.smacks then
+ flags[#flags+1] = "sm";
+ end
+ if session.ip and session.ip:match(":") then
+ flags[#flags+1] = "IPv6";
+ end
+ line[#line+1] = "("..t_concat(flags, ", ")..")";
+
+ return t_concat(line, " ");
+end
+
+local function list_s2s_this_handler(self, data, state)
+ local count_in, count_out = 0, 0;
+ local s2s_list = {};
+
+ local s2s_sessions = module:shared"/*/s2s/sessions";
+ for _, session in pairs(s2s_sessions) do
+ local remotehost, localhost, direction;
+ if session.direction == "outgoing" then
+ direction = "->";
+ count_out = count_out + 1;
+ remotehost, localhost = session.to_host or "?", session.from_host or "?";
+ else
+ direction = "<-";
+ count_in = count_in + 1;
+ remotehost, localhost = session.from_host or "?", session.to_host or "?";
+ end
+ local sess_lines = { r = remotehost,
+ session_flags(session, { "", direction, remotehost or "?" })};
+
+ if localhost == module_host then
+ s2s_list[#s2s_list+1] = sess_lines;
+ end
+ end
+
+ t_sort(s2s_list, function(a, b)
+ return a.r < b.r;
+ end);
+
+ for i, sess_lines in ipairs(s2s_list) do
+ s2s_list[i] = sess_lines[1];
+ end
+
+ return { status = "completed", result = { layout = list_s2s_this_result; values = {
+ sessions = t_concat(s2s_list, "\n"),
+ num_in = tostring(count_in),
+ num_out = tostring(count_out)
+ } } };
+end
+
-- Getting a list of loaded modules
local list_modules_result = dataforms_new {
title = "List of loaded modules";
@@ -489,7 +570,7 @@
for _, host in pairs(hosts) do
loaded_modules:append(array(keys(host.modules)));
end
- loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
+ loaded_modules = array(set.new(loaded_modules):items()):sort();
return { module = loaded_modules };
end, function(fields, err)
local is_global = false;
@@ -533,6 +614,7 @@
end);
local function send_to_online(message, server)
+ local sessions;
if server then
sessions = { [server] = hosts[server] };
else
@@ -631,7 +713,7 @@
for _, host in pairs(hosts) do
loaded_modules:append(array(keys(host.modules)));
end
- loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
+ loaded_modules = array(set.new(loaded_modules):items()):sort();
return { module = loaded_modules };
end, function(fields, err)
local is_global = false;
@@ -727,6 +809,7 @@
local get_user_roster_desc = adhoc_new("Get User Roster","http://jabber.org/protocol/admin#get-user-roster", get_user_roster_handler, "admin");
local get_user_stats_desc = adhoc_new("Get User Statistics","http://jabber.org/protocol/admin#user-stats", get_user_stats_handler, "admin");
local get_online_users_desc = adhoc_new("Get List of Online Users", "http://jabber.org/protocol/admin#get-online-users-list", get_online_users_command_handler, "admin");
+local list_s2s_this_desc = adhoc_new("List S2S connections", "http://prosody.im/protocol/s2s#list", list_s2s_this_handler, "admin");
local list_modules_desc = adhoc_new("List loaded modules", "http://prosody.im/protocol/modules#list", list_modules_handler, "admin");
local load_module_desc = adhoc_new("Load module", "http://prosody.im/protocol/modules#load", load_module_handler, "admin");
local globally_load_module_desc = adhoc_new("Globally load module", "http://prosody.im/protocol/modules#global-load", globally_load_module_handler, "global_admin");
@@ -747,6 +830,7 @@
module:provides("adhoc", get_user_roster_desc);
module:provides("adhoc", get_user_stats_desc);
module:provides("adhoc", get_online_users_desc);
+module:provides("adhoc", list_s2s_this_desc);
module:provides("adhoc", list_modules_desc);
module:provides("adhoc", load_module_desc);
module:provides("adhoc", globally_load_module_desc);
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_admin_telnet.lua
--- a/plugins/mod_admin_telnet.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_admin_telnet.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -17,22 +17,21 @@
local prosody = _G.prosody;
local hosts = prosody.hosts;
-local incoming_s2s = prosody.incoming_s2s;
local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" };
local iterators = require "util.iterators";
local keys, values = iterators.keys, iterators.values;
-local jid_bare, jid_split = import("util.jid", "bare", "prepped_split");
+local jid_bare, jid_split, jid_join = import("util.jid", "bare", "prepped_split", "join");
local set, array = require "util.set", require "util.array";
local cert_verify_identity = require "util.x509".verify_identity;
local envload = require "util.envload".envload;
local envloadfile = require "util.envload".envloadfile;
+local has_pposix, pposix = pcall(require, "util.pposix");
local commands = module:shared("commands")
local def_env = module:shared("env");
local default_env_mt = { __index = def_env };
-local core_post_stanza = prosody.core_post_stanza;
local function redirect_output(_G, session)
local env = setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end });
@@ -60,20 +59,20 @@
disconnect = function () conn:close(); end;
};
session.env = setmetatable({}, default_env_mt);
-
+
-- Load up environment with helper objects
for name, t in pairs(def_env) do
if type(t) == "table" then
session.env[name] = setmetatable({ session = session }, { __index = t });
end
end
-
+
return session;
end
function console:process_line(session, line)
local useglobalenv;
-
+
if line:match("^>") then
line = line:gsub("^>", "");
useglobalenv = true;
@@ -87,9 +86,9 @@
return;
end
end
-
+
session.env._ = line;
-
+
local chunkname = "=console";
local env = (useglobalenv and redirect_output(_G, session)) or session.env or nil
local chunk, err = envload("return "..line, chunkname, env);
@@ -103,20 +102,20 @@
return;
end
end
-
+
local ranok, taskok, message = pcall(chunk);
-
+
if not (ranok or message or useglobalenv) and commands[line:lower()] then
commands[line:lower()](session, line);
return;
end
-
+
if not ranok then
session.print("Fatal error while running command, it did not complete");
session.print("Error: "..taskok);
return;
end
-
+
if not message then
session.print("Result: "..tostring(taskok));
return;
@@ -125,7 +124,7 @@
session.print("Message: "..tostring(message));
return;
end
-
+
session.print("OK: "..tostring(message));
end
@@ -155,6 +154,14 @@
session.partial_data = data:match("[^\n]+$");
end
+function console_listener.onreadtimeout(conn)
+ local session = sessions[conn];
+ if session then
+ session.send("\0");
+ return true;
+ end
+end
+
function console_listener.ondisconnect(conn, err)
local session = sessions[conn];
if session then
@@ -217,9 +224,11 @@
print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]]
print [[c2s:show_insecure() - Show all unencrypted client connections]]
print [[c2s:show_secure() - Show all encrypted client connections]]
+ print [[c2s:show_tls() - Show TLS cipher info for encrypted sessions]]
print [[c2s:close(jid) - Close all sessions for the specified JID]]
elseif section == "s2s" then
print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]]
+ print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]]
print [[s2s:close(from, to) - Close a connection from one domain to another]]
print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]]
elseif section == "module" then
@@ -272,6 +281,8 @@
-- Session environment --
-- Anything in def_env will be accessible within the session as a global variable
+--luacheck: ignore 212/self
+
def_env.server = {};
function def_env.server:insane_reload()
@@ -313,9 +324,8 @@
end
function def_env.server:memory()
- local pposix = require("util.pposix");
- if not pposix.meminfo then
- return true, "Lua is using "..collectgarbage("count");
+ if not has_pposix or not pposix.meminfo then
+ return true, "Lua is using "..human(collectgarbage("count"));
end
local mem, lua_mem = pposix.meminfo(), collectgarbage("count");
local print = self.session.print;
@@ -337,10 +347,9 @@
elseif type(hosts) == "string" then
return set.new { hosts };
elseif hosts == nil then
- local mm = require "modulemanager";
local hosts_set = set.new(array.collect(keys(prosody.hosts)))
- / function (host) return (prosody.hosts[host].type == "local" or module and mm.is_loaded(host, module)) and host or nil; end;
- if module and mm.get_module("*", module) then
+ / function (host) return (prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module)) and host or nil; end;
+ if module and modulemanager.get_module("*", module) then
hosts_set:add("*");
end
return hosts_set;
@@ -348,15 +357,13 @@
end
function def_env.module:load(name, hosts, config)
- local mm = require "modulemanager";
-
hosts = get_hosts_set(hosts);
-
+
-- Load the module for each host
local ok, err, count, mod = true, nil, 0, nil;
for host in hosts do
- if (not mm.is_loaded(host, name)) then
- mod, err = mm.load(host, name, config);
+ if (not modulemanager.is_loaded(host, name)) then
+ mod, err = modulemanager.load(host, name, config);
if not mod then
ok = false;
if err == "global-module-already-loaded" then
@@ -372,20 +379,18 @@
end
end
end
-
- return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
+
+ return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
end
function def_env.module:unload(name, hosts)
- local mm = require "modulemanager";
+ hosts = get_hosts_set(hosts, name);
- hosts = get_hosts_set(hosts, name);
-
-- Unload the module for each host
local ok, err, count = true, nil, 0;
for host in hosts do
- if mm.is_loaded(host, name) then
- ok, err = mm.unload(host, name);
+ if modulemanager.is_loaded(host, name) then
+ ok, err = modulemanager.unload(host, name);
if not ok then
ok = false;
self.session.print(err or "Unknown error unloading module");
@@ -399,8 +404,6 @@
end
function def_env.module:reload(name, hosts)
- local mm = require "modulemanager";
-
hosts = array.collect(get_hosts_set(hosts, name)):sort(function (a, b)
if a == "*" then return true
elseif b == "*" then return false
@@ -410,8 +413,8 @@
-- Reload the module for each host
local ok, err, count = true, nil, 0;
for _, host in ipairs(hosts) do
- if mm.is_loaded(host, name) then
- ok, err = mm.reload(host, name);
+ if modulemanager.is_loaded(host, name) then
+ ok, err = modulemanager.reload(host, name);
if not ok then
ok = false;
self.session.print(err or "Unknown error reloading module");
@@ -438,7 +441,7 @@
if type(hosts) ~= "table" then
return false, "Please supply a host or a list of hosts you would like to see";
end
-
+
local print = self.session.print;
for _, host in ipairs(hosts) do
print((host == "*" and "Global" or host)..":");
@@ -477,61 +480,109 @@
return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
end
-def_env.hosts = {};
-function def_env.hosts:list()
- for host, host_session in pairs(hosts) do
- self.session.print(host);
+local function common_info(session, line)
+ if session.id then
+ line[#line+1] = "["..session.id.."]"
+ else
+ line[#line+1] = "["..session.type..(tostring(session):match("%x*$")).."]"
end
- return true, "Done";
end
-function def_env.hosts:add(name)
+local function session_flags(session, line)
+ line = line or {};
+ common_info(session, line);
+ if session.type == "c2s" then
+ local status, priority = "unavailable", tostring(session.priority or "-");
+ if session.presence then
+ status = session.presence:get_child_text("show") or "available";
+ end
+ line[#line+1] = status.."("..priority..")";
+ end
+ if session.cert_identity_status == "valid" then
+ line[#line+1] = "(authenticated)";
+ end
+ if session.secure then
+ line[#line+1] = "(encrypted)";
+ end
+ if session.compressed then
+ line[#line+1] = "(compressed)";
+ end
+ if session.smacks then
+ line[#line+1] = "(sm)";
+ end
+ if session.ip and session.ip:match(":") then
+ line[#line+1] = "(IPv6)";
+ end
+ if session.remote then
+ line[#line+1] = "(remote)";
+ end
+ return table.concat(line, " ");
+end
+
+local function tls_info(session, line)
+ line = line or {};
+ common_info(session, line);
+ if session.secure then
+ local sock = session.conn and session.conn.socket and session.conn:socket();
+ if sock and sock.info then
+ local info = sock:info();
+ line[#line+1] = ("(%s with %s)"):format(info.protocol, info.cipher);
+ else
+ line[#line+1] = "(cipher info unavailable)";
+ end
+ else
+ line[#line+1] = "(insecure)";
+ end
+ return table.concat(line, " ");
end
def_env.c2s = {};
+local function get_jid(session)
+ if session.username then
+ return session.full_jid or jid_join(session.username, session.host, session.resource);
+ end
+
+ local conn = session.conn;
+ local ip = session.ip or "?";
+ local clientport = conn and conn:clientport() or "?";
+ local serverip = conn and conn.server and conn:server():ip() or "?";
+ local serverport = conn and conn:serverport() or "?"
+ return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport);
+end
+
local function show_c2s(callback)
- for hostname, host in pairs(hosts) do
- for username, user in pairs(host.sessions or {}) do
- for resource, session in pairs(user.sessions or {}) do
- local jid = username.."@"..hostname.."/"..resource;
- callback(jid, session);
+ local c2s = array.collect(values(module:shared"/*/c2s/sessions"));
+ c2s:sort(function(a, b)
+ if a.host == b.host then
+ if a.username == b.username then
+ return (a.resource or "") > (b.resource or "");
end
+ return (a.username or "") > (b.username or "");
end
- end
+ return (a.host or "") > (b.host or "");
+ end):map(function (session)
+ callback(get_jid(session), session)
+ end);
end
function def_env.c2s:count(match_jid)
- local count = 0;
- show_c2s(function (jid, session)
- if (not match_jid) or jid:match(match_jid) then
- count = count + 1;
- end
- end);
- return true, "Total: "..count.." clients";
+ return true, "Total: ".. iterators.count(values(module:shared"/*/c2s/sessions")) .." clients";
end
-function def_env.c2s:show(match_jid)
+function def_env.c2s:show(match_jid, annotate)
local print, count = self.session.print, 0;
- local curr_host;
+ annotate = annotate or session_flags;
+ local curr_host = false;
show_c2s(function (jid, session)
if curr_host ~= session.host then
curr_host = session.host;
- print(curr_host);
+ print(curr_host or "(not connected to any host yet)");
end
if (not match_jid) or jid:match(match_jid) then
count = count + 1;
- local status, priority = "unavailable", tostring(session.priority or "-");
- if session.presence then
- status = session.presence:child_with_name("show");
- if status then
- status = status:get_text() or "[invalid!]";
- else
- status = "available";
- end
- end
- print(" "..jid.." - "..status.."("..priority..")");
- end
+ print(annotate(session, { " ", jid }));
+ end
end);
return true, "Total: "..count.." clients";
end
@@ -542,7 +593,7 @@
if ((not match_jid) or jid:match(match_jid)) and not session.secure then
count = count + 1;
print(jid);
- end
+ end
end);
return true, "Total: "..count.." insecure client connections";
end
@@ -553,11 +604,15 @@
if ((not match_jid) or jid:match(match_jid)) and session.secure then
count = count + 1;
print(jid);
- end
+ end
end);
return true, "Total: "..count.." secure client connections";
end
+function def_env.c2s:show_tls(match_jid)
+ return self:show(match_jid, tls_info);
+end
+
function def_env.c2s:close(match_jid)
local count = 0;
show_c2s(function (jid, session)
@@ -569,99 +624,87 @@
return true, "Total: "..count.." sessions closed";
end
-local function session_flags(session, line)
- if session.cert_identity_status == "valid" then
- line[#line+1] = "(secure)";
- elseif session.secure then
- line[#line+1] = "(encrypted)";
- end
- if session.compressed then
- line[#line+1] = "(compressed)";
- end
- if session.smacks then
- line[#line+1] = "(sm)";
- end
- if session.conn and session.conn:ip():match(":") then
- line[#line+1] = "(IPv6)";
- end
- return table.concat(line, " ");
-end
def_env.s2s = {};
-function def_env.s2s:show(match_jid)
- local _print = self.session.print;
+function def_env.s2s:show(match_jid, annotate)
local print = self.session.print;
-
+ annotate = annotate or session_flags;
+
local count_in, count_out = 0,0;
-
- for host, host_session in pairs(hosts) do
- print = function (...) _print(host); _print(...); print = _print; end
- for remotehost, session in pairs(host_session.s2sout) do
- if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
- count_out = count_out + 1;
- print(session_flags(session, {" ", host, "->", remotehost}));
- if session.sendq then
- print(" There are "..#session.sendq.." queued outgoing stanzas for this connection");
- end
- if session.type == "s2sout_unauthed" then
- if session.connecting then
- print(" Connection not yet established");
- if not session.srv_hosts then
- if not session.conn then
- print(" We do not yet have a DNS answer for this host's SRV records");
- else
- print(" This host has no SRV records, using A record instead");
- end
- elseif session.srv_choice then
- print(" We are on SRV record "..session.srv_choice.." of "..#session.srv_hosts);
- local srv_choice = session.srv_hosts[session.srv_choice];
- print(" Using "..(srv_choice.target or ".")..":"..(srv_choice.port or 5269));
+ local s2s_list = { };
+
+ local s2s_sessions = module:shared"/*/s2s/sessions";
+ for _, session in pairs(s2s_sessions) do
+ local remotehost, localhost, direction;
+ if session.direction == "outgoing" then
+ direction = "->";
+ count_out = count_out + 1;
+ remotehost, localhost = session.to_host or "?", session.from_host or "?";
+ else
+ direction = "<-";
+ count_in = count_in + 1;
+ remotehost, localhost = session.from_host or "?", session.to_host or "?";
+ end
+ local sess_lines = { l = localhost, r = remotehost,
+ annotate(session, { "", direction, remotehost or "?" })};
+
+ if (not match_jid) or remotehost:match(match_jid) or localhost:match(match_jid) then
+ table.insert(s2s_list, sess_lines);
+ local print = function (s) table.insert(sess_lines, " "..s); end
+ if session.sendq then
+ print("There are "..#session.sendq.." queued outgoing stanzas for this connection");
+ end
+ if session.type == "s2sout_unauthed" then
+ if session.connecting then
+ print("Connection not yet established");
+ if not session.srv_hosts then
+ if not session.conn then
+ print("We do not yet have a DNS answer for this host's SRV records");
+ else
+ print("This host has no SRV records, using A record instead");
end
- elseif session.notopen then
- print(" The has not yet been opened");
- elseif not session.dialback_key then
- print(" Dialback has not been initiated yet");
- elseif session.dialback_key then
- print(" Dialback has been requested, but no result received");
+ elseif session.srv_choice then
+ print("We are on SRV record "..session.srv_choice.." of "..#session.srv_hosts);
+ local srv_choice = session.srv_hosts[session.srv_choice];
+ print("Using "..(srv_choice.target or ".")..":"..(srv_choice.port or 5269));
end
+ elseif session.notopen then
+ print("The has not yet been opened");
+ elseif not session.dialback_key then
+ print("Dialback has not been initiated yet");
+ elseif session.dialback_key then
+ print("Dialback has been requested, but no result received");
end
end
- end
- local subhost_filter = function (h)
- return (match_jid and h:match(match_jid));
- end
- for session in pairs(incoming_s2s) do
- if session.to_host == host and ((not match_jid) or host:match(match_jid)
- or (session.from_host and session.from_host:match(match_jid))
- -- Pft! is what I say to list comprehensions
- or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
- count_in = count_in + 1;
- print(session_flags(session, {" ", host, "<-", session.from_host or "(unknown)"}));
- if session.type == "s2sin_unauthed" then
- print(" Connection not yet authenticated");
- end
+ if session.type == "s2sin_unauthed" then
+ print("Connection not yet authenticated");
+ elseif session.type == "s2sin" then
for name in pairs(session.hosts) do
if name ~= session.from_host then
- print(" also hosts "..tostring(name));
+ print("also hosts "..tostring(name));
end
end
end
end
-
- print = _print;
end
-
- for session in pairs(incoming_s2s) do
- if not session.to_host and ((not match_jid) or session.from_host and session.from_host:match(match_jid)) then
- count_in = count_in + 1;
- print("Other incoming s2s connections");
- print(" (unknown) <- "..(session.from_host or "(unknown)"));
- end
+
+ -- Sort by local host, then remote host
+ table.sort(s2s_list, function(a,b)
+ if a.l == b.l then return a.r < b.r; end
+ return a.l < b.l;
+ end);
+ local lasthost;
+ for _, sess_lines in ipairs(s2s_list) do
+ if sess_lines.l ~= lasthost then print(sess_lines.l); lasthost=sess_lines.l end
+ for _, line in ipairs(sess_lines) do print(line); end
end
-
return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
end
+function def_env.s2s:show_tls(match_jid)
+ return self:show(match_jid, tls_info);
+end
+
local function print_subject(print, subject)
for _, entry in ipairs(subject) do
print(
@@ -688,16 +731,10 @@
end
function def_env.s2s:showcert(domain)
- local ser = require "util.serialization".serialize;
local print = self.session.print;
- local domain_sessions = set.new(array.collect(keys(incoming_s2s)))
- /function(session) return session.from_host == domain and session or nil; end;
- for local_host in values(prosody.hosts) do
- local s2sout = local_host.s2sout;
- if s2sout and s2sout[domain] then
- domain_sessions:add(s2sout[domain]);
- end
- end
+ local s2s_sessions = module:shared"/*/s2s/sessions";
+ local domain_sessions = set.new(array.collect(values(s2s_sessions)))
+ /function(session) return (session.to_host == domain or session.from_host == domain) and session or nil; end;
local cert_set = {};
for session in domain_sessions do
local conn = session.conn;
@@ -736,18 +773,18 @@
local domain_certs = array.collect(values(cert_set));
-- Phew. We now have a array of unique certificates presented by domain.
local n_certs = #domain_certs;
-
+
if n_certs == 0 then
return "No certificates found for "..domain;
end
-
+
local function _capitalize_and_colon(byte)
return string.upper(byte)..":";
end
local function pretty_fingerprint(hash)
return hash:gsub("..", _capitalize_and_colon):sub(1, -2);
end
-
+
for cert_info in values(domain_certs) do
local certs = cert_info.certs;
local cert = certs[1];
@@ -788,76 +825,38 @@
function def_env.s2s:close(from, to)
local print, count = self.session.print, 0;
-
- if not (from and to) then
+ local s2s_sessions = module:shared"/*/s2s/sessions";
+
+ local match_id;
+ if from and not to then
+ match_id, from = from;
+ elseif not to then
return false, "Syntax: s2s:close('from', 'to') - Closes all s2s sessions from 'from' to 'to'";
elseif from == to then
return false, "Both from and to are the same... you can't do that :)";
end
-
- if hosts[from] and not hosts[to] then
- -- Is an outgoing connection
- local session = hosts[from].s2sout[to];
- if not session then
- print("No outgoing connection from "..from.." to "..to)
- else
+
+ for _, session in pairs(s2s_sessions) do
+ local id = session.type..tostring(session):match("[a-f0-9]+$");
+ if (match_id and match_id == id)
+ or (session.from_host == from and session.to_host == to) then
+ print(("Closing connection from %s to %s [%s]"):format(session.from_host, session.to_host, id));
(session.close or s2smanager.destroy_session)(session);
- count = count + 1;
- print("Closed outgoing session from "..from.." to "..to);
+ count = count + 1 ;
end
- elseif hosts[to] and not hosts[from] then
- -- Is an incoming connection
- for session in pairs(incoming_s2s) do
- if session.to_host == to and session.from_host == from then
- (session.close or s2smanager.destroy_session)(session);
- count = count + 1;
- end
- end
-
- if count == 0 then
- print("No incoming connections from "..from.." to "..to);
- else
- print("Closed "..count.." incoming session"..((count == 1 and "") or "s").." from "..from.." to "..to);
- end
- elseif hosts[to] and hosts[from] then
- return false, "Both of the hostnames you specified are local, there are no s2s sessions to close";
- else
- return false, "Neither of the hostnames you specified are being used on this server";
end
-
return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
end
function def_env.s2s:closeall(host)
- local count = 0;
-
- if not host or type(host) ~= "string" then return false, "wrong syntax: please use s2s:closeall('hostname.tld')"; end
- if hosts[host] then
- for session in pairs(incoming_s2s) do
- if session.to_host == host then
- (session.close or s2smanager.destroy_session)(session);
- count = count + 1;
- end
- end
- for _, session in pairs(hosts[host].s2sout) do
- (session.close or s2smanager.destroy_session)(session);
- count = count + 1;
- end
- else
- for session in pairs(incoming_s2s) do
- if session.from_host == host then
- (session.close or s2smanager.destroy_session)(session);
- count = count + 1;
- end
+ local count = 0;
+ local s2s_sessions = module:shared"/*/s2s/sessions";
+ for _,session in pairs(s2s_sessions) do
+ if not host or session.from_host == host or session.to_host == host then
+ session:close();
+ count = count + 1;
end
- for _, h in pairs(hosts) do
- if h.s2sout[host] then
- (h.s2sout[host].close or s2smanager.destroy_session)(h.s2sout[host]);
- count = count + 1;
- end
- end
- end
-
+ end
if count == 0 then return false, "No sessions to close.";
else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end
end
@@ -874,9 +873,19 @@
function def_env.host:list()
local print = self.session.print;
local i = 0;
+ local type;
for host in values(array.collect(keys(prosody.hosts)):sort()) do
i = i + 1;
- print(host);
+ type = hosts[host].type;
+ if type == "local" then
+ print(host);
+ else
+ type = module:context(host):get_option_string("component_module", type);
+ if type ~= "component" then
+ type = type .. " component";
+ end
+ print(("%s (%s)"):format(host, type));
+ end
end
return true, i.." hosts";
end
@@ -946,11 +955,11 @@
end
function def_env.muc:create(room_jid)
- local room, host = check_muc(room_jid);
+ local room_name, host = check_muc(room_jid);
if not room_name then
return room_name, host;
end
- if not room then return nil, host end
+ if not room_name then return nil, host end
if hosts[host].modules.muc.rooms[room_jid] then return nil, "Room exists already" end
return hosts[host].modules.muc.create_room(room_jid);
end
@@ -967,6 +976,20 @@
return setmetatable({ room = room_obj }, console_room_mt);
end
+function def_env.muc:list(host)
+ local host_session = hosts[host];
+ if not host_session or not host_session.modules.muc then
+ return nil, "Please supply the address of a local MUC component";
+ end
+ local print = self.session.print;
+ local c = 0;
+ for name in keys(host_session.modules.muc.rooms) do
+ print(name);
+ c = c + 1;
+ end
+ return true, c.." rooms";
+end
+
local um = require"core.usermanager";
def_env.user = {};
@@ -1007,7 +1030,7 @@
elseif not um.user_exists(username, host) then
return nil, "No such user";
end
- local ok, err = um.set_password(username, password, host);
+ local ok, err = um.set_password(username, password, host, nil);
if ok then
return true, "User password changed";
else
@@ -1038,9 +1061,8 @@
local st = require "util.stanza";
function def_env.xmpp:ping(localhost, remotehost)
if hosts[localhost] then
- core_post_stanza(hosts[localhost],
- st.iq{ from=localhost, to=remotehost, type="get", id="ping" }
- :tag("ping", {xmlns="urn:xmpp:ping"}));
+ module:send(st.iq{ from=localhost, to=remotehost, type="get", id="ping" }
+ :tag("ping", {xmlns="urn:xmpp:ping"}), hosts[localhost]);
return true, "Sent ping";
else
return nil, "No such host";
@@ -1089,17 +1111,17 @@
for host in pairs(prosody.hosts) do
local http_apps = modulemanager.get_items("http-provider", host);
if #http_apps > 0 then
- local http_host = module:context(host):get_option("http_host");
+ local http_host = module:context(host):get_option_string("http_host");
print("HTTP endpoints on "..host..(http_host and (" (using "..http_host.."):") or ":"));
for _, provider in ipairs(http_apps) do
- local url = module:context(host):http_url(provider.name);
+ local url = module:context(host):http_url(provider.name, provider.default_path);
print("", url);
end
print("");
end
end
- local default_host = module:get_option("http_default_host");
+ local default_host = module:get_option_string("http_default_host");
if not default_host then
print("HTTP requests to unknown hosts will return 404 Not Found");
else
@@ -1108,32 +1130,34 @@
return true;
end
+module:hook("server-stopping", function(event)
+ for conn, session in pairs(sessions) do
+ session.print("Shutting down: "..(event.reason or "unknown reason"));
+ end
+end);
+
-------------
function printbanner(session)
- local option = module:get_option("console_banner");
- if option == nil or option == "full" or option == "graphic" then
+ local option = module:get_option_string("console_banner", "full");
+ if option == "full" or option == "graphic" then
session.print [[
- ____ \ / _
- | _ \ _ __ ___ ___ _-_ __| |_ _
+ ____ \ / _
+ | _ \ _ __ ___ ___ _-_ __| |_ _
| |_) | '__/ _ \/ __|/ _ \ / _` | | | |
| __/| | | (_) \__ \ |_| | (_| | |_| |
|_| |_| \___/|___/\___/ \__,_|\__, |
- A study in simplicity |___/
+ A study in simplicity |___/
]]
end
- if option == nil or option == "short" or option == "full" then
+ if option == "short" or option == "full" then
session.print("Welcome to the Prosody administration console. For a list of commands, type: help");
session.print("You may find more help on using this console in our online documentation at ");
session.print("http://prosody.im/doc/console\n");
end
- if option and option ~= "short" and option ~= "full" and option ~= "graphic" then
- if type(option) == "string" then
- session.print(option)
- elseif type(option) == "function" then
- module:log("warn", "Using functions as value for the console_banner option is no longer supported");
- end
+ if option ~= "short" and option ~= "full" and option ~= "graphic" then
+ session.print(option);
end
end
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_announce.lua
--- a/plugins/mod_announce.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_announce.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -39,22 +39,22 @@
function handle_announcement(event)
local origin, stanza = event.origin, event.stanza;
local node, host, resource = jid.split(stanza.attr.to);
-
+
if resource ~= "announce/online" then
return; -- Not an announcement
end
-
+
if not is_admin(stanza.attr.from) then
-- Not an admin? Not allowed!
module:log("warn", "Non-admin '%s' tried to send server announcement", stanza.attr.from);
return;
end
-
+
module:log("info", "Sending server announcement to all online users");
local message = st.clone(stanza);
message.attr.type = "headline";
message.attr.from = host;
-
+
local c = send_to_online(message, host);
module:log("info", "Announcement sent to %d online users", c);
return true;
@@ -83,9 +83,9 @@
module:log("info", "Sending server announcement to all online users");
local message = st.message({type = "headline"}, fields.announcement):up()
:tag("subject"):text(fields.subject or "Announcement");
-
+
local count = send_to_online(message, data.to);
-
+
module:log("info", "Announcement sent to %d online users", count);
return { status = "completed", info = ("Announcement sent to %d online users"):format(count) };
else
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_auth_anonymous.lua
--- a/plugins/mod_auth_anonymous.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_auth_anonymous.lua Wed May 30 21:55:09 2018 +0100
@@ -5,6 +5,7 @@
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
+-- luacheck: ignore 212
local new_sasl = require "util.sasl".new;
local datamanager = require "util.datamanager";
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_auth_cyrus.lua
--- a/plugins/mod_auth_cyrus.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_auth_cyrus.lua Wed May 30 21:55:09 2018 +0100
@@ -5,6 +5,7 @@
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
+-- luacheck: ignore 212
local log = require "util.logger".init("auth_cyrus");
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_auth_internal_hashed.lua
--- a/plugins/mod_auth_internal_hashed.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_auth_internal_hashed.lua Wed May 30 21:55:09 2018 +0100
@@ -7,44 +7,30 @@
-- COPYING file in the source package for more information.
--
-local log = require "util.logger".init("auth_internal_hashed");
+local max = math.max;
+
local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1;
local usermanager = require "core.usermanager";
local generate_uuid = require "util.uuid".generate;
local new_sasl = require "util.sasl".new;
+local hex = require"util.hex";
+local to_hex, from_hex = hex.to, hex.from;
+
+local log = module._log;
+local host = module.host;
local accounts = module:open_store("accounts");
-local to_hex;
-do
- local function replace_byte_with_hex(byte)
- return ("%02x"):format(byte:byte());
- end
- function to_hex(binary_string)
- return binary_string:gsub(".", replace_byte_with_hex);
- end
-end
-
-local from_hex;
-do
- local function replace_hex_with_byte(hex)
- return string.char(tonumber(hex, 16));
- end
- function from_hex(hex_string)
- return hex_string:gsub("..", replace_hex_with_byte);
- end
-end
-- Default; can be set per-user
-local iteration_count = 4096;
+local default_iteration_count = 4096;
-local host = module.host;
-- define auth provider
local provider = {};
-log("debug", "initializing internal_hashed authentication provider for host '%s'", host);
function provider.test_password(username, password)
+ log("debug", "test password for user '%s'", username);
local credentials = accounts:get(username) or {};
if credentials.password ~= nil and string.len(credentials.password) ~= 0 then
@@ -62,12 +48,12 @@
if credentials.iteration_count == nil or credentials.salt == nil or string.len(credentials.salt) == 0 then
return nil, "Auth failed. Stored salt and iteration count information is not complete.";
end
-
+
local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, credentials.salt, credentials.iteration_count);
-
+
local stored_key_hex = to_hex(stored_key);
local server_key_hex = to_hex(server_key);
-
+
if valid and stored_key_hex == credentials.stored_key and server_key_hex == credentials.server_key then
return true;
else
@@ -76,14 +62,15 @@
end
function provider.set_password(username, password)
+ log("debug", "set_password for username '%s'", username);
local account = accounts:get(username);
if account then
- account.salt = account.salt or generate_uuid();
- account.iteration_count = account.iteration_count or iteration_count;
+ account.salt = generate_uuid();
+ account.iteration_count = max(account.iteration_count or 0, default_iteration_count);
local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, account.salt, account.iteration_count);
local stored_key_hex = to_hex(stored_key);
local server_key_hex = to_hex(server_key);
-
+
account.stored_key = stored_key_hex
account.server_key = server_key_hex
@@ -96,7 +83,7 @@
function provider.user_exists(username)
local account = accounts:get(username);
if not account then
- log("debug", "account not found for username '%s' at host '%s'", username, host);
+ log("debug", "account not found for username '%s'", username);
return nil, "Auth failed. Invalid username";
end
return true;
@@ -111,10 +98,13 @@
return accounts:set(username, {});
end
local salt = generate_uuid();
- local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count);
+ local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, default_iteration_count);
local stored_key_hex = to_hex(stored_key);
local server_key_hex = to_hex(server_key);
- return accounts:set(username, {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = iteration_count});
+ return accounts:set(username, {
+ stored_key = stored_key_hex, server_key = server_key_hex,
+ salt = salt, iteration_count = default_iteration_count
+ });
end
function provider.delete_user(username)
@@ -123,19 +113,22 @@
function provider.get_sasl_handler()
local testpass_authentication_profile = {
- plain_test = function(sasl, username, password, realm)
+ plain_test = function(_, username, password, realm)
return usermanager.test_password(username, realm, password), true;
end,
- scram_sha_1 = function(sasl, username, realm)
+ scram_sha_1 = function(_, username)
local credentials = accounts:get(username);
if not credentials then return; end
if credentials.password then
- usermanager.set_password(username, credentials.password, host);
+ if provider.set_password(username, credentials.password) == nil then
+ return nil, "Auth failed. Could not set hashed password from plaintext.";
+ end
credentials = accounts:get(username);
if not credentials then return; end
end
-
- local stored_key, server_key, iteration_count, salt = credentials.stored_key, credentials.server_key, credentials.iteration_count, credentials.salt;
+
+ local stored_key, server_key = credentials.stored_key, credentials.server_key;
+ local iteration_count, salt = credentials.iteration_count, credentials.salt;
stored_key = stored_key and from_hex(stored_key);
server_key = server_key and from_hex(server_key);
return stored_key, server_key, iteration_count, salt, true;
@@ -143,6 +136,6 @@
};
return new_sasl(host, testpass_authentication_profile);
end
-
+
module:provides("auth", provider);
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_auth_internal_plain.lua
--- a/plugins/mod_auth_internal_plain.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_auth_internal_plain.lua Wed May 30 21:55:09 2018 +0100
@@ -16,10 +16,9 @@
-- define auth provider
local provider = {};
-log("debug", "initializing internal_plain authentication provider for host '%s'", host);
function provider.test_password(username, password)
- log("debug", "test password for user %s at host %s", username, host);
+ log("debug", "test password for user '%s'", username);
local credentials = accounts:get(username) or {};
if password == credentials.password then
@@ -30,11 +29,12 @@
end
function provider.get_password(username)
- log("debug", "get_password for username '%s' at host '%s'", username, host);
+ log("debug", "get_password for username '%s'", username);
return (accounts:get(username) or {}).password;
end
function provider.set_password(username, password)
+ log("debug", "set_password for username '%s'", username);
local account = accounts:get(username);
if account then
account.password = password;
@@ -46,7 +46,7 @@
function provider.user_exists(username)
local account = accounts:get(username);
if not account then
- log("debug", "account not found for username '%s' at host '%s'", username, host);
+ log("debug", "account not found for username '%s'", username);
return nil, "Auth failed. Invalid username";
end
return true;
@@ -66,7 +66,7 @@
function provider.get_sasl_handler()
local getpass_authentication_profile = {
- plain = function(sasl, username, realm)
+ plain = function(_, username, realm)
local password = usermanager.get_password(username, realm);
if not password then
return "", nil;
@@ -76,6 +76,6 @@
};
return new_sasl(host, getpass_authentication_profile);
end
-
+
module:provides("auth", provider);
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_blocklist.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_blocklist.lua Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,331 @@
+-- Prosody IM
+-- Copyright (C) 2009-2010 Matthew Wild
+-- Copyright (C) 2009-2010 Waqas Hussain
+-- Copyright (C) 2014-2015 Kim Alvefur
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+-- This module implements XEP-0191: Blocking Command
+--
+
+local user_exists = require"core.usermanager".user_exists;
+local rostermanager = require"core.rostermanager";
+local is_contact_subscribed = rostermanager.is_contact_subscribed;
+local is_contact_pending_in = rostermanager.is_contact_pending_in;
+local load_roster = rostermanager.load_roster;
+local save_roster = rostermanager.save_roster;
+local st = require"util.stanza";
+local st_error_reply = st.error_reply;
+local jid_prep = require"util.jid".prep;
+local jid_split = require"util.jid".split;
+
+local storage = module:open_store();
+local sessions = prosody.hosts[module.host].sessions;
+local full_sessions = prosody.full_sessions;
+
+-- First level cache of blocklists by username.
+-- Weak table so may randomly expire at any time.
+local cache = setmetatable({}, { __mode = "v" });
+
+-- Second level of caching, keeps a fixed number of items, also anchors
+-- items in the above cache.
+--
+-- The size of this affects how often we will need to load a blocklist from
+-- disk, which we want to avoid during routing. On the other hand, we don't
+-- want to use too much memory either, so this can be tuned by advanced
+-- users. TODO use science to figure out a better default, 64 is just a guess.
+local cache_size = module:get_option_number("blocklist_cache_size", 64);
+local cache2 = require"util.cache".new(cache_size);
+
+local null_blocklist = {};
+
+module:add_feature("urn:xmpp:blocking");
+
+local function set_blocklist(username, blocklist)
+ local ok, err = storage:set(username, blocklist);
+ if not ok then
+ return ok, err;
+ end
+ -- Successful save, update the cache
+ cache2:set(username, blocklist);
+ cache[username] = blocklist;
+ return true;
+end
+
+-- Migrates from the old mod_privacy storage
+local function migrate_privacy_list(username)
+ local legacy_data = module:open_store("privacy"):get(username);
+ if not legacy_data or not legacy_data.lists or not legacy_data.default then return; end
+ local default_list = legacy_data.lists[legacy_data.default];
+ if not default_list or not default_list.items then return; end
+
+ local migrated_data = { [false] = { created = os.time(); migrated = "privacy" }};
+
+ module:log("info", "Migrating blocklist from mod_privacy storage for user '%s'", username);
+ for _, item in ipairs(default_list.items) do
+ if item.type == "jid" and item.action == "deny" then
+ local jid = jid_prep(item.value);
+ if not jid then
+ module:log("warn", "Invalid JID in privacy store for user '%s' not migrated: %s", username, tostring(item.value));
+ else
+ migrated_data[jid] = true;
+ end
+ end
+ end
+ set_blocklist(username, migrated_data);
+ return migrated_data;
+end
+
+local function get_blocklist(username)
+ local blocklist = cache2:get(username);
+ if not blocklist then
+ if not user_exists(username, module.host) then
+ return null_blocklist;
+ end
+ blocklist = storage:get(username);
+ if not blocklist then
+ blocklist = migrate_privacy_list(username);
+ end
+ if not blocklist then
+ blocklist = { [false] = { created = os.time(); }; };
+ end
+ cache2:set(username, blocklist);
+ end
+ cache[username] = blocklist;
+ return blocklist;
+end
+
+module:hook("iq-get/self/urn:xmpp:blocking:blocklist", function (event)
+ local origin, stanza = event.origin, event.stanza;
+ local username = origin.username;
+ local reply = st.reply(stanza):tag("blocklist", { xmlns = "urn:xmpp:blocking" });
+ local blocklist = cache[username] or get_blocklist(username);
+ for jid in pairs(blocklist) do
+ if jid then
+ reply:tag("item", { jid = jid }):up();
+ end
+ end
+ origin.interested_blocklist = true; -- Gets notified about changes
+ origin.send(reply);
+ return true;
+end, -1);
+
+-- Add or remove some jid(s) from the blocklist
+-- We want this to be atomic and not do a partial update
+local function edit_blocklist(event)
+ local origin, stanza = event.origin, event.stanza;
+ local username = origin.username;
+ local action = stanza.tags[1]; -- "block" or "unblock"
+ local is_blocking = action.name == "block" or nil; -- nil if unblocking
+ local new = {}; -- JIDs to block depending or unblock on action
+
+ -- XEP-0191 sayeth:
+ -- > When the user blocks communications with the contact, the user's
+ -- > server MUST send unavailable presence information to the contact (but
+ -- > only if the contact is allowed to receive presence notifications [...]
+ -- So contacts we need to do that for are added to the set below.
+ local send_unavailable = is_blocking and {};
+
+ -- Because blocking someone currently also blocks the ability to reject
+ -- subscription requests, we'll preemptively reject such
+ local remove_pending = is_blocking and {};
+
+ for item in action:childtags("item") do
+ local jid = jid_prep(item.attr.jid);
+ if not jid then
+ origin.send(st_error_reply(stanza, "modify", "jid-malformed"));
+ return true;
+ end
+ item.attr.jid = jid; -- echo back prepped
+ new[jid] = true;
+ if is_blocking then
+ if is_contact_subscribed(username, module.host, jid) then
+ send_unavailable[jid] = true;
+ elseif is_contact_pending_in(username, module.host, jid) then
+ remove_pending[jid] = true;
+ end
+ end
+ end
+
+ if is_blocking and not next(new) then
+ -- element does not contain at least one child element
+ origin.send(st_error_reply(stanza, "modify", "bad-request"));
+ return true;
+ end
+
+ local blocklist = cache[username] or get_blocklist(username);
+
+ local new_blocklist = {
+ -- We set the [false] key to someting as a signal not to migrate privacy lists
+ [false] = blocklist[false] or { created = os.time(); };
+ };
+ if type(blocklist[false]) == "table" then
+ new_blocklist[false].modified = os.time();
+ end
+
+ if is_blocking or next(new) then
+ for jid in pairs(blocklist) do
+ if jid then new_blocklist[jid] = true; end
+ end
+ for jid in pairs(new) do
+ new_blocklist[jid] = is_blocking;
+ end
+ -- else empty the blocklist
+ end
+
+ local ok, err = set_blocklist(username, new_blocklist);
+ if ok then
+ origin.send(st.reply(stanza));
+ else
+ origin.send(st_error_reply(stanza, "wait", "internal-server-error", err));
+ return true;
+ end
+
+ if is_blocking then
+ for jid in pairs(send_unavailable) do
+ if not blocklist[jid] then
+ for _, session in pairs(sessions[username].sessions) do
+ if session.presence then
+ module:send(st.presence({ type = "unavailable", to = jid, from = session.full_jid }));
+ end
+ end
+ end
+ end
+
+ if next(remove_pending) then
+ local roster = load_roster(username, module.host);
+ for jid in pairs(remove_pending) do
+ roster[false].pending[jid] = nil;
+ end
+ save_roster(username, module.host, roster);
+ -- Not much we can do about save failing here
+ end
+ end
+
+ local blocklist_push = st.iq({ type = "set", id = "blocklist-push" })
+ :add_child(action); -- I am lazy
+
+ for _, session in pairs(sessions[username].sessions) do
+ if session.interested_blocklist then
+ blocklist_push.attr.to = session.full_jid;
+ session.send(blocklist_push);
+ end
+ end
+
+ return true;
+end
+
+module:hook("iq-set/self/urn:xmpp:blocking:block", edit_blocklist, -1);
+module:hook("iq-set/self/urn:xmpp:blocking:unblock", edit_blocklist, -1);
+
+-- Cache invalidation, solved!
+module:hook_global("user-deleted", function (event)
+ if event.host == module.host then
+ cache2:set(event.username, nil);
+ cache[event.username] = nil;
+ end
+end);
+
+-- Buggy clients
+module:hook("iq-error/self/blocklist-push", function (event)
+ local origin, stanza = event.origin, event.stanza;
+ local _, condition, text = stanza:get_error();
+ local log = (origin.log or module._log);
+ log("warn", "Client returned an error in response to notification from mod_%s: %s%s%s",
+ module.name, condition, text and ": " or "", text or "");
+ return true;
+end);
+
+local function is_blocked(user, jid)
+ local blocklist = cache[user] or get_blocklist(user);
+ if blocklist[jid] then return true; end
+ local node, host = jid_split(jid);
+ return blocklist[host] or node and blocklist[node..'@'..host];
+end
+
+-- Event handlers for bouncing or dropping stanzas
+local function drop_stanza(event)
+ local stanza = event.stanza;
+ local attr = stanza.attr;
+ local to, from = attr.to, attr.from;
+ to = to and jid_split(to);
+ if to and from then
+ return is_blocked(to, from);
+ end
+end
+
+local function bounce_stanza(event)
+ local origin, stanza = event.origin, event.stanza;
+ if drop_stanza(event) then
+ origin.send(st_error_reply(stanza, "cancel", "service-unavailable"));
+ return true;
+ end
+end
+
+local function bounce_iq(event)
+ local type = event.stanza.attr.type;
+ if type == "set" or type == "get" then
+ return bounce_stanza(event);
+ end
+ return drop_stanza(event); -- result or error
+end
+
+local function bounce_message(event)
+ local stanza = event.stanza;
+ local type = stanza.attr.type;
+ if type == "chat" or not type or type == "normal" then
+ if full_sessions[stanza.attr.to] then
+ -- See #690
+ return drop_stanza(event);
+ end
+ return bounce_stanza(event);
+ end
+ return drop_stanza(event); -- drop headlines, groupchats etc
+end
+
+local function drop_outgoing(event)
+ local origin, stanza = event.origin, event.stanza;
+ local username = origin.username or jid_split(stanza.attr.from);
+ if not username then return end
+ local to = stanza.attr.to;
+ if to then return is_blocked(username, to); end
+ -- nil 'to' means a self event, don't bock those
+end
+
+local function bounce_outgoing(event)
+ local origin, stanza = event.origin, event.stanza;
+ local type = stanza.attr.type;
+ if type == "error" or stanza.name == "iq" and type == "result" then
+ return drop_outgoing(event);
+ end
+ if drop_outgoing(event) then
+ origin.send(st_error_reply(stanza, "cancel", "not-acceptable", "You have blocked this JID")
+ :tag("blocked", { xmlns = "urn:xmpp:blocking:errors" }));
+ return true;
+ end
+end
+
+-- Hook all the events!
+local prio_in, prio_out = 100, 100;
+module:hook("presence/bare", drop_stanza, prio_in);
+module:hook("presence/full", drop_stanza, prio_in);
+
+module:hook("message/bare", bounce_message, prio_in);
+module:hook("message/full", bounce_message, prio_in);
+
+module:hook("iq/bare", bounce_iq, prio_in);
+module:hook("iq/full", bounce_iq, prio_in);
+
+module:hook("pre-message/bare", bounce_outgoing, prio_out);
+module:hook("pre-message/full", bounce_outgoing, prio_out);
+module:hook("pre-message/host", bounce_outgoing, prio_out);
+
+module:hook("pre-presence/bare", bounce_outgoing, -1);
+module:hook("pre-presence/host", bounce_outgoing, -1);
+module:hook("pre-presence/full", bounce_outgoing, prio_out);
+
+module:hook("pre-iq/bare", bounce_outgoing, prio_out);
+module:hook("pre-iq/full", bounce_outgoing, prio_out);
+module:hook("pre-iq/host", bounce_outgoing, prio_out);
+
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_bosh.lua
--- a/plugins/mod_bosh.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_bosh.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -13,7 +13,6 @@
local sm = require "core.sessionmanager";
local sm_destroy_session = sm.destroy_session;
local new_uuid = require "util.uuid".generate;
-local fire_event = prosody.events.fire_event;
local core_process_stanza = prosody.core_process_stanza;
local st = require "util.stanza";
local logger = require "util.logger";
@@ -22,6 +21,7 @@
local math_min = math.min;
local xpcall, tostring, type = xpcall, tostring, type;
local traceback = debug.traceback;
+local nameprep = require "util.encodings".stringprep.nameprep;
local xmlns_streams = "http://etherx.jabber.org/streams";
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
@@ -30,33 +30,25 @@
local stream_callbacks = {
stream_ns = xmlns_bosh, stream_tag = "body", default_ns = "jabber:client" };
-local BOSH_DEFAULT_HOLD = module:get_option_number("bosh_default_hold", 1);
-local BOSH_DEFAULT_INACTIVITY = module:get_option_number("bosh_max_inactivity", 60);
-local BOSH_DEFAULT_POLLING = module:get_option_number("bosh_max_polling", 5);
-local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2);
+-- These constants are implicitly assumed within the code, and cannot be changed
+local BOSH_HOLD = 1;
+local BOSH_MAX_REQUESTS = 2;
+
+-- The number of seconds a BOSH session should remain open with no requests
+local bosh_max_inactivity = module:get_option_number("bosh_max_inactivity", 60);
+-- The minimum amount of time between requests with no payload
+local bosh_max_polling = module:get_option_number("bosh_max_polling", 5);
+-- The maximum amount of time that the server will hold onto a request before replying
+-- (the client can set this to a lower value when it connects, if it chooses)
local bosh_max_wait = module:get_option_number("bosh_max_wait", 120);
local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure");
-
-local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
-
local cross_domain = module:get_option("cross_domain_bosh", false);
-if cross_domain then
- default_headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS";
- default_headers["Access-Control-Allow-Headers"] = "Content-Type";
- default_headers["Access-Control-Max-Age"] = "7200";
- if cross_domain == true then
- default_headers["Access-Control-Allow-Origin"] = "*";
- elseif type(cross_domain) == "table" then
- cross_domain = table.concat(cross_domain, ", ");
- end
- if type(cross_domain) == "string" then
- default_headers["Access-Control-Allow-Origin"] = cross_domain;
- end
-end
+if cross_domain == true then cross_domain = "*"; end
+if type(cross_domain) == "table" then cross_domain = table.concat(cross_domain, ", "); end
-local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items;
+local trusted_proxies = module:get_option_set("trusted_proxies", { "127.0.0.1", "::1" })._items;
local function get_ip_from_request(request)
local ip = request.conn:ip();
@@ -79,7 +71,7 @@
local sessions, inactive_sessions = module:shared("sessions", "inactive_sessions");
-- Used to respond to idle sessions (those with waiting requests)
-local waiting_requests = {};
+local waiting_requests = module:shared("waiting_requests");
function on_destroy_request(request)
log("debug", "Request destroyed: %s", tostring(request));
waiting_requests[request] = nil;
@@ -92,7 +84,7 @@
break;
end
end
-
+
-- If this session now has no requests open, mark it as inactive
local max_inactive = session.bosh_max_inactive;
if max_inactive and #requests == 0 then
@@ -102,11 +94,20 @@
end
end
-function handle_OPTIONS(request)
- local headers = {};
- for k,v in pairs(default_headers) do headers[k] = v; end
- headers["Content-Type"] = nil;
- return { headers = headers, body = "" };
+local function set_cross_domain_headers(response)
+ local headers = response.headers;
+ headers.access_control_allow_methods = "GET, POST, OPTIONS";
+ headers.access_control_allow_headers = "Content-Type";
+ headers.access_control_max_age = "7200";
+ headers.access_control_allow_origin = cross_domain;
+ return response;
+end
+
+function handle_OPTIONS(event)
+ if cross_domain and event.request.headers.origin then
+ set_cross_domain_headers(event.response);
+ end
+ return "";
end
function handle_POST(event)
@@ -119,14 +120,27 @@
local context = { request = request, response = response, notopen = true };
local stream = new_xmpp_stream(context, stream_callbacks);
response.context = context;
-
+
+ local headers = response.headers;
+ headers.content_type = "text/xml; charset=utf-8";
+
+ if cross_domain and event.request.headers.origin then
+ set_cross_domain_headers(response);
+ end
+
-- stream:feed() calls the stream_callbacks, so all stanzas in
-- the body are processed in this next line before it returns.
-- In particular, the streamopened() stream callback is where
-- much of the session logic happens, because it's where we first
-- get to see the 'sid' of this request.
- stream:feed(body);
-
+ local ok, err = stream:feed(body);
+ if not ok then
+ module:log("warn", "Error parsing BOSH payload; %s", err)
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
+ return tostring(close_reply);
+ end
+
-- Stanzas (if any) in the request have now been processed, and
-- we take care of the high-level BOSH logic here, including
-- giving a response or putting the request "on hold".
@@ -139,12 +153,9 @@
end
local r = session.requests;
- log("debug", "Session %s has %d out of %d requests open", context.sid, #r, session.bosh_hold);
+ log("debug", "Session %s has %d out of %d requests open", context.sid, #r, BOSH_HOLD);
log("debug", "and there are %d things in the send_buffer:", #session.send_buffer);
- for i, thing in ipairs(session.send_buffer) do
- log("debug", " %s", tostring(thing));
- end
- if #r > session.bosh_hold then
+ if #r > BOSH_HOLD then
-- We are holding too many requests, send what's in the buffer,
log("debug", "We are holding too many requests, so...");
if #session.send_buffer > 0 then
@@ -162,7 +173,7 @@
session.send_buffer = {};
session.send(resp);
end
-
+
if not response.finished then
-- We're keeping this request open, to respond later
log("debug", "Have nothing to say, so leaving request unanswered for now");
@@ -170,7 +181,7 @@
waiting_requests[response] = os_time() + session.bosh_wait;
end
end
-
+
if session.bosh_terminate then
session.log("debug", "Closing session with %d requests open", #session.requests);
session:close();
@@ -178,7 +189,13 @@
else
return true; -- Inform http server we shall reply later
end
+ elseif response.finished then
+ return; -- A response has been sent already
end
+ module:log("warn", "Unable to associate request with a session (incomplete request?)");
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "item-not-found" });
+ return tostring(close_reply) .. "\n";
end
@@ -188,10 +205,10 @@
local function bosh_close_stream(session, reason)
(session.log or log)("info", "BOSH client disconnected");
-
+
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams });
-
+
if reason then
close_reply.attr.condition = "remote-stream-error";
@@ -217,10 +234,9 @@
local response_body = tostring(close_reply);
for _, held_request in ipairs(session.requests) do
- held_request.headers = default_headers;
held_request:send(response_body);
end
- sessions[session.sid] = nil;
+ sessions[session.sid] = nil;
inactive_sessions[session] = nil;
sm_destroy_session(session);
end
@@ -233,9 +249,17 @@
if not sid then
-- New session request
context.notopen = nil; -- Signals that we accept this opening tag
-
- -- TODO: Sanity checks here (rid, to, known host, etc.)
- if not hosts[attr.to] then
+
+ local to_host = nameprep(attr.to);
+ local rid = tonumber(attr.rid);
+ local wait = tonumber(attr.wait);
+ if not to_host then
+ log("debug", "BOSH client tried to connect to invalid host: %s", tostring(attr.to));
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" });
+ response:send(tostring(close_reply));
+ return;
+ elseif not hosts[to_host] then
-- Unknown host
log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to));
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
@@ -243,25 +267,37 @@
response:send(tostring(close_reply));
return;
end
-
+ if not rid or (not wait and attr.wait or wait < 0 or wait % 1 ~= 0) then
+ log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", tostring(attr.rid), tostring(attr.wait));
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
+ response:send(tostring(close_reply));
+ return;
+ end
+
+ rid = rid - 1;
+ wait = math_min(wait, bosh_max_wait);
+
-- New session
sid = new_uuid();
local session = {
- type = "c2s_unauthed", conn = request.conn, sid = sid, rid = tonumber(attr.rid)-1, host = attr.to,
- bosh_version = attr.ver, bosh_wait = math_min(attr.wait, bosh_max_wait), streamid = sid,
- bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY,
+ type = "c2s_unauthed", conn = request.conn, sid = sid, rid = rid, host = to_host,
+ bosh_version = attr.ver, bosh_wait = wait, streamid = sid,
+ bosh_max_inactive = bosh_max_inactivity,
requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream,
close = bosh_close_stream, dispatch_stanza = core_process_stanza, notopen = true,
log = logger.init("bosh"..sid), secure = consider_bosh_secure or request.secure,
ip = get_ip_from_request(request);
};
sessions[sid] = session;
-
+
local filter = initialize_filters(session);
-
+
session.log("debug", "BOSH session created for request from %s", session.ip);
log("info", "New BOSH session, assigned it sid '%s'", sid);
+ hosts[session.host].events.fire_event("bosh-session", { session = session, request = request });
+
-- Send creation response
local creating_session = true;
@@ -274,12 +310,12 @@
end
s = filter("stanzas/out", s);
--log("debug", "Sending BOSH data: %s", tostring(s));
+ if not s then return true end
t_insert(session.send_buffer, tostring(s));
local oldest_request = r[1];
if oldest_request and not session.bosh_processing then
log("debug", "We have an open request, so sending on that");
- oldest_request.headers = default_headers;
local body_attr = { xmlns = "http://jabber.org/protocol/httpbind",
["xmlns:stream"] = "http://etherx.jabber.org/streams";
type = session.bosh_terminate and "terminate" or nil;
@@ -287,11 +323,11 @@
};
if creating_session then
creating_session = nil;
- body_attr.inactivity = tostring(BOSH_DEFAULT_INACTIVITY);
- body_attr.polling = tostring(BOSH_DEFAULT_POLLING);
- body_attr.requests = tostring(BOSH_DEFAULT_REQUESTS);
+ body_attr.requests = tostring(BOSH_MAX_REQUESTS);
+ body_attr.hold = tostring(BOSH_HOLD);
+ body_attr.inactivity = tostring(bosh_max_inactivity);
+ body_attr.polling = tostring(bosh_max_polling);
body_attr.wait = tostring(session.bosh_wait);
- body_attr.hold = tostring(session.bosh_hold);
body_attr.authid = sid;
body_attr.secure = "true";
body_attr.ver = '1.6';
@@ -299,43 +335,55 @@
body_attr["xmlns:xmpp"] = "urn:xmpp:xbosh";
body_attr["xmpp:version"] = "1.0";
end
- oldest_request:send(st.stanza("body", body_attr):top_tag()..t_concat(session.send_buffer).."
- $title
- $message
- $extra
+{title}
+{message}
+{extra?}
");
+ session.bosh_last_response = st.stanza("body", body_attr):top_tag()..t_concat(session.send_buffer).."";
+ oldest_request:send(session.bosh_last_response);
session.send_buffer = {};
end
return true;
end
request.sid = sid;
end
-
+
local session = sessions[sid];
if not session then
-- Unknown sid
log("info", "Client tried to use sid '%s' which we don't know about", sid);
- response.headers = default_headers;
response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })));
context.notopen = nil;
return;
end
session.conn = request.conn;
-
+
if session.rid then
local rid = tonumber(attr.rid);
local diff = rid - session.rid;
- if diff > 1 then
- session.log("warn", "rid too large (means a request was lost). Last rid: %d New rid: %s", session.rid, attr.rid);
- elseif diff <= 0 then
- -- Repeated, ignore
- session.log("debug", "rid repeated, ignoring: %s (diff %d)", session.rid, diff);
+ -- Diff should be 1 for a healthy request
+ if diff ~= 1 then
+ context.sid = sid;
context.notopen = nil;
+ if diff == 2 then
+ -- Hold request, but don't process it (ouch!)
+ session.log("debug", "rid skipped: %d, deferring this request", rid-1)
+ context.defer = true;
+ session.bosh_deferred = { context = context, sid = sid, rid = rid, terminate = attr.type == "terminate" };
+ return;
+ end
context.ignore = true;
- context.sid = sid;
- t_insert(session.requests, response);
+ if diff == 0 then
+ -- Re-send previous response, ignore stanzas in this request
+ session.log("debug", "rid repeated, ignoring: %s (diff %d)", session.rid, diff);
+ response:send(session.bosh_last_response);
+ return;
+ end
+ -- Session broken, destroy it
+ session.log("debug", "rid out of range: %d (diff %d)", rid, diff);
+ response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })));
return;
end
session.rid = rid;
end
-
+
if attr.type == "terminate" then
-- Client wants to end this session, which we'll do
-- after processing any stanzas in this request
@@ -350,8 +398,7 @@
if session.notopen then
local features = st.stanza("stream:features");
hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
- fire_event("stream-features", session, features);
- session.send(tostring(features));
+ session.send(features);
session.notopen = nil;
end
end
@@ -365,16 +412,38 @@
if stanza.attr.xmlns == xmlns_bosh then
stanza.attr.xmlns = nil;
end
- stanza = session.filter("stanzas/in", stanza);
- if stanza then
- return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+ if context.defer and session.bosh_deferred then
+ log("debug", "Deferring this stanza");
+ t_insert(session.bosh_deferred, stanza);
+ else
+ stanza = session.filter("stanzas/in", stanza);
+ if stanza then
+ return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+ end
end
+ else
+ log("debug", "No session for this stanza! (sid: %s)", context.sid or "none!");
end
end
-function stream_callbacks.streamclosed(request)
- local session = sessions[request.sid];
+function stream_callbacks.streamclosed(context)
+ local session = sessions[context.sid];
if session then
+ if not context.defer and session.bosh_deferred then
+ -- Handle deferred stanzas now
+ local deferred_stanzas = session.bosh_deferred;
+ local context = deferred_stanzas.context;
+ session.bosh_deferred = nil;
+ log("debug", "Handling deferred stanzas from rid %d", deferred_stanzas.rid);
+ session.rid = deferred_stanzas.rid;
+ t_insert(session.requests, context.response);
+ for _, stanza in ipairs(deferred_stanzas) do
+ stream_callbacks.handlestanza(context, stanza);
+ end
+ if deferred_stanzas.terminate then
+ session.bosh_terminate = true;
+ end
+ end
session.bosh_processing = false;
if #session.send_buffer > 0 then
session.send("");
@@ -386,12 +455,12 @@
log("debug", "Error parsing BOSH request payload; %s", error);
if not context.sid then
local response = context.response;
- response.headers = default_headers;
- response.status_code = 400;
- response:send();
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
+ response:send(tostring(close_reply));
return;
end
-
+
local session = sessions[context.sid];
if error == "stream-error" then -- Remote stream error, we close normally
session:close();
@@ -400,7 +469,7 @@
end
end
-local dead_sessions = {};
+local dead_sessions = module:shared("dead_sessions");
function on_timer()
-- log("debug", "Checking for requests soon to timeout...");
-- Identify requests timing out within the next few seconds
@@ -415,7 +484,7 @@
end
end
end
-
+
now = now - 3;
local n_dead_sessions = 0;
for session, close_after in pairs(inactive_sessions) do
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_c2s.lua
--- a/plugins/mod_c2s.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_c2s.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -23,20 +23,29 @@
local log = module._log;
-local c2s_timeout = module:get_option_number("c2s_timeout");
+local c2s_timeout = module:get_option_number("c2s_timeout", 300);
local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5);
local opt_keepalives = module:get_option_boolean("c2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true));
+local measure_connections = module:measure("connections", "amount");
+
local sessions = module:shared("sessions");
local core_process_stanza = prosody.core_process_stanza;
local hosts = prosody.hosts;
-local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza };
+local stream_callbacks = { default_ns = "jabber:client" };
local listener = {};
+module:hook("stats-update", function ()
+ local count = 0;
+ for _ in pairs(sessions) do
+ count = count + 1;
+ end
+ measure_connections(count);
+end);
+
--- Stream events handlers
local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
function stream_callbacks.streamopened(session, attr)
local send = session.send;
@@ -57,15 +66,13 @@
session.streamid = uuid_generate();
(session.log or session)("debug", "Client sent opening to %s", session.host);
- if not hosts[session.host] or not hosts[session.host].users then
+ if not hosts[session.host] or not hosts[session.host].modules.c2s then
-- We don't serve this host...
session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
return;
end
- send(""..st.stanza("stream:stream", {
- xmlns = 'jabber:client', ["xmlns:stream"] = 'http://etherx.jabber.org/streams';
- id = session.streamid, from = session.host, version = '1.0', ["xml:lang"] = 'en' }):top_tag());
+ session:open_stream();
(session.log or log)("debug", "Sent reply to client");
session.notopen = nil;
@@ -74,21 +81,27 @@
-- since we now have a new stream header, session is secured
if session.secure == false then
session.secure = true;
+ session.encrypted = true;
- -- Check if TLS compression is used
local sock = session.conn:socket();
if sock.info then
- session.compressed = sock:info"compression";
- elseif sock.compression then
- session.compressed = sock:compression(); --COMPAT mw/luasec-hg
+ local info = sock:info();
+ (session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher);
+ session.compressed = info.compression;
+ else
+ (session.log or log)("info", "Stream encrypted");
+ session.compressed = sock.compression and sock:compression(); --COMPAT mw/luasec-hg
end
end
local features = st.stanza("stream:features");
hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
- module:fire_event("stream-features", session, features);
-
- send(features);
+ if features.tags[1] or session.full_jid then
+ send(features);
+ else
+ (session.log or log)("warn", "No stream features to offer");
+ session:close{ condition = "undefined-condition", text = "No stream features to proceed with" };
+ end
end
function stream_callbacks.streamclosed(session)
@@ -134,8 +147,7 @@
local log = session.log or log;
if session.conn then
if session.notopen then
- session.send("");
- session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
+ session:open_stream();
end
if reason then -- nil == no err, initiated by us, false == initiated by client
local stream_error = st.stanza("stream:error");
@@ -158,31 +170,31 @@
log("debug", "Disconnecting client, is: %s", stream_error);
session.send(stream_error);
end
-
+
session.send("");
function session.send() return false; end
-
- local reason = (reason and (reason.name or reason.text or reason.condition)) or reason;
- session.log("info", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason or "session closed");
+
+ local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason;
+ session.log("debug", "c2s stream for %s closed: %s", session.full_jid or session.ip or "", reason_text or "session closed");
-- Authenticated incoming stream may still be sending us stanzas, so wait for from remote
local conn = session.conn;
- if reason == nil and not session.notopen and session.type == "c2s" then
+ if reason_text == nil and not session.notopen and session.type == "c2s" then
-- Grace time to process data from authenticated cleanly-closed stream
add_task(stream_close_timeout, function ()
if not session.destroyed then
session.log("warn", "Failed to receive a stream close response, closing connection anyway...");
- sm_destroy_session(session, reason);
+ sm_destroy_session(session, reason_text);
conn:close();
end
end);
else
- sm_destroy_session(session, reason);
+ sm_destroy_session(session, reason_text);
conn:close();
end
else
- local reason = (reason and (reason.name or reason.text or reason.condition)) or reason;
- sm_destroy_session(session, reason);
+ local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason;
+ sm_destroy_session(session, reason_text);
end
end
@@ -190,22 +202,35 @@
local username, host = event.username, event.host;
local user = hosts[host].sessions[username];
if user and user.sessions then
- for jid, session in pairs(user.sessions) do
+ for _, session in pairs(user.sessions) do
session:close{ condition = "not-authorized", text = "Account deleted" };
end
end
end, 200);
+module:hook_global("user-password-changed", function(event)
+ local username, host, resource = event.username, event.host, event.resource;
+ local user = hosts[host].sessions[username];
+ if user and user.sessions then
+ for r, session in pairs(user.sessions) do
+ if r ~= resource then
+ session:close{ condition = "reset", text = "Password changed" };
+ end
+ end
+ end
+end, 200);
+
--- Port listener
function listener.onconnect(conn)
local session = sm_new_session(conn);
sessions[conn] = session;
-
+
session.log("info", "Client connected");
-
+
-- Client is using legacy SSL (otherwise mod_tls sets this flag)
if conn:ssl() then
session.secure = true;
+ session.encrypted = true;
-- Check if TLS compression is used
local sock = conn:socket();
@@ -215,34 +240,37 @@
session.compressed = sock:compression(); --COMPAT mw/luasec-hg
end
end
-
+
if opt_keepalives then
conn:setoption("keepalive", opt_keepalives);
end
-
+
session.close = session_close;
-
+
local stream = new_xmpp_stream(session, stream_callbacks);
session.stream = stream;
session.notopen = true;
-
+
function session.reset_stream()
session.notopen = true;
session.stream:reset();
end
-
+
local filter = session.filter;
function session.data(data)
- data = filter("bytes/in", data);
+ -- Parse the data, which will store stanzas in session.pending_stanzas
if data then
- local ok, err = stream:feed(data);
- if ok then return; end
- log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
- session:close("not-well-formed");
+ data = filter("bytes/in", data);
+ if data then
+ local ok, err = stream:feed(data);
+ if not ok then
+ log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
+ session:close("not-well-formed");
+ end
+ end
end
end
-
if c2s_timeout then
add_task(c2s_timeout, function ()
if session.type == "c2s_unauthed" then
@@ -271,14 +299,30 @@
end
end
+function listener.onreadtimeout(conn)
+ local session = sessions[conn];
+ if session then
+ return (hosts[session.host] or prosody).events.fire_event("c2s-read-timeout", { session = session });
+ end
+end
+
+local function keepalive(event)
+ local session = event.session;
+ if not session.notopen then
+ return event.session.send(' ');
+ end
+end
+
function listener.associate_session(conn, session)
sessions[conn] = session;
end
-function listener.ondetach(conn)
- sessions[conn] = nil;
+function module.add_host(module)
+ module:hook("c2s-read-timeout", keepalive, -1);
end
+module:hook("c2s-read-timeout", keepalive, -1);
+
module:hook("server-stopping", function(event)
local reason = event.reason;
for _, session in pairs(sessions) do
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_carbons.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_carbons.lua Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,115 @@
+-- XEP-0280: Message Carbons implementation for Prosody
+-- Copyright (C) 2011-2016 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+
+local st = require "util.stanza";
+local jid_bare = require "util.jid".bare;
+local xmlns_carbons = "urn:xmpp:carbons:2";
+local xmlns_forward = "urn:xmpp:forward:0";
+local full_sessions, bare_sessions = prosody.full_sessions, prosody.bare_sessions;
+
+local function toggle_carbons(event)
+ local origin, stanza = event.origin, event.stanza;
+ local state = stanza.tags[1].name;
+ module:log("debug", "%s %sd carbons", origin.full_jid, state);
+ origin.want_carbons = state == "enable" and stanza.tags[1].attr.xmlns;
+ origin.send(st.reply(stanza));
+ return true;
+end
+module:hook("iq-set/self/"..xmlns_carbons..":disable", toggle_carbons);
+module:hook("iq-set/self/"..xmlns_carbons..":enable", toggle_carbons);
+
+local function message_handler(event, c2s)
+ local origin, stanza = event.origin, event.stanza;
+ local orig_type = stanza.attr.type or "normal";
+ local orig_from = stanza.attr.from;
+ local bare_from = jid_bare(orig_from);
+ local orig_to = stanza.attr.to;
+ local bare_to = jid_bare(orig_to);
+
+ if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body"))) then
+ return -- Only chat type messages
+ end
+
+ -- Stanza sent by a local client
+ local bare_jid = bare_from; -- JID of the local user
+ local target_session = origin;
+ local top_priority = false;
+ local user_sessions = bare_sessions[bare_from];
+
+ -- Stanza about to be delivered to a local client
+ if not c2s then
+ bare_jid = bare_to;
+ target_session = full_sessions[orig_to];
+ user_sessions = bare_sessions[bare_jid];
+ if not target_session and user_sessions then
+ -- The top resources will already receive this message per normal routing rules,
+ -- so we are going to skip them in order to avoid sending duplicated messages.
+ local top_resources = user_sessions.top_resources;
+ top_priority = top_resources and top_resources[1].priority
+ end
+ end
+
+ if not user_sessions then
+ module:log("debug", "Skip carbons for offline user");
+ return -- No use in sending carbons to an offline user
+ end
+
+ if stanza:get_child("private", xmlns_carbons) then
+ if not c2s then
+ stanza:maptags(function(tag)
+ if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then
+ return tag;
+ end
+ end);
+ end
+ module:log("debug", "Message tagged private, ignoring");
+ return
+ elseif stanza:get_child("no-copy", "urn:xmpp:hints") then
+ module:log("debug", "Message has no-copy hint, ignoring");
+ return
+ elseif not c2s and bare_jid == orig_from and stanza:get_child("x", "http://jabber.org/protocol/muc#user") then
+ module:log("debug", "MUC PM, ignoring");
+ return
+ end
+
+ -- Create the carbon copy and wrap it as per the Stanza Forwarding XEP
+ local copy = st.clone(stanza);
+ if c2s and not orig_to then
+ stanza.attr.to = bare_from;
+ end
+ copy.attr.xmlns = "jabber:client";
+ local carbon = st.message{ from = bare_jid, type = orig_type, }
+ :tag(c2s and "sent" or "received", { xmlns = xmlns_carbons })
+ :tag("forwarded", { xmlns = xmlns_forward })
+ :add_child(copy):reset();
+
+ user_sessions = user_sessions and user_sessions.sessions;
+ for _, session in pairs(user_sessions) do
+ -- Carbons are sent to resources that have enabled it
+ if session.want_carbons
+ -- but not the resource that sent the message, or the one that it's directed to
+ and session ~= target_session
+ -- and isn't among the top resources that would receive the message per standard routing rules
+ and (c2s or session.priority ~= top_priority) then
+ carbon.attr.to = session.full_jid;
+ module:log("debug", "Sending carbon to %s", session.full_jid);
+ session.send(carbon);
+ end
+ end
+end
+
+local function c2s_message_handler(event)
+ return message_handler(event, true)
+end
+
+-- Stanzas sent by local clients
+module:hook("pre-message/host", c2s_message_handler, -0.5);
+module:hook("pre-message/bare", c2s_message_handler, -0.5);
+module:hook("pre-message/full", c2s_message_handler, -0.5);
+-- Stanzas to local clients
+module:hook("message/bare", message_handler, -0.5);
+module:hook("message/full", message_handler, -0.5);
+
+module:add_feature(xmlns_carbons);
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_component.lua
--- a/plugins/mod_component.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_component.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -29,41 +29,50 @@
local sessions = module:shared("sessions");
+local function keepalive(event)
+ local session = event.session;
+ if not session.notopen then
+ return event.session.send(' ');
+ end
+end
+
function module.add_host(module)
if module:get_host_type() ~= "component" then
error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0);
end
-
+
local env = module.environment;
env.connected = false;
+ env.session = false;
local send;
- local function on_destroy(session, err)
+ local function on_destroy(session, err) --luacheck: ignore 212/err
env.connected = false;
+ env.session = false;
send = nil;
session.on_destroy = nil;
end
-
+
-- Handle authentication attempts by component
local function handle_component_auth(event)
local session, stanza = event.origin, event.stanza;
-
+
if session.type ~= "component_unauthed" then return; end
-
+
if (not session.host) or #stanza.tags > 0 then
(session.log or log)("warn", "Invalid component handshake for host: %s", session.host);
session:close("not-authorized");
return true;
end
-
- local secret = module:get_option("component_secret");
+
+ local secret = module:get_option_string("component_secret");
if not secret then
(session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host);
session:close("not-authorized");
return true;
end
-
+
local supplied_token = t_concat(stanza);
local calculated_token = sha1(session.streamid..secret, true);
if supplied_token:lower() ~= calculated_token:lower() then
@@ -71,14 +80,20 @@
session:close{ condition = "not-authorized", text = "Given token does not match calculated token" };
return true;
end
-
+
if env.connected then
- module:log("error", "Second component attempted to connect, denying connection");
- session:close{ condition = "conflict", text = "Component already connected" };
- return true;
+ local policy = module:get_option_string("component_conflict_resolve", "kick_new");
+ if policy == "kick_old" then
+ env.session:close{ condition = "conflict", text = "Replaced by a new connection" };
+ else -- kick_new
+ module:log("error", "Second component attempted to connect, denying connection");
+ session:close{ condition = "conflict", text = "Component already connected" };
+ return true;
+ end
end
-
+
env.connected = true;
+ env.session = session;
send = session.send;
session.on_destroy = on_destroy;
session.component_validate_from = module:get_option_boolean("validate_from_addresses", true);
@@ -86,7 +101,7 @@
module:log("info", "External component successfully authenticated");
session.send(st.stanza("handshake"));
module:fire_event("component-authenticated", { session = session });
-
+
return true;
end
module:hook("stanza/jabber:component:accept:handshake", handle_component_auth, -1);
@@ -105,7 +120,8 @@
local name = module:get_option_string("name");
if name then
event.origin.send(st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#info" })
- :tag("identity", { category = "component", type = "generic", name = module:get_option_string("name", "Prosody") }))
+ :tag("identity", { category = "component", type = "generic", name = module:get_option_string("name", "Prosody") })):up()
+ :tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up()
return true;
end
end
@@ -117,7 +133,7 @@
end
return true;
end
-
+
module:hook("iq/bare", handle_stanza, -1);
module:hook("message/bare", handle_stanza, -1);
module:hook("presence/bare", handle_stanza, -1);
@@ -127,8 +143,12 @@
module:hook("iq/host", handle_stanza, -1);
module:hook("message/host", handle_stanza, -1);
module:hook("presence/host", handle_stanza, -1);
+
+ module:hook("component-read-timeout", keepalive, -1);
end
+module:hook("component-read-timeout", keepalive, -1);
+
--- Network and stream part ---
local xmlns_component = 'jabber:component:accept';
@@ -141,7 +161,7 @@
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
-function stream_callbacks.error(session, error, data, data2)
+function stream_callbacks.error(session, error, data)
if session.destroyed then return; end
module:log("warn", "Error processing component stream: %s", tostring(error));
if error == "no-stream" then
@@ -176,9 +196,7 @@
session.streamid = uuid_gen();
session.notopen = nil;
-- Return stream header
- session.send("");
- session.send(st.stanza("stream:stream", { xmlns=xmlns_component,
- ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag());
+ session:open_stream();
end
function stream_callbacks.streamclosed(session)
@@ -274,26 +292,26 @@
if opt_keepalives then
conn:setoption("keepalive", opt_keepalives);
end
-
+
session.log("info", "Incoming Jabber component connection");
-
+
local stream = new_xmpp_stream(session, stream_callbacks);
session.stream = stream;
-
+
session.notopen = true;
-
+
function session.reset_stream()
session.notopen = true;
session.stream:reset();
end
- function session.data(conn, data)
+ function session.data(_, data)
local ok, err = stream:feed(data);
if ok then return; end
module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
session:close("not-well-formed");
end
-
+
session.dispatch_stanza = stream_callbacks.handlestanza;
sessions[conn] = session;
@@ -306,6 +324,9 @@
local session = sessions[conn];
if session then
(session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
+ if session.host then
+ module:context(session.host):fire_event("component-disconnected", { session = session, reason = err });
+ end
if session.on_destroy then session:on_destroy(err); end
sessions[conn] = nil;
for k in pairs(session) do
@@ -314,7 +335,6 @@
end
end
session.destroyed = true;
- session = nil;
end
end
@@ -322,6 +342,13 @@
sessions[conn] = nil;
end
+function listener.onreadtimeout(conn)
+ local session = sessions[conn];
+ if session then
+ return (hosts[session.host] or prosody).events.fire_event("component-read-timeout", { session = session });
+ end
+end
+
module:provides("net", {
name = "component";
private = true;
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_compression.lua
--- a/plugins/mod_compression.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_compression.lua Wed May 30 21:55:09 2018 +0100
@@ -1,201 +1,9 @@
-- Prosody IM
--- Copyright (C) 2009-2012 Tobias Markmann
---
+-- Copyright (C) 2016 Matthew Wild
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-local st = require "util.stanza";
-local zlib = require "zlib";
-local pcall = pcall;
-local tostring = tostring;
-
-local xmlns_compression_feature = "http://jabber.org/features/compress"
-local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
-local xmlns_stream = "http://etherx.jabber.org/streams";
-local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
-local add_filter = require "util.filters".add_filter;
-
-local compression_level = module:get_option_number("compression_level", 7);
-
-if not compression_level or compression_level < 1 or compression_level > 9 then
- module:log("warn", "Invalid compression level in config: %s", tostring(compression_level));
- module:log("warn", "Module loading aborted. Compression won't be available.");
- return;
-end
-
-module:hook("stream-features", function(event)
- local origin, features = event.origin, event.features;
- if not origin.compressed and (origin.type == "c2s" or origin.type == "s2sin" or origin.type == "s2sout") then
- -- FIXME only advertise compression support when TLS layer has no compression enabled
- features:add_child(compression_stream_feature);
- end
-end);
-
-module:hook("s2s-stream-features", function(event)
- local origin, features = event.origin, event.features;
- -- FIXME only advertise compression support when TLS layer has no compression enabled
- if not origin.compressed and (origin.type == "c2s" or origin.type == "s2sin" or origin.type == "s2sout") then
- features:add_child(compression_stream_feature);
- end
-end);
-
--- Hook to activate compression if remote server supports it.
-module:hook_stanza(xmlns_stream, "features",
- function (session, stanza)
- if not session.compressed and (session.type == "c2s" or session.type == "s2sin" or session.type == "s2sout") then
- -- does remote server support compression?
- local comp_st = stanza:child_with_name("compression");
- if comp_st then
- -- do we support the mechanism
- for a in comp_st:children() do
- local algorithm = a[1]
- if algorithm == "zlib" then
- session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib"))
- session.log("debug", "Enabled compression using zlib.")
- return true;
- end
- end
- session.log("debug", "Remote server supports no compression algorithm we support.")
- end
- end
- end
-, 250);
-
-
--- returns either nil or a fully functional ready to use inflate stream
-local function get_deflate_stream(session)
- local status, deflate_stream = pcall(zlib.deflate, compression_level);
- if status == false then
- local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
- (session.sends2s or session.send)(error_st);
- session.log("error", "Failed to create zlib.deflate filter.");
- module:log("error", "%s", tostring(deflate_stream));
- return
- end
- return deflate_stream
-end
-
--- returns either nil or a fully functional ready to use inflate stream
-local function get_inflate_stream(session)
- local status, inflate_stream = pcall(zlib.inflate);
- if status == false then
- local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
- (session.sends2s or session.send)(error_st);
- session.log("error", "Failed to create zlib.inflate filter.");
- module:log("error", "%s", tostring(inflate_stream));
- return
- end
- return inflate_stream
-end
-
--- setup compression for a stream
-local function setup_compression(session, deflate_stream)
- add_filter(session, "bytes/out", function(t)
- local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
- if status == false then
- module:log("warn", "%s", tostring(compressed));
- session:close({
- condition = "undefined-condition";
- text = compressed;
- extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
- });
- return;
- end
- return compressed;
- end);
-end
-
--- setup decompression for a stream
-local function setup_decompression(session, inflate_stream)
- add_filter(session, "bytes/in", function(data)
- local status, decompressed, eof = pcall(inflate_stream, data);
- if status == false then
- module:log("warn", "%s", tostring(decompressed));
- session:close({
- condition = "undefined-condition";
- text = decompressed;
- extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
- });
- return;
- end
- return decompressed;
- end);
-end
-
-module:hook("stanza/http://jabber.org/protocol/compress:compressed", function(event)
- local session = event.origin;
-
- if session.type == "s2sout" then
- session.log("debug", "Activating compression...")
- -- create deflate and inflate streams
- local deflate_stream = get_deflate_stream(session);
- if not deflate_stream then return true; end
-
- local inflate_stream = get_inflate_stream(session);
- if not inflate_stream then return true; end
-
- -- setup compression for session.w
- setup_compression(session, deflate_stream);
-
- -- setup decompression for session.data
- setup_decompression(session, inflate_stream);
- session:reset_stream();
- session:open_stream(session.from_host, session.to_host);
- session.compressed = true;
- return true;
- end
-end);
-
-module:hook("stanza/http://jabber.org/protocol/compress:failure", function(event)
- local err = event.stanza:get_child();
- (event.origin.log or module._log)("warn", "Compression setup failed (%s)", err and err.name or "unknown reason");
- return true;
-end);
-
-module:hook("stanza/http://jabber.org/protocol/compress:compress", function(event)
- local session, stanza = event.origin, event.stanza;
-
- if session.type == "c2s" or session.type == "s2sin" then
- -- fail if we are already compressed
- if session.compressed then
- local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
- (session.sends2s or session.send)(error_st);
- session.log("debug", "Client tried to establish another compression layer.");
- return true;
- end
-
- -- checking if the compression method is supported
- local method = stanza:child_with_name("method");
- method = method and (method[1] or "");
- if method == "zlib" then
- session.log("debug", "zlib compression enabled.");
-
- -- create deflate and inflate streams
- local deflate_stream = get_deflate_stream(session);
- if not deflate_stream then return true; end
-
- local inflate_stream = get_inflate_stream(session);
- if not inflate_stream then return true; end
-
- (session.sends2s or session.send)(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
- session:reset_stream();
-
- -- setup compression for session.w
- setup_compression(session, deflate_stream);
-
- -- setup decompression for session.data
- setup_decompression(session, inflate_stream);
-
- session.compressed = true;
- elseif method then
- session.log("debug", "%s compression selected, but we don't support it.", tostring(method));
- local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
- (session.sends2s or session.send)(error_st);
- else
- (session.sends2s or session.send)(st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"));
- end
- return true;
- end
-end);
-
+-- COMPAT w/ pre-0.10 configs
+error("mod_compression has been removed in Prosody 0.10+. Please see https://prosody.im/doc/modules/mod_compression for more information.");
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_debug_sql.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_debug_sql.lua Wed May 30 21:55:09 2018 +0100
@@ -0,0 +1,27 @@
+-- Enables SQL query logging
+--
+-- luacheck: ignore 213/uri
+
+module:set_global();
+
+local engines = module:shared("/*/sql/connections");
+
+for uri, engine in pairs(engines) do
+ engine:debug(true);
+end
+
+setmetatable(engines, {
+ __newindex = function (t, uri, engine)
+ engine:debug(true);
+ rawset(t, uri, engine);
+ end
+});
+
+function module.unload()
+ setmetatable(engines, nil);
+ for uri, engine in pairs(engines) do
+ engine:debug(false);
+ end
+end
+
+
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_dialback.lua
--- a/plugins/mod_dialback.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_dialback.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -14,20 +14,45 @@
local sha256_hash = require "util.hashes".sha256;
local sha256_hmac = require "util.hashes".hmac_sha256;
local nameprep = require "util.encodings".stringprep.nameprep;
+local uuid_gen = require"util.uuid".generate;
local xmlns_stream = "http://etherx.jabber.org/streams";
local dialback_requests = setmetatable({}, { __mode = 'v' });
+local dialback_secret = sha256_hash(module:get_option_string("dialback_secret", uuid_gen()), true);
+local dwd = module:get_option_boolean("dialback_without_dialback", false);
+
+--- Helper to check that a session peer's certificate is valid
+function check_cert_status(session)
+ local host = session.direction == "outgoing" and session.to_host or session.from_host
+ local conn = session.conn:socket()
+ local cert
+ if conn.getpeercertificate then
+ cert = conn:getpeercertificate()
+ end
+
+ return module:fire_event("s2s-check-certificate", { host = host, session = session, cert = cert });
+end
+
+
+function module.save()
+ return { dialback_secret = dialback_secret };
+end
+
+function module.restore(state)
+ dialback_secret = state.dialback_secret;
+end
+
function generate_dialback(id, to, from)
- return sha256_hmac(sha256_hash(hosts[from].dialback_secret), to .. ' ' .. from .. ' ' .. id, true);
+ return sha256_hmac(dialback_secret, to .. ' ' .. from .. ' ' .. id, true);
end
function initiate_dialback(session)
-- generate dialback key
session.dialback_key = generate_dialback(session.streamid, session.to_host, session.from_host);
session.sends2s(st.stanza("db:result", { from = session.from_host, to = session.to_host }):text(session.dialback_key));
- session.log("info", "sent dialback key on outgoing s2s stream");
+ session.log("debug", "sent dialback key on outgoing s2s stream");
end
function verify_dialback(id, to, from, key)
@@ -36,7 +61,7 @@
module:hook("stanza/jabber:server:dialback:verify", function(event)
local origin, stanza = event.origin, event.stanza;
-
+
if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then
-- We are being asked to verify the key, to ensure it was generated by us
origin.log("debug", "verifying that dialback key is ours...");
@@ -63,26 +88,36 @@
module:hook("stanza/jabber:server:dialback:result", function(event)
local origin, stanza = event.origin, event.stanza;
-
+
if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then
-- he wants to be identified through dialback
-- We need to check the key with the Authoritative server
local attr = stanza.attr;
local to, from = nameprep(attr.to), nameprep(attr.from);
-
+
if not hosts[to] then
-- Not a host that we serve
- origin.log("info", "%s tried to connect to %s, which we don't serve", from, to);
+ origin.log("warn", "%s tried to connect to %s, which we don't serve", from, to);
origin:close("host-unknown");
return true;
elseif not from then
origin:close("improper-addressing");
end
-
+
+ if dwd and origin.secure then
+ if check_cert_status(origin, from) == false then
+ return
+ elseif origin.cert_chain_status == "valid" and origin.cert_identity_status == "valid" then
+ origin.sends2s(st.stanza("db:result", { to = from, from = to, id = attr.id, type = "valid" }));
+ module:fire_event("s2s-authenticated", { session = origin, host = from });
+ return true;
+ end
+ end
+
origin.hosts[from] = { dialback_key = stanza[1] };
-
+
dialback_requests[from.."/"..origin.streamid] = origin;
-
+
-- COMPAT: ejabberd, gmail and perhaps others do not always set 'to' and 'from'
-- on streams. We fill in the session's to/from here instead.
if not origin.from_host then
@@ -103,7 +138,7 @@
module:hook("stanza/jabber:server:dialback:verify", function(event)
local origin, stanza = event.origin, event.stanza;
-
+
if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then
local attr = stanza.attr;
local dialback_verifying = dialback_requests[attr.from.."/"..(attr.id or "")];
@@ -118,7 +153,8 @@
valid = "invalid";
end
if dialback_verifying.destroyed then
- log("warn", "Incoming s2s session %s was closed in the meantime, so we can't notify it of the db result", tostring(dialback_verifying):match("%w+$"));
+ log("warn", "Incoming s2s session %s was closed in the meantime, so we can't notify it of the dialback result",
+ tostring(dialback_verifying):match("%w+$"));
else
dialback_verifying.sends2s(
st.stanza("db:result", { from = attr.to, to = attr.from, id = attr.id, type = valid })
@@ -132,10 +168,10 @@
module:hook("stanza/jabber:server:dialback:result", function(event)
local origin, stanza = event.origin, event.stanza;
-
+
if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then
-- Remote server is telling us whether we passed dialback
-
+
local attr = stanza.attr;
if not hosts[attr.to] then
origin:close("host-unknown");
@@ -154,7 +190,7 @@
end
end);
-module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza)
+module:hook_tag("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza) -- luacheck: ignore 212/stanza
if origin.external_auth == "failed" then
module:log("debug", "SASL EXTERNAL failed, falling back to dialback");
initiate_dialback(origin);
@@ -162,7 +198,7 @@
end
end, 100);
-module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
+module:hook_tag(xmlns_stream, "features", function (origin, stanza) -- luacheck: ignore 212/stanza
if not origin.external_auth or origin.external_auth == "failed" then
module:log("debug", "Initiating dialback...");
initiate_dialback(origin);
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_disco.lua
--- a/plugins/mod_disco.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_disco.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -13,7 +13,7 @@
local st = require "util.stanza"
local calculate_hash = require "util.caps".calculate_hash;
-local disco_items = module:get_option("disco_items") or {};
+local disco_items = module:get_option_array("disco_items", {})
do -- validate disco_items
for _, item in ipairs(disco_items) do
local err;
@@ -32,7 +32,9 @@
end
end
-module:add_identity("server", "im", module:get_option_string("name", "Prosody")); -- FIXME should be in the non-existing mod_router
+if module:get_host_type() == "local" then
+ module:add_identity("server", "im", module:get_option_string("name", "Prosody")); -- FIXME should be in the non-existing mod_router
+end
module:add_feature("http://jabber.org/protocol/disco#info");
module:add_feature("http://jabber.org/protocol/disco#items");
@@ -97,7 +99,18 @@
local origin, stanza = event.origin, event.stanza;
if stanza.attr.type ~= "get" then return; end
local node = stanza.tags[1].attr.node;
- if node and node ~= "" and node ~= "http://prosody.im#"..get_server_caps_hash() then return; end -- TODO fire event?
+ if node and node ~= "" and node ~= "http://prosody.im#"..get_server_caps_hash() then
+ local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info', node=node});
+ local node_event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+ local ret = module:fire_event("host-disco-info-node", node_event);
+ if ret ~= nil then return ret; end
+ if node_event.exists then
+ origin.send(reply);
+ else
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist"));
+ end
+ return true;
+ end
local reply_query = get_server_disco_info();
reply_query.attr.node = node;
local reply = st.reply(stanza):add_child(reply_query);
@@ -108,9 +121,21 @@
local origin, stanza = event.origin, event.stanza;
if stanza.attr.type ~= "get" then return; end
local node = stanza.tags[1].attr.node;
- if node and node ~= "" then return; end -- TODO fire event?
-
+ if node and node ~= "" then
+ local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items', node=node});
+ local node_event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+ local ret = module:fire_event("host-disco-items-node", node_event);
+ if ret ~= nil then return ret; end
+ if node_event.exists then
+ origin.send(reply);
+ else
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist"));
+ end
+ return true;
+ end
local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
+ local ret = module:fire_event("host-disco-items", { origin = origin, stanza = stanza, reply = reply });
+ if ret ~= nil then return ret; end
for jid, name in pairs(get_children(module.host)) do
reply:tag("item", {jid = jid, name = name~=true and name or nil}):up();
end
@@ -123,23 +148,36 @@
-- Handle caps stream feature
module:hook("stream-features", function (event)
- if event.origin.type == "c2s" then
+ if event.origin.type == "c2s" or event.origin.type == "c2s_unbound" then
event.features:add_child(get_server_caps_feature());
end
end);
-- Handle disco requests to user accounts
+if module:get_host_type() ~= "local" then return end -- skip for components
module:hook("iq/bare/http://jabber.org/protocol/disco#info:query", function(event)
local origin, stanza = event.origin, event.stanza;
if stanza.attr.type ~= "get" then return; end
local node = stanza.tags[1].attr.node;
- if node and node ~= "" then return; end -- TODO fire event?
local username = jid_split(stanza.attr.to) or origin.username;
if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
+ if node and node ~= "" then
+ local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info', node=node});
+ if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
+ local node_event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+ local ret = module:fire_event("account-disco-info-node", node_event);
+ if ret ~= nil then return ret; end
+ if node_event.exists then
+ origin.send(reply);
+ else
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist"));
+ end
+ return true;
+ end
local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info'});
if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
reply:tag('identity', {category='account', type='registered'}):up();
- module:fire_event("account-disco-info", { origin = origin, stanza = reply });
+ module:fire_event("account-disco-info", { origin = origin, reply = reply });
origin.send(reply);
return true;
end
@@ -148,12 +186,24 @@
local origin, stanza = event.origin, event.stanza;
if stanza.attr.type ~= "get" then return; end
local node = stanza.tags[1].attr.node;
- if node and node ~= "" then return; end -- TODO fire event?
local username = jid_split(stanza.attr.to) or origin.username;
if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
+ if node and node ~= "" then
+ local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items', node=node});
+ if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
+ local node_event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+ local ret = module:fire_event("account-disco-items-node", node_event);
+ if ret ~= nil then return ret; end
+ if node_event.exists then
+ origin.send(reply);
+ else
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist"));
+ end
+ return true;
+ end
local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items'});
if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
- module:fire_event("account-disco-items", { origin = origin, stanza = reply });
+ module:fire_event("account-disco-items", { origin = origin, stanza = stanza, reply = reply });
origin.send(reply);
return true;
end
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_groups.lua
--- a/plugins/mod_groups.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_groups.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -10,18 +10,18 @@
local groups;
local members;
-local groups_file;
-
local jid, datamanager = require "util.jid", require "util.datamanager";
local jid_prep = jid.prep;
local module_host = module:get_host();
-function inject_roster_contacts(username, host, roster)
+function inject_roster_contacts(event)
+ local username, host= event.username, event.host;
--module:log("debug", "Injecting group members to roster");
local bare_jid = username.."@"..host;
if not members[bare_jid] and not members[false] then return; end -- Not a member of any groups
-
+
+ local roster = event.roster;
local function import_jids_to_roster(group_name)
for jid in pairs(groups[group_name]) do
-- Add them to roster
@@ -48,7 +48,7 @@
import_jids_to_roster(group_name);
end
end
-
+
-- Import public groups
if members[false] then
for _, group_name in ipairs(members[false]) do
@@ -56,7 +56,7 @@
import_jids_to_roster(group_name);
end
end
-
+
if roster[false] then
roster[false].version = true;
end
@@ -80,12 +80,12 @@
end
function module.load()
- groups_file = module:get_option_string("groups_file");
+ local groups_file = module:get_option_path("groups_file", nil, "config");
if not groups_file then return; end
-
+
module:hook("roster-load", inject_roster_contacts);
datamanager.add_callback(remove_virtual_contacts);
-
+
groups = { default = {} };
members = { };
local curr_group = "default";
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_http.lua
--- a/plugins/mod_http.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_http.lua Wed May 30 21:55:09 2018 +0100
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2012 Matthew Wild
-- Copyright (C) 2008-2012 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -48,6 +48,11 @@
:gsub("%$(%w+)", { host = host_module.host });
end
+local function redir_handler(event)
+ event.response.headers.location = event.request.path.."/";
+ return 301;
+end
+
local ports_by_scheme = { http = 80, https = 443, };
-- Helper to deduce a module's external URL
@@ -104,6 +109,9 @@
local path = event.request.path:sub(base_path_len);
return _handler(event, path);
end;
+ module:hook_object_event(server, event_name:sub(1, -3), redir_handler, -1);
+ elseif event_name:sub(-1, -1) == "/" then
+ module:hook_object_event(server, event_name:sub(1, -2), redir_handler, -1);
end
if not app_handlers[event_name] then
app_handlers[event_name] = handler;
@@ -122,7 +130,7 @@
module:log("warn", "Not listening on any ports, '%s' will be unreachable", app_name);
end
end
-
+
local function http_app_removed(event)
local app_handlers = apps[event.item.name];
apps[event.item.name] = nil;
@@ -130,7 +138,7 @@
module:unhook_object_event(server, event, handler);
end
end
-
+
module:handle_items("http-provider", http_app_added, http_app_removed);
server.add_host(host);
@@ -153,7 +161,9 @@
listener = server.listener;
default_port = 5281;
encryption = "ssl";
- ssl_config = { verify = "none" };
+ ssl_config = {
+ verify = "none";
+ };
multiplex = {
pattern = "^[A-Z]";
};
diff -r c6b45cac9423 -r 7ec098b68042 plugins/mod_http_errors.lua
--- a/plugins/mod_http_errors.lua Wed May 30 21:51:15 2018 +0100
+++ b/plugins/mod_http_errors.lua Wed May 30 21:55:09 2018 +0100
@@ -2,6 +2,8 @@
local server = require "net.http.server";
local codes = require "net.http.codes";
+local xml_escape = require "util.stanza".xml_escape;
+local render = require "util.interpolation".new("%b{}", xml_escape);
local show_private = module:get_option_boolean("http_errors_detailed", false);
local always_serve = module:get_option_boolean("http_errors_always_show", true);
@@ -21,55 +23,52 @@