# HG changeset patch
# User Kim Alvefur
# Date 1543258087 -3600
# Node ID cc642c9c5ad59e2320341f390a3a010e340781e7
# Parent e31547ab1ca202ddaabf9475931647debc484707# Parent bb8486491b48431236c0d32548c20d9853781e69
Merge 0.10->0.11
diff -r bb8486491b48 -r cc642c9c5ad5 .busted
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.busted Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,13 @@
+return {
+ _all = {
+ },
+ default = {
+ ["exclude-tags"] = "mod_bosh,storage";
+ };
+ bosh = {
+ tags = "mod_bosh";
+ };
+ storage = {
+ tags = "storage";
+ };
+}
diff -r bb8486491b48 -r cc642c9c5ad5 .hgignore
--- a/.hgignore Sun Nov 25 13:16:17 2018 +0100
+++ b/.hgignore Mon Nov 26 19:48:07 2018 +0100
@@ -15,7 +15,6 @@
*.rej
*.save
*~
-*.report
*.o
*.so
*.install
@@ -27,3 +26,6 @@
*.exp
*.lib
*.obj
+luacov.report.out
+luacov.report.out.index
+luacov.stats.out
\ No newline at end of file
diff -r bb8486491b48 -r cc642c9c5ad5 .hgtags
--- a/.hgtags Sun Nov 25 13:16:17 2018 +0100
+++ b/.hgtags Mon Nov 26 19:48:07 2018 +0100
@@ -65,3 +65,4 @@
4ae8dd415e9431924ad4aa0b57bcee8a4a9272f8 0.10.1
29c6d2681bad9f67d8bd548bb3a7973473bae91e 0.9.14
7ec098b68042f60687f1002e788b34b06048945d 0.10.2
+83f3a05c1b1bb9b54b3b153077a06eb02e247c8e 0.11.0
diff -r bb8486491b48 -r cc642c9c5ad5 .luacheckrc
--- a/.luacheckrc Sun Nov 25 13:16:17 2018 +0100
+++ b/.luacheckrc Mon Nov 26 19:48:07 2018 +0100
@@ -1,19 +1,33 @@
cache = true
-read_globals = { "prosody", "hosts", "import" }
-globals = { "_M" }
-allow_defined_top = true
-module = true
-unused_secondaries = false
codes = true
-ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV", "431/log" }
+ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV", "431/log", "143/table", "113/unpack" }
max_line_length = 150
+read_globals = {
+ "prosody",
+ "import",
+};
+files["prosody"] = {
+ allow_defined_top = true;
+ module = true;
+}
+files["prosodyctl"] = {
+ allow_defined_top = true;
+ module = true;
+};
files["core/"] = {
- read_globals = { "prosody", "hosts" };
- globals = { "prosody.hosts.?", "hosts.?" };
+ globals = {
+ "prosody.hosts.?",
+ };
+}
+files["util/"] = {
+ -- Ignore unwrapped license text
+ max_comment_line_length = false;
}
files["plugins/"] = {
+ module = true;
+ allow_defined_top = true;
read_globals = {
-- Module instance
"module.name",
@@ -51,8 +65,6 @@
"module.get_option_set",
"module.get_option_string",
"module.handle_items",
- "module.has_feature",
- "module.has_identity",
"module.hook",
"module.hook_global",
"module.hook_object_event",
@@ -74,10 +86,11 @@
"module.wrap_event",
"module.wrap_global",
"module.wrap_object_event",
+
+ -- mod_http API
+ "module.http_url",
};
globals = {
- "_M",
-
-- Methods that can be set on module API
"module.unload",
"module.add_host",
@@ -89,16 +102,69 @@
"module.environment",
};
}
-files["tests/"] = {
- read_globals = {
- "testlib_new_env",
- "assert_equal",
- "assert_table",
- "assert_function",
- "assert_string",
- "assert_boolean",
- "assert_is",
- "assert_is_not",
- "runtest",
+files["spec/"] = {
+ std = "+busted";
+ globals = { "randomize" };
+}
+files["prosody.cfg.lua"] = {
+ ignore = { "131" };
+ globals = {
+ "Host",
+ "host",
+ "VirtualHost",
+ "Component",
+ "component",
+ "Include",
+ "include",
+ "RunScript"
};
}
+
+if os.getenv("PROSODY_STRICT_LINT") ~= "1" then
+ -- These files have not yet been brought up to standard
+ -- Do not add more files here, but do help us fix these!
+ unused_secondaries = false
+
+ local exclude_files = {
+ "doc/net.server.lua";
+
+ "fallbacks/bit.lua";
+ "fallbacks/lxp.lua";
+
+ "net/adns.lua";
+ "net/cqueues.lua";
+ "net/dns.lua";
+ "net/server_select.lua";
+
+ "plugins/mod_storage_sql1.lua";
+
+ "spec/core_configmanager_spec.lua";
+ "spec/core_moduleapi_spec.lua";
+ "spec/net_http_parser_spec.lua";
+ "spec/util_events_spec.lua";
+ "spec/util_http_spec.lua";
+ "spec/util_ip_spec.lua";
+ "spec/util_multitable_spec.lua";
+ "spec/util_rfc6724_spec.lua";
+ "spec/util_throttle_spec.lua";
+ "spec/util_xmppstream_spec.lua";
+
+ "tools/ejabberd2prosody.lua";
+ "tools/ejabberdsql2prosody.lua";
+ "tools/erlparse.lua";
+ "tools/jabberd14sql2prosody.lua";
+ "tools/migration/migrator.cfg.lua";
+ "tools/migration/migrator/jabberd14.lua";
+ "tools/migration/migrator/mtools.lua";
+ "tools/migration/migrator/prosody_files.lua";
+ "tools/migration/migrator/prosody_sql.lua";
+ "tools/migration/prosody-migrator.lua";
+ "tools/openfire2prosody.lua";
+ "tools/xep227toprosody.lua";
+
+ "util/sasl/digest-md5.lua";
+ }
+ for _, file in ipairs(exclude_files) do
+ files[file] = { only = {} }
+ end
+end
diff -r bb8486491b48 -r cc642c9c5ad5 .luacov
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.luacov Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,3 @@
+exclude = {
+ "^%./spec/";
+}
diff -r bb8486491b48 -r cc642c9c5ad5 CHANGES
--- a/CHANGES Sun Nov 25 13:16:17 2018 +0100
+++ b/CHANGES Mon Nov 26 19:48:07 2018 +0100
@@ -1,3 +1,30 @@
+0.11.0
+======
+
+**2018-11-18**
+
+New features
+------------
+
+- Rewritten more extensible MUC module
+ - Store inactive rooms to disk
+ - Store rooms to disk on shutdown
+ - Voice requests
+ - Tombstones in place of destroyed rooms
+- PubSub features
+ - Persistence
+ - Affiliations
+ - Access models
+ - "publish-options"
+- PEP now uses our pubsub code and now shares the above features
+- Asynchronous operations
+- Busted for tests
+- mod\_muc\_mam (XEP-0313 in groupchats)
+- mod\_vcard\_legacy (XEP-0398)
+- mod\_vcard4 (XEP-0292)
+- mod\_csi, mod\_csi\_simple (XEP-0352)
+- New experimental network backend "epoll"
+
0.10.0
======
diff -r bb8486491b48 -r cc642c9c5ad5 DEPENDS
--- a/DEPENDS Sun Nov 25 13:16:17 2018 +0100
+++ b/DEPENDS Mon Nov 26 19:48:07 2018 +0100
@@ -1,6 +1,6 @@
For full information on our dependencies, version requirements, and
-where to find them, see http://prosody.im/doc/depends
+where to find them, see https://prosody.im/doc/depends
If you have luarocks available on your platform, install the following:
diff -r bb8486491b48 -r cc642c9c5ad5 GNUmakefile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/GNUmakefile Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,112 @@
+
+include config.unix
+
+BIN = $(DESTDIR)$(PREFIX)/bin
+CONFIG = $(DESTDIR)$(SYSCONFDIR)
+MODULES = $(DESTDIR)$(LIBDIR)/prosody/modules
+SOURCE = $(DESTDIR)$(LIBDIR)/prosody
+DATA = $(DESTDIR)$(DATADIR)
+MAN = $(DESTDIR)$(PREFIX)/share/man
+
+INSTALLEDSOURCE = $(LIBDIR)/prosody
+INSTALLEDCONFIG = $(SYSCONFDIR)
+INSTALLEDMODULES = $(LIBDIR)/prosody/modules
+INSTALLEDDATA = $(DATADIR)
+
+INSTALL=install -p
+INSTALL_DATA=$(INSTALL) -m644
+INSTALL_EXEC=$(INSTALL) -m755
+MKDIR=install -d
+MKDIR_PRIVATE=$(MKDIR) -m750
+
+LUACHECK=luacheck
+BUSTED=busted
+
+.PHONY: all test coverage clean install
+
+all: prosody.install prosodyctl.install prosody.cfg.lua.install prosody.version
+ $(MAKE) -C util-src install
+ifeq ($(EXCERTS),yes)
+ -$(MAKE) -C certs localhost.crt example.com.crt
+endif
+
+install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodings.so util/encodings.so util/pposix.so util/signal.so
+ $(MKDIR) $(BIN) $(CONFIG) $(MODULES) $(SOURCE)
+ $(MKDIR_PRIVATE) $(DATA)
+ $(MKDIR) $(MAN)/man1
+ $(MKDIR) $(CONFIG)/certs
+ $(MKDIR) $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util
+ $(INSTALL_EXEC) ./prosody.install $(BIN)/prosody
+ $(INSTALL_EXEC) ./prosodyctl.install $(BIN)/prosodyctl
+ $(INSTALL_DATA) core/*.lua $(SOURCE)/core
+ $(INSTALL_DATA) net/*.lua $(SOURCE)/net
+ $(MKDIR) $(SOURCE)/net/http $(SOURCE)/net/resolvers $(SOURCE)/net/websocket
+ $(INSTALL_DATA) net/http/*.lua $(SOURCE)/net/http
+ $(INSTALL_DATA) net/resolvers/*.lua $(SOURCE)/net/resolvers
+ $(INSTALL_DATA) net/websocket/*.lua $(SOURCE)/net/websocket
+ $(INSTALL_DATA) util/*.lua $(SOURCE)/util
+ $(INSTALL_DATA) util/*.so $(SOURCE)/util
+ $(MKDIR) $(SOURCE)/util/sasl
+ $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl
+ $(MKDIR) $(MODULES)/mod_s2s $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam
+ $(INSTALL_DATA) plugins/*.lua $(MODULES)
+ $(INSTALL_DATA) plugins/mod_s2s/*.lua $(MODULES)/mod_s2s
+ $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub
+ $(INSTALL_DATA) plugins/adhoc/*.lua $(MODULES)/adhoc
+ $(INSTALL_DATA) plugins/muc/*.lua $(MODULES)/muc
+ $(INSTALL_DATA) plugins/mod_mam/*.lua $(MODULES)/mod_mam
+ $(INSTALL_DATA) certs/* $(CONFIG)/certs
+ $(INSTALL_DATA) man/prosodyctl.man $(MAN)/man1/prosodyctl.1
+ test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
+ -test -f prosody.version && $(INSTALL_DATA) prosody.version $(SOURCE)/prosody.version
+ $(MAKE) install -C util-src
+
+clean:
+ rm -f prosody.install
+ rm -f prosodyctl.install
+ rm -f prosody.cfg.lua.install
+ rm -f prosody.version
+ $(MAKE) clean -C util-src
+
+test:
+ $(BUSTED) --lua=$(RUNWITH)
+
+coverage:
+ -rm -- luacov.*
+ $(BUSTED) --lua=$(RUNWITH) -c
+ luacov
+ luacov-console
+ luacov-console -s
+ @echo "To inspect individual files run: luacov-console -l FILENAME"
+
+lint:
+ $(LUACHECK) -q $$(HGPLAIN= hg files -I '**.lua') prosody prosodyctl
+ @echo $$(sed -n '/^\tlocal exclude_files/,/^}/p;' .luacheckrc | sed '1d;$d' | wc -l) files ignored
+ shellcheck configure
+
+util/%.so:
+ $(MAKE) install -C util-src
+
+%.install: %
+ sed "1s| lua$$| $(RUNWITH)|; \
+ s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \
+ s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|; \
+ s|^CFG_DATADIR=.*;$$|CFG_DATADIR='$(INSTALLEDDATA)';|; \
+ s|^CFG_PLUGINDIR=.*;$$|CFG_PLUGINDIR='$(INSTALLEDMODULES)/';|;" < $^ > $@
+
+prosody.cfg.lua.install: prosody.cfg.lua.dist
+ sed 's|certs/|$(INSTALLEDCONFIG)/certs/|' $^ > $@
+
+%.version: %.release
+ cp $^ $@
+
+%.version: .hg_archival.txt
+ sed -n 's/^node: \(............\).*/\1/p' $^ > $@
+
+%.version: .hg/dirstate
+ hexdump -n6 -e'6/1 "%02x"' $^ > $@
+
+%.version:
+ echo unknown > $@
+
+
diff -r bb8486491b48 -r cc642c9c5ad5 HACKERS
--- a/HACKERS Sun Nov 25 13:16:17 2018 +0100
+++ b/HACKERS Mon Nov 26 19:48:07 2018 +0100
@@ -2,11 +2,11 @@
This project accepts and *encourages* contributions. If you would like to get
involved you can join us on our mailing list and discussion rooms. More
-information on these at http://prosody.im/discuss
+information on these at https://prosody.im/discuss
Patches are welcome, though before sending we would appreciate if you read
docs/coding_style.txt for guidelines on how to format your code, and other tips.
-Documentation for developers can be found at http://prosody.im/doc/developers
+Documentation for developers can be found at https://prosody.im/doc/developers
Have fun :)
diff -r bb8486491b48 -r cc642c9c5ad5 INSTALL
--- a/INSTALL Sun Nov 25 13:16:17 2018 +0100
+++ b/INSTALL Mon Nov 26 19:48:07 2018 +0100
@@ -1,5 +1,5 @@
(This file was created from
-http://prosody.im/doc/installing_from_source on 2013-03-31)
+https://prosody.im/doc/installing_from_source on 2013-03-31)
====== Installing from source ======
==== Dependencies ====
diff -r bb8486491b48 -r cc642c9c5ad5 Makefile
--- a/Makefile Sun Nov 25 13:16:17 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-
-include config.unix
-
-BIN = $(DESTDIR)$(PREFIX)/bin
-CONFIG = $(DESTDIR)$(SYSCONFDIR)
-MODULES = $(DESTDIR)$(LIBDIR)/prosody/modules
-SOURCE = $(DESTDIR)$(LIBDIR)/prosody
-DATA = $(DESTDIR)$(DATADIR)
-MAN = $(DESTDIR)$(PREFIX)/share/man
-
-INSTALLEDSOURCE = $(LIBDIR)/prosody
-INSTALLEDCONFIG = $(SYSCONFDIR)
-INSTALLEDMODULES = $(LIBDIR)/prosody/modules
-INSTALLEDDATA = $(DATADIR)
-
-INSTALL=install -p
-INSTALL_DATA=$(INSTALL) -m644
-INSTALL_EXEC=$(INSTALL) -m755
-MKDIR=install -d
-MKDIR_PRIVATE=$(MKDIR) -m750
-
-.PHONY: all test clean install
-
-all: prosody.install prosodyctl.install prosody.cfg.lua.install prosody.version
- $(MAKE) -C util-src install
-ifeq ($(EXCERTS),yes)
- -$(MAKE) -C certs localhost.crt example.com.crt
-endif
-
-install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodings.so util/encodings.so util/pposix.so util/signal.so
- $(MKDIR) $(BIN) $(CONFIG) $(MODULES) $(SOURCE)
- $(MKDIR_PRIVATE) $(DATA)
- $(MKDIR) $(MAN)/man1
- $(MKDIR) $(CONFIG)/certs
- $(MKDIR) $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util
- $(INSTALL_EXEC) ./prosody.install $(BIN)/prosody
- $(INSTALL_EXEC) ./prosodyctl.install $(BIN)/prosodyctl
- $(INSTALL_DATA) core/*.lua $(SOURCE)/core
- $(INSTALL_DATA) net/*.lua $(SOURCE)/net
- $(MKDIR) $(SOURCE)/net/http $(SOURCE)/net/websocket
- $(INSTALL_DATA) net/http/*.lua $(SOURCE)/net/http
- $(INSTALL_DATA) net/websocket/*.lua $(SOURCE)/net/websocket
- $(INSTALL_DATA) util/*.lua $(SOURCE)/util
- $(INSTALL_DATA) util/*.so $(SOURCE)/util
- $(MKDIR) $(SOURCE)/util/sasl
- $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl
- $(MKDIR) $(MODULES)/mod_s2s $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam
- $(INSTALL_DATA) plugins/*.lua $(MODULES)
- $(INSTALL_DATA) plugins/mod_s2s/*.lua $(MODULES)/mod_s2s
- $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub
- $(INSTALL_DATA) plugins/adhoc/*.lua $(MODULES)/adhoc
- $(INSTALL_DATA) plugins/muc/*.lua $(MODULES)/muc
- $(INSTALL_DATA) plugins/mod_mam/*.lua $(MODULES)/mod_mam
- $(INSTALL_DATA) certs/* $(CONFIG)/certs
- $(INSTALL_DATA) man/prosodyctl.man $(MAN)/man1/prosodyctl.1
- test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
- -test -f prosody.version && $(INSTALL_DATA) prosody.version $(SOURCE)/prosody.version
- $(MAKE) install -C util-src
-
-clean:
- rm -f prosody.install
- rm -f prosodyctl.install
- rm -f prosody.cfg.lua.install
- 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
-
-%.install: %
- sed "1s| lua$$| $(RUNWITH)|; \
- s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \
- s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|; \
- s|^CFG_DATADIR=.*;$$|CFG_DATADIR='$(INSTALLEDDATA)';|; \
- s|^CFG_PLUGINDIR=.*;$$|CFG_PLUGINDIR='$(INSTALLEDMODULES)/';|;" < $^ > $@
-
-prosody.cfg.lua.install: prosody.cfg.lua.dist
- sed 's|certs/|$(INSTALLEDCONFIG)/certs/|' $^ > $@
-
-%.version: %.release
- cp $^ $@
-
-%.version: .hg_archival.txt
- sed -n 's/^node: \(............\).*/\1/p' $^ > $@
-
-%.version: .hg/dirstate
- hexdump -n6 -e'6/1 "%02x"' $^ > $@
-
-%.version:
- echo unknown > $@
-
-
diff -r bb8486491b48 -r cc642c9c5ad5 README
--- a/README Sun Nov 25 13:16:17 2018 +0100
+++ b/README Mon Nov 26 19:48:07 2018 +0100
@@ -9,29 +9,29 @@
## Useful links
-Homepage: http://prosody.im/
-Download: http://prosody.im/download
-Documentation: http://prosody.im/doc/
+Homepage: https://prosody.im/
+Download: https://prosody.im/download
+Documentation: https://prosody.im/doc/
Jabber/XMPP Chat:
Address:
prosody@conference.prosody.im
Web interface:
- http://prosody.im/webchat
+ https://prosody.im/webchat
Mailing lists:
User support and discussion:
- http://groups.google.com/group/prosody-users
+ https://groups.google.com/group/prosody-users
Development discussion:
- http://groups.google.com/group/prosody-dev
+ https://groups.google.com/group/prosody-dev
Issue tracker changes:
- http://groups.google.com/group/prosody-issues
+ https://groups.google.com/group/prosody-issues
## Installation
See the accompanying INSTALL file for help on building Prosody from source. Alternatively
-see our guide at http://prosody.im/doc/install
+see our guide at https://prosody.im/doc/install
diff -r bb8486491b48 -r cc642c9c5ad5 certs/GNUmakefile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/certs/GNUmakefile Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,66 @@
+.DEFAULT: localhost.crt
+keysize=2048
+
+# How to:
+# First, `make yourhost.cnf` which creates a openssl config file.
+# Then edit this file and fill in the details you want it to have,
+# and add or change hosts and components it should cover.
+# Then `make yourhost.key` to create your private key, you can
+# include keysize=number to change the size of the key.
+# Then you can either `make yourhost.csr` to generate a certificate
+# signing request that you can submit to a CA, or `make yourhost.crt`
+# to generate a self signed certificate.
+
+.PRECIOUS: %.cnf %.key
+
+# To request a cert
+%.csr: %.cnf %.key
+ openssl req -new -key $(lastword $^) \
+ -sha256 -utf8 -config $(firstword $^) -out $@
+
+%.csr: %.cnf
+ umask 0077 && touch $*.key
+ openssl req -new -newkey rsa:$(keysize) -nodes -keyout $*.key \
+ -sha256 -utf8 -config $^ -out $@
+ @chmod 400 $*.key
+
+%.csr: %.key
+ openssl req -new -key $^ -utf8 -subj /CN=$* -out $@
+
+%.csr:
+ umask 0077 && touch $*.key
+ openssl req -new -newkey rsa:$(keysize) -nodes -keyout $*.key \
+ -utf8 -subj /CN=$* -out $@
+ @chmod 400 $*.key
+
+# Self signed
+%.crt: %.cnf %.key
+ openssl req -new -x509 -key $(lastword $^) -days 365 -sha256 -utf8 \
+ -config $(firstword $^) -out $@
+
+%.crt: %.cnf
+ umask 0077 && touch $*.key
+ openssl req -new -x509 -newkey rsa:$(keysize) -nodes -keyout $*.key \
+ -days 365 -sha256 -utf8 -config $(firstword $^) -out $@
+ @chmod 400 $*.key
+
+%.crt: %.key
+ openssl req -new -x509 -key $^ -days 365 -sha256 -utf8 -subj /CN=$* -out $@
+
+%.crt:
+ umask 0077 && touch $*.key
+ openssl req -new -x509 -newkey rsa:$(keysize) -nodes -keyout $*.key \
+ -days 365 -sha256 -out $@ -utf8 -subj /CN=$*
+ @chmod 400 $*.key
+
+# Generate a config from the example
+%.cnf:
+ sed 's,example\.com,$*,g' openssl.cnf > $@
+
+%.key:
+ umask 0077 && openssl genrsa -out $@ $(keysize)
+ @chmod 400 $@
+
+# Generate Diffie-Hellman parameters
+dh-%.pem:
+ openssl dhparam -out $@ $*
diff -r bb8486491b48 -r cc642c9c5ad5 certs/Makefile
--- a/certs/Makefile Sun Nov 25 13:16:17 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-.DEFAULT: localhost.crt
-keysize=2048
-
-# How to:
-# First, `make yourhost.cnf` which creates a openssl config file.
-# Then edit this file and fill in the details you want it to have,
-# and add or change hosts and components it should cover.
-# Then `make yourhost.key` to create your private key, you can
-# include keysize=number to change the size of the key.
-# Then you can either `make yourhost.csr` to generate a certificate
-# signing request that you can submit to a CA, or `make yourhost.crt`
-# to generate a self signed certificate.
-
-.PRECIOUS: %.cnf %.key
-
-# To request a cert
-%.csr: %.cnf %.key
- openssl req -new -key $(lastword $^) \
- -sha256 -utf8 -config $(firstword $^) -out $@
-
-%.csr: %.cnf
- umask 0077 && touch $*.key
- openssl req -new -newkey rsa:$(keysize) -nodes -keyout $*.key \
- -sha256 -utf8 -config $^ -out $@
- @chmod 400 $*.key
-
-%.csr: %.key
- openssl req -new -key $^ -utf8 -subj /CN=$* -out $@
-
-%.csr:
- umask 0077 && touch $*.key
- openssl req -new -newkey rsa:$(keysize) -nodes -keyout $*.key \
- -utf8 -subj /CN=$* -out $@
- @chmod 400 $*.key
-
-# Self signed
-%.crt: %.cnf %.key
- openssl req -new -x509 -key $(lastword $^) -days 365 -sha256 -utf8 \
- -config $(firstword $^) -out $@
-
-%.crt: %.cnf
- umask 0077 && touch $*.key
- openssl req -new -x509 -newkey rsa:$(keysize) -nodes -keyout $*.key \
- -days 365 -sha256 -utf8 -config $(firstword $^) -out $@
- @chmod 400 $*.key
-
-%.crt: %.key
- openssl req -new -x509 -key $^ -days 365 -sha256 -utf8 -subj /CN=$* -out $@
-
-%.crt:
- umask 0077 && touch $*.key
- openssl req -new -x509 -newkey rsa:$(keysize) -nodes -keyout $*.key \
- -days 365 -sha256 -out $@ -utf8 -subj /CN=$*
- @chmod 400 $*.key
-
-# Generate a config from the example
-%.cnf:
- sed 's,example\.com,$*,g' openssl.cnf > $@
-
-%.key:
- umask 0077 && openssl genrsa -out $@ $(keysize)
- @chmod 400 $@
-
-# Generate Diffie-Hellman parameters
-dh-%.pem:
- openssl dhparam -out $@ $*
diff -r bb8486491b48 -r cc642c9c5ad5 certs/localhost.cnf
--- a/certs/localhost.cnf Sun Nov 25 13:16:17 2018 +0100
+++ b/certs/localhost.cnf Mon Nov 26 19:48:07 2018 +0100
@@ -11,7 +11,7 @@
[distinguished_name]
countryName = GB
organizationName = Prosody IM
-organizationalUnitName = http://prosody.im/doc/certificates
+organizationalUnitName = https://prosody.im/doc/certificates
commonName = Example certificate
[req]
diff -r bb8486491b48 -r cc642c9c5ad5 certs/makefile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/certs/makefile Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,18 @@
+.DEFAULT: localhost.crt
+keysize=2048
+
+# How to:
+# First, `make yourhost.cnf` which creates a openssl config file.
+# Then edit this file and fill in the details you want it to have,
+# and add or change hosts and components it should cover.
+# Then `make yourhost.key` to create your private key, you can
+# include keysize=number to change the size of the key.
+# Then you can either `make yourhost.csr` to generate a certificate
+# signing request that you can submit to a CA, or `make yourhost.crt`
+# to generate a self signed certificate.
+
+${.TARGETS:M*.crt}:
+ openssl req -new -x509 -newkey rsa:$(keysize) -nodes -keyout ${.TARGET:R}.key \
+ -days 365 -sha256 -out $@ -utf8 -subj /CN=${.TARGET:R}
+
+.SUFFIXES: .key .crt
diff -r bb8486491b48 -r cc642c9c5ad5 configure
--- a/configure Sun Nov 25 13:16:17 2018 +0100
+++ b/configure Mon Nov 26 19:48:07 2018 +0100
@@ -92,7 +92,7 @@
# Helper functions
find_program() {
- prog=`command -v "$1" 2>/dev/null`
+ prog=$(command -v "$1" 2>/dev/null)
if [ -n "$prog" ]
then
dirname "$prog"
@@ -107,26 +107,8 @@
exit 1
}
-find_helper() {
- explanation="$1"
- shift
- tried="$*"
- while [ -n "$1" ]
-do
- found=`find_program "$1"`
- if [ -n "$found" ]
- then
- echo "$1 found at $found"
- HELPER=$1
- return
- fi
- shift
- done
- echo "Could not find $explanation. Tried: $tried."
- die "Make sure one of them is installed and available in your PATH."
-}
-
-case `echo -n x` in
+# shellcheck disable=SC2039
+case $(echo -n x) in
-n*) echo_n_flag='';;
*) echo_n_flag='-n';;
esac
@@ -143,12 +125,14 @@
while [ -n "$1" ]
do
- value="`echo $1 | sed 's/[^=]*.\(.*\)/\1/'`"
- key="`echo $1 | sed 's/=.*//'`"
- if `echo "$value" | grep "~" >/dev/null 2>/dev/null`
+ value=$(echo "$1" | sed 's/[^=]*.\(.*\)/\1/')
+ key=$(echo "$1" | sed 's/=.*//')
+ # shellcheck disable=SC2088
+ if echo "$value" | grep "~" >/dev/null 2>/dev/null
then
echo
echo '*WARNING*: the "~" sign is not expanded in flags.'
+ # shellcheck disable=SC2016
echo 'If you mean the home directory, use $HOME instead.'
echo
fi
@@ -169,9 +153,8 @@
;;
--ostype)
# TODO make this a switch?
- OSTYPE="$value"
- OSTYPE_SET=yes
- if [ "$OSTYPE" = "debian" ]; then
+ OSPRESET="$value"
+ if [ "$OSPRESET" = "debian" ]; then
if [ "$LUA_SUFFIX_SET" != "yes" ]; then
LUA_SUFFIX="5.1";
LUA_SUFFIX_SET=yes
@@ -184,7 +167,7 @@
LUA_INCDIR_SET=yes
CFLAGS="$CFLAGS -ggdb"
fi
- if [ "$OSTYPE" = "macosx" ]; then
+ if [ "$OSPRESET" = "macosx" ]; then
LUA_INCDIR=/usr/local/include;
LUA_INCDIR_SET=yes
LUA_LIBDIR=/usr/local/lib
@@ -192,14 +175,14 @@
CFLAGS="$CFLAGS -mmacosx-version-min=10.3"
LDFLAGS="-bundle -undefined dynamic_lookup"
fi
- if [ "$OSTYPE" = "linux" ]; then
+ if [ "$OSPRESET" = "linux" ]; then
LUA_INCDIR=/usr/local/include;
LUA_INCDIR_SET=yes
LUA_LIBDIR=/usr/local/lib
LUA_LIBDIR_SET=yes
CFLAGS="$CFLAGS -ggdb"
fi
- if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]; then
+ if [ "$OSPRESET" = "freebsd" ] || [ "$OSPRESET" = "openbsd" ]; then
LUA_INCDIR="/usr/local/include/lua51"
LUA_INCDIR_SET=yes
CFLAGS="-Wall -fPIC -I/usr/local/include"
@@ -211,11 +194,11 @@
CC=cc
LD=ld
fi
- if [ "$OSTYPE" = "openbsd" ]; then
+ if [ "$OSPRESET" = "openbsd" ]; then
LUA_INCDIR="/usr/local/include";
LUA_INCDIR_SET="yes"
fi
- if [ "$OSTYPE" = "netbsd" ]; then
+ if [ "$OSPRESET" = "netbsd" ]; then
LUA_INCDIR="/usr/pkg/include/lua-5.1"
LUA_INCDIR_SET=yes
LUA_LIBDIR="/usr/pkg/lib/lua/5.1"
@@ -223,7 +206,7 @@
CFLAGS="-Wall -fPIC -I/usr/pkg/include"
LDFLAGS="-L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib -shared"
fi
- if [ "$OSTYPE" = "pkg-config" ]; then
+ if [ "$OSPRESET" = "pkg-config" ]; then
if [ "$LUA_SUFFIX_SET" != "yes" ]; then
LUA_SUFFIX="5.1";
LUA_SUFFIX_SET=yes
@@ -254,7 +237,7 @@
--lua-version|--with-lua-version)
[ -n "$value" ] || die "Missing value in flag $key."
LUA_VERSION="$value"
- [ "$LUA_VERSION" = "5.1" -o "$LUA_VERSION" = "5.2" -o "$LUA_VERSION" = "5.3" ] || die "Invalid Lua version in flag $key."
+ [ "$LUA_VERSION" = "5.1" ] || [ "$LUA_VERSION" = "5.2" ] || [ "$LUA_VERSION" = "5.3" ] || die "Invalid Lua version in flag $key."
LUA_VERSION_SET=yes
;;
--with-lua)
@@ -335,7 +318,7 @@
shift
done
-if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ]
+if [ "$PREFIX_SET" = "yes" ] && [ ! "$SYSCONFDIR_SET" = "yes" ]
then
if [ "$PREFIX" = "/usr" ]
then SYSCONFDIR=/etc/$APP_DIRNAME
@@ -343,7 +326,7 @@
fi
fi
-if [ "$PREFIX_SET" = "yes" -a ! "$DATADIR_SET" = "yes" ]
+if [ "$PREFIX_SET" = "yes" ] && [ ! "$DATADIR_SET" = "yes" ]
then
if [ "$PREFIX" = "/usr" ]
then DATADIR=/var/lib/$APP_DIRNAME
@@ -351,13 +334,13 @@
fi
fi
-if [ "$PREFIX_SET" = "yes" -a ! "$LIBDIR_SET" = "yes" ]
+if [ "$PREFIX_SET" = "yes" ] && [ ! "$LIBDIR_SET" = "yes" ]
then
LIBDIR=$PREFIX/lib
fi
detect_lua_version() {
- detected_lua=`$1 -e 'print(_VERSION:match(" (5%.[123])$"))' 2> /dev/null`
+ detected_lua=$("$1" -e 'print(_VERSION:match(" (5%.[123])$"))' 2> /dev/null)
if [ "$detected_lua" != "nil" ]
then
if [ "$LUA_VERSION_SET" != "yes" ]
@@ -386,9 +369,9 @@
find_lua="$LUA_BINDIR"
fi
else
- find_lua=`find_program lua$suffix`
+ find_lua=$(find_program lua"$suffix")
fi
- if [ -n "$find_lua" -a -x "$find_lua/lua$suffix" ]
+ if [ -n "$find_lua" ] && [ -x "$find_lua/lua$suffix" ]
then
if detect_lua_version "$find_lua/lua$suffix"
then
@@ -399,7 +382,7 @@
fi
if [ "$LUA_DIR_SET" != "yes" ]
then
- LUA_DIR=`dirname "$find_lua"`
+ LUA_DIR=$(dirname "$find_lua")
fi
LUA_SUFFIX="$suffix"
return 0
@@ -411,19 +394,19 @@
lua_interp_found=no
if [ "$LUA_SUFFIX_SET" != "yes" ]
then
- if [ "$LUA_VERSION_SET" = "yes" -a "$LUA_VERSION" = "5.1" ]
+ if [ "$LUA_VERSION_SET" = "yes" ] && [ "$LUA_VERSION" = "5.1" ]
then
suffixes="5.1 51 -5.1 -51"
- elif [ "$LUA_VERSION_SET" = "yes" -a "$LUA_VERSION" = "5.2" ]
+ elif [ "$LUA_VERSION_SET" = "yes" ] && [ "$LUA_VERSION" = "5.2" ]
then
suffixes="5.2 52 -5.2 -52"
- elif [ "$LUA_VERSION_SET" = "yes" -a "$LUA_VERSION" = "5.3" ]
+ elif [ "$LUA_VERSION_SET" = "yes" ] && [ "$LUA_VERSION" = "5.3" ]
then
suffixes="5.3 53 -5.3 -53"
else
suffixes="5.1 51 -5.1 -51 5.2 52 -5.2 -52 5.3 53 -5.3 -53"
fi
- for suffix in "" `echo $suffixes`
+ for suffix in "" $suffixes
do
search_interpreter "$suffix" && {
lua_interp_found=yes
@@ -436,15 +419,23 @@
}
fi
-if [ "$lua_interp_found" != "yes" -a "$RUNWITH_SET" != "yes" ]
+if [ "$lua_interp_found" != "yes" ] && [ "$RUNWITH_SET" != "yes" ]
then
- [ "$LUA_VERSION_SET" ] && { interp="Lua $LUA_VERSION" ;} || { interp="Lua" ;}
- [ "$LUA_DIR_SET" -o "$LUA_BINDIR_SET" ] && { where="$LUA_BINDIR" ;} || { where="\$PATH" ;}
+ if [ "$LUA_VERSION_SET" ]; then
+ interp="Lua $LUA_VERSION";
+ else
+ interp="Lua";
+ fi
+ if [ "$LUA_DIR_SET" ] || [ "$LUA_BINDIR_SET" ]; then
+ where="$LUA_BINDIR";
+ else
+ where="\$PATH";
+ fi
echo "$interp interpreter not found in $where"
die "You may want to use the flags --with-lua, --with-lua-bin and/or --lua-suffix. See --help."
fi
-if [ "$LUA_VERSION_SET" = "yes" -a "$RUNWITH_SET" != "yes" ]
+if [ "$LUA_VERSION_SET" = "yes" ] && [ "$RUNWITH_SET" != "yes" ]
then
echo_n "Checking if $LUA_BINDIR/lua$LUA_SUFFIX is Lua version $LUA_VERSION... "
if detect_lua_version "$LUA_BINDIR/lua$LUA_SUFFIX"
@@ -528,6 +519,8 @@
if [ "$PRNG" = "OPENSSL" ]; then
PRNGLIBS=$OPENSSL_LIBS
+elif [ "$PRNG" = "ARC4RANDOM" ] && [ "$(uname)" = "Linux" ]; then
+ PRNGLIBS="-lbsd"
fi
# Write config
diff -r bb8486491b48 -r cc642c9c5ad5 core/certmanager.lua
--- a/core/certmanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/certmanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -55,6 +55,7 @@
};
local _ENV = nil;
+-- luacheck: std none
-- Global SSL options if not overridden per-host
local global_ssl_config = configmanager.get("*", "ssl");
diff -r bb8486491b48 -r cc642c9c5ad5 core/configmanager.lua
--- a/core/configmanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/configmanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -7,12 +7,10 @@
--
local _G = _G;
-local setmetatable, rawget, rawset, io, error, dofile, type, pairs, table =
- setmetatable, rawget, rawset, io, error, dofile, type, pairs, table;
+local setmetatable, rawget, rawset, io, os, error, dofile, type, pairs =
+ setmetatable, rawget, rawset, io, os, error, dofile, type, pairs;
local format, math_max = string.format, math.max;
-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;
@@ -24,10 +22,11 @@
local _M = {};
local _ENV = nil;
+-- luacheck: std none
_M.resolve_relative_path = resolve_relative_path; -- COMPAT
-local parsers = {};
+local parser = nil;
local config_mt = { __index = function (t, _) return rawget(t, "*"); end};
local config = setmetatable({ ["*"] = { } }, config_mt);
@@ -77,19 +76,14 @@
function _M.load(filename, config_format)
config_format = config_format or filename:match("%w+$");
- if parsers[config_format] and parsers[config_format].load then
+ if config_format == "lua" then
local f, err = io.open(filename);
if f then
local new_config = setmetatable({ ["*"] = { } }, config_mt);
- local ok, err = parsers[config_format].load(f:read("*a"), filename, new_config);
+ local ok, err = parser.load(f:read("*a"), filename, new_config);
f:close();
if ok then
config = new_config;
- fire_event("config-reloaded", {
- filename = filename,
- format = config_format,
- config = config
- });
end
return ok, "parser", err;
end
@@ -103,26 +97,11 @@
end
end
-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 config_format in pairs(parsers) do
- table.insert(p, config_format);
- end
- return p;
-end
-
-- Built-in Lua parser
do
local pcall = _G.pcall;
- parsers.lua = {};
- function parsers.lua.load(data, config_file, config_table)
+ parser = {};
+ function parser.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({
@@ -130,6 +109,9 @@
Component = true, component = true,
Include = true, include = true, RunScript = true }, {
__index = function (_, k)
+ if k:match("^ENV_") then
+ return os.getenv(k:sub(5));
+ end
return rawget(_G, k);
end,
__newindex = function (_, k, v)
@@ -211,7 +193,7 @@
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);
+ local ret, err = parser.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
diff -r bb8486491b48 -r cc642c9c5ad5 core/hostmanager.lua
--- a/core/hostmanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/hostmanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -12,8 +12,6 @@
local disco_items = require "util.multitable".new();
local NULL = {};
-local jid_split = require "util.jid".split;
-
local log = require "util.logger".init("hostmanager");
local hosts = prosody.hosts;
@@ -24,11 +22,12 @@
local incoming_s2s = _G.prosody.incoming_s2s;
local core_route_stanza = _G.prosody.core_route_stanza;
-local pairs, select, rawget = pairs, select, rawget;
+local pairs, rawget = pairs, rawget;
local tostring, type = tostring, type;
local setmetatable = setmetatable;
local _ENV = nil;
+-- luacheck: std none
local host_mt = { }
function host_mt:__tostring()
@@ -71,13 +70,6 @@
prosody_events.add_handler("server-starting", load_enabled_hosts);
local function host_send(stanza)
- 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));
- return;
- end
core_route_stanza(nil, stanza);
end
diff -r bb8486491b48 -r cc642c9c5ad5 core/loggingmanager.lua
--- a/core/loggingmanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/loggingmanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -5,7 +5,6 @@
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
--- luacheck: globals log prosody.log
local format = require "util.format".format;
local setmetatable, rawset, pairs, ipairs, type =
@@ -18,12 +17,9 @@
local config = require "core.configmanager";
local logger = require "util.logger";
-local prosody = prosody;
-
-_G.log = logger.init("general");
-prosody.log = logger.init("general");
local _ENV = nil;
+-- luacheck: std none
-- The log config used if none specified in the config file (see reload_logging for initialization)
local default_logging;
@@ -154,13 +150,8 @@
for name, sink_maker in pairs(old_sink_types) do
log_sink_types[name] = sink_maker;
end
-
- prosody.events.fire_event("logging-reloaded");
end
-reload_logging();
-prosody.events.add_handler("config-reloaded", reload_logging);
-
--- Definition of built-in logging sinks ---
-- Null sink, must enter log_sink_types *first*
diff -r bb8486491b48 -r cc642c9c5ad5 core/moduleapi.lua
--- a/core/moduleapi.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/moduleapi.lua Mon Nov 26 19:48:07 2018 +0100
@@ -6,7 +6,6 @@
-- COPYING file in the source package for more information.
--
-local config = require "core.configmanager";
local array = require "util.array";
local set = require "util.set";
local it = require "util.iterators";
@@ -14,15 +13,15 @@
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 = ipairs, pairs, select;
-local unpack = table.unpack or unpack; --luacheck: ignore 113
local tonumber, tostring = tonumber, tostring;
local require = require;
+local pack = table.pack or function(...) return {n=select("#",...), ...}; end -- table.pack is only in 5.2
+local unpack = table.unpack or unpack; --luacheck: ignore 113 -- renamed in 5.2
local prosody = prosody;
local hosts = prosody.hosts;
@@ -70,20 +69,6 @@
function api:add_extension(data)
self:add_item("extension", data);
end
-function api:has_feature(xmlns)
- for _, feature in ipairs(self:get_host_items("feature")) do
- if feature == xmlns then return true; end
- end
- return false;
-end
-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 == identity_type and id.name == name then
- return true;
- end
- end
- return false;
-end
function api:fire_event(...)
return (hosts[self.host] or prosody).events.fire_event(...);
@@ -144,6 +129,9 @@
function api:depends(name)
local modulemanager = require"core.modulemanager";
+ if self:get_option_inherited_set("modules_disabled", {}):contains(name) then
+ error("Dependency on disabled module mod_"..name);
+ end
if not self.dependencies then
self.dependencies = {};
self:hook("module-reloaded", function (event)
@@ -176,36 +164,36 @@
return mod;
end
--- Returns one or more shared tables at the specified virtual paths
--- Intentionally does not allow the table at a path to be _set_, it
+local function get_shared_table_from_path(module, tables, path)
+ if path:sub(1,1) ~= "/" then -- Prepend default components
+ local default_path_components = { module.host, module.name };
+ local n_components = select(2, path:gsub("/", "%1"));
+ path = (n_components<#default_path_components and "/" or "")
+ ..t_concat(default_path_components, "/", 1, #default_path_components-n_components).."/"..path;
+ end
+ local shared = tables[path];
+ if not shared then
+ shared = {};
+ if path:match("%-cache$") then
+ setmetatable(shared, { __mode = "kv" });
+ end
+ tables[path] = shared;
+ end
+ return shared;
+end
+
+-- Returns a shared table at the specified virtual path
+-- Intentionally does not allow the table to be _set_, it
-- is auto-created if it does not exist.
-function api:shared(...)
+function api:shared(path)
if not self.shared_data then self.shared_data = {}; end
- local paths = { n = select("#", ...), ... };
- local data_array = {};
- local default_path_components = { self.host, self.name };
- for i = 1, paths.n do
- local path = paths[i];
- if path:sub(1,1) ~= "/" then -- Prepend default components
- local n_components = select(2, path:gsub("/", "%1"));
- path = (n_components<#default_path_components and "/" or "")
- ..t_concat(default_path_components, "/", 1, #default_path_components-n_components).."/"..path;
- end
- local shared = shared_data[path];
- if not shared then
- shared = {};
- if path:match("%-cache$") then
- setmetatable(shared, { __mode = "kv" });
- end
- shared_data[path] = shared;
- end
- t_insert(data_array, shared);
- self.shared_data[path] = shared;
- end
- return unpack(data_array);
+ local shared = get_shared_table_from_path(self, shared_data, path);
+ self.shared_data[path] = shared;
+ return shared;
end
function api:get_option(name, default_value)
+ local config = require "core.configmanager";
local value = config.get(self.host, name);
if value == nil then
value = default_value;
@@ -299,7 +287,7 @@
function api:get_option_path(name, default, parent)
if parent == nil then
- parent = parent or self:get_directory();
+ parent = self:get_directory();
elseif prosody.paths[parent] then
parent = prosody.paths[parent];
end
@@ -377,15 +365,33 @@
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);
+ self:send(new_stanza);
end
end
-function api:add_timer(delay, callback)
- return timer.add_task(delay, function (t)
- if self.loaded == false then return; end
- return callback(t);
- end);
+local timer_methods = { }
+local timer_mt = {
+ __index = timer_methods;
+}
+function timer_methods:stop( )
+ timer.stop(self.id);
+end
+timer_methods.disarm = timer_methods.stop
+function timer_methods:reschedule(delay)
+ timer.reschedule(self.id, delay)
+end
+
+local function timer_callback(now, id, t) --luacheck: ignore 212/id
+ if t.module_env.loaded == false then return; end
+ return t.callback(now, unpack(t, 1, t.n));
+end
+
+function api:add_timer(delay, callback, ...)
+ local t = pack(...)
+ t.module_env = self;
+ t.callback = callback;
+ t.id = timer.add_task(delay, timer_callback, t);
+ return setmetatable(t, timer_mt);
end
local path_sep = package.config:sub(1,1);
@@ -403,6 +409,7 @@
end
function api:measure(name, stat_type)
+ local measure = require "core.statsmanager".measure;
return measure(stat_type, "/"..self.host.."/mod_"..self.name.."/"..name);
end
diff -r bb8486491b48 -r cc642c9c5ad5 core/modulemanager.lua
--- a/core/modulemanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/modulemanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -15,21 +15,13 @@
local new_multitable = require "util.multitable".new;
local api = require "core.moduleapi"; -- Module API container
-local hosts = hosts;
local prosody = prosody;
+local hosts = prosody.hosts;
-local xpcall = xpcall;
-local setmetatable, rawget = setmetatable, rawget;
-local ipairs, pairs, type, tostring, t_insert = ipairs, pairs, type, tostring, table.insert;
-
+local xpcall = require "util.xpcall".xpcall;
local debug_traceback = debug.traceback;
-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 setmetatable, rawget = setmetatable, rawget;
+local ipairs, pairs, type, t_insert = ipairs, pairs, type, table.insert;
local autoload_modules = {prosody.platform, "presence", "message", "iq", "offline", "c2s", "s2s", "s2s_auth_certs"};
local component_inheritable_modules = {"tls", "saslauth", "dialback", "iq", "s2s"};
@@ -38,6 +30,7 @@
local _G = _G;
local _ENV = nil;
+-- luacheck: std none
local load_modules_for_host, load, unload, reload, get_module, get_items;
local get_modules, is_loaded, module_has_method, call_module_method;
@@ -45,8 +38,8 @@
-- [host] = { [module] = module_env }
local modulemap = { ["*"] = {} };
--- Load modules when a host is activated
-function load_modules_for_host(host)
+-- Get the list of modules to be loaded on a host
+local function get_modules_for_host(host)
local component = config.get(host, "component_module");
local global_modules_enabled = config.get("*", "modules_enabled");
@@ -70,8 +63,16 @@
modules:add("admin_telnet");
end
- if component then
- load(host, component);
+ return modules, component;
+end
+
+-- Load modules when a host is activated
+function load_modules_for_host(host)
+ local modules, component_module = get_modules_for_host(host);
+
+ -- Ensure component module is loaded first
+ if component_module then
+ load(host, component_module);
end
for module in modules do
load(host, module);
@@ -174,7 +175,7 @@
api_instance.path = err;
modulemap[host][module_name] = pluginenv;
- local ok, err = pcall(mod);
+ local ok, err = xpcall(mod, debug_traceback);
if ok then
-- Call module's "load"
if module_has_method(pluginenv, "load") then
@@ -316,13 +317,14 @@
function call_module_method(module, method, ...)
local f = rawget(module.module, method);
if type(f) == "function" then
- return pcall(f, ...);
+ return xpcall(f, debug_traceback, ...);
else
return false, "no-such-method";
end
end
return {
+ get_modules_for_host = get_modules_for_host;
load_modules_for_host = load_modules_for_host;
load = load;
unload = unload;
diff -r bb8486491b48 -r cc642c9c5ad5 core/portmanager.lua
--- a/core/portmanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/portmanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -15,6 +15,7 @@
local fire_event = prosody.events.fire_event;
local _ENV = nil;
+-- luacheck: std none
--- Config
diff -r bb8486491b48 -r cc642c9c5ad5 core/rostermanager.lua
--- a/core/rostermanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/rostermanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -11,11 +11,13 @@
local log = require "util.logger".init("rostermanager");
+local new_id = require "util.id".short;
+
local pairs = pairs;
local tostring = tostring;
local type = type;
-local hosts = hosts;
+local hosts = prosody.hosts;
local bare_sessions = prosody.bare_sessions;
local um_user_exists = require "core.usermanager".user_exists;
@@ -23,6 +25,7 @@
local storagemanager = require "core.storagemanager";
local _ENV = nil;
+-- luacheck: std none
local save_roster; -- forward declaration
@@ -60,7 +63,7 @@
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"});
+ local stanza = st.iq({type="set", id=new_id()});
stanza:tag("query", {xmlns = "jabber:iq:roster", ver = tostring(roster[false].version or "1") });
if item then
stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
diff -r bb8486491b48 -r cc642c9c5ad5 core/s2smanager.lua
--- a/core/s2smanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/s2smanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -17,12 +17,13 @@
local log = logger_init("s2smanager");
local prosody = _G.prosody;
-incoming_s2s = {};
+local incoming_s2s = {};
+_G.incoming_s2s = incoming_s2s;
prosody.incoming_s2s = incoming_s2s;
-local incoming_s2s = incoming_s2s;
local fire_event = prosody.events.fire_event;
local _ENV = nil;
+-- luacheck: std none
local function new_incoming(conn)
local session = { conn = conn, type = "s2sin_unauthed", direction = "incoming", hosts = {} };
@@ -64,6 +65,7 @@
function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end
function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
+ session.thread = { run = function (_, data) return session.data(data) end };
session.sends2s = session.send;
return setmetatable(session, resting_session);
end
diff -r bb8486491b48 -r cc642c9c5ad5 core/sessionmanager.lua
--- a/core/sessionmanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/sessionmanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -10,7 +10,7 @@
local tostring, setmetatable = tostring, setmetatable;
local pairs, next= pairs, next;
-local hosts = hosts;
+local hosts = prosody.hosts;
local full_sessions = prosody.full_sessions;
local bare_sessions = prosody.bare_sessions;
@@ -20,12 +20,13 @@
local config_get = require "core.configmanager".get;
local resourceprep = require "util.encodings".stringprep.resourceprep;
local nodeprep = require "util.encodings".stringprep.nodeprep;
-local uuid_generate = require "util.uuid".generate;
+local generate_identifier = require "util.id".short;
local initialize_filters = require "util.filters".initialize;
local gettime = require "socket".gettime;
local _ENV = nil;
+-- luacheck: std none
local function new_session(conn)
local session = { conn = conn, type = "c2s_unauthed", conntime = gettime() };
@@ -74,6 +75,7 @@
function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); return false; end
function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
+ session.thread = { run = function (_, data) return session.data(data) end };
return setmetatable(session, resting_session);
end
@@ -137,7 +139,7 @@
end
resource = resourceprep(resource);
- resource = resource ~= "" and resource or uuid_generate();
+ resource = resource ~= "" and resource or generate_identifier();
--FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
if not hosts[session.host].sessions[session.username] then
@@ -151,7 +153,7 @@
local policy = config_get(session.host, "conflict_resolve");
local increment;
if policy == "random" then
- resource = uuid_generate();
+ resource = generate_identifier();
increment = true;
elseif policy == "increment" then
increment = true; -- TODO ping old resource
diff -r bb8486491b48 -r cc642c9c5ad5 core/stanza_router.lua
--- a/core/stanza_router.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/stanza_router.lua Mon Nov 26 19:48:07 2018 +0100
@@ -19,16 +19,6 @@
local core_post_stanza, core_process_stanza, core_route_stanza;
-function deprecated_warning(f)
- _G[f] = function(...)
- log("warn", "Using the global %s() is deprecated, use module:send() or prosody.%s(). %s", f, f, debug.traceback());
- return prosody[f](...);
- end
-end
-deprecated_warning"core_post_stanza";
-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) --luacheck: ignore 212/host
local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
diff -r bb8486491b48 -r cc642c9c5ad5 core/storagemanager.lua
--- a/core/storagemanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/storagemanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -1,17 +1,21 @@
local type, pairs = type, pairs;
local setmetatable = setmetatable;
+local rawset = rawset;
local config = require "core.configmanager";
local datamanager = require "util.datamanager";
local modulemanager = require "core.modulemanager";
local multitable = require "util.multitable";
-local hosts = hosts;
local log = require "util.logger".init("storagemanager");
+local async = require "util.async";
+local debug = debug;
local prosody = prosody;
+local hosts = prosody.hosts;
local _ENV = nil;
+-- luacheck: std none
local olddm = {}; -- maintain old datamanager, for backwards compatibility
for k,v in pairs(datamanager) do olddm[k] = v; end
@@ -28,8 +32,34 @@
}
);
+local async_check = config.get("*", "storage_async_check") == true;
+
local stores_available = multitable.new();
+local function check_async_wrapper(event)
+ local store = event.store;
+ event.store = setmetatable({}, {
+ __index = function (t, method_name)
+ local original_method = store[method_name];
+ if type(original_method) ~= "function" then
+ if original_method then
+ rawset(t, method_name, original_method);
+ end
+ return original_method;
+ end
+ local wrapped_method = function (...)
+ if not async.ready() then
+ log("warn", "ASYNC-01: Attempt to access storage outside async context, "
+ .."see https://prosody.im/doc/developers/async - %s", debug.traceback());
+ end
+ return original_method(...);
+ end
+ rawset(t, method_name, wrapped_method);
+ return wrapped_method;
+ end;
+ });
+end
+
local function initialize_host(host)
local host_session = hosts[host];
host_session.events.add_handler("item-added/storage-provider", function (event)
@@ -41,6 +71,9 @@
local item = event.item;
stores_available:set(host, item.name, nil);
end);
+ if async_check then
+ host_session.events.add_handler("store-opened", check_async_wrapper);
+ end
end
prosody.events.add_handler("host-activated", initialize_host, 101);
@@ -137,7 +170,7 @@
};
}
-local open;
+local open; -- forward declaration
local function create_map_shim(host, store)
local keyval_store, err = open(host, store, "keyval");
diff -r bb8486491b48 -r cc642c9c5ad5 core/usermanager.lua
--- a/core/usermanager.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/core/usermanager.lua Mon Nov 26 19:48:07 2018 +0100
@@ -13,17 +13,18 @@
local jid_bare = require "util.jid".bare;
local jid_prep = require "util.jid".prep;
local config = require "core.configmanager";
-local hosts = hosts;
local sasl_new = require "util.sasl".new;
local storagemanager = require "core.storagemanager";
local prosody = _G.prosody;
+local hosts = prosody.hosts;
local setmetatable = setmetatable;
local default_provider = "internal_plain";
local _ENV = nil;
+-- luacheck: std none
local function new_null_provider()
local function dummy() return nil, "method not implemented"; end;
diff -r bb8486491b48 -r cc642c9c5ad5 doc/coding_style.txt
--- a/doc/coding_style.txt Sun Nov 25 13:16:17 2018 +0100
+++ b/doc/coding_style.txt Mon Nov 26 19:48:07 2018 +0100
@@ -7,7 +7,7 @@
== Spacing ==
-No space between function names and parenthesis and parenthesis and paramters:
+No space between function names and parenthesis and parenthesis and parameters:
function foo(bar, baz)
diff -r bb8486491b48 -r cc642c9c5ad5 doc/names.txt
--- a/doc/names.txt Sun Nov 25 13:16:17 2018 +0100
+++ b/doc/names.txt Mon Nov 26 19:48:07 2018 +0100
@@ -15,7 +15,7 @@
Eclaire - Idem (French)
Adel - Random
Younha - Read as "yuna"
- Quezacotl - Mayan gods -> google for correct form and pronounciation
+ Quezacotl - Mayan gods -> google for correct form and pronunciation
Carbuncle - FF8 Guardian Force ^^
Protos - Mars satellite
mins - Derived from minstrel
diff -r bb8486491b48 -r cc642c9c5ad5 doc/net.server.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/net.server.lua Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,258 @@
+-- Prosody IM
+-- Copyright (C) 2014,2016 Daurnimator
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+
+--luacheck: ignore
+
+--[[
+This file is a template for writing a net.server compatible backend.
+]]
+
+--[[
+Read patterns (also called modes) can be one of:
+ - "*a": Read as much as possible
+ - "*l": Read until end of line
+]]
+
+--- Handle API
+local handle_mt = {};
+local handle_methods = {};
+handle_mt.__index = handle_methods;
+
+function handle_methods:set_mode(new_pattern)
+end
+
+function handle_methods:setlistener(listeners)
+end
+
+function handle_methods:setoption(option, value)
+end
+
+function handle_methods:ip()
+end
+
+function handle_methods:starttls(sslctx)
+end
+
+function handle_methods:write(data)
+end
+
+function handle_methods:close()
+end
+
+function handle_methods:pause()
+end
+
+function handle_methods:resume()
+end
+
+--[[
+Returns
+ - socket: the socket object underlying this handle
+]]
+function handle_methods:socket()
+end
+
+--[[
+Returns
+ - boolean: if an ssl context has been set on this handle
+]]
+function handle_methods:ssl()
+end
+
+
+--- Listeners API
+local listeners = {}
+
+--[[ connect
+Called when a client socket has established a connection with it's peer
+]]
+function listeners.onconnect(handle)
+end
+
+--[[ incoming
+Called when data is received
+If reading data failed this will be called with `nil, "error message"`
+]]
+function listeners.onincoming(handle, buff, err)
+end
+
+--[[ status
+Known statuses:
+ - "ssl-handshake-complete"
+]]
+function listeners.onstatus(handle, status)
+end
+
+--[[ disconnect
+Called when the peer has closed the connection
+]]
+function listeners.ondisconnect(handle)
+end
+
+--[[ drain
+Called when the handle's write buffer is empty
+]]
+function listeners.ondrain(handle)
+end
+
+--[[ readtimeout
+Called when a socket inactivity timeout occurs
+]]
+function listeners.onreadtimeout(handle)
+end
+
+--[[ detach: Called when other listeners are going to be removed
+Allows for clean-up
+]]
+function listeners.ondetach(handle)
+end
+
+--- Top level functions
+
+--[[ Returns the syscall level event mechanism in use.
+
+Returns:
+ - backend: e.g. "select", "epoll"
+]]
+local function get_backend()
+end
+
+--[[ Starts the event loop.
+
+Returns:
+ - "quitting"
+]]
+local function loop()
+end
+
+--[[ Stop a running loop()
+]]
+local function setquitting(quit)
+end
+
+
+--[[ Links to two handles together, so anything written to one is piped to the other
+
+Arguments:
+ - sender, receiver: handles to link
+ - buffersize: maximum #bytes until sender will be locked
+]]
+local function link(sender, receiver, buffersize)
+end
+
+--[[ Binds and listens on the given address and port
+If `sslctx` is given, the connecting clients will have to negotiate an SSL session
+
+Arguments:
+ - address: address to bind to, may be "*" to bind all addresses. will be resolved if it is a string.
+ - port: port to bind (as number)
+ - listeners: a table of listeners
+ - pattern: the read pattern
+ - sslctx: is a valid luasec constructor
+
+Returns:
+ - handle
+ - nil, "an error message": on failure (e.g. out of file descriptors)
+]]
+local function addserver(address, port, listeners, pattern, sslctx)
+end
+
+--[[ Wraps a lua-socket socket client socket in a handle.
+The socket must be already connected to the remote end.
+If `sslctx` is given, a SSL session will be negotiated before listeners are called.
+
+Arguments:
+ - socket: the lua-socket object to wrap
+ - ip: returned by `handle:ip()`
+ - port:
+ - listeners: a table of listeners
+ - pattern: the read pattern
+ - sslctx: is a valid luasec constructor
+ - typ: the socket type, one of:
+ - "tcp"
+ - "tcp6"
+ - "udp"
+
+Returns:
+ - handle, socket
+ - nil, "an error message": on failure (e.g. )
+]]
+local function wrapclient(socket, ip, serverport, listeners, pattern, sslctx)
+end
+
+--[[ Connects to the given address and port
+If `sslctx` is given, a SSL session will be negotiated before listeners are called.
+
+Arguments:
+ - address: address to connect to. will be resolved if it is a string.
+ - port: port to connect to (as number)
+ - listeners: a table of listeners
+ - pattern: the read pattern
+ - sslctx: is a valid luasec constructor
+ - typ: the socket type, one of:
+ - "tcp"
+ - "tcp6"
+ - "udp"
+
+Returns:
+ - handle
+ - nil, "an error message": on failure (e.g. out of file descriptors)
+]]
+local function addclient(address, port, listeners, pattern, sslctx, typ)
+end
+
+--[[ Close all handles
+]]
+local function closeall()
+end
+
+--[[ The callback should be called after `delay` seconds.
+The callback should be called with the time at the point of firing.
+If the callback returns a number, it should be called again after that many seconds.
+
+Arguments:
+ - delay: number of seconds to wait
+ - callback: function to call.
+]]
+local function add_task(delay, callback)
+end
+
+--[[ Adds a handler for when a signal is fired.
+Optional to implement
+callback does not take any arguments
+
+Arguments:
+ - signal_id: the signal id (as number) to listen for
+ - handler: callback
+]]
+local function hook_signal(signal_id, handler)
+end
+
+--[[ Adds a low-level FD watcher
+Arguments:
+- fd_number: A non-negative integer representing a file descriptor or
+ object with a :getfd() method returning one
+- on_readable: Optional callback for when the FD is readable
+- on_writable: Optional callback for when the FD is writable
+
+Returns:
+- net.server handle
+]]
+local function watchfd(fd_number, on_readable, on_writable)
+end
+
+return {
+ get_backend = get_backend;
+ loop = loop;
+ setquitting = setquitting;
+ link = link;
+ addserver = addserver;
+ wrapclient = wrapclient;
+ addclient = addclient;
+ closeall = closeall;
+ hook_signal = hook_signal;
+ watchfd = watchfd;
+}
diff -r bb8486491b48 -r cc642c9c5ad5 doc/session.txt
--- a/doc/session.txt Sun Nov 25 13:16:17 2018 +0100
+++ b/doc/session.txt Mon Nov 26 19:48:07 2018 +0100
@@ -15,12 +15,12 @@
full_jid -- convenience for the above 3 as string in username@host/resource form (not defined before resource binding)
priority -- the resource priority, default: 0
presence -- the last non-directed presence with no type attribute. initially nil. reset to nil on unavailable presence.
- interested -- true if the resource requested the roster. Interested resources recieve roster updates. Initially nil.
+ interested -- true if the resource requested the roster. Interested resources receive roster updates. Initially nil.
roster -- the user's roster. Loaded as soon as the resource is bound (session becomes a connected resource).
-- methods --
send(x) -- converts x to a string, and writes it to the connection
- disconnect(x) -- Disconnect the user and clean up the session, best call sessionmanager.destroy_session() instead of this in most cases
+ close(x) -- Disconnect the user and clean up the session, best call sessionmanager.destroy_session() instead of this in most cases
}
if session.full_jid (also session.roster and session.resource) then this is a "connected resource"
diff -r bb8486491b48 -r cc642c9c5ad5 makefile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/makefile Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,101 @@
+
+include config.unix
+
+BIN = $(DESTDIR)$(PREFIX)/bin
+CONFIG = $(DESTDIR)$(SYSCONFDIR)
+MODULES = $(DESTDIR)$(LIBDIR)/prosody/modules
+SOURCE = $(DESTDIR)$(LIBDIR)/prosody
+DATA = $(DESTDIR)$(DATADIR)
+MAN = $(DESTDIR)$(PREFIX)/share/man
+
+INSTALLEDSOURCE = $(LIBDIR)/prosody
+INSTALLEDCONFIG = $(SYSCONFDIR)
+INSTALLEDMODULES = $(LIBDIR)/prosody/modules
+INSTALLEDDATA = $(DATADIR)
+
+INSTALL=install -p
+INSTALL_DATA=$(INSTALL) -m644
+INSTALL_EXEC=$(INSTALL) -m755
+MKDIR=install -d
+MKDIR_PRIVATE=$(MKDIR) -m750
+
+.PHONY: all test clean install
+
+all: prosody.install prosodyctl.install prosody.cfg.lua.install prosody.version
+ $(MAKE) -C util-src install
+.if $(EXCERTS) == "yes"
+ $(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
+ $(MKDIR) $(BIN) $(CONFIG) $(MODULES) $(SOURCE)
+ $(MKDIR_PRIVATE) $(DATA)
+ $(MKDIR) $(MAN)/man1
+ $(MKDIR) $(CONFIG)/certs
+ $(MKDIR) $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util
+ $(INSTALL_EXEC) ./prosody.install $(BIN)/prosody
+ $(INSTALL_EXEC) ./prosodyctl.install $(BIN)/prosodyctl
+ $(INSTALL_DATA) core/*.lua $(SOURCE)/core
+ $(INSTALL_DATA) net/*.lua $(SOURCE)/net
+ $(MKDIR) $(SOURCE)/net/http $(SOURCE)/net/resolvers $(SOURCE)/net/websocket
+ $(INSTALL_DATA) net/http/*.lua $(SOURCE)/net/http
+ $(INSTALL_DATA) net/resolvers/*.lua $(SOURCE)/net/resolvers
+ $(INSTALL_DATA) net/websocket/*.lua $(SOURCE)/net/websocket
+ $(INSTALL_DATA) util/*.lua $(SOURCE)/util
+ $(INSTALL_DATA) util/*.so $(SOURCE)/util
+ $(MKDIR) $(SOURCE)/util/sasl
+ $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl
+ $(MKDIR) $(MODULES)/mod_s2s $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam
+ $(INSTALL_DATA) plugins/*.lua $(MODULES)
+ $(INSTALL_DATA) plugins/mod_s2s/*.lua $(MODULES)/mod_s2s
+ $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub
+ $(INSTALL_DATA) plugins/adhoc/*.lua $(MODULES)/adhoc
+ $(INSTALL_DATA) plugins/muc/*.lua $(MODULES)/muc
+ $(INSTALL_DATA) plugins/mod_mam/*.lua $(MODULES)/mod_mam
+.if $(EXCERTS) == "yes"
+ $(INSTALL_DATA) certs/localhost.crt certs/localhost.key $(CONFIG)/certs
+ $(INSTALL_DATA) certs/example.com.crt certs/example.com.key $(CONFIG)/certs
+.endif
+ $(INSTALL_DATA) man/prosodyctl.man $(MAN)/man1/prosodyctl.1
+ test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
+ -test -f prosody.version && $(INSTALL_DATA) prosody.version $(SOURCE)/prosody.version
+ $(MAKE) install -C util-src
+
+clean:
+ rm -f prosody.install
+ rm -f prosodyctl.install
+ rm -f prosody.cfg.lua.install
+ rm -f prosody.version
+ $(MAKE) clean -C util-src
+
+test:
+ busted --lua=$(RUNWITH)
+
+
+prosody.install: prosody
+ sed "1s| lua$$| $(RUNWITH)|; \
+ s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \
+ s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|; \
+ s|^CFG_DATADIR=.*;$$|CFG_DATADIR='$(INSTALLEDDATA)';|; \
+ s|^CFG_PLUGINDIR=.*;$$|CFG_PLUGINDIR='$(INSTALLEDMODULES)/';|;" < prosody > $@
+
+prosodyctl.install: prosodyctl
+ sed "1s| lua$$| $(RUNWITH)|; \
+ s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \
+ s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|; \
+ s|^CFG_DATADIR=.*;$$|CFG_DATADIR='$(INSTALLEDDATA)';|; \
+ s|^CFG_PLUGINDIR=.*;$$|CFG_PLUGINDIR='$(INSTALLEDMODULES)/';|;" < prosodyctl > $@
+
+prosody.cfg.lua.install: prosody.cfg.lua.dist
+ sed 's|certs/|$(INSTALLEDCONFIG)/certs/|' prosody.cfg.lua.dist > $@
+
+prosody.version:
+ test -f prosody.release && \
+ cp prosody.release $@ || \
+ test -f .hg_archival.txt && \
+ sed -n 's/^node: \(............\).*/\1/p' .hg_archival.txt > $@ || \
+ test -f .hg/dirstate && \
+ hexdump -n6 -e'6/1 "%02x"' .hg/dirstate > $@ || \
+ echo unknown > $@
+
+
diff -r bb8486491b48 -r cc642c9c5ad5 net/adns.lua
--- a/net/adns.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/adns.lua Mon Nov 26 19:48:07 2018 +0100
@@ -17,6 +17,7 @@
local function dummy_send(sock, data, i, j) return (j-i)+1; end
local _ENV = nil;
+-- luacheck: std none
local async_resolver_methods = {};
local async_resolver_mt = { __index = async_resolver_methods };
diff -r bb8486491b48 -r cc642c9c5ad5 net/connect.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/net/connect.lua Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,89 @@
+local server = require "net.server";
+local log = require "util.logger".init("net.connect");
+local new_id = require "util.id".short;
+
+local pending_connection_methods = {};
+local pending_connection_mt = {
+ __name = "pending_connection";
+ __index = pending_connection_methods;
+ __tostring = function (p)
+ return "";
+ end;
+};
+
+function pending_connection_methods:log(level, message, ...)
+ log(level, "[pending connection %s] "..message, self.id, ...);
+end
+
+-- pending_connections_map[conn] = pending_connection
+local pending_connections_map = {};
+
+local pending_connection_listeners = {};
+
+local function attempt_connection(p)
+ p:log("debug", "Checking for targets...");
+ if p.conn then
+ pending_connections_map[p.conn] = nil;
+ p.conn = nil;
+ end
+ p.target_resolver:next(function (conn_type, ip, port, extra)
+ if not conn_type then
+ -- No more targets to try
+ p:log("debug", "No more connection targets to try");
+ if p.listeners.onfail then
+ p.listeners.onfail(p.data, p.last_error or "unable to resolve service");
+ end
+ return;
+ end
+ p:log("debug", "Next target to try is %s:%d", ip, port);
+ local conn, err = server.addclient(ip, port, pending_connection_listeners, p.options.pattern or "*a", p.options.sslctx, conn_type, extra);
+ if not conn then
+ log("debug", "Connection attempt failed immediately: %s", tostring(err));
+ p.last_error = err or "unknown reason";
+ return attempt_connection(p);
+ end
+ p.conn = conn;
+ pending_connections_map[conn] = p;
+ end);
+end
+
+function pending_connection_listeners.onconnect(conn)
+ local p = pending_connections_map[conn];
+ if not p then
+ log("warn", "Successful connection, but unexpected! Closing.");
+ conn:close();
+ return;
+ end
+ pending_connections_map[conn] = nil;
+ p:log("debug", "Successfully connected");
+ conn:setlistener(p.listeners, p.data);
+ return p.listeners.onconnect(conn);
+end
+
+function pending_connection_listeners.ondisconnect(conn, reason)
+ local p = pending_connections_map[conn];
+ if not p then
+ log("warn", "Failed connection, but unexpected!");
+ return;
+ end
+ p.last_error = reason or "unknown reason";
+ p:log("debug", "Connection attempt failed: %s", p.last_error);
+ attempt_connection(p);
+end
+
+local function connect(target_resolver, listeners, options, data)
+ local p = setmetatable({
+ id = new_id();
+ target_resolver = target_resolver;
+ listeners = assert(listeners);
+ options = options or {};
+ data = data;
+ }, pending_connection_mt);
+
+ p:log("debug", "Starting connection process");
+ attempt_connection(p);
+end
+
+return {
+ connect = connect;
+};
diff -r bb8486491b48 -r cc642c9c5ad5 net/connlisteners.lua
--- a/net/connlisteners.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/connlisteners.lua Mon Nov 26 19:48:07 2018 +0100
@@ -3,15 +3,15 @@
local traceback = debug.traceback;
local _ENV = nil;
+-- luacheck: std none
local function fail()
- log("error", "Attempt to use legacy connlisteners API. For more info see http://prosody.im/doc/developers/network");
+ log("error", "Attempt to use legacy connlisteners API. For more info see https://prosody.im/doc/developers/network");
log("error", "Legacy connlisteners API usage, %s", traceback("", 2));
end
return {
register = fail;
- register = fail;
get = fail;
start = fail;
-- epic fail
diff -r bb8486491b48 -r cc642c9c5ad5 net/cqueues.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/net/cqueues.lua Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,74 @@
+-- Prosody IM
+-- Copyright (C) 2014 Daurnimator
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+-- This module allows you to use cqueues with a net.server mainloop
+--
+
+local server = require "net.server";
+local cqueues = require "cqueues";
+assert(cqueues.VERSION >= 20150113, "cqueues newer than 20150113 required")
+
+-- Create a single top level cqueue
+local cq;
+
+if server.cq then -- server provides cqueues object
+ cq = server.cq;
+elseif server.get_backend() == "select" and server._addtimer then -- server_select
+ cq = cqueues.new();
+ local function step()
+ assert(cq:loop(0));
+ end
+
+ -- Use wrapclient (as wrapconnection isn't exported) to get server_select to watch cq fd
+ local handler = server.wrapclient({
+ getfd = function() return cq:pollfd(); end;
+ settimeout = function() end; -- Method just needs to exist
+ close = function() end; -- Need close method for 'closeall'
+ }, nil, nil, {});
+
+ -- Only need to listen for readable; cqueues handles everything under the hood
+ -- readbuffer is called when `select` notes an fd as readable
+ handler.readbuffer = step;
+
+ -- Use server_select low lever timer facility,
+ -- this callback gets called *every* time there is a timeout in the main loop
+ server._addtimer(function(current_time)
+ -- This may end up in extra step()'s, but cqueues handles it for us.
+ step();
+ return cq:timeout();
+ end);
+elseif server.event and server.base then -- server_event
+ cq = cqueues.new();
+ -- Only need to listen for readable; cqueues handles everything under the hood
+ local EV_READ = server.event.EV_READ;
+ -- Convert a cqueues timeout to an acceptable timeout for luaevent
+ local function luaevent_safe_timeout(cq)
+ local t = cq:timeout();
+ -- if you give luaevent 0 or nil, it re-uses the previous timeout.
+ if t == 0 then
+ t = 0.000001; -- 1 microsecond is the smallest that works (goes into a `struct timeval`)
+ elseif t == nil then -- pick something big if we don't have one
+ t = 0x7FFFFFFF; -- largest 32bit int
+ end
+ return t
+ end
+ local event_handle;
+ event_handle = server.base:addevent(cq:pollfd(), EV_READ, function(e)
+ -- Need to reference event_handle or this callback will get collected
+ -- This creates a circular reference that can only be broken if event_handle is manually :close()'d
+ local _ = event_handle;
+ -- Run as many cqueues things as possible (with a timeout of 0)
+ -- If an error is thrown, it will break the libevent loop; but prosody resumes after logging a top level error
+ assert(cq:loop(0));
+ return EV_READ, luaevent_safe_timeout(cq);
+ end, luaevent_safe_timeout(cq));
+else
+ error "NYI"
+end
+
+return {
+ cq = cq;
+}
diff -r bb8486491b48 -r cc642c9c5ad5 net/dns.lua
--- a/net/dns.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/dns.lua Mon Nov 26 19:48:07 2018 +0100
@@ -15,6 +15,7 @@
local socket = require "socket";
local timer = require "util.timer";
local new_ip = require "util.ip".new_ip;
+local have_util_net, util_net = pcall(require, "util.net");
local _, windows = pcall(require, "util.windows");
local is_windows = (_ and windows) or os.getenv("WINDIR");
@@ -72,6 +73,7 @@
-------------------------------------------------- module dns
local _ENV = nil;
+-- luacheck: std none
local dns = {};
@@ -119,11 +121,99 @@
dns.types = {
- 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', 'NULL', 'WKS',
- 'PTR', 'HINFO', 'MINFO', 'MX', 'TXT',
- [ 28] = 'AAAA', [ 29] = 'LOC', [ 33] = 'SRV',
- [252] = 'AXFR', [253] = 'MAILB', [254] = 'MAILA', [255] = '*' };
-
+ [1] = "A", -- a host address,[RFC1035],,
+ [2] = "NS", -- an authoritative name server,[RFC1035],,
+ [3] = "MD", -- a mail destination (OBSOLETE - use MX),[RFC1035],,
+ [4] = "MF", -- a mail forwarder (OBSOLETE - use MX),[RFC1035],,
+ [5] = "CNAME", -- the canonical name for an alias,[RFC1035],,
+ [6] = "SOA", -- marks the start of a zone of authority,[RFC1035],,
+ [7] = "MB", -- a mailbox domain name (EXPERIMENTAL),[RFC1035],,
+ [8] = "MG", -- a mail group member (EXPERIMENTAL),[RFC1035],,
+ [9] = "MR", -- a mail rename domain name (EXPERIMENTAL),[RFC1035],,
+ [10] = "NULL", -- a null RR (EXPERIMENTAL),[RFC1035],,
+ [11] = "WKS", -- a well known service description,[RFC1035],,
+ [12] = "PTR", -- a domain name pointer,[RFC1035],,
+ [13] = "HINFO", -- host information,[RFC1035],,
+ [14] = "MINFO", -- mailbox or mail list information,[RFC1035],,
+ [15] = "MX", -- mail exchange,[RFC1035],,
+ [16] = "TXT", -- text strings,[RFC1035],,
+ [17] = "RP", -- for Responsible Person,[RFC1183],,
+ [18] = "AFSDB", -- for AFS Data Base location,[RFC1183][RFC5864],,
+ [19] = "X25", -- for X.25 PSDN address,[RFC1183],,
+ [20] = "ISDN", -- for ISDN address,[RFC1183],,
+ [21] = "RT", -- for Route Through,[RFC1183],,
+ [22] = "NSAP", -- "for NSAP address, NSAP style A record",[RFC1706],,
+ [23] = "NSAP-PTR", -- "for domain name pointer, NSAP style",[RFC1348][RFC1637][RFC1706],,
+ [24] = "SIG", -- for security signature,[RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2931][RFC3110][RFC3008],,
+ [25] = "KEY", -- for security key,[RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2539][RFC3008][RFC3110],,
+ [26] = "PX", -- X.400 mail mapping information,[RFC2163],,
+ [27] = "GPOS", -- Geographical Position,[RFC1712],,
+ [28] = "AAAA", -- IP6 Address,[RFC3596],,
+ [29] = "LOC", -- Location Information,[RFC1876],,
+ [30] = "NXT", -- Next Domain (OBSOLETE),[RFC3755][RFC2535],,
+ [31] = "EID", -- Endpoint Identifier,[Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt],,1995-06
+ [32] = "NIMLOC", -- Nimrod Locator,[1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt],,1995-06
+ [33] = "SRV", -- Server Selection,[1][RFC2782],,
+ [34] = "ATMA", -- ATM Address,"[ ATM Forum Technical Committee, ""ATM Name System, V2.0"", Doc ID: AF-DANS-0152.000, July 2000. Available from and held in escrow by IANA.]",,
+ [35] = "NAPTR", -- Naming Authority Pointer,[RFC2915][RFC2168][RFC3403],,
+ [36] = "KX", -- Key Exchanger,[RFC2230],,
+ [37] = "CERT", -- CERT,[RFC4398],,
+ [38] = "A6", -- A6 (OBSOLETE - use AAAA),[RFC3226][RFC2874][RFC6563],,
+ [39] = "DNAME", -- DNAME,[RFC6672],,
+ [40] = "SINK", -- SINK,[Donald_E_Eastlake][http://tools.ietf.org/html/draft-eastlake-kitchen-sink],,1997-11
+ [41] = "OPT", -- OPT,[RFC6891][RFC3225],,
+ [42] = "APL", -- APL,[RFC3123],,
+ [43] = "DS", -- Delegation Signer,[RFC4034][RFC3658],,
+ [44] = "SSHFP", -- SSH Key Fingerprint,[RFC4255],,
+ [45] = "IPSECKEY", -- IPSECKEY,[RFC4025],,
+ [46] = "RRSIG", -- RRSIG,[RFC4034][RFC3755],,
+ [47] = "NSEC", -- NSEC,[RFC4034][RFC3755],,
+ [48] = "DNSKEY", -- DNSKEY,[RFC4034][RFC3755],,
+ [49] = "DHCID", -- DHCID,[RFC4701],,
+ [50] = "NSEC3", -- NSEC3,[RFC5155],,
+ [51] = "NSEC3PARAM", -- NSEC3PARAM,[RFC5155],,
+ [52] = "TLSA", -- TLSA,[RFC6698],,
+ [53] = "SMIMEA", -- S/MIME cert association,[RFC8162],SMIMEA/smimea-completed-template,2015-12-01
+ -- [54] = "Unassigned", -- ,,,
+ [55] = "HIP", -- Host Identity Protocol,[RFC8005],,
+ [56] = "NINFO", -- NINFO,[Jim_Reid],NINFO/ninfo-completed-template,2008-01-21
+ [57] = "RKEY", -- RKEY,[Jim_Reid],RKEY/rkey-completed-template,2008-01-21
+ [58] = "TALINK", -- Trust Anchor LINK,[Wouter_Wijngaards],TALINK/talink-completed-template,2010-02-17
+ [59] = "CDS", -- Child DS,[RFC7344],CDS/cds-completed-template,2011-06-06
+ [60] = "CDNSKEY", -- DNSKEY(s) the Child wants reflected in DS,[RFC7344],,2014-06-16
+ [61] = "OPENPGPKEY", -- OpenPGP Key,[RFC7929],OPENPGPKEY/openpgpkey-completed-template,2014-08-12
+ [62] = "CSYNC", -- Child-To-Parent Synchronization,[RFC7477],,2015-01-27
+ -- [63 .. 98] = "Unassigned", -- ,,,
+ [99] = "SPF", -- ,[RFC7208],,
+ [100] = "UINFO", -- ,[IANA-Reserved],,
+ [101] = "UID", -- ,[IANA-Reserved],,
+ [102] = "GID", -- ,[IANA-Reserved],,
+ [103] = "UNSPEC", -- ,[IANA-Reserved],,
+ [104] = "NID", -- ,[RFC6742],ILNP/nid-completed-template,
+ [105] = "L32", -- ,[RFC6742],ILNP/l32-completed-template,
+ [106] = "L64", -- ,[RFC6742],ILNP/l64-completed-template,
+ [107] = "LP", -- ,[RFC6742],ILNP/lp-completed-template,
+ [108] = "EUI48", -- an EUI-48 address,[RFC7043],EUI48/eui48-completed-template,2013-03-27
+ [109] = "EUI64", -- an EUI-64 address,[RFC7043],EUI64/eui64-completed-template,2013-03-27
+ -- [110 .. 248] = "Unassigned", -- ,,,
+ [249] = "TKEY", -- Transaction Key,[RFC2930],,
+ [250] = "TSIG", -- Transaction Signature,[RFC2845],,
+ [251] = "IXFR", -- incremental transfer,[RFC1995],,
+ [252] = "AXFR", -- transfer of an entire zone,[RFC1035][RFC5936],,
+ [253] = "MAILB", -- "mailbox-related RRs (MB, MG or MR)",[RFC1035],,
+ [254] = "MAILA", -- mail agent RRs (OBSOLETE - see MX),[RFC1035],,
+ [255] = "*", -- A request for all records the server/cache has available,[RFC1035][RFC6895],,
+ [256] = "URI", -- URI,[RFC7553],URI/uri-completed-template,2011-02-22
+ [257] = "CAA", -- Certification Authority Restriction,[RFC6844],CAA/caa-completed-template,2011-04-07
+ [258] = "AVC", -- Application Visibility and Control,[Wolfgang_Riedel],AVC/avc-completed-template,2016-02-26
+ [259] = "DOA", -- Digital Object Architecture,[draft-durand-doa-over-dns],DOA/doa-completed-template,2017-08-30
+ -- [260 .. 32767] = "Unassigned", -- ,,,
+ [32768] = "TA", -- DNSSEC Trust Authorities,"[Sam_Weiler][http://cameo.library.cmu.edu/][ Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, Information Networking Institute, Carnegie Mellon University, April 2004.]",,2005-12-13
+ [32769] = "DLV", -- DNSSEC Lookaside Validation,[RFC4431],,
+ -- [32770 .. 65279] = "Unassigned", -- ,,,
+ -- [65280 .. 65534] = "Private use", -- ,,,
+ -- [65535] = "Reserved", -- ,,,
+}
dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' };
@@ -391,6 +481,12 @@
rr.a = string.format('%i.%i.%i.%i', b1, b2, b3, b4);
end
+if have_util_net and util_net.ntop then
+ function resolver:A(rr)
+ rr.a = util_net.ntop(self:sub(4));
+ end
+end
+
function resolver:AAAA(rr)
local addr = {};
for _ = 1, rr.rdlength, 2 do
@@ -411,6 +507,12 @@
rr.aaaa = addr:gsub(zeros[1], "::", 1):gsub("^0::", "::"):gsub("::0$", "::");
end
+if have_util_net and util_net.ntop then
+ function resolver:AAAA(rr)
+ rr.aaaa = util_net.ntop(self:sub(16));
+ end
+end
+
function resolver:CNAME(rr) -- - - - - - - - - - - - - - - - - - - - CNAME
rr.cname = self:name();
end
diff -r bb8486491b48 -r cc642c9c5ad5 net/http.lua
--- a/net/http.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/http.lua Mon Nov 26 19:48:07 2018 +0100
@@ -13,20 +13,22 @@
local events = require "util.events";
local verify_identity = require"util.x509".verify_identity;
-local ssl_available = pcall(require, "ssl");
+local basic_resolver = require "net.resolvers.basic";
+local connect = require "net.connect".connect;
-local server = require "net.server"
+local ssl_available = pcall(require, "ssl");
local t_insert, t_concat = table.insert, table.concat;
local pairs = pairs;
-local tonumber, tostring, xpcall, traceback =
- tonumber, tostring, xpcall, debug.traceback;
+local tonumber, tostring, traceback =
+ tonumber, tostring, debug.traceback;
+local xpcall = require "util.xpcall".xpcall;
local error = error
-local setmetatable = setmetatable;
local log = require "util.logger".init("http");
local _ENV = nil;
+-- luacheck: std none
local requests = {}; -- Open requests
@@ -34,9 +36,78 @@
local listener = { default_port = 80, default_mode = "*a" };
+-- Request-related helper functions
+local function handleerr(err) log("error", "Traceback[http]: %s", traceback(tostring(err), 2)); return err; end
+local function log_if_failed(req, ret, ...)
+ if not ret then
+ log("error", "Request '%s': error in callback: %s", req.id, tostring((...)));
+ if not req.suppress_errors then
+ error(...);
+ end
+ end
+ return ...;
+end
+
+local function destroy_request(request)
+ local conn = request.conn;
+ if conn then
+ request.conn = nil;
+ conn:close()
+ end
+end
+
+local function request_reader(request, data, err)
+ if not request.parser then
+ local function error_cb(reason)
+ if request.callback then
+ request.callback(reason or "connection-closed", 0, request);
+ request.callback = nil;
+ 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);
+ request.callback = nil;
+ end
+ destroy_request(request);
+ end
+ local function options_cb()
+ return request;
+ end
+ request.parser = httpstream_new(success_cb, error_cb, "client", options_cb);
+ end
+ request.parser:feed(data);
+end
+
+-- Connection listener callbacks
function listener.onconnect(conn)
local req = requests[conn];
+ -- Initialize request object
+ req.write = function (...) return req.conn:write(...); end
+ local callback = req.callback;
+ req.callback = function (content, code, response, request)
+ do
+ local event = { http = req.http, url = req.url, request = req, response = response, content = content, code = code, callback = req.callback };
+ req.http.events.fire_event("response", event);
+ content, code, response = event.content, event.code, event.response;
+ end
+
+ log("debug", "Request '%s': Calling callback, status %s", req.id, code or "---");
+ return log_if_failed(req.id, xpcall(callback, handleerr, content, code, response, request));
+ end
+ req.reader = request_reader;
+ req.state = "status";
+
+ requests[req.conn] = req;
+
-- Validate certificate
if not req.insecure and conn:ssl() then
local sock = conn:socket();
@@ -96,58 +167,24 @@
requests[conn] = nil;
end
+function listener.onattach(conn, req)
+ requests[conn] = req;
+ req.conn = conn;
+end
+
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)
- if request.callback then
- request.callback(reason or "connection-closed", 0, request);
- request.callback = nil;
- 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);
- request.callback = nil;
- end
- destroy_request(request);
- end
- local function options_cb()
- return request;
- end
- request.parser = httpstream_new(success_cb, error_cb, "client", options_cb);
- end
- request.parser:feed(data);
-end
-
-local function handleerr(err) log("error", "Traceback[http]: %s", traceback(tostring(err), 2)); end
-local function log_if_failed(id, ret, ...)
- if not ret then
- log("error", "Request '%s': error in callback: %s", id, tostring((...)));
- end
- return ...;
+function listener.onfail(req, reason)
+ req.http.events.fire_event("request-connection-error", { http = req.http, request = req, url = req.url, err = reason });
+ req.callback(reason or "connection failed", 0, req);
end
local function request(self, u, ex, callback)
local req = url.parse(u);
req.url = u;
+ req.http = self;
if not (req and req.host) then
callback("invalid-url", 0, req);
@@ -166,7 +203,7 @@
if ret then
return ret;
end
- req, u, ex, callback = event.request, event.url, event.options, event.callback;
+ req, u, ex, req.callback = event.request, event.url, event.options, event.callback;
end
local method, headers, body;
@@ -204,6 +241,7 @@
end
end
req.insecure = ex.insecure;
+ req.suppress_errors = ex.suppress_errors;
end
log("debug", "Making %s %s request '%s' to %s", req.scheme:upper(), method or "GET", req.id, (ex and ex.suppress_url and host_header) or u);
@@ -222,29 +260,8 @@
sslctx = ex and ex.sslctx or self.options and self.options.sslctx;
end
- local handler, conn = server.addclient(host, port_number, listener, "*a", sslctx)
- if not handler then
- self.events.fire_event("request-connection-error", { http = self, request = req, url = u, err = conn });
- callback(conn, 0, req);
- return nil, conn;
- end
- req.handler, req.conn = handler, conn
- req.write = function (...) return req.handler:write(...); end
-
- req.callback = function (content, code, response, request)
- do
- local event = { http = self, url = u, request = req, response = response, content = content, code = code, callback = callback };
- self.events.fire_event("response", event);
- content, code, response = event.content, event.code, event.response;
- end
-
- log("debug", "Request '%s': Calling callback, status %s", req.id, code or "---");
- return log_if_failed(req.id, xpcall(function () return callback(content, code, response, request) end, handleerr));
- end
- req.reader = request_reader;
- req.state = "status";
-
- requests[req.handler] = req;
+ local http_service = basic_resolver.new(host, port_number);
+ connect(http_service, listener, { sslctx = sslctx }, req);
self.events.fire_event("request", { http = self, request = req, url = u });
return req;
@@ -255,7 +272,12 @@
options = options;
request = request;
new = options and function (new_options)
- return new(setmetatable(new_options, { __index = options }));
+ local final_options = {};
+ for k, v in pairs(options) do final_options[k] = v; end
+ if new_options then
+ for k, v in pairs(new_options) do final_options[k] = v; end
+ end
+ return new(final_options);
end or new;
events = events.new();
};
@@ -264,6 +286,7 @@
local default_http = new({
sslctx = { mode = "client", protocol = "sslv23", options = { "no_sslv2", "no_sslv3" } };
+ suppress_errors = true;
});
return {
diff -r bb8486491b48 -r cc642c9c5ad5 net/http/codes.lua
--- a/net/http/codes.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/http/codes.lua Mon Nov 26 19:48:07 2018 +0100
@@ -1,73 +1,85 @@
local response_codes = {
-- Source: http://www.iana.org/assignments/http-status-codes
- -- s/^\(\d*\)\s*\(.*\S\)\s*\[RFC.*\]\s*$/^I["\1"] = "\2";
- [100] = "Continue";
- [101] = "Switching Protocols";
+
+ [100] = "Continue"; -- RFC7231, Section 6.2.1
+ [101] = "Switching Protocols"; -- RFC7231, Section 6.2.2
[102] = "Processing";
+ [103] = "Early Hints";
+ -- [104-199] = "Unassigned";
- [200] = "OK";
- [201] = "Created";
- [202] = "Accepted";
- [203] = "Non-Authoritative Information";
- [204] = "No Content";
- [205] = "Reset Content";
- [206] = "Partial Content";
+ [200] = "OK"; -- RFC7231, Section 6.3.1
+ [201] = "Created"; -- RFC7231, Section 6.3.2
+ [202] = "Accepted"; -- RFC7231, Section 6.3.3
+ [203] = "Non-Authoritative Information"; -- RFC7231, Section 6.3.4
+ [204] = "No Content"; -- RFC7231, Section 6.3.5
+ [205] = "Reset Content"; -- RFC7231, Section 6.3.6
+ [206] = "Partial Content"; -- RFC7233, Section 4.1
[207] = "Multi-Status";
[208] = "Already Reported";
+ -- [209-225] = "Unassigned";
[226] = "IM Used";
+ -- [227-299] = "Unassigned";
- [300] = "Multiple Choices";
- [301] = "Moved Permanently";
- [302] = "Found";
- [303] = "See Other";
- [304] = "Not Modified";
- [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";
+ [300] = "Multiple Choices"; -- RFC7231, Section 6.4.1
+ [301] = "Moved Permanently"; -- RFC7231, Section 6.4.2
+ [302] = "Found"; -- RFC7231, Section 6.4.3
+ [303] = "See Other"; -- RFC7231, Section 6.4.4
+ [304] = "Not Modified"; -- RFC7232, Section 4.1
+ [305] = "Use Proxy"; -- RFC7231, Section 6.4.5
+ -- [306] = "(Unused)"; -- RFC7231, Section 6.4.6
+ [307] = "Temporary Redirect"; -- RFC7231, Section 6.4.7
[308] = "Permanent Redirect";
+ -- [309-399] = "Unassigned";
- [400] = "Bad Request";
- [401] = "Unauthorized";
- [402] = "Payment Required";
- [403] = "Forbidden";
- [404] = "Not Found";
- [405] = "Method Not Allowed";
- [406] = "Not Acceptable";
- [407] = "Proxy Authentication Required";
- [408] = "Request Timeout";
- [409] = "Conflict";
- [410] = "Gone";
- [411] = "Length Required";
- [412] = "Precondition Failed";
- [413] = "Payload Too Large";
- [414] = "URI Too Long";
- [415] = "Unsupported Media Type";
- [416] = "Range Not Satisfiable";
- [417] = "Expectation Failed";
- [418] = "I'm a teapot";
- [421] = "Misdirected Request";
+ [400] = "Bad Request"; -- RFC7231, Section 6.5.1
+ [401] = "Unauthorized"; -- RFC7235, Section 3.1
+ [402] = "Payment Required"; -- RFC7231, Section 6.5.2
+ [403] = "Forbidden"; -- RFC7231, Section 6.5.3
+ [404] = "Not Found"; -- RFC7231, Section 6.5.4
+ [405] = "Method Not Allowed"; -- RFC7231, Section 6.5.5
+ [406] = "Not Acceptable"; -- RFC7231, Section 6.5.6
+ [407] = "Proxy Authentication Required"; -- RFC7235, Section 3.2
+ [408] = "Request Timeout"; -- RFC7231, Section 6.5.7
+ [409] = "Conflict"; -- RFC7231, Section 6.5.8
+ [410] = "Gone"; -- RFC7231, Section 6.5.9
+ [411] = "Length Required"; -- RFC7231, Section 6.5.10
+ [412] = "Precondition Failed"; -- RFC7232, Section 4.2
+ [413] = "Payload Too Large"; -- RFC7231, Section 6.5.11
+ [414] = "URI Too Long"; -- RFC7231, Section 6.5.12
+ [415] = "Unsupported Media Type"; -- RFC7231, Section 6.5.13
+ [416] = "Range Not Satisfiable"; -- RFC7233, Section 4.4
+ [417] = "Expectation Failed"; -- RFC7231, Section 6.5.14
+ [418] = "I'm a teapot"; -- RFC2324, Section 2.3.2
+ -- [419-420] = "Unassigned";
+ [421] = "Misdirected Request"; -- RFC7540, Section 9.1.2
[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";
+ [425] = "Too Early";
+ [426] = "Upgrade Required"; -- RFC7231, Section 6.5.15
+ -- [427] = "Unassigned";
[428] = "Precondition Required";
[429] = "Too Many Requests";
+ -- [430] = "Unassigned";
[431] = "Request Header Fields Too Large";
+ -- [432-450] = "Unassigned";
[451] = "Unavailable For Legal Reasons";
+ -- [452-499] = "Unassigned";
- [500] = "Internal Server Error";
- [501] = "Not Implemented";
- [502] = "Bad Gateway";
- [503] = "Service Unavailable";
- [504] = "Gateway Timeout";
- [505] = "HTTP Version Not Supported";
- [506] = "Variant Also Negotiates"; -- Experimental
+ [500] = "Internal Server Error"; -- RFC7231, Section 6.6.1
+ [501] = "Not Implemented"; -- RFC7231, Section 6.6.2
+ [502] = "Bad Gateway"; -- RFC7231, Section 6.6.3
+ [503] = "Service Unavailable"; -- RFC7231, Section 6.6.4
+ [504] = "Gateway Timeout"; -- RFC7231, Section 6.6.5
+ [505] = "HTTP Version Not Supported"; -- RFC7231, Section 6.6.6
+ [506] = "Variant Also Negotiates";
[507] = "Insufficient Storage";
[508] = "Loop Detected";
+ -- [509] = "Unassigned";
[510] = "Not Extended";
[511] = "Network Authentication Required";
+ -- [512-599] = "Unassigned";
};
for k,v in pairs(response_codes) do response_codes[k] = k.." "..v; end
diff -r bb8486491b48 -r cc642c9c5ad5 net/http/server.lua
--- a/net/http/server.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/http/server.lua Mon Nov 26 19:48:07 2018 +0100
@@ -8,7 +8,7 @@
local pairs = pairs;
local s_upper = string.upper;
local setmetatable = setmetatable;
-local xpcall = xpcall;
+local xpcall = require "util.xpcall".xpcall;
local traceback = debug.traceback;
local tostring = tostring;
local cache = require "util.cache";
@@ -88,8 +88,6 @@
});
local handle_request;
-local _1, _2, _3;
-local function _handle_request() return handle_request(_1, _2, _3); end
local last_err;
local function _traceback_handler(err) last_err = err; log("error", "Traceback[httpserver]: %s", traceback(tostring(err), 2)); end
@@ -107,9 +105,7 @@
while sessions[conn] and #pending > 0 do
local request = t_remove(pending);
--log("debug", "process_next: %s", request.path);
- --handle_request(conn, request, process_next);
- _1, _2, _3 = conn, request, process_next;
- if not xpcall(_handle_request, _traceback_handler) then
+ if not xpcall(handle_request, _traceback_handler, conn, request, process_next) then
conn:write("HTTP/1.0 500 Internal Server Error\r\n\r\n"..events.fire_event("http-error", { code = 500, private_message = last_err }));
conn:close();
end
@@ -217,14 +213,6 @@
local err_code, err;
if not request.path then
err_code, err = 400, "Invalid path";
- elseif not hosts[host] then
- if hosts[default_host] then
- host = default_host;
- elseif host then
- err_code, err = 404, "Unknown host: "..host;
- else
- err_code, err = 400, "Missing or invalid 'Host' header";
- end
end
if err then
@@ -233,10 +221,32 @@
return;
end
- local event = request.method.." "..host..request.path:match("[^?]*");
+ local global_event = request.method.." "..request.path:match("[^?]*");
+
local payload = { request = request, response = response };
- log("debug", "Firing event: %s", event);
- local result = events.fire_event(event, payload);
+ log("debug", "Firing event: %s", global_event);
+ local result = events.fire_event(global_event, payload);
+ if result == nil then
+ if not hosts[host] then
+ if hosts[default_host] then
+ host = default_host;
+ elseif host then
+ err_code, err = 404, "Unknown host: "..host;
+ else
+ err_code, err = 400, "Missing or invalid 'Host' header";
+ end
+ end
+
+ if err then
+ response.status_code = err_code;
+ response:send(events.fire_event("http-error", { code = err_code, message = err, response = response }));
+ return;
+ end
+
+ local host_event = request.method.." "..host..request.path:match("[^?]*");
+ log("debug", "Firing event: %s", host_event);
+ result = events.fire_event(host_event, payload);
+ end
if result ~= nil then
if result ~= true then
local body;
diff -r bb8486491b48 -r cc642c9c5ad5 net/httpserver.lua
--- a/net/httpserver.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/httpserver.lua Mon Nov 26 19:48:07 2018 +0100
@@ -3,9 +3,10 @@
local traceback = debug.traceback;
local _ENV = nil;
+-- luacheck: std none
-function fail()
- log("error", "Attempt to use legacy HTTP API. For more info see http://prosody.im/doc/developers/legacy_http");
+local function fail()
+ log("error", "Attempt to use legacy HTTP API. For more info see https://prosody.im/doc/developers/legacy_http");
log("error", "Legacy HTTP API usage, %s", traceback("", 2));
end
diff -r bb8486491b48 -r cc642c9c5ad5 net/resolvers/basic.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/net/resolvers/basic.lua Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,71 @@
+local adns = require "net.adns";
+local inet_pton = require "util.net".pton;
+
+local methods = {};
+local resolver_mt = { __index = methods };
+
+-- Find the next target to connect to, and
+-- pass it to cb()
+function methods:next(cb)
+ if self.targets then
+ if #self.targets == 0 then
+ cb(nil);
+ return;
+ end
+ local next_target = table.remove(self.targets, 1);
+ cb(unpack(next_target, 1, 4));
+ return;
+ end
+
+ local targets = {};
+ local n = 2;
+ local function ready()
+ n = n - 1;
+ if n > 0 then return; end
+ self.targets = targets;
+ self:next(cb);
+ end
+
+ local is_ip = inet_pton(self.hostname);
+ if is_ip then
+ if #is_ip == 16 then
+ cb(self.conn_type.."6", self.hostname, self.port, self.extra);
+ elseif #is_ip == 4 then
+ cb(self.conn_type.."4", self.hostname, self.port, self.extra);
+ end
+ return;
+ end
+
+ -- Resolve DNS to target list
+ local dns_resolver = adns.resolver();
+ dns_resolver:lookup(function (answer)
+ if answer then
+ for _, record in ipairs(answer) do
+ table.insert(targets, { self.conn_type.."4", record.a, self.port, self.extra });
+ end
+ end
+ ready();
+ end, self.hostname, "A", "IN");
+
+ dns_resolver:lookup(function (answer)
+ if answer then
+ for _, record in ipairs(answer) do
+ table.insert(targets, { self.conn_type.."6", record.aaaa, self.port, self.extra });
+ end
+ end
+ ready();
+ end, self.hostname, "AAAA", "IN");
+end
+
+local function new(hostname, port, conn_type, extra)
+ return setmetatable({
+ hostname = hostname;
+ port = port;
+ conn_type = conn_type or "tcp";
+ extra = extra;
+ }, resolver_mt);
+end
+
+return {
+ new = new;
+};
diff -r bb8486491b48 -r cc642c9c5ad5 net/resolvers/manual.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/net/resolvers/manual.lua Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,25 @@
+local methods = {};
+local resolver_mt = { __index = methods };
+
+-- Find the next target to connect to, and
+-- pass it to cb()
+function methods:next(cb)
+ if #self.targets == 0 then
+ cb(nil);
+ return;
+ end
+ local next_target = table.remove(self.targets, 1);
+ cb(unpack(next_target, 1, 4));
+end
+
+local function new(targets, conn_type, extra)
+ return setmetatable({
+ conn_type = conn_type;
+ extra = extra;
+ targets = targets or {};
+ }, resolver_mt);
+end
+
+return {
+ new = new;
+};
diff -r bb8486491b48 -r cc642c9c5ad5 net/resolvers/service.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/net/resolvers/service.lua Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,70 @@
+local adns = require "net.adns";
+local basic = require "net.resolvers.basic";
+
+local methods = {};
+local resolver_mt = { __index = methods };
+
+-- Find the next target to connect to, and
+-- pass it to cb()
+function methods:next(cb)
+ if self.targets then
+ if #self.targets == 0 then
+ cb(nil);
+ return;
+ end
+ local next_target = table.remove(self.targets, 1);
+ self.resolver = basic.new(unpack(next_target, 1, 4));
+ self.resolver:next(function (...)
+ if ... == nil then
+ self:next(cb);
+ else
+ cb(...);
+ end
+ end);
+ return;
+ end
+
+ local targets = {};
+ local function ready()
+ self.targets = targets;
+ self:next(cb);
+ end
+
+ -- Resolve DNS to target list
+ local dns_resolver = adns.resolver();
+ dns_resolver:lookup(function (answer)
+ if answer then
+ if #answer == 0 then
+ if self.extra and self.extra.default_port then
+ table.insert(targets, { self.hostname, self.extra.default_port, self.conn_type, self.extra });
+ end
+ ready();
+ return;
+ end
+
+ if #answer == 1 and answer[1].srv.target == "." then -- No service here
+ ready();
+ return;
+ end
+
+ table.sort(answer, function (a, b) return a.srv.priority < b.srv.priority end);
+ for _, record in ipairs(answer) do
+ table.insert(targets, { record.srv.target, record.srv.port, self.conn_type, self.extra });
+ end
+ end
+ ready();
+ end, "_" .. self.service .. "._" .. self.conn_type .. "." .. self.hostname, "SRV", "IN");
+end
+
+local function new(hostname, service, conn_type, extra)
+ return setmetatable({
+ hostname = hostname;
+ service = service;
+ conn_type = conn_type or "tcp";
+ extra = extra;
+ }, resolver_mt);
+end
+
+return {
+ new = new;
+};
diff -r bb8486491b48 -r cc642c9c5ad5 net/server.lua
--- a/net/server.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/server.lua Mon Nov 26 19:48:07 2018 +0100
@@ -6,25 +6,83 @@
-- COPYING file in the source package for more information.
--
-local use_luaevent = prosody and require "core.configmanager".get("*", "use_libevent");
+if not (prosody and prosody.config_loaded) then
+ -- This module only supports loading inside Prosody, outside Prosody
+ -- you should directly require net.server_select or server_event, etc.
+ error(debug.traceback("Loading outside Prosody or Prosody not yet initialized"), 0);
+end
+
+local log = require "util.logger".init("net.server");
+local server_type = require "core.configmanager".get("*", "network_backend") or "select";
-if use_luaevent then
- use_luaevent = pcall(require, "luaevent.core");
- if not use_luaevent then
+if require "core.configmanager".get("*", "use_libevent") then
+ server_type = "event";
+end
+
+if server_type == "event" then
+ if not pcall(require, "luaevent.core") then
log("error", "libevent not found, falling back to select()");
+ server_type = "select"
end
end
local server;
-
-if use_luaevent then
+local set_config;
+if server_type == "event" then
server = require "net.server_event";
- -- Overwrite signal.signal() because we need to ask libevent to
- -- handle them instead
- local ok, signal = pcall(require, "util.signal");
- if ok and signal then
- local _signal_signal = signal.signal;
+ local defaults = {};
+ for k,v in pairs(server.cfg) do
+ defaults[k] = v;
+ end
+ function set_config(settings)
+ local event_settings = {
+ ACCEPT_DELAY = settings.accept_retry_interval;
+ ACCEPT_QUEUE = settings.tcp_backlog;
+ CLEAR_DELAY = settings.event_clear_interval;
+ CONNECT_TIMEOUT = settings.connect_timeout;
+ DEBUG = settings.debug;
+ HANDSHAKE_TIMEOUT = settings.ssl_handshake_timeout;
+ MAX_CONNECTIONS = settings.max_connections;
+ MAX_HANDSHAKE_ATTEMPTS = settings.max_ssl_handshake_roundtrips;
+ MAX_READ_LENGTH = settings.max_receive_buffer_size;
+ MAX_SEND_LENGTH = settings.max_send_buffer_size;
+ READ_TIMEOUT = settings.read_timeout;
+ WRITE_TIMEOUT = settings.send_timeout;
+ };
+
+ for k,default in pairs(defaults) do
+ server.cfg[k] = event_settings[k] or default;
+ end
+ end
+elseif server_type == "select" then
+ server = require "net.server_select";
+
+ local defaults = {};
+ for k,v in pairs(server.getsettings()) do
+ defaults[k] = v;
+ end
+ function set_config(settings)
+ local select_settings = {};
+ for k,default in pairs(defaults) do
+ select_settings[k] = settings[k] or default;
+ end
+ server.changesettings(select_settings);
+ end
+else
+ server = require("net.server_"..server_type);
+ set_config = server.set_config;
+ if not server.get_backend then
+ function server.get_backend()
+ return server_type;
+ end
+ end
+end
+
+-- If server.hook_signal exists, replace signal.signal()
+local has_signal, signal = pcall(require, "util.signal");
+if has_signal then
+ if server.hook_signal then
function signal.signal(signal_id, handler)
if type(signal_id) == "string" then
signal_id = signal[signal_id:upper()];
@@ -34,46 +92,22 @@
end
return server.hook_signal(signal_id, handler);
end
+ else
+ server.hook_signal = signal.signal;
end
else
- use_luaevent = false;
- server = require "net.server_select";
+ if not server.hook_signal then
+ server.hook_signal = function()
+ return false, "signal hooking not supported"
+ end
+ end
end
-if prosody then
+if prosody and set_config then
local config_get = require "core.configmanager".get;
- local defaults = {};
- for k,v in pairs(server.cfg or server.getsettings()) do
- defaults[k] = v;
- end
local function load_config()
local settings = config_get("*", "network_settings") or {};
- if use_luaevent then
- local event_settings = {
- ACCEPT_DELAY = settings.accept_retry_interval;
- ACCEPT_QUEUE = settings.tcp_backlog;
- CLEAR_DELAY = settings.event_clear_interval;
- CONNECT_TIMEOUT = settings.connect_timeout;
- DEBUG = settings.debug;
- HANDSHAKE_TIMEOUT = settings.ssl_handshake_timeout;
- MAX_CONNECTIONS = settings.max_connections;
- MAX_HANDSHAKE_ATTEMPTS = settings.max_ssl_handshake_roundtrips;
- MAX_READ_LENGTH = settings.max_receive_buffer_size;
- MAX_SEND_LENGTH = settings.max_send_buffer_size;
- READ_TIMEOUT = settings.read_timeout;
- WRITE_TIMEOUT = settings.send_timeout;
- };
-
- for k,default in pairs(defaults) do
- server.cfg[k] = event_settings[k] or default;
- end
- else
- local select_settings = {};
- for k,default in pairs(defaults) do
- select_settings[k] = settings[k] or default;
- end
- server.changesettings(select_settings);
- end
+ return set_config(settings);
end
load_config();
prosody.events.add_handler("config-reloaded", load_config);
diff -r bb8486491b48 -r cc642c9c5ad5 net/server_epoll.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/net/server_epoll.lua Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,794 @@
+-- Prosody IM
+-- Copyright (C) 2016-2018 Kim Alvefur
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+local t_sort = table.sort;
+local t_insert = table.insert;
+local t_remove = table.remove;
+local t_concat = table.concat;
+local setmetatable = setmetatable;
+local tostring = tostring;
+local pcall = pcall;
+local type = type;
+local next = next;
+local pairs = pairs;
+local log = require "util.logger".init("server_epoll");
+local socket = require "socket";
+local luasec = require "ssl";
+local gettime = require "util.time".now;
+local createtable = require "util.table".create;
+local inet = require "util.net";
+local inet_pton = inet.pton;
+local _SOCKETINVALID = socket._SOCKETINVALID or -1;
+
+local poller = require "util.poll"
+local EEXIST = poller.EEXIST;
+local ENOENT = poller.ENOENT;
+
+local poll = assert(poller.new());
+
+local _ENV = nil;
+-- luacheck: std none
+
+local default_config = { __index = {
+ read_timeout = 14 * 60;
+ write_timeout = 7;
+ tcp_backlog = 128;
+ accept_retry_interval = 10;
+ read_retry_delay = 1e-06;
+ read_size = 8192;
+ connect_timeout = 20;
+ handshake_timeout = 60;
+ max_wait = 86400;
+ min_wait = 1e-06;
+}};
+local cfg = default_config.__index;
+
+local fds = createtable(10, 0); -- FD -> conn
+
+-- Timer and scheduling --
+
+local timers = {};
+
+local function noop() end
+local function closetimer(t)
+ t[1] = 0;
+ t[2] = noop;
+end
+
+-- Set to true when timers have changed
+local resort_timers = false;
+
+-- Add absolute timer
+local function at(time, f)
+ local timer = { time, f, close = closetimer };
+ t_insert(timers, timer);
+ resort_timers = true;
+ return timer;
+end
+
+-- Add relative timer
+local function addtimer(timeout, f)
+ return at(gettime() + timeout, f);
+end
+
+-- Run callbacks of expired timers
+-- Return time until next timeout
+local function runtimers(next_delay, min_wait)
+ -- Any timers at all?
+ if not timers[1] then
+ return next_delay;
+ end
+
+ if resort_timers then
+ -- Sort earliest timers to the end
+ t_sort(timers, function (a, b) return a[1] > b[1]; end);
+ resort_timers = false;
+ end
+
+ -- Iterate from the end and remove completed timers
+ for i = #timers, 1, -1 do
+ local timer = timers[i];
+ local t, f = timer[1], timer[2];
+ -- Get time for every iteration to increase accuracy
+ local now = gettime();
+ if t > now then
+ -- This timer should not fire yet
+ local diff = t - now;
+ if diff < next_delay then
+ next_delay = diff;
+ end
+ break;
+ end
+ local new_timeout = f(now);
+ if new_timeout then
+ -- Schedule for 'delay' from the time actually scheduled,
+ -- not from now, in order to prevent timer drift.
+ timer[1] = t + new_timeout;
+ resort_timers = true;
+ else
+ t_remove(timers, i);
+ end
+ end
+
+ if resort_timers or next_delay < min_wait then
+ -- Timers may be added from within a timer callback.
+ -- Those would not be considered for next_delay,
+ -- and we might sleep for too long, so instead
+ -- we return a shorter timeout so we can
+ -- properly sort all new timers.
+ next_delay = min_wait;
+ end
+
+ return next_delay;
+end
+
+-- Socket handler interface
+
+local interface = {};
+local interface_mt = { __index = interface };
+
+function interface_mt:__tostring()
+ if self.sockname and self.peername then
+ return ("FD %d (%s, %d, %s, %d)"):format(self:getfd(), self.peername, self.peerport, self.sockname, self.sockport);
+ elseif self.sockname or self.peername then
+ return ("FD %d (%s, %d)"):format(self:getfd(), self.sockname or self.peername, self.sockport or self.peerport);
+ end
+ return ("FD %d"):format(self:getfd());
+end
+
+-- Replace the listener and tell the old one
+function interface:setlistener(listeners, data)
+ self:on("detach");
+ self.listeners = listeners;
+ self:on("attach", data);
+end
+
+-- Call a listener callback
+function interface:on(what, ...)
+ if not self.listeners then
+ log("error", "%s has no listeners", self);
+ return;
+ end
+ local listener = self.listeners["on"..what];
+ if not listener then
+ -- log("debug", "Missing listener 'on%s'", what); -- uncomment for development and debugging
+ return;
+ end
+ local ok, err = pcall(listener, self, ...);
+ if not ok then
+ log("error", "Error calling on%s: %s", what, err);
+ end
+ return err;
+end
+
+-- Return the file descriptor number
+function interface:getfd()
+ if self.conn then
+ return self.conn:getfd();
+ end
+ return _SOCKETINVALID;
+end
+
+function interface:server()
+ return self._server or self;
+end
+
+-- Get IP address
+function interface:ip()
+ return self.peername or self.sockname;
+end
+
+-- Get a port number, doesn't matter which
+function interface:port()
+ return self.sockport or self.peerport;
+end
+
+-- Get local port number
+function interface:clientport()
+ return self.sockport;
+end
+
+-- Get remote port
+function interface:serverport()
+ if self.sockport then
+ return self.sockport;
+ elseif self._server then
+ self._server:port();
+ end
+end
+
+-- Return underlying socket
+function interface:socket()
+ return self.conn;
+end
+
+function interface:set_mode(new_mode)
+ self.read_size = new_mode;
+end
+
+function interface:setoption(k, v)
+ -- LuaSec doesn't expose setoption :(
+ if self.conn.setoption then
+ self.conn:setoption(k, v);
+ end
+end
+
+-- Timeout for detecting dead or idle sockets
+function interface:setreadtimeout(t)
+ if t == false then
+ if self._readtimeout then
+ self._readtimeout:close();
+ self._readtimeout = nil;
+ end
+ return
+ end
+ t = t or cfg.read_timeout;
+ if self._readtimeout then
+ self._readtimeout[1] = gettime() + t;
+ resort_timers = true;
+ else
+ self._readtimeout = addtimer(t, function ()
+ if self:on("readtimeout") then
+ return cfg.read_timeout;
+ else
+ self:on("disconnect", "read timeout");
+ self:destroy();
+ end
+ end);
+ end
+end
+
+-- Timeout for detecting dead sockets
+function interface:setwritetimeout(t)
+ if t == false then
+ if self._writetimeout then
+ self._writetimeout:close();
+ self._writetimeout = nil;
+ end
+ return
+ end
+ t = t or cfg.write_timeout;
+ if self._writetimeout then
+ self._writetimeout[1] = gettime() + t;
+ resort_timers = true;
+ else
+ self._writetimeout = addtimer(t, function ()
+ self:on("disconnect", "write timeout");
+ self:destroy();
+ end);
+ end
+end
+
+function interface:add(r, w)
+ local fd = self:getfd();
+ if fd < 0 then
+ return nil, "invalid fd";
+ end
+ if r == nil then r = self._wantread; end
+ if w == nil then w = self._wantwrite; end
+ local ok, err, errno = poll:add(fd, r, w);
+ if not ok then
+ if errno == EEXIST then
+ log("debug", "%s already registered!", self);
+ return self:set(r, w); -- So try to change its flags
+ end
+ log("error", "Could not register %s: %s(%d)", self, err, errno);
+ return ok, err;
+ end
+ self._wantread, self._wantwrite = r, w;
+ fds[fd] = self;
+ log("debug", "Watching %s", self);
+ return true;
+end
+
+function interface:set(r, w)
+ local fd = self:getfd();
+ if fd < 0 then
+ return nil, "invalid fd";
+ end
+ if r == nil then r = self._wantread; end
+ if w == nil then w = self._wantwrite; end
+ local ok, err, errno = poll:set(fd, r, w);
+ if not ok then
+ log("error", "Could not update poller state %s: %s(%d)", self, err, errno);
+ return ok, err;
+ end
+ self._wantread, self._wantwrite = r, w;
+ return true;
+end
+
+function interface:del()
+ local fd = self:getfd();
+ if fd < 0 then
+ return nil, "invalid fd";
+ end
+ if fds[fd] ~= self then
+ return nil, "unregistered fd";
+ end
+ local ok, err, errno = poll:del(fd);
+ if not ok and errno ~= ENOENT then
+ log("error", "Could not unregister %s: %s(%d)", self, err, errno);
+ return ok, err;
+ end
+ self._wantread, self._wantwrite = nil, nil;
+ fds[fd] = nil;
+ log("debug", "Unwatched %s", self);
+ return true;
+end
+
+function interface:setflags(r, w)
+ if not(self._wantread or self._wantwrite) then
+ if not(r or w) then
+ return true; -- no change
+ end
+ return self:add(r, w);
+ end
+ if not(r or w) then
+ return self:del();
+ end
+ return self:set(r, w);
+end
+
+-- Called when socket is readable
+function interface:onreadable()
+ local data, err, partial = self.conn:receive(self.read_size or cfg.read_size);
+ if data then
+ self:onconnect();
+ self:on("incoming", data);
+ else
+ if partial and partial ~= "" then
+ self:onconnect();
+ self:on("incoming", partial, err);
+ end
+ if err == "wantread" then
+ self:set(true, nil);
+ elseif err == "wantwrite" then
+ self:set(nil, true);
+ elseif err ~= "timeout" then
+ self:on("disconnect", err);
+ self:destroy()
+ return;
+ end
+ end
+ if not self.conn then return; end
+ if self.conn:dirty() then
+ self:setreadtimeout(false);
+ self:pausefor(cfg.read_retry_delay);
+ else
+ self:setreadtimeout();
+ end
+end
+
+-- Called when socket is writable
+function interface:onwritable()
+ self:onconnect();
+ if not self.conn then return; end -- could have been closed in onconnect
+ local buffer = self.writebuffer;
+ local data = t_concat(buffer);
+ local ok, err, partial = self.conn:send(data);
+ if ok then
+ self:set(nil, false);
+ for i = #buffer, 1, -1 do
+ buffer[i] = nil;
+ end
+ self:setwritetimeout(false);
+ self:ondrain(); -- Be aware of writes in ondrain
+ return;
+ elseif partial then
+ buffer[1] = data:sub(partial+1);
+ for i = #buffer, 2, -1 do
+ buffer[i] = nil;
+ end
+ self:setwritetimeout();
+ end
+ if err == "wantwrite" or err == "timeout" then
+ self:set(nil, true);
+ elseif err == "wantread" then
+ self:set(true, nil);
+ elseif err ~= "timeout" then
+ self:on("disconnect", err);
+ self:destroy();
+ end
+end
+
+-- The write buffer has been successfully emptied
+function interface:ondrain()
+ return self:on("drain");
+end
+
+-- Add data to write buffer and set flag for wanting to write
+function interface:write(data)
+ local buffer = self.writebuffer;
+ if buffer then
+ t_insert(buffer, data);
+ else
+ self.writebuffer = { data };
+ end
+ self:setwritetimeout();
+ self:set(nil, true);
+ return #data;
+end
+interface.send = interface.write;
+
+-- Close, possibly after writing is done
+function interface:close()
+ if self.writebuffer and self.writebuffer[1] then
+ self:set(false, true); -- Flush final buffer contents
+ self.write, self.send = noop, noop; -- No more writing
+ log("debug", "Close %s after writing", self);
+ self.ondrain = interface.close;
+ else
+ log("debug", "Close %s now", self);
+ self.write, self.send = noop, noop;
+ self.close = noop;
+ self:on("disconnect");
+ self:destroy();
+ end
+end
+
+function interface:destroy()
+ self:del();
+ self:setwritetimeout(false);
+ self:setreadtimeout(false);
+ self.onreadable = noop;
+ self.onwritable = noop;
+ self.destroy = noop;
+ self.close = noop;
+ self.on = noop;
+ self.conn:close();
+ self.conn = nil;
+end
+
+function interface:ssl()
+ return self._tls;
+end
+
+function interface:starttls(tls_ctx)
+ if tls_ctx then self.tls_ctx = tls_ctx; end
+ self.starttls = false;
+ if self.writebuffer and self.writebuffer[1] then
+ log("debug", "Start TLS on %s after write", self);
+ self.ondrain = interface.starttls;
+ self:set(nil, true); -- make sure wantwrite is set
+ else
+ if self.ondrain == interface.starttls then
+ self.ondrain = nil;
+ end
+ self.onwritable = interface.tlshandskake;
+ self.onreadable = interface.tlshandskake;
+ self:set(true, true);
+ log("debug", "Prepare to start TLS on %s", self);
+ end
+end
+
+function interface:tlshandskake()
+ self:setwritetimeout(false);
+ self:setreadtimeout(false);
+ if not self._tls then
+ self._tls = true;
+ log("debug", "Start TLS on %s now", self);
+ self:del();
+ local ok, conn, err = pcall(luasec.wrap, self.conn, self.tls_ctx);
+ if not ok then
+ conn, err = ok, conn;
+ log("error", "Failed to initialize TLS: %s", err);
+ end
+ if not conn then
+ self:on("disconnect", err);
+ self:destroy();
+ return conn, err;
+ end
+ conn:settimeout(0);
+ self.conn = conn;
+ self:on("starttls");
+ self.ondrain = nil;
+ self.onwritable = interface.tlshandskake;
+ self.onreadable = interface.tlshandskake;
+ return self:init();
+ end
+ local ok, err = self.conn:dohandshake();
+ if ok then
+ log("debug", "TLS handshake on %s complete", self);
+ self.onwritable = nil;
+ self.onreadable = nil;
+ self:on("status", "ssl-handshake-complete");
+ self:setwritetimeout();
+ self:set(true, true);
+ elseif err == "wantread" then
+ log("debug", "TLS handshake on %s to wait until readable", self);
+ self:set(true, false);
+ self:setreadtimeout(cfg.handshake_timeout);
+ elseif err == "wantwrite" then
+ log("debug", "TLS handshake on %s to wait until writable", self);
+ self:set(false, true);
+ self:setwritetimeout(cfg.handshake_timeout);
+ else
+ log("debug", "TLS handshake error on %s: %s", self, err);
+ self:on("disconnect", err);
+ self:destroy();
+ end
+end
+
+local function wrapsocket(client, server, read_size, listeners, tls_ctx) -- luasocket object -> interface object
+ client:settimeout(0);
+ local conn = setmetatable({
+ conn = client;
+ _server = server;
+ created = gettime();
+ listeners = listeners;
+ read_size = read_size or (server and server.read_size);
+ writebuffer = {};
+ tls_ctx = tls_ctx or (server and server.tls_ctx);
+ tls_direct = server and server.tls_direct;
+ }, interface_mt);
+
+ conn:updatenames();
+ return conn;
+end
+
+function interface:updatenames()
+ local conn = self.conn;
+ local ok, peername, peerport = pcall(conn.getpeername, conn);
+ if ok then
+ self.peername, self.peerport = peername, peerport;
+ end
+ local ok, sockname, sockport = pcall(conn.getsockname, conn);
+ if ok then
+ self.sockname, self.sockport = sockname, sockport;
+ end
+end
+
+-- A server interface has new incoming connections waiting
+-- This replaces the onreadable callback
+function interface:onacceptable()
+ local conn, err = self.conn:accept();
+ if not conn then
+ log("debug", "Error accepting new client: %s, server will be paused for %ds", err, cfg.accept_retry_interval);
+ self:pausefor(cfg.accept_retry_interval);
+ return;
+ end
+ local client = wrapsocket(conn, self, nil, self.listeners);
+ log("debug", "New connection %s", tostring(client));
+ client:init();
+ if self.tls_direct then
+ client:starttls(self.tls_ctx);
+ end
+end
+
+-- Initialization
+function interface:init()
+ self:setwritetimeout();
+ return self:add(true, true);
+end
+
+function interface:pause()
+ return self:set(false);
+end
+
+function interface:resume()
+ return self:set(true);
+end
+
+-- Pause connection for some time
+function interface:pausefor(t)
+ if self._pausefor then
+ self._pausefor:close();
+ end
+ if t == false then return; end
+ self:set(false);
+ self._pausefor = addtimer(t, function ()
+ self._pausefor = nil;
+ if self.conn and self.conn:dirty() then
+ self:onreadable();
+ end
+ self:set(true);
+ end);
+end
+
+-- Connected!
+function interface:onconnect()
+ if self.conn and not self.peername and self.conn.getpeername then
+ self.peername, self.peerport = self.conn:getpeername();
+ end
+ self.onconnect = noop;
+ self:on("connect");
+end
+
+local function addserver(addr, port, listeners, read_size, tls_ctx)
+ local conn, err = socket.bind(addr, port, cfg.tcp_backlog);
+ if not conn then return conn, err; end
+ conn:settimeout(0);
+ local server = setmetatable({
+ conn = conn;
+ created = gettime();
+ listeners = listeners;
+ read_size = read_size;
+ onreadable = interface.onacceptable;
+ tls_ctx = tls_ctx;
+ tls_direct = tls_ctx and true or false;
+ sockname = addr;
+ sockport = port;
+ }, interface_mt);
+ server:add(true, false);
+ return server;
+end
+
+-- COMPAT
+local function wrapclient(conn, addr, port, listeners, read_size, tls_ctx)
+ local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx);
+ if not client.peername then
+ client.peername, client.peerport = addr, port;
+ end
+ local ok, err = client:init();
+ if not ok then return ok, err; end
+ if tls_ctx then
+ client:starttls(tls_ctx);
+ end
+ return client;
+end
+
+-- New outgoing TCP connection
+local function addclient(addr, port, listeners, read_size, tls_ctx, typ)
+ local create;
+ if not typ then
+ local n = inet_pton(addr);
+ if not n then return nil, "invalid-ip"; end
+ if #n == 16 then
+ typ = "tcp6";
+ else
+ typ = "tcp4";
+ end
+ end
+ if typ then
+ create = socket[typ];
+ end
+ if type(create) ~= "function" then
+ return nil, "invalid socket type";
+ end
+ local conn, err = create();
+ local ok, err = conn:settimeout(0);
+ if not ok then return ok, err; end
+ local ok, err = conn:setpeername(addr, port);
+ if not ok and err ~= "timeout" then return ok, err; end
+ local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx)
+ local ok, err = client:init();
+ if not ok then return ok, err; end
+ if tls_ctx then
+ client:starttls(tls_ctx);
+ end
+ return client, conn;
+end
+
+local function watchfd(fd, onreadable, onwritable)
+ local conn = setmetatable({
+ conn = fd;
+ onreadable = onreadable;
+ onwritable = onwritable;
+ close = function (self)
+ self:del();
+ end
+ }, interface_mt);
+ if type(fd) == "number" then
+ conn.getfd = function ()
+ return fd;
+ end;
+ -- Otherwise it'll need to be something LuaSocket-compatible
+ end
+ conn:add(onreadable, onwritable);
+ return conn;
+end;
+
+-- Dump all data from one connection into another
+local function link(from, to)
+ from.listeners = setmetatable({
+ onincoming = function (_, data)
+ from:pause();
+ to:write(data);
+ end,
+ }, {__index=from.listeners});
+ to.listeners = setmetatable({
+ ondrain = function ()
+ from:resume();
+ end,
+ }, {__index=to.listeners});
+ from:set(true, nil);
+ to:set(nil, true);
+end
+
+-- COMPAT
+-- net.adns calls this but then replaces :send so this can be a noop
+function interface:set_send(new_send) -- luacheck: ignore 212
+end
+
+-- Close all connections and servers
+local function closeall()
+ for fd, conn in pairs(fds) do -- luacheck: ignore 213/fd
+ conn:close();
+ end
+end
+
+local quitting = nil;
+
+-- Signal main loop about shutdown via above upvalue
+local function setquitting(quit)
+ if quit then
+ quitting = "quitting";
+ closeall();
+ else
+ quitting = nil;
+ end
+end
+
+-- Main loop
+local function loop(once)
+ repeat
+ local t = runtimers(cfg.max_wait, cfg.min_wait);
+ local fd, r, w = poll:wait(t);
+ if fd then
+ local conn = fds[fd];
+ if conn then
+ if r then
+ conn:onreadable();
+ end
+ if w then
+ conn:onwritable();
+ end
+ else
+ log("debug", "Removing unknown fd %d", fd);
+ poll:del(fd);
+ end
+ elseif r ~= "timeout" and r ~= "signal" then
+ log("debug", "epoll_wait error: %s[%d]", r, w);
+ end
+ until once or (quitting and next(fds) == nil);
+ return quitting;
+end
+
+return {
+ get_backend = function () return "epoll"; end;
+ addserver = addserver;
+ addclient = addclient;
+ add_task = addtimer;
+ at = at;
+ loop = loop;
+ closeall = closeall;
+ setquitting = setquitting;
+ wrapclient = wrapclient;
+ watchfd = watchfd;
+ link = link;
+ set_config = function (newconfig)
+ cfg = setmetatable(newconfig, default_config);
+ end;
+
+ -- libevent emulation
+ event = { EV_READ = "r", EV_WRITE = "w", EV_READWRITE = "rw", EV_LEAVE = -1 };
+ addevent = function (fd, mode, callback)
+ local function onevent(self)
+ local ret = self:callback();
+ if ret == -1 then
+ self:set(false, false);
+ elseif ret then
+ self:set(mode == "r" or mode == "rw", mode == "w" or mode == "rw");
+ end
+ end
+
+ local conn = setmetatable({
+ getfd = function () return fd; end;
+ callback = callback;
+ onreadable = onevent;
+ onwritable = onevent;
+ close = function (self)
+ self:del();
+ fds[fd] = nil;
+ end;
+ }, interface_mt);
+ local ok, err = conn:add(mode == "r" or mode == "rw", mode == "w" or mode == "rw");
+ if not ok then return ok, err; end
+ return conn;
+ end;
+};
diff -r bb8486491b48 -r cc642c9c5ad5 net/server_event.lua
--- a/net/server_event.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/server_event.lua Mon Nov 26 19:48:07 2018 +0100
@@ -5,9 +5,9 @@
notes:
-- when using luaevent, never register 2 or more EV_READ at one socket, same for EV_WRITE
- -- you cant even register a new EV_READ/EV_WRITE callback inside another one
+ -- you can't even register a new EV_READ/EV_WRITE callback inside another one
-- to do some of the above, use timeout events or something what will called from outside
- -- dont let garbagecollect eventcallbacks, as long they are running
+ -- don't let garbagecollect eventcallbacks, as long they are running
-- when using luasec, there are 4 cases of timeout errors: wantread or wantwrite during reading or writing
--]]
@@ -26,7 +26,7 @@
MAX_SEND_LENGTH = 1024 * 1024 * 1024 * 1024, -- max bytes size of write buffer (for writing on sockets)
ACCEPT_QUEUE = 128, -- might influence the length of the pending sockets queue
ACCEPT_DELAY = 10, -- seconds to wait until the next attempt of a full server to accept
- READ_TIMEOUT = 60 * 60 * 6, -- timeout in seconds for read data from socket
+ READ_TIMEOUT = 14 * 60, -- timeout in seconds for read data from socket
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)
@@ -50,9 +50,10 @@
local has_luasec, ssl = pcall ( require , "ssl" )
local socket = require "socket"
local levent = require "luaevent.core"
+local inet = require "util.net";
+local inet_pton = inet.pton;
local socket_gettime = socket.gettime
-local getaddrinfo = socket.dns.getaddrinfo
local log = require ("util.logger").init("socket")
@@ -106,6 +107,12 @@
self:_close()
debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
else
+ if EV_READWRITE == event then
+ if self.readcallback(event) == -1 then
+ -- Fatal error occurred
+ return -1;
+ end
+ end
if plainssl and has_luasec then -- start ssl session
self:starttls(self._sslctx, true)
else -- normal connection
@@ -116,7 +123,7 @@
self.eventconnect = nil
return -1
end
- self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT )
+ self.eventconnect = addevent( base, self.conn, EV_READWRITE, callback, cfg.CONNECT_TIMEOUT )
return true
end
function interface_mt:_start_session(call_onconnect) -- new session, for example after startssl
@@ -151,7 +158,7 @@
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
+ self.ondisconnect = nil -- don't call this when client isn't really connected
end
self:_close()
debug( "fatal error while ssl wrapping:", err )
@@ -194,7 +201,7 @@
end
if self.fatalerror then
if call_onconnect then
- self.ondisconnect = nil -- dont call this when client isnt really connected
+ self.ondisconnect = nil -- don't call this when client isn't really connected
end
self:_close()
debug( "handshake failed because:", self.fatalerror )
@@ -223,7 +230,8 @@
_ = 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)
+ -- call ondisconnect listener (won't be the case if handshake failed on connect)
+ _ = self.ondisconnect and self:ondisconnect( self.fatalerror ~= "client to close" and self.fatalerror)
_ = self.conn and self.conn:close( ) -- close connection
_ = self._server and self._server:counter(-1);
self.eventread, self.eventwrite = nil, nil
@@ -408,7 +416,7 @@
return false, "setoption not implemented";
end
-function interface_mt:setlistener(listener)
+function interface_mt:setlistener(listener, data)
self:ondetach(); -- Notify listener that it is no longer responsible for this connection
self.onconnect = listener.onconnect;
self.ondisconnect = listener.ondisconnect;
@@ -417,7 +425,9 @@
self.onreadtimeout = listener.onreadtimeout;
self.onstatus = listener.onstatus;
self.ondetach = listener.ondetach;
+ self.onattach = listener.onattach;
self.ondrain = listener.ondrain;
+ self:onattach(data);
end
-- Stub handlers
@@ -439,6 +449,8 @@
end
function interface_mt:ondetach()
end
+function interface_mt:onattach()
+end
function interface_mt:onstatus()
end
@@ -510,7 +522,7 @@
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
+ if succ then -- writing successful
interface.writebuffer[1] = nil
interface.writebufferlen = 0
interface:ondrain();
@@ -539,7 +551,7 @@
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..." )
+ debug( "wantread during write attempt, reg it in readcallback but don't 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
@@ -595,8 +607,8 @@
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...
+ debug( "wantwrite during read attempt, reg it in writecallback but don't know what really happens next..." )
+ -- to be honest i don't 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 )
@@ -717,15 +729,15 @@
return nil, "luasec not found"
end
if not typ then
- local addrinfo, err = getaddrinfo(addr)
- if not addrinfo then return nil, err end
- if addrinfo[1] and addrinfo[1].family == "inet6" then
- typ = "tcp6"
- else
- typ = "tcp"
+ local n = inet_pton(addr);
+ if not n then return nil, "invalid-ip"; end
+ if #n == 16 then
+ typ = "tcp6";
+ elseif #n == 4 then
+ typ = "tcp4";
end
end
- local create = socket[typ]
+ local create = socket[typ];
if type( create ) ~= "function" then
return nil, "invalid socket type"
end
@@ -735,7 +747,7 @@
return nil, err
end
client:settimeout( 0 ) -- set nonblocking
- local res, err = client:connect( addr, serverport ) -- connect
+ local res, err = client:setpeername( addr, serverport ) -- connect
if res or ( err == "timeout" ) then
local ip, port = client:getsockname( )
local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx )
@@ -767,13 +779,15 @@
local function setquitting(yes)
if yes then
-- Quit now
- closeallservers();
+ if yes ~= "once" then
+ closeallservers();
+ end
base:loopexit();
end
end
local function get_backend()
- return base:method();
+ return "libevent " .. base:method();
end
-- We need to hold onto the events to stop them
@@ -811,6 +825,48 @@
sender:set_mode("*a");
end
+local function add_task(delay, callback)
+ local event_handle;
+ event_handle = base:addevent(nil, 0, function ()
+ local ret = callback(socket_gettime());
+ if ret then
+ return 0, ret;
+ elseif event_handle then
+ return -1;
+ end
+ end
+ , delay);
+ return event_handle;
+end
+
+local function watchfd(fd, onreadable, onwriteable)
+ local handle = {};
+ function handle:setflags(r,w)
+ if r ~= nil then
+ if r and not self.wantread then
+ self.wantread = base:addevent(fd, EV_READ, function ()
+ onreadable(self);
+ end);
+ elseif not r and self.wantread then
+ self.wantread:close();
+ self.wantread = nil;
+ end
+ end
+ if w ~= nil then
+ if w and not self.wantwrite then
+ self.wantwrite = base:addevent(fd, EV_WRITE, function ()
+ onwriteable(self);
+ end);
+ elseif not r and self.wantread then
+ self.wantwrite:close();
+ self.wantwrite = nil;
+ end
+ end
+ end
+ handle:setflags(onreadable, onwriteable);
+ return handle;
+end
+
return {
cfg = cfg,
base = base,
@@ -826,6 +882,8 @@
closeall = closeallservers,
get_backend = get_backend,
hook_signal = hook_signal,
+ add_task = add_task,
+ watchfd = watchfd,
__NAME = SCRIPT_NAME,
__DATE = LAST_MODIFIED,
diff -r bb8486491b48 -r cc642c9c5ad5 net/server_select.lua
--- a/net/server_select.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/server_select.lua Mon Nov 26 19:48:07 2018 +0100
@@ -40,6 +40,7 @@
local math_min = math.min
local math_huge = math.huge
local table_concat = table.concat
+local table_insert = table.insert
local string_sub = string.sub
local coroutine_wrap = coroutine.wrap
local coroutine_yield = coroutine.yield
@@ -49,13 +50,13 @@
local has_luasec, luasec = pcall ( require , "ssl" )
local luasocket = use "socket" or require "socket"
local luasocket_gettime = luasocket.gettime
-local getaddrinfo = luasocket.dns.getaddrinfo
+local inet = require "util.net";
+local inet_pton = inet.pton;
--// extern lib methods //--
local ssl_wrap = ( has_luasec and luasec.wrap )
local socket_bind = luasocket.bind
-local socket_sleep = luasocket.sleep
local socket_select = luasocket.select
--// functions //--
@@ -100,7 +101,6 @@
local _readtraffic
local _selecttimeout
-local _sleeptime
local _tcpbacklog
local _accepretry
@@ -114,8 +114,6 @@
local _sendtimeout
local _readtimeout
-local _timer
-
local _maxselectlen
local _maxfd
@@ -135,13 +133,12 @@
_readlistlen = 0 -- length of readlist
_sendlistlen = 0 -- length of sendlist
-_timerlistlen = 0 -- lenght of timerlist
+_timerlistlen = 0 -- length of timerlist
_sendtraffic = 0 -- some stats
_readtraffic = 0
_selecttimeout = 1 -- timeout of socket.select
-_sleeptime = 0 -- time to wait at the end of every loop
_tcpbacklog = 128 -- some kind of hint to the OS
_accepretry = 10 -- seconds to wait until the next attempt of a full server to accept
@@ -150,7 +147,7 @@
_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
+_readtimeout = 14 * 60 -- allowed read idle time in secs
local is_windows = package.config:sub(1,1) == "\\" -- check the directory separator, to detemine whether this is Windows
_maxfd = (is_windows and math.huge) or luasocket._SETSIZE or 1024 -- max fd number, limit to 1024 by default to prevent glibc buffer overflow, but not on Windows
@@ -301,7 +298,6 @@
local bufferqueuelen = 0 -- end of buffer array
local toclose
- local fatalerror
local needtls
local bufferlen = 0
@@ -326,7 +322,7 @@
end
handler.onreadtimeout = onreadtimeout;
- handler.setlistener = function( self, listeners )
+ handler.setlistener = function( self, listeners, data )
if detach then
detach(self) -- Notify listener that it is no longer responsible for this connection
end
@@ -336,6 +332,9 @@
drain = listeners.ondrain
handler.onreadtimeout = listeners.onreadtimeout
detach = listeners.ondetach
+ if listeners.onattach then
+ listeners.onattach(self, data)
+ end
end
handler.getstats = function( )
return readtraffic, sendtraffic
@@ -425,7 +424,7 @@
bufferlen = bufferlen + #data
if bufferlen > maxsendlen then
_closelist[ handler ] = "send buffer exceeded" -- cannot close the client at the moment, have to wait to the end of the cycle
- handler.write = idfalse -- dont write anymore
+ handler.write = idfalse -- don't write anymore
return false
elseif socket and not _sendlist[ socket ] then
_sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
@@ -517,7 +516,6 @@
return dispatch( handler, buffer, err )
else -- connections was closed or fatal error
out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) )
- fatalerror = true
_ = handler and handler:force_close( err )
return false
end
@@ -537,7 +535,7 @@
else
succ, err, count = false, "unexpected close", 0;
end
- if succ then -- sending succesful
+ if succ then -- sending successful
bufferqueuelen = 0
bufferlen = 0
_sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) -- delete socket from writelist
@@ -557,7 +555,6 @@
return true
else -- connection was closed during sending or fatal error
out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) )
- fatalerror = true
_ = handler and handler:force_close( err )
return false
end
@@ -806,7 +803,6 @@
getsettings = function( )
return {
select_timeout = _selecttimeout;
- select_sleep_time = _sleeptime;
tcp_backlog = _tcpbacklog;
max_send_buffer_size = _maxsendlen;
max_receive_buffer_size = _maxreadlen;
@@ -825,7 +821,6 @@
return nil, "invalid settings table"
end
_selecttimeout = tonumber( new.select_timeout ) or _selecttimeout
- _sleeptime = tonumber( new.select_sleep_time ) or _sleeptime
_maxsendlen = tonumber( new.max_send_buffer_size ) or _maxsendlen
_maxreadlen = tonumber( new.max_receive_buffer_size ) or _maxreadlen
_checkinterval = tonumber( new.select_idle_check_interval ) or _checkinterval
@@ -848,6 +843,49 @@
return true
end
+local add_task do
+ local data = {};
+ local new_data = {};
+
+ function add_task(delay, callback)
+ local current_time = luasocket_gettime();
+ delay = delay + current_time;
+ if delay >= current_time then
+ table_insert(new_data, {delay, callback});
+ else
+ local r = callback(current_time);
+ if r and type(r) == "number" then
+ return add_task(r, callback);
+ end
+ end
+ end
+
+ addtimer(function(current_time)
+ if #new_data > 0 then
+ for _, d in pairs(new_data) do
+ table_insert(data, d);
+ end
+ new_data = {};
+ end
+
+ local next_time = math_huge;
+ for i, d in pairs(data) do
+ local t, callback = d[1], d[2];
+ if t <= current_time then
+ data[i] = nil;
+ local r = callback(current_time);
+ if type(r) == "number" then
+ add_task(r, callback);
+ next_time = math_min(next_time, r);
+ end
+ else
+ next_time = math_min(next_time, t - current_time);
+ end
+ end
+ return next_time;
+ end);
+end
+
stats = function( )
return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen
end
@@ -855,15 +893,31 @@
local quitting;
local function setquitting(quit)
- quitting = not not quit;
+ quitting = quit;
end
loop = function(once) -- this is the main loop of the program
if quitting then return "quitting"; end
if once then quitting = "once"; end
- local next_timer_time = math_huge;
+ _currenttime = luasocket_gettime( )
repeat
+ -- Fire timers
+ local next_timer_time = math_huge;
+ for i = 1, _timerlistlen do
+ local t = _timerlist[ i ]( _currenttime ) -- fire timers
+ if t then next_timer_time = math_min(next_timer_time, t); end
+ end
+
local read, write, err = socket_select( _readlist, _sendlist, math_min(_selecttimeout, next_timer_time) )
+ for _, socket in ipairs( read ) do -- receive data
+ local handler = _socketlist[ socket ]
+ if handler then
+ handler.readbuffer( )
+ else
+ closesocket( socket )
+ out_put "server.lua: found no handler and closed socket (readlist)" -- this can happen
+ end
+ end
for _, socket in ipairs( write ) do -- send data waiting in writequeues
local handler = _socketlist[ socket ]
if handler then
@@ -873,15 +927,6 @@
out_put "server.lua: found no handler and closed socket (writelist)" -- this should not happen
end
end
- for _, socket in ipairs( read ) do -- receive data
- local handler = _socketlist[ socket ]
- if handler then
- handler.readbuffer( )
- else
- closesocket( socket )
- out_put "server.lua: found no handler and closed socket (readlist)" -- this can happen
- end
- end
for handler, err in pairs( _closelist ) do
handler.disconnect( )( handler, err )
handler:force_close() -- forced disconnect
@@ -910,29 +955,14 @@
end
end
- -- Fire timers
- if _currenttime - _timer >= math_min(next_timer_time, 1) then
- next_timer_time = math_huge;
- for i = 1, _timerlistlen do
- local t = _timerlist[ i ]( _currenttime ) -- fire timers
- if t then next_timer_time = math_min(next_timer_time, t); end
- end
- _timer = _currenttime
- else
- next_timer_time = next_timer_time - (_currenttime - _timer);
- end
-
for server, paused_time in pairs( _fullservers ) do
if _currenttime - paused_time > _accepretry then
_fullservers[ server ] = nil;
server.resume();
end
end
-
- -- wait some time (0 by default)
- socket_sleep( _sleeptime )
until quitting;
- if once and quitting == "once" then quitting = nil; return; end
+ if quitting == "once" then quitting = nil; return; end
closeall();
return "quitting"
end
@@ -952,6 +982,7 @@
if not handler then return nil, err end
_socketlist[ socket ] = handler
if not sslctx then
+ _readlistlen = addsocket(_readlist, socket, _readlistlen)
_sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
if listeners.onconnect then
-- When socket is writeable, call onconnect
@@ -978,15 +1009,15 @@
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"
+ local n = inet_pton(address);
+ if not n then return nil, "invalid-ip"; end
+ if #n == 16 then
+ typ = "tcp6";
+ elseif #n == 4 then
+ typ = "tcp4";
end
end
- local create = luasocket[typ]
+ local create = luasocket[typ];
if type( create ) ~= "function" then
err = "invalid socket type"
end
@@ -1001,15 +1032,55 @@
return nil, err
end
client:settimeout( 0 )
- local ok, err = client:connect( address, port )
- if ok or err == "timeout" then
+ local ok, err = client:setpeername( address, port )
+ if ok or err == "timeout" or err == "Operation already in progress" then
return wrapclient( client, address, port, listeners, pattern, sslctx )
else
return nil, err
end
end
---// EXPERIMENTAL //--
+local closewatcher = function (handler)
+ local socket = handler.conn;
+ _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
+ _readlistlen = removesocket( _readlist, socket, _readlistlen )
+ _socketlist[ socket ] = nil
+end;
+
+local addremove = function (handler, read, send)
+ local socket = handler.conn
+ _socketlist[ socket ] = handler
+ if read ~= nil then
+ if read then
+ _readlistlen = addsocket( _readlist, socket, _readlistlen )
+ else
+ _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
+ end
+ end
+ if send ~= nil then
+ if send then
+ _sendlistlen = addsocket( _sendlist, socket, _sendlistlen )
+ else
+ _readlistlen = removesocket( _readlist, socket, _readlistlen )
+ end
+ end
+end
+
+local watchfd = function ( fd, onreadable, onwriteable )
+ local socket = fd
+ if type(fd) == "number" then
+ socket = { getfd = function () return fd; end }
+ end
+ local handler = {
+ conn = socket;
+ readbuffer = onreadable or id;
+ sendbuffer = onwriteable or id;
+ close = closewatcher;
+ setflags = addremove;
+ };
+ addremove( handler, onreadable, onwriteable )
+ return handler
+end
----------------------------------// BEGIN //--
@@ -1017,7 +1088,6 @@
use "setmetatable" ( _readtimes, { __mode = "k" } )
use "setmetatable" ( _writetimes, { __mode = "k" } )
-_timer = luasocket_gettime( )
_starttime = luasocket_gettime( )
local function setlogger(new_logger)
@@ -1032,9 +1102,11 @@
return {
_addtimer = addtimer,
+ add_task = add_task;
addclient = addclient,
wrapclient = wrapclient,
+ watchfd = watchfd,
loop = loop,
link = link,
diff -r bb8486491b48 -r cc642c9c5ad5 net/websocket.lua
--- a/net/websocket.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/websocket.lua Mon Nov 26 19:48:07 2018 +0100
@@ -21,9 +21,9 @@
local websockets = {};
local websocket_listeners = {};
-function websocket_listeners.ondisconnect(handler, err)
- local s = websockets[handler];
- websockets[handler] = nil;
+function websocket_listeners.ondisconnect(conn, err)
+ local s = websockets[conn];
+ websockets[conn] = nil;
if s.close_timer then
timer.stop(s.close_timer);
s.close_timer = nil;
@@ -33,19 +33,19 @@
if s.onclose then s:onclose(s.close_code, s.close_message or err); end
end
-function websocket_listeners.ondetach(handler)
- websockets[handler] = nil;
+function websocket_listeners.ondetach(conn)
+ websockets[conn] = nil;
end
local function fail(s, code, reason)
log("warn", "WebSocket connection failed, closing. %d %s", code, reason);
s:close(code, reason);
- s.handler:close();
+ s.conn:close();
return false
end
-function websocket_listeners.onincoming(handler, buffer, err) -- luacheck: ignore 212/err
- local s = websockets[handler];
+function websocket_listeners.onincoming(conn, buffer, err) -- luacheck: ignore 212/err
+ local s = websockets[conn];
s.readbuffer = s.readbuffer..buffer;
while true do
local frame, len = frames.parse(s.readbuffer);
@@ -111,7 +111,7 @@
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));
+ conn:write(frames.build(frame));
elseif frame.opcode == 0xA then -- Pong frame
log("debug", "Received unexpected pong frame: " .. tostring(frame.data));
else
@@ -126,15 +126,15 @@
local function close_timeout_cb(now, timerid, s) -- luacheck: ignore 212/now 212/timerid
s.close_timer = nil;
log("warn", "Close timeout waiting for server to close, closing manually.");
- s.handler:close();
+ s.conn: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));
+ local conn = self.conn;
+ conn: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
@@ -144,8 +144,8 @@
timer.stop(self.close_timer);
self.close_timer = nil;
end
- local handler = self.handler;
- handler:close();
+ local conn = self.conn;
+ conn:close();
else
log("debug", "tried to close a closed WebSocket, ignoring.");
end
@@ -168,7 +168,7 @@
data = tostring(data);
};
log("debug", "WebSocket sending frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
- return self.handler:write(frames.build(frame));
+ return self.conn:write(frames.build(frame));
end
local websocket_metatable = {
@@ -216,7 +216,7 @@
local s = setmetatable({
readbuffer = "";
databuffer = nil;
- handler = nil;
+ conn = nil;
close_code = nil;
close_message = nil;
close_timer = nil;
@@ -236,6 +236,7 @@
method = "GET";
headers = headers;
sslctx = ex.sslctx;
+ insecure = ex.insecure;
}, function(b, c, r, http_req)
if c ~= 101
or r.headers["connection"]:lower() ~= "upgrade"
@@ -252,16 +253,16 @@
s.protocol = r.headers["sec-websocket-protocol"];
-- Take possession of socket from http
+ local conn = http_req.conn;
http_req.conn = nil;
- local handler = http_req.handler;
- s.handler = handler;
- websockets[handler] = s;
- handler:setlistener(websocket_listeners);
+ s.conn = conn;
+ websockets[conn] = s;
+ conn: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);
+ websocket_listeners.onincoming(conn, b);
end);
return s;
diff -r bb8486491b48 -r cc642c9c5ad5 net/websocket/frames.lua
--- a/net/websocket/frames.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/net/websocket/frames.lua Mon Nov 26 19:48:07 2018 +0100
@@ -21,8 +21,8 @@
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;
+local s_pack = string.pack; -- luacheck: ignore 143
+local s_unpack = string.unpack; -- luacheck: ignore 143
if not s_pack and softreq"struct" then
s_pack = softreq"struct".pack;
@@ -112,9 +112,9 @@
-- 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
+ if from < 0 then from = #str + from + 1 end -- negative indices
to = to or #str
- if to < 0 then to = #str + to + 1 end -- negative indicies
+ if to < 0 then to = #str + to + 1 end -- negative indices
local key_len = #key
local counter = 0;
local data = {};
diff -r bb8486491b48 -r cc642c9c5ad5 plugins/adhoc/adhoc.lib.lua
--- a/plugins/adhoc/adhoc.lib.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/plugins/adhoc/adhoc.lib.lua Mon Nov 26 19:48:07 2018 +0100
@@ -36,30 +36,30 @@
local data, state = command:handler(dataIn, states[sessionid]);
states[sessionid] = state;
- local cmdtag;
+ local cmdreply;
if data.status == "completed" then
states[sessionid] = nil;
- cmdtag = command:cmdtag("completed", sessionid);
+ cmdreply = command:cmdtag("completed", sessionid);
elseif data.status == "canceled" then
states[sessionid] = nil;
- cmdtag = command:cmdtag("canceled", sessionid);
+ cmdreply = command:cmdtag("canceled", sessionid);
elseif data.status == "error" then
states[sessionid] = nil;
local reply = st.error_reply(stanza, data.error.type, data.error.condition, data.error.message);
origin.send(reply);
return true;
else
- cmdtag = command:cmdtag("executing", sessionid);
+ cmdreply = command:cmdtag("executing", sessionid);
data.actions = data.actions or { "complete" };
end
for name, content in pairs(data) do
if name == "info" then
- cmdtag:tag("note", {type="info"}):text(content):up();
+ cmdreply:tag("note", {type="info"}):text(content):up();
elseif name == "warn" then
- cmdtag:tag("note", {type="warn"}):text(content):up();
+ cmdreply:tag("note", {type="warn"}):text(content):up();
elseif name == "error" then
- cmdtag:tag("note", {type="error"}):text(content.message):up();
+ cmdreply:tag("note", {type="error"}):text(content.message):up();
elseif name == "actions" then
local actions = st.stanza("actions", { execute = content.default });
for _, action in ipairs(content) do
@@ -70,17 +70,17 @@
command.name, command.node, action);
end
end
- cmdtag:add_child(actions);
+ cmdreply:add_child(actions);
elseif name == "form" then
- cmdtag:add_child((content.layout or content):form(content.values));
+ cmdreply:add_child((content.layout or content):form(content.values));
elseif name == "result" then
- cmdtag:add_child((content.layout or content):form(content.values, "result"));
+ cmdreply:add_child((content.layout or content):form(content.values, "result"));
elseif name == "other" then
- cmdtag:add_child(content);
+ cmdreply:add_child(content);
end
end
local reply = st.reply(stanza);
- reply:add_child(cmdtag);
+ reply:add_child(cmdreply);
origin.send(reply);
return true;
diff -r bb8486491b48 -r cc642c9c5ad5 plugins/adhoc/mod_adhoc.lua
--- a/plugins/adhoc/mod_adhoc.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/plugins/adhoc/mod_adhoc.lua Mon Nov 26 19:48:07 2018 +0100
@@ -5,9 +5,8 @@
-- COPYING file in the source package for more information.
--
+local it = require "util.iterators";
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;
@@ -45,8 +44,8 @@
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
+ local stanza, reply, disco_node = event.stanza, event.reply, event.node;
+ if disco_node ~= xmlns_cmd then
return;
end
@@ -54,9 +53,7 @@
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];
+ for node, command in it.sorted_pairs(commands) do
if (command.permission == "admin" and admin)
or (command.permission == "global_admin" and global_admin)
or (command.permission == "local_user" and hostname == module.host)
@@ -69,28 +66,26 @@
event.exists = true;
end);
-module:hook("iq/host/"..xmlns_cmd..":command", function (event)
+module:hook("iq-set/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
- 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
- adhoc_handle_cmd(commands[node], origin, stanza);
- return true;
+ local node = stanza.tags[1].attr.node
+ 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
+ adhoc_handle_cmd(commands[node], origin, stanza);
+ return true;
end
end, 500);
@@ -103,5 +98,5 @@
commands[event.item.node] = nil;
end
-module:handle_items("adhoc", adhoc_added, adhoc_removed);
+module:handle_items("adhoc", adhoc_added, adhoc_removed); -- COMPAT pre module:provides() introduced in 0.9
module:handle_items("adhoc-provider", adhoc_added, adhoc_removed);
diff -r bb8486491b48 -r cc642c9c5ad5 plugins/mod_admin_adhoc.lua
--- a/plugins/mod_admin_adhoc.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/plugins/mod_admin_adhoc.lua Mon Nov 26 19:48:07 2018 +0100
@@ -3,6 +3,7 @@
-- This file is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
+-- luacheck: ignore 212/self 212/data 212/state 412/err 422/err
local _G = _G;
@@ -95,7 +96,12 @@
end
local username, host, resource = jid.split(fields.accountjid);
if module_host ~= host then
- return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. module_host}};
+ return {
+ status = "completed",
+ error = {
+ message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. module_host
+ }
+ };
end
if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host, nil) then
return { status = "completed", info = "Password successfully changed" };
@@ -207,8 +213,8 @@
return generate_error_message(err);
end
local user, host, resource = jid.split(fields.accountjid);
- local accountjid = "";
- local password = "";
+ local accountjid;
+ local password;
if host ~= module_host then
return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. module_host } };
elseif usermanager_user_exists(user, host) then
@@ -246,15 +252,15 @@
local roster = rm_load_roster(user, host);
local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
- for jid in pairs(roster) do
- if jid then
+ for contact_jid in pairs(roster) do
+ if contact_jid then
query:tag("item", {
- jid = jid,
- subscription = roster[jid].subscription,
- ask = roster[jid].ask,
- name = roster[jid].name,
+ jid = contact_jid,
+ subscription = roster[contact_jid].subscription,
+ ask = roster[contact_jid].ask,
+ name = roster[contact_jid].name,
});
- for group in pairs(roster[jid].groups) do
+ for group in pairs(roster[contact_jid].groups) do
query:tag("group"):text(group):up();
end
query:up();
@@ -289,7 +295,7 @@
return generate_error_message(err);
end
- local user, host, resource = jid.split(fields.accountjid);
+ local user, host = jid.split(fields.accountjid);
if host ~= module_host then
return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. module_host } };
elseif not usermanager_user_exists(user, host) then
@@ -299,8 +305,8 @@
local rostersize = 0;
local IPs = "";
local resources = "";
- for jid in pairs(roster) do
- if jid then
+ for contact_jid in pairs(roster) do
+ if contact_jid then
rostersize = rostersize + 1;
end
end
@@ -319,7 +325,7 @@
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
{ name = "max_items", type = "list-single", label = "Maximum number of users",
- value = { "25", "50", "75", "100", "150", "200", "all" } };
+ options = { "25", "50", "75", "100", "150", "200", "all" } };
{ name = "details", type = "boolean", label = "Show details" };
};
@@ -369,7 +375,7 @@
{ 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_in", type = "text-single", label = "#incoming connections:" };
{ name = "num_out", type = "text-single", label = "#outgoing connections:" };
};
@@ -588,7 +594,7 @@
end
local ok_list, err_list = {}, {};
- for host_name, host in pairs(hosts) do
+ for host_name in pairs(hosts) do
if modulemanager.is_loaded(host_name, fields.module) then
local ok, err = modulemanager.reload(host_name, fields.module);
if ok then
@@ -641,13 +647,16 @@
{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
{ name = "delay", type = "list-single", label = "Time delay before shutting down",
- value = { {label = "30 seconds", value = "30"},
- {label = "60 seconds", value = "60"},
- {label = "90 seconds", value = "90"},
- {label = "2 minutes", value = "120"},
- {label = "3 minutes", value = "180"},
- {label = "4 minutes", value = "240"},
- {label = "5 minutes", value = "300"},
+ value = "5",
+ options = {
+ {label = "5 seconds", value = "5"},
+ {label = "30 seconds", value = "30"},
+ {label = "60 seconds", value = "60"},
+ {label = "90 seconds", value = "90"},
+ {label = "2 minutes", value = "120"},
+ {label = "3 minutes", value = "180"},
+ {label = "4 minutes", value = "240"},
+ {label = "5 minutes", value = "300"},
};
};
{ name = "announcement", type = "text-multi", label = "Announcement" };
@@ -664,7 +673,7 @@
send_to_online(message);
end
- timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end);
+ timer_add_task(tonumber(fields.delay or "5"), function() prosody.shutdown("Shutdown by adhoc command") end);
return { status = "completed", info = "Server is about to shut down" };
end);
@@ -730,7 +739,7 @@
end
local ok_list, err_list = {}, {};
- for host_name, host in pairs(hosts) do
+ for host_name in pairs(hosts) do
if modulemanager.is_loaded(host_name, fields.module) then
local ok, err = modulemanager.unload(host_name, fields.module);
if ok then
@@ -799,6 +808,7 @@
end
end);
+-- luacheck: max_line_length 180
local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin");
local change_user_password_desc = adhoc_new("Change User Password", "http://jabber.org/protocol/admin#change-user-password", change_user_password_command_handler, "admin");
diff -r bb8486491b48 -r cc642c9c5ad5 plugins/mod_admin_telnet.lua
--- a/plugins/mod_admin_telnet.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/plugins/mod_admin_telnet.lua Mon Nov 26 19:48:07 2018 +0100
@@ -5,6 +5,7 @@
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
+-- luacheck: ignore 212/self
module:set_global();
@@ -13,11 +14,11 @@
local s2smanager = require "core.s2smanager";
local portmanager = require "core.portmanager";
local helpers = require "util.helpers";
+local server = require "net.server";
local _G = _G;
local prosody = _G.prosody;
-local hosts = prosody.hosts;
local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" };
@@ -34,8 +35,8 @@
local def_env = module:shared("env");
local default_env_mt = { __index = def_env };
-local function redirect_output(_G, session)
- local env = setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end });
+local function redirect_output(target, session)
+ local env = setmetatable({ print = session.print }, { __index = function (_, k) return rawget(target, k); end });
env.dofile = function(name)
local f, err = envloadfile(name, env);
if not f then return f, err; end
@@ -163,7 +164,7 @@
end
end
-function console_listener.ondisconnect(conn, err)
+function console_listener.ondisconnect(conn, err) -- luacheck: ignore 212/err
local session = sessions[conn];
if session then
session.disconnect();
@@ -361,7 +362,7 @@
hosts = get_hosts_set(hosts);
-- Load the module for each host
- local ok, err, count, mod = true, nil, 0, nil;
+ local ok, err, count, mod = true, nil, 0;
for host in hosts do
if (not modulemanager.is_loaded(host, name)) then
mod, err = modulemanager.load(host, name, config);
@@ -404,12 +405,14 @@
return ok, (ok and "Module unloaded from "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
end
+local function _sort_hosts(a, b)
+ if a == "*" then return true
+ elseif b == "*" then return false
+ else return a < b; end
+end
+
function def_env.module:reload(name, hosts)
- hosts = array.collect(get_hosts_set(hosts, name)):sort(function (a, b)
- if a == "*" then return true
- elseif b == "*" then return false
- else return a < b; end
- end);
+ hosts = array.collect(get_hosts_set(hosts, name)):sort(_sort_hosts)
-- Reload the module for each host
local ok, err, count = true, nil, 0;
@@ -567,7 +570,7 @@
end);
end
-function def_env.c2s:count(match_jid)
+function def_env.c2s:count()
return true, "Total: ".. iterators.count(values(module:shared"/*/c2s/sessions")) .." clients";
end
@@ -651,6 +654,7 @@
if (not match_jid) or remotehost:match(match_jid) or localhost:match(match_jid) then
table.insert(s2s_list, sess_lines);
+ -- luacheck: ignore 421/print
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");
@@ -830,7 +834,7 @@
local match_id;
if from and not to then
- match_id, from = from;
+ match_id, from = from, nil;
elseif not to then
return false, "Syntax: s2s:close('from', 'to') - Closes all s2s sessions from 'from' to 'to'";
elseif from == to then
@@ -875,9 +879,9 @@
local print = self.session.print;
local i = 0;
local type;
- for host in values(array.collect(keys(prosody.hosts)):sort()) do
+ for host, host_session in iterators.sorted_pairs(prosody.hosts) do
i = i + 1;
- type = hosts[host].type;
+ type = host_session.type;
if type == "local" then
print(host);
else
@@ -896,14 +900,11 @@
function def_env.port:list()
local print = self.session.print;
local services = portmanager.get_active_services().data;
- local ordered_services, n_ports = {}, 0;
- for service, interfaces in pairs(services) do
- table.insert(ordered_services, service);
- end
- table.sort(ordered_services);
- for _, service in ipairs(ordered_services) do
+ local n_services, n_ports = 0, 0;
+ for service, interfaces in iterators.sorted_pairs(services) do
+ n_services = n_services + 1;
local ports_list = {};
- for interface, ports in pairs(services[service]) do
+ for interface, ports in pairs(interfaces) do
for port in pairs(ports) do
table.insert(ports_list, "["..interface.."]:"..port);
end
@@ -911,14 +912,14 @@
n_ports = n_ports + #ports_list;
print(service..": "..table.concat(ports_list, ", "));
end
- return true, #ordered_services.." services listening on "..n_ports.." ports";
+ return true, n_services.." services listening on "..n_ports.." ports";
end
function def_env.port:close(close_port, close_interface)
close_port = assert(tonumber(close_port), "Invalid port number");
local n_closed = 0;
local services = portmanager.get_active_services().data;
- for service, interfaces in pairs(services) do
+ for service, interfaces in pairs(services) do -- luacheck: ignore 213
for interface, ports in pairs(interfaces) do
if not close_interface or close_interface == interface then
if ports[close_port] then
@@ -947,22 +948,23 @@
local function check_muc(jid)
local room_name, host = jid_split(jid);
- if not hosts[host] then
+ if not prosody.hosts[host] then
return nil, "No such host: "..host;
- elseif not hosts[host].modules.muc then
+ elseif not prosody.hosts[host].modules.muc then
return nil, "Host '"..host.."' is not a MUC service";
end
return room_name, host;
end
-function def_env.muc:create(room_jid)
+function def_env.muc:create(room_jid, config)
local room_name, host = check_muc(room_jid);
if not room_name then
return room_name, host;
end
if not room_name then return nil, host end
- if hosts[host].modules.muc.rooms[room_jid] then return nil, "Room exists already" end
- return hosts[host].modules.muc.create_room(room_jid);
+ if config ~= nil and type(config) ~= "table" then return nil, "Config must be a table"; end
+ if prosody.hosts[host].modules.muc.get_room_from_jid(room_jid) then return nil, "Room exists already" end
+ return prosody.hosts[host].modules.muc.create_room(room_jid, config);
end
function def_env.muc:room(room_jid)
@@ -970,7 +972,7 @@
if not room_name then
return room_name, host;
end
- local room_obj = hosts[host].modules.muc.rooms[room_jid];
+ local room_obj = prosody.hosts[host].modules.muc.get_room_from_jid(room_jid);
if not room_obj then
return nil, "No such room: "..room_jid;
end
@@ -978,14 +980,14 @@
end
function def_env.muc:list(host)
- local host_session = hosts[host];
+ local host_session = prosody.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);
+ for room in host_session.modules.muc.each_room() do
+ print(room.jid);
c = c + 1;
end
return true, c.." rooms";
@@ -996,7 +998,7 @@
def_env.user = {};
function def_env.user:create(jid, password)
local username, host = jid_split(jid);
- if not hosts[host] then
+ if not prosody.hosts[host] then
return nil, "No such host: "..host;
elseif um.user_exists(username, host) then
return nil, "User exists";
@@ -1011,7 +1013,7 @@
function def_env.user:delete(jid)
local username, host = jid_split(jid);
- if not hosts[host] then
+ if not prosody.hosts[host] then
return nil, "No such host: "..host;
elseif not um.user_exists(username, host) then
return nil, "No such user";
@@ -1026,7 +1028,7 @@
function def_env.user:password(jid, password)
local username, host = jid_split(jid);
- if not hosts[host] then
+ if not prosody.hosts[host] then
return nil, "No such host: "..host;
elseif not um.user_exists(username, host) then
return nil, "No such user";
@@ -1042,7 +1044,7 @@
function def_env.user:list(host, pat)
if not host then
return nil, "No host given";
- elseif not hosts[host] then
+ elseif not prosody.hosts[host] then
return nil, "No such host";
end
local print = self.session.print;
@@ -1061,9 +1063,9 @@
local st = require "util.stanza";
function def_env.xmpp:ping(localhost, remotehost)
- if hosts[localhost] then
+ if prosody.hosts[localhost] then
module:send(st.iq{ from=localhost, to=remotehost, type="get", id="ping" }
- :tag("ping", {xmlns="urn:xmpp:ping"}), hosts[localhost]);
+ :tag("ping", {xmlns="urn:xmpp:ping"}), prosody.hosts[localhost]);
return true, "Sent ping";
else
return nil, "No such host";
@@ -1141,22 +1143,374 @@
function def_env.debug:events(host, event)
local events_obj;
if host and host ~= "*" then
- if not hosts[host] then
+ if host == "http" then
+ events_obj = require "net.http.server"._events;
+ elseif not prosody.hosts[host] then
return false, "Unknown host: "..host;
+ else
+ events_obj = prosody.hosts[host].events;
end
- events_obj = hosts[host].events;
else
events_obj = prosody.events;
end
return true, helpers.show_events(events_obj, event);
end
+function def_env.debug:timers()
+ local socket = require "socket";
+ local print = self.session.print;
+ local add_task = require"util.timer".add_task;
+ local h, params = add_task.h, add_task.params;
+ if h then
+ print("-- util.timer");
+ for i, id in ipairs(h.ids) do
+ if not params[id] then
+ print(os.date("%F %T", h.priorities[i]), h.items[id]);
+ elseif not params[id].callback then
+ print(os.date("%F %T", h.priorities[i]), h.items[id], unpack(params[id]));
+ else
+ print(os.date("%F %T", h.priorities[i]), params[id].callback, unpack(params[id]));
+ end
+ end
+ end
+ if server.event_base then
+ local count = 0;
+ for _, v in pairs(debug.getregistry()) do
+ if type(v) == "function" and v.callback and v.callback == add_task._on_timer then
+ count = count + 1;
+ end
+ end
+ print(count .. " libevent callbacks");
+ end
+ if h then
+ local next_time = h:peek();
+ if next_time then
+ return true, os.date("Next event at %F %T (in %%.6fs)", next_time):format(next_time - socket.gettime());
+ end
+ end
+ return true;
+end
+
+-- COMPAT: debug:timers() was timer:info() for some time in trunk
+def_env.timer = { info = def_env.debug.timers };
+
module:hook("server-stopping", function(event)
- for conn, session in pairs(sessions) do
+ for _, session in pairs(sessions) do
session.print("Shutting down: "..(event.reason or "unknown reason"));
end
end);
+def_env.stats = {};
+
+local function format_stat(type, value, ref_value)
+ ref_value = ref_value or value;
+ --do return tostring(value) end
+ if type == "duration" then
+ if ref_value < 0.001 then
+ return ("%d µs"):format(value*1000000);
+ elseif ref_value < 0.9 then
+ return ("%0.2f ms"):format(value*1000);
+ end
+ return ("%0.2f"):format(value);
+ elseif type == "size" then
+ if ref_value > 1048576 then
+ return ("%d MB"):format(value/1048576);
+ elseif ref_value > 1024 then
+ return ("%d KB"):format(value/1024);
+ end
+ return ("%d bytes"):format(value);
+ elseif type == "rate" then
+ if ref_value < 0.9 then
+ return ("%0.2f/min"):format(value*60);
+ end
+ return ("%0.2f/sec"):format(value);
+ end
+ return tostring(value);
+end
+
+local stats_methods = {};
+function stats_methods:bounds(_lower, _upper)
+ for _, stat_info in ipairs(self) do
+ local data = stat_info[4];
+ if data then
+ local lower = _lower or data.min;
+ local upper = _upper or data.max;
+ local new_data = {
+ min = lower;
+ max = upper;
+ samples = {};
+ sample_count = 0;
+ count = data.count;
+ units = data.units;
+ };
+ local sum = 0;
+ for _, v in ipairs(data.samples) do
+ if v > upper then
+ break;
+ elseif v>=lower then
+ table.insert(new_data.samples, v);
+ sum = sum + v;
+ end
+ end
+ new_data.sample_count = #new_data.samples;
+ stat_info[4] = new_data;
+ stat_info[3] = sum/new_data.sample_count;
+ end
+ end
+ return self;
+end
+
+function stats_methods:trim(lower, upper)
+ upper = upper or (100-lower);
+ local statistics = require "util.statistics";
+ for _, stat_info in ipairs(self) do
+ -- Strip outliers
+ local data = stat_info[4];
+ if data then
+ local new_data = {
+ min = statistics.get_percentile(data, lower);
+ max = statistics.get_percentile(data, upper);
+ samples = {};
+ sample_count = 0;
+ count = data.count;
+ units = data.units;
+ };
+ local sum = 0;
+ for _, v in ipairs(data.samples) do
+ if v > new_data.max then
+ break;
+ elseif v>=new_data.min then
+ table.insert(new_data.samples, v);
+ sum = sum + v;
+ end
+ end
+ new_data.sample_count = #new_data.samples;
+ stat_info[4] = new_data;
+ stat_info[3] = sum/new_data.sample_count;
+ end
+ end
+ return self;
+end
+
+function stats_methods:max(upper)
+ return self:bounds(nil, upper);
+end
+
+function stats_methods:min(lower)
+ return self:bounds(lower, nil);
+end
+
+function stats_methods:summary()
+ local statistics = require "util.statistics";
+ for _, stat_info in ipairs(self) do
+ local type, value, data = stat_info[2], stat_info[3], stat_info[4];
+ if data and data.samples then
+ table.insert(stat_info.output, string.format("Count: %d (%d captured)",
+ data.count,
+ data.sample_count
+ ));
+ table.insert(stat_info.output, string.format("Min: %s Mean: %s Max: %s",
+ format_stat(type, data.min),
+ format_stat(type, value),
+ format_stat(type, data.max)
+ ));
+ table.insert(stat_info.output, string.format("Q1: %s Median: %s Q3: %s",
+ format_stat(type, statistics.get_percentile(data, 25)),
+ format_stat(type, statistics.get_percentile(data, 50)),
+ format_stat(type, statistics.get_percentile(data, 75))
+ ));
+ end
+ end
+ return self;
+end
+
+function stats_methods:cfgraph()
+ for _, stat_info in ipairs(self) do
+ local name, type, value, data = unpack(stat_info, 1, 4);
+ local function print(s)
+ table.insert(stat_info.output, s);
+ end
+
+ if data and data.sample_count and data.sample_count > 0 then
+ local raw_histogram = require "util.statistics".get_histogram(data);
+
+ local graph_width, graph_height = 50, 10;
+ local eighth_chars = " ▁▂▃▄▅▆▇█";
+
+ local range = data.max - data.min;
+
+ if range > 0 then
+ local x_scaling = #raw_histogram/graph_width;
+ local histogram = {};
+ for i = 1, graph_width do
+ histogram[i] = math.max(raw_histogram[i*x_scaling-1] or 0, raw_histogram[i*x_scaling] or 0);
+ end
+
+ print("");
+ print(("_"):rep(52)..format_stat(type, data.max));
+ for row = graph_height, 1, -1 do
+ local row_chars = {};
+ local min_eighths, max_eighths = 8, 0;
+ for i = 1, #histogram do
+ local char_eighths = math.ceil(math.max(math.min((graph_height/(data.max/histogram[i]))-(row-1), 1), 0)*8);
+ if char_eighths < min_eighths then
+ min_eighths = char_eighths;
+ end
+ if char_eighths > max_eighths then
+ max_eighths = char_eighths;
+ end
+ if char_eighths == 0 then
+ row_chars[i] = "-";
+ else
+ local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3);
+ row_chars[i] = char;
+ end
+ end
+ print(table.concat(row_chars).."|-"..format_stat(type, data.max/(graph_height/(row-0.5))));
+ end
+ print(("\\ "):rep(11));
+ local x_labels = {};
+ for i = 1, 11 do
+ local s = ("%-4s"):format((i-1)*10);
+ if #s > 4 then
+ s = s:sub(1, 3).."…";
+ end
+ x_labels[i] = s;
+ end
+ print(" "..table.concat(x_labels, " "));
+ local units = "%";
+ local margin = math.floor((graph_width-#units)/2);
+ print((" "):rep(margin)..units);
+ else
+ print("[range too small to graph]");
+ end
+ print("");
+ end
+ end
+ return self;
+end
+
+function stats_methods:histogram()
+ for _, stat_info in ipairs(self) do
+ local name, type, value, data = unpack(stat_info, 1, 4);
+ local function print(s)
+ table.insert(stat_info.output, s);
+ end
+
+ if not data then
+ print("[no data]");
+ return self;
+ elseif not data.sample_count then
+ print("[not a sampled metric type]");
+ return self;
+ end
+
+ local graph_width, graph_height = 50, 10;
+ local eighth_chars = " ▁▂▃▄▅▆▇█";
+
+ local range = data.max - data.min;
+
+ if range > 0 then
+ local n_buckets = graph_width;
+
+ local histogram = {};
+ for i = 1, n_buckets do
+ histogram[i] = 0;
+ end
+ local max_bin_samples = 0;
+ for _, d in ipairs(data.samples) do
+ local bucket = math.floor(1+(n_buckets-1)/(range/(d-data.min)));
+ histogram[bucket] = histogram[bucket] + 1;
+ if histogram[bucket] > max_bin_samples then
+ max_bin_samples = histogram[bucket];
+ end
+ end
+
+ print("");
+ print(("_"):rep(52)..max_bin_samples);
+ for row = graph_height, 1, -1 do
+ local row_chars = {};
+ local min_eighths, max_eighths = 8, 0;
+ for i = 1, #histogram do
+ local char_eighths = math.ceil(math.max(math.min((graph_height/(max_bin_samples/histogram[i]))-(row-1), 1), 0)*8);
+ if char_eighths < min_eighths then
+ min_eighths = char_eighths;
+ end
+ if char_eighths > max_eighths then
+ max_eighths = char_eighths;
+ end
+ if char_eighths == 0 then
+ row_chars[i] = "-";
+ else
+ local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3);
+ row_chars[i] = char;
+ end
+ end
+ print(table.concat(row_chars).."|-"..math.ceil((max_bin_samples/graph_height)*(row-0.5)));
+ end
+ print(("\\ "):rep(11));
+ local x_labels = {};
+ for i = 1, 11 do
+ local s = ("%-4s"):format(format_stat(type, data.min+range*i/11, data.min):match("^%S+"));
+ if #s > 4 then
+ s = s:sub(1, 3).."…";
+ end
+ x_labels[i] = s;
+ end
+ print(" "..table.concat(x_labels, " "));
+ local units = format_stat(type, data.min):match("%s+(.+)$") or data.units or "";
+ local margin = math.floor((graph_width-#units)/2);
+ print((" "):rep(margin)..units);
+ else
+ print("[range too small to graph]");
+ end
+ print("");
+ end
+ return self;
+end
+
+local function stats_tostring(stats)
+ local print = stats.session.print;
+ for _, stat_info in ipairs(stats) do
+ if #stat_info.output > 0 then
+ print("\n#"..stat_info[1]);
+ print("");
+ for _, v in ipairs(stat_info.output) do
+ print(v);
+ end
+ print("");
+ else
+ print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], stat_info[3])));
+ end
+ end
+ return #stats.." statistics displayed";
+end
+
+local stats_mt = {__index = stats_methods, __tostring = stats_tostring }
+local function new_stats_context(self)
+ return setmetatable({ session = self.session, stats = true }, stats_mt);
+end
+
+function def_env.stats:show(filter)
+ local stats, changed, extra = require "core.statsmanager".get_stats();
+ local available, displayed = 0, 0;
+ local displayed_stats = new_stats_context(self);
+ for name, value in pairs(stats) do
+ available = available + 1;
+ if not filter or name:match(filter) then
+ displayed = displayed + 1;
+ local type = name:match(":(%a+)$");
+ table.insert(displayed_stats, {
+ name, type, value, extra[name];
+ output = {};
+ });
+ end
+ end
+ return displayed_stats;
+end
+
+
+
-------------
function printbanner(session)
@@ -1175,7 +1529,7 @@
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");
+ session.print("https://prosody.im/doc/console\n");
end
if option ~= "short" and option ~= "full" and option ~= "graphic" then
session.print(option);
diff -r bb8486491b48 -r cc642c9c5ad5 plugins/mod_announce.lua
--- a/plugins/mod_announce.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/plugins/mod_announce.lua Mon Nov 26 19:48:07 2018 +0100
@@ -37,7 +37,7 @@
-- Old -based jabberd-style announcement sending
function handle_announcement(event)
- local origin, stanza = event.origin, event.stanza;
+ local stanza = event.stanza;
local node, host, resource = jid.split(stanza.attr.to);
if resource ~= "announce/online" then
@@ -72,7 +72,7 @@
{ name = "announcement", type = "text-multi", required = true, label = "Announcement" };
};
-function announce_handler(self, data, state)
+function announce_handler(_, data, state)
if state then
if data.action == "cancel" then
return { status = "canceled" };
@@ -91,10 +91,9 @@
else
return { status = "executing", actions = {"next", "complete", default = "complete"}, form = announce_layout }, "executing";
end
-
- return true;
end
+module:depends "adhoc";
local adhoc_new = module:require "adhoc".new;
local announce_desc = adhoc_new("Send Announcement to Online Users", "http://jabber.org/protocol/admin#announce", announce_handler, "admin");
module:provides("adhoc", announce_desc);
diff -r bb8486491b48 -r cc642c9c5ad5 plugins/mod_auth_insecure.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_auth_insecure.lua Mon Nov 26 19:48:07 2018 +0100
@@ -0,0 +1,53 @@
+-- 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 212
+
+local datamanager = require "util.datamanager";
+local new_sasl = require "util.sasl".new;
+
+local host = module.host;
+local provider = { name = "insecure" };
+
+assert(module:get_option_string("insecure_open_authentication") == "Yes please, I know what I'm doing!");
+
+function provider.test_password(username, password)
+ return true;
+end
+
+function provider.set_password(username, password)
+ local account = datamanager.load(username, host, "accounts");
+ if account then
+ account.password = password;
+ return datamanager.store(username, host, "accounts", account);
+ end
+ return nil, "Account not available.";
+end
+
+function provider.user_exists(username)
+ return true;
+end
+
+function provider.create_user(username, password)
+ return datamanager.store(username, host, "accounts", {password = password});
+end
+
+function provider.delete_user(username)
+ return datamanager.store(username, host, "accounts", nil);
+end
+
+function provider.get_sasl_handler()
+ local getpass_authentication_profile = {
+ plain_test = function(sasl, username, password, realm)
+ return true, true;
+ end
+ };
+ return new_sasl(module.host, getpass_authentication_profile);
+end
+
+module:add_item("auth-provider", provider);
+
diff -r bb8486491b48 -r cc642c9c5ad5 plugins/mod_blocklist.lua
--- a/plugins/mod_blocklist.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/plugins/mod_blocklist.lua Mon Nov 26 19:48:07 2018 +0100
@@ -114,12 +114,14 @@
-- 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 now = os.time();
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 is_blocking = action.name == "block" and now 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
@@ -158,15 +160,15 @@
local new_blocklist = {
-- We set the [false] key to someting as a signal not to migrate privacy lists
- [false] = blocklist[false] or { created = os.time(); };
+ [false] = blocklist[false] or { created = now; };
};
if type(blocklist[false]) == "table" then
- new_blocklist[false].modified = os.time();
+ new_blocklist[false].modified = now;
end
if is_blocking or next(new) then
- for jid in pairs(blocklist) do
- if jid then new_blocklist[jid] = true; end
+ for jid, t in pairs(blocklist) do
+ if jid then new_blocklist[jid] = t; end
end
for jid in pairs(new) do
new_blocklist[jid] = is_blocking;
diff -r bb8486491b48 -r cc642c9c5ad5 plugins/mod_bosh.lua
--- a/plugins/mod_bosh.lua Sun Nov 25 13:16:17 2018 +0100
+++ b/plugins/mod_bosh.lua Mon Nov 26 19:48:07 2018 +0100
@@ -6,9 +6,8 @@
-- COPYING file in the source package for more information.
--
-module:set_global(); -- Global module
+module:set_global();
-local hosts = _G.hosts;
local new_xmpp_stream = require "util.xmppstream".new;
local sm = require "core.sessionmanager";
local sm_destroy_session = sm.destroy_session;
@@ -16,12 +15,14 @@
local core_process_stanza = prosody.core_process_stanza;
local st = require "util.stanza";
local logger = require "util.logger";
-local log = logger.init("mod_bosh");
+local log = module._log;
local initialize_filters = require "util.filters".initialize;
local math_min = math.min;
-local xpcall, tostring, type = xpcall, tostring, type;
+local tostring, type = tostring, type;
local traceback = debug.traceback;
+local runner = require"util.async".runner;
local nameprep = require "util.encodings".stringprep.nameprep;
+local cache = require "util.cache";
local xmlns_streams = "http://etherx.jabber.org/streams";
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
@@ -48,33 +49,14 @@
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", "::1" })._items;
-
-local function get_ip_from_request(request)
- local ip = request.conn:ip();
- local forwarded_for = request.headers.x_forwarded_for;
- if forwarded_for then
- forwarded_for = forwarded_for..", "..ip;
- for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do
- if not trusted_proxies[forwarded_ip] then
- ip = forwarded_ip;
- end
- end
- end
- return ip;
-end
-
local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
-local os_time = os.time;
-- All sessions, and sessions that have no requests open
-local sessions, inactive_sessions = module:shared("sessions", "inactive_sessions");
+local sessions = module:shared("sessions");
-- Used to respond to idle sessions (those with 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;
local session = sessions[request.context.sid];
if session then
local requests = session.requests;
@@ -88,9 +70,24 @@
-- 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
- inactive_sessions[session] = os_time() + max_inactive;
+ if session.inactive_timer then
+ session.inactive_timer:stop();
+ end
+ session.inactive_timer = module:add_timer(max_inactive, check_inactive, session, request.context,
+ "BOSH client silent for over "..max_inactive.." seconds");
(session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive);
end
+ if session.bosh_wait_timer then
+ session.bosh_wait_timer:stop();
+ session.bosh_wait_timer = nil;
+ end
+ end
+end
+
+function check_inactive(now, session, context, reason) -- luacheck: ignore 212/now
+ if not session.destroyed then
+ sessions[context.sid] = nil;
+ sm_destroy_session(session, reason);
end
end
@@ -124,7 +121,7 @@
local headers = response.headers;
headers.content_type = "text/xml; charset=utf-8";
- if cross_domain and event.request.headers.origin then
+ if cross_domain and request.headers.origin then
set_cross_domain_headers(response);
end
@@ -148,8 +145,14 @@
if session then
-- Session was marked as inactive, since we have
-- a request open now, unmark it
- if inactive_sessions[session] and #session.requests > 0 then
- inactive_sessions[session] = nil;
+ if session.inactive_timer and #session.requests > 0 then
+ session.inactive_timer:stop();
+ session.inactive_timer = nil;
+ end
+
+ if session.bosh_wait_timer then
+ session.bosh_wait_timer:stop();
+ session.bosh_wait_timer = nil;
end
local r = session.requests;
@@ -177,9 +180,6 @@
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");
- if session.bosh_wait then
- waiting_requests[response] = os_time() + session.bosh_wait;
- end
end
if session.bosh_terminate then
@@ -187,10 +187,22 @@
session:close();
return nil;
else
+ if session.bosh_wait and #session.requests > 0 then
+ session.bosh_wait_timer = module:add_timer(session.bosh_wait, after_bosh_wait, session.requests[1], session)
+ end
+
return true; -- Inform http server we shall reply later
end
- elseif response.finished then
- return; -- A response has been sent already
+ elseif response.finished or context.ignore_request then
+ if response.finished then
+ module:log("debug", "Response finished");
+ end
+ if context.ignore_request then
+ module:log("debug", "Ignoring this request");
+ end
+ -- A response has been sent already, or we're ignoring this request
+ -- (e.g. so a different instance of the module can handle it)
+ return;
end
module:log("warn", "Unable to associate request with a session (incomplete request?)");
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
@@ -198,13 +210,17 @@
return tostring(close_reply) .. "\n";
end
+function after_bosh_wait(now, request, session) -- luacheck: ignore 212
+ if request.conn then
+ session.send("");
+ end
+end
local function bosh_reset_stream(session) session.notopen = true; end
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");
+ (session.log or log)("info", "BOSH client disconnected: %s", tostring((reason and reason.condition or reason) or "session close"));
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams });
@@ -237,21 +253,22 @@
held_request:send(response_body);
end
sessions[session.sid] = nil;
- inactive_sessions[session] = nil;
sm_destroy_session(session);
end
+local runner_callbacks = { };
+
-- Handle the tag in the request payload.
function stream_callbacks.streamopened(context, attr)
local request, response = context.request, context.response;
- local sid = attr.sid;
+ local sid, rid = attr.sid, tonumber(attr.rid);
log("debug", "BOSH body open (sid: %s)", sid or "");
+ context.rid = rid;
if not sid then
-- New session request
context.notopen = nil; -- Signals that we accept this opening tag
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));
@@ -259,13 +276,6 @@
["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",
- ["xmlns:stream"] = xmlns_streams, condition = "host-unknown" });
- 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));
@@ -275,28 +285,32 @@
return;
end
- rid = rid - 1;
wait = math_min(wait, bosh_max_wait);
-- New session
sid = new_uuid();
local session = {
- type = "c2s_unauthed", conn = request.conn, sid = sid, rid = rid, host = to_host,
+ type = "c2s_unauthed", conn = request.conn, sid = sid, host = attr.to,
+ rid = rid - 1, -- Hack for initial session setup, "previous" rid was $current_request - 1
bosh_version = attr.ver, bosh_wait = wait, streamid = sid,
- bosh_max_inactive = bosh_max_inactivity,
+ bosh_max_inactive = bosh_max_inactivity, bosh_responses = cache.new(BOSH_HOLD+1):table();
requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream,
close = bosh_close_stream, dispatch_stanza = core_process_stanza, notopen = true,
log = logger.init("bosh"..sid), secure = consider_bosh_secure or request.secure,
- ip = get_ip_from_request(request);
+ ip = request.ip;
};
sessions[sid] = session;
+ session.thread = runner(function (stanza)
+ session:dispatch_stanza(stanza);
+ end, runner_callbacks, session);
+
local filter = initialize_filters(session);
session.log("debug", "BOSH session created for request from %s", session.ip);
log("info", "New BOSH session, assigned it sid '%s'", sid);
- hosts[session.host].events.fire_event("bosh-session", { session = session, request = request });
+ module:fire_event("bosh-session", { session = session, request = request });
-- Send creation response
local creating_session = true;
@@ -335,8 +349,9 @@
body_attr["xmlns:xmpp"] = "urn:xmpp:xbosh";
body_attr["xmpp:version"] = "1.0";
end
- session.bosh_last_response = st.stanza("body", body_attr):top_tag()..t_concat(session.send_buffer).."";
- oldest_request:send(session.bosh_last_response);
+ local response_xml = st.stanza("body", body_attr):top_tag()..t_concat(session.send_buffer).."
It works! Now point your BOSH client to this URL to connect to Prosody.
- For more information see Prosody: Setting up BOSH.
+ For more information see Prosody: Setting up BOSH.