Merge 0.9->0.10
authorKim Alvefur <zash@zash.se>
Sun, 22 May 2016 12:39:14 +0000 (14:39 +0200)
committerKim Alvefur <zash@zash.se>
Sun, 22 May 2016 12:39:14 +0000 (14:39 +0200)
236 files changed:
.luacheckrc [new file with mode: 0644]
CHANGES [new file with mode: 0644]
Makefile
certs/Makefile
configure
core/certmanager.lua
core/configmanager.lua
core/hostmanager.lua
core/loggingmanager.lua
core/moduleapi.lua
core/modulemanager.lua
core/portmanager.lua
core/rostermanager.lua
core/s2smanager.lua
core/sessionmanager.lua
core/stanza_router.lua
core/statsmanager.lua [new file with mode: 0644]
core/storagemanager.lua
core/usermanager.lua
fallbacks/bit.lua
fallbacks/lxp.lua
man/Makefile [new file with mode: 0644]
man/prosodyctl.man
man/prosodyctl.markdown [new file with mode: 0644]
net/adns.lua
net/connlisteners.lua
net/dns.lua
net/http.lua
net/http/codes.lua
net/http/server.lua
net/httpserver.lua
net/server.lua
net/server_event.lua
net/server_select.lua
net/websocket.lua [new file with mode: 0644]
net/websocket/frames.lua [new file with mode: 0644]
plugins/adhoc/adhoc.lib.lua
plugins/adhoc/mod_adhoc.lua
plugins/mod_admin_adhoc.lua
plugins/mod_admin_telnet.lua
plugins/mod_announce.lua
plugins/mod_auth_internal_hashed.lua
plugins/mod_auth_internal_plain.lua
plugins/mod_blocklist.lua [new file with mode: 0644]
plugins/mod_bosh.lua
plugins/mod_c2s.lua
plugins/mod_carbons.lua [new file with mode: 0644]
plugins/mod_component.lua
plugins/mod_compression.lua
plugins/mod_debug_sql.lua [new file with mode: 0644]
plugins/mod_dialback.lua
plugins/mod_disco.lua
plugins/mod_groups.lua
plugins/mod_http.lua
plugins/mod_http_errors.lua
plugins/mod_http_files.lua
plugins/mod_iq.lua
plugins/mod_lastactivity.lua
plugins/mod_legacyauth.lua
plugins/mod_message.lua
plugins/mod_motd.lua
plugins/mod_offline.lua
plugins/mod_pep.lua
plugins/mod_ping.lua
plugins/mod_posix.lua
plugins/mod_presence.lua
plugins/mod_privacy.lua
plugins/mod_private.lua
plugins/mod_proxy65.lua
plugins/mod_pubsub.lua [deleted file]
plugins/mod_pubsub/mod_pubsub.lua [new file with mode: 0644]
plugins/mod_pubsub/pubsub.lib.lua [new file with mode: 0644]
plugins/mod_register.lua
plugins/mod_roster.lua
plugins/mod_s2s/mod_s2s.lua
plugins/mod_s2s/s2sout.lib.lua
plugins/mod_s2s_auth_certs.lua [new file with mode: 0644]
plugins/mod_saslauth.lua
plugins/mod_storage_internal.lua
plugins/mod_storage_none.lua
plugins/mod_storage_sql.lua
plugins/mod_storage_sql1.lua [new file with mode: 0644]
plugins/mod_storage_xep0227.lua [new file with mode: 0644]
plugins/mod_time.lua
plugins/mod_tls.lua
plugins/mod_unknown.lua [new file with mode: 0644]
plugins/mod_uptime.lua
plugins/mod_vcard.lua
plugins/mod_version.lua
plugins/mod_watchregistrations.lua
plugins/mod_websocket.lua [new file with mode: 0644]
plugins/mod_welcome.lua
plugins/mod_windows.lua [new file with mode: 0644]
plugins/muc/mod_muc.lua
plugins/muc/muc.lib.lua
plugins/sql.lib.lua [deleted file]
plugins/storage/mod_xep0227.lua [deleted file]
plugins/storage/sqlbasic.lib.lua [deleted file]
plugins/storage/xep227store.lib.lua [deleted file]
prosody
prosody.cfg.lua.dist
prosodyctl
tests/json/fail1.json [new file with mode: 0644]
tests/json/fail10.json [new file with mode: 0644]
tests/json/fail11.json [new file with mode: 0644]
tests/json/fail12.json [new file with mode: 0644]
tests/json/fail13.json [new file with mode: 0644]
tests/json/fail14.json [new file with mode: 0644]
tests/json/fail15.json [new file with mode: 0644]
tests/json/fail16.json [new file with mode: 0644]
tests/json/fail17.json [new file with mode: 0644]
tests/json/fail18.json [new file with mode: 0644]
tests/json/fail19.json [new file with mode: 0644]
tests/json/fail2.json [new file with mode: 0644]
tests/json/fail20.json [new file with mode: 0644]
tests/json/fail21.json [new file with mode: 0644]
tests/json/fail22.json [new file with mode: 0644]
tests/json/fail23.json [new file with mode: 0644]
tests/json/fail24.json [new file with mode: 0644]
tests/json/fail25.json [new file with mode: 0644]
tests/json/fail26.json [new file with mode: 0644]
tests/json/fail27.json [new file with mode: 0644]
tests/json/fail28.json [new file with mode: 0644]
tests/json/fail29.json [new file with mode: 0644]
tests/json/fail3.json [new file with mode: 0644]
tests/json/fail30.json [new file with mode: 0644]
tests/json/fail31.json [new file with mode: 0644]
tests/json/fail32.json [new file with mode: 0644]
tests/json/fail33.json [new file with mode: 0644]
tests/json/fail4.json [new file with mode: 0644]
tests/json/fail5.json [new file with mode: 0644]
tests/json/fail6.json [new file with mode: 0644]
tests/json/fail7.json [new file with mode: 0644]
tests/json/fail8.json [new file with mode: 0644]
tests/json/fail9.json [new file with mode: 0644]
tests/json/pass1.json [new file with mode: 0644]
tests/json/pass2.json [new file with mode: 0644]
tests/json/pass3.json [new file with mode: 0644]
tests/modulemanager_option_conversion.lua
tests/run_tests.sh
tests/test.lua
tests/test_core_configmanager.lua
tests/test_core_modulemanager.lua [deleted file]
tests/test_core_s2smanager.lua
tests/test_core_stanza_router.lua
tests/test_net_http.lua [deleted file]
tests/test_sasl.lua
tests/test_util_cache.lua [new file with mode: 0644]
tests/test_util_http.lua [new file with mode: 0644]
tests/test_util_ip.lua [new file with mode: 0644]
tests/test_util_jid.lua
tests/test_util_json.lua [new file with mode: 0644]
tests/test_util_json.sh [new file with mode: 0755]
tests/test_util_multitable.lua
tests/test_util_queue.lua [new file with mode: 0644]
tests/test_util_random.lua [new file with mode: 0644]
tests/test_util_rfc3484.lua [deleted file]
tests/test_util_rfc6724.lua [new file with mode: 0644]
tests/test_util_stanza.lua
tests/test_util_throttle.lua [new file with mode: 0644]
tests/test_util_uuid.lua [new file with mode: 0644]
tests/test_util_xml.lua [new file with mode: 0644]
tests/test_util_xmppstream.lua [new file with mode: 0644]
tests/util/logger.lua
tools/ejabberd2prosody.lua
tools/ejabberdsql2prosody.lua
tools/erlparse.lua
tools/jabberd14sql2prosody.lua
tools/migration/migrator/prosody_sql.lua
tools/migration/prosody-migrator.lua
tools/openfire2prosody.lua
tools/xep227toprosody.lua
util-src/Makefile
util-src/crand.c [new file with mode: 0644]
util-src/encodings.c
util-src/hashes.c
util-src/net.c
util-src/pposix.c
util-src/ringbuffer.c [new file with mode: 0644]
util-src/signal.c
util-src/table.c [new file with mode: 0644]
util-src/windows.c
util/array.lua
util/cache.lua [new file with mode: 0644]
util/caps.lua
util/dataforms.lua
util/datamanager.lua
util/datetime.lua
util/debug.lua
util/dependencies.lua
util/events.lua
util/filters.lua
util/helpers.lua
util/hex.lua [new file with mode: 0644]
util/hmac.lua
util/import.lua
util/interpolation.lua [new file with mode: 0644]
util/ip.lua
util/iterators.lua
util/jid.lua
util/json.lua
util/logger.lua
util/mercurial.lua [new file with mode: 0644]
util/multitable.lua
util/openssl.lua
util/paths.lua [new file with mode: 0644]
util/pluginloader.lua
util/presence.lua [new file with mode: 0644]
util/prosodyctl.lua
util/pubsub.lua
util/queue.lua [new file with mode: 0644]
util/random.lua [new file with mode: 0644]
util/rfc6724.lua
util/sasl.lua
util/sasl/anonymous.lua
util/sasl/digest-md5.lua
util/sasl/external.lua [new file with mode: 0644]
util/sasl/plain.lua
util/sasl/scram.lua
util/sasl_cyrus.lua
util/serialization.lua
util/session.lua [new file with mode: 0644]
util/set.lua
util/sql.lua
util/sslconfig.lua [new file with mode: 0644]
util/stanza.lua
util/statistics.lua [new file with mode: 0644]
util/template.lua
util/termcolours.lua
util/throttle.lua
util/timer.lua
util/uuid.lua
util/watchdog.lua
util/x509.lua
util/xml.lua
util/xmppstream.lua

diff --git a/.luacheckrc b/.luacheckrc
new file mode 100644 (file)
index 0000000..590f9c3
--- /dev/null
@@ -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 (file)
index 0000000..725e10d
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,24 @@
+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)
+-   Pluggable connection timeout handling
+-   mod\_websocket (RFC 7395)
+
+Removed
+-------
+
+-   mod\_privacy (XEP-0016)
+
index 46a8f49da10a9c877c82b1325fb4c330d0519a22..06e67c9ce119df61296797db8ebdf2357eeec1a8 100644 (file)
--- 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,10 @@ clean:
        rm -f prosody.version
        $(MAKE) clean -C util-src
 
+test:
+       cd tests && $(RUNWITH) test.lua 0
+       # Skipping: cd tests && RUNWITH=$(RUNWITH) ./test_util_json.sh
+
 util/%.so:
        $(MAKE) install -C util-src
 
@@ -64,8 +69,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 > $@
+
+
index c709ff91f24a45677f697a3854892f712708726a..b3011a89ddc3ff7a1eaec22d9e9fc0168dd739d3 100644 (file)
@@ -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 $@ $*
index 199f5fcf8c0a9b45b24de6b6c264b73f2ba509e6..77aa132920646ff73328f30222c08dad19b7d50b 100755 (executable)
--- 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
 
index 624bd841ef433ceed10667b45e946a3036d873b2..29a5a6c8403906428bddec299950a79efaf5f506 100644 (file)
 -- 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 = tonumber(luasec_major) * 100 + tonumber(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;
+};
index c8aa7b9a08c107db23b5a637beb05585e33bb718..16d4b8e2f4a0ecb2841600bc01973c6c83d00b60 100644 (file)
@@ -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;
index 06ba72a100c5d3ebdf73fa80bf14707a7b065703..53c1cd4eeaa4cd72b54507952ad1401738f60553 100644 (file)
@@ -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;
+}
index c69dede85edf747d4c5f935f99fea5b06887d72a..e3a83817b5d239c2009bc0261b674ad71ef98b7b 100644 (file)
@@ -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.
 --
 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;
+}
index ed75669b60b64f6c90e5c19f9ee843328f20ec55..21b2216ca2f3452c805a7b4cabe122d1910080b2 100644 (file)
@@ -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
@@ -339,8 +374,16 @@ function api:provides(name, item)
        self:add_item(name.."-provider", item);
 end
 
-function api:send(stanza)
-       return core_post_stanza(hosts[self.host], stanza);
+function api:send(stanza, origin)
+       return core_post_stanza(origin or hosts[self.host], stanza);
+end
+
+function api:broadcast(jids, stanza, iter)
+       for jid in (iter or it.values)(jids) do
+               local new_stanza = st.clone(stanza);
+               new_stanza.attr.to = jid;
+               core_post_stanza(hosts[self.host], new_stanza);
+       end
 end
 
 function api:add_timer(delay, callback)
@@ -356,12 +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;
index 4df950697d905984808ac38eec0d030b36f9c9cf..7bed17950e0c435eeac929d4cde535c153a00649 100644 (file)
@@ -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,13 +119,15 @@ 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);
+               if not modulemap["*"][module_name] then
+                       log("debug", "%s is already loaded for %s, so not loading again", module_name, host);
+               end
                return nil, "module-already-loaded";
        elseif modulemap["*"][module_name] then
                local mod = modulemap["*"][module_name];
@@ -131,7 +135,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 +151,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 +321,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;
+};
index 37442a31d675341aac7bd1b918f52182b9846038..ad1a0be397edd976f45852e5744f2aa354cce5e1 100644 (file)
@@ -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
 
@@ -29,7 +29,7 @@ if socket.tcp6 and config.get("*", "use_ipv6") ~= false then
        table.insert(default_local_interfaces, "::1");
 end
 
-local default_mode = config.get("*", "network_default_read_size") or "*a";
+local default_mode = config.get("*", "network_default_read_size") or 4096;
 
 --- Private state
 
@@ -41,7 +41,7 @@ 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;
+};
index 5e06e3f7a4203ec5c82971f638f4b81322c9e74a..58d1f16e1c5bf67b8dd8eeeef01940a10ed443cf 100644 (file)
@@ -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"});
@@ -72,14 +75,28 @@ function roster_push(username, host, jid)
                -- stanza ready
                for _, session in pairs(hosts[host].sessions[username].sessions) do
                        if session.interested then
-                               -- FIXME do we need to set stanza.attr.to?
                                session.send(stanza);
                        end
                end
        end
 end
 
-function load_roster(username, host)
+local function roster_metadata(roster, err)
+       local metadata = roster[false];
+       if not metadata then
+               metadata = { broken = err or nil };
+               roster[false] = metadata;
+       end
+       if roster.pending and type(roster.pending.subscription) ~= "string" then
+               metadata.pending = roster.pending;
+               roster.pending = nil;
+       elseif not metadata.pending then
+               metadata.pending = {};
+       end
+       return metadata;
+end
+
+local function load_roster(username, host)
        local jid = username.."@"..host;
        log("debug", "load_roster: asked for: %s", jid);
        local user = bare_sessions[jid];
@@ -91,27 +108,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 +138,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 +165,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 +189,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 +213,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 +222,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 +237,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 +265,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 +280,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 +295,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 +340,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;
+};
index fb5c4299db35aab7e99a00a915b941170ac99427..a8d399d2761307a27c1d3893750238feb2ddbf1f 100644 (file)
@@ -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;
+};
index 4b014d18cd5aea4002bbee1e67985291e709a0bb..c8856634205ddcd108ba8af7e508489bd3b35b84 100644 (file)
@@ -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;
@@ -70,22 +75,22 @@ function retire_session(session)
        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 +99,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 +116,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 +171,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 +191,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 +212,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 +227,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;
+};
index a2c7b3963abfba49273e12de367aebb864e28ade..55c94bf4b51261ebf897f238c3de0c9cede45c3e 100644 (file)
@@ -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 (file)
index 0000000..7771a2b
--- /dev/null
@@ -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;
+};
index 1c82af6d8b2fdf9934d89e41a68e9fd536974c39..8d83f677db81104b8690577a4d1cf8668baae72b 100644 (file)
@@ -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,62 @@ 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;
+                               end
+                               current = {};
+                       end
+                       for k,v in pairs(keydatas) do
+                               if v == self.remove then v = nil; end
+                               current[k] = v;
+                       end
+                       return self.keyval_store:set(username, current);
+               end;
+               remove = {};
+       };
+}
+
+local open;
+
+local function create_map_shim(host, store)
+       local keyval_store, err = open(host, store, "keyval");
+       if keyval_store == nil then return nil, err end
+       return setmetatable({
+               keyval_store = keyval_store;
+       }, map_shim_mt);
+end
+
 function open(host, store, typ)
        local driver, driver_name = get_driver(host, store);
        local ret, err = driver:open(store, typ);
        if not ret then
                if err == "unsupported-store" then
+                       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 +164,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
@@ -121,7 +196,7 @@ end
 function datamanager.users(host, datastore, typ)
        local driver = open(host, datastore, typ);
        if not driver.users then
-               return function() log("warn", "storage driver %s does not support listing users", driver.name) end
+               return function() log("warn", "Storage driver %s does not support listing users", driver.name) end
        end
        return driver:users();
 end
@@ -132,4 +207,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;
+};
index 08343bee3c580233e45ab4ccb4eb87fd61f3e577..d5132662b08a9b9fb404de2b0682704e88bbefcc 100644 (file)
@@ -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;
+};
index 2482c473e8cab4f7fd0b6a58dc0e2bd0d0d518e8..28dca4e6a0f5e15f4bc876048aace1110679bc76 100644 (file)
@@ -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.
 --
index 6d3297d1559983b19e0da7edc76ac594b26734ce..ac1c9a03d2ba6889be98b303b9fc9700d7f9770a 100644 (file)
@@ -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 (file)
index 0000000..79bdd90
--- /dev/null
@@ -0,0 +1,4 @@
+all: prosodyctl.man
+
+%.man: %.markdown
+       pandoc -s -t man -o $@ $^
index 6dcb04cda678feb9d403844d79df32ef346c87bb..b91502a846ae746c5eeb6a2cf0ce254cb5804759 100644 (file)
-.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 (file)
index 0000000..217dfd3
--- /dev/null
@@ -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/>
index 3fc958f4771d6e69a0cc4fc157529dbad52146f0..d3da2065526e7218afdffce097d2319d5523cd87 100644 (file)
@@ -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;
+};
index 99ddc720aa570fb02765191ca6b1c47eaddaaee0..000bfa63effa2d85598ca02eb72652240bbc4928 100644 (file)
@@ -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
+};
index d123731c14b578aeb26f3726d46908375ef4257f..689020a47a792e986fc95ad01546c654eca25a1e 100644 (file)
@@ -22,8 +22,8 @@ local is_windows = (_ and windows) or os.getenv("WINDIR");
 local coroutine, io, math, string, table =
       coroutine, io, math, string, table;
 
-local ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type=
-      ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type;
+local ipairs, next, pairs, print, setmetatable, tostring, assert, error, select, type, unpack=
+      ipairs, next, pairs, print, setmetatable, tostring, assert, error, select, type, table.unpack or unpack;
 
 local ztact = { -- public domain 20080404 lua@ztact.com
        get = function(parent, ...)
@@ -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);
index 8ce474948c9737a12f346c1fb3c195455e93d9bd..b78f84382b40137484b3261ce541ded64c153d27 100644 (file)
@@ -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;
+};
index 0cadd07956006e9470202be014879d97b1a38c1a..bc31c7dda8faf42f5926c063e2c496470c75e176 100644 (file)
@@ -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
index f091595cec2db7a8d3a7b1b56aaef0ee9da7ae61..aeaa7416065f1bf646d5c13b6ce9d511b2317d84 100644 (file)
@@ -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;
index 7d574788253ea25a6f67bf20f0e426e0449c34ce..6e2e31b9b3f1102794ca12d4e5397a4897826fdc 100644 (file)
@@ -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;
+};
index 9b0d27e17cd90c20162075c430fb93f9977fe1d9..41e180faa6ce9cd5cee9518b89c0671d9a0be8ae 100644 (file)
@@ -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.
 --
index 882d10ed95d0e45d3436ed663c73c84258bd4ddb..2cb45553b9fdd552413602e66aea1a77ebf49b77 100644 (file)
@@ -11,6 +11,7 @@
                        -- when using luasec, there are 4 cases of timeout errors: wantread or wantwrite during reading or writing
 
 --]]
+-- luacheck: ignore 212/self 431/err 211/ret
 
 local SCRIPT_NAME           = "server_event.lua"
 local SCRIPT_VERSION        = "0.05"
@@ -29,30 +30,36 @@ local cfg = {
        WRITE_TIMEOUT         = 180,  -- timeout in seconds for write data on socket
        CONNECT_TIMEOUT       = 20,  -- timeout in seconds for connection attempts
        CLEAR_DELAY           = 5,  -- seconds to wait for clearing interface list (and calling ondisconnect listeners)
+       READ_RETRY_DELAY      = 1e-06, -- if, after reading, there is still data in buffer, wait this long and continue reading
        DEBUG                 = true,  -- show debug messages
 }
 
-local function use(x) return rawget(_G, x); end
-local ipairs = use "ipairs"
-local string = use "string"
-local select = use "select"
-local require = use "require"
-local tostring = use "tostring"
-local coroutine = use "coroutine"
-local setmetatable = use "setmetatable"
+local pairs = pairs
+local select = select
+local require = require
+local tostring = tostring
+local setmetatable = setmetatable
 
 local t_insert = table.insert
 local t_concat = table.concat
+local s_sub = string.sub
 
-local ssl = use "ssl"
-local socket = use "socket" or require "socket"
+local coroutine_wrap = coroutine.wrap
+local coroutine_yield = coroutine.yield
+
+local has_luasec, ssl = pcall ( require , "ssl" )
+local socket = require "socket"
+local levent = require "luaevent.core"
+
+local socket_gettime = socket.gettime
+local getaddrinfo = socket.dns.getaddrinfo
 
 local log = require ("util.logger").init("socket")
 
 local function debug(...)
        return log("debug", ("%s "):rep(select('#', ...)), ...)
 end
-local vdebug = debug;
+-- local vdebug = debug;
 
 local bitor = ( function( ) -- thx Rici Lake
        local hasbit = function( x, p )
@@ -72,741 +79,680 @@ 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) -- called from wrapclient
+       local callback = function( event )
+               if EV_TIMEOUT == event then  -- timeout during connection
+                       self.fatalerror = "connection timeout"
+                       self:ontimeout()  -- call timeout listener
+                       self:_close()
+                       debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
+               else
+                       if plainssl and has_luasec then  -- start ssl session
+                               self:starttls(self._sslctx, true)
+                       else  -- normal connection
+                               self:_start_session(true)
                        end
-                       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 self.readcallback and not self.eventread then
-                       self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT );  -- register callback
-                       return true;
-               end
+--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 self.readcallback and not self.eventread then
+               self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT );  -- register callback
+               return true;
        end
-       
-       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 = listener.onconnect;
+       self.ondisconnect = listener.ondisconnect;
+       self.onincoming = listener.onincoming;
+       self.ontimeout = listener.ontimeout;
+       self.onreadtimeout = listener.onreadtimeout;
+       self.onstatus = listener.onstatus;
+       self.ondetach = listener.ondetach;
+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.writebufferlen ~= 0 then
-                                               -- data possibly written from ondrain
-                                               return EV_WRITE, cfg.WRITE_TIMEOUT
-                                       elseif interface.eventreadtimeout then
-                                               return EV_WRITE, cfg.WRITE_TIMEOUT
-                                       end
-                                       interface.eventwrite = nil
-                                       return -1
-                               elseif byte and (err == "timeout" or err == "wantwrite") then  -- want write again
-                                       --vdebug( "writebuffer is not empty:", err )
-                                       interface.writebuffer[1] = string_sub( interface.writebuffer[1], byte + 1, interface.writebufferlen )  -- new buffer
-                                       interface.writebufferlen = interface.writebufferlen - byte
-                                       if "wantread" == err then  -- happens only with luasec
-                                               local callback = function( )
-                                                       interface:_close()
-                                                       interface.eventwritetimeout = nil
-                                                       return -1;
-                                               end
-                                               interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT )  -- reg a new timeout event
-                                               debug( "wantread during write attempt, reg it in readcallback but dont know what really happens next..." )
-                                               -- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent
-                                               return -1
-                                       end
-                                       return EV_WRITE, cfg.WRITE_TIMEOUT
-                               else  -- connection was closed during writing or fatal error
-                                       interface.fatalerror = err or "fatal error"
-                                       debug( "connection failed in write event:", interface.fatalerror )
-                                       interface:_close()
-                                       interface.eventwrite = nil
-                                       return -1
+                               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.writebufferlen ~= 0 then
+                                       -- data possibly written from ondrain
+                                       return EV_WRITE, cfg.WRITE_TIMEOUT
+                               elseif interface.eventreadtimeout then
+                                       return EV_WRITE, cfg.WRITE_TIMEOUT
                                end
-                               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
+       interface.readcallback = function( event )  -- called on read events
+               --vdebug( "new client read event, id/ip/port:", tostring(interface.id), tostring(ip), tostring(port) )
+               if interface.noreading or interface.fatalerror then  -- leave this event
+                       --vdebug( "leaving this event because:", tostring(interface.noreading or interface.fatalerror) )
+                       interface.eventread = nil
+                       return -1
+               end
+               if EV_TIMEOUT == event and not interface.conn:dirty() and interface:onreadtimeout() ~= true then
+                       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
-                       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
+                       if interface.eventreadtimeout then
+                               interface.eventreadtimeout:close( )
+                               interface.eventreadtimeout = nil
                        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
+               local buffer, err, part = interface.conn:receive( interface._pattern )  -- receive buffer with "pattern"
+               --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )
+               buffer = buffer or part
+               if buffer and #buffer > cfg.MAX_READ_LENGTH then  -- check buffer length
+                       interface.fatalerror = "receive buffer exceeded"
+                       debug( "fatal error:", interface.fatalerror )
+                       interface:_close()
+                       interface.eventread = nil
+                       return -1
+               end
+               if err and ( err ~= "timeout" and err ~= "wantread" ) then
+                       if "wantwrite" == err then -- need to read on write event
+                               if not interface.eventwrite then  -- register new write event if needed
+                                       interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )
                                end
-                               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
+                               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
-                       return EV_READ
+               else
+                       interface.onincoming( interface, buffer, err )  -- send new data to listener
                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
+               if interface.noreading then
+                       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
-                       end
+               if interface.conn:dirty() then -- still data left in buffer
+                       return EV_TIMEOUT, cfg.READ_RETRY_DELAY;
                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 )
+               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
@@ -815,9 +761,9 @@ end
 
 local function setquitting(yes)
        if yes then
-                -- Quit now
-                closeallservers();
-                base:loopexit();
+               -- Quit now
+               closeallservers();
+               base:loopexit();
        end
 end
 
@@ -829,7 +775,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
@@ -842,14 +788,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
@@ -861,12 +807,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,
index c50a6ce11ff1e6aa1e6165f988b4fa0db56ce3c8..3503c30d0079bd2f50daa04e89867b091efeefb2 100644 (file)
@@ -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
 --
 
@@ -38,7 +38,6 @@ local coroutine = use "coroutine"
 
 --// lua lib methods //--
 
-local os_difftime = os.difftime
 local math_min = math.min
 local math_huge = math.huge
 local table_concat = table.concat
@@ -48,13 +47,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 +149,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 +295,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 +325,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 +335,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 +408,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 +582,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 +602,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 +635,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 +662,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 +723,7 @@ local function link(sender, receiver, buffersize)
                        sender_locked = nil;
                end
        end
-       
+
        local _readbuffer = sender.readbuffer;
        function sender.readbuffer()
                _readbuffer();
@@ -727,22 +738,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 )
@@ -878,21 +890,22 @@ loop = function(once) -- this is the main loop of the program
                _currenttime = luasocket_gettime( )
 
                -- Check for socket timeouts
-               local difftime = os_difftime( _currenttime - _starttime )
-               if difftime > _checkinterval then
+               if _currenttime - _starttime > _checkinterval then
                        _starttime = _currenttime
                        for handler, timestamp in pairs( _writetimes ) do
-                               if os_difftime( _currenttime - timestamp ) > _sendtimeout then
-                                       --_writetimes[ handler ] = nil
+                               if _currenttime - timestamp > _sendtimeout then
                                        handler.disconnect( )( handler, "send timeout" )
                                        handler:force_close()    -- forced disconnect
                                end
                        end
                        for handler, timestamp in pairs( _readtimes ) do
-                               if os_difftime( _currenttime - timestamp ) > _readtimeout then
-                                       --_readtimes[ handler ] = nil
-                                       handler.disconnect( )( handler, "read timeout" )
-                                       handler:close( )        -- forced disconnect?
+                               if _currenttime - timestamp > _readtimeout then
+                                       if not(handler.onreadtimeout) or handler:onreadtimeout() ~= true then
+                                               handler.disconnect( )( handler, "read timeout" )
+                                               handler:close( )        -- forced disconnect?
+                                       else
+                                               _readtimes[ handler ] = _currenttime -- reset timer
+                                       end
                                end
                        end
                end
@@ -920,6 +933,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 +966,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 +1035,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 (file)
index 0000000..a4274ee
--- /dev/null
@@ -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 (file)
index 0000000..5fe96d4
--- /dev/null
@@ -0,0 +1,219 @@
+-- Prosody IM
+-- Copyright (C) 2012 Florian Zeitz
+-- Copyright (C) 2014 Daurnimator
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local softreq = require "util.dependencies".softreq;
+local random_bytes = require "util.random".bytes;
+
+local bit = assert(softreq"bit" or softreq"bit32",
+       "No bit module found. See https://prosody.im/doc/depends#bitop");
+local band = bit.band;
+local bor = bit.bor;
+local bxor = bit.bxor;
+local lshift = bit.lshift;
+local rshift = bit.rshift;
+
+local t_concat = table.concat;
+local s_byte = string.byte;
+local s_char= string.char;
+local s_sub = string.sub;
+local s_pack = string.pack;
+local s_unpack = string.unpack;
+
+if not s_pack and softreq"struct" then
+       s_pack = softreq"struct".pack;
+       s_unpack = softreq"struct".unpack;
+end
+
+local function read_uint16be(str, pos)
+       local l1, l2 = s_byte(str, pos, pos+1);
+       return l1*256 + l2;
+end
+-- FIXME: this may lose precision
+local function read_uint64be(str, pos)
+       local l1, l2, l3, l4, l5, l6, l7, l8 = s_byte(str, pos, pos+7);
+       local h = lshift(l1, 24) + lshift(l2, 16) + lshift(l3, 8) + l4;
+       local l = lshift(l5, 24) + lshift(l6, 16) + lshift(l7, 8) + l8;
+       return h * 2^32 + l;
+end
+local function pack_uint16be(x)
+       return s_char(rshift(x, 8), band(x, 0xFF));
+end
+local function get_byte(x, n)
+       return band(rshift(x, n), 0xFF);
+end
+local function pack_uint64be(x)
+       local h = band(x / 2^32, 2^32-1);
+       return s_char(get_byte(h, 24), get_byte(h, 16), get_byte(h, 8), band(h, 0xFF),
+               get_byte(x, 24), get_byte(x, 16), get_byte(x, 8), band(x, 0xFF));
+end
+
+if s_pack then
+       function pack_uint16be(x)
+               return s_pack(">I2", x);
+       end
+       function pack_uint64be(x)
+               return s_pack(">I8", x);
+       end
+end
+
+if s_unpack then
+       function read_uint16be(str, pos)
+               return s_unpack(">I2", str, pos);
+       end
+       function read_uint64be(str, pos)
+               return s_unpack(">I8", str, pos);
+       end
+end
+
+local function parse_frame_header(frame)
+       if #frame < 2 then return; end
+
+       local byte1, byte2 = s_byte(frame, 1, 2);
+       local result = {
+               FIN = band(byte1, 0x80) > 0;
+               RSV1 = band(byte1, 0x40) > 0;
+               RSV2 = band(byte1, 0x20) > 0;
+               RSV3 = band(byte1, 0x10) > 0;
+               opcode = band(byte1, 0x0F);
+
+               MASK = band(byte2, 0x80) > 0;
+               length = band(byte2, 0x7F);
+       };
+
+       local length_bytes = 0;
+       if result.length == 126 then
+               length_bytes = 2;
+       elseif result.length == 127 then
+               length_bytes = 8;
+       end
+
+       local header_length = 2 + length_bytes + (result.MASK and 4 or 0);
+       if #frame < header_length then return; end
+
+       if length_bytes == 2 then
+               result.length = read_uint16be(frame, 3);
+       elseif length_bytes == 8 then
+               result.length = read_uint64be(frame, 3);
+       end
+
+       if result.MASK then
+               result.key = { s_byte(frame, length_bytes+3, length_bytes+6) };
+       end
+
+       return result, header_length;
+end
+
+-- XORs the string `str` with the array of bytes `key`
+-- TODO: optimize
+local function apply_mask(str, key, from, to)
+       from = from or 1
+       if from < 0 then from = #str + from + 1 end -- negative indicies
+       to = to or #str
+       if to < 0 then to = #str + to + 1 end -- negative indicies
+       local key_len = #key
+       local counter = 0;
+       local data = {};
+       for i = from, to do
+               local key_index = counter%key_len + 1;
+               counter = counter + 1;
+               data[counter] = s_char(bxor(key[key_index], s_byte(str, i)));
+       end
+       return t_concat(data);
+end
+
+local function parse_frame_body(frame, header, pos)
+       if header.MASK then
+               return apply_mask(frame, header.key, pos, pos + header.length - 1);
+       else
+               return frame:sub(pos, pos + header.length - 1);
+       end
+end
+
+local function parse_frame(frame)
+       local result, pos = parse_frame_header(frame);
+       if result == nil or #frame < (pos + result.length) then return; end
+       result.data = parse_frame_body(frame, result, pos+1);
+       return result, pos + result.length;
+end
+
+local function build_frame(desc)
+       local data = desc.data or "";
+
+       assert(desc.opcode and desc.opcode >= 0 and desc.opcode <= 0xF, "Invalid WebSocket opcode");
+       if desc.opcode >= 0x8 then
+               -- RFC 6455 5.5
+               assert(#data <= 125, "WebSocket control frames MUST have a payload length of 125 bytes or less.");
+       end
+
+       local b1 = bor(desc.opcode,
+               desc.FIN and 0x80 or 0,
+               desc.RSV1 and 0x40 or 0,
+               desc.RSV2 and 0x20 or 0,
+               desc.RSV3 and 0x10 or 0);
+
+       local b2 = #data;
+       local length_extra;
+       if b2 <= 125 then -- 7-bit length
+               length_extra = "";
+       elseif b2 <= 0xFFFF then -- 2-byte length
+               b2 = 126;
+               length_extra = pack_uint16be(#data);
+       else -- 8-byte length
+               b2 = 127;
+               length_extra = pack_uint64be(#data);
+       end
+
+       local key = ""
+       if desc.MASK then
+               local key_a = desc.key
+               if key_a then
+                       key = s_char(unpack(key_a, 1, 4));
+               else
+                       key = random_bytes(4);
+                       key_a = {key:byte(1,4)};
+               end
+               b2 = bor(b2, 0x80);
+               data = apply_mask(data, key_a);
+       end
+
+       return s_char(b1, b2) .. length_extra .. key .. data
+end
+
+local function parse_close(data)
+       local code, message
+       if #data >= 2 then
+               code = read_uint16be(data, 1);
+               if #data > 2 then
+                       message = s_sub(data, 3);
+               end
+       end
+       return code, message
+end
+
+local function build_close(code, message, mask)
+       local data = pack_uint16be(code);
+       if message then
+               assert(#message<=123, "Close reason must be <=123 bytes");
+               data = data .. message;
+       end
+       return build_frame({
+               opcode = 0x8;
+               FIN = true;
+               MASK = mask;
+               data = data;
+       });
+end
+
+return {
+       parse_header = parse_frame_header;
+       parse_body = parse_frame_body;
+       parse = parse_frame;
+       build = build_frame;
+       parse_close = parse_close;
+       build_close = build_close;
+};
index b544ddc8e4ac8ecee986adf2a9a2b94b1748daf0..5c90c91ba8931360cd87d2921504091b292d57eb 100644 (file)
@@ -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;
index 69b2c8da9903e101afce81f053f4d00d7a0197d6..1c956021aaf19fddb91ad2f8c40b50d14896c8aa 100644 (file)
@@ -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);
index 232fa5f777525980fe20f5b29a9f692e92e30162..392e715e3b8954fd80a22e119811ae9ec56a29a7 100644 (file)
@@ -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);
index 864036067df9fcd4d45cb65dec90fd503c5fd51c..9dfbbc7a379c524ad1dcdc79381a6ce2123014e9 100644 (file)
@@ -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
 
index 96976d6f843df6ae51611fea541c846587ece9b3..9327556c5e1b8354dbf3680d07700a0aecee6807 100644 (file)
@@ -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
index 2b041e432021283ce9aac727532afb6e95376421..78abe50d04be08d7747e01435e317c381bdcbd8d 100644 (file)
@@ -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);
 
index d226fdbefa5cb2adbd6ef443457751941f5272c7..db5284320e78e495f2bebda1999619da40715f63 100644 (file)
@@ -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 (file)
index 0000000..8efbfd9
--- /dev/null
@@ -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);
+
index d9c8defd90ead6d891c5948ede45d90e04ce98a0..34613cbb2837348749551770c968d560228607f0 100644 (file)
@@ -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 new_xmpp_stream = require "util.xmppstream".new;
 local sm = require "core.sessionmanager";
 local sm_destroy_session = sm.destroy_session;
 local new_uuid = require "util.uuid".generate;
-local fire_event = prosody.events.fire_event;
 local core_process_stanza = prosody.core_process_stanza;
 local st = require "util.stanza";
 local logger = require "util.logger";
@@ -22,6 +21,7 @@ local initialize_filters = require "util.filters".initialize;
 local math_min = math.min;
 local xpcall, tostring, type = xpcall, tostring, type;
 local traceback = debug.traceback;
+local nameprep = require "util.encodings".stringprep.nameprep;
 
 local xmlns_streams = "http://etherx.jabber.org/streams";
 local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
@@ -37,24 +37,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 +65,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 +78,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 +88,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 +114,27 @@ 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);
-       
+       local ok, err = stream:feed(body);
+       if not ok then
+               module:log("warn", "Error parsing BOSH payload; %s", err)
+               local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+                       ["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
+               return tostring(close_reply);
+       end
+
        -- Stanzas (if any) in the request have now been processed, and
        -- we take care of the high-level BOSH logic here, including
        -- giving a response or putting the request "on hold".
@@ -141,9 +149,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 +167,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 +175,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();
@@ -178,7 +183,13 @@ function handle_POST(event)
                else
                        return true; -- Inform http server we shall reply later
                end
+       elseif response.finished then
+               return; -- A response has been sent already
        end
+       module:log("warn", "Unable to associate request with a session (incomplete request?)");
+       local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+               ["xmlns:stream"] = xmlns_streams, condition = "item-not-found" });
+       return tostring(close_reply) .. "\n";
 end
 
 
@@ -188,10 +199,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,10 +228,9 @@ 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
@@ -233,9 +243,17 @@ 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
+
+               local to_host = nameprep(attr.to);
+               local rid = tonumber(attr.rid);
+               local wait = tonumber(attr.wait);
+               if not to_host then
+                       log("debug", "BOSH client tried to connect to invalid host: %s", tostring(attr.to));
+                       local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+                               ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" });
+                       response:send(tostring(close_reply));
+                       return;
+               elseif not hosts[to_host] then
                        -- Unknown host
                        log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to));
                        local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
@@ -243,12 +261,22 @@ function stream_callbacks.streamopened(context, attr)
                        response:send(tostring(close_reply));
                        return;
                end
-               
+               if not rid or (not wait and attr.wait or wait < 0 or wait % 1 ~= 0) then
+                       log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", tostring(attr.rid), tostring(attr.wait));
+                       local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+                               ["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
+                       response:send(tostring(close_reply));
+                       return;
+               end
+
+               rid = rid - 1;
+               wait = math_min(wait, bosh_max_wait);
+
                -- New session
                sid = new_uuid();
                local session = {
-                       type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid)-1, host = attr.to,
-                       bosh_version = attr.ver, bosh_wait = math_min(attr.wait, bosh_max_wait), streamid = sid,
+                       type = "c2s_unauthed", conn = {}, sid = sid, rid = rid, host = attr.to,
+                       bosh_version = attr.ver, bosh_wait = wait, streamid = sid,
                        bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY,
                        requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream,
                        close = bosh_close_stream, dispatch_stanza = core_process_stanza, notopen = true,
@@ -256,12 +284,14 @@ function stream_callbacks.streamopened(context, attr)
                        ip = get_ip_from_request(request);
                };
                sessions[sid] = session;
-               
+
                local filter = initialize_filters(session);
-               
+
                session.log("debug", "BOSH session created for request from %s", session.ip);
                log("info", "New BOSH session, assigned it sid '%s'", sid);
 
+               hosts[session.host].events.fire_event("bosh-session", { session = session, request = request });
+
                -- Send creation response
                local creating_session = true;
 
@@ -274,12 +304,12 @@ function stream_callbacks.streamopened(context, attr)
                        end
                        s = filter("stanzas/out", s);
                        --log("debug", "Sending BOSH data: %s", tostring(s));
+                       if not s then return true end
                        t_insert(session.send_buffer, tostring(s));
 
                        local oldest_request = r[1];
                        if oldest_request and not session.bosh_processing then
                                log("debug", "We have an open request, so sending on that");
-                               oldest_request.headers = default_headers;
                                local body_attr = { xmlns = "http://jabber.org/protocol/httpbind",
                                        ["xmlns:stream"] = "http://etherx.jabber.org/streams";
                                        type = session.bosh_terminate and "terminate" or nil;
@@ -306,17 +336,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 +362,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,8 +377,7 @@ 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
@@ -370,8 +398,8 @@ function stream_callbacks.handlestanza(context, 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 +412,12 @@ 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();
+               local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+                       ["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
+               response:send(tostring(close_reply));
                return;
        end
-       
+
        local session = sessions[context.sid];
        if error == "stream-error" then -- Remote stream error, we close normally
                session:close();
@@ -398,7 +426,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 +441,7 @@ function on_timer()
                        end
                end
        end
-       
+
        now = now - 3;
        local n_dead_sessions = 0;
        for session, close_after in pairs(inactive_sessions) do
index 2bb919f8f22c1453ee217252e6a5315e117f6b88..2829d5fdb61fe221d59f22d65d618abcf8c5ee68 100644 (file)
@@ -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,16 +27,17 @@ 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 = {};
 
 --- 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 +51,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 +66,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)
@@ -129,8 +134,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 +157,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;
@@ -193,14 +197,16 @@ end, 200);
 
 --- 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();
@@ -210,34 +216,37 @@ 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
-       
+
        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
@@ -257,6 +266,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");
@@ -266,14 +276,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 (file)
index 0000000..9ef1471
--- /dev/null
@@ -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);
index 11abab799e3b2be35979ad4ba3ccc87e287ca22f..eebaaf3eed542f6317d134528c440e06fd24b683 100644 (file)
@@ -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)
+       local function on_destroy(session, err) --luacheck: ignore 212/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);
@@ -141,7 +149,7 @@ local stream_callbacks = { default_ns = xmlns_component };
 
 local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
 
-function stream_callbacks.error(session, error, data, data2)
+function stream_callbacks.error(session, error, data)
        if session.destroyed then return; end
        module:log("warn", "Error processing component stream: %s", tostring(error));
        if error == "no-stream" then
@@ -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)
@@ -289,7 +295,7 @@ function listener.onconnect(conn)
                session.stream:reset();
        end
 
-       function session.data(conn, data)
+       function session.data(_, data)
                local ok, err = stream:feed(data);
                if ok then return; end
                module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
@@ -308,6 +314,7 @@ function listener.ondisconnect(conn, err)
        local session = sessions[conn];
        if session then
                (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
+               module:fire_event("component-disconnected", { session = session, reason = err });
                if session.on_destroy then session:on_destroy(err); end
                sessions[conn] = nil;
                for k in pairs(session) do
@@ -316,7 +323,6 @@ function listener.ondisconnect(conn, err)
                        end
                end
                session.destroyed = true;
-               session = nil;
        end
 end
 
index 1ec4c85ad1e5b0dd8860272e59f6761aa4f4242e..17be2ef2c0dbfceba4bccda74e82f2797b55f936 100644 (file)
@@ -1,201 +1,9 @@
 -- Prosody IM
--- Copyright (C) 2009-2012 Tobias Markmann
--- 
+-- Copyright (C) 2016 Matthew Wild
+--
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 --
 
-local st = require "util.stanza";
-local zlib = require "zlib";
-local pcall = pcall;
-local tostring = tostring;
-
-local xmlns_compression_feature = "http://jabber.org/features/compress"
-local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
-local xmlns_stream = "http://etherx.jabber.org/streams";
-local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
-local add_filter = require "util.filters".add_filter;
-
-local compression_level = module:get_option_number("compression_level", 7);
-
-if not compression_level or compression_level < 1 or compression_level > 9 then
-       module:log("warn", "Invalid compression level in config: %s", tostring(compression_level));
-       module:log("warn", "Module loading aborted. Compression won't be available.");
-       return;
-end
-
-module:hook("stream-features", function(event)
-       local origin, features = event.origin, event.features;
-       if not origin.compressed and (origin.type == "c2s" or origin.type == "s2sin" or origin.type == "s2sout") then
-               -- FIXME only advertise compression support when TLS layer has no compression enabled
-               features:add_child(compression_stream_feature);
-       end
-end);
-
-module:hook("s2s-stream-features", function(event)
-       local origin, features = event.origin, event.features;
-       -- FIXME only advertise compression support when TLS layer has no compression enabled
-       if not origin.compressed and (origin.type == "c2s" or origin.type == "s2sin" or origin.type == "s2sout") then
-               features:add_child(compression_stream_feature);
-       end
-end);
-
--- Hook to activate compression if remote server supports it.
-module:hook_stanza(xmlns_stream, "features",
-               function (session, stanza)
-                       if not session.compressed and (session.type == "c2s" or session.type == "s2sin" or session.type == "s2sout") then
-                               -- does remote server support compression?
-                               local comp_st = stanza:child_with_name("compression");
-                               if comp_st then
-                                       -- do we support the mechanism
-                                       for a in comp_st:children() do
-                                               local algorithm = a[1]
-                                               if algorithm == "zlib" then
-                                                       session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib"))
-                                                       session.log("debug", "Enabled compression using zlib.")
-                                                       return true;
-                                               end
-                                       end
-                                       session.log("debug", "Remote server supports no compression algorithm we support.")
-                               end
-                       end
-               end
-, 250);
-
-
--- returns either nil or a fully functional ready to use inflate stream
-local function get_deflate_stream(session)
-       local status, deflate_stream = pcall(zlib.deflate, compression_level);
-       if status == false then
-               local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
-               (session.sends2s or session.send)(error_st);
-               session.log("error", "Failed to create zlib.deflate filter.");
-               module:log("error", "%s", tostring(deflate_stream));
-               return
-       end
-       return deflate_stream
-end
-
--- returns either nil or a fully functional ready to use inflate stream
-local function get_inflate_stream(session)
-       local status, inflate_stream = pcall(zlib.inflate);
-       if status == false then
-               local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
-               (session.sends2s or session.send)(error_st);
-               session.log("error", "Failed to create zlib.inflate filter.");
-               module:log("error", "%s", tostring(inflate_stream));
-               return
-       end
-       return inflate_stream
-end
-
--- setup compression for a stream
-local function setup_compression(session, deflate_stream)
-       add_filter(session, "bytes/out", function(t)
-               local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
-               if status == false then
-                       module:log("warn", "%s", tostring(compressed));
-                       session:close({
-                               condition = "undefined-condition";
-                               text = compressed;
-                               extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
-                       });
-                       return;
-               end
-               return compressed;
-       end);   
-end
-
--- setup decompression for a stream
-local function setup_decompression(session, inflate_stream)
-       add_filter(session, "bytes/in", function(data)
-               local status, decompressed, eof = pcall(inflate_stream, data);
-               if status == false then
-                       module:log("warn", "%s", tostring(decompressed));
-                       session:close({
-                               condition = "undefined-condition";
-                               text = decompressed;
-                               extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
-                       });
-                       return;
-               end
-               return decompressed;
-       end);
-end
-
-module:hook("stanza/http://jabber.org/protocol/compress:compressed", function(event)
-       local session = event.origin;
-       
-       if session.type == "s2sout" then
-               session.log("debug", "Activating compression...")
-               -- create deflate and inflate streams
-               local deflate_stream = get_deflate_stream(session);
-               if not deflate_stream then return true; end
-               
-               local inflate_stream = get_inflate_stream(session);
-               if not inflate_stream then return true; end
-               
-               -- setup compression for session.w
-               setup_compression(session, deflate_stream);
-                       
-               -- setup decompression for session.data
-               setup_decompression(session, inflate_stream);
-               session:reset_stream();
-               session:open_stream(session.from_host, session.to_host);
-               session.compressed = true;
-               return true;
-       end
-end);
-
-module:hook("stanza/http://jabber.org/protocol/compress:failure", function(event)
-       local err = event.stanza:get_child();
-       (event.origin.log or module._log)("warn", "Compression setup failed (%s)", err and err.name or "unknown reason");
-       return true;
-end);
-
-module:hook("stanza/http://jabber.org/protocol/compress:compress", function(event)
-       local session, stanza = event.origin, event.stanza;
-
-       if session.type == "c2s" or session.type == "s2sin" then
-               -- fail if we are already compressed
-               if session.compressed then
-                       local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
-                       (session.sends2s or session.send)(error_st);
-                       session.log("debug", "Client tried to establish another compression layer.");
-                       return true;
-               end
-               
-               -- checking if the compression method is supported
-               local method = stanza:child_with_name("method");
-               method = method and (method[1] or "");
-               if method == "zlib" then
-                       session.log("debug", "zlib compression enabled.");
-                       
-                       -- create deflate and inflate streams
-                       local deflate_stream = get_deflate_stream(session);
-                       if not deflate_stream then return true; end
-                       
-                       local inflate_stream = get_inflate_stream(session);
-                       if not inflate_stream then return true; end
-                       
-                       (session.sends2s or session.send)(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
-                       session:reset_stream();
-                       
-                       -- setup compression for session.w
-                       setup_compression(session, deflate_stream);
-                               
-                       -- setup decompression for session.data
-                       setup_decompression(session, inflate_stream);
-                       
-                       session.compressed = true;
-               elseif method then
-                       session.log("debug", "%s compression selected, but we don't support it.", tostring(method));
-                       local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
-                       (session.sends2s or session.send)(error_st);
-               else
-                       (session.sends2s or session.send)(st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"));
-               end
-               return true;
-       end
-end);
-
+-- COMPAT w/ pre-0.10 configs
+error("mod_compression has been removed in Prosody 0.10+. Please see https://prosody.im/doc/modules/mod_compression for more information.");
diff --git a/plugins/mod_debug_sql.lua b/plugins/mod_debug_sql.lua
new file mode 100644 (file)
index 0000000..7bbbbd8
--- /dev/null
@@ -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
+
+
index dc3c3f10a7a06cf8a52ec9aa8463c2039ed62ab4..f0fe949aef8d2b91049dd32666da7a6b51187316 100644 (file)
@@ -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...");
index 72c9a34c85ca87ec954e2e1c0dec6ebf45f4ad1c..617495801e37497794c500699745d309fc108b18 100644 (file)
@@ -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
index f7f632c230564697a68ac314621ed10aa33c737a..d696d45388df400f49caa168de6423ae314589d7 100644 (file)
@@ -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.
 --
 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";
index 9b574bc8eff9c45985174ff1e20c9cdd91dbdeb1..086887fbcc9a9b63ccd078c3a65804481d12a419 100644 (file)
@@ -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]";
        };
index 2568ea80aa8a73f31d824acfcd9276d206ea1985..0c37e1044eed1ea5b3df81491ac7e1cbcc2e041f 100644 (file)
@@ -53,7 +53,7 @@ local entities = {
 
 local function tohtml(plain)
        return (plain:gsub("[<>&'\"\n]", entities));
-       
+
 end
 
 local function get_page(code, extra)
index 53b6469bb0e2f7b3a4c01a0d4c8003aed3fbd9a3..3b60249559c6e7e42bee94699d0f14ba122f7c4b 100644 (file)
@@ -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.
 --
index e7901ab451c1014a73dee303d870b4ad4b9318a2..c6d62e859dbbc526bd17168376907b42299bc7b8 100644 (file)
@@ -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.
 --
index 11053709bc0b99239ab21ce3bd7d080541a48a79..2dd61699999dcf5a56fb44dd62a5f0b4514ffe22 100644 (file)
@@ -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);
index 5fb664415682661f6fb30fac87152e72b703b3fc..5edc26bb2567604348e42d33b00854f4f24db921 100644 (file)
@@ -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")
index e85da613198d2a17a52e3b5acc7c8a00e4b8aa4e..fc337db09d62a30a57d0f674898e211cde291466 100644 (file)
@@ -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;
index 3dd6b8168aefbacc7e28eacf705183a9cb36df57..574a9cf408e6fe883c801ae65d6a35d5e3485e1b 100644 (file)
@@ -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.
 --
index 1ac62f94737a70ad17ab5401ab01a63c2776d5f9..08ab8490bbc90a8743b1c91cf068a4466ccb49f1 100644 (file)
@@ -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);
index 227908691cb80f5565420f20be71253cebb4fab4..896f3e78b764842d04dfaee7c410d806f7c6ad38 100644 (file)
@@ -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);
index 0bfcac667d96e332573c7a4be1efb38a46f0f37b..1a5034094c9f2ac61744c1a4324e316dc065ba94 100644 (file)
@@ -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
 
index b289fa44b29adf5bad14812f63e48514f5af7bd7..7e6d87992adc90505a6c696be0eddef9fededb13 100644 (file)
@@ -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();
index a5b4f282a4da5635cedf7b81f7e86ac5c9bc17de..cf762edc880cf6c5cfa8a05fbb8d0015aadf094f 100644 (file)
@@ -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,7 +10,7 @@ local log = module._log;
 
 local require = require;
 local pairs = pairs;
-local t_concat, t_insert = table.concat, table.insert;
+local t_concat = table.concat;
 local s_find = string.find;
 local tonumber = tonumber;
 
@@ -27,42 +27,20 @@ local NULL = {};
 local rostermanager = require "core.rostermanager";
 local sessionmanager = require "core.sessionmanager";
 
-local function select_top_resources(user)
-       local priority = 0;
-       local recipients = {};
-       for _, session in pairs(user.sessions) do -- find resource with greatest priority
-               if session.presence then
-                       -- TODO check active privacy list for session
-                       local p = session.priority;
-                       if p > priority then
-                               priority = p;
-                               recipients = {session};
-                       elseif p == priority then
-                               t_insert(recipients, session);
-                       end
-               end
-       end
-       return recipients;
-end
-local function recalc_resource_map(user)
-       if user then
-               user.top_resources = select_top_resources(user);
-               if #user.top_resources == 0 then user.top_resources = nil; end
-       end
-end
+local recalc_resource_map = require "util.presence".recalc_resource_map;
 
-local ignore_presence_priority = module:get_option("ignore_presence_priority");
+local ignore_presence_priority = module:get_option_boolean("ignore_presence_priority", false);
 
 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 +68,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 +84,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
@@ -228,7 +205,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
@@ -313,7 +290,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
@@ -348,7 +325,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
@@ -381,3 +358,27 @@ module:hook("resource-unbind", function(event)
                session.directed = nil;
        end
 end);
+
+module:hook("roster-item-removed", function (event)
+       local username = event.username;
+       local session = event.origin;
+       local roster = event.roster or session and session.roster;
+       local jid = event.jid;
+       local item = event.item;
+       local from_jid = session.full_jid or (username .. "@" .. module.host);
+
+       local subscription = item and item.subscription or "none";
+       local ask = item and item.ask;
+       local pending = roster and roster[false].pending[jid];
+
+       if subscription == "both" or subscription == "from" or pending then
+               core_post_stanza(session, st.presence({type="unsubscribed", from=from_jid, to=jid}));
+       end
+
+       if subscription == "both" or subscription == "to" or ask then
+               send_presence_of_available_resources(username, module.host, jid, session, st.presence({type="unavailable"}));
+               core_post_stanza(session, st.presence({type="unsubscribe", from=from_jid, to=jid}));
+       end
+
+end, -1);
+
index 49c9427fd1759a021b65597454ddecdf9d5c0493..b749b7c72a73ef82038f93f9f159bb47e692bb89 100644 (file)
 -- 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");
index 365a997c8d99abce4755d7d7841760c5581ff1f8..c01053d509b8effaed36d55572d44ffa2e96be43 100644 (file)
@@ -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);
index 1fa42bd86b88e299eddde68c12f4a364a17227a5..cbbfad12cb45fd06c2653ec47bf369f8ba294592 100644 (file)
@@ -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 (file)
index 04f2b61..0000000
+++ /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 (file)
index 0000000..e93a523
--- /dev/null
@@ -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 (file)
index 0000000..1497c21
--- /dev/null
@@ -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;
index 3d7a068c61809c4d8d556bd3f4ca0859ebe32ca8..fda717f723cdb7be53abdc0a314de9b0a63dd487 100644 (file)
@@ -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;
+
+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 or nil);
 
-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 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 });
index d530bb456eeb9e55bce64bd745aa56fb6a8f2ceb..454acebb563ee928486108cb7dfd1a59c69a36b4 100644 (file)
@@ -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);
@@ -77,13 +75,9 @@ module:hook("iq/self/jabber:iq:roster:query", function(event)
                                                local roster = session.roster;
                                                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
-                                                               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
-                                                               core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare}));
-                                                       end
+                                                       module:fire_event("roster-item-removed", {
+                                                               username = node, jid = jid, item = r_item, origin = session, roster = roster,
+                                                       });
                                                        local success, err_type, err_cond, err_msg = rm_remove_from_roster(session, jid);
                                                        if success then
                                                                session.send(st.reply(stanza));
@@ -140,16 +134,20 @@ end);
 
 module:hook_global("user-deleted", function(event)
        local username, host = event.username, event.host;
+       local origin = event.origin or prosody.hosts[host];
        if host ~= module.host then return end
        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
-                               module:send(st.presence({type="unsubscribed", from=bare, to=jid}));
-                       end
-                       if item.subscription == "both" or item.subscription == "to" or item.ask then
-                               module:send(st.presence({type="unsubscribe", from=bare, to=jid}));
+               if jid then
+                       module:fire_event("roster-item-removed", {
+                               username = username, jid = jid, item = item, roster = roster, origin = origin,
+                       });
+               else
+                       for jid in pairs(item.pending) do
+                               module:fire_event("roster-item-removed", {
+                                       username = username, jid = jid, roster = roster, origin = origin,
+                               });
                        end
                end
        end
index 4173fcfa351b19709c2eca2067474bba0cd8ab16..16320ad169afd651a081daaa4d05f23556aead9a 100644 (file)
@@ -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,20 +344,26 @@ 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;
                if not attr.id then
-                       log("error", "Stream response did not give us a stream id!");
+                       log("error", "Stream response from %s did not give us a stream id!", session.to_host);
                        session:close({ condition = "undefined-condition", text = "Missing stream ID" });
                        return;
                end
@@ -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
index dc122af72392f520c86d1c484172bf7cb1e3a174..4241316469e35bfce1fc237fa05c251d37fec367 100644 (file)
@@ -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 (file)
index 0000000..dd0eb3c
--- /dev/null
@@ -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);
+
index c5d3dc917bd847b00869efc563ccc0640f75aca6..bb36600b2e066641e59fc975c4a01281b90f23dd 100644 (file)
@@ -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", { "DIGEST-MD5" });
 
 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);
index 972ecbee33d1b1a71c7f5a8b582ff6bb4c99c6ec..ade4f0a656ec7b12ee15076c4823fe48be382b2c 100644 (file)
@@ -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)
index 8f2d2f5602fcb68734ba978044365c356eb9703d..fa925b7616281e8dcc769b18621adce96650a5e0 100644 (file)
@@ -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 {};
index eed3fec900784518a8860b1d5ed79199a85e397b..4f46b3f662a5ef2a199ea4cad07240c454ac60da 100644 (file)
 
---[[
-
-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
+-- luacheck: ignore 212/self
 
+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;
 
-local resolve_relative_path = require "core.configmanager".resolve_relative_path;
+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 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;
+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
-               module:log("debug", "Successfully connected to database");
-               dbh:autocommit(false); -- don't commit automatically
-               connection = dbh;
-
-               connections[dburi] = dbh;
-       end
-       return connection;
+       end, result, nil;
 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
+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
+                               local t, value = assert(serialize(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
+                       local t, extradata = assert(serialize(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()
+               local data;
+               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`=? LIMIT 1", host, username or "", self.store, key) do
+                               data = deserialize(row[1], row[2]);
+                       end
+                       return data;
+               else
+                       for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=? LIMIT 1", host, username or "", self.store, "") do
+                               data = deserialize(row[1], row[2]);
+                       end
+                       return data and data[key] or nil;
+               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`=? LIMIT 1", host, username or "", self.store, "") do
+                                       extradata = deserialize(row[1], row[2]);
+                               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 = assert(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
+                               for row in stats do
+                                       total = row[1];
+                               end
+                       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 (file)
index 0000000..a5bb5bf
--- /dev/null
@@ -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/mod_storage_xep0227.lua b/plugins/mod_storage_xep0227.lua
new file mode 100644 (file)
index 0000000..ef227ca
--- /dev/null
@@ -0,0 +1,178 @@
+
+local ipairs, pairs = ipairs, pairs;
+local setmetatable = setmetatable;
+local tostring = tostring;
+local next = next;
+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 = 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 = 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 s = tostring(xml);
+               f:write(s);
+               f:close();
+               return true;
+       else
+               f:close();
+               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='urn:xmpp:pie:0'})
+               :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
+                       t_remove(array, i);
+                       return;
+               end
+       end
+end
+local function removeStanzaChild(s, child)
+       removeFromArray(s.tags, child);
+       removeFromArray(s, child);
+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)
+               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 then
+                       local xml = getXml(user, self.host);
+                       if not xml then xml = createOuterXml(user, self.host); end
+                       local usere = getUserElement(xml);
+                       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);
+               end
+       end;
+};
+handlers.vcard = {
+       get = function(self, user)
+               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)
+               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 = {};
+
+function driver:open(datastore, typ)
+       local handler = handlers[datastore];
+       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
+
+module:provides("storage", driver);
index cb69ebe791d1d1d1920e24a8d291fe9a6a22adad..ae7da91627b195bdb661866972e7ebeffd742c95 100644 (file)
@@ -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.
 --
index 2741b8d401f04a1ebee22a66a543b96ae3cb058a..69aafe82d90043e540ac9964d03862a6dea44653 100644 (file)
@@ -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 (file)
index 0000000..4d20b8a
--- /dev/null
@@ -0,0 +1,4 @@
+-- Unknown platform stub
+module:set_global();
+
+-- TODO Do things that make sense if we don't know about the platform
index 3f275b2fe7efcdce55c7612c5fe8722080bedc11..2e369b16f38a25ab21d363bfe7ebb90171eda64f 100644 (file)
@@ -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.
 --
index 26b30e3ab3c10621e9f8fa47a9eb05a4cca8413b..72f92ef763d70015598ec2332923ece774e934ea 100644 (file)
@@ -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.
 --
index d35103b643266f7d5ef6dcab88951793b1f87e0c..be244beb31fd1c75bd7aa20bcc458056a3d3f3ef 100644 (file)
@@ -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.
 --
index abca90bdab36103d3fb3ab16c0d42a2e923b8a84..1e696f30e1f72329b935cf5cdebdec067025054f 100644 (file)
@@ -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,7 +11,7 @@ local host = module:get_host();
 local jid_prep = require "util.jid".prep;
 
 local registration_watchers = module:get_option_set("registration_watchers", module:get_option("admins", {})) / jid_prep;
-local registration_notification = module:get_option("registration_notification", "User $username just registered on $host from $ip");
+local registration_notification = module:get_option_string("registration_notification", "User $username just registered on $host from $ip");
 
 local st = require "util.stanza";
 
diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua
new file mode 100644 (file)
index 0000000..a3f5318
--- /dev/null
@@ -0,0 +1,313 @@
+-- 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, MAY be sent unsolicited, eg as keepalive
+                       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, -1000);
+
+       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
+
+local function keepalive(event)
+       local session = event.session;
+       if session.open_stream == session_open_stream then
+               return session.conn:write(build_frame({ opcode = 0x9, }));
+       end
+end
+
+module:hook("c2s-read-timeout", keepalive, -0.9);
+
+function module.add_host(module)
+       module:depends("http");
+       module:provides("http", {
+               name = "websocket";
+               default_path = "xmpp-websocket";
+               route = {
+                       ["GET"] = handle_request;
+                       ["GET /"] = handle_request;
+               };
+       });
+       module:hook("c2s-read-timeout", keepalive, -0.9);
+end
index e498f0b31f8c0f19973d396b8981d9b168d0258b..d74643de174ca4ff46fec5fe1ec51977f3148b28 100644 (file)
@@ -1,13 +1,13 @@
 -- 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 host = module:get_host();
-local welcome_text = module:get_option("welcome_message") or "Hello $username, welcome to the $host IM server!";
+local welcome_text = module:get_option_string("welcome_message", "Hello $username, welcome to the $host IM server!");
 
 local st = require "util.stanza";
 
diff --git a/plugins/mod_windows.lua b/plugins/mod_windows.lua
new file mode 100644 (file)
index 0000000..8085fd8
--- /dev/null
@@ -0,0 +1,4 @@
+-- Windows platform stub
+module:set_global();
+
+-- TODO Add Windows-specific things here
index acc2da0d7c040beb1c66ec3b7aa2c11ba3aa4c3e..ee90d5527bffca841c46480f1c3303376f3d98c0 100644 (file)
@@ -1,27 +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 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);
 end
 
 local muc_host = module:get_host();
-local muc_name = module:get_option("name");
-if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end
+local muc_name = module:get_option_string("name", "Prosody Chatrooms");
 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 +43,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 +91,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 +125,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 +142,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 +229,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 +239,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);
index f8e8f74dcfe7026dc1df765d846a9be1dbe76634..88ee43c1f4ac0ed0cafef209519169705a437b43 100644 (file)
@@ -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;
@@ -675,86 +688,52 @@ function room_mt:process_form(origin, stanza)
                return true;
        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);
-       end
-
-       local description = fields['muc#roomconfig_roomdesc'];
-       if description ~= self:get_description() then
-               self:set_description(description);
+       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 persistent = fields['muc#roomconfig_persistentroom'];
-       dirty = dirty or (self:is_persistent() ~= persistent)
-       module:log("debug", "persistent=%s", tostring(persistent));
+       local changed = {};
 
-       local moderated = fields['muc#roomconfig_moderatedroom'];
-       dirty = dirty or (self:is_moderated() ~= moderated)
-       module:log("debug", "moderated=%s", tostring(moderated));
-
-       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
@@ -890,7 +869,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
@@ -900,11 +879,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"));
@@ -952,7 +931,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 (file)
index 005ee45..0000000
+++ /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/mod_xep0227.lua b/plugins/storage/mod_xep0227.lua
deleted file mode 100644 (file)
index 5d07a2e..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-
-local ipairs, pairs = ipairs, pairs;
-local setmetatable = setmetatable;
-local tostring = tostring;
-local next = next;
-local t_remove = table.remove;
-local os_remove = os.remove;
-local io_open = io.open;
-
-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 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
-                       t_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 = {};
-
-function driver:open(host, datastore, typ)
-       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
-       return instance;
-end
-
-module:provides("storage", driver);
diff --git a/plugins/storage/sqlbasic.lib.lua b/plugins/storage/sqlbasic.lib.lua
deleted file mode 100644 (file)
index ab3648f..0000000
+++ /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 (file)
index 5ef8df5..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-\r
-local st = require "util.stanza";\r
-\r
-local function getXml(user, host)\r
-       local jid = user.."@"..host;\r
-       local path = "data/"..jid..".xml";\r
-       local f = io.open(path);\r
-       if not f then return; end\r
-       local s = f:read("*a");\r
-       return parse_xml_real(s);\r
-end\r
-local function setXml(user, host, xml)\r
-       local jid = user.."@"..host;\r
-       local path = "data/"..jid..".xml";\r
-       if xml then\r
-               local f = io.open(path, "w");\r
-               if not f then return; end\r
-               local s = tostring(xml);\r
-               f:write(s);\r
-               f:close();\r
-               return true;\r
-       else\r
-               return os.remove(path);\r
-       end\r
-end\r
-local function getUserElement(xml)\r
-       if xml and xml.name == "server-data" then\r
-               local host = xml.tags[1];\r
-               if host and host.name == "host" then\r
-                       local user = host.tags[1];\r
-                       if user and user.name == "user" then\r
-                               return user;\r
-                       end\r
-               end\r
-       end\r
-end\r
-local function createOuterXml(user, host)\r
-       return st.stanza("server-data", {xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'})\r
-               :tag("host", {jid=host})\r
-                       :tag("user", {name = user});\r
-end\r
-local function removeFromArray(array, value)\r
-       for i,item in ipairs(array) do\r
-               if item == value then\r
-                       table.remove(array, i);\r
-                       return;\r
-               end\r
-       end\r
-end\r
-local function removeStanzaChild(s, child)\r
-       removeFromArray(s.tags, child);\r
-       removeFromArray(s, child);\r
-end\r
-\r
-local handlers = {};\r
-\r
-handlers.accounts = {\r
-       get = function(self, user)\r
-               local user = getUserElement(getXml(user, self.host));\r
-               if user and user.attr.password then\r
-                       return { password = user.attr.password };\r
-               end\r
-       end;\r
-       set = function(self, user, data)\r
-               if data and data.password then\r
-                       local xml = getXml(user, self.host);\r
-                       if not xml then xml = createOuterXml(user, self.host); end\r
-                       local usere = getUserElement(xml);\r
-                       usere.attr.password = data.password;\r
-                       return setXml(user, self.host, xml);\r
-               else\r
-                       return setXml(user, self.host, nil);\r
-               end\r
-       end;\r
-};\r
-handlers.vcard = {\r
-       get = function(self, user)\r
-               local user = getUserElement(getXml(user, self.host));\r
-               if user then\r
-                       local vcard = user:get_child("vCard", 'vcard-temp');\r
-                       if vcard then\r
-                               return st.preserialize(vcard);\r
-                       end\r
-               end\r
-       end;\r
-       set = function(self, user, data)\r
-               local xml = getXml(user, self.host);\r
-               local usere = xml and getUserElement(xml);\r
-               if usere then\r
-                       local vcard = usere:get_child("vCard", 'vcard-temp');\r
-                       if vcard then\r
-                               removeStanzaChild(usere, vcard);\r
-                       elseif not data then\r
-                               return true;\r
-                       end\r
-                       if data then\r
-                               vcard = st.deserialize(data);\r
-                               usere:add_child(vcard);\r
-                       end\r
-                       return setXml(user, self.host, xml);\r
-               end\r
-               return true;\r
-       end;\r
-};\r
-handlers.private = {\r
-       get = function(self, user)\r
-               local user = getUserElement(getXml(user, self.host));\r
-               if user then\r
-                       local private = user:get_child("query", "jabber:iq:private");\r
-                       if private then\r
-                               local r = {};\r
-                               for _, tag in ipairs(private.tags) do\r
-                                       r[tag.name..":"..tag.attr.xmlns] = st.preserialize(tag);\r
-                               end\r
-                               return r;\r
-                       end\r
-               end\r
-       end;\r
-       set = function(self, user, data)\r
-               local xml = getXml(user, self.host);\r
-               local usere = xml and getUserElement(xml);\r
-               if usere then\r
-                       local private = usere:get_child("query", 'jabber:iq:private');\r
-                       if private then removeStanzaChild(usere, private); end\r
-                       if data and next(data) ~= nil then\r
-                               private = st.stanza("query", {xmlns='jabber:iq:private'});\r
-                               for _,tag in pairs(data) do\r
-                                       private:add_child(st.deserialize(tag));\r
-                               end\r
-                               usere:add_child(private);\r
-                       end\r
-                       return setXml(user, self.host, xml);\r
-               end\r
-               return true;\r
-       end;\r
-};\r
-\r
------------------------------\r
-local driver = {};\r
-driver.__index = driver;\r
-\r
-function driver:open(host, datastore, typ)\r
-       local cache_key = host.." "..datastore;\r
-       if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end\r
-       local instance = setmetatable({}, self);\r
-       instance.host = host;\r
-       instance.datastore = datastore;\r
-       local handler = handlers[datastore];\r
-       if not handler then return nil; end\r
-       for key,val in pairs(handler) do\r
-               instance[key] = val;\r
-       end\r
-       if instance.init then instance:init(); end\r
-       self.ds_cache[cache_key] = instance;\r
-       return instance;\r
-end\r
-\r
------------------------------\r
-local _M = {};\r
-\r
-function _M.new()\r
-       local instance = setmetatable({}, driver);\r
-       instance.__index = instance;\r
-       instance.ds_cache = {};\r
-       return instance;\r
-end\r
-\r
-return _M;\r
diff --git a/prosody b/prosody
index 446dbfb7344fb15263a1d9cc9d69e7d052be0051..9cc32cee4a740a85bbf76aad88d78b7a08cd98f7 100755 (executable)
--- a/prosody
+++ b/prosody
 
 -- Will be modified by configure script if run --
 
-CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR");
-CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
-CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR");
-CFG_DATADIR=os.getenv("PROSODY_DATADIR");
+CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR");
+CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR");
+CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR");
+CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR");
 
 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 
@@ -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)
index 230329325b39eaf702f3114bf4b15fbce6188040..d2af75a05b4fcd045fdc4953f7352d8d3c8592d6 100644 (file)
@@ -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
index 4c3ae981521e6ee2b109589fc729ba09af18cf45..c8366faf9ae68193ccc98767f8ea68fd62a4f047 100755 (executable)
 
 -- Will be modified by configure script if run --
 
-CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR");
-CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
-CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR");
-CFG_DATADIR=os.getenv("PROSODY_DATADIR");
+CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR");
+CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR");
+CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR");
+CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR");
 
 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 
@@ -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
@@ -564,8 +590,11 @@ function commands.about(arg)
                        module_versions[name] = module._VERSION;
                end
        end
+       if luaevent then
+               module_versions["libevent"] = luaevent.core.libevent_version();
+       end
        local sorted_keys = array.collect(keys(module_versions)):sort();
-       for _, name in ipairs(array.collect(keys(module_versions)):sort()) do
+       for _, name in ipairs(sorted_keys) do
                print(name..":"..string.rep(" ", longest_name-#name), module_versions[name]);
        end
        print("");
@@ -650,40 +679,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 +759,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 +781,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 +799,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 +807,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 +839,456 @@ 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",
+                       "vcard_compatibility",
+               });
+               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 jid, host_options in enabled_hosts() do
+                       local all_targets_ok, some_targets_ok = true, false;
+                       local node, host = jid_split(jid);
+                       
+                       local is_component = not not host_options.component_module;
+                       print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."...");
+                       if node then
+                               print("Only the domain part ("..host..") is used in DNS.")
+                       end
+                       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_targets_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 create_context = require "core.certmanager".create_context;
+               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 host_ssl_config = config.rawget(host, "ssl")
+                                       or config.rawget(host:match("%.(.*)"), "ssl");
+                               local global_ssl_config = config.rawget("*", "ssl");
+                               local ok, err, ssl_config = create_context(host, "server", host_ssl_config, global_ssl_config);
+                               if not ok then
+                                       print("  Error: "..err);
+                                       cert_ok = false
+                               elseif not ssl_config.certificate then
+                                       print("  No 'certificate' found for "..host)
+                                       cert_ok = false
+                               elseif not ssl_config.key then
+                                       print("  No 'key' found 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
+                                               elseif not cert:validat(os.time() + 86400) then
+                                                       print("    Certificate expires within one day.")
+                                                       cert_ok = false
+                                               elseif not cert:validat(os.time() + 86400*7) then
+                                                       print("    Certificate expires within one week.")
+                                               elseif not cert:validat(os.time() + 86400*31) then
+                                                       print("    Certificate expires within one month.")
+                                               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/json/fail1.json b/tests/json/fail1.json
new file mode 100644 (file)
index 0000000..6216b86
--- /dev/null
@@ -0,0 +1 @@
+"A JSON payload should be an object or array, not a string."
\ No newline at end of file
diff --git a/tests/json/fail10.json b/tests/json/fail10.json
new file mode 100644 (file)
index 0000000..5d8c004
--- /dev/null
@@ -0,0 +1 @@
+{"Extra value after close": true} "misplaced quoted value"
\ No newline at end of file
diff --git a/tests/json/fail11.json b/tests/json/fail11.json
new file mode 100644 (file)
index 0000000..76eb95b
--- /dev/null
@@ -0,0 +1 @@
+{"Illegal expression": 1 + 2}
\ No newline at end of file
diff --git a/tests/json/fail12.json b/tests/json/fail12.json
new file mode 100644 (file)
index 0000000..77580a4
--- /dev/null
@@ -0,0 +1 @@
+{"Illegal invocation": alert()}
\ No newline at end of file
diff --git a/tests/json/fail13.json b/tests/json/fail13.json
new file mode 100644 (file)
index 0000000..379406b
--- /dev/null
@@ -0,0 +1 @@
+{"Numbers cannot have leading zeroes": 013}
\ No newline at end of file
diff --git a/tests/json/fail14.json b/tests/json/fail14.json
new file mode 100644 (file)
index 0000000..0ed366b
--- /dev/null
@@ -0,0 +1 @@
+{"Numbers cannot be hex": 0x14}
\ No newline at end of file
diff --git a/tests/json/fail15.json b/tests/json/fail15.json
new file mode 100644 (file)
index 0000000..fc8376b
--- /dev/null
@@ -0,0 +1 @@
+["Illegal backslash escape: \x15"]
\ No newline at end of file
diff --git a/tests/json/fail16.json b/tests/json/fail16.json
new file mode 100644 (file)
index 0000000..3fe21d4
--- /dev/null
@@ -0,0 +1 @@
+[\naked]
\ No newline at end of file
diff --git a/tests/json/fail17.json b/tests/json/fail17.json
new file mode 100644 (file)
index 0000000..62b9214
--- /dev/null
@@ -0,0 +1 @@
+["Illegal backslash escape: \017"]
\ No newline at end of file
diff --git a/tests/json/fail18.json b/tests/json/fail18.json
new file mode 100644 (file)
index 0000000..edac927
--- /dev/null
@@ -0,0 +1 @@
+[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
\ No newline at end of file
diff --git a/tests/json/fail19.json b/tests/json/fail19.json
new file mode 100644 (file)
index 0000000..3b9c46f
--- /dev/null
@@ -0,0 +1 @@
+{"Missing colon" null}
\ No newline at end of file
diff --git a/tests/json/fail2.json b/tests/json/fail2.json
new file mode 100644 (file)
index 0000000..6b7c11e
--- /dev/null
@@ -0,0 +1 @@
+["Unclosed array"
\ No newline at end of file
diff --git a/tests/json/fail20.json b/tests/json/fail20.json
new file mode 100644 (file)
index 0000000..27c1af3
--- /dev/null
@@ -0,0 +1 @@
+{"Double colon":: null}
\ No newline at end of file
diff --git a/tests/json/fail21.json b/tests/json/fail21.json
new file mode 100644 (file)
index 0000000..6247457
--- /dev/null
@@ -0,0 +1 @@
+{"Comma instead of colon", null}
\ No newline at end of file
diff --git a/tests/json/fail22.json b/tests/json/fail22.json
new file mode 100644 (file)
index 0000000..a775258
--- /dev/null
@@ -0,0 +1 @@
+["Colon instead of comma": false]
\ No newline at end of file
diff --git a/tests/json/fail23.json b/tests/json/fail23.json
new file mode 100644 (file)
index 0000000..494add1
--- /dev/null
@@ -0,0 +1 @@
+["Bad value", truth]
\ No newline at end of file
diff --git a/tests/json/fail24.json b/tests/json/fail24.json
new file mode 100644 (file)
index 0000000..caff239
--- /dev/null
@@ -0,0 +1 @@
+['single quote']
\ No newline at end of file
diff --git a/tests/json/fail25.json b/tests/json/fail25.json
new file mode 100644 (file)
index 0000000..8b7ad23
--- /dev/null
@@ -0,0 +1 @@
+["     tab     character       in      string  "]
\ No newline at end of file
diff --git a/tests/json/fail26.json b/tests/json/fail26.json
new file mode 100644 (file)
index 0000000..845d26a
--- /dev/null
@@ -0,0 +1 @@
+["tab\   character\   in\  string\  "]
\ No newline at end of file
diff --git a/tests/json/fail27.json b/tests/json/fail27.json
new file mode 100644 (file)
index 0000000..6b01a2c
--- /dev/null
@@ -0,0 +1,2 @@
+["line
+break"]
\ No newline at end of file
diff --git a/tests/json/fail28.json b/tests/json/fail28.json
new file mode 100644 (file)
index 0000000..621a010
--- /dev/null
@@ -0,0 +1,2 @@
+["line\
+break"]
\ No newline at end of file
diff --git a/tests/json/fail29.json b/tests/json/fail29.json
new file mode 100644 (file)
index 0000000..47ec421
--- /dev/null
@@ -0,0 +1 @@
+[0e]
\ No newline at end of file
diff --git a/tests/json/fail3.json b/tests/json/fail3.json
new file mode 100644 (file)
index 0000000..168c81e
--- /dev/null
@@ -0,0 +1 @@
+{unquoted_key: "keys must be quoted"}
\ No newline at end of file
diff --git a/tests/json/fail30.json b/tests/json/fail30.json
new file mode 100644 (file)
index 0000000..8ab0bc4
--- /dev/null
@@ -0,0 +1 @@
+[0e+]
\ No newline at end of file
diff --git a/tests/json/fail31.json b/tests/json/fail31.json
new file mode 100644 (file)
index 0000000..1cce602
--- /dev/null
@@ -0,0 +1 @@
+[0e+-1]
\ No newline at end of file
diff --git a/tests/json/fail32.json b/tests/json/fail32.json
new file mode 100644 (file)
index 0000000..45cba73
--- /dev/null
@@ -0,0 +1 @@
+{"Comma instead if closing brace": true,
\ No newline at end of file
diff --git a/tests/json/fail33.json b/tests/json/fail33.json
new file mode 100644 (file)
index 0000000..ca5eb19
--- /dev/null
@@ -0,0 +1 @@
+["mismatch"}
\ No newline at end of file
diff --git a/tests/json/fail4.json b/tests/json/fail4.json
new file mode 100644 (file)
index 0000000..9de168b
--- /dev/null
@@ -0,0 +1 @@
+["extra comma",]
\ No newline at end of file
diff --git a/tests/json/fail5.json b/tests/json/fail5.json
new file mode 100644 (file)
index 0000000..ddf3ce3
--- /dev/null
@@ -0,0 +1 @@
+["double extra comma",,]
\ No newline at end of file
diff --git a/tests/json/fail6.json b/tests/json/fail6.json
new file mode 100644 (file)
index 0000000..ed91580
--- /dev/null
@@ -0,0 +1 @@
+[   , "<-- missing value"]
\ No newline at end of file
diff --git a/tests/json/fail7.json b/tests/json/fail7.json
new file mode 100644 (file)
index 0000000..8a96af3
--- /dev/null
@@ -0,0 +1 @@
+["Comma after the close"],
\ No newline at end of file
diff --git a/tests/json/fail8.json b/tests/json/fail8.json
new file mode 100644 (file)
index 0000000..b28479c
--- /dev/null
@@ -0,0 +1 @@
+["Extra close"]]
\ No newline at end of file
diff --git a/tests/json/fail9.json b/tests/json/fail9.json
new file mode 100644 (file)
index 0000000..5815574
--- /dev/null
@@ -0,0 +1 @@
+{"Extra comma": true,}
\ No newline at end of file
diff --git a/tests/json/pass1.json b/tests/json/pass1.json
new file mode 100644 (file)
index 0000000..70e2685
--- /dev/null
@@ -0,0 +1,58 @@
+[
+    "JSON Test Pattern pass1",
+    {"object with 1 member":["array with 1 element"]},
+    {},
+    [],
+    -42,
+    true,
+    false,
+    null,
+    {
+        "integer": 1234567890,
+        "real": -9876.543210,
+        "e": 0.123456789e-12,
+        "E": 1.234567890E+34,
+        "":  23456789012E66,
+        "zero": 0,
+        "one": 1,
+        "space": " ",
+        "quote": "\"",
+        "backslash": "\\",
+        "controls": "\b\f\n\r\t",
+        "slash": "/ & \/",
+        "alpha": "abcdefghijklmnopqrstuvwyz",
+        "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
+        "digit": "0123456789",
+        "0123456789": "digit",
+        "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
+        "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
+        "true": true,
+        "false": false,
+        "null": null,
+        "array":[  ],
+        "object":{  },
+        "address": "50 St. James Street",
+        "url": "http://www.JSON.org/",
+        "comment": "// /* <!-- --",
+        "# -- --> */": " ",
+        " s p a c e d " :[1,2 , 3
+
+,
+
+4 , 5        ,          6           ,7        ],"compact":[1,2,3,4,5,6,7],
+        "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
+        "quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
+        "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
+: "A key can be any string"
+    },
+    0.5 ,98.6
+,
+99.44
+,
+
+1066,
+1e1,
+0.1e1,
+1e-1,
+1e00,2e+00,2e-00
+,"rosebud"]
\ No newline at end of file
diff --git a/tests/json/pass2.json b/tests/json/pass2.json
new file mode 100644 (file)
index 0000000..d3c63c7
--- /dev/null
@@ -0,0 +1 @@
+[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
\ No newline at end of file
diff --git a/tests/json/pass3.json b/tests/json/pass3.json
new file mode 100644 (file)
index 0000000..4528d51
--- /dev/null
@@ -0,0 +1,6 @@
+{
+    "JSON Test Pattern pass3": {
+        "The outermost value": "must be an object or array.",
+        "In this test": "It is an object."
+    }
+}
index 7dceeaede07381d06078f3e0bac0494f177ba785..100dbe8302bb2d8e03c87c7e1c5f36232c8f1827 100644 (file)
@@ -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");
index d93cd39b31941d73efeb31c062ce1352a267fc70..bfb13d0089aed7191ca37a1bf4731bf205d9d519 100755 (executable)
@@ -1,3 +1,3 @@
 #!/bin/sh
 rm reports/*.report
-lua test.lua $*
+exec lua test.lua $*
index bb11ab267bd487584e8782b70d47f83af20728cd..9ab2cad87d80c827025b0831c0d9e3c6efa36f97 100644 (file)
@@ -1,26 +1,33 @@
 -- 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.json"
        dotest "util.stanza"
        dotest "util.sasl.scram"
-       
+       dotest "util.cache"
+       dotest "util.throttle"
+       dotest "util.uuid"
+       dotest "util.random"
+       dotest "util.xml"
+       dotest "util.xmppstream"
+
        dosingletest("test_sasl.lua", "latin1toutf8");
        dosingletest("test_utf8.lua", "valid");
 end
@@ -39,6 +46,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 +85,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 +124,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 +138,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 +181,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 +201,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 +210,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 +249,5 @@ function new_line_coverage_monitor(file)
 end
 
 run_all_tests()
+
+os.exit(tests_passed and 0 or 1);
index 132dfc7456bd98360556bf3c4ec52328c6f908e4..5bd469c68ce39f28ec6689f6a3fc0e4590c67086 100644 (file)
@@ -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 (file)
index 9498875..0000000
+++ /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
index b49c7da6cd2827790d538ee66c40e6dabbf30c2c..d2dbf8301565ef0384c12adf3b78ae148db72b7f 100644 (file)
@@ -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);
index 0a93694f90257100ecd135bf0c0bf151108e29ba..ca6b78fce385c26b166860e900aa6eb76dd58c90 100644 (file)
@@ -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_net_http.lua b/tests/test_net_http.lua
deleted file mode 100644 (file)
index e68f96e..0000000
+++ /dev/null
@@ -1,37 +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.
---
-
-function urlencode(urlencode)
-       assert_equal(urlencode("helloworld123"), "helloworld123", "Normal characters not escaped");
-       assert_equal(urlencode("hello world"), "hello%20world", "Spaces escaped");
-       assert_equal(urlencode("This & that = something"), "This%20%26%20that%20%3d%20something", "Important URL chars escaped");
-end
-
-function urldecode(urldecode)
-       assert_equal("helloworld123", urldecode("helloworld123"), "Normal characters not escaped");
-       assert_equal("hello world", urldecode("hello%20world"), "Spaces escaped");
-       assert_equal("This & that = something", urldecode("This%20%26%20that%20%3d%20something"), "Important URL chars escaped");
-       assert_equal("This & that = something", urldecode("This%20%26%20that%20%3D%20something"), "Important URL chars escaped");
-end
-
-function formencode(formencode)
-       assert_equal(formencode({ { name = "one", value = "1"}, { name = "two", value = "2" } }), "one=1&two=2", "Form encoded");
-       assert_equal(formencode({ { name = "one two", value = "1"}, { name = "two one&", value = "2" } }), "one+two=1&two+one%26=2", "Form encoded");
-end
-
-function formdecode(formdecode)
-       local t = formdecode("one=1&two=2");
-       assert_table(t[1]);
-       assert_equal(t[1].name, "one"); assert_equal(t[1].value, "1");
-       assert_table(t[2]);
-       assert_equal(t[2].name, "two"); assert_equal(t[2].value, "2");
-
-       local t = formdecode("one+two=1&two+one%26=2");
-       assert_equal(t[1].name, "one two"); assert_equal(t[1].value, "1");
-       assert_equal(t[2].name, "two one&"); assert_equal(t[2].value, "2");
-end
index 271fa69a5a3b6bd1cad49c4f8d6b17dabcb37e57..dd63c5a00ec403dab610f31d4676ef803bcbcdae 100644 (file)
@@ -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 (file)
index 0000000..78666ed
--- /dev/null
@@ -0,0 +1,298 @@
+function new(new)
+       local c = new(5);
+
+       local function expect_kv(key, value, actual_key, actual_value)
+               assert_equal(key, actual_key, "key incorrect");
+               assert_equal(value, actual_value, "value incorrect");
+       end
+
+       expect_kv(nil, nil, c:head());
+       expect_kv(nil, nil, c:tail());
+
+       assert_equal(c:count(), 0);
+       
+       c:set("one", 1)
+       assert_equal(c:count(), 1);
+       expect_kv("one", 1, c:head());
+       expect_kv("one", 1, c:tail());
+
+       c:set("two", 2)
+       expect_kv("two", 2, c:head());
+       expect_kv("one", 1, c:tail());
+
+       c:set("three", 3)
+       expect_kv("three", 3, c:head());
+       expect_kv("one", 1, c:tail());
+
+       c:set("four", 4)
+       c:set("five", 5);
+       assert_equal(c:count(), 5);
+       expect_kv("five", 5, c:head());
+       expect_kv("one", 1, c:tail());
+       
+       c:set("foo", nil);
+       assert_equal(c:count(), 5);
+       expect_kv("five", 5, c:head());
+       expect_kv("one", 1, c:tail());
+       
+       assert_equal(c:get("one"), 1);
+       expect_kv("five", 5, c:head());
+       expect_kv("one", 1, c:tail());
+
+       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);
+       expect_kv("six", 6, c:head());
+       expect_kv("two", 2, c:tail());
+       
+       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
+                       -- Sanity check for what we're evicting
+                       assert_equal(_key, "a");
+                       assert_equal(_value, 1);
+                       -- We're going to block eviction of this key/value, so set to nil...
+                       evicted_key, evicted_value = nil, nil;
+                       -- Returning false to block eviction
+                       return false
+               end
+       end);
+       local function set(k, v, should_evict_key, should_evict_value)
+               evicted_key, evicted_value = nil, nil;
+               local ret = c3:set(k, v);
+               assert_equal(evicted_key, should_evict_key);
+               assert_equal(evicted_value, should_evict_value);
+               return ret;
+       end
+       set("a", 1)
+       set("a", 1)
+       set("a", 1)
+       set("a", 1)
+       set("a", 1)
+
+       -- Our on_evict prevents "a" from being evicted, causing this to fail...
+       assert_equal(set("b", 2), false, "Failed to prevent eviction, or signal result");
+       
+       expect_kv("a", 1, c3:head());
+       expect_kv("a", 1, c3:tail());
+       
+       -- 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);
+
+
+       local c4 = new(3, false);
+       
+       assert_equal(c4:set("a", 1), true);
+       assert_equal(c4:set("a", 1), true);
+       assert_equal(c4:set("a", 1), true);
+       assert_equal(c4:set("a", 1), true);
+       assert_equal(c4:set("b", 2), true);
+       assert_equal(c4:set("c", 3), true);
+       assert_equal(c4:set("d", 4), false);
+       assert_equal(c4:set("d", 4), false);
+       assert_equal(c4:set("d", 4), false);
+
+       expect_kv("c", 3, c4:head());
+       expect_kv("a", 1, c4:tail());
+
+       local c5 = new(3, function (k, v)
+               if k == "a" then
+                       return nil;
+               elseif k == "b" then
+                       return true;
+               end
+               return false;
+       end);
+       
+       assert_equal(c5:set("a", 1), true);
+       assert_equal(c5:set("a", 1), true);
+       assert_equal(c5:set("a", 1), true);
+       assert_equal(c5:set("a", 1), true);
+       assert_equal(c5:set("b", 2), true);
+       assert_equal(c5:set("c", 3), true);
+       assert_equal(c5:set("d", 4), true); -- "a" evicted (cb returned nil)
+       assert_equal(c5:set("d", 4), true); -- nop
+       assert_equal(c5:set("d", 4), true); -- nop
+       assert_equal(c5:set("e", 5), true); -- "b" evicted (cb returned true)
+       assert_equal(c5:set("f", 6), false); -- "c" won't evict (cb returned false)
+
+       expect_kv("e", 5, c5:head());
+       expect_kv("c", 3, c5:tail());
+
+end
diff --git a/tests/test_util_http.lua b/tests/test_util_http.lua
new file mode 100644 (file)
index 0000000..a195df6
--- /dev/null
@@ -0,0 +1,37 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+function urlencode(urlencode)
+       assert_equal(urlencode("helloworld123"), "helloworld123", "Normal characters not escaped");
+       assert_equal(urlencode("hello world"), "hello%20world", "Spaces escaped");
+       assert_equal(urlencode("This & that = something"), "This%20%26%20that%20%3d%20something", "Important URL chars escaped");
+end
+
+function urldecode(urldecode)
+       assert_equal("helloworld123", urldecode("helloworld123"), "Normal characters not escaped");
+       assert_equal("hello world", urldecode("hello%20world"), "Spaces escaped");
+       assert_equal("This & that = something", urldecode("This%20%26%20that%20%3d%20something"), "Important URL chars escaped");
+       assert_equal("This & that = something", urldecode("This%20%26%20that%20%3D%20something"), "Important URL chars escaped");
+end
+
+function formencode(formencode)
+       assert_equal(formencode({ { name = "one", value = "1"}, { name = "two", value = "2" } }), "one=1&two=2", "Form encoded");
+       assert_equal(formencode({ { name = "one two", value = "1"}, { name = "two one&", value = "2" } }), "one+two=1&two+one%26=2", "Form encoded");
+end
+
+function formdecode(formdecode)
+       local t = formdecode("one=1&two=2");
+       assert_table(t[1]);
+       assert_equal(t[1].name, "one"); assert_equal(t[1].value, "1");
+       assert_table(t[2]);
+       assert_equal(t[2].name, "two"); assert_equal(t[2].value, "2");
+
+       local t = formdecode("one+two=1&two+one%26=2");
+       assert_equal(t[1].name, "one two"); assert_equal(t[1].value, "1");
+       assert_equal(t[2].name, "two one&"); assert_equal(t[2].value, "2");
+end
diff --git a/tests/test_util_ip.lua b/tests/test_util_ip.lua
new file mode 100644 (file)
index 0000000..0ded112
--- /dev/null
@@ -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
index a817e644c1a5d4e16ec6b33afb42ea7c5600928c..c697e63f5c33c85f66374092f52ad8a78a555fbe 100644 (file)
@@ -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.
 --
@@ -71,3 +71,73 @@ function compare(compare)
        assert_equal(compare("user@other-host", "host"), false, "host should not match");
        assert_equal(compare("user@other-host", "user@host"), false, "host should not match");
 end
+
+function node(node)
+       local function test(jid, expected_node)
+               assert_equal(node(jid), expected_node, "Unexpected node for "..tostring(jid));
+       end
+
+       test("example.com", nil);
+       test("foo.example.com", nil);
+       test("foo.example.com/resource", nil);
+       test("foo.example.com/some resource", nil);
+       test("foo.example.com/some@resource", nil);
+
+       test("foo@foo.example.com/some@resource", "foo");
+       test("foo@example/some@resource", "foo");
+
+       test("foo@example/@resource", "foo");
+       test("foo@example@resource", nil);
+       test("foo@example", "foo");
+       test("foo", nil);
+
+       test(nil, nil);
+end
+
+function host(host)
+       local function test(jid, expected_host)
+               assert_equal(host(jid), expected_host, "Unexpected host for "..tostring(jid));
+       end
+
+       test("example.com", "example.com");
+       test("foo.example.com", "foo.example.com");
+       test("foo.example.com/resource", "foo.example.com");
+       test("foo.example.com/some resource", "foo.example.com");
+       test("foo.example.com/some@resource", "foo.example.com");
+
+       test("foo@foo.example.com/some@resource", "foo.example.com");
+       test("foo@example/some@resource", "example");
+
+       test("foo@example/@resource", "example");
+       test("foo@example@resource", nil);
+       test("foo@example", "example");
+       test("foo", "foo");
+
+       test(nil, nil);
+end
+
+function resource(resource)
+       local function test(jid, expected_resource)
+               assert_equal(resource(jid), expected_resource, "Unexpected resource for "..tostring(jid));
+       end
+
+       test("example.com", nil);
+       test("foo.example.com", nil);
+       test("foo.example.com/resource", "resource");
+       test("foo.example.com/some resource", "some resource");
+       test("foo.example.com/some@resource", "some@resource");
+
+       test("foo@foo.example.com/some@resource", "some@resource");
+       test("foo@example/some@resource", "some@resource");
+
+       test("foo@example/@resource", "@resource");
+       test("foo@example@resource", nil);
+       test("foo@example", nil);
+       test("foo", nil);
+       test("/foo", nil);
+       test("@x/foo", nil);
+       test("@/foo", nil);
+
+       test(nil, nil);
+end
+
diff --git a/tests/test_util_json.lua b/tests/test_util_json.lua
new file mode 100644 (file)
index 0000000..2c1a9ce
--- /dev/null
@@ -0,0 +1,21 @@
+
+function encode(encode, json)
+       local function test(f, j, e)
+               if e then
+                       assert_equal(f(j), e);
+               end
+               assert_equal(f(j), f(json.decode(f(j))));
+       end
+       test(encode, json.null, "null")
+       test(encode, {}, "{}")
+       test(encode, {a=1});
+       test(encode, {a={1,2,3}});
+       test(encode, {1}, "[1]");
+end
+
+function decode(decode)
+       local empty_array = decode("[]");
+       assert_equal(type(empty_array), "table");
+       assert_equal(#empty_array, 0);
+       assert_equal(next(empty_array), nil);
+end
diff --git a/tests/test_util_json.sh b/tests/test_util_json.sh
new file mode 100755 (executable)
index 0000000..bbbd132
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+export LUA_PATH="../?.lua;;"
+export LUA_CPATH="../?.so;;"
+
+#set -x
+
+if ! which "$RUNWITH"; then
+       echo "Unable to find interpreter $RUNWITH";
+       exit 1;
+fi
+
+if ! $RUNWITH -e 'assert(require"util.json")' 2>/dev/null; then
+       echo "Unable to find util.json";
+       exit 1;
+fi
+
+FAIL=0
+
+for f in json/pass*.json; do
+       if ! $RUNWITH -e 'local j=require"util.json" assert(j.decode(io.read("*a"))~=nil)' <"$f" 2>/dev/null; then
+               echo "Failed to decode valid JSON: $f";
+               FAIL=1
+       fi
+done
+
+for f in json/fail*.json; do
+       if ! $RUNWITH -e 'local j=require"util.json" assert(j.decode(io.read("*a"))==nil)' <"$f" 2>/dev/null; then
+               echo "Invalid JSON decoded without error: $f";
+               FAIL=1
+       fi
+done
+
+if [ "$FAIL" == "1" ]; then
+       echo "JSON tests failed"
+       exit 1;
+fi
+
+exit 0;
index ed10b1283011b6ec64d60ea09d9b138e18edb92c..71a834505b37a13df37744608318b4209759fd18 100644 (file)
@@ -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 (file)
index 0000000..b0e1fa3
--- /dev/null
@@ -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 (file)
index 0000000..79572ef
--- /dev/null
@@ -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 (file)
index 18ae310..0000000
+++ /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 (file)
index 0000000..bb73e92
--- /dev/null
@@ -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
index fce26f3a7267da0575495e0962edf47341a2f96c..e5c96425c250a002e681dcb134dc579152e26e56 100644 (file)
@@ -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,10 +18,125 @@ 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");
        assert_equal(stanza2.attr.a, "a", "Deserialized stanza retains attributes");
        assert_table(getmetatable(stanza2), "Deserialized stanza has metatable");
 end
+
+function stanza(stanza)
+       local s = stanza("foo", { xmlns = "myxmlns", a = "attr-a" });
+       assert_equal(s.name, "foo");
+       assert_equal(s.attr.xmlns, "myxmlns");
+       assert_equal(s.attr.a, "attr-a");
+
+       local s1 = stanza("s1");
+       assert_equal(s1.name, "s1");
+       assert_equal(s1.attr.xmlns, nil);
+       assert_equal(#s1, 0);
+       assert_equal(#s1.tags, 0);
+       
+       s1:tag("child1");
+       assert_equal(#s1.tags, 1);
+       assert_equal(s1.tags[1].name, "child1");
+
+       s1:tag("grandchild1"):up();
+       assert_equal(#s1.tags, 1);
+       assert_equal(s1.tags[1].name, "child1");
+       assert_equal(#s1.tags[1], 1);
+       assert_equal(s1.tags[1][1].name, "grandchild1");
+       
+       s1:up():tag("child2");
+       assert_equal(#s1.tags, 2, tostring(s1));
+       assert_equal(s1.tags[1].name, "child1");
+       assert_equal(s1.tags[2].name, "child2");
+       assert_equal(#s1.tags[1], 1);
+       assert_equal(s1.tags[1][1].name, "grandchild1");
+
+       s1:up():text("Hello world");
+       assert_equal(#s1.tags, 2);
+       assert_equal(#s1, 3);
+       assert_equal(s1.tags[1].name, "child1");
+       assert_equal(s1.tags[2].name, "child2");
+       assert_equal(#s1.tags[1], 1);
+       assert_equal(s1.tags[1][1].name, "grandchild1");
+end
+
+function message(message)
+       local m = message();
+       assert_equal(m.name, "message");
+end
+
+function iq(iq)
+       local i = iq();
+       assert_equal(i.name, "iq");
+end
+
+function presence(presence)
+       local p = presence();
+       assert_equal(p.name, "presence");
+end
+
+function reply(reply, _M)
+       -- Test stanza
+       local s = _M.stanza("s", { to = "touser", from = "fromuser", id = "123" })
+               :tag("child1");
+       -- Make reply stanza
+       local r = reply(s);
+       assert_equal(r.name, s.name);
+       assert_equal(r.id, s.id);
+       assert_equal(r.attr.to, s.attr.from);
+       assert_equal(r.attr.from, s.attr.to);
+       assert_equal(#r.tags, 0, "A reply should not include children of the original stanza");
+
+       -- Test stanza
+       local s = _M.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" })
+               :tag("child1");
+       -- Make reply stanza
+       local r = reply(s);
+       assert_equal(r.name, s.name);
+       assert_equal(r.id, s.id);
+       assert_equal(r.attr.to, s.attr.from);
+       assert_equal(r.attr.from, s.attr.to);
+       assert_equal(r.attr.type, "result");
+       assert_equal(#r.tags, 0, "A reply should not include children of the original stanza");
+
+       -- Test stanza
+       local s = _M.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "set" })
+               :tag("child1");
+       -- Make reply stanza
+       local r = reply(s);
+       assert_equal(r.name, s.name);
+       assert_equal(r.id, s.id);
+       assert_equal(r.attr.to, s.attr.from);
+       assert_equal(r.attr.from, s.attr.to);
+       assert_equal(r.attr.type, "result");
+       assert_equal(#r.tags, 0, "A reply should not include children of the original stanza");
+end
+
+function error_reply(error_reply, _M)
+       -- Test stanza
+       local s = _M.stanza("s", { to = "touser", from = "fromuser", id = "123" })
+               :tag("child1");
+       -- Make reply stanza
+       local r = error_reply(s);
+       assert_equal(r.name, s.name);
+       assert_equal(r.id, s.id);
+       assert_equal(r.attr.to, s.attr.from);
+       assert_equal(r.attr.from, s.attr.to);
+       assert_equal(#r.tags, 1);
+       
+       -- Test stanza
+       local s = _M.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" })
+               :tag("child1");
+       -- Make reply stanza
+       local r = error_reply(s);
+       assert_equal(r.name, s.name);
+       assert_equal(r.id, s.id);
+       assert_equal(r.attr.to, s.attr.from);
+       assert_equal(r.attr.from, s.attr.to);
+       assert_equal(r.attr.type, "error");
+       assert_equal(#r.tags, 1);
+end
diff --git a/tests/test_util_throttle.lua b/tests/test_util_throttle.lua
new file mode 100644 (file)
index 0000000..582f499
--- /dev/null
@@ -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 (file)
index 0000000..d3f72bb
--- /dev/null
@@ -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/test_util_xml.lua b/tests/test_util_xml.lua
new file mode 100644 (file)
index 0000000..ba44da1
--- /dev/null
@@ -0,0 +1,12 @@
+function parse(parse)
+       local x =
+[[<x xmlns:a="b">
+       <y xmlns:a="c"> <!-- this overwrites 'a' -->
+           <a:z/>
+       </y>
+       <a:z/> <!-- prefix 'a' is nil here, but should be 'b' -->
+</x>
+]]
+       local stanza = parse(x);
+       assert_equal(stanza.tags[2].attr.xmlns, "b");
+end
diff --git a/tests/test_util_xmppstream.lua b/tests/test_util_xmppstream.lua
new file mode 100644 (file)
index 0000000..791cf99
--- /dev/null
@@ -0,0 +1,83 @@
+function new(new_stream, _M)
+       local function test(xml, expect_success, ex)
+               local stanzas = {};
+               local session = { notopen = true };
+               local callbacks = {
+                       stream_ns = "streamns";
+                       stream_tag = "stream";
+                       default_ns = "stanzans";
+                       streamopened = function (_session)
+                               assert_equal(session, _session);
+                               assert_equal(session.notopen, true);
+                               _session.notopen = nil;
+                               return true;
+                       end;
+                       handlestanza = function (_session, stanza)
+                               assert_equal(session, _session);
+                               assert_equal(_session.notopen, nil);
+                               table.insert(stanzas, stanza);
+                       end;
+                       streamclosed = function (_session)
+                               assert_equal(session, _session);
+                               assert_equal(_session.notopen, nil);
+                               _session.notopen = nil;
+                       end;
+               }
+               if type(ex) == "table" then
+                       for k, v in pairs(ex) do
+                               if k ~= "_size_limit" then
+                                       callbacks[k] = v;
+                               end
+                       end
+               end
+               local stream = new_stream(session, callbacks, size_limit);
+               local ok, err = pcall(function ()
+                       assert(stream:feed(xml));
+               end);
+
+               if ok and type(expect_success) == "function" then
+                       expect_success(stanzas);
+               end
+               assert_equal(not not ok, not not expect_success, "Expected "..(expect_success and ("success ("..tostring(err)..")") or "failure"));
+       end
+
+       local function test_stanza(stanza, expect_success, ex)
+               return test([[<stream:stream xmlns:stream="streamns" xmlns="stanzans">]]..stanza, expect_success, ex);
+       end
+
+       test([[<stream:stream xmlns:stream="streamns"/>]], true);
+       test([[<stream xmlns="streamns"/>]], true);
+
+       test([[<stream1 xmlns="streamns"/>]], false);
+       test([[<stream xmlns="streamns1"/>]], false);
+       test("<>", false);
+
+       test_stanza("<message/>", function (stanzas)
+               assert_equal(#stanzas, 1);
+               assert_equal(stanzas[1].name, "message");
+       end);
+       test_stanza("< message>>>>/>\n", false);
+
+       test_stanza([[<x xmlns:a="b">
+               <y xmlns:a="c">
+                       <a:z/>
+               </y>
+               <a:z/>
+       </x>]], function (stanzas)
+               assert_equal(#stanzas, 1);
+               local s = stanzas[1];
+               assert_equal(s.name, "x");
+               assert_equal(#s.tags, 2);
+
+               assert_equal(s.tags[1].name, "y");
+               assert_equal(s.tags[1].attr.xmlns, nil);
+
+               assert_equal(s.tags[1].tags[1].name, "z");
+               assert_equal(s.tags[1].tags[1].attr.xmlns, "c");
+
+               assert_equal(s.tags[2].name, "z");
+               assert_equal(s.tags[2].attr.xmlns, "b");
+
+               assert_equal(s.namespaces, nil);
+       end);
+end
index 35facd4e45ae0472e3a9eeb0908920b778f5fade..c133e33233b5c45903bef1c16d38b1a95429d74f 100644 (file)
@@ -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.
 --
index 069b5161fde917f5b66c2d93f281fe0ff9a3c886..46a48f57fca2f8acf1d5556ee35ce76cf0c95240 100755 (executable)
@@ -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.
 --
index 40be8190dbe9e6fc13ae798a5ef230e7f239cc32..69c8cfe813fed6f0880d3f246e13898b50fa3cf5 100644 (file)
@@ -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.
 --
index 174585d338433d3c9db14a4f722ed4c029e41c68..25c38bcf4d72d1756c18543af1ee9d41ac33c529 100644 (file)
@@ -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.
 --
index 386bdcf0ab625fc3412e95fed5df3d59b18fb858..e43dc29602f7345eb0afbffed820ee1d42696efa 100644 (file)
@@ -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
index 27b5835e81cb7ae5a1170f7816d66f425bc21964..180ae910725cf38d32852310bdd8875563da3afd 100644 (file)
@@ -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();
index 7c933b88118a748cc43f82f01872895b0ce3518a..b86e9892a0da9630637496c35614ea2f5ce32586 100644 (file)
@@ -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]);
index 5ef47602a9b3b7254ef82accbbb7e3fc74ba76ee..cd3e62e5d850ed8b71c35fd8e7c2e11c39163fc6 100644 (file)
@@ -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.
 --
index 81c9863b73b9d53fa2c2a4e2614a0af9a1778c26..521851e93f3ac8bcc54850fb9d619e7f4ada345d 100755 (executable)
@@ -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.
 --
index 3a1ba3f2af1f69f506c6ddfb93c5c4f51837e90c..9c6c377c14a56fd4816729ebf4f45b77a9e5e17f 100644 (file)
@@ -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 (file)
index 0000000..735135f
--- /dev/null
@@ -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;
+}
+
index 91826ca42f3b682fbd196b5d0ca1e7ad2d256ec0..35677095181fbf990c2c56a61527442e9009bcbf 100644 (file)
@@ -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.
 --
 #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 chars = 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_StateL) {
        lua_pushboolean(L, check_utf8(L, 1, NULL) != NULL);
        return 1;
 }
 
-static int Lutf8_length(lua_State *L) {
+static int Lutf8_length(lua_StateL) {
        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 charinput;
        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;
+UStringPrepProfileicu_nameprep;
+UStringPrepProfileicu_nodeprep;
+UStringPrepProfile* icu_resourceprep;
+UStringPrepProfileicu_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 chars;
        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 chars = 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 chars = 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 chars = 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;
 }
index 33041e83bdb8929f3673d860a408a3e89b22c064..ecab2e328e892833d8d5780066e6afaf8c8f88c8 100644 (file)
@@ -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 charhex_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;
+       voidctx, *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_StateL) {
        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 charstr = luaL_checklstring(L, 1, &str_len);
+       const charsalt = luaL_checklstring(L, 2, &salt_len);
+       charsalt2;
        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;
 }
index e307c628146351873175d0dd92eb25d66188f73d..3ccc7618e95dfbb1292f7f25e7b14a52cc1bc6cd 100644 (file)
 #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 ifaddrsaddr = 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 chartmp = 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;
 }
index df814c2899fc98d90ccfed116cd1ada3cd3a2d1d..1b69852d2a703cacf2619adeffe821ad5ddb3b94 100644 (file)
 #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 passwdp;
                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 groupg;
                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 passwdp;
 
-       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_StateL) {
        int arguments = lua_gettop(L);
-       const char *resource = NULL;
+       const charresource = 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 (file)
index 0000000..f5fa136
--- /dev/null
@@ -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;
+}
index 961d2d3e06acda484613a0ebdfc54a8f25625586..725555fa58ec7b79b9dd95f029871de2dc3b79dd 100644 (file)
@@ -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>
 #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_StateLsig = 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_eventnext_event;
+}signal_queue = NULL;
 
-static struct signal_event *last_event = NULL;
+static struct signal_eventlast_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 (file)
index 0000000..6ad45dc
--- /dev/null
@@ -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;
+}
index 3d14ca956496c4cd8a02103182c7a3b356016b1a..4fcbf21ea5bed5cfc72966584a24e3358d68c7ff 100644 (file)
@@ -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.
 --
 #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_StateL, 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_StateL) {
        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_StateL) {
        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_StateL) {
+       lua_newtable(L);
+       luaL_setfuncs(L, Reg, 0);
        lua_pushliteral(L, "-3.14");
-       lua_settable(L,-3);
+       lua_setfield(L, -2, "version");
        return 1;
 }
index 2d58e7fb72d623169faa0deeada3dac881154462..3ddc97f610172008a3046c75a7a6a0b9aaf6d258 100644 (file)
@@ -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/cache.lua b/util/cache.lua
new file mode 100644 (file)
index 0000000..54f3e10
--- /dev/null
@@ -0,0 +1,129 @@
+
+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
+       if self._count == self.size then
+               local tail = self._tail;
+               local on_evict, evicted_key, evicted_value = self._on_evict, tail.key, tail.value;
+               if on_evict ~= nil and (on_evict == false or on_evict(evicted_key, evicted_value) == false) then
+                       -- Cache is full, and we're not allowed to evict
+                       return false;
+               end
+               _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);
+       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:values()
+       local m = self._head;
+       return function ()
+               if not m then
+                       return;
+               end
+               local v = m.value;
+               m = m.next;
+               return v;
+       end
+end
+
+function cache_methods:count()
+       return self._count;
+end
+
+function cache_methods:head()
+       local head = self._head;
+       if not head then return nil, nil; end
+       return head.key, head.value;
+end
+
+function cache_methods:tail()
+       local tail = self._tail;
+       if not tail then return nil, nil; end
+       return tail.key, tail.value;
+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;
+}
index a61e740375048b406b608a7a79f3e8b1365d9e10..cd5ff9c0efdddfb6dd8fbb663a67e4464ec5acdd 100644 (file)
@@ -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;
+};
index ee37157a48f268615721c9a99fa0b3b8d103d755..79b4d1a41b923cf6bd6cb539102c2cf20a968ace 100644 (file)
@@ -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;
+};
 
 
 --[=[
index c69ecd25dd3815a4db14a05285f73ac72bf1360d..fb9ba3a4ce6283f6eb33abce53a218f692cc96c6 100644 (file)
@@ -17,7 +17,9 @@ local io_open = io.open;
 local os_remove = os.remove;
 local os_rename = os.rename;
 local tonumber = tonumber;
+local tostring = tostring;
 local next = next;
+local type = type;
 local t_insert = table.insert;
 local t_concat = table.concat;
 local envloadfile = require"util.envload".envloadfile;
@@ -43,7 +45,7 @@ pcall(function()
        fallocate = pposix.fallocate or fallocate;
 end);
 
-module "datamanager"
+local _ENV = nil;
 
 ---- utils -----
 local encode, decode;
@@ -74,7 +76,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 +89,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 +108,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 +121,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");
@@ -144,24 +146,26 @@ end
 local function atomic_store(filename, data)
        local scratch = filename.."~";
        local f, ok, msg;
-       repeat
-               f, msg = io_open(scratch, "w");
-               if not f then break end
 
-               ok, msg = f:write(data);
-               if not ok then break end
+       f, msg = io_open(scratch, "w");
+       if not f then
+               return nil, msg;
+       end
 
-               ok, msg = f:close();
-               f = nil; -- no longer valid
-               if not ok then break end
+       ok, msg = f:write(data);
+       if not ok then
+               f:close();
+               os_remove(scratch);
+               return nil, msg;
+       end
 
-               return os_rename(scratch, filename);
-       until false;
+       ok, msg = f:close();
+       if not ok then
+               os_remove(scratch);
+               return nil, msg;
+       end
 
-       -- Cleanup
-       if f then f:close(); end
-       os_remove(scratch);
-       return nil, msg;
+       return os_rename(scratch, filename);
 end
 
 if prosody and prosody.platform ~= "posix" then
@@ -176,7 +180,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 +214,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 +293,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 +321,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 +340,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 +380,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 +400,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;
+};
index a1f62a48563ed48fc2d30c5c1f5e8b94c66e0e17..abb4e867b2aaa51ef2c254a963dcdc019ac69310 100644 (file)
@@ -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.
 --
 local os_date = os.date;
 local os_time = os.time;
 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 +53,10 @@ function parse(s)
        end
 end
 
-return _M;
+return {
+       date     = date;
+       datetime = datetime;
+       time     = time;
+       legacy   = legacy;
+       parse    = parse;
+};
index bff0e347a22fa7b58676e280484c9e017bc8f24b..00f476d03fdb1352a2a19640e70a6008442b78d7 100644 (file)
@@ -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;
+};
index 4d50cf63a17452d76f17891cdba3f9e57d5eec51..b3f072573e443bf97e1f6a7a20b7892dfd681da9 100644 (file)
@@ -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,10 +98,10 @@ 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
+               if err:match("module '[^']*' not found") then
                        missingdep("util.encodings", { ["Windows"] = "Make sure you have encodings.dll from the Prosody distribution in util/";
                                                ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/encodings.so";
                                        });
@@ -120,7 +118,7 @@ function check_dependencies()
 
        local hashes, err = softreq "util.hashes"
        if not hashes then
-               if err:match("not found") then
+               if err:match("module '[^']*' not found") then
                        missingdep("util.hashes", { ["Windows"] = "Make sure you have hashes.dll from the Prosody distribution in util/";
                                                ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/hashes.so";
                                        });
@@ -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;
+};
index 412acccd762cb755a9c0627f2689ea82b5989465..e2943e4497f75bdda61b50ca11914e623864e6a6 100644 (file)
@@ -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 == false 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;
+};
index 6290e53b1744bf82c7c24a0cdeb6b759aaf12a3c..f405c0bdd91f70a47a51fa3e2a11d0cba8f93b97 100644 (file)
@@ -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;
+};
index 08b86a7c0907ee462af619f11e982dc73afae6ef..bf76d258d7237eaef4490f442bd490b69d2cafe9 100644 (file)
@@ -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 (file)
index 0000000..4cc28d3
--- /dev/null
@@ -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 }
index 51211c7a2fe52aa23f9d2c4814cb4287c368579f..2c4cc6ef549de995611665505d38eada12ecae18 100644 (file)
@@ -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.
 --
index 81401e8b503a0433c7382cd99cda9e13342cf2ae..c2b9dce19ee9f411194dc80d5a757e21e548f8b7 100644 (file)
@@ -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 (file)
index 0000000..315cc20
--- /dev/null
@@ -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;
+};
index acfd7f247c3c7e0c056544763cb4350edaacbff4..ec3b4d7e56ba4c8342d11e5af00a044d25fe418e 100644 (file)
@@ -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};
index 1f6aacb81a4d36e77ba23429ff2001f6cdf14ad3..bd150ff28cd3572ca407274a9bf4df2276e827df 100644 (file)
@@ -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.
 --
 
 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 = {};
@@ -18,18 +23,18 @@ function it.reverse(f, s, var)
        while true do
                local ret = { f(s, var) };
                var = ret[1];
-               if var == nil then break; end
-               table.insert(results, 1, ret);
+               if var == nil then break; end
+               t_insert(results, 1, ret);
        end
-       
+
        -- Then return our reverse one
        local i,max = 0, #results;
-       return function (results)
-                       if i<max then
-                               i = i + 1;
-                               return unpack(results[i]);
-                       end
-               end, results;
+       return function (_results)
+               if i<max then
+                       i = i + 1;
+                       return unpack(_results[i]);
+               end
+       end, results;
 end
 
 -- Iterate only over keys in a table
@@ -43,24 +48,33 @@ end
 -- Iterate only over values in a table
 function it.values(t)
        local key, val;
-       return function (t)
-               key, val = next(t, key);
+       return function (_t)
+               key, val = next(_t, key);
                return val;
        end, t;
 end
 
+-- Iterate over the n:th return value
+function it.select(n, f, s, var)
+       return function (_s)
+               local ret = pack(f(_s, var));
+               var = ret[1];
+               return ret[n];
+       end, s, var;
+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
+                       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,32 +83,31 @@ 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];
-               if var == nil then break; end
+               var = f(s, var);
+               if var == nil then break; end
                x = x + 1;
        end
-       
+
        return x;
 end
 
 -- Return the first n items an iterator returns
 function it.head(n, f, s, var)
        local c = 0;
-       return function (s, var)
+       return function (_s, _var)
                if c >= n then
                        return nil;
                end
                c = c + 1;
-               return f(s, var);
-       end, s;
+               return f(_s, _var);
+       end, s, var;
 end
 
 -- Skip the first n items an iterator returns
 function it.skip(n, f, s, var)
-       for i=1,n do
+       for _ = 1, n do
                var = f(s, var);
        end
        return f, s, var;
@@ -104,9 +117,9 @@ 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
+               if var == nil then break; end
                results[(count%n)+1] = ret;
                count = count + 1;
        end
@@ -117,9 +130,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))); -- !
+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 reverse(head(n, reverse(f, s, var)));
+       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
@@ -135,11 +163,11 @@ end
 
 -- Convert the values returned by an iterator to an array
 function it.to_array(f, s, var)
-       local t, var = {};
+       local t = {};
        while true do
                var = f(s, var);
-               if var == nil then break; end
-               table.insert(t, var);
+               if var == nil then break; end
+               t_insert(t, var);
        end
        return t;
 end
@@ -150,7 +178,7 @@ function it.to_table(f, s, var)
        local t, var2 = {};
        while true do
                var, var2 = f(s, var);
-               if var == nil then break; end
+               if var == nil then break; end
                t[var] = var2;
        end
        return t;
index 8e0a784cc49abb3a0a25531043f993e23ed0cf13..522fb126747439aea5add0853db74f35643fc014 100644 (file)
@@ -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 select = select;
 local match, sub = string.match, string.sub;
 local nodeprep = require "util.encodings".stringprep.nodeprep;
 local nameprep = require "util.encodings".stringprep.nameprep;
@@ -23,9 +24,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 +35,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 +63,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
+
+local function prep(jid)
+       local node, host, resource = prepped_split(jid);
+       return join(node, host, resource);
 end
 
-function compare(jid, acl)
+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 +94,31 @@ 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 node(jid)
+       return (select(1, split(jid)));
+end
+
+local function host(jid)
+       return (select(2, split(jid)));
+end
+
+local function resource(jid)
+       return (select(3, split(jid)));
+end
 
-return _M;
+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 {
+       split = split;
+       bare = bare;
+       prepped_split = prepped_split;
+       join = join;
+       prep = prep;
+       compare = compare;
+       node = node;
+       host = host;
+       resource = resource;
+       escape = escape;
+       unescape = unescape;
+};
index 82ebcc4330ef61e56f3185f9cbd75a4f7554500c..cba54e8e1b3c8b52f4d5f0d81f3e941437a08e71 100644 (file)
@@ -12,21 +12,17 @@ local s_char = string.char;
 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");
 local array_mt = has_array and getmetatable(array()) or {};
 
 --module("json")
-local json = {};
+local module = {};
 
-local null = newproxy and newproxy(true) or {};
-if getmetatable and getmetatable(null) then
-       getmetatable(null).__tostring = function() return "null"; end;
-end
-json.null = null;
+local null = setmetatable({}, { __tostring = function() return "null"; end; });
+module.null = null;
 
 local escapes = {
        ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b",
@@ -73,7 +69,7 @@ end
 function arraysave(o, buffer)
        t_insert(buffer, "[");
        if next(o) then
-               for i,v in ipairs(o) do
+               for _, v in ipairs(o) do
                        simplesave(v, buffer);
                        t_insert(buffer, ",");
                end
@@ -148,7 +144,9 @@ end
 
 function simplesave(o, buffer)
        local t = type(o);
-       if t == "number" then
+       if o == null then
+               t_insert(buffer, "null");
+       elseif t == "number" then
                t_insert(buffer, tostring(o));
        elseif t == "string" then
                stringsave(o, buffer);
@@ -166,17 +164,17 @@ function simplesave(o, buffer)
        end
 end
 
-function json.encode(obj)
+function module.encode(obj)
        local t = {};
        simplesave(obj, t);
        return t_concat(t);
 end
-function json.encode_ordered(obj)
+function module.encode_ordered(obj)
        local t = { ordered = true };
        simplesave(obj, t);
        return t_concat(t);
 end
-function json.encode_array(obj)
+function module.encode_array(obj)
        local t = {};
        arraysave(obj, t);
        return t_concat(t);
@@ -192,7 +190,7 @@ local function _fixobject(obj)
        local __array = obj.__array;
        if __array then
                obj.__array = nil;
-               for i,v in ipairs(__array) do
+               for _, v in ipairs(__array) do
                        t_insert(obj, v);
                end
        end
@@ -200,7 +198,7 @@ local function _fixobject(obj)
        if __hash then
                obj.__hash = nil;
                local k;
-               for i,v in ipairs(__hash) do
+               for _, v in ipairs(__hash) do
                        if k ~= nil then
                                obj[k] = v; k = nil;
                        else
@@ -345,12 +343,12 @@ local first_escape = {
        ["\\u" ] = "\\u";
 };
 
-function json.decode(json)
+function module.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
@@ -358,10 +356,10 @@ function json.decode(json)
        return val;
 end
 
-function json.test(object)
-       local encoded = json.encode(object);
-       local decoded = json.decode(encoded);
-       local recoded = json.encode(decoded);
+function module.test(object)
+       local encoded = module.encode(object);
+       local decoded = module.decode(encoded);
+       local recoded = module.encode(decoded);
        if encoded ~= recoded then
                print("FAILED");
                print("encoded:", encoded);
@@ -372,4 +370,4 @@ function json.test(object)
        return encoded == recoded;
 end
 
-return json;
+return module;
index 26206d4d83379ff49b255372197c1fb891337f50..e72b29bc05843987867a66c42526fefd1bb2527f 100644 (file)
@@ -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 (file)
index 0000000..3f75c4c
--- /dev/null
@@ -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;
index dbf34d28ff33e4ba8c453270f2b751eeda128066..e4321d3d34e120e8740b1db80278cabdbbe93e3d 100644 (file)
@@ -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;
+};
index 39fe99d6abf3013112259fda1e876e8ed894ee1c..757259f66469d6a5fdfb879338fea06741d8cd50 100644 (file)
@@ -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 (file)
index 0000000..89f4cad
--- /dev/null
@@ -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;
index 112c0d529a7ab4707b26f168423b1de3b35c84fa..004855f092ee7cabb4655b9533b14642ecb685cf 100644 (file)
@@ -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/presence.lua b/util/presence.lua
new file mode 100644 (file)
index 0000000..f637035
--- /dev/null
@@ -0,0 +1,38 @@
+-- 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 = table.insert;
+
+local function select_top_resources(user)
+       local priority = 0;
+       local recipients = {};
+       for _, session in pairs(user.sessions) do -- find resource with greatest priority
+               if session.presence then
+                       -- TODO check active privacy list for session
+                       local p = session.priority;
+                       if p > priority then
+                               priority = p;
+                               recipients = {session};
+                       elseif p == priority then
+                               t_insert(recipients, session);
+                       end
+               end
+       end
+       return recipients;
+end
+local function recalc_resource_map(user)
+       if user then
+               user.top_resources = select_top_resources(user);
+               if #user.top_resources == 0 then user.top_resources = nil; end
+       end
+end
+
+return {
+       select_top_resources = select_top_resources;
+       recalc_resource_map = recalc_resource_map;
+}
index c6fe1986718b87e964ff12ac9dac09d6fe5ad4c5..cde1cdd4ce7b839d07648835c2948eeaf25dbcd7 100644 (file)
@@ -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,32 +22,26 @@ local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep;
 
 local io, os = io, os;
 local print = print;
-local tostring, tonumber = tostring, tonumber;
+local tonumber = tonumber;
 
 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,44 +140,44 @@ 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 user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
+local function user_exists(params)
+       local user, host = nodeprep(params.user), nameprep(params.host);
 
        storagemanager.initialize_host(host);
        local provider = prosody.hosts[host].users;
        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;
+};
index e1418c62d51fdccf96b4f8605789ce0378e11a9e..6d12690aa8488ab4852a2ab12d5830e315012920 100644 (file)
@@ -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 (file)
index 0000000..728e905
--- /dev/null
@@ -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 (file)
index 0000000..574e2e1
--- /dev/null
@@ -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;
+};
index c8aec631ae4af2478b0eb08b4ddaf5b3098d873e..81f78d55f764bb14c227231fe25f7bae4124108d 100644 (file)
@@ -10,7 +10,6 @@
 -- We can't hand this off to getaddrinfo, since it blocks
 
 local ip_commonPrefixLength = require"util.ip".commonPrefixLength
-local new_ip = require"util.ip".new_ip;
 
 local function commonPrefixLength(ipA, ipB)
        local len = ip_commonPrefixLength(ipA, ipB);
index afb3861b08f90d70db33d9657da4df77efe43036..5845f34a45e31cedfb20c07ec1592ef5ff833bdb 100644 (file)
@@ -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;
+};
index ca5fe4040d90f4350711c9e293c2c5ac8b7687ea..6201db3226197e844287b0a5babae447f4ee7285 100644 (file)
 --
 --    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-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 +37,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;
+}
index 591d85371ace5db490cd90c5ab78b91d9f699709..695dd2a3ed3c2762fd6891f65b9f852d76492ab2 100644 (file)
@@ -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 (file)
index 0000000..5ba9019
--- /dev/null
@@ -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;
+}
index c9ec2911797ecaacae50ac0e6f969adb062b5591..26e653354b479bc4e8b3c972ab770fff11e6bc63 100644 (file)
@@ -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;
+}
index cf2f0ede940c3e8eb3696192487efe2b80848c9d..d2b2abde2e950e7d731029cc91f60ba2b29e6ee7 100644 (file)
@@ -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)
-                               if state == nil then return "failure", "not-authorized"
-                               elseif state == false then return "failure", "account-disabled" end
-                               
+                               local password, status = self.profile.plain(self, username, self.realm)
+                               if status == nil then return "failure", "not-authorized"
+                               elseif status == 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);
+                               local succ;
+                               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);
-                               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
+                       elseif self.profile[profile_name] then
+                               local status;
+                               stored_key, server_key, iteration_count, salt, status = self.profile[profile_name](self, username, self.realm);
+                               if status == nil then return "failure", "not-authorized"
+                               elseif status == false then return "failure", "account-disabled" end
                        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;
+}
index 196845876c84141b3c8a10a402ee5a3fae424559..4e9a4af5c72798db85b6f3bee69ed95e874a683f 100644 (file)
@@ -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;
+};
index 8a259184c61b715326b03d176e95a6e5d56e60fe..206f5fbbf59bda7766172dfb8cc6e3dceaf5c0a3 100644 (file)
@@ -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 (file)
index 0000000..b2a726c
--- /dev/null
@@ -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;
+}
index fa065a9c5b8a2f7df7375273df27f1ae06bd8318..c136a522d3a272e1b7a08ec851708ad539a3ad89 100644 (file)
@@ -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;
+};
index f360d6d09d04f07b8d04b3b511719ad334856422..6fed1373ae9f027af12aa6cb92ce901dfcbb674e 100644 (file)
@@ -1,8 +1,9 @@
 
 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 type = type;
+local assert, pcall, xpcall, debug_traceback = assert, pcall, xpcall, debug.traceback;
 local t_concat = table.concat;
 local s_char = string.char;
 local log = require "util.logger".init("sql");
@@ -13,7 +14,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 +22,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() return "Integer()" end
+local function String() 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 +43,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 +70,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,43 +96,44 @@ 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")
-       local dbh, err = DBI.Connect(
+       log("debug", "Connecting to [%s] %s...", params.driver, params.database);
+       local ok, dbh, err = pcall(DBI.Connect,
                params.driver, params.database,
                params.username, params.password,
                params.host, params.port
        );
+       if not ok then return ok, dbh; end
        if not dbh then return nil, err; end
        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:prepquery(sql)
+       if self.params.driver == "PostgreSQL" then
+               sql = sql:gsub("`", "\"");
+       end
+       return sql;
+end
+
 function engine:execute(sql, ...)
        local success, err = self:connect();
        if not success then return success, err; end
@@ -177,22 +153,23 @@ 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("`", "\"");
-       end
+       sql = self:prepquery(sql);
        local stmt = assert(self.conn:prepare(sql));
        assert(stmt:execute(...));
        return stmt:rows();
 end
 function engine:execute_update(sql, ...)
-       if self.params.driver == "PostgreSQL" then
-               sql = sql:gsub("`", "\"");
-       end
+       sql = self:prepquery(sql);
        local prepared = self.prepared;
        local stmt = prepared[sql];
        if not stmt then
@@ -200,22 +177,47 @@ 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
+local function handleerr(err)
+       log("error", "Error in SQL transaction: %s", debug_traceback(err, 3));
+       return err;
+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);
+       local success, a, b, c = xpcall(f, handleerr);
        self.__transaction = nil;
        if success then
                log("debug", "SQL transaction success [%s]", tostring(func));
@@ -229,15 +231,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 +253,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 +301,52 @@ 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()
+                       for encoding in self:select"PRAGMA encoding;" do
+                               if encoding[1] == "UTF-8" then
+                                       self.charset = "utf8";
+                               end
+                       end
+               end);
+       end
+       local set_names_query = "SET NAMES '%s';"
+       local charset = "utf8";
+       if driver == "MySQL" then
+               self:transaction(function()
+                       for row in self:select"SELECT `CHARACTER_SET_NAME` FROM `information_schema`.`CHARACTER_SETS` WHERE `CHARACTER_SET_NAME` LIKE 'utf8%' ORDER BY MAXLEN DESC LIMIT 1;" do
+                               charset = row and row[1] or charset;
+                       end
+               end);
+               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);
+               local charset_ok = true;
+               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);
+                               charset_ok = false;
+                       end
+               end
+               if not charset_ok then
+                       return false, "Failed to set connection encoding";
+               end
+       end
+
+       return true;
+end
 local engine_mt = { __index = engine };
 
 local function db2uri(params)
@@ -286,55 +359,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 (file)
index 0000000..c849aa2
--- /dev/null
@@ -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;
+};
index 7c21421083d57daafbcf2ad807c3d7b7166c36a5..8bb9ba0545df6e8bc4d7a2eb7a1bf85526d6494b 100644 (file)
@@ -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,17 +35,15 @@ 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 new_stanza(name, attr)
        local stanza = { name = name, attr = attr or {}, tags = {} };
        return setmetatable(stanza, stanza_mt);
 end
-local stanza = stanza;
 
 function stanza_mt:query(xmlns)
        return self:tag("query", { xmlns = xmlns });
@@ -56,7 +54,7 @@ function stanza_mt:body(text, attr)
 end
 
 function stanza_mt:tag(name, attrs)
-       local s = stanza(name, attrs);
+       local s = new_stanza(name, attrs);
        local last_add = self.last_add;
        if not last_add then last_add = {}; self.last_add = last_add; end
        (last_add[#last_add] or self):add_direct_child(s);
@@ -99,7 +97,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 +150,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,14 +198,10 @@ 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 function _dostring(t, buf, self, _xml_escape, parentns)
        local nsid = 0;
        local name = t.name
        t_insert(buf, "<"..name);
@@ -215,9 +209,9 @@ local function _dostring(t, buf, self, xml_escape, parentns)
                if s_find(k, "\1", 1, true) then
                        local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$");
                        nsid = nsid + 1;
-                       t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'");
+                       t_insert(buf, " xmlns:ns"..nsid.."='".._xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='".._xml_escape(v).."'");
                elseif not(k == "xmlns" and v == parentns) then
-                       t_insert(buf, " "..k.."='"..xml_escape(v).."'");
+                       t_insert(buf, " "..k.."='".._xml_escape(v).."'");
                end
        end
        local len = #t;
@@ -228,9 +222,9 @@ local function _dostring(t, buf, self, xml_escape, parentns)
                for n=1,len do
                        local child = t[n];
                        if child.name then
-                               self(child, buf, self, xml_escape, t.attr.xmlns);
+                               self(child, buf, self, _xml_escape, t.attr.xmlns);
                        else
-                               t_insert(buf, xml_escape(child));
+                               t_insert(buf, _xml_escape(child));
                        end
                end
                t_insert(buf, "</"..name..">");
@@ -257,14 +251,14 @@ function stanza_mt.get_text(t)
 end
 
 function stanza_mt.get_error(stanza)
-       local type, condition, text;
-       
+       local error_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;
-       
+       error_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
@@ -277,18 +271,16 @@ function stanza_mt.get_error(stanza)
                        end
                end
        end
-       return type, condition or "undefined-condition", text;
+       return error_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 +292,7 @@ function preserialize(stanza)
        return s;
 end
 
-function deserialize(stanza)
+local function deserialize(stanza)
        -- Set metatable
        if stanza then
                local attr = stanza.attr;
@@ -333,56 +325,53 @@ 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);
+               return new_stanza("message", attr);
        else
-               return stanza("message", attr):tag("body"):text(body):up();
+               return new_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() });
+       return new_stanza("iq", attr or { id = new_id() });
 end
 
-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) });
+local function reply(orig)
+       return new_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, error_type, condition, error_message)
+       local t = reply(orig);
+       t.attr.type = "error";
+       t:tag("error", {type = error_type}) --COMPAT: Some day xmlns:stanzas goes here
+       :tag(condition, xmpp_stanzas_attr):up();
+       if error_message then t:tag("text", xmpp_stanzas_attr):text(error_message):up(); end
+       return t; -- stanza ready for adding app-specific errors
 end
 
-function presence(attr)
-       return stanza("presence", attr);
+local function presence(attr)
+       return new_stanza("presence", attr);
 end
 
 if do_pretty_printing then
@@ -390,14 +379,14 @@ 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, ">");
        local tag_format = top_tag_format.."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
        function stanza_mt.pretty_print(t)
                local children_text = "";
-               for n, child in ipairs(t) do
+               for _, child in ipairs(t) do
                        if type(child) == "string" then
                                children_text = children_text .. xml_escape(child);
                        else
@@ -411,7 +400,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 +414,17 @@ else
        stanza_mt.pretty_top_tag = stanza_mt.top_tag;
 end
 
-return _M;
+return {
+       stanza_mt = stanza_mt;
+       stanza = new_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 (file)
index 0000000..2635502
--- /dev/null
@@ -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;
+}
index 66d4fca7ae9084b2a41a1d76c2dee4f662f82eb8..04ebb93d2285df05563c4678692fa2d682c9e884 100644 (file)
@@ -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
index 6ef3b689e20dc893eb04c2970033bc97ec11d1f9..53633b45b1b5744d5fbe94b1d47078e77b6da62b 100644 (file)
@@ -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;
@@ -12,6 +14,10 @@ local char, format = string.char, string.format;
 local tonumber = tonumber;
 local ipairs = ipairs;
 local io_write = io.write;
+local m_floor = math.floor;
+local type = type;
+local setmetatable = setmetatable;
+local pairs = pairs;
 
 local windows;
 if os.getenv("WINDIR") then
@@ -19,7 +25,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 +51,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 +59,45 @@ function getstring(style, text)
        end
 end
 
-function getstyle(...)
+local function gray(n)
+       return m_floor(n*3/32)+0xe8;
+end
+local function color(r,g,b)
+       if r == g and g == b then
+               return gray(r);
+       end
+       r = m_floor(r*3/128);
+       g = m_floor(g*3/128);
+       b = m_floor(b*3/128);
+       return 0x10 + ( r * 36 ) + ( g * 6 ) + ( b );
+end
+local function hex2rgb(hex)
+       local r = tonumber(hex:sub(1,2),16);
+       local g = tonumber(hex:sub(3,4),16);
+       local b = tonumber(hex:sub(5,6),16);
+       return r,g,b;
+end
+
+setmetatable(stylemap, { __index = function(_, style)
+       if type(style) == "string" and style:find("%x%x%x%x%x%x") == 1 then
+               local g = style:sub(7) == " background" and "48;5;" or "38;5;";
+               return g .. color(hex2rgb(style));
+       end
+end } );
+
+local csscolors = {
+       red = "ff0000"; fuchsia = "ff00ff"; green = "008000"; white = "ffffff";
+       lime = "00ff00"; yellow = "ffff00"; purple = "800080"; blue = "0000ff";
+       aqua = "00ffff"; olive  = "808000"; black  = "000000"; navy = "000080";
+       teal = "008080"; silver = "c0c0c0"; maroon = "800000"; gray = "808080";
+}
+for colorname, rgb in pairs(csscolors) do
+       stylemap[colorname] = stylemap[colorname] or stylemap[rgb];
+       colorname, rgb = colorname .. " background", rgb .. " background"
+       stylemap[colorname] = stylemap[colorname] or stylemap[rgb];
+end
+
+local function getstyle(...)
        local styles, result = { ... }, {};
        for i, style in ipairs(styles) do
                style = stylemap[style];
@@ -65,7 +109,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 +126,7 @@ if windows then
                end
        end
        if not orig_color then
-               function setstyle(style) end
+               function setstyle() end
        end
 end
 
@@ -95,8 +139,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;
+};
index 55e1d07b3f5a836b450fc65cf23a1c31b7363e49..3d3f5d2d8b029f9134de0276620411b06390abac 100644 (file)
@@ -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;
+};
index af1e57b6d176c487b47c5e2d94e40c4eddb08247..3713625db3d6cbcb3d2c78fe5ff79c95aff13b79 100644 (file)
@@ -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;
+};
index 3576be8fa2bfbe39f57985f5c33c987fb0341186..f4fd21f64ec72b9fc3e45f2973c9a1d9d9fabeff 100644 (file)
@@ -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;
+};
index bcb2e274dd609935b9613b4c63dbc7db352ea980..aa8c6486781a51de97f96b4a9c7bf7fd4018b0f0 100644 (file)
@@ -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;
+};
index 19d4ec6d791d5ca79268af567a989d480484b19a..f228b20130316e21e76f96b4e44d62dc9d2c0ebc 100644 (file)
 
 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;
+};
index 076490faf8b9655daa940beb646c3cd6552e2b73..733d821ad7c8b87dc93d7e4a0b3ab2cfa030fd85 100644 (file)
@@ -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;
+};
index 138c86b744832b4e45f98798e64caf30fac664b5..7be63285dfdb48c8d219cab5190e930ffdaa6b9d 100644 (file)
@@ -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;
+};