aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.luacheckrc12
-rw-r--r--CHANGES25
-rw-r--r--Makefile30
-rw-r--r--certs/Makefile42
-rwxr-xr-xconfigure94
-rw-r--r--core/certmanager.lua210
-rw-r--r--core/configmanager.lua158
-rw-r--r--core/hostmanager.lua47
-rw-r--r--core/loggingmanager.lua198
-rw-r--r--core/moduleapi.lua116
-rw-r--r--core/modulemanager.lua60
-rw-r--r--core/portmanager.lua107
-rw-r--r--core/rostermanager.lua155
-rw-r--r--core/s2smanager.lua30
-rw-r--r--core/sessionmanager.lua84
-rw-r--r--core/stanza_router.lua10
-rw-r--r--core/statsmanager.lua72
-rw-r--r--core/storagemanager.lua117
-rw-r--r--core/usermanager.lua72
-rw-r--r--fallbacks/bit.lua2
-rw-r--r--fallbacks/lxp.lua4
-rw-r--r--man/Makefile4
-rw-r--r--man/prosodyctl.man195
-rw-r--r--man/prosodyctl.markdown127
-rw-r--r--net/adns.lua20
-rw-r--r--net/connlisteners.lua15
-rw-r--r--net/dns.lua17
-rw-r--r--net/http.lua79
-rw-r--r--net/http/codes.lua12
-rw-r--r--net/http/server.lua40
-rw-r--r--net/httpserver.lua11
-rw-r--r--net/server.lua2
-rw-r--r--net/server_event.lua1299
-rw-r--r--net/server_select.lua97
-rw-r--r--net/websocket.lua272
-rw-r--r--net/websocket/frames.lua220
-rw-r--r--plugins/adhoc/adhoc.lib.lua7
-rw-r--r--plugins/adhoc/mod_adhoc.lua114
-rw-r--r--plugins/mod_admin_adhoc.lua98
-rw-r--r--plugins/mod_admin_telnet.lua473
-rw-r--r--plugins/mod_announce.lua14
-rw-r--r--plugins/mod_auth_internal_hashed.lua55
-rw-r--r--plugins/mod_auth_internal_plain.lua10
-rw-r--r--plugins/mod_blocklist.lua325
-rw-r--r--plugins/mod_bosh.lua125
-rw-r--r--plugins/mod_c2s.lua112
-rw-r--r--plugins/mod_carbons.lua110
-rw-r--r--plugins/mod_component.lua20
-rw-r--r--plugins/mod_compression.lua41
-rw-r--r--plugins/mod_debug_sql.lua25
-rw-r--r--plugins/mod_dialback.lua57
-rw-r--r--plugins/mod_disco.lua67
-rw-r--r--plugins/mod_groups.lua20
-rw-r--r--plugins/mod_http.lua22
-rw-r--r--plugins/mod_http_errors.lua2
-rw-r--r--plugins/mod_http_files.lua2
-rw-r--r--plugins/mod_iq.lua2
-rw-r--r--plugins/mod_lastactivity.lua5
-rw-r--r--plugins/mod_legacyauth.lua15
-rw-r--r--plugins/mod_message.lua6
-rw-r--r--plugins/mod_motd.lua2
-rw-r--r--plugins/mod_offline.lua10
-rw-r--r--plugins/mod_pep.lua24
-rw-r--r--plugins/mod_ping.lua11
-rw-r--r--plugins/mod_posix.lua58
-rw-r--r--plugins/mod_presence.lua19
-rw-r--r--plugins/mod_privacy.lua443
-rw-r--r--plugins/mod_private.lua62
-rw-r--r--plugins/mod_proxy65.lua41
-rw-r--r--plugins/mod_pubsub.lua463
-rw-r--r--plugins/mod_pubsub/mod_pubsub.lua238
-rw-r--r--plugins/mod_pubsub/pubsub.lib.lua317
-rw-r--r--plugins/mod_register.lua71
-rw-r--r--plugins/mod_roster.lua18
-rw-r--r--plugins/mod_s2s/mod_s2s.lua213
-rw-r--r--plugins/mod_s2s/s2sout.lib.lua49
-rw-r--r--plugins/mod_s2s_auth_certs.lua49
-rw-r--r--plugins/mod_saslauth.lua146
-rw-r--r--plugins/mod_storage_internal.lua3
-rw-r--r--plugins/mod_storage_none.lua7
-rw-r--r--plugins/mod_storage_sql.lua708
-rw-r--r--plugins/mod_storage_sql1.lua414
-rw-r--r--plugins/mod_storage_xep0227.lua (renamed from plugins/storage/mod_xep0227.lua)51
-rw-r--r--plugins/mod_time.lua2
-rw-r--r--plugins/mod_tls.lua88
-rw-r--r--plugins/mod_unknown.lua4
-rw-r--r--plugins/mod_uptime.lua2
-rw-r--r--plugins/mod_vcard.lua2
-rw-r--r--plugins/mod_version.lua2
-rw-r--r--plugins/mod_watchregistrations.lua2
-rw-r--r--plugins/mod_websocket.lua304
-rw-r--r--plugins/mod_welcome.lua2
-rw-r--r--plugins/mod_windows.lua4
-rw-r--r--plugins/muc/mod_muc.lua87
-rw-r--r--plugins/muc/muc.lib.lua270
-rw-r--r--plugins/sql.lib.lua9
-rw-r--r--plugins/storage/sqlbasic.lib.lua97
-rw-r--r--plugins/storage/xep227store.lib.lua168
-rwxr-xr-xprosody34
-rw-r--r--prosody.cfg.lua.dist16
-rwxr-xr-xprosodyctl563
-rw-r--r--tests/modulemanager_option_conversion.lua4
-rwxr-xr-xtests/run_tests.sh2
-rw-r--r--tests/test.lua56
-rw-r--r--tests/test_core_configmanager.lua30
-rw-r--r--tests/test_core_modulemanager.lua48
-rw-r--r--tests/test_core_s2smanager.lua7
-rw-r--r--tests/test_core_stanza_router.lua44
-rw-r--r--tests/test_sasl.lua4
-rw-r--r--tests/test_util_cache.lua229
-rw-r--r--tests/test_util_http.lua (renamed from tests/test_net_http.lua)2
-rw-r--r--tests/test_util_ip.lua89
-rw-r--r--tests/test_util_jid.lua2
-rw-r--r--tests/test_util_multitable.lua8
-rw-r--r--tests/test_util_queue.lua68
-rw-r--r--tests/test_util_random.lua10
-rw-r--r--tests/test_util_rfc3484.lua51
-rw-r--r--tests/test_util_rfc6724.lua97
-rw-r--r--tests/test_util_stanza.lua4
-rw-r--r--tests/test_util_throttle.lua34
-rw-r--r--tests/test_util_uuid.lua24
-rw-r--r--tests/util/logger.lua2
-rwxr-xr-xtools/ejabberd2prosody.lua2
-rw-r--r--tools/ejabberdsql2prosody.lua2
-rw-r--r--tools/erlparse.lua2
-rw-r--r--tools/jabberd14sql2prosody.lua438
-rw-r--r--tools/migration/migrator/prosody_sql.lua2
-rw-r--r--tools/migration/prosody-migrator.lua2
-rw-r--r--tools/openfire2prosody.lua2
-rwxr-xr-xtools/xep227toprosody.lua2
-rw-r--r--util-src/Makefile47
-rw-r--r--util-src/crand.c156
-rw-r--r--util-src/encodings.c360
-rw-r--r--util-src/hashes.c69
-rw-r--r--util-src/net.c77
-rw-r--r--util-src/pposix.c613
-rw-r--r--util-src/ringbuffer.c232
-rw-r--r--util-src/signal.c505
-rw-r--r--util-src/table.c14
-rw-r--r--util-src/windows.c66
-rw-r--r--util/array.lua65
-rw-r--r--util/async.lua158
-rw-r--r--util/cache.lua105
-rw-r--r--util/caps.lua10
-rw-r--r--util/dataforms.lua74
-rw-r--r--util/datamanager.lua107
-rw-r--r--util/datetime.lua22
-rw-r--r--util/debug.lua79
-rw-r--r--util/dependencies.lua52
-rw-r--r--util/events.lua92
-rw-r--r--util/filters.lua36
-rw-r--r--util/helpers.lua37
-rw-r--r--util/hex.lua26
-rw-r--r--util/hmac.lua2
-rw-r--r--util/import.lua3
-rw-r--r--util/interpolation.lua85
-rw-r--r--util/ip.lua39
-rw-r--r--util/iterators.lua47
-rw-r--r--util/jid.lua68
-rw-r--r--util/json.lua11
-rw-r--r--util/logger.lua26
-rw-r--r--util/mercurial.lua34
-rw-r--r--util/multitable.lua16
-rw-r--r--util/openssl.lua35
-rw-r--r--util/paths.lua44
-rw-r--r--util/pluginloader.lua31
-rw-r--r--util/prosodyctl.lua115
-rw-r--r--util/pubsub.lua98
-rw-r--r--util/queue.lua73
-rw-r--r--util/random.lua30
-rw-r--r--util/sasl.lua58
-rw-r--r--util/sasl/anonymous.lua8
-rw-r--r--util/sasl/digest-md5.lua8
-rw-r--r--util/sasl/external.lua27
-rw-r--r--util/sasl/plain.lua8
-rw-r--r--util/sasl/scram.lua156
-rw-r--r--util/sasl_cyrus.lua12
-rw-r--r--util/serialization.lua18
-rw-r--r--util/session.lua65
-rw-r--r--util/set.lua144
-rw-r--r--util/sql.lua255
-rw-r--r--util/sslconfig.lua120
-rw-r--r--util/stanza.lua91
-rw-r--r--util/statistics.lua160
-rw-r--r--util/template.lua12
-rw-r--r--util/termcolours.lua23
-rw-r--r--util/throttle.lua8
-rw-r--r--util/timer.lua12
-rw-r--r--util/uuid.lua34
-rw-r--r--util/watchdog.lua8
-rw-r--r--util/x509.lua40
-rw-r--r--util/xml.lua14
-rw-r--r--util/xmppstream.lua59
193 files changed, 11155 insertions, 6066 deletions
diff --git a/.luacheckrc b/.luacheckrc
new file mode 100644
index 00000000..590f9c37
--- /dev/null
+++ b/.luacheckrc
@@ -0,0 +1,12 @@
+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" }
+
+files["plugins/"] = {
+ ignore = { "122/module" };
+}
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 00000000..3d277bfd
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,25 @@
+0.10.not-released-yet
+=====================
+
+**YYYY-MM-DD**
+
+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)
+- Asynchronous operations
+- Pluggable connection timeout handling
+- mod\_websocket (RFC 7395)
+
+Removed
+-------
+
+- mod\_privacy (XEP-0016)
+
diff --git a/Makefile b/Makefile
index 46a8f49d..4e4a407d 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@ INSTALLEDDATA = $(DATADIR)
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
@@ -31,8 +31,9 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin
install -m755 ./prosodyctl.install $(BIN)/prosodyctl
install -m644 core/*.lua $(SOURCE)/core
install -m644 net/*.lua $(SOURCE)/net
- install -d $(SOURCE)/net/http
+ install -d $(SOURCE)/net/http $(SOURCE)/net/websocket
install -m644 net/http/*.lua $(SOURCE)/net/http
+ install -m644 net/websocket/*.lua $(SOURCE)/net/websocket
install -m644 util/*.lua $(SOURCE)/util
install -m644 util/*.so $(SOURCE)/util
install -d $(SOURCE)/util/sasl
@@ -40,8 +41,8 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin
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
+ test -f $(CONFIG)/prosody.cfg.lua || install -m644 prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
+ -test -f prosody.version && install -m644 prosody.version $(SOURCE)/prosody.version
$(MAKE) install -C util-src
clean:
@@ -51,6 +52,9 @@ clean:
rm -f prosody.version
$(MAKE) clean -C util-src
+test:
+ cd tests && $(RUNWITH) test.lua
+
util/%.so:
$(MAKE) install -C util-src
@@ -64,8 +68,16 @@ util/%.so:
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 --git a/certs/Makefile b/certs/Makefile
index c709ff91..b3011a89 100644
--- a/certs/Makefile
+++ b/certs/Makefile
@@ -15,16 +15,52 @@ keysize=2048
# 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 -c
+
+%.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 -c
# 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 -c
+%.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 -c
+
+# Generate a config from the example
%.cnf:
sed 's,example\.com,$*,g' openssl.cnf > $@
%.key:
umask 0077 && openssl genrsa -out $@ $(keysize)
@chmod 400 $@ -c
+
+# Generate Diffie-Hellman parameters
+dh-%.pem:
+ openssl dhparam -out $@ $*
diff --git a/configure b/configure
index 199f5fcf..77aa1329 100755
--- a/configure
+++ b/configure
@@ -19,6 +19,8 @@ CXX=g++
LD=gcc
RUNWITH=lua
EXCERTS=yes
+PRNG=
+PRNGLIBS=
CFLAGS="-fPIC -Wall"
LDFLAGS="-shared"
@@ -32,7 +34,7 @@ Configure Prosody prior to building.
--help This help.
--ostype=OS Use one of the OS presets.
- May be one of: debian, macosx, linux, freebsd
+ May be one of: debian, macosx, linux, freebsd, openbsd
--prefix=DIR Prefix where Prosody should be installed.
Default is $PREFIX
--sysconfdir=DIR Location where the config file should be installed.
@@ -58,6 +60,11 @@ Configure Prosody prior to building.
icu: use ICU from IBM
--with-ssl=LIB The name of the SSL to link with.
Default is $OPENSSL_LIB
+--with-random=METHOD CSPRNG backend to use. One of
+ getrandom: Linux kernel
+ arc4random: OpenBSD kernel
+ openssl: OpenSSL RAND method
+ Default is to use /dev/urandom
--cflags=FLAGS Flags to pass to the compiler
Default is $CFLAGS
--ldflags=FLAGS Flags to pass to the linker
@@ -99,32 +106,32 @@ do
--ostype=*)
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;
+ 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
+ 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="-Wall -fPIC"
- CFLAGS="$CFLAGS -D_GNU_SOURCE"
+ CFLAGS="-Wall -fPIC -D_GNU_SOURCE"
LDFLAGS="-shared"
- fi
- if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]
- then LUA_INCDIR="/usr/local/include/lua51"
+ 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"
@@ -132,10 +139,12 @@ do
LUA_SUFFIX_SET=yes
LUA_DIR=/usr/local
LUA_DIR_SET=yes
- fi
- if [ "$OSTYPE" = "openbsd" ]
- then LUA_INCDIR="/usr/local/include";
- fi
+ CC=cc
+ LD=ld
+ fi
+ if [ "$OSTYPE" = "openbsd" ]; then
+ LUA_INCDIR="/usr/local/include";
+ fi
;;
--libdir=*)
LIBDIR="$value"
@@ -172,6 +181,16 @@ do
--with-ssl=*)
OPENSSL_LIB="$value"
;;
+ --with-random=getrandom)
+ PRNG=GETRANDOM
+ ;;
+ --with-random=openssl)
+ PRNG=OPENSSL
+ PRNGLIBS=-lcrypto
+ ;;
+ --with-random=arc4random)
+ PRNG=ARC4RANDOM
+ ;;
--cflags=*)
CFLAGS="$value"
;;
@@ -226,7 +245,7 @@ find_program() {
found="no"
while [ "$item" ]
do
- if [ -e "$item/$1" ]
+ if [ -f "$item/$1" ]
then
found="yes"
break
@@ -249,7 +268,7 @@ then
LUA_SUFFIX="$suffix"
if [ "$LUA_DIR_SET" = "yes" ]
then
- if [ -e "$LUA_DIR/bin/lua$suffix" ]
+ if [ -f "$LUA_DIR/bin/lua$suffix" ]
then
find_lua="$LUA_DIR"
fi
@@ -264,7 +283,7 @@ then
done
fi
-if ! [ "$LUA_DIR_SET" = "yes" ]
+if [ "$LUA_DIR_SET" != "yes" ]
then
echo -n "Looking for Lua... "
if [ ! "$find_lua" ]
@@ -283,12 +302,12 @@ then
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
@@ -303,14 +322,16 @@ then
IDNA_LIBS="$ICU_FLAGS"
CFLAGS="$CFLAGS -DUSE_STRINGPREP_ICU"
fi
-if [ "$IDN_LIBRARY" = "idn" ]
+if [ "$IDN_LIBRARY" = "idn" ]
then
IDNA_LIBS="-l$IDN_LIB"
fi
+OPENSSL_LIBS="-l$OPENSSL_LIB"
+
echo -n "Checking Lua includes... "
lua_h="$LUA_INCDIR/lua.h"
-if [ -e "$lua_h" ]
+if [ -f "$lua_h" ]
then
echo "lua.h found in $lua_h"
else
@@ -360,7 +381,7 @@ 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
@@ -368,6 +389,9 @@ CXX=$CXX
LD=$LD
RUNWITH=$RUNWITH
EXCERTS=$EXCERTS
+RANDOM=$PRNG
+RANDOM_LIBS=$PRNGLIBS
+
EOF
diff --git a/core/certmanager.lua b/core/certmanager.lua
index 624bd841..a4c9d891 100644
--- a/core/certmanager.lua
+++ b/core/certmanager.lua
@@ -1,97 +1,173 @@
-- 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 tostring = tostring;
+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 tonumber, tostring = tonumber, tostring;
+local pairs = pairs;
local type = type;
local io_open = io.open;
+local select = select;
local prosody = prosody;
-local resolve_path = configmanager.resolve_relative_path;
+local resolve_path = require"util.paths".resolve_relative_path;
local config_path = prosody.paths.config;
-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 = luasec_major * 100 + luasec_minor;
+local luasec_has = {
+ -- TODO If LuaSec ever starts exposing these things itself, use that instead
+ 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" };
-
-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);
+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);
+ 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 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
+ return { certificate = crt_path, key = key_path };
+ end
+ elseif stat(key_path, "mode") == "file" then
+ 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";
+
+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
-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_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;
-
- 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;
+-- 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.cipher_server_preference;
+ no_ticket = luasec_has.no_ticket;
+ no_compression = luasec_has.no_compression and configmanager.get("*", "ssl_compression") ~= true;
+ single_dh_use = luasec_has.single_dh_use;
+ single_ecdh_use = luasec_has.single_ecdh_use;
};
+ verifyext = { "lsec_continue", "lsec_ignore_purpose" };
+ curve = "secp384r1";
+ ciphers = "HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL";
+}
+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("^(%w+) 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.key then return nil, "No key present in SSL/TLS configuration for "..host; end
+ if not user_ssl_config.certificate then return nil, "No certificate 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
@@ -100,9 +176,9 @@ function create_context(host, mode, user_ssl_config)
local file = err:match("^error loading (.-) %(");
if file then
if file == "private key" then
- file = ssl_config.key or "your private key";
+ file = user_ssl_config.key or "your private key";
elseif file == "certificate" then
- file = ssl_config.certificate or "your certificate file";
+ file = user_ssl_config.certificate or "your certificate file";
end
local reason = err:match("%((.+)%)$") or "some reason";
if reason == "Permission denied" then
@@ -121,13 +197,19 @@ function create_context(host, mode, user_ssl_config)
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");
+ if luasec_has.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;
+};
diff --git a/core/configmanager.lua b/core/configmanager.lua
index c8aa7b9a..16d4b8e2 100644
--- a/core/configmanager.lua
+++ b/core/configmanager.lua
@@ -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 fire_event = prosody and prosody.events.fire_event or function () end;
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
+
+local _M = {};
+local _ENV = nil;
-module "configmanager"
+_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 @@ function _M.rawget(host, key, _oldkey)
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 @@ function _M.set(host, key, value, _oldvalue)
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,95 @@ function load(filename, format)
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 +204,39 @@ do
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 --git a/core/hostmanager.lua b/core/hostmanager.lua
index 06ba72a1..53c1cd4e 100644
--- a/core/hostmanager.lua
+++ b/core/hostmanager.lua
@@ -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 disco_items = require "util.multitable".new();
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 core_route_stanza = _G.prosody.core_route_stanza;
local pairs, select, rawget = pairs, select, rawget;
local tostring, type = tostring, type;
+local setmetatable = setmetatable;
+
+local _ENV = nil;
-module "hostmanager"
+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 @@ local function load_enabled_hosts(config)
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 @@ end
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,10 @@ function activate(host, host_config)
host = host;
s2sout = {};
events = events_new();
- dialback_secret = configmanager.get(host, "dialback_secret") or uuid_gen();
send = host_send;
modules = {};
};
+ setmetatable(host_session, host_mt);
if not host_config.component_module then -- host
host_session.type = "local";
host_session.sessions = {};
@@ -93,7 +108,7 @@ function activate(host, host_config)
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 +119,11 @@ function deactivate(host, reason)
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 +166,12 @@ function deactivate(host, reason)
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 --git a/core/loggingmanager.lua b/core/loggingmanager.lua
index c69dede8..e3a83817 100644
--- a/core/loggingmanager.lua
+++ b/core/loggingmanager.lua
@@ -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,29 +10,28 @@
local format = string.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 tostring = tostring;
+local select = select;
+local unpack = table.unpack or unpack; --luacheck: ignore 113
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 +44,16 @@ local logging_levels = { "debug", "info", "warn", "error" }
-- 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 +62,7 @@ end
-- 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 +81,7 @@ function apply_sink_rules(sink_type)
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 +127,7 @@ function get_levels(criteria, set)
end
end
end
-
+
for _, level in ipairs(criteria) do
set[level] = true;
end
@@ -136,14 +135,14 @@ function get_levels(criteria, set)
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 +151,13 @@ function reload_logging()
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 +167,98 @@ prosody.events.add_handler("config-reloaded", reload_logging);
--- 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
+log_sink_types.nowhere = log_to_nowhere;
+
+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;
--- Column width for "source" (used by stdout and console)
-local sourcewidth = 20;
+ local timestamps = sink_config.timestamps;
-function log_sink_types.stdout(config)
- local timestamps = config.timestamps;
-
if timestamps == true 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), " ");
- 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");
- end
- end
-end
-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");
+ if sink_config.buffer_mode ~= false then
+ logfile:setvbuf(sink_config.buffer_mode or "line");
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 timestamps == true then
- timestamps = default_timestamp; -- Default format
+ -- Column width for "source" (used by stdout and console)
+ local sourcewidth = sink_config.source_width;
+
+ return function (name, level, message, ...)
+ local n = select('#', ...);
+ if n ~= 0 then
+ local arg = { ... };
+ for i = 1, n do
+ arg[i] = tostring(arg[i]);
+ end
+ message = format(message, unpack(arg, 1, n));
end
- return function (name, level, message, ...)
+ if sourcewidth then
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
+ name = name .. rep(" ", sourcewidth-#name);
+ else
+ name = name .. "\t";
end
+ write(logfile, timestamps and os_date(timestamps) or "", name, level, "\t", message, "\n");
end
end
+log_sink_types.file = log_to_file;
-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;
+local function log_to_stdout(sink_config)
+ if not sink_config.timestamps then
+ sink_config.timestamps = false;
+ end
+ if sink_config.source_width == nil then
+ sink_config.source_width = 20;
end
- local write, flush = logfile.write, logfile.flush;
+ return log_to_file(sink_config, stdout);
+end
+log_sink_types.stdout = log_to_stdout;
- local timestamps = config.timestamps;
+local do_pretty_printing = true;
- if timestamps == nil or 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
+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, ...)
- if timestamps then
- write(logfile, os_date(timestamps), " ");
+ local logstyle = logstyles[level];
+ if logstyle then
+ level = getstring(logstyle, level);
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;
+ return logstdout(name, level, message, ...);
+ end
end
+log_sink_types.console = log_to_console;
-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 --git a/core/moduleapi.lua b/core/moduleapi.lua
index ed75669b..402c7927 100644
--- a/core/moduleapi.lua
+++ b/core/moduleapi.lua
@@ -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 @@ function api:get_host()
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 @@ end
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 @@ function api:has_feature(xmlns)
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 @@ function api:hook_object_event(object, event, handler, priority)
end
function api:unhook_object_event(object, event, handler)
+ self.event_handlers:set(object, event, handler, nil);
return object.remove_handler(event, handler);
end
@@ -113,16 +119,30 @@ function api:hook_tag(xmlns, name, handler, 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)
@@ -252,21 +272,21 @@ function api:get_option_array(name, ...)
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 +302,20 @@ function api:get_option_inherited_set(name, ...)
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 +338,16 @@ function api:remove_item(key, value)
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
@@ -343,6 +378,14 @@ function api:send(stanza)
return core_post_stanza(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)
return timer.add_task(delay, function (t)
if self.loaded == false then return; end
@@ -356,12 +399,35 @@ function api:get_directory()
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, "duration");
+ 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 --git a/core/modulemanager.lua b/core/modulemanager.lua
index 4df95069..65542f9a 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -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,33 @@ local pluginloader = require "util.pluginloader";
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, get_modules, is_loaded, module_has_method, call_module_method;
-- [host] = { [module] = module_env }
local modulemap = { ["*"] = {} };
@@ -45,28 +47,28 @@ local modulemap = { ["*"] = {} };
-- 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 +86,18 @@ end);
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,11 +119,11 @@ local function do_load_module(host, module_name, state)
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);
return nil, "module-already-loaded";
@@ -131,7 +133,7 @@ local function do_load_module(host, module_name, state)
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 +149,19 @@ local function do_load_module(host, module_name, state)
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 +319,15 @@ function call_module_method(module, method, ...)
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 --git a/core/portmanager.lua b/core/portmanager.lua
index 37442a31..60f7731d 100644
--- a/core/portmanager.lua
+++ b/core/portmanager.lua
@@ -9,12 +9,12 @@ local set = require "util.set";
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
@@ -41,7 +41,7 @@ local active_services = multitable.new();
--- 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 @@ local function error_to_friendly_message(service_name, port, err)
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 @@ function activate(service_name)
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,7 +96,7 @@ function activate(service_name)
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);
@@ -127,24 +108,15 @@ function activate(service_name)
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");
end
@@ -170,8 +142,10 @@ function activate(service_name)
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 +154,7 @@ function deactivate(service_name, service_info)
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 +164,12 @@ function register_service(service_name, service_info)
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 +184,14 @@ function unregister_service(service_name, service_info)
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 +202,37 @@ function get_service_at(interface, port)
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 --git a/core/rostermanager.lua b/core/rostermanager.lua
index 5e06e3f7..7f6fb82a 100644
--- a/core/rostermanager.lua
+++ b/core/rostermanager.lua
@@ -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,21 +13,24 @@ local log = require "util.logger".init("rostermanager");
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";
+
+local _ENV = nil;
-module "rostermanager"
+local save_roster; -- forward declaration
-function add_to_roster(session, jid, item)
+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 @@ function add_to_roster(session, jid, item)
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 @@ function remove_from_roster(session, jid)
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"});
@@ -79,7 +82,22 @@ function roster_push(username, host, jid)
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];
@@ -91,27 +109,28 @@ function load_roster(username, host)
else -- Attempt to load roster for non-loaded user
log("debug", "load_roster: loading for offline user: %s@%s", username, host);
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
+ roster_metadata(roster, err);
if roster[jid] then
roster[jid] = nil;
log("warn", "roster for %s has a self-contact", jid);
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 +139,24 @@ function save_roster(username, host, roster)
--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 +166,13 @@ function process_inbound_subscription_approval(username, host, jid)
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 +190,18 @@ function process_inbound_subscription_cancellation(username, host, jid)
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 +214,7 @@ function process_inbound_unsubscribe(username, host, jid)
end
end
if changed then
- return save_roster(username, host, roster);
+ return save_roster(username, host, roster, jid);
end
end
@@ -198,13 +223,13 @@ local function _get_online_roster_subscription(jidA, jidB)
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];
@@ -213,24 +238,23 @@ end
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
@@ -242,9 +266,9 @@ function set_contact_pending_out(username, host, jid) -- subscribe
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
@@ -257,9 +281,9 @@ function unsubscribe(username, host, jid)
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];
@@ -272,38 +296,37 @@ function subscribed(username, host, jid)
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);
+ local success = (pending or is_subscribed) and save_roster(username, host, roster, jid);
return success, pending, 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
@@ -318,4 +341,22 @@ end]]
-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_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 --git a/core/s2smanager.lua b/core/s2smanager.lua
index fb5c4299..a8d399d2 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -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 @@ prosody.incoming_s2s = incoming_s2s;
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 @@ local resting_session = { -- Resting, not dead
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,17 @@ function retire_session(session, reason)
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 ""));
-
+
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 +91,15 @@ function destroy_session(session, reason)
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 --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index 4b014d18..6aa0a4f0 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -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,8 +10,8 @@ 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 +24,9 @@ local uuid_generate = require "util.uuid".generate;
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;
@@ -37,14 +37,19 @@ function new_session(conn)
if t then
t = filter("bytes/out", tostring(t));
if t then
- return w(conn, t);
+ local ret, err = w(conn, t);
+ if not ret then
+ session.log("debug", "Error writing to connection: %s", tostring(err));
+ return false, err;
+ end
end
end
+ return true;
end
session.ip = conn:ip();
local conn_name = "c2s"..tostring(session):match("[a-f0-9]+$");
session.log = logger.init(conn_name);
-
+
return session;
end
@@ -54,11 +59,11 @@ local resting_session = { -- Resting, not dead
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;
@@ -67,25 +72,26 @@ function retire_session(session)
function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); return false; end
function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
+ session.thread = { run = function (_, data) return session.data(data) end };
return setmetatable(session, resting_session);
end
-function destroy_session(session, err)
+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;
@@ -94,11 +100,11 @@ function destroy_session(session, err)
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;
@@ -111,15 +117,25 @@ end
-- 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;
@@ -156,12 +172,12 @@ function bind_resource(session, resource)
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;
-
+
local err;
session.roster, err = rm_load_roster(session.username, session.host);
if err then
@@ -176,18 +192,18 @@ function bind_resource(session, resource)
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;
@@ -197,12 +213,12 @@ function send_to_available_resources(user, host, stanza)
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;
@@ -212,4 +228,12 @@ function send_to_interested_resources(user, host, stanza)
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 --git a/core/stanza_router.lua b/core/stanza_router.lua
index a2c7b396..55c94bf4 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -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,7 +30,7 @@ deprecated_warning"core_process_stanza";
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
@@ -46,7 +46,7 @@ local function handle_unhandled_stanza(host, origin, stanza)
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
+ 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
@@ -199,7 +199,7 @@ function core_route_stanza(origin, stanza)
-- 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);
@@ -221,6 +221,8 @@ function core_route_stanza(origin, stanza)
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 --git a/core/statsmanager.lua b/core/statsmanager.lua
new file mode 100644
index 00000000..7771a2b3
--- /dev/null
+++ b/core/statsmanager.lua
@@ -0,0 +1,72 @@
+
+local stats = require "util.statistics".new();
+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_config = config.get("*", "statistics_interval");
+local stats_interval = tonumber(stats_config);
+if stats_config and not stats_interval then
+ log("error", "Invalid 'statistics_interval' setting, statistics will be disabled");
+end
+
+local measure, collect;
+local latest_stats = {};
+local changed_stats = {};
+local stats_extra = {};
+
+if stats_interval then
+ log("debug", "Statistics collection is enabled every %d seconds", stats_interval);
+ function measure(type, name)
+ local f = assert(stats[type], "unknown stat type: "..type);
+ return f(name);
+ end
+
+ 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");
+ 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
+ mark_collection_done();
+ 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();
+ return stats_interval;
+ end
+
+ timer.add_task(stats_interval, collect);
+ prosody.events.add_handler("server-started", function () collect() end, -1);
+else
+ log("debug", "Statistics collection is disabled");
+ -- nop
+ function measure()
+ return measure;
+ end
+ function collect()
+ end
+end
+
+return {
+ measure = measure;
+ collect = collect;
+ get_stats = function ()
+ return latest_stats, changed_stats, stats_extra;
+ end;
+ get = function (name)
+ return latest_stats[name], stats_extra[name];
+ end;
+};
diff --git a/core/storagemanager.lua b/core/storagemanager.lua
index 1c82af6d..4f04e594 100644
--- a/core/storagemanager.lua
+++ b/core/storagemanager.lua
@@ -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 log = require "util.logger".init("storagemanager");
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 @@ local null_storage_driver = setmetatable(
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 null_storage_driver = setmetatable(
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 @@ function initialize_host(host)
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,28 @@ function load_driver(host, driver_name)
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 +89,7 @@ function get_driver(host, store)
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,11 +99,64 @@ function get_driver(host, store)
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;
+ else
+ current = keydatas;
+ end
+ else
+ for k,v in pairs(keydatas) do
+ if v == self.remove then v = nil; end
+ current[k] = v;
+ end
+ 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
+ if typ == "map" then -- Use shim on top of keyval store
+ log("debug", "map storage driver unavailable, using shim on top of keyval store.");
+ return create_map_shim(host, store);
+ end
log("debug", "Storage driver %s does not support store %s (%s), falling back to null driver",
driver_name, store, typ or "<nil>");
ret = null_storage_driver;
@@ -94,14 +166,19 @@ function open(host, store, typ)
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
@@ -132,4 +209,12 @@ function datamanager.purge(username, host)
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 --git a/core/usermanager.lua b/core/usermanager.lua
index 08343bee..d5132662 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -10,7 +10,6 @@ local modulemanager = require "core.modulemanager";
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 setmetatable = setmetatable;
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 @@ function initialize_host(host)
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,98 @@ function initialize_host(host)
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)
+local function set_password(username, password, host)
return hosts[host].users.set_password(username, password);
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 --git a/fallbacks/bit.lua b/fallbacks/bit.lua
index 2482c473..28dca4e6 100644
--- a/fallbacks/bit.lua
+++ b/fallbacks/bit.lua
@@ -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 --git a/fallbacks/lxp.lua b/fallbacks/lxp.lua
index 6d3297d1..ac1c9a03 100644
--- a/fallbacks/lxp.lua
+++ b/fallbacks/lxp.lua
@@ -61,7 +61,7 @@ local function parser(data, handlers, ns_separator)
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 @@ local function parser(data, handlers, ns_separator)
ns = getmetatable(ns);
return tag;
end
-
+
while true do
if peek() == "<" then
local elem = read_until(">"):sub(2,-2);
diff --git a/man/Makefile b/man/Makefile
new file mode 100644
index 00000000..79bdd90a
--- /dev/null
+++ b/man/Makefile
@@ -0,0 +1,4 @@
+all: prosodyctl.man
+
+%.man: %.markdown
+ pandoc -s -t man -o $@ $^
diff --git a/man/prosodyctl.man b/man/prosodyctl.man
index 6dcb04cd..b91502a8 100644
--- a/man/prosodyctl.man
+++ b/man/prosodyctl.man
@@ -1,83 +1,140 @@
-.TH PROSODYCTL 1 "2009-07-02"
-
+.\" Automatically generated by Pandoc 1.15.2
+.\"
+.hy
+.TH "PROSODYCTL" "1" "2015\-12\-23" "" ""
.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.
-
-.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.
-
+.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 Debugging
+.PP
+prosodyctl can also show some information about the environment,
+dependencies and such to aid in debugging.
+.TP
+.B about
+Shows environment, various paths used by Prosody and installed
+dependencies.
+.RS
+.RE
+.TP
+.B check [what]
+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
-\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"
-
+.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
+
+unregister\ user\ server
+\f[]
+.fi
.SH OPTIONS
-.IP \fI--help\fP
+.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: <https://prosody.im/>
.SH AUTHORS
-Dwayne Bent <dbb.1@liqd.org>
+Dwayne Bent <dbb.1@liqd.org>; Kim Alvefur.
diff --git a/man/prosodyctl.markdown b/man/prosodyctl.markdown
new file mode 100644
index 00000000..217dfd3d
--- /dev/null
+++ b/man/prosodyctl.markdown
@@ -0,0 +1,127 @@
+---
+author:
+- 'Dwayne Bent <dbb.1@liqd.org>'
+- Kim Alvefur
+date: '2015-12-23'
+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.
+
+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
+=======
+
+`--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: <https://prosody.im/>
diff --git a/net/adns.lua b/net/adns.lua
index 3fc958f4..d3da2065 100644
--- a/net/adns.lua
+++ b/net/adns.lua
@@ -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.
--
@@ -16,9 +16,9 @@ local coroutine, tostring, pcall = coroutine, tostring, pcall;
local function dummy_send(sock, data, i, j) return (j-i)+1; end
-module "adns"
+local _ENV = nil;
-function lookup(handler, qname, qtype, qclass)
+local function lookup(handler, qname, qtype, qclass)
return coroutine.wrap(function (peek)
if peek then
log("debug", "Records for %s already cached, using those...", qname);
@@ -43,12 +43,12 @@ function lookup(handler, qname, qtype, qclass)
end)(dns.peek(qname, qtype, qclass));
end
-function cancel(handle, call_handler, reason)
+local 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 = "<unknown>";
local listener = {};
local handler = {};
@@ -65,7 +65,7 @@ function new_async_socket(sock, resolver)
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 +73,7 @@ function new_async_socket(sock, resolver)
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
@@ -88,4 +88,8 @@ end
dns.socket_wrapper_set(new_async_socket);
-return _M;
+return {
+ lookup = lookup;
+ cancel = cancel;
+ new_async_socket = new_async_socket;
+};
diff --git a/net/connlisteners.lua b/net/connlisteners.lua
index 99ddc720..000bfa63 100644
--- a/net/connlisteners.lua
+++ b/net/connlisteners.lua
@@ -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 --git a/net/dns.lua b/net/dns.lua
index d123731c..b047ec54 100644
--- a/net/dns.lua
+++ b/net/dns.lua
@@ -71,8 +71,8 @@ local get, set = ztact.get, ztact.set;
local default_timeout = 15;
-------------------------------------------------- module dns
-module('dns')
-local dns = _M;
+local _ENV = nil;
+local dns = {};
-- dns type & class codes ------------------------------ dns type & class codes
@@ -213,15 +213,6 @@ function cache_metatable.__tostring(cache)
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
@@ -629,7 +620,7 @@ function resolver:getsocket(servernum) -- - - - - - - - - - - - - getsocket
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
@@ -1054,8 +1045,6 @@ end
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 --git a/net/http.lua b/net/http.lua
index 8ce47494..b78f8438 100644
--- a/net/http.lua
+++ b/net/http.lua
@@ -1,12 +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.
--
-local socket = require "socket"
local b64 = require "util.encodings".base64.encode;
local url = require "socket.url"
local httpstream_new = require "net.http.parser".new;
@@ -24,7 +23,7 @@ local assert, error = assert, error
local log = require "util.logger".init("http");
-module "http"
+local _ENV = nil;
local requests = {}; -- Open requests
@@ -37,7 +36,7 @@ function listener.onconnect(conn)
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 +44,7 @@ function listener.onconnect(conn)
conn:write(t_concat(t));
end
conn:write("\r\n");
-
+
if req.body then
conn:write(req.body);
end
@@ -76,6 +75,13 @@ function listener.ondetach(conn)
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 +91,12 @@ local function request_reader(request, data, err)
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 +113,20 @@ local function request_reader(request, data, err)
end
local function handleerr(err) log("error", "Traceback[http]: %s", traceback(tostring(err), 2)); end
-function request(u, ex, callback)
+local function request(u, ex, callback)
local req = url.parse(u);
-
+
if not (req and req.host) then
callback(nil, 0, req);
return nil, "invalid-url";
end
-
+
if not req.path then
req.path = "/";
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 +140,7 @@ function request(u, ex, callback)
["Host"] = host_header;
["User-Agent"] = "Prosody XMPP Server";
};
-
+
if req.userinfo then
headers["Authorization"] = "Basic "..b64(req.userinfo);
end
@@ -154,33 +160,29 @@ function request(u, ex, callback)
end
end
end
-
+
-- 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" } };
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
+ callback(nil, 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.reader = request_reader;
req.state = "status";
@@ -189,17 +191,12 @@ function request(u, ex, callback)
return req;
end
-function destroy_request(request)
- if request.conn then
- request.conn = nil;
- request.handler:close()
- end
-end
-
-local urlencode, urldecode = util_http.urlencode, util_http.urldecode;
-local formencode, formdecode = util_http.formencode, util_http.formdecode;
-
-_M.urlencode, _M.urldecode = urlencode, urldecode;
-_M.formencode, _M.formdecode = formencode, formdecode;
-
-return _M;
+return {
+ request = request;
+
+ -- COMPAT
+ urlencode = util_http.urlencode;
+ urldecode = util_http.urldecode;
+ formencode = util_http.formencode;
+ formdecode = util_http.formdecode;
+};
diff --git a/net/http/codes.lua b/net/http/codes.lua
index 0cadd079..bc31c7dd 100644
--- a/net/http/codes.lua
+++ b/net/http/codes.lua
@@ -25,6 +25,7 @@ local response_codes = {
[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,21 @@ local response_codes = {
[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";
[500] = "Internal Server Error";
[501] = "Not Implemented";
@@ -61,6 +66,7 @@ local response_codes = {
[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
diff --git a/net/http/server.lua b/net/http/server.lua
index f091595c..aeaa7416 100644
--- a/net/http/server.lua
+++ b/net/http/server.lua
@@ -11,6 +11,7 @@ local setmetatable = setmetatable;
local xpcall = xpcall;
local traceback = debug.traceback;
local tostring = tostring;
+local cache = require "util.cache";
local codes = require "net.http.codes";
local _M = {};
@@ -27,7 +28,10 @@ local function is_wildcard_match(wildcard_event, event)
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)
+ rawset(_handlers, key, nil);
+end);
local event_map = events._event_map;
setmetatable(events._handlers, {
@@ -62,10 +66,7 @@ setmetatable(events._handlers, {
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;
@@ -189,6 +190,7 @@ function handle_request(conn, request, finish_cb)
persistent = persistent;
conn = conn;
send = _M.send_response;
+ done = _M.finish_response;
finish_cb = finish_cb;
};
conn._http_open_response = response;
@@ -208,7 +210,7 @@ function handle_request(conn, request, finish_cb)
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 }));
@@ -250,24 +252,30 @@ function handle_request(conn, request, finish_cb)
response.status_code = 404;
response:send(events.fire_event("http-error", { code = 404 }));
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.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;
@@ -286,7 +294,7 @@ function _M.remove_handler(event, handler)
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 --git a/net/httpserver.lua b/net/httpserver.lua
index 7d574788..6e2e31b9 100644
--- a/net/httpserver.lua
+++ b/net/httpserver.lua
@@ -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 --git a/net/server.lua b/net/server.lua
index 9b0d27e1..41e180fa 100644
--- a/net/server.lua
+++ b/net/server.lua
@@ -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 --git a/net/server_event.lua b/net/server_event.lua
index 45938a13..e5705ab5 100644
--- a/net/server_event.lua
+++ b/net/server_event.lua
@@ -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"
@@ -32,27 +33,32 @@ local cfg = {
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,737 +78,673 @@ local bitor = ( function( ) -- thx Rici Lake
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"
+local interfacelist = { }
+
+-- Client interface methods
+local interface_mt = {}; interface_mt.__index = interface_mt;
+
+-- Private methods
+function interface_mt:_close()
+ return self:_destroy();
+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 has_luasec then -- start ssl session
+ self:starttls(self._sslctx, true)
+ else -- normal connection
+ self:_start_session(true)
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
+ debug( "new connection established. id:", self.id )
+ end
+ self.eventconnect = nil
+ return -1
+ end
+ 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
- len = len - 1
- return len
- else
- return array
+ 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
-end )( )
-
--- 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();
- 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)
+ 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
+ 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
- debug( "new connection established. id:", self.id )
+ self:_start_session( call_onconnect )
+ debug( "ssl handshake done" )
+ self.eventhandshake = nil
+ return -1
end
- self.eventconnect = nil
- return -1
- end
- 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()
+ if err == "wantwrite" then
+ event = EV_WRITE
+ elseif err == "wantread" then
+ event = EV_READ
+ else
+ debug( "ssl handshake error:", err )
+ self.fatalerror = err
end
- 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 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
- 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
+ debug( "handshake failed because:", self.fatalerror )
+ self.eventhandshake = nil
+ return -1
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
+ )
+ 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:pause()
- return self:_lock(self.nointerface, true, self.nowriting);
- 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:resume()
- self:_lock(self.nointerface, false, self.nowriting);
- if not self.eventread then
- self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback
- end
+--TODO: Deprecate
+function interface_mt:lock_read(switch)
+ if switch then
+ return self:pause();
+ else
+ return self:resume();
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
- 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: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:pause()
+ return self:_lock(self.nointerface, true, self.nowriting);
+end
- function interface_mt:type()
- return self._type or "client"
+function interface_mt:resume()
+ self:_lock(self.nointerface, false, self.nowriting);
+ if not self.eventread then
+ self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback
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
+
+function interface_mt:counter(c)
+ if c then
+ self._connections = self._connections + c
end
+ return self._connections
+end
- function interface_mt:set_mode(pattern)
- if pattern then
- self._pattern = pattern;
- end
- return self._pattern;
- end
-
- function interface_mt:set_send(new_send)
- -- No-op, we always use the underlying connection's send
- 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
+-- 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 "ssl session delayed until writebuffer is empty..."
+ 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
- self.starttls = false;
+ 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: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: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:ondetach()
+end
+
+function interface_mt:set_mode(pattern)
+ if pattern then
+ self._pattern = pattern;
end
- function interface_mt:onstatus()
+ 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: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.onreadtimeout, self.onstatus, self.ondetach
+ = listener.onconnect, listener.ondisconnect, listener.onincoming, listener.ontimeout,
+ listener.onreadtimeout, 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: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
- 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
+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
- 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.eventreadtimeout then
- return EV_WRITE, EV_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
+ 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
- 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 )
- 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
+ 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.eventreadtimeout then
+ return EV_WRITE, EV_TIMEOUT
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.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.eventread = nil
- return -1
+ interface.eventwritetimeout = 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;
+ 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_READ, cfg.READ_TIMEOUT
+ 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
- client:settimeout( 0 ) -- set non blocking
- setmetatable(interface, interface_mt)
- interfacelist( "add", interface ) -- add to interfacelist
- return interface
- 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
+ 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 interface:onreadtimeout() ~= true then
+ 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
- --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 "<unknown IP>", client_port or "<unknown port>", "to", port or "<unknown port>");
-
- client, err = server:accept() -- try to accept again
+ if interface.eventreadtimeout then
+ interface.eventreadtimeout:close( )
+ interface.eventreadtimeout = nil
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
+ 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
- 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
+ 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
- local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- new server handler
- debug( "new server created with id:", tostring(interface))
- return interface
+ return EV_READ, cfg.READ_TIMEOUT
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
+ 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
- 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
+ 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
- local sslctx
- if sslcfg then -- handle ssl/new context
- if not ssl then
- debug "need luasec, but not available"
- return nil, "luasec not found"
+ --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
- sslctx, err = sslcfg
- if err then
- debug( "cannot create new ssl context:", err )
- return nil, err
+ 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 "<unknown IP>", client_port or "<unknown port>", "to", port or "<unknown port>");
+
+ client, err = server:accept() -- try to accept again
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
+ 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
- debug( "new connection failed:", err )
- return nil, err
+ 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 )
+ interface:_start_connection( sslctx )
+ debug( "new connection id:", interface.id )
+ return interface, err
+ else
+ debug( "new connection failed:", err )
+ return nil, err
+ 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
@@ -811,9 +753,9 @@ end
local function setquitting(yes)
if yes then
- -- Quit now
- closeallservers();
- base:loopexit();
+ -- Quit now
+ closeallservers();
+ base:loopexit();
end
end
@@ -825,7 +767,7 @@ end
-- 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
@@ -838,14 +780,14 @@ end
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
@@ -857,12 +799,11 @@ local function link(sender, receiver, buffersize)
end
return {
-
cfg = cfg,
base = base,
loop = loop,
link = link,
- event = event,
+ event = levent,
event_base = base,
addevent = newevent,
addserver = addserver,
diff --git a/net/server_select.lua b/net/server_select.lua
index c50a6ce1..52a0d5f1 100644
--- a/net/server_select.lua
+++ b/net/server_select.lua
@@ -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
--
@@ -48,13 +48,14 @@ local coroutine_yield = coroutine.yield
--// 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 +150,7 @@ _accepretry = 10 -- seconds to wait until the next attempt of a full server to a
_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 +296,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
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 +326,8 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
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 +336,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
disconnect = listeners.ondisconnect
status = listeners.onstatus
drain = listeners.ondrain
+ handler.onreadtimeout = listeners.onreadtimeout
detach = listeners.ondetach
end
handler.getstats = function( )
@@ -404,6 +409,9 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
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
@@ -575,6 +583,9 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
_ = 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
@@ -592,13 +603,14 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
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);
@@ -624,7 +636,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
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 )
@@ -651,7 +663,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
_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);
@@ -712,7 +724,7 @@ local function link(sender, receiver, buffersize)
sender_locked = nil;
end
end
-
+
local _readbuffer = sender.readbuffer;
function sender.readbuffer()
_readbuffer();
@@ -727,22 +739,23 @@ end
----------------------------------// 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 )
@@ -883,16 +896,18 @@ loop = function(once) -- this is the main loop of the program
_starttime = _currenttime
for handler, timestamp in pairs( _writetimes ) do
if os_difftime( _currenttime - timestamp ) > _sendtimeout then
- --_writetimes[ handler ] = nil
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 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
@@ -920,6 +935,7 @@ loop = function(once) -- this is the main loop of the program
socket_sleep( _sleeptime )
until quitting;
if once and quitting == "once" then quitting = nil; return; end
+ closeall();
return "quitting"
end
@@ -952,17 +968,46 @@ local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx
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
@@ -992,7 +1037,7 @@ return {
addclient = addclient,
wrapclient = wrapclient,
-
+
loop = loop,
link = link,
step = step,
diff --git a/net/websocket.lua b/net/websocket.lua
new file mode 100644
index 00000000..a4274eec
--- /dev/null
+++ b/net/websocket.lua
@@ -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)
+ module: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)
+ 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)
+ 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, {
+ 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 --git a/net/websocket/frames.lua b/net/websocket/frames.lua
new file mode 100644
index 00000000..737f46bb
--- /dev/null
+++ b/net/websocket/frames.lua
@@ -0,0 +1,220 @@
+-- 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 log = require "util.logger".init "websocket.frames";
+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 --git a/plugins/adhoc/adhoc.lib.lua b/plugins/adhoc/adhoc.lib.lua
index b544ddc8..5c90c91b 100644
--- a/plugins/adhoc/adhoc.lib.lua
+++ b/plugins/adhoc/adhoc.lib.lua
@@ -25,12 +25,13 @@ function _M.new(name, node, handler, permission)
end
function _M.handle_cmd(command, origin, stanza)
- local sessionid = stanza.tags[1].attr.sessionid or uuid.generate();
+ local cmdtag = stanza.tags[1]
+ local sessionid = cmdtag.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");
+ dataIn.action = cmdtag.attr.action or "execute";
+ dataIn.form = cmdtag:get_child("x", "jabber:x:data");
local data, state = command:handler(dataIn, states[sessionid]);
states[sessionid] = state;
diff --git a/plugins/adhoc/mod_adhoc.lua b/plugins/adhoc/mod_adhoc.lua
index 69b2c8da..1c956021 100644
--- a/plugins/adhoc/mod_adhoc.lua
+++ b/plugins/adhoc/mod_adhoc.lua
@@ -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);
- 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);
+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;
-
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 --git a/plugins/mod_admin_adhoc.lua b/plugins/mod_admin_adhoc.lua
index 232fa5f7..392e715e 100644
--- a/plugins/mod_admin_adhoc.lua
+++ b/plugins/mod_admin_adhoc.lua
@@ -9,6 +9,7 @@ local _G = _G;
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 st, jid = require "util.stanza", require "util.jid";
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;
@@ -245,7 +247,7 @@ local get_user_roster_handler = adhoc_simple(get_user_roster_layout, function(fi
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 get_user_stats_handler = adhoc_simple(get_user_stats_layout, function(fiel
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 @@ local get_online_users_command_handler = adhoc_simple(get_online_users_layout, f
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 "<unknown>";
if session.presence then
status = session.presence:child_with_name("show");
if status then
@@ -354,13 +356,92 @@ local get_online_users_command_handler = adhoc_simple(get_online_users_layout, f
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 @@ local globally_reload_module_handler = adhoc_initial(globally_reload_module_layo
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, function(fields, err)
end);
local function send_to_online(message, server)
+ local sessions;
if server then
sessions = { [server] = hosts[server] };
else
@@ -631,7 +713,7 @@ local globally_unload_module_handler = adhoc_initial(globally_unload_module_layo
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_password_desc = adhoc_new("Get User Password", "http://jabber.org
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_password_desc);
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 --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua
index 86403606..9dfbbc7a 100644
--- a/plugins/mod_admin_telnet.lua
+++ b/plugins/mod_admin_telnet.lua
@@ -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,17 +17,17 @@ local _G = _G;
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");
@@ -60,20 +60,20 @@ function console:new_session(conn)
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 +87,9 @@ function console:process_line(session, line)
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 +103,20 @@ function console:process_line(session, line)
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 +125,7 @@ function console:process_line(session, line)
session.print("Message: "..tostring(message));
return;
end
-
+
session.print("OK: "..tostring(message));
end
@@ -155,6 +155,14 @@ function console_listener.onincoming(conn, data)
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 +225,11 @@ function commands.help(session, data)
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 +282,8 @@ end
-- 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,8 +325,7 @@ local function human(kb)
end
function def_env.server:memory()
- local pposix = require("util.pposix");
- if not pposix.meminfo then
+ if not has_pposix or not pposix.meminfo then
return true, "Lua is using "..collectgarbage("count");
end
local mem, lua_mem = pposix.meminfo(), collectgarbage("count");
@@ -337,10 +348,9 @@ local function get_hosts_set(hosts, module)
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 +358,13 @@ local function get_hosts_set(hosts, module)
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 +380,18 @@ function def_env.module:load(name, hosts, config)
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);
-
+
-- 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 +405,6 @@ function def_env.module:unload(name, hosts)
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 +414,8 @@ function def_env.module:reload(name, hosts)
-- 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 +442,7 @@ function def_env.module:list(hosts)
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 +481,109 @@ function def_env.config:reload()
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 +594,7 @@ function def_env.c2s:show_insecure(match_jid)
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 +605,15 @@ function def_env.c2s:show_secure(match_jid)
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 +625,87 @@ function def_env.c2s:close(match_jid)
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 <stream> 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 <stream> 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(
@@ -690,14 +734,9 @@ 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 +775,18 @@ function def_env.s2s:showcert(domain)
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 +827,38 @@ end
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);
- 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);
+ count = count + 1 ;
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
- 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
+ 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
- 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 +875,19 @@ end
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
@@ -967,6 +978,20 @@ function def_env.muc:room(room_jid)
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 = {};
@@ -1111,29 +1136,25 @@ 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 --git a/plugins/mod_announce.lua b/plugins/mod_announce.lua
index 96976d6f..9327556c 100644
--- a/plugins/mod_announce.lua
+++ b/plugins/mod_announce.lua
@@ -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 @@ end
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 @@ function announce_handler(self, data, state)
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 --git a/plugins/mod_auth_internal_hashed.lua b/plugins/mod_auth_internal_hashed.lua
index 2b041e43..78abe50d 100644
--- a/plugins/mod_auth_internal_hashed.lua
+++ b/plugins/mod_auth_internal_hashed.lua
@@ -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 @@ function provider.test_password(username, password)
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 @@ function provider.test_password(username, 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.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 @@ end
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,10 @@ function provider.create_user(username, password)
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)
@@ -134,7 +121,7 @@ function provider.get_sasl_handler()
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;
stored_key = stored_key and from_hex(stored_key);
server_key = server_key and from_hex(server_key);
@@ -143,6 +130,6 @@ function provider.get_sasl_handler()
};
return new_sasl(host, testpass_authentication_profile);
end
-
+
module:provides("auth", provider);
diff --git a/plugins/mod_auth_internal_plain.lua b/plugins/mod_auth_internal_plain.lua
index d226fdbe..db528432 100644
--- a/plugins/mod_auth_internal_plain.lua
+++ b/plugins/mod_auth_internal_plain.lua
@@ -16,10 +16,9 @@ local accounts = module:open_store("accounts");
-- 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 @@ function provider.test_password(username, password)
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 @@ end
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;
@@ -76,6 +76,6 @@ function provider.get_sasl_handler()
};
return new_sasl(host, getpass_authentication_profile);
end
-
+
module:provides("auth", provider);
diff --git a/plugins/mod_blocklist.lua b/plugins/mod_blocklist.lua
new file mode 100644
index 00000000..8efbfd96
--- /dev/null
+++ b/plugins/mod_blocklist.lua
@@ -0,0 +1,325 @@
+-- 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;
+
+-- 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 migrated_data = { [false] = "not empty" };
+ local legacy_data = module:open_store("privacy"):get(username);
+ if legacy_data and legacy_data.lists and legacy_data.default then
+ legacy_data = legacy_data.lists[legacy_data.default];
+ legacy_data = legacy_data and legacy_data.items;
+ else
+ return migrated_data;
+ end
+ if legacy_data then
+ module:log("info", "Migrating blocklist from mod_privacy storage for user '%s'", username);
+ local item, jid;
+ for i = 1, #legacy_data do
+ item = legacy_data[i];
+ if item.type == "jid" and item.action == "deny" then
+ 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
+ end
+ set_blocklist(username, migrated_data);
+ return migrated_data;
+end
+
+local function get_blocklist(username)
+ local blocklist = cache[username];
+ if not blocklist then
+ blocklist = cache2:get(username);
+ end
+ 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
+ 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 = 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);
+
+-- 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
+ -- <block/> element does not contain at least one <item/> child element
+ origin.send(st_error_reply(stanza, "modify", "bad-request"));
+ return true;
+ end
+
+ local blocklist = get_blocklist(username);
+
+ local new_blocklist = {};
+
+ if is_blocking or next(new) then
+ for jid in pairs(blocklist) do
+ new_blocklist[jid] = true;
+ end
+ for jid in pairs(new) do
+ new_blocklist[jid] = is_blocking;
+ end
+ -- else empty the blocklist
+ end
+ new_blocklist[false] = "not empty"; -- In order to avoid doing the migration thing twice
+
+ 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);
+module:hook("iq-set/self/urn:xmpp:blocking:unblock", edit_blocklist);
+
+-- 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 _, condition, text = event.stanza:get_error();
+ (event.origin.log or module._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 type = event.stanza.attr.type;
+ if type == "chat" or not type or type == "normal" then
+ 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);
+
+-- Note: MUST bounce these, but we don't because this would produce
+-- lots of error replies due to server-generated presence.
+-- FIXME some day, likely needing changes to mod_presence
+module:hook("pre-presence/bare", drop_outgoing, prio_out);
+module:hook("pre-presence/full", drop_outgoing, prio_out);
+module:hook("pre-presence/host", drop_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 --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
index d9c8defd..fd33226c 100644
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -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,6 +22,7 @@ local initialize_filters = require "util.filters".initialize;
local math_min = math.min;
local xpcall, tostring, type = xpcall, tostring, type;
local traceback = debug.traceback;
+local runner = require"util.async".runner;
local xmlns_streams = "http://etherx.jabber.org/streams";
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
@@ -37,24 +38,10 @@ local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2);
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;
@@ -79,7 +66,7 @@ local os_time = os.time;
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 +79,7 @@ function on_destroy_request(request)
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 +89,20 @@ function on_destroy_request(request)
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 +115,24 @@ function handle_POST(event)
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);
-
+ if not stream:feed(body) then
+ module:log("warn", "Error parsing BOSH payload")
+ return 400;
+ 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".
@@ -141,9 +147,6 @@ function handle_POST(event)
local r = session.requests;
log("debug", "Session %s has %d out of %d requests open", context.sid, #r, session.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
-- We are holding too many requests, send what's in the buffer,
log("debug", "We are holding too many requests, so...");
@@ -162,7 +165,7 @@ function handle_POST(event)
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 +173,7 @@ function handle_POST(event)
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();
@@ -179,6 +182,8 @@ function handle_POST(event)
return true; -- Inform http server we shall reply later
end
end
+ module:log("warn", "Unable to associate request with a session (incomplete request?)");
+ return 400;
end
@@ -188,10 +193,10 @@ local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" };
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,14 +222,15 @@ local function bosh_close_stream(session, reason)
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
+local runner_callbacks = { };
+
-- Handle the <body> tag in the request payload.
function stream_callbacks.streamopened(context, attr)
local request, response = context.request, context.response;
@@ -233,7 +239,7 @@ function stream_callbacks.streamopened(context, attr)
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
-- Unknown host
@@ -243,7 +249,7 @@ function stream_callbacks.streamopened(context, attr)
response:send(tostring(close_reply));
return;
end
-
+
-- New session
sid = new_uuid();
local session = {
@@ -256,12 +262,18 @@ function stream_callbacks.streamopened(context, attr)
ip = get_ip_from_request(request);
};
sessions[sid] = session;
-
+
+ session.thread = runner(function (stanza)
+ session:dispatch_stanza(stanza);
+ end, runner_callbacks, 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;
@@ -279,7 +291,6 @@ function stream_callbacks.streamopened(context, attr)
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;
@@ -306,17 +317,16 @@ function stream_callbacks.streamopened(context, attr)
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
-
+
if session.rid then
local rid = tonumber(attr.rid);
local diff = rid - session.rid;
@@ -333,7 +343,7 @@ function stream_callbacks.streamopened(context, attr)
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
@@ -348,13 +358,17 @@ function stream_callbacks.streamopened(context, attr)
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
local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(tostring(err), 2)); end
+
+function runner_callbacks:error(err)
+ return handleerr(err);
+end
+
function stream_callbacks.handlestanza(context, stanza)
if context.ignore then return; end
log("debug", "BOSH stanza received: %s\n", stanza:top_tag());
@@ -364,14 +378,12 @@ function stream_callbacks.handlestanza(context, stanza)
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);
- end
+ session.thread:run(stanza);
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
session.bosh_processing = false;
if #session.send_buffer > 0 then
@@ -384,12 +396,11 @@ function stream_callbacks.error(context, error)
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();
return;
end
-
+
local session = sessions[context.sid];
if error == "stream-error" then -- Remote stream error, we close normally
session:close();
@@ -398,7 +409,7 @@ function stream_callbacks.error(context, error)
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
@@ -413,7 +424,7 @@ function on_timer()
end
end
end
-
+
now = now - 3;
local n_dead_sessions = 0;
for session, close_after in pairs(inactive_sessions) do
diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua
index 8524c37e..c638cd03 100644
--- a/plugins/mod_c2s.lua
+++ b/plugins/mod_c2s.lua
@@ -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,9 +15,10 @@ local sessionmanager = require "core.sessionmanager";
local st = require "util.stanza";
local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
local uuid_generate = require "util.uuid".generate;
+local runner = require "util.async".runner;
local xpcall, tostring, type = xpcall, tostring, type;
-local traceback = debug.traceback;
+local t_insert, t_remove = table.insert, table.remove;
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
@@ -27,16 +28,18 @@ local c2s_timeout = module:get_option_number("c2s_timeout");
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", "counter");
+
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 = {};
+local runner_callbacks = {};
--- 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;
@@ -50,15 +53,13 @@ function stream_callbacks.streamopened(session, attr)
session.streamid = uuid_generate();
(session.log or session)("debug", "Client sent opening <stream:stream> 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("<?xml version='1.0'?>"..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 <stream:stream> to client");
session.notopen = nil;
@@ -67,21 +68,27 @@ function stream_callbacks.streamopened(session, attr)
-- 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 features to offer");
+ session:close{ condition = "undefined-condition", text = "No features to proceed with" };
+ end
end
function stream_callbacks.streamclosed(session)
@@ -116,12 +123,9 @@ function stream_callbacks.error(session, error, data)
end
end
-local function handleerr(err) log("error", "Traceback[c2s]: %s", traceback(tostring(err), 2)); end
function stream_callbacks.handlestanza(session, stanza)
stanza = session.filter("stanzas/in", stanza);
- if stanza then
- return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
- end
+ session.thread:run(stanza);
end
--- Session methods
@@ -129,8 +133,7 @@ local function session_close(session, reason)
local log = session.log or log;
if session.conn then
if session.notopen then
- session.send("<?xml version='1.0'?>");
- 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");
@@ -153,12 +156,12 @@ local function session_close(session, reason)
log("debug", "Disconnecting client, <stream:error> is: %s", stream_error);
session.send(stream_error);
end
-
+
session.send("</stream:stream>");
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");
+ session.log("debug", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason or "session closed");
-- Authenticated incoming stream may still be sending us stanzas, so wait for </stream:stream> from remote
local conn = session.conn;
@@ -188,16 +191,30 @@ module:hook_global("user-deleted", function(event)
end
end, 200);
+function runner_callbacks:ready()
+ self.data.conn:resume();
+end
+
+function runner_callbacks:waiting()
+ self.data.conn:pause();
+end
+
+function runner_callbacks:error(err)
+ (self.data.log or log)("error", "Traceback[c2s]: %s", err);
+end
+
--- Port listener
function listener.onconnect(conn)
+ measure_connections(1);
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();
@@ -207,34 +224,41 @@ function listener.onconnect(conn)
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
-
+
+ session.thread = runner(function (stanza)
+ core_process_stanza(session, stanza);
+ end, runner_callbacks, session);
+
local filter = session.filter;
function session.data(data)
+ -- Parse the data, which will store stanzas in session.pending_stanzas
+ if data then
data = filter("bytes/in", data);
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");
+ 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
@@ -254,6 +278,7 @@ function listener.onincoming(conn, data)
end
function listener.ondisconnect(conn, err)
+ measure_connections(-1);
local session = sessions[conn];
if session then
(session.log or log)("info", "Client disconnected: %s", err or "connection closed");
@@ -262,14 +287,27 @@ function listener.ondisconnect(conn, err)
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)
+ return event.session.send(' ');
+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 --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua
new file mode 100644
index 00000000..9ef14713
--- /dev/null
+++ b/plugins/mod_carbons.lua
@@ -0,0 +1,110 @@
+-- XEP-0280: Message Carbons implementation for Prosody
+-- Copyright (C) 2011 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 orig_to = stanza.attr.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 = jid_bare(orig_from);
+ local target_session = origin;
+ local top_priority = false;
+ local user_sessions = bare_sessions[bare_jid];
+
+ -- Stanza about to be delivered to a local client
+ if not c2s then
+ bare_jid = jid_bare(orig_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 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);
+ 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, 1);
+module:hook("pre-message/bare", c2s_message_handler, 1);
+module:hook("pre-message/full", c2s_message_handler, 1);
+-- Stanzas to local clients
+module:hook("message/bare", message_handler, 1);
+module:hook("message/full", message_handler, 1);
+
+module:add_feature(xmlns_carbons);
diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua
index 11abab79..a5136f6c 100644
--- a/plugins/mod_component.lua
+++ b/plugins/mod_component.lua
@@ -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.
--
@@ -36,11 +36,13 @@ function module.add_host(module)
local env = module.environment;
env.connected = false;
+ env.session = false;
local send;
local function on_destroy(session, err)
env.connected = false;
+ env.session = false;
send = nil;
session.on_destroy = nil;
end
@@ -73,12 +75,18 @@ function module.add_host(module)
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);
@@ -178,9 +186,7 @@ function stream_callbacks.streamopened(session, attr)
session.streamid = uuid_gen();
session.notopen = nil;
-- Return stream header
- session.send("<?xml version='1.0'?>");
- 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)
diff --git a/plugins/mod_compression.lua b/plugins/mod_compression.lua
index 1ec4c85a..d49e3880 100644
--- a/plugins/mod_compression.lua
+++ b/plugins/mod_compression.lua
@@ -1,6 +1,6 @@
-- Prosody IM
-- Copyright (C) 2009-2012 Tobias Markmann
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -26,16 +26,14 @@ 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
+ if not origin.compressed and origin.type == "c2s" then
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
+ if not origin.compressed and origin.type == "s2sin" then
features:add_child(compression_stream_feature);
end
end);
@@ -43,13 +41,13 @@ 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
+ if not session.compressed and session.type == "s2sout" then
-- does remote server support compression?
- local comp_st = stanza:child_with_name("compression");
+ local comp_st = stanza:get_child("compression", xmlns_compression_feature);
if comp_st then
-- do we support the mechanism
- for a in comp_st:children() do
- local algorithm = a[1]
+ for a in comp_st:childtags("method") do
+ local algorithm = a:get_text();
if algorithm == "zlib" then
session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib"))
session.log("debug", "Enabled compression using zlib.")
@@ -103,7 +101,7 @@ local function setup_compression(session, deflate_stream)
return;
end
return compressed;
- end);
+ end);
end
-- setup decompression for a stream
@@ -131,13 +129,13 @@ module:hook("stanza/http://jabber.org/protocol/compress:compressed", function(ev
-- 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();
@@ -164,29 +162,28 @@ module:hook("stanza/http://jabber.org/protocol/compress:compress", function(even
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 "");
+ local method = stanza:get_child_text("method");
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));
diff --git a/plugins/mod_debug_sql.lua b/plugins/mod_debug_sql.lua
new file mode 100644
index 00000000..7bbbbd88
--- /dev/null
+++ b/plugins/mod_debug_sql.lua
@@ -0,0 +1,25 @@
+-- Enables SQL query logging
+--
+-- luacheck: ignore 213/uri
+
+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 --git a/plugins/mod_dialback.lua b/plugins/mod_dialback.lua
index dc3c3f10..f0fe949a 100644
--- a/plugins/mod_dialback.lua
+++ b/plugins/mod_dialback.lua
@@ -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,33 @@ local st = require "util.stanza";
local sha256_hash = require "util.hashes".sha256;
local sha256_hmac = require "util.hashes".hmac_sha256;
local nameprep = require "util.encodings".stringprep.nameprep;
+local check_cert_status = module:depends"s2s".check_cert_status;
+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);
+
+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 +49,7 @@ end
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 +76,36 @@ end);
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 +126,7 @@ end);
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 "")];
@@ -132,10 +155,10 @@ end);
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,14 +177,6 @@ module:hook("stanza/jabber:server:dialback:result", function(event)
end
end);
-module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza)
- if origin.external_auth == "failed" then
- module:log("debug", "SASL EXTERNAL failed, falling back to dialback");
- initiate_dialback(origin);
- return true;
- end
-end, 100);
-
module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
if not origin.external_auth or origin.external_auth == "failed" then
module:log("debug", "Initiating dialback...");
diff --git a/plugins/mod_disco.lua b/plugins/mod_disco.lua
index 72c9a34c..61749580 100644
--- a/plugins/mod_disco.lua
+++ b/plugins/mod_disco.lua
@@ -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.
--
@@ -32,7 +32,9 @@ do -- validate disco_items
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 @@ module:hook("iq/host/http://jabber.org/protocol/disco#info:query", function(even
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 event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+ local ret = module:fire_event("host-disco-info-node", event);
+ if ret ~= nil then return ret; end
+ if 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.node = node;
local reply = st.reply(stanza):add_child(reply_query);
@@ -108,9 +121,21 @@ module:hook("iq/host/http://jabber.org/protocol/disco#items:query", function(eve
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 event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+ local ret = module:fire_event("host-disco-items-node", event);
+ if ret ~= nil then return ret; end
+ if 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
@@ -133,12 +158,24 @@ module:hook("iq/bare/http://jabber.org/protocol/disco#info:query", function(even
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 event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+ local ret = module:fire_event("account-disco-info-node", event);
+ if ret ~= nil then return ret; end
+ if 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
- 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
@@ -147,12 +184,24 @@ module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", function(eve
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 event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+ local ret = module:fire_event("account-disco-items-node", event);
+ if ret ~= nil then return ret; end
+ if 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 --git a/plugins/mod_groups.lua b/plugins/mod_groups.lua
index f7f632c2..d696d453 100644
--- a/plugins/mod_groups.lua
+++ b/plugins/mod_groups.lua
@@ -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 @@ function inject_roster_contacts(username, host, roster)
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 @@ function inject_roster_contacts(username, host, roster)
import_jids_to_roster(group_name);
end
end
-
+
if roster[false] then
roster[false].version = true;
end
@@ -80,12 +80,12 @@ function remove_virtual_contacts(username, host, datastore, data)
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 --git a/plugins/mod_http.lua b/plugins/mod_http.lua
index 9b574bc8..086887fb 100644
--- a/plugins/mod_http.lua
+++ b/plugins/mod_http.lua
@@ -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.
--
@@ -45,6 +45,11 @@ local function get_base_path(host_module, app_name, default_app_path)
: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
@@ -101,6 +106,9 @@ function module.add_host(module)
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;
@@ -119,7 +127,7 @@ function module.add_host(module)
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;
@@ -127,7 +135,7 @@ function module.add_host(module)
module:unhook_object_event(server, event, handler);
end
end
-
+
module:handle_items("http-provider", http_app_added, http_app_removed);
server.add_host(host);
@@ -150,7 +158,13 @@ module:provides("net", {
listener = server.listener;
default_port = 5281;
encryption = "ssl";
- ssl_config = { verify = "none" };
+ ssl_config = {
+ verify = {
+ peer = false,
+ client_once = false,
+ "none",
+ }
+ };
multiplex = {
pattern = "^[A-Z]";
};
diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua
index 2568ea80..0c37e104 100644
--- a/plugins/mod_http_errors.lua
+++ b/plugins/mod_http_errors.lua
@@ -53,7 +53,7 @@ local entities = {
local function tohtml(plain)
return (plain:gsub("[<>&'\"\n]", entities));
-
+
end
local function get_page(code, extra)
diff --git a/plugins/mod_http_files.lua b/plugins/mod_http_files.lua
index 6275cca5..0c542714 100644
--- a/plugins/mod_http_files.lua
+++ b/plugins/mod_http_files.lua
@@ -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 --git a/plugins/mod_iq.lua b/plugins/mod_iq.lua
index e7901ab4..c6d62e85 100644
--- a/plugins/mod_iq.lua
+++ b/plugins/mod_iq.lua
@@ -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 --git a/plugins/mod_lastactivity.lua b/plugins/mod_lastactivity.lua
index 11053709..2dd61699 100644
--- a/plugins/mod_lastactivity.lua
+++ b/plugins/mod_lastactivity.lua
@@ -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.
--
@@ -19,8 +19,7 @@ module:hook("pre-presence/bare", function(event)
local stanza = event.stanza;
if not(stanza.attr.to) and stanza.attr.type == "unavailable" then
local t = os.time();
- local s = stanza:child_with_name("status");
- s = s and #s.tags == 0 and s[1] or "";
+ local s = stanza:get_child_text("status");
map[event.origin.username] = {s = s, t = t};
end
end, 10);
diff --git a/plugins/mod_legacyauth.lua b/plugins/mod_legacyauth.lua
index 5fb66441..5edc26bb 100644
--- a/plugins/mod_legacyauth.lua
+++ b/plugins/mod_legacyauth.lua
@@ -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.
--
@@ -11,8 +11,8 @@
local st = require "util.stanza";
local t_concat = table.concat;
-local secure_auth_only = module:get_option("c2s_require_encryption")
- or module:get_option("require_encryption")
+local secure_auth_only = module:get_option("c2s_require_encryption",
+ module:get_option("require_encryption"))
or not(module:get_option("allow_unencrypted_plain_auth"));
local sessionmanager = require "core.sessionmanager";
@@ -43,10 +43,11 @@ module:hook("stanza/iq/jabber:iq:auth:query", function(event)
session.send(st.error_reply(stanza, "modify", "not-acceptable", "Encryption (SSL or TLS) is required to connect to this server"));
return true;
end
-
- local username = stanza.tags[1]:child_with_name("username");
- local password = stanza.tags[1]:child_with_name("password");
- local resource = stanza.tags[1]:child_with_name("resource");
+
+ local query = stanza.tags[1];
+ local username = query:get_child("username");
+ local password = query:get_child("password");
+ local resource = query:get_child("resource");
if not (username and password and resource) then
local reply = st.reply(stanza);
session.send(reply:query("jabber:iq:auth")
diff --git a/plugins/mod_message.lua b/plugins/mod_message.lua
index e85da613..fc337db0 100644
--- a/plugins/mod_message.lua
+++ b/plugins/mod_message.lua
@@ -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,7 +17,7 @@ local user_exists = require "core.usermanager".user_exists;
local function process_to_bare(bare, origin, stanza)
local user = bare_sessions[bare];
-
+
local t = stanza.attr.type;
if t == "error" then
-- discard
@@ -66,7 +66,7 @@ end
module:hook("message/full", function(data)
-- message to full JID recieved
local origin, stanza = data.origin, data.stanza;
-
+
local session = full_sessions[stanza.attr.to];
if session and session.send(stanza) then
return true;
diff --git a/plugins/mod_motd.lua b/plugins/mod_motd.lua
index 3dd6b816..574a9cf4 100644
--- a/plugins/mod_motd.lua
+++ b/plugins/mod_motd.lua
@@ -2,7 +2,7 @@
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2010 Jeff Mitchell
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
diff --git a/plugins/mod_offline.lua b/plugins/mod_offline.lua
index 1ac62f94..08ab8490 100644
--- a/plugins/mod_offline.lua
+++ b/plugins/mod_offline.lua
@@ -1,7 +1,7 @@
-- Prosody IM
-- Copyright (C) 2008-2009 Matthew Wild
-- Copyright (C) 2008-2009 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -24,13 +24,13 @@ module:hook("message/offline/handle", function(event)
else
node, host = origin.username, origin.host;
end
-
+
stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();
local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));
stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
-
+
return result;
-end);
+end, -1);
module:hook("message/offline/broadcast", function(event)
local origin = event.origin;
@@ -48,4 +48,4 @@ module:hook("message/offline/broadcast", function(event)
end
datamanager.list_store(node, host, "offline", nil);
return true;
-end);
+end, -1);
diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua
index 22790869..896f3e78 100644
--- a/plugins/mod_pep.lua
+++ b/plugins/mod_pep.lua
@@ -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.
--
@@ -46,7 +46,8 @@ local function subscription_presence(user_bare, recipient)
return is_contact_subscribed(username, host, recipient_bare);
end
-local function publish(session, node, id, item)
+module:hook("pep-publish-item", function (event)
+ local session, node, id, item = event.session, event.node, event.id, event.item;
item.attr.xmlns = nil;
local disable = #item.tags ~= 1 or #item.tags[1] == 0;
if #item.tags == 0 then item.name = "retract"; end
@@ -77,7 +78,8 @@ local function publish(session, node, id, item)
core_post_stanza(session, stanza);
end
end
-end
+end);
+
local function publish_all(user, recipient, session)
local d = data[user];
local notify = recipients[user] and recipients[user][recipient];
@@ -180,7 +182,9 @@ module:hook("iq/bare/http://jabber.org/protocol/pubsub:pubsub", function(event)
local id = payload.attr.id or "1";
payload.attr.id = id;
session.send(st.reply(stanza));
- publish(session, node, id, st.clone(payload));
+ module:fire_event("pep-publish-item", {
+ node = node, actor = session.jid, id = id, session = session, item = st.clone(payload);
+ });
return true;
end
end
@@ -271,19 +275,19 @@ module:hook("iq-result/bare/disco", function(event)
end);
module:hook("account-disco-info", function(event)
- local stanza = event.stanza;
- stanza:tag('identity', {category='pubsub', type='pep'}):up();
- stanza:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up();
+ local reply = event.reply;
+ reply:tag('identity', {category='pubsub', type='pep'}):up();
+ reply:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up();
end);
module:hook("account-disco-items", function(event)
- local stanza = event.stanza;
- local bare = stanza.attr.to;
+ local reply = event.reply;
+ local bare = reply.attr.to;
local user_data = data[bare];
if user_data then
for node, _ in pairs(user_data) do
- stanza:tag('item', {jid=bare, node=node}):up(); -- TODO we need to handle queries to these nodes
+ reply:tag('item', {jid=bare, node=node}):up(); -- TODO we need to handle queries to these nodes
end
end
end);
diff --git a/plugins/mod_ping.lua b/plugins/mod_ping.lua
index 0bfcac66..1a503409 100644
--- a/plugins/mod_ping.lua
+++ b/plugins/mod_ping.lua
@@ -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.
--
@@ -11,14 +11,11 @@ local st = require "util.stanza";
module:add_feature("urn:xmpp:ping");
local function ping_handler(event)
- if event.stanza.attr.type == "get" then
- event.origin.send(st.reply(event.stanza));
- return true;
- end
+ return event.origin.send(st.reply(event.stanza));
end
-module:hook("iq/bare/urn:xmpp:ping:ping", ping_handler);
-module:hook("iq/host/urn:xmpp:ping:ping", ping_handler);
+module:hook("iq-get/bare/urn:xmpp:ping:ping", ping_handler);
+module:hook("iq-get/host/urn:xmpp:ping:ping", ping_handler);
-- Ad-hoc command
diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua
index b289fa44..7e6d8799 100644
--- a/plugins/mod_posix.lua
+++ b/plugins/mod_posix.lua
@@ -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,8 +14,8 @@ if pposix._VERSION ~= want_pposix_version then
module:log("warn", "Unknown version (%s) of binary pposix module, expected %s. Perhaps you need to recompile?", tostring(pposix._VERSION), want_pposix_version);
end
-local signal = select(2, pcall(require, "util.signal"));
-if type(signal) == "string" then
+local have_signal, signal = pcall(require, "util.signal");
+if not have_signal then
module:log("warn", "Couldn't load signal library, won't respond to SIGTERM");
end
@@ -31,27 +31,27 @@ pposix.umask(umask);
-- Allow switching away from root, some people like strange ports.
module:hook("server-started", function ()
- local uid = module:get_option("setuid");
- local gid = module:get_option("setgid");
- if gid then
- local success, msg = pposix.setgid(gid);
- if success then
- module:log("debug", "Changed group to %s successfully.", gid);
- else
- module:log("error", "Failed to change group to %s. Error: %s", gid, msg);
- prosody.shutdown("Failed to change group to %s", gid);
- end
+ local uid = module:get_option("setuid");
+ local gid = module:get_option("setgid");
+ if gid then
+ local success, msg = pposix.setgid(gid);
+ if success then
+ module:log("debug", "Changed group to %s successfully.", gid);
+ else
+ module:log("error", "Failed to change group to %s. Error: %s", gid, msg);
+ prosody.shutdown("Failed to change group to %s", gid);
end
- if uid then
- local success, msg = pposix.setuid(uid);
- if success then
- module:log("debug", "Changed user to %s successfully.", uid);
- else
- module:log("error", "Failed to change user to %s. Error: %s", uid, msg);
- prosody.shutdown("Failed to change user to %s", uid);
- end
+ end
+ if uid then
+ local success, msg = pposix.setuid(uid);
+ if success then
+ module:log("debug", "Changed user to %s successfully.", uid);
+ else
+ module:log("error", "Failed to change user to %s. Error: %s", uid, msg);
+ prosody.shutdown("Failed to change user to %s", uid);
end
- end);
+ end
+end);
-- Don't even think about it!
if not prosody.start_time then -- server-starting
@@ -128,15 +128,7 @@ function syslog_sink_maker(config)
end
require "core.loggingmanager".register_sink_type("syslog", syslog_sink_maker);
-local daemonize = module:get_option("daemonize");
-if daemonize == nil then
- local no_daemonize = module:get_option("no_daemonize"); --COMPAT w/ 0.5
- daemonize = not no_daemonize;
- if no_daemonize ~= nil then
- module:log("warn", "The 'no_daemonize' option is now replaced by 'daemonize'");
- module:log("warn", "Update your config from 'no_daemonize = %s' to 'daemonize = %s'", tostring(no_daemonize), tostring(daemonize));
- end
-end
+local daemonize = module:get_option("daemonize", prosody.installed);
local function remove_log_sinks()
local lm = require "core.loggingmanager";
@@ -170,7 +162,7 @@ end
module:hook("server-stopped", remove_pidfile);
-- Set signal handlers
-if signal.signal then
+if have_signal then
signal.signal("SIGTERM", function ()
module:log("warn", "Received SIGTERM");
prosody.unlock_globals();
@@ -183,7 +175,7 @@ if signal.signal then
prosody.reload_config();
prosody.reopen_logfiles();
end);
-
+
signal.signal("SIGINT", function ()
module:log("info", "Received SIGINT");
prosody.unlock_globals();
diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua
index 8dac2d35..ab57a158 100644
--- a/plugins/mod_presence.lua
+++ b/plugins/mod_presence.lua
@@ -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.
--
@@ -55,14 +55,14 @@ local ignore_presence_priority = module:get_option("ignore_presence_priority");
function handle_normal_presence(origin, stanza)
if ignore_presence_priority then
- local priority = stanza:child_with_name("priority");
+ local priority = stanza:get_child("priority");
if priority and priority[1] ~= "0" then
for i=#priority.tags,1,-1 do priority.tags[i] = nil; end
for i=#priority,1,-1 do priority[i] = nil; end
priority[1] = "0";
end
end
- local priority = stanza:child_with_name("priority");
+ local priority = stanza:get_child("priority");
if priority and #priority > 0 then
priority = t_concat(priority);
if s_find(priority, "^[+-]?[0-9]+$") then
@@ -90,6 +90,7 @@ function handle_normal_presence(origin, stanza)
end
end
if stanza.attr.type == nil and not origin.presence then -- initial presence
+ module:fire_event("presence/initial", { origin = origin, stanza = stanza } );
origin.presence = stanza; -- FIXME repeated later
local probe = st.presence({from = origin.full_jid, type = "probe"});
for jid, item in pairs(roster) do -- probe all contacts we are subscribed to
@@ -105,10 +106,8 @@ function handle_normal_presence(origin, stanza)
res.presence.attr.to = nil;
end
end
- if roster.pending then -- resend incoming subscription requests
- for jid in pairs(roster.pending) do
- origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original?
- end
+ for jid in pairs(roster[false].pending) do -- resend incoming subscription requests
+ origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original?
end
local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host});
for jid, item in pairs(roster) do -- resend outgoing subscription requests
@@ -227,7 +226,7 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b
local st_from, st_to = stanza.attr.from, stanza.attr.to;
stanza.attr.from, stanza.attr.to = from_bare, to_bare;
log("debug", "inbound presence %s from %s for %s", stanza.attr.type, from_bare, to_bare);
-
+
if stanza.attr.type == "probe" then
local result, err = rostermanager.is_contact_subscribed(node, host, from_bare);
if result then
@@ -312,7 +311,7 @@ module:hook("presence/bare", function(data)
if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes sent to bare JID
return handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to));
end
-
+
local user = bare_sessions[to];
if user then
for _, session in pairs(user.sessions) do
@@ -347,7 +346,7 @@ end);
module:hook("presence/host", function(data)
-- inbound presence to the host
local stanza = data.stanza;
-
+
local from_bare = jid_bare(stanza.attr.from);
local t = stanza.attr.type;
if t == "probe" then
diff --git a/plugins/mod_privacy.lua b/plugins/mod_privacy.lua
index 49c9427f..b749b7c7 100644
--- a/plugins/mod_privacy.lua
+++ b/plugins/mod_privacy.lua
@@ -2,447 +2,12 @@
-- Copyright (C) 2009-2010 Matthew Wild
-- Copyright (C) 2009-2010 Waqas Hussain
-- Copyright (C) 2009 Thilo Cestonaro
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-module:add_feature("jabber:iq:privacy");
-
-local st = require "util.stanza";
-local bare_sessions, full_sessions = prosody.bare_sessions, prosody.full_sessions;
-local util_Jid = require "util.jid";
-local jid_bare = util_Jid.bare;
-local jid_split, jid_join = util_Jid.split, util_Jid.join;
-local load_roster = require "core.rostermanager".load_roster;
-local to_number = tonumber;
-
-local privacy_storage = module:open_store();
-
-function isListUsed(origin, name, privacy_lists)
- local user = bare_sessions[origin.username.."@"..origin.host];
- if user then
- for resource, session in pairs(user.sessions) do
- if resource ~= origin.resource then
- if session.activePrivacyList == name then
- return true;
- elseif session.activePrivacyList == nil and privacy_lists.default == name then
- return true;
- end
- end
- end
- end
-end
-
-function isAnotherSessionUsingDefaultList(origin)
- local user = bare_sessions[origin.username.."@"..origin.host];
- if user then
- for resource, session in pairs(user.sessions) do
- if resource ~= origin.resource and session.activePrivacyList == nil then
- return true;
- end
- end
- end
-end
-
-function declineList(privacy_lists, origin, stanza, which)
- if which == "default" then
- if isAnotherSessionUsingDefaultList(origin) then
- return { "cancel", "conflict", "Another session is online and using the default list."};
- end
- privacy_lists.default = nil;
- origin.send(st.reply(stanza));
- elseif which == "active" then
- origin.activePrivacyList = nil;
- origin.send(st.reply(stanza));
- else
- return {"modify", "bad-request", "Neither default nor active list specifed to decline."};
- end
- return true;
-end
-
-function activateList(privacy_lists, origin, stanza, which, name)
- local list = privacy_lists.lists[name];
-
- if which == "default" and list then
- if isAnotherSessionUsingDefaultList(origin) then
- return {"cancel", "conflict", "Another session is online and using the default list."};
- end
- privacy_lists.default = name;
- origin.send(st.reply(stanza));
- elseif which == "active" and list then
- origin.activePrivacyList = name;
- origin.send(st.reply(stanza));
- elseif not list then
- return {"cancel", "item-not-found", "No such list: "..name};
- else
- return {"modify", "bad-request", "No list chosen to be active or default."};
- end
- return true;
-end
-
-function deleteList(privacy_lists, origin, stanza, name)
- local list = privacy_lists.lists[name];
-
- if list then
- if isListUsed(origin, name, privacy_lists) then
- return {"cancel", "conflict", "Another session is online and using the list which should be deleted."};
- end
- if privacy_lists.default == name then
- privacy_lists.default = nil;
- end
- if origin.activePrivacyList == name then
- origin.activePrivacyList = nil;
- end
- privacy_lists.lists[name] = nil;
- origin.send(st.reply(stanza));
- return true;
- end
- return {"modify", "bad-request", "Not existing list specifed to be deleted."};
-end
-
-function createOrReplaceList (privacy_lists, origin, stanza, name, entries)
- local bare_jid = origin.username.."@"..origin.host;
-
- if privacy_lists.lists == nil then
- privacy_lists.lists = {};
- end
-
- local list = {};
- privacy_lists.lists[name] = list;
-
- local orderCheck = {};
- list.name = name;
- list.items = {};
-
- for _,item in ipairs(entries) do
- if to_number(item.attr.order) == nil or to_number(item.attr.order) < 0 or orderCheck[item.attr.order] ~= nil then
- return {"modify", "bad-request", "Order attribute not valid."};
- end
-
- if item.attr.type ~= nil and item.attr.type ~= "jid" and item.attr.type ~= "subscription" and item.attr.type ~= "group" then
- return {"modify", "bad-request", "Type attribute not valid."};
- end
-
- local tmp = {};
- orderCheck[item.attr.order] = true;
-
- tmp["type"] = item.attr.type;
- tmp["value"] = item.attr.value;
- tmp["action"] = item.attr.action;
- tmp["order"] = to_number(item.attr.order);
- tmp["presence-in"] = false;
- tmp["presence-out"] = false;
- tmp["message"] = false;
- tmp["iq"] = false;
-
- if #item.tags > 0 then
- for _,tag in ipairs(item.tags) do
- tmp[tag.name] = true;
- end
- end
-
- if tmp.type == "subscription" then
- if tmp.value ~= "both" and
- tmp.value ~= "to" and
- tmp.value ~= "from" and
- tmp.value ~= "none" then
- return {"cancel", "bad-request", "Subscription value must be both, to, from or none."};
- end
- end
-
- if tmp.action ~= "deny" and tmp.action ~= "allow" then
- return {"cancel", "bad-request", "Action must be either deny or allow."};
- end
- list.items[#list.items + 1] = tmp;
- end
-
- table.sort(list.items, function(a, b) return a.order < b.order; end);
-
- origin.send(st.reply(stanza));
- if bare_sessions[bare_jid] ~= nil then
- local iq = st.iq ( { type = "set", id="push1" } );
- iq:tag ("query", { xmlns = "jabber:iq:privacy" } );
- iq:tag ("list", { name = list.name } ):up();
- iq:up();
- for resource, session in pairs(bare_sessions[bare_jid].sessions) do
- iq.attr.to = bare_jid.."/"..resource
- session.send(iq);
- end
- else
- return {"cancel", "bad-request", "internal error."};
- end
- return true;
-end
-
-function getList(privacy_lists, origin, stanza, name)
- local reply = st.reply(stanza);
- reply:tag("query", {xmlns="jabber:iq:privacy"});
-
- if name == nil then
- if privacy_lists.lists then
- if origin.activePrivacyList then
- reply:tag("active", {name=origin.activePrivacyList}):up();
- end
- if privacy_lists.default then
- reply:tag("default", {name=privacy_lists.default}):up();
- end
- for name,list in pairs(privacy_lists.lists) do
- reply:tag("list", {name=name}):up();
- end
- end
- else
- local list = privacy_lists.lists[name];
- if list then
- reply = reply:tag("list", {name=list.name});
- for _,item in ipairs(list.items) do
- reply:tag("item", {type=item.type, value=item.value, action=item.action, order=item.order});
- if item["message"] then reply:tag("message"):up(); end
- if item["iq"] then reply:tag("iq"):up(); end
- if item["presence-in"] then reply:tag("presence-in"):up(); end
- if item["presence-out"] then reply:tag("presence-out"):up(); end
- reply:up();
- end
- else
- return {"cancel", "item-not-found", "Unknown list specified."};
- end
- end
-
- origin.send(reply);
- return true;
-end
-
-module:hook("iq/bare/jabber:iq:privacy:query", function(data)
- local origin, stanza = data.origin, data.stanza;
-
- if stanza.attr.to == nil then -- only service requests to own bare JID
- local query = stanza.tags[1]; -- the query element
- local valid = false;
- local privacy_lists = privacy_storage:get(origin.username) or { lists = {} };
-
- if privacy_lists.lists[1] then -- Code to migrate from old privacy lists format, remove in 0.8
- module:log("info", "Upgrading format of stored privacy lists for %s@%s", origin.username, origin.host);
- local lists = privacy_lists.lists;
- for idx, list in ipairs(lists) do
- lists[list.name] = list;
- lists[idx] = nil;
- end
- end
-
- if stanza.attr.type == "set" then
- if #query.tags == 1 then -- the <query/> element MUST NOT include more than one child element
- for _,tag in ipairs(query.tags) do
- if tag.name == "active" or tag.name == "default" then
- if tag.attr.name == nil then -- Client declines the use of active / default list
- valid = declineList(privacy_lists, origin, stanza, tag.name);
- else -- Client requests change of active / default list
- valid = activateList(privacy_lists, origin, stanza, tag.name, tag.attr.name);
- end
- elseif tag.name == "list" and tag.attr.name then -- Client adds / edits a privacy list
- if #tag.tags == 0 then -- Client removes a privacy list
- valid = deleteList(privacy_lists, origin, stanza, tag.attr.name);
- else -- Client edits a privacy list
- valid = createOrReplaceList(privacy_lists, origin, stanza, tag.attr.name, tag.tags);
- end
- end
- end
- end
- elseif stanza.attr.type == "get" then
- local name = nil;
- local listsToRetrieve = 0;
- if #query.tags >= 1 then
- for _,tag in ipairs(query.tags) do
- if tag.name == "list" then -- Client requests a privacy list from server
- name = tag.attr.name;
- listsToRetrieve = listsToRetrieve + 1;
- end
- end
- end
- if listsToRetrieve == 0 or listsToRetrieve == 1 then
- valid = getList(privacy_lists, origin, stanza, name);
- end
- end
-
- if valid ~= true then
- valid = valid or { "cancel", "bad-request", "Couldn't understand request" };
- if valid[1] == nil then
- valid[1] = "cancel";
- end
- if valid[2] == nil then
- valid[2] = "bad-request";
- end
- origin.send(st.error_reply(stanza, valid[1], valid[2], valid[3]));
- else
- privacy_storage:set(origin.username, privacy_lists);
- end
- return true;
- end
-end);
-
-function checkIfNeedToBeBlocked(e, session)
- local origin, stanza = e.origin, e.stanza;
- local privacy_lists = privacy_storage:get(session.username) or {};
- local bare_jid = session.username.."@"..session.host;
- local to = stanza.attr.to or bare_jid;
- local from = stanza.attr.from;
-
- local is_to_user = bare_jid == jid_bare(to);
- local is_from_user = bare_jid == jid_bare(from);
-
- --module:log("debug", "stanza: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from));
-
- if privacy_lists.lists == nil or
- not (session.activePrivacyList or privacy_lists.default)
- then
- return; -- Nothing to block, default is Allow all
- end
- if is_from_user and is_to_user then
- --module:log("debug", "Not blocking communications between user's resources");
- return; -- from one of a user's resource to another => HANDS OFF!
- end
-
- local listname = session.activePrivacyList;
- if listname == nil then
- listname = privacy_lists.default; -- no active list selected, use default list
- end
- local list = privacy_lists.lists[listname];
- if not list then -- should never happen
- module:log("warn", "given privacy list not found. name: %s for user %s", listname, bare_jid);
- return;
- end
- for _,item in ipairs(list.items) do
- local apply = false;
- local block = false;
- if (
- (stanza.name == "message" and item.message) or
- (stanza.name == "iq" and item.iq) or
- (stanza.name == "presence" and is_to_user and item["presence-in"]) or
- (stanza.name == "presence" and is_from_user and item["presence-out"]) or
- (item.message == false and item.iq == false and item["presence-in"] == false and item["presence-out"] == false)
- ) then
- apply = true;
- end
- if apply then
- local evilJid = {};
- apply = false;
- if is_to_user then
- --module:log("debug", "evil jid is (from): %s", from);
- evilJid.node, evilJid.host, evilJid.resource = jid_split(from);
- else
- --module:log("debug", "evil jid is (to): %s", to);
- evilJid.node, evilJid.host, evilJid.resource = jid_split(to);
- end
- if item.type == "jid" and
- (evilJid.node and evilJid.host and evilJid.resource and item.value == evilJid.node.."@"..evilJid.host.."/"..evilJid.resource) or
- (evilJid.node and evilJid.host and item.value == evilJid.node.."@"..evilJid.host) or
- (evilJid.host and evilJid.resource and item.value == evilJid.host.."/"..evilJid.resource) or
- (evilJid.host and item.value == evilJid.host) then
- apply = true;
- block = (item.action == "deny");
- elseif item.type == "group" then
- local roster = load_roster(session.username, session.host);
- local roster_entry = roster[jid_join(evilJid.node, evilJid.host)];
- if roster_entry then
- local groups = roster_entry.groups;
- for group in pairs(groups) do
- if group == item.value then
- apply = true;
- block = (item.action == "deny");
- break;
- end
- end
- end
- elseif item.type == "subscription" then -- we need a valid bare evil jid
- local roster = load_roster(session.username, session.host);
- local roster_entry = roster[jid_join(evilJid.node, evilJid.host)];
- if (not(roster_entry) and item.value == "none")
- or (roster_entry and roster_entry.subscription == item.value) then
- apply = true;
- block = (item.action == "deny");
- end
- elseif item.type == nil then
- apply = true;
- block = (item.action == "deny");
- end
- end
- if apply then
- if block then
- -- drop and not bounce groupchat messages, otherwise users will get kicked
- if stanza.attr.type == "groupchat" then
- return true;
- end
- module:log("debug", "stanza blocked: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from));
- if stanza.name == "message" then
- origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
- elseif stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
- origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
- end
- return true; -- stanza blocked !
- else
- --module:log("debug", "stanza explicitly allowed!")
- return;
- end
- end
- end
-end
-
-function preCheckIncoming(e)
- local session;
- if e.stanza.attr.to ~= nil then
- local node, host, resource = jid_split(e.stanza.attr.to);
- if node == nil or host == nil then
- return;
- end
- if resource == nil then
- local prio = 0;
- if bare_sessions[node.."@"..host] ~= nil then
- for resource, session_ in pairs(bare_sessions[node.."@"..host].sessions) do
- if session_.priority ~= nil and session_.priority > prio then
- session = session_;
- prio = session_.priority;
- end
- end
- end
- else
- session = full_sessions[node.."@"..host.."/"..resource];
- end
- if session ~= nil then
- return checkIfNeedToBeBlocked(e, session);
- else
- --module:log("debug", "preCheckIncoming: Couldn't get session for jid: %s@%s/%s", tostring(node), tostring(host), tostring(resource));
- end
- end
-end
-
-function preCheckOutgoing(e)
- local session = e.origin;
- if e.stanza.attr.from == nil then
- e.stanza.attr.from = session.username .. "@" .. session.host;
- if session.resource ~= nil then
- e.stanza.attr.from = e.stanza.attr.from .. "/" .. session.resource;
- end
- end
- if session.username then -- FIXME do properly
- return checkIfNeedToBeBlocked(e, session);
- end
-end
-
-module:hook("pre-message/full", preCheckOutgoing, 500);
-module:hook("pre-message/bare", preCheckOutgoing, 500);
-module:hook("pre-message/host", preCheckOutgoing, 500);
-module:hook("pre-iq/full", preCheckOutgoing, 500);
-module:hook("pre-iq/bare", preCheckOutgoing, 500);
-module:hook("pre-iq/host", preCheckOutgoing, 500);
-module:hook("pre-presence/full", preCheckOutgoing, 500);
-module:hook("pre-presence/bare", preCheckOutgoing, 500);
-module:hook("pre-presence/host", preCheckOutgoing, 500);
-module:hook("message/full", preCheckIncoming, 500);
-module:hook("message/bare", preCheckIncoming, 500);
-module:hook("message/host", preCheckIncoming, 500);
-module:hook("iq/full", preCheckIncoming, 500);
-module:hook("iq/bare", preCheckIncoming, 500);
-module:hook("iq/host", preCheckIncoming, 500);
-module:hook("presence/full", preCheckIncoming, 500);
-module:hook("presence/bare", preCheckIncoming, 500);
-module:hook("presence/host", preCheckIncoming, 500);
+-- COMPAT w/ pre 0.10
+module:log("error", "The mod_privacy plugin has been replaced by mod_blocklist. Please update your config. For more information see https://prosody.im/doc/modules/mod_privacy");
+module:depends("blocklist");
diff --git a/plugins/mod_private.lua b/plugins/mod_private.lua
index 365a997c..c01053d5 100644
--- a/plugins/mod_private.lua
+++ b/plugins/mod_private.lua
@@ -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,38 +15,40 @@ module:add_feature("jabber:iq:private");
module:hook("iq/self/jabber:iq:private:query", function(event)
local origin, stanza = event.origin, event.stanza;
- local type = stanza.attr.type;
local query = stanza.tags[1];
- if #query.tags == 1 then
- local tag = query.tags[1];
- local key = tag.name..":"..tag.attr.xmlns;
- local data, err = private_storage:get(origin.username);
- if err then
- origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
+ if #query.tags ~= 1 then
+ origin.send(st.error_reply(stanza, "modify", "bad-format"));
+ return true;
+ end
+ local tag = query.tags[1];
+ local key = tag.name..":"..tag.attr.xmlns;
+ local data, err = private_storage:get(origin.username);
+ if err then
+ origin.send(st.error_reply(stanza, "wait", "internal-server-error", err));
+ return true;
+ end
+ if stanza.attr.type == "get" then
+ if data and data[key] then
+ origin.send(st.reply(stanza):query("jabber:iq:private"):add_child(st.deserialize(data[key])));
+ return true;
+ else
+ origin.send(st.reply(stanza):add_child(query));
return true;
end
- if stanza.attr.type == "get" then
- if data and data[key] then
- origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:private"}):add_child(st.deserialize(data[key])));
- else
- origin.send(st.reply(stanza):add_child(stanza.tags[1]));
- end
- else -- set
- if not data then data = {}; end;
- if #tag == 0 then
- data[key] = nil;
- else
- data[key] = st.preserialize(tag);
- end
- -- TODO delete datastore if empty
- if private_storage:set(origin.username, data) then
- origin.send(st.reply(stanza));
- else
- origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
- end
+ else -- type == set
+ if not data then data = {}; end;
+ if #tag == 0 then
+ data[key] = nil;
+ else
+ data[key] = st.preserialize(tag);
end
- else
- origin.send(st.error_reply(stanza, "modify", "bad-format"));
+ -- TODO delete datastore if empty
+ local ok, err = private_storage:set(origin.username, data);
+ if not ok then
+ origin.send(st.error_reply(stanza, "wait", "internal-server-error", err));
+ return true;
+ end
+ origin.send(st.reply(stanza));
+ return true;
end
- return true;
end);
diff --git a/plugins/mod_proxy65.lua b/plugins/mod_proxy65.lua
index 1fa42bd8..cbbfad12 100644
--- a/plugins/mod_proxy65.lua
+++ b/plugins/mod_proxy65.lua
@@ -2,7 +2,7 @@
-- Copyright (C) 2008-2011 Matthew Wild
-- Copyright (C) 2008-2011 Waqas Hussain
-- Copyright (C) 2009 Thilo Cestonaro
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -30,7 +30,7 @@ function listener.onincoming(conn, data)
(conn == initiator and target or initiator):write(data);
return;
end -- FIXME server.link should be doing this?
-
+
if not session.greeting_done then
local nmethods = data:byte(2) or 0;
if data:byte(1) == 0x05 and nmethods > 0 and #data == 2 + nmethods then -- check if we have all the data
@@ -90,10 +90,10 @@ end
function module.add_host(module)
local host, name = module:get_host(), module:get_option_string("name", "SOCKS5 Bytestreams Service");
-
- local proxy_address = module:get_option("proxy65_address", host);
+
+ local proxy_address = module:get_option_string("proxy65_address", host);
local proxy_port = next(portmanager.get_active_services():search("proxy65", nil)[1] or {});
- local proxy_acl = module:get_option("proxy65_acl");
+ local proxy_acl = module:get_option_array("proxy65_acl");
-- COMPAT w/pre-0.9 where proxy65_port was specified in the components section of the config
local legacy_config = module:get_option_number("proxy65_port");
@@ -101,30 +101,13 @@ function module.add_host(module)
module:log("warn", "proxy65_port is deprecated, please put proxy65_ports = { %d } into the global section instead", legacy_config);
end
+ module:depends("disco");
module:add_identity("proxy", "bytestreams", name);
module:add_feature("http://jabber.org/protocol/bytestreams");
-
- module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
- local origin, stanza = event.origin, event.stanza;
- if not stanza.tags[1].attr.node then
- origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
- :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
- :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
- return true;
- end
- end, -1);
-
- module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
- local origin, stanza = event.origin, event.stanza;
- if not stanza.tags[1].attr.node then
- origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
- return true;
- end
- end, -1);
-
+
module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
local origin, stanza = event.origin, event.stanza;
-
+
-- check ACL
while proxy_acl and #proxy_acl > 0 do -- using 'while' instead of 'if' so we can break out of it
local jid = stanza.attr.from;
@@ -137,22 +120,22 @@ function module.add_host(module)
origin.send(st.error_reply(stanza, "auth", "forbidden"));
return true;
end
-
+
local sid = stanza.tags[1].attr.sid;
origin.send(st.reply(stanza):tag("query", {xmlns="http://jabber.org/protocol/bytestreams", sid=sid})
:tag("streamhost", {jid=host, host=proxy_address, port=proxy_port}));
return true;
end);
-
+
module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event)
local origin, stanza = event.origin, event.stanza;
-
+
local query = stanza.tags[1];
local sid = query.attr.sid;
local from = stanza.attr.from;
local to = query:get_child_text("activate");
local prepped_to = jid_prep(to);
-
+
local info = "sid: "..tostring(sid)..", initiator: "..tostring(from)..", target: "..tostring(prepped_to or to);
if prepped_to and sid then
local sha = sha1(sid .. from .. prepped_to, true);
diff --git a/plugins/mod_pubsub.lua b/plugins/mod_pubsub.lua
deleted file mode 100644
index 04f2b615..00000000
--- a/plugins/mod_pubsub.lua
+++ /dev/null
@@ -1,463 +0,0 @@
-local pubsub = require "util.pubsub";
-local st = require "util.stanza";
-local jid_bare = require "util.jid".bare;
-local uuid_generate = require "util.uuid".generate;
-local usermanager = require "core.usermanager";
-
-local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
-local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
-local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
-local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
-
-local autocreate_on_publish = module:get_option_boolean("autocreate_on_publish", false);
-local autocreate_on_subscribe = module:get_option_boolean("autocreate_on_subscribe", false);
-local pubsub_disco_name = module:get_option("name");
-if type(pubsub_disco_name) ~= "string" then pubsub_disco_name = "Prosody PubSub Service"; end
-
-local service;
-
-local handlers = {};
-
-function handle_pubsub_iq(event)
- local origin, stanza = event.origin, event.stanza;
- local pubsub = stanza.tags[1];
- local action = pubsub.tags[1];
- if not action then
- return origin.send(st.error_reply(stanza, "cancel", "bad-request"));
- end
- local handler = handlers[stanza.attr.type.."_"..action.name];
- if handler then
- handler(origin, stanza, action);
- return true;
- end
-end
-
-local pubsub_errors = {
- ["conflict"] = { "cancel", "conflict" };
- ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
- ["jid-required"] = { "modify", "bad-request", nil, "jid-required" };
- ["nodeid-required"] = { "modify", "bad-request", nil, "nodeid-required" };
- ["item-not-found"] = { "cancel", "item-not-found" };
- ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" };
- ["forbidden"] = { "auth", "forbidden" };
-};
-function pubsub_error_reply(stanza, error)
- local e = pubsub_errors[error];
- local reply = st.error_reply(stanza, unpack(e, 1, 3));
- if e[4] then
- reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
- end
- return reply;
-end
-
-function handlers.get_items(origin, stanza, items)
- local node = items.attr.node;
- local item = items:get_child("item");
- local id = item and item.attr.id;
-
- if not node then
- return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
- end
- local ok, results = service:get_items(node, stanza.attr.from, id);
- if not ok then
- return origin.send(pubsub_error_reply(stanza, results));
- end
-
- local data = st.stanza("items", { node = node });
- for _, entry in pairs(results) do
- data:add_child(entry);
- end
- local reply;
- if data then
- reply = st.reply(stanza)
- :tag("pubsub", { xmlns = xmlns_pubsub })
- :add_child(data);
- else
- reply = pubsub_error_reply(stanza, "item-not-found");
- end
- return origin.send(reply);
-end
-
-function handlers.get_subscriptions(origin, stanza, subscriptions)
- local node = subscriptions.attr.node;
- local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from);
- if not ok then
- return origin.send(pubsub_error_reply(stanza, ret));
- end
- local reply = st.reply(stanza)
- :tag("pubsub", { xmlns = xmlns_pubsub })
- :tag("subscriptions");
- for _, sub in ipairs(ret) do
- reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up();
- end
- return origin.send(reply);
-end
-
-function handlers.set_create(origin, stanza, create)
- local node = create.attr.node;
- local ok, ret, reply;
- if node then
- ok, ret = service:create(node, stanza.attr.from);
- if ok then
- reply = st.reply(stanza);
- else
- reply = pubsub_error_reply(stanza, ret);
- end
- else
- repeat
- node = uuid_generate();
- ok, ret = service:create(node, stanza.attr.from);
- until ok or ret ~= "conflict";
- if ok then
- reply = st.reply(stanza)
- :tag("pubsub", { xmlns = xmlns_pubsub })
- :tag("create", { node = node });
- else
- reply = pubsub_error_reply(stanza, ret);
- end
- end
- return origin.send(reply);
-end
-
-function handlers.set_delete(origin, stanza, delete)
- local node = delete.attr.node;
-
- local reply, notifier;
- if not node then
- return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
- end
- local ok, ret = service:delete(node, stanza.attr.from);
- if ok then
- reply = st.reply(stanza);
- else
- reply = pubsub_error_reply(stanza, ret);
- end
- return origin.send(reply);
-end
-
-function handlers.set_subscribe(origin, stanza, subscribe)
- local node, jid = subscribe.attr.node, subscribe.attr.jid;
- if not (node and jid) then
- return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
- end
- --[[
- local options_tag, options = stanza.tags[1]:get_child("options"), nil;
- if options_tag then
- options = options_form:data(options_tag.tags[1]);
- end
- --]]
- local options_tag, options; -- FIXME
- local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options);
- local reply;
- if ok then
- reply = st.reply(stanza)
- :tag("pubsub", { xmlns = xmlns_pubsub })
- :tag("subscription", {
- node = node,
- jid = jid,
- subscription = "subscribed"
- }):up();
- if options_tag then
- reply:add_child(options_tag);
- end
- else
- reply = pubsub_error_reply(stanza, ret);
- end
- origin.send(reply);
-end
-
-function handlers.set_unsubscribe(origin, stanza, unsubscribe)
- local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
- if not (node and jid) then
- return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
- end
- local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
- local reply;
- if ok then
- reply = st.reply(stanza);
- else
- reply = pubsub_error_reply(stanza, ret);
- end
- return origin.send(reply);
-end
-
-function handlers.set_publish(origin, stanza, publish)
- local node = publish.attr.node;
- if not node then
- return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
- end
- local item = publish:get_child("item");
- local id = (item and item.attr.id);
- if not id then
- id = uuid_generate();
- if item then
- item.attr.id = id;
- end
- end
- local ok, ret = service:publish(node, stanza.attr.from, id, item);
- local reply;
- if ok then
- reply = st.reply(stanza)
- :tag("pubsub", { xmlns = xmlns_pubsub })
- :tag("publish", { node = node })
- :tag("item", { id = id });
- else
- reply = pubsub_error_reply(stanza, ret);
- end
- return origin.send(reply);
-end
-
-function handlers.set_retract(origin, stanza, retract)
- local node, notify = retract.attr.node, retract.attr.notify;
- notify = (notify == "1") or (notify == "true");
- local item = retract:get_child("item");
- local id = item and item.attr.id
- if not (node and id) then
- return origin.send(pubsub_error_reply(stanza, node and "item-not-found" or "nodeid-required"));
- end
- local reply, notifier;
- if notify then
- notifier = st.stanza("retract", { id = id });
- end
- local ok, ret = service:retract(node, stanza.attr.from, id, notifier);
- if ok then
- reply = st.reply(stanza);
- else
- reply = pubsub_error_reply(stanza, ret);
- end
- return origin.send(reply);
-end
-
-function handlers.set_purge(origin, stanza, purge)
- local node, notify = purge.attr.node, purge.attr.notify;
- notify = (notify == "1") or (notify == "true");
- local reply;
- if not node then
- return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
- end
- local ok, ret = service:purge(node, stanza.attr.from, notify);
- if ok then
- reply = st.reply(stanza);
- else
- reply = pubsub_error_reply(stanza, ret);
- end
- return origin.send(reply);
-end
-
-function simple_broadcast(kind, node, jids, item)
- if item then
- item = st.clone(item);
- item.attr.xmlns = nil; -- Clear the pubsub namespace
- end
- local message = st.message({ from = module.host, type = "headline" })
- :tag("event", { xmlns = xmlns_pubsub_event })
- :tag(kind, { node = node })
- :add_child(item);
- for jid in pairs(jids) do
- module:log("debug", "Sending notification to %s", jid);
- message.attr.to = jid;
- module:send(message);
- end
-end
-
-module:hook("iq/host/"..xmlns_pubsub..":pubsub", handle_pubsub_iq);
-module:hook("iq/host/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq);
-
-local disco_info;
-
-local feature_map = {
- create = { "create-nodes", "instant-nodes", "item-ids" };
- retract = { "delete-items", "retract-items" };
- purge = { "purge-nodes" };
- publish = { "publish", autocreate_on_publish and "auto-create" };
- delete = { "delete-nodes" };
- get_items = { "retrieve-items" };
- add_subscription = { "subscribe" };
- get_subscriptions = { "retrieve-subscriptions" };
-};
-
-local function add_disco_features_from_service(disco, service)
- for method, features in pairs(feature_map) do
- if service[method] then
- for _, feature in ipairs(features) do
- if feature then
- disco:tag("feature", { var = xmlns_pubsub.."#"..feature }):up();
- end
- end
- end
- end
- for affiliation in pairs(service.config.capabilities) do
- if affiliation ~= "none" and affiliation ~= "owner" then
- disco:tag("feature", { var = xmlns_pubsub.."#"..affiliation.."-affiliation" }):up();
- end
- end
-end
-
-local function build_disco_info(service)
- local disco_info = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" })
- :tag("identity", { category = "pubsub", type = "service", name = pubsub_disco_name }):up()
- :tag("feature", { var = "http://jabber.org/protocol/pubsub" }):up();
- add_disco_features_from_service(disco_info, service);
- return disco_info;
-end
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function (event)
- local origin, stanza = event.origin, event.stanza;
- local node = stanza.tags[1].attr.node;
- if not node then
- return origin.send(st.reply(stanza):add_child(disco_info));
- else
- local ok, ret = service:get_nodes(stanza.attr.from);
- if ok and not ret[node] then
- ok, ret = false, "item-not-found";
- end
- if not ok then
- return origin.send(pubsub_error_reply(stanza, ret));
- end
- local reply = st.reply(stanza)
- :tag("query", { xmlns = "http://jabber.org/protocol/disco#info", node = node })
- :tag("identity", { category = "pubsub", type = "leaf" });
- return origin.send(reply);
- end
-end);
-
-local function handle_disco_items_on_node(event)
- local stanza, origin = event.stanza, event.origin;
- local query = stanza.tags[1];
- local node = query.attr.node;
- local ok, ret = service:get_items(node, stanza.attr.from);
- if not ok then
- return origin.send(pubsub_error_reply(stanza, ret));
- end
-
- local reply = st.reply(stanza)
- :tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = node });
-
- for id, item in pairs(ret) do
- reply:tag("item", { jid = module.host, name = id }):up();
- end
-
- return origin.send(reply);
-end
-
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function (event)
- if event.stanza.tags[1].attr.node then
- return handle_disco_items_on_node(event);
- end
- local ok, ret = service:get_nodes(event.stanza.attr.from);
- if not ok then
- event.origin.send(pubsub_error_reply(event.stanza, ret));
- else
- local reply = st.reply(event.stanza)
- :tag("query", { xmlns = "http://jabber.org/protocol/disco#items" });
- for node, node_obj in pairs(ret) do
- reply:tag("item", { jid = module.host, node = node, name = node_obj.config.name }):up();
- end
- event.origin.send(reply);
- end
- return true;
-end);
-
-local admin_aff = module:get_option_string("default_admin_affiliation", "owner");
-local function get_affiliation(jid)
- local bare_jid = jid_bare(jid);
- if bare_jid == module.host or usermanager.is_admin(bare_jid, module.host) then
- return admin_aff;
- end
-end
-
-function set_service(new_service)
- service = new_service;
- module.environment.service = service;
- disco_info = build_disco_info(service);
-end
-
-function module.save()
- return { service = service };
-end
-
-function module.restore(data)
- set_service(data.service);
-end
-
-set_service(pubsub.new({
- capabilities = {
- none = {
- create = false;
- publish = false;
- retract = false;
- get_nodes = true;
-
- subscribe = true;
- unsubscribe = true;
- get_subscription = true;
- get_subscriptions = true;
- get_items = true;
-
- subscribe_other = false;
- unsubscribe_other = false;
- get_subscription_other = false;
- get_subscriptions_other = false;
-
- be_subscribed = true;
- be_unsubscribed = true;
-
- set_affiliation = false;
- };
- publisher = {
- create = false;
- publish = true;
- retract = true;
- get_nodes = true;
-
- subscribe = true;
- unsubscribe = true;
- get_subscription = true;
- get_subscriptions = true;
- get_items = true;
-
- subscribe_other = false;
- unsubscribe_other = false;
- get_subscription_other = false;
- get_subscriptions_other = false;
-
- be_subscribed = true;
- be_unsubscribed = true;
-
- set_affiliation = false;
- };
- owner = {
- create = true;
- publish = true;
- retract = true;
- delete = true;
- get_nodes = true;
-
- subscribe = true;
- unsubscribe = true;
- get_subscription = true;
- get_subscriptions = true;
- get_items = true;
-
-
- subscribe_other = true;
- unsubscribe_other = true;
- get_subscription_other = true;
- get_subscriptions_other = true;
-
- be_subscribed = true;
- be_unsubscribed = true;
-
- set_affiliation = true;
- };
- };
-
- autocreate_on_publish = autocreate_on_publish;
- autocreate_on_subscribe = autocreate_on_subscribe;
-
- broadcaster = simple_broadcast;
- get_affiliation = get_affiliation;
-
- normalize_jid = jid_bare;
-}));
diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua
new file mode 100644
index 00000000..e93a5238
--- /dev/null
+++ b/plugins/mod_pubsub/mod_pubsub.lua
@@ -0,0 +1,238 @@
+local pubsub = require "util.pubsub";
+local st = require "util.stanza";
+local jid_bare = require "util.jid".bare;
+local usermanager = require "core.usermanager";
+
+local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
+local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
+local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
+
+local autocreate_on_publish = module:get_option_boolean("autocreate_on_publish", false);
+local autocreate_on_subscribe = module:get_option_boolean("autocreate_on_subscribe", false);
+local pubsub_disco_name = module:get_option("name");
+if type(pubsub_disco_name) ~= "string" then pubsub_disco_name = "Prosody PubSub Service"; end
+local expose_publisher = module:get_option_boolean("expose_publisher", false)
+
+local service;
+
+local lib_pubsub = module:require "pubsub";
+local handlers = lib_pubsub.handlers;
+local pubsub_error_reply = lib_pubsub.pubsub_error_reply;
+
+module:depends("disco");
+module:add_identity("pubsub", "service", pubsub_disco_name);
+module:add_feature("http://jabber.org/protocol/pubsub");
+
+function handle_pubsub_iq(event)
+ local origin, stanza = event.origin, event.stanza;
+ local pubsub = stanza.tags[1];
+ local action = pubsub.tags[1];
+ if not action then
+ origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+ return true;
+ end
+ local handler = handlers[stanza.attr.type.."_"..action.name];
+ if handler then
+ handler(origin, stanza, action, service);
+ return true;
+ end
+end
+
+function simple_broadcast(kind, node, jids, item, actor)
+ if item then
+ item = st.clone(item);
+ item.attr.xmlns = nil; -- Clear the pubsub namespace
+ if expose_publisher and actor then
+ item.attr.publisher = actor
+ end
+ end
+ local message = st.message({ from = module.host, type = "headline" })
+ :tag("event", { xmlns = xmlns_pubsub_event })
+ :tag(kind, { node = node })
+ :add_child(item);
+ for jid in pairs(jids) do
+ module:log("debug", "Sending notification to %s", jid);
+ message.attr.to = jid;
+ module:send(message);
+ end
+end
+
+module:hook("iq/host/"..xmlns_pubsub..":pubsub", handle_pubsub_iq);
+module:hook("iq/host/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq);
+
+local feature_map = {
+ create = { "create-nodes", "instant-nodes", "item-ids" };
+ retract = { "delete-items", "retract-items" };
+ purge = { "purge-nodes" };
+ publish = { "publish", autocreate_on_publish and "auto-create" };
+ delete = { "delete-nodes" };
+ get_items = { "retrieve-items" };
+ add_subscription = { "subscribe" };
+ get_subscriptions = { "retrieve-subscriptions" };
+ set_configure = { "config-node" };
+ get_default = { "retrieve-default" };
+};
+
+local function add_disco_features_from_service(service)
+ for method, features in pairs(feature_map) do
+ if service[method] then
+ for _, feature in ipairs(features) do
+ if feature then
+ module:add_feature(xmlns_pubsub.."#"..feature);
+ end
+ end
+ end
+ end
+ for affiliation in pairs(service.config.capabilities) do
+ if affiliation ~= "none" and affiliation ~= "owner" then
+ module:add_feature(xmlns_pubsub.."#"..affiliation.."-affiliation");
+ end
+ end
+end
+
+module:hook("host-disco-info-node", function (event)
+ local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node;
+ local ok, ret = service:get_nodes(stanza.attr.from);
+ if not ok or not ret[node] then
+ return;
+ end
+ event.exists = true;
+ reply:tag("identity", { category = "pubsub", type = "leaf" });
+end);
+
+module:hook("host-disco-items-node", function (event)
+ local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node;
+ local ok, ret = service:get_items(node, stanza.attr.from);
+ if not ok then
+ return;
+ end
+
+ for _, id in ipairs(ret) do
+ reply:tag("item", { jid = module.host, name = id }):up();
+ end
+ event.exists = true;
+end);
+
+
+module:hook("host-disco-items", function (event)
+ local stanza, origin, reply = event.stanza, event.origin, event.reply;
+ local ok, ret = service:get_nodes(event.stanza.attr.from);
+ if not ok then
+ return;
+ end
+ for node, node_obj in pairs(ret) do
+ reply:tag("item", { jid = module.host, node = node, name = node_obj.config.name }):up();
+ end
+end);
+
+local admin_aff = module:get_option_string("default_admin_affiliation", "owner");
+local unowned_aff = module:get_option_string("default_unowned_affiliation");
+local function get_affiliation(jid, node)
+ local bare_jid = jid_bare(jid);
+ if bare_jid == module.host or usermanager.is_admin(bare_jid, module.host) then
+ return admin_aff;
+ end
+ if not node then
+ return unowned_aff;
+ end
+end
+
+function set_service(new_service)
+ service = new_service;
+ module.environment.service = service;
+ add_disco_features_from_service(service);
+end
+
+function module.save()
+ return { service = service };
+end
+
+function module.restore(data)
+ set_service(data.service);
+end
+
+function module.load()
+ if module.reloading then return; end
+
+ set_service(pubsub.new({
+ capabilities = {
+ none = {
+ create = false;
+ publish = false;
+ retract = false;
+ get_nodes = true;
+
+ subscribe = true;
+ unsubscribe = true;
+ get_subscription = true;
+ get_subscriptions = true;
+ get_items = true;
+
+ subscribe_other = false;
+ unsubscribe_other = false;
+ get_subscription_other = false;
+ get_subscriptions_other = false;
+
+ be_subscribed = true;
+ be_unsubscribed = true;
+
+ set_affiliation = false;
+ };
+ publisher = {
+ create = false;
+ publish = true;
+ retract = true;
+ get_nodes = true;
+
+ subscribe = true;
+ unsubscribe = true;
+ get_subscription = true;
+ get_subscriptions = true;
+ get_items = true;
+
+ subscribe_other = false;
+ unsubscribe_other = false;
+ get_subscription_other = false;
+ get_subscriptions_other = false;
+
+ be_subscribed = true;
+ be_unsubscribed = true;
+
+ set_affiliation = false;
+ };
+ owner = {
+ create = true;
+ publish = true;
+ retract = true;
+ delete = true;
+ get_nodes = true;
+ configure = true;
+
+ subscribe = true;
+ unsubscribe = true;
+ get_subscription = true;
+ get_subscriptions = true;
+ get_items = true;
+
+
+ subscribe_other = true;
+ unsubscribe_other = true;
+ get_subscription_other = true;
+ get_subscriptions_other = true;
+
+ be_subscribed = true;
+ be_unsubscribed = true;
+
+ set_affiliation = true;
+ };
+ };
+
+ autocreate_on_publish = autocreate_on_publish;
+ autocreate_on_subscribe = autocreate_on_subscribe;
+
+ broadcaster = simple_broadcast;
+ get_affiliation = get_affiliation;
+
+ normalize_jid = jid_bare;
+ }));
+end
diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua
new file mode 100644
index 00000000..1497c21c
--- /dev/null
+++ b/plugins/mod_pubsub/pubsub.lib.lua
@@ -0,0 +1,317 @@
+local st = require "util.stanza";
+local uuid_generate = require "util.uuid".generate;
+local dataform = require"util.dataforms".new;
+
+local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
+local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
+local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
+
+local _M = {};
+
+local handlers = {};
+_M.handlers = handlers;
+
+local pubsub_errors = {
+ ["conflict"] = { "cancel", "conflict" };
+ ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
+ ["jid-required"] = { "modify", "bad-request", nil, "jid-required" };
+ ["nodeid-required"] = { "modify", "bad-request", nil, "nodeid-required" };
+ ["item-not-found"] = { "cancel", "item-not-found" };
+ ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" };
+ ["forbidden"] = { "auth", "forbidden" };
+ ["not-allowed"] = { "cancel", "not-allowed" };
+};
+local function pubsub_error_reply(stanza, error)
+ local e = pubsub_errors[error];
+ local reply = st.error_reply(stanza, unpack(e, 1, 3));
+ if e[4] then
+ reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
+ end
+ return reply;
+end
+_M.pubsub_error_reply = pubsub_error_reply;
+
+local node_config_form = require"util.dataforms".new {
+ {
+ type = "hidden";
+ name = "FORM_TYPE";
+ value = "http://jabber.org/protocol/pubsub#node_config";
+ };
+ {
+ type = "text-single";
+ name = "pubsub#max_items";
+ label = "Max # of items to persist";
+ };
+};
+
+function handlers.get_items(origin, stanza, items, service)
+ local node = items.attr.node;
+ local item = items:get_child("item");
+ local id = item and item.attr.id;
+
+ if not node then
+ origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+ return true;
+ end
+ local ok, results = service:get_items(node, stanza.attr.from, id);
+ if not ok then
+ origin.send(pubsub_error_reply(stanza, results));
+ return true;
+ end
+
+ local data = st.stanza("items", { node = node });
+ for _, id in ipairs(results) do
+ data:add_child(results[id]);
+ end
+ local reply;
+ if data then
+ reply = st.reply(stanza)
+ :tag("pubsub", { xmlns = xmlns_pubsub })
+ :add_child(data);
+ else
+ reply = pubsub_error_reply(stanza, "item-not-found");
+ end
+ origin.send(reply);
+ return true;
+end
+
+function handlers.get_subscriptions(origin, stanza, subscriptions, service)
+ local node = subscriptions.attr.node;
+ local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from);
+ if not ok then
+ origin.send(pubsub_error_reply(stanza, ret));
+ return true;
+ end
+ local reply = st.reply(stanza)
+ :tag("pubsub", { xmlns = xmlns_pubsub })
+ :tag("subscriptions");
+ for _, sub in ipairs(ret) do
+ reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up();
+ end
+ origin.send(reply);
+ return true;
+end
+
+function handlers.set_create(origin, stanza, create, service)
+ local node = create.attr.node;
+ local ok, ret, reply;
+ if node then
+ ok, ret = service:create(node, stanza.attr.from);
+ if ok then
+ reply = st.reply(stanza);
+ else
+ reply = pubsub_error_reply(stanza, ret);
+ end
+ else
+ repeat
+ node = uuid_generate();
+ ok, ret = service:create(node, stanza.attr.from);
+ until ok or ret ~= "conflict";
+ if ok then
+ reply = st.reply(stanza)
+ :tag("pubsub", { xmlns = xmlns_pubsub })
+ :tag("create", { node = node });
+ else
+ reply = pubsub_error_reply(stanza, ret);
+ end
+ end
+ origin.send(reply);
+ return true;
+end
+
+function handlers.set_delete(origin, stanza, delete, service)
+ local node = delete.attr.node;
+
+ local reply, notifier;
+ if not node then
+ origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+ return true;
+ end
+ local ok, ret = service:delete(node, stanza.attr.from);
+ if ok then
+ reply = st.reply(stanza);
+ else
+ reply = pubsub_error_reply(stanza, ret);
+ end
+ origin.send(reply);
+ return true;
+end
+
+function handlers.set_subscribe(origin, stanza, subscribe, service)
+ local node, jid = subscribe.attr.node, subscribe.attr.jid;
+ if not (node and jid) then
+ origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
+ return true;
+ end
+ --[[
+ local options_tag, options = stanza.tags[1]:get_child("options"), nil;
+ if options_tag then
+ options = options_form:data(options_tag.tags[1]);
+ end
+ --]]
+ local options_tag, options; -- FIXME
+ local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options);
+ local reply;
+ if ok then
+ reply = st.reply(stanza)
+ :tag("pubsub", { xmlns = xmlns_pubsub })
+ :tag("subscription", {
+ node = node,
+ jid = jid,
+ subscription = "subscribed"
+ }):up();
+ if options_tag then
+ reply:add_child(options_tag);
+ end
+ else
+ reply = pubsub_error_reply(stanza, ret);
+ end
+ origin.send(reply);
+end
+
+function handlers.set_unsubscribe(origin, stanza, unsubscribe, service)
+ local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
+ if not (node and jid) then
+ origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
+ return true;
+ end
+ local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
+ local reply;
+ if ok then
+ reply = st.reply(stanza);
+ else
+ reply = pubsub_error_reply(stanza, ret);
+ end
+ origin.send(reply);
+ return true;
+end
+
+function handlers.set_publish(origin, stanza, publish, service)
+ local node = publish.attr.node;
+ if not node then
+ origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+ return true;
+ end
+ local item = publish:get_child("item");
+ local id = (item and item.attr.id);
+ if not id then
+ id = uuid_generate();
+ if item then
+ item.attr.id = id;
+ end
+ end
+ local ok, ret = service:publish(node, stanza.attr.from, id, item);
+ local reply;
+ if ok then
+ reply = st.reply(stanza)
+ :tag("pubsub", { xmlns = xmlns_pubsub })
+ :tag("publish", { node = node })
+ :tag("item", { id = id });
+ else
+ reply = pubsub_error_reply(stanza, ret);
+ end
+ origin.send(reply);
+ return true;
+end
+
+function handlers.set_retract(origin, stanza, retract, service)
+ local node, notify = retract.attr.node, retract.attr.notify;
+ notify = (notify == "1") or (notify == "true");
+ local item = retract:get_child("item");
+ local id = item and item.attr.id
+ if not (node and id) then
+ origin.send(pubsub_error_reply(stanza, node and "item-not-found" or "nodeid-required"));
+ return true;
+ end
+ local reply, notifier;
+ if notify then
+ notifier = st.stanza("retract", { id = id });
+ end
+ local ok, ret = service:retract(node, stanza.attr.from, id, notifier);
+ if ok then
+ reply = st.reply(stanza);
+ else
+ reply = pubsub_error_reply(stanza, ret);
+ end
+ origin.send(reply);
+ return true;
+end
+
+function handlers.set_purge(origin, stanza, purge, service)
+ local node, notify = purge.attr.node, purge.attr.notify;
+ notify = (notify == "1") or (notify == "true");
+ local reply;
+ if not node then
+ origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+ return true;
+ end
+ local ok, ret = service:purge(node, stanza.attr.from, notify);
+ if ok then
+ reply = st.reply(stanza);
+ else
+ reply = pubsub_error_reply(stanza, ret);
+ end
+ origin.send(reply);
+ return true;
+end
+
+function handlers.get_configure(origin, stanza, config, service)
+ local node = config.attr.node;
+ if not node then
+ origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+ return true;
+ end
+
+ if not service:may(node, stanza.attr.from, "configure") then
+ origin.send(pubsub_error_reply(stanza, "forbidden"));
+ return true;
+ end
+
+ local node_obj = service.nodes[node];
+ if not node_obj then
+ origin.send(pubsub_error_reply(stanza, "item-not-found"));
+ return true;
+ end
+
+ local reply = st.reply(stanza)
+ :tag("pubsub", { xmlns = xmlns_pubsub_owner })
+ :tag("configure", { node = node })
+ :add_child(node_config_form:form(node_obj.config));
+ origin.send(reply);
+ return true;
+end
+
+function handlers.set_configure(origin, stanza, config, service)
+ local node = config.attr.node;
+ if not node then
+ origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+ return true;
+ end
+ if not service:may(node, stanza.attr.from, "configure") then
+ origin.send(pubsub_error_reply(stanza, "forbidden"));
+ return true;
+ end
+ local new_config, err = node_config_form:data(config.tags[1]);
+ if not new_config then
+ origin.send(st.error_reply(stanza, "modify", "bad-request", err));
+ return true;
+ end
+ local ok, err = service:set_node_config(node, stanza.attr.from, new_config);
+ if not ok then
+ origin.send(pubsub_error_reply(stanza, err));
+ return true;
+ end
+ origin.send(st.reply(stanza));
+ return true;
+end
+
+function handlers.get_default(origin, stanza, default, service)
+ local reply = st.reply(stanza)
+ :tag("pubsub", { xmlns = xmlns_pubsub_owner })
+ :tag("default")
+ :add_child(node_config_form:form(service.node_defaults));
+ origin.send(reply);
+ return true;
+end
+
+return _M;
diff --git a/plugins/mod_register.lua b/plugins/mod_register.lua
index 3d7a068c..e4b4bd51 100644
--- a/plugins/mod_register.lua
+++ b/plugins/mod_register.lua
@@ -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,9 +13,10 @@ local usermanager_user_exists = require "core.usermanager".user_exists;
local usermanager_create_user = require "core.usermanager".create_user;
local usermanager_set_password = require "core.usermanager".set_password;
local usermanager_delete_user = require "core.usermanager".delete_user;
-local os_time = os.time;
local nodeprep = require "util.encodings".stringprep.nodeprep;
local jid_bare = require "util.jid".bare;
+local create_throttle = require "util.throttle".create;
+local new_cache = require "util.cache".new;
local compat = module:get_option_boolean("registration_compat", true);
local allow_registration = module:get_option_boolean("allow_registration", false);
@@ -72,7 +73,7 @@ module:add_feature("jabber:iq:register");
local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up();
module:hook("stream-features", function(event)
- local session, features = event.origin, event.features;
+ local session, features = event.origin, event.features;
-- Advertise registration to unauthorized clients only.
if not(allow_registration) or session.type ~= "c2s_unauthed" then
@@ -84,6 +85,7 @@ end);
local function handle_registration_stanza(event)
local session, stanza = event.origin, event.stanza;
+ local log = session.log or module._log;
local query = stanza.tags[1];
if stanza.attr.type == "get" then
@@ -97,22 +99,23 @@ local function handle_registration_stanza(event)
if query.tags[1] and query.tags[1].name == "remove" then
local username, host = session.username, session.host;
+ -- This one weird trick sends a reply to this stanza before the user is deleted
local old_session_close = session.close;
session.close = function(session, ...)
session.send(st.reply(stanza));
return old_session_close(session, ...);
end
-
+
local ok, err = usermanager_delete_user(username, host);
-
+
if not ok then
- module:log("debug", "Removing user account %s@%s failed: %s", username, host, err);
+ log("debug", "Removing user account %s@%s failed: %s", username, host, err);
session.close = old_session_close;
session.send(st.error_reply(stanza, "cancel", "service-unavailable", err));
return true;
end
-
- module:log("info", "User removed their account: %s@%s", username, host);
+
+ log("info", "User removed their account: %s@%s", username, host);
module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
else
local username = nodeprep(query:get_child_text("username"));
@@ -169,17 +172,36 @@ local function parse_response(query)
end
end
-local recent_ips = {};
-local min_seconds_between_registrations = module:get_option("min_seconds_between_registrations");
-local whitelist_only = module:get_option("whitelist_registration_only");
-local whitelisted_ips = module:get_option("registration_whitelist") or { "127.0.0.1" };
-local blacklisted_ips = module:get_option("registration_blacklist") or {};
+local min_seconds_between_registrations = module:get_option_number("min_seconds_between_registrations");
+local whitelist_only = module:get_option_boolean("whitelist_registration_only");
+local whitelisted_ips = module:get_option_set("registration_whitelist", { "127.0.0.1" })._items;
+local blacklisted_ips = module:get_option_set("registration_blacklist", {})._items;
-for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end
-for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end
+local throttle_max = module:get_option_number("registration_throttle_max", min_seconds_between_registrations and 1);
+local throttle_period = module:get_option_number("registration_throttle_period", min_seconds_between_registrations);
+local throttle_cache_size = module:get_option_number("registration_throttle_cache_size", 100);
+local blacklist_overflow = module:get_option_boolean("blacklist_on_registration_throttle_overload", false);
+
+local throttle_cache = new_cache(throttle_cache_size, blacklist_overflow and function (ip, throttle)
+ if not throttle:peek() then
+ module:log("info", "Adding ip %s to registration blacklist", ip);
+ blacklisted_ips[ip] = true;
+ end
+end);
+
+local function check_throttle(ip)
+ if not throttle_max then return true end
+ local throttle = throttle_cache:get(ip);
+ if not throttle then
+ throttle = create_throttle(throttle_max, throttle_period);
+ end
+ throttle_cache:set(ip, throttle);
+ return throttle:poll(1);
+end
module:hook("stanza/iq/jabber:iq:register:query", function(event)
local session, stanza = event.origin, event.stanza;
+ local log = session.log or module._log;
if not(allow_registration) or session.type ~= "c2s_unauthed" then
session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
@@ -199,23 +221,14 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event)
else
-- Check that the user is not blacklisted or registering too often
if not session.ip then
- module:log("debug", "User's IP not known; can't apply blacklist/whitelist");
+ log("debug", "User's IP not known; can't apply blacklist/whitelist");
elseif blacklisted_ips[session.ip] or (whitelist_only and not whitelisted_ips[session.ip]) then
session.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not allowed to register an account."));
return true;
elseif min_seconds_between_registrations and not whitelisted_ips[session.ip] then
- if not recent_ips[session.ip] then
- recent_ips[session.ip] = { time = os_time(), count = 1 };
- else
- local ip = recent_ips[session.ip];
- ip.count = ip.count + 1;
-
- if os_time() - ip.time < min_seconds_between_registrations then
- ip.time = os_time();
- session.send(st.error_reply(stanza, "wait", "not-acceptable"));
- return true;
- end
- ip.time = os_time();
+ if check_throttle(session.ip) then
+ session.send(st.error_reply(stanza, "wait", "not-acceptable"));
+ return true;
end
end
local username, password = nodeprep(data.username), data.password;
@@ -241,7 +254,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event)
return true;
end
session.send(st.reply(stanza)); -- user created!
- module:log("info", "User account created: %s@%s", username, host);
+ log("info", "User account created: %s@%s", username, host);
module:fire_event("user-registered", {
username = username, host = host, source = "mod_register",
session = session });
diff --git a/plugins/mod_roster.lua b/plugins/mod_roster.lua
index d530bb45..a674740c 100644
--- a/plugins/mod_roster.lua
+++ b/plugins/mod_roster.lua
@@ -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.
--
@@ -36,15 +36,15 @@ module:hook("iq/self/jabber:iq:roster:query", function(event)
if stanza.attr.type == "get" then
local roster = st.reply(stanza);
-
+
local client_ver = tonumber(stanza.tags[1].attr.ver);
local server_ver = tonumber(session.roster[false].version or 1);
-
+
if not (client_ver and server_ver) or client_ver ~= server_ver then
roster:query("jabber:iq:roster");
-- Client does not support versioning, or has stale roster
for jid, item in pairs(session.roster) do
- if jid ~= "pending" and jid then
+ if jid then
roster:tag("item", {
jid = jid,
subscription = item.subscription,
@@ -64,9 +64,7 @@ module:hook("iq/self/jabber:iq:roster:query", function(event)
else -- stanza.attr.type == "set"
local query = stanza.tags[1];
if #query.tags == 1 and query.tags[1].name == "item"
- and query.tags[1].attr.xmlns == "jabber:iq:roster" and query.tags[1].attr.jid
- -- Protection against overwriting roster.pending, until we move it
- and query.tags[1].attr.jid ~= "pending" then
+ and query.tags[1].attr.xmlns == "jabber:iq:roster" and query.tags[1].attr.jid then
local item = query.tags[1];
local from_node, from_host = jid_split(stanza.attr.from);
local jid = jid_prep(item.attr.jid);
@@ -78,7 +76,7 @@ module:hook("iq/self/jabber:iq:roster:query", function(event)
local r_item = roster[jid];
if r_item then
local to_bare = node and (node.."@"..host) or host; -- bare JID
- if r_item.subscription == "both" or r_item.subscription == "from" or (roster.pending and roster.pending[jid]) then
+ if r_item.subscription == "both" or r_item.subscription == "from" or roster[false].pending[jid] then
core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare}));
end
if r_item.subscription == "both" or r_item.subscription == "to" or r_item.ask then
@@ -144,8 +142,8 @@ module:hook_global("user-deleted", function(event)
local bare = username .. "@" .. host;
local roster = rm_load_roster(username, host);
for jid, item in pairs(roster) do
- if jid and jid ~= "pending" then
- if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
+ if jid then
+ if item.subscription == "both" or item.subscription == "from" or roster[false].pending[jid] then
module:send(st.presence({type="unsubscribed", from=bare, to=jid}));
end
if item.subscription == "both" or item.subscription == "to" or item.ask then
diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua
index 4173fcfa..3e80c77b 100644
--- a/plugins/mod_s2s/mod_s2s.lua
+++ b/plugins/mod_s2s/mod_s2s.lua
@@ -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,7 +15,6 @@ local core_process_stanza = prosody.core_process_stanza;
local tostring, type = tostring, type;
local t_insert = table.insert;
local xpcall, traceback = xpcall, debug.traceback;
-local NULL = {};
local add_task = require "util.timer".add_task;
local st = require "util.stanza";
@@ -26,7 +25,6 @@ local s2s_new_incoming = require "core.s2smanager".new_incoming;
local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
local s2s_destroy_session = require "core.s2smanager".destroy_session;
local uuid_gen = require "util.uuid".generate;
-local cert_verify_identity = require "util.x509".verify_identity;
local fire_global_event = prosody.events.fire_event;
local s2sout = module:require("s2sout");
@@ -39,6 +37,8 @@ local secure_domains, insecure_domains =
module:get_option_set("s2s_secure_domains", {})._items, module:get_option_set("s2s_insecure_domains", {})._items;
local require_encryption = module:get_option_boolean("s2s_require_encryption", false);
+local measure_connections = module:measure("connections", "counter");
+
local sessions = module:shared("sessions");
local log = module._log;
@@ -135,6 +135,12 @@ function route_to_new_session(event)
return true;
end
+local function keepalive(event)
+ return event.session.sends2s(' ');
+end
+
+module:hook("s2s-read-timeout", keepalive, -1);
+
function module.add_host(module)
if module:get_option_boolean("disallow_s2s", false) then
module:log("warn", "The 'disallow_s2s' config option is deprecated, please see http://prosody.im/doc/s2s#disabling");
@@ -143,15 +149,28 @@ function module.add_host(module)
module:hook("route/remote", route_to_existing_session, -1);
module:hook("route/remote", route_to_new_session, -10);
module:hook("s2s-authenticated", make_authenticated, -1);
+ module:hook("s2s-read-timeout", keepalive, -1);
+ module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
+ if session.type == "s2sout" then
+ -- Stream is authenticated and we are seem to be done with feature negotiation,
+ -- so the stream is ready for stanzas. RFC 6120 Section 4.3
+ mark_connected(session);
+ return true;
+ elseif not session.dialback_verifying then
+ session.log("warn", "No SASL EXTERNAL offer and Dialback doesn't seem to be enabled, giving up");
+ session:close();
+ return false;
+ end
+ end, -1);
end
-- Stream is authorised, and ready for normal stanzas
function mark_connected(session)
local sendq = session.sendq;
-
+
local from, to = session.from_host, session.to_host;
-
- session.log("info", "%s s2s connection %s->%s complete", session.direction, from, to);
+
+ session.log("info", "%s s2s connection %s->%s complete", session.direction:gsub("^.", string.upper), from, to);
local event_data = { session = session };
if session.type == "s2sout" then
@@ -166,7 +185,7 @@ function mark_connected(session)
fire_global_event("s2sin-established", event_data);
hosts[to].events.fire_event("s2sin-established", event_data);
end
-
+
if session.direction == "outgoing" then
if sendq then
session.log("debug", "sending %d queued stanzas across new outgoing connection to %s", #sendq, session.to_host);
@@ -177,7 +196,7 @@ function mark_connected(session)
end
session.sendq = nil;
end
-
+
session.ip_hosts = nil;
session.srv_hosts = nil;
end
@@ -212,14 +231,17 @@ function make_authenticated(event)
return false;
end
session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host);
-
- mark_connected(session);
-
+
+ if (session.type == "s2sout" and session.external_auth ~= "succeeded") or session.type == "s2sin" then
+ -- Stream either used dialback for authentication or is an incoming stream.
+ mark_connected(session);
+ end
+
return true;
end
--- Helper to check that a session peer's certificate is valid
-local function check_cert_status(session)
+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
@@ -227,39 +249,6 @@ local function check_cert_status(session)
cert = conn:getpeercertificate()
end
- if cert then
- local chain_valid, errors;
- if conn.getpeerverification then
- chain_valid, errors = conn:getpeerverification();
- elseif conn.getpeerchainvalid then -- COMPAT mw/luasec-hg
- chain_valid, errors = conn:getpeerchainvalid();
- errors = (not chain_valid) and { { errors } } or nil;
- else
- chain_valid, errors = false, { { "Chain verification not supported by this version of LuaSec" } };
- end
- -- Is there any interest in printing out all/the number of errors here?
- if not chain_valid then
- (session.log or log)("debug", "certificate chain validation result: invalid");
- for depth, t in pairs(errors or NULL) do
- (session.log or log)("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", "))
- end
- session.cert_chain_status = "invalid";
- else
- (session.log or log)("debug", "certificate chain validation result: valid");
- session.cert_chain_status = "valid";
-
- -- We'll go ahead and verify the asserted identity if the
- -- connecting server specified one.
- if host then
- if cert_verify_identity(host, "xmpp-server", cert) then
- session.cert_identity_status = "valid"
- else
- session.cert_identity_status = "invalid"
- end
- (session.log or log)("debug", "certificate identity validation result: %s", session.cert_identity_status);
- end
- end
- end
return module:fire_event("s2s-check-certificate", { host = host, session = session, cert = cert });
end
@@ -271,23 +260,26 @@ local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
function stream_callbacks.streamopened(session, attr)
session.version = tonumber(attr.version) or 0;
-
+
-- TODO: Rename session.secure to session.encrypted
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
if session.direction == "incoming" then
-- Send a reply stream header
-
+
-- Validate to/from
local to, from = nameprep(attr.to), nameprep(attr.from);
if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts)
@@ -298,7 +290,7 @@ function stream_callbacks.streamopened(session, attr)
session:close({ condition = "improper-addressing", text = "Invalid 'from' address" });
return;
end
-
+
-- Set session.[from/to]_host if they have not been set already and if
-- this session isn't already authenticated
if session.type == "s2sin_unauthed" and from and not session.from_host then
@@ -313,10 +305,10 @@ function stream_callbacks.streamopened(session, attr)
session:close({ condition = "improper-addressing", text = "New stream 'to' attribute does not match original" });
return;
end
-
+
-- For convenience we'll put the sanitised values into these variables
to, from = session.to_host, session.from_host;
-
+
session.streamid = uuid_gen();
(session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag());
if to then
@@ -352,15 +344,21 @@ function stream_callbacks.streamopened(session, attr)
session.notopen = nil;
if session.version >= 1.0 then
local features = st.stanza("stream:features");
-
+
if to then
hosts[to].events.fire_event("s2s-stream-features", { origin = session, features = features });
else
(session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", from or session.ip or "unknown host");
+ fire_global_event("s2s-stream-features-legacy", { origin = session, features = features });
+ end
+
+ if ( session.type == "s2sin" or session.type == "s2sout" ) or features.tags[1] then
+ log("debug", "Sending stream features: %s", tostring(features));
+ session.sends2s(features);
+ else
+ (session.log or log)("warn", "No features to offer, giving up");
+ session:close({ condition = "undefined-condition", text = "No features to offer" });
end
-
- log("debug", "Sending stream features: %s", tostring(features));
- session.sends2s(features);
end
elseif session.direction == "outgoing" then
session.notopen = nil;
@@ -390,7 +388,7 @@ function stream_callbacks.streamopened(session, attr)
end
end
session.send_buffer = nil;
-
+
-- If server is pre-1.0, don't wait for features, just do dialback
if session.version < 1.0 then
if not session.dialback_verifying then
@@ -483,10 +481,10 @@ local function session_close(session, reason, remote_reason)
session.sends2s("</stream:stream>");
function session.sends2s() return false; end
-
+
local reason = remote_reason or (reason and (reason.text or reason.condition)) or reason;
- session.log("info", "%s s2s stream %s->%s closed: %s", session.direction, session.from_host or "(unknown host)", session.to_host or "(unknown host)", reason or "stream closed");
-
+ session.log("info", "%s s2s stream %s->%s closed: %s", session.direction:gsub("^.", string.upper), session.from_host or "(unknown host)", session.to_host or "(unknown host)", reason or "stream closed");
+
-- Authenticated incoming stream may still be sending us stanzas, so wait for </stream:stream> from remote
local conn = session.conn;
if reason == nil and not session.notopen and session.type == "s2sin" then
@@ -504,47 +502,58 @@ local function session_close(session, reason, remote_reason)
end
end
-function session_open_stream(session, from, to)
- local attr = {
- ["xmlns:stream"] = 'http://etherx.jabber.org/streams',
- xmlns = 'jabber:server',
- version = session.version and (session.version > 0 and "1.0" or nil),
- ["xml:lang"] = 'en',
- id = session.streamid,
- from = from or "", to = to or "",
- }
+function session_stream_attrs(session, from, to, attr)
if not from or (hosts[from] and hosts[from].modules.dialback) then
attr["xmlns:db"] = 'jabber:server:dialback';
end
-
- session.sends2s("<?xml version='1.0'?>");
- session.sends2s(st.stanza("stream:stream", attr):top_tag());
- return true;
+ if not from then
+ attr.from = '';
+ end
+ if not to then
+ attr.to = '';
+ end
end
-- Session initialization logic shared by incoming and outgoing
local function initialize_session(session)
local stream = new_xmpp_stream(session, stream_callbacks);
+ local log = session.log or log;
session.stream = stream;
-
+
session.notopen = true;
-
+
function session.reset_stream()
session.notopen = true;
session.streamid = nil;
session.stream:reset();
end
- session.open_stream = session_open_stream;
-
- local filter = session.filter;
+ session.stream_attrs = session_stream_attrs;
+
+ local filter = initialize_filters(session);
+ local conn = session.conn;
+ local w = conn.write;
+
+ function session.sends2s(t)
+ log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^[^>]*>?"));
+ if t.name then
+ t = filter("stanzas/out", t);
+ end
+ if t then
+ t = filter("bytes/out", tostring(t));
+ if t then
+ return w(conn, t);
+ end
+ end
+ end
+
function session.data(data)
data = filter("bytes/in", data);
if data then
local ok, err = stream:feed(data);
if ok then return; end
- (session.log or log)("warn", "Received invalid XML: %s", data);
- (session.log or log)("warn", "Problem was: %s", err);
+ log("warn", "Received invalid XML: %s", data);
+ log("warn", "Problem was: %s", err);
session:close("not-well-formed");
end
end
@@ -556,6 +565,8 @@ local function initialize_session(session)
return handlestanza(session, stanza);
end
+ module:fire_event("s2s-created", { session = session });
+
add_task(connect_timeout, function ()
if session.type == "s2sin" or session.type == "s2sout" then
return; -- Ok, we're connected
@@ -570,32 +581,18 @@ local function initialize_session(session)
end
function listener.onconnect(conn)
+ measure_connections(1);
conn:setoption("keepalive", opt_keepalives);
local session = sessions[conn];
if not session then -- New incoming connection
session = s2s_new_incoming(conn);
sessions[conn] = session;
session.log("debug", "Incoming s2s connection");
-
- local filter = initialize_filters(session);
- local w = conn.write;
- session.sends2s = function (t)
- log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)"));
- if t.name then
- t = filter("stanzas/out", t);
- end
- if t then
- t = filter("bytes/out", tostring(t));
- if t then
- return w(conn, t);
- end
- end
- end
-
initialize_session(session);
else -- Outgoing session connected
session:open_stream(session.from_host, session.to_host);
end
+ session.ip = conn:ip();
end
function listener.onincoming(conn, data)
@@ -604,7 +601,7 @@ function listener.onincoming(conn, data)
session.data(data);
end
end
-
+
function listener.onstatus(conn, status)
if status == "ssl-handshake-complete" then
local session = sessions[conn];
@@ -615,14 +612,19 @@ function listener.onstatus(conn, status)
end
end
+function listener.ontimeout(conn)
+ -- Called instead of onconnect when the connection times out
+ measure_connections(1);
+end
+
function listener.ondisconnect(conn, err)
+ measure_connections(-1);
local session = sessions[conn];
if session then
sessions[conn] = nil;
if err and session.direction == "outgoing" and session.notopen then
(session.log or log)("debug", "s2s connection attempt failed: %s", err);
if s2sout.attempt_connection(session, err) then
- (session.log or log)("debug", "...so we're going to try another target");
return; -- Session lives for now
end
end
@@ -631,8 +633,15 @@ function listener.ondisconnect(conn, err)
end
end
+function listener.onreadtimeout(conn)
+ local session = sessions[conn];
+ local host = session.host or session.to_host;
+ if session then
+ return (hosts[host] or prosody).events.fire_event("s2s-read-timeout", { session = session });
+ end
+end
+
function listener.register_outgoing(conn, session)
- session.direction = "outgoing";
sessions[conn] = session;
initialize_session(session);
end
@@ -650,7 +659,7 @@ function check_auth_policy(event)
elseif must_secure and insecure_domains[host] then
must_secure = false;
end
-
+
if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then
module:log("warn", "Forbidding insecure connection to/from %s", host or session.ip or "(unknown host)");
if session.direction == "incoming" then
diff --git a/plugins/mod_s2s/s2sout.lib.lua b/plugins/mod_s2s/s2sout.lib.lua
index dc122af7..42413164 100644
--- a/plugins/mod_s2s/s2sout.lib.lua
+++ b/plugins/mod_s2s/s2sout.lib.lua
@@ -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.
--
@@ -46,14 +46,14 @@ end
function s2sout.initiate_connection(host_session)
initialize_filters(host_session);
host_session.version = 1;
-
+
-- Kick the connection attempting machine into life
if not s2sout.attempt_connection(host_session) then
-- Intentionally not returning here, the
-- session is needed, connected or not
s2s_destroy_session(host_session);
end
-
+
if not host_session.sends2s then
-- A sends2s which buffers data (until the stream is opened)
-- note that data in this buffer will be sent before the stream is authed
@@ -74,22 +74,23 @@ end
function s2sout.attempt_connection(host_session, err)
local to_host = host_session.to_host;
local connect_host, connect_port = to_host and idna_to_ascii(to_host), 5269;
-
+
if not connect_host then
return false;
end
-
+
if not err then -- This is our first attempt
log("debug", "First attempt to connect to %s, starting with SRV lookup...", to_host);
host_session.connecting = true;
local handle;
handle = adns.lookup(function (answer)
handle = nil;
+ local srv_hosts = { answer = answer };
+ host_session.srv_hosts = srv_hosts;
+ host_session.srv_choice = 0;
host_session.connecting = nil;
if answer and #answer > 0 then
log("debug", "%s has SRV records, handling...", to_host);
- local srv_hosts = { answer = answer };
- host_session.srv_hosts = srv_hosts;
for _, record in ipairs(answer) do
t_insert(srv_hosts, record.srv);
end
@@ -99,7 +100,7 @@ function s2sout.attempt_connection(host_session, err)
return;
end
t_sort(srv_hosts, compare_srv_priorities);
-
+
local srv_choice = srv_hosts[1];
host_session.srv_choice = 1;
if srv_choice then
@@ -118,7 +119,7 @@ function s2sout.attempt_connection(host_session, err)
end
end
end, "_xmpp-server._tcp."..connect_host..".", "SRV");
-
+
return true; -- Attempt in progress
elseif host_session.ip_hosts then
return s2sout.try_connect(host_session, connect_host, connect_port, err);
@@ -128,11 +129,11 @@ function s2sout.attempt_connection(host_session, err)
connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
host_session.log("info", "Connection failed (%s). Attempt #%d: This time to %s:%d", tostring(err), host_session.srv_choice, connect_host, connect_port);
else
- host_session.log("info", "Out of connection options, can't connect to %s", tostring(host_session.to_host));
+ host_session.log("info", "Failed in all attempts to connect to %s", tostring(host_session.to_host));
-- We're out of options
return false;
end
-
+
if not (connect_host and connect_port) then
-- Likely we couldn't resolve DNS
log("warn", "Hmm, we're without a host (%s) and port (%s) to connect to for %s, giving up :(", tostring(connect_host), tostring(connect_port), tostring(to_host));
@@ -252,11 +253,12 @@ function s2sout.try_connect(host_session, connect_host, connect_port, err)
end
function s2sout.make_connect(host_session, connect_host, connect_port)
- (host_session.log or log)("info", "Beginning new connection attempt to %s ([%s]:%d)", host_session.to_host, connect_host.addr, connect_port);
+ (host_session.log or log)("debug", "Beginning new connection attempt to %s ([%s]:%d)", host_session.to_host, connect_host.addr, connect_port);
-- Reset secure flag in case this is another
-- connection attempt after a failed STARTTLS
host_session.secure = nil;
+ host_session.encrypted = nil;
local conn, handler;
local proto = connect_host.proto;
@@ -267,7 +269,7 @@ function s2sout.make_connect(host_session, connect_host, connect_port)
else
handler = "Unsupported protocol: "..tostring(proto);
end
-
+
if not conn then
log("warn", "Failed to create outgoing connection, system error: %s", handler);
return false, handler;
@@ -279,29 +281,14 @@ function s2sout.make_connect(host_session, connect_host, connect_port)
log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host.addr, connect_port, err);
return false, err;
end
-
+
conn = wrapclient(conn, connect_host.addr, connect_port, s2s_listener, "*a");
host_session.conn = conn;
-
- local filter = initialize_filters(host_session);
- local w, log = conn.write, host_session.log;
- host_session.sends2s = function (t)
- log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
- if t.name then
- t = filter("stanzas/out", t);
- end
- if t then
- t = filter("bytes/out", tostring(t));
- if t then
- return w(conn, tostring(t));
- end
- end
- end
-
+
-- Register this outgoing connection so that xmppserver_listener knows about it
-- otherwise it will assume it is a new incoming connection
s2s_listener.register_outgoing(conn, host_session);
-
+
log("debug", "Connection attempt in progress...");
return true;
end
diff --git a/plugins/mod_s2s_auth_certs.lua b/plugins/mod_s2s_auth_certs.lua
new file mode 100644
index 00000000..dd0eb3cb
--- /dev/null
+++ b/plugins/mod_s2s_auth_certs.lua
@@ -0,0 +1,49 @@
+module:set_global();
+
+local cert_verify_identity = require "util.x509".verify_identity;
+local NULL = {};
+local log = module._log;
+
+module:hook("s2s-check-certificate", function(event)
+ local session, host, cert = event.session, event.host, event.cert;
+ local conn = session.conn:socket();
+ local log = session.log or log;
+
+ if not cert then
+ log("warn", "No certificate provided by %s", host or "unknown host");
+ return;
+ end
+
+ local chain_valid, errors;
+ if conn.getpeerverification then
+ chain_valid, errors = conn:getpeerverification();
+ elseif conn.getpeerchainvalid then -- COMPAT mw/luasec-hg
+ chain_valid, errors = conn:getpeerchainvalid();
+ errors = (not chain_valid) and { { errors } } or nil;
+ else
+ chain_valid, errors = false, { { "Chain verification not supported by this version of LuaSec" } };
+ end
+ -- Is there any interest in printing out all/the number of errors here?
+ if not chain_valid then
+ log("debug", "certificate chain validation result: invalid");
+ for depth, t in pairs(errors or NULL) do
+ log("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", "))
+ end
+ session.cert_chain_status = "invalid";
+ else
+ log("debug", "certificate chain validation result: valid");
+ session.cert_chain_status = "valid";
+
+ -- We'll go ahead and verify the asserted identity if the
+ -- connecting server specified one.
+ if host then
+ if cert_verify_identity(host, "xmpp-server", cert) then
+ session.cert_identity_status = "valid"
+ else
+ session.cert_identity_status = "invalid"
+ end
+ log("debug", "certificate identity validation result: %s", session.cert_identity_status);
+ end
+ end
+end, 509);
+
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index c5d3dc91..7e9b0720 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -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,13 +13,13 @@ local sm_bind_resource = require "core.sessionmanager".bind_resource;
local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
local base64 = require "util.encodings".base64;
-local cert_verify_identity = require "util.x509".verify_identity;
-
local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
local tostring = tostring;
-local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
-local allow_unencrypted_plain_auth = module:get_option("allow_unencrypted_plain_auth")
+local secure_auth_only = module:get_option_boolean("c2s_require_encryption", module:get_option_boolean("require_encryption", false));
+local allow_unencrypted_plain_auth = module:get_option_boolean("allow_unencrypted_plain_auth", false)
+local insecure_mechanisms = module:get_option_set("insecure_sasl_mechanisms", allow_unencrypted_plain_auth and {} or {"PLAIN", "LOGIN"});
+local disabled_mechanisms = module:get_option_set("disable_sasl_mechanisms", {});
local log = module._log;
@@ -28,15 +28,15 @@ local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind';
local function build_reply(status, ret, err_msg)
local reply = st.stanza(status, {xmlns = xmlns_sasl});
- if status == "challenge" then
- --log("debug", "CHALLENGE: %s", ret or "");
- reply:text(base64.encode(ret or ""));
- elseif status == "failure" then
+ if status == "failure" then
reply:tag(ret):up();
if err_msg then reply:tag("text"):text(err_msg); end
- elseif status == "success" then
- --log("debug", "SUCCESS: %s", ret or "");
- reply:text(base64.encode(ret or ""));
+ elseif status == "challenge" or status == "success" then
+ if ret == "" then
+ reply:text("=")
+ elseif ret then
+ reply:text(base64.encode(ret));
+ end
else
module:log("error", "Unknown sasl status: %s", status);
end
@@ -99,12 +99,10 @@ module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
module:log("info", "SASL EXTERNAL with %s failed", session.to_host)
-- TODO: Log the failure reason
session.external_auth = "failed"
+ session:close();
+ return true;
end, 500)
-module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
- -- TODO: Dialback wasn't loaded. Do something useful.
-end, 90)
-
module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
if session.type ~= "s2sout_unauthed" or not session.secure then return; end
@@ -124,71 +122,52 @@ module:hook_stanza("http://etherx.jabber.org/streams", "features", function (ses
end, 150);
local function s2s_external_auth(session, stanza)
+ if session.external_auth ~= "offered" then return end -- Unexpected request
+
local mechanism = stanza.attr.mechanism;
- if not session.secure then
- if mechanism == "EXTERNAL" then
- session.sends2s(build_reply("failure", "encryption-required"))
- else
- session.sends2s(build_reply("failure", "invalid-mechanism"))
- end
+ if mechanism ~= "EXTERNAL" then
+ session.sends2s(build_reply("failure", "invalid-mechanism"));
return true;
end
- if mechanism ~= "EXTERNAL" or session.cert_chain_status ~= "valid" then
- session.sends2s(build_reply("failure", "invalid-mechanism"))
+ if not session.secure then
+ session.sends2s(build_reply("failure", "encryption-required"));
return true;
end
- local text = stanza[1]
+ local text = stanza[1];
if not text then
- session.sends2s(build_reply("failure", "malformed-request"))
- return true
+ session.sends2s(build_reply("failure", "malformed-request"));
+ return true;
end
- -- Either the value is "=" and we've already verified the external
- -- cert identity, or the value is a string and either matches the
- -- from_host (
-
- text = base64.decode(text)
+ text = base64.decode(text);
if not text then
- session.sends2s(build_reply("failure", "incorrect-encoding"))
+ session.sends2s(build_reply("failure", "incorrect-encoding"));
return true;
end
- if session.cert_identity_status == "valid" then
- if text ~= "" and text ~= session.from_host then
- session.sends2s(build_reply("failure", "invalid-authzid"))
- return true
- end
- else
- if text == "" then
- session.sends2s(build_reply("failure", "invalid-authzid"))
- return true
- end
-
- local cert = session.conn:socket():getpeercertificate()
- if (cert_verify_identity(text, "xmpp-server", cert)) then
- session.cert_identity_status = "valid"
- else
- session.cert_identity_status = "invalid"
- session.sends2s(build_reply("failure", "invalid-authzid"))
- return true
- end
+ -- The text value is either "" or equals session.from_host
+ if not ( text == "" or text == session.from_host ) then
+ session.sends2s(build_reply("failure", "invalid-authzid"));
+ return true;
end
- session.external_auth = "succeeded"
-
- if not session.from_host then
- session.from_host = text;
+ -- We've already verified the external cert identity before offering EXTERNAL
+ if session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid" then
+ session.sends2s(build_reply("failure", "not-authorized"));
+ session:close();
+ return true;
end
- session.sends2s(build_reply("success"))
- local domain = text ~= "" and text or session.from_host;
- module:log("info", "Accepting SASL EXTERNAL identity from %s", domain);
- module:fire_event("s2s-authenticated", { session = session, host = domain });
+ -- Success!
+ session.external_auth = "succeeded";
+ session.sends2s(build_reply("success"));
+ module:log("info", "Accepting SASL EXTERNAL identity from %s", session.from_host);
+ module:fire_event("s2s-authenticated", { session = session, host = session.from_host });
session:reset_stream();
- return true
+ return true;
end
module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event)
@@ -206,9 +185,12 @@ module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event)
session.sasl_handler = usermanager_get_sasl_handler(module.host, session);
end
local mechanism = stanza.attr.mechanism;
- if not session.secure and (secure_auth_only or (mechanism == "PLAIN" and not allow_unencrypted_plain_auth)) then
+ if not session.secure and (secure_auth_only or insecure_mechanisms:contains(mechanism)) then
session.send(build_reply("failure", "encryption-required"));
return true;
+ elseif disabled_mechanisms:contains(mechanism) then
+ session.send(build_reply("failure", "invalid-mechanism"));
+ return true;
end
local valid_mechanism = session.sasl_handler:select(mechanism);
if not valid_mechanism then
@@ -232,6 +214,10 @@ module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event)
return true;
end);
+local function tls_unique(self)
+ return self.userdata["tls-unique"]:getpeerfinished();
+end
+
local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' };
local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' };
local xmpp_session_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-session' };
@@ -241,14 +227,32 @@ module:hook("stream-features", function(event)
if secure_auth_only and not origin.secure then
return;
end
- origin.sasl_handler = usermanager_get_sasl_handler(module.host, origin);
+ local sasl_handler = usermanager_get_sasl_handler(module.host, origin)
+ origin.sasl_handler = sasl_handler;
+ if origin.encrypted then
+ -- check wether LuaSec has the nifty binding to the function needed for tls-unique
+ -- FIXME: would be nice to have this check only once and not for every socket
+ if sasl_handler.add_cb_handler then
+ local socket = origin.conn:socket();
+ if socket.getpeerfinished then
+ sasl_handler:add_cb_handler("tls-unique", tls_unique);
+ end
+ sasl_handler["userdata"] = {
+ ["tls-unique"] = socket;
+ };
+ end
+ end
local mechanisms = st.stanza("mechanisms", mechanisms_attr);
- for mechanism in pairs(origin.sasl_handler:mechanisms()) do
- if mechanism ~= "PLAIN" or origin.secure or allow_unencrypted_plain_auth then
+ for mechanism in pairs(sasl_handler:mechanisms()) do
+ if (not disabled_mechanisms:contains(mechanism)) and (origin.secure or not insecure_mechanisms:contains(mechanism)) then
mechanisms:tag("mechanism"):text(mechanism):up();
end
end
- if mechanisms[1] then features:add_child(mechanisms); end
+ if mechanisms[1] then
+ features:add_child(mechanisms);
+ else
+ (origin.log or log)("warn", "No SASL mechanisms to offer");
+ end
else
features:tag("bind", bind_attr):tag("required"):up():up();
features:tag("session", xmpp_session_attr):tag("optional"):up():up();
@@ -258,10 +262,10 @@ end);
module:hook("s2s-stream-features", function(event)
local origin, features = event.origin, event.features;
if origin.secure and origin.type == "s2sin_unauthed" then
- -- Offer EXTERNAL if chain is valid and either we didn't validate
- -- the identity or it passed.
- if origin.cert_chain_status == "valid" and origin.cert_identity_status ~= "invalid" then --TODO: Configurable
- module:log("debug", "Offering SASL EXTERNAL")
+ -- Offer EXTERNAL only if both chain and identity is valid.
+ if origin.cert_chain_status == "valid" and origin.cert_identity_status == "valid" then
+ module:log("debug", "Offering SASL EXTERNAL");
+ origin.external_auth = "offered"
features:tag("mechanisms", { xmlns = xmlns_sasl })
:tag("mechanism"):text("EXTERNAL")
:up():up();
@@ -274,7 +278,7 @@ module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event)
local resource;
if stanza.attr.type == "set" then
local bind = stanza.tags[1];
- resource = bind:child_with_name("resource");
+ resource = bind:get_child("resource");
resource = resource and #resource.tags == 0 and resource[1] or nil;
end
local success, err_type, err, err_msg = sm_bind_resource(origin, resource);
diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua
index 972ecbee..ade4f0a6 100644
--- a/plugins/mod_storage_internal.lua
+++ b/plugins/mod_storage_internal.lua
@@ -6,6 +6,9 @@ local driver = {};
local driver_mt = { __index = driver };
function driver:open(store, typ)
+ if typ and typ ~= "keyval" then
+ return nil, "unsupported-store";
+ end
return setmetatable({ store = store, type = typ }, driver_mt);
end
function driver:get(user)
diff --git a/plugins/mod_storage_none.lua b/plugins/mod_storage_none.lua
index 8f2d2f56..fa925b76 100644
--- a/plugins/mod_storage_none.lua
+++ b/plugins/mod_storage_none.lua
@@ -1,8 +1,11 @@
local driver = {};
local driver_mt = { __index = driver };
-function driver:open(store)
- return setmetatable({ store = store }, driver_mt);
+function driver:open(store, typ)
+ if typ and typ ~= "keyval" then
+ return nil, "unsupported-store";
+ end
+ return setmetatable({ store = store, type = typ }, driver_mt);
end
function driver:get(user)
return {};
diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua
index eed3fec9..bf3c1c7b 100644
--- a/plugins/mod_storage_sql.lua
+++ b/plugins/mod_storage_sql.lua
@@ -1,184 +1,38 @@
---[[
-
-DB Tables:
- Prosody - key-value, map
- | host | user | store | key | type | value |
- ProsodyArchive - list
- | host | user | store | key | time | stanzatype | jsonvalue |
-
-Mapping:
- Roster - Prosody
- | host | user | "roster" | "contactjid" | type | value |
- | host | user | "roster" | NULL | "json" | roster[false] data |
- Account - Prosody
- | host | user | "accounts" | "username" | type | value |
-
- Offline - ProsodyArchive
- | host | user | "offline" | "contactjid" | time | "message" | json|XML |
-
-]]
-
-local type = type;
-local tostring = tostring;
-local tonumber = tonumber;
-local pairs = pairs;
-local next = next;
-local setmetatable = setmetatable;
-local xpcall = xpcall;
-local json = require "util.json";
-local build_url = require"socket.url".build;
-
-local DBI;
-local connection;
-local host,user,store = module.host;
-local params = module:get_option("sql");
-
-local dburi;
-local connections = module:shared "/*/sql/connection-cache";
-
-local function db2uri(params)
- return build_url{
- scheme = params.driver,
- user = params.username,
- password = params.password,
- host = params.host,
- port = params.port,
- path = params.database,
- };
-end
-
-
-local resolve_relative_path = require "core.configmanager".resolve_relative_path;
+-- luacheck: ignore 212/self
-local function test_connection()
- if not connection then return nil; end
- if connection:ping() then
- return true;
- else
- module:log("debug", "Database connection closed");
- connection = nil;
- connections[dburi] = nil;
- end
-end
-local function connect()
- if not test_connection() then
- prosody.unlock_globals();
- local dbh, err = DBI.Connect(
- params.driver, params.database,
- params.username, params.password,
- params.host, params.port
- );
- prosody.lock_globals();
- if not dbh then
- module:log("debug", "Database connection failed: %s", tostring(err));
- return nil, err;
- end
- module:log("debug", "Successfully connected to database");
- dbh:autocommit(false); -- don't commit automatically
- connection = dbh;
+local json = require "util.json";
+local sql = require "util.sql";
+local xml_parse = require "util.xml".parse;
+local uuid = require "util.uuid";
+local resolve_relative_path = require "util.paths".resolve_relative_path;
- connections[dburi] = dbh;
- end
- return connection;
-end
+local stanza_mt = require"util.stanza".stanza_mt;
+local getmetatable = getmetatable;
+local t_concat = table.concat;
+local function is_stanza(x) return getmetatable(x) == stanza_mt; end
-local function create_table()
- if not module:get_option("sql_manage_tables", true) then
- return;
- end
- local create_sql = "CREATE TABLE `prosody` (`host` TEXT, `user` TEXT, `store` TEXT, `key` TEXT, `type` TEXT, `value` TEXT);";
- if params.driver == "PostgreSQL" then
- create_sql = create_sql:gsub("`", "\"");
- elseif params.driver == "MySQL" then
- create_sql = create_sql:gsub("`value` TEXT", "`value` MEDIUMTEXT");
- end
-
- local stmt, err = connection:prepare(create_sql);
- if stmt then
- local ok = stmt:execute();
- local commit_ok = connection:commit();
- if ok and commit_ok then
- module:log("info", "Initialized new %s database with prosody table", params.driver);
- local index_sql = "CREATE INDEX `prosody_index` ON `prosody` (`host`, `user`, `store`, `key`)";
- if params.driver == "PostgreSQL" then
- index_sql = index_sql:gsub("`", "\"");
- elseif params.driver == "MySQL" then
- index_sql = index_sql:gsub("`([,)])", "`(20)%1");
- end
- local stmt, err = connection:prepare(index_sql);
- local ok, commit_ok, commit_err;
- if stmt then
- ok, err = stmt:execute();
- commit_ok, commit_err = connection:commit();
- end
- if not(ok and commit_ok) then
- module:log("warn", "Failed to create index (%s), lookups may not be optimised", err or commit_err);
- end
- elseif params.driver == "MySQL" then -- COMPAT: Upgrade tables from 0.8.0
- -- Failed to create, but check existing MySQL table here
- local stmt = connection:prepare("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'");
- local ok = stmt:execute();
- local commit_ok = connection:commit();
- if ok and commit_ok then
- if stmt:rowcount() > 0 then
- module:log("info", "Upgrading database schema...");
- local stmt = connection:prepare("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT");
- local ok, err = stmt:execute();
- local commit_ok = connection:commit();
- if ok and commit_ok then
- module:log("info", "Database table automatically upgraded");
- else
- module:log("error", "Failed to upgrade database schema (%s), please see "
- .."http://prosody.im/doc/mysql for help",
- err or "unknown error");
- end
- end
- repeat until not stmt:fetch();
- end
+local noop = function() end
+local unpack = unpack
+local function iterator(result)
+ return function(result_)
+ local row = result_();
+ if row ~= nil then
+ return unpack(row);
end
- elseif params.driver ~= "SQLite3" then -- SQLite normally fails to prepare for existing table
- module:log("warn", "Prosody was not able to automatically check/create the database table (%s), "
- .."see http://prosody.im/doc/modules/mod_storage_sql#table_management for help.",
- err or "unknown error");
- end
+ end, result, nil;
end
-do -- process options to get a db connection
- local ok;
- prosody.unlock_globals();
- ok, DBI = pcall(require, "DBI");
- if not ok then
- package.loaded["DBI"] = {};
- module:log("error", "Failed to load the LuaDBI library for accessing SQL databases: %s", DBI);
- module:log("error", "More information on installing LuaDBI can be found at http://prosody.im/doc/depends#luadbi");
- end
- prosody.lock_globals();
- if not ok or not DBI.Connect then
- return; -- Halt loading of this module
- end
+local default_params = { driver = "SQLite3" };
- params = params or { driver = "SQLite3" };
-
- if params.driver == "SQLite3" then
- params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
- end
-
- assert(params.driver and params.database, "Both the SQL driver and the database need to be specified");
-
- dburi = db2uri(params);
- connection = connections[dburi];
-
- assert(connect());
-
- -- Automatically create table, ignore failure (table probably already exists)
- create_table();
-end
+local engine;
local function serialize(value)
local t = type(value);
if t == "string" or t == "boolean" or t == "number" then
return t, tostring(value);
+ elseif is_stanza(value) then
+ return "xml", tostring(value);
elseif t == "table" then
local value,err = json.encode(value);
if value then return "json", value; end
@@ -194,55 +48,21 @@ local function deserialize(t, value)
elseif t == "number" then return tonumber(value);
elseif t == "json" then
return json.decode(value);
+ elseif t == "xml" then
+ return xml_parse(value);
end
end
-local function dosql(sql, ...)
- if params.driver == "PostgreSQL" then
- sql = sql:gsub("`", "\"");
- end
- -- do prepared statement stuff
- local stmt, err = connection:prepare(sql);
- if not stmt and not test_connection() then error("connection failed"); end
- if not stmt then module:log("error", "QUERY FAILED: %s %s", err, debug.traceback()); return nil, err; end
- -- run query
- local ok, err = stmt:execute(...);
- if not ok and not test_connection() then error("connection failed"); end
- if not ok then return nil, err; end
-
- return stmt;
-end
-local function getsql(sql, ...)
- return dosql(sql, host or "", user or "", store or "", ...);
-end
-local function setsql(sql, ...)
- local stmt, err = getsql(sql, ...);
- if not stmt then return stmt, err; end
- return stmt:affected();
-end
-local function transact(...)
- -- ...
-end
-local function rollback(...)
- if connection then connection:rollback(); end -- FIXME check for rollback error?
- return ...;
-end
-local function commit(...)
- local success,err = connection:commit();
- if not success then return nil, "SQL commit failed: "..tostring(err); end
- return ...;
-end
+local host = module.host;
+local user, store;
local function keyval_store_get()
- local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?");
- if not stmt then return rollback(nil, err); end
-
local haveany;
local result = {};
- for row in stmt:rows(true) do
+ for row in engine:select("SELECT `key`,`type`,`value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user or "", store) do
haveany = true;
- local k = row.key;
- local v = deserialize(row.type, row.value);
+ local k = row[1];
+ local v = deserialize(row[2], row[3]);
if k and v then
if k ~= "" then result[k] = v; elseif type(v) == "table" then
for a,b in pairs(v) do
@@ -251,164 +71,434 @@ local function keyval_store_get()
end
end
end
- return commit(haveany and result or nil);
+ if haveany then
+ return result;
+ end
end
local function keyval_store_set(data)
- local affected, err = setsql("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?");
- if not affected then return rollback(affected, err); end
-
+ engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user or "", store);
+
if data and next(data) ~= nil then
local extradata = {};
for key, value in pairs(data) do
if type(key) == "string" and key ~= "" then
local t, value = serialize(value);
- if not t then return rollback(t, value); end
- local ok, err = setsql("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", key, t, value);
- if not ok then return rollback(ok, err); end
+ assert(t, value);
+ engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user or "", store, key, t, value);
else
extradata[key] = value;
end
end
if next(extradata) ~= nil then
local t, extradata = serialize(extradata);
- if not t then return rollback(t, extradata); end
- local ok, err = setsql("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", "", t, extradata);
- if not ok then return rollback(ok, err); end
+ assert(t, extradata);
+ engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user or "", store, "", t, extradata);
end
end
- return commit(true);
+ return true;
end
+--- Key/value store API (default store type)
+
local keyval_store = {};
keyval_store.__index = keyval_store;
function keyval_store:get(username)
- user,store = username,self.store;
- if not connection and not connect() then return nil, "Unable to connect to database"; end
- local success, ret, err = xpcall(keyval_store_get, debug.traceback);
- if not connection and connect() then
- success, ret, err = xpcall(keyval_store_get, debug.traceback);
+ user, store = username, self.store;
+ local ok, result = engine:transaction(keyval_store_get);
+ if not ok then
+ module:log("error", "Unable to read from database %s store for %s: %s", store, username or "<host>", result);
+ return nil, result;
end
- if success then return ret, err; else return rollback(nil, ret); end
+ return result;
end
function keyval_store:set(username, data)
user,store = username,self.store;
- if not connection and not connect() then return nil, "Unable to connect to database"; end
- local success, ret, err = xpcall(function() return keyval_store_set(data); end, debug.traceback);
- if not connection and connect() then
- success, ret, err = xpcall(function() return keyval_store_set(data); end, debug.traceback);
- end
- if success then return ret, err; else return rollback(nil, ret); end
+ return engine:transaction(function()
+ return keyval_store_set(data);
+ end);
end
function keyval_store:users()
- local stmt, err = dosql("SELECT DISTINCT `user` FROM `prosody` WHERE `host`=? AND `store`=?", host, self.store);
- if not stmt then
- return rollback(nil, err);
- end
- local next = stmt:rows();
- return commit(function()
- local row = next();
- return row and row[1];
+ local ok, result = engine:transaction(function()
+ return engine:select("SELECT DISTINCT `user` FROM `prosody` WHERE `host`=? AND `store`=?", host, self.store);
end);
+ if not ok then return ok, result end
+ return iterator(result);
end
-local function map_store_get(key)
- local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or "");
- if not stmt then return rollback(nil, err); end
-
- local haveany;
- local result = {};
- for row in stmt:rows(true) do
- haveany = true;
- local k = row.key;
- local v = deserialize(row.type, row.value);
- if k and v then
- if k ~= "" then result[k] = v; elseif type(v) == "table" then
- for a,b in pairs(v) do
- result[a] = b;
+--- Archive store API
+
+-- luacheck: ignore 512 431/user 431/store
+local map_store = {};
+map_store.__index = map_store;
+map_store.remove = {};
+function map_store:get(username, key)
+ local ok, result = engine:transaction(function()
+ if type(key) == "string" and key ~= "" then
+ for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", host, username or "", self.store, key) do
+ return deserialize(row[1], row[2]);
+ end
+ else
+ for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", host, username or "", self.store, "") do
+ local data = deserialize(row[1], row[2]);
+ return data and data[key] or nil;
+ end
+ end
+ end);
+ if not ok then return nil, result; end
+ return result;
+end
+function map_store:set(username, key, data)
+ if data == nil then data = self.remove; end
+ return self:set_keys(username, { [key] = data });
+end
+function map_store:set_keys(username, keydatas)
+ local ok, result = engine:transaction(function()
+ for key, data in pairs(keydatas) do
+ if type(key) == "string" and key ~= "" then
+ engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?",
+ host, username or "", self.store, key);
+ if data ~= self.remove then
+ local t, value = assert(serialize(data));
+ engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, username or "", self.store, key, t, value);
+ end
+ else
+ local extradata = {};
+ for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", host, username or "", self.store, "") do
+ extradata = deserialize(row[1], row[2]);
+ break;
end
+ engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?",
+ host, username or "", self.store, "");
+ extradata[key] = data;
+ local t, value = assert(serialize(extradata));
+ engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, username or "", self.store, "", t, value);
end
end
+ return true;
+ end);
+ if not ok then return nil, result; end
+ return result;
+end
+
+local archive_store = {}
+archive_store.caps = {
+ total = true;
+};
+archive_store.__index = archive_store
+function archive_store:append(username, key, value, when, with)
+ if type(when) ~= "number" then
+ when, with, value = value, when, with;
end
- return commit(haveany and result[key] or nil);
+ local user,store = username,self.store;
+ return engine:transaction(function()
+ if key then
+ engine:delete("DELETE FROM `prosodyarchive` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", host, user or "", store, key);
+ else
+ key = uuid.generate();
+ end
+ local t, value = serialize(value);
+ engine:insert("INSERT INTO `prosodyarchive` (`host`, `user`, `store`, `when`, `with`, `key`, `type`, `value`) VALUES (?,?,?,?,?,?,?,?)", host, user or "", store, when, with, key, t, value);
+ return key;
+ end);
end
-local function map_store_set(key, data)
- local affected, err = setsql("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or "");
- if not affected then return rollback(affected, err); end
-
- if data and next(data) ~= nil then
- if type(key) == "string" and key ~= "" then
- local t, value = serialize(data);
- if not t then return rollback(t, value); end
- local ok, err = setsql("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", key, t, value);
- if not ok then return rollback(ok, err); end
+
+-- Helpers for building the WHERE clause
+local function archive_where(query, args, where)
+ -- Time range, inclusive
+ if query.start then
+ args[#args+1] = query.start
+ where[#where+1] = "`when` >= ?"
+ end
+
+ if query["end"] then
+ args[#args+1] = query["end"];
+ if query.start then
+ where[#where] = "`when` BETWEEN ? AND ?" -- is this inclusive?
else
- -- TODO non-string keys
+ where[#where+1] = "`when` <= ?"
end
end
- return commit(true);
-end
-local map_store = {};
-map_store.__index = map_store;
-function map_store:get(username, key)
- user,store = username,self.store;
- local success, ret, err = xpcall(function() return map_store_get(key); end, debug.traceback);
- if success then return ret, err; else return rollback(nil, ret); end
+ -- Related name
+ if query.with then
+ where[#where+1] = "`with` = ?";
+ args[#args+1] = query.with
+ end
+
+ -- Unique id
+ if query.key then
+ where[#where+1] = "`key` = ?";
+ args[#args+1] = query.key
+ end
end
-function map_store:set(username, key, data)
- user,store = username,self.store;
- local success, ret, err = xpcall(function() return map_store_set(key, data); end, debug.traceback);
- if success then return ret, err; else return rollback(nil, ret); end
+local function archive_where_id_range(query, args, where)
+ local args_len = #args
+ -- Before or after specific item, exclusive
+ if query.after then -- keys better be unique!
+ where[#where+1] = "`sort_id` > COALESCE((SELECT `sort_id` FROM `prosodyarchive` WHERE `key` = ? AND `host` = ? AND `user` = ? AND `store` = ? LIMIT 1), 0)"
+ args[args_len+1], args[args_len+2], args[args_len+3], args[args_len+4] = query.after, args[1], args[2], args[3];
+ args_len = args_len + 4
+ end
+ if query.before then
+ where[#where+1] = "`sort_id` < COALESCE((SELECT `sort_id` FROM `prosodyarchive` WHERE `key` = ? AND `host` = ? AND `user` = ? AND `store` = ? LIMIT 1), (SELECT MAX(`sort_id`)+1 FROM `prosodyarchive`))"
+ args[args_len+1], args[args_len+2], args[args_len+3], args[args_len+4] = query.before, args[1], args[2], args[3];
+ end
end
-local list_store = {};
-list_store.__index = list_store;
-function list_store:scan(username, from, to, jid, typ)
- user,store = username,self.store;
-
- local cols = {"from", "to", "jid", "typ"};
- local vals = { from , to , jid , typ };
- local stmt, err;
- local query = "SELECT * FROM `prosodyarchive` WHERE `host`=? AND `user`=? AND `store`=?";
-
- query = query.." ORDER BY time";
- --local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or "");
-
- return nil, "not-implemented"
+function archive_store:find(username, query)
+ query = query or {};
+ local user,store = username,self.store;
+ local total;
+ local ok, result = engine:transaction(function()
+ local sql_query = "SELECT `key`, `type`, `value`, `when`, `with` FROM `prosodyarchive` WHERE %s ORDER BY `sort_id` %s%s;";
+ local args = { host, user or "", store, };
+ local where = { "`host` = ?", "`user` = ?", "`store` = ?", };
+
+ archive_where(query, args, where);
+
+ -- Total matching
+ if query.total then
+ local stats = engine:select("SELECT COUNT(*) FROM `prosodyarchive` WHERE " .. t_concat(where, " AND "), unpack(args));
+ if stats then
+ local _total = stats()
+ total = _total and _total[1];
+ end
+ if query.limit == 0 then -- Skip the real query
+ return noop, total;
+ end
+ end
+
+ archive_where_id_range(query, args, where);
+
+ if query.limit then
+ args[#args+1] = query.limit;
+ end
+
+ sql_query = sql_query:format(t_concat(where, " AND "), query.reverse and "DESC" or "ASC", query.limit and " LIMIT ?" or "");
+ return engine:select(sql_query, unpack(args));
+ end);
+ if not ok then return ok, result end
+ return function()
+ local row = result();
+ if row ~= nil then
+ return row[1], deserialize(row[2], row[3]), row[4], row[5];
+ end
+ end, total;
end
+function archive_store:delete(username, query)
+ query = query or {};
+ local user,store = username,self.store;
+ return engine:transaction(function()
+ local sql_query = "DELETE FROM `prosodyarchive` WHERE %s;";
+ local args = { host, user or "", store, };
+ local where = { "`host` = ?", "`user` = ?", "`store` = ?", };
+ if user == true then
+ table.remove(args, 2);
+ table.remove(where, 2);
+ end
+ archive_where(query, args, where);
+ archive_where_id_range(query, args, where);
+ sql_query = sql_query:format(t_concat(where, " AND "));
+ return engine:delete(sql_query, unpack(args));
+ end);
+end
+
+local stores = {
+ keyval = keyval_store;
+ map = map_store;
+ archive = archive_store;
+};
+
+--- Implement storage driver API
+
+-- FIXME: Some of these operations need to operate on the archive store(s) too
+
local driver = {};
function driver:open(store, typ)
- if not typ then -- default key-value store
- return setmetatable({ store = store }, keyval_store);
+ local store_mt = stores[typ or "keyval"];
+ if store_mt then
+ return setmetatable({ store = store }, store_mt);
end
return nil, "unsupported-store";
end
function driver:stores(username)
- local sql = "SELECT DISTINCT `store` FROM `prosody` WHERE `host`=? AND `user`" ..
+ local query = "SELECT DISTINCT `store` FROM `prosody` WHERE `host`=? AND `user`" ..
(username == true and "!=?" or "=?");
if username == true or not username then
username = "";
end
- local stmt, err = dosql(sql, host, username);
- if not stmt then
- return rollback(nil, err);
- end
- local next = stmt:rows();
- return commit(function()
- local row = next();
- return row and row[1];
+ local ok, result = engine:transaction(function()
+ return engine:select(query, host, username);
end);
+ if not ok then return ok, result end
+ return iterator(result);
end
function driver:purge(username)
- local stmt, err = dosql("DELETE FROM `prosody` WHERE `host`=? AND `user`=?", host, username);
- if not stmt then return rollback(stmt, err); end
- local changed, err = stmt:affected();
- if not changed then return rollback(changed, err); end
- return commit(true, changed);
+ return engine:transaction(function()
+ local stmt,err = engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=?", host, username);
+ return true, err;
+ end);
+end
+
+--- Initialization
+
+
+local function create_table(name)
+ local Table, Column, Index = sql.Table, sql.Column, sql.Index;
+
+ local ProsodyTable = Table {
+ name= name or "prosody";
+ Column { name="host", type="TEXT", nullable=false };
+ Column { name="user", type="TEXT", nullable=false };
+ Column { name="store", type="TEXT", nullable=false };
+ Column { name="key", type="TEXT", nullable=false };
+ Column { name="type", type="TEXT", nullable=false };
+ Column { name="value", type="MEDIUMTEXT", nullable=false };
+ Index { name="prosody_index", "host", "user", "store", "key" };
+ };
+ engine:transaction(function()
+ ProsodyTable:create(engine);
+ end);
+
+ local ProsodyArchiveTable = Table {
+ name="prosodyarchive";
+ Column { name="sort_id", type="INTEGER", primary_key=true, auto_increment=true };
+ Column { name="host", type="TEXT", nullable=false };
+ Column { name="user", type="TEXT", nullable=false };
+ Column { name="store", type="TEXT", nullable=false };
+ Column { name="key", type="TEXT", nullable=false }; -- item id
+ Column { name="when", type="INTEGER", nullable=false }; -- timestamp
+ Column { name="with", type="TEXT", nullable=false }; -- related id
+ Column { name="type", type="TEXT", nullable=false };
+ Column { name="value", type="MEDIUMTEXT", nullable=false };
+ Index { name="prosodyarchive_index", unique = true, "host", "user", "store", "key" };
+ };
+ engine:transaction(function()
+ ProsodyArchiveTable:create(engine);
+ end);
+end
+
+local function upgrade_table(params, apply_changes)
+ local changes = false;
+ if params.driver == "MySQL" then
+ local success,err = engine:transaction(function()
+ local result = engine:execute("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'");
+ if result:rowcount() > 0 then
+ changes = true;
+ if apply_changes then
+ module:log("info", "Upgrading database schema...");
+ engine:execute("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT");
+ module:log("info", "Database table automatically upgraded");
+ end
+ end
+ return true;
+ end);
+ if not success then
+ module:log("error", "Failed to check/upgrade database schema (%s), please see "
+ .."http://prosody.im/doc/mysql for help",
+ err or "unknown error");
+ return false;
+ end
+
+ -- COMPAT w/pre-0.10: Upgrade table to UTF-8 if not already
+ local check_encoding_query = "SELECT `COLUMN_NAME`,`COLUMN_TYPE`,`TABLE_NAME` FROM `information_schema`.`columns` WHERE `TABLE_NAME` LIKE 'prosody%%' AND ( `CHARACTER_SET_NAME`!='%s' OR `COLLATION_NAME`!='%s_bin' );";
+ check_encoding_query = check_encoding_query:format(engine.charset, engine.charset);
+ success,err = engine:transaction(function()
+ local result = engine:execute(check_encoding_query);
+ local n_bad_columns = result:rowcount();
+ if n_bad_columns > 0 then
+ changes = true;
+ if apply_changes then
+ module:log("warn", "Found %d columns in prosody table requiring encoding change, updating now...", n_bad_columns);
+ local fix_column_query1 = "ALTER TABLE `%s` CHANGE `%s` `%s` BLOB;";
+ local fix_column_query2 = "ALTER TABLE `%s` CHANGE `%s` `%s` %s CHARACTER SET '%s' COLLATE '%s_bin';";
+ for row in result:rows() do
+ local column_name, column_type, table_name = unpack(row);
+ module:log("debug", "Fixing column %s in table %s", column_name, table_name);
+ engine:execute(fix_column_query1:format(table_name, column_name, column_name));
+ engine:execute(fix_column_query2:format(table_name, column_name, column_name, column_type, engine.charset, engine.charset));
+ end
+ module:log("info", "Database encoding upgrade complete!");
+ end
+ end
+ end);
+ success,err = engine:transaction(function() return engine:execute(check_encoding_query); end);
+ if not success then
+ module:log("error", "Failed to check/upgrade database encoding: %s", err or "unknown error");
+ return false;
+ end
+ end
+ return changes;
+end
+
+local function normalize_params(params)
+ if params.driver == "SQLite3" then
+ if params.database ~= ":memory:" then
+ params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
+ end
+ end
+ assert(params.driver and params.database, "Configuration error: Both the SQL driver and the database need to be specified");
+ return params;
end
-module:provides("storage", driver);
+function module.load()
+ if prosody.prosodyctl then return; end
+ local engines = module:shared("/*/sql/connections");
+ local params = normalize_params(module:get_option("sql", default_params));
+ engine = engines[sql.db2uri(params)];
+ if not engine then
+ module:log("debug", "Creating new engine");
+ engine = sql:create_engine(params, function (engine)
+ if module:get_option("sql_manage_tables", true) then
+ -- Automatically create table, ignore failure (table probably already exists)
+ -- FIXME: we should check in information_schema, etc.
+ create_table();
+ -- Check whether the table needs upgrading
+ if upgrade_table(params, false) then
+ module:log("error", "Old database format detected. Please run: prosodyctl mod_%s upgrade", module.name);
+ return false, "database upgrade needed";
+ end
+ end
+ end);
+ engines[sql.db2uri(params)] = engine;
+ end
+
+ module:provides("storage", driver);
+end
+
+function module.command(arg)
+ local config = require "core.configmanager";
+ local prosodyctl = require "util.prosodyctl";
+ local command = table.remove(arg, 1);
+ if command == "upgrade" then
+ -- We need to find every unique dburi in the config
+ local uris = {};
+ for host in pairs(prosody.hosts) do
+ local params = config.get(host, "sql") or default_params;
+ uris[sql.db2uri(params)] = params;
+ end
+ print("We will check and upgrade the following databases:\n");
+ for _, params in pairs(uris) do
+ print("", "["..params.driver.."] "..params.database..(params.host and " on "..params.host or ""));
+ end
+ print("");
+ print("Ensure you have working backups of the above databases before continuing! ");
+ if not prosodyctl.show_yesno("Continue with the database upgrade? [yN]") then
+ print("Ok, no upgrade. But you do have backups, don't you? ...don't you?? :-)");
+ return;
+ end
+ -- Upgrade each one
+ for _, params in pairs(uris) do
+ print("Checking "..params.database.."...");
+ engine = sql:create_engine(params);
+ upgrade_table(params, true);
+ end
+ print("All done!");
+ else
+ print("Unknown command: "..command);
+ end
+end
diff --git a/plugins/mod_storage_sql1.lua b/plugins/mod_storage_sql1.lua
new file mode 100644
index 00000000..a5bb5bfa
--- /dev/null
+++ b/plugins/mod_storage_sql1.lua
@@ -0,0 +1,414 @@
+
+--[[
+
+DB Tables:
+ Prosody - key-value, map
+ | host | user | store | key | type | value |
+ ProsodyArchive - list
+ | host | user | store | key | time | stanzatype | jsonvalue |
+
+Mapping:
+ Roster - Prosody
+ | host | user | "roster" | "contactjid" | type | value |
+ | host | user | "roster" | NULL | "json" | roster[false] data |
+ Account - Prosody
+ | host | user | "accounts" | "username" | type | value |
+
+ Offline - ProsodyArchive
+ | host | user | "offline" | "contactjid" | time | "message" | json|XML |
+
+]]
+
+local type = type;
+local tostring = tostring;
+local tonumber = tonumber;
+local pairs = pairs;
+local next = next;
+local setmetatable = setmetatable;
+local xpcall = xpcall;
+local json = require "util.json";
+local build_url = require"socket.url".build;
+
+local DBI;
+local connection;
+local host,user,store = module.host;
+local params = module:get_option("sql");
+
+local dburi;
+local connections = module:shared "/*/sql/connection-cache";
+
+local function db2uri(params)
+ return build_url{
+ scheme = params.driver,
+ user = params.username,
+ password = params.password,
+ host = params.host,
+ port = params.port,
+ path = params.database,
+ };
+end
+
+
+local resolve_relative_path = require "util.paths".resolve_relative_path;
+
+local function test_connection()
+ if not connection then return nil; end
+ if connection:ping() then
+ return true;
+ else
+ module:log("debug", "Database connection closed");
+ connection = nil;
+ connections[dburi] = nil;
+ end
+end
+local function connect()
+ if not test_connection() then
+ prosody.unlock_globals();
+ local dbh, err = DBI.Connect(
+ params.driver, params.database,
+ params.username, params.password,
+ params.host, params.port
+ );
+ prosody.lock_globals();
+ if not dbh then
+ module:log("debug", "Database connection failed: %s", tostring(err));
+ return nil, err;
+ end
+ module:log("debug", "Successfully connected to database");
+ dbh:autocommit(false); -- don't commit automatically
+ connection = dbh;
+
+ connections[dburi] = dbh;
+ end
+ return connection;
+end
+
+local function create_table()
+ if not module:get_option("sql_manage_tables", true) then
+ return;
+ end
+ local create_sql = "CREATE TABLE `prosody` (`host` TEXT, `user` TEXT, `store` TEXT, `key` TEXT, `type` TEXT, `value` TEXT);";
+ if params.driver == "PostgreSQL" then
+ create_sql = create_sql:gsub("`", "\"");
+ elseif params.driver == "MySQL" then
+ create_sql = create_sql:gsub("`value` TEXT", "`value` MEDIUMTEXT");
+ end
+
+ local stmt, err = connection:prepare(create_sql);
+ if stmt then
+ local ok = stmt:execute();
+ local commit_ok = connection:commit();
+ if ok and commit_ok then
+ module:log("info", "Initialized new %s database with prosody table", params.driver);
+ local index_sql = "CREATE INDEX `prosody_index` ON `prosody` (`host`, `user`, `store`, `key`)";
+ if params.driver == "PostgreSQL" then
+ index_sql = index_sql:gsub("`", "\"");
+ elseif params.driver == "MySQL" then
+ index_sql = index_sql:gsub("`([,)])", "`(20)%1");
+ end
+ local stmt, err = connection:prepare(index_sql);
+ local ok, commit_ok, commit_err;
+ if stmt then
+ ok, err = stmt:execute();
+ commit_ok, commit_err = connection:commit();
+ end
+ if not(ok and commit_ok) then
+ module:log("warn", "Failed to create index (%s), lookups may not be optimised", err or commit_err);
+ end
+ elseif params.driver == "MySQL" then -- COMPAT: Upgrade tables from 0.8.0
+ -- Failed to create, but check existing MySQL table here
+ local stmt = connection:prepare("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'");
+ local ok = stmt:execute();
+ local commit_ok = connection:commit();
+ if ok and commit_ok then
+ if stmt:rowcount() > 0 then
+ module:log("info", "Upgrading database schema...");
+ local stmt = connection:prepare("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT");
+ local ok, err = stmt:execute();
+ local commit_ok = connection:commit();
+ if ok and commit_ok then
+ module:log("info", "Database table automatically upgraded");
+ else
+ module:log("error", "Failed to upgrade database schema (%s), please see "
+ .."http://prosody.im/doc/mysql for help",
+ err or "unknown error");
+ end
+ end
+ repeat until not stmt:fetch();
+ end
+ end
+ elseif params.driver ~= "SQLite3" then -- SQLite normally fails to prepare for existing table
+ module:log("warn", "Prosody was not able to automatically check/create the database table (%s), "
+ .."see http://prosody.im/doc/modules/mod_storage_sql#table_management for help.",
+ err or "unknown error");
+ end
+end
+
+do -- process options to get a db connection
+ local ok;
+ prosody.unlock_globals();
+ ok, DBI = pcall(require, "DBI");
+ if not ok then
+ package.loaded["DBI"] = {};
+ module:log("error", "Failed to load the LuaDBI library for accessing SQL databases: %s", DBI);
+ module:log("error", "More information on installing LuaDBI can be found at http://prosody.im/doc/depends#luadbi");
+ end
+ prosody.lock_globals();
+ if not ok or not DBI.Connect then
+ return; -- Halt loading of this module
+ end
+
+ params = params or { driver = "SQLite3" };
+
+ if params.driver == "SQLite3" then
+ params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
+ end
+
+ assert(params.driver and params.database, "Both the SQL driver and the database need to be specified");
+
+ dburi = db2uri(params);
+ connection = connections[dburi];
+
+ assert(connect());
+
+ -- Automatically create table, ignore failure (table probably already exists)
+ create_table();
+end
+
+local function serialize(value)
+ local t = type(value);
+ if t == "string" or t == "boolean" or t == "number" then
+ return t, tostring(value);
+ elseif t == "table" then
+ local value,err = json.encode(value);
+ if value then return "json", value; end
+ return nil, err;
+ end
+ return nil, "Unhandled value type: "..t;
+end
+local function deserialize(t, value)
+ if t == "string" then return value;
+ elseif t == "boolean" then
+ if value == "true" then return true;
+ elseif value == "false" then return false; end
+ elseif t == "number" then return tonumber(value);
+ elseif t == "json" then
+ return json.decode(value);
+ end
+end
+
+local function dosql(sql, ...)
+ if params.driver == "PostgreSQL" then
+ sql = sql:gsub("`", "\"");
+ end
+ -- do prepared statement stuff
+ local stmt, err = connection:prepare(sql);
+ if not stmt and not test_connection() then error("connection failed"); end
+ if not stmt then module:log("error", "QUERY FAILED: %s %s", err, debug.traceback()); return nil, err; end
+ -- run query
+ local ok, err = stmt:execute(...);
+ if not ok and not test_connection() then error("connection failed"); end
+ if not ok then return nil, err; end
+
+ return stmt;
+end
+local function getsql(sql, ...)
+ return dosql(sql, host or "", user or "", store or "", ...);
+end
+local function setsql(sql, ...)
+ local stmt, err = getsql(sql, ...);
+ if not stmt then return stmt, err; end
+ return stmt:affected();
+end
+local function transact(...)
+ -- ...
+end
+local function rollback(...)
+ if connection then connection:rollback(); end -- FIXME check for rollback error?
+ return ...;
+end
+local function commit(...)
+ local success,err = connection:commit();
+ if not success then return nil, "SQL commit failed: "..tostring(err); end
+ return ...;
+end
+
+local function keyval_store_get()
+ local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?");
+ if not stmt then return rollback(nil, err); end
+
+ local haveany;
+ local result = {};
+ for row in stmt:rows(true) do
+ haveany = true;
+ local k = row.key;
+ local v = deserialize(row.type, row.value);
+ if k and v then
+ if k ~= "" then result[k] = v; elseif type(v) == "table" then
+ for a,b in pairs(v) do
+ result[a] = b;
+ end
+ end
+ end
+ end
+ return commit(haveany and result or nil);
+end
+local function keyval_store_set(data)
+ local affected, err = setsql("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?");
+ if not affected then return rollback(affected, err); end
+
+ if data and next(data) ~= nil then
+ local extradata = {};
+ for key, value in pairs(data) do
+ if type(key) == "string" and key ~= "" then
+ local t, value = serialize(value);
+ if not t then return rollback(t, value); end
+ local ok, err = setsql("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", key, t, value);
+ if not ok then return rollback(ok, err); end
+ else
+ extradata[key] = value;
+ end
+ end
+ if next(extradata) ~= nil then
+ local t, extradata = serialize(extradata);
+ if not t then return rollback(t, extradata); end
+ local ok, err = setsql("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", "", t, extradata);
+ if not ok then return rollback(ok, err); end
+ end
+ end
+ return commit(true);
+end
+
+local keyval_store = {};
+keyval_store.__index = keyval_store;
+function keyval_store:get(username)
+ user,store = username,self.store;
+ if not connection and not connect() then return nil, "Unable to connect to database"; end
+ local success, ret, err = xpcall(keyval_store_get, debug.traceback);
+ if not connection and connect() then
+ success, ret, err = xpcall(keyval_store_get, debug.traceback);
+ end
+ if success then return ret, err; else return rollback(nil, ret); end
+end
+function keyval_store:set(username, data)
+ user,store = username,self.store;
+ if not connection and not connect() then return nil, "Unable to connect to database"; end
+ local success, ret, err = xpcall(function() return keyval_store_set(data); end, debug.traceback);
+ if not connection and connect() then
+ success, ret, err = xpcall(function() return keyval_store_set(data); end, debug.traceback);
+ end
+ if success then return ret, err; else return rollback(nil, ret); end
+end
+function keyval_store:users()
+ local stmt, err = dosql("SELECT DISTINCT `user` FROM `prosody` WHERE `host`=? AND `store`=?", host, self.store);
+ if not stmt then
+ return rollback(nil, err);
+ end
+ local next = stmt:rows();
+ return commit(function()
+ local row = next();
+ return row and row[1];
+ end);
+end
+
+local function map_store_get(key)
+ local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or "");
+ if not stmt then return rollback(nil, err); end
+
+ local haveany;
+ local result = {};
+ for row in stmt:rows(true) do
+ haveany = true;
+ local k = row.key;
+ local v = deserialize(row.type, row.value);
+ if k and v then
+ if k ~= "" then result[k] = v; elseif type(v) == "table" then
+ for a,b in pairs(v) do
+ result[a] = b;
+ end
+ end
+ end
+ end
+ return commit(haveany and result[key] or nil);
+end
+local function map_store_set(key, data)
+ local affected, err = setsql("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or "");
+ if not affected then return rollback(affected, err); end
+
+ if data and next(data) ~= nil then
+ if type(key) == "string" and key ~= "" then
+ local t, value = serialize(data);
+ if not t then return rollback(t, value); end
+ local ok, err = setsql("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", key, t, value);
+ if not ok then return rollback(ok, err); end
+ else
+ -- TODO non-string keys
+ end
+ end
+ return commit(true);
+end
+
+local map_store = {};
+map_store.__index = map_store;
+function map_store:get(username, key)
+ user,store = username,self.store;
+ local success, ret, err = xpcall(function() return map_store_get(key); end, debug.traceback);
+ if success then return ret, err; else return rollback(nil, ret); end
+end
+function map_store:set(username, key, data)
+ user,store = username,self.store;
+ local success, ret, err = xpcall(function() return map_store_set(key, data); end, debug.traceback);
+ if success then return ret, err; else return rollback(nil, ret); end
+end
+
+local list_store = {};
+list_store.__index = list_store;
+function list_store:scan(username, from, to, jid, typ)
+ user,store = username,self.store;
+
+ local cols = {"from", "to", "jid", "typ"};
+ local vals = { from , to , jid , typ };
+ local stmt, err;
+ local query = "SELECT * FROM `prosodyarchive` WHERE `host`=? AND `user`=? AND `store`=?";
+
+ query = query.." ORDER BY time";
+ --local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or "");
+
+ return nil, "not-implemented"
+end
+
+local driver = {};
+
+function driver:open(store, typ)
+ if typ and typ ~= "keyval" then
+ return nil, "unsupported-store";
+ end
+ return setmetatable({ store = store }, keyval_store);
+end
+
+function driver:stores(username)
+ local sql = "SELECT DISTINCT `store` FROM `prosody` WHERE `host`=? AND `user`" ..
+ (username == true and "!=?" or "=?");
+ if username == true or not username then
+ username = "";
+ end
+ local stmt, err = dosql(sql, host, username);
+ if not stmt then
+ return rollback(nil, err);
+ end
+ local next = stmt:rows();
+ return commit(function()
+ local row = next();
+ return row and row[1];
+ end);
+end
+
+function driver:purge(username)
+ local stmt, err = dosql("DELETE FROM `prosody` WHERE `host`=? AND `user`=?", host, username);
+ if not stmt then return rollback(stmt, err); end
+ local changed, err = stmt:affected();
+ if not changed then return rollback(changed, err); end
+ return commit(true, changed);
+end
+
+module:provides("storage", driver);
diff --git a/plugins/storage/mod_xep0227.lua b/plugins/mod_storage_xep0227.lua
index 5d07a2ea..ef227ca3 100644
--- a/plugins/storage/mod_xep0227.lua
+++ b/plugins/mod_storage_xep0227.lua
@@ -7,28 +7,31 @@ local t_remove = table.remove;
local os_remove = os.remove;
local io_open = io.open;
+local paths = require"util.paths";
local st = require "util.stanza";
local parse_xml_real = require "util.xml".parse;
local function getXml(user, host)
local jid = user.."@"..host;
- local path = "data/"..jid..".xml";
+ local path = paths.join(prosody.paths.data, jid..".xml");
local f = io_open(path);
if not f then return; end
local s = f:read("*a");
+ f:close();
return parse_xml_real(s);
end
local function setXml(user, host, xml)
local jid = user.."@"..host;
- local path = "data/"..jid..".xml";
+ local path = paths.join(prosody.paths.data, jid..".xml");
+ local f, err = io_open(path, "w");
+ if not f then return f, err; end
if xml then
- local f = io_open(path, "w");
- if not f then return; end
local s = tostring(xml);
f:write(s);
f:close();
return true;
else
+ f:close();
return os_remove(path);
end
end
@@ -44,7 +47,7 @@ local function getUserElement(xml)
end
end
local function createOuterXml(user, host)
- return st.stanza("server-data", {xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'})
+ return st.stanza("server-data", {xmlns='urn:xmpp:pie:0'})
:tag("host", {jid=host})
:tag("user", {name = user});
end
@@ -63,19 +66,36 @@ end
local handlers = {};
+-- In order to support mod_auth_internal_hashed
+local extended = "http://prosody.im/protocol/extended-xep0227\1";
+
handlers.accounts = {
get = function(self, user)
- local user = getUserElement(getXml(user, self.host));
+ user = getUserElement(getXml(user, self.host));
if user and user.attr.password then
return { password = user.attr.password };
+ elseif user then
+ local data = {};
+ for k, v in pairs(user.attr) do
+ if k:sub(1, #extended) == extended then
+ data[k:sub(#extended+1)] = v;
+ end
+ end
+ return data;
end
end;
set = function(self, user, data)
- if data and data.password then
+ if data then
local xml = getXml(user, self.host);
if not xml then xml = createOuterXml(user, self.host); end
local usere = getUserElement(xml);
- usere.attr.password = data.password;
+ for k, v in pairs(data) do
+ if k == "password" then
+ usere.attr.password = v;
+ else
+ usere.attr[extended..k] = v;
+ end
+ end
return setXml(user, self.host, xml);
else
return setXml(user, self.host, nil);
@@ -84,7 +104,7 @@ handlers.accounts = {
};
handlers.vcard = {
get = function(self, user)
- local user = getUserElement(getXml(user, self.host));
+ user = getUserElement(getXml(user, self.host));
if user then
local vcard = user:get_child("vCard", 'vcard-temp');
if vcard then
@@ -113,7 +133,7 @@ handlers.vcard = {
};
handlers.private = {
get = function(self, user)
- local user = getUserElement(getXml(user, self.host));
+ user = getUserElement(getXml(user, self.host));
if user then
local private = user:get_child("query", "jabber:iq:private");
if private then
@@ -147,15 +167,10 @@ handlers.private = {
-----------------------------
local driver = {};
-function driver:open(host, datastore, typ)
- local instance = setmetatable({}, self);
- instance.host = host;
- instance.datastore = datastore;
+function driver:open(datastore, typ)
local handler = handlers[datastore];
- if not handler then return nil; end
- for key,val in pairs(handler) do
- instance[key] = val;
- end
+ if not handler then return nil, "unsupported-datastore"; end
+ local instance = setmetatable({ host = module.host; datastore = datastore; }, { __index = handler });
if instance.init then instance:init(); end
return instance;
end
diff --git a/plugins/mod_time.lua b/plugins/mod_time.lua
index cb69ebe7..ae7da916 100644
--- a/plugins/mod_time.lua
+++ b/plugins/mod_time.lua
@@ -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 --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua
index 2741b8d4..69aafe82 100644
--- a/plugins/mod_tls.lua
+++ b/plugins/mod_tls.lua
@@ -1,16 +1,16 @@
-- 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 config = require "core.configmanager";
local create_context = require "core.certmanager".create_context;
+local rawgetopt = require"core.configmanager".rawget;
local st = require "util.stanza";
-local c2s_require_encryption = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
+local c2s_require_encryption = module:get_option("c2s_require_encryption", module:get_option("require_encryption"));
local s2s_require_encryption = module:get_option("s2s_require_encryption");
local allow_s2s_tls = module:get_option("s2s_allow_encryption") ~= false;
local s2s_secure_auth = module:get_option("s2s_secure_auth");
@@ -22,6 +22,7 @@ end
local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls';
local starttls_attr = { xmlns = xmlns_starttls };
+local starttls_initiate= st.stanza("starttls", starttls_attr);
local starttls_proceed = st.stanza("proceed", starttls_attr);
local starttls_failure = st.stanza("failure", starttls_attr);
local c2s_feature = st.stanza("starttls", starttls_attr);
@@ -29,20 +30,56 @@ local s2s_feature = st.stanza("starttls", starttls_attr);
if c2s_require_encryption then c2s_feature:tag("required"):up(); end
if s2s_require_encryption then s2s_feature:tag("required"):up(); end
-local global_ssl_ctx = prosody.global_ssl_ctx;
-
local hosts = prosody.hosts;
local host = hosts[module.host];
+local ssl_ctx_c2s, ssl_ctx_s2sout, ssl_ctx_s2sin;
+local ssl_cfg_c2s, ssl_cfg_s2sout, ssl_cfg_s2sin;
+do
+ local NULL, err = {};
+ local modhost = module.host;
+ local parent = modhost:match("%.(.*)$");
+
+ local parent_ssl = rawgetopt(parent, "ssl") or NULL;
+ local host_ssl = rawgetopt(modhost, "ssl") or parent_ssl;
+
+ local global_c2s = rawgetopt("*", "c2s_ssl") or NULL;
+ local parent_c2s = rawgetopt(parent, "c2s_ssl") or NULL;
+ local host_c2s = rawgetopt(modhost, "c2s_ssl") or parent_c2s;
+
+ local global_s2s = rawgetopt("*", "s2s_ssl") or NULL;
+ local parent_s2s = rawgetopt(parent, "s2s_ssl") or NULL;
+ local host_s2s = rawgetopt(modhost, "s2s_ssl") or parent_s2s;
+
+ ssl_ctx_c2s, err, ssl_cfg_c2s = create_context(host.host, "server", host_c2s, host_ssl, global_c2s); -- for incoming client connections
+ if not ssl_ctx_c2s then module:log("error", "Error creating context for c2s: %s", err); end
+
+ ssl_ctx_s2sout, err, ssl_cfg_s2sout = create_context(host.host, "client", host_s2s, host_ssl, global_s2s); -- for outgoing server connections
+ if not ssl_ctx_s2sout then module:log("error", "Error creating contexts for s2sout: %s", err); end
+
+ ssl_ctx_s2sin, err, ssl_cfg_s2sin = create_context(host.host, "server", host_s2s, host_ssl, global_s2s); -- for incoming server connections
+ if not ssl_ctx_s2sin then module:log("error", "Error creating contexts for s2sin: %s", err); end
+end
+
local function can_do_tls(session)
+ if session.ssl_ctx == false or not session.conn.starttls then
+ return false;
+ elseif session.ssl_ctx then
+ return true;
+ end
if session.type == "c2s_unauthed" then
- return session.conn.starttls and host.ssl_ctx_in;
+ session.ssl_ctx = ssl_ctx_c2s;
+ session.ssl_cfg = ssl_cfg_c2s;
elseif session.type == "s2sin_unauthed" and allow_s2s_tls then
- return session.conn.starttls and host.ssl_ctx_in;
+ session.ssl_ctx = ssl_ctx_s2sin;
+ session.ssl_cfg = ssl_cfg_s2sin;
elseif session.direction == "outgoing" and allow_s2s_tls then
- return session.conn.starttls and host.ssl_ctx;
+ session.ssl_ctx = ssl_ctx_s2sout;
+ session.ssl_cfg = ssl_cfg_s2sout;
+ else
+ return false;
end
- return false;
+ return session.ssl_ctx;
end
-- Hook <starttls/>
@@ -51,9 +88,7 @@ module:hook("stanza/urn:ietf:params:xml:ns:xmpp-tls:starttls", function(event)
if can_do_tls(origin) then
(origin.sends2s or origin.send)(starttls_proceed);
origin:reset_stream();
- local host = origin.to_host or origin.host;
- local ssl_ctx = host and hosts[host].ssl_ctx_in or global_ssl_ctx;
- origin.conn:starttls(ssl_ctx);
+ origin.conn:starttls(origin.ssl_ctx);
origin.log("debug", "TLS negotiation started for %s...", origin.type);
origin.secure = false;
else
@@ -81,9 +116,9 @@ end);
-- For s2sout connections, start TLS if we can
module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
module:log("debug", "Received features element");
- if can_do_tls(session) and stanza:child_with_ns(xmlns_starttls) then
+ if can_do_tls(session) and stanza:get_child("starttls", xmlns_starttls) then
module:log("debug", "%s is offering TLS, taking up the offer...", session.to_host);
- session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
+ session.sends2s(starttls_initiate);
return true;
end
end, 500);
@@ -91,30 +126,7 @@ end, 500);
module:hook_stanza(xmlns_starttls, "proceed", function (session, stanza)
module:log("debug", "Proceeding with TLS on s2sout...");
session:reset_stream();
- local ssl_ctx = session.from_host and hosts[session.from_host].ssl_ctx or global_ssl_ctx;
- session.conn:starttls(ssl_ctx);
+ session.conn:starttls(session.ssl_ctx);
session.secure = false;
return true;
end);
-
-local function assert_log(ret, err)
- if not ret then
- module:log("error", "Unable to initialize TLS: %s", err);
- end
- return ret;
-end
-
-function module.load()
- local ssl_config = config.rawget(module.host, "ssl");
- if not ssl_config then
- local base_host = module.host:match("%.(.*)");
- ssl_config = config.get(base_host, "ssl");
- end
- host.ssl_ctx = assert_log(create_context(host.host, "client", ssl_config)); -- for outgoing connections
- host.ssl_ctx_in = assert_log(create_context(host.host, "server", ssl_config)); -- for incoming connections
-end
-
-function module.unload()
- host.ssl_ctx = nil;
- host.ssl_ctx_in = nil;
-end
diff --git a/plugins/mod_unknown.lua b/plugins/mod_unknown.lua
new file mode 100644
index 00000000..4d20b8ad
--- /dev/null
+++ b/plugins/mod_unknown.lua
@@ -0,0 +1,4 @@
+-- Unknown platform stub
+module:set_global();
+
+-- TODO Do things that make sense if we don't know about the platform
diff --git a/plugins/mod_uptime.lua b/plugins/mod_uptime.lua
index 3f275b2f..2e369b16 100644
--- a/plugins/mod_uptime.lua
+++ b/plugins/mod_uptime.lua
@@ -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 --git a/plugins/mod_vcard.lua b/plugins/mod_vcard.lua
index 26b30e3a..72f92ef7 100644
--- a/plugins/mod_vcard.lua
+++ b/plugins/mod_vcard.lua
@@ -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 --git a/plugins/mod_version.lua b/plugins/mod_version.lua
index d35103b6..be244beb 100644
--- a/plugins/mod_version.lua
+++ b/plugins/mod_version.lua
@@ -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 --git a/plugins/mod_watchregistrations.lua b/plugins/mod_watchregistrations.lua
index abca90bd..b7be5daf 100644
--- a/plugins/mod_watchregistrations.lua
+++ b/plugins/mod_watchregistrations.lua
@@ -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 --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua
new file mode 100644
index 00000000..418cd846
--- /dev/null
+++ b/plugins/mod_websocket.lua
@@ -0,0 +1,304 @@
+-- Prosody IM
+-- Copyright (C) 2012-2014 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+-- luacheck: ignore 431/log
+
+module:set_global();
+
+local add_task = require "util.timer".add_task;
+local add_filter = require "util.filters".add_filter;
+local sha1 = require "util.hashes".sha1;
+local base64 = require "util.encodings".base64.encode;
+local st = require "util.stanza";
+local parse_xml = require "util.xml".parse;
+local portmanager = require "core.portmanager";
+local sm_destroy_session = require"core.sessionmanager".destroy_session;
+local log = module._log;
+
+local websocket_frames = require"net.websocket.frames";
+local parse_frame = websocket_frames.parse;
+local build_frame = websocket_frames.build;
+local build_close = websocket_frames.build_close;
+local parse_close = websocket_frames.parse_close;
+
+local t_concat = table.concat;
+
+local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5);
+local consider_websocket_secure = module:get_option_boolean("consider_websocket_secure");
+local cross_domain = module:get_option("cross_domain_websocket");
+if cross_domain then
+ if cross_domain == true then
+ cross_domain = "*";
+ elseif type(cross_domain) == "table" then
+ cross_domain = t_concat(cross_domain, ", ");
+ end
+ if type(cross_domain) ~= "string" then
+ cross_domain = nil;
+ end
+end
+
+local xmlns_framing = "urn:ietf:params:xml:ns:xmpp-framing";
+local xmlns_streams = "http://etherx.jabber.org/streams";
+local xmlns_client = "jabber:client";
+local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
+
+module:depends("c2s")
+local sessions = module:shared("c2s/sessions");
+local c2s_listener = portmanager.get_service("c2s").listener;
+
+--- Session methods
+local function session_open_stream(session)
+ local attr = {
+ xmlns = xmlns_framing,
+ version = "1.0",
+ id = session.streamid or "",
+ from = session.host
+ };
+ session.send(st.stanza("open", attr));
+end
+
+local function session_close(session, reason)
+ local log = session.log or log;
+ if session.conn then
+ if session.notopen then
+ session:open_stream();
+ end
+ if reason then -- nil == no err, initiated by us, false == initiated by client
+ local stream_error = st.stanza("stream:error");
+ if type(reason) == "string" then -- assume stream error
+ stream_error:tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' });
+ elseif type(reason) == "table" then
+ if reason.condition then
+ stream_error:tag(reason.condition, stream_xmlns_attr):up();
+ if reason.text then
+ stream_error:tag("text", stream_xmlns_attr):text(reason.text):up();
+ end
+ if reason.extra then
+ stream_error:add_child(reason.extra);
+ end
+ elseif reason.name then -- a stanza
+ stream_error = reason;
+ end
+ end
+ log("debug", "Disconnecting client, <stream:error> is: %s", tostring(stream_error));
+ session.send(stream_error);
+ end
+
+ session.send(st.stanza("close", { xmlns = xmlns_framing }));
+ function session.send() return false; end
+
+ local reason = (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..">"), reason or "session closed");
+
+ -- Authenticated incoming stream may still be sending us stanzas, so wait for </stream:stream> from remote
+ local conn = session.conn;
+ if reason == 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);
+ conn:write(build_close(1000, "Stream closed"));
+ conn:close();
+ end
+ end);
+ else
+ sm_destroy_session(session, reason);
+ conn:write(build_close(1000, "Stream closed"));
+ conn:close();
+ end
+ end
+end
+
+
+--- Filters
+local function filter_open_close(data)
+ if not data:find(xmlns_framing, 1, true) then return data; end
+
+ local oc = parse_xml(data);
+ if not oc then return data; end
+ if oc.attr.xmlns ~= xmlns_framing then return data; end
+ if oc.name == "close" then return "</stream:stream>"; end
+ if oc.name == "open" then
+ oc.name = "stream:stream";
+ oc.attr.xmlns = nil;
+ oc.attr["xmlns:stream"] = xmlns_streams;
+ return oc:top_tag();
+ end
+
+ return data;
+end
+function handle_request(event)
+ local request, response = event.request, event.response;
+ local conn = response.conn;
+
+ if not request.headers.sec_websocket_key then
+ response.headers.content_type = "text/html";
+ return [[<!DOCTYPE html><html><head><title>Websocket</title></head><body>
+ <p>It works! Now point your WebSocket client to this URL to connect to Prosody.</p>
+ </body></html>]];
+ end
+
+ local wants_xmpp = false;
+ (request.headers.sec_websocket_protocol or ""):gsub("([^,]*),?", function (proto)
+ if proto == "xmpp" then wants_xmpp = true; end
+ end);
+
+ if not wants_xmpp then
+ return 501;
+ end
+
+ local function websocket_close(code, message)
+ conn:write(build_close(code, message));
+ conn:close();
+ end
+
+ local dataBuffer;
+ local function handle_frame(frame)
+ local opcode = frame.opcode;
+ local length = frame.length;
+ module: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
+ websocket_close(1002, "Reserved bits not zero");
+ return false;
+ end
+
+ if opcode == 0x8 then -- close frame
+ if length == 1 then
+ websocket_close(1002, "Close frame with payload, but too short for status code");
+ return false;
+ elseif length >= 2 then
+ local status_code = parse_close(frame.data)
+ if status_code < 1000 then
+ websocket_close(1002, "Closed with invalid status code");
+ return false;
+ elseif ((status_code > 1003 and status_code < 1007) or status_code > 1011) and status_code < 3000 then
+ websocket_close(1002, "Closed with reserved status code");
+ return false;
+ end
+ end
+ end
+
+ if opcode >= 0x8 then
+ if length > 125 then -- Control frame with too much payload
+ websocket_close(1002, "Payload too large");
+ return false;
+ end
+
+ if not frame.FIN then -- Fragmented control frame
+ websocket_close(1002, "Fragmented control frame");
+ return false;
+ end
+ end
+
+ if (opcode > 0x2 and opcode < 0x8) or (opcode > 0xA) then
+ websocket_close(1002, "Reserved opcode");
+ return false;
+ end
+
+ if opcode == 0x0 and not dataBuffer then
+ websocket_close(1002, "Unexpected continuation frame");
+ return false;
+ end
+
+ if (opcode == 0x1 or opcode == 0x2) and dataBuffer then
+ websocket_close(1002, "Continuation frame expected");
+ return false;
+ end
+
+ -- Valid cases
+ if opcode == 0x0 then -- Continuation frame
+ dataBuffer[#dataBuffer+1] = frame.data;
+ elseif opcode == 0x1 then -- Text frame
+ dataBuffer = {frame.data};
+ elseif opcode == 0x2 then -- Binary frame
+ websocket_close(1003, "Only text frames are supported");
+ return;
+ elseif opcode == 0x8 then -- Close request
+ websocket_close(1000, "Goodbye");
+ return;
+ elseif opcode == 0x9 then -- Ping frame
+ frame.opcode = 0xA;
+ conn:write(build_frame(frame));
+ return "";
+ elseif opcode == 0xA then -- Pong frame
+ module:log("warn", "Received unexpected pong frame: " .. tostring(frame.data));
+ return "";
+ else
+ log("warn", "Received frame with unsupported opcode %i", opcode);
+ return "";
+ end
+
+ if frame.FIN then
+ local data = t_concat(dataBuffer, "");
+ dataBuffer = nil;
+ return data;
+ end
+ return "";
+ end
+
+ conn:setlistener(c2s_listener);
+ c2s_listener.onconnect(conn);
+
+ local session = sessions[conn];
+
+ session.secure = consider_websocket_secure or session.secure;
+
+ session.open_stream = session_open_stream;
+ session.close = session_close;
+
+ local frameBuffer = "";
+ add_filter(session, "bytes/in", function(data)
+ local cache = {};
+ frameBuffer = frameBuffer .. data;
+ local frame, length = parse_frame(frameBuffer);
+
+ while frame do
+ frameBuffer = frameBuffer:sub(length + 1);
+ local result = handle_frame(frame);
+ if not result then return; end
+ cache[#cache+1] = filter_open_close(result);
+ frame, length = parse_frame(frameBuffer);
+ end
+ return t_concat(cache, "");
+ end);
+
+ add_filter(session, "stanzas/out", function(stanza)
+ local attr = stanza.attr;
+ attr.xmlns = attr.xmlns or xmlns_client;
+ if stanza.name:find("^stream:") then
+ attr["xmlns:stream"] = attr["xmlns:stream"] or xmlns_streams;
+ end
+ return stanza;
+ end);
+
+ add_filter(session, "bytes/out", function(data)
+ return build_frame({ FIN = true, opcode = 0x01, data = tostring(data)});
+ end);
+
+ response.status_code = 101;
+ response.headers.upgrade = "websocket";
+ response.headers.connection = "Upgrade";
+ response.headers.sec_webSocket_accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
+ response.headers.sec_webSocket_protocol = "xmpp";
+ response.headers.access_control_allow_origin = cross_domain;
+
+ return "";
+end
+
+function module.add_host(module)
+ module:depends("http");
+ module:provides("http", {
+ name = "websocket";
+ default_path = "xmpp-websocket";
+ route = {
+ ["GET"] = handle_request;
+ ["GET /"] = handle_request;
+ };
+ });
+end
diff --git a/plugins/mod_welcome.lua b/plugins/mod_welcome.lua
index e498f0b3..9c0c821b 100644
--- a/plugins/mod_welcome.lua
+++ b/plugins/mod_welcome.lua
@@ -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 --git a/plugins/mod_windows.lua b/plugins/mod_windows.lua
new file mode 100644
index 00000000..8085fd88
--- /dev/null
+++ b/plugins/mod_windows.lua
@@ -0,0 +1,4 @@
+-- Windows platform stub
+module:set_global();
+
+-- TODO Add Windows-specific things here
diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua
index acc2da0d..69351504 100644
--- a/plugins/muc/mod_muc.lua
+++ b/plugins/muc/mod_muc.lua
@@ -1,11 +1,12 @@
-- 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 array = require "util.array";
if module:get_host_type() ~= "component" then
error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0);
@@ -16,12 +17,15 @@ local muc_name = module:get_option("name");
if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end
local restrict_room_creation = module:get_option("restrict_room_creation");
if restrict_room_creation then
- if restrict_room_creation == true then
+ if restrict_room_creation == true then
restrict_room_creation = "admin";
elseif restrict_room_creation ~= "admin" and restrict_room_creation ~= "local" then
restrict_room_creation = nil;
end
end
+local lock_rooms = module:get_option_boolean("muc_room_locking", false);
+local lock_room_timeout = module:get_option_number("muc_room_lock_timeout", 300);
+
local muclib = module:require "muc";
local muc_new_room = muclib.new_room;
local jid_split = require "util.jid".split;
@@ -40,12 +44,17 @@ local room_configs = module:open_store("config");
-- Configurable options
muclib.set_max_history_length(module:get_option_number("max_history_messages"));
+module:depends("disco");
+module:add_identity("conference", "text", muc_name);
+module:add_feature("http://jabber.org/protocol/muc");
+
local function is_admin(jid)
return um_is_admin(jid, module.host);
end
-local _set_affiliation = muc_new_room.room_mt.set_affiliation;
-local _get_affiliation = muc_new_room.room_mt.get_affiliation;
+room_mt = muclib.room_mt; -- Yes, global.
+local _set_affiliation = room_mt.set_affiliation;
+local _get_affiliation = room_mt.get_affiliation;
function muclib.room_mt:get_affiliation(jid)
if is_admin(jid) then return "owner"; end
return _get_affiliation(self, jid);
@@ -83,6 +92,16 @@ function create_room(jid)
room.route_stanza = room_route_stanza;
room.save = room_save;
rooms[jid] = room;
+ if lock_rooms then
+ room.locked = true;
+ if lock_room_timeout and lock_room_timeout > 0 then
+ module:add_timer(lock_room_timeout, function ()
+ if room.locked then
+ room:destroy(); -- Not unlocked in time
+ end
+ end);
+ end
+ end
module:fire_event("muc-room-created", { room = room });
return room;
end
@@ -107,20 +126,15 @@ local host_room = muc_new_room(muc_host);
host_room.route_stanza = room_route_stanza;
host_room.save = room_save;
-local function get_disco_info(stanza)
- return st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
- :tag("identity", {category='conference', type='text', name=muc_name}):up()
- :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
-end
-local function get_disco_items(stanza)
- local reply = st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
+module:hook("host-disco-items", function(event)
+ local reply = event.reply;
+ module:log("debug", "host-disco-items called");
for jid, room in pairs(rooms) do
- if not room:is_hidden() then
+ if not room:get_hidden() then
reply:tag("item", {jid=jid, name=room:get_name()}):up();
end
end
- return reply; -- TODO cache disco reply
-end
+end);
local function handle_to_domain(event)
local origin, stanza = event.origin, event.stanza;
@@ -129,11 +143,7 @@ local function handle_to_domain(event)
if stanza.name == "iq" and type == "get" then
local xmlns = stanza.tags[1].attr.xmlns;
local node = stanza.tags[1].attr.node;
- if xmlns == "http://jabber.org/protocol/disco#info" and not node then
- origin.send(get_disco_info(stanza));
- elseif xmlns == "http://jabber.org/protocol/disco#items" and not node then
- origin.send(get_disco_items(stanza));
- elseif xmlns == "http://jabber.org/protocol/muc#unique" then
+ if xmlns == "http://jabber.org/protocol/muc#unique" then
origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions
else
origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
@@ -220,7 +230,8 @@ function shutdown_component()
if not saved then
local stanza = st.presence({type = "unavailable"})
:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
- :tag("item", { affiliation='none', role='none' }):up();
+ :tag("item", { affiliation='none', role='none' }):up()
+ :tag("status", { code = "332"}):up();
for roomjid, room in pairs(rooms) do
shutdown_room(room, stanza);
end
@@ -229,3 +240,39 @@ function shutdown_component()
end
module.unload = shutdown_component;
module:hook_global("server-stopping", shutdown_component);
+
+-- Ad-hoc commands
+module:depends("adhoc")
+local t_concat = table.concat;
+local keys = require "util.iterators".keys;
+local adhoc_new = module:require "adhoc".new;
+local adhoc_initial = require "util.adhoc".new_initial_data_form;
+local dataforms_new = require "util.dataforms".new;
+
+local destroy_rooms_layout = dataforms_new {
+ title = "Destroy rooms";
+ instructions = "Select the rooms to destroy";
+
+ { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" };
+ { name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"};
+};
+
+local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function()
+ return { rooms = array.collect(keys(rooms)):sort() };
+end, function(fields, errors)
+ if errors then
+ local errmsg = {};
+ for name, err in pairs(errors) do
+ errmsg[#errmsg + 1] = name .. ": " .. err;
+ end
+ return { status = "completed", error = { message = t_concat(errmsg, "\n") } };
+ end
+ for _, room in ipairs(fields.rooms) do
+ rooms[room]:destroy();
+ rooms[room] = nil;
+ end
+ return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") };
+end);
+local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin");
+
+module:provides("adhoc", destroy_rooms_desc);
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
index 5879c256..552b9e49 100644
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -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.
--
@@ -27,28 +27,16 @@ local muc_domain = nil; --module:get_host();
local default_history_length, max_history_length = 20, math.huge;
------------
-local function filter_xmlns_from_array(array, filters)
- local count = 0;
- for i=#array,1,-1 do
- local attr = array[i].attr;
- if filters[attr and attr.xmlns] then
- t_remove(array, i);
- count = count + 1;
- end
- end
- return count;
-end
-local function filter_xmlns_from_stanza(stanza, filters)
- if filters then
- if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then
- return stanza, filter_xmlns_from_array(stanza, filters);
- end
+local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
+local function presence_filter(tag)
+ if presence_filters[tag.attr.xmlns] then
+ return nil;
end
- return stanza, 0;
+ return tag;
end
-local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
+
local function get_filtered_presence(stanza)
- return filter_xmlns_from_stanza(st.clone(stanza):reset(), presence_filters);
+ return st.clone(stanza):maptags(presence_filter);
end
local kickable_error_conditions = {
["gone"] = true;
@@ -72,17 +60,6 @@ local function is_kickable_error(stanza)
local cond = get_error_condition(stanza);
return kickable_error_conditions[cond] and cond;
end
-local function getUsingPath(stanza, path, getText)
- local tag = stanza;
- for _, name in ipairs(path) do
- if type(tag) ~= 'table' then return; end
- tag = tag:child_with_name(name);
- end
- if tag and getText then tag = table.concat(tag); end
- return tag;
-end
-local function getTag(stanza, path) return getUsingPath(stanza, path); end
-local function getText(stanza, path) return getUsingPath(stanza, path, true); end
-----------
local room_mt = {};
@@ -98,8 +75,8 @@ function room_mt:get_default_role(affiliation)
elseif affiliation == "member" then
return "participant";
elseif not affiliation then
- if not self:is_members_only() then
- return self:is_moderated() and "visitor" or "participant";
+ if not self:get_members_only() then
+ return self:get_moderated() and "visitor" or "participant";
end
end
end
@@ -130,18 +107,21 @@ function room_mt:broadcast_message(stanza, historic)
end
stanza.attr.to = to;
if historic then -- add to history
- local history = self._data['history'];
- if not history then history = {}; self._data['history'] = history; end
- stanza = st.clone(stanza);
- stanza.attr.to = "";
- local stamp = datetime.datetime();
- stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
- stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
- local entry = { stanza = stanza, stamp = stamp };
- t_insert(history, entry);
- while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
+ return self:save_to_history(stanza)
end
end
+function room_mt:save_to_history(stanza)
+ local history = self._data['history'];
+ if not history then history = {}; self._data['history'] = history; end
+ stanza = st.clone(stanza);
+ stanza.attr.to = "";
+ local stamp = datetime.datetime();
+ stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
+ stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
+ local entry = { stanza = stanza, stamp = stamp };
+ t_insert(history, entry);
+ while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
+end
function room_mt:broadcast_except_nick(stanza, nick)
for rnick, occupant in pairs(self._occupants) do
if rnick ~= nick then
@@ -170,10 +150,10 @@ function room_mt:send_history(to, stanza)
if history then
local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc");
local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
-
+
local maxchars = history_tag and tonumber(history_tag.attr.maxchars);
if maxchars then maxchars = math.floor(maxchars); end
-
+
local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history);
if not history_tag then maxstanzas = 20; end
@@ -186,7 +166,7 @@ function room_mt:send_history(to, stanza)
local n = 0;
local charcount = 0;
-
+
for i=#history,1,-1 do
local entry = history[i];
if maxchars then
@@ -207,6 +187,8 @@ function room_mt:send_history(to, stanza)
self:_route_stanza(msg);
end
end
+end
+function room_mt:send_subject(to)
if self._data['subject'] then
self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject']));
end
@@ -214,21 +196,28 @@ end
function room_mt:get_disco_info(stanza)
local count = 0; for _ in pairs(self._occupants) do count = count + 1; end
- return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
+ local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info")
:tag("identity", {category="conference", type="text", name=self:get_name()}):up()
:tag("feature", {var="http://jabber.org/protocol/muc"}):up()
:tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up()
- :tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
- :tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up()
- :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up()
- :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up()
+ :tag("feature", {var=self:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
+ :tag("feature", {var=self:get_members_only() and "muc_membersonly" or "muc_open"}):up()
+ :tag("feature", {var=self:get_persistent() and "muc_persistent" or "muc_temporary"}):up()
+ :tag("feature", {var=self:get_hidden() and "muc_hidden" or "muc_public"}):up()
:tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up()
- :add_child(dataform.new({
- { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" },
- { name = "muc#roominfo_description", label = "Description", value = "" },
- { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }
- }):form({["muc#roominfo_description"] = self:get_description()}, 'result'))
;
+ local dataform = dataform.new({
+ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" },
+ { name = "muc#roominfo_description", label = "Description", value = "" },
+ { name = "muc#roominfo_occupants", label = "Number of occupants", value = "" }
+ });
+ local formdata = {
+ ["muc#roominfo_description"] = self:get_description(),
+ ["muc#roominfo_occupants"] = tostring(count),
+ };
+ module:fire_event("muc-disco#info", { room = self, reply = reply, form = dataform, formdata = formdata });
+ reply:add_child(dataform:form(formdata, 'result'))
+ return reply;
end
function room_mt:get_disco_items(stanza)
local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
@@ -238,7 +227,6 @@ function room_mt:get_disco_items(stanza)
return reply;
end
function room_mt:set_subject(current_nick, subject)
- -- TODO check nick's authority
if subject == "" then subject = nil; end
self._data['subject'] = subject;
self._data['subject_from'] = current_nick;
@@ -296,7 +284,7 @@ function room_mt:set_moderated(moderated)
if self.save then self:save(true); end
end
end
-function room_mt:is_moderated()
+function room_mt:get_moderated()
return self._data.moderated;
end
function room_mt:set_members_only(members_only)
@@ -306,7 +294,7 @@ function room_mt:set_members_only(members_only)
if self.save then self:save(true); end
end
end
-function room_mt:is_members_only()
+function room_mt:get_members_only()
return self._data.members_only;
end
function room_mt:set_persistent(persistent)
@@ -316,7 +304,7 @@ function room_mt:set_persistent(persistent)
if self.save then self:save(true); end
end
end
-function room_mt:is_persistent()
+function room_mt:get_persistent()
return self._data.persistent;
end
function room_mt:set_hidden(hidden)
@@ -326,9 +314,15 @@ function room_mt:set_hidden(hidden)
if self.save then self:save(true); end
end
end
-function room_mt:is_hidden()
+function room_mt:get_hidden()
return self._data.hidden;
end
+function room_mt:get_public()
+ return not self:get_hidden();
+end
+function room_mt:set_public(public)
+ return self:set_hidden(not public);
+end
function room_mt:set_changesubject(changesubject)
changesubject = changesubject and true or nil;
if self._data.changesubject ~= changesubject then
@@ -351,12 +345,25 @@ function room_mt:set_historylength(length)
end
+local valid_whois = { moderators = true, anyone = true };
+
+function room_mt:set_whois(whois)
+ if valid_whois[whois] and self._data.whois ~= whois then
+ self._data.whois = whois;
+ if self.save then self:save(true); end
+ end
+end
+
+function room_mt:get_whois()
+ return self._data.whois;
+end
+
local function construct_stanza_id(room, stanza)
local from_jid, to_nick = stanza.attr.from, stanza.attr.to;
local from_nick = room._jid_nick[from_jid];
local occupant = room._occupants[to_nick];
local to_jid = occupant.jid;
-
+
return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid));
end
local function deconstruct_stanza_id(room, stanza)
@@ -485,6 +492,12 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
log("debug", "%s joining as %s", from, to);
if not next(self._affiliations) then -- new room, no owners
self._affiliations[jid_bare(from)] = "owner";
+ if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then
+ self.locked = nil; -- Older groupchat protocol doesn't lock
+ end
+ elseif self.locked then -- Deny entry
+ origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
+ return;
end
local affiliation = self:get_affiliation(from);
local role = self:get_default_role(affiliation)
@@ -506,9 +519,13 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
if self._data.whois == 'anyone' then
pr:tag("status", {code='100'}):up();
end
+ if self.locked then
+ pr:tag("status", {code='201'}):up();
+ end
pr.attr.to = from;
self:_route_stanza(pr);
self:send_history(from, stanza);
+ self:send_subject(from);
elseif not affiliation then -- registration required for entering members-only room
local reply = st.error_reply(stanza, "auth", "registration-required"):up();
reply.tags[1].attr.code = "407";
@@ -560,6 +577,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
end
stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
else -- message
+ stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up();
stanza.attr.from = current_nick;
for jid in pairs(o_data.sessions) do
stanza.attr.to = jid;
@@ -575,11 +593,11 @@ end
function room_mt:send_form(origin, stanza)
origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
- :add_child(self:get_form_layout():form())
+ :add_child(self:get_form_layout(stanza.attr.from):form())
);
end
-function room_mt:get_form_layout()
+function room_mt:get_form_layout(actor)
local form = dataform.new({
title = "Configuration for "..self.jid,
instructions = "Complete and submit this form to configure the room.",
@@ -604,13 +622,13 @@ function room_mt:get_form_layout()
name = 'muc#roomconfig_persistentroom',
type = 'boolean',
label = 'Make Room Persistent?',
- value = self:is_persistent()
+ value = self:get_persistent()
},
{
name = 'muc#roomconfig_publicroom',
type = 'boolean',
label = 'Make Room Publicly Searchable?',
- value = not self:is_hidden()
+ value = not self:get_hidden()
},
{
name = 'muc#roomconfig_changesubject',
@@ -637,13 +655,13 @@ function room_mt:get_form_layout()
name = 'muc#roomconfig_moderatedroom',
type = 'boolean',
label = 'Make Room Moderated?',
- value = self:is_moderated()
+ value = self:get_moderated()
},
{
name = 'muc#roomconfig_membersonly',
type = 'boolean',
label = 'Make Room Members-Only?',
- value = self:is_members_only()
+ value = self:get_members_only()
},
{
name = 'muc#roomconfig_historylength',
@@ -652,14 +670,9 @@ function room_mt:get_form_layout()
value = tostring(self:get_historylength())
}
});
- return module:fire_event("muc-config-form", { room = self, form = form }) or form;
+ return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form;
end
-local valid_whois = {
- moderators = true,
- anyone = true,
-}
-
function room_mt:process_form(origin, stanza)
local query = stanza.tags[1];
local form;
@@ -668,85 +681,52 @@ function room_mt:process_form(origin, stanza)
if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end
- local fields = self:get_form_layout():data(form);
- if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return; end
-
- local dirty = false
-
- local event = { room = self, fields = fields, changed = dirty };
- module:fire_event("muc-config-submitted", event);
- dirty = event.changed or dirty;
-
- local name = fields['muc#roomconfig_roomname'];
- if name ~= self:get_name() then
- self:set_name(name);
+ local fields, errors, present = self:get_form_layout(stanza.attr.from):data(form);
+ if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then
+ origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration"));
+ return;
end
- local description = fields['muc#roomconfig_roomdesc'];
- if description ~= self:get_description() then
- self:set_description(description);
- end
-
- local persistent = fields['muc#roomconfig_persistentroom'];
- dirty = dirty or (self:is_persistent() ~= persistent)
- module:log("debug", "persistent=%s", tostring(persistent));
-
- local moderated = fields['muc#roomconfig_moderatedroom'];
- dirty = dirty or (self:is_moderated() ~= moderated)
- module:log("debug", "moderated=%s", tostring(moderated));
+ local changed = {};
- local membersonly = fields['muc#roomconfig_membersonly'];
- dirty = dirty or (self:is_members_only() ~= membersonly)
- module:log("debug", "membersonly=%s", tostring(membersonly));
-
- local public = fields['muc#roomconfig_publicroom'];
- dirty = dirty or (self:is_hidden() ~= (not public and true or nil))
-
- local changesubject = fields['muc#roomconfig_changesubject'];
- dirty = dirty or (self:get_changesubject() ~= (not changesubject and true or nil))
- module:log('debug', 'changesubject=%s', changesubject and "true" or "false")
-
- local historylength = tonumber(fields['muc#roomconfig_historylength']);
- dirty = dirty or (historylength and (self:get_historylength() ~= historylength));
- module:log('debug', 'historylength=%s', historylength)
-
-
- local whois = fields['muc#roomconfig_whois'];
- if not valid_whois[whois] then
- origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'"));
- return;
+ local function handle_option(name, field, allowed)
+ if not present[field] then return; end
+ local new = fields[field];
+ if allowed and not allowed[new] then return; end
+ if new == self["get_"..name](self) then return; end
+ changed[name] = true;
+ self["set_"..name](self, new);
end
- local whois_changed = self._data.whois ~= whois
- self._data.whois = whois
- module:log('debug', 'whois=%s', whois)
- local password = fields['muc#roomconfig_roomsecret'];
- if self:get_password() ~= password then
- self:set_password(password);
- end
- self:set_moderated(moderated);
- self:set_members_only(membersonly);
- self:set_persistent(persistent);
- self:set_hidden(not public);
- self:set_changesubject(changesubject);
- self:set_historylength(historylength);
+ local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option };
+ module:fire_event("muc-config-submitted", event);
+
+ handle_option("name", "muc#roomconfig_roomname");
+ handle_option("description", "muc#roomconfig_roomdesc");
+ handle_option("persistent", "muc#roomconfig_persistentroom");
+ handle_option("moderated", "muc#roomconfig_moderatedroom");
+ handle_option("members_only", "muc#roomconfig_membersonly");
+ handle_option("public", "muc#roomconfig_publicroom");
+ handle_option("changesubject", "muc#roomconfig_changesubject");
+ handle_option("historylength", "muc#roomconfig_historylength");
+ handle_option("whois", "muc#roomconfig_whois", valid_whois);
+ handle_option("password", "muc#roomconfig_roomsecret");
if self.save then self:save(true); end
+ if self.locked then
+ module:fire_event("muc-room-unlocked", { room = self });
+ self.locked = nil;
+ end
origin.send(st.reply(stanza));
- if dirty or whois_changed then
+ if next(changed) then
local msg = st.message({type='groupchat', from=self.jid})
- :tag('x', {xmlns='http://jabber.org/protocol/muc#user'});
-
- if dirty then
- msg.tags[1]:tag('status', {code = '104'}):up();
- end
- if whois_changed then
- local code = (whois == 'moderators') and "173" or "172";
+ :tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
+ :tag('status', {code = '104'}):up();
+ if changed.whois then
+ local code = (self:get_whois() == 'moderators') and "173" or "172";
msg.tags[1]:tag('status', {code = code}):up();
end
- msg:up();
-
self:broadcast_message(msg, false)
end
end
@@ -882,7 +862,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
end
elseif stanza.name == "message" and type == "groupchat" then
- local from, to = stanza.attr.from, stanza.attr.to;
+ local from = stanza.attr.from;
local current_nick = self._jid_nick[from];
local occupant = self._occupants[current_nick];
if not occupant then -- not in room
@@ -892,11 +872,11 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
else
local from = stanza.attr.from;
stanza.attr.from = current_nick;
- local subject = getText(stanza, {"subject"});
+ local subject = stanza:get_child_text("subject");
if subject then
if occupant.role == "moderator" or
( self._data.changesubject and occupant.role == "participant" ) then -- and participant
- self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
+ self:set_subject(current_nick, subject);
else
stanza.attr.from = from;
origin.send(st.error_reply(stanza, "auth", "forbidden"));
@@ -944,7 +924,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
:tag('body') -- Add a plain message for clients which don't support invites
:text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or ""))
:up();
- if self:is_members_only() and not self:get_affiliation(_invitee) then
+ if self:get_members_only() and not self:get_affiliation(_invitee) then
log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to);
self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from])
end
diff --git a/plugins/sql.lib.lua b/plugins/sql.lib.lua
deleted file mode 100644
index 005ee45d..00000000
--- a/plugins/sql.lib.lua
+++ /dev/null
@@ -1,9 +0,0 @@
-local cache = module:shared("/*/sql.lib/util.sql");
-
-if not cache._M then
- prosody.unlock_globals();
- cache._M = require "util.sql";
- prosody.lock_globals();
-end
-
-return cache._M;
diff --git a/plugins/storage/sqlbasic.lib.lua b/plugins/storage/sqlbasic.lib.lua
deleted file mode 100644
index ab3648f9..00000000
--- a/plugins/storage/sqlbasic.lib.lua
+++ /dev/null
@@ -1,97 +0,0 @@
-
--- Basic SQL driver
--- This driver stores data as simple key-values
-
-local ser = require "util.serialization".serialize;
-local envload = require "util.envload".envload;
-local deser = function(data)
- module:log("debug", "deser: %s", tostring(data));
- if not data then return nil; end
- local f = envload("return "..data, nil, {});
- if not f then return nil; end
- local s, d = pcall(f);
- if not s then return nil; end
- return d;
-end;
-
-local driver = {};
-driver.__index = driver;
-
-driver.item_table = "item";
-driver.list_table = "list";
-
-function driver:prepare(sql)
- module:log("debug", "query: %s", sql);
- local err;
- if not self.sqlcache then self.sqlcache = {}; end
- local r = self.sqlcache[sql];
- if r then return r; end
- r, err = self.connection:prepare(sql);
- if not r then error("Unable to prepare SQL statement: "..err); end
- self.sqlcache[sql] = r;
- return r;
-end
-
-function driver:load(username, host, datastore)
- local select = self:prepare("select data from "..self.item_table.." where username=? and host=? and datastore=?");
- select:execute(username, host, datastore);
- local row = select:fetch();
- return row and deser(row[1]) or nil;
-end
-
-function driver:store(username, host, datastore, data)
- if not data or next(data) == nil then
- local delete = self:prepare("delete from "..self.item_table.." where username=? and host=? and datastore=?");
- delete:execute(username, host, datastore);
- return true;
- else
- local d = self:load(username, host, datastore);
- if d then -- update
- local update = self:prepare("update "..self.item_table.." set data=? where username=? and host=? and datastore=?");
- return update:execute(ser(data), username, host, datastore);
- else -- insert
- local insert = self:prepare("insert into "..self.item_table.." values (?, ?, ?, ?)");
- return insert:execute(username, host, datastore, ser(data));
- end
- end
-end
-
-function driver:list_append(username, host, datastore, data)
- if not data then return; end
- local insert = self:prepare("insert into "..self.list_table.." values (?, ?, ?, ?)");
- return insert:execute(username, host, datastore, ser(data));
-end
-
-function driver:list_store(username, host, datastore, data)
- -- remove existing data
- local delete = self:prepare("delete from "..self.list_table.." where username=? and host=? and datastore=?");
- delete:execute(username, host, datastore);
- if data and next(data) ~= nil then
- -- add data
- for _, d in ipairs(data) do
- self:list_append(username, host, datastore, ser(d));
- end
- end
- return true;
-end
-
-function driver:list_load(username, host, datastore)
- local select = self:prepare("select data from "..self.list_table.." where username=? and host=? and datastore=?");
- select:execute(username, host, datastore);
- local r = {};
- for row in select:rows() do
- table.insert(r, deser(row[1]));
- end
- return r;
-end
-
-local _M = {};
-function _M.new(dbtype, dbname, ...)
- local d = {};
- setmetatable(d, driver);
- local dbh = get_database(dbtype, dbname, ...);
- --d:set_connection(dbh);
- d.connection = dbh;
- return d;
-end
-return _M;
diff --git a/plugins/storage/xep227store.lib.lua b/plugins/storage/xep227store.lib.lua
deleted file mode 100644
index 5ef8df54..00000000
--- a/plugins/storage/xep227store.lib.lua
+++ /dev/null
@@ -1,168 +0,0 @@
-
-local st = require "util.stanza";
-
-local function getXml(user, host)
- local jid = user.."@"..host;
- local path = "data/"..jid..".xml";
- local f = io.open(path);
- if not f then return; end
- local s = f:read("*a");
- return parse_xml_real(s);
-end
-local function setXml(user, host, xml)
- local jid = user.."@"..host;
- local path = "data/"..jid..".xml";
- if xml then
- local f = io.open(path, "w");
- if not f then return; end
- local s = tostring(xml);
- f:write(s);
- f:close();
- return true;
- else
- return os.remove(path);
- end
-end
-local function getUserElement(xml)
- if xml and xml.name == "server-data" then
- local host = xml.tags[1];
- if host and host.name == "host" then
- local user = host.tags[1];
- if user and user.name == "user" then
- return user;
- end
- end
- end
-end
-local function createOuterXml(user, host)
- return st.stanza("server-data", {xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'})
- :tag("host", {jid=host})
- :tag("user", {name = user});
-end
-local function removeFromArray(array, value)
- for i,item in ipairs(array) do
- if item == value then
- table.remove(array, i);
- return;
- end
- end
-end
-local function removeStanzaChild(s, child)
- removeFromArray(s.tags, child);
- removeFromArray(s, child);
-end
-
-local handlers = {};
-
-handlers.accounts = {
- get = function(self, user)
- local user = getUserElement(getXml(user, self.host));
- if user and user.attr.password then
- return { password = user.attr.password };
- end
- end;
- set = function(self, user, data)
- if data and data.password then
- local xml = getXml(user, self.host);
- if not xml then xml = createOuterXml(user, self.host); end
- local usere = getUserElement(xml);
- usere.attr.password = data.password;
- return setXml(user, self.host, xml);
- else
- return setXml(user, self.host, nil);
- end
- end;
-};
-handlers.vcard = {
- get = function(self, user)
- local user = getUserElement(getXml(user, self.host));
- if user then
- local vcard = user:get_child("vCard", 'vcard-temp');
- if vcard then
- return st.preserialize(vcard);
- end
- end
- end;
- set = function(self, user, data)
- local xml = getXml(user, self.host);
- local usere = xml and getUserElement(xml);
- if usere then
- local vcard = usere:get_child("vCard", 'vcard-temp');
- if vcard then
- removeStanzaChild(usere, vcard);
- elseif not data then
- return true;
- end
- if data then
- vcard = st.deserialize(data);
- usere:add_child(vcard);
- end
- return setXml(user, self.host, xml);
- end
- return true;
- end;
-};
-handlers.private = {
- get = function(self, user)
- local user = getUserElement(getXml(user, self.host));
- if user then
- local private = user:get_child("query", "jabber:iq:private");
- if private then
- local r = {};
- for _, tag in ipairs(private.tags) do
- r[tag.name..":"..tag.attr.xmlns] = st.preserialize(tag);
- end
- return r;
- end
- end
- end;
- set = function(self, user, data)
- local xml = getXml(user, self.host);
- local usere = xml and getUserElement(xml);
- if usere then
- local private = usere:get_child("query", 'jabber:iq:private');
- if private then removeStanzaChild(usere, private); end
- if data and next(data) ~= nil then
- private = st.stanza("query", {xmlns='jabber:iq:private'});
- for _,tag in pairs(data) do
- private:add_child(st.deserialize(tag));
- end
- usere:add_child(private);
- end
- return setXml(user, self.host, xml);
- end
- return true;
- end;
-};
-
------------------------------
-local driver = {};
-driver.__index = driver;
-
-function driver:open(host, datastore, typ)
- local cache_key = host.." "..datastore;
- if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end
- local instance = setmetatable({}, self);
- instance.host = host;
- instance.datastore = datastore;
- local handler = handlers[datastore];
- if not handler then return nil; end
- for key,val in pairs(handler) do
- instance[key] = val;
- end
- if instance.init then instance:init(); end
- self.ds_cache[cache_key] = instance;
- return instance;
-end
-
------------------------------
-local _M = {};
-
-function _M.new()
- local instance = setmetatable({}, driver);
- instance.__index = instance;
- instance.ds_cache = {};
- return instance;
-end
-
-return _M;
diff --git a/prosody b/prosody
index 446dbfb7..6a3b50db 100755
--- a/prosody
+++ b/prosody
@@ -43,6 +43,12 @@ if CFG_DATADIR then
end
end
+if #arg > 0 and arg[1] ~= "--config" then
+ print("Unknown command-line option: "..tostring(arg[1]));
+ print("Perhaps you meant to use prosodyctl instead?");
+ return 1;
+end
+
-- Global 'prosody' object
local prosody = { events = require "util.events".new(); };
_G.prosody = prosody;
@@ -121,6 +127,7 @@ end
function load_libraries()
-- Load socket framework
+ socket = require "socket";
server = require "net.server"
end
@@ -151,9 +158,12 @@ function sandbox_require()
-- for neat sandboxing of modules
local _realG = _G;
local _real_require = require;
- if not getfenv then
+ local getfenv = getfenv or function (f)
-- FIXME: This is a hack to replace getfenv() in Lua 5.2
- function getfenv(f) return debug.getupvalue(debug.getinfo(f or 1).func, 1); end
+ local name, env = debug.getupvalue(debug.getinfo(f or 1).func, 1);
+ if name == "_ENV" then
+ return env;
+ end
end
function require(...)
local curr_env = getfenv(2);
@@ -262,18 +272,16 @@ function init_global_state()
end
-- Function to initiate prosody shutdown
- function prosody.shutdown(reason)
+ function prosody.shutdown(reason, code)
log("info", "Shutting down: %s", reason or "unknown reason");
prosody.shutdown_reason = reason;
- prosody.events.fire_event("server-stopping", {reason = reason});
+ prosody.shutdown_code = code;
+ prosody.events.fire_event("server-stopping", {
+ reason = reason;
+ code = code;
+ });
server.setquitting(true);
end
-
- -- Load SSL settings from config, and create a ctx table
- local certmanager = require "core.certmanager";
- local global_ssl_ctx = certmanager.create_context("*", "server");
- prosody.global_ssl_ctx = global_ssl_ctx;
-
end
function read_version()
@@ -295,6 +303,7 @@ function load_secondary_libraries()
require "util.import"
require "util.xmppstream"
require "core.stanza_router"
+ require "core.statsmanager"
require "core.hostmanager"
require "core.portmanager"
require "core.modulemanager"
@@ -373,8 +382,10 @@ function loop()
prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
end
+ local sleep = require"socket".sleep;
+
while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
- socket.sleep(0.2);
+ sleep(0.2);
end
end
@@ -411,3 +422,4 @@ cleanup();
prosody.events.fire_event("server-stopped");
log("info", "Shutdown complete");
+os.exit(prosody.shutdown_code)
diff --git a/prosody.cfg.lua.dist b/prosody.cfg.lua.dist
index 23032932..d2af75a0 100644
--- a/prosody.cfg.lua.dist
+++ b/prosody.cfg.lua.dist
@@ -4,7 +4,7 @@
-- website at http://prosody.im/doc/configure
--
-- Tip: You can check that the syntax of this file is correct
--- when you have finished by running: luac -p prosody.cfg.lua
+-- when you have finished by running: prosodyctl check config
-- If there are any errors, it will let you know what and where
-- they are, otherwise it will keep quiet.
--
@@ -24,7 +24,7 @@ admins = { }
-- Enable use of libevent for better performance under high load
-- For more information see: http://prosody.im/doc/libevent
---use_libevent = true;
+--use_libevent = true
-- This is the list of modules Prosody will load on startup.
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
@@ -43,7 +43,7 @@ modules_enabled = {
"vcard"; -- Allow users to set vCards
-- These are commented by default as they have a performance impact
- --"privacy"; -- Support privacy lists
+ --"blocklist"; -- Allow users to block communications with other users
--"compression"; -- Stream compression
-- Nice to have
@@ -63,14 +63,13 @@ modules_enabled = {
--"http_files"; -- Serve static files from a directory over HTTP
-- Other specific functionality
- --"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
--"groups"; -- Shared roster support
--"announce"; -- Send announcement to all online users
--"welcome"; -- Welcome users who register accounts
--"watchregistrations"; -- Alert admins of registrations
--"motd"; -- Send a message to users when they log in
--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
-};
+}
-- These modules are auto-loaded, but should you want
-- to disable them then uncomment them here:
@@ -78,11 +77,12 @@ modules_disabled = {
-- "offline"; -- Store offline messages
-- "c2s"; -- Handle client connections
-- "s2s"; -- Handle server-to-server connections
-};
+ -- "posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
+}
-- Disable account creation by default, for security
-- For more information see http://prosody.im/doc/creating_accounts
-allow_registration = false;
+allow_registration = false
-- These are the SSL/TLS-related settings. If you don't want
-- to use SSL/TLS, you may comment or remove this
@@ -94,7 +94,7 @@ ssl = {
-- Force clients to use encrypted connections? This option will
-- prevent clients from authenticating unless they are using encryption.
-c2s_require_encryption = false
+c2s_require_encryption = true
-- Force certificate authentication for server-to-server connections?
-- This provides ideal security, but requires servers you communicate
diff --git a/prosodyctl b/prosodyctl
index 4c3ae981..95e091de 100755
--- a/prosodyctl
+++ b/prosodyctl
@@ -233,6 +233,7 @@ local function make_host(hostname)
type = "local",
events = prosody.events,
modules = {},
+ sessions = {},
users = require "core.usermanager".new_null_provider(hostname)
};
end
@@ -244,13 +245,14 @@ end
local modulemanager = require "core.modulemanager"
local prosodyctl = require "util.prosodyctl"
-require "socket"
+local socket = require "socket"
-----------------------
-- FIXME: Duplicate code waiting for util.startup
function read_version()
-- Try to determine version
local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
+ prosody.version = "unknown";
if version_file then
prosody.version = version_file:read("*a"):gsub("%s*$", "");
version_file:close();
@@ -258,7 +260,9 @@ function read_version()
prosody.version = "hg:"..prosody.version;
end
else
- prosody.version = "unknown";
+ local hg = require"util.mercurial";
+ local hgid = hg.check_id(CFG_SOURCEDIR or ".");
+ if hgid then prosody.version = "hg:" .. hgid; end
end
end
@@ -320,7 +324,7 @@ function commands.passwd(arg)
show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]);
return 1;
end
- local user, host = jid_split(arg[1])
+ local user, host = jid_split(arg[1]);
if not user and host then
show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
show_usage [[passwd user@host]]
@@ -413,7 +417,11 @@ function commands.start(arg)
local ok, ret = prosodyctl.start();
if ok then
- if config.get("*", "daemonize") ~= false then
+ local daemonize = config.get("*", "daemonize");
+ if daemonize == nil then
+ daemonize = prosody.installed;
+ end
+ if daemonize then
local i=1;
while true do
local ok, running = prosodyctl.isrunning();
@@ -524,16 +532,32 @@ function commands.about(arg)
return 1;
end
+ local pwd = ".";
+ local lfs = require "lfs";
local array = require "util.array";
local keys = require "util.iterators".keys;
+ local hg = require"util.mercurial";
+ local relpath = config.resolve_relative_path;
print("Prosody "..(prosody.version or "(unknown version)"));
print("");
print("# Prosody directories");
- print("Data directory: ", CFG_DATADIR or "./");
- print("Plugin directory:", CFG_PLUGINDIR or "./");
- print("Config directory:", CFG_CONFIGDIR or "./");
- print("Source directory:", CFG_SOURCEDIR or "./");
+ print("Data directory: "..relpath(pwd, data_path));
+ print("Config directory: "..relpath(pwd, CFG_CONFIGDIR or "."));
+ print("Source directory: "..relpath(pwd, CFG_SOURCEDIR or "."));
+ print("Plugin directories:")
+ print(" "..(prosody.paths.plugins:gsub("([^;]+);?", function(path)
+ local opath = path;
+ path = config.resolve_relative_path(pwd, path);
+ local hgid, hgrepo = hg.check_id(path);
+ if not hgid and hgrepo then
+ return path.." - "..hgrepo .."!\n ";
+ end
+ -- 010452cfaf53 is the first commit in the prosody-modules repository
+ hgrepo = hgrepo == "010452cfaf53" and "prosody-modules";
+ return path..(hgid and " - "..(hgrepo or "HG").." rev: "..hgid or "")
+ .."\n ";
+ end)));
print("");
print("# Lua environment");
print("Lua version: ", _G._VERSION);
@@ -555,6 +579,8 @@ function commands.about(arg)
print("");
print("# Lua module versions");
local module_versions, longest_name = {}, 8;
+ local luaevent =dependencies.softreq"luaevent";
+ local ssl = dependencies.softreq"ssl";
for name, module in pairs(package.loaded) do
if type(module) == "table" and rawget(module, "_VERSION")
and name ~= "_G" and not name:match("%.") then
@@ -650,40 +676,65 @@ local lfs;
local cert_commands = {};
-local function ask_overwrite(filename)
- return lfs.attributes(filename) and not show_yesno("Overwrite "..filename .. "?");
+-- If a file already exists, ask if the user wants to use it or replace it
+-- Backups the old file if replaced
+local function use_existing(filename)
+ local attrs = lfs.attributes(filename);
+ if attrs then
+ if show_yesno(filename .. " exists, do you want to replace it? [y/n]") then
+ local backup = filename..".bkp~"..os.date("%FT%T", attrs.change);
+ os.rename(filename, backup);
+ show_message(filename.." backed up to "..backup);
+ else
+ -- Use the existing file
+ return true;
+ end
+ end
end
function cert_commands.config(arg)
if #arg >= 1 and arg[1] ~= "--help" then
local conf_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".cnf";
- if ask_overwrite(conf_filename) then
+ if use_existing(conf_filename) then
return nil, conf_filename;
end
+ local distinguished_name;
+ if arg[#arg]:find("^/") then
+ distinguished_name = table.remove(arg);
+ end
local conf = openssl.config.new();
conf:from_prosody(hosts, config, arg);
- show_message("Please provide details to include in the certificate config file.");
- show_message("Leave the field empty to use the default value or '.' to exclude the field.")
- for i, k in ipairs(openssl._DN_order) do
- local v = conf.distinguished_name[k];
- if v then
- local nv;
- if k == "commonName" then
- v = arg[1]
- elseif k == "emailAddress" then
- v = "xmpp@" .. arg[1];
- elseif k == "countryName" then
- local tld = arg[1]:match"%.([a-z]+)$";
- if tld and #tld == 2 and tld ~= "uk" then
- v = tld:upper();
+ if distinguished_name then
+ local dn = {};
+ for k, v in distinguished_name:gmatch("/([^=/]+)=([^/]+)") do
+ table.insert(dn, k);
+ dn[k] = v;
+ end
+ conf.distinguished_name = dn;
+ else
+ show_message("Please provide details to include in the certificate config file.");
+ show_message("Leave the field empty to use the default value or '.' to exclude the field.")
+ for i, k in ipairs(openssl._DN_order) do
+ local v = conf.distinguished_name[k];
+ if v then
+ local nv;
+ if k == "commonName" then
+ v = arg[1]
+ elseif k == "emailAddress" then
+ v = "xmpp@" .. arg[1];
+ elseif k == "countryName" then
+ local tld = arg[1]:match"%.([a-z]+)$";
+ if tld and #tld == 2 and tld ~= "uk" then
+ v = tld:upper();
+ end
end
+ nv = show_prompt(("%s (%s):"):format(k, nv or v));
+ nv = (not nv or nv == "") and v or nv;
+ if nv:find"[\192-\252][\128-\191]+" then
+ conf.req.string_mask = "utf8only"
+ end
+ conf.distinguished_name[k] = nv ~= "." and nv or nil;
end
- nv = show_prompt(("%s (%s):"):format(k, nv or v));
- nv = (not nv or nv == "") and v or nv;
- if nv:find"[\192-\252][\128-\191]+" then
- conf.req.string_mask = "utf8only"
- end
- conf.distinguished_name[k] = nv ~= "." and nv or nil;
end
end
local conf_file, err = io.open(conf_filename, "w");
@@ -705,7 +756,7 @@ end
function cert_commands.key(arg)
if #arg >= 1 and arg[1] ~= "--help" then
local key_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".key";
- if ask_overwrite(key_filename) then
+ if use_existing(key_filename) then
return nil, key_filename;
end
os.remove(key_filename); -- This file, if it exists is unlikely to have write permissions
@@ -727,12 +778,12 @@ end
function cert_commands.request(arg)
if #arg >= 1 and arg[1] ~= "--help" then
local req_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".req";
- if ask_overwrite(req_filename) then
+ if use_existing(req_filename) then
return nil, req_filename;
end
local _, key_filename = cert_commands.key({arg[1]});
local _, conf_filename = cert_commands.config(arg);
- if openssl.req{new=true, key=key_filename, utf8=true, config=conf_filename, out=req_filename} then
+ if openssl.req{new=true, key=key_filename, utf8=true, sha256=true, config=conf_filename, out=req_filename} then
show_message("Certificate request written to ".. req_filename);
else
show_message("There was a problem, see OpenSSL output");
@@ -745,7 +796,7 @@ end
function cert_commands.generate(arg)
if #arg >= 1 and arg[1] ~= "--help" then
local cert_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".crt";
- if ask_overwrite(cert_filename) then
+ if use_existing(cert_filename) then
return nil, cert_filename;
end
local _, key_filename = cert_commands.key({arg[1]});
@@ -753,8 +804,10 @@ function cert_commands.generate(arg)
local ret;
if key_filename and conf_filename and cert_filename
and openssl.req{new=true, x509=true, nodes=true, key=key_filename,
- days=365, sha1=true, utf8=true, config=conf_filename, out=cert_filename} then
+ days=365, sha256=true, utf8=true, config=conf_filename, out=cert_filename} then
show_message("Certificate written to ".. cert_filename);
+ print();
+ show_message(("Example config:\n\nssl = {\n\tcertificate = %q;\n\tkey = %q;\n}"):format(cert_filename, key_filename));
else
show_message("There was a problem, see OpenSSL output");
end
@@ -783,6 +836,444 @@ function commands.cert(arg)
show_usage("cert config|request|generate|key", "Helpers for generating X.509 certificates and keys.")
end
+function commands.check(arg)
+ if arg[1] == "--help" then
+ show_usage([[check]], [[Perform basic checks on your Prosody installation]]);
+ return 1;
+ end
+ local what = table.remove(arg, 1);
+ local array, set = require "util.array", require "util.set";
+ local it = require "util.iterators";
+ local ok = true;
+ local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end
+ local function enabled_hosts() return it.filter(disabled_hosts, pairs(config.getconfig())); end
+ if not what or what == "disabled" then
+ local disabled_hosts = set.new();
+ for host, host_options in it.filter("*", pairs(config.getconfig())) do
+ if host_options.enabled == false then
+ disabled_hosts:add(host);
+ end
+ end
+ if not disabled_hosts:empty() then
+ local msg = "Checks will be skipped for these disabled hosts: %s";
+ if what then msg = "These hosts are disabled: %s"; end
+ show_warning(msg, tostring(disabled_hosts));
+ if what then return 0; end
+ print""
+ end
+ end
+ if not what or what == "config" then
+ print("Checking config...");
+ local deprecated = set.new({
+ "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", "require_encryption",
+ });
+ local known_global_options = set.new({
+ "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize",
+ "umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings",
+ "network_backend", "http_default_host",
+ });
+ local config = config.getconfig();
+ -- Check that we have any global options (caused by putting a host at the top)
+ if it.count(it.filter("log", pairs(config["*"]))) == 0 then
+ ok = false;
+ print("");
+ print(" No global options defined. Perhaps you have put a host definition at the top")
+ print(" of the config file? They should be at the bottom, see http://prosody.im/doc/configure#overview");
+ end
+ if it.count(enabled_hosts()) == 0 then
+ ok = false;
+ print("");
+ if it.count(it.filter("*", pairs(config))) == 0 then
+ print(" No hosts are defined, please add at least one VirtualHost section")
+ elseif config["*"]["enabled"] == false then
+ print(" No hosts are enabled. Remove enabled = false from the global section or put enabled = true under at least one VirtualHost section")
+ else
+ print(" All hosts are disabled. Remove enabled = false from at least one VirtualHost section")
+ end
+ end
+ if not config["*"].modules_enabled then
+ print(" No global modules_enabled is set?");
+ local suggested_global_modules;
+ for host, options in enabled_hosts() do
+ if not options.component_module and options.modules_enabled then
+ suggested_global_modules = set.intersection(suggested_global_modules or set.new(options.modules_enabled), set.new(options.modules_enabled));
+ end
+ end
+ if not suggested_global_modules:empty() then
+ print(" Consider moving these modules into modules_enabled in the global section:")
+ print(" "..tostring(suggested_global_modules / function (x) return ("%q"):format(x) end));
+ end
+ print();
+ end
+ -- Check for global options under hosts
+ local global_options = set.new(it.to_array(it.keys(config["*"])));
+ local deprecated_global_options = set.intersection(global_options, deprecated);
+ if not deprecated_global_options:empty() then
+ print("");
+ print(" You have some deprecated options in the global section:");
+ print(" "..tostring(deprecated_global_options))
+ ok = false;
+ end
+ for host, options in enabled_hosts() do
+ local host_options = set.new(it.to_array(it.keys(options)));
+ local misplaced_options = set.intersection(host_options, known_global_options);
+ for name in pairs(options) do
+ if name:match("^interfaces?")
+ or name:match("_ports?$") or name:match("_interfaces?$")
+ or (name:match("_ssl$") and not name:match("^[cs]2s_ssl$")) then
+ misplaced_options:add(name);
+ end
+ end
+ if not misplaced_options:empty() then
+ ok = false;
+ print("");
+ local n = it.count(misplaced_options);
+ print(" You have "..n.." option"..(n>1 and "s " or " ").."set under "..host.." that should be");
+ print(" in the global section of the config file, above any VirtualHost or Component definitions,")
+ print(" see http://prosody.im/doc/configure#overview for more information.")
+ print("");
+ print(" You need to move the following option"..(n>1 and "s" or "")..": "..table.concat(it.to_array(misplaced_options), ", "));
+ end
+ local subdomain = host:match("^[^.]+");
+ if not(host_options:contains("component_module")) and (subdomain == "jabber" or subdomain == "xmpp"
+ or subdomain == "chat" or subdomain == "im") then
+ print("");
+ print(" Suggestion: If "..host.. " is a new host with no real users yet, consider renaming it now to");
+ print(" "..host:gsub("^[^.]+%.", "")..". You can use SRV records to redirect XMPP clients and servers to "..host..".");
+ print(" For more information see: http://prosody.im/doc/dns");
+ end
+ end
+ local all_modules = set.new(config["*"].modules_enabled);
+ local all_options = set.new(it.to_array(it.keys(config["*"])));
+ for host in enabled_hosts() do
+ all_options:include(set.new(it.to_array(it.keys(config[host]))));
+ all_modules:include(set.new(config[host].modules_enabled));
+ end
+ for mod in all_modules do
+ if mod:match("^mod_") then
+ print("");
+ print(" Modules in modules_enabled should not have the 'mod_' prefix included.");
+ print(" Change '"..mod.."' to '"..mod:match("^mod_(.*)").."'.");
+ elseif mod:match("^auth_") then
+ print("");
+ print(" Authentication modules should not be added to modules_enabled,");
+ print(" but be specified in the 'authentication' option.");
+ print(" Remove '"..mod.."' from modules_enabled and instead add");
+ print(" authentication = '"..mod:match("^auth_(.*)").."'");
+ print(" For more information see https://prosody.im/doc/authentication");
+ elseif mod:match("^storage_") then
+ print("");
+ print(" storage modules should not be added to modules_enabled,");
+ print(" but be specified in the 'storage' option.");
+ print(" Remove '"..mod.."' from modules_enabled and instead add");
+ print(" storage = '"..mod:match("^storage_(.*)").."'");
+ print(" For more information see https://prosody.im/doc/storage");
+ end
+ end
+ local ssl = dependencies.softreq"ssl";
+ if not ssl then
+ if not set.intersection(all_options, set.new({"require_encryption", "c2s_require_encryption", "s2s_require_encryption"})):empty() then
+ print("");
+ print(" You require encryption but LuaSec is not available.");
+ print(" Connections will fail.");
+ ok = false;
+ end
+ elseif not ssl.loadcertificate then
+ if all_options:contains("s2s_secure_auth") then
+ print("");
+ print(" You have set s2s_secure_auth but your version of LuaSec does ");
+ print(" not support certificate validation, so all s2s connections will");
+ print(" fail.");
+ ok = false;
+ elseif all_options:contains("s2s_secure_domains") then
+ local secure_domains = set.new();
+ for host in enabled_hosts() do
+ if config[host].s2s_secure_auth == true then
+ secure_domains:add("*");
+ else
+ secure_domains:include(set.new(config[host].s2s_secure_domains));
+ end
+ end
+ if not secure_domains:empty() then
+ print("");
+ print(" You have set s2s_secure_domains but your version of LuaSec does ");
+ print(" not support certificate validation, so s2s connections to/from ");
+ print(" these domains will fail.");
+ ok = false;
+ end
+ end
+ end
+
+ print("Done.\n");
+ end
+ if not what or what == "dns" then
+ local dns = require "net.dns";
+ local idna = require "util.encodings".idna;
+ local ip = require "util.ip";
+ local c2s_ports = set.new(config.get("*", "c2s_ports") or {5222});
+ local s2s_ports = set.new(config.get("*", "s2s_ports") or {5269});
+
+ local c2s_srv_required, s2s_srv_required;
+ if not c2s_ports:contains(5222) then
+ c2s_srv_required = true;
+ end
+ if not s2s_ports:contains(5269) then
+ s2s_srv_required = true;
+ end
+
+ local problem_hosts = set.new();
+
+ local external_addresses, internal_addresses = set.new(), set.new();
+
+ local fqdn = socket.dns.tohostname(socket.dns.gethostname());
+ if fqdn then
+ local res = dns.lookup(idna.to_ascii(fqdn), "A");
+ if res then
+ for _, record in ipairs(res) do
+ external_addresses:add(record.a);
+ end
+ end
+ local res = dns.lookup(idna.to_ascii(fqdn), "AAAA");
+ if res then
+ for _, record in ipairs(res) do
+ external_addresses:add(record.aaaa);
+ end
+ end
+ end
+
+ local local_addresses = require"util.net".local_addresses() or {};
+
+ for addr in it.values(local_addresses) do
+ if not ip.new_ip(addr).private then
+ external_addresses:add(addr);
+ else
+ internal_addresses:add(addr);
+ end
+ end
+
+ if external_addresses:empty() then
+ print("");
+ print(" Failed to determine the external addresses of this server. Checks may be inaccurate.");
+ c2s_srv_required, s2s_srv_required = true, true;
+ end
+
+ local v6_supported = not not socket.tcp6;
+
+ for host, host_options in enabled_hosts() do
+ local all_targets_ok, some_targets_ok = true, false;
+
+ local is_component = not not host_options.component_module;
+ print("Checking DNS for "..(is_component and "component" or "host").." "..host.."...");
+ local target_hosts = set.new();
+ if not is_component then
+ local res = dns.lookup("_xmpp-client._tcp."..idna.to_ascii(host)..".", "SRV");
+ if res then
+ for _, record in ipairs(res) do
+ target_hosts:add(record.srv.target);
+ if not c2s_ports:contains(record.srv.port) then
+ print(" SRV target "..record.srv.target.." contains unknown client port: "..record.srv.port);
+ end
+ end
+ else
+ if c2s_srv_required then
+ print(" No _xmpp-client SRV record found for "..host..", but it looks like you need one.");
+ all_targst_ok = false;
+ else
+ target_hosts:add(host);
+ end
+ end
+ end
+ local res = dns.lookup("_xmpp-server._tcp."..idna.to_ascii(host)..".", "SRV");
+ if res then
+ for _, record in ipairs(res) do
+ target_hosts:add(record.srv.target);
+ if not s2s_ports:contains(record.srv.port) then
+ print(" SRV target "..record.srv.target.." contains unknown server port: "..record.srv.port);
+ end
+ end
+ else
+ if s2s_srv_required then
+ print(" No _xmpp-server SRV record found for "..host..", but it looks like you need one.");
+ all_targets_ok = false;
+ else
+ target_hosts:add(host);
+ end
+ end
+ if target_hosts:empty() then
+ target_hosts:add(host);
+ end
+
+ if target_hosts:contains("localhost") then
+ print(" Target 'localhost' cannot be accessed from other servers");
+ target_hosts:remove("localhost");
+ end
+
+ local modules = set.new(it.to_array(it.values(host_options.modules_enabled or {})))
+ + set.new(it.to_array(it.values(config.get("*", "modules_enabled") or {})))
+ + set.new({ config.get(host, "component_module") });
+
+ if modules:contains("proxy65") then
+ local proxy65_target = config.get(host, "proxy65_address") or host;
+ local A, AAAA = dns.lookup(idna.to_ascii(proxy65_target), "A"), dns.lookup(idna.to_ascii(proxy65_target), "AAAA");
+ local prob = {};
+ if not A then
+ table.insert(prob, "A");
+ end
+ if v6_supported and not AAAA then
+ table.insert(prob, "AAAA");
+ end
+ if #prob > 0 then
+ print(" File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/").." record. Create one or set 'proxy65_address' to the correct host/IP.");
+ end
+ end
+
+ for host in target_hosts do
+ local host_ok_v4, host_ok_v6;
+ local res = dns.lookup(idna.to_ascii(host), "A");
+ if res then
+ for _, record in ipairs(res) do
+ if external_addresses:contains(record.a) then
+ some_targets_ok = true;
+ host_ok_v4 = true;
+ elseif internal_addresses:contains(record.a) then
+ host_ok_v4 = true;
+ some_targets_ok = true;
+ print(" "..host.." A record points to internal address, external connections might fail");
+ else
+ print(" "..host.." A record points to unknown address "..record.a);
+ all_targets_ok = false;
+ end
+ end
+ end
+ local res = dns.lookup(idna.to_ascii(host), "AAAA");
+ if res then
+ for _, record in ipairs(res) do
+ if external_addresses:contains(record.aaaa) then
+ some_targets_ok = true;
+ host_ok_v6 = true;
+ elseif internal_addresses:contains(record.aaaa) then
+ host_ok_v6 = true;
+ some_targets_ok = true;
+ print(" "..host.." AAAA record points to internal address, external connections might fail");
+ else
+ print(" "..host.." AAAA record points to unknown address "..record.aaaa);
+ all_targets_ok = false;
+ end
+ end
+ end
+
+ local bad_protos = {}
+ if not host_ok_v4 then
+ table.insert(bad_protos, "IPv4");
+ end
+ if not host_ok_v6 then
+ table.insert(bad_protos, "IPv6");
+ end
+ if #bad_protos > 0 then
+ print(" Host "..host.." does not seem to resolve to this server ("..table.concat(bad_protos, "/")..")");
+ end
+ if host_ok_v6 and not v6_supported then
+ print(" Host "..host.." has AAAA records, but your version of LuaSocket does not support IPv6.");
+ print(" Please see http://prosody.im/doc/ipv6 for more information.");
+ end
+ end
+ if not all_targets_ok then
+ print(" "..(some_targets_ok and "Only some" or "No").." targets for "..host.." appear to resolve to this server.");
+ if is_component then
+ print(" DNS records are necessary if you want users on other servers to access this component.");
+ end
+ problem_hosts:add(host);
+ end
+ print("");
+ end
+ if not problem_hosts:empty() then
+ print("");
+ print("For more information about DNS configuration please see http://prosody.im/doc/dns");
+ print("");
+ ok = false;
+ end
+ end
+ if not what or what == "certs" then
+ local cert_ok;
+ print"Checking certificates..."
+ local x509_verify_identity = require"util.x509".verify_identity;
+ local ssl = dependencies.softreq"ssl";
+ -- local datetime_parse = require"util.datetime".parse_x509;
+ local load_cert = ssl and ssl.loadcertificate;
+ -- or ssl.cert_from_pem
+ if not ssl then
+ print("LuaSec not available, can't perform certificate checks")
+ if what == "certs" then cert_ok = false end
+ elseif not load_cert then
+ print("This version of LuaSec (" .. ssl._VERSION .. ") does not support certificate checking");
+ cert_ok = false
+ else
+ for host in enabled_hosts() do
+ print("Checking certificate for "..host);
+ -- First, let's find out what certificate this host uses.
+ local ssl_config = config.rawget(host, "ssl");
+ if not ssl_config then
+ local base_host = host:match("%.(.*)");
+ ssl_config = config.get(base_host, "ssl");
+ end
+ if not ssl_config then
+ print(" No 'ssl' option defined for "..host)
+ cert_ok = false
+ elseif not ssl_config.certificate then
+ print(" No 'certificate' set in ssl option for "..host)
+ cert_ok = false
+ elseif not ssl_config.key then
+ print(" No 'key' set in ssl option for "..host)
+ cert_ok = false
+ else
+ local key, err = io.open(ssl_config.key); -- Permissions check only
+ if not key then
+ print(" Could not open "..ssl_config.key..": "..err);
+ cert_ok = false
+ else
+ key:close();
+ end
+ local cert_fh, err = io.open(ssl_config.certificate); -- Load the file.
+ if not cert_fh then
+ print(" Could not open "..ssl_config.certificate..": "..err);
+ cert_ok = false
+ else
+ print(" Certificate: "..ssl_config.certificate)
+ local cert = load_cert(cert_fh:read"*a"); cert_fh = cert_fh:close();
+ if not cert:validat(os.time()) then
+ print(" Certificate has expired.")
+ cert_ok = false
+ end
+ if config.get(host, "component_module") == nil
+ and not x509_verify_identity(host, "_xmpp-client", cert) then
+ print(" Not vaild for client connections to "..host..".")
+ cert_ok = false
+ end
+ if (not (config.get(host, "anonymous_login")
+ or config.get(host, "authentication") == "anonymous"))
+ and not x509_verify_identity(host, "_xmpp-server", cert) then
+ print(" Not vaild for server-to-server connections to "..host..".")
+ cert_ok = false
+ end
+ end
+ end
+ end
+ if cert_ok == false then
+ print("")
+ print("For more information about certificates please see http://prosody.im/doc/certificates");
+ ok = false
+ end
+ end
+ print("")
+ end
+ if not ok then
+ print("Problems found, see above.");
+ else
+ print("All checks passed, congratulations!");
+ end
+ return ok and 0 or 2;
+end
+
---------------------
if command and command:match("^mod_") then -- Is a command in a module
diff --git a/tests/modulemanager_option_conversion.lua b/tests/modulemanager_option_conversion.lua
index 7dceeaed..100dbe83 100644
--- a/tests/modulemanager_option_conversion.lua
+++ b/tests/modulemanager_option_conversion.lua
@@ -18,7 +18,7 @@ function test_value(value, returns)
assert(module:get_option_number("opt") == returns.number, "number doesn't match");
assert(module:get_option_string("opt") == returns.string, "string doesn't match");
assert(module:get_option_boolean("opt") == returns.boolean, "boolean doesn't match");
-
+
if type(returns.array) == "table" then
local target_array, returned_array = returns.array, module:get_option_array("opt");
assert(#target_array == #returned_array, "array length doesn't match");
@@ -28,7 +28,7 @@ function test_value(value, returns)
else
assert(module:get_option_array("opt") == returns.array, "array is returned (not nil)");
end
-
+
if type(returns.set) == "table" then
local target_items, returned_items = set.new(returns.set), module:get_option_set("opt");
assert(target_items == returned_items, "set doesn't match");
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index d93cd39b..bfb13d00 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -1,3 +1,3 @@
#!/bin/sh
rm reports/*.report
-lua test.lua $*
+exec lua test.lua $*
diff --git a/tests/test.lua b/tests/test.lua
index bb11ab26..0fcc4907 100644
--- a/tests/test.lua
+++ b/tests/test.lua
@@ -1,26 +1,30 @@
-- 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 tests_passed = true;
function run_all_tests()
package.loaded["net.connlisteners"] = { get = function () return {} end };
dotest "util.jid"
dotest "util.multitable"
- dotest "util.rfc3484"
- dotest "net.http"
- dotest "core.modulemanager"
+ dotest "util.rfc6724"
+ dotest "util.http"
dotest "core.stanza_router"
dotest "core.s2smanager"
dotest "core.configmanager"
+ dotest "util.ip"
dotest "util.stanza"
dotest "util.sasl.scram"
-
+ dotest "util.cache"
+ dotest "util.throttle"
+ dotest "util.uuid"
+ dotest "util.random"
+
dosingletest("test_sasl.lua", "latin1toutf8");
dosingletest("test_utf8.lua", "valid");
end
@@ -39,6 +43,8 @@ local _realG = _G;
require "util.import"
+local envloadfile = require "util.envload".envloadfile;
+
local env_mt = { __index = function (t,k) return rawget(_realG, k) or print("WARNING: Attempt to access nil global '"..tostring(k).."'"); end };
function testlib_new_env(t)
return setmetatable(t or {}, env_mt);
@@ -76,29 +82,29 @@ function dosingletest(testname, fname)
local tests = setmetatable({}, { __index = _realG });
tests.__unit = testname;
tests.__test = fname;
- local chunk, err = loadfile(testname);
+ local chunk, err = envloadfile(testname, tests);
if not chunk then
print("WARNING: ", "Failed to load tests for "..testname, err);
return;
end
- setfenv(chunk, tests);
local success, err = pcall(chunk);
if not success then
print("WARNING: ", "Failed to initialise tests for "..testname, err);
return;
end
-
+
if type(tests[fname]) ~= "function" then
error(testname.." has no test '"..fname.."'", 0);
end
-
-
+
+
local line_hook, line_info = new_line_coverage_monitor(testname);
debug.sethook(line_hook, "l")
local success, ret = pcall(tests[fname]);
debug.sethook();
if not success then
+ tests_passed = false;
print("TEST FAILED! Unit: ["..testname.."] Function: ["..fname.."]");
print(" Location: "..ret:gsub(":%s*\n", "\n"));
line_info(fname, false, report_file);
@@ -115,13 +121,12 @@ function dotest(unitname)
_fakeG._G = _fakeG;
local tests = setmetatable({}, { __index = _fakeG });
tests.__unit = unitname;
- local chunk, err = loadfile("test_"..unitname:gsub("%.", "_")..".lua");
+ local chunk, err = envloadfile("test_"..unitname:gsub("%.", "_")..".lua", tests);
if not chunk then
print("WARNING: ", "Failed to load tests for "..unitname, err);
return;
end
- setfenv(chunk, tests);
local success, err = pcall(chunk);
if not success then
print("WARNING: ", "Failed to initialise tests for "..unitname, err);
@@ -130,25 +135,30 @@ function dotest(unitname)
if tests.env then setmetatable(tests.env, { __index = _realG }); end
local unit = setmetatable({}, { __index = setmetatable({ _G = tests.env or _fakeG }, { __index = tests.env or _fakeG }) });
local fn = "../"..unitname:gsub("%.", "/")..".lua";
- local chunk, err = loadfile(fn);
+ local chunk, err = envloadfile(fn, unit);
if not chunk then
print("WARNING: ", "Failed to load module: "..unitname, err);
return;
end
-
+
local oldmodule, old_M = _fakeG.module, _fakeG._M;
_fakeG.module = function ()
setmetatable(unit, nil);
unit._M = unit;
end
- setfenv(chunk, unit);
- local success, err = pcall(chunk);
+ local success, ret = pcall(chunk);
_fakeG.module, _fakeG._M = oldmodule, old_M;
if not success then
- print("WARNING: ", "Failed to initialise module: "..unitname, err);
+ print("WARNING: ", "Failed to initialise module: "..unitname, ret);
return;
end
-
+
+ if type(ret) == "table" then
+ for k,v in pairs(ret) do
+ unit[k] = v;
+ end
+ end
+
for name, f in pairs(unit) do
local test = rawget(tests, name);
if type(f) ~= "function" then
@@ -168,6 +178,7 @@ function dotest(unitname)
local success, ret = pcall(test, f, unit);
debug.sethook();
if not success then
+ tests_passed = false;
print("TEST FAILED! Unit: ["..unitname.."] Function: ["..name.."]");
print(" Location: "..ret:gsub(":%s*\n", "\n"));
line_info(name, false, report_file);
@@ -187,6 +198,7 @@ function runtest(f, msg)
if success and verbosity >= 2 then
print("SUBTEST PASSED: "..(msg or "(no description)"));
elseif (not success) and verbosity >= 0 then
+ tests_passed = false;
print("SUBTEST FAILED: "..(msg or "(no description)"));
error(ret, 0);
end
@@ -195,11 +207,11 @@ end
function new_line_coverage_monitor(file)
local lines_hit, funcs_hit = {}, {};
local total_lines, covered_lines = 0, 0;
-
+
for line in io.lines(file) do
total_lines = total_lines + 1;
end
-
+
return function (event, line) -- Line hook
if not lines_hit[line] then
local info = debug.getinfo(2, "fSL")
@@ -234,3 +246,5 @@ function new_line_coverage_monitor(file)
end
run_all_tests()
+
+os.exit(tests_passed and 0 or 1);
diff --git a/tests/test_core_configmanager.lua b/tests/test_core_configmanager.lua
index 132dfc74..5bd469c6 100644
--- a/tests/test_core_configmanager.lua
+++ b/tests/test_core_configmanager.lua
@@ -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.
--
@@ -9,27 +9,23 @@
function get(get, config)
- config.set("example.com", "test", "testkey", 123);
- assert_equal(get("example.com", "test", "testkey"), 123, "Retrieving a set key");
-
- config.set("*", "test", "testkey1", 321);
- assert_equal(get("*", "test", "testkey1"), 321, "Retrieving a set global key");
- assert_equal(get("example.com", "test", "testkey1"), 321, "Retrieving a set key of undefined host, of which only a globally set one exists");
-
- config.set("example.com", "test", ""); -- Creates example.com host in config
- assert_equal(get("example.com", "test", "testkey1"), 321, "Retrieving a set key, of which only a globally set one exists");
-
+ config.set("example.com", "testkey", 123);
+ assert_equal(get("example.com", "testkey"), 123, "Retrieving a set key");
+
+ config.set("*", "testkey1", 321);
+ assert_equal(get("*", "testkey1"), 321, "Retrieving a set global key");
+ assert_equal(get("example.com", "testkey1"), 321, "Retrieving a set key of undefined host, of which only a globally set one exists");
+
+ config.set("example.com", ""); -- Creates example.com host in config
+ assert_equal(get("example.com", "testkey1"), 321, "Retrieving a set key, of which only a globally set one exists");
+
assert_equal(get(), nil, "No parameters to get()");
assert_equal(get("undefined host"), nil, "Getting for undefined host");
- assert_equal(get("undefined host", "undefined section"), nil, "Getting for undefined host & section");
- assert_equal(get("undefined host", "undefined section", "undefined key"), nil, "Getting for undefined host & section & key");
-
- assert_equal(get("example.com", "undefined section", "testkey"), nil, "Defined host, undefined section");
+ assert_equal(get("undefined host", "undefined key"), nil, "Getting for undefined host & key");
end
function set(set, u)
- assert_equal(set("*"), false, "Set with no section/key");
- assert_equal(set("*", "set_test"), false, "Set with no key");
+ assert_equal(set("*"), false, "Set with no key");
assert_equal(set("*", "set_test", "testkey"), true, "Setting a nil global value");
assert_equal(set("*", "set_test", "testkey", 123), true, "Setting a global value");
diff --git a/tests/test_core_modulemanager.lua b/tests/test_core_modulemanager.lua
deleted file mode 100644
index 9498875a..00000000
--- a/tests/test_core_modulemanager.lua
+++ /dev/null
@@ -1,48 +0,0 @@
--- 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 config = require "core.configmanager";
-local helpers = require "util.helpers";
-local set = require "util.set";
-
-function load_modules_for_host(load_modules_for_host, mm)
- local test_num = 0;
- local function test_load(global_modules_enabled, global_modules_disabled, host_modules_enabled, host_modules_disabled, expected_modules)
- test_num = test_num + 1;
- -- Prepare
- hosts = { ["example.com"] = {} };
- config.set("*", "core", "modules_enabled", global_modules_enabled);
- config.set("*", "core", "modules_disabled", global_modules_disabled);
- config.set("example.com", "core", "modules_enabled", host_modules_enabled);
- config.set("example.com", "core", "modules_disabled", host_modules_disabled);
-
- expected_modules = set.new(expected_modules);
- expected_modules:add_list(helpers.get_upvalue(load_modules_for_host, "autoload_modules"));
-
- local loaded_modules = set.new();
- function mm.load(host, module)
- assert_equal(host, "example.com", test_num..": Host isn't example.com but "..tostring(host));
- assert_equal(expected_modules:contains(module), true, test_num..": Loading unexpected module '"..tostring(module).."'");
- loaded_modules:add(module);
- end
- load_modules_for_host("example.com");
- assert_equal((expected_modules - loaded_modules):empty(), true, test_num..": Not all modules loaded: "..tostring(expected_modules - loaded_modules));
- end
-
- test_load({ "one", "two", "three" }, nil, nil, nil, { "one", "two", "three" });
- test_load({ "one", "two", "three" }, {}, nil, nil, { "one", "two", "three" });
- test_load({ "one", "two", "three" }, { "two" }, nil, nil, { "one", "three" });
- test_load({ "one", "two", "three" }, { "three" }, nil, nil, { "one", "two" });
- test_load({ "one", "two", "three" }, nil, nil, { "three" }, { "one", "two" });
- test_load({ "one", "two", "three" }, nil, { "three" }, { "three" }, { "one", "two", "three" });
-
- test_load({ "one", "two" }, nil, { "three" }, nil, { "one", "two", "three" });
- test_load({ "one", "two", "three" }, nil, { "three" }, nil, { "one", "two", "three" });
- test_load({ "one", "two", "three" }, { "three" }, { "three" }, nil, { "one", "two", "three" });
- test_load({ "one", "two" }, { "three" }, { "three" }, nil, { "one", "two", "three" });
-end
diff --git a/tests/test_core_s2smanager.lua b/tests/test_core_s2smanager.lua
index b49c7da6..d2dbf830 100644
--- a/tests/test_core_s2smanager.lua
+++ b/tests/test_core_s2smanager.lua
@@ -1,11 +1,14 @@
-- 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.
--
+env = {
+ prosody = { events = require "util.events".new() };
+};
function compare_srv_priorities(csp)
local r1 = { priority = 10, weight = 0 }
@@ -13,7 +16,7 @@ function compare_srv_priorities(csp)
local r3 = { priority = 1000, weight = 2 }
local r4 = { priority = 1000, weight = 2 }
local r5 = { priority = 1000, weight = 5 }
-
+
assert_equal(csp(r1, r1), false);
assert_equal(csp(r1, r2), true);
assert_equal(csp(r1, r3), true);
diff --git a/tests/test_core_stanza_router.lua b/tests/test_core_stanza_router.lua
index 0a93694f..ca6b78fc 100644
--- a/tests/test_core_stanza_router.lua
+++ b/tests/test_core_stanza_router.lua
@@ -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,7 +14,7 @@ function core_process_stanza(core_process_stanza, u)
local s2sin_session = { from_host = "remotehost", to_host = "localhost", type = "s2sin", hosts = { ["remotehost"] = { authed = true } } }
local local_host_session = { host = "localhost", type = "local", s2sout = { ["remotehost"] = s2sout_session } }
local local_user_session = { username = "user", host = "localhost", resource = "resource", full_jid = "user@localhost/resource", type = "c2s" }
-
+
_G.prosody.hosts["localhost"] = local_host_session;
_G.prosody.full_sessions["user@localhost/resource"] = local_user_session;
_G.prosody.bare_sessions["user@localhost"] = { sessions = { resource = local_user_session } };
@@ -23,15 +23,15 @@ function core_process_stanza(core_process_stanza, u)
local function test_message_full_jid()
local env = testlib_new_env();
local msg = stanza.stanza("message", { to = "user@localhost/resource", type = "chat" }):tag("body"):text("Hello world");
-
+
local target_routed;
-
+
function env.core_post_stanza(p_origin, p_stanza)
assert_equal(p_origin, local_user_session, "origin of routed stanza is not correct");
assert_equal(p_stanza, msg, "routed stanza is not correct one: "..p_stanza:pretty_print());
target_routed = true;
end
-
+
env.hosts = hosts;
env.prosody = { hosts = hosts };
setfenv(core_process_stanza, env);
@@ -42,9 +42,9 @@ function core_process_stanza(core_process_stanza, u)
local function test_message_bare_jid()
local env = testlib_new_env();
local msg = stanza.stanza("message", { to = "user@localhost", type = "chat" }):tag("body"):text("Hello world");
-
+
local target_routed;
-
+
function env.core_post_stanza(p_origin, p_stanza)
assert_equal(p_origin, local_user_session, "origin of routed stanza is not correct");
assert_equal(p_stanza, msg, "routed stanza is not correct one: "..p_stanza:pretty_print());
@@ -60,9 +60,9 @@ function core_process_stanza(core_process_stanza, u)
local function test_message_no_to()
local env = testlib_new_env();
local msg = stanza.stanza("message", { type = "chat" }):tag("body"):text("Hello world");
-
+
local target_handled;
-
+
function env.core_post_stanza(p_origin, p_stanza)
assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct");
assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print());
@@ -78,9 +78,9 @@ function core_process_stanza(core_process_stanza, u)
local function test_message_to_remote_bare()
local env = testlib_new_env();
local msg = stanza.stanza("message", { to = "user@remotehost", type = "chat" }):tag("body"):text("Hello world");
-
+
local target_routed;
-
+
function env.core_route_stanza(p_origin, p_stanza)
assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct");
assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print());
@@ -88,7 +88,7 @@ function core_process_stanza(core_process_stanza, u)
end
function env.core_post_stanza(...) env.core_route_stanza(...); end
-
+
env.hosts = hosts;
setfenv(core_process_stanza, env);
assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value");
@@ -98,9 +98,9 @@ function core_process_stanza(core_process_stanza, u)
local function test_message_to_remote_server()
local env = testlib_new_env();
local msg = stanza.stanza("message", { to = "remotehost", type = "chat" }):tag("body"):text("Hello world");
-
+
local target_routed;
-
+
function env.core_route_stanza(p_origin, p_stanza)
assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct");
assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print());
@@ -123,9 +123,9 @@ function core_process_stanza(core_process_stanza, u)
local function test_iq_to_remote_server()
local env = testlib_new_env();
local msg = stanza.stanza("iq", { to = "remotehost", type = "get", id = "id" }):tag("body"):text("Hello world");
-
+
local target_routed;
-
+
function env.core_route_stanza(p_origin, p_stanza)
assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct");
assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print());
@@ -145,9 +145,9 @@ function core_process_stanza(core_process_stanza, u)
local function test_iq_error_to_local_user()
local env = testlib_new_env();
local msg = stanza.stanza("iq", { to = "user@localhost/resource", from = "user@remotehost", type = "error", id = "id" }):tag("error", { type = 'cancel' }):tag("item-not-found", { xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' });
-
+
local target_routed;
-
+
function env.core_route_stanza(p_origin, p_stanza)
assert_equal(p_origin, s2sin_session, "origin of handled stanza is not correct");
assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print());
@@ -167,9 +167,9 @@ function core_process_stanza(core_process_stanza, u)
local function test_iq_to_local_bare()
local env = testlib_new_env();
local msg = stanza.stanza("iq", { to = "user@localhost", from = "user@localhost", type = "get", id = "id" }):tag("ping", { xmlns = "urn:xmpp:ping:0" });
-
+
local target_handled;
-
+
function env.core_post_stanza(p_origin, p_stanza)
assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct");
assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print());
@@ -209,11 +209,11 @@ function core_route_stanza(core_route_stanza)
local msg2 = stanza.stanza("iq", { to = "user@localhost/foo", from = "user@localhost", type = "error" }):tag("ping", { xmlns = "urn:xmpp:ping:0" });
--package.loaded["core.usermanager"] = { user_exists = function (user, host) print("RAR!") return true or user == "user" and host == "localhost" and true; end };
local target_handled, target_replied;
-
+
function env.core_post_stanza(p_origin, p_stanza)
target_handled = true;
end
-
+
function local_user_session.send(data)
--print("Replying with: ", tostring(data));
--print(debug.traceback())
diff --git a/tests/test_sasl.lua b/tests/test_sasl.lua
index 271fa69a..dd63c5a0 100644
--- a/tests/test_sasl.lua
+++ b/tests/test_sasl.lua
@@ -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,7 +30,7 @@ function latin1toutf8()
local function assert_utf8(latin, utf8)
assert_equal(_latin1toutf8(latin), utf8, "Incorrect UTF8 from Latin1: "..tostring(latin));
end
-
+
assert_utf8("", "")
assert_utf8("test", "test")
assert_utf8(nil, nil)
diff --git a/tests/test_util_cache.lua b/tests/test_util_cache.lua
new file mode 100644
index 00000000..72cb5a85
--- /dev/null
+++ b/tests/test_util_cache.lua
@@ -0,0 +1,229 @@
+
+function new(new)
+ local c = new(5);
+
+ assert_equal(c:count(), 0);
+
+ c:set("one", 1)
+ assert_equal(c:count(), 1);
+ c:set("two", 2)
+ c:set("three", 3)
+ c:set("four", 4)
+ c:set("five", 5);
+ assert_equal(c:count(), 5);
+
+ c:set("foo", nil);
+ assert_equal(c:count(), 5);
+
+ assert_equal(c:get("one"), 1);
+ assert_equal(c:get("two"), 2);
+ assert_equal(c:get("three"), 3);
+ assert_equal(c:get("four"), 4);
+ assert_equal(c:get("five"), 5);
+
+ assert_equal(c:get("foo"), nil);
+ assert_equal(c:get("bar"), nil);
+
+ c:set("six", 6);
+ assert_equal(c:count(), 5);
+
+ assert_equal(c:get("one"), nil);
+ assert_equal(c:get("two"), 2);
+ assert_equal(c:get("three"), 3);
+ assert_equal(c:get("four"), 4);
+ assert_equal(c:get("five"), 5);
+ assert_equal(c:get("six"), 6);
+
+ c:set("three", nil);
+ assert_equal(c:count(), 4);
+
+ assert_equal(c:get("one"), nil);
+ assert_equal(c:get("two"), 2);
+ assert_equal(c:get("three"), nil);
+ assert_equal(c:get("four"), 4);
+ assert_equal(c:get("five"), 5);
+ assert_equal(c:get("six"), 6);
+
+ c:set("seven", 7);
+ assert_equal(c:count(), 5);
+
+ assert_equal(c:get("one"), nil);
+ assert_equal(c:get("two"), 2);
+ assert_equal(c:get("three"), nil);
+ assert_equal(c:get("four"), 4);
+ assert_equal(c:get("five"), 5);
+ assert_equal(c:get("six"), 6);
+ assert_equal(c:get("seven"), 7);
+
+ c:set("eight", 8);
+ assert_equal(c:count(), 5);
+
+ assert_equal(c:get("one"), nil);
+ assert_equal(c:get("two"), nil);
+ assert_equal(c:get("three"), nil);
+ assert_equal(c:get("four"), 4);
+ assert_equal(c:get("five"), 5);
+ assert_equal(c:get("six"), 6);
+ assert_equal(c:get("seven"), 7);
+ assert_equal(c:get("eight"), 8);
+
+ c:set("four", 4);
+ assert_equal(c:count(), 5);
+
+ assert_equal(c:get("one"), nil);
+ assert_equal(c:get("two"), nil);
+ assert_equal(c:get("three"), nil);
+ assert_equal(c:get("four"), 4);
+ assert_equal(c:get("five"), 5);
+ assert_equal(c:get("six"), 6);
+ assert_equal(c:get("seven"), 7);
+ assert_equal(c:get("eight"), 8);
+
+ c:set("nine", 9);
+ assert_equal(c:count(), 5);
+
+ assert_equal(c:get("one"), nil);
+ assert_equal(c:get("two"), nil);
+ assert_equal(c:get("three"), nil);
+ assert_equal(c:get("four"), 4);
+ assert_equal(c:get("five"), nil);
+ assert_equal(c:get("six"), 6);
+ assert_equal(c:get("seven"), 7);
+ assert_equal(c:get("eight"), 8);
+ assert_equal(c:get("nine"), 9);
+
+ local keys = { "nine", "four", "eight", "seven", "six" };
+ local values = { 9, 4, 8, 7, 6 };
+ local i = 0;
+ for k, v in c:items() do
+ i = i + 1;
+ assert_equal(k, keys[i]);
+ assert_equal(v, values[i]);
+ end
+ assert_equal(i, 5);
+
+ c:set("four", "2+2");
+ assert_equal(c:count(), 5);
+
+ assert_equal(c:get("one"), nil);
+ assert_equal(c:get("two"), nil);
+ assert_equal(c:get("three"), nil);
+ assert_equal(c:get("four"), "2+2");
+ assert_equal(c:get("five"), nil);
+ assert_equal(c:get("six"), 6);
+ assert_equal(c:get("seven"), 7);
+ assert_equal(c:get("eight"), 8);
+ assert_equal(c:get("nine"), 9);
+
+ local keys = { "four", "nine", "eight", "seven", "six" };
+ local values = { "2+2", 9, 8, 7, 6 };
+ local i = 0;
+ for k, v in c:items() do
+ i = i + 1;
+ assert_equal(k, keys[i]);
+ assert_equal(v, values[i]);
+ end
+ assert_equal(i, 5);
+
+ c:set("foo", nil);
+ assert_equal(c:count(), 5);
+
+ assert_equal(c:get("one"), nil);
+ assert_equal(c:get("two"), nil);
+ assert_equal(c:get("three"), nil);
+ assert_equal(c:get("four"), "2+2");
+ assert_equal(c:get("five"), nil);
+ assert_equal(c:get("six"), 6);
+ assert_equal(c:get("seven"), 7);
+ assert_equal(c:get("eight"), 8);
+ assert_equal(c:get("nine"), 9);
+
+ local keys = { "four", "nine", "eight", "seven", "six" };
+ local values = { "2+2", 9, 8, 7, 6 };
+ local i = 0;
+ for k, v in c:items() do
+ i = i + 1;
+ assert_equal(k, keys[i]);
+ assert_equal(v, values[i]);
+ end
+ assert_equal(i, 5);
+
+ c:set("four", nil);
+
+ assert_equal(c:get("one"), nil);
+ assert_equal(c:get("two"), nil);
+ assert_equal(c:get("three"), nil);
+ assert_equal(c:get("four"), nil);
+ assert_equal(c:get("five"), nil);
+ assert_equal(c:get("six"), 6);
+ assert_equal(c:get("seven"), 7);
+ assert_equal(c:get("eight"), 8);
+ assert_equal(c:get("nine"), 9);
+
+ local keys = { "nine", "eight", "seven", "six" };
+ local values = { 9, 8, 7, 6 };
+ local i = 0;
+ for k, v in c:items() do
+ i = i + 1;
+ assert_equal(k, keys[i]);
+ assert_equal(v, values[i]);
+ end
+ assert_equal(i, 4);
+
+ local evicted_key, evicted_value;
+ local c = new(3, function (_key, _value)
+ evicted_key, evicted_value = _key, _value;
+ end);
+ local function set(k, v, should_evict_key, should_evict_value)
+ evicted_key, evicted_value = nil, nil;
+ c:set(k, v);
+ assert_equal(evicted_key, should_evict_key);
+ assert_equal(evicted_value, should_evict_value);
+ end
+ set("a", 1)
+ set("a", 1)
+ set("a", 1)
+ set("a", 1)
+ set("a", 1)
+
+ set("b", 2)
+ set("c", 3)
+ set("b", 2)
+ set("d", 4, "a", 1)
+ set("e", 5, "c", 3)
+
+
+ local evicted_key, evicted_value;
+ local c3 = new(1, function (_key, _value, c3)
+ evicted_key, evicted_value = _key, _value;
+ if _key == "a" then
+ -- Put it back in...
+ -- Check that the newest key/value was set before on_evict was called
+ assert_equal(c3:get("b"), 2);
+ -- Sanity check for what we're evicting
+ assert_equal(_key, "a");
+ assert_equal(_value, 1);
+ -- Re-insert the evicted key (causes this evict function to run again with "b",2)
+ c3:set(_key, _value)
+ assert_equal(c3:get(_key), _value)
+ end
+ end);
+ local function set(k, v, should_evict_key, should_evict_value)
+ evicted_key, evicted_value = nil, nil;
+ c3:set(k, v);
+ assert_equal(evicted_key, should_evict_key);
+ assert_equal(evicted_value, should_evict_value);
+ end
+ set("a", 1)
+ set("a", 1)
+ set("a", 1)
+ set("a", 1)
+ set("a", 1)
+
+ -- The evict handler re-inserts "a"->1, so "b" gets evicted:
+ set("b", 2, "b", 2)
+ -- Check the final state is what we expect
+ assert_equal(c3:get("a"), 1);
+ assert_equal(c3:get("b"), nil);
+ assert_equal(c3:count(), 1);
+end
diff --git a/tests/test_net_http.lua b/tests/test_util_http.lua
index e68f96e9..a195df6b 100644
--- a/tests/test_net_http.lua
+++ b/tests/test_util_http.lua
@@ -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 --git a/tests/test_util_ip.lua b/tests/test_util_ip.lua
new file mode 100644
index 00000000..0ded1123
--- /dev/null
+++ b/tests/test_util_ip.lua
@@ -0,0 +1,89 @@
+
+function match(match, _M)
+ local _ = _M.new_ip;
+ local ip = _"10.20.30.40";
+ assert_equal(match(ip, _"10.0.0.0", 8), true);
+ assert_equal(match(ip, _"10.0.0.0", 16), false);
+ assert_equal(match(ip, _"10.0.0.0", 24), false);
+ assert_equal(match(ip, _"10.0.0.0", 32), false);
+
+ assert_equal(match(ip, _"10.20.0.0", 8), true);
+ assert_equal(match(ip, _"10.20.0.0", 16), true);
+ assert_equal(match(ip, _"10.20.0.0", 24), false);
+ assert_equal(match(ip, _"10.20.0.0", 32), false);
+
+ assert_equal(match(ip, _"0.0.0.0", 32), false);
+ assert_equal(match(ip, _"0.0.0.0", 0), true);
+ assert_equal(match(ip, _"0.0.0.0"), false);
+
+ assert_equal(match(ip, _"10.0.0.0", 255), false, "excessive number of bits");
+ assert_equal(match(ip, _"10.0.0.0", -8), true, "negative number of bits");
+ assert_equal(match(ip, _"10.0.0.0", -32), true, "negative number of bits");
+ assert_equal(match(ip, _"10.0.0.0", 0), true, "zero bits");
+ assert_equal(match(ip, _"10.0.0.0"), false, "no specified number of bits (differing ip)");
+ assert_equal(match(ip, _"10.20.30.40"), true, "no specified number of bits (same ip)");
+
+ assert_equal(match(_"127.0.0.1", _"127.0.0.1"), true, "simple ip");
+
+ assert_equal(match(_"8.8.8.8", _"8.8.0.0", 16), true);
+ assert_equal(match(_"8.8.4.4", _"8.8.0.0", 16), true);
+end
+
+function parse_cidr(parse_cidr, _M)
+ local new_ip = _M.new_ip;
+
+ assert_equal(new_ip"0.0.0.0", new_ip"0.0.0.0")
+
+ local function assert_cidr(cidr, ip, bits)
+ local parsed_ip, parsed_bits = parse_cidr(cidr);
+ assert_equal(new_ip(ip), parsed_ip, cidr.." parsed ip is "..ip);
+ assert_equal(bits, parsed_bits, cidr.." parsed bits is "..tostring(bits));
+ end
+ assert_cidr("0.0.0.0", "0.0.0.0", nil);
+ assert_cidr("127.0.0.1", "127.0.0.1", nil);
+ assert_cidr("127.0.0.1/0", "127.0.0.1", 0);
+ assert_cidr("127.0.0.1/8", "127.0.0.1", 8);
+ assert_cidr("127.0.0.1/32", "127.0.0.1", 32);
+ assert_cidr("127.0.0.1/256", "127.0.0.1", 256);
+ assert_cidr("::/48", "::", 48);
+end
+
+function new_ip(new_ip)
+ local v4, v6 = "IPv4", "IPv6";
+ local function assert_proto(s, proto)
+ local ip = new_ip(s);
+ if proto then
+ assert_equal(ip and ip.proto, proto, "protocol is correct for "..("%q"):format(s));
+ else
+ assert_equal(ip, nil, "address is invalid");
+ end
+ end
+ assert_proto("127.0.0.1", v4);
+ assert_proto("::1", v6);
+ assert_proto("", nil);
+ assert_proto("abc", nil);
+ assert_proto(" ", nil);
+end
+
+function commonPrefixLength(cpl, _M)
+ local new_ip = _M.new_ip;
+ local function assert_cpl6(a, b, len, v4)
+ local ipa, ipb = new_ip(a), new_ip(b);
+ if v4 then len = len+96; end
+ assert_equal(cpl(ipa, ipb), len, "common prefix length of "..a.." and "..b.." is "..len);
+ assert_equal(cpl(ipb, ipa), len, "common prefix length of "..b.." and "..a.." is "..len);
+ end
+ local function assert_cpl4(a, b, len)
+ return assert_cpl6(a, b, len, "IPv4");
+ end
+ assert_cpl4("0.0.0.0", "0.0.0.0", 32);
+ assert_cpl4("255.255.255.255", "0.0.0.0", 0);
+ assert_cpl4("255.255.255.255", "255.255.0.0", 16);
+ assert_cpl4("255.255.255.255", "255.255.255.255", 32);
+ assert_cpl4("255.255.255.255", "255.255.255.255", 32);
+
+ assert_cpl6("::1", "::1", 128);
+ assert_cpl6("abcd::1", "abcd::1", 128);
+ assert_cpl6("abcd::abcd", "abcd::", 112);
+ assert_cpl6("abcd::abcd", "abcd::abcd:abcd", 96);
+end
diff --git a/tests/test_util_jid.lua b/tests/test_util_jid.lua
index a817e644..02a90c3b 100644
--- a/tests/test_util_jid.lua
+++ b/tests/test_util_jid.lua
@@ -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 --git a/tests/test_util_multitable.lua b/tests/test_util_multitable.lua
index ed10b128..71a83450 100644
--- a/tests/test_util_multitable.lua
+++ b/tests/test_util_multitable.lua
@@ -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.
--
@@ -41,7 +41,7 @@ function get(get, multitable)
end
mt = multitable.new();
-
+
local trigger1, trigger2, trigger3 = {}, {}, {};
local item1, item2, item3 = {}, {}, {};
@@ -51,12 +51,12 @@ function get(get, multitable)
mt:add(1, 2, 3, item1);
assert_has_all("Has item1 for 1, 2, 3", mt:get(1, 2, 3), item1);
-
+
-- Doesn't support nil
--[[ mt:add(nil, item1);
mt:add(nil, item2);
mt:add(nil, item3);
-
+
assert_has_all("Has all items with (nil)", mt:get(nil), item1, item2, item3);
]]
end
diff --git a/tests/test_util_queue.lua b/tests/test_util_queue.lua
new file mode 100644
index 00000000..b0e1fa3d
--- /dev/null
+++ b/tests/test_util_queue.lua
@@ -0,0 +1,68 @@
+local new = require "util.queue".new;
+
+local q = new(10);
+
+assert(q.size == 10);
+assert(q:count() == 0);
+
+assert(q:push("one"));
+assert(q:push("two"));
+assert(q:push("three"));
+
+for i = 4, 10 do
+ print("pushing "..i)
+ assert(q:push("hello"));
+ assert(q:count() == i, "count is not "..i.."("..q:count()..")");
+end
+assert(q:push("hello") == nil, "queue overfull!");
+assert(q:push("hello") == nil, "queue overfull!");
+assert(q:pop() == "one", "queue item incorrect");
+assert(q:pop() == "two", "queue item incorrect");
+assert(q:push("hello"));
+assert(q:push("hello"));
+assert(q:pop() == "three", "queue item incorrect");
+assert(q:push("hello"));
+assert(q:push("hello") == nil, "queue overfull!");
+assert(q:push("hello") == nil, "queue overfull!");
+
+assert(q:count() == 10, "queue count incorrect");
+
+for i = 1, 10 do
+ assert(q:pop() == "hello", "queue item incorrect");
+end
+
+assert(q:count() == 0, "queue count incorrect");
+
+assert(q:push(1));
+for i = 1, 1001 do
+ assert(q:pop() == i);
+ assert(q:count() == 0);
+ assert(q:push(i+1));
+ assert(q:count() == 1);
+end
+assert(q:pop() == 1002);
+assert(q:push(1));
+for i = 1, 1000000 do
+ q:pop();
+ q:push(i+1);
+end
+
+-- Test queues that purge old items when pushing to a full queue
+local q = new(10, true);
+
+for i = 1, 10 do
+ q:push(i);
+end
+
+assert(q:count() == 10);
+
+assert(q:push(11));
+assert(q:count() == 10);
+assert(q:pop() == 2); -- First item should have been purged
+
+for i = 12, 32 do
+ assert(q:push(i));
+end
+
+assert(q:count() == 10);
+assert(q:pop() == 23);
diff --git a/tests/test_util_random.lua b/tests/test_util_random.lua
new file mode 100644
index 00000000..79572ef8
--- /dev/null
+++ b/tests/test_util_random.lua
@@ -0,0 +1,10 @@
+-- Makes no attempt at testing how random the bytes are,
+-- just that it returns the number of bytes requested
+
+function bytes(bytes)
+ assert_is(bytes(16));
+
+ for i = 1, 255 do
+ assert_equal(i, #bytes(i));
+ end
+end
diff --git a/tests/test_util_rfc3484.lua b/tests/test_util_rfc3484.lua
deleted file mode 100644
index 18ae310e..00000000
--- a/tests/test_util_rfc3484.lua
+++ /dev/null
@@ -1,51 +0,0 @@
--- Prosody IM
--- Copyright (C) 2011 Florian Zeitz
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-function source(source)
- local new_ip = require"util.ip".new_ip;
- assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("3ffe::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr, "3ffe::1", "prefer appropriate scope");
- assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
- assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "2001::1", "prefer appropriate scope");
- assert_equal(source(new_ip("ff05::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
- assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::1", "IPv6"), new_ip("2002::1", "IPv6")}).addr, "2001::1", "prefer same address");
- assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fec0::2", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::2", "prefer appropriate scope");
- assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::2", "IPv6"), new_ip("3ffe::2", "IPv6")}).addr, "2001::2", "longest matching prefix");
- assert_equal(source(new_ip("2002:836b:2179::1", "IPv6"), {new_ip("2002:836b:2179::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001::2", "IPv6")}).addr, "2002:836b:2179::d5e3:7953:13eb:22e8", "prefer matching label");
-end
-
-function destination(dest)
- local order;
- local new_ip = require"util.ip".new_ip;
- order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")})
- assert_equal(order[1].addr, "2001::1", "prefer matching scope");
- assert_equal(order[2].addr, "131.107.65.121", "prefer matching scope")
-
- order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("fe80::1", "IPv6"), new_ip("131.107.65.117", "IPv4")})
- assert_equal(order[1].addr, "131.107.65.121", "prefer matching scope")
- assert_equal(order[2].addr, "2001::1", "prefer matching scope")
-
- order = dest({new_ip("2001::1", "IPv6"), new_ip("10.1.2.3", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")})
- assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
- assert_equal(order[2].addr, "10.1.2.3", "prefer higher precedence");
-
- order = dest({new_ip("2001::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert_equal(order[1].addr, "fe80::1", "prefer smaller scope");
- assert_equal(order[2].addr, "fec0::1", "prefer smaller scope");
- assert_equal(order[3].addr, "2001::1", "prefer smaller scope");
-
- order = dest({new_ip("2001::1", "IPv6"), new_ip("3ffe::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert_equal(order[1].addr, "2001::1", "longest matching prefix");
- assert_equal(order[2].addr, "3ffe::1", "longest matching prefix");
-
- order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert_equal(order[1].addr, "2002:836b:4179::1", "prefer matching label");
- assert_equal(order[2].addr, "2001::1", "prefer matching label");
-
- order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("2001::2", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
- assert_equal(order[2].addr, "2002:836b:4179::1", "prefer higher precedence");
-end
diff --git a/tests/test_util_rfc6724.lua b/tests/test_util_rfc6724.lua
new file mode 100644
index 00000000..bb73e921
--- /dev/null
+++ b/tests/test_util_rfc6724.lua
@@ -0,0 +1,97 @@
+-- Prosody IM
+-- Copyright (C) 2011-2013 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+function source(source)
+ local new_ip = require"util.ip".new_ip;
+ assert_equal(source(new_ip("2001:db8:1::1", "IPv6"),
+ {new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr,
+ "2001:db8:3::1",
+ "prefer appropriate scope");
+ assert_equal(source(new_ip("ff05::1", "IPv6"),
+ {new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr,
+ "2001:db8:3::1",
+ "prefer appropriate scope");
+ assert_equal(source(new_ip("2001:db8:1::1", "IPv6"),
+ {new_ip("2001:db8:1::1", "IPv6"), new_ip("2001:db8:2::1", "IPv6")}).addr,
+ "2001:db8:1::1",
+ "prefer same address"); -- "2001:db8:1::1" should be marked "deprecated" here, we don't handle that right now
+ assert_equal(source(new_ip("fe80::1", "IPv6"),
+ {new_ip("fe80::2", "IPv6"), new_ip("2001:db8:1::1", "IPv6")}).addr,
+ "fe80::2",
+ "prefer appropriate scope"); -- "fe80::2" should be marked "deprecated" here, we don't handle that right now
+ assert_equal(source(new_ip("2001:db8:1::1", "IPv6"),
+ {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::2", "IPv6")}).addr,
+ "2001:db8:1::2",
+ "longest matching prefix");
+--[[ "2001:db8:1::2" should be a care-of address and "2001:db8:3::2" a home address, we can't handle this and would fail
+ assert_equal(source(new_ip("2001:db8:1::1", "IPv6"),
+ {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::2", "IPv6")}).addr,
+ "2001:db8:3::2",
+ "prefer home address");
+]]
+ assert_equal(source(new_ip("2002:c633:6401::1", "IPv6"),
+ {new_ip("2002:c633:6401::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001:db8:1::2", "IPv6")}).addr,
+ "2002:c633:6401::d5e3:7953:13eb:22e8",
+ "prefer matching label"); -- "2002:c633:6401::d5e3:7953:13eb:22e8" should be marked "temporary" here, we don't handle that right now
+ assert_equal(source(new_ip("2001:db8:1::d5e3:0:0:1", "IPv6"),
+ {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:1::d5e3:7953:13eb:22e8", "IPv6")}).addr,
+ "2001:db8:1::d5e3:7953:13eb:22e8",
+ "prefer temporary address") -- "2001:db8:1::2" should be marked "public" and "2001:db8:1::d5e3:7953:13eb:22e8" should be marked "temporary" here, we don't handle that right now
+end
+
+function destination(dest)
+ local order;
+ local new_ip = require"util.ip".new_ip;
+ order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("198.51.100.121", "IPv4")},
+ {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")})
+ assert_equal(order[1].addr, "2001:db8:1::1", "prefer matching scope");
+ assert_equal(order[2].addr, "198.51.100.121", "prefer matching scope");
+
+ order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("198.51.100.121", "IPv4")},
+ {new_ip("fe80::1", "IPv6"), new_ip("198.51.100.117", "IPv4")})
+ assert_equal(order[1].addr, "198.51.100.121", "prefer matching scope");
+ assert_equal(order[2].addr, "2001:db8:1::1", "prefer matching scope");
+
+ order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("10.1.2.3", "IPv4")},
+ {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")})
+ assert_equal(order[1].addr, "2001:db8:1::1", "prefer higher precedence");
+ assert_equal(order[2].addr, "10.1.2.3", "prefer higher precedence");
+
+ order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")},
+ {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "fe80::1", "prefer smaller scope");
+ assert_equal(order[2].addr, "2001:db8:1::1", "prefer smaller scope");
+
+--[[ "2001:db8:1::2" and "fe80::2" should be marked "care-of address", while "2001:db8:3::1" should be marked "home address", we can't currently handle this and would fail the test
+ order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")},
+ {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2001:db8:1::1", "prefer home address");
+ assert_equal(order[2].addr, "fe80::1", "prefer home address");
+]]
+
+--[[ "fe80::2" should be marked "deprecated", we can't currently handle this and would fail the test
+ order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")},
+ {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2001:db8:1::1", "avoid deprecated addresses");
+ assert_equal(order[2].addr, "fe80::1", "avoid deprecated addresses");
+]]
+
+ order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("2001:db8:3ffe::1", "IPv6")},
+ {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2001:db8:1::1", "longest matching prefix");
+ assert_equal(order[2].addr, "2001:db8:3ffe::1", "longest matching prefix");
+
+ order = dest({new_ip("2002:c633:6401::1", "IPv6"), new_ip("2001:db8:1::1", "IPv6")},
+ {new_ip("2002:c633:6401::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2002:c633:6401::1", "prefer matching label");
+ assert_equal(order[2].addr, "2001:db8:1::1", "prefer matching label");
+
+ order = dest({new_ip("2002:c633:6401::1", "IPv6"), new_ip("2001:db8:1::1", "IPv6")},
+ {new_ip("2002:c633:6401::2", "IPv6"), new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2001:db8:1::1", "prefer higher precedence");
+ assert_equal(order[2].addr, "2002:c633:6401::1", "prefer higher precedence");
+end
diff --git a/tests/test_util_stanza.lua b/tests/test_util_stanza.lua
index fce26f3a..20cc8274 100644
--- a/tests/test_util_stanza.lua
+++ b/tests/test_util_stanza.lua
@@ -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.
--
@@ -18,7 +18,7 @@ end
function deserialize(deserialize, st)
local stanza = st.stanza("message", { a = "a" });
-
+
local stanza2 = deserialize(st.preserialize(stanza));
assert_is(stanza2 and stanza.name, "deserialize returns a stanza");
assert_table(stanza2.attr, "Deserialized stanza has attributes");
diff --git a/tests/test_util_throttle.lua b/tests/test_util_throttle.lua
new file mode 100644
index 00000000..582f499d
--- /dev/null
+++ b/tests/test_util_throttle.lua
@@ -0,0 +1,34 @@
+
+local now = 0; -- wibbly-wobbly... timey-wimey... stuff
+local function predictable_gettime()
+ return now;
+end
+local function later(n)
+ now = now + n; -- time passes at a different rate
+end
+
+local function override_gettime(throttle)
+ local i = 0;
+ repeat
+ i = i + 1;
+ local name = debug.getupvalue(throttle.update, i);
+ if name then
+ debug.setupvalue(throttle.update, i, predictable_gettime);
+ return throttle;
+ end
+ until not name;
+end
+
+function create(create)
+ local a = override_gettime( create(3, 10) );
+
+ assert_equal(a:poll(1), true); -- 3 -> 2
+ assert_equal(a:poll(1), true); -- 2 -> 1
+ assert_equal(a:poll(1), true); -- 1 -> 0
+ assert_equal(a:poll(1), false); -- MEEP, out of credits!
+ later(1); -- ... what about
+ assert_equal(a:poll(1), false); -- now? - Still no!
+ later(9); -- Later that day
+ assert_equal(a:poll(1), true); -- Should be back at 3 credits ... 2
+end
+
diff --git a/tests/test_util_uuid.lua b/tests/test_util_uuid.lua
new file mode 100644
index 00000000..d3f72bb4
--- /dev/null
+++ b/tests/test_util_uuid.lua
@@ -0,0 +1,24 @@
+-- This tests the format, not the randomness
+
+-- https://tools.ietf.org/html/rfc4122#section-4.4
+
+local pattern = "^" .. table.concat({
+ string.rep("%x", 8),
+ string.rep("%x", 4),
+ "4" .. -- version
+ string.rep("%x", 3),
+ "[89ab]" .. -- reserved bits of 1 and 0
+ string.rep("%x", 3),
+ string.rep("%x", 12),
+}, "%-") .. "$";
+
+function generate(generate)
+ for i = 1, 100 do
+ assert_is(generate():match(pattern));
+ end
+end
+
+function seed(seed)
+ assert_equal(seed("random string here"), nil, "seed doesn't return anything");
+end
+
diff --git a/tests/util/logger.lua b/tests/util/logger.lua
index 35facd4e..c133e332 100644
--- a/tests/util/logger.lua
+++ b/tests/util/logger.lua
@@ -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 --git a/tools/ejabberd2prosody.lua b/tools/ejabberd2prosody.lua
index 069b5161..46a48f57 100755
--- a/tools/ejabberd2prosody.lua
+++ b/tools/ejabberd2prosody.lua
@@ -2,7 +2,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 --git a/tools/ejabberdsql2prosody.lua b/tools/ejabberdsql2prosody.lua
index 40be8190..69c8cfe8 100644
--- a/tools/ejabberdsql2prosody.lua
+++ b/tools/ejabberdsql2prosody.lua
@@ -2,7 +2,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 --git a/tools/erlparse.lua b/tools/erlparse.lua
index 174585d3..25c38bcf 100644
--- a/tools/erlparse.lua
+++ b/tools/erlparse.lua
@@ -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 --git a/tools/jabberd14sql2prosody.lua b/tools/jabberd14sql2prosody.lua
index 386bdcf0..e43dc296 100644
--- a/tools/jabberd14sql2prosody.lua
+++ b/tools/jabberd14sql2prosody.lua
@@ -5,242 +5,242 @@ do
local _parse_sql_actions = { [0] =
- 0, 1, 0, 1, 1, 2, 0, 2, 2, 0, 9, 2, 0, 10, 2, 0, 11, 2, 0, 13,
- 2, 1, 2, 2, 1, 6, 3, 0, 3, 4, 3, 0, 3, 5, 3, 0, 3, 7, 3, 0,
+ 0, 1, 0, 1, 1, 2, 0, 2, 2, 0, 9, 2, 0, 10, 2, 0, 11, 2, 0, 13,
+ 2, 1, 2, 2, 1, 6, 3, 0, 3, 4, 3, 0, 3, 5, 3, 0, 3, 7, 3, 0,
3, 8, 3, 0, 3, 12, 4, 0, 2, 3, 7, 4, 0, 3, 8, 11
};
local _parse_sql_trans_keys = { [0] =
- 0, 0, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47, 82, 82,
- 69, 69, 65, 65, 84, 84, 69, 69, 32, 32, 68, 84, 65,
- 65, 84, 84, 65, 65, 66, 66, 65, 65, 83, 83, 69, 69,
- 9, 47, 9, 96, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47,
- 10, 96, 10, 96, 9, 47, 9, 59, 45, 45, 10, 10, 42,
- 42, 10, 42, 10, 47, 65, 65, 66, 66, 76, 76, 69, 69,
- 32, 32, 73, 96, 70, 70, 32, 32, 78, 78, 79, 79, 84, 84,
- 32, 32, 69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83,
- 83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40, 40,
- 10, 10, 32, 41, 32, 32, 75, 96, 69, 69, 89, 89, 32, 32,
- 96, 96, 10, 96, 10, 96, 10, 10, 82, 82, 73, 73, 77,
- 77, 65, 65, 82, 82, 89, 89, 32, 32, 75, 75, 69, 69,
- 89, 89, 32, 32, 78, 78, 73, 73, 81, 81, 85, 85, 69, 69,
- 32, 32, 75, 75, 10, 96, 10, 96, 10, 10, 10, 59, 10,
- 59, 82, 82, 79, 79, 80, 80, 32, 32, 84, 84, 65, 65,
- 66, 66, 76, 76, 69, 69, 32, 32, 73, 73, 70, 70, 32, 32,
- 69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83, 83, 32,
- 32, 96, 96, 10, 96, 10, 96, 59, 59, 78, 78, 83, 83,
- 69, 69, 82, 82, 84, 84, 32, 32, 73, 73, 78, 78, 84, 84,
- 79, 79, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40,
- 86, 10, 41, 32, 32, 86, 86, 65, 65, 76, 76, 85, 85,
- 69, 69, 83, 83, 32, 32, 40, 40, 39, 78, 10, 92, 10, 92,
- 41, 44, 44, 59, 32, 78, 48, 57, 41, 57, 48, 57, 41,
- 57, 85, 85, 76, 76, 76, 76, 34, 116, 79, 79, 67, 67,
- 75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69, 69,
- 83, 83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 87,
- 87, 82, 82, 73, 73, 84, 84, 69, 69, 69, 69, 84, 84,
- 32, 32, 10, 59, 10, 59, 78, 83, 76, 76, 79, 79, 67, 67,
- 75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69,
+ 0, 0, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47, 82, 82,
+ 69, 69, 65, 65, 84, 84, 69, 69, 32, 32, 68, 84, 65,
+ 65, 84, 84, 65, 65, 66, 66, 65, 65, 83, 83, 69, 69,
+ 9, 47, 9, 96, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47,
+ 10, 96, 10, 96, 9, 47, 9, 59, 45, 45, 10, 10, 42,
+ 42, 10, 42, 10, 47, 65, 65, 66, 66, 76, 76, 69, 69,
+ 32, 32, 73, 96, 70, 70, 32, 32, 78, 78, 79, 79, 84, 84,
+ 32, 32, 69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83,
+ 83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40, 40,
+ 10, 10, 32, 41, 32, 32, 75, 96, 69, 69, 89, 89, 32, 32,
+ 96, 96, 10, 96, 10, 96, 10, 10, 82, 82, 73, 73, 77,
+ 77, 65, 65, 82, 82, 89, 89, 32, 32, 75, 75, 69, 69,
+ 89, 89, 32, 32, 78, 78, 73, 73, 81, 81, 85, 85, 69, 69,
+ 32, 32, 75, 75, 10, 96, 10, 96, 10, 10, 10, 59, 10,
+ 59, 82, 82, 79, 79, 80, 80, 32, 32, 84, 84, 65, 65,
+ 66, 66, 76, 76, 69, 69, 32, 32, 73, 73, 70, 70, 32, 32,
+ 69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83, 83, 32,
+ 32, 96, 96, 10, 96, 10, 96, 59, 59, 78, 78, 83, 83,
+ 69, 69, 82, 82, 84, 84, 32, 32, 73, 73, 78, 78, 84, 84,
+ 79, 79, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40,
+ 86, 10, 41, 32, 32, 86, 86, 65, 65, 76, 76, 85, 85,
+ 69, 69, 83, 83, 32, 32, 40, 40, 39, 78, 10, 92, 10, 92,
+ 41, 44, 44, 59, 32, 78, 48, 57, 41, 57, 48, 57, 41,
+ 57, 85, 85, 76, 76, 76, 76, 34, 116, 79, 79, 67, 67,
+ 75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69, 69,
+ 83, 83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 87,
+ 87, 82, 82, 73, 73, 84, 84, 69, 69, 69, 69, 84, 84,
+ 32, 32, 10, 59, 10, 59, 78, 83, 76, 76, 79, 79, 67, 67,
+ 75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69,
69, 83, 83, 69, 69, 9, 85, 0
};
local _parse_sql_key_spans = { [0] =
- 0, 1, 1, 1, 33, 38, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1,
- 39, 88, 1, 1, 1, 33, 38, 87, 87, 39, 51, 1, 1, 1, 33, 38, 1, 1, 1, 1,
- 1, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1,
- 1, 10, 1, 22, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 50, 50, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 47, 32, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 40, 83, 83, 4, 16, 47, 10, 17, 10, 17, 1, 1, 1, 83, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1, 33, 38, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1,
+ 39, 88, 1, 1, 1, 33, 38, 87, 87, 39, 51, 1, 1, 1, 33, 38, 1, 1, 1, 1,
+ 1, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1,
+ 1, 10, 1, 22, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 50, 50, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 47, 32, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 40, 83, 83, 4, 16, 47, 10, 17, 10, 17, 1, 1, 1, 83, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1,
1, 50, 50, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 77
};
local _parse_sql_index_offsets = { [0] =
- 0, 0, 2, 4, 6, 40, 79, 81, 83, 85, 87, 89, 91, 109, 111, 113, 115, 117, 119, 121,
- 123, 163, 252, 254, 256, 258, 292, 331, 419, 507, 547, 599, 601, 603, 605, 639, 678, 680, 682, 684,
- 686, 688, 713, 715, 717, 719, 721, 723, 725, 727, 729, 731, 733, 735, 737, 739, 741, 829, 917, 919,
- 921, 923, 934, 936, 959, 961, 963, 965, 967, 1055, 1143, 1145, 1147, 1149, 1151, 1153, 1155, 1157, 1159, 1161,
- 1163, 1165, 1167, 1169, 1171, 1173, 1175, 1177, 1179, 1181, 1269, 1357, 1359, 1410, 1461, 1463, 1465, 1467, 1469, 1471,
- 1473, 1475, 1477, 1479, 1481, 1483, 1485, 1487, 1489, 1491, 1493, 1495, 1497, 1499, 1501, 1503, 1591, 1679, 1681, 1683,
- 1685, 1687, 1689, 1691, 1693, 1695, 1697, 1699, 1701, 1703, 1705, 1793, 1881, 1883, 1931, 1964, 1966, 1968, 1970, 1972,
- 1974, 1976, 1978, 1980, 1982, 2023, 2107, 2191, 2196, 2213, 2261, 2272, 2290, 2301, 2319, 2321, 2323, 2325, 2409, 2411,
- 2413, 2415, 2417, 2419, 2421, 2423, 2425, 2427, 2429, 2431, 2433, 2521, 2609, 2611, 2613, 2615, 2617, 2619, 2621, 2623,
+ 0, 0, 2, 4, 6, 40, 79, 81, 83, 85, 87, 89, 91, 109, 111, 113, 115, 117, 119, 121,
+ 123, 163, 252, 254, 256, 258, 292, 331, 419, 507, 547, 599, 601, 603, 605, 639, 678, 680, 682, 684,
+ 686, 688, 713, 715, 717, 719, 721, 723, 725, 727, 729, 731, 733, 735, 737, 739, 741, 829, 917, 919,
+ 921, 923, 934, 936, 959, 961, 963, 965, 967, 1055, 1143, 1145, 1147, 1149, 1151, 1153, 1155, 1157, 1159, 1161,
+ 1163, 1165, 1167, 1169, 1171, 1173, 1175, 1177, 1179, 1181, 1269, 1357, 1359, 1410, 1461, 1463, 1465, 1467, 1469, 1471,
+ 1473, 1475, 1477, 1479, 1481, 1483, 1485, 1487, 1489, 1491, 1493, 1495, 1497, 1499, 1501, 1503, 1591, 1679, 1681, 1683,
+ 1685, 1687, 1689, 1691, 1693, 1695, 1697, 1699, 1701, 1703, 1705, 1793, 1881, 1883, 1931, 1964, 1966, 1968, 1970, 1972,
+ 1974, 1976, 1978, 1980, 1982, 2023, 2107, 2191, 2196, 2213, 2261, 2272, 2290, 2301, 2319, 2321, 2323, 2325, 2409, 2411,
+ 2413, 2415, 2417, 2419, 2421, 2423, 2425, 2427, 2429, 2431, 2433, 2521, 2609, 2611, 2613, 2615, 2617, 2619, 2621, 2623,
2625, 2627, 2678, 2729, 2736, 2738, 2740, 2742, 2744, 2746, 2748, 2750, 2752, 2754, 2756, 2758, 2760
};
local _parse_sql_indicies = { [0] =
- 0, 1, 2, 0, 3, 1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
- 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3,
- 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
- 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 6, 3, 7,
- 1, 8, 1, 9, 1, 10, 1, 11, 1, 12, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 14, 1, 15, 1, 16, 1, 17, 1, 18, 1, 19, 1, 20,
- 1, 21, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24,
- 1, 25, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24,
- 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 27, 1, 23, 27, 28, 1, 29, 28,
- 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
- 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 29, 28, 28, 28, 28, 28, 28, 28,
- 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
- 28, 28, 28, 28, 30, 28, 28, 28, 28, 22, 28, 32, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 1, 31, 32,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 33, 31, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 36, 1, 37, 1, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 36, 1, 37, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 38,
- 1, 35, 38, 39, 1, 40, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
- 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 40,
- 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
- 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 39, 39, 39, 34, 39, 42, 1,
- 43, 1, 44, 1, 45, 1, 46, 1, 47, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 48, 1, 49, 1, 50, 1, 51, 1, 52,
- 1, 53, 1, 54, 1, 55, 1, 56, 1, 57, 1, 58, 1, 59, 1, 60, 1, 61, 1, 48,
- 1, 63, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
- 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
- 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
- 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
- 62, 62, 62, 62, 62, 62, 62, 1, 62, 65, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 66, 64, 67, 1, 68,
- 1, 69, 1, 70, 1, 1, 1, 1, 1, 1, 1, 1, 71, 1, 72, 1, 73, 1, 1, 1,
- 1, 74, 1, 1, 1, 1, 75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 76, 1, 77,
- 1, 78, 1, 79, 1, 80, 1, 82, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
- 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
- 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
- 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
- 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 1, 81, 82, 81, 81, 81, 81,
- 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
- 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
- 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
- 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
- 81, 83, 81, 69, 83, 84, 1, 85, 1, 86, 1, 87, 1, 88, 1, 89, 1, 90, 1, 91,
- 1, 92, 1, 93, 1, 83, 1, 94, 1, 95, 1, 96, 1, 97, 1, 98, 1, 99, 1, 73,
- 1, 101, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
- 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
- 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
- 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
- 100, 100, 100, 100, 100, 100, 100, 1, 100, 103, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
- 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
- 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
- 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
- 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 104, 102, 105, 83, 106,
- 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
- 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
- 71, 71, 71, 71, 71, 71, 71, 71, 107, 71, 108, 71, 71, 71, 71, 71, 71, 71, 71, 71,
- 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
- 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 107,
- 71, 109, 1, 110, 1, 111, 1, 112, 1, 113, 1, 114, 1, 115, 1, 116, 1, 117, 1, 118,
- 1, 119, 1, 120, 1, 121, 1, 122, 1, 123, 1, 124, 1, 125, 1, 126, 1, 127, 1, 128,
- 1, 129, 1, 131, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
- 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
- 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
- 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
- 130, 130, 130, 130, 130, 130, 130, 130, 130, 1, 130, 131, 130, 130, 130, 130, 130, 130, 130, 130,
- 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
- 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
- 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
- 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 132, 130, 6,
- 1, 133, 1, 134, 1, 135, 1, 136, 1, 137, 1, 138, 1, 139, 1, 140, 1, 141, 1, 142,
- 1, 143, 1, 144, 1, 146, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
- 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
- 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
- 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
- 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 1, 145, 148, 147, 147, 147, 147, 147, 147,
- 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
- 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
- 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
- 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 149,
- 147, 150, 1, 151, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 152, 1, 153, 151, 151, 151, 151, 151, 151, 151, 151,
- 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151,
- 151, 151, 154, 151, 155, 1, 152, 1, 156, 1, 157, 1, 158, 1, 159, 1, 160, 1, 161, 1,
- 162, 1, 163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165,
- 165, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 166, 1, 168, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167,
- 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 169, 167, 167, 167, 167, 167, 167, 167,
- 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167,
- 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167,
- 167, 167, 167, 167, 167, 170, 167, 172, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
- 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 173, 171, 171, 171,
- 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
- 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
- 171, 171, 171, 171, 171, 171, 171, 171, 171, 174, 171, 175, 1, 1, 176, 1, 161, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 177, 1, 178, 1, 1, 1, 1, 1, 1,
- 163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 166,
- 1, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 180, 1, 1, 181, 1, 182, 1, 179,
- 179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183,
- 1, 180, 1, 1, 181, 1, 1, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 1, 184,
- 1, 185, 1, 186, 1, 171, 1, 1, 171, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 171,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 171, 1, 171, 1, 1, 171, 1, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 171, 1, 1, 1, 171, 1, 171, 1, 187, 1, 188, 1, 189, 1, 190, 1, 191, 1, 192,
- 1, 193, 1, 194, 1, 195, 1, 196, 1, 197, 1, 198, 1, 200, 199, 199, 199, 199, 199, 199,
- 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
- 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
- 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
- 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 1,
- 199, 200, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
- 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
- 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
- 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
- 199, 199, 199, 199, 199, 199, 199, 201, 199, 202, 1, 203, 1, 204, 1, 205, 1, 206, 1, 132,
- 1, 207, 1, 208, 1, 209, 1, 210, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
- 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
- 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 211, 209, 2, 209,
- 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
- 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
- 209, 209, 209, 209, 209, 209, 209, 211, 209, 212, 1, 1, 1, 1, 213, 1, 214, 1, 215, 1,
- 216, 1, 217, 1, 218, 1, 219, 1, 220, 1, 221, 1, 222, 1, 223, 1, 132, 1, 127, 1,
- 6, 2, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 224, 1, 225, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 226, 227,
+ 0, 1, 2, 0, 3, 1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3,
+ 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 6, 3, 7,
+ 1, 8, 1, 9, 1, 10, 1, 11, 1, 12, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 14, 1, 15, 1, 16, 1, 17, 1, 18, 1, 19, 1, 20,
+ 1, 21, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24,
+ 1, 25, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24,
+ 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 27, 1, 23, 27, 28, 1, 29, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 29, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 30, 28, 28, 28, 28, 22, 28, 32, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 1, 31, 32,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 33, 31, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 36, 1, 37, 1, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 36, 1, 37, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 38,
+ 1, 35, 38, 39, 1, 40, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
+ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 40,
+ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
+ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 39, 39, 39, 34, 39, 42, 1,
+ 43, 1, 44, 1, 45, 1, 46, 1, 47, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 48, 1, 49, 1, 50, 1, 51, 1, 52,
+ 1, 53, 1, 54, 1, 55, 1, 56, 1, 57, 1, 58, 1, 59, 1, 60, 1, 61, 1, 48,
+ 1, 63, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 1, 62, 65, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 66, 64, 67, 1, 68,
+ 1, 69, 1, 70, 1, 1, 1, 1, 1, 1, 1, 1, 71, 1, 72, 1, 73, 1, 1, 1,
+ 1, 74, 1, 1, 1, 1, 75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 76, 1, 77,
+ 1, 78, 1, 79, 1, 80, 1, 82, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 1, 81, 82, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 83, 81, 69, 83, 84, 1, 85, 1, 86, 1, 87, 1, 88, 1, 89, 1, 90, 1, 91,
+ 1, 92, 1, 93, 1, 83, 1, 94, 1, 95, 1, 96, 1, 97, 1, 98, 1, 99, 1, 73,
+ 1, 101, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
+ 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
+ 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
+ 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
+ 100, 100, 100, 100, 100, 100, 100, 1, 100, 103, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 104, 102, 105, 83, 106,
+ 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
+ 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
+ 71, 71, 71, 71, 71, 71, 71, 71, 107, 71, 108, 71, 71, 71, 71, 71, 71, 71, 71, 71,
+ 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
+ 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 107,
+ 71, 109, 1, 110, 1, 111, 1, 112, 1, 113, 1, 114, 1, 115, 1, 116, 1, 117, 1, 118,
+ 1, 119, 1, 120, 1, 121, 1, 122, 1, 123, 1, 124, 1, 125, 1, 126, 1, 127, 1, 128,
+ 1, 129, 1, 131, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
+ 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
+ 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
+ 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
+ 130, 130, 130, 130, 130, 130, 130, 130, 130, 1, 130, 131, 130, 130, 130, 130, 130, 130, 130, 130,
+ 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
+ 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
+ 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
+ 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 132, 130, 6,
+ 1, 133, 1, 134, 1, 135, 1, 136, 1, 137, 1, 138, 1, 139, 1, 140, 1, 141, 1, 142,
+ 1, 143, 1, 144, 1, 146, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
+ 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
+ 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
+ 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
+ 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 1, 145, 148, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 149,
+ 147, 150, 1, 151, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 152, 1, 153, 151, 151, 151, 151, 151, 151, 151, 151,
+ 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151,
+ 151, 151, 154, 151, 155, 1, 152, 1, 156, 1, 157, 1, 158, 1, 159, 1, 160, 1, 161, 1,
+ 162, 1, 163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165,
+ 165, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 166, 1, 168, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167,
+ 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 169, 167, 167, 167, 167, 167, 167, 167,
+ 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167,
+ 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167,
+ 167, 167, 167, 167, 167, 170, 167, 172, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
+ 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 173, 171, 171, 171,
+ 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
+ 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
+ 171, 171, 171, 171, 171, 171, 171, 171, 171, 174, 171, 175, 1, 1, 176, 1, 161, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 177, 1, 178, 1, 1, 1, 1, 1, 1,
+ 163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 166,
+ 1, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 180, 1, 1, 181, 1, 182, 1, 179,
+ 179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183,
+ 1, 180, 1, 1, 181, 1, 1, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 1, 184,
+ 1, 185, 1, 186, 1, 171, 1, 1, 171, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 171,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 171, 1, 171, 1, 1, 171, 1, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 171, 1, 1, 1, 171, 1, 171, 1, 187, 1, 188, 1, 189, 1, 190, 1, 191, 1, 192,
+ 1, 193, 1, 194, 1, 195, 1, 196, 1, 197, 1, 198, 1, 200, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 1,
+ 199, 200, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 199, 199, 201, 199, 202, 1, 203, 1, 204, 1, 205, 1, 206, 1, 132,
+ 1, 207, 1, 208, 1, 209, 1, 210, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 211, 209, 2, 209,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
+ 209, 209, 209, 209, 209, 209, 209, 211, 209, 212, 1, 1, 1, 1, 213, 1, 214, 1, 215, 1,
+ 216, 1, 217, 1, 218, 1, 219, 1, 220, 1, 221, 1, 222, 1, 223, 1, 132, 1, 127, 1,
+ 6, 2, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 224, 1, 225, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 226, 227,
1, 1, 1, 1, 228, 1, 1, 229, 1, 1, 1, 1, 1, 1, 230, 1, 231, 1, 0
};
local _parse_sql_trans_targs = { [0] =
- 2, 0, 196, 4, 4, 5, 196, 7, 8, 9, 10, 11, 12, 13, 36, 14, 15, 16, 17, 18,
- 19, 20, 21, 21, 22, 24, 27, 23, 25, 25, 26, 28, 28, 29, 30, 30, 31, 33, 32, 34,
- 34, 35, 37, 38, 39, 40, 41, 42, 56, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
- 54, 55, 57, 57, 57, 57, 58, 59, 60, 61, 62, 92, 63, 64, 71, 82, 89, 65, 66, 67,
- 68, 69, 69, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88,
- 90, 90, 90, 90, 91, 70, 92, 93, 196, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,
- 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 116, 117, 119, 120, 121, 122, 123, 124, 125,
- 126, 127, 128, 129, 130, 131, 131, 131, 131, 132, 133, 134, 137, 134, 135, 136, 138, 139, 140, 141,
- 142, 143, 144, 145, 150, 151, 154, 146, 146, 147, 157, 146, 146, 147, 157, 148, 149, 196, 144, 151,
- 148, 149, 152, 153, 155, 156, 147, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171,
- 171, 172, 173, 174, 175, 176, 177, 179, 180, 181, 181, 182, 184, 195, 185, 186, 187, 188, 189, 190,
+ 2, 0, 196, 4, 4, 5, 196, 7, 8, 9, 10, 11, 12, 13, 36, 14, 15, 16, 17, 18,
+ 19, 20, 21, 21, 22, 24, 27, 23, 25, 25, 26, 28, 28, 29, 30, 30, 31, 33, 32, 34,
+ 34, 35, 37, 38, 39, 40, 41, 42, 56, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+ 54, 55, 57, 57, 57, 57, 58, 59, 60, 61, 62, 92, 63, 64, 71, 82, 89, 65, 66, 67,
+ 68, 69, 69, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88,
+ 90, 90, 90, 90, 91, 70, 92, 93, 196, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,
+ 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 116, 117, 119, 120, 121, 122, 123, 124, 125,
+ 126, 127, 128, 129, 130, 131, 131, 131, 131, 132, 133, 134, 137, 134, 135, 136, 138, 139, 140, 141,
+ 142, 143, 144, 145, 150, 151, 154, 146, 146, 147, 157, 146, 146, 147, 157, 148, 149, 196, 144, 151,
+ 148, 149, 152, 153, 155, 156, 147, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171,
+ 171, 172, 173, 174, 175, 176, 177, 179, 180, 181, 181, 182, 184, 195, 185, 186, 187, 188, 189, 190,
191, 192, 193, 194, 1, 3, 6, 94, 118, 158, 178, 183
};
local _parse_sql_trans_actions = { [0] =
- 1, 0, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1,
- 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 5, 20, 1, 3, 30, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 5, 20, 1, 3, 26, 3, 3, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 5, 20, 1, 3, 42, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1,
- 1, 1, 11, 1, 5, 5, 1, 5, 20, 46, 5, 1, 3, 34, 1, 14, 1, 17, 1, 1,
- 51, 38, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1,
+ 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 5, 20, 1, 3, 30, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 5, 20, 1, 3, 26, 3, 3, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 5, 20, 1, 3, 42, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1,
+ 1, 1, 11, 1, 5, 5, 1, 5, 20, 46, 5, 1, 3, 34, 1, 14, 1, 17, 1, 1,
+ 51, 38, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
};
@@ -277,7 +277,7 @@ function parse_sql(data, h)
local mark, token;
local table_name, columns, value_lists, value_list, value_count;
-
+
cs = parse_sql_start;
-- ragel flat exec
@@ -322,10 +322,10 @@ function parse_sql(data, h)
_inds = _parse_sql_index_offsets[cs];
_slen = _parse_sql_key_spans[cs];
- if _slen > 0 and
- _parse_sql_trans_keys[_keys] <= data:byte(p) and
- data:byte(p) <= _parse_sql_trans_keys[_keys + 1] then
- _trans = _parse_sql_indicies[ _inds + data:byte(p) - _parse_sql_trans_keys[_keys] ];
+ if _slen > 0 and
+ _parse_sql_trans_keys[_keys] <= data:byte(p) and
+ data:byte(p) <= _parse_sql_trans_keys[_keys + 1] then
+ _trans = _parse_sql_indicies[ _inds + data:byte(p) - _parse_sql_trans_keys[_keys] ];
else _trans =_parse_sql_indicies[ _inds + _slen ]; end
cs = _parse_sql_trans_targs[_trans];
@@ -364,7 +364,7 @@ function parse_sql(data, h)
h.create(table_name, columns); -- ACTION
elseif _tempval == 7 then --4 FROM_STATE_ACTION_SWITCH
-- line 65 "sql.rl" -- end of line directive
-
+
value_count = value_count + 1; value_list[value_count] = token:gsub("\\.", _sql_unescapes);
-- ACTION
elseif _tempval == 8 then --4 FROM_STATE_ACTION_SWITCH
@@ -392,7 +392,7 @@ function parse_sql(data, h)
end
if _trigger_goto then _continue = true; break; end
- end -- endif
+ end -- endif
if _goto_level <= _again then
if cs == 0 then
diff --git a/tools/migration/migrator/prosody_sql.lua b/tools/migration/migrator/prosody_sql.lua
index 27b5835e..180ae910 100644
--- a/tools/migration/migrator/prosody_sql.lua
+++ b/tools/migration/migrator/prosody_sql.lua
@@ -24,7 +24,7 @@ local function create_table(connection, params)
elseif params.driver == "MySQL" then
create_sql = create_sql:gsub("`value` TEXT", "`value` MEDIUMTEXT");
end
-
+
local stmt = connection:prepare(create_sql);
if stmt then
local ok = stmt:execute();
diff --git a/tools/migration/prosody-migrator.lua b/tools/migration/prosody-migrator.lua
index 7c933b88..b86e9892 100644
--- a/tools/migration/prosody-migrator.lua
+++ b/tools/migration/prosody-migrator.lua
@@ -115,7 +115,7 @@ if have_err then
print("");
os.exit(1);
end
-
+
local itype = config[from_store].type;
local otype = config[to_store].type;
local reader = require("migrator."..itype).reader(config[from_store]);
diff --git a/tools/openfire2prosody.lua b/tools/openfire2prosody.lua
index 5ef47602..cd3e62e5 100644
--- a/tools/openfire2prosody.lua
+++ b/tools/openfire2prosody.lua
@@ -1,7 +1,7 @@
#!/usr/bin/env lua
-- Prosody IM
-- Copyright (C) 2008-2009 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
diff --git a/tools/xep227toprosody.lua b/tools/xep227toprosody.lua
index 81c9863b..521851e9 100755
--- a/tools/xep227toprosody.lua
+++ b/tools/xep227toprosody.lua
@@ -3,7 +3,7 @@
-- Copyright (C) 2008-2009 Matthew Wild
-- Copyright (C) 2008-2009 Waqas Hussain
-- Copyright (C) 2010 Stefan Gehn
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
diff --git a/util-src/Makefile b/util-src/Makefile
index 3a1ba3f2..9c6c377c 100644
--- a/util-src/Makefile
+++ b/util-src/Makefile
@@ -1,41 +1,34 @@
include ../config.unix
-LUA_SUFFIX?=5.1
-LUA_INCDIR?=/usr/include/lua$(LUA_SUFFIX)
-LUA_LIB?=lua$(LUA_SUFFIX)
-IDN_LIB?=idn
-OPENSSL_LIB?=crypto
-CC?=gcc
-CXX?=g++
-LD?=gcc
-CFLAGS+=-ggdb
+CFLAGS+=-ggdb -Wall -pedantic -I$(LUA_INCDIR)
+
+INSTALL_DATA=install -m644
+TARGET?=../util/
+
+ALL=encodings.so hashes.so net.so pposix.so signal.so table.so ringbuffer.so
+
+ifdef RANDOM
+ALL+=crand.so
+endif
.PHONY: all install clean
.SUFFIXES: .c .o .so
-all: encodings.so hashes.so net.so pposix.so signal.so
+all: $(ALL)
-install: encodings.so hashes.so net.so pposix.so signal.so
- install *.so ../util/
+install: $(ALL)
+ $(INSTALL_DATA) $^ $(TARGET)
clean:
- rm -f *.o
- rm -f *.so
- rm -f ../util/*.so
-
-encodings.so: encodings.o
- MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET;
- $(CC) -o $@ $< $(LDFLAGS) $(IDNA_LIBS)
+ rm -f $(ALL) $(patsubst %.so,%.o,$(ALL))
-hashes.so: hashes.o
- MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET;
- $(CC) -o $@ $< $(LDFLAGS) -l$(OPENSSL_LIB)
+encodings.so: LDLIBS+=$(IDNA_LIBS)
-.c.o:
- $(CC) $(CFLAGS) -I$(LUA_INCDIR) -c -o $@ $<
+hashes.so: LDLIBS+=$(OPENSSL_LIBS)
-.o.so:
- MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET;
- $(LD) -o $@ $< $(LDFLAGS)
+crand.o: CFLAGS+=-DWITH_$(RANDOM)
+crand.so: LDLIBS+=$(RANDOM_LIBS)
+%.so: %.o
+ $(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS)
diff --git a/util-src/crand.c b/util-src/crand.c
new file mode 100644
index 00000000..735135fb
--- /dev/null
+++ b/util-src/crand.c
@@ -0,0 +1,156 @@
+/* Prosody IM
+-- Copyright (C) 2008-2016 Matthew Wild
+-- Copyright (C) 2008-2016 Waqas Hussain
+-- Copyright (C) 2016 Kim Alvefur
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+*/
+
+/*
+* crand.c
+* C PRNG interface
+*/
+
+#include "lualib.h"
+#include "lauxlib.h"
+
+#include <string.h>
+#include <errno.h>
+
+/*
+ * TODO: Decide on fixed size or dynamically allocated buffer
+ */
+#if 1
+#include <stdlib.h>
+#else
+#define BUFLEN 256
+#endif
+
+#if defined(WITH_GETRANDOM)
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <linux/random.h>
+
+#ifndef SYS_getrandom
+#error getrandom() requires Linux 3.17 or later
+#endif
+
+/* Was this not supposed to be a function? */
+int getrandom(char *buf, size_t len, int flags) {
+ return syscall(SYS_getrandom, buf, len, flags);
+}
+
+#elif defined(WITH_ARC4RANDOM)
+#include <stdlib.h>
+#elif defined(WITH_OPENSSL)
+#include <openssl/rand.h>
+#else
+#error util.crand compiled without a random source
+#endif
+
+int Lrandom(lua_State *L) {
+#ifdef BUFLEN
+ unsigned char buf[BUFLEN];
+#else
+ unsigned char *buf;
+#endif
+ int ret = 0;
+ size_t len = (size_t)luaL_checkint(L, 1);
+#ifdef BUFLEN
+ len = len > BUFLEN ? BUFLEN : len;
+#else
+ buf = malloc(len);
+
+ if(buf == NULL) {
+ lua_pushnil(L);
+ lua_pushstring(L, "out of memory");
+ /* or it migth be better to
+ * return lua_error(L);
+ */
+ return 2;
+ }
+#endif
+
+#if defined(WITH_GETRANDOM)
+ ret = getrandom(buf, len, 0);
+
+ if(ret < 0) {
+#ifndef BUFLEN
+ free(buf);
+#endif
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return 3;
+ }
+
+#elif defined(WITH_ARC4RANDOM)
+ arc4random_buf(buf, len);
+ ret = len;
+#elif defined(WITH_OPENSSL)
+ ret = RAND_bytes(buf, len);
+
+ if(ret == 1) {
+ ret = len;
+ } else {
+#ifndef BUFLEN
+ free(buf);
+#endif
+ lua_pushnil(L);
+ lua_pushstring(L, "failed");
+ /* lua_pushinteger(L, ERR_get_error()); */
+ return 2;
+ }
+
+#endif
+
+ lua_pushlstring(L, buf, ret);
+#ifndef BUFLEN
+ free(buf);
+#endif
+ return 1;
+}
+
+#ifdef ENABLE_SEEDING
+int Lseed(lua_State *L) {
+ size_t len;
+ const char *seed = lua_tolstring(L, 1, &len);
+
+#if defined(WITH_OPENSSL)
+ RAND_add(seed, len, len);
+ return 0;
+#else
+ lua_pushnil(L);
+ lua_pushliteral(L, "not-supported");
+ return 2;
+#endif
+}
+#endif
+
+int luaopen_util_crand(lua_State *L) {
+ lua_newtable(L);
+ lua_pushcfunction(L, Lrandom);
+ lua_setfield(L, -2, "bytes");
+#ifdef ENABLE_SEEDING
+ lua_pushcfunction(L, Lseed);
+ lua_setfield(L, -2, "seed");
+#endif
+
+#if defined(WITH_GETRANDOM)
+ lua_pushstring(L, "Linux");
+#elif defined(WITH_ARC4RANDOM)
+ lua_pushstring(L, "arc4random()");
+#elif defined(WITH_OPENSSL)
+ lua_pushstring(L, "OpenSSL");
+#endif
+ lua_setfield(L, -2, "_source");
+
+#if defined(WITH_OPENSSL) && defined(_WIN32)
+ /* Do we need to seed this on Windows? */
+#endif
+
+ return 1;
+}
+
diff --git a/util-src/encodings.c b/util-src/encodings.c
index 91826ca4..35677095 100644
--- a/util-src/encodings.c
+++ b/util-src/encodings.c
@@ -2,7 +2,7 @@
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 1994-2015 Lua.org, PUC-Rio.
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -21,97 +21,131 @@
#include "lua.h"
#include "lauxlib.h"
+#if (LUA_VERSION_NUM == 501)
+#define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R)
+#endif
+
/***************** BASE64 *****************/
-static const char code[]=
-"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char code[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-static void base64_encode(luaL_Buffer *b, unsigned int c1, unsigned int c2, unsigned int c3, int n)
-{
- unsigned long tuple=c3+256UL*(c2+256UL*c1);
+static void base64_encode(luaL_Buffer* b, unsigned int c1, unsigned int c2, unsigned int c3, int n) {
+ unsigned long tuple = c3 + 256UL * (c2 + 256UL * c1);
int i;
char s[4];
- for (i=0; i<4; i++) {
- s[3-i] = code[tuple % 64];
+
+ for(i = 0; i < 4; i++) {
+ s[3 - i] = code[tuple % 64];
tuple /= 64;
}
- for (i=n+1; i<4; i++) s[i]='=';
- luaL_addlstring(b,s,4);
+
+ for(i = n + 1; i < 4; i++) {
+ s[i] = '=';
+ }
+
+ luaL_addlstring(b, s, 4);
}
-static int Lbase64_encode(lua_State *L) /** encode(s) */
-{
+static int Lbase64_encode(lua_State* L) { /** encode(s) */
size_t l;
- const unsigned char *s=(const unsigned char*)luaL_checklstring(L,1,&l);
+ const unsigned char* s = (const unsigned char*)luaL_checklstring(L, 1, &l);
luaL_Buffer b;
int n;
- luaL_buffinit(L,&b);
- for (n=l/3; n--; s+=3) base64_encode(&b,s[0],s[1],s[2],3);
- switch (l%3)
- {
- case 1: base64_encode(&b,s[0],0,0,1); break;
- case 2: base64_encode(&b,s[0],s[1],0,2); break;
+ luaL_buffinit(L, &b);
+
+ for(n = l / 3; n--; s += 3) {
+ base64_encode(&b, s[0], s[1], s[2], 3);
+ }
+
+ switch(l % 3) {
+ case 1:
+ base64_encode(&b, s[0], 0, 0, 1);
+ break;
+ case 2:
+ base64_encode(&b, s[0], s[1], 0, 2);
+ break;
}
+
luaL_pushresult(&b);
return 1;
}
-static void base64_decode(luaL_Buffer *b, int c1, int c2, int c3, int c4, int n)
-{
- unsigned long tuple=c4+64L*(c3+64L*(c2+64L*c1));
+static void base64_decode(luaL_Buffer* b, int c1, int c2, int c3, int c4, int n) {
+ unsigned long tuple = c4 + 64L * (c3 + 64L * (c2 + 64L * c1));
char s[3];
- switch (--n)
- {
- case 3: s[2]=(char) tuple;
- case 2: s[1]=(char) (tuple >> 8);
- case 1: s[0]=(char) (tuple >> 16);
+
+ switch(--n) {
+ case 3:
+ s[2] = (char) tuple;
+ case 2:
+ s[1] = (char)(tuple >> 8);
+ case 1:
+ s[0] = (char)(tuple >> 16);
}
- luaL_addlstring(b,s,n);
+
+ luaL_addlstring(b, s, n);
}
-static int Lbase64_decode(lua_State *L) /** decode(s) */
-{
+static int Lbase64_decode(lua_State* L) { /** decode(s) */
size_t l;
- const char *s=luaL_checklstring(L,1,&l);
+ const char* s = luaL_checklstring(L, 1, &l);
luaL_Buffer b;
- int n=0;
+ int n = 0;
char t[4];
- luaL_buffinit(L,&b);
- for (;;)
- {
- int c=*s++;
- switch (c)
- {
- const char *p;
+ luaL_buffinit(L, &b);
+
+ for(;;) {
+ int c = *s++;
+
+ switch(c) {
+ const char* p;
default:
- p=strchr(code,c); if (p==NULL) return 0;
- t[n++]= (char) (p-code);
- if (n==4)
- {
- base64_decode(&b,t[0],t[1],t[2],t[3],4);
- n=0;
+ p = strchr(code, c);
+
+ if(p == NULL) {
+ return 0;
}
+
+ t[n++] = (char)(p - code);
+
+ if(n == 4) {
+ base64_decode(&b, t[0], t[1], t[2], t[3], 4);
+ n = 0;
+ }
+
break;
case '=':
- switch (n)
- {
- case 1: base64_decode(&b,t[0],0,0,0,1); break;
- case 2: base64_decode(&b,t[0],t[1],0,0,2); break;
- case 3: base64_decode(&b,t[0],t[1],t[2],0,3); break;
+
+ switch(n) {
+ case 1:
+ base64_decode(&b, t[0], 0, 0, 0, 1);
+ break;
+ case 2:
+ base64_decode(&b, t[0], t[1], 0, 0, 2);
+ break;
+ case 3:
+ base64_decode(&b, t[0], t[1], t[2], 0, 3);
+ break;
}
- n=0;
+
+ n = 0;
break;
case 0:
luaL_pushresult(&b);
return 1;
- case '\n': case '\r': case '\t': case ' ': case '\f': case '\b':
+ case '\n':
+ case '\r':
+ case '\t':
+ case ' ':
+ case '\f':
+ case '\b':
break;
}
}
}
-static const luaL_Reg Reg_base64[] =
-{
+static const luaL_Reg Reg_base64[] = {
{ "encode", Lbase64_encode },
{ "decode", Lbase64_decode },
{ NULL, NULL }
@@ -129,70 +163,89 @@ static const luaL_Reg Reg_base64[] =
/*
* Decode one UTF-8 sequence, returning NULL if byte sequence is invalid.
*/
-static const char *utf8_decode (const char *o, int *val) {
+static const char* utf8_decode(const char* o, int* val) {
static unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF};
- const unsigned char *s = (const unsigned char *)o;
+ const unsigned char* s = (const unsigned char*)o;
unsigned int c = s[0];
unsigned int res = 0; /* final result */
- if (c < 0x80) /* ascii? */
+
+ if(c < 0x80) { /* ascii? */
res = c;
- else {
+ } else {
int count = 0; /* to count number of continuation bytes */
- while (c & 0x40) { /* still have continuation bytes? */
+
+ while(c & 0x40) { /* still have continuation bytes? */
int cc = s[++count]; /* read next byte */
- if ((cc & 0xC0) != 0x80) /* not a continuation byte? */
- return NULL; /* invalid byte sequence */
+
+ if((cc & 0xC0) != 0x80) { /* not a continuation byte? */
+ return NULL; /* invalid byte sequence */
+ }
+
res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */
c <<= 1; /* to test next bit */
}
+
res |= ((c & 0x7F) << (count * 5)); /* add first byte */
- if (count > 3 || res > MAXUNICODE || res <= limits[count] || (0xd800 <= res && res <= 0xdfff) )
- return NULL; /* invalid byte sequence */
+
+ if(count > 3 || res > MAXUNICODE || res <= limits[count] || (0xd800 <= res && res <= 0xdfff)) {
+ return NULL; /* invalid byte sequence */
+ }
+
s += count; /* skip continuation bytes read */
}
- if (val) *val = res;
- return (const char *)s + 1; /* +1 to include first byte */
+
+ if(val) {
+ *val = res;
+ }
+
+ return (const char*)s + 1; /* +1 to include first byte */
}
/*
* Check that a string is valid UTF-8
* Returns NULL if not
*/
-const char* check_utf8 (lua_State *L, int idx, size_t *l) {
+const char* check_utf8(lua_State* L, int idx, size_t* l) {
size_t pos, len;
- const char *s = luaL_checklstring(L, 1, &len);
+ const char* s = luaL_checklstring(L, 1, &len);
pos = 0;
- while (pos <= len) {
- const char *s1 = utf8_decode(s + pos, NULL);
- if (s1 == NULL) { /* conversion error? */
+
+ while(pos <= len) {
+ const char* s1 = utf8_decode(s + pos, NULL);
+
+ if(s1 == NULL) { /* conversion error? */
return NULL;
}
+
pos = s1 - s;
}
+
if(l != NULL) {
*l = len;
}
+
return s;
}
-static int Lutf8_valid(lua_State *L) {
+static int Lutf8_valid(lua_State* L) {
lua_pushboolean(L, check_utf8(L, 1, NULL) != NULL);
return 1;
}
-static int Lutf8_length(lua_State *L) {
+static int Lutf8_length(lua_State* L) {
size_t len;
+
if(!check_utf8(L, 1, &len)) {
lua_pushnil(L);
lua_pushliteral(L, "invalid utf8");
return 2;
}
+
lua_pushinteger(L, len);
return 1;
}
-static const luaL_Reg Reg_utf8[] =
-{
+static const luaL_Reg Reg_utf8[] = {
{ "valid", Lutf8_valid },
{ "length", Lutf8_length },
{ NULL, NULL }
@@ -206,61 +259,71 @@ static const luaL_Reg Reg_utf8[] =
#include <unicode/ustring.h>
#include <unicode/utrace.h>
-static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile)
-{
+static int icu_stringprep_prep(lua_State* L, const UStringPrepProfile* profile) {
size_t input_len;
int32_t unprepped_len, prepped_len, output_len;
- const char *input;
+ const char* input;
char output[1024];
UChar unprepped[1024]; /* Temporary unicode buffer (1024 characters) */
UChar prepped[1024];
-
+
UErrorCode err = U_ZERO_ERROR;
if(!lua_isstring(L, 1)) {
lua_pushnil(L);
return 1;
}
+
input = lua_tolstring(L, 1, &input_len);
- if (input_len >= 1024) {
+
+ if(input_len >= 1024) {
lua_pushnil(L);
return 1;
}
+
u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err);
- if (U_FAILURE(err)) {
+
+ if(U_FAILURE(err)) {
lua_pushnil(L);
return 1;
}
+
prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, 0, NULL, &err);
- if (U_FAILURE(err)) {
+
+ if(U_FAILURE(err)) {
lua_pushnil(L);
return 1;
} else {
u_strToUTF8(output, 1024, &output_len, prepped, prepped_len, &err);
- if (U_SUCCESS(err) && output_len < 1024)
+
+ if(U_SUCCESS(err) && output_len < 1024) {
lua_pushlstring(L, output, output_len);
- else
+ } else {
lua_pushnil(L);
+ }
+
return 1;
}
}
-UStringPrepProfile *icu_nameprep;
-UStringPrepProfile *icu_nodeprep;
-UStringPrepProfile *icu_resourceprep;
-UStringPrepProfile *icu_saslprep;
+UStringPrepProfile* icu_nameprep;
+UStringPrepProfile* icu_nodeprep;
+UStringPrepProfile* icu_resourceprep;
+UStringPrepProfile* icu_saslprep;
/* initialize global ICU stringprep profiles */
-void init_icu()
-{
+void init_icu() {
UErrorCode err = U_ZERO_ERROR;
utrace_setLevel(UTRACE_VERBOSE);
icu_nameprep = usprep_openByType(USPREP_RFC3491_NAMEPREP, &err);
icu_nodeprep = usprep_openByType(USPREP_RFC3920_NODEPREP, &err);
icu_resourceprep = usprep_openByType(USPREP_RFC3920_RESOURCEPREP, &err);
icu_saslprep = usprep_openByType(USPREP_RFC4013_SASLPREP, &err);
- if (U_FAILURE(err)) fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName((UErrorCode)err));
+
+ if(U_FAILURE(err)) {
+ fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName((UErrorCode)err));
+ }
}
#define MAKE_PREP_FUNC(myFunc, prep) \
@@ -271,8 +334,7 @@ MAKE_PREP_FUNC(Lstringprep_nodeprep, icu_nodeprep) /** stringprep.nodeprep(s) *
MAKE_PREP_FUNC(Lstringprep_resourceprep, icu_resourceprep) /** stringprep.resourceprep(s) */
MAKE_PREP_FUNC(Lstringprep_saslprep, icu_saslprep) /** stringprep.saslprep(s) */
-static const luaL_Reg Reg_stringprep[] =
-{
+static const luaL_Reg Reg_stringprep[] = {
{ "nameprep", Lstringprep_nameprep },
{ "nodeprep", Lstringprep_nodeprep },
{ "resourceprep", Lstringprep_resourceprep },
@@ -285,24 +347,28 @@ static const luaL_Reg Reg_stringprep[] =
#include <stringprep.h>
-static int stringprep_prep(lua_State *L, const Stringprep_profile *profile)
-{
+static int stringprep_prep(lua_State* L, const Stringprep_profile* profile) {
size_t len;
- const char *s;
+ const char* s;
char string[1024];
int ret;
+
if(!lua_isstring(L, 1)) {
lua_pushnil(L);
return 1;
}
+
s = check_utf8(L, 1, &len);
- if (s == NULL || len >= 1024 || len != strlen(s)) {
+
+ if(s == NULL || len >= 1024 || len != strlen(s)) {
lua_pushnil(L);
return 1; /* TODO return error message */
}
+
strcpy(string, s);
ret = stringprep(string, 1024, (Stringprep_profile_flags)0, profile);
- if (ret == STRINGPREP_OK) {
+
+ if(ret == STRINGPREP_OK) {
lua_pushstring(L, string);
return 1;
} else {
@@ -319,8 +385,7 @@ MAKE_PREP_FUNC(Lstringprep_nodeprep, stringprep_xmpp_nodeprep) /** stringprep.n
MAKE_PREP_FUNC(Lstringprep_resourceprep, stringprep_xmpp_resourceprep) /** stringprep.resourceprep(s) */
MAKE_PREP_FUNC(Lstringprep_saslprep, stringprep_saslprep) /** stringprep.saslprep(s) */
-static const luaL_Reg Reg_stringprep[] =
-{
+static const luaL_Reg Reg_stringprep[] = {
{ "nameprep", Lstringprep_nameprep },
{ "nodeprep", Lstringprep_nodeprep },
{ "resourceprep", Lstringprep_resourceprep },
@@ -334,62 +399,70 @@ static const luaL_Reg Reg_stringprep[] =
#include <unicode/ustdio.h>
#include <unicode/uidna.h>
/* IDNA2003 or IDNA2008 ? ? ? */
-static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */
-{
+static int Lidna_to_ascii(lua_State* L) { /** idna.to_ascii(s) */
size_t len;
int32_t ulen, dest_len, output_len;
- const char *s = luaL_checklstring(L, 1, &len);
+ const char* s = luaL_checklstring(L, 1, &len);
UChar ustr[1024];
UErrorCode err = U_ZERO_ERROR;
UChar dest[1024];
char output[1024];
u_strFromUTF8(ustr, 1024, &ulen, s, len, &err);
- if (U_FAILURE(err)) {
+
+ if(U_FAILURE(err)) {
lua_pushnil(L);
return 1;
}
dest_len = uidna_IDNToASCII(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err);
- if (U_FAILURE(err)) {
+
+ if(U_FAILURE(err)) {
lua_pushnil(L);
return 1;
} else {
u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err);
- if (U_SUCCESS(err) && output_len < 1024)
+
+ if(U_SUCCESS(err) && output_len < 1024) {
lua_pushlstring(L, output, output_len);
- else
+ } else {
lua_pushnil(L);
+ }
+
return 1;
}
}
-static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */
-{
+static int Lidna_to_unicode(lua_State* L) { /** idna.to_unicode(s) */
size_t len;
int32_t ulen, dest_len, output_len;
- const char *s = luaL_checklstring(L, 1, &len);
+ const char* s = luaL_checklstring(L, 1, &len);
UChar ustr[1024];
UErrorCode err = U_ZERO_ERROR;
UChar dest[1024];
char output[1024];
u_strFromUTF8(ustr, 1024, &ulen, s, len, &err);
- if (U_FAILURE(err)) {
+
+ if(U_FAILURE(err)) {
lua_pushnil(L);
return 1;
}
dest_len = uidna_IDNToUnicode(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err);
- if (U_FAILURE(err)) {
+
+ if(U_FAILURE(err)) {
lua_pushnil(L);
return 1;
} else {
u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err);
- if (U_SUCCESS(err) && output_len < 1024)
+
+ if(U_SUCCESS(err) && output_len < 1024) {
lua_pushlstring(L, output, output_len);
- else
+ } else {
lua_pushnil(L);
+ }
+
return 1;
}
}
@@ -400,17 +473,20 @@ static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */
#include <idna.h>
#include <idn-free.h>
-static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */
-{
+static int Lidna_to_ascii(lua_State* L) { /** idna.to_ascii(s) */
size_t len;
- const char *s = check_utf8(L, 1, &len);
- if (s == NULL || len != strlen(s)) {
+ const char* s = check_utf8(L, 1, &len);
+ char* output = NULL;
+ int ret;
+
+ if(s == NULL || len != strlen(s)) {
lua_pushnil(L);
return 1; /* TODO return error message */
}
- char* output = NULL;
- int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES);
- if (ret == IDNA_SUCCESS) {
+
+ ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES);
+
+ if(ret == IDNA_SUCCESS) {
lua_pushstring(L, output);
idn_free(output);
return 1;
@@ -421,13 +497,13 @@ static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */
}
}
-static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */
-{
+static int Lidna_to_unicode(lua_State* L) { /** idna.to_unicode(s) */
size_t len;
- const char *s = luaL_checklstring(L, 1, &len);
+ const char* s = luaL_checklstring(L, 1, &len);
char* output = NULL;
int ret = idna_to_unicode_8z8z(s, &output, 0);
- if (ret == IDNA_SUCCESS) {
+
+ if(ret == IDNA_SUCCESS) {
lua_pushstring(L, output);
idn_free(output);
return 1;
@@ -439,8 +515,7 @@ static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */
}
#endif
-static const luaL_Reg Reg_idna[] =
-{
+static const luaL_Reg Reg_idna[] = {
{ "to_ascii", Lidna_to_ascii },
{ "to_unicode", Lidna_to_unicode },
{ NULL, NULL }
@@ -448,40 +523,29 @@ static const luaL_Reg Reg_idna[] =
/***************** end *****************/
-static const luaL_Reg Reg[] =
-{
- { NULL, NULL }
-};
-
-LUALIB_API int luaopen_util_encodings(lua_State *L)
-{
+LUALIB_API int luaopen_util_encodings(lua_State* L) {
#ifdef USE_STRINGPREP_ICU
init_icu();
#endif
- luaL_register(L, "encodings", Reg);
+ lua_newtable(L);
- lua_pushliteral(L, "base64");
lua_newtable(L);
- luaL_register(L, NULL, Reg_base64);
- lua_settable(L,-3);
+ luaL_setfuncs(L, Reg_base64, 0);
+ lua_setfield(L, -2, "base64");
- lua_pushliteral(L, "stringprep");
lua_newtable(L);
- luaL_register(L, NULL, Reg_stringprep);
- lua_settable(L,-3);
+ luaL_setfuncs(L, Reg_stringprep, 0);
+ lua_setfield(L, -2, "stringprep");
- lua_pushliteral(L, "idna");
lua_newtable(L);
- luaL_register(L, NULL, Reg_idna);
- lua_settable(L,-3);
+ luaL_setfuncs(L, Reg_idna, 0);
+ lua_setfield(L, -2, "idna");
- lua_pushliteral(L, "utf8");
lua_newtable(L);
- luaL_register(L, NULL, Reg_utf8);
- lua_settable(L, -3);
+ luaL_setfuncs(L, Reg_utf8, 0);
+ lua_setfield(L, -2, "utf8");
- lua_pushliteral(L, "version"); /** version */
lua_pushliteral(L, "-3.14");
- lua_settable(L,-3);
+ lua_setfield(L, -2, "version");
return 1;
}
diff --git a/util-src/hashes.c b/util-src/hashes.c
index 33041e83..ecab2e32 100644
--- a/util-src/hashes.c
+++ b/util-src/hashes.c
@@ -1,7 +1,7 @@
/* Prosody IM
-- Copyright (C) 2009-2010 Matthew Wild
-- Copyright (C) 2009-2010 Waqas Hussain
---
+--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
@@ -27,15 +27,20 @@ typedef unsigned __int32 uint32_t;
#include <openssl/sha.h>
#include <openssl/md5.h>
+#if (LUA_VERSION_NUM == 501)
+#define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R)
+#endif
+
#define HMAC_IPAD 0x36363636
#define HMAC_OPAD 0x5c5c5c5c
-const char *hex_tab = "0123456789abcdef";
-void toHex(const unsigned char *in, int length, unsigned char *out) {
+const char* hex_tab = "0123456789abcdef";
+void toHex(const unsigned char* in, int length, unsigned char* out) {
int i;
- for (i = 0; i < length; i++) {
- out[i*2] = hex_tab[(in[i] >> 4) & 0xF];
- out[i*2+1] = hex_tab[(in[i]) & 0xF];
+
+ for(i = 0; i < length; i++) {
+ out[i * 2] = hex_tab[(in[i] >> 4) & 0xF];
+ out[i * 2 + 1] = hex_tab[(in[i]) & 0xF];
}
}
@@ -64,15 +69,14 @@ MAKE_HASH_FUNCTION(Lmd5, MD5, MD5_DIGEST_LENGTH)
struct hash_desc {
int (*Init)(void*);
- int (*Update)(void*, const void *, size_t);
+ int (*Update)(void*, const void*, size_t);
int (*Final)(unsigned char*, void*);
size_t digestLength;
- void *ctx, *ctxo;
+ void* ctx, *ctxo;
};
-static void hmac(struct hash_desc *desc, const char *key, size_t key_len,
- const char *msg, size_t msg_len, unsigned char *result)
-{
+static void hmac(struct hash_desc* desc, const char* key, size_t key_len,
+ const char* msg, size_t msg_len, unsigned char* result) {
union xory {
unsigned char bytes[64];
uint32_t quadbytes[16];
@@ -82,7 +86,7 @@ static void hmac(struct hash_desc *desc, const char *key, size_t key_len,
unsigned char hashedKey[64]; /* Maximum used digest length */
union xory k_ipad, k_opad;
- if (key_len > 64) {
+ if(key_len > 64) {
desc->Init(desc->ctx);
desc->Update(desc->ctx, key, key_len);
desc->Final(hashedKey, desc->ctx);
@@ -94,7 +98,7 @@ static void hmac(struct hash_desc *desc, const char *key, size_t key_len,
memset(k_ipad.bytes + key_len, 0, 64 - key_len);
memcpy(k_opad.bytes, k_ipad.bytes, 64);
- for (i = 0; i < 16; i++) {
+ for(i = 0; i < 16; i++) {
k_ipad.quadbytes[i] ^= HMAC_IPAD;
k_opad.quadbytes[i] ^= HMAC_OPAD;
}
@@ -139,10 +143,10 @@ MAKE_HMAC_FUNCTION(Lhmac_sha256, SHA256, SHA256_DIGEST_LENGTH, SHA256_CTX)
MAKE_HMAC_FUNCTION(Lhmac_sha512, SHA512, SHA512_DIGEST_LENGTH, SHA512_CTX)
MAKE_HMAC_FUNCTION(Lhmac_md5, MD5, MD5_DIGEST_LENGTH, MD5_CTX)
-static int LscramHi(lua_State *L) {
+static int LscramHi(lua_State* L) {
union xory {
unsigned char bytes[SHA_DIGEST_LENGTH];
- uint32_t quadbytes[SHA_DIGEST_LENGTH/4];
+ uint32_t quadbytes[SHA_DIGEST_LENGTH / 4];
};
int i;
SHA_CTX ctx, ctxo;
@@ -151,32 +155,39 @@ static int LscramHi(lua_State *L) {
union xory res;
size_t str_len, salt_len;
struct hash_desc desc;
- const char *str = luaL_checklstring(L, 1, &str_len);
- const char *salt = luaL_checklstring(L, 2, &salt_len);
- char *salt2;
+ const char* str = luaL_checklstring(L, 1, &str_len);
+ const char* salt = luaL_checklstring(L, 2, &salt_len);
+ char* salt2;
const int iter = luaL_checkinteger(L, 3);
desc.Init = (int (*)(void*))SHA1_Init;
- desc.Update = (int (*)(void*, const void *, size_t))SHA1_Update;
+ desc.Update = (int (*)(void*, const void*, size_t))SHA1_Update;
desc.Final = (int (*)(unsigned char*, void*))SHA1_Final;
desc.digestLength = SHA_DIGEST_LENGTH;
desc.ctx = &ctx;
desc.ctxo = &ctxo;
salt2 = malloc(salt_len + 4);
- if (salt2 == NULL)
- luaL_error(L, "Out of memory in scramHi");
+
+ if(salt2 == NULL) {
+ return luaL_error(L, "Out of memory in scramHi");
+ }
+
memcpy(salt2, salt, salt_len);
memcpy(salt2 + salt_len, "\0\0\0\1", 4);
hmac(&desc, str, str_len, salt2, salt_len + 4, Ust);
free(salt2);
memcpy(res.bytes, Ust, sizeof(res));
- for (i = 1; i < iter; i++) {
+
+ for(i = 1; i < iter; i++) {
int j;
hmac(&desc, str, str_len, (char*)Ust, sizeof(Ust), Und.bytes);
- for (j = 0; j < SHA_DIGEST_LENGTH/4; j++)
+
+ for(j = 0; j < SHA_DIGEST_LENGTH / 4; j++) {
res.quadbytes[j] ^= Und.quadbytes[j];
+ }
+
memcpy(Ust, Und.bytes, sizeof(Ust));
}
@@ -185,8 +196,7 @@ static int LscramHi(lua_State *L) {
return 1;
}
-static const luaL_Reg Reg[] =
-{
+static const luaL_Reg Reg[] = {
{ "sha1", Lsha1 },
{ "sha224", Lsha224 },
{ "sha256", Lsha256 },
@@ -201,11 +211,10 @@ static const luaL_Reg Reg[] =
{ NULL, NULL }
};
-LUALIB_API int luaopen_util_hashes(lua_State *L)
-{
- luaL_register(L, "hashes", Reg);
- lua_pushliteral(L, "version"); /** version */
+LUALIB_API int luaopen_util_hashes(lua_State* L) {
+ lua_newtable(L);
+ luaL_setfuncs(L, Reg, 0);;
lua_pushliteral(L, "-3.14");
- lua_settable(L,-3);
+ lua_setfield(L, -2, "version");
return 1;
}
diff --git a/util-src/net.c b/util-src/net.c
index e307c628..3ccc7618 100644
--- a/util-src/net.c
+++ b/util-src/net.c
@@ -14,34 +14,37 @@
#include <errno.h>
#ifndef _WIN32
- #include <sys/ioctl.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <net/if.h>
- #include <ifaddrs.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
#endif
#include <lua.h>
#include <lauxlib.h>
+#if (LUA_VERSION_NUM == 501)
+#define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R)
+#endif
+
/* Enumerate all locally configured IP addresses */
-const char * const type_strings[] = {
+const char* const type_strings[] = {
"both",
"ipv4",
"ipv6",
NULL
};
-static int lc_local_addresses(lua_State *L)
-{
+static int lc_local_addresses(lua_State* L) {
#ifndef _WIN32
/* Link-local IPv4 addresses; see RFC 3927 and RFC 5735 */
const long ip4_linklocal = htonl(0xa9fe0000); /* 169.254.0.0 */
const long ip4_mask = htonl(0xffff0000);
- struct ifaddrs *addr = NULL, *a;
+ struct ifaddrs* addr = NULL, *a;
#endif
int n = 1;
int type = luaL_checkoption(L, 1, "both", type_strings);
@@ -50,68 +53,84 @@ static int lc_local_addresses(lua_State *L)
const char ipv6 = (type == 0 || type == 2);
#ifndef _WIN32
- if (getifaddrs(&addr) < 0) {
+
+ if(getifaddrs(&addr) < 0) {
lua_pushnil(L);
lua_pushfstring(L, "getifaddrs failed (%d): %s", errno,
- strerror(errno));
+ strerror(errno));
return 2;
}
+
#endif
lua_newtable(L);
#ifndef _WIN32
- for (a = addr; a; a = a->ifa_next) {
+
+ for(a = addr; a; a = a->ifa_next) {
int family;
char ipaddr[INET6_ADDRSTRLEN];
- const char *tmp = NULL;
+ const char* tmp = NULL;
- if (a->ifa_addr == NULL || a->ifa_flags & IFF_LOOPBACK)
+ if(a->ifa_addr == NULL || a->ifa_flags & IFF_LOOPBACK) {
continue;
+ }
family = a->ifa_addr->sa_family;
- if (ipv4 && family == AF_INET) {
- struct sockaddr_in *sa = (struct sockaddr_in *)a->ifa_addr;
- if (!link_local &&((sa->sin_addr.s_addr & ip4_mask) == ip4_linklocal))
+ if(ipv4 && family == AF_INET) {
+ struct sockaddr_in* sa = (struct sockaddr_in*)a->ifa_addr;
+
+ if(!link_local && ((sa->sin_addr.s_addr & ip4_mask) == ip4_linklocal)) {
continue;
+ }
+
tmp = inet_ntop(family, &sa->sin_addr, ipaddr, sizeof(ipaddr));
- } else if (ipv6 && family == AF_INET6) {
- struct sockaddr_in6 *sa = (struct sockaddr_in6 *)a->ifa_addr;
- if (!link_local && IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr))
+ } else if(ipv6 && family == AF_INET6) {
+ struct sockaddr_in6* sa = (struct sockaddr_in6*)a->ifa_addr;
+
+ if(!link_local && IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
continue;
- if (IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr) || IN6_IS_ADDR_V4COMPAT(&sa->sin6_addr))
+ }
+
+ if(IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr) || IN6_IS_ADDR_V4COMPAT(&sa->sin6_addr)) {
continue;
+ }
+
tmp = inet_ntop(family, &sa->sin6_addr, ipaddr, sizeof(ipaddr));
}
- if (tmp != NULL) {
+ if(tmp != NULL) {
lua_pushstring(L, tmp);
lua_rawseti(L, -2, n++);
}
+
/* TODO: Error reporting? */
}
freeifaddrs(addr);
#else
- if (ipv4) {
+
+ if(ipv4) {
lua_pushstring(L, "0.0.0.0");
lua_rawseti(L, -2, n++);
}
- if (ipv6) {
+
+ if(ipv6) {
lua_pushstring(L, "::");
lua_rawseti(L, -2, n++);
}
+
#endif
return 1;
}
-int luaopen_util_net(lua_State* L)
-{
+int luaopen_util_net(lua_State* L) {
luaL_Reg exports[] = {
{ "local_addresses", lc_local_addresses },
{ NULL, NULL }
};
- luaL_register(L, "net", exports);
+ lua_newtable(L);
+ luaL_setfuncs(L, exports, 0);
return 1;
}
diff --git a/util-src/pposix.c b/util-src/pposix.c
index df814c28..1b69852d 100644
--- a/util-src/pposix.c
+++ b/util-src/pposix.c
@@ -35,40 +35,39 @@
#include "lualib.h"
#include "lauxlib.h"
+#if (LUA_VERSION_NUM == 501)
+#define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R)
+#endif
+
#include <fcntl.h>
#if defined(__linux__) && defined(_GNU_SOURCE)
#include <linux/falloc.h>
#endif
#if (defined(_SVID_SOURCE) && !defined(WITHOUT_MALLINFO))
- #include <malloc.h>
- #define WITH_MALLINFO
+#include <malloc.h>
+#define WITH_MALLINFO
#endif
/* Daemonization support */
-static int lc_daemonize(lua_State *L)
-{
+static int lc_daemonize(lua_State* L) {
pid_t pid;
- if ( getppid() == 1 )
- {
+ if(getppid() == 1) {
lua_pushboolean(L, 0);
lua_pushstring(L, "already-daemonized");
return 2;
}
/* Attempt initial fork */
- if((pid = fork()) < 0)
- {
+ if((pid = fork()) < 0) {
/* Forking failed */
lua_pushboolean(L, 0);
lua_pushstring(L, "fork-failed");
return 2;
- }
- else if(pid != 0)
- {
+ } else if(pid != 0) {
/* We are the parent process */
lua_pushboolean(L, 1);
lua_pushnumber(L, pid);
@@ -76,8 +75,7 @@ static int lc_daemonize(lua_State *L)
}
/* and we are the child process */
- if(setsid() == -1)
- {
+ if(setsid() == -1) {
/* We failed to become session leader */
/* (we probably already were) */
lua_pushboolean(L, 0);
@@ -95,8 +93,9 @@ static int lc_daemonize(lua_State *L)
open("/dev/null", O_WRONLY);
/* Final fork, use it wisely */
- if(fork())
+ if(fork()) {
exit(0);
+ }
/* Show's over, let's continue */
lua_pushboolean(L, 1);
@@ -106,59 +105,59 @@ static int lc_daemonize(lua_State *L)
/* Syslog support */
-const char * const facility_strings[] = {
- "auth",
+const char* const facility_strings[] = {
+ "auth",
#if !(defined(sun) || defined(__sun))
- "authpriv",
+ "authpriv",
#endif
- "cron",
- "daemon",
+ "cron",
+ "daemon",
#if !(defined(sun) || defined(__sun))
- "ftp",
+ "ftp",
#endif
- "kern",
- "local0",
- "local1",
- "local2",
- "local3",
- "local4",
- "local5",
- "local6",
- "local7",
- "lpr",
- "mail",
- "syslog",
- "user",
- "uucp",
- NULL
- };
+ "kern",
+ "local0",
+ "local1",
+ "local2",
+ "local3",
+ "local4",
+ "local5",
+ "local6",
+ "local7",
+ "lpr",
+ "mail",
+ "syslog",
+ "user",
+ "uucp",
+ NULL
+};
int facility_constants[] = {
- LOG_AUTH,
+ LOG_AUTH,
#if !(defined(sun) || defined(__sun))
- LOG_AUTHPRIV,
+ LOG_AUTHPRIV,
#endif
- LOG_CRON,
- LOG_DAEMON,
+ LOG_CRON,
+ LOG_DAEMON,
#if !(defined(sun) || defined(__sun))
- LOG_FTP,
+ LOG_FTP,
#endif
- LOG_KERN,
- LOG_LOCAL0,
- LOG_LOCAL1,
- LOG_LOCAL2,
- LOG_LOCAL3,
- LOG_LOCAL4,
- LOG_LOCAL5,
- LOG_LOCAL6,
- LOG_LOCAL7,
- LOG_LPR,
- LOG_MAIL,
- LOG_NEWS,
- LOG_SYSLOG,
- LOG_USER,
- LOG_UUCP,
- -1
- };
+ LOG_KERN,
+ LOG_LOCAL0,
+ LOG_LOCAL1,
+ LOG_LOCAL2,
+ LOG_LOCAL3,
+ LOG_LOCAL4,
+ LOG_LOCAL5,
+ LOG_LOCAL6,
+ LOG_LOCAL7,
+ LOG_LPR,
+ LOG_MAIL,
+ LOG_NEWS,
+ LOG_SYSLOG,
+ LOG_USER,
+ LOG_UUCP,
+ -1
+};
/* "
The parameter ident in the call of openlog() is probably stored as-is.
@@ -170,15 +169,15 @@ int facility_constants[] = {
*/
char* syslog_ident = NULL;
-int lc_syslog_open(lua_State* L)
-{
+int lc_syslog_open(lua_State* L) {
int facility = luaL_checkoption(L, 2, "daemon", facility_strings);
facility = facility_constants[facility];
luaL_checkstring(L, 1);
- if(syslog_ident)
+ if(syslog_ident) {
free(syslog_ident);
+ }
syslog_ident = strdup(lua_tostring(L, 1));
@@ -186,53 +185,52 @@ int lc_syslog_open(lua_State* L)
return 0;
}
-const char * const level_strings[] = {
- "debug",
- "info",
- "notice",
- "warn",
- "error",
- NULL
- };
+const char* const level_strings[] = {
+ "debug",
+ "info",
+ "notice",
+ "warn",
+ "error",
+ NULL
+};
int level_constants[] = {
- LOG_DEBUG,
- LOG_INFO,
- LOG_NOTICE,
- LOG_WARNING,
- LOG_CRIT,
- -1
- };
-int lc_syslog_log(lua_State* L)
-{
+ LOG_DEBUG,
+ LOG_INFO,
+ LOG_NOTICE,
+ LOG_WARNING,
+ LOG_CRIT,
+ -1
+};
+int lc_syslog_log(lua_State* L) {
int level = level_constants[luaL_checkoption(L, 1, "notice", level_strings)];
- if(lua_gettop(L) == 3)
+ if(lua_gettop(L) == 3) {
syslog(level, "%s: %s", luaL_checkstring(L, 2), luaL_checkstring(L, 3));
- else
+ } else {
syslog(level, "%s", lua_tostring(L, 2));
+ }
return 0;
}
-int lc_syslog_close(lua_State* L)
-{
+int lc_syslog_close(lua_State* L) {
closelog();
- if(syslog_ident)
- {
+
+ if(syslog_ident) {
free(syslog_ident);
syslog_ident = NULL;
}
+
return 0;
}
-int lc_syslog_setmask(lua_State* L)
-{
+int lc_syslog_setmask(lua_State* L) {
int level_idx = luaL_checkoption(L, 1, "notice", level_strings);
int mask = 0;
- do
- {
+
+ do {
mask |= LOG_MASK(level_constants[level_idx]);
- } while (++level_idx<=4);
+ } while(++level_idx <= 4);
setlogmask(mask);
return 0;
@@ -240,72 +238,67 @@ int lc_syslog_setmask(lua_State* L)
/* getpid */
-int lc_getpid(lua_State* L)
-{
+int lc_getpid(lua_State* L) {
lua_pushinteger(L, getpid());
return 1;
}
/* UID/GID functions */
-int lc_getuid(lua_State* L)
-{
+int lc_getuid(lua_State* L) {
lua_pushinteger(L, getuid());
return 1;
}
-int lc_getgid(lua_State* L)
-{
+int lc_getgid(lua_State* L) {
lua_pushinteger(L, getgid());
return 1;
}
-int lc_setuid(lua_State* L)
-{
+int lc_setuid(lua_State* L) {
int uid = -1;
- if(lua_gettop(L) < 1)
+
+ if(lua_gettop(L) < 1) {
return 0;
- if(!lua_isnumber(L, 1) && lua_tostring(L, 1))
- {
+ }
+
+ if(!lua_isnumber(L, 1) && lua_tostring(L, 1)) {
/* Passed UID is actually a string, so look up the UID */
- struct passwd *p;
+ struct passwd* p;
p = getpwnam(lua_tostring(L, 1));
- if(!p)
- {
+
+ if(!p) {
lua_pushboolean(L, 0);
lua_pushstring(L, "no-such-user");
return 2;
}
+
uid = p->pw_uid;
- }
- else
- {
+ } else {
uid = lua_tonumber(L, 1);
}
- if(uid>-1)
- {
+ if(uid > -1) {
/* Ok, attempt setuid */
errno = 0;
- if(setuid(uid))
- {
+
+ if(setuid(uid)) {
/* Fail */
lua_pushboolean(L, 0);
- switch(errno)
- {
- case EINVAL:
- lua_pushstring(L, "invalid-uid");
- break;
- case EPERM:
- lua_pushstring(L, "permission-denied");
- break;
- default:
- lua_pushstring(L, "unknown-error");
+
+ switch(errno) {
+ case EINVAL:
+ lua_pushstring(L, "invalid-uid");
+ break;
+ case EPERM:
+ lua_pushstring(L, "permission-denied");
+ break;
+ default:
+ lua_pushstring(L, "unknown-error");
}
+
return 2;
- }
- else
- {
+ } else {
/* Success! */
lua_pushboolean(L, 1);
return 1;
@@ -318,52 +311,50 @@ int lc_setuid(lua_State* L)
return 2;
}
-int lc_setgid(lua_State* L)
-{
+int lc_setgid(lua_State* L) {
int gid = -1;
- if(lua_gettop(L) < 1)
+
+ if(lua_gettop(L) < 1) {
return 0;
- if(!lua_isnumber(L, 1) && lua_tostring(L, 1))
- {
+ }
+
+ if(!lua_isnumber(L, 1) && lua_tostring(L, 1)) {
/* Passed GID is actually a string, so look up the GID */
- struct group *g;
+ struct group* g;
g = getgrnam(lua_tostring(L, 1));
- if(!g)
- {
+
+ if(!g) {
lua_pushboolean(L, 0);
lua_pushstring(L, "no-such-group");
return 2;
}
+
gid = g->gr_gid;
- }
- else
- {
+ } else {
gid = lua_tonumber(L, 1);
}
- if(gid>-1)
- {
+ if(gid > -1) {
/* Ok, attempt setgid */
errno = 0;
- if(setgid(gid))
- {
+
+ if(setgid(gid)) {
/* Fail */
lua_pushboolean(L, 0);
- switch(errno)
- {
- case EINVAL:
- lua_pushstring(L, "invalid-gid");
- break;
- case EPERM:
- lua_pushstring(L, "permission-denied");
- break;
- default:
- lua_pushstring(L, "unknown-error");
+
+ switch(errno) {
+ case EINVAL:
+ lua_pushstring(L, "invalid-gid");
+ break;
+ case EPERM:
+ lua_pushstring(L, "permission-denied");
+ break;
+ default:
+ lua_pushstring(L, "unknown-error");
}
+
return 2;
- }
- else
- {
+ } else {
/* Success! */
lua_pushboolean(L, 1);
return 1;
@@ -376,90 +367,89 @@ int lc_setgid(lua_State* L)
return 2;
}
-int lc_initgroups(lua_State* L)
-{
+int lc_initgroups(lua_State* L) {
int ret;
gid_t gid;
- struct passwd *p;
+ struct passwd* p;
- if(!lua_isstring(L, 1))
- {
+ if(!lua_isstring(L, 1)) {
lua_pushnil(L);
lua_pushstring(L, "invalid-username");
return 2;
}
+
p = getpwnam(lua_tostring(L, 1));
- if(!p)
- {
+
+ if(!p) {
lua_pushnil(L);
lua_pushstring(L, "no-such-user");
return 2;
}
- if(lua_gettop(L) < 2)
- lua_pushnil(L);
- switch(lua_type(L, 2))
- {
- case LUA_TNIL:
- gid = p->pw_gid;
- break;
- case LUA_TNUMBER:
- gid = lua_tointeger(L, 2);
- break;
- default:
+
+ if(lua_gettop(L) < 2) {
lua_pushnil(L);
- lua_pushstring(L, "invalid-gid");
- return 2;
}
- ret = initgroups(lua_tostring(L, 1), gid);
- if(ret)
- {
- switch(errno)
- {
- case ENOMEM:
- lua_pushnil(L);
- lua_pushstring(L, "no-memory");
+
+ switch(lua_type(L, 2)) {
+ case LUA_TNIL:
+ gid = p->pw_gid;
break;
- case EPERM:
- lua_pushnil(L);
- lua_pushstring(L, "permission-denied");
+ case LUA_TNUMBER:
+ gid = lua_tointeger(L, 2);
break;
default:
lua_pushnil(L);
- lua_pushstring(L, "unknown-error");
- }
+ lua_pushstring(L, "invalid-gid");
+ return 2;
}
- else
- {
+
+ ret = initgroups(lua_tostring(L, 1), gid);
+
+ if(ret) {
+ switch(errno) {
+ case ENOMEM:
+ lua_pushnil(L);
+ lua_pushstring(L, "no-memory");
+ break;
+ case EPERM:
+ lua_pushnil(L);
+ lua_pushstring(L, "permission-denied");
+ break;
+ default:
+ lua_pushnil(L);
+ lua_pushstring(L, "unknown-error");
+ }
+ } else {
lua_pushboolean(L, 1);
lua_pushnil(L);
}
+
return 2;
}
-int lc_umask(lua_State* L)
-{
+int lc_umask(lua_State* L) {
char old_mode_string[7];
mode_t old_mode = umask(strtoul(luaL_checkstring(L, 1), NULL, 8));
snprintf(old_mode_string, sizeof(old_mode_string), "%03o", old_mode);
- old_mode_string[sizeof(old_mode_string)-1] = 0;
+ old_mode_string[sizeof(old_mode_string) - 1] = 0;
lua_pushstring(L, old_mode_string);
return 1;
}
-int lc_mkdir(lua_State* L)
-{
+int lc_mkdir(lua_State* L) {
int ret = mkdir(luaL_checkstring(L, 1), S_IRUSR | S_IWUSR | S_IXUSR
- | S_IRGRP | S_IWGRP | S_IXGRP
- | S_IROTH | S_IXOTH); /* mode 775 */
+ | S_IRGRP | S_IWGRP | S_IXGRP
+ | S_IROTH | S_IXOTH); /* mode 775 */
- lua_pushboolean(L, ret==0);
- if(ret)
- {
+ lua_pushboolean(L, ret == 0);
+
+ if(ret) {
lua_pushstring(L, strerror(errno));
return 2;
}
+
return 1;
}
@@ -473,89 +463,132 @@ int lc_mkdir(lua_State* L)
* Example usage:
* pposix.setrlimit("NOFILE", 1000, 2000)
*/
-int string2resource(const char *s) {
- if (!strcmp(s, "CORE")) return RLIMIT_CORE;
- if (!strcmp(s, "CPU")) return RLIMIT_CPU;
- if (!strcmp(s, "DATA")) return RLIMIT_DATA;
- if (!strcmp(s, "FSIZE")) return RLIMIT_FSIZE;
- if (!strcmp(s, "NOFILE")) return RLIMIT_NOFILE;
- if (!strcmp(s, "STACK")) return RLIMIT_STACK;
+int string2resource(const char* s) {
+ if(!strcmp(s, "CORE")) {
+ return RLIMIT_CORE;
+ }
+
+ if(!strcmp(s, "CPU")) {
+ return RLIMIT_CPU;
+ }
+
+ if(!strcmp(s, "DATA")) {
+ return RLIMIT_DATA;
+ }
+
+ if(!strcmp(s, "FSIZE")) {
+ return RLIMIT_FSIZE;
+ }
+
+ if(!strcmp(s, "NOFILE")) {
+ return RLIMIT_NOFILE;
+ }
+
+ if(!strcmp(s, "STACK")) {
+ return RLIMIT_STACK;
+ }
+
#if !(defined(sun) || defined(__sun))
- if (!strcmp(s, "MEMLOCK")) return RLIMIT_MEMLOCK;
- if (!strcmp(s, "NPROC")) return RLIMIT_NPROC;
- if (!strcmp(s, "RSS")) return RLIMIT_RSS;
+
+ if(!strcmp(s, "MEMLOCK")) {
+ return RLIMIT_MEMLOCK;
+ }
+
+ if(!strcmp(s, "NPROC")) {
+ return RLIMIT_NPROC;
+ }
+
+ if(!strcmp(s, "RSS")) {
+ return RLIMIT_RSS;
+ }
+
#endif
#ifdef RLIMIT_NICE
- if (!strcmp(s, "NICE")) return RLIMIT_NICE;
+
+ if(!strcmp(s, "NICE")) {
+ return RLIMIT_NICE;
+ }
+
#endif
return -1;
}
-int lc_setrlimit(lua_State *L) {
+unsigned long int arg_to_rlimit(lua_State* L, int idx, rlim_t current) {
+ switch(lua_type(L, idx)) {
+ case LUA_TSTRING:
+
+ if(strcmp(lua_tostring(L, idx), "unlimited") == 0) {
+ return RLIM_INFINITY;
+ }
+
+ case LUA_TNUMBER:
+ return lua_tointeger(L, idx);
+ case LUA_TNONE:
+ case LUA_TNIL:
+ return current;
+ default:
+ return luaL_argerror(L, idx, "unexpected type");
+ }
+}
+
+int lc_setrlimit(lua_State* L) {
+ struct rlimit lim;
int arguments = lua_gettop(L);
- int softlimit = -1;
- int hardlimit = -1;
- const char *resource = NULL;
int rid = -1;
+
if(arguments < 1 || arguments > 3) {
lua_pushboolean(L, 0);
lua_pushstring(L, "incorrect-arguments");
return 2;
}
- resource = luaL_checkstring(L, 1);
- softlimit = luaL_checkinteger(L, 2);
- hardlimit = luaL_checkinteger(L, 3);
+ rid = string2resource(luaL_checkstring(L, 1));
- rid = string2resource(resource);
- if (rid != -1) {
- struct rlimit lim;
- struct rlimit lim_current;
-
- if (softlimit < 0 || hardlimit < 0) {
- if (getrlimit(rid, &lim_current)) {
- lua_pushboolean(L, 0);
- lua_pushstring(L, "getrlimit-failed");
- return 2;
- }
- }
+ if(rid == -1) {
+ lua_pushboolean(L, 0);
+ lua_pushstring(L, "invalid-resource");
+ return 2;
+ }
- if (softlimit < 0) lim.rlim_cur = lim_current.rlim_cur;
- else lim.rlim_cur = softlimit;
- if (hardlimit < 0) lim.rlim_max = lim_current.rlim_max;
- else lim.rlim_max = hardlimit;
+ /* Fetch current values to use as defaults */
+ if(getrlimit(rid, &lim)) {
+ lua_pushboolean(L, 0);
+ lua_pushstring(L, "getrlimit-failed");
+ return 2;
+ }
- if (setrlimit(rid, &lim)) {
- lua_pushboolean(L, 0);
- lua_pushstring(L, "setrlimit-failed");
- return 2;
- }
- } else {
- /* Unsupported resoucrce. Sorry I'm pretty limited by POSIX standard. */
+ lim.rlim_cur = arg_to_rlimit(L, 2, lim.rlim_cur);
+ lim.rlim_max = arg_to_rlimit(L, 3, lim.rlim_max);
+
+ if(setrlimit(rid, &lim)) {
lua_pushboolean(L, 0);
- lua_pushstring(L, "invalid-resource");
+ lua_pushstring(L, "setrlimit-failed");
return 2;
}
+
lua_pushboolean(L, 1);
return 1;
}
-int lc_getrlimit(lua_State *L) {
+int lc_getrlimit(lua_State* L) {
int arguments = lua_gettop(L);
- const char *resource = NULL;
+ const char* resource = NULL;
int rid = -1;
struct rlimit lim;
- if (arguments != 1) {
+ if(arguments != 1) {
lua_pushboolean(L, 0);
lua_pushstring(L, "invalid-arguments");
return 2;
}
+
+
resource = luaL_checkstring(L, 1);
rid = string2resource(resource);
- if (rid != -1) {
- if (getrlimit(rid, &lim)) {
+
+ if(rid != -1) {
+ if(getrlimit(rid, &lim)) {
lua_pushboolean(L, 0);
lua_pushstring(L, "getrlimit-failed.");
return 2;
@@ -566,27 +599,38 @@ int lc_getrlimit(lua_State *L) {
lua_pushstring(L, "invalid-resource");
return 2;
}
+
lua_pushboolean(L, 1);
- lua_pushnumber(L, lim.rlim_cur);
- lua_pushnumber(L, lim.rlim_max);
+
+ if(lim.rlim_cur == RLIM_INFINITY) {
+ lua_pushstring(L, "unlimited");
+ } else {
+ lua_pushnumber(L, lim.rlim_cur);
+ }
+
+ if(lim.rlim_max == RLIM_INFINITY) {
+ lua_pushstring(L, "unlimited");
+ } else {
+ lua_pushnumber(L, lim.rlim_max);
+ }
+
return 3;
}
-int lc_abort(lua_State* L)
-{
+int lc_abort(lua_State* L) {
abort();
return 0;
}
-int lc_uname(lua_State* L)
-{
+int lc_uname(lua_State* L) {
struct utsname uname_info;
- if(uname(&uname_info) != 0)
- {
+
+ if(uname(&uname_info) != 0) {
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2;
}
+
lua_newtable(L);
lua_pushstring(L, uname_info.sysname);
lua_setfield(L, -2, "sysname");
@@ -598,31 +642,32 @@ int lc_uname(lua_State* L)
lua_setfield(L, -2, "version");
lua_pushstring(L, uname_info.machine);
lua_setfield(L, -2, "machine");
+#ifdef _GNU_SOURCE
+ lua_pushstring(L, uname_info.domainname);
+ lua_setfield(L, -2, "domainname");
+#endif
return 1;
}
-int lc_setenv(lua_State* L)
-{
- const char *var = luaL_checkstring(L, 1);
- const char *value;
+int lc_setenv(lua_State* L) {
+ const char* var = luaL_checkstring(L, 1);
+ const char* value;
/* If the second argument is nil or nothing, unset the var */
- if(lua_isnoneornil(L, 2))
- {
- if(unsetenv(var) != 0)
- {
+ if(lua_isnoneornil(L, 2)) {
+ if(unsetenv(var) != 0) {
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2;
}
+
lua_pushboolean(L, 1);
return 1;
}
value = luaL_checkstring(L, 2);
- if(setenv(var, value, 1) != 0)
- {
+ if(setenv(var, value, 1) != 0) {
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2;
@@ -633,8 +678,7 @@ int lc_setenv(lua_State* L)
}
#ifdef WITH_MALLINFO
-int lc_meminfo(lua_State* L)
-{
+int lc_meminfo(lua_State* L) {
struct mallinfo info = mallinfo();
lua_newtable(L);
/* This is the total size of memory allocated with sbrk by malloc, in bytes. */
@@ -662,13 +706,14 @@ int lc_meminfo(lua_State* L)
* */
#if _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L || defined(_GNU_SOURCE)
-int lc_fallocate(lua_State* L)
-{
+int lc_fallocate(lua_State* L) {
int ret;
off_t offset, len;
- FILE *f = *(FILE**) luaL_checkudata(L, 1, LUA_FILEHANDLE);
- if (f == NULL)
- luaL_error(L, "attempt to use a closed file");
+ FILE* f = *(FILE**) luaL_checkudata(L, 1, LUA_FILEHANDLE);
+
+ if(f == NULL) {
+ return luaL_error(L, "attempt to use a closed file");
+ }
offset = luaL_checkinteger(L, 2);
len = luaL_checkinteger(L, 3);
@@ -676,20 +721,23 @@ int lc_fallocate(lua_State* L)
#if defined(__linux__) && defined(_GNU_SOURCE)
errno = 0;
ret = fallocate(fileno(f), FALLOC_FL_KEEP_SIZE, offset, len);
- if(ret == 0)
- {
+
+ if(ret == 0) {
lua_pushboolean(L, 1);
return 1;
}
+
/* Some old versions of Linux apparently use the return value instead of errno */
- if(errno == 0) errno = ret;
+ if(errno == 0) {
+ errno = ret;
+ }
- if(errno != ENOSYS && errno != EOPNOTSUPP)
- {
+ if(errno != ENOSYS && errno != EOPNOTSUPP) {
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2;
}
+
#else
#warning Only using posix_fallocate() fallback.
#warning Linux fallocate() is strongly recommended if available: recompile with -D_GNU_SOURCE
@@ -697,18 +745,19 @@ int lc_fallocate(lua_State* L)
#endif
ret = posix_fallocate(fileno(f), offset, len);
- if(ret == 0)
- {
+
+ if(ret == 0) {
lua_pushboolean(L, 1);
return 1;
- }
- else
- {
+ } else {
lua_pushnil(L);
lua_pushstring(L, strerror(ret));
/* posix_fallocate() can leave a bunch of NULs at the end, so we cut that
* this assumes that offset == length of the file */
- ftruncate(fileno(f), offset);
+ if(ftruncate(fileno(f), offset) != 0) {
+ lua_pushstring(L, strerror(errno));
+ return 3;
+ }
return 2;
}
}
@@ -716,8 +765,7 @@ int lc_fallocate(lua_State* L)
/* Register functions */
-int luaopen_util_pposix(lua_State *L)
-{
+int luaopen_util_pposix(lua_State* L) {
luaL_Reg exports[] = {
{ "abort", lc_abort },
@@ -758,7 +806,8 @@ int luaopen_util_pposix(lua_State *L)
{ NULL, NULL }
};
- luaL_register(L, "pposix", exports);
+ lua_newtable(L);
+ luaL_setfuncs(L, exports, 0);
lua_pushliteral(L, "pposix");
lua_setfield(L, -2, "_NAME");
diff --git a/util-src/ringbuffer.c b/util-src/ringbuffer.c
new file mode 100644
index 00000000..f5fa136b
--- /dev/null
+++ b/util-src/ringbuffer.c
@@ -0,0 +1,232 @@
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+
+#define MIN(a, b) ((a)>(b)?(b):(a))
+#define MAX(a, b) ((a)>(b)?(a):(b))
+
+typedef struct {
+ size_t rpos; /* read position */
+ size_t wpos; /* write position */
+ size_t alen; /* allocated size */
+ size_t blen; /* current content size */
+ char* buffer;
+} ringbuffer;
+
+char readchar(ringbuffer* b) {
+ b->blen--;
+ return b->buffer[(b->rpos++) % b->alen];
+}
+
+void writechar(ringbuffer* b, char c) {
+ b->blen++;
+ b->buffer[(b->wpos++) % b->alen] = c;
+}
+
+/* make sure position counters stay within the allocation */
+void modpos(ringbuffer* b) {
+ b->rpos = b->rpos % b->alen;
+ b->wpos = b->wpos % b->alen;
+}
+
+int find(ringbuffer* b, const char* s, int l) {
+ size_t i, j;
+ int m;
+
+ if(b->rpos == b->wpos) { /* empty */
+ return 0;
+ }
+
+ for(i = 0; i <= b->blen - l; i++) {
+ if(b->buffer[(b->rpos + i) % b->alen] == *s) {
+ m = 1;
+
+ for(j = 1; j < l; j++)
+ if(b->buffer[(b->rpos + i + j) % b->alen] != s[j]) {
+ m = 0;
+ break;
+ }
+
+ if(m) {
+ return i + l;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int rb_find(lua_State* L) {
+ size_t l, m;
+ ringbuffer* b = luaL_checkudata(L, 1, "ringbuffer_mt");
+ const char* s = luaL_checklstring(L, 2, &l);
+ m = find(b, s, l);
+
+ if(m > 0) {
+ lua_pushinteger(L, m);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int rb_read(lua_State* L) {
+ ringbuffer* b = luaL_checkudata(L, 1, "ringbuffer_mt");
+ int r = luaL_checkinteger(L, 2);
+ int peek = lua_toboolean(L, 3);
+
+ if(r > b->blen) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ if((b->rpos + r) > b->alen) {
+ lua_pushlstring(L, &b->buffer[b->rpos], b->alen - b->rpos);
+ lua_pushlstring(L, b->buffer, r - (b->alen - b->rpos));
+ lua_concat(L, 2);
+ } else {
+ lua_pushlstring(L, &b->buffer[b->rpos], r);
+ }
+
+ if(!peek) {
+ b->blen -= r;
+ b->rpos += r;
+ modpos(b);
+ }
+
+ return 1;
+}
+
+
+int rb_readuntil(lua_State* L) {
+ size_t l, m;
+ ringbuffer* b = luaL_checkudata(L, 1, "ringbuffer_mt");
+ const char* s = luaL_checklstring(L, 2, &l);
+ m = find(b, s, l);
+
+ if(m > 0) {
+ lua_settop(L, 1);
+ lua_pushinteger(L, m);
+ return rb_read(L);
+ }
+
+ return 0;
+}
+
+int rb_write(lua_State* L) {
+ size_t l, w = 0;
+ ringbuffer* b = luaL_checkudata(L, 1, "ringbuffer_mt");
+ const char* s = luaL_checklstring(L, 2, &l);
+
+ /* Does `l` bytes fit? */
+ if((l + b->blen) > b->alen) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ while(l-- > 0) {
+ writechar(b, *s++);
+ w++;
+ }
+
+ modpos(b);
+
+ lua_pushinteger(L, w);
+
+ return 1;
+}
+
+int rb_tostring(lua_State* L) {
+ ringbuffer* b = luaL_checkudata(L, 1, "ringbuffer_mt");
+ lua_pushfstring(L, "ringbuffer: %p->%p %d/%d", b, b->buffer, b->blen, b->alen);
+ return 1;
+}
+
+int rb_length(lua_State* L) {
+ ringbuffer* b = luaL_checkudata(L, 1, "ringbuffer_mt");
+ lua_pushinteger(L, b->blen);
+ return 1;
+}
+
+int rb_size(lua_State* L) {
+ ringbuffer* b = luaL_checkudata(L, 1, "ringbuffer_mt");
+ lua_pushinteger(L, b->alen);
+ return 1;
+}
+
+int rb_free(lua_State* L) {
+ ringbuffer* b = luaL_checkudata(L, 1, "ringbuffer_mt");
+ lua_pushinteger(L, b->alen - b->blen);
+ return 1;
+}
+
+int rb_new(lua_State* L) {
+ size_t size = luaL_optinteger(L, 1, sysconf(_SC_PAGESIZE));
+ ringbuffer* b = lua_newuserdata(L, sizeof(ringbuffer));
+ b->rpos = 0;
+ b->wpos = 0;
+ b->alen = size;
+ b->blen = 0;
+ b->buffer = malloc(size);
+
+ if(b->buffer == NULL) {
+ return 0;
+ }
+
+ luaL_getmetatable(L, "ringbuffer_mt");
+ lua_setmetatable(L, -2);
+
+ return 1;
+}
+
+int rb_gc(lua_State* L) {
+ ringbuffer* b = luaL_checkudata(L, 1, "ringbuffer_mt");
+
+ if(b->buffer != NULL) {
+ free(b->buffer);
+ }
+
+ return 0;
+}
+
+int luaopen_util_ringbuffer(lua_State* L) {
+ if(luaL_newmetatable(L, "ringbuffer_mt")) {
+ lua_pushcfunction(L, rb_tostring);
+ lua_setfield(L, -2, "__tostring");
+ lua_pushcfunction(L, rb_length);
+ lua_setfield(L, -2, "__len");
+ lua_pushcfunction(L, rb_gc);
+ lua_setfield(L, -2, "__gc");
+
+ lua_newtable(L); /* __index */
+ {
+ lua_pushcfunction(L, rb_find);
+ lua_setfield(L, -2, "find");
+ lua_pushcfunction(L, rb_read);
+ lua_setfield(L, -2, "read");
+ lua_pushcfunction(L, rb_readuntil);
+ lua_setfield(L, -2, "readuntil");
+ lua_pushcfunction(L, rb_write);
+ lua_setfield(L, -2, "write");
+ lua_pushcfunction(L, rb_size);
+ lua_setfield(L, -2, "size");
+ lua_pushcfunction(L, rb_length);
+ lua_setfield(L, -2, "length");
+ lua_pushcfunction(L, rb_free);
+ lua_setfield(L, -2, "free");
+ }
+ lua_setfield(L, -2, "__index");
+ }
+
+ lua_newtable(L);
+ lua_pushcfunction(L, rb_new);
+ lua_setfield(L, -2, "new");
+ return 1;
+}
diff --git a/util-src/signal.c b/util-src/signal.c
index 961d2d3e..725555fa 100644
--- a/util-src/signal.c
+++ b/util-src/signal.c
@@ -23,7 +23,7 @@
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
+ * OTHER DEALINGS IN THE SOFTWARE.
*/
#include <signal.h>
@@ -32,14 +32,17 @@
#include "lua.h"
#include "lauxlib.h"
+#if (LUA_VERSION_NUM == 501)
+#define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R)
+#endif
+
#ifndef lsig
#define lsig
-struct lua_signal
-{
- char *name; /* name of the signal */
- int sig; /* the signal */
+struct lua_signal {
+ char* name; /* name of the signal */
+ int sig; /* the signal */
};
#endif
@@ -47,170 +50,163 @@ struct lua_signal
#define LUA_SIGNAL "lua_signal"
static const struct lua_signal lua_signals[] = {
- /* ANSI C signals */
+ /* ANSI C signals */
#ifdef SIGABRT
- {"SIGABRT", SIGABRT},
+ {"SIGABRT", SIGABRT},
#endif
#ifdef SIGFPE
- {"SIGFPE", SIGFPE},
+ {"SIGFPE", SIGFPE},
#endif
#ifdef SIGILL
- {"SIGILL", SIGILL},
+ {"SIGILL", SIGILL},
#endif
#ifdef SIGINT
- {"SIGINT", SIGINT},
+ {"SIGINT", SIGINT},
#endif
#ifdef SIGSEGV
- {"SIGSEGV", SIGSEGV},
+ {"SIGSEGV", SIGSEGV},
#endif
#ifdef SIGTERM
- {"SIGTERM", SIGTERM},
+ {"SIGTERM", SIGTERM},
#endif
- /* posix signals */
+ /* posix signals */
#ifdef SIGHUP
- {"SIGHUP", SIGHUP},
+ {"SIGHUP", SIGHUP},
#endif
#ifdef SIGQUIT
- {"SIGQUIT", SIGQUIT},
+ {"SIGQUIT", SIGQUIT},
#endif
#ifdef SIGTRAP
- {"SIGTRAP", SIGTRAP},
+ {"SIGTRAP", SIGTRAP},
#endif
#ifdef SIGKILL
- {"SIGKILL", SIGKILL},
+ {"SIGKILL", SIGKILL},
#endif
#ifdef SIGUSR1
- {"SIGUSR1", SIGUSR1},
+ {"SIGUSR1", SIGUSR1},
#endif
#ifdef SIGUSR2
- {"SIGUSR2", SIGUSR2},
+ {"SIGUSR2", SIGUSR2},
#endif
#ifdef SIGPIPE
- {"SIGPIPE", SIGPIPE},
+ {"SIGPIPE", SIGPIPE},
#endif
#ifdef SIGALRM
- {"SIGALRM", SIGALRM},
+ {"SIGALRM", SIGALRM},
#endif
#ifdef SIGCHLD
- {"SIGCHLD", SIGCHLD},
+ {"SIGCHLD", SIGCHLD},
#endif
#ifdef SIGCONT
- {"SIGCONT", SIGCONT},
+ {"SIGCONT", SIGCONT},
#endif
#ifdef SIGSTOP
- {"SIGSTOP", SIGSTOP},
+ {"SIGSTOP", SIGSTOP},
#endif
#ifdef SIGTTIN
- {"SIGTTIN", SIGTTIN},
+ {"SIGTTIN", SIGTTIN},
#endif
#ifdef SIGTTOU
- {"SIGTTOU", SIGTTOU},
+ {"SIGTTOU", SIGTTOU},
#endif
- /* some BSD signals */
+ /* some BSD signals */
#ifdef SIGIOT
- {"SIGIOT", SIGIOT},
+ {"SIGIOT", SIGIOT},
#endif
#ifdef SIGBUS
- {"SIGBUS", SIGBUS},
+ {"SIGBUS", SIGBUS},
#endif
#ifdef SIGCLD
- {"SIGCLD", SIGCLD},
+ {"SIGCLD", SIGCLD},
#endif
#ifdef SIGURG
- {"SIGURG", SIGURG},
+ {"SIGURG", SIGURG},
#endif
#ifdef SIGXCPU
- {"SIGXCPU", SIGXCPU},
+ {"SIGXCPU", SIGXCPU},
#endif
#ifdef SIGXFSZ
- {"SIGXFSZ", SIGXFSZ},
+ {"SIGXFSZ", SIGXFSZ},
#endif
#ifdef SIGVTALRM
- {"SIGVTALRM", SIGVTALRM},
+ {"SIGVTALRM", SIGVTALRM},
#endif
#ifdef SIGPROF
- {"SIGPROF", SIGPROF},
+ {"SIGPROF", SIGPROF},
#endif
#ifdef SIGWINCH
- {"SIGWINCH", SIGWINCH},
+ {"SIGWINCH", SIGWINCH},
#endif
#ifdef SIGPOLL
- {"SIGPOLL", SIGPOLL},
+ {"SIGPOLL", SIGPOLL},
#endif
#ifdef SIGIO
- {"SIGIO", SIGIO},
+ {"SIGIO", SIGIO},
#endif
- /* add odd signals */
+ /* add odd signals */
#ifdef SIGSTKFLT
- {"SIGSTKFLT", SIGSTKFLT}, /* stack fault */
+ {"SIGSTKFLT", SIGSTKFLT}, /* stack fault */
#endif
#ifdef SIGSYS
- {"SIGSYS", SIGSYS},
+ {"SIGSYS", SIGSYS},
#endif
- {NULL, 0}
+ {NULL, 0}
};
-static lua_State *Lsig = NULL;
+static lua_State* Lsig = NULL;
static lua_Hook Hsig = NULL;
static int Hmask = 0;
static int Hcount = 0;
-static struct signal_event
-{
+static struct signal_event {
int Nsig;
- struct signal_event *next_event;
-} *signal_queue = NULL;
+ struct signal_event* next_event;
+}* signal_queue = NULL;
-static struct signal_event *last_event = NULL;
+static struct signal_event* last_event = NULL;
-static void sighook(lua_State *L, lua_Debug *ar)
-{
- struct signal_event *event;
- /* restore the old hook */
- lua_sethook(L, Hsig, Hmask, Hcount);
+static void sighook(lua_State* L, lua_Debug* ar) {
+ struct signal_event* event;
+ /* restore the old hook */
+ lua_sethook(L, Hsig, Hmask, Hcount);
- lua_pushstring(L, LUA_SIGNAL);
- lua_gettable(L, LUA_REGISTRYINDEX);
+ lua_pushstring(L, LUA_SIGNAL);
+ lua_gettable(L, LUA_REGISTRYINDEX);
- while((event = signal_queue))
- {
- lua_pushnumber(L, event->Nsig);
- lua_gettable(L, -2);
- lua_call(L, 0, 0);
- signal_queue = event->next_event;
- free(event);
- };
+ while((event = signal_queue)) {
+ lua_pushnumber(L, event->Nsig);
+ lua_gettable(L, -2);
+ lua_call(L, 0, 0);
+ signal_queue = event->next_event;
+ free(event);
+ };
- lua_pop(L, 1); /* pop lua_signal table */
+ lua_pop(L, 1); /* pop lua_signal table */
}
-static void handle(int sig)
-{
- if(!signal_queue)
- {
- /* Store the existing debug hook (if any) and its parameters */
- Hsig = lua_gethook(Lsig);
- Hmask = lua_gethookmask(Lsig);
- Hcount = lua_gethookcount(Lsig);
-
- signal_queue = malloc(sizeof(struct signal_event));
- signal_queue->Nsig = sig;
- signal_queue->next_event = NULL;
-
- last_event = signal_queue;
-
- /* Set our new debug hook */
- lua_sethook(Lsig, sighook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1);
- }
- else
- {
- last_event->next_event = malloc(sizeof(struct signal_event));
- last_event->next_event->Nsig = sig;
- last_event->next_event->next_event = NULL;
-
- last_event = last_event->next_event;
- }
+static void handle(int sig) {
+ if(!signal_queue) {
+ /* Store the existing debug hook (if any) and its parameters */
+ Hsig = lua_gethook(Lsig);
+ Hmask = lua_gethookmask(Lsig);
+ Hcount = lua_gethookcount(Lsig);
+
+ signal_queue = malloc(sizeof(struct signal_event));
+ signal_queue->Nsig = sig;
+ signal_queue->next_event = NULL;
+
+ last_event = signal_queue;
+
+ /* Set our new debug hook */
+ lua_sethook(Lsig, sighook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1);
+ } else {
+ last_event->next_event = malloc(sizeof(struct signal_event));
+ last_event->next_event->Nsig = sig;
+ last_event->next_event->next_event = NULL;
+
+ last_event = last_event->next_event;
+ }
}
/*
@@ -222,108 +218,113 @@ static void handle(int sig)
* if caught, Lua function _must_
* exit, as the stack is most likely
* in an unstable state.
-*/
-
-static int l_signal(lua_State *L)
-{
- int args = lua_gettop(L);
- int t, sig; /* type, signal */
-
- /* get type of signal */
- luaL_checkany(L, 1);
- t = lua_type(L, 1);
- if (t == LUA_TNUMBER)
- sig = (int) lua_tonumber(L, 1);
- else if (t == LUA_TSTRING)
- {
- lua_pushstring(L, LUA_SIGNAL);
- lua_gettable(L, LUA_REGISTRYINDEX);
- lua_pushvalue(L, 1);
- lua_gettable(L, -2);
- if (!lua_isnumber(L, -1))
- luaL_error(L, "invalid signal string");
- sig = (int) lua_tonumber(L, -1);
- lua_pop(L, 1); /* get rid of number we pushed */
- } else
- luaL_checknumber(L, 1); /* will always error, with good error msg */
-
- /* set handler */
- if (args == 1 || lua_isnil(L, 2)) /* clear handler */
- {
- lua_pushstring(L, LUA_SIGNAL);
- lua_gettable(L, LUA_REGISTRYINDEX);
- lua_pushnumber(L, sig);
- lua_gettable(L, -2); /* return old handler */
- lua_pushnumber(L, sig);
- lua_pushnil(L);
- lua_settable(L, -4);
- lua_remove(L, -2); /* remove LUA_SIGNAL table */
- signal(sig, SIG_DFL);
- } else
- {
- luaL_checktype(L, 2, LUA_TFUNCTION);
-
- lua_pushstring(L, LUA_SIGNAL);
- lua_gettable(L, LUA_REGISTRYINDEX);
-
- lua_pushnumber(L, sig);
- lua_pushvalue(L, 2);
- lua_settable(L, -3);
-
- /* Set the state for the handler */
- Lsig = L;
-
- if (lua_toboolean(L, 3)) /* c hook? */
- {
- if (signal(sig, handle) == SIG_ERR)
- lua_pushboolean(L, 0);
- else
- lua_pushboolean(L, 1);
- } else /* lua_hook */
- {
- if (signal(sig, handle) == SIG_ERR)
- lua_pushboolean(L, 0);
- else
- lua_pushboolean(L, 1);
- }
- }
- return 1;
+*/
+
+static int l_signal(lua_State* L) {
+ int args = lua_gettop(L);
+ int t, sig; /* type, signal */
+
+ /* get type of signal */
+ luaL_checkany(L, 1);
+ t = lua_type(L, 1);
+
+ if(t == LUA_TNUMBER) {
+ sig = (int) lua_tonumber(L, 1);
+ } else if(t == LUA_TSTRING) {
+ lua_pushstring(L, LUA_SIGNAL);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+ lua_pushvalue(L, 1);
+ lua_gettable(L, -2);
+
+ if(!lua_isnumber(L, -1)) {
+ return luaL_error(L, "invalid signal string");
+ }
+
+ sig = (int) lua_tonumber(L, -1);
+ lua_pop(L, 1); /* get rid of number we pushed */
+ } else {
+ luaL_checknumber(L, 1); /* will always error, with good error msg */
+ return luaL_error(L, "unreachable: invalid number was accepted");
+ }
+
+ /* set handler */
+ if(args == 1 || lua_isnil(L, 2)) { /* clear handler */
+ lua_pushstring(L, LUA_SIGNAL);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+ lua_pushnumber(L, sig);
+ lua_gettable(L, -2); /* return old handler */
+ lua_pushnumber(L, sig);
+ lua_pushnil(L);
+ lua_settable(L, -4);
+ lua_remove(L, -2); /* remove LUA_SIGNAL table */
+ signal(sig, SIG_DFL);
+ } else {
+ luaL_checktype(L, 2, LUA_TFUNCTION);
+
+ lua_pushstring(L, LUA_SIGNAL);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+
+ lua_pushnumber(L, sig);
+ lua_pushvalue(L, 2);
+ lua_settable(L, -3);
+
+ /* Set the state for the handler */
+ Lsig = L;
+
+ if(lua_toboolean(L, 3)) { /* c hook? */
+ if(signal(sig, handle) == SIG_ERR) {
+ lua_pushboolean(L, 0);
+ } else {
+ lua_pushboolean(L, 1);
+ }
+ } else { /* lua_hook */
+ if(signal(sig, handle) == SIG_ERR) {
+ lua_pushboolean(L, 0);
+ } else {
+ lua_pushboolean(L, 1);
+ }
+ }
+ }
+
+ return 1;
}
/*
* l_raise == raise(signal)
*
* signal = signal number or string
-*/
-
-static int l_raise(lua_State *L)
-{
- /* int args = lua_gettop(L); */
- int t = 0; /* type */
- lua_Number ret;
-
- luaL_checkany(L, 1);
-
- t = lua_type(L, 1);
- if (t == LUA_TNUMBER)
- {
- ret = (lua_Number) raise((int) lua_tonumber(L, 1));
- lua_pushnumber(L, ret);
- } else if (t == LUA_TSTRING)
- {
- lua_pushstring(L, LUA_SIGNAL);
- lua_gettable(L, LUA_REGISTRYINDEX);
- lua_pushvalue(L, 1);
- lua_gettable(L, -2);
- if (!lua_isnumber(L, -1))
- luaL_error(L, "invalid signal string");
- ret = (lua_Number) raise((int) lua_tonumber(L, -1));
- lua_pop(L, 1); /* get rid of number we pushed */
- lua_pushnumber(L, ret);
- } else
- luaL_checknumber(L, 1); /* will always error, with good error msg */
-
- return 1;
+*/
+
+static int l_raise(lua_State* L) {
+ /* int args = lua_gettop(L); */
+ int t = 0; /* type */
+ lua_Number ret;
+
+ luaL_checkany(L, 1);
+
+ t = lua_type(L, 1);
+
+ if(t == LUA_TNUMBER) {
+ ret = (lua_Number) raise((int) lua_tonumber(L, 1));
+ lua_pushnumber(L, ret);
+ } else if(t == LUA_TSTRING) {
+ lua_pushstring(L, LUA_SIGNAL);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+ lua_pushvalue(L, 1);
+ lua_gettable(L, -2);
+
+ if(!lua_isnumber(L, -1)) {
+ return luaL_error(L, "invalid signal string");
+ }
+
+ ret = (lua_Number) raise((int) lua_tonumber(L, -1));
+ lua_pop(L, 1); /* get rid of number we pushed */
+ lua_pushnumber(L, ret);
+ } else {
+ luaL_checknumber(L, 1); /* will always error, with good error msg */
+ }
+
+ return 1;
}
#if defined(__unix__) || defined(__APPLE__)
@@ -335,78 +336,80 @@ static int l_raise(lua_State *L)
*
* pid = process id
* signal = signal number or string
-*/
-
-static int l_kill(lua_State *L)
-{
- int t; /* type */
- lua_Number ret; /* return value */
-
- luaL_checknumber(L, 1); /* must be int for pid */
- luaL_checkany(L, 2); /* check for a second arg */
-
- t = lua_type(L, 2);
- if (t == LUA_TNUMBER)
- {
- ret = (lua_Number) kill((int) lua_tonumber(L, 1),
- (int) lua_tonumber(L, 2));
- lua_pushnumber(L, ret);
- } else if (t == LUA_TSTRING)
- {
- lua_pushstring(L, LUA_SIGNAL);
- lua_gettable(L, LUA_REGISTRYINDEX);
- lua_pushvalue(L, 2);
- lua_gettable(L, -2);
- if (!lua_isnumber(L, -1))
- luaL_error(L, "invalid signal string");
- ret = (lua_Number) kill((int) lua_tonumber(L, 1),
- (int) lua_tonumber(L, -1));
- lua_pop(L, 1); /* get rid of number we pushed */
- lua_pushnumber(L, ret);
- } else
- luaL_checknumber(L, 2); /* will always error, with good error msg */
- return 1;
+*/
+
+static int l_kill(lua_State* L) {
+ int t; /* type */
+ lua_Number ret; /* return value */
+
+ luaL_checknumber(L, 1); /* must be int for pid */
+ luaL_checkany(L, 2); /* check for a second arg */
+
+ t = lua_type(L, 2);
+
+ if(t == LUA_TNUMBER) {
+ ret = (lua_Number) kill((int) lua_tonumber(L, 1),
+ (int) lua_tonumber(L, 2));
+ lua_pushnumber(L, ret);
+ } else if(t == LUA_TSTRING) {
+ lua_pushstring(L, LUA_SIGNAL);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+ lua_pushvalue(L, 2);
+ lua_gettable(L, -2);
+
+ if(!lua_isnumber(L, -1)) {
+ return luaL_error(L, "invalid signal string");
+ }
+
+ ret = (lua_Number) kill((int) lua_tonumber(L, 1),
+ (int) lua_tonumber(L, -1));
+ lua_pop(L, 1); /* get rid of number we pushed */
+ lua_pushnumber(L, ret);
+ } else {
+ luaL_checknumber(L, 2); /* will always error, with good error msg */
+ }
+
+ return 1;
}
#endif
static const struct luaL_Reg lsignal_lib[] = {
- {"signal", l_signal},
- {"raise", l_raise},
+ {"signal", l_signal},
+ {"raise", l_raise},
#if defined(__unix__) || defined(__APPLE__)
- {"kill", l_kill},
+ {"kill", l_kill},
#endif
- {NULL, NULL}
+ {NULL, NULL}
};
-int luaopen_util_signal(lua_State *L)
-{
- int i = 0;
-
- /* add the library */
- luaL_register(L, "signal", lsignal_lib);
-
- /* push lua_signals table into the registry */
- /* put the signals inside the library table too,
- * they are only a reference */
- lua_pushstring(L, LUA_SIGNAL);
- lua_createtable(L, 0, 0);
-
- while (lua_signals[i].name != NULL)
- {
- /* registry table */
- lua_pushstring(L, lua_signals[i].name);
- lua_pushnumber(L, lua_signals[i].sig);
- lua_settable(L, -3);
- /* signal table */
- lua_pushstring(L, lua_signals[i].name);
- lua_pushnumber(L, lua_signals[i].sig);
- lua_settable(L, -5);
- i++;
- }
-
- /* add newtable to the registry */
- lua_settable(L, LUA_REGISTRYINDEX);
-
- return 1;
+int luaopen_util_signal(lua_State* L) {
+ int i = 0;
+
+ /* add the library */
+ lua_newtable(L);
+ luaL_setfuncs(L, lsignal_lib, 0);
+
+ /* push lua_signals table into the registry */
+ /* put the signals inside the library table too,
+ * they are only a reference */
+ lua_pushstring(L, LUA_SIGNAL);
+ lua_newtable(L);
+
+ while(lua_signals[i].name != NULL) {
+ /* registry table */
+ lua_pushstring(L, lua_signals[i].name);
+ lua_pushnumber(L, lua_signals[i].sig);
+ lua_settable(L, -3);
+ /* signal table */
+ lua_pushstring(L, lua_signals[i].name);
+ lua_pushnumber(L, lua_signals[i].sig);
+ lua_settable(L, -5);
+ i++;
+ }
+
+ /* add newtable to the registry */
+ lua_settable(L, LUA_REGISTRYINDEX);
+
+ return 1;
}
diff --git a/util-src/table.c b/util-src/table.c
new file mode 100644
index 00000000..6ad45dce
--- /dev/null
+++ b/util-src/table.c
@@ -0,0 +1,14 @@
+#include <lua.h>
+#include <lauxlib.h>
+
+static int Lcreate_table(lua_State* L) {
+ lua_createtable(L, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2));
+ return 1;
+}
+
+int luaopen_util_table(lua_State* L) {
+ lua_newtable(L);
+ lua_pushcfunction(L, Lcreate_table);
+ lua_setfield(L, -2, "create");
+ return 1;
+}
diff --git a/util-src/windows.c b/util-src/windows.c
index 3d14ca95..4fcbf21e 100644
--- a/util-src/windows.c
+++ b/util-src/windows.c
@@ -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.
--
@@ -19,23 +19,30 @@
#include "lua.h"
#include "lauxlib.h"
-static int Lget_nameservers(lua_State *L) {
+#if (LUA_VERSION_NUM == 501)
+#define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R)
+#endif
+
+static int Lget_nameservers(lua_State* L) {
char stack_buffer[1024]; // stack allocated buffer
IP4_ARRAY* ips = (IP4_ARRAY*) stack_buffer;
DWORD len = sizeof(stack_buffer);
DNS_STATUS status;
status = DnsQueryConfig(DnsConfigDnsServerList, FALSE, NULL, NULL, ips, &len);
- if (status == 0) {
+
+ if(status == 0) {
DWORD i;
lua_createtable(L, ips->AddrCount, 0);
- for (i = 0; i < ips->AddrCount; i++) {
+
+ for(i = 0; i < ips->AddrCount; i++) {
DWORD ip = ips->AddrArray[i];
char ip_str[16] = "";
sprintf_s(ip_str, sizeof(ip_str), "%d.%d.%d.%d", (ip >> 0) & 255, (ip >> 8) & 255, (ip >> 16) & 255, (ip >> 24) & 255);
lua_pushstring(L, ip_str);
- lua_rawseti(L, -2, i+1);
+ lua_rawseti(L, -2, i + 1);
}
+
return 1;
} else {
lua_pushnil(L);
@@ -44,46 +51,61 @@ static int Lget_nameservers(lua_State *L) {
}
}
-static int lerror(lua_State *L, char* string) {
+static int lerror(lua_State* L, char* string) {
lua_pushnil(L);
lua_pushfstring(L, "%s: %d", string, GetLastError());
return 2;
}
-static int Lget_consolecolor(lua_State *L) {
+static int Lget_consolecolor(lua_State* L) {
HWND console = GetStdHandle(STD_OUTPUT_HANDLE);
- WORD color; DWORD read_len;
-
+ WORD color;
+ DWORD read_len;
+
CONSOLE_SCREEN_BUFFER_INFO info;
-
- if (console == INVALID_HANDLE_VALUE) return lerror(L, "GetStdHandle");
- if (!GetConsoleScreenBufferInfo(console, &info)) return lerror(L, "GetConsoleScreenBufferInfo");
- if (!ReadConsoleOutputAttribute(console, &color, 1, info.dwCursorPosition, &read_len)) return lerror(L, "ReadConsoleOutputAttribute");
+
+ if(console == INVALID_HANDLE_VALUE) {
+ return lerror(L, "GetStdHandle");
+ }
+
+ if(!GetConsoleScreenBufferInfo(console, &info)) {
+ return lerror(L, "GetConsoleScreenBufferInfo");
+ }
+
+ if(!ReadConsoleOutputAttribute(console, &color, 1, info.dwCursorPosition, &read_len)) {
+ return lerror(L, "ReadConsoleOutputAttribute");
+ }
lua_pushnumber(L, color);
return 1;
}
-static int Lset_consolecolor(lua_State *L) {
+static int Lset_consolecolor(lua_State* L) {
int color = luaL_checkint(L, 1);
HWND console = GetStdHandle(STD_OUTPUT_HANDLE);
- if (console == INVALID_HANDLE_VALUE) return lerror(L, "GetStdHandle");
- if (!SetConsoleTextAttribute(console, color)) return lerror(L, "SetConsoleTextAttribute");
+
+ if(console == INVALID_HANDLE_VALUE) {
+ return lerror(L, "GetStdHandle");
+ }
+
+ if(!SetConsoleTextAttribute(console, color)) {
+ return lerror(L, "SetConsoleTextAttribute");
+ }
+
lua_pushboolean(L, 1);
return 1;
}
-static const luaL_Reg Reg[] =
-{
+static const luaL_Reg Reg[] = {
{ "get_nameservers", Lget_nameservers },
{ "get_consolecolor", Lget_consolecolor },
{ "set_consolecolor", Lset_consolecolor },
{ NULL, NULL }
};
-LUALIB_API int luaopen_util_windows(lua_State *L) {
- luaL_register(L, "windows", Reg);
- lua_pushliteral(L, "version"); /** version */
+LUALIB_API int luaopen_util_windows(lua_State* L) {
+ lua_newtable(L);
+ luaL_setfuncs(L, Reg, 0);
lua_pushliteral(L, "-3.14");
- lua_settable(L,-3);
+ lua_setfield(L, -2, "version");
return 1;
}
diff --git a/util/array.lua b/util/array.lua
index 2d58e7fb..3ddc97f6 100644
--- a/util/array.lua
+++ b/util/array.lua
@@ -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.
--
@@ -11,8 +11,10 @@ local t_insert, t_sort, t_remove, t_concat
local setmetatable = setmetatable;
local math_random = math.random;
+local math_floor = math.floor;
local pairs, ipairs = pairs, ipairs;
local tostring = tostring;
+local type = type;
local array = {};
local array_base = {};
@@ -35,7 +37,7 @@ setmetatable(array, { __call = new_array });
-- Read-only methods
function array_methods:random()
- return self[math_random(1,#self)];
+ return self[math_random(1, #self)];
end
-- These methods can be called two ways:
@@ -43,7 +45,7 @@ end
-- existing_array:method([params, ...]) -- Transform existing array into result
--
function array_base.map(outa, ina, func)
- for k,v in ipairs(ina) do
+ for k, v in ipairs(ina) do
outa[k] = func(v);
end
return outa;
@@ -52,20 +54,20 @@ end
function array_base.filter(outa, ina, func)
local inplace, start_length = ina == outa, #ina;
local write = 1;
- for read=1,start_length do
+ for read = 1, start_length do
local v = ina[read];
if func(v) then
outa[write] = v;
write = write + 1;
end
end
-
+
if inplace and write <= start_length then
- for i=write,start_length do
+ for i = write, start_length do
outa[i] = nil;
end
end
-
+
return outa;
end
@@ -78,34 +80,44 @@ function array_base.sort(outa, ina, ...)
end
function array_base.pluck(outa, ina, key)
- for i=1,#ina do
+ for i = 1, #ina do
outa[i] = ina[i][key];
end
return outa;
end
+function array_base.reverse(outa, ina)
+ local len = #ina;
+ if ina == outa then
+ local middle = math_floor(len/2);
+ len = len + 1;
+ local o; -- opposite
+ for i = 1, middle do
+ o = len - i;
+ outa[i], outa[o] = outa[o], outa[i];
+ end
+ else
+ local off = len + 1;
+ for i = 1, len do
+ outa[i] = ina[off - i];
+ end
+ end
+ return outa;
+end
+
--- These methods only mutate the array
function array_methods:shuffle(outa, ina)
local len = #self;
- for i=1,#self do
- local r = math_random(i,len);
+ for i = 1, #self do
+ local r = math_random(i, len);
self[i], self[r] = self[r], self[i];
end
return self;
end
-function array_methods:reverse()
- local len = #self-1;
- for i=len,1,-1 do
- self:push(self[i]);
- self:pop(i);
- end
- return self;
-end
-
function array_methods:append(array)
- local len,len2 = #self, #array;
- for i=1,len2 do
+ local len, len2 = #self, #array;
+ for i = 1, len2 do
self[len+i] = array[i];
end
return self;
@@ -116,11 +128,7 @@ function array_methods:push(x)
return self;
end
-function array_methods:pop(x)
- local v = self[x];
- t_remove(self, x);
- return v;
-end
+array_methods.pop = t_remove;
function array_methods:concat(sep)
return t_concat(array.map(self, tostring), sep);
@@ -135,7 +143,7 @@ function array.collect(f, s, var)
local t = {};
while true do
var = f(s, var);
- if var == nil then break; end
+ if var == nil then break; end
t_insert(t, var);
end
return setmetatable(t, array_mt);
@@ -157,7 +165,4 @@ for method, f in pairs(array_base) do
end
end
-_G.array = array;
-module("array");
-
return array;
diff --git a/util/async.lua b/util/async.lua
new file mode 100644
index 00000000..968ec804
--- /dev/null
+++ b/util/async.lua
@@ -0,0 +1,158 @@
+local log = require "util.logger".init("util.async");
+
+local function runner_continue(thread)
+ -- ASSUMPTION: runner is in 'waiting' state (but we don't have the runner to know for sure)
+ if coroutine.status(thread) ~= "suspended" then -- This should suffice
+ return false;
+ end
+ local ok, state, runner = coroutine.resume(thread);
+ if not ok then
+ local level = 0;
+ while debug.getinfo(thread, level, "") do level = level + 1; end
+ ok, runner = debug.getlocal(thread, level-1, 1);
+ local error_handler = runner.watchers.error;
+ if error_handler then error_handler(runner, debug.traceback(thread, state)); end
+ elseif state == "ready" then
+ -- If state is 'ready', it is our responsibility to update runner.state from 'waiting'.
+ -- We also have to :run(), because the queue might have further items that will not be
+ -- processed otherwise. FIXME: It's probably best to do this in a nexttick (0 timer).
+ runner.state = "ready";
+ runner:run();
+ end
+ return true;
+end
+
+local function waiter(num)
+ local thread = coroutine.running();
+ if not thread then
+ error("Not running in an async context, see http://prosody.im/doc/developers/async");
+ end
+ num = num or 1;
+ local waiting;
+ return function ()
+ if num == 0 then return; end -- already done
+ waiting = true;
+ coroutine.yield("wait");
+ end, function ()
+ num = num - 1;
+ if num == 0 and waiting then
+ runner_continue(thread);
+ elseif num < 0 then
+ error("done() called too many times");
+ end
+ end;
+end
+
+local function guarder()
+ local guards = {};
+ return function (id, func)
+ local thread = coroutine.running();
+ if not thread then
+ error("Not running in an async context, see http://prosody.im/doc/developers/async");
+ end
+ local guard = guards[id];
+ if not guard then
+ guard = {};
+ guards[id] = guard;
+ log("debug", "New guard!");
+ else
+ table.insert(guard, thread);
+ log("debug", "Guarded. %d threads waiting.", #guard)
+ coroutine.yield("wait");
+ end
+ local function exit()
+ local next_waiting = table.remove(guard, 1);
+ if next_waiting then
+ log("debug", "guard: Executing next waiting thread (%d left)", #guard)
+ runner_continue(next_waiting);
+ else
+ log("debug", "Guard off duty.")
+ guards[id] = nil;
+ end
+ end
+ if func then
+ func();
+ exit();
+ return;
+ end
+ return exit;
+ end;
+end
+
+local runner_mt = {};
+runner_mt.__index = runner_mt;
+
+local function runner_create_thread(func, self)
+ local thread = coroutine.create(function (self)
+ while true do
+ func(coroutine.yield("ready", self));
+ end
+ end);
+ assert(coroutine.resume(thread, self)); -- Start it up, it will return instantly to wait for the first input
+ return thread;
+end
+
+local empty_watchers = {};
+local function runner(func, watchers, data)
+ return setmetatable({ func = func, thread = false, state = "ready", notified_state = "ready",
+ queue = {}, watchers = watchers or empty_watchers, data = data }
+ , runner_mt);
+end
+
+function runner_mt:run(input)
+ if input ~= nil then
+ table.insert(self.queue, input);
+ end
+ if self.state ~= "ready" then
+ return true, self.state, #self.queue;
+ end
+
+ local q, thread = self.queue, self.thread;
+ if not thread or coroutine.status(thread) == "dead" then
+ thread = runner_create_thread(self.func, self);
+ self.thread = thread;
+ end
+
+ local n, state, err = #q, self.state, nil;
+ self.state = "running";
+ while n > 0 and state == "ready" do
+ local consumed;
+ for i = 1,n do
+ local input = q[i];
+ local ok, new_state = coroutine.resume(thread, input);
+ if not ok then
+ consumed, state, err = i, "ready", debug.traceback(thread, new_state);
+ self.thread = nil;
+ break;
+ elseif new_state == "wait" then
+ consumed, state = i, "waiting";
+ break;
+ end
+ end
+ if not consumed then consumed = n; end
+ if q[n+1] ~= nil then
+ n = #q;
+ end
+ for i = 1, n do
+ q[i] = q[consumed+i];
+ end
+ n = #q;
+ end
+ self.state = state;
+ if err or state ~= self.notified_state then
+ if err then
+ state = "error"
+ else
+ self.notified_state = state;
+ end
+ local handler = self.watchers[state];
+ if handler then handler(self, err); end
+ end
+ return true, state, n;
+end
+
+function runner_mt:enqueue(input)
+ table.insert(self.queue, input);
+end
+
+return { waiter = waiter, guarder = guarder, runner = runner };
diff --git a/util/cache.lua b/util/cache.lua
new file mode 100644
index 00000000..d3639b3f
--- /dev/null
+++ b/util/cache.lua
@@ -0,0 +1,105 @@
+
+local function _remove(list, m)
+ if m.prev then
+ m.prev.next = m.next;
+ end
+ if m.next then
+ m.next.prev = m.prev;
+ end
+ if list._tail == m then
+ list._tail = m.prev;
+ end
+ if list._head == m then
+ list._head = m.next;
+ end
+ list._count = list._count - 1;
+end
+
+local function _insert(list, m)
+ if list._head then
+ list._head.prev = m;
+ end
+ m.prev, m.next = nil, list._head;
+ list._head = m;
+ if not list._tail then
+ list._tail = m;
+ end
+ list._count = list._count + 1;
+end
+
+local cache_methods = {};
+local cache_mt = { __index = cache_methods };
+
+function cache_methods:set(k, v)
+ local m = self._data[k];
+ if m then
+ -- Key already exists
+ if v ~= nil then
+ -- Bump to head of list
+ _remove(self, m);
+ _insert(self, m);
+ m.value = v;
+ else
+ -- Remove from list
+ _remove(self, m);
+ self._data[k] = nil;
+ end
+ return true;
+ end
+ -- New key
+ if v == nil then
+ return true;
+ end
+ -- Check whether we need to remove oldest k/v
+ local on_evict, evicted_key, evicted_value;
+ if self._count == self.size then
+ local tail = self._tail;
+ on_evict, evicted_key, evicted_value = self._on_evict, tail.key, tail.value;
+ _remove(self, tail);
+ self._data[evicted_key] = nil;
+ end
+
+ m = { key = k, value = v, prev = nil, next = nil };
+ self._data[k] = m;
+ _insert(self, m);
+ if on_evict and evicted_key then
+ on_evict(evicted_key, evicted_value, self);
+ end
+ return true;
+end
+
+function cache_methods:get(k)
+ local m = self._data[k];
+ if m then
+ return m.value;
+ end
+ return nil;
+end
+
+function cache_methods:items()
+ local m = self._head;
+ return function ()
+ if not m then
+ return;
+ end
+ local k, v = m.key, m.value;
+ m = m.next;
+ return k, v;
+ end
+end
+
+function cache_methods:count()
+ return self._count;
+end
+
+local function new(size, on_evict)
+ size = assert(tonumber(size), "cache size must be a number");
+ size = math.floor(size);
+ assert(size > 0, "cache size must be greater than zero");
+ local data = {};
+ return setmetatable({ _data = data, _count = 0, size = size, _head = nil, _tail = nil, _on_evict = on_evict }, cache_mt);
+end
+
+return {
+ new = new;
+}
diff --git a/util/caps.lua b/util/caps.lua
index a61e7403..cd5ff9c0 100644
--- a/util/caps.lua
+++ b/util/caps.lua
@@ -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.
--
@@ -12,9 +12,9 @@ local sha1 = require "util.hashes".sha1;
local t_insert, t_sort, t_concat = table.insert, table.sort, table.concat;
local ipairs = ipairs;
-module "caps"
+local _ENV = nil;
-function calculate_hash(disco_info)
+local function calculate_hash(disco_info)
local identities, features, extensions = {}, {}, {};
for _, tag in ipairs(disco_info) do
if tag.name == "identity" then
@@ -58,4 +58,6 @@ function calculate_hash(disco_info)
return ver, S;
end
-return _M;
+return {
+ calculate_hash = calculate_hash;
+};
diff --git a/util/dataforms.lua b/util/dataforms.lua
index ee37157a..79b4d1a4 100644
--- a/util/dataforms.lua
+++ b/util/dataforms.lua
@@ -1,26 +1,26 @@
-- 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 setmetatable = setmetatable;
-local pairs, ipairs = pairs, ipairs;
+local ipairs = ipairs;
local tostring, type, next = tostring, type, next;
local t_concat = table.concat;
local st = require "util.stanza";
local jid_prep = require "util.jid".prep;
-module "dataforms"
+local _ENV = nil;
local xmlns_forms = 'jabber:x:data';
local form_t = {};
local form_mt = { __index = form_t };
-function new(layout)
+local function new(layout)
return setmetatable(layout, form_mt);
end
@@ -32,13 +32,13 @@ function form_t.form(layout, data, formtype)
if layout.instructions then
form:tag("instructions"):text(layout.instructions):up();
end
- for n, field in ipairs(layout) do
+ for _, field in ipairs(layout) do
local field_type = field.type or "text-single";
-- Add field tag
form:tag("field", { type = field_type, var = field.name, label = field.label });
local value = (data and data[field.name]) or field.value;
-
+
if value then
-- Add value, depending on type
if field_type == "hidden" then
@@ -102,11 +102,11 @@ function form_t.form(layout, data, formtype)
end
form:up();
end
-
+
if field.required then
form:tag("required"):up();
end
-
+
-- Jump back up to list of fields
form:up();
end
@@ -118,6 +118,7 @@ local field_readers = {};
function form_t.data(layout, stanza)
local data = {};
local errors = {};
+ local present = {};
for _, field in ipairs(layout) do
local tag;
@@ -133,6 +134,7 @@ function form_t.data(layout, stanza)
errors[field.name] = "Required value missing";
end
else
+ present[field.name] = true;
local reader = field_readers[field.type];
if reader then
data[field.name], errors[field.name] = reader(tag, field.required);
@@ -140,35 +142,34 @@ function form_t.data(layout, stanza)
end
end
if next(errors) then
- return data, errors;
+ return data, errors, present;
end
- return data;
+ return data, nil, present;
end
-field_readers["text-single"] =
- function (field_tag, required)
- local data = field_tag:get_child_text("value");
- if data and #data > 0 then
- return data
- elseif required then
- return nil, "Required value missing";
- end
+local function simple_text(field_tag, required)
+ local data = field_tag:get_child_text("value");
+ -- XEP-0004 does not say if an empty string is acceptable for a required value
+ -- so we will follow HTML5 which says that empty string means missing
+ if required and (data == nil or data == "") then
+ return nil, "Required value missing";
end
+ return data; -- Return whatever get_child_text returned, even if empty string
+end
-field_readers["text-private"] =
- field_readers["text-single"];
+field_readers["text-single"] = simple_text;
+
+field_readers["text-private"] = simple_text;
field_readers["jid-single"] =
function (field_tag, required)
- local raw_data = field_tag:get_child_text("value")
+ local raw_data, err = simple_text(field_tag, required);
+ if not raw_data then return raw_data, err; end
local data = jid_prep(raw_data);
- if data and #data > 0 then
- return data
- elseif raw_data then
+ if not data then
return nil, "Invalid JID: " .. raw_data;
- elseif required then
- return nil, "Required value missing";
end
+ return data;
end
field_readers["jid-multi"] =
@@ -212,8 +213,7 @@ field_readers["text-multi"] =
return data, err;
end
-field_readers["list-single"] =
- field_readers["text-single"];
+field_readers["list-single"] = simple_text;
local boolean_values = {
["1"] = true, ["true"] = true,
@@ -222,15 +222,13 @@ local boolean_values = {
field_readers["boolean"] =
function (field_tag, required)
- local raw_value = field_tag:get_child_text("value");
- local value = boolean_values[raw_value ~= nil and raw_value];
- if value ~= nil then
- return value;
- elseif raw_value then
- return nil, "Invalid boolean representation";
- elseif required then
- return nil, "Required value missing";
+ local raw_value, err = simple_text(field_tag, required);
+ if not raw_value then return raw_value, err; end
+ local value = boolean_values[raw_value];
+ if value == nil then
+ return nil, "Invalid boolean representation:" .. raw_value;
end
+ return value;
end
field_readers["hidden"] =
@@ -238,7 +236,9 @@ field_readers["hidden"] =
return field_tag:get_child_text("value");
end
-return _M;
+return {
+ new = new;
+};
--[=[
diff --git a/util/datamanager.lua b/util/datamanager.lua
index c69ecd25..07b2fe4a 100644
--- a/util/datamanager.lua
+++ b/util/datamanager.lua
@@ -43,7 +43,7 @@ pcall(function()
fallocate = pposix.fallocate or fallocate;
end);
-module "datamanager"
+local _ENV = nil;
---- utils -----
local encode, decode;
@@ -74,7 +74,7 @@ local callbacks = {};
------- API -------------
-function set_data_path(path)
+local function set_data_path(path)
log("debug", "Setting data path to: %s", path);
data_path = path;
end
@@ -87,14 +87,14 @@ local function callback(username, host, datastore, data)
return username, host, datastore, data;
end
-function add_callback(func)
+local function add_callback(func)
if not callbacks[func] then -- Would you really want to set the same callback more than once?
callbacks[func] = true;
callbacks[#callbacks+1] = func;
return true;
end
end
-function remove_callback(func)
+local function remove_callback(func)
if callbacks[func] then
for i, f in ipairs(callbacks) do
if f == func then
@@ -106,7 +106,7 @@ function remove_callback(func)
end
end
-function getpath(username, host, datastore, ext, create)
+local function getpath(username, host, datastore, ext, create)
ext = ext or "dat";
host = (host and encode(host)) or "_global";
username = username and encode(username);
@@ -119,7 +119,7 @@ function getpath(username, host, datastore, ext, create)
end
end
-function load(username, host, datastore)
+local function load(username, host, datastore)
local data, ret = envloadfile(getpath(username, host, datastore), {});
if not data then
local mode = lfs.attributes(getpath(username, host, datastore), "mode");
@@ -176,7 +176,7 @@ if prosody and prosody.platform ~= "posix" then
end
end
-function store(username, host, datastore, data)
+local function store(username, host, datastore, data)
if not data then
data = {};
end
@@ -210,33 +210,62 @@ function store(username, host, datastore, data)
return true;
end
-function list_append(username, host, datastore, data)
- if not data then return; end
- if callback(username, host, datastore) == false then return true; end
- -- save the datastore
- local f, msg = io_open(getpath(username, host, datastore, "list", true), "r+");
- if not f then
- f, msg = io_open(getpath(username, host, datastore, "list", true), "w");
- end
+-- Append a blob of data to a file
+local function append(username, host, datastore, ext, data)
+ if type(data) ~= "string" then return; end
+ local filename = getpath(username, host, datastore, ext, true);
+
+ local ok;
+ local f, msg = io_open(filename, "r+");
if not f then
- log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil");
- return;
+ -- File did probably not exist, let's create it
+ f, msg = io_open(filename, "w");
+ if not f then
+ return nil, msg, "open";
+ end
end
- local data = "item(" .. serialize(data) .. ");\n";
+
local pos = f:seek("end");
- local ok, msg = fallocate(f, pos, #data);
- f:seek("set", pos);
- if ok then
- f:write(data);
- else
+ ok, msg = fallocate(f, pos, #data);
+ if not ok then
+ log("warn", "fallocate() failed: %s", tostring(msg));
+ -- This doesn't work on every file system
+ end
+
+ if f:seek() ~= pos then
+ log("debug", "fallocate() changed file position");
+ f:seek("set", pos);
+ end
+
+ ok, msg = f:write(data);
+ if not ok then
+ f:close();
+ return ok, msg, "write";
+ end
+
+ ok, msg = f:close();
+ if not ok then
+ return ok, msg;
+ end
+
+ return true, pos;
+end
+
+local function list_append(username, host, datastore, data)
+ if not data then return; end
+ if callback(username, host, datastore) == false then return true; end
+ -- save the datastore
+
+ data = "item(" .. serialize(data) .. ");\n";
+ local ok, msg = append(username, host, datastore, "list", data);
+ if not ok then
log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil");
return ok, msg;
end
- f:close();
return true;
end
-function list_store(username, host, datastore, data)
+local function list_store(username, host, datastore, data)
if not data then
data = {};
end
@@ -260,7 +289,7 @@ function list_store(username, host, datastore, data)
return true;
end
-function list_load(username, host, datastore)
+local function list_load(username, host, datastore)
local items = {};
local data, ret = envloadfile(getpath(username, host, datastore, "list"), {item = function(i) t_insert(items, i); end});
if not data then
@@ -288,7 +317,7 @@ local type_map = {
list = "list";
}
-function users(host, store, typ)
+local function users(host, store, typ)
typ = type_map[typ or "keyval"];
local store_dir = format("%s/%s/%s", data_path, encode(host), store);
@@ -307,7 +336,7 @@ function users(host, store, typ)
end, state;
end
-function stores(username, host, typ)
+local function stores(username, host, typ)
typ = type_map[typ or "keyval"];
local store_dir = format("%s/%s/", data_path, encode(host));
@@ -347,7 +376,7 @@ local function do_remove(path)
return true
end
-function purge(username, host)
+local function purge(username, host)
local host_dir = format("%s/%s/", data_path, encode(host));
local ok, iter, state, var = pcall(lfs.dir, host_dir);
if not ok then
@@ -367,6 +396,20 @@ function purge(username, host)
return #errs == 0, t_concat(errs, ", ");
end
-_M.path_decode = decode;
-_M.path_encode = encode;
-return _M;
+return {
+ set_data_path = set_data_path;
+ add_callback = add_callback;
+ remove_callback = remove_callback;
+ getpath = getpath;
+ load = load;
+ store = store;
+ append_raw = append;
+ list_append = list_append;
+ list_store = list_store;
+ list_load = list_load;
+ users = users;
+ stores = stores;
+ purge = purge;
+ path_decode = decode;
+ path_encode = encode;
+};
diff --git a/util/datetime.lua b/util/datetime.lua
index a1f62a48..27f28ace 100644
--- a/util/datetime.lua
+++ b/util/datetime.lua
@@ -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,25 +15,25 @@ local os_difftime = os.difftime;
local error = error;
local tonumber = tonumber;
-module "datetime"
+local _ENV = nil;
-function date(t)
+local function date(t)
return os_date("!%Y-%m-%d", t);
end
-function datetime(t)
+local function datetime(t)
return os_date("!%Y-%m-%dT%H:%M:%SZ", t);
end
-function time(t)
+local function time(t)
return os_date("!%H:%M:%S", t);
end
-function legacy(t)
+local function legacy(t)
return os_date("!%Y%m%dT%H:%M:%S", t);
end
-function parse(s)
+local function parse(s)
if s then
local year, month, day, hour, min, sec, tzd;
year, month, day, hour, min, sec, tzd = s:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$");
@@ -54,4 +54,10 @@ function parse(s)
end
end
-return _M;
+return {
+ date = date;
+ datetime = datetime;
+ time = time;
+ legacy = legacy;
+ parse = parse;
+};
diff --git a/util/debug.lua b/util/debug.lua
index bff0e347..00f476d0 100644
--- a/util/debug.lua
+++ b/util/debug.lua
@@ -1,6 +1,9 @@
-- Variables ending with these names will not
-- have their values printed ('password' includes
-- 'new_password', etc.)
+--
+-- luacheck: ignore 122/debug
+
local censored_names = {
password = true;
passwd = true;
@@ -13,7 +16,7 @@ local termcolours = require "util.termcolours";
local getstring = termcolours.getstring;
local styles;
do
- _ = termcolours.getstyle;
+ local _ = termcolours.getstyle;
styles = {
boundary_padding = _("bright");
filename = _("bright", "blue");
@@ -22,20 +25,23 @@ do
location = _("yellow");
};
end
-module("debugx", package.seeall);
-function get_locals_table(level)
- level = level + 1; -- Skip this function itself
+local function get_locals_table(thread, level)
local locals = {};
for local_num = 1, math.huge do
- local name, value = debug.getlocal(level, local_num);
+ local name, value;
+ if thread then
+ name, value = debug.getlocal(thread, level, local_num);
+ else
+ name, value = debug.getlocal(level+1, local_num);
+ end
if not name then break; end
table.insert(locals, { name = name, value = value });
end
return locals;
end
-function get_upvalues_table(func)
+local function get_upvalues_table(func)
local upvalues = {};
if func then
for upvalue_num = 1, math.huge do
@@ -47,7 +53,7 @@ function get_upvalues_table(func)
return upvalues;
end
-function string_from_var_table(var_table, max_line_len, indent_str)
+local function string_from_var_table(var_table, max_line_len, indent_str)
local var_string = {};
local col_pos = 0;
max_line_len = max_line_len or math.huge;
@@ -83,33 +89,25 @@ function string_from_var_table(var_table, max_line_len, indent_str)
end
end
-function get_traceback_table(thread, start_level)
+local function get_traceback_table(thread, start_level)
local levels = {};
for level = start_level, math.huge do
local info;
if thread then
- info = debug.getinfo(thread, level+1);
+ info = debug.getinfo(thread, level);
else
info = debug.getinfo(level+1);
end
if not info then break; end
-
+
levels[(level-start_level)+1] = {
level = level;
info = info;
- locals = get_locals_table(level+1);
+ locals = get_locals_table(thread, level+(thread and 0 or 1));
upvalues = get_upvalues_table(info.func);
};
- end
- return levels;
-end
-
-function traceback(...)
- local ok, ret = pcall(_traceback, ...);
- if not ok then
- return "Error in error handling: "..ret;
end
- return ret;
+ return levels;
end
local function build_source_boundary_marker(last_source_desc)
@@ -117,7 +115,7 @@ local function build_source_boundary_marker(last_source_desc)
return getstring(styles.boundary_padding, "v"..padding).." "..getstring(styles.filename, last_source_desc).." "..getstring(styles.boundary_padding, padding..(#last_source_desc%2==0 and "-v" or "v "));
end
-function _traceback(thread, message, level)
+local function _traceback(thread, message, level)
-- Lua manual says: debug.traceback ([thread,] [message [, level]])
-- I fathom this to mean one of:
@@ -134,15 +132,15 @@ function _traceback(thread, message, level)
return nil; -- debug.traceback() does this
end
- level = level or 1;
+ level = level or 0;
message = message and (message.."\n") or "";
-
- -- +3 counts for this function, and the pcall() and wrapper above us
- local levels = get_traceback_table(thread, level+3);
-
+
+ -- +3 counts for this function, and the pcall() and wrapper above us, the +1... I don't know.
+ local levels = get_traceback_table(thread, level+(thread == nil and 4 or 0));
+
local last_source_desc;
-
+
local lines = {};
for nlevel, level in ipairs(levels) do
local info = level.info;
@@ -171,9 +169,11 @@ function _traceback(thread, message, level)
nlevel = nlevel-1;
table.insert(lines, "\t"..(nlevel==0 and ">" or " ")..getstring(styles.level_num, "("..nlevel..") ")..line);
local npadding = (" "):rep(#tostring(nlevel));
- local locals_str = string_from_var_table(level.locals, optimal_line_length, "\t "..npadding);
- if locals_str then
- table.insert(lines, "\t "..npadding.."Locals: "..locals_str);
+ if level.locals then
+ local locals_str = string_from_var_table(level.locals, optimal_line_length, "\t "..npadding);
+ if locals_str then
+ table.insert(lines, "\t "..npadding.."Locals: "..locals_str);
+ end
end
local upvalues_str = string_from_var_table(level.upvalues, optimal_line_length, "\t "..npadding);
if upvalues_str then
@@ -186,8 +186,23 @@ function _traceback(thread, message, level)
return message.."stack traceback:\n"..table.concat(lines, "\n");
end
-function use()
+local function traceback(...)
+ local ok, ret = pcall(_traceback, ...);
+ if not ok then
+ return "Error in error handling: "..ret;
+ end
+ return ret;
+end
+
+local function use()
debug.traceback = traceback;
end
-return _M;
+return {
+ get_locals_table = get_locals_table;
+ get_upvalues_table = get_upvalues_table;
+ string_from_var_table = string_from_var_table;
+ get_traceback_table = get_traceback_table;
+ traceback = traceback;
+ use = use;
+};
diff --git a/util/dependencies.lua b/util/dependencies.lua
index 4d50cf63..9ab40765 100644
--- a/util/dependencies.lua
+++ b/util/dependencies.lua
@@ -1,21 +1,19 @@
-- 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.
--
-module("dependencies", package.seeall)
-
-function softreq(...) local ok, lib = pcall(require, ...); if ok then return lib; else return nil, lib; end end
+local function softreq(...) local ok, lib = pcall(require, ...); if ok then return lib; else return nil, lib; end end
-- Required to be able to find packages installed with luarocks
if not softreq "luarocks.loader" then -- LuaRocks 2.x
softreq "luarocks.require"; -- LuaRocks <1.x
end
-function missingdep(name, sources, msg)
+local function missingdep(name, sources, msg)
print("");
print("**************************");
print("Prosody was unable to find "..tostring(name));
@@ -35,7 +33,7 @@ function missingdep(name, sources, msg)
print("");
end
--- COMPAT w/pre-0.8 Debian: The Debian config file used to use
+-- COMPAT w/pre-0.8 Debian: The Debian config file used to use
-- util.ztact, which has been removed from Prosody in 0.8. This
-- is to log an error for people who still use it, so they can
-- update their configs.
@@ -48,19 +46,19 @@ package.preload["util.ztact"] = function ()
end
end;
-function check_dependencies()
- if _VERSION ~= "Lua 5.1" then
+local function check_dependencies()
+ if _VERSION < "Lua 5.1" then
print "***********************************"
print("Unsupported Lua version: ".._VERSION);
- print("Only Lua 5.1 is supported.");
+ print("At least Lua 5.1 is required.");
print "***********************************"
return false;
end
local fatal;
-
+
local lxp = softreq "lxp"
-
+
if not lxp then
missingdep("luaexpat", {
["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-expat0";
@@ -69,9 +67,9 @@ function check_dependencies()
});
fatal = true;
end
-
+
local socket = softreq "socket"
-
+
if not socket then
missingdep("luasocket", {
["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-socket2";
@@ -80,7 +78,7 @@ function check_dependencies()
});
fatal = true;
end
-
+
local lfs, err = softreq "lfs"
if not lfs then
missingdep("luafilesystem", {
@@ -90,9 +88,9 @@ function check_dependencies()
});
fatal = true;
end
-
+
local ssl = softreq "ssl"
-
+
if not ssl then
missingdep("LuaSec", {
["Debian/Ubuntu"] = "http://prosody.im/download/start#debian_and_ubuntu";
@@ -100,7 +98,7 @@ function check_dependencies()
["Source"] = "http://www.inf.puc-rio.br/~brunoos/luasec/";
}, "SSL/TLS support will not be available");
end
-
+
local encodings, err = softreq "util.encodings"
if not encodings then
if err:match("not found") then
@@ -137,22 +135,27 @@ function check_dependencies()
return not fatal;
end
-function log_warnings()
+local function log_warnings()
+ if _VERSION > "Lua 5.1" then
+ prosody.log("warn", "Support for %s is experimental, please report any issues", _VERSION);
+ end
+ local ssl = softreq"ssl";
if ssl then
local major, minor, veryminor, patched = ssl._VERSION:match("(%d+)%.(%d+)%.?(%d*)(M?)");
if not major or ((tonumber(major) == 0 and (tonumber(minor) or 0) <= 3 and (tonumber(veryminor) or 0) <= 2) and patched ~= "M") then
- log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends");
+ prosody.log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends");
end
end
+ local lxp = softreq"lxp";
if lxp then
if not pcall(lxp.new, { StartDoctypeDecl = false }) then
- log("error", "The version of LuaExpat on your system leaves Prosody "
+ prosody.log("error", "The version of LuaExpat on your system leaves Prosody "
.."vulnerable to denial-of-service attacks. You should upgrade to "
.."LuaExpat 1.3.0 or higher as soon as possible. See "
.."http://prosody.im/doc/depends#luaexpat for more information.");
end
if not lxp.new({}).getcurrentbytecount then
- log("error", "The version of LuaExpat on your system does not support "
+ prosody.log("error", "The version of LuaExpat on your system does not support "
.."stanza size limits, which may leave servers on untrusted "
.."networks (e.g. the internet) vulnerable to denial-of-service "
.."attacks. You should upgrade to LuaExpat 1.3.0 or higher as "
@@ -162,4 +165,9 @@ function log_warnings()
end
end
-return _M;
+return {
+ softreq = softreq;
+ missingdep = missingdep;
+ check_dependencies = check_dependencies;
+ log_warnings = log_warnings;
+};
diff --git a/util/events.lua b/util/events.lua
index 412acccd..073d2a60 100644
--- a/util/events.lua
+++ b/util/events.lua
@@ -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.
--
@@ -9,15 +9,23 @@
local pairs = pairs;
local t_insert = table.insert;
+local t_remove = table.remove;
local t_sort = table.sort;
local setmetatable = setmetatable;
local next = next;
-module "events"
+local _ENV = nil;
-function new()
+local function new()
+ -- Map event name to ordered list of handlers (lazily built): handlers[event_name] = array_of_handler_functions
local handlers = {};
+ -- Array of wrapper functions that wrap all events (nil if empty)
+ local global_wrappers;
+ -- Per-event wrappers: wrappers[event_name] = wrapper_function
+ local wrappers = {};
+ -- Event map: event_map[handler_function] = priority_number
local event_map = {};
+ -- Called on-demand to build handlers entries
local function _rebuild_index(handlers, event)
local _handlers = event_map[event];
if not _handlers or next(_handlers) == nil then return; end
@@ -50,6 +58,9 @@ function new()
end
end
end;
+ local function get_handlers(event)
+ return handlers[event];
+ end;
local function add_handlers(handlers)
for event, handler in pairs(handlers) do
add_handler(event, handler);
@@ -60,24 +71,91 @@ function new()
remove_handler(event, handler);
end
end;
- local function fire_event(event, ...)
- local h = handlers[event];
+ local function _fire_event(event_name, event_data)
+ local h = handlers[event_name];
if h then
for i=1,#h do
- local ret = h[i](...);
+ local ret = h[i](event_data);
if ret ~= nil then return ret; end
end
end
end;
+ local function fire_event(event_name, event_data)
+ local w = wrappers[event_name] or global_wrappers;
+ if w then
+ local curr_wrapper = #w;
+ local function c(event_name, event_data)
+ curr_wrapper = curr_wrapper - 1;
+ if curr_wrapper == 0 then
+ if global_wrappers == nil or w == global_wrappers then
+ return _fire_event(event_name, event_data);
+ end
+ w, curr_wrapper = global_wrappers, #global_wrappers;
+ return w[curr_wrapper](c, event_name, event_data);
+ else
+ return w[curr_wrapper](c, event_name, event_data);
+ end
+ end
+ return w[curr_wrapper](c, event_name, event_data);
+ end
+ return _fire_event(event_name, event_data);
+ end
+ local function add_wrapper(event_name, wrapper)
+ local w;
+ if event_name == false then
+ w = global_wrappers;
+ if not w then
+ w = {};
+ global_wrappers = w;
+ end
+ else
+ w = wrappers[event_name];
+ if not w then
+ w = {};
+ wrappers[event_name] = w;
+ end
+ end
+ w[#w+1] = wrapper;
+ end
+ local function remove_wrapper(event_name, wrapper)
+ local w;
+ if event_name == false then
+ w = global_wrappers;
+ else
+ w = wrappers[event_name];
+ end
+ if not w then return; end
+ for i = #w, 1 do
+ if w[i] == wrapper then
+ t_remove(w, i);
+ end
+ end
+ if #w == 0 then
+ if event_name == nil then
+ global_wrappers = nil;
+ else
+ wrappers[event_name] = nil;
+ end
+ end
+ end
return {
add_handler = add_handler;
remove_handler = remove_handler;
add_handlers = add_handlers;
remove_handlers = remove_handlers;
+ get_handlers = get_handlers;
+ wrappers = {
+ add_handler = add_wrapper;
+ remove_handler = remove_wrapper;
+ };
+ add_wrapper = add_wrapper;
+ remove_wrapper = remove_wrapper;
fire_event = fire_event;
_handlers = handlers;
_event_map = event_map;
};
end
-return _M;
+return {
+ new = new;
+};
diff --git a/util/filters.lua b/util/filters.lua
index 6290e53b..f405c0bd 100644
--- a/util/filters.lua
+++ b/util/filters.lua
@@ -1,22 +1,22 @@
-- 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 t_insert, t_remove = table.insert, table.remove;
-module "filters"
+local _ENV = nil;
local new_filter_hooks = {};
-function initialize(session)
+local function initialize(session)
if not session.filters then
local filters = {};
session.filters = filters;
-
+
function session.filter(type, data)
local filter_list = filters[type];
if filter_list then
@@ -28,19 +28,19 @@ function initialize(session)
return data;
end
end
-
+
for i=1,#new_filter_hooks do
new_filter_hooks[i](session);
end
-
+
return session.filter;
end
-function add_filter(session, type, callback, priority)
+local function add_filter(session, type, callback, priority)
if not session.filters then
initialize(session);
end
-
+
local filter_list = session.filters[type];
if not filter_list then
filter_list = {};
@@ -48,19 +48,19 @@ function add_filter(session, type, callback, priority)
elseif filter_list[callback] then
return; -- Filter already added
end
-
+
priority = priority or 0;
-
+
local i = 0;
repeat
i = i + 1;
until not filter_list[i] or filter_list[filter_list[i]] < priority;
-
+
t_insert(filter_list, i, callback);
filter_list[callback] = priority;
end
-function remove_filter(session, type, callback)
+local function remove_filter(session, type, callback)
if not session.filters then return; end
local filter_list = session.filters[type];
if filter_list and filter_list[callback] then
@@ -74,11 +74,11 @@ function remove_filter(session, type, callback)
end
end
-function add_filter_hook(callback)
+local function add_filter_hook(callback)
t_insert(new_filter_hooks, callback);
end
-function remove_filter_hook(callback)
+local function remove_filter_hook(callback)
for i=1,#new_filter_hooks do
if new_filter_hooks[i] == callback then
t_remove(new_filter_hooks, i);
@@ -86,4 +86,10 @@ function remove_filter_hook(callback)
end
end
-return _M;
+return {
+ initialize = initialize;
+ add_filter = add_filter;
+ remove_filter = remove_filter;
+ add_filter_hook = add_filter_hook;
+ remove_filter_hook = remove_filter_hook;
+};
diff --git a/util/helpers.lua b/util/helpers.lua
index 08b86a7c..bf76d258 100644
--- a/util/helpers.lua
+++ b/util/helpers.lua
@@ -1,28 +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.
--
local debug = require "util.debug";
-module("helpers", package.seeall);
-
-- Helper functions for debugging
local log = require "util.logger".init("util.debug");
-function log_host_events(host)
- return log_events(prosody.hosts[host].events, host);
-end
-
-function revert_log_host_events(host)
- return revert_log_events(prosody.hosts[host].events);
-end
-
-function log_events(events, name, logger)
+local function log_events(events, name, logger)
local f = events.fire_event;
if not f then
error("Object does not appear to be a util.events object");
@@ -37,11 +27,19 @@ function log_events(events, name, logger)
return events;
end
-function revert_log_events(events)
+local function revert_log_events(events)
events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :))
end
-function show_events(events, specific_event)
+local function log_host_events(host)
+ return log_events(prosody.hosts[host].events, host);
+end
+
+local function revert_log_host_events(host)
+ return revert_log_events(prosody.hosts[host].events);
+end
+
+local function show_events(events, specific_event)
local event_handlers = events._handlers;
local events_array = {};
local event_handler_arrays = {};
@@ -70,7 +68,7 @@ function show_events(events, specific_event)
return table.concat(events_array, "\n");
end
-function get_upvalue(f, get_name)
+local function get_upvalue(f, get_name)
local i, name, value = 0;
repeat
i = i + 1;
@@ -79,4 +77,11 @@ function get_upvalue(f, get_name)
return value;
end
-return _M;
+return {
+ log_host_events = log_host_events;
+ revert_log_host_events = revert_log_host_events;
+ log_events = log_events;
+ revert_log_events = revert_log_events;
+ show_events = show_events;
+ get_upvalue = get_upvalue;
+};
diff --git a/util/hex.lua b/util/hex.lua
new file mode 100644
index 00000000..4cc28d33
--- /dev/null
+++ b/util/hex.lua
@@ -0,0 +1,26 @@
+local s_char = string.char;
+local s_format = string.format;
+local s_gsub = string.gsub;
+local s_lower = string.lower;
+
+local char_to_hex = {};
+local hex_to_char = {};
+
+do
+ local char, hex;
+ for i = 0,255 do
+ char, hex = s_char(i), s_format("%02x", i);
+ char_to_hex[char] = hex;
+ hex_to_char[hex] = char;
+ end
+end
+
+local function to(s)
+ return (s_gsub(s, ".", char_to_hex));
+end
+
+local function from(s)
+ return (s_gsub(s_lower(s), "%X*(%x%x)%X*", hex_to_char));
+end
+
+return { to = to, from = from }
diff --git a/util/hmac.lua b/util/hmac.lua
index 51211c7a..2c4cc6ef 100644
--- a/util/hmac.lua
+++ b/util/hmac.lua
@@ -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 --git a/util/import.lua b/util/import.lua
index 81401e8b..c2b9dce1 100644
--- a/util/import.lua
+++ b/util/import.lua
@@ -1,13 +1,14 @@
-- 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 unpack = table.unpack or unpack; --luacheck: ignore 113
local t_insert = table.insert;
function import(module, ...)
local m = package.loaded[module] or require(module);
diff --git a/util/interpolation.lua b/util/interpolation.lua
new file mode 100644
index 00000000..315cc203
--- /dev/null
+++ b/util/interpolation.lua
@@ -0,0 +1,85 @@
+-- Simple template language
+--
+-- The new() function takes a pattern and an escape function and returns
+-- a render() function. Both are required.
+--
+-- The function render() takes a string template and a table of values.
+-- Sequences like {name} in the template string are substituted
+-- with values from the table, optionally depending on a modifier
+-- symbol.
+--
+-- Variants are:
+-- {name} is substituted for values["name"] and is escaped using the
+-- second argument to new_render(). To disable the escaping, use {name!}.
+-- {name.item} can be used to access table items.
+-- To renter lists of items: {name# item number {idx} is {item} }
+-- Or key-value pairs: {name% t[ {idx} ] = {item} }
+-- To show a defaults for missing values {name? sub-template } can be used,
+-- which renders a sub-template if values["name"] is false-ish.
+-- {name& sub-template } does the opposite, the sub-template is rendered
+-- if the selected value is anything but false or nil.
+
+local type, tostring = type, tostring;
+local pairs, ipairs = pairs, ipairs;
+local s_sub, s_gsub, s_match = string.sub, string.gsub, string.match;
+local t_concat = table.concat;
+
+local function new_render(pat, escape, funcs)
+ -- assert(type(pat) == "string", "bad argument #1 to 'new_render' (string expected)");
+ -- assert(type(escape) == "function", "bad argument #2 to 'new_render' (function expected)");
+ local function render(template, values)
+ -- assert(type(template) == "string", "bad argument #1 to 'render' (string expected)");
+ -- assert(type(values) == "table", "bad argument #2 to 'render' (table expected)");
+ return (s_gsub(template, pat, function (block)
+ block = s_sub(block, 2, -2);
+ local name, opt, e = s_match(block, "^([%a_][%w_.]*)(%p?)()");
+ if not name then return end
+ local value = values[name];
+ if not value and name:find(".", 2, true) then
+ value = values;
+ for word in name:gmatch"[^.]+" do
+ value = value[word];
+ if not value then break; end
+ end
+ end
+ if funcs then
+ while value ~= nil and opt == '|' do
+ local f;
+ f, opt, e = s_match(block, "^([%a_][%w_.]*)(%p?)()", e);
+ f = funcs[f];
+ if f then value = f(value); end
+ end
+ end
+ if opt == '#' or opt == '%' then
+ if type(value) ~= "table" then return ""; end
+ local iter = opt == '#' and ipairs or pairs;
+ local out, i, subtpl = {}, 1, s_sub(block, e);
+ local subvalues = setmetatable({}, { __index = values });
+ for idx, item in iter(value) do
+ subvalues.idx = idx;
+ subvalues.item = item;
+ out[i], i = render(subtpl, subvalues), i+1;
+ end
+ return t_concat(out);
+ elseif opt == '&' then
+ if not value then return ""; end
+ return render(s_sub(block, e), values);
+ elseif opt == '?' and not value then
+ return render(s_sub(block, e), values);
+ elseif value ~= nil then
+ if type(value) ~= "string" then
+ value = tostring(value);
+ end
+ if opt ~= '!' then
+ return escape(value);
+ end
+ return value;
+ end
+ end));
+ end
+ return render;
+end
+
+return {
+ new = new_render;
+};
diff --git a/util/ip.lua b/util/ip.lua
index acfd7f24..ec3b4d7e 100644
--- a/util/ip.lua
+++ b/util/ip.lua
@@ -96,7 +96,7 @@ local function v6scope(ip)
if ip:match("^[0:]*1$") then
return 0x2;
-- Link-local unicast:
- elseif ip:match("^[Ff][Ee][89ABab]") then
+ elseif ip:match("^[Ff][Ee][89ABab]") then
return 0x2;
-- Site-local unicast:
elseif ip:match("^[Ff][Ee][CcDdEeFf]") then
@@ -206,5 +206,40 @@ function ip_methods:scope()
return value;
end
+function ip_methods:private()
+ local private = self.scope ~= 0xE;
+ if not private and self.proto == "IPv4" then
+ local ip = self.addr;
+ local fields = {};
+ ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
+ if fields[1] == 127 or fields[1] == 10 or (fields[1] == 192 and fields[2] == 168)
+ or (fields[1] == 172 and (fields[2] >= 16 or fields[2] <= 32)) then
+ private = true;
+ end
+ end
+ self.private = private;
+ return private;
+end
+
+local function parse_cidr(cidr)
+ local bits;
+ local ip_len = cidr:find("/", 1, true);
+ if ip_len then
+ bits = tonumber(cidr:sub(ip_len+1, -1));
+ cidr = cidr:sub(1, ip_len-1);
+ end
+ return new_ip(cidr), bits;
+end
+
+local function match(ipA, ipB, bits)
+ local common_bits = commonPrefixLength(ipA, ipB);
+ if bits and ipB.proto == "IPv4" then
+ common_bits = common_bits - 96; -- v6 mapped addresses always share these bits
+ end
+ return common_bits >= (bits or 128);
+end
+
return {new_ip = new_ip,
- commonPrefixLength = commonPrefixLength};
+ commonPrefixLength = commonPrefixLength,
+ parse_cidr = parse_cidr,
+ match=match};
diff --git a/util/iterators.lua b/util/iterators.lua
index 1f6aacb8..868ba786 100644
--- a/util/iterators.lua
+++ b/util/iterators.lua
@@ -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,6 +10,11 @@
local it = {};
+local t_insert = table.insert;
+local select, next = select, next;
+local unpack = table.unpack or unpack; --luacheck: ignore 113
+local pack = table.pack or function (...) return { n = select("#", ...), ... }; end
+
-- Reverse an iterator
function it.reverse(f, s, var)
local results = {};
@@ -19,9 +24,9 @@ function it.reverse(f, s, var)
local ret = { f(s, var) };
var = ret[1];
if var == nil then break; end
- table.insert(results, 1, ret);
+ t_insert(results, 1, ret);
end
-
+
-- Then return our reverse one
local i,max = 0, #results;
return function (results)
@@ -52,15 +57,15 @@ end
-- Given an iterator, iterate only over unique items
function it.unique(f, s, var)
local set = {};
-
+
return function ()
while true do
- local ret = { f(s, var) };
+ local ret = pack(f(s, var));
var = ret[1];
if var == nil then break; end
if not set[var] then
set[var] = true;
- return var;
+ return unpack(ret, 1, ret.n);
end
end
end;
@@ -69,14 +74,13 @@ end
--[[ Return the number of items an iterator returns ]]--
function it.count(f, s, var)
local x = 0;
-
+
while true do
- local ret = { f(s, var) };
- var = ret[1];
+ var = f(s, var);
if var == nil then break; end
x = x + 1;
end
-
+
return x;
end
@@ -104,7 +108,7 @@ end
function it.tail(n, f, s, var)
local results, count = {}, 0;
while true do
- local ret = { f(s, var) };
+ local ret = pack(f(s, var));
var = ret[1];
if var == nil then break; end
results[(count%n)+1] = ret;
@@ -117,9 +121,24 @@ function it.tail(n, f, s, var)
return function ()
pos = pos + 1;
if pos > n then return nil; end
- return unpack(results[((count-1+pos)%n)+1]);
+ local ret = results[((count-1+pos)%n)+1];
+ return unpack(ret, 1, ret.n);
end
- --return reverse(head(n, reverse(f, s, var)));
+ --return reverse(head(n, reverse(f, s, var))); -- !
+end
+
+function it.filter(filter, f, s, var)
+ if type(filter) ~= "function" then
+ local filter_value = filter;
+ function filter(x) return x ~= filter_value; end
+ end
+ return function (s, var)
+ local ret;
+ repeat ret = pack(f(s, var));
+ var = ret[1];
+ until var == nil or filter(unpack(ret, 1, ret.n));
+ return unpack(ret, 1, ret.n);
+ end, s, var;
end
local function _ripairs_iter(t, key) if key > 1 then return key-1, t[key-1]; end end
@@ -139,7 +158,7 @@ function it.to_array(f, s, var)
while true do
var = f(s, var);
if var == nil then break; end
- table.insert(t, var);
+ t_insert(t, var);
end
return t;
end
diff --git a/util/jid.lua b/util/jid.lua
index 8e0a784c..76155ac7 100644
--- a/util/jid.lua
+++ b/util/jid.lua
@@ -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,9 +23,9 @@ local escapes = {
local unescapes = {};
for k,v in pairs(escapes) do unescapes[v] = k; end
-module "jid"
+local _ENV = nil;
-local function _split(jid)
+local function split(jid)
if not jid then return; end
local node, nodepos = match(jid, "^([^@/]+)@()");
local host, hostpos = match(jid, "^([^@/]+)()", nodepos)
@@ -34,18 +34,17 @@ local function _split(jid)
if (not host) or ((not resource) and #jid >= hostpos) then return nil, nil, nil; end
return node, host, resource;
end
-split = _split;
-function bare(jid)
- local node, host = _split(jid);
+local function bare(jid)
+ local node, host = split(jid);
if node and host then
return node.."@"..host;
end
return host;
end
-local function _prepped_split(jid)
- local node, host, resource = _split(jid);
+local function prepped_split(jid)
+ local node, host, resource = split(jid);
if host then
if sub(host, -1, -1) == "." then -- Strip empty root label
host = sub(host, 1, -2);
@@ -63,39 +62,29 @@ local function _prepped_split(jid)
return node, host, resource;
end
end
-prepped_split = _prepped_split;
-
-function prep(jid)
- local node, host, resource = _prepped_split(jid);
- if host then
- if node then
- host = node .. "@" .. host;
- end
- if resource then
- host = host .. "/" .. resource;
- end
- end
- return host;
-end
-function join(node, host, resource)
- if node and host and resource then
+local function join(node, host, resource)
+ if not host then return end
+ if node and resource then
return node.."@"..host.."/"..resource;
- elseif node and host then
+ elseif node then
return node.."@"..host;
- elseif host and resource then
+ elseif resource then
return host.."/"..resource;
- elseif host then
- return host;
end
- return nil; -- Invalid JID
+ return host;
end
-function compare(jid, acl)
+local function prep(jid)
+ local node, host, resource = prepped_split(jid);
+ return join(node, host, resource);
+end
+
+local function compare(jid, acl)
-- compare jid to single acl rule
-- TODO compare to table of rules?
- local jid_node, jid_host, jid_resource = _split(jid);
- local acl_node, acl_host, acl_resource = _split(acl);
+ local jid_node, jid_host, jid_resource = split(jid);
+ local acl_node, acl_host, acl_resource = split(acl);
if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and
((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and
((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then
@@ -104,7 +93,16 @@ function compare(jid, acl)
return false
end
-function escape(s) return s and (s:gsub(".", escapes)); end
-function unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end
+local function escape(s) return s and (s:gsub(".", escapes)); end
+local function unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end
-return _M;
+return {
+ split = split;
+ bare = bare;
+ prepped_split = prepped_split;
+ join = join;
+ prep = prep;
+ compare = compare;
+ escape = escape;
+ unescape = unescape;
+};
diff --git a/util/json.lua b/util/json.lua
index 82ebcc43..becd295d 100644
--- a/util/json.lua
+++ b/util/json.lua
@@ -13,7 +13,7 @@ local tostring, tonumber = tostring, tonumber;
local pairs, ipairs = pairs, ipairs;
local next = next;
local error = error;
-local newproxy, getmetatable, setmetatable = newproxy, getmetatable, setmetatable;
+local getmetatable, setmetatable = getmetatable, setmetatable;
local print = print;
local has_array, array = pcall(require, "util.array");
@@ -22,10 +22,7 @@ local array_mt = has_array and getmetatable(array()) or {};
--module("json")
local json = {};
-local null = newproxy and newproxy(true) or {};
-if getmetatable and getmetatable(null) then
- getmetatable(null).__tostring = function() return "null"; end;
-end
+local null = setmetatable({}, { __tostring = function() return "null"; end; });
json.null = null;
local escapes = {
@@ -348,9 +345,9 @@ local first_escape = {
function json.decode(json)
json = json:gsub("\\.", first_escape) -- get rid of all escapes except \uXXXX, making string parsing much simpler
--:gsub("[\r\n]", "\t"); -- \r\n\t are equivalent, we care about none of them, and none of them can be in strings
-
+
-- TODO do encoding verification
-
+
local val, index = _readvalue(json, 1);
if val == nil then return val, index; end
if json:find("[^ \t\r\n]", index) then return nil, "garbage at eof"; end
diff --git a/util/logger.lua b/util/logger.lua
index 26206d4d..e72b29bc 100644
--- a/util/logger.lua
+++ b/util/logger.lua
@@ -1,23 +1,21 @@
-- 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: ignore 213/level
-local pcall = pcall;
-
-local find = string.find;
-local ipairs, pairs, setmetatable = ipairs, pairs, setmetatable;
+local pairs = pairs;
-module "logger"
+local _ENV = nil;
local level_sinks = {};
local make_logger;
-function init(name)
+local function init(name)
local log_debug = make_logger(name, "debug");
local log_info = make_logger(name, "info");
local log_warn = make_logger(name, "warn");
@@ -52,7 +50,7 @@ function make_logger(source_name, level)
return logger;
end
-function reset()
+local function reset()
for level, handler_list in pairs(level_sinks) do
-- Clear all handlers for this level
for i = 1, #handler_list do
@@ -61,7 +59,7 @@ function reset()
end
end
-function add_level_sink(level, sink_function)
+local function add_level_sink(level, sink_function)
if not level_sinks[level] then
level_sinks[level] = { sink_function };
else
@@ -69,6 +67,10 @@ function add_level_sink(level, sink_function)
end
end
-_M.new = make_logger;
-
-return _M;
+return {
+ init = init;
+ make_logger = make_logger;
+ reset = reset;
+ add_level_sink = add_level_sink;
+ new = make_logger;
+};
diff --git a/util/mercurial.lua b/util/mercurial.lua
new file mode 100644
index 00000000..3f75c4c1
--- /dev/null
+++ b/util/mercurial.lua
@@ -0,0 +1,34 @@
+
+local lfs = require"lfs";
+
+local hg = { };
+
+function hg.check_id(path)
+ if lfs.attributes(path, 'mode') ~= "directory" then
+ return nil, "not a directory";
+ end
+ local hg_dirstate = io.open(path.."/.hg/dirstate");
+ local hgid, hgrepo
+ if hg_dirstate then
+ hgid = ("%02x%02x%02x%02x%02x%02x"):format(hg_dirstate:read(6):byte(1, 6));
+ hg_dirstate:close();
+ local hg_changelog = io.open(path.."/.hg/store/00changelog.i");
+ if hg_changelog then
+ hg_changelog:seek("set", 0x20);
+ hgrepo = ("%02x%02x%02x%02x%02x%02x"):format(hg_changelog:read(6):byte(1, 6));
+ hg_changelog:close();
+ end
+ else
+ local hg_archival,e = io.open(path.."/.hg_archival.txt");
+ if hg_archival then
+ local repo = hg_archival:read("*l");
+ local node = hg_archival:read("*l");
+ hg_archival:close()
+ hgid = node and node:match("^node: (%x%x%x%x%x%x%x%x%x%x%x%x)")
+ hgrepo = repo and repo:match("^repo: (%x%x%x%x%x%x%x%x%x%x%x%x)")
+ end
+ end
+ return hgid, hgrepo;
+end
+
+return hg;
diff --git a/util/multitable.lua b/util/multitable.lua
index dbf34d28..e4321d3d 100644
--- a/util/multitable.lua
+++ b/util/multitable.lua
@@ -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 select = select;
local t_insert = table.insert;
-local unpack, pairs, next, type = unpack, pairs, next, type;
+local pairs, next, type = pairs, next, type;
+local unpack = table.unpack or unpack; --luacheck: ignore 113
-module "multitable"
+local _ENV = nil;
local function get(self, ...)
local t = self.data;
@@ -126,7 +127,7 @@ local function search_add(self, results, ...)
return results;
end
-function iter(self, ...)
+local function iter(self, ...)
local query = { ... };
local maxdepth = select("#", ...);
local stack = { self.data };
@@ -161,7 +162,7 @@ function iter(self, ...)
return it, self;
end
-function new()
+local function new()
return {
data = {};
get = get;
@@ -174,4 +175,7 @@ function new()
};
end
-return _M;
+return {
+ iter = iter;
+ new = new;
+};
diff --git a/util/openssl.lua b/util/openssl.lua
index 39fe99d6..757259f6 100644
--- a/util/openssl.lua
+++ b/util/openssl.lua
@@ -12,7 +12,7 @@ local config = {};
_M.config = config;
local ssl_config = {};
-local ssl_config_mt = {__index=ssl_config};
+local ssl_config_mt = { __index = ssl_config };
function config.new()
return setmetatable({
@@ -65,13 +65,12 @@ function ssl_config:serialize()
s = s .. ("[%s]\n"):format(k);
if k == "subject_alternative_name" then
for san, n in pairs(t) do
- for i = 1,#n do
+ for i = 1, #n do
s = s .. s_format("%s.%d = %s\n", san, i -1, n[i]);
end
end
elseif k == "distinguished_name" then
- for i=1,#DN_order do
- local k = DN_order[i]
+ for i, k in ipairs(t[1] and t or DN_order) do
local v = t[k];
if v then
s = s .. ("%s = %s\n"):format(k, v);
@@ -107,7 +106,7 @@ end
function ssl_config:add_sRVName(host, service)
t_insert(self.subject_alternative_name.otherName,
- s_format("%s;%s", oid_dnssrv, ia5string("_" .. service .."." .. idna_to_ascii(host))));
+ s_format("%s;%s", oid_dnssrv, ia5string("_" .. service .. "." .. idna_to_ascii(host))));
end
function ssl_config:add_xmppAddr(host)
@@ -118,10 +117,10 @@ end
function ssl_config:from_prosody(hosts, config, certhosts)
-- TODO Decide if this should go elsewhere
local found_matching_hosts = false;
- for i = 1,#certhosts do
+ for i = 1, #certhosts do
local certhost = certhosts[i];
for name in pairs(hosts) do
- if name == certhost or name:sub(-1-#certhost) == "."..certhost then
+ if name == certhost or name:sub(-1-#certhost) == "." .. certhost then
found_matching_hosts = true;
self:add_dNSName(name);
--print(name .. "#component_module: " .. (config.get(name, "component_module") or "nil"));
@@ -144,30 +143,30 @@ end
do -- Lua to shell calls.
local function shell_escape(s)
- return s:gsub("'",[['\'']]);
+ return "'" .. tostring(s):gsub("'",[['\'']]) .. "'";
end
- local function serialize(f,o)
- local r = {"openssl", f};
- for k,v in pairs(o) do
+ local function serialize(command, args)
+ local commandline = { "openssl", command };
+ for k, v in pairs(args) do
if type(k) == "string" then
- t_insert(r, ("-%s"):format(k));
+ t_insert(commandline, ("-%s"):format(k));
if v ~= true then
- t_insert(r, ("'%s'"):format(shell_escape(tostring(v))));
+ t_insert(commandline, shell_escape(v));
end
end
end
- for _,v in ipairs(o) do
- t_insert(r, ("'%s'"):format(shell_escape(tostring(v))));
+ for _, v in ipairs(args) do
+ t_insert(commandline, shell_escape(v));
end
- return t_concat(r, " ");
+ return t_concat(commandline, " ");
end
local os_execute = os.execute;
setmetatable(_M, {
- __index=function(_,f)
+ __index = function(_, command)
return function(opts)
- return 0 == os_execute(serialize(f, type(opts) == "table" and opts or {}));
+ return 0 == os_execute(serialize(command, type(opts) == "table" and opts or {}));
end;
end;
});
diff --git a/util/paths.lua b/util/paths.lua
new file mode 100644
index 00000000..89f4cad9
--- /dev/null
+++ b/util/paths.lua
@@ -0,0 +1,44 @@
+local t_concat = table.concat;
+
+local path_sep = package.config:sub(1,1);
+
+local path_util = {}
+
+-- Helper function to resolve relative paths (needed by config)
+function path_util.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
+
+-- Helper function to convert a glob to a Lua pattern
+function path_util.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 path_util.join(...)
+ return t_concat({...}, path_sep);
+end
+
+return path_util;
diff --git a/util/pluginloader.lua b/util/pluginloader.lua
index 112c0d52..004855f0 100644
--- a/util/pluginloader.lua
+++ b/util/pluginloader.lua
@@ -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,9 +17,7 @@ end
local io_open = io.open;
local envload = require "util.envload".envload;
-module "pluginloader"
-
-function load_file(names)
+local function load_file(names)
local file, err, path;
for i=1,#plugin_dir do
for j=1,#names do
@@ -35,7 +33,7 @@ function load_file(names)
return file, err;
end
-function load_resource(plugin, resource)
+local function load_resource(plugin, resource)
resource = resource or "mod_"..plugin..".lua";
local names = {
@@ -48,7 +46,7 @@ function load_resource(plugin, resource)
return load_file(names);
end
-function load_code(plugin, resource, env)
+local function load_code(plugin, resource, env)
local content, err = load_resource(plugin, resource);
if not content then return content, err; end
local path = err;
@@ -57,4 +55,23 @@ function load_code(plugin, resource, env)
return f, path;
end
-return _M;
+local function load_code_ext(plugin, resource, extension, env)
+ local content, err = load_resource(plugin, resource.."."..extension);
+ if not content then
+ content, err = load_resource(resource, resource.."."..extension);
+ if not content then
+ return content, err;
+ end
+ end
+ local path = err;
+ local f, err = envload(content, "@"..path, env);
+ if not f then return f, err; end
+ return f, path;
+end
+
+return {
+ load_file = load_file;
+ load_resource = load_resource;
+ load_code = load_code;
+ load_code_ext = load_code_ext;
+};
diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua
index c6fe1986..f8f28644 100644
--- a/util/prosodyctl.lua
+++ b/util/prosodyctl.lua
@@ -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,25 +29,19 @@ local CFG_SOURCEDIR = _G.CFG_SOURCEDIR;
local _G = _G;
local prosody = prosody;
-module "prosodyctl"
-
-- UI helpers
-function show_message(msg, ...)
- print(msg:format(...));
-end
-
-function show_warning(msg, ...)
+local function show_message(msg, ...)
print(msg:format(...));
end
-function show_usage(usage, desc)
+local function show_usage(usage, desc)
print("Usage: ".._G.arg[0].." "..usage);
if desc then
print(" "..desc);
end
end
-function getchar(n)
+local function getchar(n)
local stty_ret = os.execute("stty raw -echo 2>/dev/null");
local ok, char;
if stty_ret == 0 then
@@ -64,14 +58,14 @@ function getchar(n)
end
end
-function getline()
+local function getline()
local ok, line = pcall(io.read, "*l");
if ok then
return line;
end
end
-function getpass()
+local function getpass()
local stty_ret = os.execute("stty -echo 2>/dev/null");
if stty_ret ~= 0 then
io.write("\027[08m"); -- ANSI 'hidden' text attribute
@@ -88,7 +82,7 @@ function getpass()
end
end
-function show_yesno(prompt)
+local function show_yesno(prompt)
io.write(prompt, " ");
local choice = getchar():lower();
io.write("\n");
@@ -99,7 +93,7 @@ function show_yesno(prompt)
return (choice == "y");
end
-function read_password()
+local function read_password()
local password;
while true do
io.write("Enter new password: ");
@@ -120,7 +114,7 @@ function read_password()
return password;
end
-function show_prompt(prompt)
+local function show_prompt(prompt)
io.write(prompt, " ");
local line = getline();
line = line and line:gsub("\n$","");
@@ -128,7 +122,7 @@ function show_prompt(prompt)
end
-- Server control
-function adduser(params)
+local function adduser(params)
local user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
if not user then
return false, "invalid-username";
@@ -146,15 +140,15 @@ function adduser(params)
if not(provider) or provider.name == "null" then
usermanager.initialize_host(host);
end
-
+
local ok, errmsg = usermanager.create_user(user, password, host);
if not ok then
- return false, errmsg;
+ return false, errmsg or "creating-user-failed";
end
return true;
end
-function user_exists(params)
+local function user_exists(params)
local user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
storagemanager.initialize_host(host);
@@ -162,28 +156,28 @@ function user_exists(params)
if not(provider) or provider.name == "null" then
usermanager.initialize_host(host);
end
-
+
return usermanager.user_exists(user, host);
end
-function passwd(params)
- if not _M.user_exists(params) then
+local function passwd(params)
+ if not user_exists(params) then
return false, "no-such-user";
end
-
- return _M.adduser(params);
+
+ return adduser(params);
end
-function deluser(params)
- if not _M.user_exists(params) then
+local function deluser(params)
+ if not user_exists(params) then
return false, "no-such-user";
end
local user, host = nodeprep(params.user), nameprep(params.host);
-
+
return usermanager.delete_user(user, host);
end
-function getpid()
+local function getpid()
local pidfile = config.get("*", "pidfile");
if not pidfile then
return false, "no-pidfile";
@@ -192,35 +186,35 @@ function getpid()
if type(pidfile) ~= "string" then
return false, "invalid-pidfile";
end
-
- local modules_enabled = set.new(config.get("*", "modules_enabled"));
- if not modules_enabled:contains("posix") then
+
+ local modules_enabled = set.new(config.get("*", "modules_disabled"));
+ if prosody.platform ~= "posix" or modules_enabled:contains("posix") then
return false, "no-posix";
end
-
+
local file, err = io.open(pidfile, "r+");
if not file then
return false, "pidfile-read-failed", err;
end
-
+
local locked, err = lfs.lock(file, "w");
if locked then
file:close();
return false, "pidfile-not-locked";
end
-
+
local pid = tonumber(file:read("*a"));
file:close();
-
+
if not pid then
return false, "invalid-pid";
end
-
+
return true, pid;
end
-function isrunning()
- local ok, pid, err = _M.getpid();
+local function isrunning()
+ local ok, pid, err = getpid();
if not ok then
if pid == "pidfile-read-failed" or pid == "pidfile-not-locked" then
-- Report as not running, since we can't open the pidfile
@@ -232,8 +226,8 @@ function isrunning()
return true, signal.kill(pid, 0) == 0;
end
-function start()
- local ok, ret = _M.isrunning();
+local function start()
+ local ok, ret = isrunning();
if not ok then
return ok, ret;
end
@@ -248,36 +242,55 @@ function start()
return true;
end
-function stop()
- local ok, ret = _M.isrunning();
+local function stop()
+ local ok, ret = isrunning();
if not ok then
return ok, ret;
end
if not ret then
return false, "not-running";
end
-
- local ok, pid = _M.getpid()
+
+ local ok, pid = getpid()
if not ok then return false, pid; end
-
+
signal.kill(pid, signal.SIGTERM);
return true;
end
-function reload()
- local ok, ret = _M.isrunning();
+local function reload()
+ local ok, ret = isrunning();
if not ok then
return ok, ret;
end
if not ret then
return false, "not-running";
end
-
- local ok, pid = _M.getpid()
+
+ local ok, pid = getpid()
if not ok then return false, pid; end
-
+
signal.kill(pid, signal.SIGHUP);
return true;
end
-return _M;
+return {
+ show_message = show_message;
+ show_warning = show_message;
+ show_usage = show_usage;
+ getchar = getchar;
+ getline = getline;
+ getpass = getpass;
+ show_yesno = show_yesno;
+ read_password = read_password;
+ show_prompt = show_prompt;
+ adduser = adduser;
+ user_exists = user_exists;
+ passwd = passwd;
+ deluser = deluser;
+ getpid = getpid;
+ isrunning = isrunning;
+ start = start;
+ stop = stop;
+ reload = reload;
+};
diff --git a/util/pubsub.lua b/util/pubsub.lua
index e1418c62..6d12690a 100644
--- a/util/pubsub.lua
+++ b/util/pubsub.lua
@@ -1,23 +1,27 @@
local events = require "util.events";
-
-module("pubsub", package.seeall);
+local t_remove = table.remove;
local service = {};
local service_mt = { __index = service };
-local default_config = {
+local default_config = { __index = {
broadcaster = function () end;
get_affiliation = function () end;
capabilities = {};
-};
+} };
+local default_node_config = { __index = {
+ ["pubsub#max_items"] = "20";
+} };
-function new(config)
+local function new(config)
config = config or {};
return setmetatable({
- config = setmetatable(config, { __index = default_config });
+ config = setmetatable(config, default_config);
+ node_defaults = setmetatable(config.node_defaults or {}, default_node_config);
affiliations = {};
subscriptions = {};
nodes = {};
+ data = {};
events = events.new();
}, service_mt);
end
@@ -29,13 +33,13 @@ end
function service:may(node, actor, action)
if actor == true then return true; end
-
+
local node_obj = self.nodes[node];
local node_aff = node_obj and node_obj.affiliations[actor];
local service_aff = self.affiliations[actor]
or self.config.get_affiliation(actor, node, action)
or "none";
-
+
-- Check if node allows/forbids it
local node_capabilities = node_obj and node_obj.capabilities;
if node_capabilities then
@@ -47,7 +51,7 @@ function service:may(node, actor, action)
end
end
end
-
+
-- Check service-wide capabilities instead
local service_capabilities = self.config.capabilities;
local caps = service_capabilities[node_aff or service_aff];
@@ -57,7 +61,7 @@ function service:may(node, actor, action)
return can;
end
end
-
+
return false;
end
@@ -202,7 +206,7 @@ function service:get_subscription(node, actor, jid)
return true, node_obj.subscribers[jid];
end
-function service:create(node, actor)
+function service:create(node, actor, options)
-- Access checking
if not self:may(node, actor, "create") then
return false, "forbidden";
@@ -211,17 +215,20 @@ function service:create(node, actor)
if self.nodes[node] then
return false, "conflict";
end
-
+
+ self.data[node] = {};
self.nodes[node] = {
name = node;
subscribers = {};
- config = {};
- data = {};
+ config = setmetatable(options or {}, {__index=self.node_defaults});
affiliations = {};
};
+ setmetatable(self.nodes[node], { __index = { data = self.data[node] } }); -- COMPAT
+ self.events.fire_event("node-created", { node = node, actor = actor });
local ok, err = self:set_affiliation(node, true, actor, "owner");
if not ok then
self.nodes[node] = nil;
+ self.data[node] = nil;
end
return ok, err;
end
@@ -237,10 +244,31 @@ function service:delete(node, actor)
return false, "item-not-found";
end
self.nodes[node] = nil;
+ self.data[node] = nil;
+ self.events.fire_event("node-deleted", { node = node, actor = actor });
self.config.broadcaster("delete", node, node_obj.subscribers);
return true;
end
+local function remove_item_by_id(data, id)
+ if not data[id] then return end
+ data[id] = nil;
+ for i, _id in ipairs(data) do
+ if id == _id then
+ t_remove(data, i);
+ return i;
+ end
+ end
+end
+
+local function trim_items(data, max)
+ max = tonumber(max);
+ if not max or #data <= max then return end
+ repeat
+ data[t_remove(data, 1)] = nil;
+ until #data <= max
+end
+
function service:publish(node, actor, id, item)
-- Access checking
if not self:may(node, actor, "publish") then
@@ -258,9 +286,13 @@ function service:publish(node, actor, id, item)
end
node_obj = self.nodes[node];
end
- node_obj.data[id] = item;
+ local node_data = self.data[node];
+ remove_item_by_id(node_data, id);
+ node_data[#node_data + 1] = id;
+ node_data[id] = item;
+ trim_items(node_data, node_obj.config["pubsub#max_items"]);
self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item });
- self.config.broadcaster("items", node, node_obj.subscribers, item);
+ self.config.broadcaster("items", node, node_obj.subscribers, item, actor);
return true;
end
@@ -271,10 +303,11 @@ function service:retract(node, actor, id, retract)
end
--
local node_obj = self.nodes[node];
- if (not node_obj) or (not node_obj.data[id]) then
+ if (not node_obj) or (not self.data[node][id]) then
return false, "item-not-found";
end
- node_obj.data[id] = nil;
+ self.events.fire_event("item-retracted", { node = node, actor = actor, id = id });
+ remove_item_by_id(self.data[node], id);
if retract then
self.config.broadcaster("items", node, node_obj.subscribers, retract);
end
@@ -291,7 +324,8 @@ function service:purge(node, actor, notify)
if not node_obj then
return false, "item-not-found";
end
- node_obj.data = {}; -- Purge
+ self.data[node] = {}; -- Purge
+ self.events.fire_event("node-purged", { node = node, actor = actor });
if notify then
self.config.broadcaster("purge", node, node_obj.subscribers);
end
@@ -309,9 +343,9 @@ function service:get_items(node, actor, id)
return false, "item-not-found";
end
if id then -- Restrict results to a single specific item
- return true, { [id] = node_obj.data[id] };
+ return true, { id, [id] = self.data[node][id] };
else
- return true, node_obj.data;
+ return true, self.data[node];
end
end
@@ -388,4 +422,24 @@ function service:set_node_capabilities(node, actor, capabilities)
return true;
end
-return _M;
+function service:set_node_config(node, actor, new_config)
+ if not self:may(node, actor, "configure") then
+ return false, "forbidden";
+ end
+
+ local node_obj = self.nodes[node];
+ if not node_obj then
+ return false, "item-not-found";
+ end
+
+ for k,v in pairs(new_config) do
+ node_obj.config[k] = v;
+ end
+ trim_items(self.data[node], node_obj.config["pubsub#max_items"]);
+
+ return true;
+end
+
+return {
+ new = new;
+};
diff --git a/util/queue.lua b/util/queue.lua
new file mode 100644
index 00000000..728e905f
--- /dev/null
+++ b/util/queue.lua
@@ -0,0 +1,73 @@
+-- Prosody IM
+-- Copyright (C) 2008-2015 Matthew Wild
+-- Copyright (C) 2008-2015 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+-- Small ringbuffer library (i.e. an efficient FIFO queue with a size limit)
+-- (because unbounded dynamically-growing queues are a bad thing...)
+
+local have_utable, utable = pcall(require, "util.table"); -- For pre-allocation of table
+
+local function new(size, allow_wrapping)
+ -- Head is next insert, tail is next read
+ local head, tail = 1, 1;
+ local items = 0; -- Number of stored items
+ local t = have_utable and utable.create(size, 0) or {}; -- Table to hold items
+ --luacheck: ignore 212/self
+ return {
+ _items = t;
+ size = size;
+ count = function (self) return items; end;
+ push = function (self, item)
+ if items >= size then
+ if allow_wrapping then
+ tail = (tail%size)+1; -- Advance to next oldest item
+ items = items - 1;
+ else
+ return nil, "queue full";
+ end
+ end
+ t[head] = item;
+ items = items + 1;
+ head = (head%size)+1;
+ return true;
+ end;
+ pop = function (self)
+ if items == 0 then
+ return nil;
+ end
+ local item;
+ item, t[tail] = t[tail], 0;
+ tail = (tail%size)+1;
+ items = items - 1;
+ return item;
+ end;
+ peek = function (self)
+ if items == 0 then
+ return nil;
+ end
+ return t[tail];
+ end;
+ items = function (self)
+ --luacheck: ignore 431/t
+ return function (t, pos)
+ if pos >= t:count() then
+ return nil;
+ end
+ local read_pos = tail + pos;
+ if read_pos > t.size then
+ read_pos = (read_pos%size);
+ end
+ return pos+1, t._items[read_pos];
+ end, self, 0;
+ end;
+ };
+end
+
+return {
+ new = new;
+};
+
diff --git a/util/random.lua b/util/random.lua
new file mode 100644
index 00000000..574e2e1c
--- /dev/null
+++ b/util/random.lua
@@ -0,0 +1,30 @@
+-- Prosody IM
+-- Copyright (C) 2008-2014 Matthew Wild
+-- Copyright (C) 2008-2014 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local ok, crand = pcall(require, "util.crand");
+if ok then return crand; end
+
+local urandom, urandom_err = io.open("/dev/urandom", "r");
+
+local function seed()
+end
+
+local function bytes(n)
+ return urandom:read(n);
+end
+
+if not urandom then
+ function bytes()
+ error("Unable to obtain a secure random number generator, please see https://prosody.im/doc/random ("..urandom_err..")");
+ end
+end
+
+return {
+ seed = seed;
+ bytes = bytes;
+};
diff --git a/util/sasl.lua b/util/sasl.lua
index afb3861b..5845f34a 100644
--- a/util/sasl.lua
+++ b/util/sasl.lua
@@ -19,7 +19,7 @@ local setmetatable = setmetatable;
local assert = assert;
local require = require;
-module "sasl"
+local _ENV = nil;
--[[
Authentication Backend Prototypes:
@@ -27,19 +27,38 @@ Authentication Backend Prototypes:
state = false : disabled
state = true : enabled
state = nil : non-existant
+
+Channel Binding:
+
+To enable support of channel binding in some mechanisms you need to provide appropriate callbacks in a table
+at profile.cb.
+
+Example:
+ profile.cb["tls-unique"] = function(self)
+ return self.user
+ end
+
]]
local method = {};
method.__index = method;
local mechanisms = {};
local backend_mechanism = {};
+local mechanism_channelbindings = {};
-- register a new SASL mechanims
-function registerMechanism(name, backends, f)
+local function registerMechanism(name, backends, f, cb_backends)
assert(type(name) == "string", "Parameter name MUST be a string.");
assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table.");
assert(type(f) == "function", "Parameter f MUST be a function.");
+ if cb_backends then assert(type(cb_backends) == "table"); end
mechanisms[name] = f
+ if cb_backends then
+ mechanism_channelbindings[name] = {};
+ for _, cb_name in ipairs(cb_backends) do
+ mechanism_channelbindings[name][cb_name] = true;
+ end
+ end
for _, backend_name in ipairs(backends) do
if backend_mechanism[backend_name] == nil then backend_mechanism[backend_name] = {}; end
t_insert(backend_mechanism[backend_name], name);
@@ -47,7 +66,7 @@ function registerMechanism(name, backends, f)
end
-- create a new SASL object which can be used to authenticate clients
-function new(realm, profile)
+local function new(realm, profile)
local mechanisms = profile.mechanisms;
if not mechanisms then
mechanisms = {};
@@ -63,6 +82,15 @@ function new(realm, profile)
return setmetatable({ profile = profile, realm = realm, mechs = mechanisms }, method);
end
+-- add a channel binding handler
+function method:add_cb_handler(name, f)
+ if type(self.profile.cb) ~= "table" then
+ self.profile.cb = {};
+ end
+ self.profile.cb[name] = f;
+ return self;
+end
+
-- get a fresh clone with the same realm and profile
function method:clean_clone()
return new(self.realm, self.profile)
@@ -70,7 +98,23 @@ end
-- get a list of possible SASL mechanims to use
function method:mechanisms()
- return self.mechs;
+ local current_mechs = {};
+ for mech, _ in pairs(self.mechs) do
+ if mechanism_channelbindings[mech] then
+ if self.profile.cb then
+ local ok = false;
+ for cb_name, _ in pairs(self.profile.cb) do
+ if mechanism_channelbindings[mech][cb_name] then
+ ok = true;
+ end
+ end
+ if ok == true then current_mechs[mech] = true; end
+ end
+ else
+ current_mechs[mech] = true;
+ end
+ end
+ return current_mechs;
end
-- select a mechanism to use
@@ -92,5 +136,9 @@ require "util.sasl.plain" .init(registerMechanism);
require "util.sasl.digest-md5".init(registerMechanism);
require "util.sasl.anonymous" .init(registerMechanism);
require "util.sasl.scram" .init(registerMechanism);
+require "util.sasl.external" .init(registerMechanism);
-return _M;
+return {
+ registerMechanism = registerMechanism;
+ new = new;
+};
diff --git a/util/sasl/anonymous.lua b/util/sasl/anonymous.lua
index ca5fe404..af05c0e7 100644
--- a/util/sasl/anonymous.lua
+++ b/util/sasl/anonymous.lua
@@ -16,7 +16,7 @@ local s_match = string.match;
local log = require "util.logger".init("sasl");
local generate_uuid = require "util.uuid".generate;
-module "sasl.anonymous"
+local _ENV = nil;
--=========================
--SASL ANONYMOUS according to RFC 4505
@@ -39,8 +39,10 @@ local function anonymous(self, message)
return "success"
end
-function init(registerMechanism)
+local function init(registerMechanism)
registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);
end
-return _M;
+return {
+ init = init;
+}
diff --git a/util/sasl/digest-md5.lua b/util/sasl/digest-md5.lua
index 591d8537..695dd2a3 100644
--- a/util/sasl/digest-md5.lua
+++ b/util/sasl/digest-md5.lua
@@ -25,7 +25,7 @@ local log = require "util.logger".init("sasl");
local generate_uuid = require "util.uuid".generate;
local nodeprep = require "util.encodings".stringprep.nodeprep;
-module "sasl.digest-md5"
+local _ENV = nil;
--=========================
--SASL DIGEST-MD5 according to RFC 2831
@@ -241,8 +241,10 @@ local function digest(self, message)
end
end
-function init(registerMechanism)
+local function init(registerMechanism)
registerMechanism("DIGEST-MD5", {"plain"}, digest);
end
-return _M;
+return {
+ init = init;
+}
diff --git a/util/sasl/external.lua b/util/sasl/external.lua
new file mode 100644
index 00000000..5ba90190
--- /dev/null
+++ b/util/sasl/external.lua
@@ -0,0 +1,27 @@
+local saslprep = require "util.encodings".stringprep.saslprep;
+
+local _ENV = nil;
+
+local function external(self, message)
+ message = saslprep(message);
+ local state
+ self.username, state = self.profile.external(message);
+
+ if state == false then
+ return "failure", "account-disabled";
+ elseif state == nil then
+ return "failure", "not-authorized";
+ elseif state == "expired" then
+ return "false", "credentials-expired";
+ end
+
+ return "success";
+end
+
+local function init(registerMechanism)
+ registerMechanism("EXTERNAL", {"external"}, external);
+end
+
+return {
+ init = init;
+}
diff --git a/util/sasl/plain.lua b/util/sasl/plain.lua
index c9ec2911..26e65335 100644
--- a/util/sasl/plain.lua
+++ b/util/sasl/plain.lua
@@ -16,7 +16,7 @@ local saslprep = require "util.encodings".stringprep.saslprep;
local nodeprep = require "util.encodings".stringprep.nodeprep;
local log = require "util.logger".init("sasl");
-module "sasl.plain"
+local _ENV = nil;
-- ================================
-- SASL PLAIN according to RFC 4616
@@ -82,8 +82,10 @@ local function plain(self, message)
return "success";
end
-function init(registerMechanism)
+local function init(registerMechanism)
registerMechanism("PLAIN", {"plain", "plain_test"}, plain);
end
-return _M;
+return {
+ init = init;
+}
diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua
index cf2f0ede..a1b5117a 100644
--- a/util/sasl/scram.lua
+++ b/util/sasl/scram.lua
@@ -13,7 +13,6 @@
local s_match = string.match;
local type = type
-local string = string
local base64 = require "util.encodings".base64;
local hmac_sha1 = require "util.hashes".hmac_sha1;
local sha1 = require "util.hashes".sha1;
@@ -26,7 +25,7 @@ local t_concat = table.concat;
local char = string.char;
local byte = string.byte;
-module "sasl.scram"
+local _ENV = nil;
--=========================
--SASL SCRAM-SHA-1 according to RFC 5802
@@ -39,18 +38,14 @@ scram_{MECH}:
function(username, realm)
return stored_key, server_key, iteration_count, salt, state;
end
+
+Supported Channel Binding Backends
+
+'tls-unique' according to RFC 5929
]]
local default_i = 4096
-local function bp( b )
- local result = ""
- for i=1, b:len() do
- result = result.."\\"..b:byte(i)
- end
- return result
-end
-
local xor_map = {0;1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;1;0;3;2;5;4;7;6;9;8;11;10;13;12;15;14;2;3;0;1;6;7;4;5;10;11;8;9;14;15;12;13;3;2;1;0;7;6;5;4;11;10;9;8;15;14;13;12;4;5;6;7;0;1;2;3;12;13;14;15;8;9;10;11;5;4;7;6;1;0;3;2;13;12;15;14;9;8;11;10;6;7;4;5;2;3;0;1;14;15;12;13;10;11;8;9;7;6;5;4;3;2;1;0;15;14;13;12;11;10;9;8;8;9;10;11;12;13;14;15;0;1;2;3;4;5;6;7;9;8;11;10;13;12;15;14;1;0;3;2;5;4;7;6;10;11;8;9;14;15;12;13;2;3;0;1;6;7;4;5;11;10;9;8;15;14;13;12;3;2;1;0;7;6;5;4;12;13;14;15;8;9;10;11;4;5;6;7;0;1;2;3;13;12;15;14;9;8;11;10;5;4;7;6;1;0;3;2;14;15;12;13;10;11;8;9;6;7;4;5;2;3;0;1;15;14;13;12;11;10;9;8;7;6;5;4;3;2;1;0;};
local result = {};
@@ -73,11 +68,11 @@ local function validate_username(username, _nodeprep)
return false
end
end
-
+
-- replace =2C with , and =3D with =
username = username:gsub("=2C", ",");
username = username:gsub("=3D", "=");
-
+
-- apply SASLprep
username = saslprep(username);
@@ -92,7 +87,7 @@ local function hashprep(hashname)
return hashname:lower():gsub("-", "_");
end
-function getAuthenticationDatabaseSHA1(password, salt, iteration_count)
+local function getAuthenticationDatabaseSHA1(password, salt, iteration_count)
if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then
return false, "inappropriate argument types"
end
@@ -106,96 +101,131 @@ function getAuthenticationDatabaseSHA1(password, salt, iteration_count)
end
local function scram_gen(hash_name, H_f, HMAC_f)
+ local profile_name = "scram_" .. hashprep(hash_name);
local function scram_hash(self, message)
- if not self.state then self["state"] = {} end
-
+ local support_channel_binding = false;
+ if self.profile.cb then support_channel_binding = true; end
+
if type(message) ~= "string" or #message == 0 then return "failure", "malformed-request" end
- if not self.state.name then
+ local state = self.state;
+ if not state then
-- we are processing client_first_message
local client_first_message = message;
-
+
-- TODO: fail if authzid is provided, since we don't support them yet
- self.state["client_first_message"] = client_first_message;
- self.state["gs2_cbind_flag"], self.state["authzid"], self.state["name"], self.state["clientnonce"]
- = client_first_message:match("^(%a),(.*),n=(.*),r=([^,]*).*");
+ local gs2_header, gs2_cbind_flag, gs2_cbind_name, authzid, client_first_message_bare, username, clientnonce
+ = s_match(client_first_message, "^(([pny])=?([^,]*),([^,]*),)(m?=?[^,]*,?n=([^,]*),r=([^,]*),?.*)$");
- -- we don't do any channel binding yet
- if self.state.gs2_cbind_flag ~= "n" and self.state.gs2_cbind_flag ~= "y" then
+ if not gs2_cbind_flag then
return "failure", "malformed-request";
end
- if not self.state.name or not self.state.clientnonce then
- return "failure", "malformed-request", "Channel binding isn't support at this time.";
+ if support_channel_binding and gs2_cbind_flag == "y" then
+ -- "y" -> client does support channel binding
+ -- but thinks the server does not.
+ return "failure", "malformed-request";
+ end
+
+ if gs2_cbind_flag == "n" then
+ -- "n" -> client doesn't support channel binding.
+ support_channel_binding = false;
end
-
- self.state.name = validate_username(self.state.name, self.profile.nodeprep);
- if not self.state.name then
+
+ if support_channel_binding and gs2_cbind_flag == "p" then
+ -- check whether we support the proposed channel binding type
+ if not self.profile.cb[gs2_cbind_name] then
+ return "failure", "malformed-request", "Proposed channel binding type isn't supported.";
+ end
+ else
+ -- no channel binding,
+ gs2_cbind_name = nil;
+ end
+
+ username = validate_username(username, self.profile.nodeprep);
+ if not username then
log("debug", "Username violates either SASLprep or contains forbidden character sequences.")
return "failure", "malformed-request", "Invalid username.";
end
-
- self.state["servernonce"] = generate_uuid();
-
+
-- retreive credentials
+ local stored_key, server_key, salt, iteration_count;
if self.profile.plain then
- local password, state = self.profile.plain(self, self.state.name, self.realm)
+ local password, state = self.profile.plain(self, username, self.realm)
if state == nil then return "failure", "not-authorized"
elseif state == false then return "failure", "account-disabled" end
-
+
password = saslprep(password);
if not password then
log("debug", "Password violates SASLprep.");
return "failure", "not-authorized", "Invalid password."
end
- self.state.salt = generate_uuid();
- self.state.iteration_count = default_i;
+ salt = generate_uuid();
+ iteration_count = default_i;
local succ = false;
- succ, self.state.stored_key, self.state.server_key = getAuthenticationDatabaseSHA1(password, self.state.salt, default_i, self.state.iteration_count);
+ succ, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count);
if not succ then
- log("error", "Generating authentication database failed. Reason: %s", self.state.stored_key);
+ log("error", "Generating authentication database failed. Reason: %s", stored_key);
return "failure", "temporary-auth-failure";
end
- elseif self.profile["scram_"..hashprep(hash_name)] then
- local stored_key, server_key, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self, self.state.name, self.realm);
+ elseif self.profile[profile_name] then
+ local state;
+ stored_key, server_key, iteration_count, salt, state = self.profile[profile_name](self, username, self.realm);
if state == nil then return "failure", "not-authorized"
elseif state == false then return "failure", "account-disabled" end
-
- self.state.stored_key = stored_key;
- self.state.server_key = server_key;
- self.state.iteration_count = iteration_count;
- self.state.salt = salt
end
-
- local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..self.state.iteration_count;
- self.state["server_first_message"] = server_first_message;
+
+ local nonce = clientnonce .. generate_uuid();
+ local server_first_message = "r="..nonce..",s="..base64.encode(salt)..",i="..iteration_count;
+ self.state = {
+ gs2_header = gs2_header;
+ gs2_cbind_name = gs2_cbind_name;
+ username = username;
+ nonce = nonce;
+
+ server_key = server_key;
+ stored_key = stored_key;
+ client_first_message_bare = client_first_message_bare;
+ server_first_message = server_first_message;
+ }
return "challenge", server_first_message
else
-- we are processing client_final_message
local client_final_message = message;
-
- self.state["channelbinding"], self.state["nonce"], self.state["proof"] = client_final_message:match("^c=(.*),r=(.*),.*p=(.*)");
-
- if not self.state.proof or not self.state.nonce or not self.state.channelbinding then
+
+ local client_final_message_without_proof, channelbinding, nonce, proof
+ = s_match(client_final_message, "(c=([^,]*),r=([^,]*),?.-),p=(.*)$");
+
+ if not proof or not nonce or not channelbinding then
return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message.";
end
- if self.state.nonce ~= self.state.clientnonce..self.state.servernonce then
+ local client_gs2_header = base64.decode(channelbinding)
+ local our_client_gs2_header = state["gs2_header"]
+ if state.gs2_cbind_name then
+ -- we support channelbinding, so check if the value is valid
+ our_client_gs2_header = our_client_gs2_header .. self.profile.cb[state.gs2_cbind_name](self);
+ end
+ if client_gs2_header ~= our_client_gs2_header then
+ return "failure", "malformed-request", "Invalid channel binding value.";
+ end
+
+ if nonce ~= state.nonce then
return "failure", "malformed-request", "Wrong nonce in client-final-message.";
end
-
- local ServerKey = self.state.server_key;
- local StoredKey = self.state.stored_key;
-
- local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
+
+ local ServerKey = state.server_key;
+ local StoredKey = state.stored_key;
+
+ local AuthMessage = state.client_first_message_bare .. "," .. state.server_first_message .. "," .. client_final_message_without_proof
local ClientSignature = HMAC_f(StoredKey, AuthMessage)
- local ClientKey = binaryXOR(ClientSignature, base64.decode(self.state.proof))
+ local ClientKey = binaryXOR(ClientSignature, base64.decode(proof))
local ServerSignature = HMAC_f(ServerKey, AuthMessage)
if StoredKey == H_f(ClientKey) then
local server_final_message = "v="..base64.encode(ServerSignature);
- self["username"] = self.state.name;
+ self["username"] = state.username;
return "success", server_final_message;
else
return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated.";
@@ -205,12 +235,18 @@ local function scram_gen(hash_name, H_f, HMAC_f)
return scram_hash;
end
-function init(registerMechanism)
+local function init(registerMechanism)
local function registerSCRAMMechanism(hash_name, hash, hmac_hash)
registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash));
+
+ -- register channel binding equivalent
+ registerMechanism("SCRAM-"..hash_name.."-PLUS", {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash), {"tls-unique"});
end
registerSCRAMMechanism("SHA-1", sha1, hmac_sha1);
end
-return _M;
+return {
+ getAuthenticationDatabaseSHA1 = getAuthenticationDatabaseSHA1;
+ init = init;
+}
diff --git a/util/sasl_cyrus.lua b/util/sasl_cyrus.lua
index 19684587..4e9a4af5 100644
--- a/util/sasl_cyrus.lua
+++ b/util/sasl_cyrus.lua
@@ -60,7 +60,7 @@ local sasl_errstring = {
};
setmetatable(sasl_errstring, { __index = function() return "undefined error!" end });
-module "sasl_cyrus"
+local _ENV = nil;
local method = {};
method.__index = method;
@@ -78,11 +78,11 @@ local function init(service_name)
end
-- create a new SASL object which can be used to authenticate clients
--- host_fqdn may be nil in which case gethostname() gives the value.
+-- host_fqdn may be nil in which case gethostname() gives the value.
-- For GSSAPI, this determines the hostname in the service ticket (after
-- reverse DNS canonicalization, only if [libdefaults] rdns = true which
--- is the default).
-function new(realm, service_name, app_name, host_fqdn)
+-- is the default).
+local function new(realm, service_name, app_name, host_fqdn)
init(app_name or service_name);
@@ -163,4 +163,6 @@ function method:process(message)
end
end
-return _M;
+return {
+ new = new;
+};
diff --git a/util/serialization.lua b/util/serialization.lua
index 8a259184..206f5fbb 100644
--- a/util/serialization.lua
+++ b/util/serialization.lua
@@ -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.
--
@@ -11,18 +11,16 @@ local type = type;
local tostring = tostring;
local t_insert = table.insert;
local t_concat = table.concat;
-local error = error;
local pairs = pairs;
local next = next;
-local loadstring = loadstring;
local pcall = pcall;
local debug_traceback = debug.traceback;
local log = require "util.logger".init("serialization");
local envload = require"util.envload".envload;
-module "serialization"
+local _ENV = nil;
local indent = function(i)
return string_rep("\t", i);
@@ -73,16 +71,16 @@ local function _simplesave(o, ind, t, func)
end
end
-function append(t, o)
+local function append(t, o)
_simplesave(o, 1, t, t.write or t_insert);
return t;
end
-function serialize(o)
+local function serialize(o)
return t_concat(append({}, o));
end
-function deserialize(str)
+local function deserialize(str)
if type(str) ~= "string" then return nil; end
str = "return "..str;
local f, err = envload(str, "@data", {});
@@ -92,4 +90,8 @@ function deserialize(str)
return ret;
end
-return _M;
+return {
+ append = append;
+ serialize = serialize;
+ deserialize = deserialize;
+};
diff --git a/util/session.lua b/util/session.lua
new file mode 100644
index 00000000..b2a726ce
--- /dev/null
+++ b/util/session.lua
@@ -0,0 +1,65 @@
+local initialize_filters = require "util.filters".initialize;
+local logger = require "util.logger";
+
+local function new_session(typ)
+ local session = {
+ type = typ .. "_unauthed";
+ };
+ return session;
+end
+
+local function set_id(session)
+ local id = session.type .. tostring(session):match("%x+$"):lower();
+ session.id = id;
+ return session;
+end
+
+local function set_logger(session)
+ local log = logger.init(session.id);
+ session.log = log;
+ return session;
+end
+
+local function set_conn(session, conn)
+ session.conn = conn;
+ session.ip = conn:ip();
+ return session;
+end
+
+local function set_send(session)
+ local conn = session.conn;
+ if not conn then
+ function session.send(data)
+ session.log("debug", "Discarding data sent to unconnected session: %s", tostring(data));
+ return false;
+ end
+ return session;
+ end
+ local filter = initialize_filters(session);
+ local w = conn.write;
+ session.send = function (t)
+ if t.name then
+ t = filter("stanzas/out", t);
+ end
+ if t then
+ t = filter("bytes/out", tostring(t));
+ if t then
+ local ret, err = w(conn, t);
+ if not ret then
+ session.log("debug", "Error writing to connection: %s", tostring(err));
+ return false, err;
+ end
+ end
+ end
+ return true;
+ end
+ return session;
+end
+
+return {
+ new = new_session;
+ set_id = set_id;
+ set_logger = set_logger;
+ set_conn = set_conn;
+ set_send = set_send;
+}
diff --git a/util/set.lua b/util/set.lua
index fa065a9c..c136a522 100644
--- a/util/set.lua
+++ b/util/set.lua
@@ -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,86 +10,49 @@ local ipairs, pairs, setmetatable, next, tostring =
ipairs, pairs, setmetatable, next, tostring;
local t_concat = table.concat;
-module "set"
+local _ENV = nil;
local set_mt = {};
function set_mt.__call(set, _, k)
return next(set._items, k);
end
-function set_mt.__add(set1, set2)
- return _M.union(set1, set2);
-end
-function set_mt.__sub(set1, set2)
- return _M.difference(set1, set2);
-end
-function set_mt.__div(set, func)
- local new_set = _M.new();
- local items, new_items = set._items, new_set._items;
- for item in pairs(items) do
- local new_item = func(item);
- if new_item ~= nil then
- new_items[new_item] = true;
- end
- end
- return new_set;
-end
-function set_mt.__eq(set1, set2)
- local set1, set2 = set1._items, set2._items;
- for item in pairs(set1) do
- if not set2[item] then
- return false;
- end
- end
-
- for item in pairs(set2) do
- if not set1[item] then
- return false;
- end
- end
-
- return true;
-end
-function set_mt.__tostring(set)
- local s, items = { }, set._items;
- for item in pairs(items) do
- s[#s+1] = tostring(item);
- end
- return t_concat(s, ", ");
-end
local items_mt = {};
function items_mt.__call(items, _, k)
return next(items, k);
end
-function new(list)
+local function new(list)
local items = setmetatable({}, items_mt);
local set = { _items = items };
-
+
+ -- We access the set through an upvalue in these methods, so ignore 'self' being unused
+ --luacheck: ignore 212/self
+
function set:add(item)
items[item] = true;
end
-
+
function set:contains(item)
return items[item];
end
-
+
function set:items()
- return items;
+ return next, items;
end
-
+
function set:remove(item)
items[item] = nil;
end
-
- function set:add_list(list)
- if list then
- for _, item in ipairs(list) do
+
+ function set:add_list(item_list)
+ if item_list then
+ for _, item in ipairs(item_list) do
items[item] = true;
end
end
end
-
+
function set:include(otherset)
for item in otherset do
items[item] = true;
@@ -101,22 +64,22 @@ function new(list)
items[item] = nil;
end
end
-
+
function set:empty()
return not next(items);
end
-
+
if list then
set:add_list(list);
end
-
+
return setmetatable(set, set_mt);
end
-function union(set1, set2)
+local function union(set1, set2)
local set = new();
local items = set._items;
-
+
for item in pairs(set1._items) do
items[item] = true;
end
@@ -124,14 +87,14 @@ function union(set1, set2)
for item in pairs(set2._items) do
items[item] = true;
end
-
+
return set;
end
-function difference(set1, set2)
+local function difference(set1, set2)
local set = new();
local items = set._items;
-
+
for item in pairs(set1._items) do
items[item] = (not set2._items[item]) or nil;
end
@@ -139,21 +102,68 @@ function difference(set1, set2)
return set;
end
-function intersection(set1, set2)
+local function intersection(set1, set2)
local set = new();
local items = set._items;
-
+
set1, set2 = set1._items, set2._items;
-
+
for item in pairs(set1) do
items[item] = (not not set2[item]) or nil;
end
-
+
return set;
end
-function xor(set1, set2)
+local function xor(set1, set2)
return union(set1, set2) - intersection(set1, set2);
end
-return _M;
+function set_mt.__add(set1, set2)
+ return union(set1, set2);
+end
+function set_mt.__sub(set1, set2)
+ return difference(set1, set2);
+end
+function set_mt.__div(set, func)
+ local new_set = new();
+ local items, new_items = set._items, new_set._items;
+ for item in pairs(items) do
+ local new_item = func(item);
+ if new_item ~= nil then
+ new_items[new_item] = true;
+ end
+ end
+ return new_set;
+end
+function set_mt.__eq(set1, set2)
+ set1, set2 = set1._items, set2._items;
+ for item in pairs(set1) do
+ if not set2[item] then
+ return false;
+ end
+ end
+
+ for item in pairs(set2) do
+ if not set1[item] then
+ return false;
+ end
+ end
+
+ return true;
+end
+function set_mt.__tostring(set)
+ local s, items = { }, set._items;
+ for item in pairs(items) do
+ s[#s+1] = tostring(item);
+ end
+ return t_concat(s, ", ");
+end
+
+return {
+ new = new;
+ union = union;
+ difference = difference;
+ intersection = intersection;
+ xor = xor;
+};
diff --git a/util/sql.lua b/util/sql.lua
index f360d6d0..9981ac3c 100644
--- a/util/sql.lua
+++ b/util/sql.lua
@@ -1,6 +1,6 @@
local setmetatable, getmetatable = setmetatable, getmetatable;
-local ipairs, unpack, select = ipairs, unpack, select;
+local ipairs, unpack, select = ipairs, table.unpack or unpack, select; --luacheck: ignore 113
local tonumber, tostring = tonumber, tostring;
local assert, xpcall, debug_traceback = assert, xpcall, debug.traceback;
local t_concat = table.concat;
@@ -13,7 +13,7 @@ local DBI = require "DBI";
DBI.Drivers();
local build_url = require "socket.url".build;
-module("sql")
+local _ENV = nil;
local column_mt = {};
local table_mt = {};
@@ -21,42 +21,17 @@ local query_mt = {};
--local op_mt = {};
local index_mt = {};
-function is_column(x) return getmetatable(x)==column_mt; end
-function is_index(x) return getmetatable(x)==index_mt; end
-function is_table(x) return getmetatable(x)==table_mt; end
-function is_query(x) return getmetatable(x)==query_mt; end
---function is_op(x) return getmetatable(x)==op_mt; end
---function expr(...) return setmetatable({...}, op_mt); end
-function Integer(n) return "Integer()" end
-function String(n) return "String()" end
+local function is_column(x) return getmetatable(x)==column_mt; end
+local function is_index(x) return getmetatable(x)==index_mt; end
+local function is_table(x) return getmetatable(x)==table_mt; end
+local function is_query(x) return getmetatable(x)==query_mt; end
+local function Integer(n) return "Integer()" end
+local function String(n) return "String()" end
---[[local ops = {
- __add = function(a, b) return "("..a.."+"..b..")" end;
- __sub = function(a, b) return "("..a.."-"..b..")" end;
- __mul = function(a, b) return "("..a.."*"..b..")" end;
- __div = function(a, b) return "("..a.."/"..b..")" end;
- __mod = function(a, b) return "("..a.."%"..b..")" end;
- __pow = function(a, b) return "POW("..a..","..b..")" end;
- __unm = function(a) return "NOT("..a..")" end;
- __len = function(a) return "COUNT("..a..")" end;
- __eq = function(a, b) return "("..a.."=="..b..")" end;
- __lt = function(a, b) return "("..a.."<"..b..")" end;
- __le = function(a, b) return "("..a.."<="..b..")" end;
-};
-
-local functions = {
-
-};
-
-local cmap = {
- [Integer] = Integer();
- [String] = String();
-};]]
-
-function Column(definition)
+local function Column(definition)
return setmetatable(definition, column_mt);
end
-function Table(definition)
+local function Table(definition)
local c = {}
for i,col in ipairs(definition) do
if is_column(col) then
@@ -67,7 +42,7 @@ function Table(definition)
end
return setmetatable({ __table__ = definition, c = c, name = definition.name }, table_mt);
end
-function Index(definition)
+local function Index(definition)
return setmetatable(definition, index_mt);
end
@@ -94,7 +69,6 @@ function index_mt:__tostring()
return s..' }';
-- return 'Index{ name="'..self.name..'", type="'..self.type..'" }'
end
---
local function urldecode(s) return s and (s:gsub("%%(%x%x)", function (c) return s_char(tonumber(c,16)); end)); end
local function parse_url(url)
@@ -121,32 +95,13 @@ local function parse_url(url)
};
end
---[[local session = {};
-
-function session.query(...)
- local rets = {...};
- local query = setmetatable({ __rets = rets, __filters }, query_mt);
- return query;
-end
---
-
-local function db2uri(params)
- return build_url{
- scheme = params.driver,
- user = params.username,
- password = params.password,
- host = params.host,
- port = params.port,
- path = params.database,
- };
-end]]
-
local engine = {};
function engine:connect()
if self.conn then return true; end
local params = self.params;
assert(params.driver, "no driver")
+ log("debug", "Connecting to [%s] %s...", params.driver, params.database);
local dbh, err = DBI.Connect(
params.driver, params.database,
params.username, params.password,
@@ -156,8 +111,19 @@ function engine:connect()
dbh:autocommit(false); -- don't commit automatically
self.conn = dbh;
self.prepared = {};
+ local ok, err = self:set_encoding();
+ if not ok then
+ return ok, err;
+ end
+ local ok, err = self:onconnect();
+ if ok == false then
+ return ok, err;
+ end
return true;
end
+function engine:onconnect()
+ -- Override from create_engine()
+end
function engine:execute(sql, ...)
local success, err = self:connect();
if not success then return success, err; end
@@ -177,10 +143,15 @@ function engine:execute(sql, ...)
end
local result_mt = { __index = {
- affected = function(self) return self.__affected; end;
- rowcount = function(self) return self.__rowcount; end;
+ affected = function(self) return self.__stmt:affected(); end;
+ rowcount = function(self) return self.__stmt:rowcount(); end;
} };
+local function debugquery(where, sql, ...)
+ local i = 0; local a = {...}
+ log("debug", "[%s] %s", where, sql:gsub("%?", function () i = i + 1; local v = a[i]; if type(v) == "string" then v = ("%q"):format(v); end return tostring(v); end));
+end
+
function engine:execute_query(sql, ...)
if self.params.driver == "PostgreSQL" then
sql = sql:gsub("`", "\"");
@@ -200,20 +171,41 @@ function engine:execute_update(sql, ...)
prepared[sql] = stmt;
end
assert(stmt:execute(...));
- return setmetatable({ __affected = stmt:affected(), __rowcount = stmt:rowcount() }, result_mt);
+ return setmetatable({ __stmt = stmt }, result_mt);
end
engine.insert = engine.execute_update;
engine.select = engine.execute_query;
engine.delete = engine.execute_update;
engine.update = engine.execute_update;
+local function debugwrap(name, f)
+ return function (self, sql, ...)
+ debugquery(name, sql, ...)
+ return f(self, sql, ...)
+ end
+end
+function engine:debug(enable)
+ self._debug = enable;
+ if enable then
+ engine.insert = debugwrap("insert", engine.execute_update);
+ engine.select = debugwrap("select", engine.execute_query);
+ engine.delete = debugwrap("delete", engine.execute_update);
+ engine.update = debugwrap("update", engine.execute_update);
+ else
+ engine.insert = engine.execute_update;
+ engine.select = engine.execute_query;
+ engine.delete = engine.execute_update;
+ engine.update = engine.execute_update;
+ end
+end
function engine:_transaction(func, ...)
if not self.conn then
- local a,b = self:connect();
- if not a then return a,b; end
+ local ok, err = self:connect();
+ if not ok then return ok, err; end
end
--assert(not self.__transaction, "Recursive transactions not allowed");
local args, n_args = {...}, select("#", ...);
local function f() return func(unpack(args, 1, n_args)); end
+ log("debug", "SQL transaction begin [%s]", tostring(func));
self.__transaction = true;
local success, a, b, c = xpcall(f, debug_traceback);
self.__transaction = nil;
@@ -229,15 +221,15 @@ function engine:_transaction(func, ...)
end
end
function engine:transaction(...)
- local a,b = self:_transaction(...);
- if not a then
+ local ok, ret = self:_transaction(...);
+ if not ok then
local conn = self.conn;
if not conn or not conn:ping() then
self.conn = nil;
- a,b = self:_transaction(...);
+ ok, ret = self:_transaction(...);
end
end
- return a,b;
+ return ok, ret;
end
function engine:_create_index(index)
local sql = "CREATE INDEX `"..index.name.."` ON `"..index.table.."` (";
@@ -251,19 +243,44 @@ function engine:_create_index(index)
elseif self.params.driver == "MySQL" then
sql = sql:gsub("`([,)])", "`(20)%1");
end
- --print(sql);
+ if index.unique then
+ sql = sql:gsub("^CREATE", "CREATE UNIQUE");
+ end
+ if self._debug then
+ debugquery("create", sql);
+ end
return self:execute(sql);
end
function engine:_create_table(table)
local sql = "CREATE TABLE `"..table.name.."` (";
for i,col in ipairs(table.c) do
- sql = sql.."`"..col.name.."` "..col.type;
+ local col_type = col.type;
+ if col_type == "MEDIUMTEXT" and self.params.driver ~= "MySQL" then
+ col_type = "TEXT"; -- MEDIUMTEXT is MySQL-specific
+ end
+ if col.auto_increment == true and self.params.driver == "PostgreSQL" then
+ col_type = "BIGSERIAL";
+ end
+ sql = sql.."`"..col.name.."` "..col_type;
if col.nullable == false then sql = sql.." NOT NULL"; end
+ if col.primary_key == true then sql = sql.." PRIMARY KEY"; end
+ if col.auto_increment == true then
+ if self.params.driver == "MySQL" then
+ sql = sql.." AUTO_INCREMENT";
+ elseif self.params.driver == "SQLite3" then
+ sql = sql.." AUTOINCREMENT";
+ end
+ end
if i ~= #table.c then sql = sql..", "; end
end
sql = sql.. ");"
if self.params.driver == "PostgreSQL" then
sql = sql:gsub("`", "\"");
+ elseif self.params.driver == "MySQL" then
+ sql = sql:gsub(";$", (" CHARACTER SET '%s' COLLATE '%s_bin';"):format(self.charset, self.charset));
+ end
+ if self._debug then
+ debugquery("create", sql);
end
local success,err = self:execute(sql);
if not success then return success,err; end
@@ -274,6 +291,46 @@ function engine:_create_table(table)
end
return success;
end
+function engine:set_encoding() -- to UTF-8
+ local driver = self.params.driver;
+ if driver == "SQLite3" then
+ return self:transaction(function()
+ if self:select"PRAGMA encoding;"()[1] == "UTF-8" then
+ self.charset = "utf8";
+ end
+ end);
+ end
+ local set_names_query = "SET NAMES '%s';"
+ local charset = "utf8";
+ if driver == "MySQL" then
+ local ok, charsets = self:transaction(function()
+ return self:select"SELECT `CHARACTER_SET_NAME` FROM `information_schema`.`CHARACTER_SETS` WHERE `CHARACTER_SET_NAME` LIKE 'utf8%' ORDER BY MAXLEN DESC LIMIT 1;";
+ end);
+ local row = ok and charsets();
+ charset = row and row[1] or charset;
+ set_names_query = set_names_query:gsub(";$", (" COLLATE '%s';"):format(charset.."_bin"));
+ end
+ self.charset = charset;
+ log("debug", "Using encoding '%s' for database connection", charset);
+ local ok, err = self:transaction(function() return self:execute(set_names_query:format(charset)); end);
+ if not ok then
+ return ok, err;
+ end
+
+ if driver == "MySQL" then
+ local ok, actual_charset = self:transaction(function ()
+ return self:select"SHOW SESSION VARIABLES LIKE 'character_set_client'";
+ end);
+ for row in actual_charset do
+ if row[2] ~= charset then
+ log("error", "MySQL %s is actually %q (expected %q)", row[1], row[2], charset);
+ return false, "Failed to set connection encoding";
+ end
+ end
+ end
+
+ return true;
+end
local engine_mt = { __index = engine };
local function db2uri(params)
@@ -286,55 +343,21 @@ local function db2uri(params)
path = params.database,
};
end
-local engine_cache = {}; -- TODO make weak valued
-function create_engine(self, params)
- local url = db2uri(params);
- if not engine_cache[url] then
- local engine = setmetatable({ url = url, params = params }, engine_mt);
- engine_cache[url] = engine;
- end
- return engine_cache[url];
-end
-
---[[Users = Table {
- name="users";
- Column { name="user_id", type=String(), primary_key=true };
-};
-print(Users)
-print(Users.c.user_id)]]
-
---local engine = create_engine('postgresql://scott:tiger@localhost:5432/mydatabase');
---[[local engine = create_engine{ driver = "SQLite3", database = "./alchemy.sqlite" };
-
-local i = 0;
-for row in assert(engine:execute("select * from sqlite_master")):rows(true) do
- i = i+1;
- print(i);
- for k,v in pairs(row) do
- print("",k,v);
- end
+local function create_engine(self, params, onconnect)
+ return setmetatable({ url = db2uri(params), params = params, onconnect = onconnect }, engine_mt);
end
-print("---")
-Prosody = Table {
- name="prosody";
- Column { name="host", type="TEXT", nullable=false };
- Column { name="user", type="TEXT", nullable=false };
- Column { name="store", type="TEXT", nullable=false };
- Column { name="key", type="TEXT", nullable=false };
- Column { name="type", type="TEXT", nullable=false };
- Column { name="value", type="TEXT", nullable=false };
- Index { name="prosody_index", "host", "user", "store", "key" };
+return {
+ is_column = is_column;
+ is_index = is_index;
+ is_table = is_table;
+ is_query = is_query;
+ Integer = Integer;
+ String = String;
+ Column = Column;
+ Table = Table;
+ Index = Index;
+ create_engine = create_engine;
+ db2uri = db2uri;
};
---print(Prosody);
-assert(engine:transaction(function()
- assert(Prosody:create(engine));
-end));
-
-for row in assert(engine:execute("select user from prosody")):rows(true) do
- print("username:", row['username'])
-end
---result.close();]]
-
-return _M;
diff --git a/util/sslconfig.lua b/util/sslconfig.lua
new file mode 100644
index 00000000..c849aa28
--- /dev/null
+++ b/util/sslconfig.lua
@@ -0,0 +1,120 @@
+-- util to easily merge multiple sets of LuaSec context options
+
+local type = type;
+local pairs = pairs;
+local rawset = rawset;
+local t_concat = table.concat;
+local t_insert = table.insert;
+local setmetatable = setmetatable;
+
+local _ENV = nil;
+
+local handlers = { };
+local finalisers = { };
+local id = function (v) return v end
+
+-- All "handlers" behave like extended rawset(table, key, value) with extra
+-- processing usually merging the new value with the old in some reasonable
+-- way
+-- If a field does not have a defined handler then a new value simply
+-- replaces the old.
+
+
+-- Convert either a list or a set into a special type of set where each
+-- item is either positive or negative in order for a later set of options
+-- to be able to remove options from this set by filtering out the negative ones
+function handlers.options(config, field, new)
+ local options = config[field] or { };
+ if type(new) ~= "table" then new = { new } end
+ for key, value in pairs(new) do
+ if value == true or value == false then
+ options[key] = value;
+ else -- list item
+ options[value] = true;
+ end
+ end
+ config[field] = options;
+end
+
+handlers.verify = handlers.options;
+handlers.verifyext = handlers.options;
+
+-- finalisers take something produced by handlers and return what luasec
+-- expects it to be
+
+-- Produce a list of "positive" options from the set
+function finalisers.options(options)
+ local output = {};
+ for opt, enable in pairs(options) do
+ if enable then
+ output[#output+1] = opt;
+ end
+ end
+ return output;
+end
+
+finalisers.verify = finalisers.options;
+finalisers.verifyext = finalisers.options;
+
+-- We allow ciphers to be a list
+
+function finalisers.ciphers(cipherlist)
+ if type(cipherlist) == "table" then
+ return t_concat(cipherlist, ":");
+ end
+ return cipherlist;
+end
+
+-- protocol = "x" should enable only that protocol
+-- protocol = "x+" should enable x and later versions
+
+local protocols = { "sslv2", "sslv3", "tlsv1", "tlsv1_1", "tlsv1_2" };
+for i = 1, #protocols do protocols[protocols[i] .. "+"] = i - 1; end
+
+-- this interacts with ssl.options as well to add no_x
+local function protocol(config)
+ local min_protocol = protocols[config.protocol];
+ if min_protocol then
+ config.protocol = "sslv23";
+ for i = 1, min_protocol do
+ t_insert(config.options, "no_"..protocols[i]);
+ end
+ end
+end
+
+-- Merge options from 'new' config into 'config'
+local function apply(config, new)
+ if type(new) == "table" then
+ for field, value in pairs(new) do
+ (handlers[field] or rawset)(config, field, value);
+ end
+ end
+end
+
+-- Finalize the config into the form LuaSec expects
+local function final(config)
+ local output = { };
+ for field, value in pairs(config) do
+ output[field] = (finalisers[field] or id)(value);
+ end
+ -- Need to handle protocols last because it adds to the options list
+ protocol(output);
+ return output;
+end
+
+local sslopts_mt = {
+ __index = {
+ apply = apply;
+ final = final;
+ };
+};
+
+local function new()
+ return setmetatable({options={}}, sslopts_mt);
+end
+
+return {
+ apply = apply;
+ final = final;
+ new = new;
+};
diff --git a/util/stanza.lua b/util/stanza.lua
index 7c214210..90422a06 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -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.
--
@@ -35,13 +35,12 @@ end
local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas";
-module "stanza"
+local _ENV = nil;
-stanza_mt = { __type = "stanza" };
+local stanza_mt = { __type = "stanza" };
stanza_mt.__index = stanza_mt;
-local stanza_mt = stanza_mt;
-function stanza(name, attr)
+local function stanza(name, attr)
local stanza = { name = name, attr = attr or {}, tags = {} };
return setmetatable(stanza, stanza_mt);
end
@@ -99,7 +98,7 @@ function stanza_mt:get_child(name, xmlns)
if (not name or child.name == name)
and ((not xmlns and self.attr.xmlns == child.attr.xmlns)
or child.attr.xmlns == xmlns) then
-
+
return child;
end
end
@@ -152,7 +151,7 @@ end
function stanza_mt:maptags(callback)
local tags, curr_tag = self.tags, 1;
local n_children, n_tags = #self, #tags;
-
+
local i = 1;
while curr_tag <= n_tags and n_tags > 0 do
if self[i] == tags[curr_tag] then
@@ -200,12 +199,8 @@ function stanza_mt:find(path)
end
-local xml_escape
-do
- local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
- function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
- _M.xml_escape = xml_escape;
-end
+local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
+local function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
local function _dostring(t, buf, self, xml_escape, parentns)
local nsid = 0;
@@ -258,13 +253,13 @@ end
function stanza_mt.get_error(stanza)
local type, condition, text;
-
+
local error_tag = stanza:get_child("error");
if not error_tag then
return nil, nil, nil;
end
type = error_tag.attr.type;
-
+
for _, child in ipairs(error_tag.tags) do
if child.attr.xmlns == xmlns_stanzas then
if not text and child.name == "text" then
@@ -280,15 +275,13 @@ function stanza_mt.get_error(stanza)
return type, condition or "undefined-condition", text;
end
-do
- local id = 0;
- function new_id()
- id = id + 1;
- return "lx"..id;
- end
+local id = 0;
+local function new_id()
+ id = id + 1;
+ return "lx"..id;
end
-function preserialize(stanza)
+local function preserialize(stanza)
local s = { name = stanza.name, attr = stanza.attr };
for _, child in ipairs(stanza) do
if type(child) == "table" then
@@ -300,7 +293,7 @@ function preserialize(stanza)
return s;
end
-function deserialize(stanza)
+local function deserialize(stanza)
-- Set metatable
if stanza then
local attr = stanza.attr;
@@ -333,55 +326,52 @@ function deserialize(stanza)
stanza.tags = tags;
end
end
-
+
return stanza;
end
-local function _clone(stanza)
+local function clone(stanza)
local attr, tags = {}, {};
for k,v in pairs(stanza.attr) do attr[k] = v; end
local new = { name = stanza.name, attr = attr, tags = tags };
for i=1,#stanza do
local child = stanza[i];
if child.name then
- child = _clone(child);
+ child = clone(child);
t_insert(tags, child);
end
t_insert(new, child);
end
return setmetatable(new, stanza_mt);
end
-clone = _clone;
-function message(attr, body)
+local function message(attr, body)
if not body then
return stanza("message", attr);
else
return stanza("message", attr):tag("body"):text(body):up();
end
end
-function iq(attr)
+local function iq(attr)
if attr and not attr.id then attr.id = new_id(); end
return stanza("iq", attr or { id = new_id() });
end
-function reply(orig)
+local function reply(orig)
return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) });
end
-do
- local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
- function error_reply(orig, type, condition, message)
- local t = reply(orig);
- t.attr.type = "error";
- t:tag("error", {type = type}) --COMPAT: Some day xmlns:stanzas goes here
- :tag(condition, xmpp_stanzas_attr):up();
- if (message) then t:tag("text", xmpp_stanzas_attr):text(message):up(); end
- return t; -- stanza ready for adding app-specific errors
- end
+local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
+local function error_reply(orig, type, condition, message)
+ local t = reply(orig);
+ t.attr.type = "error";
+ t:tag("error", {type = type}) --COMPAT: Some day xmlns:stanzas goes here
+ :tag(condition, xmpp_stanzas_attr):up();
+ if (message) then t:tag("text", xmpp_stanzas_attr):text(message):up(); end
+ return t; -- stanza ready for adding app-specific errors
end
-function presence(attr)
+local function presence(attr)
return stanza("presence", attr);
end
@@ -390,7 +380,7 @@ if do_pretty_printing then
local style_attrv = getstyle("red");
local style_tagname = getstyle("red");
local style_punc = getstyle("magenta");
-
+
local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'");
local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">");
--local tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">").."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
@@ -411,7 +401,7 @@ if do_pretty_printing then
end
return s_format(tag_format, t.name, attr_string, children_text, t.name);
end
-
+
function stanza_mt.pretty_top_tag(t)
local attr_string = "";
if t.attr then
@@ -425,4 +415,17 @@ else
stanza_mt.pretty_top_tag = stanza_mt.top_tag;
end
-return _M;
+return {
+ stanza_mt = stanza_mt;
+ stanza = stanza;
+ new_id = new_id;
+ preserialize = preserialize;
+ deserialize = deserialize;
+ clone = clone;
+ message = message;
+ iq = iq;
+ reply = reply;
+ error_reply = error_reply;
+ presence = presence;
+ xml_escape = xml_escape;
+};
diff --git a/util/statistics.lua b/util/statistics.lua
new file mode 100644
index 00000000..26355026
--- /dev/null
+++ b/util/statistics.lua
@@ -0,0 +1,160 @@
+local t_sort = table.sort
+local m_floor = math.floor;
+local time = require "socket".gettime;
+
+local function nop_function() end
+
+local function percentile(arr, length, pc)
+ local n = pc/100 * (length + 1);
+ local k, d = m_floor(n), n%1;
+ if k == 0 then
+ return arr[1] or 0;
+ elseif k >= length then
+ return arr[length];
+ end
+ return arr[k] + d*(arr[k+1] - arr[k]);
+end
+
+local function new_registry(config)
+ config = config or {};
+ local duration_sample_interval = config.duration_sample_interval or 5;
+ local duration_max_samples = config.duration_max_stored_samples or 5000;
+
+ local function get_distribution_stats(events, n_actual_events, since, new_time, units)
+ local n_stored_events = #events;
+ t_sort(events);
+ local sum = 0;
+ for i = 1, n_stored_events do
+ sum = sum + events[i];
+ end
+
+ return {
+ samples = events;
+ sample_count = n_stored_events;
+ count = n_actual_events,
+ rate = n_actual_events/(new_time-since);
+ average = n_stored_events > 0 and sum/n_stored_events or 0,
+ min = events[1] or 0,
+ max = events[n_stored_events] or 0,
+ units = units,
+ };
+ end
+
+
+ local registry = {};
+ local methods;
+ methods = {
+ amount = function (name, initial)
+ local v = initial or 0;
+ registry[name..":amount"] = function () return "amount", v; end
+ return function (new_v) v = new_v; end
+ end;
+ counter = function (name, initial)
+ local v = initial or 0;
+ registry[name..":amount"] = function () return "amount", v; end
+ return function (delta)
+ v = v + delta;
+ end;
+ end;
+ rate = function (name)
+ local since, n = time(), 0;
+ registry[name..":rate"] = function ()
+ local t = time();
+ local stats = {
+ rate = n/(t-since);
+ count = n;
+ };
+ since, n = t, 0;
+ return "rate", stats.rate, stats;
+ end;
+ return function ()
+ n = n + 1;
+ end;
+ end;
+ distribution = function (name, unit, type)
+ type = type or "distribution";
+ local events, last_event = {}, 0;
+ local n_actual_events = 0;
+ local since = time();
+
+ registry[name..":"..type] = function ()
+ local new_time = time();
+ local stats = get_distribution_stats(events, n_actual_events, since, new_time, unit);
+ events, last_event = {}, 0;
+ n_actual_events = 0;
+ since = new_time;
+ return type, stats.average, stats;
+ end;
+
+ return function (value)
+ n_actual_events = n_actual_events + 1;
+ if n_actual_events%duration_sample_interval == 1 then
+ last_event = (last_event%duration_max_samples) + 1;
+ events[last_event] = value;
+ end
+ end;
+ end;
+ sizes = function (name)
+ return methods.distribution(name, "bytes", "size");
+ end;
+ times = function (name)
+ local events, last_event = {}, 0;
+ local n_actual_events = 0;
+ local since = time();
+
+ registry[name..":duration"] = function ()
+ local new_time = time();
+ local stats = get_distribution_stats(events, n_actual_events, since, new_time, "seconds");
+ events, last_event = {}, 0;
+ n_actual_events = 0;
+ since = new_time;
+ return "duration", stats.average, stats;
+ end;
+
+ return function ()
+ n_actual_events = n_actual_events + 1;
+ if n_actual_events%duration_sample_interval ~= 1 then
+ return nop_function;
+ end
+
+ local start_time = time();
+ return function ()
+ local end_time = time();
+ local duration = end_time - start_time;
+ last_event = (last_event%duration_max_samples) + 1;
+ events[last_event] = duration;
+ end
+ end;
+ end;
+
+ get_stats = function ()
+ return registry;
+ end;
+ };
+ return methods;
+end
+
+return {
+ new = new_registry;
+ get_histogram = function (duration, n_buckets)
+ n_buckets = n_buckets or 100;
+ local events, n_events = duration.samples, duration.sample_count;
+ if not (events and n_events) then
+ return nil, "not a valid distribution stat";
+ end
+ local histogram = {};
+
+ for i = 1, 100, 100/n_buckets do
+ histogram[i] = percentile(events, n_events, i);
+ end
+ return histogram;
+ end;
+
+ get_percentile = function (duration, pc)
+ local events, n_events = duration.samples, duration.sample_count;
+ if not (events and n_events) then
+ return nil, "not a valid distribution stat";
+ end
+ return percentile(events, n_events, pc);
+ end;
+}
diff --git a/util/template.lua b/util/template.lua
index 66d4fca7..04ebb93d 100644
--- a/util/template.lua
+++ b/util/template.lua
@@ -1,4 +1,4 @@
-
+-- luacheck: ignore 213/i
local stanza_mt = require "util.stanza".stanza_mt;
local setmetatable = setmetatable;
local pairs = pairs;
@@ -9,7 +9,7 @@ local debug = debug;
local t_remove = table.remove;
local parse_xml = require "util.xml".parse;
-module("template")
+local _ENV = nil;
local function trim_xml(stanza)
for i=#stanza,1,-1 do
@@ -67,12 +67,12 @@ end
local function create_cloner(stanza, chunkname)
local lookup = {};
local name = create_clone_string(stanza, lookup, "");
- local f = "local setmetatable,stanza_mt=...;return function(data)";
+ local src = "local setmetatable,stanza_mt=...;return function(data)";
for i=1,#lookup do
- f = f.."local _"..i.."="..lookup[i]..";";
+ src = src.."local _"..i.."="..lookup[i]..";";
end
- f = f.."return "..name..";end";
- local f,err = loadstring(f, chunkname);
+ src = src.."return "..name..";end";
+ local f,err = loadstring(src, chunkname);
if not f then error(err); end
return f(setmetatable, stanza_mt);
end
diff --git a/util/termcolours.lua b/util/termcolours.lua
index 6ef3b689..279ff601 100644
--- a/util/termcolours.lua
+++ b/util/termcolours.lua
@@ -1,10 +1,12 @@
-- 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: ignore 213/i
local t_concat, t_insert = table.concat, table.insert;
@@ -19,7 +21,7 @@ if os.getenv("WINDIR") then
end
local orig_color = windows and windows.get_consolecolor and windows.get_consolecolor();
-module "termcolours"
+local _ENV = nil;
local stylemap = {
reset = 0; bright = 1, dim = 2, underscore = 4, blink = 5, reverse = 7, hidden = 8;
@@ -45,7 +47,7 @@ local cssmap = {
};
local fmt_string = char(0x1B).."[%sm%s"..char(0x1B).."[0m";
-function getstring(style, text)
+local function getstring(style, text)
if style then
return format(fmt_string, style, text);
else
@@ -53,7 +55,7 @@ function getstring(style, text)
end
end
-function getstyle(...)
+local function getstyle(...)
local styles, result = { ... }, {};
for i, style in ipairs(styles) do
style = stylemap[style];
@@ -65,7 +67,7 @@ function getstyle(...)
end
local last = "0";
-function setstyle(style)
+local function setstyle(style)
style = style or "0";
if style ~= last then
io_write("\27["..style.."m");
@@ -82,7 +84,7 @@ if windows then
end
end
if not orig_color then
- function setstyle(style) end
+ function setstyle() end
end
end
@@ -95,8 +97,13 @@ local function ansi2css(ansi_codes)
return "</span><span style='"..t_concat(css, ";").."'>";
end
-function tohtml(input)
+local function tohtml(input)
return input:gsub("\027%[(.-)m", ansi2css);
end
-return _M;
+return {
+ getstring = getstring;
+ getstyle = getstyle;
+ setstyle = setstyle;
+ tohtml = tohtml;
+};
diff --git a/util/throttle.lua b/util/throttle.lua
index 55e1d07b..3d3f5d2d 100644
--- a/util/throttle.lua
+++ b/util/throttle.lua
@@ -3,7 +3,7 @@ local gettime = require "socket".gettime;
local setmetatable = setmetatable;
local floor = math.floor;
-module "throttle"
+local _ENV = nil;
local throttle = {};
local throttle_mt = { __index = throttle };
@@ -39,8 +39,10 @@ function throttle:poll(cost, split)
end
end
-function create(max, period)
+local function create(max, period)
return setmetatable({ rate = max / period, max = max, t = 0, balance = max }, throttle_mt);
end
-return _M;
+return {
+ create = create;
+};
diff --git a/util/timer.lua b/util/timer.lua
index af1e57b6..3713625d 100644
--- a/util/timer.lua
+++ b/util/timer.lua
@@ -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,7 +17,7 @@ local type = type;
local data = {};
local new_data = {};
-module "timer"
+local _ENV = nil;
local _add_task;
if not server.event then
@@ -42,7 +42,7 @@ if not server.event then
end
new_data = {};
end
-
+
local next_time = math_huge;
for i, d in pairs(data) do
local t, callback = d[1], d[2];
@@ -78,6 +78,6 @@ else
end
end
-add_task = _add_task;
-
-return _M;
+return {
+ add_task = _add_task;
+};
diff --git a/util/uuid.lua b/util/uuid.lua
index 3576be8f..f4fd21f6 100644
--- a/util/uuid.lua
+++ b/util/uuid.lua
@@ -1,36 +1,32 @@
-- 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 error = error;
-local round_up = math.ceil;
-local urandom, urandom_err = io.open("/dev/urandom", "r");
-
-module "uuid"
+local random = require "util.random";
+local random_bytes = random.bytes;
+local hex = require "util.hex".to;
+local m_ceil = math.ceil;
local function get_nibbles(n)
- local binary_random = urandom:read(round_up(n/2));
- local hex_random = binary_random:gsub(".",
- function (x) return ("%02x"):format(x:byte()) end);
- return hex_random:sub(1, n);
+ return hex(random_bytes(m_ceil(n/2))):sub(1, n);
end
+
local function get_twobits()
- return ("%x"):format(urandom:read(1):byte() % 4 + 8);
+ return ("%x"):format(random_bytes(1):byte() % 4 + 8);
end
-function generate()
- if not urandom then
- error("Unable to obtain a secure random number generator, please see https://prosody.im/doc/random ("..urandom_err..")");
- end
+local function generate()
-- generate RFC 4122 complaint UUIDs (version 4 - random)
return get_nibbles(8).."-"..get_nibbles(4).."-4"..get_nibbles(3).."-"..(get_twobits())..get_nibbles(3).."-"..get_nibbles(12);
end
-function seed()
-end
-
-return _M;
+return {
+ get_nibbles=get_nibbles;
+ generate = generate ;
+ -- COMPAT
+ seed = random.seed;
+};
diff --git a/util/watchdog.lua b/util/watchdog.lua
index bcb2e274..aa8c6486 100644
--- a/util/watchdog.lua
+++ b/util/watchdog.lua
@@ -2,12 +2,12 @@ local timer = require "util.timer";
local setmetatable = setmetatable;
local os_time = os.time;
-module "watchdog"
+local _ENV = nil;
local watchdog_methods = {};
local watchdog_mt = { __index = watchdog_methods };
-function new(timeout, callback)
+local function new(timeout, callback)
local watchdog = setmetatable({ timeout = timeout, last_reset = os_time(), callback = callback }, watchdog_mt);
timer.add_task(timeout+1, function (current_time)
local last_reset = watchdog.last_reset;
@@ -31,4 +31,6 @@ function watchdog_methods:cancel()
self.last_reset = nil;
end
-return _M;
+return {
+ new = new;
+};
diff --git a/util/x509.lua b/util/x509.lua
index 19d4ec6d..f228b201 100644
--- a/util/x509.lua
+++ b/util/x509.lua
@@ -20,13 +20,11 @@
local nameprep = require "util.encodings".stringprep.nameprep;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
+local base64 = require "util.encodings".base64;
local log = require "util.logger".init("x509");
-local pairs, ipairs = pairs, ipairs;
local s_format = string.format;
-local t_insert = table.insert;
-local t_concat = table.concat;
-module "x509"
+local _ENV = nil;
local oid_commonname = "2.5.4.3"; -- [LDAP] 2.3
local oid_subjectaltname = "2.5.29.17"; -- [PKIX] 4.2.1.6
@@ -149,7 +147,10 @@ local function compare_srvname(host, service, asserted_names)
return false
end
-function verify_identity(host, service, cert)
+local function verify_identity(host, service, cert)
+ if cert.setencode then
+ cert:setencode("utf8");
+ end
local ext = cert:extensions()
if ext[oid_subjectaltname] then
local sans = ext[oid_subjectaltname];
@@ -161,7 +162,9 @@ function verify_identity(host, service, cert)
if sans[oid_xmppaddr] then
had_supported_altnames = true
- if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end
+ if service == "_xmpp-client" or service == "_xmpp-server" then
+ if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end
+ end
end
if sans[oid_dnssrv] then
@@ -212,4 +215,27 @@ function verify_identity(host, service, cert)
return false
end
-return _M;
+local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n"..
+"([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-";
+
+local function pem2der(pem)
+ local typ, data = pem:match(pat);
+ if typ and data then
+ return base64.decode(data), typ;
+ end
+end
+
+local wrap = ('.'):rep(64);
+local envelope = "-----BEGIN %s-----\n%s\n-----END %s-----\n"
+
+local function der2pem(data, typ)
+ typ = typ and typ:upper() or "CERTIFICATE";
+ data = base64.encode(data);
+ return s_format(envelope, typ, data:gsub(wrap, '%0\n', (#data-1)/64), typ);
+end
+
+return {
+ verify_identity = verify_identity;
+ pem2der = pem2der;
+ der2pem = der2pem;
+};
diff --git a/util/xml.lua b/util/xml.lua
index 076490fa..733d821a 100644
--- a/util/xml.lua
+++ b/util/xml.lua
@@ -2,7 +2,7 @@
local st = require "util.stanza";
local lxp = require "lxp";
-module("xml")
+local _ENV = nil;
local parse_xml = (function()
local ns_prefixes = {
@@ -11,6 +11,7 @@ local parse_xml = (function()
local ns_separator = "\1";
local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
return function(xml)
+ --luacheck: ignore 212/self
local handler = {};
local stanza = st.stanza("root");
function handler:StartElement(tagname, attr)
@@ -26,8 +27,8 @@ local parse_xml = (function()
attr[i] = nil;
local ns, nm = k:match(ns_pattern);
if nm ~= "" then
- ns = ns_prefixes[ns];
- if ns then
+ ns = ns_prefixes[ns];
+ if ns then
attr[ns..":"..nm] = attr[k];
attr[k] = nil;
end
@@ -38,7 +39,7 @@ local parse_xml = (function()
function handler:CharacterData(data)
stanza:text(data);
end
- function handler:EndElement(tagname)
+ function handler:EndElement()
stanza:up();
end
local parser = lxp.new(handler, "\1");
@@ -53,5 +54,6 @@ local parse_xml = (function()
end;
end)();
-parse = parse_xml;
-return _M;
+return {
+ parse = parse_xml;
+};
diff --git a/util/xmppstream.lua b/util/xmppstream.lua
index 138c86b7..7be63285 100644
--- a/util/xmppstream.lua
+++ b/util/xmppstream.lua
@@ -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.
--
@@ -24,7 +24,7 @@ local lxp_supports_bytecount = not not lxp.new({}).getcurrentbytecount;
local default_stanza_size_limit = 1024*1024*10; -- 10MB
-module "xmppstream"
+local _ENV = nil;
local new_parser = lxp.new;
@@ -40,29 +40,26 @@ local xmlns_streams = "http://etherx.jabber.org/streams";
local ns_separator = "\1";
local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
-_M.ns_separator = ns_separator;
-_M.ns_pattern = ns_pattern;
-
local function dummy_cb() end
-function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
+local function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
local xml_handlers = {};
-
+
local cb_streamopened = stream_callbacks.streamopened;
local cb_streamclosed = stream_callbacks.streamclosed;
local cb_error = stream_callbacks.error or function(session, e, stanza) error("XML stream error: "..tostring(e)..(stanza and ": "..tostring(stanza) or ""),2); end;
local cb_handlestanza = stream_callbacks.handlestanza;
cb_handleprogress = cb_handleprogress or dummy_cb;
-
+
local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
local stream_tag = stream_callbacks.stream_tag or "stream";
if stream_ns ~= "" then
stream_tag = stream_ns..ns_separator..stream_tag;
end
local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
-
+
local stream_default_ns = stream_callbacks.default_ns;
-
+
local stack = {};
local chardata, stanza = {};
local stanza_size = 0;
@@ -82,7 +79,7 @@ function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
attr.xmlns = curr_ns;
non_streamns_depth = non_streamns_depth + 1;
end
-
+
for i=1,#attr do
local k = attr[i];
attr[i] = nil;
@@ -92,7 +89,7 @@ function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
attr[k] = nil;
end
end
-
+
if not stanza then --if we are not currently inside a stanza
if lxp_supports_bytecount then
stanza_size = self:getcurrentbytecount();
@@ -116,7 +113,7 @@ function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
cb_error(session, "invalid-top-level-element");
end
-
+
stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
else -- we are inside a stanza, so add a tag
if lxp_supports_bytecount then
@@ -205,26 +202,26 @@ function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
error("Failed to abort parsing");
end
end
-
+
if lxp_supports_doctype then
xml_handlers.StartDoctypeDecl = restricted_handler;
end
xml_handlers.Comment = restricted_handler;
xml_handlers.ProcessingInstruction = restricted_handler;
-
+
local function reset()
stanza, chardata, stanza_size = nil, {}, 0;
stack = {};
end
-
+
local function set_session(stream, new_session)
session = new_session;
end
-
+
return xml_handlers, { reset = reset, set_session = set_session };
end
-function new(session, stream_callbacks, stanza_size_limit)
+local function new(session, stream_callbacks, stanza_size_limit)
-- Used to track parser progress (e.g. to enforce size limits)
local n_outstanding_bytes = 0;
local handle_progress;
@@ -241,6 +238,25 @@ function new(session, stream_callbacks, stanza_size_limit)
local parser = new_parser(handlers, ns_separator, false);
local parse = parser.parse;
+ function session.open_stream(session, from, to)
+ local send = session.sends2s or session.send;
+
+ local attr = {
+ ["xmlns:stream"] = "http://etherx.jabber.org/streams",
+ ["xml:lang"] = "en",
+ xmlns = stream_callbacks.default_ns,
+ version = session.version and (session.version > 0 and "1.0" or nil),
+ id = session.streamid,
+ from = from or session.host, to = to,
+ };
+ if session.stream_attrs then
+ session:stream_attrs(from, to, attr)
+ end
+ send("<?xml version='1.0'?>");
+ send(st.stanza("stream:stream", attr):top_tag());
+ return true;
+ end
+
return {
reset = function ()
parser = new_parser(handlers, ns_separator, false);
@@ -262,4 +278,9 @@ function new(session, stream_callbacks, stanza_size_limit)
};
end
-return _M;
+return {
+ ns_separator = ns_separator;
+ ns_pattern = ns_pattern;
+ new_sax_handlers = new_sax_handlers;
+ new = new;
+};