./PaxHeaders/prosody-0.12.40000644000000000000000000000013214475665104012447 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.604765815 30 ctime=1693936196.840763501 prosody-0.12.4/0000755000175000017500000000000014475665104014571 5ustar00prosodyprosody00000000000000prosody-0.12.4/PaxHeaders/.busted0000644000000000000000000000012014475665104013653 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.608765776 prosody-0.12.4/.busted0000644000175000017500000000025614475665104016063 0ustar00prosodyprosody00000000000000return { _all = { }, default = { ["exclude-tags"] = "mod_bosh,storage,SLOW"; }; bosh = { tags = "mod_bosh"; }; storage = { tags = "storage"; }; } prosody-0.12.4/PaxHeaders/.editorconfig0000644000000000000000000000012014475665104015041 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.608765776 prosody-0.12.4/.editorconfig0000644000175000017500000000040214475665104017242 0ustar00prosodyprosody00000000000000root = true [*] end_of_line = lf indent_style = tab insert_final_newline = true trim_trailing_whitespace = true [CHANGES] indent_size = 4 indent_style = space [configure] indent_size = 3 indent_style = space [**.xml] indent_size = 2 indent_style = space prosody-0.12.4/PaxHeaders/.hg_archival.txt0000644000000000000000000000012014475665104015452 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.604765815 prosody-0.12.4/.hg_archival.txt0000644000175000017500000000016714475665104017663 0ustar00prosodyprosody00000000000000repo: 3e3171b59028ee70122cfec6ecf98f518f946b59 node: a2ba3f06dcf44b713b65355351653826cccea59c branch: 0.12 tag: 0.12.4 prosody-0.12.4/PaxHeaders/.luacheckrc0000644000000000000000000000012014475665104014471 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.608765776 prosody-0.12.4/.luacheckrc0000644000175000017500000000721414475665104016702 0ustar00prosodyprosody00000000000000cache = true codes = true ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV", "431/log", "214", "581" } std = "lua53c" max_line_length = 150 read_globals = { "prosody", "import", }; files["prosody"] = { allow_defined_top = true; module = true; globals = { "prosody"; } } files["prosodyctl"] = { allow_defined_top = true; module = true; }; files["core/"] = { globals = { "prosody.hosts.?", }; } files["util/"] = { -- Ignore unwrapped license text max_comment_line_length = false; } files["util/jsonschema.lua"] = { ignore = { "211" }; } files["util/datamapper.lua"] = { ignore = { "211" }; } files["plugins/"] = { module = true; allow_defined_top = true; read_globals = { -- Module instance "module.name", "module.host", "module._log", "module.event_handlers", "module.reloading", "module.saved_state", "module.global", "module.path", "module.items", -- Module API "module.add_extension", "module.add_feature", "module.add_identity", "module.add_item", "module.add_timer", "module.weekly", "module.daily", "module.hourly", "module.broadcast", "module.context", "module.depends", "module.fire_event", "module.get_directory", "module.get_host", "module.get_host_items", "module.get_host_type", "module.get_name", "module.get_option", "module.get_option_array", "module.get_option_boolean", "module.get_option_inherited_set", "module.get_option_number", "module.get_option_path", "module.get_option_scalar", "module.get_option_set", "module.get_option_string", "module.get_status", "module.handle_items", "module.hook", "module.hook_global", "module.hook_object_event", "module.hook_tag", "module.load_resource", "module.log", "module.log_status", "module.measure", "module.metric", "module.open_store", "module.provides", "module.remove_item", "module.require", "module.send", "module.send_iq", "module.set_global", "module.set_status", "module.shared", "module.unhook", "module.unhook_object_event", "module.wrap_event", "module.wrap_global", "module.wrap_object_event", -- mod_http API "module.http_url", }; globals = { -- Methods that can be set on module API "module.unload", "module.add_host", "module.load", "module.add_host", "module.save", "module.restore", "module.command", "module.environment", }; } files["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! local exclude_files = { "doc/net.server.lua"; "fallbacks/bit.lua"; "fallbacks/lxp.lua"; "net/dns.lua"; "net/server_select.lua"; "util/vcard.lua"; "plugins/mod_storage_sql1.lua"; "spec/core_moduleapi_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"; "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"; } for _, file in ipairs(exclude_files) do files[file] = { only = {} } end end prosody-0.12.4/PaxHeaders/.luacov0000644000000000000000000000012014475665104013656 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/.luacov0000644000175000017500000000003414475665104016060 0ustar00prosodyprosody00000000000000exclude = { "^%./spec/"; } prosody-0.12.4/PaxHeaders/.semgrep.yml0000644000000000000000000000012014475665104014627 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/.semgrep.yml0000644000175000017500000000143414475665104017036 0ustar00prosodyprosody00000000000000rules: - id: log-variable-fmtstring patterns: - pattern: log("...", $A) - pattern-not: log("...", "...") message: Variable passed as format string to logging languages: [lua] severity: ERROR - id: module-log-variable-fmtstring patterns: - pattern: module:log("...", $A) - pattern-not: module:log("...", "...") message: Variable passed as format string to logging languages: [lua] severity: ERROR - id: module-getopt-string-default patterns: - pattern: module:get_option_string("...", $A) - pattern-not: module:get_option_string("...", "...") - pattern-not: module:get_option_string("...", host) - pattern-not: module:get_option_string("...", module.host) message: Non-string default from :get_option_string severity: ERROR languages: [lua] prosody-0.12.4/PaxHeaders/AUTHORS0000644000000000000000000000012014475665104013434 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/AUTHORS0000644000175000017500000000056714475665104015651 0ustar00prosodyprosody00000000000000 The Prosody project is open to contributions (see HACKERS file), but is maintained daily by: - Matthew Wild (mail: matthew [at] prosody.im) - Waqas Hussain (mail: waqas [at] prosody.im) - Kim Alvefur (mail: zash [at] prosody.im) You can reach us collectively by email: developers [at] prosody.im or in realtime in the Prosody chatroom: prosody@conference.prosody.im prosody-0.12.4/PaxHeaders/CHANGES0000644000000000000000000000012014475665104013357 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/CHANGES0000644000175000017500000001013414475665104015563 0ustar00prosodyprosody000000000000000.12.0 ====== **2022-03-14** ## New ### Modules - mod_mimicking: Prevent address spoofing - mod_s2s_bidi: Bi-directional server-to-server (XEP-0288) - mod_external_services: generic XEP-0215 support - mod_turn_external: easy setup XEP-0215 for STUN+TURN - mod_http_file_share: File sharing via HTTP (XEP-0363) - mod_http_openmetrics for exposing metrics to stats collectors - mod_smacks: Stream management and resumption (XEP-0198) - mod_auth_ldap: LDAP authentication - mod_cron: One module to rule all the periodic tasks - mod_admin_shell: New home of the Console admin interface - mod_admin_socket: Enable secure connections to the Console - mod_tombstones: Prevent registration of deleted accounts - mod_invites: Create and manage invites - mod_invites_register: Create accounts using invites - mod_invites_adhoc: Create invites via AdHoc command - mod_bookmarks: Synchronise open rooms between clients ### Security and authentication - SNI support (including automatic certificate selection) - ALPN support in mod_net_multiplex - DANE support in low-level network layer - Direct TLS support (c2s and s2s) - SCRAM-SHA-256 - Direct TLS (including https) certificates updated on reload - Pluggable authorization providers (mod_authz_) - Easy use of Mozilla TLS recommendations presets - Unencrypted HTTP port (5280) restricted to loopback by default - require_encryption options default to 'true' if unspecified - Authentication module defaults to 'internal_hashed' if unspecified ### HTTP - CORS handling now provided by mod_http - Built-in HTTP server now handles HEAD requests - Uploads can be handled incrementally ### API - Module statuses (API change) - util.error for encapsulating errors - Promise based API for sending queries - API for adding periodic tasks - More APIs supporting ES6 Promises - Async can be used during shutdown ### Other - Plugin installer - MUC presence broadcast controls - MUC: support for XEP-0421 occupant identifiers - `prosodyctl check connectivity` via observe.jabber.network - STUN/TURN server tests in `prosodyctl check` - libunbound for DNS queries - The POSIX poll() API used by server_epoll on \*nix other than Linux ## Changes - Improved rules for mobile optimizations - Improved rules for what messages should be archived - mod_limits: Exempted JIDs - mod_server_contact_info now loaded on components if enabled - Statistics now based on OpenMetrics - Statistics scheduling can be done by plugin - Offline messages aren't sent to MAM clients - Archive quotas (means?) - Rewritten migrator with archive support - Improved automatic certificate locating and selecting - Logging to syslog no longer missing startup messages - Graceful shutdown sequence that closes ports first and waits for connections to close ## Removed - `daemonize` option deprecated - SASL DIGEST-MD5 removed - mod_auth_cyrus (older LDAP support) - Network backend server_select deprecated (not actually removed yet) 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 ====== **2017-10-02** New features ------------ - Rewritten SQL storage module with Archive support - SCRAM-SHA-1-PLUS - `prosodyctl check` - Statistics - Improved TLS configuration - Lua 5.2 support - mod\_blocklist (XEP-0191) - mod\_carbons (XEP-0280) - Pluggable connection timeout handling - mod\_websocket (RFC 7395) - mod\_mam (XEP-0313) Removed ------- - mod\_privacy (XEP-0016) - mod\_compression (XEP-0138) prosody-0.12.4/PaxHeaders/CONTRIBUTING0000644000000000000000000000012014475665104014216 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/CONTRIBUTING0000644000175000017500000000053314475665104016424 0ustar00prosodyprosody00000000000000Thanks for your interest in contributing to the project! There are many ways to contribute, such as helping improve the documentation, reporting bugs, spreading the word or testing the latest development version. You can find more information on how to contribute at See also the HACKERS and README files. prosody-0.12.4/PaxHeaders/COPYING0000644000000000000000000000012014475665104013417 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/COPYING0000644000175000017500000000336714475665104015635 0ustar00prosodyprosody00000000000000All source code in this project is released under the below MIT license. Some components are not authored by the Prosody maintainers, but such code is itself either released under a MIT license or declared public domain. --- Copyright (C) 2008-2022 Matthew Wild Copyright (C) 2008-2020 Waqas Hussain Copyright (C) 2010-2022 Kim Alvefur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- util-src/encodings.c: Parts included from Lua 5.3. Copyright (C) 1994-2015 Lua.org, PUC-Rio. util-src/signal.c: Copyright (C) 2007 Patrick J. Donnelly (batrick@batbytes.com) See full copyright notice in the source file. util-src/struct.c: Copyright (C) 2010-2018 Lua.org, PUC-Rio. All rights reserved. See full copyright notice in the source file. net/dns.lua: public domain 20080404 lua@ztact.com prosody-0.12.4/PaxHeaders/DEPENDS0000644000000000000000000000012014475665104013371 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/DEPENDS0000644000175000017500000000037714475665104015605 0ustar00prosodyprosody00000000000000 For full information on our dependencies, version requirements, and where to find them, see https://prosody.im/doc/depends If you have luarocks available on your platform, install the following: - luaexpat - luasocket - luafilesystem - luasec prosody-0.12.4/PaxHeaders/GNUmakefile0000644000000000000000000000012014475665104014436 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/GNUmakefile0000644000175000017500000001130414475665104016642 0ustar00prosodyprosody00000000000000 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 SCANSION=scansion .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-etc: prosody.cfg.lua.install $(MKDIR) $(CONFIG) $(MKDIR) $(CONFIG)/certs $(INSTALL_DATA) certs/* $(CONFIG)/certs test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua install-bin: prosody.install prosodyctl.install $(MKDIR) $(BIN) $(INSTALL_EXEC) ./prosody.install $(BIN)/prosody $(INSTALL_EXEC) ./prosodyctl.install $(BIN)/prosodyctl install-core: $(MKDIR) $(SOURCE) $(MKDIR) $(SOURCE)/core $(INSTALL_DATA) core/*.lua $(SOURCE)/core install-net: $(MKDIR) $(SOURCE) $(MKDIR) $(SOURCE)/net $(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-util: util/encodings.so util/encodings.so util/pposix.so util/signal.so util/struct.so $(MKDIR) $(SOURCE) $(MKDIR) $(SOURCE)/util $(INSTALL_DATA) util/*.lua $(SOURCE)/util $(MAKE) install -C util-src $(INSTALL_DATA) util/*.so $(SOURCE)/util $(MKDIR) $(SOURCE)/util/sasl $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl $(MKDIR) $(SOURCE)/util/human $(INSTALL_DATA) util/human/*.lua $(SOURCE)/util/human $(MKDIR) $(SOURCE)/util/prosodyctl $(INSTALL_DATA) util/prosodyctl/*.lua $(SOURCE)/util/prosodyctl install-plugins: $(MKDIR) $(MODULES) $(MKDIR) $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam $(INSTALL_DATA) plugins/*.lua $(MODULES) $(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-man: $(MKDIR) $(MAN)/man1 $(INSTALL_DATA) man/prosodyctl.man $(MAN)/man1/prosodyctl.1 install-meta: -test -f prosody.version && $(INSTALL_DATA) prosody.version $(SOURCE)/prosody.version install-data: $(MKDIR_PRIVATE) $(DATA) install: install-util install-net install-core install-plugins install-bin install-etc install-man install-meta install-data 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) test-%: $(BUSTED) --lua=$(RUNWITH) -r $* integration-test: all $(MKDIR) data $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua start $(SCANSION) -d ./spec/scansion; R=$$? \ $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua stop \ exit $$R integration-test-%: all $(MKDIR) data $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua start $(SCANSION) ./spec/scansion/$*.scs; R=$$? \ $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua stop \ exit $$R 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 vpath %.tl teal-src/ %.lua: %.tl tl -I teal-src/ --gen-compat off --gen-target 5.1 gen $^ -o $@ -lua-format -i $@ teal: util/jsonschema.lua util/datamapper.lua util/jsonpointer.lua 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 > $@ prosody-0.12.4/PaxHeaders/HACKERS0000644000000000000000000000012014475665104013367 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/HACKERS0000644000175000017500000000072714475665104015602 0ustar00prosodyprosody00000000000000Welcome hackers! 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 https://prosody.im/discuss Patches are welcome, though before sending we would appreciate if you read docs/coding_style.md for guidelines on how to format your code, and other tips. Documentation for developers can be found at https://prosody.im/doc/developers Have fun :) prosody-0.12.4/PaxHeaders/INSTALL0000644000000000000000000000012014475665104013415 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/INSTALL0000644000175000017500000000440714475665104015627 0ustar00prosodyprosody00000000000000(This file was created from https://prosody.im/doc/installing_from_source on 2013-03-31) # Installing from source ## Dependencies There are a couple of development packages which Prosody needs installed before you can build it. These are: - The [Lua](http://lua.org/) library, version 5.4 recommended - [OpenSSL](http://openssl.org/) - String processing library, one of - [ICU](https://icu.unicode.org/) (recommended) - [GNU libidn](http://www.gnu.org/software/libidn/) These can be installed on Debian/Ubuntu by running `apt build-dep prosody` or by installing the packages `liblua5.4-dev`, `libicu-dev` and `libssl-dev`. On Mandriva try: urpmi lua liblua-devel libidn-devel libopenssl-devel On Mac OS X, if you have MacPorts installed, you can try: sudo port install lua lua-luasocket lua-luasec lua-luaexpat On other systems... good luck, but please let us know of the best way of getting the dependencies for your system and we can add it here. ## configure The first step of building is to run the configure script. This creates a file called 'config.unix' which is used by the next step to control aspects of the build process. ./configure All options to configure can be seen by running ./configure --help ## make Once you have run configure successfully, then you can simply run: make Simple? :-) If you do happen to have problems at this stage, it is most likely due to the build process not finding the dependencies. Ensure you have them installed, and in the standard library paths for your system. For more help, just ask ;-) ==== install ==== At this stage you should be able to run Prosody simply with: ./prosody There is no problem with this, it is actually the easiest way to do development, as it doesn't spread parts around your system, and you can keep multiple versions around in their own directories without conflict. Should you wish to install it system-wide however, simply run: sudo make install ...it will install into /usr/local/ by default. To change this you can pass to the initial ./configure using the 'prefix' option, or edit config.unix directly. If the new path doesn't require root permission to write to, you also won't need (or want) to use 'sudo' in front of the 'make install'. Have fun, and see you on Jabber! prosody-0.12.4/PaxHeaders/README0000644000000000000000000000012014475665104013244 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.612765736 prosody-0.12.4/README0000644000175000017500000000175214475665104015456 0ustar00prosodyprosody00000000000000# Prosody IM Server ## Description Prosody is a server for Jabber/XMPP written in Lua. It aims to be easy to use and light on resources. For developers, it aims to give a flexible system on which to rapidly develop added functionality or rapidly prototype new protocols. ## Useful links Homepage: https://prosody.im/ Download: https://prosody.im/download Documentation: https://prosody.im/doc/ Issue tracker: https://issues.prosody.im/ Jabber/XMPP Chat: Address: prosody@conference.prosody.im Web interface: https://chat.prosody.im/ Mailing lists: User support and discussion: https://groups.google.com/group/prosody-users Development discussion: https://groups.google.com/group/prosody-dev ## Installation See the accompanying INSTALL file for help on building Prosody from source. Alternatively see our guide at https://prosody.im/doc/install prosody-0.12.4/PaxHeaders/TODO0000644000000000000000000000012014475665104013054 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.616765697 prosody-0.12.4/TODO0000644000175000017500000000007514475665104015263 0ustar00prosodyprosody00000000000000== 1.0 == - Roster providers - Clustering - World domination prosody-0.12.4/PaxHeaders/certs0000644000000000000000000000013214475665104013432 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.616765697 30 ctime=1693936196.616765697 prosody-0.12.4/certs/0000755000175000017500000000000014475665104015711 5ustar00prosodyprosody00000000000000prosody-0.12.4/certs/PaxHeaders/GNUmakefile0000644000000000000000000000012014475665104015556 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.616765697 prosody-0.12.4/certs/GNUmakefile0000644000175000017500000000354714475665104017774 0ustar00prosodyprosody00000000000000.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 $@ $* prosody-0.12.4/certs/PaxHeaders/localhost.cnf0000644000000000000000000000012014475665104016164 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.616765697 prosody-0.12.4/certs/localhost.cnf0000644000175000017500000000113714475665104020373 0ustar00prosodyprosody00000000000000[v3_extensions] basicConstraints = CA:TRUE subjectAltName = @subject_alternative_name [subject_alternative_name] DNS.0 = localhost otherName.0 = 1.3.6.1.5.5.7.8.7;IA5STRING:_xmpp-client.localhost otherName.1 = 1.3.6.1.5.5.7.8.7;IA5STRING:_xmpp-server.localhost otherName.2 = 1.3.6.1.5.5.7.8.5;FORMAT:UTF8,UTF8:localhost [distinguished_name] countryName = GB organizationName = Prosody IM organizationalUnitName = https://prosody.im/doc/certificates commonName = Example certificate [req] prompt = no x509_extensions = v3_extensions req_extensions = v3_extensions distinguished_name = distinguished_name prosody-0.12.4/certs/PaxHeaders/makefile0000644000000000000000000000012014475665104015204 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.616765697 prosody-0.12.4/certs/makefile0000644000175000017500000000131714475665104017413 0ustar00prosodyprosody00000000000000.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 prosody-0.12.4/certs/PaxHeaders/openssl.cnf0000644000000000000000000000012014475665104015657 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.616765697 prosody-0.12.4/certs/openssl.cnf0000644000175000017500000000306014475665104020063 0ustar00prosodyprosody00000000000000oid_section = new_oids [ new_oids ] # RFC 6120 section 13.7.1.4. defines this OID xmppAddr = 1.3.6.1.5.5.7.8.5 # RFC 4985 defines this OID SRVName = 1.3.6.1.5.5.7.8.7 [ req ] default_bits = 4096 default_keyfile = example.com.key distinguished_name = distinguished_name req_extensions = certrequest x509_extensions = selfsigned # ask about the DN? prompt = no [ distinguished_name ] commonName = example.com countryName = GB localityName = The Internet organizationName = Your Organisation organizationalUnitName = XMPP Department emailAddress = xmpp@example.com [ certrequest ] # for certificate requests (req_extensions) basicConstraints = CA:FALSE keyUsage = digitalSignature,keyEncipherment extendedKeyUsage = serverAuth,clientAuth subjectAltName = @subject_alternative_name [ selfsigned ] # and self-signed certificates (x509_extensions) basicConstraints = CA:TRUE subjectAltName = @subject_alternative_name [ subject_alternative_name ] # See http://tools.ietf.org/html/rfc6120#section-13.7.1.2 for more info. DNS.0 = example.com otherName.0 = xmppAddr;FORMAT:UTF8,UTF8:example.com otherName.1 = SRVName;IA5STRING:_xmpp-client.example.com otherName.2 = SRVName;IA5STRING:_xmpp-server.example.com DNS.1 = conference.example.com otherName.3 = xmppAddr;FORMAT:UTF8,UTF8:conference.example.com otherName.4 = SRVName;IA5STRING:_xmpp-server.conference.example.com prosody-0.12.4/PaxHeaders/configure0000644000000000000000000000012014475665104014270 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.616765697 prosody-0.12.4/configure0000755000175000017500000003624214475665104016507 0ustar00prosodyprosody00000000000000#!/bin/sh # Defaults APP_NAME="Prosody" APP_DIRNAME="prosody" PREFIX="/usr/local" SYSCONFDIR="$PREFIX/etc/$APP_DIRNAME" LIBDIR="$PREFIX/lib" DATADIR="$PREFIX/var/lib/$APP_DIRNAME" LUA_SUFFIX="" LUA_DIR="/usr" LUA_BINDIR="/usr/bin" LUA_INCDIR="/usr/include" LUA_LIBDIR="/usr/lib" IDN_LIB="idn" ICU_FLAGS="-licui18n -licudata -licuuc" OPENSSL_LIB="crypto" CC="gcc" LD="gcc" RUNWITH="lua" EXCERTS="yes" PRNG= PRNGLIBS= CFLAGS="-fPIC -std=c99" CFLAGS="$CFLAGS -Wall -pedantic -Wextra -Wshadow -Wformat=2" LDFLAGS="-shared" IDN_LIBRARY="icu" # Help show_help() { cat </dev/null) if [ -n "$prog" ] then dirname "$prog" fi } die() { echo "$*" echo echo "configure failed." echo exit 1 } # COMPAT SC2039 has been phased out, remove in the future # shellcheck disable=SC2039,SC3037 case $(echo -n x) in -n*) echo_n_flag='';; *) echo_n_flag='-n';; esac echo_n() { echo $echo_n_flag "$*" } # ---------------------------------------------------------------------------- # MAIN PROGRAM # ---------------------------------------------------------------------------- # Parse options while [ -n "$1" ] do value=$(echo "$1" | sed 's/[^=]*.\(.*\)/\1/') key=$(echo "$1" | sed 's/=.*//') # 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 case "$key" in --help) show_help exit 0 ;; --prefix) [ -n "$value" ] || die "Missing value in flag $key." PREFIX="$value" PREFIX_SET=yes ;; --sysconfdir) [ -n "$value" ] || die "Missing value in flag $key." SYSCONFDIR="$value" SYSCONFDIR_SET=yes ;; --ostype) OSPRESET="$value" OSPRESET_SET="yes" ;; --libdir) LIBDIR="$value" LIBDIR_SET=yes ;; --datadir) DATADIR="$value" DATADIR_SET=yes ;; --lua-suffix) [ -n "$value" ] || die "Missing value in flag $key." LUA_SUFFIX="$value" LUA_SUFFIX_SET=yes ;; --lua-version|--with-lua-version) [ -n "$value" ] || die "Missing value in flag $key." LUA_VERSION="$value" [ "$LUA_VERSION" = "5.1" ] || [ "$LUA_VERSION" = "5.2" ] || [ "$LUA_VERSION" = "5.3" ] || [ "$LUA_VERSION" = "5.4" ] || die "Invalid Lua version in flag $key." LUA_VERSION_SET=yes ;; --with-lua) [ -n "$value" ] || die "Missing value in flag $key." LUA_DIR="$value" LUA_DIR_SET=yes ;; --with-lua-bin) [ -n "$value" ] || die "Missing value in flag $key." LUA_BINDIR="$value" LUA_BINDIR_SET=yes ;; --with-lua-include) [ -n "$value" ] || die "Missing value in flag $key." LUA_INCDIR="$value" LUA_INCDIR_SET=yes ;; --with-lua-lib) [ -n "$value" ] || die "Missing value in flag $key." LUA_LIBDIR="$value" LUA_LIBDIR_SET=yes ;; --with-idn) IDN_LIB="$value" ;; --idn-library) IDN_LIBRARY="$value" ;; --with-ssl) OPENSSL_LIB="$value" ;; --with-random) case "$value" in getrandom) PRNG=GETRANDOM ;; openssl) PRNG=OPENSSL ;; arc4random) PRNG=ARC4RANDOM ;; esac ;; --cflags) CFLAGS="$value" ;; --add-cflags) CFLAGS="$CFLAGS $value" ;; --ldflags) LDFLAGS="$value" ;; --add-ldflags) LDFLAGS="$LDFLAGS $value" ;; --c-compiler) CC="$value" ;; --linker) LD="$value" ;; --runwith) RUNWITH="$value" RUNWITH_SET=yes ;; --no-example-certs) EXCERTS= ;; --compiler-wrapper) CC="$value $CC" LD="$value $LD" ;; *) die "Error: Unknown flag: $1" ;; esac shift done if [ "$OSPRESET_SET" = "yes" ]; then # TODO make this a switch? if [ "$OSPRESET" = "debian" ]; then CFLAGS="$CFLAGS -ggdb" fi if [ "$OSPRESET" = "macosx" ]; then if [ "$LUA_INCDIR_SET" != "yes" ]; then LUA_INCDIR=/usr/local/include; LUA_INCDIR_SET=yes fi if [ "$LUA_LIBDIR_SET" != "yes" ]; then LUA_LIBDIR=/usr/local/lib LUA_LIBDIR_SET=yes fi CFLAGS="$CFLAGS -mmacosx-version-min=10.3" LDFLAGS="-bundle -undefined dynamic_lookup" fi if [ "$OSPRESET" = "linux" ]; then CFLAGS="$CFLAGS -ggdb" fi if [ "$OSPRESET" = "freebsd" ] || [ "$OSPRESET" = "openbsd" ]; then LUA_INCDIR="/usr/local/include/lua51" LUA_INCDIR_SET=yes CFLAGS="-Wall -fPIC -I/usr/local/include" LDFLAGS="-I/usr/local/include -L/usr/local/lib -shared" LUA_SUFFIX="51" LUA_SUFFIX_SET=yes LUA_DIR=/usr/local LUA_DIR_SET=yes CC=cc LD=ld fi if [ "$OSPRESET" = "openbsd" ]; then LUA_INCDIR="/usr/local/include"; LUA_INCDIR_SET="yes" fi if [ "$OSPRESET" = "netbsd" ]; then LUA_INCDIR="/usr/pkg/include/lua-5.1" LUA_INCDIR_SET=yes LUA_LIBDIR="/usr/pkg/lib/lua/5.1" LUA_LIBDIR_SET=yes CFLAGS="-Wall -fPIC -I/usr/pkg/include" LDFLAGS="-L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib -shared" fi if [ "$OSPRESET" = "pkg-config" ]; then if [ "$LUA_SUFFIX_SET" != "yes" ]; then LUA_SUFFIX="5.1"; LUA_SUFFIX_SET=yes fi LUA_CF="$(pkg-config --cflags-only-I lua"$LUA_SUFFIX")" LUA_CF="${LUA_CF#*-I}" LUA_CF="${LUA_CF%% *}" if [ "$LUA_CF" != "" ]; then LUA_INCDIR="$LUA_CF" LUA_INCDIR_SET=yes fi fi fi if [ "$PREFIX_SET" = "yes" ] && [ ! "$SYSCONFDIR_SET" = "yes" ] then if [ "$PREFIX" = "/usr" ] then SYSCONFDIR=/etc/$APP_DIRNAME else SYSCONFDIR=$PREFIX/etc/$APP_DIRNAME fi fi if [ "$PREFIX_SET" = "yes" ] && [ ! "$DATADIR_SET" = "yes" ] then if [ "$PREFIX" = "/usr" ] then DATADIR=/var/lib/$APP_DIRNAME else DATADIR=$PREFIX/var/lib/$APP_DIRNAME fi fi if [ "$PREFIX_SET" = "yes" ] && [ ! "$LIBDIR_SET" = "yes" ] then LIBDIR=$PREFIX/lib fi detect_lua_version() { detected_lua=$("$1" -e 'print(_VERSION:match(" (5%.[1234])$"))' 2> /dev/null) if [ "$detected_lua" != "nil" ] then if [ "$LUA_VERSION_SET" != "yes" ] then echo "Lua version detected: $detected_lua" LUA_VERSION=$detected_lua return 0 elif [ "$LUA_VERSION" = "$detected_lua" ] then return 0 fi fi return 1 } search_interpreter() { suffix="$1" if [ "$LUA_BINDIR_SET" = "yes" ] then find_lua="$LUA_BINDIR" elif [ "$LUA_DIR_SET" = "yes" ] then LUA_BINDIR="$LUA_DIR/bin" if [ -f "$LUA_BINDIR/lua$suffix" ] then find_lua="$LUA_BINDIR" fi else find_lua=$(find_program lua"$suffix") fi if [ -n "$find_lua" ] && [ -x "$find_lua/lua$suffix" ] then if detect_lua_version "$find_lua/lua$suffix" then echo "Lua interpreter found: $find_lua/lua$suffix..." if [ "$LUA_BINDIR_SET" != "yes" ] then LUA_BINDIR="$find_lua" fi if [ "$LUA_DIR_SET" != "yes" ] then LUA_DIR=$(dirname "$find_lua") fi LUA_SUFFIX="$suffix" return 0 fi fi return 1 } lua_interp_found=no if [ "$LUA_SUFFIX_SET" != "yes" ] then if [ "$LUA_VERSION_SET" = "yes" ] && [ "$LUA_VERSION" = "5.1" ] then suffixes="5.1 51 -5.1 -51" elif [ "$LUA_VERSION_SET" = "yes" ] && [ "$LUA_VERSION" = "5.2" ] then suffixes="5.2 52 -5.2 -52" elif [ "$LUA_VERSION_SET" = "yes" ] && [ "$LUA_VERSION" = "5.3" ] then suffixes="5.3 53 -5.3 -53" elif [ "$LUA_VERSION_SET" = "yes" ] && [ "$LUA_VERSION" = "5.4" ] then suffixes="5.4 54 -5.4 -54" else suffixes="5.1 51 -5.1 -51" suffixes="$suffixes 5.2 52 -5.2 -52" suffixes="$suffixes 5.3 53 -5.3 -53" suffixes="$suffixes 5.4 54 -5.4 -54" fi for suffix in "" $suffixes do search_interpreter "$suffix" && { lua_interp_found=yes break } done else search_interpreter "$LUA_SUFFIX" && { lua_interp_found=yes } fi # See #1353 if [ "$LUA_DIR_SET" != "yes" ] && [ "$LUA_DIR" = "/" ] then LUA_DIR="/usr" fi if [ "$lua_interp_found" != "yes" ] && [ "$RUNWITH_SET" != "yes" ] then 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" ] && [ "$RUNWITH_SET" != "yes" ] then echo_n "Checking if $LUA_BINDIR/lua$LUA_SUFFIX is Lua version $LUA_VERSION... " if detect_lua_version "$LUA_BINDIR/lua$LUA_SUFFIX" then echo "yes" else echo "no" die "You may want to use the flags --with-lua, --with-lua-bin and/or --lua-suffix. See --help." fi fi if [ "$LUA_INCDIR_SET" != "yes" ] then LUA_INCDIR="$LUA_DIR/include" fi if [ "$LUA_LIBDIR_SET" != "yes" ] then LUA_LIBDIR="$LUA_DIR/lib" fi lua_h="$LUA_INCDIR/lua.h" echo_n "Looking for lua.h at $lua_h..." if [ -f "$lua_h" ] then echo found else echo "not found" for postfix in "$LUA_VERSION" "$LUA_SUFFIX"; do if ! [ "$postfix" = "" ]; then v_dir="$LUA_INCDIR/lua/$postfix"; else v_dir="$LUA_INCDIR/lua"; fi lua_h="$v_dir/lua.h" echo_n "Looking for lua.h at $lua_h..." if [ -f "$lua_h" ] then LUA_INCDIR="$v_dir" echo found break; else echo "not found" d_dir="$LUA_INCDIR/lua$postfix" lua_h="$d_dir/lua.h" echo_n "Looking for lua.h at $lua_h..." if [ -f "$lua_h" ] then echo found LUA_INCDIR="$d_dir" break; else echo "not found" fi fi done if [ ! -f "$lua_h" ]; then echo "lua.h not found." echo die "You may want to use the flag --with-lua or --with-lua-include. See --help." fi fi if [ "$lua_interp_found" = "yes" ] then echo_n "Checking if Lua header version matches that of the interpreter... " header_version=$(sed -n 's/.*LUA_VERSION_NUM.*5.\(.\).*/5.\1/p' "$lua_h") if [ "$header_version" = "$LUA_VERSION" ] then echo "yes" else echo "no" echo "lua.h version mismatch (interpreter: $LUA_VERSION; lua.h: $header_version)." die "You may want to use the flag --with-lua or --with-lua-include. See --help." fi fi if [ "$IDN_LIBRARY" = "icu" ] then IDNA_LIBS="$ICU_FLAGS" IDNA_FLAGS="-DUSE_STRINGPREP_ICU" fi if [ "$IDN_LIBRARY" = "idn" ] then IDNA_LIBS="-l$IDN_LIB" fi if [ -f config.unix ]; then rm -f config.unix fi if [ "$RUNWITH_SET" != yes ]; then RUNWITH="lua$LUA_SUFFIX" fi OPENSSL_LIBS="-l$OPENSSL_LIB" if [ "$PRNG" = "OPENSSL" ]; then PRNGLIBS=$OPENSSL_LIBS elif [ "$PRNG" = "ARC4RANDOM" ] && [ "$(uname)" = "Linux" ]; then PRNGLIBS="-lbsd" fi # Write config echo "Writing configuration..." echo rm -f built cat < config.unix # This file was automatically generated by the configure script. # Run "./configure --help" for details. LUA_VERSION=$LUA_VERSION PREFIX=$PREFIX SYSCONFDIR=$SYSCONFDIR LIBDIR=$LIBDIR DATADIR=$DATADIR LUA_SUFFIX=$LUA_SUFFIX LUA_DIR=$LUA_DIR LUA_DIR_SET=$LUA_DIR_SET LUA_INCDIR=$LUA_INCDIR LUA_LIBDIR=$LUA_LIBDIR LUA_BINDIR=$LUA_BINDIR IDN_LIB=$IDN_LIB IDNA_FLAGS=$IDNA_FLAGS IDNA_LIBS=$IDNA_LIBS OPENSSL_LIBS=$OPENSSL_LIBS CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS CC=$CC LD=$LD RUNWITH=$RUNWITH EXCERTS=$EXCERTS RANDOM=$PRNG RANDOM_LIBS=$PRNGLIBS EOF echo "Installation prefix: $PREFIX" echo "$APP_NAME configuration directory: $SYSCONFDIR" echo "Using Lua from: $LUA_DIR" make clean > /dev/null 2> /dev/null echo echo "Done. You can now run 'make' to build." echo prosody-0.12.4/PaxHeaders/core0000644000000000000000000000013214475665104013242 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.616765697 30 ctime=1693936196.624765619 prosody-0.12.4/core/0000755000175000017500000000000014475665104015521 5ustar00prosodyprosody00000000000000prosody-0.12.4/core/PaxHeaders/certmanager.lua0000644000000000000000000000012014475665104016307 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.616765697 prosody-0.12.4/core/certmanager.lua0000644000175000017500000003544614475665104020530 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local ssl = require "ssl"; local configmanager = require "core.configmanager"; local log = require "util.logger".init("certmanager"); local ssl_context = ssl.context or require "ssl.context"; local ssl_newcontext = ssl.newcontext; local new_config = require"util.sslconfig".new; local stat = require "lfs".attributes; local x509 = require "util.x509"; local lfs = require "lfs"; local tonumber, tostring = tonumber, tostring; local pairs = pairs; local t_remove = table.remove; local type = type; local io_open = io.open; local select = select; local now = os.time; local next = next; local pcall = pcall; local prosody = prosody; local pathutil = require"util.paths"; local resolve_path = pathutil.resolve_relative_path; local config_path = prosody.paths.config or "."; local function test_option(option) return not not ssl_newcontext({mode="server",protocol="sslv23",options={ option }}); end local luasec_major, luasec_minor = ssl._VERSION:match("^(%d+)%.(%d+)"); local luasec_version = tonumber(luasec_major) * 100 + tonumber(luasec_minor); local luasec_has = ssl.config or { algorithms = { ec = luasec_version >= 5; }; capabilities = { curves_list = luasec_version >= 7; }; options = { cipher_server_preference = test_option("cipher_server_preference"); no_ticket = test_option("no_ticket"); no_compression = test_option("no_compression"); single_dh_use = test_option("single_dh_use"); single_ecdh_use = test_option("single_ecdh_use"); no_renegotiation = test_option("no_renegotiation"); }; }; local _ENV = nil; -- luacheck: std none -- Global SSL options if not overridden per-host local global_ssl_config = configmanager.get("*", "ssl"); local global_certificates = configmanager.get("*", "certificates") or "certs"; local crt_try = { "", "/%s.crt", "/%s/fullchain.pem", "/%s.pem", }; local key_try = { "", "/%s.key", "/%s/privkey.pem", "/%s.pem", }; local function find_cert(user_certs, name) local certs = resolve_path(config_path, user_certs or global_certificates); log("debug", "Searching %s for a key and certificate for %s...", certs, name); for i = 1, #crt_try do local crt_path = certs .. crt_try[i]:format(name); local key_path = certs .. key_try[i]:format(name); if stat(crt_path, "mode") == "file" then if crt_path == key_path then if key_path:sub(-4) == ".crt" then key_path = key_path:sub(1, -4) .. "key"; elseif key_path:sub(-14) == "/fullchain.pem" then key_path = key_path:sub(1, -14) .. "privkey.pem"; end end if stat(key_path, "mode") == "file" then log("debug", "Selecting certificate %s with key %s for %s", crt_path, key_path, name); return { certificate = crt_path, key = key_path }; end end end log("debug", "No certificate/key found for %s", name); end local function find_matching_key(cert_path) return (cert_path:gsub("%.crt$", ".key"):gsub("fullchain", "privkey")); end local function index_certs(dir, files_by_name, depth_limit) files_by_name = files_by_name or {}; depth_limit = depth_limit or 3; if depth_limit <= 0 then return files_by_name; end local ok, iter, v, i = pcall(lfs.dir, dir); if not ok then log("error", "Error indexing certificate directory %s: %s", dir, iter); -- Return an empty index, otherwise this just triggers a nil indexing -- error, plus this function would get called again. -- Reloading the config after correcting the problem calls this again so -- that's what should be done. return {}, iter; end for file in iter, v, i do local full = pathutil.join(dir, file); if lfs.attributes(full, "mode") == "directory" then if file:sub(1,1) ~= "." then index_certs(full, files_by_name, depth_limit-1); end elseif file:find("%.crt$") or file:find("fullchain") then -- This should catch most fullchain files local f = io_open(full); if f then -- TODO look for chained certificates local firstline = f:read(); if firstline == "-----BEGIN CERTIFICATE-----" and lfs.attributes(find_matching_key(full), "mode") == "file" then f:seek("set") local cert = ssl.loadcertificate(f:read("*a")) -- TODO if more than one cert is found for a name, the most recently -- issued one should be used. -- for now, just filter out expired certs -- TODO also check if there's a corresponding key if cert:validat(now()) then local names = x509.get_identities(cert); log("debug", "Found certificate %s with identities %q", full, names); for name, services in pairs(names) do -- TODO check services if files_by_name[name] then files_by_name[name][full] = services; else files_by_name[name] = { [full] = services; }; end end end end f:close(); end end end log("debug", "Certificate index: %q", files_by_name); -- | hostname | filename | service | return files_by_name; end local cert_index; local function find_cert_in_index(index, host) if not host then return nil; end if not index then return nil; end local wildcard_host = host:gsub("^[^.]+%.", "*."); local certs = index[host] or index[wildcard_host]; if certs then local cert_filename, services = next(certs); if services["*"] then log("debug", "Using cert %q from index for host %q", cert_filename, host); return { certificate = cert_filename, key = find_matching_key(cert_filename), } end end return nil end local function find_host_cert(host) if not host then return nil; end if not cert_index then cert_index = index_certs(resolve_path(config_path, global_certificates)); end return find_cert_in_index(cert_index, host) or find_cert(configmanager.get(host, "certificate"), host) or find_host_cert(host:match("%.(.+)$")); end local function find_service_cert(service, port) if not cert_index then cert_index = index_certs(resolve_path(config_path, global_certificates)); end for _, certs in pairs(cert_index) do for cert_filename, services in pairs(certs) do if services[service] or services["*"] then log("debug", "Using cert %q from index for service %s port %d", cert_filename, service, port); return { certificate = cert_filename, key = find_matching_key(cert_filename), } end end end local cert_config = configmanager.get("*", service.."_certificate"); if type(cert_config) == "table" then cert_config = cert_config[port] or cert_config.default; end return find_cert(cert_config, service); end -- Built-in defaults local core_defaults = { capath = "/etc/ssl/certs"; depth = 9; protocol = "tlsv1+"; verify = "none"; options = { cipher_server_preference = luasec_has.options.cipher_server_preference; no_ticket = luasec_has.options.no_ticket; no_compression = luasec_has.options.no_compression and configmanager.get("*", "ssl_compression") ~= true; single_dh_use = luasec_has.options.single_dh_use; single_ecdh_use = luasec_has.options.single_ecdh_use; no_renegotiation = luasec_has.options.no_renegotiation; }; verifyext = { "lsec_continue", -- Continue past certificate verification errors "lsec_ignore_purpose", -- Validate client certificates as if they were server certificates }; curve = luasec_has.algorithms.ec and not luasec_has.capabilities.curves_list and "secp384r1"; curveslist = { "X25519", "P-384", "P-256", "P-521", }; ciphers = { -- Enabled ciphers in order of preference: "HIGH+kEECDH", -- Ephemeral Elliptic curve Diffie-Hellman key exchange "HIGH+kEDH", -- Ephemeral Diffie-Hellman key exchange, if a 'dhparam' file is set "HIGH", -- Other "High strength" ciphers -- Disabled cipher suites: "!PSK", -- Pre-Shared Key - not used for XMPP "!SRP", -- Secure Remote Password - not used for XMPP "!3DES", -- 3DES - slow and of questionable security "!aNULL", -- Ciphers that does not authenticate the connection }; dane = luasec_has.capabilities.dane and configmanager.get("*", "use_dane") and { "no_ee_namechecks" }; } local mozilla_ssl_configs = { -- https://wiki.mozilla.org/Security/Server_Side_TLS -- Version 5.7 as of 2023-07-09 modern = { protocol = "tlsv1_3"; options = { cipher_server_preference = false }; ciphers = "DEFAULT"; -- TLS 1.3 uses 'ciphersuites' rather than these curveslist = { "X25519"; "prime256v1"; "secp384r1" }; ciphersuites = { "TLS_AES_128_GCM_SHA256"; "TLS_AES_256_GCM_SHA384"; "TLS_CHACHA20_POLY1305_SHA256" }; }; intermediate = { protocol = "tlsv1_2+"; dhparam = nil; -- ffdhe2048.txt options = { cipher_server_preference = false }; ciphers = { "ECDHE-ECDSA-AES128-GCM-SHA256"; "ECDHE-RSA-AES128-GCM-SHA256"; "ECDHE-ECDSA-AES256-GCM-SHA384"; "ECDHE-RSA-AES256-GCM-SHA384"; "ECDHE-ECDSA-CHACHA20-POLY1305"; "ECDHE-RSA-CHACHA20-POLY1305"; "DHE-RSA-AES128-GCM-SHA256"; "DHE-RSA-AES256-GCM-SHA384"; "DHE-RSA-CHACHA20-POLY1305"; }; curveslist = { "X25519"; "prime256v1"; "secp384r1" }; ciphersuites = { "TLS_AES_128_GCM_SHA256"; "TLS_AES_256_GCM_SHA384"; "TLS_CHACHA20_POLY1305_SHA256" }; }; old = { protocol = "tlsv1+"; dhparam = nil; -- openssl dhparam 1024 options = { cipher_server_preference = true }; ciphers = { "ECDHE-ECDSA-AES128-GCM-SHA256"; "ECDHE-RSA-AES128-GCM-SHA256"; "ECDHE-ECDSA-AES256-GCM-SHA384"; "ECDHE-RSA-AES256-GCM-SHA384"; "ECDHE-ECDSA-CHACHA20-POLY1305"; "ECDHE-RSA-CHACHA20-POLY1305"; "DHE-RSA-AES128-GCM-SHA256"; "DHE-RSA-AES256-GCM-SHA384"; "DHE-RSA-CHACHA20-POLY1305"; "ECDHE-ECDSA-AES128-SHA256"; "ECDHE-RSA-AES128-SHA256"; "ECDHE-ECDSA-AES128-SHA"; "ECDHE-RSA-AES128-SHA"; "ECDHE-ECDSA-AES256-SHA384"; "ECDHE-RSA-AES256-SHA384"; "ECDHE-ECDSA-AES256-SHA"; "ECDHE-RSA-AES256-SHA"; "DHE-RSA-AES128-SHA256"; "DHE-RSA-AES256-SHA256"; "AES128-GCM-SHA256"; "AES256-GCM-SHA384"; "AES128-SHA256"; "AES256-SHA256"; "AES128-SHA"; "AES256-SHA"; "DES-CBC3-SHA"; }; curveslist = { "X25519"; "prime256v1"; "secp384r1" }; ciphersuites = { "TLS_AES_128_GCM_SHA256"; "TLS_AES_256_GCM_SHA384"; "TLS_CHACHA20_POLY1305_SHA256" }; }; }; if luasec_has.curves then for i = #core_defaults.curveslist, 1, -1 do if not luasec_has.curves[ core_defaults.curveslist[i] ] then t_remove(core_defaults.curveslist, i); end end else core_defaults.curveslist = nil; end local path_options = { -- These we pass through resolve_path() key = true, certificate = true, cafile = true, capath = true, dhparam = true } local function create_context(host, mode, ...) local cfg = new_config(); cfg:apply(core_defaults); local service_name, port = host:match("^(%S+) port (%d+)$"); -- port 0 is used with client-only things that normally don't need certificates, e.g. https if service_name and port ~= "0" then log("debug", "Automatically locating certs for service %s on port %s", service_name, port); cfg:apply(find_service_cert(service_name, tonumber(port))); else log("debug", "Automatically locating certs for host %s", host); cfg:apply(find_host_cert(host)); end cfg:apply({ mode = mode, -- We can't read the password interactively when daemonized password = function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end; }); local profile = configmanager.get("*", "tls_profile") or "intermediate"; if profile ~= "legacy" then cfg:apply(mozilla_ssl_configs[profile]); end cfg:apply(global_ssl_config); for i = select('#', ...), 1, -1 do cfg:apply(select(i, ...)); end local user_ssl_config = cfg:final(); if mode == "server" then if not user_ssl_config.certificate then log("info", "No certificate present in SSL/TLS configuration for %s. SNI will be required.", host); end if user_ssl_config.certificate and not user_ssl_config.key then return nil, "No key present in SSL/TLS configuration for "..host; end end for option in pairs(path_options) do if type(user_ssl_config[option]) == "string" then user_ssl_config[option] = resolve_path(config_path, user_ssl_config[option]); else user_ssl_config[option] = nil; end end -- LuaSec expects dhparam to be a callback that takes two arguments. -- We ignore those because it is mostly used for having a separate -- set of params for EXPORT ciphers, which we don't have by default. if type(user_ssl_config.dhparam) == "string" then local f, err = io_open(user_ssl_config.dhparam); if not f then return nil, "Could not open DH parameters: "..err end local dhparam = f:read("*a"); f:close(); user_ssl_config.dhparam = function() return dhparam; end end local ctx, err = ssl_newcontext(user_ssl_config); -- COMPAT Older LuaSec ignores the cipher list from the config, so we have to take care -- of it ourselves (W/A for #x) if ctx and user_ssl_config.ciphers then local success; success, err = ssl_context.setcipher(ctx, user_ssl_config.ciphers); if not success then ctx = nil; end end if not ctx then err = err or "invalid ssl config" local file = err:match("^error loading (.-) %("); if file then local typ; if file == "private key" then typ = file; file = user_ssl_config.key or "your private key"; elseif file == "certificate" then typ = file; file = user_ssl_config.certificate or "your certificate file"; end local reason = err:match("%((.+)%)$") or "some reason"; if reason == "Permission denied" then reason = "Check that the permissions allow Prosody to read this file."; elseif reason == "No such file or directory" then reason = "Check that the path is correct, and the file exists."; elseif reason == "system lib" then reason = "Previous error (see logs), or other system error."; elseif reason == "no start line" then reason = "Check that the file contains a "..(typ or file); elseif reason == "(null)" or not reason then reason = "Check that the file exists and the permissions are correct"; else reason = "Reason: "..tostring(reason):lower(); end log("error", "SSL/TLS: Failed to load '%s': %s (for %s)", file, reason, host); else log("error", "SSL/TLS: Error initialising for %s: %s", host, err); end end return ctx, err, user_ssl_config; end local function reload_ssl_config() global_ssl_config = configmanager.get("*", "ssl"); global_certificates = configmanager.get("*", "certificates") or "certs"; if luasec_has.options.no_compression then core_defaults.options.no_compression = configmanager.get("*", "ssl_compression") ~= true; end core_defaults.dane = configmanager.get("*", "use_dane") or false; cert_index = index_certs(resolve_path(config_path, global_certificates)); end prosody.events.add_handler("config-reloaded", reload_ssl_config); return { create_context = create_context; reload_ssl_config = reload_ssl_config; find_cert = find_cert; index_certs = index_certs; find_host_cert = find_host_cert; find_cert_in_index = find_cert_in_index; }; prosody-0.12.4/core/PaxHeaders/configmanager.lua0000644000000000000000000000012014475665104016617 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.620765658 prosody-0.12.4/core/configmanager.lua0000644000175000017500000002103114475665104021021 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local _G = _G; local setmetatable, rawget, rawset, io, os, error, dofile, type, pairs, ipairs = setmetatable, rawget, rawset, io, os, error, dofile, type, pairs, ipairs; local format, math_max, t_insert = string.format, math.max, table.insert; local envload = require"util.envload".envload; local deps = require"util.dependencies"; local resolve_relative_path = require"util.paths".resolve_relative_path; local glob_to_pattern = require"util.paths".glob_to_pattern; local path_sep = package.config:sub(1,1); local get_traceback_table = require "util.debug".get_traceback_table; local encodings = deps.softreq"util.encodings"; local nameprep = encodings and encodings.stringprep.nameprep or function (host) return host:lower(); end local _M = {}; local _ENV = nil; -- luacheck: std none _M.resolve_relative_path = resolve_relative_path; -- COMPAT local parser = nil; local config_mt = { __index = function (t, _) return rawget(t, "*"); end}; local config = setmetatable({ ["*"] = { } }, config_mt); local files = {}; -- When host not found, use global local host_mt = { __index = function(_, k) return config["*"][k] end } function _M.getconfig() return config; end function _M.get(host, key, _oldkey) if key == "core" then key = _oldkey; -- COMPAT with code that still uses "core" end return config[host][key]; end function _M.rawget(host, key, _oldkey) if key == "core" then key = _oldkey; -- COMPAT with code that still uses "core" end local hostconfig = rawget(config, host); if hostconfig then return rawget(hostconfig, key); end end local function set(config_table, host, key, value) if host and key then local hostconfig = rawget(config_table, host); if not hostconfig then hostconfig = rawset(config_table, host, setmetatable({}, host_mt))[host]; end hostconfig[key] = value; return true; end return false; end function _M.set(host, key, value, _oldvalue) if key == "core" then key, value = value, _oldvalue; --COMPAT with code that still uses "core" end return set(config, host, key, value); end function _M.load(filename, config_format) config_format = config_format or filename:match("%w+$"); if config_format == "lua" then local f, err = io.open(filename); if f then local new_config = setmetatable({ ["*"] = { } }, config_mt); local ok, err = parser.load(f:read("*a"), filename, new_config); f:close(); if ok then config = new_config; end return ok, "parser", err; end return f, "file", err; end if not config_format then return nil, "file", "no parser specified"; else return nil, "file", "no parser for "..(config_format); end end function _M.files() return files; end -- Built-in Lua parser do local pcall = _G.pcall; local function get_line_number(config_file) local tb = get_traceback_table(nil, 2); for i = 1, #tb do if tb[i].info.short_src == config_file then return tb[i].info.currentline; end end end parser = {}; function parser.load(data, config_file, config_table) local set_options = {}; -- set_options[host.."/"..option_name] = true (when the option has been set already in this file) local warnings = {}; local env; -- The ' = true' are needed so as not to set off __newindex when we assign the functions below env = setmetatable({ Host = true, host = true, VirtualHost = true, Component = true, component = true, Include = true, include = true, RunScript = true }, { __index = function (_, k) if k:match("^ENV_") then return os.getenv(k:sub(5)); end return rawget(_G, k); end, __newindex = function (_, k, v) local host = env.__currenthost or "*"; local option_path = host.."/"..k; if set_options[option_path] then t_insert(warnings, ("%s:%d: Duplicate option '%s'"):format(config_file, get_line_number(config_file), k)); end set_options[option_path] = true; set(config_table, env.__currenthost or "*", k, v); end }); rawset(env, "__currenthost", "*") -- Default is global function env.VirtualHost(name) if not name then error("Host must have a name", 2); end local prepped_name = nameprep(name); if not prepped_name then error(format("Name of Host %q contains forbidden characters", name), 0); end name = prepped_name; if rawget(config_table, name) and rawget(config_table[name], "component_module") then error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s", name, config_table[name].component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0); end rawset(env, "__currenthost", name); -- Needs at least one setting to logically exist :) set(config_table, name or "*", "defined", true); return function (config_options) rawset(env, "__currenthost", "*"); -- Return to global scope if type(config_options) == "string" then error(format("VirtualHost entries do not accept a module name (module '%s' provided for host '%s')", config_options, name), 2); elseif type(config_options) ~= "table" then error("Invalid syntax following VirtualHost, expected options but received a "..type(config_options), 2); end for option_name, option_value in pairs(config_options) do set(config_table, name or "*", option_name, option_value); end end; end env.Host, env.host = env.VirtualHost, env.VirtualHost; function env.Component(name) if not name then error("Component must have a name", 2); end local prepped_name = nameprep(name); if not prepped_name then error(format("Name of Component %q contains forbidden characters", name), 0); end name = prepped_name; if rawget(config_table, name) and rawget(config_table[name], "defined") and not rawget(config_table[name], "component_module") then error(format("Component %q clashes with previously defined VirtualHost %q, for services use a sub-domain like conference.%s", name, name, name), 0); end set(config_table, name, "component_module", "component"); -- Don't load the global modules by default set(config_table, name, "load_global_modules", false); rawset(env, "__currenthost", name); local function handle_config_options(config_options) rawset(env, "__currenthost", "*"); -- Return to global scope for option_name, option_value in pairs(config_options) do set(config_table, name or "*", option_name, option_value); end end return function (module) if type(module) == "string" then set(config_table, name, "component_module", module); return handle_config_options; end return handle_config_options(module); end end env.component = env.Component; function env.Include(file) -- Check whether this is a wildcard Include if file:match("[*?]") then local lfs = deps.softreq "lfs"; if not lfs then error(format("Error expanding wildcard pattern in Include %q - LuaFileSystem not available", file)); end local path_pos, glob = file:match("()([^"..path_sep.."]+)$"); local path = file:sub(1, math_max(path_pos-2,0)); local config_path = config_file:gsub("[^"..path_sep.."]+$", ""); if #path > 0 then path = resolve_relative_path(config_path, path); else path = config_path; end local patt = glob_to_pattern(glob); for f in lfs.dir(path) do if f:sub(1,1) ~= "." and f:match(patt) then env.Include(path..path_sep..f); end end return; end -- Not a wildcard, so resolve (potentially) relative path and run through config parser file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file); local f, err = io.open(file); if f then local ret, err = parser.load(f:read("*a"), file, config_table); if not ret then error(err:gsub("%[string.-%]", file), 0); end if err then for _, warning in ipairs(err) do t_insert(warnings, warning); end end end if not f then error("Error loading included "..file..": "..err, 0); end return f, err; end env.include = env.Include; function env.RunScript(file) return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file)); end local chunk, err = envload(data, "@"..config_file, env); if not chunk then return nil, err; end local ok, err = pcall(chunk); if not ok then return nil, err; end t_insert(files, config_file); return true, warnings; end end return _M; prosody-0.12.4/core/PaxHeaders/features.lua0000644000000000000000000000012014475665104015635 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.620765658 prosody-0.12.4/core/features.lua0000644000175000017500000000022414475665104020040 0ustar00prosodyprosody00000000000000local set = require "util.set"; return { available = set.new{ -- mod_bookmarks bundled "mod_bookmarks"; "s2sout-pre-connect-event"; }; }; prosody-0.12.4/core/PaxHeaders/hostmanager.lua0000644000000000000000000000012014475665104016327 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.620765658 prosody-0.12.4/core/hostmanager.lua0000644000175000017500000001233314475665104020536 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local configmanager = require "core.configmanager"; local modulemanager = require "core.modulemanager"; local events_new = require "util.events".new; local disco_items = require "util.multitable".new(); local NULL = {}; local log = require "util.logger".init("hostmanager"); local hosts = prosody.hosts; local prosody_events = prosody.events; if not _G.prosody.incoming_s2s then require "core.s2smanager"; end local incoming_s2s = _G.prosody.incoming_s2s; local core_route_stanza = _G.prosody.core_route_stanza; 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() if self.type == "component" then local typ = configmanager.get(self.host, "component_module"); if typ == "component" then return ("Component %q"):format(self.host); end return ("Component %q %q"):format(self.host, typ); elseif self.type == "local" then return ("VirtualHost %q"):format(self.host); end end local hosts_loaded_once; local activate, deactivate; local function load_enabled_hosts(config) local defined_hosts = config or configmanager.getconfig(); local activated_any_host; for host, host_config in pairs(defined_hosts) do if host ~= "*" and host_config.enabled ~= false then if not host_config.component_module then activated_any_host = true; end activate(host, host_config); end end if not activated_any_host then log("error", "No active VirtualHost entries in the config file. This may cause unexpected behaviour as no modules will be loaded."); end prosody_events.fire_event("hosts-activated", defined_hosts); hosts_loaded_once = true; end prosody_events.add_handler("server-starting", load_enabled_hosts); local function host_send(stanza) core_route_stanza(nil, stanza); end function activate(host, host_config) if rawget(hosts, host) then return nil, "The host "..host.." is already activated"; end host_config = host_config or configmanager.getconfig()[host]; if not host_config then return nil, "Couldn't find the host "..tostring(host).." defined in the current config"; end local host_session = { host = host; s2sout = {}; events = events_new(); send = host_send; modules = {}; }; function host_session:close(reason) log("debug", "Attempt to close host session %s with reason: %s", self.host, reason); end setmetatable(host_session, host_mt); if not host_config.component_module then -- host host_session.type = "local"; host_session.sessions = {}; else -- component host_session.type = "component"; end hosts[host] = host_session; if not host_config.disco_hidden and not host:match("[@/]") then disco_items:set(host:match("%.(.*)") or "*", host, host_config.name or true); end for option_name in pairs(host_config) do if option_name:match("_ports$") or option_name:match("_interface$") then log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in the server-wide section instead", host, option_name); end end log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host); prosody_events.fire_event("host-activated", host); return true; end function deactivate(host, reason) local host_session = hosts[host]; if not host_session then return nil, "The host "..tostring(host).." is not activated"; end log("info", "Deactivating host: %s", host); prosody_events.fire_event("host-deactivating", { host = host, host_session = host_session, reason = reason }); if type(reason) ~= "table" then reason = { condition = "host-gone", text = tostring(reason or "This server has stopped serving "..host) }; end -- Disconnect local users, s2s connections -- TODO: These should move to mod_c2s and mod_s2s (how do they know they're being unloaded and not reloaded?) if host_session.sessions then for username, user in pairs(host_session.sessions) do for resource, session in pairs(user.sessions) do log("debug", "Closing connection for %s@%s/%s", username, host, resource); session:close(reason); end end end if host_session.s2sout then for remotehost, session in pairs(host_session.s2sout) do if session.close then log("debug", "Closing outgoing connection to %s", remotehost); session:close(reason); end end end for remote_session in pairs(incoming_s2s) do if remote_session.to_host == host then log("debug", "Closing incoming connection from %s", remote_session.from_host or ""); remote_session:close(reason); end end -- TODO: This should be done in modulemanager if host_session.modules then for module in pairs(host_session.modules) do modulemanager.unload(host, module); end end hosts[host] = nil; if not host:match("[@/]") then disco_items:remove(host:match("%.(.*)") or "*", host); end prosody_events.fire_event("host-deactivated", host); log("info", "Deactivated host: %s", host); return true; end local function get_children(host) return disco_items:get(host) or NULL; end return { activate = activate; deactivate = deactivate; get_children = get_children; } prosody-0.12.4/core/PaxHeaders/loggingmanager.lua0000644000000000000000000000012014475665104017000 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.620765658 prosody-0.12.4/core/loggingmanager.lua0000644000175000017500000001761014475665104021212 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local format = require "util.format".format; local setmetatable, rawset, pairs, ipairs, type = setmetatable, rawset, pairs, ipairs, type; local stdout = io.stdout; local io_open = io.open; local math_max, rep = math.max, string.rep; local os_date = os.date; local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring; local st = require "util.stanza"; local config = require "core.configmanager"; local logger = require "util.logger"; local have_pposix, pposix = pcall(require, "util.pposix"); have_pposix = have_pposix and pposix._VERSION == "0.4.0"; 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; local default_file_logging; local default_timestamp = "%b %d %H:%M:%S "; -- The actual config loggingmanager is using local logging_config; local apply_sink_rules; local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; }); local get_levels; local logging_levels = { "debug", "info", "warn", "error" } local function id(x) return x end -- Put a rule into action. Requires that the sink type has already been registered. -- This function is called automatically when a new sink type is added [see apply_sink_rules()] local function add_rule(sink_config) local sink_maker = log_sink_types[sink_config.to]; if not sink_maker then return; -- No such sink type end -- Create sink local sink = sink_maker(sink_config); -- Set sink for all chosen levels for level in pairs(get_levels(sink_config.levels or logging_levels)) do logger.add_level_sink(level, sink); end end -- Search for all rules using a particular sink type, and apply -- them. Called automatically when a new sink type is added to -- the log_sink_types table. function apply_sink_rules(sink_type) if type(logging_config) == "table" then for _, level in ipairs(logging_levels) do if type(logging_config[level]) == "string" then local value = logging_config[level]; if sink_type == "file" and not value:match("^%*") then add_rule({ to = sink_type; filename = value; timestamps = true; levels = { min = level }; }); elseif value == "*"..sink_type then add_rule({ to = sink_type; levels = { min = level }; }); end end end for _, sink_config in ipairs(logging_config) do if (type(sink_config) == "table" and sink_config.to == sink_type) then add_rule(sink_config); elseif (type(sink_config) == "string" and sink_config:match("^%*(.+)") == sink_type) then add_rule({ levels = { min = "debug" }, to = sink_type }); end end elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then -- User specified simply a filename, and the "file" sink type -- was just added for _, sink_config in pairs(default_file_logging) do sink_config.filename = logging_config; add_rule(sink_config); sink_config.filename = nil; end elseif type(logging_config) == "string" and logging_config:match("^%*(.+)") == sink_type then -- Log all levels (debug+) to this sink add_rule({ levels = { min = "debug" }, to = sink_type }); end end --- Helper function to get a set of levels given a "criteria" table function get_levels(criteria, set) set = set or {}; if type(criteria) == "string" then set[criteria] = true; return set; end local min, max = criteria.min, criteria.max; if min or max then local in_range; for _, level in ipairs(logging_levels) do if min == level then set[level] = true; in_range = true; elseif max == level then set[level] = true; return set; elseif in_range then set[level] = true; end end end for _, level in ipairs(criteria) do set[level] = true; end return set; end -- Initialize config, etc. -- local function reload_logging() local old_sink_types = {}; for name, sink_maker in pairs(log_sink_types) do old_sink_types[name] = sink_maker; log_sink_types[name] = nil; end logger.reset(); local debug_mode = config.get("*", "debug"); default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } }; default_file_logging = { { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } }; logging_config = config.get("*", "log") or default_logging; for name, sink_maker in pairs(old_sink_types) do log_sink_types[name] = sink_maker; end end --- Definition of built-in logging sinks --- -- Null sink, must enter log_sink_types *first* local function log_to_nowhere() return function () return false; end; end log_sink_types.nowhere = log_to_nowhere; local function log_to_file(sink_config, logfile) logfile = logfile or io_open(sink_config.filename, "a+"); if not logfile then return log_to_nowhere(sink_config); end local write = logfile.write; local timestamps = sink_config.timestamps; if timestamps == true or timestamps == nil then timestamps = default_timestamp; -- Default format elseif timestamps then timestamps = timestamps .. " "; end if sink_config.buffer_mode ~= false then logfile:setvbuf(sink_config.buffer_mode or "line"); end -- Column width for "source" (used by stdout and console) local sourcewidth = sink_config.source_width; local filter = sink_config.filter or id; if sourcewidth then return function (name, level, message, ...) sourcewidth = math_max(#name+2, sourcewidth); write(logfile, timestamps and os_date(timestamps) or "", name, rep(" ", sourcewidth-#name), level, "\t", filter(format(message, ...)), "\n"); end else return function (name, level, message, ...) write(logfile, timestamps and os_date(timestamps) or "", name, "\t", level, "\t", filter(format(message, ...)), "\n"); end end end log_sink_types.file = log_to_file; local function log_to_stdout(sink_config) if not sink_config.timestamps then sink_config.timestamps = false; end if sink_config.source_width == nil then sink_config.source_width = 20; end return log_to_file(sink_config, stdout); end log_sink_types.stdout = log_to_stdout; local do_pretty_printing = not have_pposix or pposix.isatty(stdout); local logstyles, pretty; if do_pretty_printing then logstyles = {}; logstyles["info"] = getstyle("bold"); logstyles["warn"] = getstyle("bold", "yellow"); logstyles["error"] = getstyle("bold", "red"); pretty = st.pretty_print; end local function log_to_console(sink_config) -- Really if we don't want pretty colours then just use plain stdout -- FIXME refactor to allow console logging with colours on stderr if not do_pretty_printing then return log_to_stdout(sink_config); end sink_config.filter = pretty; local logstdout = log_to_stdout(sink_config); return function (name, level, message, ...) local logstyle = logstyles[level]; if logstyle then level = getstring(logstyle, level); end return logstdout(name, level, message, ...); end end log_sink_types.console = log_to_console; if have_pposix then local syslog_opened; local function log_to_syslog(sink_config) -- luacheck: ignore 212/sink_config if not syslog_opened then local facility = sink_config.syslog_facility or config.get("*", "syslog_facility"); pposix.syslog_open(sink_config.syslog_name or "prosody", facility); syslog_opened = true; end local syslog = pposix.syslog_log; return function (name, level, message, ...) syslog(level, name, format(message, ...)); end; end log_sink_types.syslog = log_to_syslog; end local function register_sink_type(name, sink_maker) local old_sink_maker = log_sink_types[name]; log_sink_types[name] = sink_maker; return old_sink_maker; end return { reload_logging = reload_logging; register_sink_type = register_sink_type; } prosody-0.12.4/core/PaxHeaders/moduleapi.lua0000644000000000000000000000012014475665104015776 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.620765658 prosody-0.12.4/core/moduleapi.lua0000644000175000017500000004402414475665104020207 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2012 Matthew Wild -- Copyright (C) 2008-2012 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local array = require "util.array"; local set = require "util.set"; local it = require "util.iterators"; local logger = require "util.logger"; local timer = require "util.timer"; local resolve_relative_path = require"util.paths".resolve_relative_path; local st = require "util.stanza"; local cache = require "util.cache"; local errors = require "util.error"; local promise = require "util.promise"; local time_now = require "util.time".now; local format = require "util.format".format; local jid_node = require "util.jid".node; local jid_resource = require "util.jid".resource; 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 tonumber, tostring = tonumber, tostring; local require = require; local pack = table.pack or require "util.table".pack; -- 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; -- FIXME: This assert() is to try and catch an obscure bug (2013-04-05) local core_post_stanza = assert(prosody.core_post_stanza, "prosody.core_post_stanza is nil, please report this as a bug"); -- Registry of shared module data local shared_data = setmetatable({}, { __mode = "v" }); local NULL = {}; local api = {}; -- Returns the name of the current module function api:get_name() return self.name; end -- Returns the host that the current module is serving function api:get_host() return self.host; end function api:get_host_type() return (self.host == "*" and "global") or hosts[self.host].type or "local"; end function api:set_global() self.host = "*"; -- Update the logger local _log = logger.init("mod_"..self.name); self.log = function (self, ...) return _log(...); end; --luacheck: ignore self self._log = _log; self.global = true; end function api:add_feature(xmlns) self:add_item("feature", xmlns); end function api:add_identity(category, identity_type, name) self:add_item("identity", {category = category, type = identity_type, name = name}); end function api:add_extension(data) self:add_item("extension", data); end function api:fire_event(...) return (hosts[self.host] or prosody).events.fire_event(...); end function api:hook_object_event(object, event, handler, priority) self.event_handlers:set(object, event, handler, true); return object.add_handler(event, handler, priority); end function api:unhook_object_event(object, event, handler) self.event_handlers:set(object, event, handler, nil); return object.remove_handler(event, handler); end function api:hook(event, handler, priority) return self:hook_object_event((hosts[self.host] or prosody).events, event, handler, priority); end function api:hook_global(event, handler, priority) return self:hook_object_event(prosody.events, event, handler, priority); end function api:hook_tag(xmlns, name, handler, priority) if not handler and type(name) == "function" then -- If only 2 options then they specified no xmlns xmlns, name, handler, priority = nil, xmlns, name, handler; elseif not (handler and name) then self:log("warn", "Error: Insufficient parameters to module:hook_tag()"); return; end return self:hook("stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority); end api.hook_stanza = api.hook_tag; -- COMPAT w/pre-0.9 function api:unhook(event, handler) return self:unhook_object_event((hosts[self.host] or prosody).events, event, handler); end function api:wrap_object_event(events_object, event, handler) return self:hook_object_event(assert(events_object.wrappers, "no wrappers"), event, handler); end function api:wrap_event(event, handler) return self:wrap_object_event((hosts[self.host] or prosody).events, event, handler); end function api:wrap_global(event, handler) return self:hook_object_event(prosody.events, event, handler); end function api:require(lib) local modulemanager = require"core.modulemanager"; local f, n = modulemanager.loader:load_code_ext(self.name, lib, "lib.lua", self.environment); if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message return f(); end function api:depends(name) local modulemanager = require"core.modulemanager"; if 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) if self.dependencies[event.module] and not self.reloading then self:log("info", "Auto-reloading due to reload of %s:%s", event.host, event.module); modulemanager.reload(self.host, self.name); return; end end); self:hook("module-unloaded", function (event) if self.dependencies[event.module] then self:log("info", "Auto-unloading due to unload of %s:%s", event.host, event.module); modulemanager.unload(self.host, self.name); end end); end local mod = modulemanager.get_module(self.host, name) or modulemanager.get_module("*", name); if mod and mod.module.host == "*" and self.host ~= "*" and modulemanager.module_has_method(mod, "add_host") then mod = nil; -- Target is a shared module, so we still want to load it on our host end if not mod then local err; mod, err = modulemanager.load(self.host, name); if not mod then return error(("Unable to load required module, mod_%s: %s"):format(name, ((err or "unknown error"):gsub("%-", " ")) )); end end self.dependencies[name] = true; return mod; end 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(path) if not self.shared_data then self.shared_data = {}; end 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; end return value; end function api:get_option_scalar(name, default_value) local value = self:get_option(name, default_value); if type(value) == "table" then if #value > 1 then self:log("error", "Config option '%s' does not take a list, using just the first item", name); end value = value[1]; end return value; end function api:get_option_string(name, default_value) local value = self:get_option_scalar(name, default_value); if value == nil then return nil; end return tostring(value); end function api:get_option_number(name, ...) local value = self:get_option_scalar(name, ...); local ret = tonumber(value); if value ~= nil and ret == nil then self:log("error", "Config option '%s' not understood, expecting a number", name); end return ret; end function api:get_option_boolean(name, ...) local value = self:get_option_scalar(name, ...); if value == nil then return nil; end local ret = value == true or value == "true" or value == 1 or nil; if ret == nil then ret = (value == false or value == "false" or value == 0); if ret then ret = false; else ret = nil; end end if ret == nil then self:log("error", "Config option '%s' not understood, expecting true/false", name); end return ret; end function api:get_option_array(name, ...) local value = self:get_option(name, ...); if value == nil then return nil; end if type(value) ~= "table" then return array{ value }; -- Assume any non-list is a single-item list end return array():append(value); -- Clone end function api:get_option_set(name, ...) local value = self:get_option_array(name, ...); if value == nil then return nil; end return set.new(value); end function api:get_option_inherited_set(name, ...) local value = self:get_option_set(name, ...); local global_value = self:context("*"):get_option_set(name, ...); if not value then return global_value; elseif not global_value then return value; end value:include(global_value); return value; end function api:get_option_path(name, default, parent) if parent == nil then parent = self:get_directory(); elseif prosody.paths[parent] then parent = prosody.paths[parent]; end local value = self:get_option_string(name, default); if value == nil then return nil; end return resolve_relative_path(parent, value); end function api:context(host) return setmetatable({ host = host or "*", global = "*" == host }, { __index = self, __newindex = self }); end function api:add_item(key, value) self.items = self.items or {}; self.items[key] = self.items[key] or {}; t_insert(self.items[key], value); self:fire_event("item-added/"..key, {source = self, item = value}); end function api:remove_item(key, value) local t = self.items and self.items[key] or NULL; for i = #t,1,-1 do if t[i] == value then t_remove(self.items[key], i); self:fire_event("item-removed/"..key, {source = self, item = value}); return value; end end end function api:get_host_items(key) local modulemanager = require"core.modulemanager"; local result = modulemanager.get_items(key, self.host) or {}; return result; end function api:handle_items(item_type, added_cb, removed_cb, existing) self:hook("item-added/"..item_type, added_cb); self:hook("item-removed/"..item_type, removed_cb); if existing ~= false then for _, item in ipairs(self:get_host_items(item_type)) do added_cb({ item = item }); end end end function api:provides(name, item) -- if not item then item = setmetatable({}, { __index = function(t,k) return rawget(self.environment, k); end }); end if not item then item = {} for k,v in pairs(self.environment) do if k ~= "module" then item[k] = v; end end end if not item.name then local item_name = self.name; -- Strip a provider prefix to find the item name -- (e.g. "auth_foo" -> "foo" for an auth provider) if item_name:find((name:gsub("%-", "_")).."_", 1, true) == 1 then item_name = item_name:sub(#name+2); end item.name = item_name; end item._provided_by = self.name; self:add_item(name.."-provider", item); end function api:send(stanza, origin) return core_post_stanza(origin or hosts[self.host], stanza); end function api:send_iq(stanza, origin, timeout) local iq_cache = self._iq_cache; if not iq_cache then iq_cache = cache.new(256, function (_, iq) iq.reject(errors.new({ type = "wait", condition = "resource-constraint", text = "evicted from iq tracking cache" })); end); self._iq_cache = iq_cache; end local event_type; if not jid_node(stanza.attr.from) then event_type = "host"; elseif jid_resource(stanza.attr.from) then event_type = "full"; else -- assume bare since we can't hook full jids event_type = "bare"; end local result_event = "iq-result/"..event_type.."/"..stanza.attr.id; local error_event = "iq-error/"..event_type.."/"..stanza.attr.id; local cache_key = event_type.."/"..stanza.attr.id; if event_type == "full" then result_event = "iq/" .. event_type; error_event = "iq/" .. event_type; end local p = promise.new(function (resolve, reject) local function result_handler(event) local response = event.stanza; if response.attr.type == "result" and response.attr.from == stanza.attr.to and response.attr.id == stanza.attr.id then resolve(event); return true; end end local function error_handler(event) local response = event.stanza; if response.attr.type == "error" and response.attr.from == stanza.attr.to and response.attr.id == stanza.attr.id then reject(errors.from_stanza(event.stanza, event)); return true; end end if iq_cache:get(cache_key) then reject(errors.new({ type = "modify", condition = "conflict", text = "IQ stanza id attribute already used", })); return; end self:hook(result_event, result_handler, 1); self:hook(error_event, error_handler, 1); local timeout_handle = self:add_timer(timeout or 120, function () reject(errors.new({ type = "wait", condition = "remote-server-timeout", text = "IQ stanza timed out", })); end); local ok = iq_cache:set(cache_key, { reject = reject, resolve = resolve, timeout_handle = timeout_handle, result_handler = result_handler, error_handler = error_handler; }); if not ok then reject(errors.new({ type = "wait", condition = "internal-server-error", text = "Could not store IQ tracking data" })); return; end local wrapped_origin = setmetatable({ -- XXX Needed in some cases for replies to work correctly when sending queries internally. send = function (reply) if reply.name == stanza.name and reply.attr.id == stanza.attr.id then resolve({ stanza = reply }); end return (origin or hosts[self.host]).send(reply) end; }, { __index = origin or hosts[self.host]; }); self:send(stanza, wrapped_origin); end); p:finally(function () local iq = iq_cache:get(cache_key); if iq then self:unhook(result_event, iq.result_handler); self:unhook(error_event, iq.error_handler); iq.timeout_handle:stop(); iq_cache:set(cache_key, nil); end end); return p; end function api:broadcast(jids, stanza, iter) for jid in (iter or it.values)(jids) do local new_stanza = st.clone(stanza); new_stanza.attr.to = jid; self:send(new_stanza); end 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 function api:cron(task_spec) self:depends("cron"); self:add_item("task", task_spec); end function api:hourly(name, fun) if type(name) == "function" then fun, name = name, nil; end self:cron({ name = name; when = "hourly"; run = fun }); end function api:daily(name, fun) if type(name) == "function" then fun, name = name, nil; end self:cron({ name = name; when = "daily"; run = fun }); end function api:weekly(name, fun) if type(name) == "function" then fun, name = name, nil; end self:cron({ name = name; when = "weekly"; run = fun }); end local path_sep = package.config:sub(1,1); function api:get_directory() return self.resource_path or self.path and (self.path:gsub("%"..path_sep.."[^"..path_sep.."]*$", "")) or nil; end function api:load_resource(path, mode) path = resolve_relative_path(self:get_directory(), path); return io.open(path, mode); end function api:open_store(name, store_type) return require"core.storagemanager".open(self.host, name or self.name, store_type); end function api:measure(name, stat_type, conf) local measure = require "core.statsmanager".measure; local fixed_label_key, fixed_label_value if self.host ~= "*" then fixed_label_key = "host" fixed_label_value = self.host end -- new_legacy_metric takes care of scoping for us, as it does not accept -- an array of labels -- the prosody_ prefix is automatically added by statsmanager for legacy -- metrics. self:add_item("measure", { name = name, type = stat_type, conf = conf }); return measure(stat_type, "mod_"..self.name.."/"..name, conf, fixed_label_key, fixed_label_value) end function api:metric(type_, name, unit, description, label_keys, conf) local metric = require "core.statsmanager".metric; local is_scoped = self.host ~= "*" if is_scoped then -- prepend `host` label to label keys if this is not a global module local orig_labels = label_keys label_keys = array { "host" } label_keys:append(orig_labels) end local mf = metric(type_, "prosody_mod_"..self.name.."/"..name, unit, description, label_keys, conf) self:add_item("metric", { name = name, mf = mf }); if is_scoped then -- make sure to scope the returned metric family to the current host return mf:with_partial_label(self.host) end return mf end local status_priorities = { error = 3, warn = 2, info = 1, core = 0 }; function api:set_status(status_type, status_message, override) local priority = status_priorities[status_type]; if not priority then self:log("error", "set_status: Invalid status type '%s', assuming 'info'"); status_type, priority = "info", status_priorities.info; end local current_priority = status_priorities[self.status_type] or 0; -- By default an 'error' status can only be overwritten by another 'error' status if (current_priority >= status_priorities.error and priority < current_priority and override ~= true) or (override == false and current_priority > priority) then self:log("debug", "moduleapi: ignoring status [prio %d override %s]: %s", priority, override, status_message); return; end self.status_type, self.status_message, self.status_time = status_type, status_message, time_now(); self:fire_event("module-status/updated", { name = self.name }); end function api:log_status(level, msg, ...) self:set_status(level, format(msg, ...)); return self:log(level, msg, ...); end function api:get_status() return self.status_type, self.status_message, self.status_time; end return api; prosody-0.12.4/core/PaxHeaders/modulemanager.lua0000644000000000000000000000012014475665104016637 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.620765658 prosody-0.12.4/core/modulemanager.lua0000644000175000017500000003356214475665104021055 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local array = require "util.array"; local logger = require "util.logger"; local log = logger.init("modulemanager"); local config = require "core.configmanager"; local pluginloader = require "util.pluginloader"; local envload = require "util.envload"; local set = require "util.set"; local core_features = require "core.features".available; local new_multitable = require "util.multitable".new; local api = require "core.moduleapi"; -- Module API container local prosody = prosody; local hosts = prosody.hosts; local xpcall = require "util.xpcall".xpcall; local debug_traceback = debug.traceback; local setmetatable, rawget = setmetatable, rawget; local ipairs, pairs, type, t_insert = ipairs, pairs, type, table.insert; local lua_version = _VERSION:match("5%.%d$"); local autoload_modules = { prosody.platform, "presence", "message", "iq", "offline", "c2s", "s2s", "s2s_auth_certs", }; local component_inheritable_modules = { "tls", "saslauth", "dialback", "iq", "s2s", "s2s_bidi", "smacks", "server_contact_info", }; -- We need this to let modules access the real global namespace local _G = _G; local _ENV = nil; -- luacheck: std none local loader = pluginloader.init({ load_filter_cb = function (path, content) local metadata = {}; for line in content:gmatch("([^\r\n]+)\r?\n") do local key, value = line:match("^%-%-%% *([%w_]+): *(.+)$"); if key then value = value:gsub("%s+$", ""); metadata[key] = value; end end if metadata.conflicts then local conflicts_features = set.new(array.collect(metadata.conflicts:gmatch("[^, ]+"))); local conflicted_features = set.intersection(conflicts_features, core_features); if not conflicted_features:empty() then log("warn", "Not loading module, due to conflicting features '%s': %s", conflicted_features, path); return; -- Don't load this module end end if metadata.requires then local required_features = set.new(array.collect(metadata.requires:gmatch("[^, ]+"))); local missing_features = required_features - core_features; if not missing_features:empty() then log("warn", "Not loading module, due to missing features '%s': %s", missing_features, path); return; -- Don't load this module end end return path, content, metadata; end; }); local load_modules_for_host, load, unload, reload, get_module, get_items; local get_modules, is_loaded, module_has_method, call_module_method; -- [host] = { [module] = module_env } local modulemap = { ["*"] = {} }; -- 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"); local global_modules_disabled = config.get("*", "modules_disabled"); local host_modules_enabled = config.get(host, "modules_enabled"); local host_modules_disabled = config.get(host, "modules_disabled"); if host_modules_enabled == global_modules_enabled then host_modules_enabled = nil; end if host_modules_disabled == global_modules_disabled then host_modules_disabled = nil; end local global_modules = set.new(autoload_modules) + set.new(global_modules_enabled) - set.new(global_modules_disabled); if component then global_modules = set.intersection(set.new(component_inheritable_modules), global_modules); end local modules = (global_modules + set.new(host_modules_enabled)) - set.new(host_modules_disabled); if modules:contains("vcard") and modules:contains("vcard_legacy") then log("error", "The mod_vcard_legacy plugin replaces mod_vcard but both are enabled. Please update your config."); modules:remove("vcard"); end 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); end end prosody.events.add_handler("host-activated", load_modules_for_host); prosody.events.add_handler("host-deactivated", function (host) modulemap[host] = nil; end); --- Private helpers --- local function do_unload_module(host, name) local mod = get_module(host, name); if not mod then return nil, "module-not-loaded"; end if module_has_method(mod, "unload") then local ok, err = call_module_method(mod, "unload"); if (not ok) and err then log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err); end end for object, event, handler in mod.module.event_handlers:iter(nil, nil, nil) do object.remove_handler(event, handler); end if mod.module.items then -- remove items local events = (host == "*" and prosody.events) or hosts[host].events; for key,t in pairs(mod.module.items) do for i = #t,1,-1 do local value = t[i]; t[i] = nil; events.fire_event("item-removed/"..key, {source = mod.module, item = value}); end end end mod.module.loaded = false; modulemap[host][name] = nil; return true; end local function do_load_module(host, module_name, state) if not (host and module_name) then return nil, "insufficient-parameters"; elseif not hosts[host] and host ~= "*"then return nil, "unknown-host"; end if not modulemap[host] then modulemap[host] = hosts[host].modules; end if modulemap[host][module_name] then if not modulemap["*"][module_name] then log("debug", "%s is already loaded for %s, so not loading again", module_name, host); end return nil, "module-already-loaded"; elseif modulemap["*"][module_name] then local mod = modulemap["*"][module_name]; if module_has_method(mod, "add_host") then local _log = logger.init(host..":"..module_name); local host_module_api = setmetatable({ global = false, host = host, event_handlers = new_multitable(), items = {}; _log = _log, log = function (self, ...) return _log(...); end; --luacheck: ignore 212/self },{ __index = modulemap["*"][module_name].module; }); local host_module = setmetatable({ module = host_module_api }, { __index = mod }); host_module_api.environment = host_module; modulemap[host][module_name] = host_module; local ok, result, module_err = call_module_method(mod, "add_host", host_module_api); if not ok or result == false then modulemap[host][module_name] = nil; return nil, ok and module_err or result; end return host_module; end return nil, "global-module-already-loaded"; end local _log = logger.init(host..":"..module_name); local api_instance = setmetatable({ name = module_name, host = host, _log = _log, log = function (self, ...) return _log(...); end, --luacheck: ignore 212/self event_handlers = new_multitable(), reloading = not not state, saved_state = state~=true and state or nil } , { __index = api }); local pluginenv = setmetatable({ module = api_instance }, { __index = _G }); api_instance.environment = pluginenv; local mod, err, meta = loader:load_code(module_name, nil, pluginenv); if not mod then log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil"); api_instance:set_status("error", "Failed to load (see log)"); return nil, err; end api_instance.path = err; api_instance.meta = meta; local custom_plugins = prosody.paths.installer; if custom_plugins and err:sub(1, #custom_plugins+1) == custom_plugins.."/" then -- Stage 1: Make it work (you are here) -- Stage 2: Make it less hacky (TODO) local manifest = {}; local luarocks_path = custom_plugins.."/lib/luarocks/rocks-"..lua_version; local manifest_filename = luarocks_path.."/manifest"; local load_manifest, err = envload.envloadfile(manifest_filename, manifest); if not load_manifest then -- COMPAT Luarocks 2.x log("debug", "Could not load LuaRocks 3.x manifest, trying 2.x", err); luarocks_path = custom_plugins.."/lib/luarocks/rocks"; manifest_filename = luarocks_path.."/manifest"; load_manifest, err = envload.envloadfile(manifest_filename, manifest); end if not load_manifest then log("error", "Could not load manifest of installed plugins: %s", err, load_manifest); else local ok, err = xpcall(load_manifest, debug_traceback); if not ok then log("error", "Could not load manifest of installed plugins: %s", err); elseif type(manifest.modules) ~= "table" then log("debug", "Expected 'table' but manifest.modules = %q", manifest.modules); log("error", "Can't look up resource path for mod_%s because '%s' does not appear to be a LuaRocks manifest", module_name, manifest_filename); else local versions = manifest.modules["mod_"..module_name]; if type(versions) == "table" and versions[1] then -- Not going to deal with multiple installed versions api_instance.resource_path = luarocks_path.."/"..versions[1]; else log("debug", "mod_%s does not appear in the installation manifest", module_name); end end end end modulemap[host][module_name] = pluginenv; local ok, err = xpcall(mod, debug_traceback); if ok then -- Call module's "load" if module_has_method(pluginenv, "load") then ok, err = call_module_method(pluginenv, "load"); if not ok then log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err or "nil"); api_instance:set_status("warn", "Error during load (see log)"); end end api_instance.reloading, api_instance.saved_state = nil, nil; if api_instance.host == "*" then if not api_instance.global then -- COMPAT w/pre-0.9 if host ~= "*" then log("warn", "mod_%s: Setting module.host = '*' deprecated, call module:set_global() instead", module_name); end api_instance:set_global(); end modulemap[host][module_name] = nil; modulemap[api_instance.host][module_name] = pluginenv; if host ~= api_instance.host and module_has_method(pluginenv, "add_host") then -- Now load the module again onto the host it was originally being loaded on ok, err = do_load_module(host, module_name); end end end if not ok then modulemap[api_instance.host][module_name] = nil; log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil"); api_instance:set_status("warn", "Error during load (see log)"); else api_instance:set_status("core", "Loaded", false); end return ok and pluginenv, err; end local function do_reload_module(host, name) local mod = get_module(host, name); if not mod then return nil, "module-not-loaded"; end local _mod, err = loader:load_code(name); -- checking for syntax errors if not _mod then log("error", "Unable to load module '%s': %s", name or "nil", err or "nil"); return nil, err; end local saved; if module_has_method(mod, "save") then -- FIXME What goes in 'err' here? local ok, ret, err = call_module_method(mod, "save"); -- luacheck: ignore 211/err if ok then saved = ret; else log("warn", "Error saving module '%s:%s' state: %s", host, name, ret); if not config.get(host, "force_module_reload") then log("warn", "Aborting reload due to error, set force_module_reload to ignore this"); return nil, "save-state-failed"; else log("warn", "Continuing with reload (using the force)"); end end end mod.module.reloading = true; do_unload_module(host, name); local ok, err = do_load_module(host, name, saved or true); if ok then mod = get_module(host, name); if module_has_method(mod, "restore") then local ok, err = call_module_method(mod, "restore", saved or {}) if (not ok) and err then log("warn", "Error restoring module '%s' from '%s': %s", name, host, err); end end end return ok and mod, err; end --- Public API --- -- Load a module and fire module-loaded event function load(host, name) local mod, err = do_load_module(host, name); if mod then (hosts[mod.module.host] or prosody).events.fire_event("module-loaded", { module = name, host = mod.module.host }); end return mod, err; end -- Unload a module and fire module-unloaded function unload(host, name) local ok, err = do_unload_module(host, name); if ok then (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host }); end return ok, err; end function reload(host, name) local mod, err = do_reload_module(host, name); if mod then modulemap[host][name].module.reloading = true; (hosts[host] or prosody).events.fire_event("module-reloaded", { module = name, host = host }); mod.module.reloading = nil; elseif not is_loaded(host, name) then (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host }); end return mod, err; end function get_module(host, name) return modulemap[host] and modulemap[host][name]; end function get_items(key, host) local result = {}; local modules = modulemap[host]; if not key or not host or not modules then return nil; end for _, module in pairs(modules) do local mod = module.module; if mod.items and mod.items[key] then for _, value in ipairs(mod.items[key]) do t_insert(result, value); end end end return result; end function get_modules(host) return modulemap[host]; end function is_loaded(host, name) return modulemap[host] and modulemap[host][name] and true; end function module_has_method(module, method) return type(rawget(module.module, method)) == "function"; end function call_module_method(module, method, ...) local f = rawget(module.module, method); if type(f) == "function" then 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; reload = reload; get_module = get_module; get_items = get_items; get_modules = get_modules; is_loaded = is_loaded; module_has_method = module_has_method; call_module_method = call_module_method; loader = loader; }; prosody-0.12.4/core/PaxHeaders/portmanager.lua0000644000000000000000000000012014475665104016336 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.620765658 prosody-0.12.4/core/portmanager.lua0000644000175000017500000002620614475665104020551 0ustar00prosodyprosody00000000000000local config = require "core.configmanager"; local certmanager = require "core.certmanager"; local server = require "net.server"; local socket = require "socket"; local log = require "util.logger".init("portmanager"); local multitable = require "util.multitable"; local set = require "util.set"; local table = table; local setmetatable, rawset, rawget = setmetatable, rawset, rawget; local type, tonumber, ipairs = type, tonumber, ipairs; local pairs = pairs; local prosody = prosody; local fire_event = prosody.events.fire_event; local _ENV = nil; -- luacheck: std none --- Config local default_interfaces = { }; local default_local_interfaces = { }; if config.get("*", "use_ipv4") ~= false then table.insert(default_interfaces, "*"); table.insert(default_local_interfaces, "127.0.0.1"); end if socket.tcp6 and config.get("*", "use_ipv6") ~= false then table.insert(default_interfaces, "::"); table.insert(default_local_interfaces, "::1"); end local default_mode = config.get("*", "network_default_read_size") or 4096; --- Private state -- service_name -> { service_info, ... } local services = setmetatable({}, { __index = function (t, k) rawset(t, k, {}); return rawget(t, k); end }); -- service_name, interface (string), port (number) local active_services = multitable.new(); --- Private helpers local function error_to_friendly_message(service_name, port, err) --luacheck: ignore 212/service_name local friendly_message = err; if err:match(" in use") then -- FIXME: Use service_name here if port == 5222 or port == 5223 or port == 5269 then friendly_message = "check that Prosody or another XMPP server is " .."not already running and using this port"; elseif port == 80 or port == 81 then friendly_message = "check that a HTTP server is not already using " .."this port"; elseif port == 5280 then friendly_message = "check that Prosody or a BOSH connection manager " .."is not already running"; else friendly_message = "this port is in use by another application"; end elseif err:match("permission") then friendly_message = "Prosody does not have sufficient privileges to use this port"; end return friendly_message; end local function get_port_ssl_ctx(port, interface, config_prefix, service_info) local global_ssl_config = config.get("*", "ssl") or {}; local prefix_ssl_config = config.get("*", config_prefix.."ssl") or global_ssl_config; log("debug", "Creating context for direct TLS service %s on port %d", service_info.name, port); local ssl, err, cfg = certmanager.create_context(service_info.name.." port "..port, "server", prefix_ssl_config[interface], prefix_ssl_config[port], prefix_ssl_config, service_info.ssl_config or {}, global_ssl_config[interface], global_ssl_config[port]); return ssl, cfg, err; end --- Public API local function activate(service_name) local service_info = services[service_name][1]; if not service_info then return nil, "Unknown service: "..service_name; end local listener = service_info.listener; local config_prefix = (service_info.config_prefix or service_name).."_"; if config_prefix == "_" then config_prefix = ""; end local bind_interfaces = config.get("*", config_prefix.."interfaces") or config.get("*", config_prefix.."interface") -- COMPAT w/pre-0.9 or (service_info.private and (config.get("*", "local_interfaces") or default_local_interfaces)) or config.get("*", "interfaces") or config.get("*", "interface") -- COMPAT w/pre-0.9 or listener.default_interface -- COMPAT w/pre0.9 or default_interfaces bind_interfaces = set.new(type(bind_interfaces)~="table" and {bind_interfaces} or bind_interfaces); local bind_ports = config.get("*", config_prefix.."ports") or service_info.default_ports or {service_info.default_port or listener.default_port -- COMPAT w/pre-0.9 } bind_ports = set.new(type(bind_ports) ~= "table" and { bind_ports } or bind_ports ); local mode = listener.default_mode or default_mode; local hooked_ports = {}; for interface in bind_interfaces do for port in bind_ports do local port_number = tonumber(port); if not port_number then log("error", "Invalid port number specified for service '%s': %s", service_info.name, port); elseif #active_services:search(nil, interface, port_number) > 0 then log("error", "Multiple services configured to listen on the same port ([%s]:%d): %s, %s", interface, port, active_services:search(nil, interface, port)[1][1].service.name or "", service_name or ""); else local ssl, cfg, err; -- Create SSL context for this service/port if service_info.encryption == "ssl" then ssl, cfg, err = get_port_ssl_ctx(port, interface, config_prefix, service_info); if not ssl then log("error", "Error binding encrypted port for %s: %s", service_info.name, error_to_friendly_message(service_name, port_number, err) or "unknown error"); end end if not err then -- Start listening on interface+port local handler, err = server.listen(interface, port_number, listener, { read_size = mode, tls_ctx = ssl, tls_direct = service_info.encryption == "ssl"; sni_hosts = {}, }); if not handler then log("error", "Failed to open server port %d on %s, %s", port_number, interface, error_to_friendly_message(service_name, port_number, err)); else table.insert(hooked_ports, "["..interface.."]:"..port_number); log("debug", "Added listening service %s to [%s]:%d", service_name, interface, port_number); active_services:add(service_name, interface, port_number, { server = handler; service = service_info; tls_cfg = cfg; }); end end end end end log("info", "Activated service '%s' on %s", service_name, #hooked_ports == 0 and "no ports" or table.concat(hooked_ports, ", ")); return true; end local close; -- forward declaration local function deactivate(service_name, service_info) for name, interface, port, n, active_service --luacheck: ignore 213/name 213/n in active_services:iter(service_name or service_info and service_info.name, nil, nil, nil) do if service_info == nil or active_service.service == service_info then close(interface, port); end end log("info", "Deactivated service '%s'", service_name or service_info.name); end local function register_service(service_name, service_info) table.insert(services[service_name], service_info); if not active_services:get(service_name) and prosody.process_type == "prosody" then log("debug", "No active service for %s, activating...", service_name); local ok, err = activate(service_name); if not ok then log("error", "Failed to activate service '%s': %s", service_name, err or "unknown error"); end end fire_event("service-added", { name = service_name, service = service_info }); return true; end local function unregister_service(service_name, service_info) log("debug", "Unregistering service: %s", service_name); local service_info_list = services[service_name]; for i, service in ipairs(service_info_list) do if service == service_info then table.remove(service_info_list, i); end end deactivate(nil, service_info); if #service_info_list > 0 then -- Other services registered with this name activate(service_name); -- Re-activate with the next available one end fire_event("service-removed", { name = service_name, service = service_info }); end local get_service_at -- forward declaration function close(interface, port) local service, service_server = get_service_at(interface, port); if not service then return false, "port-not-open"; end service_server:close(); active_services:remove(service.name, interface, port); log("debug", "Removed listening service %s from [%s]:%d", service.name, interface, port); return true; end function get_service_at(interface, port) local data = active_services:search(nil, interface, port); if not data or not data[1] or not data[1][1] then return nil, "not-found"; end data = data[1][1]; return data.service, data.server; end local function get_service(service_name) return (services[service_name] or {})[1]; end local function get_active_services() return active_services; end local function get_registered_services() return services; end -- Event handlers local function add_sni_host(host, service) log("debug", "Gathering certificates for SNI for host %s, %s service", host, service or "default"); for name, interface, port, n, active_service --luacheck: ignore 213 in active_services:iter(service, nil, nil, nil) do if active_service.server.hosts and active_service.tls_cfg then local config_prefix = (active_service.config_prefix or name).."_"; if config_prefix == "_" then config_prefix = ""; end local prefix_ssl_config = config.get(host, config_prefix.."ssl"); local alternate_host = name and config.get(host, name.."_host"); if not alternate_host and name == "https" then -- TODO should this be some generic thing? e.g. in the service definition alternate_host = config.get(host, "http_host"); end local autocert = certmanager.find_host_cert(alternate_host or host); -- luacheck: ignore 211/cfg local ssl, err, cfg = certmanager.create_context(host, "server", prefix_ssl_config, autocert, active_service.tls_cfg); if ssl then active_service.server.hosts[alternate_host or host] = ssl; else log("error", "Error creating TLS context for SNI host %s: %s", host, err); end end end end prosody.events.add_handler("item-added/net-provider", function (event) local item = event.item; register_service(item.name, item); for host in pairs(prosody.hosts) do add_sni_host(host, item.name); end end); prosody.events.add_handler("item-removed/net-provider", function (event) local item = event.item; unregister_service(item.name, item); end); prosody.events.add_handler("host-activated", add_sni_host); prosody.events.add_handler("host-deactivated", function (host) for name, interface, port, n, active_service --luacheck: ignore 213 in active_services:iter(nil, nil, nil, nil) do if active_service.tls_cfg then active_service.server.hosts[host] = nil; end end end); prosody.events.add_handler("config-reloaded", function () for service_name, interface, port, _, active_service in active_services:iter(nil, nil, nil, nil) do if active_service.tls_cfg then local service_info = active_service.service; local config_prefix = (service_info.config_prefix or service_name).."_"; if config_prefix == "_" then config_prefix = ""; end local ssl, cfg, err = get_port_ssl_ctx(port, interface, config_prefix, service_info); if ssl then active_service.server:set_sslctx(ssl); active_service.tls_cfg = cfg; else log("error", "Error reloading certificate for encrypted port for %s: %s", service_info.name, error_to_friendly_message(service_name, port, err) or "unknown error"); end end end for host in pairs(prosody.hosts) do add_sni_host(host, nil); end end, -1); return { activate = activate; deactivate = deactivate; register_service = register_service; unregister_service = unregister_service; close = close; get_service_at = get_service_at; get_service = get_service; get_active_services = get_active_services; get_registered_services = get_registered_services; }; prosody-0.12.4/core/PaxHeaders/rostermanager.lua0000644000000000000000000000012014475665104016670 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.624765619 prosody-0.12.4/core/rostermanager.lua0000644000175000017500000003356014475665104021104 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- luacheck: globals prosody.bare_sessions.?.roster local log = require "util.logger".init("rostermanager"); local new_id = require "util.id".short; local new_cache = require "util.cache".new; local pairs = pairs; local tostring = tostring; local type = type; local hosts = prosody.hosts; local bare_sessions = prosody.bare_sessions; local um_user_exists = require "core.usermanager".user_exists; local st = require "util.stanza"; local storagemanager = require "core.storagemanager"; local _ENV = nil; -- luacheck: std none local save_roster; -- forward declaration local function add_to_roster(session, jid, item) if session.roster then local old_item = session.roster[jid]; session.roster[jid] = item; if save_roster(session.username, session.host, nil, jid) then return true; else session.roster[jid] = old_item; return nil, "wait", "internal-server-error", "Unable to save roster"; end else return nil, "auth", "not-authorized", "Session's roster not loaded"; end end local function remove_from_roster(session, jid) if session.roster then local old_item = session.roster[jid]; session.roster[jid] = nil; if save_roster(session.username, session.host, nil, jid) then return true; else session.roster[jid] = old_item; return nil, "wait", "internal-server-error", "Unable to save roster"; end else return nil, "auth", "not-authorized", "Session's roster not loaded"; end end local function roster_push(username, host, jid) local roster = jid and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster; if roster then local item = hosts[host].sessions[username].roster[jid]; local stanza = st.iq({type="set", 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}); for group in pairs(item.groups) do stanza:tag("group"):text(group):up(); end else stanza:tag("item", {jid = jid, subscription = "remove"}); end stanza:up(); -- move out from item stanza:up(); -- move out from stanza -- stanza ready for _, session in pairs(hosts[host].sessions[username].sessions) do if session.interested then session.send(stanza); end end end end local function roster_metadata(roster, err) local metadata = roster[false]; if not metadata then metadata = { broken = err or nil }; roster[false] = metadata; end if roster.pending and type(roster.pending.subscription) ~= "string" then metadata.pending = roster.pending; roster.pending = nil; elseif not metadata.pending then metadata.pending = {}; end return metadata; end local function load_roster(username, host) local jid = username.."@"..host; log("debug", "load_roster: asked for: %s", jid); local user = bare_sessions[jid]; local roster; if user then roster = user.roster; if roster then return roster; end log("debug", "load_roster: loading for new user: %s", jid); else -- Attempt to load roster for non-loaded user log("debug", "load_roster: loading for offline user: %s", jid); end local roster_cache = hosts[host] and hosts[host].roster_cache; if not roster_cache then if hosts[host] then roster_cache = new_cache(1024); hosts[host].roster_cache = roster_cache; end else roster = roster_cache:get(jid); if roster then log("debug", "load_roster: cache hit"); roster_cache:set(jid, roster); if user then user.roster = roster; end return roster; else log("debug", "load_roster: cache miss, loading from storage"); end end local roster_store = storagemanager.open(host, "roster", "keyval"); local data, err = roster_store:get(username); roster = data or {}; if user then user.roster = roster; end local legacy_pending = roster.pending and type(roster.pending.subscription) ~= "string"; roster_metadata(roster, err); if legacy_pending then -- Due to map store use, we need to manually delete this entry log("debug", "Removing legacy 'pending' entry"); if not save_roster(username, host, roster, "pending") then log("warn", "Could not remove legacy 'pending' entry"); end end if roster[jid] then roster[jid] = nil; log("debug", "Roster for %s had a self-contact, removing", jid); if not save_roster(username, host, roster, jid) then log("warn", "Could not remove self-contact from roster for %s", jid); end end if not err then hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster }); end if roster_cache and not user then log("debug", "load_roster: caching loaded roster"); roster_cache:set(jid, roster); end return roster, err; end function save_roster(username, host, roster, jid) if not um_user_exists(username, host) then log("debug", "not saving roster for %s@%s: the user doesn't exist", username, host); return nil; end log("debug", "save_roster: saving roster for %s@%s, (%s)", username, host, jid or "all contacts"); if not roster then roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster; --if not roster then -- --roster = load_roster(username, host); -- return true; -- roster unchanged, no reason to save --end end if roster then local metadata = roster_metadata(roster); if metadata.version ~= true then metadata.version = (metadata.version or 0) + 1; end if metadata.broken then return nil, "Not saving broken roster" end if jid == nil then local roster_store = storagemanager.open(host, "roster", "keyval"); return roster_store:set(username, roster); else local roster_store = storagemanager.open(host, "roster", "map"); return roster_store:set_keys(username, { [false] = metadata, [jid] = roster[jid] or roster_store.remove }); end end log("warn", "save_roster: user had no roster to save"); return nil; end local function process_inbound_subscription_approval(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if item and item.ask then if item.subscription == "none" then item.subscription = "to"; else -- subscription == from item.subscription = "both"; end item.ask = nil; return save_roster(username, host, roster, jid); end end local is_contact_pending_out -- forward declaration local function process_inbound_subscription_cancellation(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; local changed = nil; if is_contact_pending_out(username, host, jid) then item.ask = nil; changed = true; end if item then if item.subscription == "to" then item.subscription = "none"; changed = true; elseif item.subscription == "both" then item.subscription = "from"; changed = true; end end if changed then return save_roster(username, host, roster, jid); end end local is_contact_pending_in -- forward declaration local function process_inbound_unsubscribe(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; local changed = nil; if is_contact_pending_in(username, host, jid) then roster[false].pending[jid] = nil; changed = true; end if item then if item.subscription == "from" then item.subscription = "none"; changed = true; elseif item.subscription == "both" then item.subscription = "to"; changed = true; end end if changed then return save_roster(username, host, roster, jid); end end local function _get_online_roster_subscription(jidA, jidB) local user = bare_sessions[jidA]; local item = user and (user.roster[jidB] or { subscription = "none" }); return item and item.subscription; end local function is_contact_subscribed(username, host, jid) do local selfjid = username.."@"..host; local user_subscription = _get_online_roster_subscription(selfjid, jid); if user_subscription then return (user_subscription == "both" or user_subscription == "from"); end local contact_subscription = _get_online_roster_subscription(jid, selfjid); if contact_subscription then return (contact_subscription == "both" or contact_subscription == "to"); end end local roster, err = load_roster(username, host); local item = roster[jid]; return item and (item.subscription == "from" or item.subscription == "both"), err; end local function is_user_subscribed(username, host, jid) do local selfjid = username.."@"..host; local user_subscription = _get_online_roster_subscription(selfjid, jid); if user_subscription then return (user_subscription == "both" or user_subscription == "to"); end local contact_subscription = _get_online_roster_subscription(jid, selfjid); if contact_subscription then return (contact_subscription == "both" or contact_subscription == "from"); end end local roster, err = load_roster(username, host); local item = roster[jid]; return item and (item.subscription == "to" or item.subscription == "both"), err; end function is_contact_pending_in(username, host, jid) local roster = load_roster(username, host); return roster[false].pending[jid] ~= nil; end local function set_contact_pending_in(username, host, jid, stanza) local roster = load_roster(username, host); local item = roster[jid]; if item and (item.subscription == "from" or item.subscription == "both") then return; -- false end roster[false].pending[jid] = st.is_stanza(stanza) and st.preserialize(stanza) or true; return save_roster(username, host, roster, jid); end function is_contact_pending_out(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; return item and item.ask; end local function is_contact_preapproved(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; return item and (item.approved == "true"); end local function set_contact_pending_out(username, host, jid) -- subscribe local roster = load_roster(username, host); local item = roster[jid]; if item and (item.ask or item.subscription == "to" or item.subscription == "both") then return true; end if not item then item = {subscription = "none", groups = {}}; roster[jid] = item; end item.ask = "subscribe"; log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid); return save_roster(username, host, roster, jid); end local function unsubscribe(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if not item then return false; end if (item.subscription == "from" or item.subscription == "none") and not item.ask then return true; end item.ask = nil; if item.subscription == "both" then item.subscription = "from"; elseif item.subscription == "to" then item.subscription = "none"; end return save_roster(username, host, roster, jid); end local function subscribed(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if is_contact_pending_in(username, host, jid) then if not item then -- FIXME should roster item be auto-created? item = {subscription = "none", groups = {}}; roster[jid] = item; end if item.subscription == "none" then item.subscription = "from"; else -- subscription == to item.subscription = "both"; end roster[false].pending[jid] = nil; return save_roster(username, host, roster, jid); elseif not item or item.subscription == "none" or item.subscription == "to" then -- Contact is not subscribed and has not sent a subscription request. -- We store a pre-approval as per RFC6121 3.4 if not item then item = {subscription = "none", groups = {}}; roster[jid] = item; end item.approved = "true"; log("debug", "Storing preapproval for %s", jid); return save_roster(username, host, roster, jid); end end local function unsubscribed(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; local pending = is_contact_pending_in(username, host, jid); if pending then roster[false].pending[jid] = nil; end local is_subscribed; if item then if item.subscription == "from" then item.subscription = "none"; is_subscribed = true; elseif item.subscription == "both" then item.subscription = "to"; is_subscribed = true; end end local success = (pending or is_subscribed) and save_roster(username, host, roster, jid); return success, pending, is_subscribed; end local function process_outbound_subscription_request(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if item and (item.subscription == "none" or item.subscription == "from") then item.ask = "subscribe"; return save_roster(username, host, roster, jid); end end --[[function process_outbound_subscription_approval(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if item and (item.subscription == "none" or item.subscription == "from" then item.ask = "subscribe"; return save_roster(username, host, roster); end end]] return { add_to_roster = add_to_roster; remove_from_roster = remove_from_roster; roster_push = roster_push; load_roster = load_roster; save_roster = save_roster; process_inbound_subscription_approval = process_inbound_subscription_approval; process_inbound_subscription_cancellation = process_inbound_subscription_cancellation; process_inbound_unsubscribe = process_inbound_unsubscribe; is_contact_subscribed = is_contact_subscribed; is_user_subscribed = is_user_subscribed; is_contact_pending_in = is_contact_pending_in; set_contact_pending_in = set_contact_pending_in; is_contact_pending_out = is_contact_pending_out; set_contact_pending_out = set_contact_pending_out; is_contact_preapproved = is_contact_preapproved; unsubscribe = unsubscribe; subscribed = subscribed; unsubscribed = unsubscribed; process_outbound_subscription_request = process_outbound_subscription_request; }; prosody-0.12.4/core/PaxHeaders/s2smanager.lua0000644000000000000000000000012014475665104016061 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.624765619 prosody-0.12.4/core/s2smanager.lua0000644000175000017500000001000714475665104020264 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local hosts = prosody.hosts; local pairs, setmetatable = pairs, setmetatable; local logger_init = require "util.logger".init; local sessionlib = require "util.session"; local log = logger_init("s2smanager"); local prosody = _G.prosody; local incoming_s2s = {}; _G.incoming_s2s = incoming_s2s; prosody.incoming_s2s = incoming_s2s; local fire_event = prosody.events.fire_event; local _ENV = nil; -- luacheck: std none local function new_incoming(conn) local host_session = sessionlib.new("s2sin"); sessionlib.set_id(host_session); sessionlib.set_logger(host_session); sessionlib.set_conn(host_session, conn); host_session.direction = "incoming"; host_session.incoming = true; host_session.hosts = {}; incoming_s2s[host_session] = true; return host_session; end local function new_outgoing(from_host, to_host) local host_session = sessionlib.new("s2sout"); sessionlib.set_id(host_session); sessionlib.set_logger(host_session); host_session.to_host = to_host; host_session.from_host = from_host; host_session.host = from_host; host_session.notopen = true; host_session.direction = "outgoing"; host_session.outgoing = true; host_session.hosts = {}; hosts[from_host].s2sout[to_host] = host_session; return host_session; end local resting_session = { -- Resting, not dead destroyed = true; type = "s2s_destroyed"; direction = "destroyed"; open_stream = function (session) session.log("debug", "Attempt to open stream on resting session"); end; close = function (session) session.log("debug", "Attempt to close already-closed session"); end; reset_stream = function (session) session.log("debug", "Attempt to reset stream of already-closed session"); end; filter = function (type, data) return data; end; --luacheck: ignore 212/type }; resting_session.__index = resting_session; local function retire_session(session, reason) local log = session.log or log; --luacheck: ignore 431/log for k in pairs(session) do if k ~= "log" and k ~= "id" and k ~= "conn" then session[k] = nil; end end session.destruction_reason = reason; function session.send(data) log("debug", "Discarding data sent to resting session: %s", data); end function session.data(data) log("debug", "Discarding data received from resting session: %s", data); end session.thread = { run = function (_, data) return session.data(data) end }; session.sends2s = session.send; return setmetatable(session, resting_session); end local function destroy_session(session, reason, bounce_reason) if session.destroyed then return; end local log = session.log or log; log("debug", "Destroying %s session %s->%s%s%s", session.direction, session.from_host, session.to_host, reason and ": " or "", reason or ""); if session.direction == "outgoing" then hosts[session.from_host].s2sout[session.to_host] = nil; session:bounce_sendq(bounce_reason or reason); elseif session.direction == "incoming" then if session.outgoing and hosts[session.to_host].s2sout[session.from_host] == session then hosts[session.to_host].s2sout[session.from_host] = nil; end incoming_s2s[session] = nil; end local event_data = { session = session, reason = reason }; fire_event("s2s-destroyed", event_data); if session.type == "s2sout" then fire_event("s2sout-destroyed", event_data); if hosts[session.from_host] then hosts[session.from_host].events.fire_event("s2sout-destroyed", event_data); end elseif session.type == "s2sin" then fire_event("s2sin-destroyed", event_data); if hosts[session.to_host] then hosts[session.to_host].events.fire_event("s2sin-destroyed", event_data); end end retire_session(session, reason); -- Clean session until it is GC'd return true; end return { incoming_s2s = incoming_s2s; new_incoming = new_incoming; new_outgoing = new_outgoing; retire_session = retire_session; destroy_session = destroy_session; }; prosody-0.12.4/core/PaxHeaders/sessionmanager.lua0000644000000000000000000000012014475665104017035 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.624765619 prosody-0.12.4/core/sessionmanager.lua0000644000175000017500000002204014475665104021240 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- luacheck: globals prosody.full_sessions prosody.bare_sessions local tostring, setmetatable = tostring, setmetatable; local pairs, next= pairs, next; local hosts = prosody.hosts; local full_sessions = prosody.full_sessions; local bare_sessions = prosody.bare_sessions; local logger = require "util.logger"; local log = logger.init("sessionmanager"); local rm_load_roster = require "core.rostermanager".load_roster; local config_get = require "core.configmanager".get; local resourceprep = require "util.encodings".stringprep.resourceprep; local nodeprep = require "util.encodings".stringprep.nodeprep; local generate_identifier = require "util.id".short; local sessionlib = require "util.session"; 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 = sessionlib.new("c2s"); sessionlib.set_id(session); sessionlib.set_logger(session); sessionlib.set_conn(session, conn); session.conntime = gettime(); local filter = initialize_filters(session); local w = conn.write; function session.rawsend(t) t = filter("bytes/out", tostring(t)); if t then local ret, err = w(conn, t); if not ret then session.log("debug", "Error writing to connection: %s", err); return false, err; end end return true; end session.send = function (t) session.log("debug", "Sending[%s]: %s", session.type, t.top_tag and t:top_tag() or t:match("^[^>]*>?")); if t.name then t = filter("stanzas/out", t); end if t then return session.rawsend(t); end return true; end session.ip = conn:ip(); local conn_name = "c2s"..tostring(session):match("[a-f0-9]+$"); session.log = logger.init(conn_name); return session; end local resting_session = { -- Resting, not dead destroyed = true; type = "c2s_destroyed"; close = function (session) session.log("debug", "Attempt to close already-closed session"); end; filter = function (type, data) return data; end; --luacheck: ignore 212/type }; resting_session.__index = resting_session; local function retire_session(session) local log = session.log or log; --luacheck: ignore 431/log for k in pairs(session) do if k ~= "log" and k ~= "id" then session[k] = nil; end end function session.send(data) log("debug", "Discarding data sent to resting session: %s", data); return false; end function session.rawsend(data) log("debug", "Discarding data sent to resting session: %s", data); return false; end function session.data(data) log("debug", "Discarding data received from resting session: %s", data); end session.thread = { run = function (_, data) return session.data(data) end }; return setmetatable(session, resting_session); end local function destroy_session(session, err) if session.destroyed then return; end -- Remove session/resource from user's session list if session.full_jid then local host_session = hosts[session.host]; -- Allow plugins to prevent session destruction if host_session.events.fire_event("pre-resource-unbind", {session=session, error=err}) then (session.log or log)("debug", "Resource unbind prevented by module"); return; end (session.log or log)("debug", "Unbinding resource for %s (%s@%s)%s", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)", err and (": "..err) or ""); session.destroyed = true; -- Past this point the session is DOOMED! host_session.sessions[session.username].sessions[session.resource] = nil; full_sessions[session.full_jid] = nil; if not next(host_session.sessions[session.username].sessions) then log("debug", "All resources of %s are now offline", session.username); host_session.sessions[session.username] = nil; bare_sessions[session.username..'@'..session.host] = nil; end host_session.events.fire_event("resource-unbind", {session=session, error=err}); else (session.log or log)("debug", "Destroying unbound session for <%s@%s>%s", session.username or "(unknown)", session.host or "(unknown)", err and (": "..err) or ""); end retire_session(session); end local function make_authenticated(session, username, scope) username = nodeprep(username); if not username or #username == 0 then return nil, "Invalid username"; end session.username = username; if session.type == "c2s_unauthed" then session.type = "c2s_unbound"; end session.auth_scope = scope; session.log("info", "Authenticated as %s@%s", username, session.host or "(unknown)"); return true; end -- returns true, nil on success -- returns nil, err_type, err, err_message on failure local function bind_resource(session, resource) if not session.username then return nil, "auth", "not-authorized", "Cannot bind resource before authentication"; end if session.resource then return nil, "cancel", "not-allowed", "Cannot bind multiple resources on a single connection"; end -- We don't support binding multiple resources local event_payload = { session = session, resource = resource }; if hosts[session.host].events.fire_event("pre-resource-bind", event_payload) == false then local err = event_payload.error; if err then return nil, err.type, err.condition, err.text; end return nil, "cancel", "not-allowed"; else -- In case a plugin wants to poke at it resource = event_payload.resource; end resource = resourceprep(resource or "", true); 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 local sessions = { sessions = {} }; hosts[session.host].sessions[session.username] = sessions; bare_sessions[session.username..'@'..session.host] = sessions; else local sessions = hosts[session.host].sessions[session.username].sessions; if sessions[resource] then -- Resource conflict local policy = config_get(session.host, "conflict_resolve"); local increment; if policy == "random" then resource = generate_identifier(); increment = true; elseif policy == "increment" then increment = true; -- TODO ping old resource elseif policy == "kick_new" then return nil, "cancel", "conflict", "Resource already exists"; else -- if policy == "kick_old" then sessions[resource]:close { condition = "conflict"; text = "Replaced by new connection"; }; if not next(sessions) then hosts[session.host].sessions[session.username] = { sessions = sessions }; bare_sessions[session.username.."@"..session.host] = hosts[session.host].sessions[session.username]; end end if increment and sessions[resource] then local count = 1; while sessions[resource.."#"..count] do count = count + 1; end resource = resource.."#"..count; end end end session.resource = resource; session.full_jid = session.username .. '@' .. session.host .. '/' .. resource; hosts[session.host].sessions[session.username].sessions[resource] = session; full_sessions[session.full_jid] = session; if session.type == "c2s_unbound" then session.type = "c2s"; end local err; session.roster, err = rm_load_roster(session.username, session.host); if err then -- FIXME: Why is all this rollback down here, instead of just doing the roster test up above? full_sessions[session.full_jid] = nil; hosts[session.host].sessions[session.username].sessions[resource] = nil; session.full_jid = nil; session.resource = nil; if session.type == "c2s" then session.type = "c2s_unbound"; end if next(bare_sessions[session.username..'@'..session.host].sessions) == nil then bare_sessions[session.username..'@'..session.host] = nil; hosts[session.host].sessions[session.username] = nil; end session.log("error", "Roster loading failed: %s", err); return nil, "cancel", "internal-server-error", "Error loading roster"; end hosts[session.host].events.fire_event("resource-bind", {session=session}); return true; end local function send_to_available_resources(username, host, stanza) local jid = username.."@"..host; local count = 0; local user = bare_sessions[jid]; if user then for _, session in pairs(user.sessions) do if session.presence then session.send(stanza); count = count + 1; end end end return count; end local function send_to_interested_resources(username, host, stanza) local jid = username.."@"..host; local count = 0; local user = bare_sessions[jid]; if user then for _, session in pairs(user.sessions) do if session.interested then session.send(stanza); count = count + 1; end end end return count; end return { new_session = new_session; retire_session = retire_session; destroy_session = destroy_session; make_authenticated = make_authenticated; bind_resource = bind_resource; send_to_available_resources = send_to_available_resources; send_to_interested_resources = send_to_interested_resources; }; prosody-0.12.4/core/PaxHeaders/stanza_router.lua0000644000000000000000000000012014475665104016717 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.624765619 prosody-0.12.4/core/stanza_router.lua0000644000175000017500000002120214475665104021121 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local log = require "util.logger".init("stanzarouter") local hosts = _G.prosody.hosts; local tostring = tostring; local st = require "util.stanza"; local jid_split = require "util.jid".split; local jid_host = require "util.jid".host; local jid_prepped_split = require "util.jid".prepped_split; local full_sessions = _G.prosody.full_sessions; local bare_sessions = _G.prosody.bare_sessions; local core_post_stanza, core_process_stanza, 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; if xmlns == "jabber:client" and valid_stanzas[name] then -- A normal stanza local st_type = stanza.attr.type; if st_type == "error" or (name == "iq" and st_type == "result") then if st_type == "error" then local err_type, err_condition, err_message = stanza:get_error(); -- luacheck: ignore 211/err_message log("debug", "Discarding unhandled error %s (%s, %s) from %s: %s", name, err_type, err_condition or "unknown condition", origin_type, stanza:top_tag()); else log("debug", "Discarding %s from %s of type: %s", name, origin_type, st_type or ''); end return; end if name == "iq" and (st_type == "get" or st_type == "set") and stanza.tags[1] then xmlns = stanza.tags[1].attr.xmlns or "jabber:client"; end log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin_type, name, xmlns); if origin.send then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end else log("warn", "Unhandled %s stream element or stanza: %s; xmlns=%s: %s", origin_type, name, xmlns, tostring(stanza)); -- we didn't handle it origin:close("unsupported-stanza-type"); end end local iq_types = { set=true, get=true, result=true, error=true }; function core_process_stanza(origin, stanza) (origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag()) if origin.type == "c2s" and not stanza.attr.xmlns then local name, st_type = stanza.name, stanza.attr.type; if st_type == "error" and #stanza.tags == 0 then return handle_unhandled_stanza(origin.host, origin, stanza); end if name == "iq" then if not iq_types[st_type] then origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid IQ type")); return; elseif not stanza.attr.id then origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing required 'id' attribute")); return; elseif (st_type == "set" or st_type == "get") and (#stanza.tags ~= 1) then origin.send(st.error_reply(stanza, "modify", "bad-request", "Incorrect number of children for IQ stanza")); return; end end -- TODO also, stanzas should be returned to their original state before the function ends stanza.attr.from = origin.full_jid; end local to, xmlns = stanza.attr.to, stanza.attr.xmlns; local from = stanza.attr.from; local node, host, resource; local from_node, from_host, from_resource; local to_bare, from_bare; if to then if full_sessions[to] or bare_sessions[to] or hosts[to] then host = jid_host(to); else node, host, resource = jid_prepped_split(to); if not host then log("warn", "Received stanza with invalid destination JID: %s", to); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The destination address is invalid: "..to)); end return; end to_bare = node and (node.."@"..host) or host; -- bare JID if resource then to = to_bare.."/"..resource; else to = to_bare; end stanza.attr.to = to; end end if from and not origin.full_jid then -- We only stamp the 'from' on c2s stanzas, so we still need to check validity from_node, from_host, from_resource = jid_prepped_split(from); if not from_host then log("warn", "Received stanza with invalid source JID: %s", from); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The source address is invalid: "..from)); end return; end from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID if from_resource then from = from_bare.."/"..from_resource; else from = from_bare; end stanza.attr.from = from; end if (origin.type == "s2sin" or origin.type == "s2sout" or origin.type == "c2s" or origin.type == "component") and xmlns == nil then if (origin.type == "s2sin" or origin.type == "s2sout") and not origin.dummy then local host_status = origin.hosts[from_host]; if not host_status or not host_status.authed then -- remote server trying to impersonate some other server? log("warn", "Received a stanza claiming to be from %s, over a stream authed for %s!", from_host, origin.from_host); origin:close("not-authorized"); return; elseif not hosts[host] then log("warn", "Remote server %s sent us a stanza for %s, closing stream", origin.from_host, host); origin:close("host-unknown"); return; end end core_post_stanza(origin, stanza, origin.full_jid); else local h = hosts[stanza.attr.to or origin.host or origin.to_host]; if h then local event; if xmlns == nil then if stanza.name == "iq" and (stanza.attr.type == "set" or stanza.attr.type == "get") and stanza.tags[1] and stanza.tags[1].attr.xmlns then event = "stanza/iq/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name; else event = "stanza/"..stanza.name; end else event = "stanza/"..xmlns..":"..stanza.name; end if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end end if host and not hosts[host] then host = nil; end -- COMPAT: workaround for a Pidgin bug which sets 'to' to the SRV result handle_unhandled_stanza(host or origin.host or origin.to_host, origin, stanza); end end function core_post_stanza(origin, stanza, preevents) local to = stanza.attr.to; local node, host, resource = jid_split(to); local to_bare = node and (node.."@"..host) or host; -- bare JID local to_type, to_self; if node then if resource then to_type = '/full'; else to_type = '/bare'; if node == origin.username and host == origin.host then stanza.attr.to = nil; to_self = true; end end else if host then to_type = '/host'; else to_type = '/bare'; to_self = true; end end local event_data = {origin=origin, stanza=stanza, to_self=to_self}; if preevents then -- c2s connection local result = hosts[origin.host].events.fire_event("pre-stanza", event_data); if result ~= nil then log("debug", "Stanza rejected by pre-stanza handler: %s", event_data.reason or "unknown reason"); return; end if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing end local h = hosts[to_bare] or hosts[host or origin.host]; if h then if h.events.fire_event(stanza.name..to_type, event_data) then return; end -- do processing if to_self and h.events.fire_event(stanza.name..'/self', event_data) then return; end -- do processing handle_unhandled_stanza(h.host, origin, stanza); else core_route_stanza(origin, stanza); end end function core_route_stanza(origin, stanza) local to_host = jid_host(stanza.attr.to); local from_host = jid_host(stanza.attr.from); -- Auto-detect origin if not specified origin = origin or hosts[from_host]; if not origin then return false; end if hosts[to_host] then -- old stanza routing code removed core_post_stanza(origin, stanza); else local host_session = hosts[from_host]; if not host_session then log("error", "No hosts[from_host] (please report): %s", stanza); else local xmlns = stanza.attr.xmlns; stanza.attr.xmlns = nil; local routed = host_session.events.fire_event("route/remote", { origin = origin, stanza = stanza, from_host = from_host, to_host = to_host }); stanza.attr.xmlns = xmlns; -- reset if not routed then log("debug", "Could not route stanza to remote"); if stanza.attr.type == "error" or (stanza.name == "iq" and stanza.attr.type == "result") then return; end core_route_stanza(host_session, st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote domains is not enabled")); end end end end --luacheck: ignore 122/prosody prosody.core_process_stanza = core_process_stanza; prosody.core_post_stanza = core_post_stanza; prosody.core_route_stanza = core_route_stanza; prosody-0.12.4/core/PaxHeaders/statsmanager.lua0000644000000000000000000000012014475665104016510 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.624765619 prosody-0.12.4/core/statsmanager.lua0000644000175000017500000002101214475665104020711 0ustar00prosodyprosody00000000000000 local config = require "core.configmanager"; local log = require "util.logger".init("stats"); local timer = require "util.timer"; local fire_event = prosody.events.fire_event; local array = require "util.array"; local timed = require "util.openmetrics".timed; local stats_interval_config = config.get("*", "statistics_interval"); local stats_interval = tonumber(stats_interval_config); if stats_interval_config and not stats_interval and stats_interval_config ~= "manual" then log("error", "Invalid 'statistics_interval' setting, statistics will be disabled"); end local stats_provider_name; local stats_provider_config = config.get("*", "statistics"); local stats_provider = stats_provider_config; if not stats_provider and stats_interval then stats_provider = "internal"; elseif stats_provider and not stats_interval then stats_interval = 60; end if stats_interval_config == "manual" then stats_interval = nil; end local builtin_providers = { internal = "util.statistics"; statsd = "util.statsd"; }; local stats, stats_err = false, nil; if stats_provider then if stats_provider:sub(1,1) == ":" then stats_provider = stats_provider:sub(2); stats_provider_name = "external "..stats_provider; elseif stats_provider then stats_provider_name = "built-in "..stats_provider; stats_provider = builtin_providers[stats_provider]; if not stats_provider then log("error", "Unrecognized statistics provider '%s', statistics will be disabled", stats_provider_config); end end local have_stats_provider, stats_lib = pcall(require, stats_provider); if not have_stats_provider then stats, stats_err = nil, stats_lib; else local stats_config = config.get("*", "statistics_config"); stats, stats_err = stats_lib.new(stats_config); stats_provider_name = stats_lib._NAME or stats_provider_name; end end if stats == nil then log("error", "Error loading statistics provider '%s': %s", stats_provider, stats_err); end local measure, collect, metric, cork, uncork; if stats then function metric(type_, name, unit, description, labels, extra) local registry = stats.metric_registry local f = assert(registry[type_], "unknown metric family type: "..type_); return f(registry, name, unit or "", description or "", labels, extra); end local function new_legacy_metric(stat_type, name, unit, description, fixed_label_key, fixed_label_value, extra) local label_keys = array() local conf = extra or {} if fixed_label_key then label_keys:push(fixed_label_key) end unit = unit or "" local mf = metric(stat_type, "prosody_" .. name, unit, description, label_keys, conf); if fixed_label_key then mf = mf:with_partial_label(fixed_label_value) end return mf:with_labels() end local function unwrap_legacy_extra(extra, type_, name, unit) local description = extra and extra.description or name.." "..type_ unit = extra and extra.unit or unit return description, unit end -- These wrappers provide the pre-OpenMetrics interface of statsmanager -- and moduleapi (module:measure). local legacy_metric_wrappers = { amount = function(name, fixed_label_key, fixed_label_value, extra) local initial = 0 if type(extra) == "number" then initial = extra else initial = extra and extra.initial or initial end local description, unit = unwrap_legacy_extra(extra, "amount", name) local m = new_legacy_metric("gauge", name, unit, description, fixed_label_key, fixed_label_value) m:set(initial or 0) return function(v) m:set(v) end end; counter = function(name, fixed_label_key, fixed_label_value, extra) if type(extra) == "number" then -- previous versions of the API allowed passing an initial -- value here; we do not allow that anymore, it is not a thing -- which makes sense with counters extra = nil end local description, unit = unwrap_legacy_extra(extra, "counter", name) local m = new_legacy_metric("counter", name, unit, description, fixed_label_key, fixed_label_value) m:set(0) return function(v) m:add(v) end end; rate = function(name, fixed_label_key, fixed_label_value, extra) if type(extra) == "number" then -- previous versions of the API allowed passing an initial -- value here; we do not allow that anymore, it is not a thing -- which makes sense with counters extra = nil end local description, unit = unwrap_legacy_extra(extra, "counter", name) local m = new_legacy_metric("counter", name, unit, description, fixed_label_key, fixed_label_value) m:set(0) return function() m:add(1) end end; times = function(name, fixed_label_key, fixed_label_value, extra) local conf = {} if extra and extra.buckets then conf.buckets = extra.buckets else conf.buckets = { 0.001, 0.01, 0.1, 1.0, 10.0, 100.0 } end local description, _ = unwrap_legacy_extra(extra, "times", name) local m = new_legacy_metric("histogram", name, "seconds", description, fixed_label_key, fixed_label_value, conf) return function() return timed(m) end end; sizes = function(name, fixed_label_key, fixed_label_value, extra) local conf = {} if extra and extra.buckets then conf.buckets = extra.buckets else conf.buckets = { 1024, 4096, 32768, 131072, 1048576, 4194304, 33554432, 134217728, 1073741824 } end local description, _ = unwrap_legacy_extra(extra, "sizes", name) local m = new_legacy_metric("histogram", name, "bytes", description, fixed_label_key, fixed_label_value, conf) return function(v) m:sample(v) end end; distribution = function(name, fixed_label_key, fixed_label_value, extra) if type(extra) == "string" then -- compat with previous API extra = { unit = extra } end local description, unit = unwrap_legacy_extra(extra, "distribution", name, "") local m = new_legacy_metric("summary", name, unit, description, fixed_label_key, fixed_label_value) return function(v) m:sample(v) end end; }; -- Argument order switched here to support the legacy statsmanager.measure -- interface. function measure(stat_type, name, extra, fixed_label_key, fixed_label_value) local wrapper = assert(legacy_metric_wrappers[stat_type], "unknown legacy metric type "..stat_type) return wrapper(name, fixed_label_key, fixed_label_value, extra) end if stats.cork then function cork() return stats:cork() end function uncork() return stats:uncork() end else function cork() end function uncork() end end if stats_interval or stats_interval_config == "manual" then local mark_collection_start = measure("times", "stats.collection"); local mark_processing_start = measure("times", "stats.processing"); function collect() local mark_collection_done = mark_collection_start(); fire_event("stats-update"); -- ensure that the backend is uncorked, in case it got stuck at -- some point, to avoid infinite resource use uncork() mark_collection_done(); local manual_result = nil if stats.metric_registry then -- only if supported by the backend, we fire the event which -- provides the current metric values local mark_processing_done = mark_processing_start(); local metric_registry = stats.metric_registry; fire_event("openmetrics-updated", { metric_registry = metric_registry }) mark_processing_done(); manual_result = metric_registry; end return stats_interval, manual_result; end if stats_interval then log("debug", "Statistics enabled using %s provider, collecting every %d seconds", stats_provider_name, stats_interval); timer.add_task(stats_interval, collect); prosody.events.add_handler("server-started", function () collect() end, -1); prosody.events.add_handler("server-stopped", function () collect() end, -1); else log("debug", "Statistics enabled using %s provider, no scheduled collection", stats_provider_name); end else log("debug", "Statistics enabled using %s provider, collection is disabled", stats_provider_name); end else log("debug", "Statistics disabled"); function measure() return measure; end local dummy_mt = {} function dummy_mt.__newindex() end function dummy_mt:__index() return self end function dummy_mt:__call() return self end local dummy = {} setmetatable(dummy, dummy_mt) function metric() return dummy; end function cork() end function uncork() end end local exported_collect = nil; if stats_interval_config == "manual" then exported_collect = collect; end return { collect = exported_collect; measure = measure; cork = cork; uncork = uncork; metric = metric; get_metric_registry = function () return stats and stats.metric_registry or nil end; }; prosody-0.12.4/core/PaxHeaders/storagemanager.lua0000644000000000000000000000012014475665104017016 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.624765619 prosody-0.12.4/core/storagemanager.lua0000644000175000017500000002040014475665104021217 0ustar00prosodyprosody00000000000000 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 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 local null_storage_method = function () return false, "no data storage active"; end local null_storage_driver = setmetatable( { name = "null", open = function (self) return self; end }, { __index = function (self, method) --luacheck: ignore 212 return null_storage_method; end } ); 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) local item = event.item; stores_available:set(host, item.name, item); end); host_session.events.add_handler("item-removed/storage-provider", function (event) local item = event.item; stores_available:set(host, item.name, nil); 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); local function load_driver(host, driver_name) if driver_name == "null" then return null_storage_driver; end local driver = stores_available:get(host, driver_name); if driver then return driver; end local ok, err = modulemanager.load(host, "storage_"..driver_name); if not ok then log("error", "Failed to load storage driver plugin %s on %s: %s", driver_name, host, err); end return stores_available:get(host, driver_name); end local function get_storage_config(host) -- COMPAT w/ unreleased Prosody 0.10 and the once-experimental mod_storage_sql2 in peoples' config files local storage_config = config.get(host, "storage"); local found_sql2; if storage_config == "sql2" then storage_config, found_sql2 = "sql", true; elseif type(storage_config) == "table" then for store_name, driver_name in pairs(storage_config) do if driver_name == "sql2" then storage_config[store_name] = "sql"; found_sql2 = true; end end end if found_sql2 then log("error", "The temporary 'sql2' storage module has now been renamed to 'sql', " .."please update your config file: https://prosody.im/doc/modules/mod_storage_sql2"); end return storage_config; end local function get_driver(host, store) local storage = get_storage_config(host); local driver_name; local option_type = type(storage); if option_type == "string" then driver_name = storage; elseif option_type == "table" then driver_name = storage[store]; end if not driver_name then driver_name = config.get(host, "default_storage") or "internal"; end local driver = load_driver(host, driver_name); if not driver then log("warn", "Falling back to null driver for %s storage on %s", store, host); driver_name = "null"; driver = null_storage_driver; end return driver, driver_name; end local map_shim_mt = { __index = { get = function(self, username, key) local ret, err = self.keyval_store:get(username); if ret == nil then return nil, err end return ret[key]; end; set = function(self, username, key, data) local current, err = self.keyval_store:get(username); if current == nil then if err then return nil, err; else current = {}; end end current[key] = data; return self.keyval_store:set(username, current); end; set_keys = function (self, username, keydatas) local current, err = self.keyval_store:get(username); if current == nil then if err then return nil, err; end current = {}; end for k,v in pairs(keydatas) do if v == self.remove then v = nil; end current[k] = v; end return self.keyval_store:set(username, current); end; remove = {}; get_all = function (self, key) if type(key) ~= "string" or key == "" then return nil, "get_all only supports non-empty string keys"; end local ret; for username in self.keyval_store:users() do local key_data = self:get(username, key); if key_data then if not ret then ret = {}; end ret[username] = key_data; end end return ret; end; delete_all = function (self, key) if type(key) ~= "string" or key == "" then return nil, "delete_all only supports non-empty string keys"; end local data = { [key] = self.remove }; local last_err; for username in self.keyval_store:users() do local ok, err = self:set_keys(username, data); if not ok then last_err = err; end end if last_err then return nil, last_err; end return true; end; }; } local open; -- forward declaration local function create_map_shim(host, store) local keyval_store, err = open(host, store, "keyval"); if keyval_store == nil then return nil, err end return setmetatable({ keyval_store = keyval_store; }, map_shim_mt); end function open(host, store, typ) local driver, driver_name = get_driver(host, store); local ret, err = driver:open(store, typ); if not ret then if err == "unsupported-store" then if typ == "map" then -- Use shim on top of keyval store log("debug", "map storage driver unavailable, using shim on top of keyval store."); ret, err = create_map_shim(host, store); else log("debug", "Storage driver %s does not support store %s (%s), falling back to null driver", driver_name, store, typ or ""); ret, err = null_storage_driver, nil; end end end if ret then local event_data = { host = host, store_name = store, store_type = typ, store = ret }; hosts[host].events.fire_event("store-opened", event_data); ret, err = event_data.store, event_data.store_err; end return ret, err; end local function purge(user, host) local storage = get_storage_config(host); if type(storage) == "table" then -- multiple storage backends in use that we need to purge local purged = {}; for store, driver_name in pairs(storage) do if not purged[driver_name] then local driver = get_driver(host, store); if driver.purge then purged[driver_name] = driver:purge(user); else log("warn", "Storage driver %s does not support removing all user data, " .."you may need to delete it manually", driver_name); end end end end get_driver(host):purge(user); -- and the default driver olddm.purge(user, host); -- COMPAT list stores, like offline messages end up in the old datamanager return true; end function datamanager.load(username, host, datastore) return open(host, datastore):get(username); end function datamanager.store(username, host, datastore, data) return open(host, datastore):set(username, data); end function datamanager.users(host, datastore, typ) local driver = open(host, datastore, typ); if not driver.users then return function() log("warn", "Storage driver %s does not support listing users", driver.name) end end return driver:users(); end function datamanager.stores(username, host, typ) return get_driver(host):stores(username, typ); end function datamanager.purge(username, host) return purge(username, host); end return { initialize_host = initialize_host; load_driver = load_driver; get_driver = get_driver; open = open; purge = purge; olddm = olddm; }; prosody-0.12.4/core/PaxHeaders/usermanager.lua0000644000000000000000000000012014475665104016330 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.624765619 prosody-0.12.4/core/usermanager.lua0000644000175000017500000001624514475665104020545 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local modulemanager = require "core.modulemanager"; local log = require "util.logger".init("usermanager"); local type = type; local it = require "util.iterators"; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local jid_prep = require "util.jid".prep; local config = require "core.configmanager"; local sasl_new = require "util.sasl".new; local storagemanager = require "core.storagemanager"; local set = require "util.set"; local prosody = _G.prosody; local hosts = prosody.hosts; local setmetatable = setmetatable; local default_provider = "internal_hashed"; local _ENV = nil; -- luacheck: std none local function new_null_provider() local function dummy() return nil, "method not implemented"; end; local function dummy_get_sasl_handler() return sasl_new(nil, {}); end return setmetatable({name = "null", get_sasl_handler = dummy_get_sasl_handler}, { __index = function(self, method) return dummy; end --luacheck: ignore 212 }); end local global_admins_config = config.get("*", "admins"); if type(global_admins_config) ~= "table" then global_admins_config = nil; -- TODO: factor out moduleapi magic config handling and use it here end local global_admins = set.new(global_admins_config) / jid_prep; local admin_role = { ["prosody:admin"] = true }; local global_authz_provider = { get_user_roles = function (user) end; --luacheck: ignore 212/user get_jid_roles = function (jid) if global_admins:contains(jid) then return admin_role; end end; get_jids_with_role = function (role) if role ~= "prosody:admin" then return {}; end return it.to_array(global_admins); end; set_user_roles = function (user, roles) end; -- luacheck: ignore 212 set_jid_roles = function (jid, roles) end; -- luacheck: ignore 212 }; local provider_mt = { __index = new_null_provider() }; local function initialize_host(host) local host_session = hosts[host]; local authz_provider_name = config.get(host, "authorization") or "internal"; local authz_mod = modulemanager.load(host, "authz_"..authz_provider_name); host_session.authz = authz_mod or global_authz_provider; if host_session.type ~= "local" then return; end host_session.events.add_handler("item-added/auth-provider", function (event) local provider = event.item; local auth_provider = config.get(host, "authentication") or default_provider; if config.get(host, "anonymous_login") then log("error", "Deprecated config option 'anonymous_login'. Use authentication = 'anonymous' instead."); auth_provider = "anonymous"; end -- COMPAT 0.7 if provider.name == auth_provider then host_session.users = setmetatable(provider, provider_mt); end if host_session.users ~= nil and host_session.users.name ~= nil then log("debug", "Host '%s' now set to use user provider '%s'", host, host_session.users.name); end end); host_session.events.add_handler("item-removed/auth-provider", function (event) local provider = event.item; if host_session.users == provider then host_session.users = new_null_provider(); end end); host_session.users = new_null_provider(); -- Start with the default usermanager provider local auth_provider = config.get(host, "authentication") or default_provider; if config.get(host, "anonymous_login") then auth_provider = "anonymous"; end -- COMPAT 0.7 if auth_provider ~= "null" then modulemanager.load(host, "auth_"..auth_provider); end end; prosody.events.add_handler("host-activated", initialize_host, 100); local function test_password(username, host, password) return hosts[host].users.test_password(username, password); end local function get_password(username, host) return hosts[host].users.get_password(username); end local function set_password(username, password, host, resource) local ok, err = hosts[host].users.set_password(username, password); if ok then prosody.events.fire_event("user-password-changed", { username = username, host = host, resource = resource }); end return ok, err; end local function user_exists(username, host) if hosts[host].sessions[username] then return true; end return hosts[host].users.user_exists(username); end local function create_user(username, password, host) return hosts[host].users.create_user(username, password); end local function delete_user(username, host) local ok, err = hosts[host].users.delete_user(username); if not ok then return nil, err; end prosody.events.fire_event("user-deleted", { username = username, host = host }); return storagemanager.purge(username, host); end local function users(host) return hosts[host].users.users(); end local function get_sasl_handler(host, session) return hosts[host].users.get_sasl_handler(session); end local function get_provider(host) return hosts[host].users; end local function get_roles(jid, host) if host and not hosts[host] then return false; end if type(jid) ~= "string" then return false; end jid = jid_bare(jid); host = host or "*"; local actor_user, actor_host = jid_split(jid); local roles; local authz_provider = (host ~= "*" and hosts[host].authz) or global_authz_provider; if actor_user and actor_host == host then -- Local user roles = authz_provider.get_user_roles(actor_user); else -- Remote user/JID roles = authz_provider.get_jid_roles(jid); end return roles; end local function set_roles(jid, host, roles) if host and not hosts[host] then return false; end if type(jid) ~= "string" then return false; end jid = jid_bare(jid); host = host or "*"; local actor_user, actor_host = jid_split(jid); local authz_provider = (host ~= "*" and hosts[host].authz) or global_authz_provider; if actor_user and actor_host == host then -- Local user local ok, err = authz_provider.set_user_roles(actor_user, roles); if ok then prosody.events.fire_event("user-roles-changed", { username = actor_user, host = actor_host }); end return ok, err; else -- Remote entity return authz_provider.set_jid_roles(jid, roles) end end local function is_admin(jid, host) local roles = get_roles(jid, host); return roles and roles["prosody:admin"]; end local function get_users_with_role(role, host) if not hosts[host] then return false; end if type(role) ~= "string" then return false; end return hosts[host].authz.get_users_with_role(role); end local function get_jids_with_role(role, host) if host and not hosts[host] then return false; end if type(role) ~= "string" then return false; end host = host or "*"; local authz_provider = (host ~= "*" and hosts[host].authz) or global_authz_provider; return authz_provider.get_jids_with_role(role); end return { new_null_provider = new_null_provider; initialize_host = initialize_host; test_password = test_password; get_password = get_password; set_password = set_password; user_exists = user_exists; create_user = create_user; delete_user = delete_user; users = users; get_sasl_handler = get_sasl_handler; get_provider = get_provider; get_roles = get_roles; set_roles = set_roles; is_admin = is_admin; get_users_with_role = get_users_with_role; get_jids_with_role = get_jids_with_role; }; prosody-0.12.4/PaxHeaders/doc0000644000000000000000000000013214475665104013057 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.624765619 30 ctime=1693936196.628765579 prosody-0.12.4/doc/0000755000175000017500000000000014475665104015336 5ustar00prosodyprosody00000000000000prosody-0.12.4/doc/PaxHeaders/coding_style.md0000644000000000000000000000012014475665104016136 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/doc/coding_style.md0000644000175000017500000004650114475665104020351 0ustar00prosodyprosody00000000000000 # Prosody Coding Style Guide This style guides lists the coding conventions used in the [Prosody](https://prosody.im/) project. It is based heavily on the [style guide used by the LuaRocks project](https://github.com/luarocks/lua-style-guide). ## Indentation and formatting * Prosody code is indented with tabs at the start of the line, a single tab per logical indent level: ```lua for i, pkg in ipairs(packages) do for name, version in pairs(pkg) do if name == searched then print(version); end end end ``` Tab width is configurable in editors, so never assume a particular width. Specifically this means you should not mix tabs and spaces, or use tabs for alignment of items at different indentation levels. * Use LF (Unix) line endings. ## Comments * Comments are encouraged where necessary to explain non-obvious code. * In general comments should be used to explain 'why', not 'how' ### Comment tags A comment may be prefixed with one of the following tags: * **FIXME**: Indicates a serious problem with the code that should be addressed * **TODO**: Indicates an open task, feature request or code restructuring that is primarily of interest to developers (otherwise it should be in the issue tracker). * **COMPAT**: Must be used on all code that is present only for backwards-compatibility, and may be removed one day. For example code that is added to support old or buggy third-party software or dependencies. **Example:** ```lua -- TODO: implement method local function something() -- FIXME: check conditions end ``` ## Variable names * Variable names with larger scope should be more descriptive than those with smaller scope. One-letter variable names should be avoided except for very small scopes (less than ten lines) or for iterators. * `i` should be used only as a counter variable in for loops (either numeric for or `ipairs`). * Prefer more descriptive names than `k` and `v` when iterating with `pairs`, unless you are writing a function that operates on generic tables. * Use `_` for ignored variables (e.g. in for loops:) ```lua for _, item in ipairs(items) do do_something_with_item(item); end ``` * Generally all identifiers (variables and function names) should use `snake_case`, i.e. lowercase words joined by `_`. ```lua -- bad local OBJEcttsssss = {} local thisIsMyObject = {} local c = function() -- ...stuff... end -- good local this_is_my_object = {}; local function do_that_thing() -- ...stuff... end ``` > **Rationale:** The standard library uses lowercase APIs, with `joinedlowercase` names, but this does not scale too well for more complex APIs. `snake_case` tends to look good enough and not too out-of-place along side the standard APIs. ```lua for _, name in pairs(names) do -- ...stuff... end ``` * Prefer using `is_` when naming boolean functions: ```lua -- bad local function evil(alignment) return alignment < 100 end -- good local function is_evil(alignment) return alignment < 100; end ``` * `UPPER_CASE` is to be used sparingly, with "constants" only. > **Rationale:** "Sparingly", since Lua does not have real constants. This notation is most useful in libraries that bind C libraries, when bringing over constants from C. * Do not use uppercase names starting with `_`, they are reserved by Lua. ## Tables * When creating a table, prefer populating its fields all at once, if possible: ```lua local player = { name = "Jack", class = "Rogue" }; ``` * Items should be separated by commas. If there are many items, put each key/value on a separate line and use a semi-colon after each item (including the last one): ```lua local player = { name = "Jack"; class = "Rogue"; } ``` > **Rationale:** This makes the structure of your tables more evident at a glance. Trailing semi-colons make it quicker to add new fields and produces shorter diffs. * Use plain `key` syntax whenever possible, use `["key"]` syntax when using names that can't be represented as identifiers and avoid mixing representations in a declaration: ```lua local mytable = { ["1394-E"] = val1; ["UTF-8"] = val2; ["and"] = val2; } ``` ## Strings * Use `"double quotes"` for strings; use `'single quotes'` when writing strings that contain double quotes. ```lua local name = "Prosody"; local sentence = 'The name of the program is "Prosody"'; ``` > **Rationale:** Double quotes are used as string delimiters in a larger number of programming languages. Single quotes are useful for avoiding escaping when using double quotes in literals. ## Line lengths * There are no hard or soft limits on line lengths. Line lengths are naturally limited by using one statement per line. If that still produces lines that are too long (e.g. an expression that produces a line over 256-characters long, for example), this means the expression is too complex and would do better split into subexpressions with reasonable names. > **Rationale:** No one works on VT100 terminals anymore. If line lengths are a proxy for code complexity, we should address code complexity instead of using line breaks to fit mind-bending statements over multiple lines. ## Function declaration syntax * Prefer function syntax over variable syntax. This helps differentiate between named and anonymous functions. ```lua -- bad local nope = function(name, options) -- ...stuff... end -- good local function yup(name, options) -- ...stuff... end ``` * Perform validation early and return as early as possible. ```lua -- bad local function is_good_name(name, options, arg) local is_good = #name > 3 is_good = is_good and #name < 30 -- ...stuff... return is_good end -- good local function is_good_name(name, options, args) if #name < 3 or #name > 30 then return false; end -- ...stuff... return true; end ``` ## Function calls * Even though Lua allows it, generally you should not omit parentheses for functions that take a unique string literal argument. ```lua -- bad local data = get_data"KRP"..tostring(area_number) -- good local data = get_data("KRP"..tostring(area_number)); local data = get_data("KRP")..tostring(area_number); ``` > **Rationale:** It is not obvious at a glace what the precedence rules are when omitting the parentheses in a function call. Can you quickly tell which of the two "good" examples in equivalent to the "bad" one? (It's the second one). * You should not omit parenthesis for functions that take a unique table argument on a single line. You may do so for table arguments that span several lines. ```lua local an_instance = a_module.new { a_parameter = 42; another_parameter = "yay"; } ``` > **Rationale:** The use as in `a_module.new` above occurs alone in a statement, so there are no precedence issues. ## Table attributes * Use dot notation when accessing known properties. ```lua local luke = { jedi = true; age = 28; } -- bad local is_jedi = luke["jedi"] -- good local is_jedi = luke.jedi; ``` * Use subscript notation `[]` when accessing properties with a variable or if using a table as a list. ```lua local vehicles = load_vehicles_from_disk("vehicles.dat") if vehicles["Porsche"] then porsche_handler(vehicles["Porsche"]); vehicles["Porsche"] = nil; end for name, cars in pairs(vehicles) do regular_handler(cars); end ``` > **Rationale:** Using dot notation makes it clearer that the given key is meant to be used as a record/object field. ## Functions in tables * When declaring modules and classes, declare functions external to the table definition: ```lua local my_module = {}; function my_module.a_function(x) -- code end ``` * When declaring metatables, declare function internal to the table definition. ```lua local version_mt = { __eq = function(a, b) -- code end; __lt = function(a, b) -- code end; } ``` > **Rationale:** Metatables contain special behavior that affect the tables they're assigned (and are used implicitly at the call site), so it's good to be able to get a view of the complete behavior of the metatable at a glance. This is not as important for objects and modules, which usually have way more code, and which don't fit in a single screen anyway, so nesting them inside the table does not gain much: when scrolling a longer file, it is more evident that `check_version` is a method of `Api` if it says `function Api:check_version()` than if it says `check_version = function()` under some indentation level. ## Variable declaration * Always use `local` to declare variables. ```lua -- bad superpower = get_superpower() -- good local superpower = get_superpower(); ``` > **Rationale:** Not doing so will result in global variables to avoid polluting the global namespace. ## Variable scope * Assign variables with the smallest possible scope. ```lua -- bad local function good() local name = get_name() test() print("doing stuff..") --...other stuff... if name == "test" then return false end return name end -- good local bad = function() test(); print("doing stuff.."); --...other stuff... local name = get_name(); if name == "test" then return false; end return name; end ``` > **Rationale:** Lua has proper lexical scoping. Declaring the function later means that its scope is smaller, so this makes it easier to check for the effects of a variable. ## Conditional expressions * False and nil are falsy in conditional expressions. Use shortcuts when you can, unless you need to know the difference between false and nil. ```lua -- bad if name ~= nil then -- ...stuff... end -- good if name then -- ...stuff... end ``` * Avoid designing APIs which depend on the difference between `nil` and `false`. * Use the `and`/`or` idiom for the pseudo-ternary operator when it results in more straightforward code. When nesting expressions, use parentheses to make it easier to scan visually: ```lua local function default_name(name) -- return the default "Waldo" if name is nil return name or "Waldo"; end local function brew_coffee(machine) return (machine and machine.is_loaded) and "coffee brewing" or "fill your water"; end ``` Note that the `x and y or z` as a substitute for `x ? y : z` does not work if `y` may be `nil` or `false` so avoid it altogether for returning booleans or values which may be nil. ## Blocks * Use single-line blocks only for `then return`, `then break` and `function return` (a.k.a "lambda") constructs: ```lua -- good if test then break end -- good if not ok then return nil, "this failed for this reason: " .. reason end -- good use_callback(x, function(k) return k.last end); -- good if test then return false end -- bad if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then do_other_complicated_function() end -- good if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then do_other_complicated_function(); return false; end ``` * Separate statements onto multiple lines. Use semicolons as statement terminators. ```lua -- bad local whatever = "sure" a = 1 b = 2 -- good local whatever = "sure"; a = 1; b = 2; ``` ## Spacing * Use a space after `--`. ```lua --bad -- good ``` * Always put a space after commas and between operators and assignment signs: ```lua -- bad local x = y*9 local numbers={1,2,3} numbers={1 , 2 , 3} numbers={1 ,2 ,3} local strings = { "hello" , "Lua" , "world" } dog.set( "attr",{ age="1 year", breed="Bernese Mountain Dog" }) -- good local x = y * 9; local numbers = {1, 2, 3}; local strings = { "hello"; "Lua"; "world"; } dog.set("attr", { age = "1 year"; breed = "Bernese Mountain Dog"; }); ``` * Indent tables and functions according to the start of the line, not the construct: ```lua -- bad local my_table = { "hello", "world", } using_a_callback(x, function(...) print("hello") end) -- good local my_table = { "hello"; "world"; } using_a_callback(x, function(...) print("hello"); end) ``` > **Rationale:** This keep indentation levels aligned at predictable places. You don't need to realign the entire block if something in the first line changes (such as replacing `x` with `xy` in the `using_a_callback` example above). * The concatenation operator gets a pass for avoiding spaces: ```lua -- okay local message = "Hello, "..user.."! This is your day # "..day.." in our platform!"; ``` > **Rationale:** Being at the baseline, the dots already provide some visual spacing. * No spaces after the name of a function in a declaration or in its arguments: ```lua -- bad local function hello ( name, language ) -- code end -- good local function hello(name, language) -- code end ``` * Add blank lines between functions: ```lua -- bad local function foo() -- code end local function bar() -- code end -- good local function foo() -- code end local function bar() -- code end ``` * Avoid aligning variable declarations: ```lua -- bad local a = 1 local long_identifier = 2 -- good local a = 1; local long_identifier = 2; ``` > **Rationale:** This produces extra diffs which add noise to `hg annotate`. * Alignment is occasionally useful when logical correspondence is to be highlighted: ```lua -- okay sys_command(form, UI_FORM_UPDATE_NODE, "a", FORM_NODE_HIDDEN, false); sys_command(form, UI_FORM_UPDATE_NODE, "sample", FORM_NODE_VISIBLE, false); ``` ## Typing * In non-performance critical code, it can be useful to add type-checking assertions for function arguments: ```lua function manif.load_manifest(repo_url, lua_version) assert(type(repo_url) == "string"); assert(type(lua_version) == "string" or not lua_version); -- ... end ``` * Use the standard functions for type conversion, avoid relying on coercion: ```lua -- bad local total_score = review_score .. "" -- good local total_score = tostring(review_score); ``` ## Errors * Functions that can fail for reasons that are expected (e.g. I/O) should return `nil` and a (string) error message on error, possibly followed by other return values such as an error code. * On errors such as API misuse, an error should be thrown, either with `error()` or `assert()`. ## Modules Follow [these guidelines](http://hisham.hm/2014/01/02/how-to-write-lua-modules-in-a-post-module-world/) for writing modules. In short: * Always require a module into a local variable named after the last component of the module’s full name. ```lua local bar = require("foo.bar"); -- requiring the module bar.say("hello"); -- using the module ``` * Don’t rename modules arbitrarily: ```lua -- bad local skt = require("socket") ``` > **Rationale:** Code is much harder to read if we have to keep going back to the top to check how you chose to call a module. * Start a module by declaring its table using the same all-lowercase local name that will be used to require it. You may use an LDoc comment to identify the whole module path. ```lua --- @module foo.bar local bar = {}; ``` * Try to use names that won't clash with your local variables. For instance, don't name your module something like “size”. * Use `local function` to declare _local_ functions only: that is, functions that won’t be accessible from outside the module. That is, `local function helper_foo()` means that `helper_foo` is really local. * Public functions are declared in the module table, with dot syntax: ```lua function bar.say(greeting) print(greeting); end ``` > **Rationale:** Visibility rules are made explicit through syntax. * Do not set any globals in your module and always return a table in the end. * If you would like your module to be used as a function, you may set the `__call` metamethod on the module table instead. > **Rationale:** Modules should return tables in order to be amenable to have their contents inspected via the Lua interactive interpreter or other tools. * Requiring a module should cause no side-effect other than loading other modules and returning the module table. * A module should not have state. If a module needs configuration, turn it into a factory. For example, do not make something like this: ```lua -- bad local mp = require "MessagePack" mp.set_integer("unsigned") ``` and do something like this instead: ```lua -- good local messagepack = require("messagepack"); local mpack = messagepack.new({integer = "unsigned"}); ``` * The invocation of require may omit parentheses around the module name: ```lua local bla = require "bla"; ``` ## Metatables, classes and objects If creating a new type of object that has a metatable and methods, the metatable and methods table should be separate, and the metatable name should end with `_mt`. ```lua local mytype_methods = {}; local mytype_mt = { __index = mytype_methods }; function mytype_methods:add_new_thing(thing) end local function new() return setmetatable({}, mytype_mt); end return { new = new }; ``` * Use the method notation when invoking methods: ``` -- bad my_object.my_method(my_object) -- good my_object:my_method(); ``` > **Rationale:** This makes it explicit that the intent is to use the function as a method. * Do not rely on the `__gc` metamethod to release resources other than memory. If your object manage resources such as files, add a `close` method to their APIs and do not auto-close via `__gc`. Auto-closing via `__gc` would entice users of your module to not close resources as soon as possible. (Note that the standard `io` library does not follow this recommendation, and users often forget that not closing files immediately can lead to "too many open files" errors when the program runs for a while.) > **Rationale:** The garbage collector performs automatic *memory* management, dealing with memory only. There is no guarantees as to when the garbage collector will be invoked, and memory pressure does not correlate to pressure on other resources. ## File structure * Lua files should be named in all lowercase. * Tests should be in a top-level `spec` directory. Prosody uses [Busted](http://olivinelabs.com/busted/) for testing. ## Static checking All code should pass [luacheck](https://github.com/mpeterv/luacheck) using the `.luacheckrc` provided in the Prosody repository, and using minimal inline exceptions. * luacheck warnings of class 211, 212, 213 (unused variable, argument or loop variable) may be ignored, if the unused variable was added explicitly: for example, sometimes it is useful, for code understandability, to spell out what the keys and values in a table are, even if you're only using one of them. Another example is a function that needs to follow a given signature for API reasons (e.g. a callback that follows a given format) but doesn't use some of its arguments; it's better to spell out in the argument what the API the function implements is, instead of adding `_` variables. ``` local foo, bar = some_function(); --luacheck: ignore 212/foo print(bar); ``` * luacheck warning 542 (empty if branch) can also be ignored, when a sequence of `if`/`elseif`/`else` blocks implements a "switch/case"-style list of cases, and one of the cases is meant to mean "pass". For example: ```lua if warning >= 600 and warning <= 699 then print("no whitespace warnings"); elseif warning == 542 then --luacheck: ignore 542 -- pass else print("got a warning: "..warning); end ``` > **Rationale:** This avoids writing negated conditions in the final fallback case, and it's easy to add another case to the construct without having to edit the fallback. prosody-0.12.4/doc/PaxHeaders/doap.xml0000644000000000000000000000012014475665104014576 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/doc/doap.xml0000644000175000017500000010042514475665104017005 0ustar00prosodyprosody00000000000000 Prosody IM Lightweight XMPP server Prosody is a server for Jabber/XMPP written in Lua. It aims to be easy to use and light on resources. For developers, it aims to give a flexible system on which to rapidly develop added functionality or rapidly prototype new protocols. 2008-08-22 Lua C Linux macOS FreeBSD OpenBSD NetBSD Matthew Wild MattJ https://matthewwild.co.uk/ Waqas Hussain waqas Kim Alvefur Zash https://www.zash.se/ 2.12.1 0.4.0 partial no support for multiple items (reported tag) 0.4.0 0.7.0 removed 2.0 0.1.0 mod_lastactivity and mod_uptime complete 0.7.0 0.10.0 removed mod_privacy 2.5rc3 0.10.0 complete 0.1 0.1.0 complete libexpat 1.34.1 0.3.0 partial 1.2 0.1.0 complete mod_private, indirectly supported via XEP-0049 1.2 0.1.0 complete mod_private 1.3.0 0.8.0 complete mod_adhoc 1.2 0.1.0 complete mod_vcard and mod_vcard_legacy 1.0 0.10.0 complete used by XEP-0313, util.rsm 1.22.0 0.9.0 partial mod_pubsub 1.8.2 0.7.0 complete mod_proxy65 1.3.0 complete 0.1.0 2.4 complete 2.5 0.1.0 partial mod_legacyauth, lacks digest method 1.9 complete via XEP-0163 1.1.1 0.1.0 complete 1.1.4 complete via XEP-0163, also mod_vcard_legacy 1.2 0.1.0 complete mod_time 1.4 0.1.0 0.12.0 removed Gone from offline messages in 0.10.0, gone from MUC in 0.12 1.1 0.1.0 complete 1.0 0.9.0 complete 1.2.1 complete via XEP-0163 1.3 complete via XEP-0163 1.6 0.4.0 complete 1.5.2 0.8.0 complete 1.3.0 complete via XEP-0163 1.0.2 0.11.0 partial 1.11.2 0.2.0 complete mod_bosh 0.10.0 removed Gone with XEP-0016 1.0.1 0.9.0 complete 1.2 0.7.0 partial mod_admin_adhoc, missing some commands 2.0 0.6.0 0.10.0 removed Compression considered insecure 1.1 0.11.0 complete via XEP-0398 1.1.1 0.10.0 complete 1.0.1 0.1.0 complete 1.2.1 0.5.0 complete 1.0 complete 1.1 complete via XEP-0163, also mod_vcard_legacy 1.2 0.4.0 partial 1.2 0.9.0 complete 1.1 complete 1.4.0 0.12.0 mod_mam archives receipts 1.0 complete 0.9.10 0.14 complete via XEP-0163 1.3 complete 0.10.0 mod_blocklist 0.3 complete via XEP-0163 0.3 complete via XEP-0163 0.3 complete via XEP-0163 0.3 complete via XEP-0163 1.6 complete 0.12.0 mod_smacks 2.0.1 0.1.0 complete 2.0 complete 0.1.0 mod_time 2.0 0.1.0 complete 1.0.2 0.12.0 complete stanza size limits, bandwidth limits, stanza-too-big error condition 1.4 partial 0.2.0 What's that about restartlogic in 1.3? 1.0 complete required level 0.7.1 complete 0.12.0 mod_external_services 1.1.1 0.1.0 partial 1.0 0.11.0 complete mod_pep 1.1 0.11.0 complete mod_pep 1.1 0.7.0 partial Used in migrator tools and mod_storage_xep0227 1.3 0.4.0 complete implied by rfc6121 1.2 0.12.0 complete mod_csi_simple 1.0.0 complete 0.10.0 1.0.0 0.11.0 complete mod_csi_simple 1.0.1 complete 0.12.0 0.11 complete 0.11.0 mod_vcard4, mod_vcard_legacy 1.0.0 0.11.0 complete Used by XEP-0280, XEP-0313 0.1 complete Core Server 0.1 complete 0.6.0 Moved into mod_muc_unique in 0.11 1.0.0 complete 0.10.0 mod_mam, mod_muc_mam 0.1 planned 0.12.0 muc/hats 0.2 0.9.0 complete refers to inclusion of delay stamp in presence 1.0.0 0.11.0 complete mod_csi+mod_csi_simple 0.4.0 0.11.6 complete triggers buffer flush in mod_csi_simple since 0.11.6; recognised by mod_carbons and mod_mam since 0.12 0.6.1 complete 0.10.0 Used in context of XEP-0313 by mod_mam and mod_muc_mam 1.0.0 complete 0.12.0 mod_http_file_share 1.1.0 complete 0.2.0 c2s_direct_tls_ports (formerly legacy_ssl_ports) for c2s and direct_tls_s2s_ports for s2s 0.3.3 complete 0.12.0 0.3.0 0.11.0 complete Used in context of XEP-0352 0.8.1 complete via XEP-0163, XEP-0222 0.2.1 0.11.0 complete mod_vcard_legacy 0.3.0 0.12.0 partial 1.1.3 0.12.0 complete mod_bookmarks 1.1.0 0.11.0 complete Server Optimization 1.1.0 0.12.0 complete mod_bookmarks 0.1.0 0.12.0 complete mod_muc 0.2.0 partial 0.2.0 complete Broken out of XEP-0313 prosody-0.12.4/doc/PaxHeaders/names.txt0000644000000000000000000000012014475665104014775 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/doc/names.txt0000644000175000017500000000225114475665104017202 0ustar00prosodyprosody00000000000000lxmppd - ... dia - Greek, 'through', pronounced "dee-ah", root of "dialogue" metaphor - An imaginative comparison between two actions/objects etc which is not literally applicable. minstrel - Itinerant medieval musician/singer/story teller/poet. parody - Imitation of a poem or another poet's style for comic/satiric effect. poesy - Archaic word for poetry. Xinshi - Chinese poetic term which literally means 'new poetry'. polylogue - Many conversations Thorns thought of: poe - Derived from "poetry" poezie - Romanian for "poesy" and "poem" Elain - Just a cool name Elane - A variation Eclaire - Idem (French) Adel - Random Younha - Read as "yuna" Quezacotl - Mayan gods -> google for correct form and pronunciation Carbuncle - FF8 Guardian Force ^^ Protos - Mars satellite mins - Derived from minstrel diapoe - gr. dia + poesy/poetry xinshi - I like it for a name just like that loom - The first application I run on the first day of using a computer Lory - Another name I happen to like Loki - Nordic god of mischief, IIRC Luna - Probably taken but I think worth mentioning Coreo - Random thought Miria - Also random Lora - Idem Kraken - :P Nebula - . prosody-0.12.4/doc/PaxHeaders/net.server.lua0000644000000000000000000000012014475665104015727 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/doc/net.server.lua0000644000175000017500000001431414475665104020137 0ustar00prosodyprosody00000000000000-- 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 --[[ Binds and listens on the given address and port Mostly the same as addserver but with all optional arguments in a table 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 - config: table of extra settings - read_size: the amount of bytes to read or a read pattern - tls_ctx: is a valid luasec constructor - tls_direct: boolean true for direct TLS, false (or nil) for starttls Returns: - handle - nil, "an error message": on failure (e.g. out of file descriptors) ]] local function listen(address, port, listeners, config) 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; listen = listen; } prosody-0.12.4/doc/PaxHeaders/roster_format.txt0000644000000000000000000000012014475665104016560 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/doc/roster_format.txt0000644000175000017500000000113114475665104020761 0ustar00prosodyprosody00000000000000 This file documents the structure of the roster object. table roster { [string bare_jid] = roster_item } table roster_item { string subscription = "none" | "to" | "from" | "both" string name = Opaque string set by client. (optional) set groups = a set of opaque strings set by the client boolean ask = nil | "subscribe" - a value of true indicates subscription is pending } The roster is available as hosts[host].sessions[username].roster and a copy is made to session.roster for all sessions. All modifications to a roster should be done through the rostermanager. prosody-0.12.4/doc/PaxHeaders/session.txt0000644000000000000000000000012014475665104015355 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/doc/session.txt0000644000175000017500000000320414475665104017561 0ustar00prosodyprosody00000000000000 Structure of a session: session { -- properties -- conn -- the tcp connection notopen -- true if stream has not been initiated, removed after receiving type -- the connection type. Valid values include: -- "c2s_unauthed" - connection has not been authenticated yet -- "c2s" - from a local client to the server username -- the node part of the client's jid (not defined before auth) host -- the host part of the client's jid (not defined before stream initiation) resource -- the resource part of the client's full jid (not defined before resource binding) 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 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 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" if session.presence then this is an "available resource" (all available resources are connected resources) if session.interested then this is an "interested resource" (all interested resources are connected resources) prosody-0.12.4/doc/PaxHeaders/stanza.txt0000644000000000000000000000012014475665104015172 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/doc/stanza.txt0000644000175000017500000000104614475665104017400 0ustar00prosodyprosody00000000000000 Structure of a stanza: stanza { --- properties --- tags -- array of tags --- static methods --- iq(attrs) -- --- read-only methods --- reply -- return new stanza with attributes of current stanza child_with_name(string name) -- return the first child of the current tag with the matching name --- write methods --- tag(name, sttrs) -- create a new child of the current tag, and set the child as current up() -- move to the parent of the current tag text(string) -- append a new text node to the current tag } prosody-0.12.4/doc/PaxHeaders/stanza_routing.txt0000644000000000000000000000012014475665104016741 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/doc/stanza_routing.txt0000644000175000017500000000152514475665104021151 0ustar00prosodyprosody00000000000000No 'to' attribute: IQ: Pass to appropriate handler Presence: Broadcast to contacts - if initial presence, also send out presence probes - if probe would be to local user, generate presence stanza for them Message: Route as if it is addressed to the bare JID of the sender To a local host: IQ: Pass to appropriate handler Presence: - Message: Deliver to admin? To local contact: Bare JID: IQ: Pass to appropriate handler Presence: Broadcast to all resources Message: Route to 'best' resource Full JID: IQ: Send to resource Presence: Send to resource Message: Send to resource Full JID but resource not connected: IQ: Return service-unavailable Message: Handle same as if to bare JID Presence: Drop (unless type=subscribe[ed]) To remote contact: Initiate s2s connection if necessary Send stanza across prosody-0.12.4/doc/PaxHeaders/storage.tld0000644000000000000000000000012014475665104015302 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/doc/storage.tld0000644000175000017500000000405714475665104017515 0ustar00prosodyprosody00000000000000-- Storage Interface API Description -- -- This is written as a TypedLua description -- Key-Value stores (the default) interface keyval_store get : ( self, string? ) -> (any) | (nil, string) set : ( self, string?, any ) -> (boolean) | (nil, string) end -- Map stores (key-key-value stores) interface map_store get : ( self, string?, any ) -> (any) | (nil, string) set : ( self, string?, any, any ) -> (boolean) | (nil, string) set_keys : ( self, string?, { any : any }) -> (boolean) | (nil, string) remove : {} end -- Archive stores typealias archive_query = { "start" : number?, -- timestamp "end" : number?, -- timestamp "with" : string?, "after" : string?, -- archive id "before" : string?, -- archive id "total" : boolean?, } interface archive_store -- Optional set of capabilities caps : { -- Optional total count of matching items returned as second return value from :find() "total" : boolean?, }? -- Add to the archive append : ( self, string?, string?, any, number?, string? ) -> (string) | (nil, string) -- Iterate over archive find : ( self, string?, archive_query? ) -> ( () -> ( string, any, number?, string? ), integer? ) -- Removal of items. API like find. Optional? delete : ( self, string?, archive_query? ) -> (boolean) | (number) | (nil, string) -- Array of dates which do have messages (Optional?) dates : ( self, string? ) -> ({ string }) | (nil, string) -- Map of counts per "with" field summary : ( self, string?, archive_query? ) -> ( { string : integer } ) | (nil, string) -- Map-store API get : ( self, string, string ) -> (stanza, number?, string?) | (nil, string) set : ( self, string, string, stanza, number?, string? ) -> (boolean) | (nil, string) end -- This represents moduleapi interface module -- If the first string is omitted then the name of the module is used -- The second string is one of "keyval" (default), "map" or "archive" open_store : (self, string?, string?) -> (keyval_store) | (map_store) | (archive_store) | (nil, string) -- Other module methods omitted end module : module prosody-0.12.4/PaxHeaders/fallbacks0000644000000000000000000000013214475665104014234 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.628765579 30 ctime=1693936196.628765579 prosody-0.12.4/fallbacks/0000755000175000017500000000000014475665104016513 5ustar00prosodyprosody00000000000000prosody-0.12.4/fallbacks/PaxHeaders/bit.lua0000644000000000000000000000012014475665104015567 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/fallbacks/bit.lua0000644000175000017500000002360514475665104020002 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local type = type; local tonumber = tonumber; local setmetatable = setmetatable; local error = error; local tostring = tostring; local print = print; local xor_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=0;[18]=3;[19]=2;[20]=5;[21]=4;[22]=7;[23]=6;[24]=9;[25]=8;[26]=11;[27]=10;[28]=13;[29]=12;[30]=15;[31]=14;[32]=2;[33]=3;[34]=0;[35]=1;[36]=6;[37]=7;[38]=4;[39]=5;[40]=10;[41]=11;[42]=8;[43]=9;[44]=14;[45]=15;[46]=12;[47]=13;[48]=3;[49]=2;[50]=1;[51]=0;[52]=7;[53]=6;[54]=5;[55]=4;[56]=11;[57]=10;[58]=9;[59]=8;[60]=15;[61]=14;[62]=13;[63]=12;[64]=4;[65]=5;[66]=6;[67]=7;[68]=0;[69]=1;[70]=2;[71]=3;[72]=12;[73]=13;[74]=14;[75]=15;[76]=8;[77]=9;[78]=10;[79]=11;[80]=5;[81]=4;[82]=7;[83]=6;[84]=1;[85]=0;[86]=3;[87]=2;[88]=13;[89]=12;[90]=15;[91]=14;[92]=9;[93]=8;[94]=11;[95]=10;[96]=6;[97]=7;[98]=4;[99]=5;[100]=2;[101]=3;[102]=0;[103]=1;[104]=14;[105]=15;[106]=12;[107]=13;[108]=10;[109]=11;[110]=8;[111]=9;[112]=7;[113]=6;[114]=5;[115]=4;[116]=3;[117]=2;[118]=1;[119]=0;[120]=15;[121]=14;[122]=13;[123]=12;[124]=11;[125]=10;[126]=9;[127]=8;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=0;[137]=1;[138]=2;[139]=3;[140]=4;[141]=5;[142]=6;[143]=7;[144]=9;[145]=8;[146]=11;[147]=10;[148]=13;[149]=12;[150]=15;[151]=14;[152]=1;[153]=0;[154]=3;[155]=2;[156]=5;[157]=4;[158]=7;[159]=6;[160]=10;[161]=11;[162]=8;[163]=9;[164]=14;[165]=15;[166]=12;[167]=13;[168]=2;[169]=3;[170]=0;[171]=1;[172]=6;[173]=7;[174]=4;[175]=5;[176]=11;[177]=10;[178]=9;[179]=8;[180]=15;[181]=14;[182]=13;[183]=12;[184]=3;[185]=2;[186]=1;[187]=0;[188]=7;[189]=6;[190]=5;[191]=4;[192]=12;[193]=13;[194]=14;[195]=15;[196]=8;[197]=9;[198]=10;[199]=11;[200]=4;[201]=5;[202]=6;[203]=7;[204]=0;[205]=1;[206]=2;[207]=3;[208]=13;[209]=12;[210]=15;[211]=14;[212]=9;[213]=8;[214]=11;[215]=10;[216]=5;[217]=4;[218]=7;[219]=6;[220]=1;[221]=0;[222]=3;[223]=2;[224]=14;[225]=15;[226]=12;[227]=13;[228]=10;[229]=11;[230]=8;[231]=9;[232]=6;[233]=7;[234]=4;[235]=5;[236]=2;[237]=3;[238]=0;[239]=1;[240]=15;[241]=14;[242]=13;[243]=12;[244]=11;[245]=10;[246]=9;[247]=8;[248]=7;[249]=6;[250]=5;[251]=4;[252]=3;[253]=2;[254]=1;[255]=0;}; local or_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=1;[18]=3;[19]=3;[20]=5;[21]=5;[22]=7;[23]=7;[24]=9;[25]=9;[26]=11;[27]=11;[28]=13;[29]=13;[30]=15;[31]=15;[32]=2;[33]=3;[34]=2;[35]=3;[36]=6;[37]=7;[38]=6;[39]=7;[40]=10;[41]=11;[42]=10;[43]=11;[44]=14;[45]=15;[46]=14;[47]=15;[48]=3;[49]=3;[50]=3;[51]=3;[52]=7;[53]=7;[54]=7;[55]=7;[56]=11;[57]=11;[58]=11;[59]=11;[60]=15;[61]=15;[62]=15;[63]=15;[64]=4;[65]=5;[66]=6;[67]=7;[68]=4;[69]=5;[70]=6;[71]=7;[72]=12;[73]=13;[74]=14;[75]=15;[76]=12;[77]=13;[78]=14;[79]=15;[80]=5;[81]=5;[82]=7;[83]=7;[84]=5;[85]=5;[86]=7;[87]=7;[88]=13;[89]=13;[90]=15;[91]=15;[92]=13;[93]=13;[94]=15;[95]=15;[96]=6;[97]=7;[98]=6;[99]=7;[100]=6;[101]=7;[102]=6;[103]=7;[104]=14;[105]=15;[106]=14;[107]=15;[108]=14;[109]=15;[110]=14;[111]=15;[112]=7;[113]=7;[114]=7;[115]=7;[116]=7;[117]=7;[118]=7;[119]=7;[120]=15;[121]=15;[122]=15;[123]=15;[124]=15;[125]=15;[126]=15;[127]=15;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=8;[137]=9;[138]=10;[139]=11;[140]=12;[141]=13;[142]=14;[143]=15;[144]=9;[145]=9;[146]=11;[147]=11;[148]=13;[149]=13;[150]=15;[151]=15;[152]=9;[153]=9;[154]=11;[155]=11;[156]=13;[157]=13;[158]=15;[159]=15;[160]=10;[161]=11;[162]=10;[163]=11;[164]=14;[165]=15;[166]=14;[167]=15;[168]=10;[169]=11;[170]=10;[171]=11;[172]=14;[173]=15;[174]=14;[175]=15;[176]=11;[177]=11;[178]=11;[179]=11;[180]=15;[181]=15;[182]=15;[183]=15;[184]=11;[185]=11;[186]=11;[187]=11;[188]=15;[189]=15;[190]=15;[191]=15;[192]=12;[193]=13;[194]=14;[195]=15;[196]=12;[197]=13;[198]=14;[199]=15;[200]=12;[201]=13;[202]=14;[203]=15;[204]=12;[205]=13;[206]=14;[207]=15;[208]=13;[209]=13;[210]=15;[211]=15;[212]=13;[213]=13;[214]=15;[215]=15;[216]=13;[217]=13;[218]=15;[219]=15;[220]=13;[221]=13;[222]=15;[223]=15;[224]=14;[225]=15;[226]=14;[227]=15;[228]=14;[229]=15;[230]=14;[231]=15;[232]=14;[233]=15;[234]=14;[235]=15;[236]=14;[237]=15;[238]=14;[239]=15;[240]=15;[241]=15;[242]=15;[243]=15;[244]=15;[245]=15;[246]=15;[247]=15;[248]=15;[249]=15;[250]=15;[251]=15;[252]=15;[253]=15;[254]=15;[255]=15;}; local and_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=0;[9]=0;[10]=0;[11]=0;[12]=0;[13]=0;[14]=0;[15]=0;[16]=0;[17]=1;[18]=0;[19]=1;[20]=0;[21]=1;[22]=0;[23]=1;[24]=0;[25]=1;[26]=0;[27]=1;[28]=0;[29]=1;[30]=0;[31]=1;[32]=0;[33]=0;[34]=2;[35]=2;[36]=0;[37]=0;[38]=2;[39]=2;[40]=0;[41]=0;[42]=2;[43]=2;[44]=0;[45]=0;[46]=2;[47]=2;[48]=0;[49]=1;[50]=2;[51]=3;[52]=0;[53]=1;[54]=2;[55]=3;[56]=0;[57]=1;[58]=2;[59]=3;[60]=0;[61]=1;[62]=2;[63]=3;[64]=0;[65]=0;[66]=0;[67]=0;[68]=4;[69]=4;[70]=4;[71]=4;[72]=0;[73]=0;[74]=0;[75]=0;[76]=4;[77]=4;[78]=4;[79]=4;[80]=0;[81]=1;[82]=0;[83]=1;[84]=4;[85]=5;[86]=4;[87]=5;[88]=0;[89]=1;[90]=0;[91]=1;[92]=4;[93]=5;[94]=4;[95]=5;[96]=0;[97]=0;[98]=2;[99]=2;[100]=4;[101]=4;[102]=6;[103]=6;[104]=0;[105]=0;[106]=2;[107]=2;[108]=4;[109]=4;[110]=6;[111]=6;[112]=0;[113]=1;[114]=2;[115]=3;[116]=4;[117]=5;[118]=6;[119]=7;[120]=0;[121]=1;[122]=2;[123]=3;[124]=4;[125]=5;[126]=6;[127]=7;[128]=0;[129]=0;[130]=0;[131]=0;[132]=0;[133]=0;[134]=0;[135]=0;[136]=8;[137]=8;[138]=8;[139]=8;[140]=8;[141]=8;[142]=8;[143]=8;[144]=0;[145]=1;[146]=0;[147]=1;[148]=0;[149]=1;[150]=0;[151]=1;[152]=8;[153]=9;[154]=8;[155]=9;[156]=8;[157]=9;[158]=8;[159]=9;[160]=0;[161]=0;[162]=2;[163]=2;[164]=0;[165]=0;[166]=2;[167]=2;[168]=8;[169]=8;[170]=10;[171]=10;[172]=8;[173]=8;[174]=10;[175]=10;[176]=0;[177]=1;[178]=2;[179]=3;[180]=0;[181]=1;[182]=2;[183]=3;[184]=8;[185]=9;[186]=10;[187]=11;[188]=8;[189]=9;[190]=10;[191]=11;[192]=0;[193]=0;[194]=0;[195]=0;[196]=4;[197]=4;[198]=4;[199]=4;[200]=8;[201]=8;[202]=8;[203]=8;[204]=12;[205]=12;[206]=12;[207]=12;[208]=0;[209]=1;[210]=0;[211]=1;[212]=4;[213]=5;[214]=4;[215]=5;[216]=8;[217]=9;[218]=8;[219]=9;[220]=12;[221]=13;[222]=12;[223]=13;[224]=0;[225]=0;[226]=2;[227]=2;[228]=4;[229]=4;[230]=6;[231]=6;[232]=8;[233]=8;[234]=10;[235]=10;[236]=12;[237]=12;[238]=14;[239]=14;[240]=0;[241]=1;[242]=2;[243]=3;[244]=4;[245]=5;[246]=6;[247]=7;[248]=8;[249]=9;[250]=10;[251]=11;[252]=12;[253]=13;[254]=14;[255]=15;} local not_map = {[0]=15;[1]=14;[2]=13;[3]=12;[4]=11;[5]=10;[6]=9;[7]=8;[8]=7;[9]=6;[10]=5;[11]=4;[12]=3;[13]=2;[14]=1;[15]=0;}; local rshift1_map = {[0]=0;[1]=0;[2]=1;[3]=1;[4]=2;[5]=2;[6]=3;[7]=3;[8]=4;[9]=4;[10]=5;[11]=5;[12]=6;[13]=6;[14]=7;[15]=7;}; local rshift1carry_map = {[0]=0;[1]=8;[2]=0;[3]=8;[4]=0;[5]=8;[6]=0;[7]=8;[8]=0;[9]=8;[10]=0;[11]=8;[12]=0;[13]=8;[14]=0;[15]=8;}; local lshift1_map = {[0]=0;[1]=2;[2]=4;[3]=6;[4]=8;[5]=10;[6]=12;[7]=14;[8]=0;[9]=2;[10]=4;[11]=6;[12]=8;[13]=10;[14]=12;[15]=14;}; local lshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=1;[9]=1;[10]=1;[11]=1;[12]=1;[13]=1;[14]=1;[15]=1;}; local arshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=8;[9]=8;[10]=8;[11]=8;[12]=8;[13]=8;[14]=8;[15]=8;}; module "bit" local bit_mt = {__tostring = function(t) return ("%x%x%x%x%x%x%x%x"):format(t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8]); end}; local function do_bop(a, b, op) return setmetatable({ op[a[1]*16+b[1]]; op[a[2]*16+b[2]]; op[a[3]*16+b[3]]; op[a[4]*16+b[4]]; op[a[5]*16+b[5]]; op[a[6]*16+b[6]]; op[a[7]*16+b[7]]; op[a[8]*16+b[8]]; }, bit_mt); end local function do_uop(a, op) return setmetatable({ op[a[1]]; op[a[2]]; op[a[3]]; op[a[4]]; op[a[5]]; op[a[6]]; op[a[7]]; op[a[8]]; }, bit_mt); end function bxor(a, b) return do_bop(a, b, xor_map); end function bor(a, b) return do_bop(a, b, or_map); end function band(a, b) return do_bop(a, b, and_map); end function bnot(a) return do_uop(a, not_map); end local function _rshift1(t) local carry = 0; for i=1,8 do local t_i = rshift1_map[t[i]] + carry; carry = rshift1carry_map[t[i]]; t[i] = t_i; end end function rshift(a, i) local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]}; for _ = 1, i do _rshift1(t); end return setmetatable(t, bit_mt); end local function _arshift1(t) local carry = arshift1carry_map[t[1]]; for i=1,8 do local t_i = rshift1_map[t[i]] + carry; carry = rshift1carry_map[t[i]]; t[i] = t_i; end end function arshift(a, i) local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]}; for _ = 1, i do _arshift1(t); end return setmetatable(t, bit_mt); end local function _lshift1(t) local carry = 0; for i=8,1,-1 do local t_i = lshift1_map[t[i]] + carry; carry = lshift1carry_map[t[i]]; t[i] = t_i; end end function lshift(a, i) local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]}; for _ = 1, i do _lshift1(t); end return setmetatable(t, bit_mt); end local function _cast(a) if type(a) == "number" then a = ("%x"):format(a); elseif type(a) == "table" then return a; elseif type(a) ~= "string" then error("string expected, got "..type(a), 2); end local t = {0,0,0,0,0,0,0,0}; a = "00000000"..a; a = a:sub(-8); for i = 1,8 do t[i] = tonumber(a:sub(i,i), 16) or error("Number format error", 2); end return setmetatable(t, bit_mt); end local function wrap1(f) return function(a, ...) if type(a) ~= "table" then a = _cast(a); end a = f(a, ...); a = tonumber(tostring(a), 16); if a > 0x7fffffff then a = a - 1 - 0xffffffff; end return a; end; end local function wrap2(f) return function(a, b, ...) if type(a) ~= "table" then a = _cast(a); end if type(b) ~= "table" then b = _cast(b); end a = f(a, b, ...); a = tonumber(tostring(a), 16); if a > 0x7fffffff then a = a - 1 - 0xffffffff; end return a; end; end bxor = wrap2(bxor); bor = wrap2(bor); band = wrap2(band); bnot = wrap1(bnot); lshift = wrap1(lshift); rshift = wrap1(rshift); arshift = wrap1(arshift); cast = wrap1(_cast); bits = 32; return _M; prosody-0.12.4/fallbacks/PaxHeaders/lxp.lua0000644000000000000000000000012014475665104015614 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/fallbacks/lxp.lua0000644000175000017500000000706314475665104020027 0ustar00prosodyprosody00000000000000 local coroutine = coroutine; local tonumber = tonumber; local string = string; local setmetatable, getmetatable = setmetatable, getmetatable; local pairs = pairs; local deadroutine = coroutine.create(function() end); coroutine.resume(deadroutine); module("lxp") local entity_map = setmetatable({ ["amp"] = "&"; ["gt"] = ">"; ["lt"] = "<"; ["apos"] = "'"; ["quot"] = "\""; }, {__index = function(_, s) if s:sub(1,1) == "#" then if s:sub(2,2) == "x" then return string.char(tonumber(s:sub(3), 16)); else return string.char(tonumber(s:sub(2))); end end end }); local function xml_unescape(str) return (str:gsub("&(.-);", entity_map)); end local function parse_tag(s) local name,sattr=(s):gmatch("([^%s]+)(.*)")(); local attr = {}; for a,b in (sattr):gmatch("([^=%s]+)=['\"]([^'\"]*)['\"]") do attr[a] = xml_unescape(b); end return name, attr; end local function parser(data, handlers, ns_separator) local function read_until(str) local pos = data:find(str, nil, true); while not pos do data = data..coroutine.yield(); pos = data:find(str, nil, true); end local r = data:sub(1, pos); data = data:sub(pos+1); return r; end local function read_before(str) local pos = data:find(str, nil, true); while not pos do data = data..coroutine.yield(); pos = data:find(str, nil, true); end local r = data:sub(1, pos-1); data = data:sub(pos); return r; end local function peek() while #data == 0 do data = coroutine.yield(); end return data:sub(1,1); end local ns = { xml = "http://www.w3.org/XML/1998/namespace" }; ns.__index = ns; local function apply_ns(name, dodefault) local prefix,n = name:match("^([^:]*):(.*)$"); if prefix and ns[prefix] then return ns[prefix]..ns_separator..n; end if dodefault and ns[""] then return ns[""]..ns_separator..name; end return name; end local function push(tag, attr) ns = setmetatable({}, ns); for k,v in pairs(attr) do local xmlns = k == "xmlns" and "" or k:match("^xmlns:(.*)$"); if xmlns then ns[xmlns] = v; attr[k] = nil; end end local newattr, n = {}, 0; for k,v in pairs(attr) do n = n+1; k = apply_ns(k); newattr[n] = k; newattr[k] = v; end tag = apply_ns(tag, true); ns[0] = tag; ns.__index = ns; return tag, newattr; end local function pop() local tag = ns[0]; ns = getmetatable(ns); return tag; end while true do if peek() == "<" then local elem = read_until(">"):sub(2,-2); if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions elseif elem:sub(1,1) == "/" then -- end tag elem = elem:sub(2); local name = pop(); handlers:EndElement(name); -- TODO check for start-end tag name match elseif elem:sub(-1,-1) == "/" then -- empty tag elem = elem:sub(1,-2); local name,attr = parse_tag(elem); name,attr = push(name,attr); handlers:StartElement(name,attr); name = pop(); handlers:EndElement(name); else -- start tag local name,attr = parse_tag(elem); name,attr = push(name,attr); handlers:StartElement(name,attr); end else local text = read_before("<"); handlers:CharacterData(xml_unescape(text)); end end end function new(handlers, ns_separator) local co = coroutine.create(parser); return { parse = function(self, data) if not data then co = deadroutine; return true; -- eof end local success, result = coroutine.resume(co, data, handlers, ns_separator); if result then co = deadroutine; return nil, result; -- error end return true; -- success end; }; end return _M; prosody-0.12.4/PaxHeaders/makefile0000644000000000000000000000012014475665104014064 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.628765579 prosody-0.12.4/makefile0000644000175000017500000001056714475665104016302 0ustar00prosodyprosody00000000000000 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 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-etc: prosody.cfg.lua.install $(MKDIR) $(CONFIG) $(MKDIR) $(CONFIG)/certs test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua .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-bin: prosody.install prosodyctl.install $(MKDIR) $(BIN) $(INSTALL_EXEC) ./prosody.install $(BIN)/prosody $(INSTALL_EXEC) ./prosodyctl.install $(BIN)/prosodyctl install-core: $(MKDIR) $(SOURCE) $(MKDIR) $(SOURCE)/core $(INSTALL_DATA) core/*.lua $(SOURCE)/core install-net: $(MKDIR) $(SOURCE) $(MKDIR) $(SOURCE)/net $(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-util: util/encodings.so util/encodings.so util/pposix.so util/signal.so $(MKDIR) $(SOURCE) $(MKDIR) $(SOURCE)/util $(INSTALL_DATA) util/*.lua $(SOURCE)/util $(MAKE) install -C util-src $(INSTALL_DATA) util/*.so $(SOURCE)/util $(MKDIR) $(SOURCE)/util/sasl $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl $(MKDIR) $(SOURCE)/util/human $(INSTALL_DATA) util/human/*.lua $(SOURCE)/util/human $(MKDIR) $(SOURCE)/util/prosodyctl $(INSTALL_DATA) util/prosodyctl/*.lua $(SOURCE)/util/prosodyctl install-plugins: $(MKDIR) $(MODULES) $(MKDIR) $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam $(INSTALL_DATA) plugins/*.lua $(MODULES) $(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-man: $(MKDIR) $(MAN)/man1 $(INSTALL_DATA) man/prosodyctl.man $(MAN)/man1/prosodyctl.1 install-meta: -test -f prosody.version && $(INSTALL_DATA) prosody.version $(SOURCE)/prosody.version install-data: $(MKDIR_PRIVATE) $(DATA) install: install-util install-net install-core install-plugins install-bin install-etc install-man install-meta install-data clean: rm -f prosody.install rm -f prosodyctl.install rm -f prosody.cfg.lua.install rm -f prosody.version $(MAKE) clean -C util-src 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 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: if [ -f prosody.release ]; then \ cp prosody.release $@; \ elif [ -f .hg_archival.txt ]; then \ sed -n 's/^node: \(............\).*/\1/p' .hg_archival.txt > $@; \ elif [ -f .hg/dirstate ]; then \ hexdump -n6 -e'6/1 "%02x"' .hg/dirstate > $@; \ else \ echo unknown > $@; \ fi prosody-0.12.4/PaxHeaders/man0000644000000000000000000000013014475665104013063 xustar0030 mtime=1693936196.840763501 29 atime=1693936196.63276554 29 ctime=1693936196.63276554 prosody-0.12.4/man/0000755000175000017500000000000014475665104015344 5ustar00prosodyprosody00000000000000prosody-0.12.4/man/PaxHeaders/Makefile0000644000000000000000000000011714475665104014605 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.63276554 prosody-0.12.4/man/Makefile0000644000175000017500000000010214475665104016775 0ustar00prosodyprosody00000000000000all: prosodyctl.man %.man: %.markdown pandoc -s -t man -o $@ $^ prosody-0.12.4/man/PaxHeaders/prosodyctl.man0000644000000000000000000000011714475665104016044 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.63276554 prosody-0.12.4/man/prosodyctl.man0000644000175000017500000001137414475665104020251 0ustar00prosodyprosody00000000000000.\" Automatically generated by Pandoc 2.17.0.1 .\" .TH "PROSODYCTL" "1" "2022-02-02" "" "" .hy .SH NAME .PP prosodyctl - Manage a Prosody XMPP server .SH SYNOPSIS .IP .nf \f[C] prosodyctl command [--help] \f[R] .fi .SH DESCRIPTION .PP prosodyctl is the control tool for the Prosody XMPP server. It may be used to control the server daemon and manage users. .PP prosodyctl needs to be executed with sufficient privileges to perform its commands. This typically means executing prosodyctl as the root user. If a user named \[lq]prosody\[rq] is found then prosodyctl will change to that user before executing its commands. .SH COMMANDS .SS User Management .PP In the following commands users are identified by a Jabber ID, jid, of the usual form: user\[at]domain. .TP adduser jid Adds a user with Jabber ID, jid, to the server. You will be prompted to enter the user\[cq]s password. .TP passwd jid Changes the password of an existing user with Jabber ID, jid. You will be prompted to enter the user\[cq]s new password. .TP deluser jid Deletes an existing user with Jabber ID, jid, from the server. .SS Daemon Management .PP Although prosodyctl has commands to manage the prosody daemon it is recommended that you utilize your distributions daemon management features if you attained Prosody through a package. .PP To perform daemon control commands prosodyctl needs a pidfile value specified in \f[C]/etc/prosody/prosody.cfg.lua\f[R]. Failure to do so will cause prosodyctl to complain. .TP start Starts the prosody server daemon. If run as root prosodyctl will attempt to change to a user named \[lq]prosody\[rq] before executing. This operation will block for up to five seconds to wait for the server to execute. .TP stop Stops the prosody server daemon. This operation will block for up to five seconds to wait for the server to stop executing. .TP restart Restarts the prosody server daemon. Equivalent to running prosodyctl stop followed by prosodyctl start. .TP reload Signals the prosody server daemon to reload configuration and reopen log files. .TP status Prints the current execution status of the prosody server daemon. .SS Certificates .PP prosodyctl can create self-signed certificates, certificate requests and private keys for use with Prosody. Commands are of the form \f[C]prosodyctl cert subcommand\f[R]. Commands take a list of hosts to be included in the certificate. .TP \f[B]\f[CB]request hosts\f[B]\f[R] Create a certificate request (CSR) file for submission to a certificate authority. Multiple hosts can be given, sub-domains are automatically included. .TP \f[B]\f[CB]generate hosts\f[B]\f[R] Generate a self-signed certificate. .TP \f[B]\f[CB]key host [size]\f[B]\f[R] Generate a private key of `size' bits (defaults to 2048). Invoked automatically by `request' and `generate' if needed. .TP \f[B]\f[CB]config hosts\f[B]\f[R] Produce a config file for the list of hosts. Invoked automatically by `request' and `generate' if needed. .TP \f[B]\f[CB]import hosts paths\f[B]\f[R] Copy certificates for hosts into the certificate path and reload prosody. .SS Debugging .PP prosodyctl can also show some information about the environment, dependencies and such to aid in debugging. .TP \f[B]\f[CB]about\f[B]\f[R] Shows environment, various paths used by Prosody and installed dependencies. .TP \f[B]\f[CB]check [what]\f[B]\f[R] Performs various sanity checks on the configuration, DNS setup and configured TLS certificates. \f[C]what\f[R] can be one of \f[C]config\f[R], \f[C]dns\f[R] \f[C]certs\f[R], \f[C]disabled\f[R] and \f[C]connectivity\f[R] to run only that check. .SS Ejabberd Compatibility .PP ejabberd is another XMPP server which provides a comparable control tool, ejabberdctl, to control its server\[cq]s operations. prosodyctl implements some commands which are compatible with ejabberdctl. For details of how these commands work you should see ejabberdctl(8). .IP .nf \f[C] register user server password unregister user server \f[R] .fi .SH OPTIONS .TP \f[B]\f[CB]--config filename\f[B]\f[R] Use the specified config file instead of the default. .TP \f[B]\f[CB]--root\f[B]\f[R] Don\[cq]t drop root privileges (e.g.\ when invoked with sudo). .TP \f[B]\f[CB]--help\f[B]\f[R] Display help text for the specified command. .TP \f[B]\f[CB]--verbose\f[B]\f[R] Increase log level to show debug messages. .TP \f[B]\f[CB]--quiet\f[B]\f[R] Reduce log level to only show errors. .TP \f[B]\f[CB]--silent\f[B]\f[R] Disable logging completely, leaving only command output. .SH FILES .TP \f[B]\f[CB]/etc/prosody/prosody.cfg.lua\f[B]\f[R] The main prosody configuration file. prosodyctl reads this to determine the process ID file of the prosody server daemon and to determine if a host has been configured. .SH ONLINE .PP More information may be found online at: .SH AUTHORS Dwayne Bent ; Kim Alvefur. prosody-0.12.4/man/PaxHeaders/prosodyctl.markdown0000644000000000000000000000011714475665104017113 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.63276554 prosody-0.12.4/man/prosodyctl.markdown0000644000175000017500000001057114475665104021316 0ustar00prosodyprosody00000000000000--- author: - Dwayne Bent - Kim Alvefur date: 2022-02-02 section: 1 title: PROSODYCTL --- # NAME prosodyctl - Manage a Prosody XMPP server # SYNOPSIS prosodyctl command [--help] # DESCRIPTION prosodyctl is the control tool for the Prosody XMPP server. It may be used to control the server daemon and manage users. prosodyctl needs to be executed with sufficient privileges to perform its commands. This typically means executing prosodyctl as the root user. If a user named "prosody" is found then prosodyctl will change to that user before executing its commands. # COMMANDS ## User Management In the following commands users are identified by a Jabber ID, jid, of the usual form: user@domain. adduser jid : Adds a user with Jabber ID, jid, to the server. You will be prompted to enter the user's password. passwd jid : Changes the password of an existing user with Jabber ID, jid. You will be prompted to enter the user's new password. deluser jid : Deletes an existing user with Jabber ID, jid, from the server. ## Daemon Management Although prosodyctl has commands to manage the prosody daemon it is recommended that you utilize your distributions daemon management features if you attained Prosody through a package. To perform daemon control commands prosodyctl needs a pidfile value specified in `/etc/prosody/prosody.cfg.lua`. Failure to do so will cause prosodyctl to complain. start : Starts the prosody server daemon. If run as root prosodyctl will attempt to change to a user named "prosody" before executing. This operation will block for up to five seconds to wait for the server to execute. stop : Stops the prosody server daemon. This operation will block for up to five seconds to wait for the server to stop executing. restart : Restarts the prosody server daemon. Equivalent to running prosodyctl stop followed by prosodyctl start. reload : Signals the prosody server daemon to reload configuration and reopen log files. status : Prints the current execution status of the prosody server daemon. ## Certificates prosodyctl can create self-signed certificates, certificate requests and private keys for use with Prosody. Commands are of the form `prosodyctl cert subcommand`. Commands take a list of hosts to be included in the certificate. `request hosts` : Create a certificate request (CSR) file for submission to a certificate authority. Multiple hosts can be given, sub-domains are automatically included. `generate hosts` : Generate a self-signed certificate. `key host [size]` : Generate a private key of 'size' bits (defaults to 2048). Invoked automatically by 'request' and 'generate' if needed. `config hosts` : Produce a config file for the list of hosts. Invoked automatically by 'request' and 'generate' if needed. `import hosts paths` : Copy certificates for hosts into the certificate path and reload prosody. ## Debugging prosodyctl can also show some information about the environment, dependencies and such to aid in debugging. `about` : Shows environment, various paths used by Prosody and installed dependencies. `check [what]` : Performs various sanity checks on the configuration, DNS setup and configured TLS certificates. `what` can be one of `config`, `dns` `certs`, `disabled` and `connectivity` to run only that check. ## Ejabberd Compatibility ejabberd is another XMPP server which provides a comparable control tool, ejabberdctl, to control its server's operations. prosodyctl implements some commands which are compatible with ejabberdctl. For details of how these commands work you should see ejabberdctl(8). register user server password unregister user server # OPTIONS `--config filename` : Use the specified config file instead of the default. `--root` : Don't drop root privileges (e.g. when invoked with sudo). `--help` : Display help text for the specified command. `--verbose` : Increase log level to show debug messages. `--quiet` : Reduce log level to only show errors. `--silent` : Disable logging completely, leaving only command output. # FILES `/etc/prosody/prosody.cfg.lua` : The main prosody configuration file. prosodyctl reads this to determine the process ID file of the prosody server daemon and to determine if a host has been configured. # ONLINE More information may be found online at: prosody-0.12.4/PaxHeaders/net0000644000000000000000000000013114475665104013077 xustar0030 mtime=1693936196.840763501 29 atime=1693936196.63276554 30 ctime=1693936196.644765423 prosody-0.12.4/net/0000755000175000017500000000000014475665104015357 5ustar00prosodyprosody00000000000000prosody-0.12.4/net/PaxHeaders/adns.lua0000644000000000000000000000011714475665104014610 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.63276554 prosody-0.12.4/net/adns.lua0000644000175000017500000001032614475665104017011 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local server = require "net.server"; local new_resolver = require "net.dns".resolver; local promise = require "util.promise"; local log = require "util.logger".init("adns"); log("debug", "Using legacy DNS API (missing lua-unbound?)"); -- TODO write docs about luaunbound -- TODO Raise log level once packages are available local coroutine, pcall = coroutine, pcall; local setmetatable = setmetatable; local function dummy_send(sock, data, i, j) return (j-i)+1; end -- luacheck: ignore 212 local _ENV = nil; -- luacheck: std none local async_resolver_methods = {}; local async_resolver_mt = { __index = async_resolver_methods }; local query_methods = {}; local query_mt = { __index = query_methods }; local function new_async_socket(sock, resolver) local peername = ""; local listener = {}; local handler = {}; function listener.onincoming(conn, data) -- luacheck: ignore 212/conn if data then resolver:feed(handler, data); end end function listener.ondisconnect(conn, err) if err then log("warn", "DNS socket for %s disconnected: %s", peername, err); local servers = resolver.server; if resolver.socketset[conn] == resolver.best_server and resolver.best_server == #servers then log("warn", "Exhausted all %d configured DNS servers, next lookup will try %s again", #servers, servers[1]); end resolver:servfail(conn); -- Let the magic commence end end do local err; handler, err = server.wrapclient(sock, "dns", 53, listener); if not handler then return nil, err; end end if handler.set then -- server_epoll: only watch for incoming data -- avoids sending empty packet on first 'onwritable' event handler:set(true, false); end handler.settimeout = function () end handler.setsockname = function (_, ...) return sock:setsockname(...); end handler.setpeername = function (_, ...) peername = (...); local ret, err = sock:setpeername(...); _:set_send(dummy_send); return ret, err; end handler.connect = function (_, ...) return sock:connect(...) end --handler.send = function (_, data) _:write(data); return _.sendbuffer and _.sendbuffer(); end handler.send = function (_, data) log("debug", "Sending DNS query to %s", peername); return sock:send(data); end return handler; end function async_resolver_methods:lookup(handler, qname, qtype, qclass) local resolver = self._resolver; return coroutine.wrap(function (peek) if peek then log("debug", "Records for %s already cached, using those...", qname); handler(peek); return; end log("debug", "Records for %s not in cache, sending query (%s)...", qname, coroutine.running()); local ok, err = resolver:query(qname, qtype, qclass); if ok then coroutine.yield(setmetatable({ resolver, qclass or "IN", qtype or "A", qname, coroutine.running()}, query_mt)); -- Wait for reply log("debug", "Reply for %s (%s)", qname, coroutine.running()); end if ok then ok, err = pcall(handler, resolver:peek(qname, qtype, qclass)); else log("error", "Error sending DNS query: %s", err); ok, err = pcall(handler, nil, err); end if not ok then log("error", "Error in DNS response handler: %s", err); end end)(resolver:peek(qname, qtype, qclass)); end function async_resolver_methods:lookup_promise(qname, qtype, qclass) return promise.new(function (resolve, reject) local function handler(answer) if not answer then return reject(); end resolve(answer); end self:lookup(handler, qname, qtype, qclass); end); end function query_methods:cancel(call_handler, reason) -- luacheck: ignore 212/reason log("warn", "Cancelling DNS lookup for %s", self[4]); self[1].cancel(self[2], self[3], self[4], self[5], call_handler); end local function new_async_resolver() local resolver = new_resolver(); resolver:socket_wrapper_set(new_async_socket); return setmetatable({ _resolver = resolver}, async_resolver_mt); end return { lookup = function (...) return new_async_resolver():lookup(...); end; resolver = new_async_resolver; new_async_socket = new_async_socket; }; prosody-0.12.4/net/PaxHeaders/connect.lua0000644000000000000000000000011714475665104015314 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.63276554 prosody-0.12.4/net/connect.lua0000644000175000017500000000557214475665104017524 0ustar00prosodyprosody00000000000000local server = require "net.server"; local log = require "util.logger".init("net.connect"); local new_id = require "util.id".short; -- TODO #1246 Happy Eyeballs -- FIXME RFC 6724 -- FIXME Error propagation from resolvers doesn't work -- FIXME #1428 Reuse DNS resolver object between service and basic resolver -- FIXME #1429 Close DNS resolver object when done 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", p.target_resolver.last_error); if p.listeners.onfail then p.listeners.onfail(p.data, p.last_error or p.target_resolver.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", extra and extra.sslctx or p.options.sslctx, conn_type, extra); if not conn then log("debug", "Connection attempt failed immediately: %s", 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; }; prosody-0.12.4/net/PaxHeaders/cqueues.lua0000644000000000000000000000011714475665104015335 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.63276554 prosody-0.12.4/net/cqueues.lua0000644000175000017500000000216314475665104017536 0ustar00prosodyprosody00000000000000-- 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"; local timer = require "util.timer"; 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.watchfd then cq = cqueues.new(); local timeout = timer.add_task(cq:timeout() or 0, function () -- FIXME It should be enough to reschedule this timeout instead of replacing it, but this does not work. See https://issues.prosody.im/1572 assert(cq:loop(0)); return cq:timeout(); end); server.watchfd(cq:pollfd(), function () assert(cq:loop(0)); local t = cq:timeout(); if t then timer.stop(timeout); timeout = timer.add_task(cq:timeout(), function () assert(cq:loop(0)); return cq:timeout(); end); end end); else error "NYI" end return { cq = cq; } prosody-0.12.4/net/PaxHeaders/dns.lua0000644000000000000000000000011714475665104014447 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.63276554 prosody-0.12.4/net/dns.lua0000644000175000017500000011034414475665104016651 0ustar00prosodyprosody00000000000000-- Prosody IM -- This file is included with Prosody IM. It has modifications, -- which are hereby placed in the public domain. -- todo: quick (default) header generation -- todo: nxdomain, error handling -- todo: cache results of encodeName -- reference: http://tools.ietf.org/html/rfc1035 -- reference: http://tools.ietf.org/html/rfc1876 (LOC) local socket = require "socket"; local have_timer, timer = pcall(require, "util.timer"); local new_ip = require "util.ip".new_ip; local have_util_net, util_net = pcall(require, "util.net"); local log = require "util.logger".init("dns"); local _, windows = pcall(require, "util.windows"); local is_windows = (_ and windows) or os.getenv("WINDIR"); local coroutine, io, math, string, table = coroutine, io, math, string, table; local ipairs, next, pairs, print, setmetatable, tostring, assert, error, select, type = ipairs, next, pairs, print, setmetatable, tostring, assert, error, select, type; local ztact = { -- public domain 20080404 lua@ztact.com get = function(parent, ...) local len = select('#', ...); for i=1,len do parent = parent[select(i, ...)]; if parent == nil then break; end end return parent; end; set = function(parent, ...) local len = select('#', ...); local key, value = select(len-1, ...); local cutpoint, cutkey; for i=1,len-2 do local key = select (i, ...) local child = parent[key] if value == nil then if child == nil then return; elseif next(child, next(child)) then cutpoint = nil; cutkey = nil; elseif cutpoint == nil then cutpoint = parent; cutkey = key; end elseif child == nil then child = {}; parent[key] = child; end parent = child end if value == nil and cutpoint then cutpoint[cutkey] = nil; else parent[key] = value; return value; end end; }; local get, set = ztact.get, ztact.set; local default_timeout = 5; local default_jitter = 1; local default_retry_jitter = 2; -------------------------------------------------- module dns local _ENV = nil; -- luacheck: std none local dns = {}; -- dns type & class codes ------------------------------ dns type & class codes local append = table.insert local function highbyte(i) -- - - - - - - - - - - - - - - - - - - highbyte return (i-(i%0x100))/0x100; end local function augment (t, prefix) -- - - - - - - - - - - - - - - - - augment local a = {}; for i,s in pairs(t) do a[i] = s; a[s] = s; a[string.lower(s)] = s; end setmetatable(a, { __index = function (_, i) if type(i) == "number" then return ("%s%d"):format(prefix, i); elseif type(i) == "string" then return i:upper(); end end; }) return a; end local function encode (t) -- - - - - - - - - - - - - - - - - - - - - encode local code = {}; for i,s in pairs(t) do local word = string.char(highbyte(i), i%0x100); code[i] = word; code[s] = word; code[string.lower(s)] = word; end return code; end dns.types = { [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] = '*' }; dns.type = augment (dns.types, "TYPE"); dns.class = augment (dns.classes, "CLASS"); dns.typecode = encode (dns.types); dns.classcode = encode (dns.classes); local function standardize(qname, qtype, qclass) -- - - - - - - standardize if string.byte(qname, -1) ~= 0x2E then qname = qname..'.'; end qname = string.lower(qname); return qname, dns.type[qtype or 'A'], dns.class[qclass or 'IN']; end local function prune(rrs, time, soft) -- - - - - - - - - - - - - - - prune time = time or socket.gettime(); for i,rr in ipairs(rrs) do if rr.tod then if rr.tod < time then rrs[rr[rr.type:lower()]] = nil; table.remove(rrs, i); return prune(rrs, time, soft); -- Re-iterate end elseif soft == 'soft' then -- What is this? I forget! assert(rr.ttl == 0); rrs[rr[rr.type:lower()]] = nil; table.remove(rrs, i); end end end -- metatables & co. ------------------------------------------ metatables & co. local resolver = {}; resolver.__index = resolver; resolver.timeout = default_timeout; local function default_rr_tostring(rr) local rr_val = rr.type and rr[rr.type:lower()]; if type(rr_val) ~= "string" then return ""; end return rr_val; end local special_tostrings = { LOC = resolver.LOC_tostring; MX = function (rr) return string.format('%2i %s', rr.pref, rr.mx); end; SRV = function (rr) local s = rr.srv; return string.format('%5d %5d %5d %s', s.priority, s.weight, s.port, s.target); end; }; local rr_metatable = {}; -- - - - - - - - - - - - - - - - - - - rr_metatable function rr_metatable.__tostring(rr) local rr_string = (special_tostrings[rr.type] or default_rr_tostring)(rr); return string.format('%2s %-5s %6i %-28s %s', rr.class, rr.type, rr.ttl, rr.name, rr_string); end local rrs_metatable = {}; -- - - - - - - - - - - - - - - - - - rrs_metatable function rrs_metatable.__tostring(rrs) local t = {}; for _, rr in ipairs(rrs) do append(t, tostring(rr)..'\n'); end return table.concat(t); end local cache_metatable = {}; -- - - - - - - - - - - - - - - - cache_metatable function cache_metatable.__tostring(cache) local time = socket.gettime(); local t = {}; for class,types in pairs(cache) do for type,names in pairs(types) do for name,rrs in pairs(names) do prune(rrs, time); append(t, tostring(rrs)); end end end return table.concat(t); end -- packet layer -------------------------------------------------- packet layer function dns.random(...) -- - - - - - - - - - - - - - - - - - - dns.random math.randomseed(math.floor(10000*socket.gettime()) % 0x80000000); dns.random = math.random; return dns.random(...); end local function encodeHeader(o) -- - - - - - - - - - - - - - - encodeHeader o = o or {}; o.id = o.id or dns.random(0, 0xffff); -- 16b (random) id o.rd = o.rd or 1; -- 1b 1 recursion desired o.tc = o.tc or 0; -- 1b 1 truncated response o.aa = o.aa or 0; -- 1b 1 authoritative response o.opcode = o.opcode or 0; -- 4b 0 query -- 1 inverse query -- 2 server status request -- 3-15 reserved o.qr = o.qr or 0; -- 1b 0 query, 1 response o.rcode = o.rcode or 0; -- 4b 0 no error -- 1 format error -- 2 server failure -- 3 name error -- 4 not implemented -- 5 refused -- 6-15 reserved o.z = o.z or 0; -- 3b 0 reserved o.ra = o.ra or 0; -- 1b 1 recursion available o.qdcount = o.qdcount or 1; -- 16b number of question RRs o.ancount = o.ancount or 0; -- 16b number of answers RRs o.nscount = o.nscount or 0; -- 16b number of nameservers RRs o.arcount = o.arcount or 0; -- 16b number of additional RRs -- string.char() rounds, so prevent roundup with -0.4999 local header = string.char( highbyte(o.id), o.id %0x100, o.rd + 2*o.tc + 4*o.aa + 8*o.opcode + 128*o.qr, o.rcode + 16*o.z + 128*o.ra, highbyte(o.qdcount), o.qdcount %0x100, highbyte(o.ancount), o.ancount %0x100, highbyte(o.nscount), o.nscount %0x100, highbyte(o.arcount), o.arcount %0x100 ); return header, o.id; end local function encodeName(name) -- - - - - - - - - - - - - - - - encodeName local t = {}; for part in string.gmatch(name, '[^.]+') do append(t, string.char(string.len(part))); append(t, part); end append(t, string.char(0)); return table.concat(t); end local function encodeQuestion(qname, qtype, qclass) -- - - - encodeQuestion qname = encodeName(qname); qtype = dns.typecode[qtype or 'a']; qclass = dns.classcode[qclass or 'in']; return qname..qtype..qclass; end function resolver:byte(len) -- - - - - - - - - - - - - - - - - - - - - byte len = len or 1; local offset = self.offset; local last = offset + len - 1; if last > #self.packet then error(string.format('out of bounds: %i>%i', last, #self.packet)); end self.offset = offset + len; return string.byte(self.packet, offset, last); end function resolver:word() -- - - - - - - - - - - - - - - - - - - - - - word local b1, b2 = self:byte(2); return 0x100*b1 + b2; end function resolver:dword () -- - - - - - - - - - - - - - - - - - - - - dword local b1, b2, b3, b4 = self:byte(4); --print('dword', b1, b2, b3, b4); return 0x1000000*b1 + 0x10000*b2 + 0x100*b3 + b4; end function resolver:sub(len) -- - - - - - - - - - - - - - - - - - - - - - sub len = len or 1; local s = string.sub(self.packet, self.offset, self.offset + len - 1); self.offset = self.offset + len; return s; end function resolver:header(force) -- - - - - - - - - - - - - - - - - - header local id = self:word(); --print(string.format(':header id %x', id)); if not self.active[id] and not force then return nil; end local h = { id = id }; local b1, b2 = self:byte(2); h.rd = b1 %2; h.tc = b1 /2%2; h.aa = b1 /4%2; h.opcode = b1 /8%16; h.qr = b1 /128; h.rcode = b2 %16; h.z = b2 /16%8; h.ra = b2 /128; h.qdcount = self:word(); h.ancount = self:word(); h.nscount = self:word(); h.arcount = self:word(); for k,v in pairs(h) do h[k] = v-v%1; end return h; end function resolver:name() -- - - - - - - - - - - - - - - - - - - - - - name local remember, pointers = nil, 0; local len = self:byte(); local n = {}; if len == 0 then return "." end -- Root label while len > 0 do if len >= 0xc0 then -- name is "compressed" pointers = pointers + 1; if pointers >= 20 then error('dns error: 20 pointers'); end; local offset = ((len-0xc0)*0x100) + self:byte(); remember = remember or self.offset; self.offset = offset + 1; -- +1 for lua else -- name is not compressed append(n, self:sub(len)..'.'); end len = self:byte(); end self.offset = remember or self.offset; return table.concat(n); end function resolver:question() -- - - - - - - - - - - - - - - - - - question local q = {}; q.name = self:name(); q.type = dns.type[self:word()]; q.class = dns.class[self:word()]; return q; end function resolver:A(rr) -- - - - - - - - - - - - - - - - - - - - - - - - A local b1, b2, b3, b4 = self:byte(4); 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 local b1, b2 = self:byte(2); table.insert(addr, ("%02x%02x"):format(b1, b2)); end addr = table.concat(addr, ":"):gsub("%f[%x]0+(%x)","%1"); local zeros = {}; for item in addr:gmatch(":[0:]+:[0:]+:") do table.insert(zeros, item) end if #zeros == 0 then rr.aaaa = addr; return elseif #zeros > 1 then table.sort(zeros, function(a, b) return #a > #b end); end 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 function resolver:MX(rr) -- - - - - - - - - - - - - - - - - - - - - - - MX rr.pref = self:word(); rr.mx = self:name(); end function resolver:LOC_nibble_power() -- - - - - - - - - - LOC_nibble_power local b = self:byte(); --print('nibbles', ((b-(b%0x10))/0x10), (b%0x10)); return ((b-(b%0x10))/0x10) * (10^(b%0x10)); end function resolver:LOC(rr) -- - - - - - - - - - - - - - - - - - - - - - LOC rr.version = self:byte(); if rr.version == 0 then rr.loc = rr.loc or {}; rr.loc.size = self:LOC_nibble_power(); rr.loc.horiz_pre = self:LOC_nibble_power(); rr.loc.vert_pre = self:LOC_nibble_power(); rr.loc.latitude = self:dword(); rr.loc.longitude = self:dword(); rr.loc.altitude = self:dword(); end end local function LOC_tostring_degrees(f, pos, neg) -- - - - - - - - - - - - - f = f - 0x80000000; if f < 0 then pos = neg; f = -f; end local deg, min, msec; msec = f%60000; f = (f-msec)/60000; min = f%60; deg = (f-min)/60; return string.format('%3d %2d %2.3f %s', deg, min, msec/1000, pos); end function resolver.LOC_tostring(rr) -- - - - - - - - - - - - - LOC_tostring local t = {}; --[[ for k,name in pairs { 'size', 'horiz_pre', 'vert_pre', 'latitude', 'longitude', 'altitude' } do append(t, string.format('%4s%-10s: %12.0f\n', '', name, rr.loc[name])); end --]] append(t, string.format( '%s %s %.2fm %.2fm %.2fm %.2fm', LOC_tostring_degrees (rr.loc.latitude, 'N', 'S'), LOC_tostring_degrees (rr.loc.longitude, 'E', 'W'), (rr.loc.altitude - 10000000) / 100, rr.loc.size / 100, rr.loc.horiz_pre / 100, rr.loc.vert_pre / 100 )); return table.concat(t); end function resolver:NS(rr) -- - - - - - - - - - - - - - - - - - - - - - - NS rr.ns = self:name(); end function resolver:SOA(rr) -- - - - - - - - - - - - - - - - - - - - - - SOA end function resolver:SRV(rr) -- - - - - - - - - - - - - - - - - - - - - - SRV rr.srv = {}; rr.srv.priority = self:word(); rr.srv.weight = self:word(); rr.srv.port = self:word(); rr.srv.target = self:name(); end function resolver:PTR(rr) rr.ptr = self:name(); end function resolver:TXT(rr) -- - - - - - - - - - - - - - - - - - - - - - TXT rr.txt = self:sub (self:byte()); end function resolver:rr() -- - - - - - - - - - - - - - - - - - - - - - - - rr local rr = {}; setmetatable(rr, rr_metatable); rr.name = self:name(self); rr.type = dns.type[self:word()] or rr.type; rr.class = dns.class[self:word()] or rr.class; rr.ttl = 0x10000*self:word() + self:word(); rr.rdlength = self:word(); rr.tod = self.time + math.max(rr.ttl, 1); local remember = self.offset; local rr_parser = self[dns.type[rr.type]]; if rr_parser then rr_parser(self, rr); end self.offset = remember; rr.rdata = self:sub(rr.rdlength); return rr; end function resolver:rrs (count) -- - - - - - - - - - - - - - - - - - - - - rrs local rrs = {}; for _ = 1, count do append(rrs, self:rr()); end return rrs; end function resolver:decode(packet, force) -- - - - - - - - - - - - - - decode self.packet, self.offset = packet, 1; local header = self:header(force); if not header then return nil; end local response = { header = header }; response.question = {}; local offset = self.offset; for _ = 1, response.header.qdcount do append(response.question, self:question()); end response.question.raw = string.sub(self.packet, offset, self.offset - 1); if not force then if not self.active[response.header.id] or not self.active[response.header.id][response.question.raw] then self.active[response.header.id] = nil; return nil; end end response.answer = self:rrs(response.header.ancount); response.authority = self:rrs(response.header.nscount); response.additional = self:rrs(response.header.arcount); return response; end -- socket layer -------------------------------------------------- socket layer resolver.delays = { 1, 2, 3, 5 }; resolver.jitter = have_timer and default_jitter or nil; resolver.retry_jitter = have_timer and default_retry_jitter or nil; function resolver:addnameserver(address) -- - - - - - - - - - addnameserver self.server = self.server or {}; append(self.server, address); end function resolver:setnameserver(address) -- - - - - - - - - - setnameserver self.server = {}; self:addnameserver(address); end function resolver:adddefaultnameservers() -- - - - - adddefaultnameservers if is_windows then if windows and windows.get_nameservers then for _, server in ipairs(windows.get_nameservers()) do self:addnameserver(server); end end if not self.server or #self.server == 0 then -- TODO log warning about no nameservers, adding opendns servers as fallback self:addnameserver("208.67.222.222"); self:addnameserver("208.67.220.220"); end else -- posix local resolv_conf = io.open("/etc/resolv.conf"); if resolv_conf then for line in resolv_conf:lines() do line = line:gsub("#.*$", "") :match('^%s*nameserver%s+([%x:%.]*%%?%S*)%s*$'); if line then local ip = new_ip(line); if ip then self:addnameserver(ip.addr); end end end resolv_conf:close(); end if not self.server or #self.server == 0 then -- TODO log warning about no nameservers, adding localhost as the default nameserver self:addnameserver("127.0.0.1"); end end end function resolver:getsocket(servernum) -- - - - - - - - - - - - - getsocket self.socket = self.socket or {}; self.socketset = self.socketset or {}; local sock = self.socket[servernum]; if sock then return sock; end local ok, err; local peer = self.server[servernum]; if peer:find(":") then sock, err = socket.udp6(); else sock, err = (socket.udp4 or socket.udp)(); end if sock and self.socket_wrapper then sock, err = self.socket_wrapper(sock, self); end if not sock then return nil, err; end sock:settimeout(0); -- todo: attempt to use a random port, fallback to 0 self.socket[servernum] = sock; self.socketset[sock] = servernum; -- set{sock,peer}name can fail, eg because of local routing table -- if so, try the next server ok, err = sock:setsockname('*', 0); if not ok then return self:servfail(sock, err); end ok, err = sock:setpeername(peer, 53); if not ok then return self:servfail(sock, err); end return sock; end function resolver:voidsocket(sock) if self.socket[sock] then self.socketset[self.socket[sock]] = nil; self.socket[sock] = nil; elseif self.socketset[sock] then self.socket[self.socketset[sock]] = nil; self.socketset[sock] = nil; end sock:close(); end function resolver:socket_wrapper_set(func) -- - - - - - - socket_wrapper_set self.socket_wrapper = func; end function resolver:closeall () -- - - - - - - - - - - - - - - - - - closeall for i,sock in ipairs(self.socket) do self.socket[i] = nil; self.socketset[sock] = nil; sock:close(); end end function resolver:remember(rr, type) -- - - - - - - - - - - - - - remember --print ('remember', type, rr.class, rr.type, rr.name) local qname, qtype, qclass = standardize(rr.name, rr.type, rr.class); if type ~= '*' then type = qtype; local all = get(self.cache, qclass, '*', qname); --print('remember all', all); if all then append(all, rr); end end self.cache = self.cache or setmetatable({}, cache_metatable); local rrs = get(self.cache, qclass, type, qname) or set(self.cache, qclass, type, qname, setmetatable({}, rrs_metatable)); if rr[qtype:lower()] and not rrs[rr[qtype:lower()]] then rrs[rr[qtype:lower()]] = true; append(rrs, rr); end if type == 'MX' then self.unsorted[rrs] = true; end end local function comp_mx(a, b) -- - - - - - - - - - - - - - - - - - - comp_mx return (a.pref == b.pref) and (a.mx < b.mx) or (a.pref < b.pref); end function resolver:peek (qname, qtype, qclass, n) -- - - - - - - - - - - - peek qname, qtype, qclass = standardize(qname, qtype, qclass); local rrs = get(self.cache, qclass, qtype, qname); if not rrs then if n then if n <= 0 then return end else n = 3 end rrs = get(self.cache, qclass, "CNAME", qname); if not (rrs and rrs[1]) then return end return self:peek(rrs[1].cname, qtype, qclass, n - 1); end if prune(rrs, socket.gettime()) and qtype == '*' or not next(rrs) then set(self.cache, qclass, qtype, qname, nil); return nil; end if self.unsorted[rrs] then table.sort (rrs, comp_mx); self.unsorted[rrs] = nil; end return rrs; end function resolver:purge(soft) -- - - - - - - - - - - - - - - - - - - purge if soft == 'soft' then self.time = socket.gettime(); for class,types in pairs(self.cache or {}) do for type,names in pairs(types) do for name,rrs in pairs(names) do prune(rrs, self.time, 'soft') end end end else self.cache = setmetatable({}, cache_metatable); end end function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query qname, qtype, qclass = standardize(qname, qtype, qclass) local co = coroutine.running(); local q = get(self.wanted, qclass, qtype, qname); if co and q then -- We are already waiting for a reply to an identical query. set(self.wanted, qclass, qtype, qname, co, true); return true; end if not self.server then self:adddefaultnameservers(); end local question = encodeQuestion(qname, qtype, qclass); local peek = self:peek (qname, qtype, qclass); if peek then return peek; end local header, id = encodeHeader(); --print ('query id', id, qclass, qtype, qname) local o = { packet = header..question, server = self.best_server, delay = 1, retry = socket.gettime() + self.delays[1]; qclass = qclass; qtype = qtype; qname = qname; }; -- remember the query self.active[id] = self.active[id] or {}; self.active[id][question] = o; local conn, err = self:getsocket(o.server) if not conn then return nil, err; end if self.jitter then timer.add_task(math.random()*self.jitter, function () conn:send(o.packet); end); else conn:send(o.packet); end -- remember which coroutine wants the answer if co then set(self.wanted, qclass, qtype, qname, co, true); end if have_timer and self.timeout then local num_servers = #self.server; local i = 1; timer.add_task(self.timeout, function () if get(self.wanted, qclass, qtype, qname, co) then log("debug", "DNS request timeout %d/%d", i, num_servers) i = i + 1; self:servfail(self.socket[o.server]); -- end end -- Still outstanding? (i.e. retried) if get(self.wanted, qclass, qtype, qname, co) then return self.timeout; -- Then wait end end) end return true; end function resolver:servfail(sock, err) -- Resend all queries for this server local num = self.socketset[sock] -- Socket is dead now sock = self:voidsocket(sock); -- Find all requests to the down server, and retry on the next server self.time = socket.gettime(); log("debug", "servfail %d (of %d)", num, #self.server); for id,queries in pairs(self.active) do for question,o in pairs(queries) do if o.server == num then -- This request was to the broken server o.server = o.server + 1 -- Use next server if o.server > #self.server then o.server = 1; end o.retries = (o.retries or 0) + 1; local retried; if o.retries < #self.server then sock, err = self:getsocket(o.server); if sock then retried = true; if self.retry_jitter then local delay = self.delays[((o.retries-1)%#self.delays)+1] + (math.random()*self.retry_jitter); log("debug", "retry %d in %0.2fs", o.retries, delay); timer.add_task(delay, function () sock:send(o.packet); end); else log("debug", "retry %d (immediate)", o.retries); sock:send(o.packet); end end end if not retried then log("debug", 'tried all servers, giving up'); self:cancel(o.qclass, o.qtype, o.qname); queries[question] = nil; end end end if next(queries) == nil then self.active[id] = nil; end end if num == self.best_server then self.best_server = self.best_server + 1; if self.best_server > #self.server then -- Exhausted all servers, try first again self.best_server = 1; end end return sock, err; end function resolver:settimeout(seconds) self.timeout = seconds; end function resolver:receive(rset) -- - - - - - - - - - - - - - - - - receive --print('receive'); print(self.socket); self.time = socket.gettime(); rset = rset or self.socket; local response; for _, sock in pairs(rset) do if self.socketset[sock] then local packet = sock:receive(); if packet then response = self:decode(packet); if response and self.active[response.header.id] and self.active[response.header.id][response.question.raw] then --print('received response'); --self.print(response); for _, rr in pairs(response.answer) do if rr.name:sub(-#response.question[1].name, -1) == response.question[1].name then self:remember(rr, response.question[1].type) end end -- retire the query local queries = self.active[response.header.id]; queries[response.question.raw] = nil; if not next(queries) then self.active[response.header.id] = nil; end if not next(self.active) then self:closeall(); end -- was the query on the wanted list? local q = response.question[1]; local cos = get(self.wanted, q.class, q.type, q.name); if cos then for co in pairs(cos) do if coroutine.status(co) == "suspended" then coroutine.resume(co); end end set(self.wanted, q.class, q.type, q.name, nil); end end end end end return response; end function resolver:feed(sock, packet, force) --print('receive'); print(self.socket); self.time = socket.gettime(); local response = self:decode(packet, force); if response and self.active[response.header.id] and self.active[response.header.id][response.question.raw] then --print('received response'); --self.print(response); for _, rr in pairs(response.answer) do self:remember(rr, rr.type); end for _, rr in pairs(response.additional) do self:remember(rr, rr.type); end -- retire the query local queries = self.active[response.header.id]; queries[response.question.raw] = nil; if not next(queries) then self.active[response.header.id] = nil; end if not next(self.active) then self:closeall(); end -- was the query on the wanted list? local q = response.question[1]; if q then local cos = get(self.wanted, q.class, q.type, q.name); if cos then for co in pairs(cos) do if coroutine.status(co) == "suspended" then coroutine.resume(co); end end set(self.wanted, q.class, q.type, q.name, nil); end end end return response; end function resolver:cancel(qclass, qtype, qname) local cos = get(self.wanted, qclass, qtype, qname); if cos then for co in pairs(cos) do if coroutine.status(co) == "suspended" then coroutine.resume(co); end end set(self.wanted, qclass, qtype, qname, nil); end end function resolver:pulse() -- - - - - - - - - - - - - - - - - - - - - pulse --print(':pulse'); while self:receive() do end if not next(self.active) then return nil; end self.time = socket.gettime(); for id,queries in pairs(self.active) do for question,o in pairs(queries) do if self.time >= o.retry then o.server = o.server + 1; if o.server > #self.server then o.server = 1; o.delay = o.delay + 1; end if o.delay > #self.delays then --print('timeout'); queries[question] = nil; if not next(queries) then self.active[id] = nil; end if not next(self.active) then return nil; end else --print('retry', o.server, o.delay); local _a = self.socket[o.server]; if _a then _a:send(o.packet); end o.retry = self.time + self.delays[o.delay]; end end end end if next(self.active) then return true; end return nil; end function resolver:lookup(qname, qtype, qclass) -- - - - - - - - - - lookup self:query (qname, qtype, qclass) while self:pulse() do local recvt = {} for i, s in ipairs(self.socket) do recvt[i] = s end socket.select(recvt, nil, 4) end --print(self.cache); return self:peek(qname, qtype, qclass); end function resolver:lookupex(handler, qname, qtype, qclass) -- - - - - - - - - - lookup return self:peek(qname, qtype, qclass) or self:query(qname, qtype, qclass); end function resolver:tohostname(ip) return dns.lookup(ip:gsub("(%d+)%.(%d+)%.(%d+)%.(%d+)", "%4.%3.%2.%1.in-addr.arpa."), "PTR"); end --print ---------------------------------------------------------------- print local hints = { -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints qr = { [0]='query', 'response' }, opcode = { [0]='query', 'inverse query', 'server status request' }, aa = { [0]='non-authoritative', 'authoritative' }, tc = { [0]='complete', 'truncated' }, rd = { [0]='recursion not desired', 'recursion desired' }, ra = { [0]='recursion not available', 'recursion available' }, z = { [0]='(reserved)' }, rcode = { [0]='no error', 'format error', 'server failure', 'name error', 'not implemented' }, type = dns.type, class = dns.class }; local function hint(p, s) -- - - - - - - - - - - - - - - - - - - - - - hint return (hints[s] and hints[s][p[s]]) or ''; end function resolver.print(response) -- - - - - - - - - - - - - resolver.print for _, s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do print( string.format('%-30s', 'header.'..s), response.header[s], hint(response.header, s) ); end for i,question in ipairs(response.question) do print(string.format ('question[%i].name ', i), question.name); print(string.format ('question[%i].type ', i), question.type); print(string.format ('question[%i].class ', i), question.class); end local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }; local tmp; for _, s in pairs({'answer', 'authority', 'additional'}) do for i,rr in pairs(response[s]) do for _, t in pairs({ 'name', 'type', 'class', 'ttl', 'rdlength' }) do tmp = string.format('%s[%i].%s', s, i, t); print(string.format('%-30s', tmp), rr[t], hint(rr, t)); end for j,t in pairs(rr) do if not common[j] then tmp = string.format('%s[%i].%s', s, i, j); print(string.format('%-30s %s', tostring(tmp), tostring(t))); end end end end end -- module api ------------------------------------------------------ module api function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, best_server = 1 }; setmetatable (r, resolver); setmetatable (r.cache, cache_metatable); setmetatable (r.unsorted, { __mode = 'kv' }); return r; end local _resolver = dns.resolver(); dns._resolver = _resolver; _resolver.jitter, _resolver.retry_jitter = false, false; function dns.lookup(...) -- - - - - - - - - - - - - - - - - - - - - lookup return _resolver:lookup(...); end function dns.tohostname(...) return _resolver:tohostname(...); end function dns.purge(...) -- - - - - - - - - - - - - - - - - - - - - - purge return _resolver:purge(...); end function dns.peek(...) -- - - - - - - - - - - - - - - - - - - - - - - peek return _resolver:peek(...); end function dns.query(...) -- - - - - - - - - - - - - - - - - - - - - - query return _resolver:query(...); end function dns.feed(...) -- - - - - - - - - - - - - - - - - - - - - - - feed return _resolver:feed(...); end function dns.cancel(...) -- - - - - - - - - - - - - - - - - - - - - - cancel return _resolver:cancel(...); end function dns.settimeout(...) return _resolver:settimeout(...); end function dns.cache() return _resolver.cache; end function dns.socket_wrapper_set(...) -- - - - - - - - - socket_wrapper_set return _resolver:socket_wrapper_set(...); end return dns; prosody-0.12.4/net/PaxHeaders/http0000644000000000000000000000013114475665104014056 xustar0030 mtime=1693936196.840763501 29 atime=1693936196.63276554 30 ctime=1693936196.636765501 prosody-0.12.4/net/http/0000755000175000017500000000000014475665104016336 5ustar00prosodyprosody00000000000000prosody-0.12.4/net/http/PaxHeaders/codes.lua0000644000000000000000000000011714475665104015737 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.63276554 prosody-0.12.4/net/http/codes.lua0000644000175000017500000000666314475665104020151 0ustar00prosodyprosody00000000000000 local response_codes = { -- Source: http://www.iana.org/assignments/http-status-codes [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"; -- 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"; -- 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"; -- 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"; [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"; -- 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] = ("%03d %s"):format(k, v); end return setmetatable(response_codes, { __index = function(_, k) return k.." Unassigned"; end }) prosody-0.12.4/net/http/PaxHeaders/errors.lua0000644000000000000000000000012014475665104016150 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/http/errors.lua0000644000175000017500000001026114475665104020355 0ustar00prosodyprosody00000000000000-- This module returns a table that is suitable for use as a util.error registry, -- and a function to return a util.error object given callback 'code' and 'body' -- parameters. local codes = require "net.http.codes"; local util_error = require "util.error"; local error_templates = { -- This code is used by us to report a client-side or connection error. -- Instead of using the code, use the supplied body text to get one of -- the more detailed errors below. [0] = { code = 0, type = "cancel", condition = "internal-server-error"; text = "Connection or internal error"; }; -- These are net.http built-in errors, they are returned in -- the body parameter when code == 0 ["cancelled"] = { code = 0, type = "cancel", condition = "remote-server-timeout"; text = "Request cancelled"; }; ["connection-closed"] = { code = 0, type = "wait", condition = "remote-server-timeout"; text = "Connection closed"; }; ["certificate-chain-invalid"] = { code = 0, type = "cancel", condition = "remote-server-timeout"; text = "Server certificate not trusted"; }; ["certificate-verify-failed"] = { code = 0, type = "cancel", condition = "remote-server-timeout"; text = "Server certificate invalid"; }; ["connection failed"] = { code = 0, type = "cancel", condition = "remote-server-not-found"; text = "Connection failed"; }; ["invalid-url"] = { code = 0, type = "modify", condition = "bad-request"; text = "Invalid URL"; }; ["unable to resolve service"] = { code = 0, type = "cancel", condition = "remote-server-not-found"; text = "DNS resolution failed"; }; -- This doesn't attempt to map every single HTTP code (not all have sane mappings), -- but all the common ones should be covered. XEP-0086 was used as reference for -- most of these. [400] = { type = "modify", condition = "bad-request" }; [401] = { type = "auth", condition = "not-authorized" }; [402] = { type = "auth", condition = "payment-required" }; [403] = { type = "auth", condition = "forbidden" }; [404] = { type = "cancel", condition = "item-not-found" }; [405] = { type = "cancel", condition = "not-allowed" }; [406] = { type = "modify", condition = "not-acceptable" }; [407] = { type = "auth", condition = "registration-required" }; [408] = { type = "wait", condition = "remote-server-timeout" }; [409] = { type = "cancel", condition = "conflict" }; [410] = { type = "cancel", condition = "gone" }; [411] = { type = "modify", condition = "bad-request" }; [412] = { type = "cancel", condition = "conflict" }; [413] = { type = "modify", condition = "resource-constraint" }; [414] = { type = "modify", condition = "resource-constraint" }; [415] = { type = "cancel", condition = "feature-not-implemented" }; [416] = { type = "modify", condition = "bad-request" }; [422] = { type = "modify", condition = "bad-request" }; [423] = { type = "wait", condition = "resource-constraint" }; [429] = { type = "wait", condition = "resource-constraint" }; [431] = { type = "modify", condition = "resource-constraint" }; [451] = { type = "auth", condition = "forbidden" }; [500] = { type = "wait", condition = "internal-server-error" }; [501] = { type = "cancel", condition = "feature-not-implemented" }; [502] = { type = "wait", condition = "remote-server-timeout" }; [503] = { type = "cancel", condition = "service-unavailable" }; [504] = { type = "wait", condition = "remote-server-timeout" }; [507] = { type = "wait", condition = "resource-constraint" }; [511] = { type = "auth", condition = "not-authorized" }; }; for k, v in pairs(codes) do if error_templates[k] then error_templates[k].code = k; error_templates[k].text = v; else error_templates[k] = { type = "cancel", condition = "undefined-condition", text = v, code = k }; end end setmetatable(error_templates, { __index = function(_, k) if type(k) ~= "number" then return nil; end return { type = "cancel"; condition = "undefined-condition"; text = codes[k] or (k.." Unassigned"); code = k; }; end }); local function new(code, body, context) if code == 0 then return util_error.new(body, context, error_templates); else return util_error.new(code, context, error_templates); end end return { registry = error_templates; new = new; }; prosody-0.12.4/net/http/PaxHeaders/files.lua0000644000000000000000000000012014475665104015736 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/http/files.lua0000644000175000017500000001064514475665104020151 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local server = require"net.http.server"; local lfs = require "lfs"; local new_cache = require "util.cache".new; local log = require "util.logger".init("net.http.files"); local os_date = os.date; local open = io.open; local stat = lfs.attributes; local build_path = require"socket.url".build_path; local path_sep = package.config:sub(1,1); local forbidden_chars_pattern = "[/%z]"; if package.config:sub(1,1) == "\\" then forbidden_chars_pattern = "[/%z\001-\031\127\"*:<>?|]" end local urldecode = require "util.http".urldecode; local function sanitize_path(path) --> util.paths or util.http? if not path then return end local out = {}; local c = 0; for component in path:gmatch("([^/]+)") do component = urldecode(component); if component:find(forbidden_chars_pattern) then return nil; elseif component == ".." then if c <= 0 then return nil; end out[c] = nil; c = c - 1; elseif component ~= "." then c = c + 1; out[c] = component; end end if path:sub(-1,-1) == "/" then out[c+1] = ""; end return "/"..table.concat(out, "/"); end local function serve(opts) if type(opts) ~= "table" then -- assume path string opts = { path = opts }; end local mime_map = opts.mime_map or { html = "text/html" }; local cache = new_cache(opts.cache_size or 256); local cache_max_file_size = tonumber(opts.cache_max_file_size) or 1024 -- luacheck: ignore 431 local base_path = opts.path; local dir_indices = opts.index_files or { "index.html", "index.htm" }; local directory_index = opts.directory_index; local function serve_file(event, path) local request, response = event.request, event.response; local sanitized_path = sanitize_path(path); if path and not sanitized_path then return 400; end path = sanitized_path; local orig_path = sanitize_path(request.path); local full_path = base_path .. (path or ""):gsub("/", path_sep); local attr = stat(full_path:match("^.*[^\\/]")); -- Strip trailing path separator because Windows if not attr then return 404; end local request_headers, response_headers = request.headers, response.headers; local last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification); response_headers.last_modified = last_modified; local etag = ('"%x-%x-%x"'):format(attr.change or 0, attr.size or 0, attr.modification or 0); response_headers.etag = etag; local if_none_match = request_headers.if_none_match local if_modified_since = request_headers.if_modified_since; if etag == if_none_match or (not if_none_match and last_modified == if_modified_since) then return 304; end local data = cache:get(orig_path); if data and data.etag == etag then response_headers.content_type = data.content_type; data = data.data; cache:set(orig_path, data); elseif attr.mode == "directory" and path then if full_path:sub(-1) ~= "/" then local dir_path = { is_absolute = true, is_directory = true }; for dir in orig_path:gmatch("[^/]+") do dir_path[#dir_path+1]=dir; end response_headers.location = build_path(dir_path); return 301; end for i=1,#dir_indices do if stat(full_path..dir_indices[i], "mode") == "file" then return serve_file(event, path..dir_indices[i]); end end if directory_index then data = server._events.fire_event("directory-index", { path = request.path, full_path = full_path }); end if not data then return 403; end cache:set(orig_path, { data = data, content_type = mime_map.html; etag = etag; }); response_headers.content_type = mime_map.html; else local f, err = open(full_path, "rb"); if not f then log("debug", "Could not open %s. Error was %s", full_path, err); return 403; end local ext = full_path:match("%.([^./]+)$"); local content_type = ext and mime_map[ext]; response_headers.content_type = content_type; if attr.size > cache_max_file_size then response_headers.content_length = ("%d"):format(attr.size); log("debug", "%d > cache_max_file_size", attr.size); return response:send_file(f); else data = f:read("*a"); f:close(); end cache:set(orig_path, { data = data; content_type = content_type; etag = etag }); end return response:send(data); end return serve_file; end return { serve = serve; } prosody-0.12.4/net/http/PaxHeaders/parser.lua0000644000000000000000000000012014475665104016130 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/http/parser.lua0000644000175000017500000001642314475665104020343 0ustar00prosodyprosody00000000000000local tonumber = tonumber; local assert = assert; local url_parse = require "socket.url".parse; local urldecode = require "util.http".urldecode; local dbuffer = require "util.dbuffer"; local function preprocess_path(path) path = urldecode((path:gsub("//+", "/"))); if path:sub(1,1) ~= "/" then path = "/"..path; end local level = 0; for component in path:gmatch("([^/]+)/") do if component == ".." then level = level - 1; elseif component ~= "." then level = level + 1; end if level < 0 then return nil; end end return path; end local httpstream = {}; function httpstream.new(success_cb, error_cb, parser_type, options_cb) local client = true; if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end local bodylimit = tonumber(options_cb and options_cb().body_size_limit) or 10*1024*1024; -- https://stackoverflow.com/a/686243 -- Individual headers can be up to 16k? What madness? local headlimit = tonumber(options_cb and options_cb().head_size_limit) or 10*1024; local buflimit = tonumber(options_cb and options_cb().buffer_size_limit) or bodylimit * 2; local buffer = dbuffer.new(buflimit); local chunked; local state = nil; local packet; local len; local have_body; local error; return { feed = function(_, data) if error then return nil, "parse has failed"; end if not data then -- EOF if state and client and not len then -- reading client body until EOF buffer:collapse(); packet.body = buffer:read_chunk() or ""; packet.partial = nil; success_cb(packet); state = nil; elseif buffer:length() ~= 0 then -- unexpected EOF error = true; return error_cb("unexpected-eof"); end return; end if not buffer:write(data) then error = true; return error_cb("max-buffer-size-exceeded"); end while buffer:length() > 0 do if state == nil then -- read request local index = buffer:sub(1, headlimit):find("\r\n\r\n", nil, true); if not index then return; end -- not enough data -- FIXME was reason_phrase meant to be passed on somewhere? local method, path, httpversion, status_code, reason_phrase; -- luacheck: ignore reason_phrase local first_line; local headers = {}; for line in buffer:read(index+3):gmatch("([^\r\n]+)\r\n") do -- parse request if first_line then local key, val = line:match("^([^%s:]+): *(.*)$"); if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers key = key:lower(); headers[key] = headers[key] and headers[key]..","..val or val; else first_line = line; if client then httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$"); status_code = tonumber(status_code); if not status_code then error = true; return error_cb("invalid-status-line"); end have_body = not ( (options_cb and options_cb().method == "HEAD") or (status_code == 204 or status_code == 304 or status_code == 301) or (status_code >= 100 and status_code < 200) ); else method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$"); if not method then error = true; return error_cb("invalid-status-line"); end end end end if not first_line then error = true; return error_cb("invalid-status-line"); end chunked = have_body and headers["transfer-encoding"] == "chunked"; len = tonumber(headers["content-length"]); -- TODO check for invalid len if client then -- FIXME handle '100 Continue' response (by skipping it) if not have_body then len = 0; end packet = { code = status_code; httpversion = httpversion; headers = headers; body = false; body_length = len; chunked = chunked; partial = true; -- COMPAT the properties below are deprecated responseversion = httpversion; responseheaders = headers; }; else local parsed_url; if path:byte() == 47 then -- starts with / local _path, _query = path:match("([^?]*).?(.*)"); if _query == "" then _query = nil; end parsed_url = { path = _path, query = _query }; else parsed_url = url_parse(path); if not(parsed_url and parsed_url.path) then error = true; return error_cb("invalid-url"); end end path = preprocess_path(parsed_url.path); headers.host = parsed_url.host or headers.host; len = len or 0; packet = { method = method; url = parsed_url; path = path; httpversion = httpversion; headers = headers; body = false; body_sink = nil; chunked = chunked; partial = true; }; end if not len or len > bodylimit then -- Early notification, for redirection success_cb(packet); if not packet.body_sink and (len and len > bodylimit) then error = true; return error_cb("content-length-limit-exceeded"); end end if chunked and not packet.body_sink then success_cb(packet); if not packet.body_sink then packet.body_buffer = dbuffer.new(buflimit); end end state = true; end if state then -- read body if chunked then local chunk_header = buffer:sub(1, 512); -- XXX How large do chunk headers grow? local chunk_size, chunk_start = chunk_header:match("^(%x+)[^\r\n]*\r\n()"); if not chunk_size then return; end chunk_size = chunk_size and tonumber(chunk_size, 16); if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end if chunk_size == 0 and chunk_header:find("\r\n\r\n", chunk_start-2, true) then local body_buffer = packet.body_buffer; if body_buffer then packet.body_buffer = nil; body_buffer:collapse(); packet.body = body_buffer:read_chunk() or ""; end buffer:collapse(); local buf = buffer:read_chunk(); buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped buffer:write(buf); state, chunked = nil, nil; packet.partial = nil; success_cb(packet); elseif buffer:length() - chunk_start - 1 >= chunk_size then -- we have a chunk buffer:discard(chunk_start - 1); (packet.body_sink or packet.body_buffer):write(buffer:read(chunk_size)); buffer:discard(2); -- CRLF else -- Partial chunk remaining break; end elseif packet.body_sink then local chunk = buffer:read_chunk(len); while chunk and (not len or len > 0) do if packet.body_sink:write(chunk) then if len then len = len - #chunk; end chunk = buffer:read_chunk(len); else error = true; return error_cb("body-sink-write-failure"); end end if len == 0 then state = nil; packet.partial = nil; success_cb(packet); end elseif not len or buffer:length() >= len then -- or not len assert(not chunked) packet.body = len and buffer:read(len) or buffer:read_chunk() or ""; state = nil; packet.partial = nil; success_cb(packet); else break; end else break; end end end; }; end return httpstream; prosody-0.12.4/net/http/PaxHeaders/server.lua0000644000000000000000000000012014475665104016142 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/http/server.lua0000644000175000017500000003131314475665104020350 0ustar00prosodyprosody00000000000000 local t_insert, t_concat = table.insert, table.concat; local parser_new = require "net.http.parser".new; local events = require "util.events".new(); local addserver = require "net.server".addserver; local log = require "util.logger".init("http.server"); local os_date = os.date; local pairs = pairs; local s_upper = string.upper; local setmetatable = setmetatable; local cache = require "util.cache"; local codes = require "net.http.codes"; local promise = require "util.promise"; local errors = require "util.error"; local blocksize = 2^16; local async = require "util.async"; local _M = {}; local sessions = {}; local incomplete = {}; local listener = {}; local hosts = {}; local default_host; local options = {}; local function is_wildcard_event(event) return event:sub(-2, -1) == "/*"; end local function is_wildcard_match(wildcard_event, event) return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1); end local _handlers = events._handlers; local recent_wildcard_events = cache.new(10000, function (key, value) -- luacheck: ignore 212/value rawset(_handlers, key, nil); end); local event_map = events._event_map; setmetatable(events._handlers, { -- Called when firing an event that doesn't exist (but may match a wildcard handler) __index = function (handlers, curr_event) if is_wildcard_event(curr_event) then return; end -- Wildcard events cannot be fired -- Find all handlers that could match this event, sort them -- and then put the array into handlers[curr_event] (and return it) local matching_handlers_set = {}; local handlers_array = {}; for event, handlers_set in pairs(event_map) do if event == curr_event or is_wildcard_event(event) and is_wildcard_match(event, curr_event) then for handler, priority in pairs(handlers_set) do matching_handlers_set[handler] = { (select(2, event:gsub("/", "%1"))), is_wildcard_event(event) and 0 or 1, priority }; table.insert(handlers_array, handler); end end end if #handlers_array > 0 then table.sort(handlers_array, function(b, a) local a_score, b_score = matching_handlers_set[a], matching_handlers_set[b]; for i = 1, #a_score do if a_score[i] ~= b_score[i] then -- If equal, compare next score value return a_score[i] < b_score[i]; end end return false; end); else handlers_array = false; end rawset(handlers, curr_event, handlers_array); if not event_map[curr_event] then -- Only wildcard handlers match, if any recent_wildcard_events:set(curr_event, true); end return handlers_array; end; __newindex = function (handlers, curr_event, handlers_array) if handlers_array == nil and is_wildcard_event(curr_event) then -- Invalidate the indexes of all matching events for event in pairs(handlers) do if is_wildcard_match(curr_event, event) then handlers[event] = nil; end end end rawset(handlers, curr_event, handlers_array); end; }); local handle_request; events.add_handler("http-error", function (error) return "Error processing request: "..codes[error.code]..". Check your error log for more information."; end, -1); local runner_callbacks = {}; function runner_callbacks:ready() self.data.conn:resume(); end function runner_callbacks:waiting() self.data.conn:pause(); end function runner_callbacks:error(err) log("error", "Traceback[httpserver]: %s", err); self.data.conn:write("HTTP/1.0 500 Internal Server Error\r\n\r\n"..events.fire_event("http-error", { code = 500, private_message = err })); self.data.conn:close(); end local function noop() end function listener.onconnect(conn) local session = { conn = conn }; local secure = conn:ssl() and true or nil; local ip = conn:ip(); session.thread = async.runner(function (request) local wait, done; if request.partial == true then -- Have the header for a request, we want to receive the rest -- when we've decided where the data should go. wait, done = noop, noop; else -- Got the entire request -- Hold off on receiving more incoming requests until this one has been handled. wait, done = async.waiter(); end handle_request(conn, request, done); wait(); end, runner_callbacks, session); local function success_cb(request) --log("debug", "success_cb: %s", request.path); request.ip = ip; request.secure = secure; session.thread:run(request); end local function error_cb(err) log("debug", "error_cb: %s", err or ""); -- FIXME don't close immediately, wait until we process current stuff -- FIXME if err, send off a bad-request response conn:close(); end local function options_cb() return options; end session.parser = parser_new(success_cb, error_cb, "server", options_cb); sessions[conn] = session; end function listener.ondisconnect(conn) local open_response = conn._http_open_response; if open_response and open_response.on_destroy then open_response.finished = true; open_response:on_destroy(); end incomplete[conn] = nil; sessions[conn] = nil; end function listener.ondetach(conn) sessions[conn] = nil; incomplete[conn] = nil; end function listener.onincoming(conn, data) sessions[conn].parser:feed(data); end function listener.ondrain(conn) local response = incomplete[conn]; if response and response._send_more then response._send_more(); end end local headerfix = setmetatable({}, { __index = function(t, k) local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": "; t[k] = v; return v; end }); local function handle_result(request, response, result) if result == nil then result = 404; end if result == true then return; end local body; local result_type = type(result); if result_type == "number" then response.status_code = result; if result >= 400 then body = events.fire_event("http-error", { request = request, response = response, code = result }); end elseif result_type == "string" then body = result; elseif errors.is_err(result) then response.status_code = result.code or 500; body = events.fire_event("http-error", { request = request, response = response, code = result.code or 500, error = result }); elseif promise.is_promise(result) then result:next(function (ret) handle_result(request, response, ret); end, function (err) response.status_code = 500; handle_result(request, response, err or 500); end); return true; elseif result_type == "table" then for k, v in pairs(result) do if k ~= "headers" then response[k] = v; else for header_name, header_value in pairs(v) do response.headers[header_name] = header_value; end end end end return response:send(body); end function _M.hijack_response(response, listener) -- luacheck: ignore error("TODO"); end function handle_request(conn, request, finish_cb) --log("debug", "handler: %s", request.path); local headers = {}; for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end request.headers = headers; request.conn = conn; local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use local conn_header = request.headers.connection; conn_header = conn_header and ","..conn_header:gsub("[ \t]", ""):lower().."," or "" local httpversion = request.httpversion local persistent = conn_header:find(",keep-alive,", 1, true) or (httpversion == "1.1" and not conn_header:find(",close,", 1, true)); local response_conn_header; if persistent then response_conn_header = "Keep-Alive"; else response_conn_header = httpversion == "1.1" and "close" or nil end local is_head_request = request.method == "HEAD"; local response = { request = request; is_head_request = is_head_request; status_code = 200; headers = { date = date_header, connection = response_conn_header }; persistent = persistent; conn = conn; send = _M.send_response; write_headers = _M.write_headers; send_file = _M.send_file; done = _M.finish_response; finish_cb = finish_cb; }; conn._http_open_response = response; local host = request.headers.host; if host then host = host:gsub(":%d+$",""); end -- Some sanity checking local err_code, err; if not request.path then err_code, err = 400, "Invalid path"; 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 global_event = request.method.." "..request.path:match("[^?]*"); local payload = { request = request, response = response }; log("debug", "Firing event: %s", global_event); local result = events.fire_event(global_event, payload); if result == nil and is_head_request then local global_head_event = "GET "..request.path:match("[^?]*"); log("debug", "Firing event: %s", global_head_event); result = events.fire_event(global_head_event, payload); end 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); if result == nil and is_head_request then local host_head_event = "GET "..host..request.path:match("[^?]*"); log("debug", "Firing event: %s", host_head_event); result = events.fire_event(host_head_event, payload); end end return handle_result(request, response, result); end local function prepare_header(response) local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]); local headers = response.headers; local output = { status_line }; for k,v in pairs(headers) do t_insert(output, headerfix[k]..v); end t_insert(output, "\r\n\r\n"); return output; end _M.prepare_header = prepare_header; function _M.write_headers(response) if response.finished then return; end local output = prepare_header(response); response.conn:write(t_concat(output)); end function _M.send_head_response(response) if response.finished then return; end _M.write_headers(response); response:done(); end function _M.send_response(response, body) if response.finished then return; end body = body or response.body or ""; -- Per RFC 7230, informational (1xx) and 204 (no content) should have no c-l header if response.status_code > 199 and response.status_code ~= 204 then response.headers.content_length = ("%d"):format(#body); end if response.is_head_request then return _M.send_head_response(response) end local output = prepare_header(response); t_insert(output, body); response.conn:write(t_concat(output)); response:done(); end function _M.send_file(response, f) if response.is_head_request then if f.close then f:close(); end return _M.send_head_response(response); end if response.finished then return; end local chunked = not response.headers.content_length; if chunked then response.headers.transfer_encoding = "chunked"; end incomplete[response.conn] = response; response._send_more = function () if response.finished then incomplete[response.conn] = nil; return; end local chunk = f:read(blocksize); if chunk then if chunked then chunk = ("%x\r\n%s\r\n"):format(#chunk, chunk); end -- io.write("."); io.flush(); response.conn:write(chunk); else incomplete[response.conn] = nil; if chunked then response.conn:write("0\r\n\r\n"); end -- io.write("\n"); if f.close then f:close(); end return response:done(); end end _M.write_headers(response); return true; end function _M.finish_response(response) if response.finished then return; end response.finished = true; response.conn._http_open_response = nil; if response.on_destroy then response:on_destroy(); response.on_destroy = nil; end if response.persistent then response:finish_cb(); else response.conn:close(); end end function _M.add_handler(event, handler, priority) events.add_handler(event, handler, priority); end function _M.remove_handler(event, handler) events.remove_handler(event, handler); end function _M.listen_on(port, interface, ssl) return addserver(interface or "*", port, listener, "*a", ssl); end function _M.add_host(host) hosts[host] = true; end function _M.remove_host(host) hosts[host] = nil; end function _M.set_default_host(host) default_host = host; end function _M.fire_event(event, ...) return events.fire_event(event, ...); end function _M.set_option(name, value) options[name] = value; end function _M.get_request_from_conn(conn) local response = conn and conn._http_open_response; return response and response.request or nil; end _M.listener = listener; _M.codes = codes; _M._events = events; return _M; prosody-0.12.4/net/PaxHeaders/http.lua0000644000000000000000000000011714475665104014642 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.63276554 prosody-0.12.4/net/http.lua0000644000175000017500000002273314475665104017050 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local b64 = require "util.encodings".base64.encode; local url = require "socket.url" local httpstream_new = require "net.http.parser".new; local util_http = require "util.http"; local events = require "util.events"; local verify_identity = require"util.x509".verify_identity; local promise = require "util.promise"; local http_errors = require "net.http.errors"; local basic_resolver = require "net.resolvers.basic"; local connect = require "net.connect".connect; local ssl_available = pcall(require, "ssl"); local t_insert, t_concat = table.insert, table.concat; local pairs = pairs; local tonumber, tostring, traceback = tonumber, tostring, debug.traceback; local os_time = os.time; local xpcall = require "util.xpcall".xpcall; local error = error local log = require "util.logger".init("http"); local _ENV = nil; -- luacheck: std none local requests = {}; -- Open requests local function make_id(req) return (tostring(req):match("%x+$")); end 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, (...)); 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 cancel_request(request, reason) if request.callback then request.callback(reason or "cancelled", 0, request); request.callback = nil; end if request.conn then destroy_request(request); 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 finalize_sink; local function success_cb(r) if r.partial then -- Request should be streamed log("debug", "Request '%s': partial response (%s%s)", request.id, r.chunked and "chunked, " or "", r.body_length and ("%d bytes"):format(r.body_length) or "unknown length" ); if request.streaming_handler then log("debug", "Request '%s': Streaming via handler", request.id); r.body_sink, finalize_sink = request.streaming_handler(r); end return; elseif finalize_sink then log("debug", "Request '%s': Finalizing response stream"); finalize_sink(r); end 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"; req.cancel = cancel_request; requests[req.conn] = req; -- Validate certificate if not req.insecure and conn:ssl() then local sock = conn:socket(); local chain_valid = sock.getpeerverification and sock:getpeerverification(); if not chain_valid then req.callback("certificate-chain-invalid", 0, req); req.callback = nil; conn:close(); return; end local cert = sock.getpeercertificate and sock:getpeercertificate(); if not cert or not verify_identity(req.host, false, cert) then req.callback("certificate-verify-failed", 0, req); req.callback = nil; conn:close(); return; end end -- Send the request local request_line = { req.method or "GET", " ", req.path, " HTTP/1.1\r\n" }; if req.query then t_insert(request_line, 4, "?"..req.query); end for k, v in pairs(req.headers) do t_insert(request_line, k .. ": " .. v .. "\r\n"); end t_insert(request_line, "\r\n") conn:write(t_concat(request_line)); if req.body then conn:write(req.body); end end function listener.onincoming(conn, data) local request = requests[conn]; if not request then log("warn", "Received response from connection %s with no request attached!", conn); return; end if data and request.reader then request:reader(data); end end function listener.ondisconnect(conn, err) local request = requests[conn]; if request and request.conn then request:reader(nil, err or "closed"); end requests[conn] = nil; end function listener.onattach(conn, req) requests[conn] = req; req.conn = conn; end function listener.ondetach(conn) requests[conn] = nil; end 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); if not (req and req.host) then callback("invalid-url", 0, req); return nil, "invalid-url"; end req.url = u; req.http = self; req.time = os_time(); if not req.path then req.path = "/"; end req.id = ex and ex.id or make_id(req); do local event = { http = self, url = u, request = req, options = ex, callback = callback }; local ret = self.events.fire_event("pre-request", event); if ret then return ret; end req, u, ex, req.callback = event.request, event.url, event.options, event.callback; end local method, headers, body; local host, port = req.host, req.port; local host_header = host; if (port == "80" and req.scheme == "http") or (port == "443" and req.scheme == "https") then port = nil; elseif port then host_header = host_header..":"..port; end headers = { ["Host"] = host_header; ["User-Agent"] = "Prosody XMPP Server"; }; if req.userinfo then headers["Authorization"] = "Basic "..b64(req.userinfo); end if ex then req.onlystatus = ex.onlystatus; body = ex.body; if body then method = "POST"; headers["Content-Length"] = tostring(#body); headers["Content-Type"] = "application/x-www-form-urlencoded"; end if ex.method then method = ex.method; end if ex.headers then for k, v in pairs(ex.headers) do headers[k] = v; end end req.insecure = ex.insecure; req.suppress_errors = ex.suppress_errors; req.streaming_handler = ex.streaming_handler; end log("debug", "Making %s %s request '%s' to %s", req.scheme:upper(), method or "GET", req.id, (ex and ex.suppress_url and host_header) or u); -- Attach to request object req.method, req.headers, req.body = method, headers, body; local using_https = req.scheme == "https"; if using_https and not ssl_available then error("SSL not available, unable to contact https URL"); end local port_number = port and tonumber(port) or (using_https and 443 or 80); local use_dane = self.options and self.options.use_dane; local sslctx = false; if using_https then sslctx = ex and ex.sslctx or self.options and self.options.sslctx; if ex and ex.use_dane ~= nil then use_dane = ex.use_dane; end end local http_service = basic_resolver.new(host, port_number, "tcp", { servername = req.host; use_dane = use_dane }); connect(http_service, listener, { sslctx = sslctx }, req); self.events.fire_event("request", { http = self, request = req, url = u }); return req; end local function new(options) local http = { options = options; request = function (self, u, ex, callback) if callback ~= nil then return request(self, u, ex, callback); else return promise.new(function (resolve, reject) request(self, u, ex, function (body, code, a, b) if code == 0 then reject(http_errors.new(body, { request = a })); else a.request = b; resolve(a); end end); end); end end; new = options and function (new_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(); }; return http; end local default_http = new({ sslctx = { mode = "client", protocol = "sslv23", options = { "no_sslv2", "no_sslv3" }, alpn = "http/1.1", verify = "peer" }; suppress_errors = true; }); return { request = function (u, ex, callback) return default_http:request(u, ex, callback); end; default = default_http; new = new; events = default_http.events; -- COMPAT urlencode = util_http.urlencode; urldecode = util_http.urldecode; formencode = util_http.formencode; formdecode = util_http.formdecode; destroy_request = destroy_request; features = { sni = true; }; }; prosody-0.12.4/net/PaxHeaders/httpserver.lua0000644000000000000000000000012014475665104016063 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/httpserver.lua0000644000175000017500000000066514475665104020277 0ustar00prosodyprosody00000000000000-- COMPAT w/pre-0.9 local log = require "util.logger".init("net.httpserver"); local traceback = debug.traceback; local _ENV = nil; -- luacheck: std none 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 return { new = fail; new_from_config = fail; set_default_handler = fail; }; prosody-0.12.4/net/PaxHeaders/resolvers0000644000000000000000000000013214475665104015124 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.636765501 30 ctime=1693936196.636765501 prosody-0.12.4/net/resolvers/0000755000175000017500000000000014475665104017403 5ustar00prosodyprosody00000000000000prosody-0.12.4/net/resolvers/PaxHeaders/basic.lua0000644000000000000000000000012014475665104016762 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/resolvers/basic.lua0000644000175000017500000000703114475665104021170 0ustar00prosodyprosody00000000000000local adns = require "net.adns"; local inet_pton = require "util.net".pton; local inet_ntop = require "util.net".ntop; local idna_to_ascii = require "util.encodings".idna.to_ascii; local unpack = table.unpack or unpack; -- luacheck: ignore 113 local methods = {}; local resolver_mt = { __index = methods }; -- FIXME RFC 6724 -- 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 if not self.hostname then self.last_error = "hostname failed IDNA"; cb(nil); return; end local secure = true; local tlsa = {}; local targets = {}; local n = 3; local function ready() n = n - 1; if n > 0 then return; end self.targets = targets; if self.extra and self.extra.use_dane then if secure and tlsa[1] then self.extra.tlsa = tlsa; self.extra.dane_hostname = self.hostname; else self.extra.tlsa = nil; self.extra.dane_hostname = nil; end end self:next(cb); end -- Resolve DNS to target list local dns_resolver = adns.resolver(); if not self.extra or self.extra.use_ipv4 ~= false then dns_resolver:lookup(function (answer, err) if answer then secure = secure and answer.secure; for _, record in ipairs(answer) do table.insert(targets, { self.conn_type.."4", record.a, self.port, self.extra }); end if answer.bogus then self.last_error = "Validation error in A lookup"; elseif answer.status then self.last_error = answer.status .. " in A lookup"; end else self.last_error = err; end ready(); end, self.hostname, "A", "IN"); else ready(); end if not self.extra or self.extra.use_ipv6 ~= false then dns_resolver:lookup(function (answer, err) if answer then secure = secure and answer.secure; for _, record in ipairs(answer) do table.insert(targets, { self.conn_type.."6", record.aaaa, self.port, self.extra }); end if answer.bogus then self.last_error = "Validation error in AAAA lookup"; elseif answer.status then self.last_error = answer.status .. " in AAAA lookup"; end else self.last_error = err; end ready(); end, self.hostname, "AAAA", "IN"); else ready(); end if self.extra and self.extra.use_dane == true then dns_resolver:lookup(function (answer, err) if answer then secure = secure and answer.secure; for _, record in ipairs(answer) do table.insert(tlsa, record.tlsa); end if answer.bogus then self.last_error = "Validation error in TLSA lookup"; elseif answer.status then self.last_error = answer.status .. " in TLSA lookup"; end else self.last_error = err; end ready(); end, ("_%d._tcp.%s"):format(self.port, self.hostname), "TLSA", "IN"); else ready(); end end local function new(hostname, port, conn_type, extra) local ascii_host = idna_to_ascii(hostname); local targets = nil; conn_type = conn_type or "tcp"; local is_ip = inet_pton(hostname); if not is_ip and hostname:sub(1,1) == '[' then is_ip = inet_pton(hostname:sub(2,-2)); end if is_ip then hostname = inet_ntop(is_ip); if #is_ip == 16 then targets = { { conn_type.."6", hostname, port, extra } }; elseif #is_ip == 4 then targets = { { conn_type.."4", hostname, port, extra } }; end end return setmetatable({ hostname = ascii_host; port = port; conn_type = conn_type; extra = extra; targets = targets; }, resolver_mt); end return { new = new; }; prosody-0.12.4/net/resolvers/PaxHeaders/chain.lua0000644000000000000000000000012014475665104016763 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/resolvers/chain.lua0000644000175000017500000000132614475665104021172 0ustar00prosodyprosody00000000000000 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.resolvers then if not self.resolver then if #self.resolvers == 0 then cb(nil); return; end local next_resolver = table.remove(self.resolvers, 1); self.resolver = next_resolver; end self.resolver:next(function (...) if self.resolver then self.last_error = self.resolver.last_error; end if ... == nil then self.resolver = nil; self:next(cb); else cb(...); end end); return; end end local function new(resolvers) return setmetatable({ resolvers = resolvers }, resolver_mt); end return { new = new; }; prosody-0.12.4/net/resolvers/PaxHeaders/manual.lua0000644000000000000000000000012014475665104017156 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/resolvers/manual.lua0000644000175000017500000000103714475665104021364 0ustar00prosodyprosody00000000000000local methods = {}; local resolver_mt = { __index = methods }; local unpack = table.unpack or unpack; -- luacheck: ignore 113 -- 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; }; prosody-0.12.4/net/resolvers/PaxHeaders/service.lua0000644000000000000000000000012014475665104017341 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/resolvers/service.lua0000644000175000017500000000535214475665104021553 0ustar00prosodyprosody00000000000000local adns = require "net.adns"; local basic = require "net.resolvers.basic"; local inet_pton = require "util.net".pton; local idna_to_ascii = require "util.encodings".idna.to_ascii; local unpack = table.unpack or unpack; -- luacheck: ignore 113 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 not self.resolver 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)); end self.resolver:next(function (...) if self.resolver then self.last_error = self.resolver.last_error; end if ... == nil then self.resolver = nil; self:next(cb); else cb(...); end end); return; end if not self.hostname then self.last_error = "hostname failed IDNA"; cb(nil); 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, err) if not answer and not err then -- net.adns returns nil if there are zero records or nxdomain answer = {}; end if answer then if self.extra and not answer.secure then self.extra.use_dane = false; elseif answer.bogus then self.last_error = "Validation error in SRV lookup"; ready(); return; end 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 }); else self.last_error = "zero SRV records found"; end ready(); return; end if #answer == 1 and answer[1].srv.target == "." then -- No service here self.last_error = "service explicitly unavailable"; 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 else self.last_error = err; end ready(); end, "_" .. self.service .. "._" .. self.conn_type .. "." .. self.hostname, "SRV", "IN"); end local function new(hostname, service, conn_type, extra) local is_ip = inet_pton(hostname); if not is_ip and hostname:sub(1,1) == '[' then is_ip = inet_pton(hostname:sub(2,-2)); end if is_ip and extra and extra.default_port then return basic.new(hostname, extra.default_port, conn_type, extra); end return setmetatable({ hostname = idna_to_ascii(hostname); service = service; conn_type = conn_type or "tcp"; extra = extra; }, resolver_mt); end return { new = new; }; prosody-0.12.4/net/PaxHeaders/server.lua0000644000000000000000000000012014475665104015163 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.636765501 prosody-0.12.4/net/server.lua0000644000175000017500000000704514475665104017376 0ustar00prosodyprosody00000000000000-- 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. -- 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 default_backend = "epoll"; local server_type = require "core.configmanager".get("*", "network_backend") or default_backend; 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 %s", default_backend); server_type = default_backend; end end local server; local set_config; if server_type == "event" then server = require "net.server_event"; 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 -- TODO Remove completely. log("warn", "select is deprecated, the new default is epoll. For more info see https://prosody.im/doc/network_backend"); 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()]; end if type(signal_id) ~= "number" then return false, "invalid-signal"; end return server.hook_signal(signal_id, handler); end else server.hook_signal = signal.signal; end else if not server.hook_signal then server.hook_signal = function() return false, "signal hooking not supported" end end end if prosody and set_config then local config_get = require "core.configmanager".get; local function load_config() local settings = config_get("*", "network_settings") or {}; return set_config(settings); end load_config(); prosody.events.add_handler("config-reloaded", load_config); end -- require "net.server" shall now forever return this, -- ie. server_select or server_event as chosen above. return server; prosody-0.12.4/net/PaxHeaders/server_epoll.lua0000644000000000000000000000012014475665104016356 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.640765462 prosody-0.12.4/net/server_epoll.lua0000644000175000017500000006716014475665104020575 0ustar00prosodyprosody00000000000000-- 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_insert = table.insert; local t_concat = table.concat; local setmetatable = setmetatable; local pcall = pcall; local type = type; local next = next; local pairs = pairs; local ipairs = ipairs; local traceback = debug.traceback; local logger = require "util.logger"; local log = logger.init("server_epoll"); local socket = require "socket"; local luasec = require "ssl"; local realtime = require "util.time".now; local monotonic = require "util.time".monotonic; local indexedbheap = require "util.indexedbheap"; local createtable = require "util.table".create; local inet = require "util.net"; local inet_pton = inet.pton; local _SOCKETINVALID = socket._SOCKETINVALID or -1; local new_id = require "util.id".short; local xpcall = require "util.xpcall".xpcall; 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 = { -- If a connection is silent for this long, close it unless onreadtimeout says not to read_timeout = 14 * 60; -- How long to wait for a socket to become writable after queuing data to send send_timeout = 180; -- How long to wait for a socket to become writable after creation connect_timeout = 20; -- Some number possibly influencing how many pending connections can be accepted tcp_backlog = 128; -- If accepting a new incoming connection fails, wait this long before trying again accept_retry_interval = 10; -- If there is still more data to read from LuaSockets buffer, wait this long and read again read_retry_delay = 1e-06; -- Size of chunks to read from sockets read_size = 8192; -- Timeout used during between steps in TLS handshakes ssl_handshake_timeout = 60; -- Maximum and minimum amount of time to sleep waiting for events (adjusted for pending timers) max_wait = 86400; min_wait = 0.001; -- Enable extra noisy debug logging verbose = false; -- EXPERIMENTAL -- Whether to kill connections in case of callback errors. fatal_errors = false; -- Or disable protection (like server_select) for potential performance gains protect_listeners = true; -- Attempt writes instantly opportunistic_writes = false; -- TCP Keepalives tcp_keepalive = false; -- boolean | number -- Whether to let the Nagle algorithm stay enabled nagle = true; -- Reuse write buffer tables keep_buffers = true; --- How long to wait after getting the shutdown signal before forcefully tearing down every socket shutdown_deadline = 5; }}; local cfg = default_config.__index; local fds = createtable(10, 0); -- FD -> conn -- Timer and scheduling -- local timers = indexedbheap.create(); local function noop() end -- Keep track of recently closed timers to avoid re-adding them local closedtimers = {}; local function closetimer(id) if timers:remove(id) then closedtimers[id] = true; end end local function reschedule(id, time) time = monotonic() + time; timers:reprioritize(id, time); end -- Add relative timer local function addtimer(timeout, f, param) local time = monotonic() + timeout; if param ~= nil then local timer_callback = f function f(current_time, timer_id) local t = timer_callback(current_time, timer_id, param) return t; end end local id = timers:insert(f, time); return id; end -- Run callbacks of expired timers -- Return time until next timeout local function runtimers(next_delay, min_wait) -- Any timers at all? local elapsed = monotonic(); local now = realtime(); local peek = timers:peek(); local readd; while peek do if peek > elapsed then break; end local _, timer, id = timers:pop(); local ok, ret = xpcall(timer, traceback, now, id); if ok and type(ret) == "number" and not closedtimers[id] then local next_time = elapsed+ret; -- Delay insertion of timers to be re-added -- so they don't get called again this tick if readd then readd[id] = { timer, next_time }; else readd = { [id] = { timer, next_time } }; end elseif not ok then log("error", "Error in timer: %s", ret); end peek = timers:peek(); end if readd then for id, timer in pairs(readd) do timers:insert(timer[1], timer[2], id); end peek = timers:peek(); end if next(closedtimers) ~= nil then closedtimers = {}; end if peek == nil then return next_delay; else next_delay = peek - elapsed; end if next_delay < min_wait then return 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 interface.log = log; function interface:debug(msg, ...) self.log("debug", msg, ...); end interface.noise = interface.debug; function interface:noise(msg, ...) if cfg.verbose then return self:debug(msg, ...); end end function interface:error(msg, ...) self.log("error", msg, ...); 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 self:error("Interface is missing listener callbacks"); return; end local listener = self.listeners["on"..what]; if not listener then self:noise("Missing listener 'on%s'", what); -- uncomment for development and debugging return; end if not cfg.protect_listeners then return listener(self, ...); end local onerror = self.listeners.onerror or traceback; local ok, err = xpcall(listener, onerror, self, ...); if not ok then if cfg.fatal_errors then self:error("Closing due to error calling on%s: %s", what, err); self:destroy(); else self:error("Error calling on%s: %s", what, err); end return nil, err; end return err; end -- Allow this one to be overridden function interface:onincoming(...) return self:on("incoming", ...); 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.peerport or self.sockport; end -- Client-side port (usually a random high port) function interface:clientport() if self._server then return self.peerport; else return self.sockport; end end -- Get port on the server function interface:serverport() if self._server then return self.sockport; else return self.peerport; 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 :( local ok, ret, err = pcall(self.conn.setoption, self.conn, k, v); if not ok then self:noise("Setting option %q = %q failed: %s", k, v, ret); return ok, ret; elseif not ret then self:noise("Setting option %q = %q failed: %s", k, v, err); return ret, err; end return ret; end -- Timeout for detecting dead or idle sockets function interface:setreadtimeout(t) if t == false then if self._readtimeout then closetimer(self._readtimeout); self._readtimeout = nil; end return end t = t or cfg.read_timeout; if self._readtimeout then reschedule(self._readtimeout, t); else self._readtimeout = addtimer(t, function () if self:on("readtimeout") then self:noise("Read timeout handled"); return cfg.read_timeout; else self:debug("Read timeout not handled, disconnecting"); 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 closetimer(self._writetimeout); self._writetimeout = nil; end return end t = t or cfg.send_timeout; if self._writetimeout then reschedule(self._writetimeout, t); else self._writetimeout = addtimer(t, function () self:noise("Write timeout"); self:on("disconnect", self._connected and "write timeout" or "connection 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 self:debug("FD already registered in poller! (EEXIST)"); return self:set(r, w); -- So try to change its flags end self:debug("Could not register in poller: %s(%d)", err, errno); return ok, err; end self._wantread, self._wantwrite = r, w; fds[fd] = self; self:noise("Registered in poller"); 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 self:debug("Could not update poller state: %s(%d)", 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 self:debug("Could not unregister: %s(%d)", err, errno); return ok, err; end self._wantread, self._wantwrite = nil, nil; fds[fd] = nil; self:noise("Unregistered from poller"); 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:onincoming(data); else if err == "wantread" then self:set(true, nil); err = "timeout"; elseif err == "wantwrite" then self:set(nil, true); self:setwritetimeout(); err = "timeout"; elseif err == "timeout" and not self._connected then err = "connection timeout"; end if partial and partial ~= "" then self:onconnect(); self:onincoming(partial, err); end if err == "closed" and self._connected then self:debug("Connection closed by remote"); self:close(err); return; elseif err ~= "timeout" then self:debug("Read error, closing (%s)", err); self:on("disconnect", err); self:destroy(); return; end end if not self.conn then return; end if self._limit and (data or partial) then local cost = self._limit * #(data or partial); if cost > cfg.min_wait then self:setreadtimeout(false); self:pausefor(cost); return; end end if not self._wantread 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._writing = true; -- prevent reentrant writes etc self:onconnect(); if not self.conn then return nil, "no-conn"; end -- could have been closed in onconnect self:on("predrain"); local buffer = self.writebuffer; local data = buffer or ""; if type(buffer) == "table" then if buffer[3] then data = t_concat(data); elseif buffer[2] then data = buffer[1] .. buffer[2]; else data = buffer[1] or ""; end end local ok, err, partial = self.conn:send(data); self._writable = ok; if ok then self:set(nil, false); if cfg.keep_buffers and type(buffer) == "table" then for i = #buffer, 1, -1 do buffer[i] = nil; end else self.writebuffer = nil; end self._writing = nil; self:setwritetimeout(false); self:ondrain(); -- Be aware of writes in ondrain return ok; elseif partial then self:debug("Sent %d out of %d buffered bytes", partial, #data); if cfg.keep_buffers and type(buffer) == "table" then buffer[1] = data:sub(partial+1); for i = #buffer, 2, -1 do buffer[i] = nil; end else self.writebuffer = data:sub(partial+1); end self:set(nil, true); self:setwritetimeout(); end self._writing = nil; if err == "wantwrite" or err == "timeout" then self:set(nil, true); self:setwritetimeout(); elseif err == "wantread" then self:set(true, nil); self:setreadtimeout(); elseif err ~= "timeout" then self:on("disconnect", err); self:destroy(); return ok, err; end return true, err; 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 type(buffer) == "table" then t_insert(buffer, data); elseif type(buffer) == "string" then self:noise("Allocating buffer!") self.writebuffer = { buffer, data }; elseif buffer == nil then self.writebuffer = data; end if not self._write_lock and not self._writing then if self._writable and cfg.opportunistic_writes and not self._opportunistic_write then self._opportunistic_write = true; local ret, err = self:onwritable(); self._opportunistic_write = nil; return ret, err; end self:setwritetimeout(); self:set(nil, true); end return #data; end interface.send = interface.write; -- Close, possibly after writing is done function interface:close() if self._connected and self.writebuffer and (self.writebuffer[1] or type(self.writebuffer) == "string") then self._connected = false; self:set(false, true); -- Flush final buffer contents self:setreadtimeout(false); self:setwritetimeout(); self.write, self.send = noop, noop; -- No more writing self:debug("Close after writing remaining buffered data"); self.ondrain = interface.close; else self:debug("Closing now"); 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:set_sslctx(sslctx) self._sslctx = sslctx; 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] or type(self.writebuffer) == "string") then self:debug("Start TLS after write"); 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.inittls; self.onreadable = interface.inittls; self:set(true, true); self:setreadtimeout(false); self:setwritetimeout(cfg.ssl_handshake_timeout); self:debug("Prepared to start TLS"); end end function interface:inittls(tls_ctx, now) if self._tls then return end if tls_ctx then self.tls_ctx = tls_ctx; end self._tls = true; self.starttls = false; self:debug("Starting TLS now"); self:updatenames(); -- Can't getpeer/sockname after wrap() local ok, conn, err = pcall(luasec.wrap, self.conn, self.tls_ctx); if not ok then conn, err = ok, conn; self:debug("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; if conn.sni then if self.servername then conn:sni(self.servername); elseif self._server and type(self._server.hosts) == "table" and next(self._server.hosts) ~= nil then conn:sni(self._server.hosts, true); end end if self.extra and self.extra.tlsa and conn.settlsa then -- TODO Error handling if not conn:setdane(self.servername or self.extra.dane_hostname) then self:debug("Could not enable DANE on connection"); else self:debug("Enabling DANE with %d TLSA records", #self.extra.tlsa); self:noise("DANE hostname is %q", self.servername or self.extra.dane_hostname); for _, tlsa in ipairs(self.extra.tlsa) do self:noise("TLSA: %q", tlsa); conn:settlsa(tlsa.use, tlsa.select, tlsa.match, tlsa.data); end end end self:on("starttls"); self.ondrain = nil; self.onwritable = interface.tlshandshake; self.onreadable = interface.tlshandshake; if now then return self:tlshandshake() end self:setreadtimeout(false); self:setwritetimeout(cfg.ssl_handshake_timeout); self:set(true, true); end function interface:tlshandshake() self:setreadtimeout(false); self:noise("Continuing TLS handshake"); local ok, err = self.conn:dohandshake(); if ok then local info = self.conn.info and self.conn:info(); if type(info) == "table" then self:debug("TLS handshake complete (%s with %s)", info.protocol, info.cipher); else self:debug("TLS handshake complete"); end self:setwritetimeout(false); self.onwritable = nil; self.onreadable = nil; self:on("status", "ssl-handshake-complete"); self:set(true, true); self:onconnect(); self:onreadable(); elseif err == "wantread" then self:noise("TLS handshake to wait until readable"); self:set(true, false); self:setwritetimeout(cfg.ssl_handshake_timeout); elseif err == "wantwrite" then self:noise("TLS handshake to wait until writable"); self:set(false, true); self:setwritetimeout(cfg.ssl_handshake_timeout); else self:debug("TLS handshake error: %s", err); self:on("disconnect", err); self:destroy(); end end local function wrapsocket(client, server, read_size, listeners, tls_ctx, extra) -- luasocket object -> interface object client:settimeout(0); local conn_id = ("conn%s"):format(new_id()); local conn = setmetatable({ conn = client; _server = server; created = realtime(); listeners = listeners; read_size = read_size or (server and server.read_size); writebuffer = nil; tls_ctx = tls_ctx or (server and server.tls_ctx); tls_direct = server and server.tls_direct; id = conn_id; log = logger.init(conn_id); extra = extra; }, interface_mt); if extra then if extra.servername then conn.servername = extra.servername; end end conn:updatenames(); return conn; end function interface:updatenames() local conn = self.conn; local ok, peername, peerport = pcall(conn.getpeername, conn); if ok and peername then self.peername, self.peerport = peername, peerport or 0; end local ok, sockname, sockport = pcall(conn.getsockname, conn); if ok and sockname then self.sockname, self.sockport = sockname, sockport or 0; 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 self: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); client:debug("New connection %s on server %s", client, self); client:defaultoptions(); client._writable = cfg.opportunistic_writes; if self.tls_direct then client:add(true, true); client:inittls(self.tls_ctx, true); else client:add(true, false); client:onconnect(); client:onreadable(); end end -- Initialization for outgoing connections function interface:init() self:setwritetimeout(cfg.connect_timeout); self:defaultoptions(); return self:add(true, true); end function interface:defaultoptions() if cfg.nagle == false then self:setoption("tcp-nodelay", true); end if cfg.tcp_keepalive then self:setoption("keepalive", true); if type(cfg.tcp_keepalive) == "number" then self:setoption("tcp-keepidle", cfg.tcp_keepalive); end end end function interface:pause() self:noise("Pause reading"); self:setreadtimeout(false); return self:set(false); end function interface:resume() self:noise("Resume reading"); self:setreadtimeout(); return self:set(true); end -- Pause connection for some time function interface:pausefor(t) self:noise("Pause for %fs", t); if self._pausefor then closetimer(self._pausefor); self._pausefor = nil; end if t == false then return; end self:set(false); self._pausefor = addtimer(t, function () self._pausefor = nil; self:set(true); self:noise("Resuming after pause"); if self.conn and self.conn:dirty() then self:noise("Have buffered incoming data to process"); self:onreadable(); end end); end function interface:setlimit(Bps) if Bps > 0 then self._limit = 1/Bps; else self._limit = nil; end end function interface:pause_writes() if self._write_lock then return end self:noise("Pause writes"); self._write_lock = true; self:setwritetimeout(false); self:set(nil, false); end function interface:resume_writes() if not self._write_lock then return end self:noise("Resume writes"); self._write_lock = nil; if self.writebuffer and (self.writebuffer[1] or type(self.writebuffer) == "string") then self:setwritetimeout(); self:set(nil, true); end end -- Connected! function interface:onconnect() self._connected = true; self:updatenames(); self:debug("Connected (%s)", self); self.onconnect = noop; self:on("connect"); end local function wrapserver(conn, addr, port, listeners, config) local server = setmetatable({ conn = conn; created = realtime(); listeners = listeners; read_size = config and config.read_size; onreadable = interface.onacceptable; tls_ctx = config and config.tls_ctx; tls_direct = config and config.tls_direct; hosts = config and config.sni_hosts; sockname = addr; sockport = port; log = logger.init(("serv%s"):format(new_id())); }, interface_mt); server:debug("Server %s created", server); server:add(true, false); return server; end local function listen(addr, port, listeners, config) local conn, err = socket.bind(addr, port, cfg.tcp_backlog); if not conn then return conn, err; end conn:settimeout(0); return wrapserver(conn, addr, port, listeners, config); end -- COMPAT local function addserver(addr, port, listeners, read_size, tls_ctx) return listen(addr, port, listeners, { read_size = read_size; tls_ctx = tls_ctx; tls_direct = tls_ctx and true or false; }); end -- COMPAT local function wrapclient(conn, addr, port, listeners, read_size, tls_ctx, extra) local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx, extra); 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, extra) 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(); if not conn then return conn, err; end 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, extra) local ok, err = client:init(); if not client.peername then -- otherwise not set until connected client.peername, client.peerport = addr, port; end if not ok then return ok, err; end client:debug("Client %s created", client); 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.id = new_id(); conn.log = logger.init(("fdwatch%s"):format(conn.id)); conn:add(onreadable, onwritable); return conn; end; -- Dump all data from one connection into another local function link(from, to, read_size) from:debug("Linking to %s", to.id); function from:onincoming(data) self:pause(); to:write(data); end function to:ondrain() -- luacheck: ignore 212/self from:resume(); end from:set_mode(read_size); 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(); addtimer(1, function () if quitting then closeall(); return 1; end end); if cfg.shutdown_deadline then addtimer(cfg.shutdown_deadline, function () if quitting then for fd, conn in pairs(fds) do -- luacheck: ignore 213/fd conn:destroy(); end end end); end 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); while fd do 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 fd, r, w = poll:wait(0); end if 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; timer = { -- API-compatible with util.timer add_task = addtimer; stop = closetimer; reschedule = reschedule; to_absolute_time = function (t) return t-monotonic()+realtime(); end; }; listen = listen; loop = loop; closeall = closeall; setquitting = setquitting; wrapclient = wrapclient; wrapserver = wrapserver; 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) log("warn", "Using deprecated libevent emulation, please update code to use watchfd API instead"); 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); conn.id = conn:getfd(); conn.log = logger.init(("fdwatch%d"):format(conn.id)); 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; }; prosody-0.12.4/net/PaxHeaders/server_event.lua0000644000000000000000000000012014475665104016364 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.640765462 prosody-0.12.4/net/server_event.lua0000644000175000017500000007560014475665104020601 0ustar00prosodyprosody00000000000000--[[ server.lua based on lua/libevent by blastbeat notes: -- when using luaevent, never register 2 or more EV_READ at one socket, same for EV_WRITE -- 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 -- 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 --]] -- luacheck: ignore 212/self 431/err 211/ret local SCRIPT_NAME = "server_event.lua" local SCRIPT_VERSION = "0.05" local SCRIPT_AUTHOR = "blastbeat" local LAST_MODIFIED = "2009/11/20" local cfg = { MAX_CONNECTIONS = 100000, -- max per server connections (use "ulimit -n" on *nix) MAX_HANDSHAKE_ATTEMPTS= 1000, -- attempts to finish ssl handshake HANDSHAKE_TIMEOUT = 60, -- timeout in seconds per handshake attempt MAX_READ_LENGTH = 1024 * 1024 * 1024 * 1024, -- max bytes allowed to read from sockets 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 = 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) READ_RETRY_DELAY = 1e-06, -- if, after reading, there is still data in buffer, wait this long and continue reading DEBUG = true, -- show debug messages } local pairs = pairs local select = select local require = require local tostring = tostring local setmetatable = setmetatable local t_insert = table.insert local t_concat = table.concat local s_sub = string.sub local coroutine_wrap = coroutine.wrap local coroutine_yield = coroutine.yield local has_luasec, ssl = pcall ( require , "ssl" ) local socket = require "socket" local levent = require "luaevent.core" local inet = require "util.net"; local inet_pton = inet.pton; local socket_gettime = socket.gettime local log = require ("util.logger").init("socket") local function debug(...) return log("debug", ("%s "):rep(select('#', ...)), ...) end -- local vdebug = debug; local bitor = ( function( ) -- thx Rici Lake local hasbit = function( x, p ) return x % ( p + p ) >= p end return function( x, y ) local p = 1 local z = 0 local limit = x > y and x or y while p <= limit do if hasbit( x, p ) or hasbit( y, p ) then z = z + p end p = p + p end return z end end )( ) local base = levent.new( ) local addevent = base.addevent local EV_READ = levent.EV_READ local EV_WRITE = levent.EV_WRITE local EV_TIMEOUT = levent.EV_TIMEOUT local EV_SIGNAL = levent.EV_SIGNAL local EV_READWRITE = bitor( EV_READ, EV_WRITE ) local interfacelist = { } -- Client interface methods local interface_mt = {}; interface_mt.__index = interface_mt; -- Private methods function interface_mt:_close() return self:_destroy(); end function interface_mt:_start_connection(plainssl) -- called from wrapclient local callback = function( event ) if EV_TIMEOUT == event then -- timeout during connection self.fatalerror = "connection timeout" self:ontimeout() -- call timeout listener self:_close() debug( "new connection failed. id:", self.id, "error:", self.fatalerror ) else if 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 self:_start_session(true) end debug( "new connection established. id:", self.id ) end self.eventconnect = nil return -1 end 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 if self.type == "client" then local callback = function( ) self:_lock( false, false, false ) --vdebug( "start listening on client socket with id:", self.id ) self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback if call_onconnect then self:onconnect() end self.eventsession = nil return -1 end self.eventsession = addevent( base, nil, EV_TIMEOUT, callback, 0 ) else self:_lock( false ) --vdebug( "start listening on server socket with id:", self.id ) self.eventread = addevent( base, self.conn, EV_READ, self.readcallback ) -- register callback end return true end function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed, therefore we have to close read/write events first --vdebug( "starting ssl session with client id:", self.id ) local _ _ = self.eventread and self.eventread:close( ) -- close events; this must be called outside of the event callbacks! _ = self.eventwrite and self.eventwrite:close( ) self.eventread, self.eventwrite = nil, nil local err self.conn, err = ssl.wrap( self.conn, self._sslctx ) if err then self.fatalerror = err self.conn = nil -- cannot be used anymore if call_onconnect then self.ondisconnect = nil -- don't call this when client isn't really connected end self:_close() debug( "fatal error while ssl wrapping:", err ) return false end if self.conn.sni then if self.servername then self.conn:sni(self.servername); elseif self._server and type(self._server.hosts) == "table" and next(self._server.hosts) ~= nil then self.conn:sni(self._server.hosts, true); end end self.conn:settimeout( 0 ) -- set non blocking local handshakecallback = coroutine_wrap(function( event ) local _, err local attempt = 0 local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPTS while attempt < maxattempt do -- no endless loop attempt = attempt + 1 debug( "ssl handshake of client with id:"..tostring(self)..", attempt:"..attempt ) if attempt > maxattempt then self.fatalerror = "max handshake attempts exceeded" elseif EV_TIMEOUT == event then self.fatalerror = "timeout during handshake" else _, err = self.conn:dohandshake( ) if not err then self:_lock( false, false, false ) -- unlock the interface; sending, closing etc allowed self.send = self.conn.send -- caching table lookups with new client object self.receive = self.conn.receive if not call_onconnect then -- trigger listener self:onstatus("ssl-handshake-complete"); end self:_start_session( call_onconnect ) debug( "ssl handshake done" ) self.eventhandshake = nil return -1 end if err == "wantwrite" then event = EV_WRITE elseif err == "wantread" then event = EV_READ else debug( "ssl handshake error:", err ) self.fatalerror = err end end if self.fatalerror then if call_onconnect then self.ondisconnect = nil -- don't call this when client isn't really connected end self:_close() debug( "handshake failed because:", self.fatalerror ) self.eventhandshake = nil return -1 end event = coroutine_yield( event, cfg.HANDSHAKE_TIMEOUT ) -- yield this monster... end end ) debug "starting handshake..." self:_lock( false, true, true ) -- unlock read/write events, but keep interface locked self.eventhandshake = addevent( base, self.conn, EV_READWRITE, handshakecallback, cfg.HANDSHAKE_TIMEOUT ) return true end function interface_mt:_destroy() -- close this interface + events and call last listener debug( "closing client with id:", self.id, self.fatalerror ) self:_lock( true, true, true ) -- first of all, lock the interface to avoid further actions local _ _ = self.eventread and self.eventread:close( ) if self.type == "client" then _ = self.eventwrite and self.eventwrite:close( ) _ = self.eventhandshake and self.eventhandshake:close( ) _ = self.eventstarthandshake and self.eventstarthandshake:close( ) _ = self.eventconnect and self.eventconnect:close( ) _ = self.eventsession and self.eventsession:close( ) _ = self.eventwritetimeout and self.eventwritetimeout:close( ) _ = self.eventreadtimeout and self.eventreadtimeout:close( ) -- 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 self.eventstarthandshake, self.eventhandshake, self.eventclose = nil, nil, nil self.readcallback, self.writecallback = nil, nil else self.conn:close( ) self.eventread, self.eventclose = nil, nil self.interface, self.readcallback = nil, nil end interfacelist[ self ] = nil return true end function interface_mt:_lock(nointerface, noreading, nowriting) -- lock or unlock this interface or events self.nointerface, self.noreading, self.nowriting = nointerface, noreading, nowriting return nointerface, noreading, nowriting end --TODO: Deprecate function interface_mt:lock_read(switch) log("warn", ":lock_read is deprecated, use :pause() and :resume()"); if switch then return self:pause(); else return self:resume(); end end function interface_mt:pause() return self:_lock(self.nointerface, true, self.nowriting); end function interface_mt:resume() self:_lock(self.nointerface, false, self.nowriting); if self.readcallback and not self.eventread then self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback return true; end end function interface_mt:pause_writes() return self:_lock(self.nointerface, self.noreading, true); end function interface_mt:resume_writes() self:_lock(self.nointerface, self.noreading, false); if self.writecallback and not self.eventwrite then self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT ); -- register callback return true; end end function interface_mt:counter(c) if c then self._connections = self._connections + c end return self._connections end -- Public methods function interface_mt:write(data) if self.nointerface then return nil, "locked"; end --vdebug( "try to send data to client, id/data:", self.id, data ) data = tostring( data ) local len = #data local total = len + self.writebufferlen if total > cfg.MAX_SEND_LENGTH then -- check buffer length local err = "send buffer exceeded" debug( "error:", err ) -- to much, check your app return nil, err end t_insert(self.writebuffer, data) -- new buffer self.writebufferlen = total if not self.eventwrite and not self.nowriting then -- register new write event --vdebug( "register new write event" ) self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT ) end return true end function interface_mt:close() if self.nointerface then return nil, "locked"; end debug( "try to close client connection with id:", self.id ) if self.type == "client" then self.fatalerror = "client to close" if self.eventwrite then -- wait for incomplete write request self:_lock( true, true, false ) debug "closing delayed until writebuffer is empty" return nil, "writebuffer not empty, waiting" else -- close now self:_lock( true, true, true ) self:_close() return true end else debug( "try to close server with id:", tostring(self.id)) self.fatalerror = "server to close" self:_lock( true ) self:_close( 0 ) return true end end function interface_mt:socket() return self.conn end function interface_mt:server() return self._server or self; end function interface_mt:port() return self._port end function interface_mt:serverport() return self._serverport end function interface_mt:ip() return self._ip end function interface_mt:ssl() return self._usingssl end interface_mt.clientport = interface_mt.port -- COMPAT server_select function interface_mt:type() return self._type or "client" end function interface_mt:connections() return self._connections end function interface_mt:address() return self.addr end function interface_mt:set_sslctx(sslctx) self._sslctx = sslctx; if sslctx then self.starttls = nil; -- use starttls() of interface_mt else self.starttls = false; -- prevent starttls() end end function interface_mt:set_mode(pattern) if pattern then self._pattern = pattern; end return self._pattern; end function interface_mt:set_send(new_send) -- luacheck: ignore 212 -- No-op, we always use the underlying connection's send end function interface_mt:starttls(sslctx, call_onconnect) debug( "try to start ssl at client id:", self.id ) local err self._sslctx = sslctx; if self._usingssl then -- startssl was already called err = "ssl already active" end if err then debug( "error:", err ) return nil, err end self._usingssl = true self.startsslcallback = function( ) -- we have to start the handshake outside of a read/write event self.startsslcallback = nil self:_start_ssl(call_onconnect); self.eventstarthandshake = nil return -1 end if not self.eventwrite then self:_lock( true, true, true ) -- lock the interface, to not disturb the handshake self.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, self.startsslcallback, 0 ) -- add event to start handshake else -- wait until writebuffer is empty self:_lock( true, true, false ) debug "ssl session delayed until writebuffer is empty..." end self.starttls = false; return true end function interface_mt:setoption(option, value) if self.conn.setoption then return self.conn:setoption(option, value); end return false, "setoption not implemented"; end function interface_mt:setlistener(listener, data) self:ondetach(); -- Notify listener that it is no longer responsible for this connection self.onconnect = listener.onconnect; self.ondisconnect = listener.ondisconnect; self.onincoming = listener.onincoming; self.ontimeout = listener.ontimeout; self.onreadtimeout = listener.onreadtimeout; self.onstatus = listener.onstatus; self.ondetach = listener.ondetach; self.onattach = listener.onattach; self.onpredrain = listener.onpredrain; self.ondrain = listener.ondrain; self:onattach(data); end -- Stub handlers function interface_mt:onconnect() end function interface_mt:onincoming() end function interface_mt:ondisconnect() end function interface_mt:ontimeout() end function interface_mt:onreadtimeout() end function interface_mt:onpredrain() end function interface_mt:ondrain() end function interface_mt:ondetach() end function interface_mt:onattach() end function interface_mt:onstatus() end -- End of client interface methods local function handleclient( client, ip, port, server, pattern, listener, sslctx, extra ) -- creates an client interface --vdebug("creating client interfacce...") local interface = { type = "client"; conn = client; currenttime = socket_gettime( ); -- safe the origin writebuffer = {}; -- writebuffer writebufferlen = 0; -- length of writebuffer send = client.send; -- caching table lookups receive = client.receive; onconnect = listener.onconnect; -- will be called when client disconnects ondisconnect = listener.ondisconnect; -- will be called when client disconnects onincoming = listener.onincoming; -- will be called when client sends data ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs onreadtimeout = listener.onreadtimeout; -- called when socket inactivity timeout occurs onpredrain = listener.onpredrain; -- called before writes ondrain = listener.ondrain; -- called when writebuffer is empty ondetach = listener.ondetach; -- called when disassociating this listener from this connection onstatus = listener.onstatus; -- called for status changes (e.g. of SSL/TLS) eventread = false, eventwrite = false, eventclose = false, eventhandshake = false, eventstarthandshake = false; -- event handler eventconnect = false, eventsession = false; -- more event handler... eventwritetimeout = false; -- even more event handler... eventreadtimeout = false; fatalerror = false; -- error message writecallback = false; -- will be called on write events readcallback = false; -- will be called on read events nointerface = true; -- lock/unlock parameter of this interface noreading = false, nowriting = false; -- locks of the read/writecallback startsslcallback = false; -- starting handshake callback position = false; -- position of client in interfacelist -- Properties _ip = ip, _port = port, _server = server, _pattern = pattern, _serverport = (server and server:port() or nil), _sslctx = sslctx; -- parameters _usingssl = false; -- client is using ssl; extra = extra; servername = extra and extra.servername; } if not has_luasec then interface.starttls = false; end interface.id = tostring(interface):match("%x+$"); interface.writecallback = function( event ) -- called on write events --vdebug( "new client write event, id/ip/port:", interface, ip, port ) if interface.nowriting or ( interface.fatalerror and ( "client to close" ~= interface.fatalerror ) ) then -- leave this event --vdebug( "leaving this event because:", interface.nowriting or interface.fatalerror ) interface.eventwrite = false return -1 end if EV_TIMEOUT == event then -- took too long to write some data to socket -> disconnect interface.fatalerror = "timeout during writing" debug( "writing failed:", interface.fatalerror ) interface:_close() interface.eventwrite = false return -1 else -- can write :) if interface._usingssl then -- handle luasec if interface.eventreadtimeout then -- we have to read first local ret = interface.readcallback( ) -- call readcallback --vdebug( "tried to read in writecallback, result:", ret ) end if interface.eventwritetimeout then -- luasec only interface.eventwritetimeout:close( ) -- first we have to close timeout event which where registered after a wantread error interface.eventwritetimeout = false end end interface:onpredrain(); 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 successful interface.writebuffer[1] = nil interface.writebufferlen = 0 interface:ondrain(); if interface.fatalerror then debug "closing client after writing" interface:_close() -- close interface if needed elseif interface.startsslcallback then -- start ssl connection if needed debug "starting ssl handshake after writing" interface.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, interface.startsslcallback, 0 ) elseif interface.writebufferlen ~= 0 then -- data possibly written from ondrain return EV_WRITE, cfg.WRITE_TIMEOUT elseif interface.eventreadtimeout then return EV_WRITE, cfg.WRITE_TIMEOUT end interface.eventwrite = nil return -1 elseif byte and (err == "timeout" or err == "wantwrite") then -- want write again --vdebug( "writebuffer is not empty:", err ) interface.writebuffer[1] = s_sub( interface.writebuffer[1], byte + 1, interface.writebufferlen ) -- new buffer interface.writebufferlen = interface.writebufferlen - byte if "wantread" == err then -- happens only with luasec local callback = function( ) interface:_close() interface.eventwritetimeout = nil return -1; end interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT ) -- reg a new timeout event debug( "wantread during write attempt, reg it in readcallback but 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 return EV_WRITE, cfg.WRITE_TIMEOUT else -- connection was closed during writing or fatal error interface.fatalerror = err or "fatal error" debug( "connection failed in write event:", interface.fatalerror ) interface:_close() interface.eventwrite = nil return -1 end end end interface.readcallback = function( event ) -- called on read events --vdebug( "new client read event, id/ip/port:", tostring(interface.id), tostring(ip), tostring(port) ) if interface.noreading or interface.fatalerror then -- leave this event --vdebug( "leaving this event because:", tostring(interface.noreading or interface.fatalerror) ) interface.eventread = nil return -1 end if EV_TIMEOUT == event and not interface.conn:dirty() and interface:onreadtimeout() ~= true then interface.fatalerror = "timeout during receiving" debug( "connection failed:", interface.fatalerror ) interface:_close() interface.eventread = nil return -1 -- took too long to get some data from client -> disconnect end if interface._usingssl then -- handle luasec if interface.eventwritetimeout then -- ok, in the past writecallback was registered local ret = interface.writecallback( ) -- call it --vdebug( "tried to write in readcallback, result:", tostring(ret) ) end if interface.eventreadtimeout then interface.eventreadtimeout:close( ) interface.eventreadtimeout = nil end end local buffer, err, part = interface.conn:receive( interface._pattern ) -- receive buffer with "pattern" --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) ) buffer = buffer or part if buffer and #buffer > cfg.MAX_READ_LENGTH then -- check buffer length interface.fatalerror = "receive buffer exceeded" debug( "fatal error:", interface.fatalerror ) interface:_close() interface.eventread = nil return -1 end if err and ( err ~= "timeout" and err ~= "wantread" ) then if "wantwrite" == err then -- need to read on write event if not interface.eventwrite then -- register new write event if needed interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT ) end interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT, function( ) interface:_close() end, cfg.READ_TIMEOUT) debug( "wantwrite during read attempt, reg it in writecallback but 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 ) interface:_close() interface.eventread = nil return -1 end else interface.onincoming( interface, buffer, err ) -- send new data to listener end if interface.noreading then interface.eventread = nil; return -1; end if interface.conn:dirty() then -- still data left in buffer return EV_TIMEOUT, cfg.READ_RETRY_DELAY; end return EV_READ, cfg.READ_TIMEOUT end client:settimeout( 0 ) -- set non blocking setmetatable(interface, interface_mt) interfacelist[ interface ] = true -- add to interfacelist return interface end local function handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- creates a server interface debug "creating server interface..." local interface = { _connections = 0; type = "server"; conn = server; onconnect = listener.onconnect; -- will be called when new client connected eventread = false; -- read event handler eventclose = false; -- close event handler readcallback = false; -- read event callback fatalerror = false; -- error message nointerface = true; -- lock/unlock parameter _ip = addr, _port = port, _pattern = pattern, _sslctx = sslctx; hosts = {}; } interface.id = tostring(interface):match("%x+$"); interface.readcallback = function( event ) -- server handler, called on incoming connections --vdebug( "server can accept, id/addr/port:", interface, addr, port ) if interface.fatalerror then --vdebug( "leaving this event because:", self.fatalerror ) interface.eventread = nil return -1 end local delay = cfg.ACCEPT_DELAY if EV_TIMEOUT == event then if interface._connections >= cfg.MAX_CONNECTIONS then -- check connection count debug( "to many connections, seconds to wait for next accept:", delay ) return EV_TIMEOUT, delay -- timeout... else return EV_READ -- accept again end end --vdebug("max connection check ok, accepting...") -- luacheck: ignore 231/err local client, err = server:accept() -- try to accept; TODO: check err while client do if interface._connections >= cfg.MAX_CONNECTIONS then client:close( ) -- refuse connection debug( "maximal connections reached, refuse client connection; accept delay:", delay ) return EV_TIMEOUT, delay -- delay for next accept attempt end local client_ip, client_port = client:getpeername( ) interface._connections = interface._connections + 1 -- increase connection count local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx ) --vdebug( "client id:", clientinterface, "startssl:", startssl ) if has_luasec and startssl then clientinterface:starttls(sslctx, true) else clientinterface:_start_session( true ) end debug( "accepted incoming client connection from:", client_ip or "", client_port or "", "to", port or ""); client, err = server:accept() -- try to accept again end return EV_READ end server:settimeout( 0 ) setmetatable(interface, interface_mt) interfacelist[ interface ] = true interface:_start_session() return interface end local function listen(addr, port, listener, config) config = config or {} if config.sslctx and not has_luasec then debug "fatal error: luasec not found" return nil, "luasec not found" end local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE ) -- create server socket if not server then debug( "creating server socket on "..addr.." port "..port.." failed:", err ) return nil, err end local interface = handleserver( server, addr, port, config.read_size, listener, config.tls_ctx, config.tls_direct) -- new server handler debug( "new server created with id:", tostring(interface)) return interface end local function addserver( addr, port, listener, pattern, sslctx ) -- TODO: check arguments --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslctx or "nil", startssl or "nil") return listen( addr, port, listener, { read_size = pattern, tls_ctx = sslctx, tls_direct = not not sslctx, }); end local function wrapclient( client, ip, port, listeners, pattern, sslctx, extra ) local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx, extra ) interface:_start_connection(sslctx) return interface, client --function handleclient( client, ip, port, server, pattern, listener, _, sslctx ) -- creates an client interface end local function addclient( addr, serverport, listener, pattern, sslctx, typ, extra ) if sslctx and not has_luasec then debug "need luasec, but not available" return nil, "luasec not found" end if not typ then 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]; if type( create ) ~= "function" then return nil, "invalid socket type" end local client, err = create() -- creating new socket if not client then debug( "cannot create socket:", err ) return nil, err end client:settimeout( 0 ) -- set nonblocking local res, err = client:setpeername( addr, serverport ) -- connect if res or ( err == "timeout" ) then -- luacheck: ignore 211/port local ip, port = client:getsockname( ) local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx, extra ) debug( "new connection id:", interface.id ) return interface, err else debug( "new connection failed:", err ) return nil, err end end local function loop( ) -- starts the event loop base:loop( ) return "quitting"; end local function newevent( ... ) return addevent( base, ... ) end local function closeallservers ( arg ) for item in pairs( interfacelist ) do if item.type == "server" then item:close( arg ) end end end local function setquitting(yes) if yes then -- Quit now if yes ~= "once" then closeallservers(); end base:loopexit(); end end local function get_backend() return "libevent " .. base:method(); end -- We need to hold onto the events to stop them -- being garbage-collected local signal_events = {}; -- [signal_num] -> event object local function hook_signal(signal_num, handler) local function _handler() local ret = handler(); if ret ~= false then -- Continue handling this signal? return EV_SIGNAL; -- Yes end return -1; -- Close this event end signal_events[signal_num] = base:addevent(signal_num, EV_SIGNAL, _handler); return signal_events[signal_num]; end local function link(sender, receiver, buffersize) local sender_locked; function receiver:ondrain() if sender_locked then sender:resume(); sender_locked = nil; end end function sender:onincoming(data) receiver:write(data); if receiver.writebufferlen >= buffersize then sender_locked = true; sender:pause(); end end 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, loop = loop, link = link, event = levent, event_base = base, addevent = newevent, addserver = addserver, listen = listen, addclient = addclient, wrapclient = wrapclient, setquitting = setquitting, closeall = closeallservers, get_backend = get_backend, hook_signal = hook_signal, add_task = add_task, watchfd = watchfd, __NAME = SCRIPT_NAME, __DATE = LAST_MODIFIED, __AUTHOR = SCRIPT_AUTHOR, __VERSION = SCRIPT_VERSION, } prosody-0.12.4/net/PaxHeaders/server_select.lua0000644000000000000000000000012014475665104016522 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.640765462 prosody-0.12.4/net/server_select.lua0000644000175000017500000007777414475665104020755 0ustar00prosodyprosody00000000000000-- -- server.lua by blastbeat of the luadch project -- Re-used here under the MIT/X Consortium License -- -- Modifications (C) 2008-2010 Matthew Wild, Waqas Hussain -- -- // wrapping luadch stuff // -- local use = function( what ) return _G[ what ] end local log, table_concat = require ("util.logger").init("socket"), table.concat; local out_put = function (...) return log("debug", table_concat{...}); end local out_error = function (...) return log("warn", table_concat{...}); end ----------------------------------// DECLARATION //-- --// constants //-- local STAT_UNIT = 1 -- byte --// lua functions //-- local type = use "type" local pairs = use "pairs" local ipairs = use "ipairs" local tonumber = use "tonumber" local tostring = use "tostring" --// lua libs //-- local table = use "table" local string = use "string" local coroutine = use "coroutine" --// lua lib methods //-- 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 --// extern libs //-- local has_luasec, luasec = pcall ( require , "ssl" ) local luasocket = use "socket" or require "socket" local luasocket_gettime = luasocket.gettime 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_select = luasocket.select --// functions //-- local id local loop local stats local idfalse local closeall local addsocket local addserver local listen local addtimer local getserver local wrapserver local getsettings local closesocket local removesocket local removeserver local wrapconnection local changesettings --// tables //-- local _server local _readlist local _timerlist local _sendlist local _socketlist local _closelist local _readtimes local _writetimes local _fullservers --// simple data types //-- local _ local _readlistlen local _sendlistlen local _timerlistlen local _sendtraffic local _readtraffic local _selecttimeout local _tcpbacklog local _accepretry local _starttime local _currenttime local _maxsendlen local _maxreadlen local _checkinterval local _sendtimeout local _readtimeout local _maxselectlen local _maxfd local _maxsslhandshake ----------------------------------// DEFINITION //-- _server = { } -- key = port, value = table; list of listening servers _readlist = { } -- array with sockets to read from _sendlist = { } -- array with sockets to write to _timerlist = { } -- array of timer functions _socketlist = { } -- key = socket, value = wrapped socket (handlers) _readtimes = { } -- key = handler, value = timestamp of last data reading _writetimes = { } -- key = handler, value = timestamp of last data writing/sending _closelist = { } -- handlers to close _fullservers = { } -- servers in a paused state while there are too many clients _readlistlen = 0 -- length of readlist _sendlistlen = 0 -- length of sendlist _timerlistlen = 0 -- length of timerlist _sendtraffic = 0 -- some stats _readtraffic = 0 _selecttimeout = 1 -- timeout of socket.select _tcpbacklog = 128 -- some kind of hint to the OS _accepretry = 10 -- seconds to wait until the next attempt of a full server to accept _maxsendlen = 51000 * 1024 -- max len of send buffer _maxreadlen = 25000 * 1024 -- max len of read buffer _checkinterval = 30 -- interval in secs to check idle clients _sendtimeout = 60000 -- allowed send 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 determine 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 _maxselectlen = luasocket._SETSIZE or 1024 -- But this still applies on Windows _maxsslhandshake = 30 -- max handshake round-trips ----------------------------------// PRIVATE //-- wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, ssldirect ) -- this function wraps a server -- FIXME Make sure FD < _maxfd if socket:getfd() >= _maxfd then out_error("server.lua: Disallowed FD number: "..socket:getfd()) socket:close() return nil, "fd-too-large" end local connections = 0 local dispatch, disconnect = listeners.onconnect, listeners.ondisconnect local accept = socket.accept --// public methods of the object //-- local handler = { } handler.shutdown = function( ) end handler.ssl = function( ) return sslctx ~= nil end handler.sslctx = function( ) return sslctx end handler.hosts = {} -- sni handler.remove = function( ) connections = connections - 1 if handler then handler.resume( ) end end handler.close = function() socket:close( ) _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _readlistlen = removesocket( _readlist, socket, _readlistlen ) _server[ip..":"..serverport] = nil; _socketlist[ socket ] = nil handler = nil socket = nil --mem_free( ) out_put "server.lua: closed server handler and removed sockets from list" end handler.pause = function( hard ) if not handler.paused then _readlistlen = removesocket( _readlist, socket, _readlistlen ) if hard then _socketlist[ socket ] = nil socket:close( ) socket = nil; end handler.paused = true; out_put("server.lua: server [", ip, "]:", serverport, " paused") end end handler.resume = function( ) if handler.paused then if not socket then socket = socket_bind( ip, serverport, _tcpbacklog ); socket:settimeout( 0 ) end _readlistlen = addsocket(_readlist, socket, _readlistlen) _socketlist[ socket ] = handler _fullservers[ handler ] = nil handler.paused = false; out_put("server.lua: server [", ip, "]:", serverport, " resumed") end end handler.ip = function( ) return ip end handler.serverport = function( ) return serverport end handler.socket = function( ) return socket end handler.readbuffer = function( ) if _readlistlen >= _maxselectlen or _sendlistlen >= _maxselectlen then handler.pause( ) _fullservers[ handler ] = _currenttime out_put( "server.lua: refused new client connection: server full" ) return false end local client, err = accept( socket ) -- try to accept if client then local ip, clientport = client:getpeername( ) local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx, ssldirect ) -- wrap new client socket if err then -- error while wrapping ssl socket return false end connections = connections + 1 out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport)) if dispatch and not ssldirect then -- SSL connections will notify onconnect when handshake completes return dispatch( handler ); end return; elseif err then -- maybe timeout or something else out_put( "server.lua: error with new client connection: ", tostring(err) ) handler.pause( ) _fullservers[ handler ] = _currenttime return false end end return handler end wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, ssldirect, extra ) -- this function wraps a client to a handler object if socket:getfd() >= _maxfd then out_error("server.lua: Disallowed FD number: "..socket:getfd()) -- PROTIP: Switch to libevent socket:close( ) -- Should we send some kind of error here? if server then _fullservers[ server ] = _currenttime server.pause( ) end return nil, nil, "fd-too-large" end socket:settimeout( 0 ) --// local import of socket methods //-- local send local receive local shutdown --// private closures of the object //-- local ssl local pending local dispatch = listeners.onincoming local status = listeners.onstatus local disconnect = listeners.ondisconnect local predrain = listeners.onpredrain local drain = listeners.ondrain local onreadtimeout = listeners.onreadtimeout; local detach = listeners.ondetach local bufferqueue = { } -- buffer array local bufferqueuelen = 0 -- end of buffer array local toclose local needtls local bufferlen = 0 local noread = false local nosend = false local sendtraffic, readtraffic = 0, 0 local maxsendlen = _maxsendlen local maxreadlen = _maxreadlen --// public methods of the object //-- local handler = bufferqueue -- saves a table ^_^ handler.extra = extra if extra then handler.servername = extra.servername end handler.dispatch = function( ) return dispatch end handler.disconnect = function( ) return disconnect end handler.onreadtimeout = onreadtimeout; handler.setlistener = function( self, listeners, data ) if detach then detach(self) -- Notify listener that it is no longer responsible for this connection end dispatch = listeners.onincoming disconnect = listeners.ondisconnect status = listeners.onstatus predrain = listeners.onpredrain drain = listeners.ondrain handler.onreadtimeout = listeners.onreadtimeout detach = listeners.ondetach if listeners.onattach then listeners.onattach(self, data) end end handler._setpending = function( ) pending = true end handler.getstats = function( ) return readtraffic, sendtraffic end handler.ssl = function( ) return ssl end handler.sslctx = function ( ) return sslctx end handler.send = function( _, data, i, j ) return send( socket, data, i, j ) end handler.receive = function( pattern, prefix ) return receive( socket, pattern, prefix ) end handler.shutdown = function( pattern ) return shutdown( socket, pattern ) end handler.setoption = function (self, option, value) if socket.setoption then return socket:setoption(option, value); end return false, "setoption not implemented"; end handler.force_close = function ( self, err ) if bufferqueuelen ~= 0 then out_put("server.lua: discarding unwritten data for ", tostring(ip), ":", tostring(clientport)) bufferqueuelen = 0; end return self:close(err); end handler.close = function( self, err ) if not handler then return true; end _readlistlen = removesocket( _readlist, socket, _readlistlen ) _readtimes[ handler ] = nil if bufferqueuelen ~= 0 then handler:sendbuffer() -- Try now to send any outstanding data if bufferqueuelen ~= 0 then -- Still not empty, so we'll try again later if handler then handler.write = nil -- ... but no further writing allowed end toclose = true return false end end if socket then _ = shutdown and shutdown( socket ) socket:close( ) _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _socketlist[ socket ] = nil socket = nil else out_put "server.lua: socket already closed" end if handler then _writetimes[ handler ] = nil _closelist[ handler ] = nil local _handler = handler; handler = nil if disconnect then disconnect(_handler, err or false); disconnect = nil end end if server then server.remove( ) end out_put "server.lua: closed client handler and removed socket from list" return true end handler.server = function ( ) return server end handler.ip = function( ) return ip end handler.serverport = function( ) return serverport end handler.clientport = function( ) return clientport end handler.port = handler.clientport -- COMPAT server_event local write = function( self, data ) if not handler then return false end 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 return false elseif not nosend and socket and not _sendlist[ socket ] then _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) end bufferqueuelen = bufferqueuelen + 1 bufferqueue[ bufferqueuelen ] = data if handler then _writetimes[ handler ] = _writetimes[ handler ] or _currenttime end return true end handler.write = write handler.bufferqueue = function( self ) return bufferqueue end handler.socket = function( self ) return socket end handler.set_mode = function( self, new ) pattern = new or pattern return pattern end handler.set_send = function ( self, newsend ) send = newsend or send return send end handler.bufferlen = function( self, readlen, sendlen ) maxsendlen = sendlen or maxsendlen maxreadlen = readlen or maxreadlen return bufferlen, maxreadlen, maxsendlen end handler.lock_read = function (self, switch) out_error( "server.lua, lock_read() is deprecated, use pause() and resume()" ) if switch == true then return self:pause() elseif switch == false then return self:resume() end return noread end handler.pause = function (self) local tmp = _readlistlen _readlistlen = removesocket( _readlist, socket, _readlistlen ) _readtimes[ handler ] = nil if _readlistlen ~= tmp then noread = true end return noread; end handler.resume = function (self) if noread then noread = false _readlistlen = addsocket(_readlist, socket, _readlistlen) _readtimes[ handler ] = _currenttime end return noread; end handler.lock = function( self, switch ) out_error( "server.lua, lock() is deprecated" ) handler.lock_read (self, switch) if switch == true then handler.pause_writes (self) elseif switch == false then handler.resume_writes (self) end return noread, nosend end handler.pause_writes = function (self) local tmp = _sendlistlen _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _writetimes[ handler ] = nil nosend = true end handler.resume_writes = function (self) nosend = false if bufferlen > 0 and socket then _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) end end local _readbuffer = function( ) -- this function reads data local buffer, err, part = receive( socket, pattern ) -- receive buffer with "pattern" if not err or (err == "wantread" or err == "timeout") then -- received something local buffer = buffer or part or "" local len = #buffer if len > maxreadlen then handler:close( "receive buffer exceeded" ) return false end local count = len * STAT_UNIT readtraffic = readtraffic + count _readtraffic = _readtraffic + count _readtimes[ handler ] = _currenttime --out_put( "server.lua: read data '", buffer:gsub("[^%w%p ]", "."), "', error: ", err ) if pending then -- connection established pending = nil if listeners.onconnect then listeners.onconnect(handler) end end 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) ) _ = handler and handler:force_close( err ) return false end end local _sendbuffer = function( ) -- this function sends data local succ, err, byte, buffer, count; if socket then if pending then pending = nil if listeners.onconnect then listeners.onconnect(handler); end end if predrain then predrain(handler); end buffer = table_concat( bufferqueue, "", 1, bufferqueuelen ) succ, err, byte = send( socket, buffer, 1, bufferlen ) count = ( succ or byte or 0 ) * STAT_UNIT sendtraffic = sendtraffic + count _sendtraffic = _sendtraffic + count for i = bufferqueuelen,1,-1 do bufferqueue[ i ] = nil end --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) ) else succ, err, count = false, "unexpected close", 0; end if succ then -- sending successful bufferqueuelen = 0 bufferlen = 0 _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) -- delete socket from writelist _writetimes[ handler ] = nil if drain then drain(handler) end _ = needtls and handler:starttls(nil) _ = toclose and handler:force_close( ) return true elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write buffer = string_sub( buffer, byte + 1, bufferlen ) -- new buffer bufferqueue[ 1 ] = buffer -- insert new buffer in queue bufferqueuelen = 1 bufferlen = bufferlen - byte _writetimes[ handler ] = _currenttime return true else -- connection was closed during sending or fatal error out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) ) _ = handler and handler:force_close( err ) return false end end -- Set the sslctx local handshake; function handler.set_sslctx(self, new_sslctx) sslctx = new_sslctx; local read, wrote handshake = coroutine_wrap( function( client ) -- create handshake coroutine local err for _ = 1, _maxsslhandshake do _sendlistlen = ( wrote and removesocket( _sendlist, client, _sendlistlen ) ) or _sendlistlen _readlistlen = ( read and removesocket( _readlist, client, _readlistlen ) ) or _readlistlen read, wrote = nil, nil _, err = client:dohandshake( ) if not err then out_put( "server.lua: ssl handshake done" ) handler.readbuffer = _readbuffer -- when handshake is done, replace the handshake function with regular functions handler.sendbuffer = _sendbuffer _ = status and status( handler, "ssl-handshake-complete" ) if self.autostart_ssl and listeners.onconnect then listeners.onconnect(self); if bufferqueuelen ~= 0 then _sendlistlen = addsocket(_sendlist, client, _sendlistlen) end end _readlistlen = addsocket(_readlist, client, _readlistlen) return true else if err == "wantwrite" then _sendlistlen = addsocket(_sendlist, client, _sendlistlen) wrote = true elseif err == "wantread" then _readlistlen = addsocket(_readlist, client, _readlistlen) read = true else break; end err = nil; coroutine_yield( ) -- handshake not finished end end err = ( err or "handshake too long" ); out_put( "server.lua: ", err ); _ = handler and handler:force_close(err) return false, err -- handshake failed end ) end if has_luasec then handler.starttls = function( self, _sslctx) if _sslctx then handler:set_sslctx(_sslctx); end if bufferqueuelen > 0 then out_put "server.lua: we need to do tls, but delaying until send buffer empty" needtls = true return end out_put( "server.lua: attempting to start tls on " .. tostring( socket ) ) local oldsocket, err = socket socket, err = ssl_wrap( socket, sslctx ) -- wrap socket if not socket then out_put( "server.lua: error while starting tls on client: ", tostring(err or "unknown error") ) return nil, err -- fatal error end if socket.sni then if self.servername then socket:sni(self.servername); elseif self._server and type(self._server.hosts) == "table" and next(self._server.hosts) ~= nil then socket:sni(self.server().hosts, true); end end socket:settimeout( 0 ) -- add the new socket to our system send = socket.send receive = socket.receive shutdown = id _socketlist[ socket ] = handler _readlistlen = addsocket(_readlist, socket, _readlistlen) -- remove traces of the old socket _readlistlen = removesocket( _readlist, oldsocket, _readlistlen ) _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen ) _socketlist[ oldsocket ] = nil handler.starttls = nil needtls = nil -- Secure now (if handshake fails connection will close) ssl = true handler.readbuffer = handshake handler.sendbuffer = handshake return handshake( socket ) -- do handshake end end handler.readbuffer = _readbuffer handler.sendbuffer = _sendbuffer send = socket.send receive = socket.receive shutdown = ( ssl and id ) or socket.shutdown _socketlist[ socket ] = handler _readlistlen = addsocket(_readlist, socket, _readlistlen) if sslctx and ssldirect and has_luasec then out_put "server.lua: auto-starting ssl negotiation..." handler.autostart_ssl = true; local ok, err = handler:starttls(sslctx); if ok == false then return nil, nil, err end end return handler, socket end id = function( ) end idfalse = function( ) return false end addsocket = function( list, socket, len ) if not list[ socket ] then len = len + 1 list[ len ] = socket list[ socket ] = len end return len; end removesocket = function( list, socket, len ) -- this function removes sockets from a list ( copied from copas ) local pos = list[ socket ] if pos then list[ socket ] = nil local last = list[ len ] list[ len ] = nil if last ~= socket then list[ last ] = pos list[ pos ] = last end return len - 1 end return len end closesocket = function( socket ) _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _readlistlen = removesocket( _readlist, socket, _readlistlen ) _socketlist[ socket ] = nil socket:close( ) --mem_free( ) end local function link(sender, receiver, buffersize) local sender_locked; local _sendbuffer = receiver.sendbuffer; function receiver.sendbuffer() _sendbuffer(receiver); if sender_locked and receiver.bufferlen() < buffersize then sender:lock_read(false); -- Unlock now sender_locked = nil; end end local _readbuffer = sender.readbuffer; function sender.readbuffer() _readbuffer(); if not sender_locked and receiver.bufferlen() >= buffersize then sender_locked = true; sender:lock_read(true); end end sender:set_mode("*a"); end ----------------------------------// PUBLIC //-- listen = function ( addr, port, listeners, config ) addr = addr or "*" config = config or {} local err local sslctx = config.tls_ctx; local ssldirect = config.tls_direct; local pattern = config.read_size; if type( listeners ) ~= "table" then err = "invalid listener table" elseif type ( addr ) ~= "string" then err = "invalid address" elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then err = "invalid port" elseif _server[ addr..":"..port ] then err = "listeners on '[" .. addr .. "]:" .. port .. "' already exist" elseif sslctx and not has_luasec then err = "luasec not found" end if err then out_error( "server.lua, [", addr, "]:", port, ": ", err ) return nil, err end local server, err = socket_bind( addr, port, _tcpbacklog ) if err then out_error( "server.lua, [", addr, "]:", port, ": ", err ) return nil, err end local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, ssldirect ) -- wrap new server socket if not handler then server:close( ) return nil, err end server:settimeout( 0 ) _readlistlen = addsocket(_readlist, server, _readlistlen) _server[ addr..":"..port ] = handler _socketlist[ server ] = handler out_put( "server.lua: new "..(sslctx and "ssl " or "").."server listener on '[", addr, "]:", port, "'" ) return handler end addserver = function( addr, port, listeners, pattern, sslctx ) -- this function provides a way for other scripts to reg a server return listen(addr, port, listeners, { read_size = pattern; tls_ctx = sslctx; tls_direct = sslctx and true or false; }); end getserver = function ( addr, port ) return _server[ addr..":"..port ]; end removeserver = function( addr, port ) local handler = _server[ addr..":"..port ] if not handler then return nil, "no server found on '[" .. addr .. "]:" .. tostring( port ) .. "'" end handler:close( ) _server[ addr..":"..port ] = nil return true end closeall = function( ) for _, handler in pairs( _socketlist ) do handler:close( ) _socketlist[ _ ] = nil end _readlistlen = 0 _sendlistlen = 0 _timerlistlen = 0 _server = { } _readlist = { } _sendlist = { } _timerlist = { } _socketlist = { } --mem_free( ) end getsettings = function( ) return { select_timeout = _selecttimeout; tcp_backlog = _tcpbacklog; max_send_buffer_size = _maxsendlen; max_receive_buffer_size = _maxreadlen; select_idle_check_interval = _checkinterval; send_timeout = _sendtimeout; read_timeout = _readtimeout; max_connections = _maxselectlen; max_ssl_handshake_roundtrips = _maxsslhandshake; highest_allowed_fd = _maxfd; accept_retry_interval = _accepretry; } end changesettings = function( new ) if type( new ) ~= "table" then return nil, "invalid settings table" end _selecttimeout = tonumber( new.select_timeout ) or _selecttimeout _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 _tcpbacklog = tonumber( new.tcp_backlog ) or _tcpbacklog _sendtimeout = tonumber( new.send_timeout ) or _sendtimeout _readtimeout = tonumber( new.read_timeout ) or _readtimeout _accepretry = tonumber( new.accept_retry_interval ) or _accepretry _maxselectlen = new.max_connections or _maxselectlen _maxsslhandshake = new.max_ssl_handshake_roundtrips or _maxsslhandshake _maxfd = new.highest_allowed_fd or _maxfd return true end addtimer = function( listener ) if type( listener ) ~= "function" then return nil, "invalid listener function" end _timerlistlen = _timerlistlen + 1 _timerlist[ _timerlistlen ] = listener 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 local quitting; local function setquitting(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 _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 handler:sendbuffer( ) else closesocket( socket ) out_put "server.lua: found no handler and closed socket (writelist)" -- this should not happen end end for handler, err in pairs( _closelist ) do handler.disconnect( )( handler, err ) handler:force_close() -- forced disconnect _closelist[ handler ] = nil; end _currenttime = luasocket_gettime( ) -- Check for socket timeouts if _currenttime - _starttime > _checkinterval then _starttime = _currenttime for handler, timestamp in pairs( _writetimes ) do if _currenttime - timestamp > _sendtimeout then handler.disconnect( )( handler, "send timeout" ) handler:force_close() -- forced disconnect end end for handler, timestamp in pairs( _readtimes ) do if _currenttime - timestamp > _readtimeout then if not(handler.onreadtimeout) or handler:onreadtimeout() ~= true then handler.disconnect( )( handler, "read timeout" ) handler:close( ) -- forced disconnect? else _readtimes[ handler ] = _currenttime -- reset timer end end end end for server, paused_time in pairs( _fullservers ) do if _currenttime - paused_time > _accepretry then _fullservers[ server ] = nil; server.resume(); end end until quitting; if quitting == "once" then quitting = nil; return; end closeall(); return "quitting" end local function step() return loop(true); end local function get_backend() return "select"; end --// EXPERIMENTAL //-- local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, extra ) local handler, socket, err = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, sslctx, extra) if not handler then return nil, err end _socketlist[ socket ] = handler if not sslctx then handler._setpending() _readlistlen = addsocket(_readlist, socket, _readlistlen) _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) end return handler, socket end local addclient = function( address, port, listeners, pattern, sslctx, typ, extra ) local err if type( listeners ) ~= "table" then err = "invalid listener table" elseif type ( address ) ~= "string" then err = "invalid address" elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then err = "invalid port" elseif sslctx and not has_luasec then err = "luasec not found" end if not typ then local 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]; if type( create ) ~= "function" then err = "invalid socket type" end if err then out_error( "server.lua, addclient: ", err ) return nil, err end local client, err = create( ) if err then return nil, err end client:settimeout( 0 ) 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, extra ) else return nil, err end end 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 //-- use "setmetatable" ( _socketlist, { __mode = "k" } ) use "setmetatable" ( _readtimes, { __mode = "k" } ) use "setmetatable" ( _writetimes, { __mode = "k" } ) _starttime = luasocket_gettime( ) local function setlogger(new_logger) local old_logger = log; if new_logger then log = new_logger; end return old_logger; end ----------------------------------// PUBLIC INTERFACE //-- return { _addtimer = addtimer, add_task = add_task; addclient = addclient, wrapclient = wrapclient, watchfd = watchfd, loop = loop, link = link, step = step, stats = stats, closeall = closeall, addserver = addserver, listen = listen, getserver = getserver, setlogger = setlogger, getsettings = getsettings, setquitting = setquitting, removeserver = removeserver, get_backend = get_backend, changesettings = changesettings, } prosody-0.12.4/net/PaxHeaders/stun.lua0000644000000000000000000000012014475665104014646 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.644765423 prosody-0.12.4/net/stun.lua0000644000175000017500000002156414475665104017063 0ustar00prosodyprosody00000000000000local base64 = require "util.encodings".base64; local hashes = require "util.hashes"; local net = require "util.net"; local random = require "util.random"; local struct = require "util.struct"; local bit32 = require"util.bitcompat"; local sxor = require"util.strbitop".sxor; local new_ip = require "util.ip".new_ip; --- Public helpers -- Following draft-uberti-behave-turn-rest-00, convert a 'secret' string -- into a username/password pair that can be used to auth to a TURN server local function get_user_pass_from_secret(secret, ttl, opt_username) ttl = ttl or 86400; local username; if opt_username then username = ("%d:%s"):format(os.time() + ttl, opt_username); else username = ("%d"):format(os.time() + ttl); end local password = base64.encode(hashes.hmac_sha1(secret, username)); return username, password, ttl; end -- Following RFC 8489 9.2, convert credentials to a HMAC key for signing local function get_long_term_auth_key(realm, username, password) return hashes.md5(username..":"..realm..":"..password); end --- Packet building/parsing local packet_methods = {}; local packet_mt = { __index = packet_methods }; local magic_cookie = string.char(0x21, 0x12, 0xA4, 0x42); local function lookup_table(t) local lookup = {}; for k, v in pairs(t) do lookup[k] = v; lookup[v] = k; end return lookup; end local methods = { binding = 0x001; -- TURN allocate = 0x003; refresh = 0x004; send = 0x006; data = 0x007; ["create-permission"] = 0x008; ["channel-bind"] = 0x009; }; local method_lookup = lookup_table(methods); local classes = { request = 0; indication = 1; success = 2; error = 3; }; local class_lookup = lookup_table(classes); local addr_families = { "IPv4", "IPv6" }; local addr_family_lookup = lookup_table(addr_families); local attributes = { ["mapped-address"] = 0x0001; ["username"] = 0x0006; ["message-integrity"] = 0x0008; ["error-code"] = 0x0009; ["unknown-attributes"] = 0x000A; ["realm"] = 0x0014; ["nonce"] = 0x0015; ["xor-mapped-address"] = 0x0020; ["software"] = 0x8022; ["alternate-server"] = 0x8023; ["fingerprint"] = 0x8028; ["message-integrity-sha256"] = 0x001C; ["password-algorithm"] = 0x001D; ["userhash"] = 0x001E; ["password-algorithms"] = 0x8002; ["alternate-domains"] = 0x8003; -- TURN ["requested-transport"] = 0x0019; ["xor-peer-address"] = 0x0012; ["data"] = 0x0013; ["xor-relayed-address"] = 0x0016; }; local attribute_lookup = lookup_table(attributes); function packet_methods:serialize_header(length) assert(#self.transaction_id == 12, "invalid transaction id length"); local header = struct.pack(">I2I2", self.type, length )..magic_cookie..self.transaction_id; return header; end function packet_methods:serialize() local payload = table.concat(self.attributes); return self:serialize_header(#payload)..payload; end function packet_methods:is_request() return bit32.band(self.type, 0x0110) == 0x0000; end function packet_methods:is_indication() return bit32.band(self.type, 0x0110) == 0x0010; end function packet_methods:is_success_resp() return bit32.band(self.type, 0x0110) == 0x0100; end function packet_methods:is_err_resp() return bit32.band(self.type, 0x0110) == 0x0110; end function packet_methods:get_method() local method = bit32.bor( bit32.rshift(bit32.band(self.type, 0x3E00), 2), bit32.rshift(bit32.band(self.type, 0x00E0), 1), bit32.band(self.type, 0x000F) ); return method, method_lookup[method]; end function packet_methods:get_class() local class = bit32.bor( bit32.rshift(bit32.band(self.type, 0x0100), 7), bit32.rshift(bit32.band(self.type, 0x0010), 4) ); return class, class_lookup[class]; end function packet_methods:set_type(method, class) if type(method) == "string" then method = assert(method_lookup[method:lower()], "unknown method: "..method); end if type(class) == "string" then class = assert(classes[class], "unknown class: "..class); end self.type = bit32.bor( bit32.lshift(bit32.band(method, 0x1F80), 2), bit32.lshift(bit32.band(method, 0x0070), 1), bit32.band(method, 0x000F), bit32.lshift(bit32.band(class, 0x0002), 7), bit32.lshift(bit32.band(class, 0x0001), 4) ); end local function _serialize_attribute(attr_type, value) local len = #value; local padding = string.rep("\0", (4 - len)%4); return struct.pack(">I2I2", attr_type, len )..value..padding; end function packet_methods:add_attribute(attr_type, value) if type(attr_type) == "string" then attr_type = assert(attributes[attr_type], "unknown attribute: "..attr_type); end table.insert(self.attributes, _serialize_attribute(attr_type, value)); end function packet_methods:deserialize(bytes) local type, len, cookie = struct.unpack(">I2I2I4", bytes); assert(#bytes == (len + 20), "incorrect packet length"); assert(cookie == 0x2112A442, "invalid magic cookie"); self.type = type; self.transaction_id = bytes:sub(9, 20); self.attributes = {}; local pos = 21; while pos < #bytes do local attr_hdr = bytes:sub(pos, pos+3); assert(#attr_hdr == 4, "packet truncated in attribute header"); local attr_type, attr_len = struct.unpack(">I2I2", attr_hdr); --luacheck: ignore 211/attr_type if attr_len == 0 then table.insert(self.attributes, attr_hdr); pos = pos + 20; else local data = bytes:sub(pos + 4, pos + 3 + attr_len); assert(#data == attr_len, "packet truncated in attribute value"); table.insert(self.attributes, attr_hdr..data); local n_padding = (4 - attr_len)%4; pos = pos + 4 + attr_len + n_padding; end end return self; end function packet_methods:get_attribute(attr_type, idx) idx = math.max(idx or 1, 1); if type(attr_type) == "string" then attr_type = assert(attribute_lookup[attr_type:lower()], "unknown attribute: "..attr_type); end for _, attribute in ipairs(self.attributes) do if struct.unpack(">I2", attribute) == attr_type then if idx == 1 then return attribute:sub(5); else idx = idx - 1; end end end end function packet_methods:_unpack_address(data, xor) local family, port = struct.unpack("x>BI2", data); local addr = data:sub(5); if xor then port = bit32.bxor(port, 0x2112); addr = sxor(addr, magic_cookie..self.transaction_id); end return { family = addr_families[family] or "unknown"; port = port; address = net.ntop(addr); }; end function packet_methods:_pack_address(family, addr, port, xor) if xor then port = bit32.bxor(port, 0x2112); addr = sxor(addr, magic_cookie..self.transaction_id); end local family_port = struct.pack("x>BI2", family, port); return family_port..addr end function packet_methods:get_mapped_address() local data = self:get_attribute("mapped-address"); if not data then return; end return self:_unpack_address(data, false); end function packet_methods:get_xor_mapped_address() local data = self:get_attribute("xor-mapped-address"); if not data then return; end return self:_unpack_address(data, true); end function packet_methods:add_xor_peer_address(address, port) local parsed_ip = assert(new_ip(address)); local family = assert(addr_family_lookup[parsed_ip.proto], "Unknown IP address family: "..parsed_ip.proto); self:add_attribute("xor-peer-address", self:_pack_address(family, parsed_ip.packed, port or 0, true)); end function packet_methods:get_xor_relayed_address(idx) local data = self:get_attribute("xor-relayed-address", idx); if not data then return; end return self:_unpack_address(data, true); end function packet_methods:get_xor_relayed_addresses() return { self:get_xor_relayed_address(1); self:get_xor_relayed_address(2); }; end function packet_methods:add_message_integrity(key) -- Add attribute with a dummy value so we can artificially increase -- the packet 'length' self:add_attribute("message-integrity", string.rep("\0", 20)); -- Get the packet data, minus the message-integrity attribute itself local pkt = self:serialize():sub(1, -25); local hash = hashes.hmac_sha1(key, pkt, false); self.attributes[#self.attributes] = nil; assert(#hash == 20, "invalid hash length"); self:add_attribute("message-integrity", hash); end do local transports = { udp = 0x11; }; function packet_methods:add_requested_transport(transport) local transport_code = transports[transport]; assert(transport_code, "unsupported transport: "..tostring(transport)); self:add_attribute("requested-transport", string.char( transport_code, 0x00, 0x00, 0x00 )); end end function packet_methods:get_error() local err_attr = self:get_attribute("error-code"); if not err_attr then return nil; end local number = err_attr:byte(4); local class = bit32.band(0x07, err_attr:byte(3)); local msg = err_attr:sub(5); return (class*100)+number, msg; end local function new_packet(method, class) local p = setmetatable({ transaction_id = random.bytes(12); length = 0; attributes = {}; }, packet_mt); p:set_type(method or "binding", class or "request"); return p; end return { new_packet = new_packet; get_user_pass_from_secret = get_user_pass_from_secret; get_long_term_auth_key = get_long_term_auth_key; }; prosody-0.12.4/net/PaxHeaders/unbound.lua0000644000000000000000000000012014475665104015327 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.644765423 prosody-0.12.4/net/unbound.lua0000644000175000017500000001363314475665104017542 0ustar00prosodyprosody00000000000000-- libunbound based net.adns replacement for Prosody IM -- Copyright (C) 2013-2015 Kim Alvefur -- -- This file is MIT licensed. -- -- luacheck: ignore prosody local setmetatable = setmetatable; local tostring = tostring; local t_concat = table.concat; local s_format = string.format; local s_lower = string.lower; local s_upper = string.upper; local noop = function() end; local logger = require "util.logger"; local log = logger.init("unbound"); local net_server = require "net.server"; local libunbound = require"lunbound"; local promise = require"util.promise"; local new_id = require "util.id".short; local gettime = require"socket".gettime; local dns_utils = require"util.dns"; local classes, types, errors = dns_utils.classes, dns_utils.types, dns_utils.errors; local parsers = dns_utils.parsers; local builtin_defaults = { hoststxt = false } local function add_defaults(conf) conf = conf or {}; for option, default in pairs(builtin_defaults) do if conf[option] == nil then conf[option] = default; end end for option, default in pairs(libunbound.config) do if conf[option] == nil then conf[option] = default; end end return conf; end local unbound_config; if prosody then local config = require"core.configmanager"; unbound_config = add_defaults(config.get("*", "unbound")); prosody.events.add_handler("config-reloaded", function() unbound_config = add_defaults(config.get("*", "unbound")); end); end -- Note: libunbound will default to using root hints if resolvconf is unset local function connect_server(unbound, server) log("debug", "Setting up net.server event handling for %s", unbound); return server.watchfd(unbound, function () log("debug", "Processing queries for %s", unbound); unbound:process() end); end local unbound, server_conn; local function initialize() unbound = libunbound.new(unbound_config); server_conn = connect_server(unbound, net_server); end if prosody then prosody.events.add_handler("server-started", initialize); end local answer_mt = { __tostring = function(self) if self._string then return self._string end local h = s_format("Status: %s", errors[self.status]); if self.secure then h = h .. ", Secure"; elseif self.bogus then h = h .. s_format(", Bogus: %s", self.bogus); end local t = { h }; for i = 1, #self do t[i+1]=self.qname.."\t"..classes[self.qclass].."\t"..types[self.qtype].."\t"..tostring(self[i]); end local _string = t_concat(t, "\n"); self._string = _string; return _string; end; }; local waiting_queries = {}; local function prep_answer(a) if not a then return end local status = errors[a.rcode]; local qclass = classes[a.qclass]; local qtype = types[a.qtype]; a.status, a.class, a.type = status, qclass, qtype; local t = s_lower(qtype); local rr_mt = { __index = a, __tostring = function(self) return tostring(self[t]) end }; local parser = parsers[qtype]; for i = 1, #a do if a.bogus then -- Discard bogus data a[i] = nil; else a[i] = setmetatable({[t] = parser(a[i])}, rr_mt); end end return setmetatable(a, answer_mt); end local function lookup(callback, qname, qtype, qclass) if not unbound then initialize(); end qtype = qtype and s_upper(qtype) or "A"; qclass = qclass and s_upper(qclass) or "IN"; local ntype, nclass = types[qtype], classes[qclass]; local startedat = gettime(); local ret; local log_query = logger.init("unbound.query"..new_id()); local function callback_wrapper(a, err) local gotdataat = gettime(); waiting_queries[ret] = nil; if a then prep_answer(a); log_query("debug", "Results for %s %s %s: %s (%s, %f sec)", qname, qclass, qtype, a.rcode == 0 and (#a .. " items") or a.status, a.secure and "Secure" or a.bogus or "Insecure", gotdataat - startedat); -- Insecure as in unsigned else log_query("error", "Results for %s %s %s: %s", qname, qclass, qtype, tostring(err)); end local ok, cerr = pcall(callback, a, err); if not ok then log_query("error", "Error in callback: %s", cerr); end end log_query("debug", "Resolve %s %s %s", qname, qclass, qtype); local err; ret, err = unbound:resolve_async(callback_wrapper, qname, ntype, nclass); if ret then waiting_queries[ret] = callback; else log_query("error", "Resolver error: %s", err); end return ret, err; end local function lookup_sync(qname, qtype, qclass) if not unbound then initialize(); end qtype = qtype and s_upper(qtype) or "A"; qclass = qclass and s_upper(qclass) or "IN"; local ntype, nclass = types[qtype], classes[qclass]; local a, err = unbound:resolve(qname, ntype, nclass); if not a then return a, err; end return prep_answer(a); end local function cancel(id) local cb = waiting_queries[id]; unbound:cancel(id); if cb then cb(nil, "canceled"); waiting_queries[id] = nil; end return true; end -- Reinitiate libunbound context, drops cache local function purge() for id in pairs(waiting_queries) do cancel(id); end if server_conn then server_conn:close(); end initialize(); return true; end local function not_implemented() error "not implemented"; end -- Public API local _M = { lookup = lookup; cancel = cancel; new_async_socket = not_implemented; dns = { lookup = lookup_sync; cancel = cancel; cache = noop; socket_wrapper_set = noop; settimeout = noop; query = noop; purge = purge; random = noop; peek = noop; types = types; classes = classes; }; }; local function lookup_promise(_, qname, qtype, qclass) return promise.new(function (resolve, reject) local function callback(answer, err) if err then return reject(err); else return resolve(answer); end end local ret, err = lookup(callback, qname, qtype, qclass) if not ret then reject(err); end end); end local wrapper = { lookup = function (_, callback, qname, qtype, qclass) return lookup(callback, qname, qtype, qclass) end; lookup_promise = lookup_promise; _resolver = { settimeout = function () end; closeall = function () end; }; } function _M.resolver() return wrapper; end return _M; prosody-0.12.4/net/PaxHeaders/websocket0000644000000000000000000000013214475665104015066 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.644765423 30 ctime=1693936196.644765423 prosody-0.12.4/net/websocket/0000755000175000017500000000000014475665104017345 5ustar00prosodyprosody00000000000000prosody-0.12.4/net/websocket/PaxHeaders/frames.lua0000644000000000000000000000012014475665104017120 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.644765423 prosody-0.12.4/net/websocket/frames.lua0000644000175000017500000000775614475665104021344 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2012 Florian Zeitz -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local random_bytes = require "util.random".bytes; local bit = require "util.bitcompat"; local band = bit.band; local bor = bit.bor; local sbit = require "util.strbitop"; local sxor = sbit.sxor; local s_char = string.char; local s_pack = require"util.struct".pack; local s_unpack = require"util.struct".unpack; local function pack_uint16be(x) return s_pack(">I2", x); end local function pack_uint64be(x) return s_pack(">I8", x); end local function read_uint16be(str, pos) if type(str) ~= "string" then str, pos = str:sub(pos, pos+1), 1; end return s_unpack(">I2", str, pos); end local function read_uint64be(str, pos) if type(str) ~= "string" then str, pos = str:sub(pos, pos+7), 1; end return s_unpack(">I8", str, pos); end local function parse_frame_header(frame) if frame:len() < 2 then return; end local byte1, byte2 = frame:byte(1, 2); local result = { FIN = band(byte1, 0x80) > 0; RSV1 = band(byte1, 0x40) > 0; RSV2 = band(byte1, 0x20) > 0; RSV3 = band(byte1, 0x10) > 0; opcode = band(byte1, 0x0F); MASK = band(byte2, 0x80) > 0; length = band(byte2, 0x7F); }; local length_bytes = 0; if result.length == 126 then length_bytes = 2; elseif result.length == 127 then length_bytes = 8; end local header_length = 2 + length_bytes + (result.MASK and 4 or 0); if frame:len() < header_length then return; end if length_bytes == 2 then result.length = read_uint16be(frame, 3); elseif length_bytes == 8 then result.length = read_uint64be(frame, 3); end if result.MASK then result.key = frame:sub(length_bytes+3, length_bytes+6); end return result, header_length; end -- XORs the string `str` with the array of bytes `key` -- TODO: optimize local function apply_mask(str, key, from, to) return sxor(str:sub(from or 1, to or -1), key); end local function parse_frame_body(frame, header, pos) if header.MASK then return apply_mask(frame, header.key, pos, pos + header.length - 1); else return frame:sub(pos, pos + header.length - 1); end end local function parse_frame(frame) local result, pos = parse_frame_header(frame); if result == nil or frame:len() < (pos + result.length) then return nil, nil, result; end result.data = parse_frame_body(frame, result, pos+1); return result, pos + result.length; end local function build_frame(desc) local data = desc.data or ""; assert(desc.opcode and desc.opcode >= 0 and desc.opcode <= 0xF, "Invalid WebSocket opcode"); if desc.opcode >= 0x8 then -- RFC 6455 5.5 assert(#data <= 125, "WebSocket control frames MUST have a payload length of 125 bytes or less."); end local b1 = bor(desc.opcode, desc.FIN and 0x80 or 0, desc.RSV1 and 0x40 or 0, desc.RSV2 and 0x20 or 0, desc.RSV3 and 0x10 or 0); local b2 = #data; local length_extra; if b2 <= 125 then -- 7-bit length length_extra = ""; elseif b2 <= 0xFFFF then -- 2-byte length b2 = 126; length_extra = pack_uint16be(#data); else -- 8-byte length b2 = 127; length_extra = pack_uint64be(#data); end local key = "" if desc.MASK then key = desc.key if not key then key = random_bytes(4); end b2 = bor(b2, 0x80); data = apply_mask(data, key); end return s_char(b1, b2) .. length_extra .. key .. data end local function parse_close(data) local code, message if #data >= 2 then code = read_uint16be(data, 1); if #data > 2 then message = data:sub(3); end end return code, message end local function build_close(code, message, mask) local data = pack_uint16be(code); if message then assert(#message<=123, "Close reason must be <=123 bytes"); data = data .. message; end return build_frame({ opcode = 0x8; FIN = true; MASK = mask; data = data; }); end return { parse_header = parse_frame_header; parse_body = parse_frame_body; parse = parse_frame; build = build_frame; parse_close = parse_close; build_close = build_close; }; prosody-0.12.4/net/PaxHeaders/websocket.lua0000644000000000000000000000012014475665104015643 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.644765423 prosody-0.12.4/net/websocket.lua0000644000175000017500000002053014475665104020050 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2012 Florian Zeitz -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local t_concat = table.concat; local http = require "net.http"; local frames = require "net.websocket.frames"; local base64 = require "util.encodings".base64; local sha1 = require "util.hashes".sha1; local random_bytes = require "util.random".bytes; local timer = require "util.timer"; local log = require "util.logger".init "websocket"; local close_timeout = 3; -- Seconds to wait after sending close frame until closing connection. local websockets = {}; local websocket_listeners = {}; function websocket_listeners.ondisconnect(conn, err) local s = websockets[conn]; if not s then return; end websockets[conn] = nil; if s.close_timer then timer.stop(s.close_timer); s.close_timer = nil; end s.readyState = 3; if s.close_code == nil and s.onerror then s:onerror(err); end if s.onclose then s:onclose(s.close_code, s.close_message or err); end end function websocket_listeners.ondetach(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.conn:close(); return false end 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); if frame == nil then break end s.readbuffer = s.readbuffer:sub(len+1); log("debug", "Websocket received frame: opcode=%0x, %i bytes", frame.opcode, #frame.data); -- Error cases if frame.RSV1 or frame.RSV2 or frame.RSV3 then -- Reserved bits non zero return fail(s, 1002, "Reserved bits not zero"); end if frame.opcode < 0x8 then local databuffer = s.databuffer; if frame.opcode == 0x0 then -- Continuation frames if not databuffer then return fail(s, 1002, "Unexpected continuation frame"); end databuffer[#databuffer+1] = frame.data; elseif frame.opcode == 0x1 or frame.opcode == 0x2 then -- Text or Binary frame if databuffer then return fail(s, 1002, "Continuation frame expected"); end databuffer = {type=frame.opcode, frame.data}; s.databuffer = databuffer; else return fail(s, 1002, "Reserved opcode"); end if frame.FIN then s.databuffer = nil; if s.onmessage then s:onmessage(t_concat(databuffer), databuffer.type); end end else -- Control frame if frame.length > 125 then -- Control frame with too much payload return fail(s, 1002, "Payload too large"); elseif not frame.FIN then -- Fragmented control frame return fail(s, 1002, "Fragmented control frame"); end if frame.opcode == 0x8 then -- Close request if frame.length == 1 then return fail(s, 1002, "Close frame with payload, but too short for status code"); end local status_code, message = frames.parse_close(frame.data); if status_code == nil then --[[ RFC 6455 7.4.1 1005 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting a status code to indicate that no status code was actually present. ]] status_code = 1005 elseif status_code < 1000 then return fail(s, 1002, "Closed with invalid status code"); elseif ((status_code > 1003 and status_code < 1007) or status_code > 1011) and status_code < 3000 then return fail(s, 1002, "Closed with reserved status code"); end s.close_code, s.close_message = status_code, message; s:close(1000); return true; elseif frame.opcode == 0x9 then -- Ping frame frame.opcode = 0xA; frame.MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked conn:write(frames.build(frame)); elseif frame.opcode == 0xA then -- Pong frame log("debug", "Received unexpected pong frame: %s", frame.data); else return fail(s, 1002, "Reserved opcode"); end end end return true; end local websocket_methods = {}; local function close_timeout_cb(now, timerid, s) -- luacheck: ignore 212/now 212/timerid s.close_timer = nil; log("warn", "Close timeout waiting for server to close, closing manually."); s.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 , reason); self.readyState = 2; 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 log("debug", "tried to close a closing WebSocket, closing the raw socket."); -- Stop timer if self.close_timer then timer.stop(self.close_timer); self.close_timer = nil; end local conn = self.conn; conn:close(); else log("debug", "tried to close a closed WebSocket, ignoring."); end end function websocket_methods:send(data, opcode) if self.readyState < 1 then return nil, "WebSocket not open yet, unable to send data."; elseif self.readyState >= 2 then return nil, "WebSocket closed, unable to send data."; end if opcode == "text" or opcode == nil then opcode = 0x1; elseif opcode == "binary" then opcode = 0x2; end local frame = { FIN = true; MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked opcode = opcode; data = tostring(data); }; log("debug", "WebSocket sending frame: opcode=%0x, %i bytes", frame.opcode, #frame.data); return self.conn:write(frames.build(frame)); end local websocket_metatable = { __index = websocket_methods; }; local function connect(url, ex, listeners) ex = ex or {}; --[[RFC 6455 4.1.7: The request MUST include a header field with the name |Sec-WebSocket-Key|. The value of this header field MUST be a nonce consisting of a randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection. ]] local key = base64.encode(random_bytes(16)); -- Either a single protocol string or an array of protocol strings. local protocol = ex.protocol; if type(protocol) == "string" then protocol = { protocol, [protocol] = true }; elseif type(protocol) == "table" and protocol[1] then for _, v in ipairs(protocol) do protocol[v] = true; end else protocol = nil; end local headers = { ["Upgrade"] = "websocket"; ["Connection"] = "Upgrade"; ["Sec-WebSocket-Key"] = key; ["Sec-WebSocket-Protocol"] = protocol and t_concat(protocol, ", "); ["Sec-WebSocket-Version"] = "13"; ["Sec-WebSocket-Extensions"] = ex.extensions; } if ex.headers then for k,v in pairs(ex.headers) do headers[k] = v; end end local s = setmetatable({ readbuffer = ""; databuffer = nil; conn = nil; close_code = nil; close_message = nil; close_timer = nil; readyState = 0; protocol = nil; url = url; onopen = listeners.onopen; onclose = listeners.onclose; onmessage = listeners.onmessage; onerror = listeners.onerror; }, websocket_metatable); local http_url = url:gsub("^(ws)", "http"); local http_req = http.request(http_url, { -- luacheck: ignore 211/http_req method = "GET"; headers = headers; sslctx = ex.sslctx; insecure = ex.insecure; }, function(b, c, r, http_req) if c ~= 101 or r.headers["connection"]:lower() ~= "upgrade" or r.headers["upgrade"] ~= "websocket" or r.headers["sec-websocket-accept"] ~= base64.encode(sha1(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")) or (protocol and not protocol[r.headers["sec-websocket-protocol"]]) then s.readyState = 3; log("warn", "WebSocket connection to %s failed: %s", url, b); if s.onerror then s:onerror("connecting-failed"); end return; end s.protocol = r.headers["sec-websocket-protocol"]; -- Take possession of socket from http local conn = http_req.conn; http_req.conn = nil; 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(conn, b); end); return s; end return { connect = connect; }; prosody-0.12.4/PaxHeaders/plugins0000644000000000000000000000013114475665104013772 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.644765423 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/0000755000175000017500000000000014475665104016252 5ustar00prosodyprosody00000000000000prosody-0.12.4/plugins/PaxHeaders/adhoc0000644000000000000000000000013214475665104015051 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.644765423 30 ctime=1693936196.644765423 prosody-0.12.4/plugins/adhoc/0000755000175000017500000000000014475665104017330 5ustar00prosodyprosody00000000000000prosody-0.12.4/plugins/adhoc/PaxHeaders/adhoc.lib.lua0000644000000000000000000000012014475665104017451 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.644765423 prosody-0.12.4/plugins/adhoc/adhoc.lib.lua0000644000175000017500000000567514475665104021673 0ustar00prosodyprosody00000000000000-- Copyright (C) 2009-2010 Florian Zeitz -- -- This file is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st, uuid = require "util.stanza", require "util.uuid"; local xmlns_cmd = "http://jabber.org/protocol/commands"; local states = {} local _M = {}; local function _cmdtag(desc, status, sessionid, action) local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status }); if sessionid then cmd.attr.sessionid = sessionid; end if action then cmd.attr.action = action; end return cmd; end function _M.new(name, node, handler, permission) if not permission then error "adhoc.new() expects a permission argument, none given" end if permission == "user" then error "the permission mode 'user' has been renamed 'any', please update your code" end return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = permission }; end function _M.handle_cmd(command, origin, stanza) local cmdtag = stanza.tags[1] local sessionid = cmdtag.attr.sessionid or uuid.generate(); local dataIn = { to = stanza.attr.to; from = stanza.attr.from; action = cmdtag.attr.action or "execute"; form = cmdtag:get_child("x", "jabber:x:data"); }; local data, state = command:handler(dataIn, states[sessionid]); states[sessionid] = state; local cmdreply; if data.status == "completed" then states[sessionid] = nil; cmdreply = command:cmdtag("completed", sessionid); elseif data.status == "canceled" then states[sessionid] = nil; cmdreply = command:cmdtag("canceled", sessionid); elseif data.status == "error" then states[sessionid] = nil; local reply = st.error_reply(stanza, data.error); origin.send(reply); return true; else cmdreply = command:cmdtag("executing", sessionid); data.actions = data.actions or { "complete" }; end for name, content in pairs(data) do if name == "info" then cmdreply:tag("note", {type="info"}):text(content):up(); elseif name == "warn" then cmdreply:tag("note", {type="warn"}):text(content):up(); elseif name == "error" then 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 if (action == "prev") or (action == "next") or (action == "complete") then actions:tag(action):up(); else module:log("error", "Command %q at node %q provided an invalid action %q", command.name, command.node, action); end end cmdreply:add_child(actions); elseif name == "form" then cmdreply:add_child((content.layout or content):form(content.values)); elseif name == "result" then cmdreply:add_child((content.layout or content):form(content.values, "result")); elseif name == "other" then cmdreply:add_child(content); end end local reply = st.reply(stanza); reply:add_child(cmdreply); origin.send(reply); return true; end return _M; prosody-0.12.4/plugins/adhoc/PaxHeaders/mod_adhoc.lua0000644000000000000000000000012014475665104017543 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.644765423 prosody-0.12.4/plugins/adhoc/mod_adhoc.lua0000644000175000017500000000750314475665104021755 0ustar00prosodyprosody00000000000000-- Copyright (C) 2009 Thilo Cestonaro -- Copyright (C) 2009-2011 Florian Zeitz -- -- This file is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local it = require "util.iterators"; local st = require "util.stanza"; local is_admin = require "core.usermanager".is_admin; local jid_host = require "util.jid".host; local adhoc_handle_cmd = module:require "adhoc".handle_cmd; local xmlns_cmd = "http://jabber.org/protocol/commands"; local commands = {}; module:add_feature(xmlns_cmd); module:hook("host-disco-info-node", function (event) local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node; if commands[node] then local from = stanza.attr.from; local privileged = is_admin(from, stanza.attr.to); local global_admin = is_admin(from); local hostname = jid_host(from); local command = commands[node]; if (command.permission == "admin" and privileged) or (command.permission == "global_admin" and global_admin) or (command.permission == "local_user" and hostname == module.host) or (command.permission == "any") then reply:tag("identity", { name = command.name, category = "automation", type = "command-node" }):up(); reply:tag("feature", { var = xmlns_cmd }):up(); reply:tag("feature", { var = "jabber:x:data" }):up(); event.exists = true; else origin.send(st.error_reply(stanza, "auth", "forbidden", "This item is not available to you")); return true; end elseif node == xmlns_cmd then reply:tag("identity", { name = "Ad-Hoc Commands", category = "automation", type = "command-list" }):up(); event.exists = true; end end); module:hook("host-disco-items-node", function (event) local stanza, reply, disco_node = event.stanza, event.reply, event.node; if disco_node ~= xmlns_cmd then return; end local from = stanza.attr.from; local admin = is_admin(from, stanza.attr.to); local global_admin = is_admin(from); local hostname = jid_host(from); 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) or (command.permission == "any") then reply:tag("item", { name = command.name, node = node, jid = module:get_host() }); reply:up(); end end event.exists = true; end); module:hook("iq-set/host/"..xmlns_cmd..":command", function (event) local origin, stanza = event.origin, event.stanza; 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 hostname = jid_host(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); local function adhoc_added(event) local item = event.item; -- Dang this was noisy module:log("debug", "Command added by mod_%s: %q, %q", item._provided_by or "", item.name, item.node); commands[item.node] = item; end local function adhoc_removed(event) commands[event.item.node] = nil; end 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); prosody-0.12.4/plugins/PaxHeaders/mod_admin_adhoc.lua0000644000000000000000000000012014475665104017635 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.644765423 prosody-0.12.4/plugins/mod_admin_adhoc.lua0000644000175000017500000010176514475665104022054 0ustar00prosodyprosody00000000000000-- Copyright (C) 2009-2011 Florian Zeitz -- -- 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; local prosody = _G.prosody; local hosts = prosody.hosts; local t_concat = table.concat; local t_sort = table.sort; local module_host = module:get_host(); local keys = require "util.iterators".keys; local usermanager_user_exists = require "core.usermanager".user_exists; local usermanager_create_user = require "core.usermanager".create_user; local usermanager_delete_user = require "core.usermanager".delete_user; local usermanager_set_password = require "core.usermanager".set_password; local hostmanager_activate = require "core.hostmanager".activate; local hostmanager_deactivate = require "core.hostmanager".deactivate; local rm_load_roster = require "core.rostermanager".load_roster; local st, jid = require "util.stanza", require "util.jid"; local timer_add_task = require "util.timer".add_task; local dataforms_new = require "util.dataforms".new; local array = require "util.array"; local modulemanager = require "core.modulemanager"; local core_post_stanza = prosody.core_post_stanza; local adhoc_simple = require "util.adhoc".new_simple_form; local adhoc_initial = require "util.adhoc".new_initial_data_form; local set = require"util.set"; module:depends("adhoc"); local adhoc_new = module:require "adhoc".new; local function generate_error_message(errors) local errmsg = {}; for name, err in pairs(errors) do errmsg[#errmsg + 1] = name .. ": " .. err; end return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end -- Adding a new user local add_user_layout = dataforms_new{ title = "Adding a User"; instructions = "Fill out this form to add a user."; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for the account to be added" }; { name = "password", type = "text-private", label = "The password for this account" }; { name = "password-verify", type = "text-private", label = "Retype password" }; }; local add_user_command_handler = adhoc_simple(add_user_layout, function(fields, err, data) if err then return generate_error_message(err); end local username, host = jid.split(fields.accountjid); if module_host ~= host then return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. module_host}}; end if (fields["password"] == fields["password-verify"]) and username and host then if usermanager_user_exists(username, host) then return { status = "completed", error = { message = "Account already exists" } }; else if usermanager_create_user(username, fields.password, host) then module:log("info", "Created new account %s@%s by %s", username, host, jid.bare(data.from)); return { status = "completed", info = "Account successfully created" }; else return { status = "completed", error = { message = "Failed to write data to disk" } }; end end else module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or ""); return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } }; end end); -- Changing a user's password local change_user_password_layout = dataforms_new{ title = "Changing a User Password"; instructions = "Fill out this form to change a user's password."; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" }; { name = "password", type = "text-private", required = true, label = "The password for this account" }; }; local change_user_password_command_handler = adhoc_simple(change_user_password_layout, function(fields, err, data) if err then return generate_error_message(err); end local username, host = 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 } }; end if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host, nil) then module:log("info", "Password of account %s@%s changed by %s", username, host, jid.bare(data.from)); return { status = "completed", info = "Password successfully changed" }; else return { status = "completed", error = { message = "User does not exist" } }; end end); -- Reloading the config local function config_reload_handler(self, data, state) module:log("info", "%s reloads the config", jid.bare(data.from)); local ok, err = prosody.reload_config(); if ok then return { status = "completed", info = "Configuration reloaded (modules may need to be reloaded for this to have an effect)" }; else return { status = "completed", error = { message = "Failed to reload config: " .. tostring(err) } }; end end -- Deleting a user's account local delete_user_layout = dataforms_new{ title = "Deleting a User"; instructions = "Fill out this form to delete a user."; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "accountjids", type = "jid-multi", required = true, label = "The Jabber ID(s) to delete" }; }; local delete_user_command_handler = adhoc_simple(delete_user_layout, function(fields, err, data) if err then return generate_error_message(err); end local failed = {}; local succeeded = {}; for _, aJID in ipairs(fields.accountjids) do local username, host = jid.split(aJID); if (host == module_host) and usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then module:log("info", "User %s has been deleted by %s", aJID, jid.bare(data.from)); succeeded[#succeeded+1] = aJID; else module:log("debug", "Tried to delete non-existent user %s", aJID); failed[#failed+1] = aJID; end end return {status = "completed", info = (#succeeded ~= 0 and "The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "").. (#failed ~= 0 and "The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") }; end); -- Ending a user's session local function disconnect_user(match_jid) local node, hostname, givenResource = jid.split(match_jid); local host = hosts[hostname]; local sessions = host.sessions[node] and host.sessions[node].sessions; for resource, session in pairs(sessions or {}) do if not givenResource or (resource == givenResource) then module:log("debug", "Disconnecting %s@%s/%s", node, hostname, resource); session:close(); end end return true; end local end_user_session_layout = dataforms_new{ title = "Ending a User Session"; instructions = "Fill out this form to end a user's session."; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions", required = true }; }; local end_user_session_handler = adhoc_simple(end_user_session_layout, function(fields, err) if err then return generate_error_message(err); end local failed = {}; local succeeded = {}; for _, aJID in ipairs(fields.accountjids) do local username, host = jid.split(aJID); if (host == module_host) and usermanager_user_exists(username, host) and disconnect_user(aJID) then succeeded[#succeeded+1] = aJID; else failed[#failed+1] = aJID; end end return {status = "completed", info = (#succeeded ~= 0 and "The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "").. (#failed ~= 0 and "The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") }; end); -- Getting a user's roster local get_user_roster_layout = dataforms_new{ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" }; }; local get_user_roster_result_layout = dataforms_new{ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "accountjid", type = "jid-single", label = "This is the roster for" }; { name = "roster", type = "text-multi", label = "Roster XML" }; }; local get_user_roster_handler = adhoc_simple(get_user_roster_layout, function(fields, err) if err then return generate_error_message(err); end local user, host = jid.split(fields.accountjid); if host ~= module_host then return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. module_host } }; elseif not usermanager_user_exists(user, host) then return { status = "completed", error = { message = "User does not exist" } }; end local roster = rm_load_roster(user, host); local query = st.stanza("query", { xmlns = "jabber:iq:roster" }); for contact_jid in pairs(roster) do if contact_jid then query:tag("item", { jid = contact_jid, subscription = roster[contact_jid].subscription, ask = roster[contact_jid].ask, name = roster[contact_jid].name, }); for group in pairs(roster[contact_jid].groups) do query:tag("group"):text(group):up(); end query:up(); end end local query_text = tostring(query):gsub("><", ">\n<"); local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result"); result:add_child(query); return { status = "completed", other = result }; end); -- Getting user statistics local get_user_stats_layout = dataforms_new{ title = "Get User Statistics"; instructions = "Fill out this form to gather user statistics."; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" }; }; local get_user_stats_result_layout = dataforms_new{ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "ipaddresses", type = "text-multi", label = "IP Addresses" }; { name = "rostersize", type = "text-single", label = "Roster size", datatype = "xs:integer" }; { name = "onlineresources", type = "text-multi", label = "Online Resources" }; }; local get_user_stats_handler = adhoc_simple(get_user_stats_layout, function(fields, err) if err then return generate_error_message(err); end 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 return { status = "completed", error = { message = "User does not exist" } }; end local roster = rm_load_roster(user, host); local rostersize = 0; local IPs = ""; local resources = ""; for contact_jid in pairs(roster) do if contact_jid then rostersize = rostersize + 1; end end for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do resources = resources .. "\n" .. resource; IPs = IPs .. "\n" .. session.ip; end return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = rostersize, onlineresources = resources}} }; end); -- Getting a list of online users local get_online_users_layout = dataforms_new{ title = "Getting List of Online Users"; instructions = "How many users should be returned at most?"; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "max_items", type = "list-single", label = "Maximum number of users", options = { "25", "50", "75", "100", "150", "200", "all" } }; { name = "details", type = "boolean", label = "Show details" }; }; local get_online_users_result_layout = dataforms_new{ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "onlineuserjids", type = "text-multi", label = "The list of all online users" }; }; local get_online_users_command_handler = adhoc_simple(get_online_users_layout, function(fields, err) if err then return generate_error_message(err); end local max_items = nil if fields.max_items ~= "all" then max_items = tonumber(fields.max_items); end local count = 0; local users = {}; for username, user in pairs(hosts[module_host].sessions or {}) do if (max_items ~= nil) and (count >= max_items) then break; end users[#users+1] = username.."@"..module_host; count = count + 1; if fields.details then for resource, session in pairs(user.sessions or {}) do local status, priority, ip = "unavailable", tostring(session.priority or "-"), session.ip or ""; if session.presence then status = session.presence:child_with_name("show"); if status then status = status:get_text() or "[invalid!]"; else status = "available"; end end users[#users+1] = " - "..resource..": "..status.."("..priority.."), IP: ["..ip.."]"; end end end return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} }; end); -- Getting a list of S2S connections (this host) local list_s2s_this_result = dataforms_new { title = "List of S2S connections on this host"; { name = "FORM_TYPE"; type = "hidden"; value = "http://prosody.im/protocol/s2s#list" }; { name = "sessions"; type = "text-multi"; label = "Connections:" }; { name = "num_in"; type = "text-single"; label = "#incoming connections:"; datatype = "xs:integer" }; { name = "num_out"; type = "text-single"; label = "#outgoing connections:"; datatype = "xs:integer" }; }; local function session_flags(session, line) line = line or {}; if session.id then line[#line+1] = "["..session.id.."]" else line[#line+1] = "["..session.type..(tostring(session):match("%x*$")).."]" end local flags = {}; if session.cert_identity_status == "valid" then flags[#flags+1] = "authenticated"; end if session.dialback_key then flags[#flags+1] = "dialback"; end if session.external_auth then flags[#flags+1] = "SASL"; end if session.secure then flags[#flags+1] = "encrypted"; end if session.compressed then flags[#flags+1] = "compressed"; end if session.smacks then flags[#flags+1] = "sm"; end if session.ip and session.ip:match(":") then flags[#flags+1] = "IPv6"; end if session.incoming and session.outgoing then flags[#flags+1] = "bidi"; elseif session.is_bidi or session.bidi_session then flags[#flags+1] = "bidi"; end line[#line+1] = "("..t_concat(flags, ", ")..")"; return t_concat(line, " "); end local function list_s2s_this_handler(self, data, state) local count_in, count_out = 0, 0; local s2s_list = {}; local s2s_sessions = module:shared"/*/s2s/sessions"; for _, session in pairs(s2s_sessions) do local remotehost, localhost, direction; if session.direction == "outgoing" then direction = "->"; count_out = count_out + 1; remotehost, localhost = session.to_host or "?", session.from_host or "?"; else direction = "<-"; count_in = count_in + 1; remotehost, localhost = session.from_host or "?", session.to_host or "?"; end local sess_lines = { r = remotehost, session_flags(session, { "", direction, remotehost or "?" })}; if localhost == module_host then s2s_list[#s2s_list+1] = sess_lines; end end t_sort(s2s_list, function(a, b) return a.r < b.r; end); for i, sess_lines in ipairs(s2s_list) do s2s_list[i] = sess_lines[1]; end return { status = "completed", result = { layout = list_s2s_this_result; values = { sessions = t_concat(s2s_list, "\n"), num_in = count_in, num_out = count_out } } }; end -- Getting a list of loaded modules local list_modules_result = dataforms_new { title = "List of loaded modules"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" }; { name = "modules", type = "text-multi", label = "The following modules are loaded:" }; }; local function list_modules_handler(self, data, state) local modules = array.collect(keys(hosts[module_host].modules)):sort():concat("\n"); return { status = "completed", result = { layout = list_modules_result; values = { modules = modules } } }; end -- Loading a module local load_module_layout = dataforms_new { title = "Load module"; instructions = "Specify the module to be loaded"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" }; { name = "module", type = "text-single", required = true, label = "Module to be loaded:"}; }; local load_module_handler = adhoc_simple(load_module_layout, function(fields, err) if err then return generate_error_message(err); end if modulemanager.is_loaded(module_host, fields.module) then return { status = "completed", info = "Module already loaded" }; end local ok, err = modulemanager.load(module_host, fields.module); if ok then return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..module_host..'".' }; else return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..module_host.. '". Error was: "'..tostring(err or "")..'"' } }; end end); -- Globally loading a module local globally_load_module_layout = dataforms_new { title = "Globally load module"; instructions = "Specify the module to be loaded on all hosts"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-load" }; { name = "module", type = "text-single", required = true, label = "Module to globally load:"}; }; local globally_load_module_handler = adhoc_simple(globally_load_module_layout, function(fields, err, data) local ok_list, err_list = {}, {}; if err then return generate_error_message(err); end local ok, err = modulemanager.load(module_host, fields.module); if ok then ok_list[#ok_list + 1] = module_host; else err_list[#err_list + 1] = module_host .. " (Error: " .. tostring(err) .. ")"; end -- Is this a global module? if modulemanager.is_loaded("*", fields.module) and not modulemanager.is_loaded(module_host, fields.module) then module:log("info", "mod_%s loaded by %s", fields.module, jid.bare(data.from)); return { status = "completed", info = 'Global module '..fields.module..' loaded.' }; end -- This is either a shared or "normal" module, load it on all other hosts for host_name, host in pairs(hosts) do if host_name ~= module_host and host.type == "local" then local ok, err = modulemanager.load(host_name, fields.module); if ok then ok_list[#ok_list + 1] = host_name; else err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; end end end module:log("info", "mod_%s loaded by %s", fields.module, jid.bare(data.from)); local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully loaded onto the hosts:\n"..t_concat(ok_list, "\n")) or "") .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. (#err_list > 0 and ("Failed to load the module "..fields.module.." onto the hosts:\n"..t_concat(err_list, "\n")) or ""); return { status = "completed", info = info }; end); -- Reloading modules local reload_modules_layout = dataforms_new { title = "Reload modules"; instructions = "Select the modules to be reloaded"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" }; { name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"}; }; local reload_modules_handler = adhoc_initial(reload_modules_layout, function() return { modules = array.collect(keys(hosts[module_host].modules)):sort() }; end, function(fields, err, data) if err then return generate_error_message(err); end local ok_list, err_list = {}, {}; for _, module in ipairs(fields.modules) do local ok, err = modulemanager.reload(module_host, module); if ok then ok_list[#ok_list + 1] = module; else err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")"; end end module:log("info", "mod_%s reloaded by %s", fields.module, jid.bare(data.from)); local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "") .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. (#err_list > 0 and ("Failed to reload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or ""); return { status = "completed", info = info }; end); -- Globally reloading a module local globally_reload_module_layout = dataforms_new { title = "Globally reload module"; instructions = "Specify the module to reload on all hosts"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-reload" }; { name = "module", type = "list-single", required = true, label = "Module to globally reload:"}; }; local globally_reload_module_handler = adhoc_initial(globally_reload_module_layout, function() local loaded_modules = array(keys(modulemanager.get_modules("*"))); for _, host in pairs(hosts) do loaded_modules:append(array(keys(host.modules))); end loaded_modules = array(set.new(loaded_modules):items()):sort(); return { module = loaded_modules }; end, function(fields, err, data) local is_global = false; if err then return generate_error_message(err); end if modulemanager.is_loaded("*", fields.module) then local ok, err = modulemanager.reload("*", fields.module); if not ok then return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err }; end is_global = true; end local ok_list, err_list = {}, {}; 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 ok_list[#ok_list + 1] = host_name; else err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; end end end if #ok_list == 0 and #err_list == 0 then if is_global then return { status = "completed", info = 'Successfully reloaded global module '..fields.module }; else return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' }; end end module:log("info", "mod_%s reloaded by %s", fields.module, jid.bare(data.from)); local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully reloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "") .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. (#err_list > 0 and ("Failed to reload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or ""); return { status = "completed", info = info }; end); local function send_to_online(message, server) local sessions; if server then sessions = { [server] = hosts[server] }; else sessions = hosts; end local c = 0; for domain, session in pairs(sessions) do for user in pairs(session.sessions or {}) do c = c + 1; message.attr.from = domain; message.attr.to = user.."@"..domain; core_post_stanza(session, message); end end return c; end -- Shutting down the service local shut_down_service_layout = dataforms_new{ title = "Shutting Down the Service"; instructions = "Fill out this form to shut down the service."; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "delay", type = "list-single", label = "Time delay before shutting down", 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" }; }; local shut_down_service_handler = adhoc_simple(shut_down_service_layout, function(fields, err, data) if err then return generate_error_message(err); end module:log("info", "Server being shut down by %s", jid.bare(data.from)); if fields.announcement and #fields.announcement > 0 then local message = st.message({type = "headline"}, fields.announcement):up() :tag("subject"):text("Server is shutting down"); send_to_online(message); 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); -- Unloading modules local unload_modules_layout = dataforms_new { title = "Unload modules"; instructions = "Select the modules to be unloaded"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" }; { name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"}; }; local unload_modules_handler = adhoc_initial(unload_modules_layout, function() return { modules = array.collect(keys(hosts[module_host].modules)):sort() }; end, function(fields, err, data) if err then return generate_error_message(err); end local ok_list, err_list = {}, {}; for _, module in ipairs(fields.modules) do local ok, err = modulemanager.unload(module_host, module); if ok then ok_list[#ok_list + 1] = module; else err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")"; end end module:log("info", "mod_%s unloaded by %s", fields.module, jid.bare(data.from)); local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "") .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. (#err_list > 0 and ("Failed to unload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or ""); return { status = "completed", info = info }; end); -- Globally unloading a module local globally_unload_module_layout = dataforms_new { title = "Globally unload module"; instructions = "Specify a module to unload on all hosts"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-unload" }; { name = "module", type = "list-single", required = true, label = "Module to globally unload:"}; }; local globally_unload_module_handler = adhoc_initial(globally_unload_module_layout, function() local loaded_modules = array(keys(modulemanager.get_modules("*"))); for _, host in pairs(hosts) do loaded_modules:append(array(keys(host.modules))); end loaded_modules = array(set.new(loaded_modules):items()):sort(); return { module = loaded_modules }; end, function(fields, err, data) local is_global = false; if err then return generate_error_message(err); end if modulemanager.is_loaded("*", fields.module) then local ok, err = modulemanager.unload("*", fields.module); if not ok then return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err }; end is_global = true; end local ok_list, err_list = {}, {}; 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 ok_list[#ok_list + 1] = host_name; else err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; end end end if #ok_list == 0 and #err_list == 0 then if is_global then return { status = "completed", info = 'Successfully unloaded global module '..fields.module }; else return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' }; end end module:log("info", "mod_%s globally unloaded by %s", fields.module, jid.bare(data.from)); local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully unloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "") .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. (#err_list > 0 and ("Failed to unload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or ""); return { status = "completed", info = info }; end); -- Activating a host local activate_host_layout = dataforms_new { title = "Activate host"; instructions = ""; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" }; { name = "host", type = "text-single", required = true, label = "Host:"}; }; local activate_host_handler = adhoc_simple(activate_host_layout, function(fields, err, data) if err then return generate_error_message(err); end local ok, err = hostmanager_activate(fields.host); if ok then module:log("info", "Host '%s' activated by %s", fields.host, jid.bare(data.from)); return { status = "completed", info = fields.host .. " activated" }; else return { status = "canceled", error = err } end end); -- Deactivating a host local deactivate_host_layout = dataforms_new { title = "Deactivate host"; instructions = ""; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" }; { name = "host", type = "text-single", required = true, label = "Host:"}; }; local deactivate_host_handler = adhoc_simple(deactivate_host_layout, function(fields, err, data) if err then return generate_error_message(err); end local ok, err = hostmanager_deactivate(fields.host); if ok then module:log("info", "Host '%s' deactivated by %s", fields.host, jid.bare(data.from)); return { status = "completed", info = fields.host .. " deactivated" }; else return { status = "canceled", error = err } 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"); local config_reload_desc = adhoc_new("Reload configuration", "http://prosody.im/protocol/config#reload", config_reload_handler, "global_admin"); local delete_user_desc = adhoc_new("Delete User", "http://jabber.org/protocol/admin#delete-user", delete_user_command_handler, "admin"); local end_user_session_desc = adhoc_new("End User Session", "http://jabber.org/protocol/admin#end-user-session", end_user_session_handler, "admin"); local get_user_roster_desc = adhoc_new("Get User Roster","http://jabber.org/protocol/admin#get-user-roster", get_user_roster_handler, "admin"); local get_user_stats_desc = adhoc_new("Get User Statistics","http://jabber.org/protocol/admin#user-stats", get_user_stats_handler, "admin"); local get_online_users_desc = adhoc_new("Get List of Online Users", "http://jabber.org/protocol/admin#get-online-users-list", get_online_users_command_handler, "admin"); local list_s2s_this_desc = adhoc_new("List S2S connections", "http://prosody.im/protocol/s2s#list", list_s2s_this_handler, "admin"); local list_modules_desc = adhoc_new("List loaded modules", "http://prosody.im/protocol/modules#list", list_modules_handler, "admin"); local load_module_desc = adhoc_new("Load module", "http://prosody.im/protocol/modules#load", load_module_handler, "admin"); local globally_load_module_desc = adhoc_new("Globally load module", "http://prosody.im/protocol/modules#global-load", globally_load_module_handler, "global_admin"); local reload_modules_desc = adhoc_new("Reload modules", "http://prosody.im/protocol/modules#reload", reload_modules_handler, "admin"); local globally_reload_module_desc = adhoc_new("Globally reload module", "http://prosody.im/protocol/modules#global-reload", globally_reload_module_handler, "global_admin"); local shut_down_service_desc = adhoc_new("Shut Down Service", "http://jabber.org/protocol/admin#shutdown", shut_down_service_handler, "global_admin"); local unload_modules_desc = adhoc_new("Unload modules", "http://prosody.im/protocol/modules#unload", unload_modules_handler, "admin"); local globally_unload_module_desc = adhoc_new("Globally unload module", "http://prosody.im/protocol/modules#global-unload", globally_unload_module_handler, "global_admin"); local activate_host_desc = adhoc_new("Activate host", "http://prosody.im/protocol/hosts#activate", activate_host_handler, "global_admin"); local deactivate_host_desc = adhoc_new("Deactivate host", "http://prosody.im/protocol/hosts#deactivate", deactivate_host_handler, "global_admin"); module:provides("adhoc", add_user_desc); module:provides("adhoc", change_user_password_desc); module:provides("adhoc", config_reload_desc); module:provides("adhoc", delete_user_desc); module:provides("adhoc", end_user_session_desc); module:provides("adhoc", get_user_roster_desc); module:provides("adhoc", get_user_stats_desc); module:provides("adhoc", get_online_users_desc); module:provides("adhoc", list_s2s_this_desc); module:provides("adhoc", list_modules_desc); module:provides("adhoc", load_module_desc); module:provides("adhoc", globally_load_module_desc); module:provides("adhoc", reload_modules_desc); module:provides("adhoc", globally_reload_module_desc); module:provides("adhoc", shut_down_service_desc); module:provides("adhoc", unload_modules_desc); module:provides("adhoc", globally_unload_module_desc); module:provides("adhoc", activate_host_desc); module:provides("adhoc", deactivate_host_desc); prosody-0.12.4/plugins/PaxHeaders/mod_admin_shell.lua0000644000000000000000000000012014475665104017666 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_admin_shell.lua0000644000175000017500000017344314475665104022107 0ustar00prosodyprosody00000000000000-- 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/self module:set_global(); module:depends("admin_socket"); local hostmanager = require "core.hostmanager"; local modulemanager = require "core.modulemanager"; local s2smanager = require "core.s2smanager"; local portmanager = require "core.portmanager"; local helpers = require "util.helpers"; local server = require "net.server"; local st = require "util.stanza"; local _G = _G; local prosody = _G.prosody; local unpack = table.unpack or unpack; -- luacheck: ignore 113 local iterators = require "util.iterators"; local keys, values = iterators.keys, iterators.values; local jid_bare, jid_split, jid_join = import("util.jid", "bare", "prepped_split", "join"); local set, array = require "util.set", require "util.array"; local cert_verify_identity = require "util.x509".verify_identity; local envload = require "util.envload".envload; local envloadfile = require "util.envload".envloadfile; local has_pposix, pposix = pcall(require, "util.pposix"); local async = require "util.async"; local serialization = require "util.serialization"; local serialize_config = serialization.new ({ fatal = false, unquoted = true}); local time = require "util.time"; local promise = require "util.promise"; local t_insert = table.insert; local t_concat = table.concat; local format_number = require "util.human.units".format; local format_table = require "util.human.io".table; local function capitalize(s) if not s then return end return (s:gsub("^%a", string.upper):gsub("_", " ")); end local function pre(prefix, str, alt) if type(str) ~= "string" or str == "" then return alt or ""; end return prefix .. str; end local function suf(str, suffix, alt) if type(str) ~= "string" or str == "" then return alt or ""; end return str .. suffix; end local commands = module:shared("commands") local def_env = module:shared("env"); local default_env_mt = { __index = def_env }; 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 return f(); end; return env; end console = {}; local runner_callbacks = {}; function runner_callbacks:error(err) module:log("error", "Traceback[shell]: %s", err); self.data.print("Fatal error while running command, it did not complete"); self.data.print("Error: "..tostring(err)); end local function send_repl_output(session, line) return session.send(st.stanza("repl-output"):text(tostring(line))); end function console:new_session(admin_session) local session = { send = function (t) return send_repl_output(admin_session, t); end; print = function (...) local t = {}; for i=1,select("#", ...) do t[i] = tostring(select(i, ...)); end return send_repl_output(admin_session, table.concat(t, "\t")); end; serialize = tostring; disconnect = function () admin_session:close(); end; }; session.env = setmetatable({}, default_env_mt); session.thread = async.runner(function (line) console:process_line(session, line); end, runner_callbacks, session); -- Load up environment with helper objects for name, t in pairs(def_env) do if type(t) == "table" then session.env[name] = setmetatable({ session = session }, { __index = t }); end end session.env.output:configure(); return session; end local function handle_line(event) local session = event.origin.shell_session; if not session then session = console:new_session(event.origin); event.origin.shell_session = session; end local line = event.stanza:get_text(); local useglobalenv; local result = st.stanza("repl-result"); if line:match("^>") then line = line:gsub("^>", ""); useglobalenv = true; else local command = line:match("^%w+") or line:match("%p"); if commands[command] then commands[command](session, line); event.origin.send(result); return; end end session.env._ = line; if not useglobalenv and commands[line:lower()] then commands[line:lower()](session, line); event.origin.send(result); return; end if useglobalenv and not session.globalenv then session.globalenv = redirect_output(_G, session); end local chunkname = "=console"; local env = (useglobalenv and session.globalenv) or session.env or nil -- luacheck: ignore 311/err local chunk, err = envload("return "..line, chunkname, env); if not chunk then chunk, err = envload(line, chunkname, env); if not chunk then err = err:gsub("^%[string .-%]:%d+: ", ""); err = err:gsub("^:%d+: ", ""); err = err:gsub("''", "the end of the line"); result.attr.type = "error"; result:text("Sorry, I couldn't understand that... "..err); event.origin.send(result); return; end end local taskok, message = chunk(); if promise.is_promise(taskok) then taskok, message = async.wait_for(taskok); end if not message then if type(taskok) ~= "string" and useglobalenv then taskok = session.serialize(taskok); end result:text("Result: "..tostring(taskok)); elseif (not taskok) and message then result.attr.type = "error"; result:text("Error: "..tostring(message)); else result:text("OK: "..tostring(message)); end event.origin.send(result); end module:hook("admin/repl-input", function (event) local ok, err = pcall(handle_line, event); if not ok then event.origin.send(st.stanza("repl-result", { type = "error" }):text(err)); end return true; end); -- Console commands -- -- These are simple commands, not valid standalone in Lua local available_columns; --forward declaration so it is reachable from the help function commands.help(session, data) local print = session.print; local section = data:match("^help (%w+)"); if not section then print [[Commands are divided into multiple sections. For help on a particular section, ]] print [[type: help SECTION (for example, 'help c2s'). Sections are: ]] print [[]] local row = format_table({ { title = "Section"; width = 7 }; { title = "Description"; width = "100%" } }) print(row()) print(row { "c2s"; "Commands to manage local client-to-server sessions" }) print(row { "s2s"; "Commands to manage sessions between this server and others" }) print(row { "http"; "Commands to inspect HTTP services" }) -- XXX plural but there is only one so far print(row { "module"; "Commands to load/reload/unload modules/plugins" }) print(row { "host"; "Commands to activate, deactivate and list virtual hosts" }) print(row { "user"; "Commands to create and delete users, and change their passwords" }) print(row { "roles"; "Show information about user roles" }) print(row { "muc"; "Commands to create, list and manage chat rooms" }) print(row { "stats"; "Commands to show internal statistics" }) print(row { "server"; "Uptime, version, shutting down, etc." }) print(row { "port"; "Commands to manage ports the server is listening on" }) print(row { "dns"; "Commands to manage and inspect the internal DNS resolver" }) print(row { "xmpp"; "Commands for sending XMPP stanzas" }) print(row { "debug"; "Commands for debugging the server" }) print(row { "config"; "Reloading the configuration, etc." }) print(row { "columns"; "Information about customizing session listings" }) print(row { "console"; "Help regarding the console itself" }) elseif section == "c2s" then print [[c2s:show(jid, columns) - Show all client sessions with the specified JID (or all if no JID given)]] print [[c2s:show_tls(jid) - Show TLS cipher info for encrypted sessions]] print [[c2s:count() - Count sessions without listing them]] print [[c2s:close(jid) - Close all sessions for the specified JID]] print [[c2s:closeall() - Close all active c2s connections ]] elseif section == "s2s" then print [[s2s:show(domain, columns) - Show all s2s connections for the given domain (or all if no domain given)]] print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]] print [[s2s:close(from, to) - Close a connection from one domain to another]] print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]] elseif section == "http" then print [[http:list(hosts) - Show HTTP endpoints]] elseif section == "module" then print [[module:info(module, host) - Show information about a loaded module]] print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]] print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]] print [[module:unload(module, host) - The same, but just unloads the module from memory]] print [[module:list(host) - List the modules loaded on the specified host]] elseif section == "host" then print [[host:activate(hostname) - Activates the specified host]] print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]] print [[host:list() - List the currently-activated hosts]] elseif section == "user" then print [[user:create(jid, password, roles) - Create the specified user account]] print [[user:password(jid, password) - Set the password for the specified user account]] print [[user:roles(jid, host) - Show current roles for an user]] print [[user:setroles(jid, host, roles) - Set roles for an user (see 'help roles')]] print [[user:delete(jid) - Permanently remove the specified user account]] print [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]] elseif section == "roles" then print [[Roles may grant access or restrict users from certain operations]] print [[Built-in roles are:]] print [[ prosody:admin - Administrator]] print [[ (empty set) - Normal user]] print [[]] print [[The canonical role format looks like: { ["example:role"] = true }]] print [[For convenience, the following formats are also accepted:]] print [["admin" - short for "prosody:admin", the normal admin status (like the admins config option)]] print [["example:role" - short for {["example:role"]=true}]] print [[{"example:role"} - short for {["example:role"]=true}]] elseif section == "muc" then -- TODO `muc:room():foo()` commands print [[muc:create(roomjid, { config }) - Create the specified MUC room with the given config]] print [[muc:list(host) - List rooms on the specified MUC component]] print [[muc:room(roomjid) - Reference the specified MUC room to access MUC API methods]] elseif section == "server" then print [[server:version() - Show the server's version number]] print [[server:uptime() - Show how long the server has been running]] print [[server:memory() - Show details about the server's memory usage]] print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]] elseif section == "port" then print [[port:list() - Lists all network ports prosody currently listens on]] print [[port:close(port, interface) - Close a port]] elseif section == "dns" then print [[dns:lookup(name, type, class) - Do a DNS lookup]] print [[dns:addnameserver(nameserver) - Add a nameserver to the list]] print [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]] print [[dns:purge() - Clear the DNS cache]] print [[dns:cache() - Show cached records]] elseif section == "xmpp" then print [[xmpp:ping(localhost, remotehost) -- Sends a ping to a remote XMPP server and reports the response]] elseif section == "config" then print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]] print [[config:get([host,] option) - Show the value of a config option.]] elseif section == "stats" then -- luacheck: ignore 542 print [[stats:show(pattern) - Show internal statistics, optionally filtering by name with a pattern]] print [[stats:show():cfgraph() - Show a cumulative frequency graph]] print [[stats:show():histogram() - Show a histogram of selected metric]] elseif section == "debug" then print [[debug:logevents(host) - Enable logging of fired events on host]] print [[debug:events(host, event) - Show registered event handlers]] print [[debug:timers() - Show information about scheduled timers]] elseif section == "console" then print [[Hey! Welcome to Prosody's admin console.]] print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]] print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]] print [[so you may have trouble using the arrow keys, etc. depending on your system.]] print [[]] print [[For now we offer a couple of handy shortcuts:]] print [[!! - Repeat the last command]] print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']] print [[]] print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]] print [[you can prefix a command with > to escape the console sandbox, and access everything in]] print [[the running server. Great fun, but be careful not to break anything :)]] elseif section == "columns" then print [[The columns shown by c2s:show() and s2s:show() can be customizied via the]] print [['columns' argument as described here.]] print [[]] print [[Columns can be specified either as "id jid ipv" or as {"id", "jid", "ipv"}.]] print [[Available columns are:]] local meta_columns = { { title = "ID"; width = 5 }; { title = "Column Title"; width = 12 }; { title = "Description"; width = 12 }; }; -- auto-adjust widths for column, spec in pairs(available_columns) do meta_columns[1].width = math.max(meta_columns[1].width or 0, #column); meta_columns[2].width = math.max(meta_columns[2].width or 0, #(spec.title or "")); meta_columns[3].width = math.max(meta_columns[3].width or 0, #(spec.description or "")); end local row = format_table(meta_columns, 120) print(row()); for column, spec in iterators.sorted_pairs(available_columns) do print(row({ column, spec.title, spec.description })); end print [[]] print [[Most fields on the internal session structures can also be used as columns]] -- Also, you can pass a table column specification directly, with mapper callback and all end end -- Session environment -- -- Anything in def_env will be accessible within the session as a global variable --luacheck: ignore 212/self local serialize_defaults = module:get_option("console_prettyprint_settings", { fatal = false; unquoted = true; maxdepth = 2; table_iterator = "pairs" }) def_env.output = {}; function def_env.output:configure(opts) if type(opts) ~= "table" then opts = { preset = opts }; end if not opts.fallback then -- XXX Error message passed to fallback is lost, does it matter? opts.fallback = tostring; end for k,v in pairs(serialize_defaults) do if opts[k] == nil then opts[k] = v; end end if opts.table_iterator == "pairs" then opts.table_iterator = pairs; elseif type(opts.table_iterator) ~= "function" then opts.table_iterator = nil; -- rawpairs is the default end self.session.serialize = serialization.new(opts); end def_env.server = {}; function def_env.server:insane_reload() prosody.unlock_globals(); dofile "prosody" prosody = _G.prosody; return true, "Server reloaded"; end function def_env.server:version() return true, tostring(prosody.version or "unknown"); end function def_env.server:uptime() local t = os.time()-prosody.start_time; local seconds = t%60; t = (t - seconds)/60; local minutes = t%60; t = (t - minutes)/60; local hours = t%24; t = (t - hours)/24; local days = t; return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); end function def_env.server:shutdown(reason, code) prosody.shutdown(reason, code); return true, "Shutdown initiated"; end local function human(kb) return format_number(kb*1024, "B", "b"); end function def_env.server:memory() if not has_pposix or not pposix.meminfo then return true, "Lua is using "..human(collectgarbage("count")); end local mem, lua_mem = pposix.meminfo(), collectgarbage("count"); local print = self.session.print; print("Process: "..human((mem.allocated+mem.allocated_mmap)/1024)); print(" Used: "..human(mem.used/1024).." ("..human(lua_mem).." by Lua)"); print(" Free: "..human(mem.unused/1024).." ("..human(mem.returnable/1024).." returnable)"); return true, "OK"; end def_env.module = {}; local function get_hosts_set(hosts) if type(hosts) == "table" then if hosts[1] then return set.new(hosts); elseif hosts._items then return hosts; end elseif type(hosts) == "string" then return set.new { hosts }; elseif hosts == nil then return set.new(array.collect(keys(prosody.hosts))); end end -- Hosts with a module or all virtualhosts if no module given -- matching modules_enabled in the global section local function get_hosts_with_module(hosts, module) local hosts_set = get_hosts_set(hosts) / function (host) if module then -- Module given, filter in hosts with this module loaded if modulemanager.is_loaded(host, module) then return host; else return nil; end end if not hosts then -- No hosts given, filter in VirtualHosts if prosody.hosts[host].type == "local" then return host; else return nil end end; -- No module given, but hosts are, don't filter at all return host; end; if module and modulemanager.get_module("*", module) then hosts_set:add("*"); end return hosts_set; end function def_env.module:info(name, hosts) if not name then return nil, "module name expected"; end local print = self.session.print; hosts = get_hosts_with_module(hosts, name); if hosts:empty() then return false, "mod_" .. name .. " does not appear to be loaded on the specified hosts"; end local function item_name(item) return item.name; end local friendly_descriptions = { ["adhoc-provider"] = "Ad-hoc commands", ["auth-provider"] = "Authentication provider", ["http-provider"] = "HTTP services", ["net-provider"] = "Network service", ["storage-provider"] = "Storage driver", ["measure"] = "Legacy metrics", ["metric"] = "Metrics", ["task"] = "Periodic task", }; local item_formatters = { ["feature"] = tostring, ["identity"] = function(ident) return ident.type .. "/" .. ident.category; end, ["adhoc-provider"] = item_name, ["auth-provider"] = item_name, ["storage-provider"] = item_name, ["http-provider"] = function(item, mod) return mod:http_url(item.name, item.default_path); end, ["net-provider"] = item_name, ["measure"] = function(item) return item.name .. " (" .. suf(item.conf and item.conf.unit, " ") .. item.type .. ")"; end, ["metric"] = function(item) return ("%s (%s%s)%s"):format(item.name, suf(item.mf.unit, " "), item.mf.type_, pre(": ", item.mf.description)); end, ["task"] = function (item) return string.format("%s (%s)", item.name or item.id, item.when); end }; for host in hosts do local mod = modulemanager.get_module(host, name); if mod.module.host == "*" then print("in global context"); else print("on " .. tostring(prosody.hosts[mod.module.host])); end print(" path: " .. (mod.module.path or "n/a")); if mod.module.status_message then print(" status: [" .. mod.module.status_type .. "] " .. mod.module.status_message); end if mod.module.items and next(mod.module.items) ~= nil then print(" provides:"); for kind, items in pairs(mod.module.items) do local label = friendly_descriptions[kind] or kind:gsub("%-", " "):gsub("^%a", string.upper); print(string.format(" - %s (%d item%s)", label, #items, #items > 1 and "s" or "")); local formatter = item_formatters[kind]; if formatter then for _, item in ipairs(items) do print(" - " .. formatter(item, mod.module)); end end end end if mod.module.dependencies and next(mod.module.dependencies) ~= nil then print(" dependencies:"); for dep in pairs(mod.module.dependencies) do print(" - mod_" .. dep); end end end return true; end function def_env.module:load(name, hosts, config) hosts = get_hosts_with_module(hosts); -- Load the module for each host 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); if not mod then ok = false; if err == "global-module-already-loaded" then if count > 0 then ok, err, count = true, nil, 1; end break; end self.session.print(err or "Unknown error loading module"); else count = count + 1; self.session.print("Loaded for "..mod.module.host); end end end return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); end function def_env.module:unload(name, hosts) hosts = get_hosts_with_module(hosts, name); -- Unload the module for each host local ok, err, count = true, nil, 0; for host in hosts do if modulemanager.is_loaded(host, name) then ok, err = modulemanager.unload(host, name); if not ok then ok = false; self.session.print(err or "Unknown error unloading module"); else count = count + 1; self.session.print("Unloaded from "..host); end end end 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:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end end function def_env.module:reload(name, hosts) hosts = array.collect(get_hosts_with_module(hosts, name)):sort(_sort_hosts) -- Reload the module for each host local ok, err, count = true, nil, 0; for _, host in ipairs(hosts) do if modulemanager.is_loaded(host, name) then ok, err = modulemanager.reload(host, name); if not ok then ok = false; self.session.print(err or "Unknown error reloading module"); else count = count + 1; if ok == nil then ok = true; end self.session.print("Reloaded on "..host); end end end return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); end function def_env.module:list(hosts) hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); local print = self.session.print; for _, host in ipairs(hosts) do print((host == "*" and "Global" or host)..":"); local modules = array.collect(keys(modulemanager.get_modules(host) or {})):sort(); if #modules == 0 then if prosody.hosts[host] then print(" No modules loaded"); else print(" Host not found"); end else for _, name in ipairs(modules) do local status, status_text = modulemanager.get_module(host, name).module:get_status(); local status_summary = ""; if status == "warn" or status == "error" then status_summary = (" (%s: %s)"):format(status, status_text); end print((" %s%s"):format(name, status_summary)); end end end end def_env.config = {}; function def_env.config:load(filename, format) local config_load = require "core.configmanager".load; local ok, err = config_load(filename, format); if not ok then return false, err or "Unknown error loading config"; end return true, "Config loaded"; end function def_env.config:get(host, key) if key == nil then host, key = "*", host; end local config_get = require "core.configmanager".get return true, serialize_config(config_get(host, key)); end function def_env.config:reload() local ok, err = prosody.reload_config(); return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err); end def_env.c2s = {}; local function get_jid(session) if session.username then return session.full_jid or jid_join(session.username, session.host, session.resource); end local conn = session.conn; local ip = session.ip or "?"; local clientport = conn and conn:clientport() or "?"; local serverip = conn and conn.server and conn:server():ip() or "?"; local serverport = conn and conn:serverport() or "?" return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport); end local function get_c2s() local c2s = array.collect(values(prosody.full_sessions)); c2s:append(array.collect(values(module:shared"/*/c2s/sessions"))); c2s:append(array.collect(values(module:shared"/*/bosh/sessions"))); c2s:unique(); return c2s; end local function _sort_by_jid(a, b) if a.host == b.host then if a.username == b.username then return (a.resource or "") > (b.resource or ""); end return (a.username or "") > (b.username or ""); end return _sort_hosts(a.host or "", b.host or ""); end local function show_c2s(callback) get_c2s():sort(_sort_by_jid):map(function (session) callback(get_jid(session), session) end); end function def_env.c2s:count() local c2s = get_c2s(); return true, "Total: ".. #c2s .." clients"; end local function get_s2s_hosts(session) --> local,remote if session.direction == "outgoing" then return session.host or session.from_host, session.to_host; elseif session.direction == "incoming" then return session.host or session.to_host, session.from_host; end end available_columns = { jid = { title = "JID"; description = "Full JID of user session"; width = 32; key = "full_jid"; mapper = function(full_jid, session) return full_jid or get_jid(session) end; }; host = { title = "Host"; description = "Local hostname"; key = "host"; width = 22; mapper = function(host, session) return host or get_s2s_hosts(session) or "?"; end; }; remote = { title = "Remote"; description = "Remote hostname"; width = 22; mapper = function(_, session) return select(2, get_s2s_hosts(session)); end; }; port = { title = "Port"; description = "Server port used"; width = 5; align = "right"; key = "conn"; mapper = function(conn) if conn then return conn:serverport(); end end; }; dir = { title = "Dir"; description = "Direction of server-to-server connection"; width = 3; key = "direction"; mapper = function(dir, session) if session.incoming and session.outgoing then return "<->"; end if dir == "outgoing" then return "-->"; end if dir == "incoming" then return "<--"; end end; }; id = { title = "Session ID"; description = "Internal session ID used in logging"; width = 20; key = "id" }; type = { title = "Type"; description = "Session type"; width = #"c2s_unauthed"; key = "type" }; method = { title = "Method"; description = "Connection method"; width = 10; mapper = function(_, session) if session.bosh_version then return "BOSH"; elseif session.websocket_request then return "WebSocket"; else return "TCP"; end end; }; ipv = { title = "IPv"; description = "Internet Protocol version (4 or 6)"; width = 4; key = "ip"; mapper = function(ip) if ip then return ip:find(":") and "IPv6" or "IPv4"; end end; }; ip = { title = "IP address"; description = "IP address the session connected from"; width = 40; key = "ip" }; status = { title = "Status"; description = "Presence status"; width = 6; key = "presence"; mapper = function(p) if not p then return ""; end return p:get_child_text("show") or "online"; end; }; secure = { title = "Security"; description = "TLS version or security status"; key = "conn"; width = 8; mapper = function(conn, session) if not session.secure then return "insecure"; end if not conn or not conn:ssl() then return "secure" end local sock = conn and conn:socket(); if not sock then return "secure"; end local tls_info = sock.info and sock:info(); return tls_info and tls_info.protocol or "secure"; end; }; encryption = { title = "Encryption"; description = "Encryption algorithm used (TLS cipher suite)"; width = 30; key = "conn"; mapper = function(conn) local sock = conn and conn:socket(); local info = sock and sock.info and sock:info(); if info then return info.cipher end end; }; cert = { title = "Certificate"; description = "Validation status of certificate"; key = "cert_identity_status"; width = 11; mapper = function(cert_status, session) if cert_status then return capitalize(cert_status); end if session.cert_chain_status == "invalid" then local cert_errors = set.new(session.cert_chain_errors[1]); if cert_errors:contains("certificate has expired") then return "Expired"; elseif cert_errors:contains("self signed certificate") then return "Self-signed"; end return "Untrusted"; elseif session.cert_identity_status == "invalid" then return "Mismatched"; end return "Unknown"; end; }; sni = { title = "SNI"; description = "Hostname requested in TLS"; width = 22; mapper = function(_, session) if not session.conn then return end local sock = session.conn:socket(); return sock and sock.getsniname and sock:getsniname() or ""; end; }; alpn = { title = "ALPN"; description = "Protocol requested in TLS"; width = 11; mapper = function(_, session) if not session.conn then return end local sock = session.conn:socket(); return sock and sock.getalpn and sock:getalpn() or ""; end; }; smacks = { title = "SM"; description = "Stream Management (XEP-0198) status"; key = "smacks"; width = 11; mapper = function(smacks_xmlns, session) if not smacks_xmlns then return "no"; end if session.hibernating then return "hibernating"; end return "yes"; end; }; smacks_queue = { title = "SM Queue"; description = "Length of Stream Management stanza queue"; key = "outgoing_stanza_queue"; width = 8; align = "right"; mapper = function (queue) return queue and tostring(queue:count_unacked()); end }; csi = { title = "CSI State"; description = "Client State Indication (XEP-0352)"; key = "state"; -- TODO include counter }; s2s_sasl = { title = "SASL"; description = "Server authentication status"; key = "external_auth"; width = 10; mapper = capitalize }; dialback = { title = "Dialback"; description = "Legacy server verification"; key = "dialback_key"; width = 13; mapper = function (dialback_key, session) if not dialback_key then if session.type == "s2sin" or session.type == "s2sout" then return "Not used"; end return "Not initiated"; elseif session.type == "s2sin_unauthed" or session.type == "s2sout_unauthed" then return "Initiated"; else return "Completed"; end end }; }; local function get_colspec(colspec, default) if type(colspec) == "string" then colspec = array(colspec:gmatch("%S+")); end local columns = {}; for i, col in pairs(colspec or default) do if type(col) == "string" then columns[i] = available_columns[col] or { title = capitalize(col); width = 20; key = col }; elseif type(col) ~= "table" then return false, ("argument %d: expected string|table but got %s"):format(i, type(col)); else columns[i] = col; end end return columns; end function def_env.c2s:show(match_jid, colspec) local print = self.session.print; local columns = get_colspec(colspec, { "id"; "jid"; "ipv"; "status"; "secure"; "smacks"; "csi" }); local row = format_table(columns, 120); local function match(session) local jid = get_jid(session) return (not match_jid) or jid == match_jid; end local group_by_host = true; for _, col in ipairs(columns) do if col.key == "full_jid" or col.key == "host" then group_by_host = false; break end end if not group_by_host then print(row()); end local currenthost = nil; local c2s_sessions = get_c2s(); local total_count = #c2s_sessions; c2s_sessions:filter(match):sort(_sort_by_jid); local shown_count = #c2s_sessions; for _, session in ipairs(c2s_sessions) do if group_by_host and session.host ~= currenthost then currenthost = session.host; print("#",prosody.hosts[currenthost] or "Unknown host"); print(row()); end print(row(session)); end if total_count ~= shown_count then return true, ("%d out of %d c2s sessions shown"):format(shown_count, total_count); end return true, ("%d c2s sessions shown"):format(total_count); end function def_env.c2s:show_tls(match_jid) return self:show(match_jid, { "jid"; "id"; "secure"; "encryption" }); end local function build_reason(text, condition) if text or condition then return { text = text, condition = condition or "undefined-condition", }; end end function def_env.c2s:close(match_jid, text, condition) local count = 0; show_c2s(function (jid, session) if jid == match_jid or jid_bare(jid) == match_jid then count = count + 1; session:close(build_reason(text, condition)); end end); return true, "Total: "..count.." sessions closed"; end function def_env.c2s:closeall(text, condition) local count = 0; --luacheck: ignore 212/jid show_c2s(function (jid, session) count = count + 1; session:close(build_reason(text, condition)); end); return true, "Total: "..count.." sessions closed"; end def_env.s2s = {}; local function _sort_s2s(a, b) local a_local, a_remote = get_s2s_hosts(a); local b_local, b_remote = get_s2s_hosts(b); if (a_local or "") == (b_local or "") then return _sort_hosts(a_remote or "", b_remote or ""); end return _sort_hosts(a_local or "", b_local or ""); end function def_env.s2s:show(match_jid, colspec) local print = self.session.print; local columns = get_colspec(colspec, { "id"; "host"; "dir"; "remote"; "ipv"; "secure"; "s2s_sasl"; "dialback" }); local row = format_table(columns, 132); local function match(session) local host, remote = get_s2s_hosts(session); return not match_jid or host == match_jid or remote == match_jid; end local group_by_host = true; local currenthost = nil; for _, col in ipairs(columns) do if col.key == "host" then group_by_host = false; break end end if not group_by_host then print(row()); end local s2s_sessions = array(iterators.values(module:shared"/*/s2s/sessions")); local total_count = #s2s_sessions; s2s_sessions:filter(match):sort(_sort_s2s); local shown_count = #s2s_sessions; for _, session in ipairs(s2s_sessions) do if group_by_host and currenthost ~= get_s2s_hosts(session) then currenthost = get_s2s_hosts(session); print("#",prosody.hosts[currenthost] or "Unknown host"); print(row()); end print(row(session)); end if total_count ~= shown_count then return true, ("%d out of %d s2s connections shown"):format(shown_count, total_count); end return true, ("%d s2s connections shown"):format(total_count); end function def_env.s2s:show_tls(match_jid) return self:show(match_jid, { "id"; "host"; "dir"; "remote"; "secure"; "encryption"; "cert" }); end local function print_subject(print, subject) for _, entry in ipairs(subject) do print( (" %s: %q"):format( entry.name or entry.oid, entry.value:gsub("[\r\n%z%c]", " ") ) ); end end -- As much as it pains me to use the 0-based depths that OpenSSL does, -- I think there's going to be more confusion among operators if we -- break from that. local function print_errors(print, errors) for depth, t in pairs(errors) do print( (" %d: %s"):format( depth-1, table.concat(t, "\n| ") ) ); end end function def_env.s2s:showcert(domain) local print = self.session.print; local s2s_sessions = module:shared"/*/s2s/sessions"; local domain_sessions = set.new(array.collect(values(s2s_sessions))) /function(session) return (session.to_host == domain or session.from_host == domain) and session or nil; end; local cert_set = {}; for session in domain_sessions do local conn = session.conn; conn = conn and conn:socket(); if not conn.getpeerchain then if conn.dohandshake then error("This version of LuaSec does not support certificate viewing"); end else local cert = conn:getpeercertificate(); if cert then local certs = conn:getpeerchain(); local digest = cert:digest("sha1"); if not cert_set[digest] then local chain_valid, chain_errors = conn:getpeerverification(); cert_set[digest] = { { from = session.from_host, to = session.to_host, direction = session.direction }; chain_valid = chain_valid; chain_errors = chain_errors; certs = certs; }; else table.insert(cert_set[digest], { from = session.from_host, to = session.to_host, direction = session.direction }); end end end end local domain_certs = array.collect(values(cert_set)); -- Phew. We now have a array of unique certificates presented by domain. local n_certs = #domain_certs; if n_certs == 0 then return "No certificates found for "..domain; end local function _capitalize_and_colon(byte) return string.upper(byte)..":"; end local function pretty_fingerprint(hash) return hash:gsub("..", _capitalize_and_colon):sub(1, -2); end for cert_info in values(domain_certs) do local certs = cert_info.certs; local cert = certs[1]; print("---") print("Fingerprint (SHA1): "..pretty_fingerprint(cert:digest("sha1"))); print(""); local n_streams = #cert_info; print("Currently used on "..n_streams.." stream"..(n_streams==1 and "" or "s")..":"); for _, stream in ipairs(cert_info) do if stream.direction == "incoming" then print(" "..stream.to.." <- "..stream.from); else print(" "..stream.from.." -> "..stream.to); end end print(""); local chain_valid, errors = cert_info.chain_valid, cert_info.chain_errors; local valid_identity = cert_verify_identity(domain, "xmpp-server", cert); if chain_valid then print("Trusted certificate: Yes"); else print("Trusted certificate: No"); print_errors(print, errors); end print(""); print("Issuer: "); print_subject(print, cert:issuer()); print(""); print("Valid for "..domain..": "..(valid_identity and "Yes" or "No")); print("Subject:"); print_subject(print, cert:subject()); end print("---"); return ("Showing "..n_certs.." certificate" ..(n_certs==1 and "" or "s") .." presented by "..domain.."."); end function def_env.s2s:close(from, to, text, condition) local print, count = self.session.print, 0; local s2s_sessions = module:shared"/*/s2s/sessions"; local match_id; if from and not to then 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 return false, "Both from and to are the same... you can't do that :)"; end for _, session in pairs(s2s_sessions) do local id = session.id or (session.type..tostring(session):match("[a-f0-9]+$")); if (match_id and match_id == id) or (session.from_host == from and session.to_host == to) then print(("Closing connection from %s to %s [%s]"):format(session.from_host, session.to_host, id)); (session.close or s2smanager.destroy_session)(session, build_reason(text, condition)); count = count + 1 ; end end return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end function def_env.s2s:closeall(host, text, condition) local count = 0; local s2s_sessions = module:shared"/*/s2s/sessions"; for _,session in pairs(s2s_sessions) do if not host or session.from_host == host or session.to_host == host then session:close(build_reason(text, condition)); count = count + 1; end end if count == 0 then return false, "No sessions to close."; else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end end def_env.host = {}; def_env.hosts = def_env.host; function def_env.host:activate(hostname, config) return hostmanager.activate(hostname, config); end function def_env.host:deactivate(hostname, reason) return hostmanager.deactivate(hostname, reason); end function def_env.host:list() local print = self.session.print; local i = 0; local type; for host, host_session in iterators.sorted_pairs(prosody.hosts, _sort_hosts) do i = i + 1; type = host_session.type; if type == "local" then print(host); else type = module:context(host):get_option_string("component_module", type); if type ~= "component" then type = type .. " component"; end print(("%s (%s)"):format(host, type)); end end return true, i.." hosts"; end def_env.port = {}; function def_env.port:list() local print = self.session.print; local services = portmanager.get_active_services().data; 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(interfaces) do for port in pairs(ports) do table.insert(ports_list, "["..interface.."]:"..port); end end n_ports = n_ports + #ports_list; print(service..": "..table.concat(ports_list, ", ")); end 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 -- luacheck: ignore 213 for interface, ports in pairs(interfaces) do if not close_interface or close_interface == interface then if ports[close_port] then self.session.print("Closing ["..interface.."]:"..close_port.."..."); local ok, err = portmanager.close(interface, close_port) if not ok then self.session.print("Failed to close "..interface.." "..close_port..": "..err); else n_closed = n_closed + 1; end end end end end return true, "Closed "..n_closed.." ports"; end def_env.muc = {}; local console_room_mt = { __index = function (self, k) return self.room[k]; end; __tostring = function (self) return "MUC room <"..self.room.jid..">"; end; }; local function check_muc(jid) local room_name, host = jid_split(jid); if not prosody.hosts[host] then return nil, "No such host: "..host; 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, 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 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) local room_name, host = check_muc(room_jid); if not room_name then return room_name, host; end 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 return setmetatable({ room = room_obj }, console_room_mt); end function def_env.muc:list(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 room in host_session.modules.muc.each_room() do print(room.jid); c = c + 1; end return true, c.." rooms"; end local um = require"core.usermanager"; local function coerce_roles(roles) if roles == "admin" then roles = "prosody:admin"; end if type(roles) == "string" then roles = { [roles] = true }; end if roles[1] then for i, role in ipairs(roles) do roles[role], roles[i] = true, nil; end end return roles; end def_env.user = {}; function def_env.user:create(jid, password, roles) local username, host = jid_split(jid); if not prosody.hosts[host] then return nil, "No such host: "..host; elseif um.user_exists(username, host) then return nil, "User exists"; end local ok, err = um.create_user(username, password, host); if ok then if ok and roles then roles = coerce_roles(roles); local roles_ok, rerr = um.set_roles(jid, host, roles); if not roles_ok then return nil, "User created, but could not set roles: " .. tostring(rerr); end end return true, "User created"; else return nil, "Could not create user: "..err; end end function def_env.user:delete(jid) local username, host = jid_split(jid); 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"; end local ok, err = um.delete_user(username, host); if ok then return true, "User deleted"; else return nil, "Could not delete user: "..err; end end function def_env.user:password(jid, password) local username, host = jid_split(jid); 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"; end local ok, err = um.set_password(username, password, host, nil); if ok then return true, "User password changed"; else return nil, "Could not change password for user: "..err; end end function def_env.user:roles(jid, host, new_roles) if new_roles or type(host) == "table" then return nil, "Use user:setroles(jid, host, roles) to change user roles"; end local username, userhost = jid_split(jid); if host == nil then host = userhost; end if host ~= "*" and not prosody.hosts[host] then return nil, "No such host: "..host; elseif prosody.hosts[userhost] and not um.user_exists(username, userhost) then return nil, "No such user"; end local roles = um.get_roles(jid, host); if not roles then return true, "No roles"; end local count = 0; local print = self.session.print; for role in pairs(roles) do count = count + 1; print(role); end return true, count == 1 and "1 role" or count.." roles"; end def_env.user.showroles = def_env.user.roles; -- COMPAT -- user:roles("someone@example.com", "example.com", {"prosody:admin"}) -- user:roles("someone@example.com", {"prosody:admin"}) function def_env.user:setroles(jid, host, new_roles) local username, userhost = jid_split(jid); if new_roles == nil then host, new_roles = userhost, host; end if host ~= "*" and not prosody.hosts[host] then return nil, "No such host: "..host; elseif prosody.hosts[userhost] and not um.user_exists(username, userhost) then return nil, "No such user"; end if host == "*" then host = nil; end return um.set_roles(jid, host, coerce_roles(new_roles)); end -- TODO switch to table view, include roles function def_env.user:list(host, pat) if not host then return nil, "No host given"; elseif not prosody.hosts[host] then return nil, "No such host"; end local print = self.session.print; local total, matches = 0, 0; for user in um.users(host) do if not pat or user:match(pat) then print(user.."@"..host); matches = matches + 1; end total = total + 1; end return true, "Showing "..(pat and (matches.." of ") or "all " )..total.." users"; end def_env.xmpp = {}; local new_id = require "util.id".medium; function def_env.xmpp:ping(localhost, remotehost, timeout) localhost = select(2, jid_split(localhost)); remotehost = select(2, jid_split(remotehost)); if not localhost then return nil, "Invalid sender hostname"; elseif not prosody.hosts[localhost] then return nil, "No such local host"; end if not remotehost then return nil, "Invalid destination hostname"; elseif prosody.hosts[remotehost] then return nil, "Both hosts are local"; end local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} :tag("ping", {xmlns="urn:xmpp:ping"}); local time_start = time.now(); local print = self.session.print; local function onchange(what) return function(event) local s2s_session = event.session; if (s2s_session.from_host == localhost and s2s_session.to_host == remotehost) or (s2s_session.to_host == localhost and s2s_session.from_host == remotehost) then local dir = available_columns.dir.mapper(s2s_session.direction, s2s_session); print(("Session %s (%s%s%s) %s (%gs)"):format(s2s_session.id, localhost, dir, remotehost, what, time.now() - time_start)); elseif s2s_session.type == "s2sin_unauthed" and s2s_session.to_host == nil and s2s_session.from_host == nil then print(("Session %s %s (%gs)"):format(s2s_session.id, what, time.now() - time_start)); end end end local onconnected = onchange("connected"); local onauthenticated = onchange("authenticated"); local onestablished = onchange("established"); local ondestroyed = onchange("destroyed"); module:hook("s2s-connected", onconnected, 1); module:context(localhost):hook("s2s-authenticated", onauthenticated, 1); module:hook("s2sout-established", onestablished, 1); module:hook("s2sin-established", onestablished, 1); module:hook("s2s-destroyed", ondestroyed, 1); return module:context(localhost):send_iq(iq, nil, timeout):finally(function() module:unhook("s2s-connected", onconnected, 1); module:context(localhost):unhook("s2s-authenticated", onauthenticated); module:unhook("s2sout-established", onestablished); module:unhook("s2sin-established", onestablished); module:unhook("s2s-destroyed", ondestroyed); end):next(function(pong) return ("pong from %s in %gs"):format(pong.stanza.attr.from, time.now() - time_start); end); end def_env.dns = {}; local adns = require"net.adns"; local function get_resolver(session) local resolver = session.dns_resolver; if not resolver then resolver = adns.resolver(); session.dns_resolver = resolver; end return resolver; end function def_env.dns:lookup(name, typ, class) local resolver = get_resolver(self.session); return resolver:lookup_promise(name, typ, class) end function def_env.dns:addnameserver(...) local resolver = get_resolver(self.session); resolver._resolver:addnameserver(...) return true end function def_env.dns:setnameserver(...) local resolver = get_resolver(self.session); resolver._resolver:setnameserver(...) return true end function def_env.dns:purge() local resolver = get_resolver(self.session); resolver._resolver:purge() return true end function def_env.dns:cache() local resolver = get_resolver(self.session); return true, "Cache:\n"..tostring(resolver._resolver.cache) end def_env.http = {}; function def_env.http:list(hosts) local print = self.session.print; hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); local output = format_table({ { title = "Module", width = "20%" }, { title = "URL", width = "80%" }, }, 132); for _, host in ipairs(hosts) do local http_apps = modulemanager.get_items("http-provider", host); if #http_apps > 0 then local http_host = module:context(host):get_option_string("http_host"); if host == "*" then print("Global HTTP endpoints available on all hosts:"); else print("HTTP endpoints on "..host..(http_host and (" (using "..http_host.."):") or ":")); end print(output()); for _, provider in ipairs(http_apps) do local mod = provider._provided_by; local url = module:context(host):http_url(provider.name, provider.default_path); mod = mod and "mod_"..mod or "" print(output{mod, url}); end print(""); end end local default_host = module:get_option_string("http_default_host"); if not default_host then print("HTTP requests to unknown hosts will return 404 Not Found"); else print("HTTP requests to unknown hosts will be handled by "..default_host); end return true; end def_env.debug = {}; function def_env.debug:logevents(host) helpers.log_host_events(host); return true; end function def_env.debug:events(host, event) local events_obj; if host and 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 else events_obj = prosody.events; end return true, helpers.show_events(events_obj, event); end function def_env.debug:timers() local print = self.session.print; local add_task = require"util.timer".add_task; local h, params = add_task.h, add_task.params; local function normalize_time(t) return t; end local function format_time(t) return os.date("%F %T", math.floor(normalize_time(t))); end if h then print("-- util.timer"); elseif server.timer then print("-- net.server.timer"); h = server.timer.add_task.timers; normalize_time = server.timer.to_absolute_time or normalize_time; end if h then local timers = {}; for i, id in ipairs(h.ids) do local t, cb = h.priorities[i], h.items[id]; if not params then local param = cb.param; if param then cb = param.callback; else cb = cb.timer_callback or cb; end elseif params[id] then cb = params[id].callback or cb; end table.insert(timers, { format_time(t), cb }); end table.sort(timers, function (a, b) return a[1] < b[1] end); for _, t in ipairs(timers) do print(t[1], t[2]) 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, ("Next event at %s (in %.6fs)"):format(format_time(next_time), normalize_time(next_time) - time.now()); end end return true; end -- COMPAT: debug:timers() was timer:info() for some time in trunk def_env.timer = { info = def_env.debug.timers }; def_env.stats = {}; local short_units = { seconds = "s", bytes = "B", }; local stats_methods = {}; function stats_methods:render_single_fancy_histogram_ex(print, prefix, metric_family, metric, cumulative) local creation_timestamp, sum, count local buckets = {} local prev_bucket_count = 0 for suffix, extra_labels, value in metric:iter_samples() do if suffix == "_created" then creation_timestamp = value elseif suffix == "_sum" then sum = value elseif suffix == "_count" then count = value elseif extra_labels then local bucket_threshold = extra_labels["le"] local bucket_count if cumulative then bucket_count = value else bucket_count = value - prev_bucket_count prev_bucket_count = value end if bucket_threshold == "+Inf" then t_insert(buckets, {threshold = 1/0, count = bucket_count}) elseif bucket_threshold ~= nil then t_insert(buckets, {threshold = tonumber(bucket_threshold), count = bucket_count}) end end end if #buckets == 0 or not creation_timestamp or not sum or not count then print("[no data or not a histogram]") return false end local graph_width, graph_height, wscale = #buckets, 10, 1; if graph_width < 8 then wscale = 8 elseif graph_width < 16 then wscale = 4 elseif graph_width < 32 then wscale = 2 end local eighth_chars = " ▁▂▃▄▅▆▇█"; local max_bin_samples = 0 for _, bucket in ipairs(buckets) do if bucket.count > max_bin_samples then max_bin_samples = bucket.count end end print(""); print(prefix) print(("_"):rep(graph_width*wscale).." "..max_bin_samples); for row = graph_height, 1, -1 do local row_chars = {}; local min_eighths, max_eighths = 8, 0; for i = 1, #buckets do local char_eighths = math.ceil(math.max(math.min((graph_height/(max_bin_samples/buckets[i].count))-(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] = ("-"):rep(wscale); else local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3); row_chars[i] = char:rep(wscale); end end print(table.concat(row_chars).."|- "..string.format("%.8g", math.ceil((max_bin_samples/graph_height)*(row-0.5)))); end local legend_pat = string.format("%%%d.%dg", wscale-1, wscale-1) local row = {} for i = 1, #buckets do local threshold = buckets[i].threshold t_insert(row, legend_pat:format(threshold)) end t_insert(row, " " .. metric_family.unit) print(t_concat(row, "/")) return true end function stats_methods:render_single_fancy_histogram(print, prefix, metric_family, metric) return self:render_single_fancy_histogram_ex(print, prefix, metric_family, metric, false) end function stats_methods:render_single_fancy_histogram_cf(print, prefix, metric_family, metric) -- cf = cumulative frequency return self:render_single_fancy_histogram_ex(print, prefix, metric_family, metric, true) end function stats_methods:cfgraph() for _, stat_info in ipairs(self) do local family_name, metric_family = unpack(stat_info, 1, 2) local function print(s) table.insert(stat_info.output, s); end if not self:render_family(print, family_name, metric_family, self.render_single_fancy_histogram_cf) then return self end end return self; end function stats_methods:histogram() for _, stat_info in ipairs(self) do local family_name, metric_family = unpack(stat_info, 1, 2) local function print(s) table.insert(stat_info.output, s); end if not self:render_family(print, family_name, metric_family, self.render_single_fancy_histogram) then return self end end return self; end function stats_methods:render_single_counter(print, prefix, metric_family, metric) local created_timestamp, current_value for suffix, _, value in metric:iter_samples() do if suffix == "_created" then created_timestamp = value elseif suffix == "_total" then current_value = value end end if current_value and created_timestamp then local base_unit = short_units[metric_family.unit] or metric_family.unit local unit = base_unit .. "/s" local factor = 1 if base_unit == "s" then -- be smart! unit = "%" factor = 100 elseif base_unit == "" then unit = "events/s" end print(("%-50s %s"):format(prefix, format_number(factor * current_value / (self.now - created_timestamp), unit.." [avg]"))); end end function stats_methods:render_single_gauge(print, prefix, metric_family, metric) local current_value for _, _, value in metric:iter_samples() do current_value = value end if current_value then local unit = short_units[metric_family.unit] or metric_family.unit print(("%-50s %s"):format(prefix, format_number(current_value, unit))); end end function stats_methods:render_single_summary(print, prefix, metric_family, metric) local sum, count for suffix, _, value in metric:iter_samples() do if suffix == "_sum" then sum = value elseif suffix == "_count" then count = value end end if sum and count then local unit = short_units[metric_family.unit] or metric_family.unit if count == 0 then print(("%-50s %s"):format(prefix, "no obs.")); else print(("%-50s %s"):format(prefix, format_number(sum / count, unit.."/event [avg]"))); end end end function stats_methods:render_family(print, family_name, metric_family, render_func) local labelkeys = metric_family.label_keys if #labelkeys > 0 then print(family_name) for labelset, metric in metric_family:iter_metrics() do local labels = {} for i, k in ipairs(labelkeys) do local v = labelset[i] t_insert(labels, ("%s=%s"):format(k, v)) end local prefix = " "..t_concat(labels, " ") render_func(self, print, prefix, metric_family, metric) end else for _, metric in metric_family:iter_metrics() do render_func(self, print, family_name, metric_family, metric) end end 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 local metric_family = stat_info[2] if metric_family.type_ == "counter" then stats:render_family(print, stat_info[1], metric_family, stats.render_single_counter) elseif metric_family.type_ == "gauge" or metric_family.type_ == "unknown" then stats:render_family(print, stat_info[1], metric_family, stats.render_single_gauge) elseif metric_family.type_ == "summary" or metric_family.type_ == "histogram" then stats:render_family(print, stat_info[1], metric_family, stats.render_single_summary) end end end return #stats.." statistics displayed"; end local stats_mt = {__index = stats_methods, __tostring = stats_tostring } local function new_stats_context(self) -- TODO: instead of now(), it might be better to take the time of the last -- interval, if the statistics backend is set to use periodic collection -- Otherwise we get strange stuff like average cpu usage decreasing until -- the next sample and so on. return setmetatable({ session = self.session, stats = true, now = time.now() }, stats_mt); end function def_env.stats:show(name_filter) local statsman = require "core.statsmanager" local collect = statsman.collect if collect then -- force collection if in manual mode collect() end local metric_registry = statsman.get_metric_registry(); local displayed_stats = new_stats_context(self); for family_name, metric_family in iterators.sorted_pairs(metric_registry:get_metric_families()) do if not name_filter or family_name:match(name_filter) then table.insert(displayed_stats, { family_name, metric_family, output = {} }) end end return displayed_stats; end ------------- function printbanner(session) local option = module:get_option_string("console_banner", "full"); if option == "full" or option == "graphic" then session.print [[ ____ \ / _ | _ \ _ __ ___ ___ _-_ __| |_ _ | |_) | '__/ _ \/ __|/ _ \ / _` | | | | | __/| | | (_) \__ \ |_| | (_| | |_| | |_| |_| \___/|___/\___/ \__,_|\__, | A study in simplicity |___/ ]] end 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("https://prosody.im/doc/console\n"); end if option ~= "short" and option ~= "full" and option ~= "graphic" then session.print(option); end end prosody-0.12.4/plugins/PaxHeaders/mod_admin_socket.lua0000644000000000000000000000012014475665104020047 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_admin_socket.lua0000644000175000017500000000510014475665104022250 0ustar00prosodyprosody00000000000000module:set_global(); local have_unix, unix = pcall(require, "socket.unix"); if have_unix and type(unix) == "function" then -- COMPAT #1717 -- Before the introduction of datagram support, only the stream socket -- constructor was exported instead of a module table. Due to the lack of a -- proper release of LuaSocket, distros have settled on shipping either the -- last RC tag or some commit since then. -- Here we accomodate both variants. unix = { stream = unix }; end if not have_unix or type(unix) ~= "table" then module:log_status("error", "LuaSocket unix socket support not available or incompatible, ensure it is up to date"); return; end local server = require "net.server"; local adminstream = require "util.adminstream"; local st = require "util.stanza"; local socket_path = module:get_option_path("admin_socket", "prosody.sock", "data"); local sessions = module:shared("sessions"); local function fire_admin_event(session, stanza) local event_data = { origin = session, stanza = stanza; }; local event_name; if stanza.attr.xmlns then event_name = "admin/"..stanza.attr.xmlns..":"..stanza.name; else event_name = "admin/"..stanza.name; end module:log("debug", "Firing %s", event_name); local ret = module:fire_event(event_name, event_data); if ret == nil then session.send(st.stanza("repl-result", { type = "error" }):text("No module handled this query. Is mod_admin_shell enabled?")); end return ret; end module:hook("server-stopping", function () for _, session in pairs(sessions) do session:close("system-shutdown"); end os.remove(socket_path); end); --- Unix domain socket management local conn, sock; local listeners = adminstream.server(sessions, fire_admin_event).listeners; local function accept_connection() module:log("debug", "accepting..."); local client = sock:accept(); if not client then return; end server.wrapclient(client, "unix", 0, listeners, "*a"); end function module.load() sock = unix.stream(); sock:settimeout(0); os.remove(socket_path); local ok, err = sock:bind(socket_path); if not ok then module:log_status("error", "Unable to bind admin socket %s: %s", socket_path, err); return; end local ok, err = sock:listen(); if not ok then module:log_status("error", "Unable to listen on admin socket %s: %s", socket_path, err); return; end if server.wrapserver then conn = server.wrapserver(sock, socket_path, 0, listeners); else conn = server.watchfd(sock:getfd(), accept_connection); end end function module.unload() if conn then conn:close(); end if sock then sock:close(); end os.remove(socket_path); end prosody-0.12.4/plugins/PaxHeaders/mod_admin_telnet.lua0000644000000000000000000000012014475665104020052 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_admin_telnet.lua0000644000175000017500000001077014475665104022264 0ustar00prosodyprosody00000000000000-- 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/self module:set_global(); module:depends("admin_shell"); local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" }; local async = require "util.async"; local st = require "util.stanza"; local def_env = module:shared("admin_shell/env"); local default_env_mt = { __index = def_env }; local function printbanner(session) local option = module:get_option_string("console_banner", "full"); if option == "full" or option == "graphic" then session.print [[ ____ \ / _ | _ \ _ __ ___ ___ _-_ __| |_ _ | |_) | '__/ _ \/ __|/ _ \ / _` | | | | | __/| | | (_) \__ \ |_| | (_| | |_| | |_| |_| \___/|___/\___/ \__,_|\__, | A study in simplicity |___/ ]] end 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("https://prosody.im/doc/console\n"); end if option ~= "short" and option ~= "full" and option ~= "graphic" then session.print(option); end end console = {}; local runner_callbacks = {}; function runner_callbacks:ready() self.data.conn:resume(); end function runner_callbacks:waiting() self.data.conn:pause(); end function runner_callbacks:error(err) module:log("error", "Traceback[telnet]: %s", err); self.data.print("Fatal error while running command, it did not complete"); self.data.print("Error: "..tostring(err)); end function console:new_session(conn) local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; local session = { conn = conn; send = function (t) if st.is_stanza(t) and (t.name == "repl-result" or t.name == "repl-output") then t = "| "..t:get_text().."\n"; end w(tostring(t)); end; print = function (...) local t = {}; for i=1,select("#", ...) do t[i] = tostring(select(i, ...)); end w("| "..table.concat(t, "\t").."\n"); end; serialize = tostring; disconnect = function () conn:close(); end; }; session.env = setmetatable({}, default_env_mt); session.thread = async.runner(function (line) console:process_line(session, line); session.send(string.char(0)); end, runner_callbacks, session); -- Load up environment with helper objects for name, t in pairs(def_env) do if type(t) == "table" then session.env[name] = setmetatable({ session = session }, { __index = t }); end end session.env.output:configure(); return session; end function console:process_line(session, line) line = line:gsub("\r?\n$", ""); if line == "bye" or line == "quit" or line == "exit" or line:byte() == 4 then session.print("See you!"); session:disconnect(); return; end return module:fire_event("admin/repl-input", { origin = session, stanza = st.stanza("repl-input"):text(line) }); end local sessions = {}; function module.save() return { sessions = sessions } end function module.restore(data) if data.sessions then for conn in pairs(data.sessions) do conn:setlistener(console_listener); local session = console:new_session(conn); sessions[conn] = session; end end end function console_listener.onconnect(conn) -- Handle new connection local session = console:new_session(conn); sessions[conn] = session; printbanner(session); session.send(string.char(0)); end function console_listener.onincoming(conn, data) local session = sessions[conn]; local partial = session.partial_data; if partial then data = partial..data; end for line in data:gmatch("[^\n]*[\n\004]") do if session.closed then return end session.thread:run(line); end session.partial_data = data:match("[^\n]+$"); end function console_listener.onreadtimeout(conn) local session = sessions[conn]; if session then session.send("\0"); return true; end end function console_listener.ondisconnect(conn, err) -- luacheck: ignore 212/err local session = sessions[conn]; if session then session.disconnect(); sessions[conn] = nil; end end function console_listener.ondetach(conn) sessions[conn] = nil; end module:provides("net", { name = "console"; listener = console_listener; default_port = 5582; private = true; }); prosody-0.12.4/plugins/PaxHeaders/mod_announce.lua0000644000000000000000000000012014475665104017215 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_announce.lua0000644000175000017500000000607114475665104021426 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st, jid = require "util.stanza", require "util.jid"; local hosts = prosody.hosts; local is_admin = require "core.usermanager".is_admin; function send_to_online(message, host) local sessions; if host then sessions = { [host] = hosts[host] }; else sessions = hosts; end local c = 0; for hostname, host_session in pairs(sessions) do if host_session.sessions then message.attr.from = hostname; for username in pairs(host_session.sessions) do c = c + 1; message.attr.to = username.."@"..hostname; module:send(message); end end end return c; end -- Old -based jabberd-style announcement sending function handle_announcement(event) local stanza = event.stanza; -- luacheck: ignore 211/node local node, host, resource = jid.split(stanza.attr.to); if resource ~= "announce/online" then return; -- Not an announcement end if not is_admin(stanza.attr.from, host) then -- Not an admin? Not allowed! module:log("warn", "Non-admin '%s' tried to send server announcement", stanza.attr.from); return; end module:log("info", "Sending server announcement to all online users"); local message = st.clone(stanza); message.attr.type = "headline"; message.attr.from = host; local c = send_to_online(message, host); module:log("info", "Announcement sent to %d online users", c); return true; end module:hook("message/host", handle_announcement); -- Ad-hoc command (XEP-0133) local dataforms_new = require "util.dataforms".new; local announce_layout = dataforms_new{ title = "Making an Announcement"; instructions = "Fill out this form to make an announcement to all\nactive users of this service."; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "subject", type = "text-single", label = "Subject" }; { name = "announcement", type = "text-multi", required = true, label = "Announcement" }; }; function announce_handler(_, data, state) if state then if data.action == "cancel" then return { status = "canceled" }; end local fields = announce_layout:data(data.form); module:log("info", "Sending server announcement to all online users"); local message = st.message({type = "headline"}, fields.announcement):up() :tag("subject"):text(fields.subject or "Announcement"); local count = send_to_online(message, data.to); module:log("info", "Announcement sent to %d online users", count); return { status = "completed", info = ("Announcement sent to %d online users"):format(count) }; else return { status = "executing", actions = {"next", "complete", default = "complete"}, form = announce_layout }, "executing"; end 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); prosody-0.12.4/plugins/PaxHeaders/mod_auth_anonymous.lua0000644000000000000000000000012014475665104020460 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_auth_anonymous.lua0000644000175000017500000000376414475665104022677 0ustar00prosodyprosody00000000000000-- 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 new_sasl = require "util.sasl".new; local datamanager = require "util.datamanager"; local hosts = prosody.hosts; local allow_storage = module:get_option_boolean("allow_anonymous_storage", false); -- define auth provider local provider = {}; function provider.test_password(username, password) return nil, "Password based auth not supported."; end function provider.get_password(username) return nil, "Password not available."; end function provider.set_password(username, password) return nil, "Password based auth not supported."; end function provider.user_exists(username) return nil, "Only anonymous users are supported."; -- FIXME check if anonymous user is connected? end function provider.create_user(username, password) return nil, "Account creation/modification not supported."; end function provider.get_sasl_handler() local anonymous_authentication_profile = { anonymous = function(sasl, username, realm) return true; -- for normal usage you should always return true here end }; return new_sasl(module.host, anonymous_authentication_profile); end function provider.users() return next, hosts[module.host].sessions, nil; end -- datamanager callback to disable writes local function dm_callback(username, host, datastore, data) if host == module.host then return false; end return username, host, datastore, data; end if not module:get_option_boolean("allow_anonymous_s2s", false) then module:hook("route/remote", function (event) return false; -- Block outgoing s2s from anonymous users end, 300); end function module.load() if not allow_storage then datamanager.add_callback(dm_callback); end end function module.unload() if not allow_storage then datamanager.remove_callback(dm_callback); end end module:provides("auth", provider); prosody-0.12.4/plugins/PaxHeaders/mod_auth_insecure.lua0000644000000000000000000000012014475665104020245 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_auth_insecure.lua0000644000175000017500000000307014475665104022452 0ustar00prosodyprosody00000000000000-- 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 saslprep = require "util.encodings".stringprep.saslprep; 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"); password = saslprep(password); if not password then return nil, "Password fails SASLprep."; end 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); prosody-0.12.4/plugins/PaxHeaders/mod_auth_internal_hashed.lua0000644000000000000000000000012014475665104021560 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_auth_internal_hashed.lua0000644000175000017500000001212714475665104023770 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2010 Jeff Mitchell -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local max = math.max; local scram_hashers = require "util.sasl.scram".hashers; local usermanager = require "core.usermanager"; local generate_uuid = require "util.uuid".generate; local new_sasl = require "util.sasl".new; local hex = require"util.hex"; local to_hex, from_hex = hex.encode, hex.decode; local saslprep = require "util.encodings".stringprep.saslprep; local secure_equals = require "util.hashes".equals; local log = module._log; local host = module.host; local accounts = module:open_store("accounts"); local hash_name = module:get_option_string("password_hash", "SHA-1"); local get_auth_db = assert(scram_hashers[hash_name], "SCRAM-"..hash_name.." not supported by SASL library"); local scram_name = "scram_"..hash_name:gsub("%-","_"):lower(); -- Default; can be set per-user local default_iteration_count = module:get_option_number("default_iteration_count", 10000); -- define auth provider local provider = {}; function provider.test_password(username, password) log("debug", "test password for user '%s'", username); local credentials = accounts:get(username) or {}; password = saslprep(password); if not password then return nil, "Password fails SASLprep."; end if credentials.password ~= nil and string.len(credentials.password) ~= 0 then if not secure_equals(saslprep(credentials.password), password) then return nil, "Auth failed. Provided password is incorrect."; end if provider.set_password(username, credentials.password) == nil then return nil, "Auth failed. Could not set hashed password from plaintext."; else return true; end end if credentials.iteration_count == nil or credentials.salt == nil or string.len(credentials.salt) == 0 then return nil, "Auth failed. Stored salt and iteration count information is not complete."; end local valid, stored_key, server_key = get_auth_db(password, credentials.salt, credentials.iteration_count); local stored_key_hex = to_hex(stored_key); local server_key_hex = to_hex(server_key); if valid and secure_equals(stored_key_hex, credentials.stored_key) and secure_equals(server_key_hex, credentials.server_key) then return true; else return nil, "Auth failed. Invalid username, password, or password hash information."; end end function provider.set_password(username, password) log("debug", "set_password for username '%s'", username); local account = accounts:get(username); if account then account.salt = generate_uuid(); account.iteration_count = max(account.iteration_count or 0, default_iteration_count); local valid, stored_key, server_key = get_auth_db(password, account.salt, account.iteration_count); if not valid then return valid, stored_key; end local stored_key_hex = to_hex(stored_key); local server_key_hex = to_hex(server_key); account.stored_key = stored_key_hex account.server_key = server_key_hex account.password = nil; return accounts:set(username, account); end return nil, "Account not available."; end function provider.user_exists(username) local account = accounts:get(username); if not account then log("debug", "account not found for username '%s'", username); return nil, "Auth failed. Invalid username"; end return true; end function provider.users() return accounts:users(); end function provider.create_user(username, password) if password == nil then return accounts:set(username, {}); end local salt = generate_uuid(); local valid, stored_key, server_key = get_auth_db(password, salt, default_iteration_count); if not valid then return valid, stored_key; end local stored_key_hex = to_hex(stored_key); local server_key_hex = to_hex(server_key); return accounts:set(username, { stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = default_iteration_count }); end function provider.delete_user(username) return accounts:set(username, nil); end function provider.get_sasl_handler() local testpass_authentication_profile = { plain_test = function(_, username, password, realm) return usermanager.test_password(username, realm, password), true; end, [scram_name] = function(_, username) local credentials = accounts:get(username); if not credentials then return; end if credentials.password then if provider.set_password(username, credentials.password) == nil then return nil, "Auth failed. Could not set hashed password from plaintext."; end credentials = accounts:get(username); if not credentials then return; end end local stored_key, server_key = credentials.stored_key, credentials.server_key; local iteration_count, salt = credentials.iteration_count, credentials.salt; stored_key = stored_key and from_hex(stored_key); server_key = server_key and from_hex(server_key); return stored_key, server_key, iteration_count, salt, true; end }; return new_sasl(host, testpass_authentication_profile); end module:provides("auth", provider); prosody-0.12.4/plugins/PaxHeaders/mod_auth_internal_plain.lua0000644000000000000000000000012014475665104021427 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_auth_internal_plain.lua0000644000175000017500000000466214475665104023644 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local usermanager = require "core.usermanager"; local new_sasl = require "util.sasl".new; local saslprep = require "util.encodings".stringprep.saslprep; local secure_equals = require "util.hashes".equals; local log = module._log; local host = module.host; local accounts = module:open_store("accounts"); -- define auth provider local provider = {}; function provider.test_password(username, password) log("debug", "test password for user '%s'", username); local credentials = accounts:get(username) or {}; password = saslprep(password); if not password then return nil, "Password fails SASLprep."; end if secure_equals(password, saslprep(credentials.password)) then return true; else return nil, "Auth failed. Invalid username or password."; end end function provider.get_password(username) log("debug", "get_password for username '%s'", username); return (accounts:get(username) or {}).password; end function provider.set_password(username, password) log("debug", "set_password for username '%s'", username); password = saslprep(password); if not password then return nil, "Password fails SASLprep."; end local account = accounts:get(username); if account then account.password = password; return accounts:set(username, account); end return nil, "Account not available."; end function provider.user_exists(username) local account = accounts:get(username); if not account then log("debug", "account not found for username '%s'", username); return nil, "Auth failed. Invalid username"; end return true; end function provider.users() return accounts:users(); end function provider.create_user(username, password) password = saslprep(password); if not password then return nil, "Password fails SASLprep."; end return accounts:set(username, {password = password}); end function provider.delete_user(username) return accounts:set(username, nil); end function provider.get_sasl_handler() local getpass_authentication_profile = { plain = function(_, username, realm) local password = usermanager.get_password(username, realm); if not password then return "", nil; end return password, true; end }; return new_sasl(host, getpass_authentication_profile); end module:provides("auth", provider); prosody-0.12.4/plugins/PaxHeaders/mod_auth_ldap.lua0000644000000000000000000000012014475665104017350 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_auth_ldap.lua0000644000175000017500000001111114475665104021550 0ustar00prosodyprosody00000000000000-- mod_auth_ldap local jid_split = require "util.jid".split; local new_sasl = require "util.sasl".new; local lualdap = require "lualdap"; local function ldap_filter_escape(s) return (s:gsub("[*()\\%z]", function(c) return ("\\%02x"):format(c:byte()) end)); end -- Config options local ldap_server = module:get_option_string("ldap_server", "localhost"); local ldap_rootdn = module:get_option_string("ldap_rootdn", ""); local ldap_password = module:get_option_string("ldap_password", ""); local ldap_tls = module:get_option_boolean("ldap_tls"); local ldap_scope = module:get_option_string("ldap_scope", "subtree"); local ldap_filter = module:get_option_string("ldap_filter", "(uid=$user)"):gsub("%%s", "$user", 1); local ldap_base = assert(module:get_option_string("ldap_base"), "ldap_base is a required option for ldap"); local ldap_mode = module:get_option_string("ldap_mode", "bind"); local ldap_admins = module:get_option_string("ldap_admin_filter", module:get_option_string("ldap_admins")); -- COMPAT with mistake in documentation local host = ldap_filter_escape(module:get_option_string("realm", module.host)); -- Initiate connection local ld = nil; module.unload = function() if ld then pcall(ld, ld.close); end end function ldap_do_once(method, ...) if ld == nil then local err; ld, err = lualdap.open_simple(ldap_server, ldap_rootdn, ldap_password, ldap_tls); if not ld then return nil, err, "reconnect"; end end -- luacheck: ignore 411/success local success, iterator, invariant, initial = pcall(ld[method], ld, ...); if not success then ld = nil; return nil, iterator, "search"; end local success, dn, attr = pcall(iterator, invariant, initial); if not success then ld = nil; return success, dn, "iter"; end return dn, attr, "return"; end function ldap_do(method, retry_count, ...) local dn, attr, where; for _=1,1+retry_count do dn, attr, where = ldap_do_once(method, ...); if dn or not(attr) then break; end -- nothing or something found module:log("warn", "LDAP: %s %s (in %s)", tostring(dn), tostring(attr), where); -- otherwise retry end if not dn and attr then module:log("error", "LDAP: %s", tostring(attr)); end return dn, attr; end function get_user(username) module:log("debug", "get_user(%q)", username); return ldap_do("search", 2, { base = ldap_base; scope = ldap_scope; sizelimit = 1; filter = ldap_filter:gsub("%$(%a+)", { user = ldap_filter_escape(username); host = host; }); }); end local provider = {}; function provider.create_user(username, password) -- luacheck: ignore 212 return nil, "Account creation not available with LDAP."; end function provider.user_exists(username) return not not get_user(username); end function provider.set_password(username, password) local dn, attr = get_user(username); if not dn then return nil, attr end if attr.userPassword == password then return true end return ldap_do("modify", 2, dn, { '=', userPassword = password }); end if ldap_mode == "getpasswd" then function provider.get_password(username) local dn, attr = get_user(username); if dn and attr then return attr.userPassword; end end function provider.test_password(username, password) return provider.get_password(username) == password; end function provider.get_sasl_handler() return new_sasl(module.host, { plain = function(sasl, username) -- luacheck: ignore 212/sasl local password = provider.get_password(username); if not password then return "", nil; end return password, true; end }); end elseif ldap_mode == "bind" then local function test_password(userdn, password) local ok, err = lualdap.open_simple(ldap_server, userdn, password, ldap_tls); if not ok then module:log("debug", "ldap open_simple error: %s", err); end return not not ok; end function provider.test_password(username, password) local dn = get_user(username); if not dn then return end return test_password(dn, password) end function provider.get_sasl_handler() return new_sasl(module.host, { plain_test = function(sasl, username, password) -- luacheck: ignore 212/sasl return provider.test_password(username, password), true; end }); end else module:log("error", "Unsupported ldap_mode %s", tostring(ldap_mode)); end if ldap_admins then function provider.is_admin(jid) local username, user_host = jid_split(jid); if user_host ~= module.host then return false; end return ldap_do("search", 2, { base = ldap_base; scope = ldap_scope; sizelimit = 1; filter = ldap_admins:gsub("%$(%a+)", { user = ldap_filter_escape(username); host = host; }); }); end end module:provides("auth", provider); prosody-0.12.4/plugins/PaxHeaders/mod_authz_internal.lua0000644000000000000000000000012014475665104020436 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_authz_internal.lua0000644000175000017500000000315214475665104022644 0ustar00prosodyprosody00000000000000local array = require "util.array"; local it = require "util.iterators"; local set = require "util.set"; local jid_split = require "util.jid".split; local normalize = require "util.jid".prep; local config_admin_jids = module:get_option_inherited_set("admins", {}) / normalize; local host = module.host; local role_store = module:open_store("roles"); local role_map_store = module:open_store("roles", "map"); local admin_role = { ["prosody:admin"] = true }; function get_user_roles(user) if config_admin_jids:contains(user.."@"..host) then return admin_role; end return role_store:get(user); end function set_user_roles(user, roles) role_store:set(user, roles) return true; end function get_users_with_role(role) local storage_role_users = it.to_array(it.keys(role_map_store:get_all(role) or {})); if role == "prosody:admin" then local config_admin_users = config_admin_jids / function (admin_jid) local j_node, j_host = jid_split(admin_jid); if j_host == host then return j_node; end end; return it.to_array(config_admin_users + set.new(storage_role_users)); end return storage_role_users; end function get_jid_roles(jid) if config_admin_jids:contains(jid) then return admin_role; end return nil; end function set_jid_roles(jid) -- luacheck: ignore 212 return false; end function get_jids_with_role(role) -- Fetch role users from storage local storage_role_jids = array.map(get_users_with_role(role), function (username) return username.."@"..host; end); if role == "prosody:admin" then return it.to_array(config_admin_jids + set.new(storage_role_jids)); end return storage_role_jids; end prosody-0.12.4/plugins/PaxHeaders/mod_blocklist.lua0000644000000000000000000000012014475665104017375 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_blocklist.lua0000644000175000017500000002575114475665104021614 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2009-2010 Matthew Wild -- Copyright (C) 2009-2010 Waqas Hussain -- Copyright (C) 2014-2015 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- This module implements XEP-0191: Blocking Command -- local user_exists = require"core.usermanager".user_exists; local rostermanager = require"core.rostermanager"; local is_contact_subscribed = rostermanager.is_contact_subscribed; local is_contact_pending_in = rostermanager.is_contact_pending_in; local load_roster = rostermanager.load_roster; local save_roster = rostermanager.save_roster; local st = require"util.stanza"; local st_error_reply = st.error_reply; local jid_prep = require"util.jid".prep; local jid_split = require"util.jid".split; local storage = module:open_store(); local sessions = prosody.hosts[module.host].sessions; local full_sessions = prosody.full_sessions; -- First level cache of blocklists by username. -- Weak table so may randomly expire at any time. local cache = setmetatable({}, { __mode = "v" }); -- Second level of caching, keeps a fixed number of items, also anchors -- items in the above cache. -- -- The size of this affects how often we will need to load a blocklist from -- disk, which we want to avoid during routing. On the other hand, we don't -- want to use too much memory either, so this can be tuned by advanced -- users. TODO use science to figure out a better default, 64 is just a guess. local cache_size = module:get_option_number("blocklist_cache_size", 64); local cache2 = require"util.cache".new(cache_size); local null_blocklist = {}; module:add_feature("urn:xmpp:blocking"); local function set_blocklist(username, blocklist) local ok, err = storage:set(username, blocklist); if not ok then return ok, err; end -- Successful save, update the cache cache2:set(username, blocklist); cache[username] = blocklist; return true; end -- Migrates from the old mod_privacy storage local function migrate_privacy_list(username) local legacy_data = module:open_store("privacy"):get(username); if not legacy_data or not legacy_data.lists or not legacy_data.default then return; end local default_list = legacy_data.lists[legacy_data.default]; if not default_list or not default_list.items then return; end local migrated_data = { [false] = { created = os.time(); migrated = "privacy" }}; module:log("info", "Migrating blocklist from mod_privacy storage for user '%s'", username); for _, item in ipairs(default_list.items) do if item.type == "jid" and item.action == "deny" then local jid = jid_prep(item.value); if not jid then module:log("warn", "Invalid JID in privacy store for user '%s' not migrated: %s", username, item.value); else migrated_data[jid] = true; end end end set_blocklist(username, migrated_data); return migrated_data; end local function get_blocklist(username) local blocklist = cache2:get(username); if not blocklist then if not user_exists(username, module.host) then return null_blocklist; end blocklist = storage:get(username); if not blocklist then blocklist = migrate_privacy_list(username); end if not blocklist then blocklist = { [false] = { created = os.time(); }; }; end cache2:set(username, blocklist); end cache[username] = blocklist; return blocklist; end module:hook("iq-get/self/urn:xmpp:blocking:blocklist", function (event) local origin, stanza = event.origin, event.stanza; local username = origin.username; local reply = st.reply(stanza):tag("blocklist", { xmlns = "urn:xmpp:blocking" }); local blocklist = cache[username] or get_blocklist(username); for jid in pairs(blocklist) do if jid then reply:tag("item", { jid = jid }):up(); end end origin.interested_blocklist = true; -- Gets notified about changes origin.send(reply); return true; end, -1); -- Add or remove some jid(s) from the blocklist -- We want this to be atomic and not do a partial update local function edit_blocklist(event) local 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" 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 -- > only if the contact is allowed to receive presence notifications [...] -- So contacts we need to do that for are added to the set below. local send_unavailable = is_blocking and {}; local send_available = not is_blocking and {}; -- Because blocking someone currently also blocks the ability to reject -- subscription requests, we'll preemptively reject such local remove_pending = is_blocking and {}; for item in action:childtags("item") do local jid = jid_prep(item.attr.jid); if not jid then origin.send(st_error_reply(stanza, "modify", "jid-malformed")); return true; end item.attr.jid = jid; -- echo back prepped new[jid] = true; if is_blocking then if is_contact_subscribed(username, module.host, jid) then send_unavailable[jid] = true; elseif is_contact_pending_in(username, module.host, jid) then remove_pending[jid] = true; end elseif is_contact_subscribed(username, module.host, jid) then send_available[jid] = true; end end if is_blocking and not next(new) then -- element does not contain at least one child element origin.send(st_error_reply(stanza, "modify", "bad-request")); return true; end local blocklist = cache[username] or get_blocklist(username); local new_blocklist = { -- We set the [false] key to something as a signal not to migrate privacy lists [false] = blocklist[false] or { created = now; }; }; if type(blocklist[false]) == "table" then new_blocklist[false].modified = now; end if is_blocking or next(new) then 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; end -- else empty the blocklist end local ok, err = set_blocklist(username, new_blocklist); if ok then origin.send(st.reply(stanza)); else origin.send(st_error_reply(stanza, "wait", "internal-server-error", err)); return true; end if is_blocking then for jid in pairs(send_unavailable) do -- Check that this JID isn't already blocked, i.e. this is not a change if not blocklist[jid] then for _, session in pairs(sessions[username].sessions) do if session.presence then module:send(st.presence({ type = "unavailable", to = jid, from = session.full_jid })); end end end end if next(remove_pending) then local roster = load_roster(username, module.host); for jid in pairs(remove_pending) do roster[false].pending[jid] = nil; end save_roster(username, module.host, roster); -- Not much we can do about save failing here end else local user_bare = username .. "@" .. module.host; for jid in pairs(send_available) do module:send(st.presence({ type = "probe", to = user_bare, from = jid })); end end local blocklist_push = st.iq({ type = "set", id = "blocklist-push" }) :add_child(action); -- I am lazy for _, session in pairs(sessions[username].sessions) do if session.interested_blocklist then blocklist_push.attr.to = session.full_jid; session.send(blocklist_push); end end return true; end module:hook("iq-set/self/urn:xmpp:blocking:block", edit_blocklist, -1); module:hook("iq-set/self/urn:xmpp:blocking:unblock", edit_blocklist, -1); -- Cache invalidation, solved! module:hook_global("user-deleted", function (event) if event.host == module.host then cache2:set(event.username, nil); cache[event.username] = nil; end end); -- Buggy clients module:hook("iq-error/self/blocklist-push", function (event) local origin, stanza = event.origin, event.stanza; local _, condition, text = stanza:get_error(); local log = (origin.log or module._log); log("warn", "Client returned an error in response to notification from mod_%s: %s%s%s", module.name, condition, text and ": " or "", text or ""); return true; end); local function is_blocked(user, jid) local blocklist = cache[user] or get_blocklist(user); if blocklist[jid] then return true; end local node, host = jid_split(jid); return blocklist[host] or node and blocklist[node..'@'..host]; end -- Event handlers for bouncing or dropping stanzas local function drop_stanza(event) local stanza = event.stanza; local attr = stanza.attr; local to, from = attr.to, attr.from; to = to and jid_split(to); if to and from then return is_blocked(to, from); end end local function bounce_stanza(event) local origin, stanza = event.origin, event.stanza; if drop_stanza(event) then origin.send(st_error_reply(stanza, "cancel", "service-unavailable")); return true; end end local function bounce_iq(event) local type = event.stanza.attr.type; if type == "set" or type == "get" then return bounce_stanza(event); end return drop_stanza(event); -- result or error end local function bounce_message(event) local stanza = event.stanza; local type = stanza.attr.type; if type == "chat" or not type or type == "normal" then if full_sessions[stanza.attr.to] then -- See #690 return drop_stanza(event); end return bounce_stanza(event); end return drop_stanza(event); -- drop headlines, groupchats etc end local function drop_outgoing(event) local origin, stanza = event.origin, event.stanza; local username = origin.username or jid_split(stanza.attr.from); if not username then return end local to = stanza.attr.to; if to then return is_blocked(username, to); end -- nil 'to' means a self event, don't bock those end local function bounce_outgoing(event) local origin, stanza = event.origin, event.stanza; local type = stanza.attr.type; if type == "error" or stanza.name == "iq" and type == "result" then return drop_outgoing(event); end if drop_outgoing(event) then origin.send(st_error_reply(stanza, "cancel", "not-acceptable", "You have blocked this JID") :tag("blocked", { xmlns = "urn:xmpp:blocking:errors" })); return true; end end -- Hook all the events! local prio_in, prio_out = 100, 100; module:hook("presence/bare", drop_stanza, prio_in); module:hook("presence/full", drop_stanza, prio_in); module:hook("message/bare", bounce_message, prio_in); module:hook("message/full", bounce_message, prio_in); module:hook("iq/bare", bounce_iq, prio_in); module:hook("iq/full", bounce_iq, prio_in); module:hook("pre-message/bare", bounce_outgoing, prio_out); module:hook("pre-message/full", bounce_outgoing, prio_out); module:hook("pre-message/host", bounce_outgoing, prio_out); module:hook("pre-presence/bare", bounce_outgoing, -1); module:hook("pre-presence/host", bounce_outgoing, -1); module:hook("pre-presence/full", bounce_outgoing, prio_out); module:hook("pre-iq/bare", bounce_outgoing, prio_out); module:hook("pre-iq/full", bounce_outgoing, prio_out); module:hook("pre-iq/host", bounce_outgoing, prio_out); prosody-0.12.4/plugins/PaxHeaders/mod_bookmarks.lua0000644000000000000000000000012014475665104017377 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.648765383 prosody-0.12.4/plugins/mod_bookmarks.lua0000644000175000017500000004157714475665104021622 0ustar00prosodyprosody00000000000000local mm = require "core.modulemanager"; if mm.get_modules_for_host(module.host):contains("bookmarks2") then error("mod_bookmarks and mod_bookmarks2 are conflicting, please disable one of them.", 0); end local st = require "util.stanza"; local jid_split = require "util.jid".split; local mod_pep = module:depends "pep"; local private_storage = module:open_store("private", "map"); local namespace = "urn:xmpp:bookmarks:1"; local namespace_old = "urn:xmpp:bookmarks:0"; local namespace_private = "jabber:iq:private"; local namespace_legacy = "storage:bookmarks"; local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local default_options = { ["persist_items"] = true; ["max_items"] = "max"; ["send_last_published_item"] = "never"; ["access_model"] = "whitelist"; }; module:hook("account-disco-info", function (event) -- This Time it’s Serious! event.reply:tag("feature", { var = namespace.."#compat" }):up(); event.reply:tag("feature", { var = namespace.."#compat-pep" }):up(); -- COMPAT XEP-0411 event.reply:tag("feature", { var = "urn:xmpp:bookmarks-conversion:0" }):up(); end); -- This must be declared on the domain JID, not the account JID. Note that -- this isn’t defined in the XEP. module:add_feature(namespace_private); local function generate_legacy_storage(items) local storage = st.stanza("storage", { xmlns = namespace_legacy }); for _, item_id in ipairs(items) do local item = items[item_id]; local bookmark = item:get_child("conference", namespace); if not bookmark then module:log("warn", "Invalid bookmark published: expected {%s}conference, got {%s}%s", namespace, item.tags[1] and item.tags[1].attr.xmlns, item.tags[1] and item.tags[1].name); end local conference = st.stanza("conference", { jid = item.attr.id, name = bookmark and bookmark.attr.name, autojoin = bookmark and bookmark.attr.autojoin, }); local nick = bookmark and bookmark:get_child_text("nick"); if nick ~= nil then conference:text_tag("nick", nick):up(); end local password = bookmark and bookmark:get_child_text("password"); if password ~= nil then conference:text_tag("password", password):up(); end storage:add_child(conference); end return storage; end local function on_retrieve_legacy_pep(event) local stanza, session = event.stanza, event.origin; local pubsub = stanza:get_child("pubsub", "http://jabber.org/protocol/pubsub"); if pubsub == nil then return; end local items = pubsub:get_child("items"); if items == nil then return; end local node = items.attr.node; if node ~= namespace_legacy then return; end local username = session.username; local jid = username.."@"..session.host; local service = mod_pep.get_pep_service(username); local ok, ret = service:get_items(namespace, session.full_jid); if not ok then if ret == "item-not-found" then module:log("debug", "Got no PEP bookmarks item for %s, returning empty private bookmarks", jid); else module:log("error", "Failed to retrieve PEP bookmarks of %s: %s", jid, ret); end session.send(st.error_reply(stanza, "cancel", ret, "Failed to retrieve bookmarks from PEP")); return true; end local storage = generate_legacy_storage(ret); module:log("debug", "Sending back legacy PEP for %s: %s", jid, storage); session.send(st.reply(stanza) :tag("pubsub", {xmlns = "http://jabber.org/protocol/pubsub"}) :tag("items", {node = namespace_legacy}) :tag("item", {id = "current"}) :add_child(storage)); return true; end local function on_retrieve_private_xml(event) local stanza, session = event.stanza, event.origin; local query = stanza:get_child("query", namespace_private); if query == nil then return; end local bookmarks = query:get_child("storage", namespace_legacy); if bookmarks == nil then return; end module:log("debug", "Getting private bookmarks: %s", bookmarks); local username = session.username; local jid = username.."@"..session.host; local service = mod_pep.get_pep_service(username); local ok, ret = service:get_items(namespace, session.full_jid); if not ok then if ret == "item-not-found" then module:log("debug", "Got no PEP bookmarks item for %s, returning empty private bookmarks", jid); session.send(st.reply(stanza):add_child(query)); else module:log("error", "Failed to retrieve PEP bookmarks of %s: %s", jid, ret); session.send(st.error_reply(stanza, "cancel", ret, "Failed to retrieve bookmarks from PEP")); end return true; end local storage = generate_legacy_storage(ret); module:log("debug", "Sending back private for %s: %s", jid, storage); session.send(st.reply(stanza):query(namespace_private):add_child(storage)); return true; end local function compare_bookmark2(a, b) if a == nil or b == nil then return false; end local a_conference = a:get_child("conference", namespace); local b_conference = b:get_child("conference", namespace); local a_nick = a_conference:get_child_text("nick"); local b_nick = b_conference:get_child_text("nick"); local a_password = a_conference:get_child_text("password"); local b_password = b_conference:get_child_text("password"); return (a.attr.id == b.attr.id and a_conference.attr.name == b_conference.attr.name and a_conference.attr.autojoin == b_conference.attr.autojoin and a_nick == b_nick and a_password == b_password); end local function publish_to_pep(jid, bookmarks, synchronise) local service = mod_pep.get_pep_service(jid_split(jid)); if #bookmarks.tags == 0 then if synchronise then -- If we set zero legacy bookmarks, purge the bookmarks 2 node. module:log("debug", "No bookmark in the set, purging instead."); return service:purge(namespace, jid, true); else return true; end end -- Retrieve the current bookmarks2. module:log("debug", "Retrieving the current bookmarks 2."); local has_bookmarks2, ret = service:get_items(namespace, jid); local bookmarks2; if not has_bookmarks2 and ret == "item-not-found" then module:log("debug", "Got item-not-found, assuming it was empty until now, creating."); local ok, err = service:create(namespace, jid, default_options); if not ok then module:log("error", "Creating bookmarks 2 node failed: %s", err); return ok, err; end bookmarks2 = {}; elseif not has_bookmarks2 then module:log("debug", "Got %s error, aborting.", ret); return false, ret; else module:log("debug", "Got existing bookmarks2."); bookmarks2 = ret; local ok, err = service:get_node_config(namespace, jid); if not ok then module:log("error", "Retrieving bookmarks 2 node config failed: %s", err); return ok, err; end local options = err; for key, value in pairs(default_options) do if options[key] and options[key] ~= value then module:log("warn", "Overriding bookmarks 2 configuration for %s, from %s to %s", jid, options[key], value); options[key] = value; end end local ok, err = service:set_node_config(namespace, jid, options); if not ok then module:log("error", "Setting bookmarks 2 node config failed: %s", err); return ok, err; end end -- Get a list of all items we may want to remove. local to_remove = {}; for i in ipairs(bookmarks2) do to_remove[bookmarks2[i]] = true; end for bookmark in bookmarks:childtags("conference", namespace_legacy) do -- Create the new conference element by copying everything from the legacy one. local conference = st.stanza("conference", { xmlns = namespace, name = bookmark.attr.name, autojoin = bookmark.attr.autojoin, }); local nick = bookmark:get_child_text("nick"); if nick ~= nil then conference:text_tag("nick", nick):up(); end local password = bookmark:get_child_text("password"); if password ~= nil then conference:text_tag("password", password):up(); end -- Create its wrapper. local item = st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = bookmark.attr.jid }) :add_child(conference); -- Then publish it only if it’s a new one or updating a previous one. if compare_bookmark2(item, bookmarks2[bookmark.attr.jid]) then module:log("debug", "Item %s identical to the previous one, skipping.", item.attr.id); to_remove[bookmark.attr.jid] = nil; else if bookmarks2[bookmark.attr.jid] == nil then module:log("debug", "Item %s not existing previously, publishing.", item.attr.id); else module:log("debug", "Item %s different from the previous one, publishing.", item.attr.id); to_remove[bookmark.attr.jid] = nil; end local ok, err = service:publish(namespace, jid, bookmark.attr.jid, item, default_options); if not ok then module:log("error", "Publishing item %s failed: %s", item.attr.id, err); return ok, err; end end end -- Now handle retracting items that have been removed. if synchronise then for id in pairs(to_remove) do module:log("debug", "Item %s removed from bookmarks.", id); local ok, err = service:retract(namespace, jid, id, st.stanza("retract", { id = id })); if not ok then module:log("error", "Retracting item %s failed: %s", id, err); return ok, err; end end end return true; end -- Synchronise legacy PEP to PEP. local function on_publish_legacy_pep(event) local stanza, session = event.stanza, event.origin; local pubsub = stanza:get_child("pubsub", "http://jabber.org/protocol/pubsub"); if pubsub == nil then return; end local publish = pubsub:get_child("publish"); if publish == nil then return end if publish.attr.node == namespace_old then session.send(st.error_reply(stanza, "modify", "not-allowed", "Your client does XEP-0402 version 0.3.0 but 0.4.0+ is required")); return true; end if publish.attr.node ~= namespace_legacy then return; end local item = publish:get_child("item"); if item == nil then return; end -- Here we ignore the item id, it’ll be generated as 'current' anyway. local bookmarks = item:get_child("storage", namespace_legacy); if bookmarks == nil then return; end -- We also ignore the publish-options. module:log("debug", "Legacy PEP bookmarks set by client, publishing to PEP."); local ok, err = publish_to_pep(session.full_jid, bookmarks, true); if not ok then module:log("error", "Failed to publish to PEP bookmarks for %s@%s: %s", session.username, session.host, err); session.send(st.error_reply(stanza, "cancel", "internal-server-error", "Failed to store bookmarks to PEP")); return true; end session.send(st.reply(stanza)); return true; end -- Synchronise Private XML to PEP. local function on_publish_private_xml(event) local stanza, session = event.stanza, event.origin; local query = stanza:get_child("query", namespace_private); if query == nil then return; end local bookmarks = query:get_child("storage", namespace_legacy); if bookmarks == nil then return; end module:log("debug", "Private bookmarks set by client, publishing to PEP."); local ok, err = publish_to_pep(session.full_jid, bookmarks, true); if not ok then module:log("error", "Failed to publish to PEP bookmarks for %s@%s: %s", session.username, session.host, err); session.send(st.error_reply(stanza, "cancel", "internal-server-error", "Failed to store bookmarks to PEP")); return true; end session.send(st.reply(stanza)); return true; end local function migrate_legacy_bookmarks(event) local session = event.session; local username = session.username; local service = mod_pep.get_pep_service(username); local jid = username.."@"..session.host; local ok, ret = service:get_items(namespace_legacy, session.full_jid); if ok and ret[1] then module:log("debug", "Legacy PEP bookmarks found for %s, migrating.", jid); local failed = false; for _, item_id in ipairs(ret) do local item = ret[item_id]; if item.attr.id ~= "current" then module:log("warn", "Legacy PEP bookmarks for %s isn’t using 'current' as its id: %s", jid, item.attr.id); end local bookmarks = item:get_child("storage", namespace_legacy); module:log("debug", "Got legacy PEP bookmarks of %s: %s", jid, bookmarks); local ok, err = publish_to_pep(session.full_jid, bookmarks, false); if not ok then module:log("error", "Failed to store legacy PEP bookmarks to bookmarks 2 for %s, aborting migration: %s", jid, err); failed = true; break; end end if not failed then module:log("debug", "Successfully migrated legacy PEP bookmarks of %s to bookmarks 2, clearing items.", jid); local ok, err = service:purge(namespace_legacy, jid, false); if not ok then module:log("error", "Failed to delete legacy PEP bookmarks for %s: %s", jid, err); end end end local ok, current_legacy_config = service:get_node_config(namespace_legacy, jid); if not ok or current_legacy_config["access_model"] ~= "whitelist" then -- The legacy node must exist in order for the access model to apply to the -- XEP-0411 COMPAT broadcasts (which bypass the pubsub service entirely), -- so create or reconfigure it to be useless. -- -- FIXME It would be handy to have a publish model that prevents the owner -- from publishing, but the affiliation takes priority local config = { ["persist_items"] = false; ["max_items"] = 1; ["send_last_published_item"] = "never"; ["access_model"] = "whitelist"; }; local ok, err; if ret == "item-not-found" then ok, err = service:create(namespace_legacy, jid, config); else ok, err = service:set_node_config(namespace_legacy, jid, config); end if not ok then module:log("error", "Setting legacy bookmarks node config failed: %s", err); return ok, err; end end local data, err = private_storage:get(username, "storage:storage:bookmarks"); if not data then module:log("debug", "No existing legacy bookmarks for %s, migration already done: %s", jid, err); local ok, ret2 = service:get_items(namespace, session.full_jid); if not ok or not ret2 then module:log("debug", "Additionally, no bookmarks 2 were existing for %s, assuming empty.", jid); module:fire_event("bookmarks/empty", { session = session }); end return; end local bookmarks = st.deserialize(data); module:log("debug", "Got legacy bookmarks of %s: %s", jid, bookmarks); module:log("debug", "Going to store legacy bookmarks to bookmarks 2 %s.", jid); local ok, err = publish_to_pep(session.full_jid, bookmarks, false); if not ok then module:log("error", "Failed to store legacy bookmarks to bookmarks 2 for %s, aborting migration: %s", jid, err); return; end module:log("debug", "Stored legacy bookmarks to bookmarks 2 for %s.", jid); local ok, err = private_storage:set(username, "storage:storage:bookmarks", nil); if not ok then module:log("error", "Failed to remove legacy bookmarks of %s: %s", jid, err); return; end module:log("debug", "Removed legacy bookmarks of %s, migration done!", jid); end module:hook("iq/bare/jabber:iq:private:query", function (event) if event.stanza.attr.type == "get" then return on_retrieve_private_xml(event); else return on_publish_private_xml(event); end end, 1); module:hook("iq/bare/http://jabber.org/protocol/pubsub:pubsub", function (event) if event.stanza.attr.type == "get" then return on_retrieve_legacy_pep(event); else return on_publish_legacy_pep(event); end end, 1); if module:get_option_boolean("upgrade_legacy_bookmarks", true) then module:hook("resource-bind", migrate_legacy_bookmarks); end -- COMPAT XEP-0411 Broadcast as per XEP-0048 + PEP local function legacy_broadcast(event) local service = event.service; local ok, bookmarks = service:get_items(namespace, event.actor); if bookmarks == "item-not-found" then ok, bookmarks = true, {} end if not ok then return end local legacy_bookmarks_item = st.stanza("item", { xmlns = xmlns_pubsub; id = "current" }) :add_child(generate_legacy_storage(bookmarks)); service:broadcast("items", namespace_legacy, { --[[ no subscribers ]] }, legacy_bookmarks_item, event.actor); end local function broadcast_legacy_removal(event) if event.node ~= namespace then return end return legacy_broadcast(event); end module:hook("presence/initial", function (event) -- Broadcasts to all clients with legacy+notify, not just the one coming online. -- Upgrade to XEP-0402 to avoid it local service = mod_pep.get_pep_service(event.origin.username); legacy_broadcast({ service = service, actor = event.origin.full_jid }); end); module:handle_items("pep-service", function (event) local service = event.item.service; module:hook_object_event(service.events, "item-published/" .. namespace, legacy_broadcast); module:hook_object_event(service.events, "item-retracted", broadcast_legacy_removal); module:hook_object_event(service.events, "node-purged", broadcast_legacy_removal); module:hook_object_event(service.events, "node-deleted", broadcast_legacy_removal); end, function (event) local service = event.item.service; module:unhook_object_event(service.events, "item-published/" .. namespace, legacy_broadcast); module:unhook_object_event(service.events, "item-retracted", broadcast_legacy_removal); module:unhook_object_event(service.events, "node-purged", broadcast_legacy_removal); module:unhook_object_event(service.events, "node-deleted", broadcast_legacy_removal); end, true); prosody-0.12.4/plugins/PaxHeaders/mod_bosh.lua0000644000000000000000000000012014475665104016342 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.652765344 prosody-0.12.4/plugins/mod_bosh.lua0000644000175000017500000004757114475665104020565 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local new_xmpp_stream = require "util.xmppstream".new; local sm = require "core.sessionmanager"; local sm_destroy_session = sm.destroy_session; local new_uuid = require "util.uuid".generate; local core_process_stanza = prosody.core_process_stanza; local st = require "util.stanza"; local logger = require "util.logger"; local log = module._log; local initialize_filters = require "util.filters".initialize; local math_min = math.min; 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"; local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send) local stream_callbacks = { stream_ns = xmlns_bosh, stream_tag = "body", default_ns = "jabber:client" }; -- These constants are implicitly assumed within the code, and cannot be changed local BOSH_HOLD = 1; local BOSH_MAX_REQUESTS = 2; -- The number of seconds a BOSH session should remain open with no requests local bosh_max_inactivity = module:get_option_number("bosh_max_inactivity", 60); -- The minimum amount of time between requests with no payload local bosh_max_polling = module:get_option_number("bosh_max_polling", 5); -- The maximum amount of time that the server will hold onto a request before replying -- (the client can set this to a lower value when it connects, if it chooses) local bosh_max_wait = module:get_option_number("bosh_max_wait", 120); local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); local cross_domain = module:get_option("cross_domain_bosh"); local stanza_size_limit = module:get_option_number("c2s_stanza_size_limit", 1024*256); if cross_domain ~= nil then module:log("info", "The 'cross_domain_bosh' option has been deprecated"); end local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; -- All sessions, and sessions that have no requests open local sessions = module:shared("sessions"); local measure_active = module:measure("active_sessions", "amount"); local measure_inactive = module:measure("inactive_sessions", "amount"); local report_bad_host = module:measure("bad_host", "rate"); local report_bad_sid = module:measure("bad_sid", "rate"); local report_new_sid = module:measure("new_sid", "rate"); local report_timeout = module:measure("timeout", "rate"); module:hook("stats-update", function () local active = 0; local inactive = 0; for _, session in pairs(sessions) do if #session.requests > 0 then active = active + 1; else inactive = inactive + 1; end end measure_active(active); measure_inactive(inactive); end); -- Used to respond to idle sessions (those with waiting requests) function on_destroy_request(request) log("debug", "Request destroyed: %s", request); local session = sessions[request.context.sid]; if session then local requests = session.requests; for i, r in ipairs(requests) do if r == request then t_remove(requests, i); break; end end -- If this session now has no requests open, mark it as inactive local max_inactive = session.bosh_max_inactive; if max_inactive and #requests == 0 then if session.inactive_timer then session.inactive_timer:stop(); end session.inactive_timer = module:add_timer(max_inactive, session_timeout, 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 session_timeout(now, session, context, reason) -- luacheck: ignore 212/now if not session.destroyed then report_timeout(); sessions[context.sid] = nil; sm_destroy_session(session, reason); end end function handle_POST(event) log("debug", "Handling new request %s: %s\n----------", event.request, event.request.body); local request, response = event.request, event.response; response.on_destroy = on_destroy_request; local body = request.body; local context = { request = request, response = response, notopen = true }; local stream = new_xmpp_stream(context, stream_callbacks, stanza_size_limit); response.context = context; local headers = response.headers; headers.content_type = "text/xml; charset=utf-8"; -- stream:feed() calls the stream_callbacks, so all stanzas in -- the body are processed in this next line before it returns. -- In particular, the streamopened() stream callback is where -- much of the session logic happens, because it's where we first -- get to see the 'sid' of this request. local ok, err = stream:feed(body); if not ok then module:log("warn", "Error parsing BOSH payload; %s", err) local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "bad-request" }); return tostring(close_reply); end -- Stanzas (if any) in the request have now been processed, and -- we take care of the high-level BOSH logic here, including -- giving a response or putting the request "on hold". local session = sessions[context.sid]; if session then -- Session was marked as inactive, since we have -- a request open now, unmark it 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; log("debug", "Session %s has %d out of %d requests open", context.sid, #r, BOSH_HOLD); log("debug", "and there are %d things in the send_buffer:", #session.send_buffer); if #r > BOSH_HOLD then -- We are holding too many requests, send what's in the buffer, log("debug", "We are holding too many requests, so..."); if #session.send_buffer > 0 then log("debug", "...sending what is in the buffer") session.send(t_concat(session.send_buffer)); session.send_buffer = {}; else -- or an empty response log("debug", "...sending an empty response"); session.send(""); end elseif #session.send_buffer > 0 then log("debug", "Session has data in the send buffer, will send now.."); local resp = t_concat(session.send_buffer); session.send_buffer = {}; session.send(resp); end if not response.finished then -- We're keeping this request open, to respond later log("debug", "Have nothing to say, so leaving request unanswered for now"); end if session.bosh_terminate then session.log("debug", "Closing session with %d requests open", #session.requests); 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 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?)"); report_bad_sid(); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "item-not-found" }); return tostring(close_reply) .. "\n"; end 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: %s", (reason and reason.condition or reason) or "session close"); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams }); if reason then close_reply.attr.condition = "remote-stream-error"; if type(reason) == "string" then -- assume stream error close_reply:tag("stream:error") :tag(reason, {xmlns = xmlns_xmpp_streams}); elseif st.is_stanza(reason) then close_reply = reason; elseif type(reason) == "table" then if reason.condition then close_reply:tag("stream:error") :tag(reason.condition, stream_xmlns_attr):up(); if reason.text then close_reply:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then close_reply:add_child(reason.extra); end end end log("info", "Disconnecting client, is: %s", close_reply); end local response_body = tostring(close_reply); for _, held_request in ipairs(session.requests) do held_request:send(response_body); end sessions[session.sid] = 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, 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 if not attr.to then log("debug", "BOSH client tried to connect without specifying a host"); report_bad_host(); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); response:send(tostring(close_reply)); return; end local to_host = nameprep(attr.to); if not to_host then log("debug", "BOSH client tried to connect to invalid host: %s", attr.to); report_bad_host(); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); response:send(tostring(close_reply)); return; end if not prosody.hosts[to_host] then log("debug", "BOSH client tried to connect to non-existent host: %s", attr.to); report_bad_host(); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); response:send(tostring(close_reply)); return; end if prosody.hosts[to_host].type ~= "local" then log("debug", "BOSH client tried to connect to %s host: %s", prosody.hosts[to_host].type, attr.to); report_bad_host(); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); response:send(tostring(close_reply)); return; end local wait = tonumber(attr.wait); if not rid or (not attr.wait or not wait or wait < 0 or wait % 1 ~= 0) then log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", attr.rid, attr.wait); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "bad-request" }); response:send(tostring(close_reply)); return; end wait = math_min(wait, bosh_max_wait); -- New session sid = new_uuid(); -- TODO use util.session local session = { 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_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 = 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); report_new_sid(); module:fire_event("bosh-session", { session = session, request = request }); -- Send creation response local creating_session = true; local r = session.requests; function session.send(s) -- We need to ensure that outgoing stanzas have the jabber:client xmlns if s.attr and not s.attr.xmlns then s = st.clone(s); s.attr.xmlns = "jabber:client"; end s = filter("stanzas/out", s); --log("debug", "Sending BOSH data: %s", s); if not s then return true end t_insert(session.send_buffer, tostring(s)); local oldest_request = r[1]; if oldest_request and not session.bosh_processing then log("debug", "We have an open request, so sending on that"); local body_attr = { xmlns = "http://jabber.org/protocol/httpbind", ["xmlns:stream"] = "http://etherx.jabber.org/streams"; type = session.bosh_terminate and "terminate" or nil; sid = sid; }; if creating_session then creating_session = nil; body_attr.requests = tostring(BOSH_MAX_REQUESTS); body_attr.hold = tostring(BOSH_HOLD); body_attr.inactivity = tostring(bosh_max_inactivity); body_attr.polling = tostring(bosh_max_polling); body_attr.wait = tostring(session.bosh_wait); body_attr.authid = sid; body_attr.secure = "true"; body_attr.ver = '1.6'; body_attr.from = session.host; body_attr["xmlns:xmpp"] = "urn:xmpp:xbosh"; body_attr["xmpp:version"] = "1.0"; end local response_xml = st.stanza("body", body_attr):top_tag()..t_concat(session.send_buffer)..""; session.bosh_responses[oldest_request.context.rid] = response_xml; oldest_request:send(response_xml); session.send_buffer = {}; end return true; end request.sid = sid; end local session = sessions[sid]; if not session then -- Unknown sid log("info", "Client tried to use sid '%s' which we don't know about", sid); report_bad_sid(); response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); context.notopen = nil; return; end session.conn = request.conn; if session.rid then local diff = rid - session.rid; -- Diff should be 1 for a healthy request session.log("debug", "rid: %d, sess: %s, diff: %d", rid, session.rid, diff) if diff ~= 1 then context.sid = sid; context.notopen = nil; if diff == 2 then -- Missed a request -- Hold request, but don't process it (ouch!) session.log("debug", "rid skipped: %d, deferring this request", rid-1) context.defer = true; session.bosh_deferred = { context = context, sid = sid, rid = rid, terminate = attr.type == "terminate" }; return; end -- Set a marker to indicate that stanzas in this request should NOT be processed -- (these stanzas will already be in the XML parser's buffer) context.ignore = true; if session.bosh_responses[rid] then -- Re-send past response, ignore stanzas in this request session.log("debug", "rid repeated within window, replaying old response"); response:send(session.bosh_responses[rid]); return; elseif diff == 0 then session.log("debug", "current rid repeated, ignoring stanzas"); t_insert(session.requests, response); context.sid = sid; return; end -- Session broken, destroy it session.log("debug", "rid out of range: %d (diff %d)", rid, diff); response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); return; end session.rid = rid; end if attr.type == "terminate" then -- Client wants to end this session, which we'll do -- after processing any stanzas in this request session.bosh_terminate = true; end context.notopen = nil; -- Signals that we accept this opening tag t_insert(session.requests, response); context.sid = sid; session.bosh_processing = true; -- Used to suppress replies until processing of this request is done if session.notopen then local features = st.stanza("stream:features"); module:context(session.host):fire_event("stream-features", { origin = session, features = features }); session.send(features); session.notopen = nil; end end local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(err, 2)); end function runner_callbacks:error(err) -- luacheck: ignore 212/self return handleerr(err); end function stream_callbacks.handlestanza(context, stanza) if context.ignore then return; end log("debug", "BOSH stanza received: %s\n", stanza:top_tag()); local session = sessions[context.sid]; if session then if stanza.attr.xmlns == xmlns_bosh then stanza.attr.xmlns = nil; end if context.defer and session.bosh_deferred then log("debug", "Deferring this stanza"); t_insert(session.bosh_deferred, stanza); else stanza = session.filter("stanzas/in", stanza); session.thread:run(stanza); end else log("debug", "No session for this stanza! (sid: %s)", context.sid or "none!"); end end function stream_callbacks.streamclosed(context) local session = sessions[context.sid]; if session then if not context.defer and session.bosh_deferred then -- Handle deferred stanzas now local deferred_stanzas = session.bosh_deferred; local deferred_context = deferred_stanzas.context; session.bosh_deferred = nil; log("debug", "Handling deferred stanzas from rid %d", deferred_stanzas.rid); session.rid = deferred_stanzas.rid; t_insert(session.requests, deferred_context.response); for _, stanza in ipairs(deferred_stanzas) do stream_callbacks.handlestanza(deferred_context, stanza); end if deferred_stanzas.terminate then session.bosh_terminate = true; end end session.bosh_processing = false; if #session.send_buffer > 0 then session.send(""); end end end function stream_callbacks.error(context, error) if not context.sid then log("debug", "Error parsing BOSH request payload; %s", error); local response = context.response; local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "bad-request" }); response:send(tostring(close_reply)); return; end local session = sessions[context.sid]; (session and session.log or log)("warn", "Error parsing BOSH request payload; %s", error); if error == "stream-error" then -- Remote stream error, we close normally session:close(); else session:close({ condition = "bad-format", text = "Error processing stream" }); end end local function GET_response(event) return module:fire_event("http-message", { response = event.response; --- title = "Prosody BOSH endpoint"; message = "It works! Now point your BOSH client to this URL to connect to Prosody."; warning = not (consider_bosh_secure or event.request.secure) and "This endpoint is not considered secure!" or nil; --

For more information see Prosody: Setting up BOSH.

}) or "This is the Prosody BOSH endpoint."; end function module.add_host(module) module:depends("http"); module:provides("http", { default_path = "/http-bind"; cors = { enabled = true; }; route = { ["GET"] = GET_response; ["GET /"] = GET_response; ["POST"] = handle_POST; ["POST /"] = handle_POST; }; }); end if require"core.modulemanager".get_modules_for_host("*"):contains(module.name) then module:add_host(); end prosody-0.12.4/plugins/PaxHeaders/mod_c2s.lua0000644000000000000000000000012014475665104016076 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.652765344 prosody-0.12.4/plugins/mod_c2s.lua0000644000175000017500000003621514475665104020312 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local add_task = require "util.timer".add_task; local new_xmpp_stream = require "util.xmppstream".new; local nameprep = require "util.encodings".stringprep.nameprep; local sessionmanager = require "core.sessionmanager"; local statsmanager = require "core.statsmanager"; local st = require "util.stanza"; local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; local uuid_generate = require "util.uuid".generate; local async = require "util.async"; local runner = async.runner; local tostring, type = tostring, type; local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; local log = module._log; local c2s_timeout = module:get_option_number("c2s_timeout", 300); local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5); local opt_keepalives = module:get_option_boolean("c2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true)); local stanza_size_limit = module:get_option_number("c2s_stanza_size_limit", 1024*256); local measure_connections = module:metric("gauge", "connections", "", "Established c2s connections", {"host", "type", "ip_family"}); local sessions = module:shared("sessions"); local core_process_stanza = prosody.core_process_stanza; local hosts = prosody.hosts; local stream_callbacks = { default_ns = "jabber:client" }; local listener = {}; local runner_callbacks = {}; local m_tls_params = module:metric( "counter", "encrypted", "", "Encrypted connections", {"protocol"; "cipher"} ); module:hook("stats-update", function () -- for push backends, avoid sending out updates for each increment of -- the metric below. statsmanager.cork() measure_connections:clear() for _, session in pairs(sessions) do local host = session.host or "" local type_ = session.type or "other" -- we want to expose both v4 and v6 counters in all cases to make -- queries smoother local is_ipv6 = session.ip and session.ip:match(":") and 1 or 0 local is_ipv4 = 1 - is_ipv6 measure_connections:with_labels(host, type_, "ipv4"):add(is_ipv4) measure_connections:with_labels(host, type_, "ipv6"):add(is_ipv6) end statsmanager.uncork() end); --- Stream events handlers local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; function stream_callbacks.streamopened(session, attr) -- run _streamopened in async context session.thread:run({ stream = "opened", attr = attr }); end function stream_callbacks._streamopened(session, attr) local send = session.send; if not attr.to then session:close{ condition = "improper-addressing", text = "A 'to' attribute is required on stream headers" }; return; end local host = nameprep(attr.to); if not host then session:close{ condition = "improper-addressing", text = "A valid 'to' attribute is required on stream headers" }; return; end if not session.host then session.host = host; elseif session.host ~= host then session:close{ condition = "not-authorized", text = "The 'to' attribute must remain the same across stream restarts" }; return; end session.version = tonumber(attr.version) or 0; session.streamid = uuid_generate(); (session.log or log)("debug", "Client sent opening to %s", session.host); if not hosts[session.host] or not hosts[session.host].modules.c2s then -- We don't serve this host... session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)}; return; end session:open_stream(host, attr.from); -- Opening the stream can cause the stream to be closed if session.destroyed then return end (session.log or log)("debug", "Sent reply to client"); session.notopen = nil; -- If session.secure is *false* (not nil) then it means we /were/ encrypting -- since we now have a new stream header, session is secured if session.secure == false then session.secure = true; session.encrypted = true; local sock = session.conn:socket(); local info = sock.info and sock:info(); if type(info) == "table" then (session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher); session.compressed = info.compression; m_tls_params:with_labels(info.protocol, info.cipher):add(1) else (session.log or log)("info", "Stream encrypted"); end end local features = st.stanza("stream:features"); hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); if features.tags[1] or session.full_jid then send(features); else if session.secure then -- Here SASL should be offered (session.log or log)("warn", "No stream features to offer on secure session. Check authentication settings."); else -- Normally STARTTLS would be offered (session.log or log)("warn", "No stream features to offer on insecure session. Check encryption and security settings."); end session:close{ condition = "undefined-condition", text = "No stream features to proceed with" }; end end function stream_callbacks.streamclosed(session, attr) -- run _streamclosed in async context session.thread:run({ stream = "closed", attr = attr }); end function stream_callbacks._streamclosed(session) session.log("debug", "Received "); session:close(false); end function stream_callbacks.error(session, error, data) if error == "no-stream" then session.log("debug", "Invalid opening stream header (%s)", (data:gsub("^([^\1]+)\1", "{%1}"))); session:close("invalid-namespace"); elseif error == "parse-error" then (session.log or log)("debug", "Client XML parse error: %s", data); session:close("not-well-formed"); elseif error == "stream-error" then local condition, text = "undefined-condition"; for child in data:childtags(nil, xmlns_xmpp_streams) do if child.name ~= "text" then condition = child.name; else text = child:get_text(); end if condition ~= "undefined-condition" and text then break; end end text = condition .. (text and (" ("..text..")") or ""); session.log("info", "Session closed by remote with error: %s", text); session:close(nil, text); end end function stream_callbacks.handlestanza(session, stanza) stanza = session.filter("stanzas/in", stanza); session.thread:run(stanza); end --- Session methods local function session_close(session, reason) local log = session.log or log; local close_event_payload = { session = session, reason = reason }; module:context(session.host):fire_event("pre-session-close", close_event_payload); reason = close_event_payload.reason; if session.conn then if session.notopen then session:open_stream(); end if reason then -- nil == no err, initiated by us, false == initiated by client local stream_error = st.stanza("stream:error"); if type(reason) == "string" then -- assume stream error stream_error:tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); elseif st.is_stanza(reason) then stream_error = reason; elseif type(reason) == "table" then if reason.condition then stream_error:tag(reason.condition, stream_xmlns_attr):up(); if reason.text then stream_error:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then stream_error:add_child(reason.extra); end end end stream_error = tostring(stream_error); log("debug", "Disconnecting client, is: %s", stream_error); session.send(stream_error); end session.send(""); function session.send() return false; end local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason; session.log("debug", "c2s stream for %s closed: %s", session.full_jid or session.ip or "", reason_text or "session closed"); -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote local conn = session.conn; if reason_text == nil and not session.notopen and session.type == "c2s" then -- Grace time to process data from authenticated cleanly-closed stream add_task(stream_close_timeout, function () if not session.destroyed then session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); sm_destroy_session(session, reason_text); if conn then conn:close(); end end end); else sm_destroy_session(session, reason_text); if conn then conn:close(); end end else local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason; sm_destroy_session(session, reason_text); end end -- Close all user sessions with the specified reason. If leave_resource is -- true, the resource named by event.resource will not be closed. local function disconnect_user_sessions(reason, leave_resource) return function (event) local username, host, resource = event.username, event.host, event.resource; local user = hosts[host].sessions[username]; if user and user.sessions then for r, session in pairs(user.sessions) do if not leave_resource or r ~= resource then session:close(reason); end end end end end module:hook_global("user-password-changed", disconnect_user_sessions({ condition = "reset", text = "Password changed" }, true), 200); module:hook_global("user-roles-changed", disconnect_user_sessions({ condition = "reset", text = "Roles changed" }), 200); module:hook_global("user-deleted", disconnect_user_sessions({ condition = "not-authorized", text = "Account deleted" }), 200); function runner_callbacks:ready() if self.data.conn then self.data.conn:resume(); else (self.data.log or log)("debug", "Session has no connection to resume"); end end function runner_callbacks:waiting() if self.data.conn then self.data.conn:pause(); else (self.data.log or log)("debug", "Session has no connection to pause while waiting"); end end function runner_callbacks:error(err) (self.data.log or log)("error", "Traceback[c2s]: %s", err); end --- Port listener function listener.onconnect(conn) local session = sm_new_session(conn); session.log("info", "Client connected"); -- Client is using Direct TLS or legacy SSL (otherwise mod_tls sets this flag) if conn:ssl() then session.secure = true; session.encrypted = true; -- Check if TLS compression is used local sock = conn:socket(); local info = sock.info and sock:info(); if type(info) == "table" then (session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher); session.compressed = info.compression; m_tls_params:with_labels(info.protocol, info.cipher):add(1) else (session.log or log)("info", "Stream encrypted"); end end if opt_keepalives then conn:setoption("keepalive", opt_keepalives); end session.close = session_close; local stream = new_xmpp_stream(session, stream_callbacks, stanza_size_limit); session.stream = stream; session.notopen = true; function session.reset_stream() session.notopen = true; session.stream:reset(); end session.thread = runner(function (stanza) if st.is_stanza(stanza) then core_process_stanza(session, stanza); elseif stanza.stream == "opened" then stream_callbacks._streamopened(session, stanza.attr); elseif stanza.stream == "closed" then stream_callbacks._streamclosed(session, stanza.attr); end end, runner_callbacks, session); local filter = session.filter; function session.data(data) -- Parse the data, which will store stanzas in session.pending_stanzas if data then data = filter("bytes/in", data); if data then local ok, err = stream:feed(data); if not ok then log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); if err == "stanza-too-large" then session:close({ condition = "policy-violation", text = "XML stanza is too big", extra = st.stanza("stanza-too-big", { xmlns = 'urn:xmpp:errors' }), }); else session:close("not-well-formed"); end end end end end if c2s_timeout then add_task(c2s_timeout, function () if session.type == "c2s_unauthed" then (session.log or log)("debug", "Connection still not authenticated after c2s_timeout=%gs, closing it", c2s_timeout); session:close("connection-timeout"); end end); end session.dispatch_stanza = stream_callbacks.handlestanza; sessions[conn] = session; end function listener.onincoming(conn, data) local session = sessions[conn]; if session then session.data(data); end end function listener.ondisconnect(conn, err) local session = sessions[conn]; if session then (session.log or log)("info", "Client disconnected: %s", err or "connection closed"); sm_destroy_session(session, err); session.conn = nil; sessions[conn] = nil; end module:fire_event("c2s-closed", { session = session; conn = conn }); end function listener.onreadtimeout(conn) local session = sessions[conn]; if session then return (hosts[session.host] or prosody).events.fire_event("c2s-read-timeout", { session = session }); end end function listener.ondrain(conn) local session = sessions[conn]; if session then return (hosts[session.host] or prosody).events.fire_event("c2s-ondrain", { session = session }); end end function listener.onpredrain(conn) local session = sessions[conn]; if session then return (hosts[session.host] or prosody).events.fire_event("c2s-pre-ondrain", { session = session }); end end local function keepalive(event) local session = event.session; if not session.notopen then return event.session.send(' '); end end function listener.associate_session(conn, session) sessions[conn] = session; end function module.add_host(module) module:hook("c2s-read-timeout", keepalive, -1); end module:hook("c2s-read-timeout", keepalive, -1); module:hook("server-stopping", function(event) -- luacheck: ignore 212/event -- Close ports local pm = require "core.portmanager"; for _, netservice in pairs(module.items["net-provider"]) do pm.unregister_service(netservice.name, netservice); end end, -80); module:hook("server-stopping", function(event) local wait, done = async.waiter(1, true); module:hook("c2s-closed", function () if next(sessions) == nil then done(); end end) -- Close sessions local reason = event.reason; for _, session in pairs(sessions) do session:close{ condition = "system-shutdown", text = reason }; end -- Wait for them to close properly if they haven't already if next(sessions) ~= nil then add_task(stream_close_timeout+1, function () done() end); module:log("info", "Waiting for sessions to close"); wait(); end end, -100); module:provides("net", { name = "c2s"; listener = listener; default_port = 5222; encryption = "starttls"; multiplex = { protocol = "xmpp-client"; pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>"; }; }); module:provides("net", { name = "c2s_direct_tls"; listener = listener; encryption = "ssl"; multiplex = { pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>"; }; }); -- COMPAT module:provides("net", { name = "legacy_ssl"; listener = listener; encryption = "ssl"; multiplex = { pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>"; }; }); prosody-0.12.4/plugins/PaxHeaders/mod_carbons.lua0000644000000000000000000000012014475665104017036 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.652765344 prosody-0.12.4/plugins/mod_carbons.lua0000644000175000017500000001264614475665104021254 0ustar00prosodyprosody00000000000000-- XEP-0280: Message Carbons implementation for Prosody -- Copyright (C) 2011-2016 Kim Alvefur -- -- This file is MIT/X11 licensed. local st = require "util.stanza"; local jid_bare = require "util.jid".bare; local jid_resource = require "util.jid".resource; local xmlns_carbons = "urn:xmpp:carbons:2"; local xmlns_forward = "urn:xmpp:forward:0"; local full_sessions, bare_sessions = prosody.full_sessions, prosody.bare_sessions; module:add_feature("urn:xmpp:carbons:rules:0"); local function is_bare(jid) return not jid_resource(jid); end local function toggle_carbons(event) local origin, stanza = event.origin, event.stanza; local state = stanza.tags[1].name; module:log("debug", "%s %sd carbons", origin.full_jid, state); origin.want_carbons = state == "enable" and stanza.tags[1].attr.xmlns; origin.send(st.reply(stanza)); return true; end module:hook("iq-set/self/"..xmlns_carbons..":disable", toggle_carbons); module:hook("iq-set/self/"..xmlns_carbons..":enable", toggle_carbons); local function should_copy(stanza, c2s, user_bare) --> boolean, reason: string local st_type = stanza.attr.type or "normal"; if stanza:get_child("private", xmlns_carbons) then return false, "private"; end if stanza:get_child("no-copy", "urn:xmpp:hints") then return false, "hint"; end if not c2s and stanza.attr.to ~= user_bare and stanza:get_child("x", "http://jabber.org/protocol/muc#user") then -- MUC PMs are normally sent to full JIDs return false, "muc-pm"; end if st_type == "chat" then return true, "type"; end if st_type == "normal" and stanza:get_child("body") then return true, "type"; end -- Normal outgoing chat messages are sent to=bare JID. This clause should -- match the error bounces from those, which would have from=bare JID and -- be incoming (not c2s). if st_type == "error" and not c2s and is_bare(stanza.attr.from) then return true, "bounce"; end if stanza:get_child(nil, "urn:xmpp:jingle-message:0") or stanza:get_child(nil, "urn:xmpp:jingle-message:1") then -- XXX Experimental XEP return true, "jingle call"; end if stanza:get_child_with_attr("stanza-id", "urn:xmpp:sid:0", "by", user_bare) then return true, "archived"; end return false, "default"; end module:hook("carbons-should-copy", function (event) local should, why = should_copy(event.stanza); event.reason = why; return should; end, -1) local function message_handler(event, c2s) local origin, stanza = event.origin, event.stanza; local orig_type = stanza.attr.type or "normal"; local orig_from = stanza.attr.from; local bare_from = jid_bare(orig_from); local orig_to = stanza.attr.to; local bare_to = jid_bare(orig_to); -- Stanza sent by a local client local bare_jid = bare_from; -- JID of the local user local target_session = origin; local top_priority = false; local user_sessions = bare_sessions[bare_from]; -- Stanza about to be delivered to a local client if not c2s then bare_jid = bare_to; target_session = full_sessions[orig_to]; user_sessions = bare_sessions[bare_jid]; if not target_session and user_sessions then -- The top resources will already receive this message per normal routing rules, -- so we are going to skip them in order to avoid sending duplicated messages. local top_resources = user_sessions.top_resources; top_priority = top_resources and top_resources[1].priority end end if not user_sessions then module:log("debug", "Skip carbons for offline user"); return -- No use in sending carbons to an offline user end local event_payload = { stanza = stanza; session = origin }; local should = module:fire_event("carbons-should-copy", event_payload); local why = event_payload.reason; if not should then module:log("debug", "Not copying stanza: %s (%s)", stanza:top_tag(), why); if why == "private" and not c2s then stanza:maptags(function(tag) if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then return tag; end end); end return; end local carbon; user_sessions = user_sessions and user_sessions.sessions; for _, session in pairs(user_sessions) do -- Carbons are sent to resources that have enabled it if session.want_carbons -- but not the resource that sent the message, or the one that it's directed to and session ~= target_session -- and isn't among the top resources that would receive the message per standard routing rules and (c2s or session.priority ~= top_priority) then if not carbon then -- Create the carbon copy and wrap it as per the Stanza Forwarding XEP local copy = st.clone(stanza); if c2s and not orig_to then stanza.attr.to = bare_from; end copy.attr.xmlns = "jabber:client"; carbon = st.message{ from = bare_jid, type = orig_type, } :tag(c2s and "sent" or "received", { xmlns = xmlns_carbons }) :tag("forwarded", { xmlns = xmlns_forward }) :add_child(copy):reset(); end carbon.attr.to = session.full_jid; module:log("debug", "Sending carbon to %s", session.full_jid); session.send(carbon); end end end local function c2s_message_handler(event) return message_handler(event, true) end -- Stanzas sent by local clients module:hook("pre-message/host", c2s_message_handler, -0.5); module:hook("pre-message/bare", c2s_message_handler, -0.5); module:hook("pre-message/full", c2s_message_handler, -0.5); -- Stanzas to local clients module:hook("message/bare", message_handler, -0.5); module:hook("message/full", message_handler, -0.5); module:add_feature(xmlns_carbons); prosody-0.12.4/plugins/PaxHeaders/mod_component.lua0000644000000000000000000000012014475665104017411 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.652765344 prosody-0.12.4/plugins/mod_component.lua0000644000175000017500000003035714475665104021626 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local t_concat = table.concat; local tostring, type = tostring, type; local xpcall = require "util.xpcall".xpcall; local traceback = debug.traceback; local logger = require "util.logger"; local sha1 = require "util.hashes".sha1; local st = require "util.stanza"; local jid_split = require "util.jid".split; local new_xmpp_stream = require "util.xmppstream".new; local uuid_gen = require "util.uuid".generate; local core_process_stanza = prosody.core_process_stanza; local hosts = prosody.hosts; local log = module._log; local opt_keepalives = module:get_option_boolean("component_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true)); local stanza_size_limit = module:get_option_number("component_stanza_size_limit", module:get_option_number("s2s_stanza_size_limit", 1024*512)); local sessions = module:shared("sessions"); local function keepalive(event) local session = event.session; if not session.notopen then return event.session.send(' '); end end function module.add_host(module) if module:get_host_type() ~= "component" then error("Don't load mod_component manually, it should be for a component, please see https://prosody.im/doc/components", 0); end local env = module.environment; env.connected = false; env.session = false; local send; local function on_destroy(session, err) --luacheck: ignore 212/err module:set_status("warn", err and ("Disconnected: "..err) or "Disconnected"); env.connected = false; env.session = false; send = nil; session.on_destroy = nil; end -- Handle authentication attempts by component local function handle_component_auth(event) local session, stanza = event.origin, event.stanza; if session.type ~= "component_unauthed" then return; end if (not session.host) or #stanza.tags > 0 then (session.log or log)("warn", "Invalid component handshake for host: %s", session.host); session:close("not-authorized"); return true; end local secret = module:get_option_string("component_secret"); if not secret then (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host); session:close("not-authorized"); return true; end local supplied_token = t_concat(stanza); local calculated_token = sha1(session.streamid..secret, true); if supplied_token:lower() ~= calculated_token:lower() then module:log("info", "Component authentication failed for %s", session.host); session:close{ condition = "not-authorized", text = "Given token does not match calculated token" }; return true; end if env.connected then local policy = module:get_option_string("component_conflict_resolve", "kick_new"); if policy == "kick_old" then env.session:close{ condition = "conflict", text = "Replaced by a new connection" }; else -- kick_new module:log("error", "Second component attempted to connect, denying connection"); session:close{ condition = "conflict", text = "Component already connected" }; return true; end end env.connected = true; env.session = session; send = session.send; session.on_destroy = on_destroy; session.component_validate_from = module:get_option_boolean("validate_from_addresses", true); session.type = "component"; module:log("info", "External component successfully authenticated"); session.send(st.stanza("handshake")); module:fire_event("component-authenticated", { session = session }); module:set_status("info", "Connected"); return true; end module:hook("stanza/jabber:component:accept:handshake", handle_component_auth, -1); -- Handle stanzas addressed to this component local function handle_stanza(event) local stanza = event.stanza; if send then stanza.attr.xmlns = nil; send(stanza); else if stanza.name == "iq" and stanza.attr.type == "get" and stanza.attr.to == module.host then local query = stanza.tags[1]; local node = query.attr.node; if query.name == "query" and query.attr.xmlns == "http://jabber.org/protocol/disco#info" and (not node or node == "") then local name = module:get_option_string("name"); if name then local reply = st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#info" }) :tag("identity", { category = "component", type = "generic", name = module:get_option_string("name", "Prosody") }):up() :tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up(); event.origin.send(reply); return true; end end end module:log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag()); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then event.origin.send(st.error_reply(stanza, "wait", "remote-server-timeout", "Component unavailable", module.host) :tag("not-connected", { xmlns = "xmpp:prosody.im/protocol/component" })); end end return true; end module:hook("iq/bare", handle_stanza, -1); module:hook("message/bare", handle_stanza, -1); module:hook("presence/bare", handle_stanza, -1); module:hook("iq/full", handle_stanza, -1); module:hook("message/full", handle_stanza, -1); module:hook("presence/full", handle_stanza, -1); module:hook("iq/host", handle_stanza, -1); module:hook("message/host", handle_stanza, -1); module:hook("presence/host", handle_stanza, -1); module:hook("component-read-timeout", keepalive, -1); end module:hook("component-read-timeout", keepalive, -1); --- Network and stream part --- local xmlns_component = 'jabber:component:accept'; local listener = {}; --- Callbacks/data for xmppstream to handle streams for us --- local stream_callbacks = { default_ns = xmlns_component }; local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; function stream_callbacks.error(session, error, data) if session.destroyed then return; end module:log("warn", "Error processing component stream: %s", error); if error == "no-stream" then session:close("invalid-namespace"); elseif error == "parse-error" then session.log("warn", "External component %s XML parse error: %s", session.host, data); session:close("not-well-formed"); elseif error == "stream-error" then local condition, text = "undefined-condition"; for child in data:childtags(nil, xmlns_xmpp_streams) do if child.name ~= "text" then condition = child.name; else text = child:get_text(); end if condition ~= "undefined-condition" and text then break; end end text = condition .. (text and (" ("..text..")") or ""); session.log("info", "Session closed by remote with error: %s", text); session:close(nil, text); end end function stream_callbacks.streamopened(session, attr) if not attr.to then session:close{ condition = "improper-addressing", text = "A 'to' attribute is required on stream headers" }; return; end if not hosts[attr.to] or not hosts[attr.to].modules.component then session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" }; return; end session.host = attr.to; session.streamid = uuid_gen(); session.notopen = nil; -- Return stream header session:open_stream(); end function stream_callbacks.streamclosed(session) session.log("debug", "Received "); session:close(); end local function handleerr(err) log("error", "Traceback[component]: %s", traceback(err, 2)); end function stream_callbacks.handlestanza(session, stanza) -- Namespaces are icky. if not stanza.attr.xmlns and stanza.name == "handshake" then stanza.attr.xmlns = xmlns_component; end if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then local from = stanza.attr.from; if from then if session.component_validate_from then local _, domain = jid_split(stanza.attr.from); if domain ~= session.host then -- Return error session.log("warn", "Component sent stanza with missing or invalid 'from' address"); session:close{ condition = "invalid-from"; text = "Component tried to send from address <"..tostring(from) .."> which is not in domain <"..tostring(session.host)..">"; }; return; end end else stanza.attr.from = session.host; -- COMPAT: Strictly we shouldn't allow this end if not stanza.attr.to then session.log("warn", "Rejecting stanza with no 'to' address"); session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas")); return; end end if stanza then return xpcall(core_process_stanza, handleerr, session, stanza); end end --- Closing a component connection local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" }; local function session_close(session, reason) if session.destroyed then return; end if session.conn then if session.notopen then session.send(""); session.send(st.stanza("stream:stream", default_stream_attr):top_tag()); end if reason then if type(reason) == "string" then -- assume stream error module:log("info", "Disconnecting component, is: %s", reason); session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); elseif st.is_stanza(reason) then module:log("info", "Disconnecting component, is: %s", reason); session.send(reason); elseif type(reason) == "table" then if reason.condition then local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); if reason.text then stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then stanza:add_child(reason.extra); end module:log("info", "Disconnecting component, is: %s", stanza); session.send(stanza); end end end session.send(""); session.conn:close(); listener.ondisconnect(session.conn, "stream error"); end end --- Component connlistener function listener.onconnect(conn) local _send = conn.write; local session = { type = "component_unauthed", conn = conn, send = function (data) return _send(conn, tostring(data)); end }; -- Logging functions -- local conn_name = "jcp"..tostring(session):match("[a-f0-9]+$"); session.log = logger.init(conn_name); session.close = session_close; if opt_keepalives then conn:setoption("keepalive", opt_keepalives); end session.log("info", "Incoming Jabber component connection"); local stream = new_xmpp_stream(session, stream_callbacks, stanza_size_limit); session.stream = stream; session.notopen = true; function session.reset_stream() session.notopen = true; session.stream:reset(); end function session.data(_, data) local ok, err = stream:feed(data); if ok then return; end log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); session:close("not-well-formed"); end session.dispatch_stanza = stream_callbacks.handlestanza; sessions[conn] = session; end function listener.onincoming(conn, data) local session = sessions[conn]; session.data(conn, data); end function listener.ondisconnect(conn, err) local session = sessions[conn]; if session then (session.log or log)("info", "component disconnected: %s (%s)", session.host, err); if session.host then module:context(session.host):fire_event("component-disconnected", { session = session, reason = err }); end if session.on_destroy then session:on_destroy(err); end sessions[conn] = nil; for k in pairs(session) do if k ~= "log" and k ~= "close" then session[k] = nil; end end session.destroyed = true; end end function listener.ondetach(conn) sessions[conn] = nil; end function listener.onreadtimeout(conn) local session = sessions[conn]; if session then return (hosts[session.host] or prosody).events.fire_event("component-read-timeout", { session = session }); end end module:provides("net", { name = "component"; private = true; listener = listener; default_port = 5347; multiplex = { pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:component:accept%1.*>"; }; }); prosody-0.12.4/plugins/PaxHeaders/mod_cron.lua0000644000000000000000000000012014475665104016350 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.652765344 prosody-0.12.4/plugins/mod_cron.lua0000644000175000017500000000406414475665104020561 0ustar00prosodyprosody00000000000000module:set_global(); local async = require("util.async"); local datetime = require("util.datetime"); local periods = { hourly = 3600; daily = 86400; weekly = 7 * 86400 } local active_hosts = {} function module.add_host(host_module) local last_run_times = host_module:open_store("cron", "map"); active_hosts[host_module.host] = true; local function save_task(task, started_at) last_run_times:set(nil, task.id, started_at); end local function task_added(event) local task = event.item; if task.name == nil then task.name = task.when; end if task.id == nil then task.id = event.source.name .. "/" .. task.name:gsub("%W", "_"):lower(); end if task.last == nil then task.last = last_run_times:get(nil, task.id); end task.save = save_task; module:log("debug", "%s task %s added, last run %s", task.when, task.id, task.last and datetime.datetime(task.last) or "never"); if task.last == nil then local now = os.time(); task.last = now - now % periods[task.when]; end return true end local function task_removed(event) local task = event.item; host_module:log("debug", "Task %s removed", task.id); return true end host_module:handle_items("task", task_added, task_removed, true); function host_module.unload() active_hosts[host_module.host] = nil; end end local function should_run(when, last) return not last or last + periods[when] * 0.995 <= os.time() end local function run_task(task) local started_at = os.time(); task:run(started_at); task.last = started_at; task:save(started_at); end local task_runner = async.runner(run_task); scheduled = module:add_timer(1, function() module:log("info", "Running periodic tasks"); local delay = 3600; for host in pairs(active_hosts) do module:log("debug", "Running periodic tasks for host %s", host); for _, task in ipairs(module:context(host):get_host_items("task")) do module:log("debug", "Considering %s task %s (%s)", task.when, task.id, task.run); if should_run(task.when, task.last) then task_runner:run(task); end end end module:log("debug", "Wait %ds", delay); return delay end); prosody-0.12.4/plugins/PaxHeaders/mod_csi.lua0000644000000000000000000000012014475665104016165 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.652765344 prosody-0.12.4/plugins/mod_csi.lua0000644000175000017500000000206014475665104020370 0ustar00prosodyprosody00000000000000local st = require "util.stanza"; local xmlns_csi = "urn:xmpp:csi:0"; local csi_feature = st.stanza("csi", { xmlns = xmlns_csi }); local csi_handler_available = nil; module:hook("stream-features", function (event) if event.origin.username and csi_handler_available then event.features:add_child(csi_feature); end end); function refire_event(name) return function (event) if event.origin.username then event.origin.state = event.stanza.name; module:fire_event(name, event); return true; end end; end module:hook("stanza/"..xmlns_csi..":active", refire_event("csi-client-active")); module:hook("stanza/"..xmlns_csi..":inactive", refire_event("csi-client-inactive")); function module.load() if prosody.hosts[module.host].events._handlers["csi-client-active"] then csi_handler_available = true; module:set_status("core", "CSI handler module loaded"); else csi_handler_available = false; module:set_status("warn", "No CSI handler module loaded"); end end module:hook("module-loaded", module.load); module:hook("module-unloaded", module.load); prosody-0.12.4/plugins/PaxHeaders/mod_csi_simple.lua0000644000000000000000000000012014475665104017536 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.656765305 prosody-0.12.4/plugins/mod_csi_simple.lua0000644000175000017500000002211514475665104021744 0ustar00prosodyprosody00000000000000-- Copyright (C) 2016-2020 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:depends"csi" local jid = require "util.jid"; local st = require "util.stanza"; local dt = require "util.datetime"; local filters = require "util.filters"; local timer = require "util.timer"; local queue_size = module:get_option_number("csi_queue_size", 256); local resume_delay = module:get_option_number("csi_resume_inactive_delay", 5); local important_payloads = module:get_option_set("csi_important_payloads", { }); function is_important(stanza) --> boolean, reason: string if stanza == " " then return true, "whitespace keepalive"; elseif type(stanza) == "string" then return true, "raw data"; elseif not st.is_stanza(stanza) then -- This should probably never happen return true, type(stanza); end if stanza.attr.xmlns ~= nil then -- stream errors, stream management etc return true, "nonza"; end local st_name = stanza.name; if not st_name then return false; end local st_type = stanza.attr.type; if st_name == "presence" then if st_type == nil or st_type == "unavailable" or st_type == "error" then return false, "presence update"; end -- TODO Some MUC awareness, e.g. check for the 'this relates to you' status code return true, "subscription request"; elseif st_name == "message" then if st_type == "headline" then -- Headline messages are ephemeral by definition return false, "headline"; end if st_type == "error" then return true, "delivery failure"; end if stanza:get_child("sent", "urn:xmpp:carbons:2") then return true, "carbon"; end local forwarded = stanza:find("{urn:xmpp:carbons:2}received/{urn:xmpp:forward:0}/{jabber:client}message"); if forwarded then stanza = forwarded; end if stanza:get_child("body") then return true, "body"; end if stanza:get_child("subject") then -- Last step of a MUC join return true, "subject"; end if stanza:get_child("encryption", "urn:xmpp:eme:0") then -- Since we can't know what an encrypted message contains, we assume it's important -- XXX Experimental XEP return true, "encrypted"; end if stanza:get_child("x", "jabber:x:conference") or stanza:find("{http://jabber.org/protocol/muc#user}x/invite") then return true, "invite"; end if stanza:get_child(nil, "urn:xmpp:jingle-message:0") or stanza:get_child(nil, "urn:xmpp:jingle-message:1") then -- XXX Experimental XEP return true, "jingle call"; end for important in important_payloads do if stanza:find(important) then return true; end end return false; elseif st_name == "iq" then return true; end end module:hook("csi-is-stanza-important", function (event) local important, why = is_important(event.stanza); event.reason = why; return important; end, -1); local function should_flush(stanza, session, ctr) --> boolean, reason: string if ctr >= queue_size then return true, "queue size limit reached"; end local event = { stanza = stanza, session = session }; local ret = module:fire_event("csi-is-stanza-important", event) return ret, event.reason; end local function with_timestamp(stanza, from) if st.is_stanza(stanza) and stanza.attr.xmlns == nil and stanza.name ~= "iq" then stanza = st.clone(stanza); stanza:add_direct_child(st.stanza("delay", {xmlns = "urn:xmpp:delay", from = from, stamp = dt.datetime()})); end return stanza; end local measure_buffer_hold = module:measure("buffer_hold", "times", { buckets = { 0.1; 1; 5; 10; 15; 30; 60; 120; 180; 300; 600; 900 } }); local flush_reasons = module:metric( "counter", "flushes", "", "CSI queue flushes", { "reason" } ); local function manage_buffer(stanza, session) local ctr = session.csi_counter or 0; if session.state ~= "inactive" then session.csi_counter = ctr + 1; return stanza; end local flush, why = should_flush(stanza, session, ctr); if flush then if session.csi_measure_buffer_hold then session.csi_measure_buffer_hold(); session.csi_measure_buffer_hold = nil; end flush_reasons:with_labels(why or "important"):add(1); session.log("debug", "Flushing buffer (%s; queue size is %d)", why or "important", session.csi_counter); session.state = "flushing"; module:fire_event("csi-flushing", { session = session }); session.conn:resume_writes(); else session.log("debug", "Holding buffer (%s; queue size is %d)", why or "unimportant", session.csi_counter); stanza = with_timestamp(stanza, jid.join(session.username, session.host)) end session.csi_counter = ctr + 1; return stanza; end local function flush_buffer(data, session) local ctr = session.csi_counter or 0; if ctr == 0 or session.state ~= "inactive" then return data end session.log("debug", "Flushing buffer (%s; queue size is %d)", "client activity", session.csi_counter); session.state = "flushing"; module:fire_event("csi-flushing", { session = session }); flush_reasons:with_labels("client activity"):add(1); if session.csi_measure_buffer_hold then session.csi_measure_buffer_hold(); session.csi_measure_buffer_hold = nil; end session.conn:resume_writes(); return data; end function enable_optimizations(session) if session.conn and session.conn.pause_writes then session.conn:pause_writes(); session.csi_measure_buffer_hold = measure_buffer_hold(); session.csi_counter = 0; if session.csi_resume then timer.stop(session.csi_resume); session.csi_resume = nil; end filters.add_filter(session, "stanzas/out", manage_buffer); filters.add_filter(session, "bytes/in", flush_buffer); else session.log("warn", "Session connection does not support write pausing"); end end function disable_optimizations(session) filters.remove_filter(session, "stanzas/out", manage_buffer); filters.remove_filter(session, "bytes/in", flush_buffer); session.csi_counter = nil; if session.csi_resume then timer.stop(session.csi_resume); session.csi_resume = nil; end if session.csi_measure_buffer_hold then session.csi_measure_buffer_hold(); session.csi_measure_buffer_hold = nil; end if session.conn and session.conn.resume_writes then session.conn:resume_writes(); end end module:hook("csi-client-inactive", function (event) local session = event.origin; enable_optimizations(session); end); module:hook("csi-client-active", function (event) local session = event.origin; disable_optimizations(session); end); module:hook("pre-resource-unbind", function (event) local session = event.session; disable_optimizations(session); end, 1); local function resume_optimizations(_, _, session) if (session.state == "flushing" or session.state == "inactive") and session.conn and session.conn.pause_writes then session.state = "inactive"; session.conn:pause_writes(); session.csi_measure_buffer_hold = measure_buffer_hold(); session.log("debug", "Buffer flushed, resuming inactive mode (queue size was %d)", session.csi_counter); session.csi_counter = 0; end session.csi_resume = nil; end module:hook("c2s-ondrain", function (event) local session = event.session; if (session.state == "flushing" or session.state == "inactive") and session.conn and session.conn.pause_writes then -- After flushing, remain in pseudo-flushing state for a moment to allow -- some followup traffic, iq replies, smacks acks to be sent without having -- to go back and forth between inactive and flush mode. if not session.csi_resume then session.csi_resume = timer.add_task(resume_delay, resume_optimizations, session); end -- Should further noise in this short grace period push back the delay? -- Probably not great if the session can be kept in pseudo-active mode -- indefinitely. end end); function module.load() for _, user_session in pairs(prosody.hosts[module.host].sessions) do for _, session in pairs(user_session.sessions) do if session.state == "inactive" then enable_optimizations(session); end end end end function module.unload() for _, user_session in pairs(prosody.hosts[module.host].sessions) do for _, session in pairs(user_session.sessions) do if session.state and session.state ~= "active" then disable_optimizations(session); end end end end function module.command(arg) if arg[1] ~= "test" then print("Usage: "..module.name.." test < test-stream.xml") print(""); print("Provide a series of stanzas to test against importance algorithm"); return 1; end -- luacheck: ignore 212/self local xmppstream = require "util.xmppstream"; local input_session = { notopen = true } local stream_callbacks = { stream_ns = "jabber:client", default_ns = "jabber:client" }; function stream_callbacks:handlestanza(stanza) local important, because = is_important(stanza); print("--"); print(stanza:indent(nil, " ")); -- :pretty_print() maybe? if important then print((because or "unspecified reason").. " -> important"); else print((because or "unspecified reason").. " -> unimportant"); end end local input_stream = xmppstream.new(input_session, stream_callbacks); input_stream:reset(); input_stream:feed(st.stanza("stream", { xmlns = "jabber:client" }):top_tag()); input_session.notopen = nil; for line in io.lines() do input_stream:feed(line); end end prosody-0.12.4/plugins/PaxHeaders/mod_debug_sql.lua0000644000000000000000000000012014475665104017354 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.656765305 prosody-0.12.4/plugins/mod_debug_sql.lua0000644000175000017500000000070014475665104021556 0ustar00prosodyprosody00000000000000-- Enables SQL query logging -- -- luacheck: ignore 213/uri module:set_global(); local engines = module:shared("/*/sql/connections"); for uri, engine in pairs(engines) do engine:debug(true); end setmetatable(engines, { __newindex = function (t, uri, engine) engine:debug(true); rawset(t, uri, engine); end }); function module.unload() setmetatable(engines, nil); for uri, engine in pairs(engines) do engine:debug(false); end end prosody-0.12.4/plugins/PaxHeaders/mod_dialback.lua0000644000000000000000000000012014475665104017141 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.656765305 prosody-0.12.4/plugins/mod_dialback.lua0000644000175000017500000001576214475665104021361 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local hosts = _G.hosts; local log = module._log; local st = require "util.stanza"; local sha256_hash = require "util.hashes".sha256; local sha256_hmac = require "util.hashes".hmac_sha256; local secure_equals = require "util.hashes".equals; local nameprep = require "util.encodings".stringprep.nameprep; local uuid_gen = require"util.uuid".generate; local xmlns_stream = "http://etherx.jabber.org/streams"; local dialback_requests = setmetatable({}, { __mode = 'v' }); local dialback_secret = sha256_hash(module:get_option_string("dialback_secret", uuid_gen()), true); function module.save() return { dialback_secret = dialback_secret }; end function module.restore(state) dialback_secret = state.dialback_secret; end function generate_dialback(id, to, from) return sha256_hmac(dialback_secret, to .. ' ' .. from .. ' ' .. id, true); end function initiate_dialback(session) -- generate dialback key session.dialback_key = generate_dialback(session.streamid, session.to_host, session.from_host); session.sends2s(st.stanza("db:result", { from = session.from_host, to = session.to_host }):text(session.dialback_key)); session.log("debug", "sent dialback key on outgoing s2s stream"); end function verify_dialback(id, to, from, key) return secure_equals(key, generate_dialback(id, to, from)); end module:hook("stanza/jabber:server:dialback:verify", function(event) local origin, stanza = event.origin, event.stanza; if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then -- We are being asked to verify the key, to ensure it was generated by us origin.log("debug", "verifying that dialback key is ours..."); local attr = stanza.attr; if attr.type then module:log("warn", "Ignoring incoming session from %s claiming a dialback key for %s is %s", origin.from_host or "(unknown)", attr.from or "(unknown)", attr.type); return true; end -- COMPAT: Grr, ejabberd breaks this one too?? it is black and white in XEP-220 example 34 --if attr.from ~= origin.to_host then error("invalid-from"); end local type; if verify_dialback(attr.id, attr.from, attr.to, stanza[1]) then type = "valid" else type = "invalid" origin.log("warn", "Asked to verify a dialback key that was incorrect. An imposter is claiming to be %s?", attr.to); end origin.log("debug", "verified dialback key... it is %s", type); origin.sends2s(st.stanza("db:verify", { from = attr.to, to = attr.from, id = attr.id, type = type }):text(stanza[1])); return true; end end); module:hook("stanza/jabber:server:dialback:result", function(event) local origin, stanza = event.origin, event.stanza; if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then -- They want to be identified through dialback -- We need to check the key with the Authoritative server local attr = stanza.attr; if not attr.to or not attr.from then origin.log("debug", "Missing Dialback addressing (from=%q, to=%q)", attr.from, attr.to); origin:close("improper-addressing"); return true; end local to, from = nameprep(attr.to), nameprep(attr.from); if not hosts[to] then -- Not a host that we serve origin.log("warn", "%s tried to connect to %s, which we don't serve", from, to); origin:close("host-unknown"); return true; elseif not from then origin:close("improper-addressing"); return true; end origin.hosts[from] = { dialback_key = stanza[1] }; dialback_requests[from.."/"..origin.streamid] = origin; -- COMPAT: ejabberd, gmail and perhaps others do not always set 'to' and 'from' -- on streams. We fill in the session's to/from here instead. if not origin.from_host then origin.from_host = from; end if not origin.to_host then origin.to_host = to; end origin.log("debug", "asking %s if key %s belongs to them", from, stanza[1]); module:fire_event("route/remote", { from_host = to, to_host = from; stanza = st.stanza("db:verify", { from = to, to = from, id = origin.streamid }):text(stanza[1]); }); return true; end end); module:hook("stanza/jabber:server:dialback:verify", function(event) local origin, stanza = event.origin, event.stanza; if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then local attr = stanza.attr; local dialback_verifying = dialback_requests[attr.from.."/"..(attr.id or "")]; if dialback_verifying and attr.from == origin.to_host then local valid; if attr.type == "valid" then module:fire_event("s2s-authenticated", { session = dialback_verifying, host = attr.from, mechanism = "dialback" }); valid = "valid"; else -- Warn the original connection that is was not verified successfully log("warn", "authoritative server for %s denied the key", attr.from or "(unknown)"); valid = "invalid"; end if dialback_verifying.destroyed then log("warn", "Incoming s2s session %s was closed in the meantime, so we can't notify it of the dialback result", tostring(dialback_verifying):match("%w+$")); else dialback_verifying.sends2s( st.stanza("db:result", { from = attr.to, to = attr.from, id = attr.id, type = valid }) :text(dialback_verifying.hosts[attr.from].dialback_key)); end dialback_requests[attr.from.."/"..(attr.id or "")] = nil; end return true; end end); module:hook("stanza/jabber:server:dialback:result", function(event) local origin, stanza = event.origin, event.stanza; if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then -- Remote server is telling us whether we passed dialback local attr = stanza.attr; if not hosts[attr.to] then origin:close("host-unknown"); return true; elseif hosts[attr.to].s2sout[attr.from] ~= origin then -- This isn't right origin:close("invalid-id"); return true; end if stanza.attr.type == "valid" then module:fire_event("s2s-authenticated", { session = origin, host = attr.from, mechanism = "dialback" }); else origin:close("not-authorized", "dialback authentication failed"); end return true; end end); module:hook_tag("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza) -- luacheck: ignore 212/stanza if origin.external_auth == "failed" then module:log("debug", "SASL EXTERNAL failed, falling back to dialback"); initiate_dialback(origin); return true; end end, 100); module:hook_tag(xmlns_stream, "features", function (origin, stanza) -- luacheck: ignore 212/stanza if not origin.external_auth or origin.external_auth == "failed" then module:log("debug", "Initiating dialback..."); initiate_dialback(origin); return true; end end, 100); module:hook("s2sout-authenticate-legacy", function (event) module:log("debug", "Initiating dialback..."); initiate_dialback(event.origin); return true; end, 100); -- Offer dialback to incoming hosts module:hook("s2s-stream-features", function (data) data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):up(); end); prosody-0.12.4/plugins/PaxHeaders/mod_disco.lua0000644000000000000000000000012014475665104016510 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.656765305 prosody-0.12.4/plugins/mod_disco.lua0000644000175000017500000002234614475665104020724 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local get_children = require "core.hostmanager".get_children; local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; local um_is_admin = require "core.usermanager".is_admin; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza" local calculate_hash = require "util.caps".calculate_hash; local expose_admins = module:get_option_boolean("disco_expose_admins", false); local disco_items = module:get_option_array("disco_items", {}) do -- validate disco_items for _, item in ipairs(disco_items) do local err; if type(item) ~= "table" then err = "item is not a table"; elseif type(item[1]) ~= "string" then err = "item jid is not a string"; elseif item[2] and type(item[2]) ~= "string" then err = "item name is not a string"; end if err then module:log("error", "option disco_items is malformed: %s", err); disco_items = {}; -- TODO clean up data instead of removing it? break; end end end if module:get_host_type() == "local" then module:add_identity("server", "im", module:get_option_string("name", "Prosody")); -- FIXME should be in the nonexisting mod_router end module:add_feature("http://jabber.org/protocol/disco#info"); module:add_feature("http://jabber.org/protocol/disco#items"); -- Generate and cache disco result and caps hash local _cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash; local function build_server_disco_info() local query = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" }); local done = {}; for _,identity in ipairs(module:get_host_items("identity")) do local identity_s = identity.category.."\0"..identity.type; if not done[identity_s] then query:tag("identity", identity):up(); done[identity_s] = true; end end for _,feature in ipairs(module:get_host_items("feature")) do if not done[feature] then query:tag("feature", {var=feature}):up(); done[feature] = true; end end for _,extension in ipairs(module:get_host_items("extension")) do if not done[extension] then query:add_child(extension); done[extension] = true; end end _cached_server_disco_info = query; _cached_server_caps_hash = calculate_hash(query); _cached_server_caps_feature = st.stanza("c", { xmlns = "http://jabber.org/protocol/caps"; hash = "sha-1"; node = "http://prosody.im"; ver = _cached_server_caps_hash; }); end local function clear_disco_cache() _cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash = nil, nil, nil; end local function get_server_disco_info() if not _cached_server_disco_info then build_server_disco_info(); end return _cached_server_disco_info; end local function get_server_caps_feature() if not _cached_server_caps_feature then build_server_disco_info(); end return _cached_server_caps_feature; end local function get_server_caps_hash() if not _cached_server_caps_hash then build_server_disco_info(); end return _cached_server_caps_hash; end module:hook("item-added/identity", clear_disco_cache); module:hook("item-added/feature", clear_disco_cache); module:hook("item-added/extension", clear_disco_cache); module:hook("item-removed/identity", clear_disco_cache); module:hook("item-removed/feature", clear_disco_cache); module:hook("item-removed/extension", clear_disco_cache); -- Handle disco requests to the server module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event) local origin, stanza = event.origin, event.stanza; local node = stanza.tags[1].attr.node; if node and node ~= "" and node ~= "http://prosody.im#"..get_server_caps_hash() then local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info', node=node}); local node_event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false}; local ret = module:fire_event("host-disco-info-node", node_event); if ret ~= nil then return ret; end if node_event.exists then origin.send(reply); else origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist")); end return true; end local reply_query = get_server_disco_info(); reply_query.attr.node = node; local reply = st.reply(stanza):add_child(reply_query); origin.send(reply); return true; end); module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event) local origin, stanza = event.origin, event.stanza; local node = stanza.tags[1].attr.node; if node and node ~= "" then local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items', node=node}); local node_event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false}; local ret = module:fire_event("host-disco-items-node", node_event); if ret ~= nil then return ret; end if node_event.exists then origin.send(reply); else origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist")); end return true; end local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); local ret = module:fire_event("host-disco-items", { origin = origin, stanza = stanza, reply = reply }); if ret ~= nil then return ret; end for jid, name in pairs(get_children(module.host)) do reply:tag("item", {jid = jid, name = name~=true and name or nil}):up(); end for _, item in ipairs(disco_items) do reply:tag("item", {jid=item[1], name=item[2]}):up(); end origin.send(reply); return true; end); -- Handle caps stream feature module:hook("stream-features", function (event) if event.origin.type == "c2s" or event.origin.type == "c2s_unbound" then event.features:add_child(get_server_caps_feature()); end end); module:hook("s2s-stream-features", function (event) if event.origin.type == "s2sin" then event.features:add_child(get_server_caps_feature()); end end); -- Handle disco requests to user accounts if module:get_host_type() ~= "local" then return end -- skip for components module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(event) local origin, stanza = event.origin, event.stanza; local node = stanza.tags[1].attr.node; local username = jid_split(stanza.attr.to) or origin.username; local is_admin = um_is_admin(stanza.attr.to or origin.full_jid, module.host) if not stanza.attr.to or (expose_admins and is_admin) or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then if node and node ~= "" then local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info', node=node}); if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account local node_event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false}; local ret = module:fire_event("account-disco-info-node", node_event); if ret ~= nil then return ret; end if node_event.exists then origin.send(reply); else origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist")); end return true; end local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info'}); if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account if is_admin then reply:tag('identity', {category='account', type='admin'}):up(); elseif prosody.hosts[module.host].users.name == "anonymous" then reply:tag('identity', {category='account', type='anonymous'}):up(); else reply:tag('identity', {category='account', type='registered'}):up(); end module:fire_event("account-disco-info", { origin = origin, reply = reply }); origin.send(reply); return true; end end); module:hook("iq-get/bare/http://jabber.org/protocol/disco#items:query", function(event) local origin, stanza = event.origin, event.stanza; local node = stanza.tags[1].attr.node; local username = jid_split(stanza.attr.to) or origin.username; if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then if node and node ~= "" then local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items', node=node}); if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account local node_event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false}; local ret = module:fire_event("account-disco-items-node", node_event); if ret ~= nil then return ret; end if node_event.exists then origin.send(reply); else origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist")); end return true; end local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items'}); if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account module:fire_event("account-disco-items", { origin = origin, stanza = stanza, reply = reply }); origin.send(reply); return true; end end); prosody-0.12.4/plugins/PaxHeaders/mod_external_services.lua0000644000000000000000000000012014475665104021134 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.656765305 prosody-0.12.4/plugins/mod_external_services.lua0000644000175000017500000001606014475665104023344 0ustar00prosodyprosody00000000000000 local dt = require "util.datetime"; local base64 = require "util.encodings".base64; local hashes = require "util.hashes"; local st = require "util.stanza"; local jid = require "util.jid"; local array = require "util.array"; local set = require "util.set"; local default_host = module:get_option_string("external_service_host", module.host); local default_port = module:get_option_number("external_service_port"); local default_secret = module:get_option_string("external_service_secret"); local default_ttl = module:get_option_number("external_service_ttl", 86400); local configured_services = module:get_option_array("external_services", {}); local access = module:get_option_set("external_service_access", {}); -- https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 local function behave_turn_rest_credentials(srv, item, secret) local ttl = default_ttl; if type(item.ttl) == "number" then ttl = item.ttl; end local expires = srv.expires or os.time() + ttl; local username; if type(item.username) == "string" then username = string.format("%d:%s", expires, item.username); else username = string.format("%d", expires); end srv.username = username; srv.password = base64.encode(hashes.hmac_sha1(secret, srv.username)); end local algorithms = { turn = behave_turn_rest_credentials; } -- filter config into well-defined service records local function prepare(item) if type(item) ~= "table" then module:log("error", "Service definition is not a table: %q", item); return nil; end local srv = { type = nil; transport = nil; host = default_host; port = default_port; username = nil; password = nil; restricted = nil; expires = nil; }; if type(item.type) == "string" then srv.type = item.type; else module:log("error", "Service missing mandatory 'type' field: %q", item); return nil; end if type(item.transport) == "string" then srv.transport = item.transport; else module:log("warn", "Service missing recommended 'transport' field: %q", item); end if type(item.host) == "string" then srv.host = item.host; end if type(item.port) == "number" then srv.port = item.port; elseif not srv.port then module:log("warn", "Service missing recommended 'port' field: %q", item); end if type(item.username) == "string" then srv.username = item.username; end if type(item.password) == "string" then srv.password = item.password; srv.restricted = true; end if item.restricted == true then srv.restricted = true; end if type(item.expires) == "number" then srv.expires = item.expires; elseif type(item.ttl) == "number" then srv.expires = os.time() + item.ttl; end if (item.secret == true and default_secret) or type(item.secret) == "string" then local secret_cb = item.credentials_cb or algorithms[item.algorithm] or algorithms[srv.type]; local secret = item.secret; if secret == true then secret = default_secret; end if secret_cb then secret_cb(srv, item, secret); srv.restricted = true; end end return srv; end function module.load() -- Trigger errors on startup local extras = module:get_host_items("external_service"); local services = ( configured_services + extras ) / prepare; if #services == 0 then module:set_status("warn", "No services configured or all had errors"); end end module:handle_items("external_service", function(added) if prepare(added.item) then module:set_status("core", "OK"); end end, module.load); -- Ensure only valid items are added in events local services_mt = { __index = getmetatable(array()).__index; __newindex = function (self, i, v) rawset(self, i, assert(prepare(v), "Invalid service entry added")); end; } function get_services() local extras = module:get_host_items("external_service"); local services = ( configured_services + extras ) / prepare; setmetatable(services, services_mt); return services; end function services_xml(services, name, namespace) local reply = st.stanza(name or "services", { xmlns = namespace or "urn:xmpp:extdisco:2" }); for _, srv in ipairs(services) do reply:tag("service", { type = srv.type; transport = srv.transport; host = srv.host; port = srv.port and string.format("%d", srv.port) or nil; username = srv.username; password = srv.password; expires = srv.expires and dt.datetime(srv.expires) or nil; restricted = srv.restricted and "1" or nil; }):up(); end return reply; end local function handle_services(event) local origin, stanza = event.origin, event.stanza; local action = stanza.tags[1]; local user_bare = jid.bare(stanza.attr.from); local user_host = jid.host(user_bare); if not ((access:empty() and origin.type == "c2s") or access:contains(user_bare) or access:contains(user_host)) then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end local services = get_services(); local requested_type = action.attr.type; if requested_type then services:filter(function(item) return item.type == requested_type; end); end module:fire_event("external_service/services", { origin = origin; stanza = stanza; requested_type = requested_type; services = services; }); local reply = st.reply(stanza):add_child(services_xml(services, action.name, action.attr.xmlns)); origin.send(reply); return true; end local function handle_credentials(event) local origin, stanza = event.origin, event.stanza; local action = stanza.tags[1]; if origin.type ~= "c2s" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end local services = get_services(); services:filter(function (item) return item.restricted; end) local requested_credentials = set.new(); for service in action:childtags("service") do if not service.attr.type or not service.attr.host then origin.send(st.error_reply(stanza, "modify", "bad-request", "The 'port' and 'type' attributes are required.")); return true; end requested_credentials:add(string.format("%s:%s:%d", service.attr.type, service.attr.host, tonumber(service.attr.port) or 0)); end module:fire_event("external_service/credentials", { origin = origin; stanza = stanza; requested_credentials = requested_credentials; services = services; }); services:filter(function (srv) local port_key = string.format("%s:%s:%d", srv.type, srv.host, srv.port or 0); local portless_key = string.format("%s:%s:%d", srv.type, srv.host, 0); return requested_credentials:contains(port_key) or requested_credentials:contains(portless_key); end); local reply = st.reply(stanza):add_child(services_xml(services, action.name, action.attr.xmlns)); origin.send(reply); return true; end -- XEP-0215 v0.7 module:add_feature("urn:xmpp:extdisco:2"); module:hook("iq-get/host/urn:xmpp:extdisco:2:services", handle_services); module:hook("iq-get/host/urn:xmpp:extdisco:2:credentials", handle_credentials); -- COMPAT XEP-0215 v0.6 -- Those still on the old version gets to deal with undefined attributes until they upgrade. module:add_feature("urn:xmpp:extdisco:1"); module:hook("iq-get/host/urn:xmpp:extdisco:1:services", handle_services); module:hook("iq-get/host/urn:xmpp:extdisco:1:credentials", handle_credentials); prosody-0.12.4/plugins/PaxHeaders/mod_groups.lua0000644000000000000000000000012014475665104016726 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.656765305 prosody-0.12.4/plugins/mod_groups.lua0000644000175000017500000000731514475665104021141 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local groups; local members; local datamanager = require "util.datamanager"; local jid_prep = require "util.jid".prep; local module_host = module:get_host(); function inject_roster_contacts(event) local username, host= event.username, event.host; --module:log("debug", "Injecting group members to roster"); local bare_jid = username.."@"..host; if not members[bare_jid] and not members[false] then return; end -- Not a member of any groups local roster = event.roster; local function import_jids_to_roster(group_name) for jid in pairs(groups[group_name]) do -- Add them to roster --module:log("debug", "processing jid %s in group %s", jid, group_name); if jid ~= bare_jid then if not roster[jid] then roster[jid] = {}; end roster[jid].subscription = "both"; if groups[group_name][jid] then roster[jid].name = groups[group_name][jid]; end if not roster[jid].groups then roster[jid].groups = { [group_name] = true }; end roster[jid].groups[group_name] = true; roster[jid].persist = false; end end end -- Find groups this JID is a member of if members[bare_jid] then for _, group_name in ipairs(members[bare_jid]) do --module:log("debug", "Importing group %s", group_name); import_jids_to_roster(group_name); end end -- Import public groups if members[false] then for _, group_name in ipairs(members[false]) do --module:log("debug", "Importing group %s", group_name); import_jids_to_roster(group_name); end end if roster[false] then roster[false].version = true; end end function remove_virtual_contacts(username, host, datastore, data) if host == module_host and datastore == "roster" then local new_roster = {}; for jid, contact in pairs(data) do if contact.persist ~= false then new_roster[jid] = contact; end end if new_roster[false] then new_roster[false].version = nil; -- Version is void end return username, host, datastore, new_roster; end return username, host, datastore, data; end function module.load() local groups_file = module:get_option_path("groups_file", nil, "config"); if not groups_file then return; end module:hook("roster-load", inject_roster_contacts); datamanager.add_callback(remove_virtual_contacts); groups = { default = {} }; members = { }; local curr_group = "default"; for line in io.lines(groups_file) do if line:match("^%s*%[.-%]%s*$") then curr_group = line:match("^%s*%[(.-)%]%s*$"); if curr_group:match("^%+") then curr_group = curr_group:gsub("^%+", ""); if not members[false] then members[false] = {}; end members[false][#members[false]+1] = curr_group; -- Is a public group end module:log("debug", "New group: %s", curr_group); groups[curr_group] = groups[curr_group] or {}; else -- Add JID local entryjid, name = line:match("([^=]*)=?(.*)"); module:log("debug", "entryjid = '%s', name = '%s'", entryjid, name); local jid; jid = jid_prep(entryjid:match("%S+")); if jid then module:log("debug", "New member of %s: %s", curr_group, jid); groups[curr_group][jid] = name or false; members[jid] = members[jid] or {}; members[jid][#members[jid]+1] = curr_group; elseif entryjid:match("%S") then module:log("warn", "Invalid JID: %q", entryjid); end end end module:log("info", "Groups loaded successfully"); end function module.unload() datamanager.remove_callback(remove_virtual_contacts); end -- Public for other modules to access function group_contains(group_name, jid) return groups[group_name][jid]; end prosody-0.12.4/plugins/PaxHeaders/mod_http.lua0000644000000000000000000000012014475665104016366 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.656765305 prosody-0.12.4/plugins/mod_http.lua0000644000175000017500000003115714475665104020602 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2012 Matthew Wild -- Copyright (C) 2008-2012 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); pcall(function () module:depends("http_errors"); end); local portmanager = require "core.portmanager"; local moduleapi = require "core.moduleapi"; local url_parse = require "socket.url".parse; local url_build = require "socket.url".build; local normalize_path = require "util.http".normalize_path; local set = require "util.set"; local ip_util = require "util.ip"; local new_ip = ip_util.new_ip; local match_ip = ip_util.match; local parse_cidr = ip_util.parse_cidr; local server = require "net.http.server"; server.set_default_host(module:get_option_string("http_default_host")); server.set_option("body_size_limit", module:get_option_number("http_max_content_size")); server.set_option("buffer_size_limit", module:get_option_number("http_max_buffer_size")); -- CORS settings local cors_overrides = module:get_option("http_cors_override", {}); local opt_methods = module:get_option_set("access_control_allow_methods", { "GET", "OPTIONS" }); local opt_headers = module:get_option_set("access_control_allow_headers", { "Content-Type" }); local opt_origins = module:get_option_set("access_control_allow_origins"); local opt_credentials = module:get_option_boolean("access_control_allow_credentials", false); local opt_max_age = module:get_option_number("access_control_max_age", 2 * 60 * 60); local opt_default_cors = module:get_option_boolean("http_default_cors_enabled", true); local function get_http_event(host, app_path, key) local method, path = key:match("^(%S+)%s+(.+)$"); if not method then -- No path specified, default to "" (base path) method, path = key, ""; end if method:sub(1,1) == "/" then return nil; end if app_path == "/" and path:sub(1,1) == "/" then app_path = ""; end if host == "*" then return method:upper().." "..app_path..path; else return method:upper().." "..host..app_path..path; end end local function get_base_path(host_module, app_name, default_app_path) return (normalize_path(host_module:get_option("http_paths", {})[app_name] -- Host or module:get_option("http_paths", {})[app_name] -- Global or default_app_path)) -- Default :gsub("%$(%w+)", { host = host_module.host }); end local function redir_handler(event) event.response.headers.location = event.request.path.."/"; if event.request.url.query then event.response.headers.location = event.response.headers.location .. "?" .. event.request.url.query end return 301; end local ports_by_scheme = { http = 80, https = 443, }; -- Helper to deduce a module's external URL function moduleapi.http_url(module, app_name, default_path) app_name = app_name or (module.name:gsub("^http_", "")); local external_url = url_parse(module:get_option_string("http_external_url")); if external_url then local url = { scheme = external_url.scheme; host = external_url.host; port = tonumber(external_url.port) or ports_by_scheme[external_url.scheme]; path = normalize_path(external_url.path or "/", true) .. (get_base_path(module, app_name, default_path or "/" .. app_name):sub(2)); } if ports_by_scheme[url.scheme] == url.port then url.port = nil end return url_build(url); end local services = portmanager.get_active_services(); local http_services = services:get("https") or services:get("http") or {}; for interface, ports in pairs(http_services) do -- luacheck: ignore 213/interface for port, service in pairs(ports) do -- luacheck: ignore 512 local url = { scheme = service[1].service.name; host = module:get_option_string("http_host", module.global and module:get_option_string("http_default_host", interface) or module.host); port = port; path = get_base_path(module, app_name, default_path or "/" .. app_name); } if ports_by_scheme[url.scheme] == url.port then url.port = nil end return url_build(url); end end if prosody.process_type == "prosody" then module:log("warn", "No http ports enabled, can't generate an external URL"); end return "http://disabled.invalid/"; end local function apply_cors_headers(response, methods, headers, max_age, allow_credentials, allowed_origins, origin) if allowed_origins and not allowed_origins[origin] then return; end response.headers.access_control_allow_methods = tostring(methods); response.headers.access_control_allow_headers = tostring(headers); response.headers.access_control_max_age = tostring(max_age) response.headers.access_control_allow_origin = origin or "*"; if allow_credentials then response.headers.access_control_allow_credentials = "true"; end end function module.add_host(module) local host = module.host; if host ~= "*" then host = module:get_option_string("http_host", host); end local apps = {}; module.environment.apps = apps; local function http_app_added(event) local app_name = event.item.name; local default_app_path = event.item.default_path or "/"..app_name; local app_path = get_base_path(module, app_name, default_app_path); if not app_name then -- TODO: Link to docs module:log("error", "HTTP app has no 'name', add one or use module:provides('http', app)"); return; end apps[app_name] = apps[app_name] or {}; local app_handlers = apps[app_name]; local app_methods = opt_methods; local app_headers = opt_headers; local app_credentials = opt_credentials; local app_origins; if opt_origins and not (opt_origins:empty() or opt_origins:contains("*")) then app_origins = opt_origins._items; end local function cors_handler(event_data) local request, response = event_data.request, event_data.response; apply_cors_headers(response, app_methods, app_headers, opt_max_age, app_credentials, app_origins, request.headers.origin); end local function options_handler(event_data) cors_handler(event_data); return ""; end local cors = cors_overrides[app_name] or event.item.cors; if cors then if cors.enabled == true then if cors.credentials ~= nil then app_credentials = cors.credentials; end if cors.headers then for header, enable in pairs(cors.headers) do if enable and not app_headers:contains(header) then app_headers = app_headers + set.new { header }; elseif not enable and app_headers:contains(header) then app_headers = app_headers - set.new { header }; end end end if cors.origins then if cors.origins == "*" or cors.origins[1] == "*" then app_origins = nil; else app_origins = set.new(cors.origins)._items; end end elseif cors.enabled == false then cors = nil; end else cors = opt_default_cors; end local streaming = event.item.streaming_uploads; if not event.item.route then -- TODO: Link to docs module:log("error", "HTTP app %q provides no 'route', add one to handle HTTP requests", app_name); return; end for key, handler in pairs(event.item.route) do local event_name = get_http_event(host, app_path, key); if event_name then local method = event_name:match("^%S+"); if not app_methods:contains(method) then app_methods = app_methods + set.new{ method }; end local options_event_name = event_name:gsub("^%S+", "OPTIONS"); if type(handler) ~= "function" then local data = handler; handler = function () return data; end elseif event_name:sub(-2, -1) == "/*" then local base_path_len = #event_name:match("/.+$"); local _handler = handler; handler = function (_event) local path = _event.request.path:sub(base_path_len); return _handler(_event, path); end; module:hook_object_event(server, event_name:sub(1, -3), redir_handler, -1); elseif event_name:sub(-1, -1) == "/" then module:hook_object_event(server, event_name:sub(1, -2), redir_handler, -1); end if not streaming then -- COMPAT Modules not compatible with streaming uploads behave as before. local _handler = handler; function handler(event) -- luacheck: ignore 432/event if event.request.body ~= false then return _handler(event); end end end if not app_handlers[event_name] then app_handlers[event_name] = { main = handler; cors = cors and cors_handler; options = cors and options_handler; }; module:hook_object_event(server, event_name, handler); if cors then module:hook_object_event(server, event_name, cors_handler, 1); module:hook_object_event(server, options_event_name, options_handler, -1); end else module:log("warn", "App %s added handler twice for '%s', ignoring", app_name, event_name); end else module:log("error", "Invalid route in %s, %q. See https://prosody.im/doc/developers/http#routes", app_name, key); end end local services = portmanager.get_active_services(); if services:get("https") or services:get("http") then module:log("info", "Serving '%s' at %s", app_name, module:http_url(app_name, app_path)); elseif prosody.process_type == "prosody" then module:log("error", "Not listening on any ports, '%s' will be unreachable", app_name); end end local function http_app_removed(event) local app_handlers = apps[event.item.name]; apps[event.item.name] = nil; for event_name, handlers in pairs(app_handlers) do module:unhook_object_event(server, event_name, handlers.main); if handlers.cors then module:unhook_object_event(server, event_name, handlers.cors); end if event_name:sub(-2, -1) == "/*" then module:unhook_object_event(server, event_name:sub(1, -3), redir_handler, -1); elseif event_name:sub(-1, -1) == "/" then module:unhook_object_event(server, event_name:sub(1, -2), redir_handler, -1); end if handlers.options then local options_event_name = event_name:gsub("^%S+", "OPTIONS"); module:unhook_object_event(server, options_event_name, handlers.options); end end end module:handle_items("http-provider", http_app_added, http_app_removed); if host ~= "*" then server.add_host(host); function module.unload() server.remove_host(host); end end end module.add_host(module); -- set up handling on global context too local trusted_proxies = module:get_option_set("trusted_proxies", { "127.0.0.1", "::1" })._items; local function is_trusted_proxy(ip) if trusted_proxies[ip] then return true; end local parsed_ip = new_ip(ip) for trusted_proxy in trusted_proxies do if match_ip(parsed_ip, parse_cidr(trusted_proxy)) then return true; end end return false end local function get_forwarded_connection_info(request) --> ip:string, secure:boolean local ip = request.ip; local secure = request.secure; -- set by net.http.server local forwarded_for = request.headers.x_forwarded_for; if forwarded_for then -- luacheck: ignore 631 -- This logic looks weird at first, but it makes sense. -- The for loop will take the last non-trusted-proxy IP from `forwarded_for`. -- We append the original request IP to the header. Then, since the last IP wins, there are two cases: -- Case a) The original request IP is *not* in trusted proxies, in which case the X-Forwarded-For header will, effectively, be ineffective; the original request IP will win because it overrides any other IP in the header. -- Case b) The original request IP is in trusted proxies. In that case, the if branch in the for loop will skip the last IP, causing it to be ignored. The second-to-last IP will be taken instead. -- Case c) If the second-to-last IP is also a trusted proxy, it will also be ignored, iteratively, up to the last IP which isn’t in trusted proxies. -- Case d) If all IPs are in trusted proxies, something went obviously wrong and the logic never overwrites `ip`, leaving it at the original request IP. forwarded_for = forwarded_for..", "..ip; for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do if not is_trusted_proxy(forwarded_ip) then ip = forwarded_ip; end end end secure = secure or request.headers.x_forwarded_proto == "https"; return ip, secure; end module:wrap_object_event(server._events, false, function (handlers, event_name, event_data) local request = event_data.request; if request and is_trusted_proxy(request.ip) then -- Not included in eg http-error events request.ip, request.secure = get_forwarded_connection_info(request); end return handlers(event_name, event_data); end); module:provides("net", { name = "http"; listener = server.listener; private = true; default_port = 5280; multiplex = { pattern = "^[A-Z]"; }; }); module:provides("net", { name = "https"; listener = server.listener; default_port = 5281; encryption = "ssl"; multiplex = { protocol = "http/1.1"; pattern = "^[A-Z]"; }; }); prosody-0.12.4/plugins/PaxHeaders/mod_http_errors.lua0000644000000000000000000000012014475665104017762 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.656765305 prosody-0.12.4/plugins/mod_http_errors.lua0000644000175000017500000000756514475665104022204 0ustar00prosodyprosody00000000000000module:set_global(); local server = require "net.http.server"; local codes = require "net.http.codes"; local xml_escape = require "util.stanza".xml_escape; local render = require "util.interpolation".new("%b{}", xml_escape); local show_private = module:get_option_boolean("http_errors_detailed", false); local always_serve = module:get_option_boolean("http_errors_always_show", true); local default_message = { module:get_option_string("http_errors_default_message", "That's all I know.") }; local default_messages = { [400] = { "What kind of request do you call that??" }; [403] = { "You're not allowed to do that." }; [404] = { "Whatever you were looking for is not here. %"; "Where did you put it?", "It's behind you.", "Keep looking." }; [500] = { "% Check your error log for more info."; "Gremlins.", "It broke.", "Don't look at me." }; ["/"] = { "A study in simplicity."; "Better catch it!"; "Don't just stand there, go after it!"; "Well, say something, before it runs too far!"; "Welcome to the world of XMPP!"; "You can do anything in XMPP!"; -- "The only limit is XML."; "You can do anything with Prosody!"; -- the only limit is memory? }; }; local messages = setmetatable(module:get_option("http_errors_messages", {}), { __index = default_messages }); local html = [[ {title}

{icon?{icon_raw!?}} {title}

{message}

{warning&

⚠ {warning?} ⚠

} {extra&

{extra?}

} ]]; local function get_page(code, extra) local message = messages[code]; if always_serve or message then message = message or default_message; return render(html, { title = rawget(codes, code) or ("Code "..tostring(code)); message = message[1]:gsub("%%", function () return message[math.random(2, math.max(#message,2))]; end); extra = extra; }); end end -- Main error page handler module:hook_object_event(server, "http-error", function (event) if event.response then event.response.headers.content_type = "text/html; charset=utf-8"; end return get_page(event.code, (show_private and event.private_message) or event.message or (event.error and event.error.text)); end); -- Way to use the template for other things so to give a consistent appearance module:hook("http-message", function (event) if event.response then event.response.headers.content_type = "text/html; charset=utf-8"; end return render(html, event); end, -1); local icon = [[ ]]; -- Something nicer shown instead of 404 at the root path, if nothing else handles this path module:hook_object_event(server, "http-error", function (event) local request, response = event.request, event.response; if request and response and request.path == "/" and response.status_code == 404 then response.status_code = 200; response.headers.content_type = "text/html; charset=utf-8"; local message = messages["/"]; return render(html, { icon_raw = icon, title = "Prosody is running!"; message = message[math.random(#message)]; }); end end, 1); prosody-0.12.4/plugins/PaxHeaders/mod_http_file_share.lua0000644000000000000000000000012014475665104020547 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_http_file_share.lua0000644000175000017500000004764014475665104022767 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2021 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- XEP-0363: HTTP File Upload -- Again, from the top! local t_insert = table.insert; local jid = require "util.jid"; local st = require "util.stanza"; local url = require "socket.url"; local dm = require "core.storagemanager".olddm; local jwt = require "util.jwt"; local errors = require "util.error"; local dataform = require "util.dataforms".new; local urlencode = require "util.http".urlencode; local dt = require "util.datetime"; local hi = require "util.human.units"; local cache = require "util.cache"; local lfs = require "lfs"; local unknown = math.abs(0/0); local unlimited = math.huge; local namespace = "urn:xmpp:http:upload:0"; module:depends("disco"); module:add_identity("store", "file", module:get_option_string("name", "HTTP File Upload")); module:add_feature(namespace); local uploads = module:open_store("uploads", "archive"); local persist_stats = module:open_store("upload_stats", "map"); -- id, , time, owner local secret = module:get_option_string(module.name.."_secret", require"util.id".long()); local external_base_url = module:get_option_string(module.name .. "_base_url"); local file_size_limit = module:get_option_number(module.name .. "_size_limit", 10 * 1024 * 1024); -- 10 MB local file_types = module:get_option_set(module.name .. "_allowed_file_types", {}); local safe_types = module:get_option_set(module.name .. "_safe_file_types", {"image/*","video/*","audio/*","text/plain"}); local expiry = module:get_option_number(module.name .. "_expires_after", 7 * 86400); local daily_quota = module:get_option_number(module.name .. "_daily_quota", file_size_limit*10); -- 100 MB / day local total_storage_limit = module:get_option_number(module.name.."_global_quota", unlimited); local access = module:get_option_set(module.name .. "_access", {}); if not external_base_url then module:depends("http"); end module:add_extension(dataform { { name = "FORM_TYPE", type = "hidden", value = namespace }, { name = "max-file-size", type = "text-single", datatype = "xs:integer" }, }:form({ ["max-file-size"] = file_size_limit }, "result")); local upload_errors = errors.init(module.name, namespace, { access = { type = "auth"; condition = "forbidden" }; filename = { type = "modify"; condition = "bad-request"; text = "Invalid filename" }; filetype = { type = "modify"; condition = "not-acceptable"; text = "File type not allowed" }; filesize = { type = "modify"; condition = "not-acceptable"; text = "File too large"; extra = {tag = st.stanza("file-too-large", {xmlns = namespace}):tag("max-file-size"):text(tostring(file_size_limit)) }; }; filesizefmt = { type = "modify"; condition = "bad-request"; text = "File size must be positive integer"; }; quota = { type = "wait"; condition = "resource-constraint"; text = "Daily quota reached"; }; outofdisk = { type = "wait"; condition = "resource-constraint"; text = "Server global storage quota reached" }; }); local upload_cache = cache.new(1024); local quota_cache = cache.new(1024); local total_storage_usage = unknown; local measure_upload_cache_size = module:measure("upload_cache", "amount"); local measure_quota_cache_size = module:measure("quota_cache", "amount"); local measure_total_storage_usage = module:measure("total_storage", "amount", { unit = "bytes" }); do local total, err = persist_stats:get(nil, "total"); if not err then total_storage_usage = tonumber(total) or 0; end end module:hook_global("stats-update", function () measure_upload_cache_size(upload_cache:count()); measure_quota_cache_size(quota_cache:count()); measure_total_storage_usage(total_storage_usage); end); local buckets = {}; for n = 10, 40, 2 do local exp = math.floor(2 ^ n); table.insert(buckets, exp); if exp >= file_size_limit then break end end local measure_uploads = module:measure("upload", "sizes", {buckets = buckets}); -- Convenience wrapper for logging file sizes local function B(bytes) if bytes ~= bytes then return "unknown" elseif bytes == unlimited then return "unlimited"; end return hi.format(bytes, "B", "b"); end local function get_filename(slot, create) return dm.getpath(slot, module.host, module.name, "bin", create) end function get_daily_quota(uploader) local now = os.time(); local max_age = now - 86400; local cached = quota_cache:get(uploader); if cached and cached.time > max_age then return cached.size; end local iter, err = uploads:find(nil, {with = uploader; start = max_age }); if not iter then return iter, err; end local total_bytes = 0; local oldest_upload = now; for _, slot, when in iter do local size = tonumber(slot.attr.size); if size then total_bytes = total_bytes + size; end if when < oldest_upload then oldest_upload = when; end end -- If there were no uploads then we end up caching [now, 0], which is fine -- since we increase the size on new uploads quota_cache:set(uploader, { time = oldest_upload, size = total_bytes }); return total_bytes; end function may_upload(uploader, filename, filesize, filetype) -- > boolean, error local uploader_host = jid.host(uploader); if not ((access:empty() and prosody.hosts[uploader_host]) or access:contains(uploader) or access:contains(uploader_host)) then return false, upload_errors.new("access"); end if not filename or filename:find"/" then -- On Linux, only '/' and '\0' are invalid in filenames and NUL can't be in XML return false, upload_errors.new("filename"); end if not filesize or filesize < 0 or filesize % 1 ~= 0 then return false, upload_errors.new("filesizefmt"); end if filesize > file_size_limit then return false, upload_errors.new("filesize"); end if total_storage_usage + filesize > total_storage_limit then module:log("warn", "Global storage quota reached, at %s / %s!", B(total_storage_usage), B(total_storage_limit)); return false, upload_errors.new("outofdisk"); end local uploader_quota = get_daily_quota(uploader); if uploader_quota + filesize > daily_quota then return false, upload_errors.new("quota"); end if not ( file_types:empty() or file_types:contains(filetype) or file_types:contains(filetype:gsub("/.*", "/*")) ) then return false, upload_errors.new("filetype"); end return true; end function get_authz(slot, uploader, filename, filesize, filetype) local now = os.time(); return jwt.sign(secret, { -- token properties sub = uploader; iat = now; exp = now+300; -- slot properties slot = slot; expires = expiry >= 0 and (now+expiry) or nil; -- file properties filename = filename; filesize = filesize; filetype = filetype; }); end function get_url(slot, filename) local base_url = external_base_url or module:http_url(); local slot_url = url.parse(base_url); slot_url.path = url.parse_path(slot_url.path or "/"); t_insert(slot_url.path, slot); if filename then t_insert(slot_url.path, filename); slot_url.path.is_directory = false; else slot_url.path.is_directory = true; end slot_url.path = url.build_path(slot_url.path); return url.build(slot_url); end function handle_slot_request(event) local stanza, origin = event.stanza, event.origin; local request = st.clone(stanza.tags[1], true); local filename = request.attr.filename; local filesize = tonumber(request.attr.size); local filetype = request.attr["content-type"] or "application/octet-stream"; local uploader = jid.bare(stanza.attr.from); local may, why_not = may_upload(uploader, filename, filesize, filetype); if not may then origin.send(st.error_reply(stanza, why_not)); return true; end module:log("info", "Issuing upload slot to %s for %s", uploader, B(filesize)); local slot, storage_err = errors.coerce(uploads:append(nil, nil, request, os.time(), uploader)) if not slot then origin.send(st.error_reply(stanza, storage_err)); return true; end total_storage_usage = total_storage_usage + filesize; module:log("debug", "Total storage usage: %s / %s", B(total_storage_usage), B(total_storage_limit)); local cached_quota = quota_cache:get(uploader); if cached_quota and cached_quota.time > os.time()-86400 then cached_quota.size = cached_quota.size + filesize; quota_cache:set(uploader, cached_quota); end local authz = get_authz(slot, uploader, filename, filesize, filetype); local slot_url = get_url(slot, filename); local upload_url = slot_url; local reply = st.reply(stanza) :tag("slot", { xmlns = namespace }) :tag("get", { url = slot_url }):up() :tag("put", { url = upload_url }) :text_tag("header", "Bearer "..authz, {name="Authorization"}) :reset(); origin.send(reply); return true; end function handle_upload(event, path) -- PUT /upload/:slot local request = event.request; local authz = request.headers.authorization; if authz then authz = authz:match("^Bearer (.*)") end if not authz then module:log("debug", "Missing or malformed Authorization header"); event.response.headers.www_authenticate = "Bearer"; return 401; end local authed, upload_info = jwt.verify(secret, authz); if not (authed and type(upload_info) == "table" and type(upload_info.exp) == "number") then module:log("debug", "Unauthorized or invalid token: %s, %q", authed, upload_info); return 401; end if not request.body_sink and upload_info.exp < os.time() then module:log("debug", "Authorization token expired on %s", dt.datetime(upload_info.exp)); return 410; end if not path or upload_info.slot ~= path:match("^[^/]+") then module:log("debug", "Invalid upload slot: %q, path: %q", upload_info.slot, path); return 400; end if request.headers.content_length and tonumber(request.headers.content_length) ~= upload_info.filesize then return 413; -- Note: We don't know the size if the upload is streamed in chunked encoding, -- so we also check the final file size on completion. end local filename = get_filename(upload_info.slot, true); do -- check if upload has been completed already -- we want to allow retry of a failed upload attempt, but not after it's been completed local f = io.open(filename, "r"); if f then f:close(); return 409; end end if not request.body_sink then module:log("debug", "Preparing to receive upload into %q, expecting %s", filename, B(upload_info.filesize)); local fh, err = io.open(filename.."~", "w"); if not fh then module:log("error", "Could not open file for writing: %s", err); return 500; end function event.response:on_destroy() -- luacheck: ignore 212/self -- Clean up incomplete upload if io.type(fh) == "file" then -- still open fh:close(); os.remove(filename.."~"); end end request.body_sink = fh; if request.body == false then if request.headers.expect == "100-continue" then request.conn:write("HTTP/1.1 100 Continue\r\n\r\n"); end return true; end end if request.body then module:log("debug", "Complete upload available, %s", B(#request.body)); -- Small enough to have been uploaded already local written, err = errors.coerce(request.body_sink:write(request.body)); if not written then return err; end request.body = nil; end if request.body_sink then local final_size = request.body_sink:seek(); local uploaded, err = errors.coerce(request.body_sink:close()); if final_size ~= upload_info.filesize then -- Could be too short as well, but we say the same thing uploaded, err = false, 413; end if uploaded then module:log("debug", "Upload of %q completed, %s", filename, B(final_size)); assert(os.rename(filename.."~", filename)); measure_uploads(final_size); upload_cache:set(upload_info.slot, { name = upload_info.filename; size = tostring(upload_info.filesize); type = upload_info.filetype; time = os.time(); }); return 201; else assert(os.remove(filename.."~")); return err; end end end local download_cache_hit = module:measure("download_cache_hit", "rate"); local download_cache_miss = module:measure("download_cache_miss", "rate"); function handle_download(event, path) -- GET /uploads/:slot+filename local request, response = event.request, event.response; local slot_id = path:match("^[^/]+"); local basename, filetime, filetype, filesize; local cached = upload_cache:get(slot_id); if cached then module:log("debug", "Cache hit"); download_cache_hit(); basename = cached.name; filesize = cached.size; filetype = cached.type; filetime = cached.time; upload_cache:set(slot_id, cached); -- TODO cache negative hits? else module:log("debug", "Cache miss"); download_cache_miss(); local slot, when = errors.coerce(uploads:get(nil, slot_id)); if not slot then module:log("debug", "uploads:get(%q) --> not-found, %s", slot_id, when); else module:log("debug", "uploads:get(%q) --> %s, %d", slot_id, slot, when); basename = slot.attr.filename; filesize = slot.attr.size; filetype = slot.attr["content-type"]; filetime = when; upload_cache:set(slot_id, { name = basename; size = slot.attr.size; type = filetype; time = when; }); end end if not basename then return 404; end local last_modified = os.date('!%a, %d %b %Y %H:%M:%S GMT', filetime); if request.headers.if_modified_since == last_modified then return 304; end local filename = get_filename(slot_id); local handle, ferr = io.open(filename); if not handle then module:log("error", "Could not open file for reading: %s", ferr); -- This can be because the upload slot wasn't used, or the file disappeared -- somehow, or permission issues. return 410; end local request_range = request.headers.range; local response_range; if request_range then local range_start, range_end = request_range:match("^bytes=(%d+)%-(%d*)$") -- Only support resumption, ie ranges from somewhere in the middle until the end of the file. if (range_start and range_start ~= "0") and (range_end == "" or range_end == filesize) then local pos, size = tonumber(range_start), tonumber(filesize); local new_pos = pos < size and handle:seek("set", pos); if new_pos and new_pos < size then response_range = "bytes "..range_start.."-"..filesize.."/"..filesize; filesize = string.format("%d", size-pos); else handle:close(); return 416; end end end if not filetype then filetype = "application/octet-stream"; end local disposition = "attachment"; if safe_types:contains(filetype) or safe_types:contains(filetype:gsub("/.*", "/*")) then disposition = "inline"; end response.headers.last_modified = last_modified; response.headers.content_length = filesize; response.headers.content_type = filetype; response.headers.content_disposition = string.format("%s; filename*=UTF-8''%s", disposition, urlencode(basename)); if response_range then response.status_code = 206; response.headers.content_range = response_range; end response.headers.accept_ranges = "bytes"; response.headers.cache_control = "max-age=31556952, immutable"; response.headers.content_security_policy = "default-src 'none'; frame-ancestors 'none';" response.headers.strict_transport_security = "max-age=31556952"; response.headers.x_content_type_options = "nosniff"; response.headers.x_frame_options = "DENY"; -- COMPAT IE missing support for CSP frame-ancestors response.headers.x_xss_protection = "1; mode=block"; return response:send_file(handle); end if expiry >= 0 and not external_base_url then -- TODO HTTP DELETE to the external endpoint? local array = require "util.array"; local async = require "util.async"; local ENOENT = require "util.pposix".ENOENT; local function sleep(t) local wait, done = async.waiter(); module:add_timer(t, done) wait(); end local prune_start = module:measure("prune", "times"); module:daily("Remove expired files", function(_, current_time) local prune_done = prune_start(); local boundary_time = (current_time or os.time()) - expiry; local iter, total = assert(uploads:find(nil, {["end"] = boundary_time; total = true})); if total == 0 then module:log("info", "No expired uploaded files to prune"); prune_done(); return; end module:log("info", "Pruning expired files uploaded earlier than %s", dt.datetime(boundary_time)); module:log("debug", "Total storage usage: %s / %s", B(total_storage_usage), B(total_storage_limit)); local obsolete_uploads = array(); local num_expired = 0; local size_sum = 0; local problem_deleting = false; for slot_id, slot_info in iter do num_expired = num_expired + 1; upload_cache:set(slot_id, nil); local filename = get_filename(slot_id); local deleted, err, errno = os.remove(filename); if deleted or errno == ENOENT then -- removed successfully or it was already gone size_sum = size_sum + tonumber(slot_info.attr.size); obsolete_uploads:push(slot_id); else module:log("error", "Could not prune expired file %q: %s", filename, err); problem_deleting = true; end if num_expired % 100 == 0 then sleep(0.1); end end -- obsolete_uploads now contains slot ids for which the files have been -- removed and that needs to be cleared from the database local deletion_query = {["end"] = boundary_time}; if not problem_deleting then module:log("info", "All (%d, %s) expired files successfully pruned", num_expired, B(size_sum)); -- we can delete based on time else module:log("warn", "%d out of %d expired files could not be pruned", num_expired-#obsolete_uploads, num_expired); -- we'll need to delete only those entries where the files were -- successfully removed, and then try again with the failed ones. -- eventually the admin ought to notice and fix the permissions or -- whatever the problem is. deletion_query = {ids = obsolete_uploads}; end total_storage_usage = total_storage_usage - size_sum; module:log("debug", "Total storage usage: %s / %s", B(total_storage_usage), B(total_storage_limit)); persist_stats:set(nil, "total", total_storage_usage); if #obsolete_uploads == 0 then module:log("debug", "No metadata to remove"); else local removed, err = uploads:delete(nil, deletion_query); if removed == true or removed == num_expired or removed == #obsolete_uploads then module:log("debug", "Expired upload metadata pruned successfully"); else module:log("error", "Problem removing metadata for expired files: %s", err); end end prune_done(); end); end local summary_start = module:measure("summary", "times"); module:weekly("Calculate total storage usage", function() local summary_done = summary_start(); local iter = assert(uploads:find(nil)); local count, sum = 0, 0; for _, file in iter do sum = sum + tonumber(file.attr.size); count = count + 1; end module:log("info", "Uploaded files total: %s in %d files", B(sum), count); if persist_stats:set(nil, "total", sum) then total_storage_usage = sum; else total_storage_usage = unknown; end module:log("debug", "Total storage usage: %s / %s", B(total_storage_usage), B(total_storage_limit)); summary_done(); end); -- Reachable from the console function check_files(query) local issues = {}; local iter = assert(uploads:find(nil, query)); for slot_id, file in iter do local filename = get_filename(slot_id); local size, err = lfs.attributes(filename, "size"); if not size then issues[filename] = err; elseif tonumber(file.attr.size) ~= size then issues[filename] = "file size mismatch"; end end return next(issues) == nil, issues; end module:hook("iq-get/host/urn:xmpp:http:upload:0:request", handle_slot_request); if not external_base_url then module:provides("http", { streaming_uploads = true; cors = { enabled = true; credentials = true; headers = { Authorization = true; }; }; route = { ["PUT /*"] = handle_upload; ["GET /*"] = handle_download; ["GET /"] = function (event) return prosody.events.fire_event("http-message", { response = event.response; --- title = "Prosody HTTP Upload endpoint"; message = "This is where files will be uploaded to, and served from."; warning = not (event.request.secure) and "This endpoint is not considered secure!" or nil; }) or "This is the Prosody HTTP Upload endpoint."; end } }); end prosody-0.12.4/plugins/PaxHeaders/mod_http_files.lua0000644000000000000000000000012014475665104017550 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_http_files.lua0000644000175000017500000000576714475665104021774 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:depends("http"); local open = io.open; local fileserver = require"net.http.files"; local base_path = module:get_option_path("http_files_dir", module:get_option_path("http_path")); local cache_size = module:get_option_number("http_files_cache_size", 128); local cache_max_file_size = module:get_option_number("http_files_cache_max_file_size", 4096); local dir_indices = module:get_option_array("http_index_files", { "index.html", "index.htm" }); local directory_index = module:get_option_boolean("http_dir_listing"); local mime_map = module:shared("/*/http_files/mime").types; if not mime_map then mime_map = { html = "text/html", htm = "text/html", xml = "application/xml", txt = "text/plain", css = "text/css", js = "application/javascript", png = "image/png", gif = "image/gif", jpeg = "image/jpeg", jpg = "image/jpeg", svg = "image/svg+xml", }; module:shared("/*/http_files/mime").types = mime_map; local mime_types, err = open(module:get_option_path("mime_types_file", "/etc/mime.types", "config"), "r"); if not mime_types then module:log("debug", "Could not open MIME database: %s", err); else local mime_data = mime_types:read("*a"); mime_types:close(); setmetatable(mime_map, { __index = function(t, ext) local typ = mime_data:match("\n(%S+)[^\n]*%s"..(ext:lower()).."%s") or "application/octet-stream"; t[ext] = typ; return typ; end }); end end local function get_calling_module() local info = debug.getinfo(3, "S"); if not info then return "An unknown module"; end return info.source:match"mod_[^/\\.]+" or info.short_src; end -- COMPAT -- TODO deprecate function serve(opts) if type(opts) ~= "table" then -- assume path string opts = { path = opts }; end if opts.directory_index == nil then opts.directory_index = directory_index; end if opts.mime_map == nil then opts.mime_map = mime_map; end if opts.cache_size == nil then opts.cache_size = cache_size; end if opts.cache_max_file_size == nil then opts.cache_max_file_size = cache_max_file_size; end if opts.index_files == nil then opts.index_files = dir_indices; end module:log("warn", "%s should be updated to use 'net.http.files' instead of mod_http_files", get_calling_module()); return fileserver.serve(opts); end function wrap_route(routes) module:log("debug", "%s should be updated to use 'net.http.files' instead of mod_http_files", get_calling_module()); for route,handler in pairs(routes) do if type(handler) ~= "function" then routes[route] = fileserver.serve(handler); end end return routes; end module:provides("http", { route = { ["GET /*"] = fileserver.serve({ path = base_path; directory_index = directory_index; mime_map = mime_map; cache_size = cache_size; cache_max_file_size = cache_max_file_size; index_files = dir_indices; }); }; }); prosody-0.12.4/plugins/PaxHeaders/mod_http_openmetrics.lua0000644000000000000000000000012014475665104020776 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_http_openmetrics.lua0000644000175000017500000000277714475665104023220 0ustar00prosodyprosody00000000000000-- Export statistics in OpenMetrics format -- -- Copyright (C) 2014 Daurnimator -- Copyright (C) 2018 Emmanuel Gil Peyrot -- Copyright (C) 2021 Jonas Schäfer -- -- This module is MIT/X11 licensed. module:set_global(); local statsman = require "core.statsmanager"; local ip = require "util.ip"; local get_metric_registry = statsman.get_metric_registry; local collect = statsman.collect; local get_metrics; local permitted_ips = module:get_option_set("openmetrics_allow_ips", { "::1", "127.0.0.1" }); local permitted_cidr = module:get_option_string("openmetrics_allow_cidr"); local function is_permitted(request) local ip_raw = request.ip; if permitted_ips:contains(ip_raw) or (permitted_cidr and ip.match(ip.new_ip(ip_raw), ip.parse_cidr(permitted_cidr))) then return true; end return false; end function get_metrics(event) if not is_permitted(event.request) then return 403; -- Forbidden end local response = event.response; response.headers.content_type = "application/openmetrics-text; version=0.0.4"; if collect then -- Ensure to get up-to-date samples when running in manual mode collect() end local registry = get_metric_registry() if registry == nil then response.headers.content_type = "text/plain; charset=utf-8" response.status_code = 404 return "No statistics provider configured\n" end return registry:render(); end module:depends "http"; module:provides("http", { default_path = "metrics"; route = { GET = get_metrics; }; }); prosody-0.12.4/plugins/PaxHeaders/mod_invites.lua0000644000000000000000000000012014475665104017070 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_invites.lua0000644000175000017500000002544714475665104021311 0ustar00prosodyprosody00000000000000local id = require "util.id"; local it = require "util.iterators"; local url = require "socket.url"; local jid_node = require "util.jid".node; local jid_split = require "util.jid".split; local default_ttl = module:get_option_number("invite_expiry", 86400 * 7); local token_storage; if prosody.process_type == "prosody" or prosody.shutdown then token_storage = module:open_store("invite_token", "map"); end local function get_uri(action, jid, token, params) --> string return url.build({ scheme = "xmpp", path = jid, query = action..";preauth="..token..(params and (";"..params) or ""), }); end local function create_invite(invite_action, invite_jid, allow_registration, additional_data, ttl, reusable) local token = id.medium(); local created_at = os.time(); local expires = created_at + (ttl or default_ttl); local invite_params = (invite_action == "roster" and allow_registration) and "ibr=y" or nil; local invite = { type = invite_action; jid = invite_jid; token = token; allow_registration = allow_registration; additional_data = additional_data; uri = get_uri(invite_action, invite_jid, token, invite_params); created_at = created_at; expires = expires; reusable = reusable; }; module:fire_event("invite-created", invite); if allow_registration then local ok, err = token_storage:set(nil, token, invite); if not ok then module:log("warn", "Failed to store account invite: %s", err); return nil, "internal-server-error"; end end if invite_action == "roster" then local username = jid_node(invite_jid); local ok, err = token_storage:set(username, token, expires); if not ok then module:log("warn", "Failed to store subscription invite: %s", err); return nil, "internal-server-error"; end end return invite; end -- Create invitation to register an account (optionally restricted to the specified username) function create_account(account_username, additional_data, ttl) --luacheck: ignore 131/create_account local jid = account_username and (account_username.."@"..module.host) or module.host; return create_invite("register", jid, true, additional_data, ttl); end -- Create invitation to reset the password for an account function create_account_reset(account_username, ttl) --luacheck: ignore 131/create_account_reset return create_account(account_username, { allow_reset = account_username }, ttl or 86400); end -- Create invitation to become a contact of a local user function create_contact(username, allow_registration, additional_data, ttl) --luacheck: ignore 131/create_contact return create_invite("roster", username.."@"..module.host, allow_registration, additional_data, ttl); end -- Create invitation to register an account and join a user group -- If explicit ttl is passed, invite is valid for multiple signups -- during that time period function create_group(group_ids, additional_data, ttl) --luacheck: ignore 131/create_group local merged_additional_data = { groups = group_ids; }; if additional_data then for k, v in pairs(additional_data) do merged_additional_data[k] = v; end end return create_invite("register", module.host, true, merged_additional_data, ttl, not not ttl); end -- Iterates pending (non-expired, unused) invites that allow registration function pending_account_invites() --luacheck: ignore 131/pending_account_invites local store = module:open_store("invite_token"); local now = os.time(); local function is_valid_invite(_, invite) return invite.expires > now; end return it.filter(is_valid_invite, pairs(store:get(nil) or {})); end function get_account_invite_info(token) --luacheck: ignore 131/get_account_invite_info if not token then return nil, "no-token"; end -- Fetch from host store (account invite) local token_info = token_storage:get(nil, token); if not token_info then return nil, "token-invalid"; elseif os.time() > token_info.expires then return nil, "token-expired"; end return token_info; end function delete_account_invite(token) --luacheck: ignore 131/delete_account_invite if not token then return nil, "no-token"; end return token_storage:set(nil, token, nil); end local valid_invite_methods = {}; local valid_invite_mt = { __index = valid_invite_methods }; function valid_invite_methods:use() if self.reusable then return true; end if self.username then -- Also remove the contact invite if present, on the -- assumption that they now have a mutual subscription token_storage:set(self.username, self.token, nil); end token_storage:set(nil, self.token, nil); return true; end -- Get a validated invite (or nil, err). Must call :use() on the -- returned invite after it is actually successfully used -- For "roster" invites, the username of the local user (who issued -- the invite) must be passed. -- If no username is passed, but the registration is a roster invite -- from a local user, the "inviter" field of the returned invite will -- be set to their username. function get(token, username) if not token then return nil, "no-token"; end local valid_until, inviter; -- Fetch from host store (account invite) local token_info = token_storage:get(nil, token); if username then -- token being used for subscription -- Fetch from user store (subscription invite) valid_until = token_storage:get(username, token); else -- token being used for account creation valid_until = token_info and token_info.expires; if token_info and token_info.type == "roster" then username = jid_node(token_info.jid); inviter = username; end end if not valid_until then module:log("debug", "Got unknown token: %s", token); return nil, "token-invalid"; elseif os.time() > valid_until then module:log("debug", "Got expired token: %s", token); return nil, "token-expired"; end return setmetatable({ token = token; username = username; inviter = inviter; type = token_info and token_info.type or "roster"; uri = token_info and token_info.uri or get_uri("roster", username.."@"..module.host, token); additional_data = token_info and token_info.additional_data or nil; reusable = token_info.reusable; }, valid_invite_mt); end function use(token) --luacheck: ignore 131/use local invite = get(token); return invite and invite:use(); end --- shell command do -- Since the console is global this overwrites the command for -- each host it's loaded on, but this should be fine. local get_module = require "core.modulemanager".get_module; local console_env = module:shared("/*/admin_shell/env"); -- luacheck: ignore 212/self console_env.invite = {}; function console_env.invite:create_account(user_jid) local username, host = jid_split(user_jid); local mod_invites, err = get_module(host, "invites"); if not mod_invites then return nil, err or "mod_invites not loaded on this host"; end local invite, err = mod_invites.create_account(username); if not invite then return nil, err; end return true, invite.landing_page or invite.uri; end function console_env.invite:create_contact(user_jid, allow_registration) local username, host = jid_split(user_jid); local mod_invites, err = get_module(host, "invites"); if not mod_invites then return nil, err or "mod_invites not loaded on this host"; end local invite, err = mod_invites.create_contact(username, allow_registration); if not invite then return nil, err; end return true, invite.landing_page or invite.uri; end end --- prosodyctl command function module.command(arg) if #arg < 2 or arg[1] ~= "generate" then print("usage: prosodyctl mod_"..module.name.." generate example.com"); return 2; end table.remove(arg, 1); -- pop command local sm = require "core.storagemanager"; local mm = require "core.modulemanager"; local host = arg[1]; assert(prosody.hosts[host], "Host "..tostring(host).." does not exist"); sm.initialize_host(host); table.remove(arg, 1); -- pop host module.host = host; --luacheck: ignore 122/module token_storage = module:open_store("invite_token", "map"); -- Load mod_invites local invites = module:depends("invites"); -- Optional community module that if used, needs to be loaded here local invites_page_module = module:get_option_string("invites_page_module", "invites_page"); if mm.get_modules_for_host(host):contains(invites_page_module) then module:depends(invites_page_module); end local allow_reset; local roles; local groups = {}; while #arg > 0 do local value = arg[1]; table.remove(arg, 1); if value == "--help" then print("usage: prosodyctl mod_"..module.name.." generate DOMAIN --reset USERNAME") print("usage: prosodyctl mod_"..module.name.." generate DOMAIN [--admin] [--role ROLE] [--group GROUPID]...") print() print("This command has two modes: password reset and new account.") print("If --reset is given, the command operates in password reset mode and in new account mode otherwise.") print() print("required arguments in password reset mode:") print() print(" --reset USERNAME Generate a password reset link for the given USERNAME.") print() print("optional arguments in new account mode:") print() print(" --admin Make the new user privileged") print(" Equivalent to --role prosody:admin") print(" --role ROLE Grant the given ROLE to the new user") print(" --group GROUPID Add the user to the group with the given ID") print(" Can be specified multiple times") print() print("--role and --admin override each other; the last one wins") print("--group can be specified multiple times; the user will be added to all groups.") print() print("--reset and the other options cannot be mixed.") return 2 elseif value == "--reset" then local nodeprep = require "util.encodings".stringprep.nodeprep; local username = nodeprep(arg[1]) table.remove(arg, 1); if not username then print("Please supply a valid username to generate a reset link for"); return 2; end allow_reset = username; elseif value == "--admin" then roles = { ["prosody:admin"] = true }; elseif value == "--role" then local rolename = arg[1]; if not rolename then print("Please supply a role name"); return 2; end roles = { [rolename] = true }; table.remove(arg, 1); elseif value == "--group" or value == "-g" then local groupid = arg[1]; if not groupid then print("Please supply a group ID") return 2; end table.insert(groups, groupid); table.remove(arg, 1); else print("unexpected argument: "..value) end end local invite; if allow_reset then if roles then print("--role/--admin and --reset are mutually exclusive") return 2; end if #groups > 0 then print("--group and --reset are mutually exclusive") end invite = assert(invites.create_account_reset(allow_reset)); else invite = assert(invites.create_account(nil, { roles = roles, groups = groups })); end print(invite.landing_page or invite.uri); end prosody-0.12.4/plugins/PaxHeaders/mod_invites_adhoc.lua0000644000000000000000000000012014475665104020226 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_invites_adhoc.lua0000644000175000017500000000773614475665104022450 0ustar00prosodyprosody00000000000000-- XEP-0401: Easy User Onboarding local dataforms = require "util.dataforms"; local datetime = require "util.datetime"; local split_jid = require "util.jid".split; local usermanager = require "core.usermanager"; local new_adhoc = module:require("adhoc").new; -- Whether local users can invite other users to create an account on this server local allow_user_invites = module:get_option_boolean("allow_user_invites", false); -- Who can see and use the contact invite command. It is strongly recommended to -- keep this available to all local users. To allow/disallow invite-registration -- on the server, use the option above instead. local allow_contact_invites = module:get_option_boolean("allow_contact_invites", true); local allow_user_invite_roles = module:get_option_set("allow_user_invites_by_roles"); local deny_user_invite_roles = module:get_option_set("deny_user_invites_by_roles"); local invites; if prosody.shutdown then -- COMPAT hack to detect prosodyctl invites = module:depends("invites"); end local invite_result_form = dataforms.new({ title = "Your invite has been created", { name = "url" ; var = "landing-url"; label = "Invite web page"; desc = "Share this link"; }, { name = "uri"; label = "Invite URI"; desc = "This alternative link can be opened with some XMPP clients"; }, { name = "expire"; label = "Invite valid until"; }, }); -- This is for checking if the specified JID may create invites -- that allow people to register accounts on this host. local function may_invite_new_users(jid) if usermanager.get_roles then local user_roles = usermanager.get_roles(jid, module.host); if not user_roles then -- User has no roles we can check, just return default return allow_user_invites; end if user_roles["prosody:admin"] then return true; end if allow_user_invite_roles then for allowed_role in allow_user_invite_roles do if user_roles[allowed_role] then return true; end end end if deny_user_invite_roles then for denied_role in deny_user_invite_roles do if user_roles[denied_role] then return false; end end end elseif usermanager.is_admin(jid, module.host) then -- COMPAT w/0.11 return true; -- Admins may always create invitations end -- No role matches, so whatever the default is return allow_user_invites; end module:depends("adhoc"); -- This command is available to all local users, even if allow_user_invites = false -- If allow_user_invites is false, creating an invite still works, but the invite will -- not be valid for registration on the current server, only for establishing a roster -- subscription. module:provides("adhoc", new_adhoc("Create new contact invite", "urn:xmpp:invite#invite", function (_, data) local username, host = split_jid(data.from); if host ~= module.host then return { status = "completed"; error = { message = "This command is only available to users of "..module.host; }; }; end local invite = invites.create_contact(username, may_invite_new_users(data.from), { source = data.from }); --TODO: check errors return { status = "completed"; form = { layout = invite_result_form; values = { uri = invite.uri; url = invite.landing_page; expire = datetime.datetime(invite.expires); }; }; }; end, allow_contact_invites and "local_user" or "admin")); -- This is an admin-only command that creates a new invitation suitable for registering -- a new account. It does not add the new user to the admin's roster. module:provides("adhoc", new_adhoc("Create new account invite", "urn:xmpp:invite#create-account", function (_, data) local invite = invites.create_account(nil, { source = data.from }); --TODO: check errors return { status = "completed"; form = { layout = invite_result_form; values = { uri = invite.uri; url = invite.landing_page; expire = datetime.datetime(invite.expires); }; }; }; end, "admin")); prosody-0.12.4/plugins/PaxHeaders/mod_invites_register.lua0000644000000000000000000000012014475665104020774 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_invites_register.lua0000644000175000017500000001425314475665104023206 0ustar00prosodyprosody00000000000000local st = require "util.stanza"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local rostermanager = require "core.rostermanager"; local require_encryption = module:get_option_boolean("c2s_require_encryption", module:get_option_boolean("require_encryption", true)); local invite_only = module:get_option_boolean("registration_invite_only", true); local invites; if prosody.process_type == "prosody" then invites = module:depends("invites"); if invite_only then module:depends("register_ibr"); end end local legacy_invite_stream_feature = st.stanza("register", { xmlns = "urn:xmpp:invite" }):up(); local invite_stream_feature = st.stanza("register", { xmlns = "urn:xmpp:ibr-token:0" }):up(); module:hook("stream-features", function(event) local session, features = event.origin, event.features; -- Advertise to unauthorized clients only. if session.type ~= "c2s_unauthed" or (require_encryption and not session.secure) then return end features:add_child(legacy_invite_stream_feature); features:add_child(invite_stream_feature); end); -- XEP-0379: Pre-Authenticated Roster Subscription module:hook("presence/bare", function (event) local stanza = event.stanza; if stanza.attr.type ~= "subscribe" then return end local preauth = stanza:get_child("preauth", "urn:xmpp:pars:0"); if not preauth then return end local token = preauth.attr.token; if not token then return end local username, host = jid_split(stanza.attr.to); local invite, err = invites.get(token, username); if not invite then module:log("debug", "Got invalid token, error: %s", err); return; end local contact = jid_bare(stanza.attr.from); module:log("debug", "Approving inbound subscription to %s from %s", username, contact); if rostermanager.set_contact_pending_in(username, host, contact, stanza) then if rostermanager.subscribed(username, host, contact) then invite:use(); rostermanager.roster_push(username, host, contact); -- Send back a subscription request (goal is mutual subscription) if not rostermanager.is_user_subscribed(username, host, contact) and not rostermanager.is_contact_pending_out(username, host, contact) then module:log("debug", "Sending automatic subscription request to %s from %s", contact, username); if rostermanager.set_contact_pending_out(username, host, contact) then rostermanager.roster_push(username, host, contact); module:send(st.presence({type = "subscribe", from = username.."@"..host, to = contact })); else module:log("warn", "Failed to set contact pending out for %s", username); end end end end end, 1); -- Client is submitting a preauth token to allow registration module:hook("stanza/iq/urn:xmpp:pars:0:preauth", function(event) local preauth = event.stanza.tags[1]; local token = preauth.attr.token; local validated_invite = invites.get(token); if not validated_invite then local reply = st.error_reply(event.stanza, "cancel", "forbidden", "The invite token is invalid or expired"); event.origin.send(reply); return true; end event.origin.validated_invite = validated_invite; local reply = st.reply(event.stanza); event.origin.send(reply); return true; end); -- Registration attempt - ensure a valid preauth token has been supplied module:hook("user-registering", function (event) local validated_invite = event.validated_invite or (event.session and event.session.validated_invite); if invite_only and not validated_invite then event.allowed = false; event.reason = "Registration on this server is through invitation only"; return; elseif not validated_invite then -- This registration is not using an invite, but -- the server is not in invite-only mode, so nothing -- for this module to do... return; end if validated_invite and validated_invite.additional_data and validated_invite.additional_data.allow_reset then event.allow_reset = validated_invite.additional_data.allow_reset; end end); -- Make a *one-way* subscription. User will see when contact is online, -- contact will not see when user is online. function subscribe(host, user_username, contact_username) local user_jid = user_username.."@"..host; local contact_jid = contact_username.."@"..host; -- Update user's roster to say subscription request is pending... rostermanager.set_contact_pending_out(user_username, host, contact_jid); -- Update contact's roster to say subscription request is pending... rostermanager.set_contact_pending_in(contact_username, host, user_jid); -- Update contact's roster to say subscription request approved... rostermanager.subscribed(contact_username, host, user_jid); -- Update user's roster to say subscription request approved... rostermanager.process_inbound_subscription_approval(user_username, host, contact_jid); end -- Make a mutual subscription between jid1 and jid2. Each JID will see -- when the other one is online. function subscribe_both(host, user1, user2) subscribe(host, user1, user2); subscribe(host, user2, user1); end -- Registration successful, if there was a preauth token, mark it as used module:hook("user-registered", function (event) local validated_invite = event.validated_invite or (event.session and event.session.validated_invite); if not validated_invite then return; end local inviter_username = validated_invite.inviter; local contact_username = event.username; validated_invite:use(); if inviter_username then module:log("debug", "Creating mutual subscription between %s and %s", inviter_username, contact_username); subscribe_both(module.host, inviter_username, contact_username); rostermanager.roster_push(inviter_username, module.host, contact_username.."@"..module.host); end if validated_invite.additional_data then module:log("debug", "Importing roles from invite"); local roles = validated_invite.additional_data.roles; if roles then module:open_store("roles"):set(contact_username, roles); end end end); -- Equivalent of user-registered but for when the account already existed -- (i.e. password reset) module:hook("user-password-reset", function (event) local validated_invite = event.validated_invite or (event.session and event.session.validated_invite); if not validated_invite then return; end validated_invite:use(); end); prosody-0.12.4/plugins/PaxHeaders/mod_iq.lua0000644000000000000000000000012014475665104016020 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_iq.lua0000644000175000017500000000452314475665104020231 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local full_sessions = prosody.full_sessions; if module:get_host_type() == "local" then module:hook("iq/full", function(data) -- IQ to full JID received local origin, stanza = data.origin, data.stanza; local session = full_sessions[stanza.attr.to]; if not (session and session.send(stanza)) then if stanza.attr.type == "get" or stanza.attr.type == "set" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end end return true; end); end module:hook("iq/bare", function(data) -- IQ to bare JID received local stanza = data.stanza; local type = stanza.attr.type; -- TODO fire post processing events if type == "get" or type == "set" then local child = stanza.tags[1]; local xmlns = child.attr.xmlns or "jabber:client"; local ret = module:fire_event("iq/bare/"..xmlns..":"..child.name, data); if ret ~= nil then return ret; end return module:fire_event("iq-"..type.."/bare/"..xmlns..":"..child.name, data); else return module:fire_event("iq-"..type.."/bare/"..stanza.attr.id, data); end end); module:hook("iq/self", function(data) -- IQ to self JID received local stanza = data.stanza; local type = stanza.attr.type; if type == "get" or type == "set" then local child = stanza.tags[1]; local xmlns = child.attr.xmlns or "jabber:client"; local ret = module:fire_event("iq/self/"..xmlns..":"..child.name, data); if ret ~= nil then return ret; end return module:fire_event("iq-"..type.."/self/"..xmlns..":"..child.name, data); else return module:fire_event("iq-"..type.."/self/"..stanza.attr.id, data); end end); module:hook("iq/host", function(data) -- IQ to a local host received local stanza = data.stanza; local type = stanza.attr.type; if type == "get" or type == "set" then local child = stanza.tags[1]; local xmlns = child.attr.xmlns or "jabber:client"; local ret = module:fire_event("iq/host/"..xmlns..":"..child.name, data); if ret ~= nil then return ret; end return module:fire_event("iq-"..type.."/host/"..xmlns..":"..child.name, data); else return module:fire_event("iq-"..type.."/host/"..stanza.attr.id, data); end end); prosody-0.12.4/plugins/PaxHeaders/mod_lastactivity.lua0000644000000000000000000000012014475665104020127 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_lastactivity.lua0000644000175000017500000000273514475665104022343 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; module:add_feature("jabber:iq:last"); local map = {}; module:hook("pre-presence/bare", function(event) local stanza = event.stanza; if not(stanza.attr.to) and stanza.attr.type == "unavailable" then local t = os.time(); local s = stanza:get_child_text("status"); map[event.origin.username] = {s = s, t = t}; end end, 10); module:hook("iq-get/bare/jabber:iq:last:query", function(event) local origin, stanza = event.origin, event.stanza; local username = jid_split(stanza.attr.to) or origin.username; if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then local seconds, text = "0", ""; if map[username] then seconds = string.format("%d", os.difftime(os.time(), map[username].t)); text = map[username].s; end origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:last', seconds=seconds}):text(text)); else origin.send(st.error_reply(stanza, 'auth', 'forbidden')); end return true; end); module.save = function() return {map = map}; end module.restore = function(data) map = data.map or {}; end prosody-0.12.4/plugins/PaxHeaders/mod_legacyauth.lua0000644000000000000000000000012014475665104017535 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_legacyauth.lua0000644000175000017500000000651414475665104021750 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local t_concat = table.concat; local secure_auth_only = module:get_option("c2s_require_encryption", module:get_option("require_encryption", true)) or not(module:get_option("allow_unencrypted_plain_auth")); local sessionmanager = require "core.sessionmanager"; local usermanager = require "core.usermanager"; local nodeprep = require "util.encodings".stringprep.nodeprep; local resourceprep = require "util.encodings".stringprep.resourceprep; module:add_feature("jabber:iq:auth"); module:hook("stream-features", function(event) local origin, features = event.origin, event.features; if secure_auth_only and not origin.secure then -- Sorry, not offering to insecure streams! return; elseif not origin.username then features:tag("auth", {xmlns='http://jabber.org/features/iq-auth'}):up(); end end); module:hook("stanza/iq/jabber:iq:auth:query", function(event) local session, stanza = event.origin, event.stanza; if session.type ~= "c2s_unauthed" then (session.sends2s or session.send)(st.error_reply(stanza, "cancel", "service-unavailable", "Legacy authentication is only allowed for unauthenticated client connections.")); return true; end if secure_auth_only and not session.secure then session.send(st.error_reply(stanza, "modify", "not-acceptable", "Encryption (SSL or TLS) is required to connect to this server")); return true; end local query = stanza.tags[1]; local username = query:get_child("username"); local password = query:get_child("password"); local resource = query:get_child("resource"); if not (username and password and resource) then local reply = st.reply(stanza); session.send(reply:query("jabber:iq:auth") :tag("username"):up() :tag("password"):up() :tag("resource"):up()); else username, password, resource = t_concat(username), t_concat(password), t_concat(resource); username = nodeprep(username); resource = resourceprep(resource) if not (username and resource) then session.send(st.error_reply(stanza, "modify", "bad-request")); return true; end if usermanager.test_password(username, session.host, password) then -- Authentication successful! local success, err = sessionmanager.make_authenticated(session, username); if success then local err_type, err_msg; success, err_type, err, err_msg = sessionmanager.bind_resource(session, resource); if not success then session.send(st.error_reply(stanza, err_type, err, err_msg)); session.username, session.type = nil, "c2s_unauthed"; -- FIXME should this be placed in sessionmanager? return true; elseif resource ~= session.resource then -- server changed resource, not supported by legacy auth session.send(st.error_reply(stanza, "cancel", "conflict", "The requested resource could not be assigned to this session.")); session:close(); -- FIXME undo resource bind and auth instead of closing the session? return true; end session.send(st.reply(stanza)); else session.send(st.error_reply(stanza, "auth", "not-authorized", err)); end else session.send(st.error_reply(stanza, "auth", "not-authorized")); end end return true; end); prosody-0.12.4/plugins/PaxHeaders/mod_limits.lua0000644000000000000000000000012014475665104016710 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_limits.lua0000644000175000017500000001074714475665104021126 0ustar00prosodyprosody00000000000000-- Because we deal with pre-authed sessions and streams we can't be host-specific module:set_global(); local filters = require "util.filters"; local throttle = require "util.throttle"; local timer = require "util.timer"; local ceil = math.ceil; local limits_cfg = module:get_option("limits", {}); local limits_resolution = module:get_option_number("limits_resolution", 1); local default_bytes_per_second = 3000; local default_burst = 2; local rate_units = { b = 1, k = 3, m = 6, g = 9, t = 12 } -- Plan for the future. local function parse_rate(rate, sess_type) local quantity, unit, exp; if rate then quantity, unit = rate:match("^(%d+) ?([^/]+)/s$"); exp = quantity and rate_units[unit:sub(1,1):lower()]; end if not exp then module:log("error", "Error parsing rate for %s: %q, using default rate (%d bytes/s)", sess_type, rate, default_bytes_per_second); return default_bytes_per_second; end return quantity*(10^exp); end local function parse_burst(burst, sess_type) if type(burst) == "string" then burst = burst:match("^(%d+) ?s$"); end local n_burst = tonumber(burst); if burst and not n_burst then module:log("error", "Unable to parse burst for %s: %q, using default burst interval (%ds)", sess_type, burst, default_burst); end return n_burst or default_burst; end -- Process config option into limits table: -- limits = { c2s = { bytes_per_second = X, burst_seconds = Y } } local limits = { c2s = { bytes_per_second = 10 * 1024; burst_seconds = 2; }; s2sin = { bytes_per_second = 30 * 1024; burst_seconds = 2; }; }; for sess_type, sess_limits in pairs(limits_cfg) do limits[sess_type] = { bytes_per_second = parse_rate(sess_limits.rate, sess_type); burst_seconds = parse_burst(sess_limits.burst, sess_type); }; end local default_filter_set = {}; function default_filter_set.bytes_in(bytes, session) local sess_throttle = session.throttle; if sess_throttle then local ok, _, outstanding = sess_throttle:poll(#bytes, true); if not ok then session.log("debug", "Session over rate limit (%d) with %d (by %d), pausing", sess_throttle.max, #bytes, outstanding); outstanding = ceil(outstanding); session.conn:pause(); -- Read no more data from the connection until there is no outstanding data local outstanding_data = bytes:sub(-outstanding); bytes = bytes:sub(1, #bytes-outstanding); timer.add_task(limits_resolution, function () if not session.conn then return; end if sess_throttle:peek(#outstanding_data) then session.log("debug", "Resuming paused session"); session.conn:resume(); end -- Handle what we can of the outstanding data session.data(outstanding_data); end); end end return bytes; end local type_filters = { c2s = default_filter_set; s2sin = default_filter_set; s2sout = default_filter_set; }; local function filter_hook(session) local session_type = session.type:match("^[^_]+"); local filter_set, opts = type_filters[session_type], limits[session_type]; if opts then if session.conn and session.conn.setlimit then session.conn:setlimit(opts.bytes_per_second); -- Currently no burst support else session.throttle = throttle.create(opts.bytes_per_second * opts.burst_seconds, opts.burst_seconds); filters.add_filter(session, "bytes/in", filter_set.bytes_in, 1000); end end end function module.load() filters.add_filter_hook(filter_hook); end function module.unload() filters.remove_filter_hook(filter_hook); end function unlimited(session) local session_type = session.type:match("^[^_]+"); if session.conn and session.conn.setlimit then session.conn:setlimit(0); -- Currently no burst support else local filter_set = type_filters[session_type]; filters.remove_filter(session, "bytes/in", filter_set.bytes_in); session.throttle = nil; end end function module.add_host(module) local unlimited_jids = module:get_option_inherited_set("unlimited_jids", {}); if not unlimited_jids:empty() then module:hook("authentication-success", function (event) local session = event.session; local jid = session.username .. "@" .. session.host; if unlimited_jids:contains(jid) then unlimited(session); end end); module:hook("s2sout-established", function (event) local session = event.session; if unlimited_jids:contains(session.to_host) then unlimited(session); end end); module:hook("s2sin-established", function (event) local session = event.session; if session.from_host and unlimited_jids:contains(session.from_host) then unlimited(session); end end); end end prosody-0.12.4/plugins/PaxHeaders/mod_mam0000644000000000000000000000013214475665104015404 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.660765266 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_mam/0000755000175000017500000000000014475665104017663 5ustar00prosodyprosody00000000000000prosody-0.12.4/plugins/mod_mam/PaxHeaders/mamprefs.lib.lua0000644000000000000000000000012014475665104020540 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.660765266 prosody-0.12.4/plugins/mod_mam/mamprefs.lib.lua0000644000175000017500000000433014475665104022745 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2017 Matthew Wild -- Copyright (C) 2008-2017 Waqas Hussain -- Copyright (C) 2011-2017 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- XEP-0313: Message Archive Management for Prosody -- -- luacheck: ignore 122/prosody local global_default_policy = module:get_option_string("default_archive_policy", true); if global_default_policy ~= "roster" then global_default_policy = module:get_option_boolean("default_archive_policy", global_default_policy); end local smart_enable = module:get_option_boolean("mam_smart_enable", false); do -- luacheck: ignore 211/prefs_format local prefs_format = { [false] = "roster", -- default ::= true | false | "roster" -- true = always, false = never, nil = global default ["romeo@montague.net"] = true, -- always ["montague@montague.net"] = false, -- newer }; end local sessions = prosody.hosts[module.host].sessions; local archive_store = module:get_option_string("archive_store", "archive"); local prefs = module:open_store(archive_store .. "_prefs"); local function get_prefs(user, explicit) local user_sessions = sessions[user]; local user_prefs = user_sessions and user_sessions.archive_prefs if not user_prefs then -- prefs not cached user_prefs = prefs:get(user); if not user_prefs then -- prefs not set if smart_enable and explicit then -- a mam-capable client was involved in this action, set defaults user_prefs = { [false] = global_default_policy }; prefs:set(user, user_prefs); end end if user_sessions then -- cache settings if they originate from user action user_sessions.archive_prefs = user_prefs; end if not user_prefs then if smart_enable then -- not yet enabled, either explicitly or "smart" user_prefs = { [false] = false }; else -- no explicit settings, return defaults user_prefs = { [false] = global_default_policy }; end end end return user_prefs; end local function set_prefs(user, user_prefs) local user_sessions = sessions[user]; if user_sessions then user_sessions.archive_prefs = user_prefs; end return prefs:set(user, user_prefs); end return { get = get_prefs, set = set_prefs, } prosody-0.12.4/plugins/mod_mam/PaxHeaders/mamprefsxml.lib.lua0000644000000000000000000000012014475665104021261 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_mam/mamprefsxml.lib.lua0000644000175000017500000000320414475665104023465 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2017 Matthew Wild -- Copyright (C) 2008-2017 Waqas Hussain -- Copyright (C) 2011-2017 Kim Alvefur -- Copyright (C) 2018 Emmanuel Gil Peyrot -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- XEP-0313: Message Archive Management for Prosody -- local st = require"util.stanza"; local jid_prep = require"util.jid".prep; local xmlns_mam = "urn:xmpp:mam:2"; local default_attrs = { always = true, [true] = "always", never = false, [false] = "never", roster = "roster", } local function tostanza(prefs) local default = prefs[false]; default = default_attrs[default]; local prefstanza = st.stanza("prefs", { xmlns = xmlns_mam, default = default }); local always = st.stanza("always"); local never = st.stanza("never"); for jid, choice in pairs(prefs) do if jid then (choice and always or never):tag("jid"):text(jid):up(); end end prefstanza:add_child(always):add_child(never); return prefstanza; end local function fromstanza(prefstanza) local prefs = {}; local default = prefstanza.attr.default; if default then prefs[false] = default_attrs[default]; end local always = prefstanza:get_child("always"); if always then for rule in always:childtags("jid") do local jid = jid_prep(rule:get_text()); if jid then prefs[jid] = true; end end end local never = prefstanza:get_child("never"); if never then for rule in never:childtags("jid") do local jid = jid_prep(rule:get_text()); if jid then prefs[jid] = false; end end end return prefs; end return { tostanza = tostanza; fromstanza = fromstanza; } prosody-0.12.4/plugins/mod_mam/PaxHeaders/mod_mam.lua0000644000000000000000000000012014475665104017572 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_mam/mod_mam.lua0000644000175000017500000005016414475665104022005 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2017 Matthew Wild -- Copyright (C) 2008-2017 Waqas Hussain -- Copyright (C) 2011-2021 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- XEP-0313: Message Archive Management for Prosody -- local xmlns_mam = "urn:xmpp:mam:2"; local xmlns_mam_ext = "urn:xmpp:mam:2#extended"; local xmlns_delay = "urn:xmpp:delay"; local xmlns_forward = "urn:xmpp:forward:0"; local xmlns_st_id = "urn:xmpp:sid:0"; local um = require "core.usermanager"; local st = require "util.stanza"; local rsm = require "util.rsm"; local get_prefs = module:require"mamprefs".get; local set_prefs = module:require"mamprefs".set; local prefs_to_stanza = module:require"mamprefsxml".tostanza; local prefs_from_stanza = module:require"mamprefsxml".fromstanza; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local jid_resource = require "util.jid".resource; local jid_prepped_split = require "util.jid".prepped_split; local dataform = require "util.dataforms".new; local get_form_type = require "util.dataforms".get_type; local host = module.host; local rm_load_roster = require "core.rostermanager".load_roster; local is_stanza = st.is_stanza; local tostring = tostring; local time_now = os.time; local m_min = math.min; local timestamp, datestamp = import( "util.datetime", "datetime", "date"); local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); local strip_tags = module:get_option_set("dont_archive_namespaces", { "http://jabber.org/protocol/chatstates" }); local archive_store = module:get_option_string("archive_store", "archive"); local archive = module:open_store(archive_store, "archive"); local cleanup_after = module:get_option_string("archive_expires_after", "1w"); local archive_item_limit = module:get_option_number("storage_archive_item_limit", archive.caps and archive.caps.quota or 1000); local archive_truncate = math.floor(archive_item_limit * 0.99); if not archive.find then error("mod_"..(archive._provided_by or archive.name and "storage_"..archive.name).." does not support archiving\n" .."See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information"); end local use_total = module:get_option_boolean("mam_include_total", true); function schedule_cleanup() -- replaced later if cleanup is enabled end -- Handle prefs. module:hook("iq/self/"..xmlns_mam..":prefs", function(event) local origin, stanza = event.origin, event.stanza; local user = origin.username; if stanza.attr.type == "set" then local new_prefs = stanza:get_child("prefs", xmlns_mam); local prefs = prefs_from_stanza(new_prefs); local ok, err = set_prefs(user, prefs); if not ok then origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error storing preferences: "..tostring(err))); return true; end end local prefs = prefs_to_stanza(get_prefs(user, true)); local reply = st.reply(stanza):add_child(prefs); origin.send(reply); return true; end); local query_form = dataform { { name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam }; { name = "with"; type = "jid-single" }; { name = "start"; type = "text-single"; datatype = "xs:dateTime" }; { name = "end"; type = "text-single"; datatype = "xs:dateTime" }; }; if archive.caps and archive.caps.full_id_range then table.insert(query_form, { name = "before-id"; type = "text-single"; }); table.insert(query_form, { name = "after-id"; type = "text-single"; }); end if archive.caps and archive.caps.ids then table.insert(query_form, { name = "ids"; type = "list-multi"; }); end -- Serve form module:hook("iq-get/self/"..xmlns_mam..":query", function(event) local origin, stanza = event.origin, event.stanza; get_prefs(origin.username, true); origin.send(st.reply(stanza):query(xmlns_mam):add_child(query_form:form())); return true; end); -- Handle archive queries module:hook("iq-set/self/"..xmlns_mam..":query", function(event) local origin, stanza = event.origin, event.stanza; local query = stanza.tags[1]; local qid = query.attr.queryid; origin.mam_requested = true; get_prefs(origin.username, true); -- Search query parameters local qwith, qstart, qend, qbefore, qafter, qids; local form = query:get_child("x", "jabber:x:data"); if form then local form_type, err = get_form_type(form); if not form_type then origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid dataform: "..err)); return true; elseif form_type ~= xmlns_mam then origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam.."'")); return true; end form, err = query_form:data(form); if err then origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); return true; end qwith, qstart, qend = form["with"], form["start"], form["end"]; qbefore, qafter = form["before-id"], form["after-id"]; qids = form["ids"]; qwith = qwith and jid_bare(qwith); -- dataforms does jidprep end -- RSM stuff local qset = rsm.get(query); local qmax = m_min(qset and qset.max or default_max_items, max_max_items); local reverse = qset and qset.before or false; local before, after = qset and qset.before or qbefore, qset and qset.after or qafter; if type(before) ~= "string" then before = nil; end -- A reverse query needs to be flipped local flip = reverse; -- A flip-page query needs to be the opposite of that. if query:get_child("flip-page") then flip = not flip end module:log("debug", "Archive query by %s id=%s with=%s when=%s...%s rsm=%q", origin.username, qid or stanza.attr.id, qwith or "*", qstart and timestamp(qstart) or "", qend and timestamp(qend) or "", qset); -- Load all the data! local data, err = archive:find(origin.username, { start = qstart; ["end"] = qend; -- Time range with = qwith; limit = qmax == 0 and 0 or qmax + 1; before = before; after = after; ids = qids; reverse = reverse; total = use_total or qmax == 0; }); if not data then module:log("debug", "Archive query id=%s failed: %s", qid or stanza.attr.id, err); if err == "item-not-found" then origin.send(st.error_reply(stanza, "modify", "item-not-found")); else origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); end return true; end local total = tonumber(err); local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to }; local results = {}; -- Wrap it in stuff and deliver local first, last; local count = 0; local complete = "true"; for id, item, when in data do count = count + 1; if count > qmax then -- We requested qmax+1 items. If that many items are retrieved then -- there are more results to page through, so: complete = nil; break; end local fwd_st = st.message(msg_reply_attr) :tag("result", { xmlns = xmlns_mam, queryid = qid, id = id }) :tag("forwarded", { xmlns = xmlns_forward }) :tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up(); if not is_stanza(item) then item = st.deserialize(item); end item.attr.xmlns = "jabber:client"; fwd_st:add_child(item); if not first then first = id; end last = id; if flip then results[count] = fwd_st; else origin.send(fwd_st); end end if flip then for i = #results, 1, -1 do origin.send(results[i]); end end if reverse then first, last = last, first; end origin.send(st.reply(stanza) :tag("fin", { xmlns = xmlns_mam, complete = complete }) :add_child(rsm.generate { first = first, last = last, count = total })); -- That's all folks! module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, complete and count or count - 1); return true; end); module:hook("iq-get/self/"..xmlns_mam..":metadata", function (event) local origin, stanza = event.origin, event.stanza; local reply = st.reply(stanza):tag("metadata", { xmlns = xmlns_mam }); do local first = archive:find(origin.username, { limit = 1 }); if not first then origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); return true; end local id, _, when = first(); if id then reply:tag("start", { id = id, timestamp = timestamp(when) }):up(); end end do local last = archive:find(origin.username, { limit = 1, reverse = true }); if not last then origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); return true; end local id, _, when = last(); if id then reply:tag("end", { id = id, timestamp = timestamp(when) }):up(); end end origin.send(reply); return true; end); local function has_in_roster(user, who) local roster = rm_load_roster(user, host); module:log("debug", "%s has %s in roster? %s", user, who, roster[who] and "yes" or "no"); return roster[who]; end local function shall_store(user, who) -- TODO Cache this? if not um.user_exists(user, host) then module:log("debug", "%s@%s does not exist", user, host) return false; end local prefs = get_prefs(user); local rule = prefs[who]; module:log("debug", "%s's rule for %s is %s", user, who, rule); if rule ~= nil then return rule; end -- Below could be done by a metatable local default = prefs[false]; module:log("debug", "%s's default rule is %s", user, default); if default == "roster" then return has_in_roster(user, who); end return default; end local function strip_stanza_id(stanza, user) if stanza:get_child("stanza-id", xmlns_st_id) then stanza = st.clone(stanza); stanza:maptags(function (tag) if tag.name == "stanza-id" and tag.attr.xmlns == xmlns_st_id then local by_user, by_host, res = jid_prepped_split(tag.attr.by); if not res and by_host == host and by_user == user then return nil; end end return tag; end); end return stanza; end local function should_store(stanza) --> boolean, reason: string local st_type = stanza.attr.type or "normal"; -- FIXME pass direction of stanza and use that along with bare/full JID addressing -- for more accurate MUC / type=groupchat check if st_type == "headline" then -- Headline messages are ephemeral by definition return false, "headline"; end if st_type == "error" then -- Errors not sent sent from a local client -- Why would a client send an error anyway? if jid_resource(stanza.attr.to) then -- Store delivery failure notifications so you know if your own messages -- were not delivered. return true, "bounce"; else -- Skip errors for messages that come from your account, such as PEP -- notifications. return false, "bounce"; end end if st_type == "groupchat" then -- MUC messages always go to the full JID, usually archived by the MUC return false, "groupchat"; end if stanza:get_child("no-store", "urn:xmpp:hints") or stanza:get_child("no-permanent-store", "urn:xmpp:hints") then -- XXX Experimental XEP return false, "hint"; end if stanza:get_child("store", "urn:xmpp:hints") then return true, "hint"; end if stanza:get_child("body") then return true, "body"; end if stanza:get_child("subject") then -- XXX Who would send a message with a subject but without a body? return true, "subject"; end if stanza:get_child("encryption", "urn:xmpp:eme:0") then -- Since we can't know what an encrypted message contains, we assume it's important -- XXX Experimental XEP return true, "encrypted"; end if stanza:get_child(nil, "urn:xmpp:receipts") then -- If it's important enough to ask for a receipt then it's important enough to archive -- and the same applies to the receipt return true, "receipt"; end if stanza:get_child(nil, "urn:xmpp:chat-markers:0") then -- XXX Experimental XEP return true, "marker"; end if stanza:get_child("x", "jabber:x:conference") or stanza:find("{http://jabber.org/protocol/muc#user}x/invite") then return true, "invite"; end if stanza:get_child(nil, "urn:xmpp:jingle-message:0") or stanza:get_child(nil, "urn:xmpp:jingle-message:1") then -- XXX Experimental XEP return true, "jingle call"; end -- The IM-NG thing to do here would be to return `not st_to_full` -- One day ... return false, "default"; end module:hook("archive-should-store", function (event) local should, why = should_store(event.stanza); event.reason = why; return should; end, -1) -- Handle messages local function message_handler(event, c2s) local origin, stanza = event.origin, event.stanza; local log = c2s and origin.log or module._log; local orig_from = stanza.attr.from; local orig_to = stanza.attr.to or orig_from; -- Stanza without 'to' are treated as if it was to their own bare jid -- Whose storage do we put it in? local store_user = c2s and origin.username or jid_split(orig_to); -- And who are they chatting with? local with = jid_bare(c2s and orig_to or orig_from); -- Filter out that claim to be from us event.stanza = strip_stanza_id(stanza, store_user); local event_payload = { stanza = stanza; session = origin }; local should = module:fire_event("archive-should-store", event_payload); local why = event_payload.reason; if not should then log("debug", "Not archiving stanza: %s (%s)", stanza:top_tag(), event_payload.reason); return; end local clone_for_storage; if not strip_tags:empty() then clone_for_storage = st.clone(stanza); clone_for_storage:maptags(function (tag) if strip_tags:contains(tag.attr.xmlns) then return nil; else return tag; end end); if #clone_for_storage.tags == 0 then log("debug", "Not archiving stanza: %s (empty when stripped)", stanza:top_tag()); return; end else clone_for_storage = stanza; end -- Check with the users preferences if shall_store(store_user, with) then log("debug", "Archiving stanza: %s (%s)", stanza:top_tag(), why); -- And stash it local time = time_now(); local ok, err = archive:append(store_user, nil, clone_for_storage, time, with); if not ok and err == "quota-limit" then if type(cleanup_after) == "number" then module:log("debug", "User '%s' over quota, cleaning archive", store_user); local cleaned = archive:delete(store_user, { ["end"] = (os.time() - cleanup_after); }); if cleaned then ok, err = archive:append(store_user, nil, clone_for_storage, time, with); end end if not ok and (archive.caps and archive.caps.truncate) then module:log("debug", "User '%s' over quota, truncating archive", store_user); local truncated = archive:delete(store_user, { truncate = archive_truncate; }); if truncated then ok, err = archive:append(store_user, nil, clone_for_storage, time, with); end end end if ok then local clone_for_other_handlers = st.clone(stanza); local id = ok; clone_for_other_handlers:tag("stanza-id", { xmlns = xmlns_st_id, by = store_user.."@"..host, id = id }):up(); event.stanza = clone_for_other_handlers; schedule_cleanup(store_user); module:fire_event("archive-message-added", { origin = origin, stanza = clone_for_storage, for_user = store_user, id = id }); else log("error", "Could not archive stanza: %s", err); end else log("debug", "Not archiving stanza: %s (prefs)", stanza:top_tag()); end end local function c2s_message_handler(event) return message_handler(event, true); end -- Filter out before the message leaves the server to prevent privacy leak. local function strip_stanza_id_after_other_events(event) event.stanza = strip_stanza_id(event.stanza, event.origin.username); end module:hook("pre-message/bare", strip_stanza_id_after_other_events, -1); module:hook("pre-message/full", strip_stanza_id_after_other_events, -1); -- Catch messages not stored by mod_offline and mark them as stored if they -- have been archived. This would generally only happen if mod_offline is -- disabled. Otherwise the message would generate a delivery failure report, -- which would not be accurate because it has been archived. module:hook("message/offline/handle", function(event) local stanza = event.stanza; local user = event.username .. "@" .. host; if stanza:get_child_with_attr("stanza-id", xmlns_st_id, "by", user) then return true; end end, -2); -- Don't broadcast offline messages to clients that have queried the archive. module:hook("message/offline/broadcast", function (event) if event.origin.mam_requested then return true; end end); if cleanup_after ~= "never" then local cleanup_storage = module:open_store("archive_cleanup"); local cleanup_map = module:open_store("archive_cleanup", "map"); local day = 86400; local multipliers = { d = day, w = day * 7, m = 31 * day, y = 365.2425 * day }; local n, m = cleanup_after:lower():match("(%d+)%s*([dwmy]?)"); if not n then module:log("error", "Could not parse archive_expires_after string %q", cleanup_after); return false; end cleanup_after = tonumber(n) * ( multipliers[m] or 1 ); module:log("debug", "archive_expires_after = %d -- in seconds", cleanup_after); if not archive.delete then module:log("error", "archive_expires_after set but mod_%s does not support deleting", archive._provided_by); return false; end -- For each day, store a set of users that have new messages. To expire -- messages, we collect the union of sets of users from dates that fall -- outside the cleanup range. if not (archive.caps and archive.caps.wildcard_delete) then local last_date = require "util.cache".new(module:get_option_number("archive_cleanup_date_cache_size", 1000)); function schedule_cleanup(username, date) date = date or datestamp(); if last_date:get(username) == date then return end local ok = cleanup_map:set(date, username, true); if ok then last_date:set(username, date); end end end local cleanup_time = module:measure("cleanup", "times"); local async = require "util.async"; module:daily("Remove expired messages", function () local cleanup_done = cleanup_time(); if archive.caps and archive.caps.wildcard_delete then local ok, err = archive:delete(true, { ["end"] = os.time() - cleanup_after }) if ok then local sum = tonumber(ok); if sum then module:log("info", "Deleted %d expired messages", sum); else -- driver did not tell module:log("info", "Deleted all expired messages"); end else module:log("error", "Could not delete messages: %s", err); end cleanup_done(); return; end local users = {}; local cut_off = datestamp(os.time() - cleanup_after); for date in cleanup_storage:users() do if date <= cut_off then module:log("debug", "Messages from %q should be expired", date); local messages_this_day = cleanup_storage:get(date); if messages_this_day then for user in pairs(messages_this_day) do users[user] = true; end if date < cut_off then -- Messages from the same day as the cut-off might not have expired yet, -- but all earlier will have, so clear storage for those days. cleanup_storage:set(date, nil); end end end end local sum, num_users = 0, 0; for user in pairs(users) do local ok, err = archive:delete(user, { ["end"] = os.time() - cleanup_after; }) if ok then num_users = num_users + 1; sum = sum + (tonumber(ok) or 0); else cleanup_map:set(cut_off, user, true); module:log("error", "Could not delete messages for user '%s': %s", user, err); end local wait, done = async.waiter(); module:add_timer(0.01, done); wait(); end module:log("info", "Deleted %d expired messages for %d users", sum, num_users); cleanup_done(); end); else module:log("debug", "Archive expiry disabled"); -- Don't ask the backend to count the potentially unbounded number of items, -- it'll get slow. use_total = module:get_option_boolean("mam_include_total", false); end -- Stanzas sent by local clients module:hook("pre-message/bare", c2s_message_handler, 0); module:hook("pre-message/full", c2s_message_handler, 0); -- Stanzas to local clients module:hook("message/bare", message_handler, 0); module:hook("message/full", message_handler, 0); local advertise_extended = archive.caps and archive.caps.full_id_range and archive.caps.ids; module:hook("account-disco-info", function(event) (event.reply or event.stanza):tag("feature", {var=xmlns_mam}):up(); if advertise_extended then (event.reply or event.stanza):tag("feature", {var=xmlns_mam_ext}):up(); end (event.reply or event.stanza):tag("feature", {var=xmlns_st_id}):up(); end); prosody-0.12.4/plugins/PaxHeaders/mod_message.lua0000644000000000000000000000012014475665104017033 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_message.lua0000644000175000017500000000477314475665104021253 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local full_sessions = prosody.full_sessions; local bare_sessions = prosody.bare_sessions; local st = require "util.stanza"; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local user_exists = require "core.usermanager".user_exists; local function process_to_bare(bare, origin, stanza) local user = bare_sessions[bare]; local t = stanza.attr.type; if t == "error" then return true; -- discard elseif t == "groupchat" then local node, host = jid_split(bare); if user_exists(node, host) then if module:fire_event("message/bare/groupchat", { origin = origin, stanza = stanza; }) then return true; end end origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); elseif t == "headline" then if user and stanza.attr.to == bare then for _, session in pairs(user.sessions) do if session.presence and session.priority >= 0 then session.send(stanza); end end end -- current policy is to discard headlines if no recipient is available else -- chat or normal message if user then -- some resources are connected local recipients = user.top_resources; if recipients then local sent; for i=1,#recipients do sent = recipients[i].send(stanza) or sent; end if sent then return true; end end end -- no resources are online local node, host = jid_split(bare); local ok if user_exists(node, host) then ok = module:fire_event('message/offline/handle', { username = node, -- username of the recipient of the offline message origin = origin, -- the sender stanza = stanza, }); end if not ok then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end end return true; end module:hook("message/full", function(data) -- message to full JID received local origin, stanza = data.origin, data.stanza; local session = full_sessions[stanza.attr.to]; if session and session.send(stanza) then return true; else -- resource not online return process_to_bare(jid_bare(stanza.attr.to), origin, stanza); end end, -1); module:hook("message/bare", function(data) -- message to bare JID received local origin, stanza = data.origin, data.stanza; return process_to_bare(stanza.attr.to or (origin.username..'@'..origin.host), origin, stanza); end, -1); prosody-0.12.4/plugins/PaxHeaders/mod_mimicking.lua0000644000000000000000000000012014475665104017356 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_mimicking.lua0000644000175000017500000000527114475665104021570 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2012 Florian Zeitz -- Copyright (C) 2019 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local encodings = require "util.encodings"; assert(encodings.confusable, "This module requires that Prosody be built with ICU"); local skeleton = encodings.confusable.skeleton; local usage = require "util.prosodyctl".show_usage; local usermanager = require "core.usermanager"; local storagemanager = require "core.storagemanager"; local skeletons function module.load() if module.host ~= "*" then skeletons = module:open_store("skeletons"); end end module:hook("user-registered", function(user) local skel = skeleton(user.username); local ok, err = skeletons:set(skel, { username = user.username }); if not ok then module:log("error", "Unable to store mimicry data (%q => %q): %s", user.username, skel, err); end end); module:hook_global("user-deleted", function(user) if user.host ~= module.host then return end local skel = skeleton(user.username); local ok, err = skeletons:set(skel, nil); if not ok and err then module:log("error", "Unable to clear mimicry data (%q): %s", skel, err); end end); module:hook("user-registering", function(user) local existing, err = skeletons:get(skeleton(user.username)); if existing then module:log("debug", "Attempt to register username '%s' which could be confused with '%s'", user.username, existing.username); user.allowed = false; elseif err then module:log("error", "Unable to check if new username '%s' can be confused with any existing user: %s", err); end end); function module.command(arg) if (arg[1] ~= "bootstrap" or not arg[2]) then usage("mod_mimicking bootstrap ", "Initialize username mimicry database"); return; end local host = arg[2]; local host_session = prosody.hosts[host]; if not host_session then return "No such host"; end storagemanager.initialize_host(host); usermanager.initialize_host(host); skeletons = storagemanager.open(host, "skeletons"); local count = 0; for user in usermanager.users(host) do local skel = skeleton(user); local existing, err = skeletons:get(skel); if existing and existing.username ~= user then module:log("warn", "Existing usernames '%s' and '%s' are confusable", existing.username, user); elseif err then module:log("error", "Error checking for existing mimicry data (%q = %q): %s", user, skel, err); end local ok, err = skeletons:set(skel, { username = user }); if ok then count = count + 1; elseif err then module:log("error", "Unable to store mimicry data (%q => %q): %s", user, skel, err); end end module:log("info", "%d usernames indexed", count); end prosody-0.12.4/plugins/PaxHeaders/mod_motd.lua0000644000000000000000000000012014475665104016352 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_motd.lua0000644000175000017500000000155114475665104020561 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2010 Jeff Mitchell -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local host = module:get_host(); local motd_text = module:get_option_string("motd_text"); local motd_jid = module:get_option_string("motd_jid", host); if not motd_text then return; end local st = require "util.stanza"; motd_text = motd_text:gsub("^%s*(.-)%s*$", "%1"):gsub("\n[ \t]+", "\n"); -- Strip indentation from the config module:hook("presence/initial", function (event) local session = event.origin; local motd_stanza = st.message({ to = session.full_jid, from = motd_jid }) :tag("body"):text(motd_text); module:send(motd_stanza); module:log("debug", "MOTD send to user %s", session.full_jid); end, 1); prosody-0.12.4/plugins/PaxHeaders/mod_muc_mam.lua0000644000000000000000000000012014475665104017025 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_muc_mam.lua0000644000175000017500000004303514475665104021237 0ustar00prosodyprosody00000000000000-- XEP-0313: Message Archive Management for Prosody MUC -- Copyright (C) 2011-2021 Kim Alvefur -- -- This file is MIT/X11 licensed. if module:get_host_type() ~= "component" then module:log_status("error", "mod_%s should be loaded only on a MUC component, not normal hosts", module.name); return; end local xmlns_mam = "urn:xmpp:mam:2"; local xmlns_mam_ext = "urn:xmpp:mam:2#extended"; local xmlns_delay = "urn:xmpp:delay"; local xmlns_forward = "urn:xmpp:forward:0"; local xmlns_st_id = "urn:xmpp:sid:0"; local xmlns_muc_user = "http://jabber.org/protocol/muc#user"; local muc_form_enable = "muc#roomconfig_enablearchiving" local st = require "util.stanza"; local rsm = require "util.rsm"; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local jid_prep = require "util.jid".prep; local dataform = require "util.dataforms".new; local get_form_type = require "util.dataforms".get_type; local mod_muc = module:depends"muc"; local get_room_from_jid = mod_muc.get_room_from_jid; local is_stanza = st.is_stanza; local tostring = tostring; local time_now = os.time; local m_min = math.min; local timestamp, datestamp = import("util.datetime", "datetime", "date"); local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); local cleanup_after = module:get_option_string("muc_log_expires_after", "1w"); local default_history_length = 20; local max_history_length = module:get_option_number("max_history_messages", math.huge); local function get_historylength(room) return math.min(room._data.history_length or default_history_length, max_history_length); end function schedule_cleanup() -- replaced by non-noop later if cleanup is enabled end local log_all_rooms = module:get_option_boolean("muc_log_all_rooms", false); local log_by_default = module:get_option_boolean("muc_log_by_default", true); local archive_store = "muc_log"; local archive = module:open_store(archive_store, "archive"); local archive_item_limit = module:get_option_number("storage_archive_item_limit", archive.caps and archive.caps.quota or 1000); local archive_truncate = math.floor(archive_item_limit * 0.99); if archive.name == "null" or not archive.find then if not archive.find then module:log("error", "Attempt to open archive storage returned a driver without archive API support"); module:log("error", "mod_%s does not support archiving", archive._provided_by or archive.name and "storage_"..archive.name.."(?)" or ""); else module:log("error", "Attempt to open archive storage returned null driver"); end module:log("info", "See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information"); return false; end local use_total = module:get_option_boolean("muc_log_include_total", true); local function archiving_enabled(room) if log_all_rooms then module:log("debug", "Archiving all rooms"); return true; end local enabled = room._data.archiving; if enabled == nil then module:log("debug", "Default is %s (for %s)", log_by_default, room.jid); return log_by_default; end module:log("debug", "Logging in room %s is %s", room.jid, enabled); return enabled; end if not log_all_rooms then module:hook("muc-config-form", function(event) local room, form = event.room, event.form; table.insert(form, { name = muc_form_enable, type = "boolean", label = "Enable archiving?", value = archiving_enabled(room), } ); end); module:hook("muc-config-submitted/"..muc_form_enable, function(event) event.room._data.archiving = event.value; event.status_codes[event.value and "170" or "171"] = true; end); end -- Note: We ignore the 'with' field as this is internally used for stanza types local query_form = dataform { { name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam }; { name = "with"; type = "jid-single" }; { name = "start"; type = "text-single"; datatype = "xs:dateTime" }; { name = "end"; type = "text-single"; datatype = "xs:dateTime" }; }; if archive.caps and archive.caps.full_id_range then table.insert(query_form, { name = "before-id"; type = "text-single"; }); table.insert(query_form, { name = "after-id"; type = "text-single"; }); end if archive.caps and archive.caps.ids then table.insert(query_form, { name = "ids"; type = "list-multi"; }); end -- Serve form module:hook("iq-get/bare/"..xmlns_mam..":query", function(event) local origin, stanza = event.origin, event.stanza; origin.send(st.reply(stanza):query(xmlns_mam):add_child(query_form:form())); return true; end); -- Handle archive queries module:hook("iq-set/bare/"..xmlns_mam..":query", function(event) local origin, stanza = event.origin, event.stanza; local room_jid = stanza.attr.to; local room_node = jid_split(room_jid); local orig_from = stanza.attr.from; local query = stanza.tags[1]; local room = get_room_from_jid(room_jid); if not room then origin.send(st.error_reply(stanza, "cancel", "item-not-found")) return true; end local from = jid_bare(orig_from); -- Banned or not a member of a members-only room? local from_affiliation = room:get_affiliation(from); if from_affiliation == "outcast" -- banned or room:get_members_only() and not from_affiliation then -- members-only, not a member origin.send(st.error_reply(stanza, "auth", "forbidden")) return true; end local qid = query.attr.queryid; -- Search query parameters local qstart, qend; local qbefore, qafter; local qids; local form = query:get_child("x", "jabber:x:data"); if form then local form_type, err = get_form_type(form); if not form_type then origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid dataform: "..err)); return true; elseif form_type ~= xmlns_mam then origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam.."'")); return true; end form, err = query_form:data(form); if err then origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); return true; end qstart, qend = form["start"], form["end"]; qbefore, qafter = form["before-id"], form["after-id"]; qids = form["ids"]; end -- RSM stuff local qset = rsm.get(query); local qmax = m_min(qset and qset.max or default_max_items, max_max_items); local reverse = qset and qset.before or false; local before, after = qset and qset.before or qbefore, qset and qset.after or qafter; if type(before) ~= "string" then before = nil; end -- A reverse query needs to be flipped local flip = reverse; -- A flip-page query needs to be the opposite of that. if query:get_child("flip-page") then flip = not flip end module:log("debug", "Archive query by %s id=%s when=%s...%s rsm=%q", from, qid or stanza.attr.id, qstart and timestamp(qstart) or "", qend and timestamp(qend) or "", qset); -- Load all the data! local data, err = archive:find(room_node, { start = qstart; ["end"] = qend; -- Time range with = "message qmax then -- We requested qmax+1 items. If that many items are retrieved then -- there are more results to page through, so: complete = nil; break; end local fwd_st = st.message(msg_reply_attr) :tag("result", { xmlns = xmlns_mam, queryid = qid, id = id }) :tag("forwarded", { xmlns = xmlns_forward }) :tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up(); -- Strip tag, containing the original senders JID, unless the room makes this public if room:get_whois() ~= "anyone" then item:maptags(function (tag) if tag.name == "x" and tag.attr.xmlns == xmlns_muc_user then return nil; end return tag; end); end if not is_stanza(item) then item = st.deserialize(item); end item.attr.to = nil; item.attr.xmlns = "jabber:client"; fwd_st:add_child(item); if not first then first = id; end last = id; if flip then results[count] = fwd_st; else origin.send(fwd_st); end end if flip then for i = #results, 1, -1 do origin.send(results[i]); end end if reverse then first, last = last, first; end origin.send(st.reply(stanza) :tag("fin", { xmlns = xmlns_mam, complete = complete }) :add_child(rsm.generate { first = first, last = last, count = total })); -- That's all folks! module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, complete and count or count - 1); return true; end); module:hook("muc-get-history", function (event) local room = event.room; if not archiving_enabled(room) then return end local room_jid = room.jid; local maxstanzas = event.maxstanzas; local maxchars = event.maxchars; local since = event.since; local to = event.to; if maxstanzas == 0 or maxchars == 0 then return -- No history requested end if not maxstanzas or maxstanzas > get_historylength(room) then maxstanzas = get_historylength(room); end if room._history and #room._history >= maxstanzas then return -- It can deal with this itself end -- Load all the data! local query = { limit = maxstanzas; start = since; reverse = true; with = "message that claim to be from us stanza:maptags(function (tag) if tag.name == "stanza-id" and tag.attr.xmlns == xmlns_st_id and jid_prep(tag.attr.by) == room.jid then return nil; end return tag; end); end, 1); -- Handle messages local function save_to_history(self, stanza) local room_node = jid_split(self.jid); local stored_stanza = stanza; if stanza.name == "message" and self:get_whois() == "anyone" then stored_stanza = st.clone(stanza); stored_stanza.attr.to = nil; local occupant = self._occupants[stanza.attr.from]; if occupant then local actor = jid_bare(occupant.jid); local affiliation = self:get_affiliation(actor) or "none"; local role = self:get_role(actor) or self:get_default_role(affiliation); stored_stanza:add_direct_child(st.stanza("x", { xmlns = xmlns_muc_user }) :tag("item", { affiliation = affiliation; role = role; jid = actor })); end end -- Policy check if not archiving_enabled(self) then return end -- Don't log -- Save the type in the 'with' field, allows storing presence without conflicts local with = stanza.name if stanza.attr.type then with = with .. "<" .. stanza.attr.type end -- And stash it local time = time_now(); local id, err = archive:append(room_node, nil, stored_stanza, time, with); if not id and err == "quota-limit" then if type(cleanup_after) == "number" then module:log("debug", "Room '%s' over quota, cleaning archive", room_node); local cleaned = archive:delete(room_node, { ["end"] = (os.time() - cleanup_after); }); if cleaned then id, err = archive:append(room_node, nil, stored_stanza, time, with); end end if not id and (archive.caps and archive.caps.truncate) then module:log("debug", "Room '%s' over quota, truncating archive", room_node); local truncated = archive:delete(room_node, { truncate = archive_truncate; }); if truncated then id, err = archive:append(room_node, nil, stored_stanza, time, with); end end end if id then schedule_cleanup(room_node); stanza:add_direct_child(st.stanza("stanza-id", { xmlns = xmlns_st_id, by = self.jid, id = id })); else module:log("error", "Could not archive stanza: %s", err); end end module:hook("muc-add-history", function (event) local room, stanza = event.room, event.stanza; save_to_history(room, stanza); end); if module:get_option_boolean("muc_log_presences", false) then module:hook("muc-occupant-joined", function (event) save_to_history(event.room, st.stanza("presence", { from = event.nick }):tag("x", { xmlns = "http://jabber.org/protocol/muc" })); end); module:hook("muc-occupant-left", function (event) save_to_history(event.room, st.stanza("presence", { type = "unavailable", from = event.nick })); end); end if not archive.delete then module:log("warn", "Storage driver %s does not support deletion", archive._provided_by); module:log("warn", "Archived message will persist after a room has been destroyed"); else module:hook("muc-room-destroyed", function(event) local room_node = jid_split(event.room.jid); archive:delete(room_node); end); end -- And role/affiliation changes? module:add_feature(xmlns_mam); local advertise_extended = archive.caps and archive.caps.full_id_range and archive.caps.ids; module:hook("muc-disco#info", function(event) if archiving_enabled(event.room) then event.reply:tag("feature", {var=xmlns_mam}):up(); if advertise_extended then (event.reply or event.stanza):tag("feature", {var=xmlns_mam_ext}):up(); end end event.reply:tag("feature", {var=xmlns_st_id}):up(); end); -- Cleanup if cleanup_after ~= "never" then local cleanup_storage = module:open_store("muc_log_cleanup"); local cleanup_map = module:open_store("muc_log_cleanup", "map"); local day = 86400; local multipliers = { d = day, w = day * 7, m = 31 * day, y = 365.2425 * day }; local n, m = cleanup_after:lower():match("(%d+)%s*([dwmy]?)"); if not n then module:log("error", "Could not parse muc_log_expires_after string %q", cleanup_after); return false; end cleanup_after = tonumber(n) * ( multipliers[m] or 1 ); module:log("debug", "muc_log_expires_after = %d -- in seconds", cleanup_after); if not archive.delete then module:log("error", "muc_log_expires_after set but mod_%s does not support deleting", archive._provided_by); return false; end -- For each day, store a set of rooms that have new messages. To expire -- messages, we collect the union of sets of rooms from dates that fall -- outside the cleanup range. local last_date = require "util.cache".new(module:get_option_number("muc_log_cleanup_date_cache_size", 1000)); if not ( archive.caps and archive.caps.wildcard_delete ) then function schedule_cleanup(roomname, date) date = date or datestamp(); if last_date:get(roomname) == date then return end local ok = cleanup_map:set(date, roomname, true); if ok then last_date:set(roomname, date); end end end local cleanup_time = module:measure("cleanup", "times"); local async = require "util.async"; module:daily("Remove expired messages", function () local cleanup_done = cleanup_time(); if archive.caps and archive.caps.wildcard_delete then local ok, err = archive:delete(true, { ["end"] = os.time() - cleanup_after }) if ok then local sum = tonumber(ok); if sum then module:log("info", "Deleted %d expired messages", sum); else -- driver did not tell module:log("info", "Deleted all expired messages"); end else module:log("error", "Could not delete messages: %s", err); end cleanup_done(); return; end local rooms = {}; local cut_off = datestamp(os.time() - cleanup_after); for date in cleanup_storage:users() do if date <= cut_off then module:log("debug", "Messages from %q should be expired", date); local messages_this_day = cleanup_storage:get(date); if messages_this_day then for room in pairs(messages_this_day) do rooms[room] = true; end if date < cut_off then -- Messages from the same day as the cut-off might not have expired yet, -- but all earlier will have, so clear storage for those days. cleanup_storage:set(date, nil); end end end end local sum, num_rooms = 0, 0; for room in pairs(rooms) do local ok, err = archive:delete(room, { ["end"] = os.time() - cleanup_after; }) if ok then num_rooms = num_rooms + 1; sum = sum + (tonumber(ok) or 0); else cleanup_map:set(cut_off, room, true); module:log("error", "Could not delete messages for room '%s': %s", room, err); end local wait, done = async.waiter(); module:add_timer(0.01, done); wait(); end module:log("info", "Deleted %d expired messages for %d rooms", sum, num_rooms); cleanup_done(); end); else module:log("debug", "Archive expiry disabled"); -- Don't ask the backend to count the potentially unbounded number of items, -- it'll get slow. use_total = module:get_option_boolean("mam_include_total", false); end prosody-0.12.4/plugins/PaxHeaders/mod_muc_unique.lua0000644000000000000000000000012014475665104017561 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_muc_unique.lua0000644000175000017500000000074614475665104021775 0ustar00prosodyprosody00000000000000-- XEP-0307: Unique Room Names for Multi-User Chat local st = require "util.stanza"; local unique_name = require "util.id".medium; module:add_feature "http://jabber.org/protocol/muc#unique" module:hook("iq-get/host/http://jabber.org/protocol/muc#unique:unique", function(event) local origin, stanza = event.origin, event.stanza; origin.send(st.reply(stanza) :tag("unique", {xmlns = "http://jabber.org/protocol/muc#unique"}) :text(unique_name():lower()) ); return true; end,-1); prosody-0.12.4/plugins/PaxHeaders/mod_net_multiplex.lua0000644000000000000000000000012014475665104020300 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_net_multiplex.lua0000644000175000017500000000645214475665104022514 0ustar00prosodyprosody00000000000000module:set_global(); local array = require "util.array"; local max_buffer_len = module:get_option_number("multiplex_buffer_size", 1024); local default_mode = module:get_option_number("network_default_read_size", 4096); local portmanager = require "core.portmanager"; local available_services = {}; local service_by_protocol = {}; local available_protocols = array(); local function add_service(service) local multiplex_pattern = service.multiplex and service.multiplex.pattern; local protocol_name = service.multiplex and service.multiplex.protocol; if protocol_name then module:log("debug", "Adding multiplex service %q with protocol %q", service.name, protocol_name); service_by_protocol[protocol_name] = service; available_protocols:push(protocol_name); end if multiplex_pattern then module:log("debug", "Adding multiplex service %q with pattern %q", service.name, multiplex_pattern); available_services[service] = multiplex_pattern; elseif not protocol_name then module:log("debug", "Service %q is not multiplex-capable", service.name); end end module:hook("service-added", function (event) add_service(event.service); end); module:hook("service-removed", function (event) available_services[event.service] = nil; if event.service.multiplex and event.service.multiplex.protocol then available_protocols:filter(function (p) return p ~= event.service.multiplex.protocol end); service_by_protocol[event.service.multiplex.protocol] = nil; end end); for _, services in pairs(portmanager.get_registered_services()) do for _, service in ipairs(services) do add_service(service); end end local buffers = {}; local listener = { default_mode = max_buffer_len }; function listener.onconnect(conn) local sock = conn:socket(); if sock.getalpn then local selected_proto = sock:getalpn(); local service = service_by_protocol[selected_proto]; if service then module:log("debug", "Routing incoming connection to %s based on ALPN %q", service.name, selected_proto); local next_listener = service.listener; conn:setlistener(next_listener); conn:set_mode(next_listener.default_mode or default_mode); local onconnect = next_listener.onconnect; if onconnect then return onconnect(conn) end end end end function listener.onincoming(conn, data) if not data then return; end local buf = buffers[conn]; buf = buf and buf..data or data; for service, multiplex_pattern in pairs(available_services) do if buf:match(multiplex_pattern) then module:log("debug", "Routing incoming connection to %s", service.name); local next_listener = service.listener; conn:setlistener(next_listener); conn:set_mode(next_listener.default_mode or default_mode); local onconnect = next_listener.onconnect; if onconnect then onconnect(conn) end return next_listener.onincoming(conn, buf); end end if #buf > max_buffer_len then -- Give up conn:close(); else buffers[conn] = buf; end end function listener.ondisconnect(conn) buffers[conn] = nil; -- warn if no buffer? end listener.ondetach = listener.ondisconnect; module:provides("net", { name = "multiplex"; config_prefix = ""; listener = listener; }); module:provides("net", { name = "multiplex_ssl"; config_prefix = "ssl"; encryption = "ssl"; ssl_config = { alpn = function () return available_protocols; end; }; listener = listener; }); prosody-0.12.4/plugins/PaxHeaders/mod_offline.lua0000644000000000000000000000012014475665104017031 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.664765227 prosody-0.12.4/plugins/mod_offline.lua0000644000175000017500000000261614475665104021243 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2009 Matthew Wild -- Copyright (C) 2008-2009 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local datetime = require "util.datetime"; local jid_split = require "util.jid".split; local offline_messages = module:open_store("offline", "archive"); module:add_feature("msgoffline"); module:hook("message/offline/handle", function(event) local origin, stanza = event.origin, event.stanza; local to = stanza.attr.to; local node; if to then node = jid_split(to) else node = origin.username; end local ok = offline_messages:append(node, nil, stanza, os.time(), ""); if ok then module:log("debug", "Saved to offline storage: %s", stanza:top_tag()); end return ok; end, -1); module:hook("message/offline/broadcast", function(event) local origin = event.origin; origin.log("debug", "Broadcasting offline messages"); local node, host = origin.username, origin.host; local data = offline_messages:find(node); if not data then return true; end for _, stanza, when in data do stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = datetime.datetime(when)}):up(); -- XEP-0203 origin.send(stanza); end local ok = offline_messages:delete(node); if type(ok) == "number" and ok > 0 then origin.log("debug", "%d offline messages consumed"); end return true; end, -1); prosody-0.12.4/plugins/PaxHeaders/mod_pep.lua0000644000000000000000000000012014475665104016173 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.668765187 prosody-0.12.4/plugins/mod_pep.lua0000644000175000017500000003561614475665104020413 0ustar00prosodyprosody00000000000000local pubsub = require "util.pubsub"; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local jid_join = require "util.jid".join; local set_new = require "util.set".new; local st = require "util.stanza"; local calculate_hash = require "util.caps".calculate_hash; local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; local cache = require "util.cache"; local set = require "util.set"; local new_id = require "util.id".medium; local storagemanager = require "core.storagemanager"; local usermanager = require "core.usermanager"; local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; local lib_pubsub = module:require "pubsub"; local empty_set = set_new(); -- username -> object passed to module:add_items() local pep_service_items = {}; -- size of caches with full pubsub service objects local service_cache_size = module:get_option_number("pep_service_cache_size", 1000); -- username -> util.pubsub service object local services = cache.new(service_cache_size, function (username, _) local item = pep_service_items[username]; pep_service_items[username] = nil; if item then module:remove_item("pep-service", item); end end):table(); -- size of caches with smaller objects local info_cache_size = module:get_option_number("pep_info_cache_size", 10000); -- username -> recipient -> set of nodes local recipients = cache.new(info_cache_size):table(); -- caps hash -> set of nodes local hash_map = cache.new(info_cache_size):table(); local host = module.host; local node_config = module:open_store("pep", "map"); local known_nodes = module:open_store("pep"); local max_max_items = module:get_option_number("pep_max_items", 256); local function tonumber_max_items(n) if n == "max" then return max_max_items; end return tonumber(n); end for _, field in ipairs(lib_pubsub.node_config_form) do if field.var == "pubsub#max_items" then field.range_max = max_max_items; break; end end function module.save() return { recipients = recipients; }; end function module.restore(data) recipients = data.recipients; end function is_item_stanza(item) return st.is_stanza(item) and item.attr.xmlns == xmlns_pubsub and item.name == "item" and #item.tags == 1; end function check_node_config(node, actor, new_config) -- luacheck: ignore 212/node 212/actor if (tonumber_max_items(new_config["max_items"]) or 1) > max_max_items then return false; end if new_config["access_model"] ~= "presence" and new_config["access_model"] ~= "whitelist" and new_config["access_model"] ~= "open" then return false; end return true; end local function subscription_presence(username, recipient) local user_bare = jid_join(username, host); local recipient_bare = jid_bare(recipient); if (recipient_bare == user_bare) then return true; end return is_contact_subscribed(username, host, recipient_bare); end local function nodestore(username) -- luacheck: ignore 212/self local store = {}; function store:get(node) local data, err = node_config:get(username, node) if data == true then -- COMPAT Previously stored only a boolean representing 'persist_items' data = { name = node; config = {}; subscribers = {}; affiliations = {}; }; end return data, err; end function store:set(node, data) return node_config:set(username, node, data); end function store:users() return pairs(known_nodes:get(username) or {}); end return store; end local function simple_itemstore(username) local driver = storagemanager.get_driver(module.host, "pep_data"); return function (config, node) local max_items = tonumber_max_items(config["max_items"]); module:log("debug", "Creating new persistent item store for user %s, node %q", username, node); local archive = driver:open("pep_"..node, "archive"); return lib_pubsub.archive_itemstore(archive, max_items, username, node, false); end end local function get_broadcaster(username) local user_bare = jid_join(username, host); local function simple_broadcast(kind, node, jids, item, _, node_obj) if node_obj then if node_obj.config["notify_"..kind] == false then return; end end if kind == "retract" then kind = "items"; -- XEP-0060 signals retraction in an container end if item then item = st.clone(item); item.attr.xmlns = nil; -- Clear the pubsub namespace if kind == "items" then if node_obj and node_obj.config.include_payload == false then item:maptags(function () return nil; end); end end end local id = new_id(); local message = st.message({ from = user_bare, type = "headline", id = id }) :tag("event", { xmlns = xmlns_pubsub_event }) :tag(kind, { node = node }); if item then message:add_child(item); end for jid in pairs(jids) do module:log("debug", "Sending notification to %s from %s for node %s", jid, user_bare, node); message.attr.to = jid; module:send(message); end end return simple_broadcast; end local function get_subscriber_filter(username) return function (jids, node) local broadcast_to = {}; for jid, opts in pairs(jids) do broadcast_to[jid] = opts; end local service_recipients = recipients[username]; if service_recipients then local service = services[username]; for recipient, nodes in pairs(service_recipients) do if nodes:contains(node) and service:may(node, recipient, "subscribe") then broadcast_to[recipient] = true; end end end return broadcast_to; end end -- Read-only service with no nodes where nobody is allowed anything to act as a -- fallback for interactions with non-existent users local nobody_service = pubsub.new({ node_defaults = { ["max_items"] = 1; ["persist_items"] = false; ["access_model"] = "presence"; ["send_last_published_item"] = "on_sub_and_presence"; }; autocreate_on_publish = false; autocreate_on_subscribe = false; get_affiliation = function () return "outcast"; end; }); function get_pep_service(username) local user_bare = jid_join(username, host); local service = services[username]; if service then return service; end if not usermanager.user_exists(username, host) then return nobody_service; end module:log("debug", "Creating pubsub service for user %q", username); service = pubsub.new({ pep_username = username; node_defaults = { ["max_items"] = 1; ["persist_items"] = true; ["access_model"] = "presence"; ["send_last_published_item"] = "on_sub_and_presence"; }; max_items = max_max_items; autocreate_on_publish = true; autocreate_on_subscribe = false; nodestore = nodestore(username); itemstore = simple_itemstore(username); broadcaster = get_broadcaster(username); subscriber_filter = get_subscriber_filter(username); itemcheck = is_item_stanza; get_affiliation = function (jid) if jid_bare(jid) == user_bare then return "owner"; end end; access_models = { presence = function (jid) if subscription_presence(username, jid) then return "member"; end return "outcast"; end; }; jid = user_bare; normalize_jid = jid_bare; check_node_config = check_node_config; }); services[username] = service; local item = { service = service, jid = user_bare } pep_service_items[username] = item; module:add_item("pep-service", item); return service; end function handle_pubsub_iq(event) local origin, stanza = event.origin, event.stanza; local service_name = origin.username; if stanza.attr.to ~= nil then service_name = jid_split(stanza.attr.to); end local service = get_pep_service(service_name); return lib_pubsub.handle_pubsub_iq(event, service) end module:hook("iq/bare/"..xmlns_pubsub..":pubsub", handle_pubsub_iq); module:hook("iq/bare/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq); local function get_caps_hash_from_presence(stanza, current) local t = stanza.attr.type; if not t then local child = stanza:get_child("c", "http://jabber.org/protocol/caps"); if child then local attr = child.attr; if attr.hash then -- new caps if attr.hash == 'sha-1' and attr.node and attr.ver then return attr.ver, attr.node.."#"..attr.ver; end else -- legacy caps if attr.node and attr.ver then return attr.node.."#"..attr.ver.."#"..(attr.ext or ""), attr.node.."#"..attr.ver; end end end return; -- no or bad caps elseif t == "unavailable" or t == "error" then return; end return current; -- no caps, could mean caps optimization, so return current end local function resend_last_item(jid, node, service) local ok, config = service:get_node_config(node, true); if ok and config.send_last_published_item ~= "on_sub_and_presence" then return end local ok, id, item = service:get_last_item(node, jid); if not (ok and id) then return; end service.config.broadcaster("items", node, { [jid] = true }, item); end local function update_subscriptions(recipient, service_name, nodes) nodes = nodes or empty_set; local service_recipients = recipients[service_name]; if not service_recipients then service_recipients = {}; recipients[service_name] = service_recipients; end local current = service_recipients[recipient]; if not current then current = empty_set; end if (current == empty_set or current:empty()) and (nodes == empty_set or nodes:empty()) then return; end local service = get_pep_service(service_name); for node in nodes - current do if service:may(node, recipient, "subscribe") then resend_last_item(recipient, node, service); end end if nodes == empty_set or nodes:empty() then nodes = nil; end service_recipients[recipient] = nodes; end module:hook("presence/bare", function(event) -- inbound presence to bare JID received local origin, stanza = event.origin, event.stanza; local t = stanza.attr.type; local is_self = not stanza.attr.to; local username = jid_split(stanza.attr.to); local user_bare = jid_bare(stanza.attr.to); if is_self then username = origin.username; user_bare = jid_join(username, host); end if not t then -- available presence if is_self or subscription_presence(username, stanza.attr.from) then local recipient = stanza.attr.from; local current = recipients[username] and recipients[username][recipient]; local hash, query_node = get_caps_hash_from_presence(stanza, current); if current == hash or (current and current == hash_map[hash]) then return; end if not hash then update_subscriptions(recipient, username); else recipients[username] = recipients[username] or {}; if hash_map[hash] then update_subscriptions(recipient, username, hash_map[hash]); else -- COMPAT from ~= stanza.attr.to because OneTeam can't deal with missing from attribute origin.send( st.stanza("iq", {from=user_bare, to=stanza.attr.from, id="disco", type="get"}) :tag("query", {xmlns = "http://jabber.org/protocol/disco#info", node = query_node}) ); end end end elseif t == "unavailable" then update_subscriptions(stanza.attr.from, username); elseif not is_self and t == "unsubscribe" then local from = jid_bare(stanza.attr.from); local subscriptions = recipients[username]; if subscriptions then for subscriber in pairs(subscriptions) do if jid_bare(subscriber) == from then update_subscriptions(subscriber, username); end end end end end, 10); module:hook("iq-result/bare/disco", function(event) local origin, stanza = event.origin, event.stanza; local disco = stanza:get_child("query", "http://jabber.org/protocol/disco#info"); if not disco then return; end -- Process disco response local is_self = stanza.attr.to == nil; local user_bare = jid_bare(stanza.attr.to); local username = jid_split(stanza.attr.to); if is_self then username = origin.username; user_bare = jid_join(username, host); end local contact = stanza.attr.from; local ver = calculate_hash(disco.tags); -- calculate hash local notify = set_new(); for _, feature in pairs(disco.tags) do if feature.name == "feature" and feature.attr.var then local nfeature = feature.attr.var:match("^(.*)%+notify$"); if nfeature then notify:add(nfeature); end end end hash_map[ver] = notify; -- update hash map if is_self then -- Optimization: Fiddle with other local users for jid, item in pairs(origin.roster) do -- for all interested contacts if jid then local contact_node, contact_host = jid_split(jid); if contact_host == host and (item.subscription == "both" or item.subscription == "from") then update_subscriptions(user_bare, contact_node, notify); end end end end update_subscriptions(contact, username, notify); end); module:hook("account-disco-info-node", function(event) local stanza, origin = event.stanza, event.origin; local service_name = origin.username; if stanza.attr.to ~= nil then service_name = jid_split(stanza.attr.to); end local service = get_pep_service(service_name); return lib_pubsub.handle_disco_info_node(event, service); end); module:hook("account-disco-info", function(event) local origin, reply = event.origin, event.reply; reply:tag('identity', {category='pubsub', type='pep'}):up(); local username = jid_split(reply.attr.from) or origin.username; local service = get_pep_service(username); local supported_features = lib_pubsub.get_feature_set(service) + set.new{ -- Features not covered by the above "auto-subscribe", "filtered-notifications", "last-published", "presence-notifications", "presence-subscribe", }; reply:tag('feature', {var=xmlns_pubsub}):up(); for feature in supported_features do reply:tag('feature', {var=xmlns_pubsub.."#"..feature}):up(); end end); module:hook("account-disco-items-node", function(event) local stanza, origin = event.stanza, event.origin; local is_self = stanza.attr.to == nil; local username = jid_split(stanza.attr.to); if is_self then username = origin.username; end local service = get_pep_service(username); return lib_pubsub.handle_disco_items_node(event, service); end); module:hook("account-disco-items", function(event) local reply, stanza, origin = event.reply, event.stanza, event.origin; local is_self = stanza.attr.to == nil; local user_bare = jid_bare(stanza.attr.to); local username = jid_split(stanza.attr.to); if is_self then username = origin.username; user_bare = jid_join(username, host); end local service = get_pep_service(username); local ok, ret = service:get_nodes(jid_bare(stanza.attr.from)); if not ok then return; end for node, node_obj in pairs(ret) do reply:tag("item", { jid = user_bare, node = node, name = node_obj.config.title }):up(); end end); module:hook_global("user-deleted", function(event) if event.host ~= host then return end local username = event.username; local service = services[username]; if not service then return end for node in pairs(service.nodes) do service:delete(node, true); end local item = pep_service_items[username]; pep_service_items[username] = nil; if item then module:remove_item("pep-service", item); end recipients[username] = nil; end); prosody-0.12.4/plugins/PaxHeaders/mod_pep_plus.lua0000644000000000000000000000012014475665104017236 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.668765187 prosody-0.12.4/plugins/mod_pep_plus.lua0000644000175000017500000000022114475665104021436 0ustar00prosodyprosody00000000000000module:log("error", "mod_pep_plus has been renamed to mod_pep, please update your config file. Auto-loading mod_pep..."); module:depends("pep"); prosody-0.12.4/plugins/PaxHeaders/mod_pep_simple.lua0000644000000000000000000000012014475665104017544 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.668765187 prosody-0.12.4/plugins/mod_pep_simple.lua0000644000175000017500000002643714475665104021765 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local st = require "util.stanza"; local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; local pairs = pairs; local next = next; local type = type; local unpack = table.unpack or unpack; -- luacheck: ignore 113 local calculate_hash = require "util.caps".calculate_hash; local core_post_stanza = prosody.core_post_stanza; local bare_sessions = prosody.bare_sessions; local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; -- Used as canonical 'empty table' local NULL = {}; -- data[user_bare_jid][node] = item_stanza local data = {}; --- recipients[user_bare_jid][contact_full_jid][subscribed_node] = true local recipients = {}; -- hash_map[hash][subscribed_nodes] = true local hash_map = {}; module.save = function() return { data = data, recipients = recipients, hash_map = hash_map }; end module.restore = function(state) data = state.data or {}; recipients = state.recipients or {}; hash_map = state.hash_map or {}; end local function subscription_presence(user_bare, recipient) local recipient_bare = jid_bare(recipient); if (recipient_bare == user_bare) then return true end local username, host = jid_split(user_bare); return is_contact_subscribed(username, host, recipient_bare); end module:hook("pep-publish-item", function (event) local session, bare, node, id, item = event.session, event.user, event.node, event.id, event.item; item.attr.xmlns = nil; local disable = #item.tags ~= 1 or #item.tags[1] == 0; if #item.tags == 0 then item.name = "retract"; end local stanza = st.message({from=bare, type='headline'}) :tag('event', {xmlns='http://jabber.org/protocol/pubsub#event'}) :tag('items', {node=node}) :add_child(item) :up() :up(); -- store for the future local user_data = data[bare]; if disable then if user_data then user_data[node] = nil; if not next(user_data) then data[bare] = nil; end end else if not user_data then user_data = {}; data[bare] = user_data; end user_data[node] = {id, item}; end -- broadcast for recipient, notify in pairs(recipients[bare] or NULL) do if notify[node] then stanza.attr.to = recipient; core_post_stanza(session, stanza); end end end); local function publish_all(user, recipient, session) local d = data[user]; local notify = recipients[user] and recipients[user][recipient]; if d and notify then for node in pairs(notify) do if d[node] then -- luacheck: ignore id local id, item = unpack(d[node]); session.send(st.message({from=user, to=recipient, type='headline'}) :tag('event', {xmlns='http://jabber.org/protocol/pubsub#event'}) :tag('items', {node=node}) :add_child(item) :up() :up()); end end end end local function get_caps_hash_from_presence(stanza, current) local t = stanza.attr.type; if not t then for _, child in pairs(stanza.tags) do if child.name == "c" and child.attr.xmlns == "http://jabber.org/protocol/caps" then local attr = child.attr; if attr.hash then -- new caps if attr.hash == 'sha-1' and attr.node and attr.ver then return attr.ver, attr.node.."#"..attr.ver; end else -- legacy caps if attr.node and attr.ver then return attr.node.."#"..attr.ver.."#"..(attr.ext or ""), attr.node.."#"..attr.ver; end end return; -- bad caps format end end elseif t == "unavailable" or t == "error" then return; end return current; -- no caps, could mean caps optimization, so return current end module:hook("presence/bare", function(event) -- inbound presence to bare JID received local origin, stanza = event.origin, event.stanza; local user = stanza.attr.to or (origin.username..'@'..origin.host); local t = stanza.attr.type; local self = not stanza.attr.to; -- Only cache subscriptions if user is online if not bare_sessions[user] then return; end if not t then -- available presence if self or subscription_presence(user, stanza.attr.from) then local recipient = stanza.attr.from; local current = recipients[user] and recipients[user][recipient]; local hash = get_caps_hash_from_presence(stanza, current); if current == hash or (current and current == hash_map[hash]) then return; end if not hash then if recipients[user] then recipients[user][recipient] = nil; end else recipients[user] = recipients[user] or {}; if hash_map[hash] then recipients[user][recipient] = hash_map[hash]; publish_all(user, recipient, origin); else recipients[user][recipient] = hash; local from_bare = origin.type == "c2s" and origin.username.."@"..origin.host; if self or origin.type ~= "c2s" or (recipients[from_bare] and recipients[from_bare][origin.full_jid]) ~= hash then -- COMPAT from ~= stanza.attr.to because OneTeam and Asterisk 1.8 can't deal with missing from attribute origin.send( st.stanza("iq", {from=user, to=stanza.attr.from, id="disco", type="get"}) :query("http://jabber.org/protocol/disco#info") ); end end end end elseif t == "unavailable" then if recipients[user] then recipients[user][stanza.attr.from] = nil; end elseif not self and t == "unsubscribe" then local from = jid_bare(stanza.attr.from); local subscriptions = recipients[user]; if subscriptions then for subscriber in pairs(subscriptions) do if jid_bare(subscriber) == from then recipients[user][subscriber] = nil; end end end end end, 10); module:hook("iq/bare/http://jabber.org/protocol/pubsub:pubsub", function(event) local session, stanza = event.origin, event.stanza; local payload = stanza.tags[1]; if stanza.attr.type == 'set' and (not stanza.attr.to or jid_bare(stanza.attr.from) == stanza.attr.to) then payload = payload.tags[1]; -- if payload and (payload.name == 'publish' or payload.name == 'retract') and payload.attr.node then local node = payload.attr.node; payload = payload.tags[1]; if payload and payload.name == "item" then -- local id = payload.attr.id or "1"; payload.attr.id = id; session.send(st.reply(stanza)); module:fire_event("pep-publish-item", { node = node, user = jid_bare(session.full_jid), actor = session.jid, id = id, session = session, item = st.clone(payload); }); return true; else module:log("debug", "Payload is missing the ", node); end else module:log("debug", "Unhandled payload: %s", payload and payload:top_tag() or "(no payload)"); end elseif stanza.attr.type == 'get' then local user = stanza.attr.to and jid_bare(stanza.attr.to) or session.username..'@'..session.host; if subscription_presence(user, stanza.attr.from) then local user_data = data[user]; local node, requested_id; payload = payload.tags[1]; if payload and payload.name == 'items' then node = payload.attr.node; local item = payload.tags[1]; if item and item.name == "item" then requested_id = item.attr.id; end end if node and user_data and user_data[node] then -- Send the last item local id, item = unpack(user_data[node]); if not requested_id or id == requested_id then local reply_stanza = st.reply(stanza) :tag('pubsub', {xmlns='http://jabber.org/protocol/pubsub'}) :tag('items', {node=node}) :add_child(item) :up() :up(); session.send(reply_stanza); return true; else -- requested item doesn't exist local reply_stanza = st.reply(stanza) :tag('pubsub', {xmlns='http://jabber.org/protocol/pubsub'}) :tag('items', {node=node}) :up(); session.send(reply_stanza); return true; end elseif node then -- node doesn't exist session.send(st.error_reply(stanza, 'cancel', 'item-not-found')); module:log("debug", "Item '%s' not found", node) return true; else --invalid request session.send(st.error_reply(stanza, 'modify', 'bad-request')); module:log("debug", "Invalid request: %s", payload); return true; end else --no presence subscription session.send(st.error_reply(stanza, 'auth', 'not-authorized') :tag('presence-subscription-required', {xmlns='http://jabber.org/protocol/pubsub#errors'})); module:log("debug", "Unauthorized request: %s", payload); return true; end end end); module:hook("iq-result/bare/disco", function(event) local session, stanza = event.origin, event.stanza; if stanza.attr.type == "result" then local disco = stanza.tags[1]; if disco and disco.name == "query" and disco.attr.xmlns == "http://jabber.org/protocol/disco#info" then -- Process disco response local self = not stanza.attr.to; local user = stanza.attr.to or (session.username..'@'..session.host); local contact = stanza.attr.from; local current = recipients[user] and recipients[user][contact]; if type(current) ~= "string" then return; end -- check if waiting for recipient's response local ver = current; if not string.find(current, "#") then ver = calculate_hash(disco.tags); -- calculate hash end local notify = {}; for _, feature in pairs(disco.tags) do if feature.name == "feature" and feature.attr.var then local nfeature = feature.attr.var:match("^(.*)%+notify$"); if nfeature then notify[nfeature] = true; end end end hash_map[ver] = notify; -- update hash map if self then for jid, item in pairs(session.roster) do -- for all interested contacts if item.subscription == "both" or item.subscription == "from" then if not recipients[jid] then recipients[jid] = {}; end recipients[jid][contact] = notify; publish_all(jid, contact, session); end end end recipients[user][contact] = notify; -- set recipient's data to calculated data -- send messages to recipient publish_all(user, contact, session); end end end); module:hook("account-disco-info", function(event) local reply = event.reply; reply:tag('identity', {category='pubsub', type='pep'}):up(); reply:tag('feature', {var=xmlns_pubsub}):up(); local features = { "access-presence", "auto-create", "auto-subscribe", "filtered-notifications", "item-ids", "last-published", "presence-notifications", "presence-subscribe", "publish", "retract-items", "retrieve-items", }; for _, feature in ipairs(features) do reply:tag('feature', {var=xmlns_pubsub.."#"..feature}):up(); end end); module:hook("account-disco-items", function(event) local reply = event.reply; local bare = reply.attr.to; local user_data = data[bare]; if user_data then for node, _ in pairs(user_data) do reply:tag('item', {jid=bare, node=node}):up(); end end end); module:hook("account-disco-info-node", function (event) local stanza, node = event.stanza, event.node; local user = stanza.attr.to; local user_data = data[user]; if user_data and user_data[node] then event.exists = true; event.reply:tag('identity', {category='pubsub', type='leaf'}):up(); end end); module:hook("resource-unbind", function (event) local user_bare_jid = event.session.username.."@"..event.session.host; if not bare_sessions[user_bare_jid] then -- User went offline -- We don't need this info cached anymore, clear it. recipients[user_bare_jid] = nil; end end); prosody-0.12.4/plugins/PaxHeaders/mod_ping.lua0000644000000000000000000000012014475665104016344 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.668765187 prosody-0.12.4/plugins/mod_ping.lua0000644000175000017500000000077314475665104020560 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; module:add_feature("urn:xmpp:ping"); local function ping_handler(event) event.origin.send(st.reply(event.stanza)); return true; end module:hook("iq-get/bare/urn:xmpp:ping:ping", ping_handler); module:hook("iq-get/host/urn:xmpp:ping:ping", ping_handler); prosody-0.12.4/plugins/PaxHeaders/mod_posix.lua0000644000000000000000000000012014475665104016551 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.668765187 prosody-0.12.4/plugins/mod_posix.lua0000644000175000017500000001155714475665104020767 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local want_pposix_version = "0.4.0"; local pposix = assert(require "util.pposix"); if pposix._VERSION ~= want_pposix_version then module:log("warn", "Unknown version (%s) of binary pposix module, expected %s." .. "Perhaps you need to recompile?", tostring(pposix._VERSION), want_pposix_version); end local have_signal, signal = pcall(require, "util.signal"); if not have_signal then module:log("warn", "Couldn't load signal library, won't respond to SIGTERM"); end local lfs = require "lfs"; local stat = lfs.attributes; local prosody = _G.prosody; module:set_global(); -- we're a global module local umask = module:get_option_string("umask", "027"); pposix.umask(umask); -- Don't even think about it! if not prosody.start_time then -- server-starting if pposix.getuid() == 0 and not module:get_option_boolean("run_as_root") then module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!"); module:log("error", "For more information on running Prosody as root, see https://prosody.im/doc/root"); prosody.shutdown("Refusing to run as root", 1); end end local pidfile; local pidfile_handle; local function remove_pidfile() if pidfile_handle then pidfile_handle:close(); os.remove(pidfile); pidfile, pidfile_handle = nil, nil; end end local function write_pidfile() if pidfile_handle then remove_pidfile(); end pidfile = module:get_option_path("pidfile", nil, "data"); if pidfile then local err; local mode = stat(pidfile) and "r+" or "w+"; pidfile_handle, err = io.open(pidfile, mode); if not pidfile_handle then module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err); prosody.shutdown("Couldn't write pidfile", 1); else if not lfs.lock(pidfile_handle, "w") then -- Exclusive lock local other_pid = pidfile_handle:read("*a"); module:log("error", "Another Prosody instance seems to be running with PID %s, quitting", other_pid); pidfile_handle = nil; prosody.shutdown("Prosody already running", 1); else pidfile_handle:close(); pidfile_handle, err = io.open(pidfile, "w+"); if not pidfile_handle then module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err); prosody.shutdown("Couldn't write pidfile", 1); else if lfs.lock(pidfile_handle, "w") then pidfile_handle:write(tostring(pposix.getpid())); pidfile_handle:flush(); end end end end end end local daemonize = prosody.opts.daemonize; if daemonize == nil then -- Fall back to config file if not specified on command-line daemonize = module:get_option_boolean("daemonize", nil); if daemonize ~= nil then module:log("warn", "The 'daemonize' option has been deprecated, specify -D or -F on the command line instead."); -- TODO: Write some docs and include a link in the warning. end end local function remove_log_sinks() local lm = require "core.loggingmanager"; lm.register_sink_type("console", nil); lm.register_sink_type("stdout", nil); lm.reload_logging(); end if daemonize then local function daemonize_server() module:log("info", "Prosody is about to detach from the console, disabling further console output"); remove_log_sinks(); local ok, ret = pposix.daemonize(); if not ok then module:log("error", "Failed to daemonize: %s", ret); elseif ret and ret > 0 then os.exit(0); else module:log("info", "Successfully daemonized to PID %d", pposix.getpid()); write_pidfile(); end end module:hook("server-started", daemonize_server) else -- Not going to daemonize, so write the pid of this process write_pidfile(); end module:hook("server-stopped", remove_pidfile); -- Set signal handlers if have_signal then module:add_timer(0, function () signal.signal("SIGTERM", function () module:log("warn", "Received SIGTERM"); prosody.main_thread:run(function () prosody.unlock_globals(); prosody.shutdown("Received SIGTERM"); prosody.lock_globals(); end); end); signal.signal("SIGHUP", function () module:log("info", "Received SIGHUP"); prosody.main_thread:run(function () prosody.reload_config(); end); -- this also reloads logging end); signal.signal("SIGINT", function () module:log("info", "Received SIGINT"); prosody.main_thread:run(function () prosody.unlock_globals(); prosody.shutdown("Received SIGINT"); prosody.lock_globals(); end); end); signal.signal("SIGUSR1", function () module:log("info", "Received SIGUSR1"); module:fire_event("signal/SIGUSR1"); end); signal.signal("SIGUSR2", function () module:log("info", "Received SIGUSR2"); module:fire_event("signal/SIGUSR2"); end); end); end -- For other modules to reference features = { signal_events = true; }; prosody-0.12.4/plugins/PaxHeaders/mod_presence.lua0000644000000000000000000000012014475665104017213 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.668765187 prosody-0.12.4/plugins/mod_presence.lua0000644000175000017500000003735714475665104021437 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local log = module._log; local require = require; local pairs = pairs; local s_find = string.find; local tonumber = tonumber; local core_post_stanza = prosody.core_post_stanza; local core_process_stanza = prosody.core_process_stanza; local st = require "util.stanza"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local datetime = require "util.datetime"; local hosts = prosody.hosts; local bare_sessions = prosody.bare_sessions; local full_sessions = prosody.full_sessions; local NULL = {}; local rostermanager = require "core.rostermanager"; local sessionmanager = require "core.sessionmanager"; local recalc_resource_map = require "util.presence".recalc_resource_map; local ignore_presence_priority = module:get_option_boolean("ignore_presence_priority", false); local pre_approval_stream_feature = st.stanza("sub", {xmlns="urn:xmpp:features:pre-approval"}); module:hook("stream-features", function(event) local origin, features = event.origin, event.features; if origin.username then features:add_child(pre_approval_stream_feature); end end); function handle_normal_presence(origin, stanza) if ignore_presence_priority then local priority = stanza:get_child("priority"); if priority and priority[1] ~= "0" then for i=#priority.tags,1,-1 do priority.tags[i] = nil; end for i=#priority,2,-1 do priority[i] = nil; end priority[1] = "0"; end end local priority = stanza:get_child_text("priority"); if priority and s_find(priority, "^[+-]?[0-9]+$") then priority = tonumber(priority); if priority < -128 then priority = -128 end if priority > 127 then priority = 127 end else priority = 0; end if full_sessions[origin.full_jid] then -- if user is still connected origin.send(stanza); -- reflect their presence back to them end local roster = origin.roster; local node, host = origin.username, origin.host; local user = bare_sessions[node.."@"..host]; for _, res in pairs(user and user.sessions or NULL) do -- broadcast to all resources if res ~= origin and res.presence then -- to resource stanza.attr.to = res.full_jid; core_post_stanza(origin, stanza, true); end end for jid, item in pairs(roster) do -- broadcast to all interested contacts if item.subscription == "both" or item.subscription == "from" then stanza.attr.to = jid; core_post_stanza(origin, stanza, true); end end stanza.attr.to = nil; if stanza.attr.type == nil and not origin.presence then -- initial presence module:fire_event("presence/initial", { origin = origin, stanza = stanza } ); origin.presence = stanza; -- FIXME repeated later local probe = st.presence({from = origin.full_jid, type = "probe"}); for jid, item in pairs(roster) do -- probe all contacts we are subscribed to if item.subscription == "both" or item.subscription == "to" then probe.attr.to = jid; core_post_stanza(origin, probe, true); end end for _, res in pairs(user and user.sessions or NULL) do -- broadcast from all available resources if res ~= origin and res.presence then res.presence.attr.to = origin.full_jid; core_post_stanza(res, res.presence, true); res.presence.attr.to = nil; end end for jid, pending_request in pairs(roster[false].pending) do -- resend incoming subscription requests if type(pending_request) == "table" then local subscribe = st.deserialize(pending_request); subscribe.attr.type, subscribe.attr.from = "subscribe", jid; origin.send(subscribe); else origin.send(st.presence({type="subscribe", from=jid})); end end local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host}); for jid, item in pairs(roster) do -- resend outgoing subscription requests if item.ask then request.attr.to = jid; core_post_stanza(origin, request, true); end end if priority >= 0 then local event = { origin = origin } module:fire_event('message/offline/broadcast', event); end end if stanza.attr.type == "unavailable" then origin.presence = nil; if origin.priority then origin.priority = nil; recalc_resource_map(user); end if origin.directed then for jid in pairs(origin.directed) do stanza.attr.to = jid; core_post_stanza(origin, stanza, true); end origin.directed = nil; end else origin.presence = stanza; stanza:tag("delay", { xmlns = "urn:xmpp:delay", from = host, stamp = datetime.datetime() }):up(); if origin.priority ~= priority then origin.priority = priority; recalc_resource_map(user); end end stanza.attr.to = nil; -- reset it end -- luacheck: ignore 212/recipient_session -- TODO This argument is used in 3rd party modules function send_presence_of_available_resources(user, host, jid, recipient_session, stanza) local h = hosts[host]; local count = 0; if h and h.type == "local" then local u = h.sessions[user]; if u then for _, session in pairs(u.sessions) do local pres = session.presence; if pres then if stanza then pres = stanza; pres.attr.from = session.full_jid; end pres.attr.to = jid; core_post_stanza(session, pres, true); pres.attr.to = nil; count = count + 1; end end end end log("debug", "broadcasted presence of %d resources from %s@%s to %s", count, user, host, jid); return count; end function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare) local node, host = jid_split(from_bare); if to_bare == from_bare then return; end -- No self contacts local st_from, st_to = stanza.attr.from, stanza.attr.to; stanza.attr.from, stanza.attr.to = from_bare, to_bare; log("debug", "outbound presence %s from %s for %s", stanza.attr.type, from_bare, to_bare); if stanza.attr.type == "probe" then stanza.attr.from, stanza.attr.to = st_from, st_to; return; elseif stanza.attr.type == "subscribe" then -- 1. route stanza -- 2. roster push (subscription = none, ask = subscribe) if rostermanager.set_contact_pending_out(node, host, to_bare) then rostermanager.roster_push(node, host, to_bare); end -- else file error core_post_stanza(origin, stanza); elseif stanza.attr.type == "unsubscribe" then -- 1. route stanza -- 2. roster push (subscription = none or from) if rostermanager.unsubscribe(node, host, to_bare) then rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed? end -- else file error core_post_stanza(origin, stanza); elseif stanza.attr.type == "subscribed" then -- 1. route stanza -- 2. roster_push () -- 3. send_presence_of_available_resources if rostermanager.subscribed(node, host, to_bare) then rostermanager.roster_push(node, host, to_bare); end if rostermanager.is_contact_subscribed(node, host, to_bare) then core_post_stanza(origin, stanza); send_presence_of_available_resources(node, host, to_bare, origin); end if rostermanager.is_user_subscribed(node, host, to_bare) then core_post_stanza(origin, st.presence({ type = "probe", from = from_bare, to = to_bare })); end elseif stanza.attr.type == "unsubscribed" then -- 1. send unavailable -- 2. route stanza -- 3. roster push (subscription = from or both) -- luacheck: ignore 211/pending_in -- Is pending_in meant to be used? local success, pending_in, subscribed = rostermanager.unsubscribed(node, host, to_bare); if success then if subscribed then rostermanager.roster_push(node, host, to_bare); end core_post_stanza(origin, stanza); if subscribed then send_presence_of_available_resources(node, host, to_bare, origin, st.presence({ type = "unavailable" })); end end else origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid presence type")); end stanza.attr.from, stanza.attr.to = st_from, st_to; return true; end function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare) local node, host = jid_split(to_bare); local st_from, st_to = stanza.attr.from, stanza.attr.to; stanza.attr.from, stanza.attr.to = from_bare, to_bare; log("debug", "inbound presence %s from %s for %s", stanza.attr.type, from_bare, to_bare); if stanza.attr.type == "probe" then local result, err = rostermanager.is_contact_subscribed(node, host, from_bare); if result then if 0 == send_presence_of_available_resources(node, host, st_from, origin) then core_post_stanza(hosts[host], st.presence({from=to_bare, to=st_from, type="unavailable"}), true); -- TODO send last activity end elseif not err then core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}), true); end elseif stanza.attr.type == "subscribe" then if rostermanager.is_contact_subscribed(node, host, from_bare) then core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"}), true); -- already subscribed -- Sending presence is not clearly stated in the RFC, but it seems appropriate if 0 == send_presence_of_available_resources(node, host, from_bare, origin) then core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- TODO send last activity end elseif rostermanager.is_contact_preapproved(node, host, from_bare) then if not rostermanager.is_contact_pending_in(node, host, from_bare) then if rostermanager.set_contact_pending_in(node, host, from_bare, stanza) then core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"}), true); end -- TODO else return error, unable to save end else core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- acknowledging receipt if not rostermanager.is_contact_pending_in(node, host, from_bare) then if rostermanager.set_contact_pending_in(node, host, from_bare, stanza) then sessionmanager.send_to_available_resources(node, host, stanza); end -- TODO else return error, unable to save end end elseif stanza.attr.type == "unsubscribe" then if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then sessionmanager.send_to_interested_resources(node, host, stanza); rostermanager.roster_push(node, host, from_bare); end elseif stanza.attr.type == "subscribed" then if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then sessionmanager.send_to_interested_resources(node, host, stanza); rostermanager.roster_push(node, host, from_bare); end elseif stanza.attr.type == "unsubscribed" then if rostermanager.process_inbound_subscription_cancellation(node, host, from_bare) then sessionmanager.send_to_interested_resources(node, host, stanza); rostermanager.roster_push(node, host, from_bare); end else origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid presence type")); end stanza.attr.from, stanza.attr.to = st_from, st_to; return true; end local outbound_presence_handler = function(data) -- outbound presence received local origin, stanza = data.origin, data.stanza; local to = stanza.attr.to; if to then local t = stanza.attr.type; if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes return handle_outbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to)); end local to_bare = jid_bare(to); local roster = origin.roster; if roster and not(roster[to_bare] and (roster[to_bare].subscription == "both" or roster[to_bare].subscription == "from")) then -- directed presence origin.directed = origin.directed or {}; if t then -- removing from directed presence list on sending an error or unavailable origin.directed[to] = nil; -- FIXME does it make more sense to add to_bare rather than to? else origin.directed[to] = true; -- FIXME does it make more sense to add to_bare rather than to? end end end -- TODO maybe handle normal presence here, instead of letting it pass to incoming handlers? end module:hook("pre-presence/full", outbound_presence_handler); module:hook("pre-presence/bare", outbound_presence_handler); module:hook("pre-presence/host", outbound_presence_handler); module:hook("presence/bare", function(data) -- inbound presence to bare JID received local origin, stanza = data.origin, data.stanza; local to = stanza.attr.to; local t = stanza.attr.type; if to then if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes sent to bare JID return handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to)); end local user = bare_sessions[to]; if user then for _, session in pairs(user.sessions) do if session.presence then -- only send to available resources session.send(stanza); end end end -- no resources not online, discard elseif not t or t == "unavailable" then handle_normal_presence(origin, stanza); else origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid presence type")); end return true; end); module:hook("presence/full", function(data) -- inbound presence to full JID received local origin, stanza = data.origin, data.stanza; local t = stanza.attr.type; if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes sent to full JID return handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to)); end local session = full_sessions[stanza.attr.to]; if session then -- TODO fire post processing event session.send(stanza); end -- resource not online, discard return true; end); module:hook("presence/host", function(data) -- inbound presence to the host local stanza = data.stanza; local from_bare = jid_bare(stanza.attr.from); local t = stanza.attr.type; if t == "probe" then core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id })); elseif t == "subscribe" then core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id, type = "subscribed" })); core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id })); end return true; end); module:hook("resource-unbind", function(event) local session, err = event.session, event.error; -- Send unavailable presence if session.presence then local pres = st.presence{ type = "unavailable" }; if err then pres:tag("status"):text("Disconnected: "..err):up(); end core_process_stanza(session, pres); elseif session.directed then local pres = st.presence{ type = "unavailable", from = session.full_jid }; if err then pres:tag("status"):text("Disconnected: "..err):up(); end for jid in pairs(session.directed) do pres.attr.to = jid; core_post_stanza(session, pres, true); end session.directed = nil; end end); module:hook("roster-item-removed", function (event) local username = event.username; local session = event.origin; local roster = event.roster or session and session.roster; local jid = event.jid; local item = event.item; local from_jid = session.full_jid or (username .. "@" .. module.host); local subscription = item and item.subscription or "none"; local ask = item and item.ask; local pending = roster and roster[false].pending[jid]; if subscription == "both" or subscription == "from" or pending then core_post_stanza(session, st.presence({type="unsubscribed", from=from_jid, to=jid})); end if subscription == "both" or subscription == "to" or ask then send_presence_of_available_resources(username, module.host, jid, session, st.presence({type="unavailable"})); core_post_stanza(session, st.presence({type="unsubscribe", from=from_jid, to=jid})); end end, -1); prosody-0.12.4/plugins/PaxHeaders/mod_private.lua0000644000000000000000000000012014475665104017061 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.668765187 prosody-0.12.4/plugins/mod_private.lua0000644000175000017500000000264714475665104021277 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza" local private_storage = module:open_store("private", "map"); module:add_feature("jabber:iq:private"); module:hook("iq/self/jabber:iq:private:query", function(event) local origin, stanza = event.origin, event.stanza; local query = stanza.tags[1]; if #query.tags ~= 1 then origin.send(st.error_reply(stanza, "modify", "bad-format")); return true; end local tag = query.tags[1]; local key = tag.name..":"..tag.attr.xmlns; if stanza.attr.type == "get" then local data, err = private_storage:get(origin.username, key); if data then origin.send(st.reply(stanza):query("jabber:iq:private"):add_child(st.deserialize(data))); elseif err then origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); else origin.send(st.reply(stanza):add_child(query)); end return true; else -- stanza.attr.type == "set" local data; if #tag ~= 0 then data = st.preserialize(tag); end -- TODO delete datastore if empty local ok, err = private_storage:set(origin.username, key, data); if not ok then origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); return true; end origin.send(st.reply(stanza)); return true; end end); prosody-0.12.4/plugins/PaxHeaders/mod_proxy65.lua0000644000000000000000000000012014475665104016743 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.668765187 prosody-0.12.4/plugins/mod_proxy65.lua0000644000175000017500000001641714475665104021161 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2011 Matthew Wild -- Copyright (C) 2008-2011 Waqas Hussain -- Copyright (C) 2009 Thilo Cestonaro -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local jid_compare, jid_prep = require "util.jid".compare, require "util.jid".prep; local st = require "util.stanza"; local sha1 = require "util.hashes".sha1; local server = require "net.server"; local portmanager = require "core.portmanager"; local sessions = module:shared("sessions"); local transfers = module:shared("transfers"); local max_buffer_size = 4096; local listener = {}; function listener.onincoming(conn, data) local session = sessions[conn] or {}; local transfer = transfers[session.sha]; if transfer and transfer.activated then -- copy data between initiator and target local initiator, target = transfer.initiator, transfer.target; (conn == initiator and target or initiator):write(data); return; end -- FIXME server.link should be doing this? if not session.greeting_done then local nmethods = data:byte(2) or 0; if data:byte(1) == 0x05 and nmethods > 0 and #data == 2 + nmethods then -- check if we have all the data if data:find("%z") then -- 0x00 = 'No authentication' is supported session.greeting_done = true; sessions[conn] = session; conn:write("\5\0"); -- send (SOCKS version 5, No authentication) module:log("debug", "SOCKS5 greeting complete"); return; end end -- else error, unexpected input conn:write("\5\255"); -- send (SOCKS version 5, no acceptable method) conn:close(); module:log("debug", "Invalid SOCKS5 greeting received: %q", data:sub(1, 300)); else -- connection request --local head = string.char( 0x05, 0x01, 0x00, 0x03, 40 ); -- ( VER=5=SOCKS5, CMD=1=CONNECT, RSV=0=RESERVED, ATYP=3=DOMAIMNAME, SHA-1 size ) if #data == 47 and data:sub(1,5) == "\5\1\0\3\40" and data:sub(-2) == "\0\0" then local sha = data:sub(6, 45); conn:pause(); conn:write("\5\0\0\3\40" .. sha .. "\0\0"); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte) if not transfers[sha] then transfers[sha] = {}; transfers[sha].target = conn; session.sha = sha; module:log("debug", "SOCKS5 target connected for session %s", sha); else -- transfers[sha].target ~= nil transfers[sha].initiator = conn; session.sha = sha; module:log("debug", "SOCKS5 initiator connected for session %s", sha); server.link(conn, transfers[sha].target, max_buffer_size); server.link(transfers[sha].target, conn, max_buffer_size); end else -- error, unexpected input conn:write("\5\1\0\3\0\0\0"); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte) conn:close(); module:log("debug", "Invalid SOCKS5 negotiation received: %q", data:sub(1, 300)); end end end function listener.ondisconnect(conn) local session = sessions[conn]; if session then if transfers[session.sha] then local initiator, target = transfers[session.sha].initiator, transfers[session.sha].target; if initiator == conn and target ~= nil then target:close(); elseif target == conn and initiator ~= nil then initiator:close(); end transfers[session.sha] = nil; end -- Clean up any session-related stuff here sessions[conn] = nil; end end function module.add_host(module) local host, name = module:get_host(), module:get_option_string("name", "SOCKS5 Bytestreams Service"); local proxy_address = module:get_option_string("proxy65_address", host); local proxy_acl = module:get_option_array("proxy65_acl"); local proxy_open_access = module:get_option_boolean("proxy65_open_access", false); -- COMPAT w/pre-0.9 where proxy65_port was specified in the components section of the config local legacy_config = module:get_option_number("proxy65_port"); if legacy_config then module:log("warn", "proxy65_port is deprecated, please put proxy65_ports = { %d } into the global section instead", legacy_config); end module:depends("disco"); module:add_identity("proxy", "bytestreams", name); module:add_feature("http://jabber.org/protocol/bytestreams"); module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event) local origin, stanza = event.origin, event.stanza; -- check ACL -- using 'while' instead of 'if' so we can break out of it local allow; if proxy_acl and #proxy_acl > 0 then local jid = stanza.attr.from; for _, acl in ipairs(proxy_acl) do if jid_compare(jid, acl) then allow = true; break; end end elseif proxy_open_access or origin.type == "c2s" then allow = true; end if not allow then module:log("warn", "Denying use of proxy for %s", stanza.attr.from); origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end local proxy_port = next(portmanager.get_active_services():search("proxy65", nil)[1] or {}); if not proxy_port then module:log("warn", "Not listening on any port"); origin.send(st.error_reply(stanza, "wait", "item-not-found", "Not listening on any port")); return true; end local sid = stanza.tags[1].attr.sid; origin.send(st.reply(stanza):tag("query", {xmlns="http://jabber.org/protocol/bytestreams", sid=sid}) :tag("streamhost", {jid=host, host=proxy_address, port=("%d"):format(proxy_port)})); return true; end); module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event) local origin, stanza = event.origin, event.stanza; local query = stanza.tags[1]; local sid = query.attr.sid; local from = stanza.attr.from; local to = query:get_child_text("activate"); local prepped_to = jid_prep(to); local info = "sid: "..tostring(sid)..", initiator: "..tostring(from)..", target: "..tostring(prepped_to or to); if prepped_to and sid then local sha = sha1(sid .. from .. prepped_to, true); if not transfers[sha] then module:log("debug", "Activation request has unknown session id; activation failed (%s)", info); origin.send(st.error_reply(stanza, "modify", "item-not-found")); elseif not transfers[sha].initiator then module:log("debug", "The sender was not connected to the proxy; activation failed (%s)", info); origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The sender (you) is not connected to the proxy")); --elseif not transfers[sha].target then -- can't happen, as target is set when a transfer object is created -- module:log("debug", "The recipient was not connected to the proxy; activation failed (%s)", info); -- origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The recipient is not connected to the proxy")); else -- if transfers[sha].initiator ~= nil and transfers[sha].target ~= nil then module:log("debug", "Transfer activated (%s)", info); transfers[sha].activated = true; transfers[sha].target:resume(); transfers[sha].initiator:resume(); origin.send(st.reply(stanza)); end elseif to and sid then module:log("debug", "Malformed activation jid; activation failed (%s)", info); origin.send(st.error_reply(stanza, "modify", "jid-malformed")); else module:log("debug", "Bad request; activation failed (%s)", info); origin.send(st.error_reply(stanza, "modify", "bad-request")); end return true; end); end module:provides("net", { default_port = 5000; listener = listener; multiplex = { pattern = "^\5"; }; }); prosody-0.12.4/plugins/PaxHeaders/mod_pubsub0000644000000000000000000000013214475665104016132 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.668765187 30 ctime=1693936196.672765148 prosody-0.12.4/plugins/mod_pubsub/0000755000175000017500000000000014475665104020411 5ustar00prosodyprosody00000000000000prosody-0.12.4/plugins/mod_pubsub/PaxHeaders/mod_pubsub.lua0000644000000000000000000000012014475665104021046 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.668765187 prosody-0.12.4/plugins/mod_pubsub/mod_pubsub.lua0000644000175000017500000001640514475665104023261 0ustar00prosodyprosody00000000000000local pubsub = require "util.pubsub"; local st = require "util.stanza"; local jid_bare = require "util.jid".bare; local usermanager = require "core.usermanager"; local new_id = require "util.id".medium; local storagemanager = require "core.storagemanager"; local xtemplate = require "util.xtemplate"; local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; local autocreate_on_publish = module:get_option_boolean("autocreate_on_publish", false); local autocreate_on_subscribe = module:get_option_boolean("autocreate_on_subscribe", false); local pubsub_disco_name = module:get_option_string("name", "Prosody PubSub Service"); local expose_publisher = module:get_option_boolean("expose_publisher", false) local service; local lib_pubsub = module:require "pubsub"; module:depends("disco"); module:add_identity("pubsub", "service", pubsub_disco_name); module:add_feature("http://jabber.org/protocol/pubsub"); function handle_pubsub_iq(event) return lib_pubsub.handle_pubsub_iq(event, service); end -- An itemstore supports the following methods: -- items(): iterator over (id, item) -- get(id): return item with id -- set(id, item): set id to item -- clear(): clear all items -- resize(n): set new limit and trim oldest items -- tail(): return the latest item -- A nodestore supports the following methods: -- set(node_name, node_data) -- get(node_name) -- users(): iterator over (node_name) local max_max_items = module:get_option_number("pubsub_max_items", 256); local function tonumber_max_items(n) if n == "max" then return max_max_items; end return tonumber(n); end for _, field in ipairs(lib_pubsub.node_config_form) do if field.var == "pubsub#max_items" then field.range_max = max_max_items; break; end end local node_store = module:open_store(module.name.."_nodes"); local function create_simple_itemstore(node_config, node_name) --> util.cache like object local driver = storagemanager.get_driver(module.host, "pubsub_data"); local archive = driver:open("pubsub_"..node_name, "archive"); local max_items = tonumber_max_items(node_config["max_items"]); return lib_pubsub.archive_itemstore(archive, max_items, nil, node_name); end function simple_broadcast(kind, node, jids, item, actor, node_obj, service) --luacheck: ignore 431/service if node_obj then if node_obj.config["notify_"..kind] == false then return; end end if kind == "retract" then kind = "items"; -- XEP-0060 signals retraction in an container end if item then item = st.clone(item); item.attr.xmlns = nil; -- Clear the pubsub namespace if kind == "items" then if node_obj and node_obj.config.include_payload == false then item:maptags(function () return nil; end); end if not expose_publisher then item.attr.publisher = nil; elseif not item.attr.publisher and actor ~= true then item.attr.publisher = service.config.normalize_jid(actor); end end end local id = new_id(); local msg_type = node_obj and node_obj.config.notification_type or "headline"; local message = st.message({ from = module.host, type = msg_type, id = id }) :tag("event", { xmlns = xmlns_pubsub_event }) :tag(kind, { node = node }); if item then message:add_child(item); end local summary; if item and item.tags[1] then local payload = item.tags[1]; local payload_type = node_obj and node_obj.config.payload_type or payload.attr.xmlns; summary = module:fire_event("pubsub-summary/"..payload_type, { kind = kind, node = node, jids = jids, actor = actor, item = item, payload = payload, }); end for jid, options in pairs(jids) do local new_stanza = st.clone(message); if summary and type(options) == "table" and options["pubsub#include_body"] then new_stanza:body(summary); end new_stanza.attr.to = jid; module:send(new_stanza); end end function check_node_config(node, actor, new_config) -- luacheck: ignore 212/node 212/actor if (tonumber_max_items(new_config["max_items"]) or 1) > max_max_items then return false; end if new_config["access_model"] ~= "whitelist" and new_config["access_model"] ~= "open" then return false; end return true; end function is_item_stanza(item) return st.is_stanza(item) and item.attr.xmlns == xmlns_pubsub and item.name == "item" and #item.tags == 1; end -- Compose a textual representation of Atom payloads local summary_templates = module:get_option("pubsub_summary_templates", { ["http://www.w3.org/2005/Atom"] = "{summary|or{{author/name|and{{author/name} posted }}{title}}}"; }) for pubsub_type, template in pairs(summary_templates) do module:hook("pubsub-summary/"..pubsub_type, function (event) local payload = event.payload; return xtemplate.render(template, payload, tostring); end, -1); end module:hook("iq/host/"..xmlns_pubsub..":pubsub", handle_pubsub_iq); module:hook("iq/host/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq); local function add_disco_features_from_service(service) --luacheck: ignore 431/service for feature in lib_pubsub.get_feature_set(service) do module:add_feature(xmlns_pubsub.."#"..feature); end end module:hook("host-disco-info-node", function (event) return lib_pubsub.handle_disco_info_node(event, service); end); module:hook("host-disco-items-node", function (event) return lib_pubsub.handle_disco_items_node(event, service); end); module:hook("host-disco-items", function (event) local stanza, reply = event.stanza, event.reply; local ok, ret = service:get_nodes(stanza.attr.from); if not ok then return; end for node, node_obj in pairs(ret) do reply:tag("item", { jid = module.host, node = node, name = node_obj.config.title }):up(); end end); local admin_aff = module:get_option_string("default_admin_affiliation", "owner"); local function get_affiliation(jid) local bare_jid = jid_bare(jid); if bare_jid == module.host or usermanager.is_admin(bare_jid, module.host) then return admin_aff; end end function get_service() return service; end function set_service(new_service) service = new_service; service.config.autocreate_on_publish = autocreate_on_publish; service.config.autocreate_on_subscribe = autocreate_on_subscribe; service.config.expose_publisher = expose_publisher; service.config.nodestore = node_store; service.config.itemstore = create_simple_itemstore; service.config.broadcaster = simple_broadcast; service.config.itemcheck = is_item_stanza; service.config.check_node_config = check_node_config; service.config.get_affiliation = get_affiliation; module.environment.service = service; add_disco_features_from_service(service); end function module.save() return { service = service }; end function module.restore(data) set_service(data.service); end function module.load() if module.reloading then return; end set_service(pubsub.new({ autocreate_on_publish = autocreate_on_publish; autocreate_on_subscribe = autocreate_on_subscribe; expose_publisher = expose_publisher; node_defaults = { ["persist_items"] = true; }; max_items = max_max_items; nodestore = node_store; itemstore = create_simple_itemstore; broadcaster = simple_broadcast; itemcheck = is_item_stanza; check_node_config = check_node_config; get_affiliation = get_affiliation; jid = module.host; normalize_jid = jid_bare; })); end prosody-0.12.4/plugins/mod_pubsub/PaxHeaders/pubsub.lib.lua0000644000000000000000000000012014475665104020754 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.672765148 prosody-0.12.4/plugins/mod_pubsub/pubsub.lib.lua0000644000175000017500000006456014475665104023174 0ustar00prosodyprosody00000000000000local t_unpack = table.unpack or unpack; -- luacheck: ignore 113 local time_now = os.time; local jid_prep = require "util.jid".prep; local set = require "util.set"; local st = require "util.stanza"; local it = require "util.iterators"; local uuid_generate = require "util.uuid".generate; local dataform = require"util.dataforms".new; local errors = require "util.error"; local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors"; local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; local _M = {}; local handlers = {}; _M.handlers = handlers; local pubsub_errors = { ["conflict"] = { "cancel", "conflict" }; ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" }; ["jid-required"] = { "modify", "bad-request", nil, "jid-required" }; ["nodeid-required"] = { "modify", "bad-request", nil, "nodeid-required" }; ["item-not-found"] = { "cancel", "item-not-found" }; ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" }; ["invalid-options"] = { "modify", "bad-request", nil, "invalid-options" }; ["forbidden"] = { "auth", "forbidden" }; ["not-allowed"] = { "cancel", "not-allowed" }; ["not-acceptable"] = { "modify", "not-acceptable" }; ["internal-server-error"] = { "wait", "internal-server-error" }; ["precondition-not-met"] = { "cancel", "conflict", nil, "precondition-not-met" }; ["invalid-item"] = { "modify", "bad-request", "invalid item" }; ["persistent-items-unsupported"] = { "cancel", "feature-not-implemented", nil, "persistent-items" }; }; local function pubsub_error_reply(stanza, error) local e = pubsub_errors[error]; if not e and errors.is_err(error) then e = { error.type, error.condition, error.text, error.pubsub_condition }; end local reply = st.error_reply(stanza, t_unpack(e, 1, 3)); if e[4] then reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up(); end return reply; end _M.pubsub_error_reply = pubsub_error_reply; local function dataform_error_message(err) -- ({ string : string }) -> string? local out = {}; for field, errmsg in pairs(err) do table.insert(out, ("%s: %s"):format(field, errmsg)) end return table.concat(out, "; "); end -- Note: If any config options are added that are of complex types, -- (not simply strings/numbers) then the publish-options code will -- need to be revisited local node_config_form = dataform { { type = "hidden"; var = "FORM_TYPE"; value = "http://jabber.org/protocol/pubsub#node_config"; }; { type = "text-single"; name = "title"; var = "pubsub#title"; label = "Title"; }; { type = "text-single"; name = "description"; var = "pubsub#description"; label = "Description"; }; { type = "text-single"; name = "payload_type"; var = "pubsub#type"; label = "The type of node data, usually specified by the namespace of the payload (if any)"; }; { type = "text-single"; datatype = "pubsub:integer-or-max"; name = "max_items"; range_min = 1; var = "pubsub#max_items"; label = "Max # of items to persist"; }; { type = "boolean"; name = "persist_items"; var = "pubsub#persist_items"; label = "Persist items to storage"; }; { type = "list-single"; name = "access_model"; var = "pubsub#access_model"; label = "Specify the subscriber model"; options = { "authorize", "open", "presence", "roster", "whitelist", }; }; { type = "list-single"; name = "publish_model"; var = "pubsub#publish_model"; label = "Specify the publisher model"; options = { "publishers"; "subscribers"; "open"; }; }; { type = "list-single"; var = "pubsub#send_last_published_item"; name = "send_last_published_item"; options = { "never"; "on_sub"; "on_sub_and_presence" }; }; { type = "boolean"; value = true; label = "Whether to deliver event notifications"; name = "notify_items"; var = "pubsub#deliver_notifications"; }; { type = "boolean"; value = true; label = "Whether to deliver payloads with event notifications"; name = "include_payload"; var = "pubsub#deliver_payloads"; }; { type = "list-single"; name = "notification_type"; var = "pubsub#notification_type"; label = "Specify the delivery style for notifications"; options = { { label = "Messages of type normal", value = "normal" }, { label = "Messages of type headline", value = "headline", default = true }, }; }; { type = "boolean"; label = "Whether to notify subscribers when the node is deleted"; name = "notify_delete"; var = "pubsub#notify_delete"; value = true; }; { type = "boolean"; label = "Whether to notify subscribers when items are removed from the node"; name = "notify_retract"; var = "pubsub#notify_retract"; value = true; }; }; _M.node_config_form = node_config_form; local subscribe_options_form = dataform { { type = "hidden"; var = "FORM_TYPE"; value = "http://jabber.org/protocol/pubsub#subscribe_options"; }; { type = "boolean"; name = "pubsub#include_body"; label = "Receive message body in addition to payload?"; }; }; _M.subscribe_options_form = subscribe_options_form; local node_metadata_form = dataform { { type = "hidden"; var = "FORM_TYPE"; value = "http://jabber.org/protocol/pubsub#meta-data"; }; { type = "text-single"; name = "pubsub#title"; }; { type = "text-single"; name = "pubsub#description"; }; { type = "text-single"; name = "pubsub#type"; }; { type = "text-single"; name = "pubsub#access_model"; }; { type = "text-single"; name = "pubsub#publish_model"; }; }; _M.node_metadata_form = node_metadata_form; local service_method_feature_map = { add_subscription = { "subscribe", "subscription-options" }; create = { "create-nodes", "instant-nodes", "item-ids", "create-and-configure" }; delete = { "delete-nodes" }; get_items = { "retrieve-items" }; get_subscriptions = { "retrieve-subscriptions" }; node_defaults = { "retrieve-default" }; publish = { "publish", "multi-items", "publish-options" }; purge = { "purge-nodes" }; retract = { "delete-items", "retract-items" }; set_node_config = { "config-node", "meta-data" }; set_affiliation = { "modify-affiliations" }; }; local service_config_feature_map = { autocreate_on_publish = { "auto-create" }; }; function _M.get_feature_set(service) local supported_features = set.new(); for method, features in pairs(service_method_feature_map) do if service[method] then for _, feature in ipairs(features) do if feature then supported_features:add(feature); end end end end for option, features in pairs(service_config_feature_map) do if service.config[option] then for _, feature in ipairs(features) do if feature then supported_features:add(feature); end end end end for affiliation in pairs(service.config.capabilities) do if affiliation ~= "none" and affiliation ~= "owner" then supported_features:add(affiliation.."-affiliation"); end end if service.node_defaults.access_model then supported_features:add("access-"..service.node_defaults.access_model); end if service.node_defaults.send_last_published_item ~= "never" then supported_features:add("last-published"); end if rawget(service.config, "itemstore") and rawget(service.config, "nodestore") then supported_features:add("persistent-items"); end if true --[[ node_metadata_form[max_items].datatype == "pubsub:integer-or-max" ]] then supported_features:add("config-node-max"); end return supported_features; end function _M.handle_disco_info_node(event, service) local stanza, reply, node = event.stanza, event.reply, event.node; local ok, ret = service:get_nodes(stanza.attr.from); if not ok then event.origin.send(pubsub_error_reply(stanza, ret)); return true; end local node_obj = ret[node]; if not node_obj then event.origin.send(pubsub_error_reply(stanza, "item-not-found")); return true; end event.exists = true; reply:tag("identity", { category = "pubsub", type = "leaf" }):up(); if node_obj.config then reply:add_child(node_metadata_form:form({ ["pubsub#title"] = node_obj.config.title; ["pubsub#description"] = node_obj.config.description; ["pubsub#type"] = node_obj.config.payload_type; ["pubsub#access_model"] = node_obj.config.access_model; ["pubsub#publish_model"] = node_obj.config.publish_model; }, "result")); end end function _M.handle_disco_items_node(event, service) local stanza, reply, node = event.stanza, event.reply, event.node; local ok, ret = service:get_items(node, stanza.attr.from); if not ok then event.origin.send(pubsub_error_reply(stanza, ret)); return true; end for _, id in ipairs(ret) do reply:tag("item", { jid = service.config.jid or module.host, name = id }):up(); end event.exists = true; end function _M.handle_pubsub_iq(event, service) local origin, stanza = event.origin, event.stanza; local pubsub_tag = stanza.tags[1]; local action = pubsub_tag.tags[1]; if not action then return origin.send(st.error_reply(stanza, "cancel", "bad-request")); end local prefix = ""; if pubsub_tag.attr.xmlns == xmlns_pubsub_owner then prefix = "owner_"; end local handler = handlers[prefix..stanza.attr.type.."_"..action.name]; if handler then handler(origin, stanza, action, service); return true; end end function handlers.get_items(origin, stanza, items, service) local node = items.attr.node; local requested_items = {}; for item in items:childtags("item") do table.insert(requested_items, item.attr.id); end if requested_items[1] == nil then requested_items = nil; end if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end local resultspec; -- TODO rsm.get() if items.attr.max_items then resultspec = { max = tonumber(items.attr.max_items) }; end local ok, results = service:get_items(node, stanza.attr.from, requested_items, resultspec); if not ok then origin.send(pubsub_error_reply(stanza, results)); return true; end local expose_publisher = service.config.expose_publisher; local data = st.stanza("items", { node = node }); local iter, v, i = ipairs(results); if not requested_items then -- XXX Hack to preserve order of explicitly requested items. iter, v, i = it.reverse(iter, v, i); end for _, id in iter, v, i do local item = results[id]; if not expose_publisher then item = st.clone(item); item.attr.publisher = nil; end data:add_child(item); end local reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :add_child(data); origin.send(reply); return true; end function handlers.get_subscriptions(origin, stanza, subscriptions, service) local node = subscriptions.attr.node; local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from); if not ok then origin.send(pubsub_error_reply(stanza, ret)); return true; end local reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("subscriptions"); for _, sub in ipairs(ret) do reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up(); end origin.send(reply); return true; end function handlers.owner_get_subscriptions(origin, stanza, subscriptions, service) local node = subscriptions.attr.node; local ok, ret = service:get_subscriptions(node, stanza.attr.from); if not ok then origin.send(pubsub_error_reply(stanza, ret)); return true; end local reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub_owner }) :tag("subscriptions"); for _, sub in ipairs(ret) do reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up(); end origin.send(reply); return true; end function handlers.owner_set_subscriptions(origin, stanza, subscriptions, service) local node = subscriptions.attr.node; if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end if not service:may(node, stanza.attr.from, "subscribe_other") then origin.send(pubsub_error_reply(stanza, "forbidden")); return true; end local node_obj = service.nodes[node]; if not node_obj then origin.send(pubsub_error_reply(stanza, "item-not-found")); return true; end for subscription_tag in subscriptions:childtags("subscription") do if subscription_tag.attr.subscription == 'subscribed' then local ok, err = service:add_subscription(node, stanza.attr.from, subscription_tag.attr.jid); if not ok then origin.send(pubsub_error_reply(stanza, err)); return true; end elseif subscription_tag.attr.subscription == 'none' then local ok, err = service:remove_subscription(node, stanza.attr.from, subscription_tag.attr.jid); if not ok then origin.send(pubsub_error_reply(stanza, err)); return true; end end end local reply = st.reply(stanza); origin.send(reply); return true; end function handlers.set_create(origin, stanza, create, service) local node = create.attr.node; local ok, ret, reply; local config; local configure = stanza.tags[1]:get_child("configure"); if configure then local config_form = configure:get_child("x", "jabber:x:data"); if not config_form then origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing dataform")); return true; end local form_data, err = node_config_form:data(config_form); if err then origin.send(st.error_reply(stanza, "modify", "bad-request", dataform_error_message(err))); return true; end config = form_data; end if node then ok, ret = service:create(node, stanza.attr.from, config); if ok then reply = st.reply(stanza); else reply = pubsub_error_reply(stanza, ret); end else repeat node = uuid_generate(); ok, ret = service:create(node, stanza.attr.from, config); until ok or ret ~= "conflict"; if ok then reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("create", { node = node }); else reply = pubsub_error_reply(stanza, ret); end end origin.send(reply); return true; end function handlers.owner_set_delete(origin, stanza, delete, service) local node = delete.attr.node; local reply; if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end local ok, ret = service:delete(node, stanza.attr.from); if ok then reply = st.reply(stanza); else reply = pubsub_error_reply(stanza, ret); end origin.send(reply); return true; end function handlers.set_subscribe(origin, stanza, subscribe, service) local node, jid = subscribe.attr.node, subscribe.attr.jid; jid = jid_prep(jid); if not (node and jid) then origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid")); return true; end local options_tag, options = stanza.tags[1]:get_child("options"), nil; if options_tag then -- FIXME form parsing errors ignored here, why? local err options, err = subscribe_options_form:data(options_tag.tags[1]); if err then origin.send(st.error_reply(stanza, "modify", "bad-request", dataform_error_message(err))); return true end end local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options); local reply; if ok then reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("subscription", { node = node, jid = jid, subscription = "subscribed" }):up(); if options_tag then reply:add_child(options_tag); end else reply = pubsub_error_reply(stanza, ret); end origin.send(reply); local ok, config = service:get_node_config(node, true); if ok and config.send_last_published_item ~= "never" then local ok, id, item = service:get_last_item(node, jid); if not (ok and id) then return; end service.config.broadcaster("items", node, { [jid] = true }, item); end end function handlers.set_unsubscribe(origin, stanza, unsubscribe, service) local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid; jid = jid_prep(jid); if not (node and jid) then origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid")); return true; end local ok, ret = service:remove_subscription(node, stanza.attr.from, jid); local reply; if ok then reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("subscription", { node = node, jid = jid, subscription = "none" }):up(); else reply = pubsub_error_reply(stanza, ret); end origin.send(reply); return true; end function handlers.get_options(origin, stanza, options, service) local node, jid = options.attr.node, options.attr.jid; jid = jid_prep(jid); if not (node and jid) then origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid")); return true; end local ok, ret = service:get_subscription(node, stanza.attr.from, jid); if not ok then origin.send(pubsub_error_reply(stanza, "not-subscribed")); return true; end if ret == true then ret = {} end origin.send(st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("options", { node = node, jid = jid }) :add_child(subscribe_options_form:form(ret))); return true; end function handlers.set_options(origin, stanza, options, service) local node, jid = options.attr.node, options.attr.jid; jid = jid_prep(jid); if not (node and jid) then origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid")); return true; end local ok, ret = service:get_subscription(node, stanza.attr.from, jid); if not ok then origin.send(pubsub_error_reply(stanza, ret)); return true; elseif not ret then origin.send(pubsub_error_reply(stanza, "not-subscribed")); return true; end local old_subopts = ret; local new_subopts, err = subscribe_options_form:data(options.tags[1], old_subopts); if err then origin.send(st.error_reply(stanza, "modify", "bad-request", dataform_error_message(err))); return true; end local ok, err = service:add_subscription(node, stanza.attr.from, jid, new_subopts); if not ok then origin.send(pubsub_error_reply(stanza, err)); return true; end origin.send(st.reply(stanza)); return true; end function handlers.set_publish(origin, stanza, publish, service) local node = publish.attr.node; if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end local required_config = nil; local publish_options = stanza.tags[1]:get_child("publish-options"); if publish_options then -- Ensure that the node configuration matches the values in publish-options local publish_options_form = publish_options:get_child("x", "jabber:x:data"); local err; required_config, err = node_config_form:data(publish_options_form); if err then origin.send(st.error_reply(stanza, "modify", "bad-request", dataform_error_message(err))); return true end end local item = publish:get_child("item"); local id = (item and item.attr.id); if not id then id = uuid_generate(); if item then item.attr.id = id; end end if item then item.attr.publisher = service.config.normalize_jid(stanza.attr.from); end local ok, ret = service:publish(node, stanza.attr.from, id, item, required_config); local reply; if ok then if type(ok) == "string" then id = ok; end reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("publish", { node = node }) :tag("item", { id = id }); else reply = pubsub_error_reply(stanza, ret); end origin.send(reply); return true; end function handlers.set_retract(origin, stanza, retract, service) local node, notify = retract.attr.node, retract.attr.notify; notify = (notify == "1") or (notify == "true"); local item = retract:get_child("item"); local id = item and item.attr.id if not (node and id) then origin.send(pubsub_error_reply(stanza, node and "item-not-found" or "nodeid-required")); return true; end local reply, notifier; if notify then notifier = st.stanza("retract", { id = id }); end local ok, ret = service:retract(node, stanza.attr.from, id, notifier); if ok then reply = st.reply(stanza); else reply = pubsub_error_reply(stanza, ret); end origin.send(reply); return true; end function handlers.owner_set_purge(origin, stanza, purge, service) local node = purge.attr.node; local reply; if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end local ok, ret = service:purge(node, stanza.attr.from, true); if ok then reply = st.reply(stanza); else reply = pubsub_error_reply(stanza, ret); end origin.send(reply); return true; end function handlers.owner_get_configure(origin, stanza, config, service) local node = config.attr.node; if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end local ok, node_config = service:get_node_config(node, stanza.attr.from); if not ok then origin.send(pubsub_error_reply(stanza, node_config)); return true; end local reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub_owner }) :tag("configure", { node = node }) :add_child(node_config_form:form(node_config)); origin.send(reply); return true; end function handlers.owner_set_configure(origin, stanza, config, service) local node = config.attr.node; if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end if not service:may(node, stanza.attr.from, "configure") then origin.send(pubsub_error_reply(stanza, "forbidden")); return true; end local config_form = config:get_child("x", "jabber:x:data"); if not config_form then origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing dataform")); return true; end local ok, old_config = service:get_node_config(node, stanza.attr.from); if not ok then origin.send(pubsub_error_reply(stanza, old_config)); return true; end local new_config, err = node_config_form:data(config_form, old_config); if err then origin.send(st.error_reply(stanza, "modify", "bad-request", dataform_error_message(err))); return true; end local ok, err = service:set_node_config(node, stanza.attr.from, new_config); if not ok then origin.send(pubsub_error_reply(stanza, err)); return true; end origin.send(st.reply(stanza)); return true; end function handlers.owner_get_default(origin, stanza, default, service) -- luacheck: ignore 212/default local reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub_owner }) :tag("default") :add_child(node_config_form:form(service.node_defaults)); origin.send(reply); return true; end function handlers.owner_get_affiliations(origin, stanza, affiliations, service) local node = affiliations.attr.node; if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end if not service:may(node, stanza.attr.from, "set_affiliation") then origin.send(pubsub_error_reply(stanza, "forbidden")); return true; end local node_obj = service.nodes[node]; if not node_obj then origin.send(pubsub_error_reply(stanza, "item-not-found")); return true; end local reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub_owner }) :tag("affiliations", { node = node }); for jid, affiliation in pairs(node_obj.affiliations) do reply:tag("affiliation", { jid = jid, affiliation = affiliation }):up(); end origin.send(reply); return true; end function handlers.owner_set_affiliations(origin, stanza, affiliations, service) local node = affiliations.attr.node; if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end if not service:may(node, stanza.attr.from, "set_affiliation") then origin.send(pubsub_error_reply(stanza, "forbidden")); return true; end local node_obj = service.nodes[node]; if not node_obj then origin.send(pubsub_error_reply(stanza, "item-not-found")); return true; end for affiliation_tag in affiliations:childtags("affiliation") do local jid = affiliation_tag.attr.jid; local affiliation = affiliation_tag.attr.affiliation; jid = jid_prep(jid); if affiliation == "none" then affiliation = nil; end local ok, err = service:set_affiliation(node, stanza.attr.from, jid, affiliation); if not ok then -- FIXME Incomplete error handling, -- see XEP 60 8.9.2.4 Multiple Simultaneous Modifications origin.send(pubsub_error_reply(stanza, err)); return true; end end local reply = st.reply(stanza); origin.send(reply); return true; end local function create_encapsulating_item(id, payload, publisher) local item = st.stanza("item", { id = id, publisher = publisher, xmlns = xmlns_pubsub }); item:add_child(payload); return item; end local function archive_itemstore(archive, max_items, user, node) module:log("debug", "Creation of archive itemstore for node %s with limit %d", node, max_items); local get_set = {}; function get_set:items() -- luacheck: ignore 212/self local data, err = archive:find(user, { limit = tonumber(max_items); reverse = true; }); if not data then module:log("error", "Unable to get items: %s", err); return true; end module:log("debug", "Listed items %s", data); return function() -- luacheck: ignore 211/when local id, payload, when, publisher = data(); if id == nil then return; end local item = create_encapsulating_item(id, payload, publisher); return id, item; end; end function get_set:get(key) -- luacheck: ignore 212/self local data, err = archive:find(user, { key = key; -- Get the last item with that key, if the archive doesn't deduplicate reverse = true, limit = 1; }); if not data then module:log("error", "Unable to get item: %s", err); return nil, err; end local id, payload, when, publisher = data(); module:log("debug", "Get item %s (published at %s by %s)", id, when, publisher); if id == nil then return nil; end return create_encapsulating_item(id, payload, publisher); end function get_set:set(key, value) -- luacheck: ignore 212/self local data, err; if value ~= nil then local publisher = value.attr.publisher; local payload = value.tags[1]; data, err = archive:append(user, key, payload, time_now(), publisher); else data, err = archive:delete(user, { key = key; }); end -- TODO archive support for maintaining maximum items archive:delete(user, { truncate = max_items; }); if not data then module:log("error", "Unable to set item: %s", err); return nil, err; end return data; end function get_set:clear() -- luacheck: ignore 212/self return archive:delete(user); end function get_set:resize(size) -- luacheck: ignore 212/self max_items = size; return archive:delete(user, { truncate = size; }); end function get_set:head() -- This should conveniently return the most recent item local item = self:get(nil); if item then return item.attr.id, item; end end return get_set; end _M.archive_itemstore = archive_itemstore; return _M; prosody-0.12.4/plugins/PaxHeaders/mod_register.lua0000644000000000000000000000012014475665104017233 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.672765148 prosody-0.12.4/plugins/mod_register.lua0000644000175000017500000000067614475665104021451 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local allow_registration = module:get_option_boolean("allow_registration", false); if allow_registration then module:depends("register_ibr"); module:depends("watchregistrations"); end module:depends("user_account_management"); prosody-0.12.4/plugins/PaxHeaders/mod_register_ibr.lua0000644000000000000000000000012014475665104020067 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.672765148 prosody-0.12.4/plugins/mod_register_ibr.lua0000644000175000017500000002001514475665104022272 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local dataform_new = require "util.dataforms".new; local usermanager_user_exists = require "core.usermanager".user_exists; local usermanager_create_user = require "core.usermanager".create_user; local usermanager_set_password = require "core.usermanager".create_user; local usermanager_delete_user = require "core.usermanager".delete_user; local nodeprep = require "util.encodings".stringprep.nodeprep; local util_error = require "util.error"; local additional_fields = module:get_option("additional_registration_fields", {}); local require_encryption = module:get_option_boolean("c2s_require_encryption", module:get_option_boolean("require_encryption", true)); pcall(function () module:depends("register_limits"); end); local account_details = module:open_store("account_details"); local field_map = { FORM_TYPE = { name = "FORM_TYPE", type = "hidden", value = "jabber:iq:register" }; username = { name = "username", type = "text-single", label = "Username", required = true }; password = { name = "password", type = "text-private", label = "Password", required = true }; nick = { name = "nick", type = "text-single", label = "Nickname" }; name = { name = "name", type = "text-single", label = "Full Name" }; first = { name = "first", type = "text-single", label = "Given Name" }; last = { name = "last", type = "text-single", label = "Family Name" }; email = { name = "email", type = "text-single", label = "Email" }; address = { name = "address", type = "text-single", label = "Street" }; city = { name = "city", type = "text-single", label = "City" }; state = { name = "state", type = "text-single", label = "State" }; zip = { name = "zip", type = "text-single", label = "Postal code" }; phone = { name = "phone", type = "text-single", label = "Telephone number" }; url = { name = "url", type = "text-single", label = "Webpage" }; date = { name = "date", type = "text-single", label = "Birth date" }; }; local title = module:get_option_string("registration_title", "Creating a new account"); local instructions = module:get_option_string("registration_instructions", "Choose a username and password for use with this service."); local registration_form = dataform_new{ title = title; instructions = instructions; field_map.FORM_TYPE; field_map.username; field_map.password; }; local registration_query = st.stanza("query", {xmlns = "jabber:iq:register"}) :tag("instructions"):text(instructions):up() :tag("username"):up() :tag("password"):up(); for _, field in ipairs(additional_fields) do if type(field) == "table" then registration_form[#registration_form + 1] = field; elseif field_map[field] or field_map[field:sub(1, -2)] then if field:match("%+$") then field = field:sub(1, -2); field_map[field].required = true; end registration_form[#registration_form + 1] = field_map[field]; registration_query:tag(field):up(); else module:log("error", "Unknown field %q", field); end end registration_query:add_child(registration_form:form()); local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up(); module:hook("stream-features", function(event) local session, features = event.origin, event.features; -- Advertise registration to unauthorized clients only. if session.type ~= "c2s_unauthed" or (require_encryption and not session.secure) then return end features:add_child(register_stream_feature); end); local function parse_response(query) local form = query:get_child("x", "jabber:x:data"); if form then return registration_form:data(form); else local data = {}; local errors = {}; for _, field in ipairs(registration_form) do local name, required = field.name, field.required; if field_map[name] then data[name] = query:get_child_text(name); if (not data[name] or #data[name] == 0) and required then errors[name] = "Required value missing"; end end end if next(errors) then return data, errors; end return data; end end -- In-band registration module:hook("stanza/iq/jabber:iq:register:query", function(event) local session, stanza = event.origin, event.stanza; local log = session.log or module._log; if session.type ~= "c2s_unauthed" then log("debug", "Attempted registration when disabled or already authenticated"); session.send(st.error_reply(stanza, "cancel", "service-unavailable")); return true; end if require_encryption and not session.secure then session.send(st.error_reply(stanza, "modify", "policy-violation", "Encryption is required")); return true; end local query = stanza.tags[1]; if stanza.attr.type == "get" then local reply = st.reply(stanza); reply:add_child(registration_query); session.send(reply); return true; end -- stanza.attr.type == "set" if query.tags[1] and query.tags[1].name == "remove" then session.send(st.error_reply(stanza, "auth", "registration-required")); return true; end local data, errors = parse_response(query); if errors then log("debug", "Error parsing registration form:"); local textual_errors = {}; for field, err in pairs(errors) do log("debug", "Field %q: %s", field, err); table.insert(textual_errors, ("%s: %s"):format(field:gsub("^%a", string.upper), err)); end session.send(st.error_reply(stanza, "modify", "not-acceptable", table.concat(textual_errors, "\n"))); return true; end local username, password = nodeprep(data.username, true), data.password; data.username, data.password = nil, nil; local host = module.host; if not username or username == "" then log("debug", "The requested username is invalid."); session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is invalid.")); return true; end local user = { username = username, password = password, host = host, additional = data, ip = session.ip, session = session, allowed = true } module:fire_event("user-registering", user); if not user.allowed then local error_type, error_condition, reason; local err = user.error; if err then error_type, error_condition, reason = err.type, err.condition, err.text; else -- COMPAT pre-util.error error_type, error_condition, reason = user.error_type, user.error_condition, user.reason; end log("debug", "Registration disallowed by module: %s", reason or "no reason given"); session.send(st.error_reply(stanza, error_type or "modify", error_condition or "not-acceptable", reason)); return true; end if usermanager_user_exists(username, host) then if user.allow_reset == username then local ok, err = util_error.coerce(usermanager_set_password(username, password, host)); if ok then module:fire_event("user-password-reset", user); session.send(st.reply(stanza)); -- reset ok! else session.log("error", "Unable to reset password for %s@%s: %s", username, host, err); session.send(st.error_reply(stanza, err.type, err.condition, err.text)); end return true; else log("debug", "Attempt to register with existing username"); session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists.")); return true; end end local created, err = usermanager_create_user(username, password, host); if created then data.registered = os.time(); if not account_details:set(username, data) then log("debug", "Could not store extra details"); usermanager_delete_user(username, host); session.send(st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk.")); return true; end session.send(st.reply(stanza)); -- user created! log("info", "User account created: %s@%s", username, host); module:fire_event("user-registered", { username = username, host = host, source = "mod_register", session = session }); else log("debug", "Could not create user", err); session.send(st.error_reply(stanza, "cancel", "feature-not-implemented", err)); end return true; end); prosody-0.12.4/plugins/PaxHeaders/mod_register_limits.lua0000644000000000000000000000012014475665104020614 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.672765148 prosody-0.12.4/plugins/mod_register_limits.lua0000644000175000017500000000731514475665104023027 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local create_throttle = require "util.throttle".create; local new_cache = require "util.cache".new; local ip_util = require "util.ip"; local new_ip = ip_util.new_ip; local match_ip = ip_util.match; local parse_cidr = ip_util.parse_cidr; local errors = require "util.error"; -- COMPAT drop old option names local min_seconds_between_registrations = module:get_option_number("min_seconds_between_registrations"); local allowlist_only = module:get_option_boolean("allowlist_registration_only", module:get_option_boolean("whitelist_registration_only")); local allowlisted_ips = module:get_option_set("registration_allowlist", module:get_option("registration_whitelist", { "127.0.0.1", "::1" }))._items; local blocklisted_ips = module:get_option_set("registration_blocklist", module:get_option_set("registration_blacklist", {}))._items; local throttle_max = module:get_option_number("registration_throttle_max", min_seconds_between_registrations and 1); local throttle_period = module:get_option_number("registration_throttle_period", min_seconds_between_registrations); local throttle_cache_size = module:get_option_number("registration_throttle_cache_size", 100); local blocklist_overflow = module:get_option_boolean("blocklist_on_registration_throttle_overload", module:get_option_boolean("blacklist_on_registration_throttle_overload", false)); local throttle_cache = new_cache(throttle_cache_size, blocklist_overflow and function (ip, throttle) if not throttle:peek() then module:log("info", "Adding ip %s to registration blocklist", ip); blocklisted_ips[ip] = true; end end or nil); local function check_throttle(ip) if not throttle_max then return true end local throttle = throttle_cache:get(ip); if not throttle then throttle = create_throttle(throttle_max, throttle_period); end throttle_cache:set(ip, throttle); return throttle:poll(1); end local function ip_in_set(set, ip) if set[ip] then return true; end ip = new_ip(ip); for in_set in pairs(set) do if match_ip(ip, parse_cidr(in_set)) then return true; end end return false; end local err_registry = { blocklisted = { text = "Your IP address is blocklisted"; type = "auth"; condition = "forbidden"; }; not_allowlisted = { text = "Your IP address is not allowlisted"; type = "auth"; condition = "forbidden"; }; throttled = { text = "Too many registrations from this IP address recently"; type = "wait"; condition = "policy-violation"; }; } module:hook("user-registering", function (event) local session = event.session; local ip = event.ip or session and session.ip; local log = session and session.log or module._log; if not ip then log("warn", "IP not known; can't apply blocklist/allowlist"); elseif ip_in_set(blocklisted_ips, ip) then log("debug", "Registration disallowed by blocklist"); event.allowed = false; event.error = errors.new("blocklisted", event, err_registry); elseif (allowlist_only and not ip_in_set(allowlisted_ips, ip)) then log("debug", "Registration disallowed by allowlist"); event.allowed = false; event.error = errors.new("not_allowlisted", event, err_registry); elseif throttle_max and not ip_in_set(allowlisted_ips, ip) then if not check_throttle(ip) then log("debug", "Registrations over limit for ip %s", ip or "?"); event.allowed = false; event.error = errors.new("throttled", event, err_registry); end end if event.error then -- COMPAT pre-util.error event.reason = event.error.text; event.error_type = event.error.type; event.error_condition = event.error.condition; end end); prosody-0.12.4/plugins/PaxHeaders/mod_roster.lua0000644000000000000000000000012014475665104016725 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.672765148 prosody-0.12.4/plugins/mod_roster.lua0000644000175000017500000001176014475665104021137 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza" local jid_split = require "util.jid".split; local jid_resource = require "util.jid".resource; local jid_prep = require "util.jid".prep; local tonumber = tonumber; local pairs = pairs; local rm_load_roster = require "core.rostermanager".load_roster; local rm_remove_from_roster = require "core.rostermanager".remove_from_roster; local rm_add_to_roster = require "core.rostermanager".add_to_roster; local rm_roster_push = require "core.rostermanager".roster_push; module:add_feature("jabber:iq:roster"); local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}); module:hook("stream-features", function(event) local origin, features = event.origin, event.features; if origin.username then features:add_child(rosterver_stream_feature); end end); module:hook("iq/self/jabber:iq:roster:query", function(event) local session, stanza = event.origin, event.stanza; if stanza.attr.type == "get" then local roster = st.reply(stanza); local client_ver = tonumber(stanza.tags[1].attr.ver); local server_ver = tonumber(session.roster[false].version or 1); if not (client_ver and server_ver) or client_ver ~= server_ver then roster:query("jabber:iq:roster"); -- Client does not support versioning, or has stale roster for jid, item in pairs(session.roster) do if jid then roster:tag("item", { jid = jid, subscription = item.subscription, ask = item.ask, name = item.name, }); for group in pairs(item.groups) do roster:text_tag("group", group); end roster:up(); -- move out from item end end roster.tags[1].attr.ver = server_ver; end session.send(roster); session.interested = true; -- resource is interested in roster updates else -- stanza.attr.type == "set" local query = stanza.tags[1]; if #query.tags == 1 and query.tags[1].name == "item" and query.tags[1].attr.xmlns == "jabber:iq:roster" and query.tags[1].attr.jid then local item = query.tags[1]; local from_node, from_host = jid_split(stanza.attr.from); local jid = jid_prep(item.attr.jid); if jid and not jid_resource(jid) then if jid ~= from_node.."@"..from_host then if item.attr.subscription == "remove" then local roster = session.roster; local r_item = roster[jid]; if r_item then module:fire_event("roster-item-removed", { username = from_node, jid = jid, item = r_item, origin = session, roster = roster, }); local success, err_type, err_cond, err_msg = rm_remove_from_roster(session, jid); if success then session.send(st.reply(stanza)); rm_roster_push(from_node, from_host, jid); else session.send(st.error_reply(stanza, err_type, err_cond, err_msg)); end else session.send(st.error_reply(stanza, "modify", "item-not-found")); end else local r_item = {name = item.attr.name, groups = {}}; if r_item.name == "" then r_item.name = nil; end if session.roster[jid] then r_item.subscription = session.roster[jid].subscription; r_item.ask = session.roster[jid].ask; else r_item.subscription = "none"; end for group in item:childtags("group") do local text = group:get_text(); if text then r_item.groups[text] = true; end end local success, err_type, err_cond, err_msg = rm_add_to_roster(session, jid, r_item); if success then -- Ok, send success session.send(st.reply(stanza)); -- and push change to all resources rm_roster_push(from_node, from_host, jid); else -- Adding to roster failed session.send(st.error_reply(stanza, err_type, err_cond, err_msg)); end end else -- Trying to add self to roster session.send(st.error_reply(stanza, "cancel", "not-allowed")); end else -- Invalid JID added to roster session.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME what's the correct error? end else -- Roster set didn't include a single item, or its name wasn't 'item' session.send(st.error_reply(stanza, "modify", "bad-request")); end end return true; end); module:hook_global("user-deleted", function(event) local username, host = event.username, event.host; local origin = event.origin or prosody.hosts[host]; if host ~= module.host then return end local roster = rm_load_roster(username, host); for jid, item in pairs(roster) do if jid then module:fire_event("roster-item-removed", { username = username, jid = jid, item = item, roster = roster, origin = origin, }); else for pending_jid in pairs(item.pending) do module:fire_event("roster-item-removed", { username = username, jid = pending_jid, roster = roster, origin = origin, }); end end end end, 300); prosody-0.12.4/plugins/PaxHeaders/mod_s2s.lua0000644000000000000000000000012014475665104016116 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.672765148 prosody-0.12.4/plugins/mod_s2s.lua0000644000175000017500000010616114475665104020330 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local prosody = prosody; local hosts = prosody.hosts; local core_process_stanza = prosody.core_process_stanza; local tostring, type = tostring, type; local t_insert = table.insert; local traceback = debug.traceback; local add_task = require "util.timer".add_task; local stop_timer = require "util.timer".stop; local st = require "util.stanza"; local initialize_filters = require "util.filters".initialize; local nameprep = require "util.encodings".stringprep.nameprep; local new_xmpp_stream = require "util.xmppstream".new; local s2s_new_incoming = require "core.s2smanager".new_incoming; local s2s_new_outgoing = require "core.s2smanager".new_outgoing; local s2s_destroy_session = require "core.s2smanager".destroy_session; local uuid_gen = require "util.uuid".generate; local async = require "util.async"; local runner = async.runner; local connect = require "net.connect".connect; local service = require "net.resolvers.service"; local resolver_chain = require "net.resolvers.chain"; local errors = require "util.error"; local set = require "util.set"; local connect_timeout = module:get_option_number("s2s_timeout", 90); local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5); local opt_keepalives = module:get_option_boolean("s2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true)); local secure_auth = module:get_option_boolean("s2s_secure_auth", false); -- One day... local secure_domains, insecure_domains = module:get_option_set("s2s_secure_domains", {})._items, module:get_option_set("s2s_insecure_domains", {})._items; local require_encryption = module:get_option_boolean("s2s_require_encryption", true); local stanza_size_limit = module:get_option_number("s2s_stanza_size_limit", 1024*512); local measure_connections_inbound = module:metric( "gauge", "connections_inbound", "", "Established incoming s2s connections", {"host", "type", "ip_family"} ); local measure_connections_outbound = module:metric( "gauge", "connections_outbound", "", "Established outgoing s2s connections", {"host", "type", "ip_family"} ); local m_accepted_tcp_connections = module:metric( "counter", "accepted_tcp", "", "Accepted incoming connections on the TCP layer" ); local m_authn_connections = module:metric( "counter", "authenticated", "", "Authenticated incoming connections", {"host", "direction", "mechanism"} ); local m_initiated_connections = module:metric( "counter", "initiated", "", "Initiated outbound connections", {"host"} ); local m_closed_connections = module:metric( "counter", "closed", "", "Closed connections", {"host", "direction", "error"} ); local m_tls_params = module:metric( "counter", "encrypted", "", "Encrypted connections", {"protocol"; "cipher"} ); local sessions = module:shared("sessions"); local runner_callbacks = {}; local listener = {}; local log = module._log; local s2s_service_options = { default_port = 5269; use_ipv4 = module:get_option_boolean("use_ipv4", true); use_ipv6 = module:get_option_boolean("use_ipv6", true); use_dane = module:get_option_boolean("use_dane", false); }; local s2s_service_options_mt = { __index = s2s_service_options } module:hook("stats-update", function () measure_connections_inbound:clear() measure_connections_outbound:clear() -- TODO: init all expected metrics once? -- or maybe create/delete them in host-activate/host-deactivate? requires -- extra API in openmetrics.lua tho for _, session in pairs(sessions) do local is_inbound = string.sub(session.type, 4, 5) == "in" local metric_family = is_inbound and measure_connections_inbound or measure_connections_outbound local host = is_inbound and session.to_host or session.from_host or "" local type_ = session.type or "other" -- we want to expose both v4 and v6 counters in all cases to make -- queries smoother local is_ipv6 = session.ip and session.ip:match(":") and 1 or 0 local is_ipv4 = 1 - is_ipv6 metric_family:with_labels(host, type_, "ipv4"):add(is_ipv4) metric_family:with_labels(host, type_, "ipv6"):add(is_ipv6) end end); --- Handle stanzas to remote domains local bouncy_stanzas = { message = true, presence = true, iq = true }; local function bounce_sendq(session, reason) local sendq = session.sendq; if not sendq then return; end session.log("info", "Sending error replies for %d queued stanzas because of failed outgoing connection to %s", #sendq, session.to_host); local dummy = { type = "s2sin"; send = function () (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", traceback()); end; dummy = true; close = function () (session.log or log)("error", "Attempting to close the dummy origin of s2s error replies, please report this! Traceback: %s", traceback()); end; }; -- FIXME Allow for more specific error conditions -- TODO use util.error ? local error_type = "cancel"; local condition = "remote-server-not-found"; local reason_text; if session.had_stream then -- set when a stream is opened by the remote error_type, condition = "wait", "remote-server-timeout"; end if errors.is_err(reason) then error_type, condition, reason_text = reason.type, reason.condition, reason.text; elseif type(reason) == "string" then reason_text = reason; end for i, data in ipairs(sendq) do local reply = data[2]; if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then reply.attr.type = "error"; reply:tag("error", {type = error_type, by = session.from_host}) :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up(); if reason_text then reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}) :text("Server-to-server connection failed: "..reason_text):up(); end core_process_stanza(dummy, reply); end sendq[i] = nil; end session.sendq = nil; end -- Handles stanzas to existing s2s sessions function route_to_existing_session(event) local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza; if not hosts[from_host] then log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host); return false; end if hosts[to_host] then log("warn", "Attempt to route stanza to a remote %s - a host we do serve?!", from_host); return false; end local host = hosts[from_host].s2sout[to_host]; if not host then return end -- We have a connection to this host already if host.type == "s2sout_unauthed" and (stanza.name ~= "db:verify" or not host.dialback_key) then (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host); -- Queue stanza until we are able to send it local queued_item = { tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza); }; if host.sendq then t_insert(host.sendq, queued_item); else -- luacheck: ignore 122 host.sendq = { queued_item }; end host.log("debug", "stanza [%s] queued ", stanza.name); return true; elseif host.type == "local" or host.type == "component" then log("error", "Trying to send a stanza to ourselves??") log("error", "Traceback: %s", traceback()); log("error", "Stanza: %s", stanza); return false; else if host.sends2s(stanza) then return true; end end end -- Create a new outgoing session for a stanza function route_to_new_session(event) local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza; log("debug", "opening a new outgoing connection for this stanza"); local host_session = s2s_new_outgoing(from_host, to_host); host_session.version = 1; -- Store in buffer host_session.bounce_sendq = bounce_sendq; host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} }; log("debug", "stanza [%s] queued until connection complete", stanza.name); -- FIXME Cleaner solution to passing extra data from resolvers to net.server -- This mt-clone allows resolvers to add extra data, currently used for DANE TLSA records module:context(from_host):fire_event("s2sout-created", { session = host_session }); local xmpp_extra = setmetatable({}, s2s_service_options_mt); local resolver = service.new(to_host, "xmpp-server", "tcp", xmpp_extra); if host_session.ssl_ctx then local sslctx = host_session.ssl_ctx; local xmpps_extra = setmetatable({ default_port = false; servername = to_host; sslctx = sslctx }, s2s_service_options_mt); resolver = resolver_chain.new({ service.new(to_host, "xmpps-server", "tcp", xmpps_extra); resolver; }); end local pre_event = { session = host_session; resolver = resolver }; module:context(from_host):fire_event("s2sout-pre-connect", pre_event); resolver = pre_event.resolver; connect(resolver, listener, nil, { session = host_session }); m_initiated_connections:with_labels(from_host):add(1) return true; end local function keepalive(event) local session = event.session; if not session.notopen then return event.session.sends2s(' '); end end module:hook("s2s-read-timeout", keepalive, -1); function module.add_host(module) if module:get_option_boolean("disallow_s2s", false) then module:log("warn", "The 'disallow_s2s' config option is deprecated, please see https://prosody.im/doc/s2s#disabling"); return nil, "This host has disallow_s2s set"; end module:hook("route/remote", route_to_existing_session, -1); module:hook("route/remote", route_to_new_session, -10); module:hook("s2s-authenticated", make_authenticated, -1); module:hook("s2s-read-timeout", keepalive, -1); module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza) -- luacheck: ignore 212/stanza if session.type == "s2sout" then -- Stream is authenticated and we are seem to be done with feature negotiation, -- so the stream is ready for stanzas. RFC 6120 Section 4.3 mark_connected(session); return true; elseif require_encryption and not session.secure then session.log("warn", "Encrypted server-to-server communication is required but was not offered by %s", session.to_host); session:close({ condition = "policy-violation", text = "Encrypted server-to-server communication is required but was not offered", }, nil, "Could not establish encrypted connection to remote server"); return true; elseif not session.dialback_verifying then session.log("warn", "No SASL EXTERNAL offer and Dialback doesn't seem to be enabled, giving up"); session:close({ condition = "unsupported-feature", text = "No viable authentication method offered", }, nil, "No viable authentication method offered by remote server"); return true; end end, -1); function module.unload() if module.reloading then return end for _, session in pairs(sessions) do if session.to_host == module.host or session.from_host == module.host then session:close("host-gone"); end end end end -- Stream is authorised, and ready for normal stanzas function mark_connected(session) local sendq = session.sendq; local from, to = session.from_host, session.to_host; session.log("info", "%s s2s connection %s->%s complete", session.direction:gsub("^.", string.upper), from, to); local event_data = { session = session }; if session.type == "s2sout" then module:fire_event("s2sout-established", event_data); module:context(from):fire_event("s2sout-established", event_data); if session.incoming then session.send = function(stanza) return module:context(from):fire_event("route/remote", { from_host = from, to_host = to, stanza = stanza }); end; end else if session.outgoing and not hosts[to].s2sout[from] then session.log("debug", "Setting up to handle route from %s to %s", to, from); hosts[to].s2sout[from] = session; -- luacheck: ignore 122 end local host_session = hosts[to]; session.send = function(stanza) return host_session.events.fire_event("route/remote", { from_host = to, to_host = from, stanza = stanza }); end; module:fire_event("s2sin-established", event_data); module:context(to):fire_event("s2sin-established", event_data); end if session.direction == "outgoing" then if sendq then session.log("debug", "sending %d queued stanzas across new outgoing connection to %s", #sendq, session.to_host); local send = session.sends2s; for i, data in ipairs(sendq) do send(data[1]); sendq[i] = nil; end session.sendq = nil; end end if session.connect_timeout then stop_timer(session.connect_timeout); session.connect_timeout = nil; end end function make_authenticated(event) local session, host = event.session, event.host; if not session.secure then if require_encryption or (secure_auth and not(insecure_domains[host])) or secure_domains[host] then session:close({ condition = "policy-violation", text = "Encrypted server-to-server communication is required but was not " ..((session.direction == "outgoing" and "offered") or "used") }, nil, "Could not establish encrypted connection to remote server"); end end if session.type == "s2sout_unauthed" and not session.authenticated_remote and secure_auth and not insecure_domains[host] then session:close({ condition = "policy-violation"; text = "Failed to verify certificate (internal error)"; }); return; end if hosts[host] then session:close({ condition = "undefined-condition", text = "Attempt to authenticate as a host we serve" }); end if session.type == "s2sout_unauthed" then session.type = "s2sout"; elseif session.type == "s2sin_unauthed" then session.type = "s2sin"; elseif session.type ~= "s2sin" and session.type ~= "s2sout" then return false; end if session.incoming and host then if not session.hosts[host] then session.hosts[host] = {}; end session.hosts[host].authed = true; end session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host); local local_host = session.direction == "incoming" and session.to_host or session.from_host m_authn_connections:with_labels(local_host, session.direction, event.mechanism or "other"):add(1) if (session.type == "s2sout" and session.external_auth ~= "succeeded") or session.type == "s2sin" then -- Stream either used dialback for authentication or is an incoming stream. mark_connected(session); end return true; end --- Helper to check that a session peer's certificate is valid local function check_cert_status(session) local host = session.direction == "outgoing" and session.to_host or session.from_host local conn = session.conn:socket() local cert if conn.getpeercertificate then cert = conn:getpeercertificate() end return module:fire_event("s2s-check-certificate", { host = host, session = session, cert = cert }); end --- XMPP stream event handlers local function session_secure(session) session.secure = true; session.encrypted = true; local sock = session.conn:socket(); local info = sock.info and sock:info(); if type(info) == "table" then (session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher); session.compressed = info.compression; m_tls_params:with_labels(info.protocol, info.cipher):add(1) else (session.log or log)("info", "Stream encrypted"); end end local stream_callbacks = { default_ns = "jabber:server" }; function stream_callbacks.handlestanza(session, stanza) stanza = session.filter("stanzas/in", stanza); session.thread:run(stanza); end local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; function stream_callbacks.streamopened(session, attr) -- run _streamopened in async context session.thread:run({ stream = "opened", attr = attr }); end function stream_callbacks._streamopened(session, attr) session.version = tonumber(attr.version) or 0; session.had_stream = true; -- Had a stream opened at least once -- TODO: Rename session.secure to session.encrypted if session.secure == false then session_secure(session); end if session.direction == "incoming" then -- Send a reply stream header -- Validate to/from local to, from = attr.to, attr.from; if to then to = nameprep(attr.to); end if from then from = nameprep(attr.from); end if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts) session:close({ condition = "improper-addressing", text = "Invalid 'to' address" }); return; end if not from and attr.from then -- COMPAT: Some servers do not reliably set 'from' (especially on stream restarts) session:close({ condition = "improper-addressing", text = "Invalid 'from' address" }); return; end -- Set session.[from/to]_host if they have not been set already and if -- this session isn't already authenticated if session.type == "s2sin_unauthed" and from and not session.from_host then session.from_host = from; elseif from ~= session.from_host then session:close({ condition = "improper-addressing", text = "New stream 'from' attribute does not match original" }); return; end if session.type == "s2sin_unauthed" and to and not session.to_host then session.to_host = to; session.host = to; elseif to ~= session.to_host then session:close({ condition = "improper-addressing", text = "New stream 'to' attribute does not match original" }); return; end -- For convenience we'll put the sanitised values into these variables to, from = session.to_host, session.from_host; session.streamid = uuid_gen(); (session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag()); if to then if not hosts[to] then -- Attempting to connect to a host we don't serve session:close({ condition = "host-unknown"; text = "This host does not serve "..to }); return; elseif not hosts[to].modules.s2s then -- Attempting to connect to a host that disallows s2s session:close({ condition = "policy-violation"; text = "Server-to-server communication is disabled for this host"; }); return; end end if hosts[from] then session:close({ condition = "undefined-condition", text = "Attempt to connect from a host we serve" }); return; end if session.secure and not session.cert_chain_status then if check_cert_status(session) == false then return; end end session:open_stream(session.to_host, session.from_host) if session.destroyed then -- sending the stream opening could have failed during an opportunistic write return end session.notopen = nil; if session.version >= 1.0 then local features = st.stanza("stream:features"); if to then module:context(to):fire_event("s2s-stream-features", { origin = session, features = features }); else (session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", from or session.ip or "unknown host"); module:fire_event("s2s-stream-features-legacy", { origin = session, features = features }); end if ( session.type == "s2sin" or session.type == "s2sout" ) or features.tags[1] then log("debug", "Sending stream features: %s", features); session.sends2s(features); else (session.log or log)("warn", "No stream features to offer, giving up"); session:close({ condition = "undefined-condition", text = "No stream features to offer" }); end end elseif session.direction == "outgoing" then session.notopen = nil; if not attr.id then log("warn", "Stream response did not give us a stream id!"); session:close({ condition = "undefined-condition", text = "Missing stream ID" }); return; end session.streamid = attr.id; if session.secure and not session.cert_chain_status then if check_cert_status(session) == false then return; else session.authenticated_remote = true; end end -- If server is pre-1.0, don't wait for features, just do dialback if session.version < 1.0 then if not session.dialback_verifying then module:context(session.from_host):fire_event("s2sout-authenticate-legacy", { origin = session }); else mark_connected(session); end end end end function stream_callbacks._streamclosed(session) (session.log or log)("debug", "Received "); session:close(false); end function stream_callbacks.streamclosed(session, attr) -- run _streamclosed in async context session.thread:run({ stream = "closed", attr = attr }); end -- Some stream conditions indicate a problem on our end, e.g. that we sent -- something invalid. Those should be investigated. Others are problems or -- events in the remote host that don't affect us, or simply that the -- connection was closed for being idle. local stream_condition_severity = { ["bad-format"] = "warn"; ["bad-namespace-prefix"] = "warn"; ["conflict"] = "warn"; ["connection-timeout"] = "debug"; ["host-gone"] = "info"; ["host-unknown"] = "info"; ["improper-addressing"] = "warn"; ["internal-server-error"] = "warn"; ["invalid-from"] = "warn"; ["invalid-namespace"] = "warn"; ["invalid-xml"] = "warn"; ["not-authorized"] = "warn"; ["not-well-formed"] = "warn"; ["policy-violation"] = "warn"; ["remote-connection-failed"] = "warn"; ["reset"] = "info"; ["resource-constraint"] = "info"; ["restricted-xml"] = "warn"; ["see-other-host"] = "info"; ["system-shutdown"] = "info"; ["undefined-condition"] = "warn"; ["unsupported-encoding"] = "warn"; ["unsupported-feature"] = "warn"; ["unsupported-stanza-type"] = "warn"; ["unsupported-version"] = "warn"; } function stream_callbacks.error(session, error, data) if error == "no-stream" then session.log("debug", "Invalid opening stream header (%s)", (data:gsub("^([^\1]+)\1", "{%1}"))); session:close("invalid-namespace"); elseif error == "parse-error" then session.log("debug", "Server-to-server XML parse error: %s", error); session:close("not-well-formed"); elseif error == "stream-error" then local condition, text = "undefined-condition"; for child in data:childtags(nil, xmlns_xmpp_streams) do if child.name ~= "text" then condition = child.name; else text = child:get_text(); end if condition ~= "undefined-condition" and text then break; end end text = condition .. (text and (" ("..text..")") or ""); session.log(stream_condition_severity[condition] or "info", "Session closed by remote with error: %s", text); session:close(nil, text); end end --- Session methods local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; -- reason: stream error to send to the remote server -- remote_reason: stream error received from the remote server -- bounce_reason: stanza error to pass to bounce_sendq because stream- and stanza errors are different local function session_close(session, reason, remote_reason, bounce_reason) local log = session.log or log; if not session.conn then log("debug", "Attempt to close without associated connection with reason %q", reason); return end local conn = session.conn; conn:pause_writes(); -- until :close if session.notopen then if session.direction == "incoming" then session:open_stream(session.to_host, session.from_host); else session:open_stream(session.from_host, session.to_host); end end local this_host = session.direction == "outgoing" and session.from_host or session.to_host if not hosts[this_host] then this_host = ":unknown"; end if reason then -- nil == no err, initiated by us, false == initiated by remote local stream_error; local condition, text, extra if type(reason) == "string" then -- assume stream error condition = reason elseif type(reason) == "table" and not st.is_stanza(reason) then condition = reason.condition or "undefined-condition" text = reason.text extra = reason.extra end if condition then stream_error = st.stanza("stream:error"):tag(condition, stream_xmlns_attr):up(); if text then stream_error:tag("text", stream_xmlns_attr):text(text):up(); end if extra then stream_error:add_child(extra); end end if this_host and condition then m_closed_connections:with_labels(this_host, session.direction, condition):add(1) end if st.is_stanza(stream_error) then -- to and from are never unknown on outgoing connections log("debug", "Disconnecting %s->%s[%s], is: %s", session.from_host or "(unknown host)" or session.ip, session.to_host or "(unknown host)", session.type, stream_error); session.sends2s(stream_error); end else m_closed_connections:with_labels(this_host or ":unknown", session.direction, reason == false and ":remote-choice" or ":local-choice"):add(1) end session.sends2s(""); function session.sends2s() return false; end -- luacheck: ignore 422/reason 412/reason -- FIXME reason should be managed in a place common to c2s, s2s, bosh, component etc local reason = remote_reason or (reason and (reason.text or reason.condition)) or reason; session.log("info", "%s s2s stream %s->%s closed: %s", session.direction:gsub("^.", string.upper), session.from_host or "(unknown host)", session.to_host or "(unknown host)", reason or "stream closed"); conn:resume_writes(); if session.connect_timeout then stop_timer(session.connect_timeout); session.connect_timeout = nil; end -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote if reason == nil and not session.notopen and session.direction == "incoming" then add_task(stream_close_timeout, function () if not session.destroyed then session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); s2s_destroy_session(session, reason, bounce_reason); conn:close(); end end); else s2s_destroy_session(session, reason, bounce_reason); conn:close(); -- Close immediately, as this is an outgoing connection or is not authed end end function session_stream_attrs(session, from, to, attr) -- luacheck: ignore 212/session if not from or (hosts[from] and hosts[from].modules.dialback) then attr["xmlns:db"] = 'jabber:server:dialback'; end if not from then attr.from = ''; end if not to then attr.to = ''; end end -- Session initialization logic shared by incoming and outgoing local function initialize_session(session) local stream = new_xmpp_stream(session, stream_callbacks, stanza_size_limit); session.thread = runner(function (stanza) if st.is_stanza(stanza) then core_process_stanza(session, stanza); elseif stanza.stream == "opened" then stream_callbacks._streamopened(session, stanza.attr); elseif stanza.stream == "closed" then stream_callbacks._streamclosed(session, stanza.attr); end end, runner_callbacks, session); local log = session.log or log; session.stream = stream; session.notopen = true; function session.reset_stream() session.notopen = true; session.streamid = nil; session.stream:reset(); end session.stream_attrs = session_stream_attrs; local filter = initialize_filters(session); local conn = session.conn; local w = conn.write; if conn:ssl() then session_secure(session); end function session.sends2s(t) log("debug", "Sending[%s]: %s", session.type, t.top_tag and t:top_tag() or t:match("^[^>]*>?")); if t.name then t = filter("stanzas/out", t); end if t then t = filter("bytes/out", tostring(t)); if t then return w(conn, t); end end end function session.data(data) data = filter("bytes/in", data); if data then local ok, err = stream:feed(data); if ok then return; end log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); if err == "stanza-too-large" then session:close({ condition = "policy-violation", text = "XML stanza is too big", extra = st.stanza("stanza-too-big", { xmlns = 'urn:xmpp:errors' }), }, nil, "Received invalid XML from remote server"); else session:close("not-well-formed", nil, "Received invalid XML from remote server"); end end end session.close = session_close; local handlestanza = stream_callbacks.handlestanza; function session.dispatch_stanza(session, stanza) -- luacheck: ignore 432/session return handlestanza(session, stanza); end module:fire_event("s2s-created", { session = session }); session.connect_timeout = add_task(connect_timeout, function () if session.type == "s2sin" or session.type == "s2sout" then return; -- Ok, we're connected elseif session.type == "s2s_destroyed" then return; -- Session already destroyed end -- Not connected, need to close session and clean up (session.log or log)("debug", "Destroying incomplete session %s->%s due to inactivity", session.from_host or "(unknown)", session.to_host or "(unknown)"); session:close("connection-timeout"); end); end function runner_callbacks:ready() self.data.log("debug", "Runner %s ready (%s)", self.thread, coroutine.status(self.thread)); self.data.conn:resume(); end function runner_callbacks:waiting() self.data.log("debug", "Runner %s waiting (%s)", self.thread, coroutine.status(self.thread)); self.data.conn:pause(); end function runner_callbacks:error(err) (self.data.log or log)("error", "Traceback[s2s]: %s", err); end function listener.onconnect(conn) conn:setoption("keepalive", opt_keepalives); local session = sessions[conn]; if not session then -- New incoming connection session = s2s_new_incoming(conn); sessions[conn] = session; session.log("debug", "Incoming s2s connection"); module:fire_event("s2sin-connected", { session = session }) initialize_session(session); m_accepted_tcp_connections:with_labels():add(1) else -- Outgoing session connected module:fire_event("s2sout-connected", { session = session }) session:open_stream(session.from_host, session.to_host); end module:fire_event("s2s-connected", { session = session }) session.ip = conn:ip(); end function listener.onincoming(conn, data) local session = sessions[conn]; if session then session.data(data); end end function listener.onstatus(conn, status) if status == "ssl-handshake-complete" then local session = sessions[conn]; if session and session.direction == "outgoing" then session.log("debug", "Sending stream header..."); session:open_stream(session.from_host, session.to_host); end end end function listener.ondisconnect(conn, err) local session = sessions[conn]; if session then sessions[conn] = nil; (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", session.from_host, session.to_host, err or "connection closed"); if session.secure == false and err then -- TODO util.error-ify this err = "Error during negotiation of encrypted connection: "..err; end s2s_destroy_session(session, err); end module:fire_event("s2s-closed", { session = session; conn = conn }); end function listener.onfail(data, err) local session = data and data.session; if session then if err and session.direction == "outgoing" and session.notopen then (session.log or log)("debug", "s2s connection attempt failed: %s", err); end (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", session.from_host, session.to_host, err or "connection closed"); s2s_destroy_session(session, err); end end function listener.onreadtimeout(conn) local session = sessions[conn]; if session then return (hosts[session.host] or prosody).events.fire_event("s2s-read-timeout", { session = session }); end end function listener.ondrain(conn) local session = sessions[conn]; if session then return (hosts[session.host] or prosody).events.fire_event("s2s-ondrain", { session = session }); end end function listener.onpredrain(conn) local session = sessions[conn]; if session then return (hosts[session.host] or prosody).events.fire_event("s2s-pre-ondrain", { session = session }); end end function listener.register_outgoing(conn, session) sessions[conn] = session; initialize_session(session); end function listener.ondetach(conn) sessions[conn] = nil; end function listener.onattach(conn, data) local session = data and data.session; if session then session.conn = conn; sessions[conn] = session; initialize_session(session); end end -- Complete the sentence "Your certificate " with what's wrong local function friendly_cert_error(session) --> string if session.cert_chain_status == "invalid" then if session.cert_chain_errors then local cert_errors = set.new(session.cert_chain_errors[1]); if cert_errors:contains("certificate has expired") then return "has expired"; elseif cert_errors:contains("self signed certificate") then return "is self-signed"; end end return "is not trusted"; -- for some other reason elseif session.cert_identity_status == "invalid" then return "is not valid for this name"; end -- this should normally be unreachable except if no s2s auth module was loaded return "could not be validated"; end function check_auth_policy(event) local host, session = event.host, event.session; local must_secure = secure_auth; if not must_secure and secure_domains[host] then must_secure = true; elseif must_secure and insecure_domains[host] then must_secure = false; end if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then local reason = friendly_cert_error(session); session.log("warn", "Forbidding insecure connection to/from %s because its certificate %s", host or session.ip or "(unknown host)", reason); -- XEP-0178 recommends closing outgoing connections without warning -- but does not give a rationale for this. -- In practice most cases are configuration mistakes or forgotten -- certificate renewals. We think it's better to let the other party -- know about the problem so that they can fix it. session:close({ condition = "not-authorized", text = "Your server's certificate "..reason }, nil, "Remote server's certificate "..reason); return false; end end module:hook("s2s-check-certificate", check_auth_policy, -1); module:hook("server-stopping", function(event) -- Close ports local pm = require "core.portmanager"; for _, netservice in pairs(module.items["net-provider"]) do pm.unregister_service(netservice.name, netservice); end -- Stop opening new connections for host in pairs(prosody.hosts) do if prosody.hosts[host].modules.s2s then module:context(host):unhook("route/remote", route_to_new_session); end end local wait, done = async.waiter(1, true); module:hook("s2s-closed", function () if next(sessions) == nil then done(); end end, 1) -- Close sessions local reason = event.reason; for _, session in pairs(sessions) do session:close{ condition = "system-shutdown", text = reason }; end -- Wait for them to close properly if they haven't already if next(sessions) ~= nil then module:log("info", "Waiting for sessions to close"); add_task(stream_close_timeout + 1, function () done() end); wait(); end end, -200); module:provides("net", { name = "s2s"; listener = listener; default_port = 5269; encryption = "starttls"; ssl_config = { -- FIXME This only applies to Direct TLS, which we don't use yet. -- This gets applied for real in mod_tls verify = { "peer", "client_once", }; }; multiplex = { protocol = "xmpp-server"; pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>"; }; }); module:provides("net", { name = "s2s_direct_tls"; listener = listener; encryption = "ssl"; ssl_config = { verify = { "peer", "client_once", }; }; multiplex = { protocol = "xmpp-server"; pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>"; }; }); prosody-0.12.4/plugins/PaxHeaders/mod_s2s_auth_certs.lua0000644000000000000000000000012014475665104020337 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.672765148 prosody-0.12.4/plugins/mod_s2s_auth_certs.lua0000644000175000017500000000337514475665104022554 0ustar00prosodyprosody00000000000000module:set_global(); local cert_verify_identity = require "util.x509".verify_identity; local NULL = {}; local log = module._log; local measure_cert_statuses = module:metric("counter", "checked", "", "Certificate validation results", { "chain"; "identity" }) module:hook("s2s-check-certificate", function(event) local session, host, cert = event.session, event.host, event.cert; local conn = session.conn:socket(); local log = session.log or log; if not cert then log("warn", "No certificate provided by %s", host or "unknown host"); return; end local chain_valid, errors; if conn.getpeerverification then chain_valid, errors = conn:getpeerverification(); else chain_valid, errors = false, { { "Chain verification not supported by this version of LuaSec" } }; end -- Is there any interest in printing out all/the number of errors here? if not chain_valid then log("debug", "certificate chain validation result: invalid"); for depth, t in pairs(errors or NULL) do log("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", ")) end session.cert_chain_status = "invalid"; session.cert_chain_errors = errors; else log("debug", "certificate chain validation result: valid"); session.cert_chain_status = "valid"; -- We'll go ahead and verify the asserted identity if the -- connecting server specified one. if host then if cert_verify_identity(host, "xmpp-server", cert) then session.cert_identity_status = "valid" else session.cert_identity_status = "invalid" end log("debug", "certificate identity validation result: %s", session.cert_identity_status); end end measure_cert_statuses:with_labels(session.cert_chain_status or "unknown", session.cert_identity_status or "unknown"):add(1); end, 509); prosody-0.12.4/plugins/PaxHeaders/mod_s2s_bidi.lua0000644000000000000000000000012014475665104017105 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.672765148 prosody-0.12.4/plugins/mod_s2s_bidi.lua0000644000175000017500000000247014475665104021315 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2019 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local xmlns_bidi_feature = "urn:xmpp:features:bidi" local xmlns_bidi = "urn:xmpp:bidi"; local require_encryption = module:get_option_boolean("s2s_require_encryption", true); module:hook("s2s-stream-features", function(event) local origin, features = event.origin, event.features; if origin.type == "s2sin_unauthed" and (not require_encryption or origin.secure) then features:tag("bidi", { xmlns = xmlns_bidi_feature }):up(); end end); module:hook_tag("http://etherx.jabber.org/streams", "features", function (session, stanza) if session.type == "s2sout_unauthed" and (not require_encryption or session.secure) then local bidi = stanza:get_child("bidi", xmlns_bidi_feature); if bidi then session.incoming = true; session.log("debug", "Requesting bidirectional stream"); session.sends2s(st.stanza("bidi", { xmlns = xmlns_bidi })); end end end, 200); module:hook_tag("urn:xmpp:bidi", "bidi", function(session) if session.type == "s2sin_unauthed" and (not require_encryption or session.secure) then session.log("debug", "Requested bidirectional stream"); session.outgoing = true; return true; end end); prosody-0.12.4/plugins/PaxHeaders/mod_saslauth.lua0000644000000000000000000000012014475665104017233 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.676765109 prosody-0.12.4/plugins/mod_saslauth.lua0000644000175000017500000003347114475665104021450 0ustar00prosodyprosody00000000000000-- 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 431/log local st = require "util.stanza"; local sm_bind_resource = require "core.sessionmanager".bind_resource; local sm_make_authenticated = require "core.sessionmanager".make_authenticated; local base64 = require "util.encodings".base64; local set = require "util.set"; local errors = require "util.error"; local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler; local secure_auth_only = module:get_option_boolean("c2s_require_encryption", module:get_option_boolean("require_encryption", true)); local allow_unencrypted_plain_auth = module:get_option_boolean("allow_unencrypted_plain_auth", false) local insecure_mechanisms = module:get_option_set("insecure_sasl_mechanisms", allow_unencrypted_plain_auth and {} or {"PLAIN", "LOGIN"}); local disabled_mechanisms = module:get_option_set("disable_sasl_mechanisms", { "DIGEST-MD5" }); local log = module._log; local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl'; local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind'; local function build_reply(status, ret, err_msg) local reply = st.stanza(status, {xmlns = xmlns_sasl}); if status == "failure" then reply:tag(ret):up(); if err_msg then reply:tag("text"):text(err_msg); end elseif status == "challenge" or status == "success" then if ret == "" then reply:text("=") elseif ret then reply:text(base64.encode(ret)); end else module:log("error", "Unknown sasl status: %s", status); end return reply; end local function handle_status(session, status, ret, err_msg) if not session.sasl_handler then return "failure", "temporary-auth-failure", "Connection gone"; end if status == "failure" then module:fire_event("authentication-failure", { session = session, condition = ret, text = err_msg }); session.sasl_handler = session.sasl_handler:clean_clone(); elseif status == "success" then local ok, err = sm_make_authenticated(session, session.sasl_handler.username, session.sasl_handler.scope); if ok then module:fire_event("authentication-success", { session = session }); session.sasl_handler = nil; session:reset_stream(); else module:log("warn", "SASL succeeded but username was invalid"); module:fire_event("authentication-failure", { session = session, condition = "not-authorized", text = err }); session.sasl_handler = session.sasl_handler:clean_clone(); return "failure", "not-authorized", "User authenticated successfully, but username was invalid"; end end return status, ret, err_msg; end local function sasl_process_cdata(session, stanza) local text = stanza[1]; if text then text = base64.decode(text); if not text then session.sasl_handler = nil; session.send(build_reply("failure", "incorrect-encoding")); return true; end end local status, ret, err_msg = session.sasl_handler:process(text); status, ret, err_msg = handle_status(session, status, ret, err_msg); local s = build_reply(status, ret, err_msg); session.send(s); return true; end module:hook_tag(xmlns_sasl, "success", function (session) if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end module:log("debug", "SASL EXTERNAL with %s succeeded", session.to_host); session.external_auth = "succeeded" session:reset_stream(); session:open_stream(session.from_host, session.to_host); module:fire_event("s2s-authenticated", { session = session, host = session.to_host, mechanism = "EXTERNAL" }); return true; end) module:hook_tag(xmlns_sasl, "failure", function (session, stanza) if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end local text = stanza:get_child_text("text"); local condition = "unknown-condition"; for child in stanza:childtags() do if child.name ~= "text" then condition = child.name; break; end end local err = errors.new({ -- TODO type = what? text = text, condition = condition, }, { session = session, stanza = stanza, }); module:log("info", "SASL EXTERNAL with %s failed: %s", session.to_host, err); session.external_auth = "failed" session.external_auth_failure_reason = err; end, 500) module:hook_tag(xmlns_sasl, "failure", function (session, stanza) -- luacheck: ignore 212/stanza session.log("debug", "No fallback from SASL EXTERNAL failure, giving up"); session:close(nil, session.external_auth_failure_reason, errors.new({ type = "wait", condition = "remote-server-timeout", text = "Could not authenticate to remote server", }, { session = session, sasl_failure = session.external_auth_failure_reason, })); return true; end, 90) module:hook_tag("http://etherx.jabber.org/streams", "features", function (session, stanza) if session.type ~= "s2sout_unauthed" or not session.secure then return; end local mechanisms = stanza:get_child("mechanisms", xmlns_sasl) if mechanisms then for mech in mechanisms:childtags() do if mech[1] == "EXTERNAL" then module:log("debug", "Initiating SASL EXTERNAL with %s", session.to_host); local reply = st.stanza("auth", {xmlns = xmlns_sasl, mechanism = "EXTERNAL"}); reply:text(base64.encode(session.from_host)) session.sends2s(reply) session.external_auth = "attempting" return true end end end end, 150); local function s2s_external_auth(session, stanza) if session.external_auth ~= "offered" then return end -- Unexpected request local mechanism = stanza.attr.mechanism; if mechanism ~= "EXTERNAL" then session.sends2s(build_reply("failure", "invalid-mechanism")); return true; end if not session.secure then session.sends2s(build_reply("failure", "encryption-required")); return true; end local text = stanza[1]; if not text then session.sends2s(build_reply("failure", "malformed-request")); return true; end text = base64.decode(text); if not text then session.sends2s(build_reply("failure", "incorrect-encoding")); return true; end -- The text value is either "" or equals session.from_host if not ( text == "" or text == session.from_host ) then session.sends2s(build_reply("failure", "invalid-authzid")); return true; end -- We've already verified the external cert identity before offering EXTERNAL if session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid" then session.sends2s(build_reply("failure", "not-authorized")); session:close(); return true; end -- Success! session.external_auth = "succeeded"; session.sends2s(build_reply("success")); module:log("info", "Accepting SASL EXTERNAL identity from %s", session.from_host); module:fire_event("s2s-authenticated", { session = session, host = session.from_host, mechanism = mechanism }); session:reset_stream(); return true; end module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event) local session, stanza = event.origin, event.stanza; if session.type == "s2sin_unauthed" then return s2s_external_auth(session, stanza) end if session.type ~= "c2s_unauthed" or module:get_host_type() ~= "local" then return; end if session.sasl_handler and session.sasl_handler.selected then session.sasl_handler = nil; -- allow starting a new SASL negotiation before completing an old one end if not session.sasl_handler then session.sasl_handler = usermanager_get_sasl_handler(module.host, session); end local mechanism = stanza.attr.mechanism; if not session.secure and (secure_auth_only or insecure_mechanisms:contains(mechanism)) then session.send(build_reply("failure", "encryption-required")); return true; elseif disabled_mechanisms:contains(mechanism) then session.send(build_reply("failure", "invalid-mechanism")); return true; end local valid_mechanism = session.sasl_handler:select(mechanism); if not valid_mechanism then session.send(build_reply("failure", "invalid-mechanism")); return true; end return sasl_process_cdata(session, stanza); end); module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:response", function(event) local session = event.origin; if not(session.sasl_handler and session.sasl_handler.selected) then session.send(build_reply("failure", "not-authorized", "Out of order SASL element")); return true; end return sasl_process_cdata(session, event.stanza); end); module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event) local session = event.origin; session.sasl_handler = nil; session.send(build_reply("failure", "aborted")); return true; end); local function tls_unique(self) return self.userdata["tls-unique"]:getpeerfinished(); end local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' }; local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' }; local xmpp_session_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-session' }; module:hook("stream-features", function(event) local origin, features = event.origin, event.features; local log = origin.log or log; if not origin.username then if secure_auth_only and not origin.secure then log("debug", "Not offering authentication on insecure connection"); return; end local sasl_handler = usermanager_get_sasl_handler(module.host, origin) origin.sasl_handler = sasl_handler; if origin.encrypted then -- check whether LuaSec has the nifty binding to the function needed for tls-unique -- FIXME: would be nice to have this check only once and not for every socket if sasl_handler.add_cb_handler then local socket = origin.conn:socket(); local info = socket.info and socket:info(); if info.protocol == "TLSv1.3" then log("debug", "Channel binding 'tls-unique' undefined in context of TLS 1.3"); elseif socket.getpeerfinished and socket:getpeerfinished() then log("debug", "Channel binding 'tls-unique' supported"); sasl_handler:add_cb_handler("tls-unique", tls_unique); else log("debug", "Channel binding 'tls-unique' not supported (by LuaSec?)"); end sasl_handler["userdata"] = { ["tls-unique"] = socket; }; else log("debug", "Channel binding not supported by SASL handler"); end end local mechanisms = st.stanza("mechanisms", mechanisms_attr); local sasl_mechanisms = sasl_handler:mechanisms() local available_mechanisms = set.new(); for mechanism in pairs(sasl_mechanisms) do available_mechanisms:add(mechanism); end log("debug", "SASL mechanisms supported by handler: %s", available_mechanisms); local usable_mechanisms = available_mechanisms - disabled_mechanisms; local available_disabled = set.intersection(available_mechanisms, disabled_mechanisms); if not available_disabled:empty() then log("debug", "Not offering disabled mechanisms: %s", available_disabled); end local available_insecure = set.intersection(available_mechanisms, insecure_mechanisms); if not origin.secure and not available_insecure:empty() then log("debug", "Session is not secure, not offering insecure mechanisms: %s", available_insecure); usable_mechanisms = usable_mechanisms - insecure_mechanisms; end if not usable_mechanisms:empty() then log("debug", "Offering usable mechanisms: %s", usable_mechanisms); for mechanism in usable_mechanisms do mechanisms:tag("mechanism"):text(mechanism):up(); end features:add_child(mechanisms); return; end local authmod = module:get_option_string("authentication", "internal_hashed"); if available_mechanisms:empty() then log("warn", "No available SASL mechanisms, verify that the configured authentication module '%s' is loaded and configured correctly", authmod); return; end if not origin.secure and not available_insecure:empty() then if not available_disabled:empty() then log("warn", "All SASL mechanisms provided by authentication module '%s' are forbidden on insecure connections (%s) or disabled (%s)", authmod, available_insecure, available_disabled); else log("warn", "All SASL mechanisms provided by authentication module '%s' are forbidden on insecure connections (%s)", authmod, available_insecure); end elseif not available_disabled:empty() then log("warn", "All SASL mechanisms provided by authentication module '%s' are disabled (%s)", authmod, available_disabled); end else features:tag("bind", bind_attr):tag("required"):up():up(); features:tag("session", xmpp_session_attr):tag("optional"):up():up(); end end); module:hook("s2s-stream-features", function(event) local origin, features = event.origin, event.features; if origin.secure and origin.type == "s2sin_unauthed" then -- Offer EXTERNAL only if both chain and identity is valid. if origin.cert_chain_status == "valid" and origin.cert_identity_status == "valid" then module:log("debug", "Offering SASL EXTERNAL"); origin.external_auth = "offered" features:tag("mechanisms", { xmlns = xmlns_sasl }) :tag("mechanism"):text("EXTERNAL") :up():up(); end end end); module:hook("stanza/iq/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event) local origin, stanza = event.origin, event.stanza; local resource; if stanza.attr.type == "set" then local bind = stanza.tags[1]; resource = bind:get_child("resource"); resource = resource and #resource.tags == 0 and resource[1] or nil; end local success, err_type, err, err_msg = sm_bind_resource(origin, resource); if success then origin.send(st.reply(stanza) :tag("bind", { xmlns = xmlns_bind }) :tag("jid"):text(origin.full_jid)); origin.log("debug", "Resource bound: %s", origin.full_jid); else origin.send(st.error_reply(stanza, err_type, err, err_msg)); origin.log("debug", "Resource bind failed: %s", err_msg or err); end return true; end); local function handle_legacy_session(event) event.origin.send(st.reply(event.stanza)); return true; end module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-session:session", handle_legacy_session); module:hook("iq/host/urn:ietf:params:xml:ns:xmpp-session:session", handle_legacy_session); prosody-0.12.4/plugins/PaxHeaders/mod_scansion_record.lua0000644000000000000000000000012014475665104020562 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.676765109 prosody-0.12.4/plugins/mod_scansion_record.lua0000644000175000017500000000676214475665104023002 0ustar00prosodyprosody00000000000000local names = { "Romeo", "Juliet", "Mercutio", "Tybalt", "Benvolio" }; local devices = { "", "phone", "laptop", "tablet", "toaster", "fridge", "shoe" }; local users = {}; local filters = require "util.filters"; local id = require "util.id"; local dt = require "util.datetime"; local dm = require "util.datamanager"; local st = require "util.stanza"; local record_id = id.short():lower(); local record_date = os.date("%Y%b%d"):lower(); local header_file = dm.getpath(record_id, "scansion", record_date, "scs", true); local record_file = dm.getpath(record_id, "scansion", record_date, "log", true); local head = io.open(header_file, "w"); local scan = io.open(record_file, "w+"); local function record(string) scan:write(string); scan:flush(); end local function record_header(string) head:write(string); head:flush(); end local function record_object(class, name, props) head:write(("[%s] %s\n"):format(class, name)); for k,v in pairs(props) do head:write(("\t%s: %s\n"):format(k, v)); end head:write("\n"); head:flush(); end local function record_event(session, event) record(session.scansion_id.." "..event.."\n\n"); end local function record_stanza(stanza, session, verb) local flattened = tostring(stanza:indent(2, "\t")); record(session.scansion_id.." "..verb..":\n\t"..flattened.."\n\n"); end local function record_stanza_in(stanza, session) if stanza.attr.xmlns == nil then local copy = st.clone(stanza); copy.attr.from = nil; record_stanza(copy, session, "sends") end return stanza; end local function record_stanza_out(stanza, session) if stanza.attr.xmlns == nil then if not (stanza.name == "iq" and stanza:get_child("bind", "urn:ietf:params:xml:ns:xmpp-bind")) then local copy = st.clone(stanza); if copy.attr.to == session.full_jid then copy.attr.to = nil; end record_stanza(copy, session, "receives"); end end return stanza; end module:hook("resource-bind", function (event) local session = event.session; if not users[session.username] then users[session.username] = { character = table.remove(names, 1) or id.short(); devices = {}; n_devices = 0; }; end local user = users[session.username]; local device = user.devices[session.resource]; if not device then user.n_devices = user.n_devices + 1; device = devices[user.n_devices] or ("device"..id.short()); user.devices[session.resource] = device; end session.scansion_character = user.character; session.scansion_device = device; session.scansion_id = user.character..(device ~= "" and "'s "..device or device); record_object("Client", session.scansion_id, { jid = session.full_jid, password = "password", }); module:log("info", "Connected: %s", session.scansion_id); record_event(session, "connects"); filters.add_filter(session, "stanzas/in", record_stanza_in); filters.add_filter(session, "stanzas/out", record_stanza_out); end); module:hook("resource-unbind", function (event) local session = event.session; if session.scansion_id then record_event(session, "disconnects"); end end) record_header("# mod_scansion_record on host '"..module.host.."' recording started "..dt.datetime().."\n\n"); record[[ ----- ]] module:hook_global("server-stopping", function () record("# recording ended on "..dt.datetime().."\n"); module:log("info", "Scansion recording available in %s", header_file); end); prosody.events.add_handler("server-cleanup", function () scan:seek("set", 0); for line in scan:lines() do head:write(line, "\n"); end scan:close(); os.remove(record_file); head:close() end); prosody-0.12.4/plugins/PaxHeaders/mod_server_contact_info.lua0000644000000000000000000000012014475665104021443 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.676765109 prosody-0.12.4/plugins/mod_server_contact_info.lua0000644000175000017500000000260614475665104023654 0ustar00prosodyprosody00000000000000-- XEP-0157: Contact Addresses for XMPP Services for Prosody -- -- Copyright (C) 2011-2018 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local array = require "util.array"; local jid = require "util.jid"; local url = require "socket.url"; -- Source: http://xmpp.org/registrar/formtypes.html#http:--jabber.org-network-serverinfo local form_layout = require "util.dataforms".new({ { var = "FORM_TYPE"; type = "hidden"; value = "http://jabber.org/network/serverinfo"; }; { name = "abuse", var = "abuse-addresses", type = "list-multi" }, { name = "admin", var = "admin-addresses", type = "list-multi" }, { name = "feedback", var = "feedback-addresses", type = "list-multi" }, { name = "sales", var = "sales-addresses", type = "list-multi" }, { name = "security", var = "security-addresses", type = "list-multi" }, { name = "status", var = "status-addresses", type = "list-multi" }, { name = "support", var = "support-addresses", type = "list-multi" }, }); -- JIDs of configured service admins are used as fallback local admins = module:get_option_inherited_set("admins", {}); local contact_config = module:get_option("contact_info", { admin = array.collect(admins / jid.prep / function(admin) return url.build({scheme = "xmpp"; path = admin}); end); }); module:add_extension(form_layout:form(contact_config, "result")); prosody-0.12.4/plugins/PaxHeaders/mod_smacks.lua0000644000000000000000000000012014475665104016670 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.676765109 prosody-0.12.4/plugins/mod_smacks.lua0000644000175000017500000007216314475665104021106 0ustar00prosodyprosody00000000000000-- XEP-0198: Stream Management for Prosody IM -- -- Copyright (C) 2010-2015 Matthew Wild -- Copyright (C) 2010 Waqas Hussain -- Copyright (C) 2012-2021 Kim Alvefur -- Copyright (C) 2012 Thijs Alkemade -- Copyright (C) 2014 Florian Zeitz -- Copyright (C) 2016-2020 Thilo Molitor -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local tonumber = tonumber; local tostring = tostring; local os_time = os.time; -- These metrics together allow to calculate an instantaneous -- "unacked stanzas" metric in the graphing frontend, without us having to -- iterate over all the queues. local tx_queued_stanzas = module:measure("tx_queued_stanzas", "counter"); local tx_dropped_stanzas = module:metric( "histogram", "tx_dropped_stanzas", "", "number of stanzas in a queue which got dropped", {}, {buckets = {0, 1, 2, 4, 8, 16, 32}} ):with_labels(); local tx_acked_stanzas = module:metric( "histogram", "tx_acked_stanzas", "", "number of items acked per ack received", {}, {buckets = {0, 1, 2, 4, 8, 16, 32}} ):with_labels(); -- number of session resumptions attempts where the session had expired local resumption_expired = module:measure("session_resumption_expired", "counter"); local resumption_age = module:metric( "histogram", "resumption_age", "seconds", "time the session had been hibernating at the time of a resumption", {}, {buckets = { 0, 1, 2, 5, 10, 30, 60, 120, 300, 600 }} ):with_labels(); local sessions_expired = module:measure("sessions_expired", "counter"); local sessions_started = module:measure("sessions_started", "counter"); local datetime = require "util.datetime"; local add_filter = require "util.filters".add_filter; local jid = require "util.jid"; local smqueue = require "util.smqueue"; local st = require "util.stanza"; local timer = require "util.timer"; local new_id = require "util.id".short; local watchdog = require "util.watchdog"; local it = require"util.iterators"; local sessionmanager = require "core.sessionmanager"; local xmlns_errors = "urn:ietf:params:xml:ns:xmpp-stanzas"; local xmlns_delay = "urn:xmpp:delay"; local xmlns_mam2 = "urn:xmpp:mam:2"; local xmlns_sm2 = "urn:xmpp:sm:2"; local xmlns_sm3 = "urn:xmpp:sm:3"; local sm2_attr = { xmlns = xmlns_sm2 }; local sm3_attr = { xmlns = xmlns_sm3 }; local queue_size = module:get_option_number("smacks_max_queue_size", 500); local resume_timeout = module:get_option_number("smacks_hibernation_time", 600); local s2s_smacks = module:get_option_boolean("smacks_enabled_s2s", true); local s2s_resend = module:get_option_boolean("smacks_s2s_resend", false); local max_unacked_stanzas = module:get_option_number("smacks_max_unacked_stanzas", 0); local max_inactive_unacked_stanzas = module:get_option_number("smacks_max_inactive_unacked_stanzas", 256); local delayed_ack_timeout = module:get_option_number("smacks_max_ack_delay", 30); local max_old_sessions = module:get_option_number("smacks_max_old_sessions", 10); local c2s_sessions = module:shared("/*/c2s/sessions"); local local_sessions = prosody.hosts[module.host].sessions; local function format_h(h) if h then return string.format("%d", h) end end local all_old_sessions = module:open_store("smacks_h"); local old_session_registry = module:open_store("smacks_h", "map"); local session_registry = module:shared "/*/smacks/resumption-tokens"; -- > user@host/resumption-token --> resource local ack_errors = require"util.error".init("mod_smacks", xmlns_sm3, { head = { condition = "undefined-condition"; text = "Client acknowledged more stanzas than sent by server" }; tail = { condition = "undefined-condition"; text = "Client acknowledged less stanzas than already acknowledged" }; pop = { condition = "internal-server-error"; text = "Something went wrong with Stream Management" }; overflow = { condition = "resource-constraint", text = "Too many unacked stanzas remaining, session can't be resumed" } }); -- COMPAT note the use of compatibility wrapper in events (queue:table()) local function ack_delayed(session, stanza) -- fire event only if configured to do so and our session is not already hibernated or destroyed if delayed_ack_timeout > 0 and session.awaiting_ack and not session.hibernating and not session.destroyed then session.log("debug", "Firing event 'smacks-ack-delayed', queue = %d", session.outgoing_stanza_queue and session.outgoing_stanza_queue:count_unacked() or 0); module:fire_event("smacks-ack-delayed", {origin = session, queue = session.outgoing_stanza_queue:table(), stanza = stanza}); end session.delayed_ack_timer = nil; end local function can_do_smacks(session, advertise_only) if session.smacks then return false, "unexpected-request", "Stream management is already enabled"; end local session_type = session.type; if session.username then if not(advertise_only) and not(session.resource) then -- Fail unless we're only advertising sm return false, "unexpected-request", "Client must bind a resource before enabling stream management"; end return true; elseif s2s_smacks and (session_type == "s2sin" or session_type == "s2sout") then return true; end return false, "service-unavailable", "Stream management is not available for this stream"; end module:hook("stream-features", function (event) if can_do_smacks(event.origin, true) then event.features:tag("sm", sm2_attr):tag("optional"):up():up(); event.features:tag("sm", sm3_attr):tag("optional"):up():up(); end end); module:hook("s2s-stream-features", function (event) if can_do_smacks(event.origin, true) then event.features:tag("sm", sm2_attr):tag("optional"):up():up(); event.features:tag("sm", sm3_attr):tag("optional"):up():up(); end end); local function should_ack(session, force) if not session then return end -- shouldn't be possible if session.destroyed then return end -- gone if not session.smacks then return end -- not using if session.hibernating then return end -- can't ack when asleep if session.awaiting_ack then return end -- already waiting if force then return force end local queue = session.outgoing_stanza_queue; local expected_h = queue:count_acked() + queue:count_unacked(); local max_unacked = max_unacked_stanzas; if session.state == "inactive" then max_unacked = max_inactive_unacked_stanzas; end -- this check of last_requested_h prevents ack-loops if misbehaving clients report wrong -- stanza counts. it is set when an is really sent (e.g. inside timer), preventing any -- further requests until a higher h-value would be expected. return queue:count_unacked() > max_unacked and expected_h ~= session.last_requested_h; end local function request_ack(session, reason) local queue = session.outgoing_stanza_queue; session.log("debug", "Sending (inside timer, before send) from %s - #queue=%d", reason, queue:count_unacked()); session.awaiting_ack = true; (session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks })) if session.destroyed then return end -- sending something can trigger destruction -- expected_h could be lower than this expression e.g. more stanzas added to the queue meanwhile) session.last_requested_h = queue:count_acked() + queue:count_unacked(); session.log("debug", "Sending (inside timer, after send) from %s - #queue=%d", reason, queue:count_unacked()); if not session.delayed_ack_timer then session.delayed_ack_timer = timer.add_task(delayed_ack_timeout, function() ack_delayed(session, nil); -- we don't know if this is the only new stanza in the queue end); end end local function request_ack_now_if_needed(session, force, reason) if should_ack(session, force) then request_ack(session, reason); end end local function outgoing_stanza_filter(stanza, session) -- XXX: Normally you wouldn't have to check the xmlns for a stanza as it's -- supposed to be nil. -- However, when using mod_smacks with mod_websocket, then mod_websocket's -- stanzas/out filter can get called before this one and adds the xmlns. if session.resending_unacked then return stanza end if not session.smacks then return stanza end local is_stanza = st.is_stanza(stanza) and (not stanza.attr.xmlns or stanza.attr.xmlns == 'jabber:client') and not stanza.name:find":"; if is_stanza then local queue = session.outgoing_stanza_queue; local cached_stanza = st.clone(stanza); if cached_stanza.name ~= "iq" and cached_stanza:get_child("delay", xmlns_delay) == nil then cached_stanza = cached_stanza:tag("delay", { xmlns = xmlns_delay, from = jid.bare(session.full_jid or session.host), stamp = datetime.datetime() }); end queue:push(cached_stanza); tx_queued_stanzas(1); if session.hibernating then session.log("debug", "hibernating since %s, stanza queued", datetime.datetime(session.hibernating)); -- FIXME queue implementation changed, anything depending on it being an array will break module:fire_event("smacks-hibernation-stanza-queued", {origin = session, queue = queue:table(), stanza = cached_stanza}); return nil; end end return stanza; end local function count_incoming_stanzas(stanza, session) if not stanza.attr.xmlns then session.handled_stanza_count = session.handled_stanza_count + 1; session.log("debug", "Handled %d incoming stanzas", session.handled_stanza_count); end return stanza; end local function wrap_session_out(session, resume) if not resume then session.outgoing_stanza_queue = smqueue.new(queue_size); end add_filter(session, "stanzas/out", outgoing_stanza_filter, -999); return session; end module:hook("pre-session-close", function(event) local session = event.session; if session.smacks == nil then return end if session.resumption_token then session.log("debug", "Revoking resumption token"); session_registry[jid.join(session.username, session.host, session.resumption_token)] = nil; old_session_registry:set(session.username, session.resumption_token, nil); session.resumption_token = nil; else session.log("debug", "Session not resumable"); end if session.hibernating_watchdog then session.log("debug", "Removing sleeping watchdog"); -- If the session is being replaced instead of resume, we don't want the -- old session around to time out and cause trouble for the new session session.hibernating_watchdog:cancel(); session.hibernating_watchdog = nil; else session.log("debug", "No watchdog set"); end -- send out last ack as per revision 1.5.2 of XEP-0198 if session.smacks and session.conn and session.handled_stanza_count then (session.sends2s or session.send)(st.stanza("a", { xmlns = session.smacks; h = format_h(session.handled_stanza_count); })); end end); local function wrap_session_in(session, resume) if not resume then sessions_started(1); session.handled_stanza_count = 0; end add_filter(session, "stanzas/in", count_incoming_stanzas, 999); return session; end local function wrap_session(session, resume) wrap_session_out(session, resume); wrap_session_in(session, resume); return session; end function handle_enable(session, stanza, xmlns_sm) local ok, err, err_text = can_do_smacks(session); if not ok then session.log("warn", "Failed to enable smacks: %s", err_text); -- TODO: XEP doesn't say we can send error text, should it? (session.sends2s or session.send)(st.stanza("failed", { xmlns = xmlns_sm }):tag(err, { xmlns = xmlns_errors})); return true; end if session.username then local old_sessions, err = all_old_sessions:get(session.username); module:log("debug", "Old sessions: %q", old_sessions) if old_sessions then local keep, count = {}, 0; for token, info in it.sorted_pairs(old_sessions, function(a, b) return (old_sessions[a].t or 0) > (old_sessions[b].t or 0); end) do count = count + 1; if count > max_old_sessions then break end keep[token] = info; end all_old_sessions:set(session.username, keep); elseif err then module:log("error", "Unable to retrieve old resumption counters: %s", err); end end module:log("debug", "Enabling stream management"); session.smacks = xmlns_sm; wrap_session(session, false); local resume_max; local resume_token; local resume = stanza.attr.resume; if (resume == "true" or resume == "1") and session.username then -- resumption on s2s is not currently supported resume_token = new_id(); session_registry[jid.join(session.username, session.host, resume_token)] = session; session.resumption_token = resume_token; resume_max = tostring(resume_timeout); end (session.sends2s or session.send)(st.stanza("enabled", { xmlns = xmlns_sm, id = resume_token, resume = resume, max = resume_max })); return true; end module:hook_tag(xmlns_sm2, "enable", function (session, stanza) return handle_enable(session, stanza, xmlns_sm2); end, 100); module:hook_tag(xmlns_sm3, "enable", function (session, stanza) return handle_enable(session, stanza, xmlns_sm3); end, 100); module:hook_tag("http://etherx.jabber.org/streams", "features", function (session, stanza) -- Needs to be done after flushing sendq since those aren't stored as -- stanzas and counting them is weird. -- TODO unify sendq and smqueue timer.add_task(1e-6, function () if can_do_smacks(session) then if stanza:get_child("sm", xmlns_sm3) then session.sends2s(st.stanza("enable", sm3_attr)); session.smacks = xmlns_sm3; elseif stanza:get_child("sm", xmlns_sm2) then session.sends2s(st.stanza("enable", sm2_attr)); session.smacks = xmlns_sm2; else return; end wrap_session_out(session, false); end end); end); function handle_enabled(session, stanza, xmlns_sm) -- luacheck: ignore 212/stanza module:log("debug", "Enabling stream management"); session.smacks = xmlns_sm; wrap_session_in(session, false); -- FIXME Resume? return true; end module:hook_tag(xmlns_sm2, "enabled", function (session, stanza) return handle_enabled(session, stanza, xmlns_sm2); end, 100); module:hook_tag(xmlns_sm3, "enabled", function (session, stanza) return handle_enabled(session, stanza, xmlns_sm3); end, 100); function handle_r(origin, stanza, xmlns_sm) -- luacheck: ignore 212/stanza if not origin.smacks then module:log("debug", "Received ack request from non-smack-enabled session"); return; end module:log("debug", "Received ack request, acking for %d", origin.handled_stanza_count); -- Reply with (origin.sends2s or origin.send)(st.stanza("a", { xmlns = xmlns_sm, h = format_h(origin.handled_stanza_count) })); -- piggyback our own ack request if needed (see request_ack_if_needed() for explanation of last_requested_h) request_ack_now_if_needed(origin, false, "piggybacked by handle_r", nil); return true; end module:hook_tag(xmlns_sm2, "r", function (origin, stanza) return handle_r(origin, stanza, xmlns_sm2); end); module:hook_tag(xmlns_sm3, "r", function (origin, stanza) return handle_r(origin, stanza, xmlns_sm3); end); function handle_a(origin, stanza) if not origin.smacks then return; end origin.awaiting_ack = nil; if origin.awaiting_ack_timer then timer.stop(origin.awaiting_ack_timer); origin.awaiting_ack_timer = nil; end if origin.delayed_ack_timer then timer.stop(origin.delayed_ack_timer) origin.delayed_ack_timer = nil; end -- Remove handled stanzas from outgoing_stanza_queue local h = tonumber(stanza.attr.h); if not h then origin:close{ condition = "invalid-xml"; text = "Missing or invalid 'h' attribute"; }; return; end local queue = origin.outgoing_stanza_queue; local handled_stanza_count = h-queue:count_acked(); local acked, err = ack_errors.coerce(queue:ack(h)); -- luacheck: ignore 211/acked if err then origin.log("warn", "The client says it handled %d new stanzas, but we sent %d :)", handled_stanza_count, queue:count_unacked()); origin.log("debug", "Client h: %d, our h: %d", tonumber(stanza.attr.h), queue:count_acked()); for i, item in queue._queue:items() do origin.log("debug", "Q item %d: %s", i, item); end origin:close(err); return; end tx_acked_stanzas:sample(handled_stanza_count); origin.log("debug", "#queue = %d (acked: %d)", queue:count_unacked(), handled_stanza_count); request_ack_now_if_needed(origin, false, "handle_a", nil) return true; end module:hook_tag(xmlns_sm2, "a", handle_a); module:hook_tag(xmlns_sm3, "a", handle_a); local function handle_unacked_stanzas(session) local queue = session.outgoing_stanza_queue; local unacked = queue:count_unacked() if unacked > 0 then tx_dropped_stanzas:sample(unacked); session.smacks = false; -- Disable queueing session.outgoing_stanza_queue = nil; for stanza in queue._queue:consume() do if not module:fire_event("delivery/failure", { session = session, stanza = stanza }) then if stanza.attr.type ~= "error" and stanza.attr.from ~= session.full_jid then local reply = st.error_reply(stanza, "cancel", "recipient-unavailable"); module:send(reply); end end end end end -- don't send delivery errors for messages which will be delivered by mam later on -- check if stanza was archived --> this will allow us to send back errors for stanzas not archived -- because the user configured the server to do so ("no-archive"-setting for one special contact for example) module:hook("delivery/failure", function(event) local session, stanza = event.session, event.stanza; -- Only deal with authenticated (c2s) sessions if session.username then if stanza.name == "message" and stanza.attr.xmlns == nil and ( stanza.attr.type == "chat" or ( stanza.attr.type or "normal" ) == "normal" ) then -- don't store messages in offline store if they are mam results local mam_result = stanza:get_child("result", xmlns_mam2); if mam_result ~= nil then return true; -- stanza already "handled", don't send an error and don't add it to offline storage end -- do nothing here for normal messages and don't send out "message delivery errors", -- because messages are already in MAM at this point (no need to frighten users) local stanza_id = stanza:get_child_with_attr("stanza-id", "urn:xmpp:sid:0", "by", jid.bare(session.full_jid)); stanza_id = stanza_id and stanza_id.attr.id; if session.mam_requested and stanza_id ~= nil then session.log("debug", "mod_smacks delivery/failure returning true for mam-handled stanza: mam-archive-id=%s", tostring(stanza_id)); return true; -- stanza handled, don't send an error end -- store message in offline store, if this client does not use mam *and* was the last client online local sessions = local_sessions[session.username] and local_sessions[session.username].sessions or nil; if sessions and next(sessions) == session.resource and next(sessions, session.resource) == nil then local ok = module:fire_event("message/offline/handle", { origin = session, username = session.username, stanza = stanza }); session.log("debug", "mod_smacks delivery/failure returning %s for offline-handled stanza", tostring(ok)); return ok; -- if stanza was handled, don't send an error end end end end); module:hook("pre-resource-unbind", function (event) local session = event.session; if not session.smacks then return end if not session.resumption_token then local queue = session.outgoing_stanza_queue; if queue:count_unacked() > 0 then session.log("debug", "Destroying session with %d unacked stanzas", queue:count_unacked()); handle_unacked_stanzas(session); end return end if session.hibernating then return end session.hibernating = os_time(); if session.hibernating_watchdog then session.log("debug", "Session already has a sleeping watchdog, replacing it"); session.hibernating_watchdog:cancel(); end session.hibernating_watchdog = watchdog.new(resume_timeout, function(this_dog) if this_dog ~= session.hibernating_watchdog then -- This really shouldn't happen? session.log("debug", "Releasing a stray watchdog"); return end session.log("debug", "mod_smacks hibernation timeout reached..."); if session.destroyed then session.log("debug", "The session has already been destroyed"); return elseif not session.resumption_token then -- This should normally not happen, the watchdog should be canceled from session:close() session.log("debug", "The session has already been resumed or replaced"); return end session.log("debug", "Destroying session for hibernating too long"); session_registry[jid.join(session.username, session.host, session.resumption_token)] = nil; old_session_registry:set(session.username, session.resumption_token, { h = session.handled_stanza_count; t = os.time() }); session.resumption_token = nil; session.resending_unacked = true; -- stop outgoing_stanza_filter from re-queueing anything anymore sessionmanager.destroy_session(session, "Hibernating too long"); sessions_expired(1); end); if session.conn then local conn = session.conn; c2s_sessions[conn] = nil; session.conn = nil; conn:close(); end session.log("debug", "Session going into hibernation (not being destroyed)") module:fire_event("smacks-hibernation-start", { origin = session; queue = session.outgoing_stanza_queue:table() }); return true; -- Postpone destruction for now end); local function handle_s2s_destroyed(event) local session = event.session; local queue = session.outgoing_stanza_queue; if queue and queue:count_unacked() > 0 then session.log("warn", "Destroying session with %d unacked stanzas", queue:count_unacked()); if s2s_resend then for stanza in queue:consume() do module:send(stanza); end session.outgoing_stanza_queue = nil; else handle_unacked_stanzas(session); end end end module:hook("s2sout-destroyed", handle_s2s_destroyed); module:hook("s2sin-destroyed", handle_s2s_destroyed); local function get_session_id(session) return session.id or (tostring(session):match("[a-f0-9]+$")); end function handle_resume(session, stanza, xmlns_sm) if session.full_jid then session.log("warn", "Tried to resume after resource binding"); session.send(st.stanza("failed", { xmlns = xmlns_sm }) :tag("unexpected-request", { xmlns = xmlns_errors }) ); return true; end local id = stanza.attr.previd; local original_session = session_registry[jid.join(session.username, session.host, id)]; if not original_session then local old_session = old_session_registry:get(session.username, id); if old_session then session.log("debug", "Tried to resume old expired session with id %s", id); session.send(st.stanza("failed", { xmlns = xmlns_sm, h = format_h(old_session.h) }) :tag("item-not-found", { xmlns = xmlns_errors }) ); old_session_registry:set(session.username, id, nil); resumption_expired(1); else session.log("debug", "Tried to resume non-existent session with id %s", id); session.send(st.stanza("failed", { xmlns = xmlns_sm }) :tag("item-not-found", { xmlns = xmlns_errors }) ); end; else if original_session.hibernating_watchdog then original_session.log("debug", "Letting the watchdog go"); original_session.hibernating_watchdog:cancel(); original_session.hibernating_watchdog = nil; elseif session.hibernating then original_session.log("error", "Hibernating session has no watchdog!") end -- zero age = was not hibernating yet local age = 0; if original_session.hibernating then local now = os_time(); age = now - original_session.hibernating; end session.log("debug", "mod_smacks resuming existing session %s...", get_session_id(original_session)); original_session.log("debug", "mod_smacks session resumed from %s...", get_session_id(session)); -- TODO: All this should move to sessionmanager (e.g. session:replace(new_session)) if original_session.conn then original_session.log("debug", "mod_smacks closing an old connection for this session"); local conn = original_session.conn; c2s_sessions[conn] = nil; conn:close(); end local migrated_session_log = session.log; original_session.ip = session.ip; original_session.conn = session.conn; original_session.rawsend = session.rawsend; original_session.rawsend.session = original_session; original_session.rawsend.conn = original_session.conn; original_session.send = session.send; original_session.send.session = original_session; original_session.close = session.close; original_session.filter = session.filter; original_session.filter.session = original_session; original_session.filters = session.filters; original_session.send.filter = original_session.filter; original_session.stream = session.stream; original_session.secure = session.secure; original_session.hibernating = nil; original_session.resumption_counter = (original_session.resumption_counter or 0) + 1; session.log = original_session.log; session.type = original_session.type; wrap_session(original_session, true); -- Inform xmppstream of the new session (passed to its callbacks) original_session.stream:set_session(original_session); -- Similar for connlisteners c2s_sessions[session.conn] = original_session; local queue = original_session.outgoing_stanza_queue; local h = tonumber(stanza.attr.h); original_session.log("debug", "Pre-resumption #queue = %d", queue:count_unacked()) local acked, err = ack_errors.coerce(queue:ack(h)); -- luacheck: ignore 211/acked if not err and not queue:resumable() then err = ack_errors.new("overflow"); end if err or not queue:resumable() then original_session.send(st.stanza("failed", { xmlns = xmlns_sm; h = format_h(original_session.handled_stanza_count); previd = id })); original_session:close(err); return false; end original_session.send(st.stanza("resumed", { xmlns = xmlns_sm, h = format_h(original_session.handled_stanza_count), previd = id })); -- Ok, we need to re-send any stanzas that the client didn't see -- ...they are what is now left in the outgoing stanza queue -- We have to use the send of "session" because we don't want to add our resent stanzas -- to the outgoing queue again session.log("debug", "resending all unacked stanzas that are still queued after resume, #queue = %d", queue:count_unacked()); -- FIXME Which session is it that the queue filter sees? session.resending_unacked = true; original_session.resending_unacked = true; for _, queued_stanza in queue:resume() do session.send(queued_stanza); end session.resending_unacked = nil; original_session.resending_unacked = nil; session.log("debug", "all stanzas resent, now disabling send() in this migrated session, #queue = %d", queue:count_unacked()); function session.send(stanza) -- luacheck: ignore 432 migrated_session_log("error", "Tried to send stanza on old session migrated by smacks resume (maybe there is a bug?): %s", tostring(stanza)); return false; end module:fire_event("smacks-hibernation-end", {origin = session, resumed = original_session, queue = queue:table()}); original_session.awaiting_ack = nil; -- Don't wait for acks from before the resumption request_ack_now_if_needed(original_session, true, "handle_resume", nil); resumption_age:sample(age); end return true; end module:hook_tag(xmlns_sm2, "resume", function (session, stanza) return handle_resume(session, stanza, xmlns_sm2); end); module:hook_tag(xmlns_sm3, "resume", function (session, stanza) return handle_resume(session, stanza, xmlns_sm3); end); -- Events when it's sensible to request an ack -- Could experiment with forcing (ignoring max_unacked) , but when and why? local request_ack_events = { ["csi-client-active"] = true; ["csi-flushing"] = false; ["c2s-pre-ondrain"] = false; ["s2s-pre-ondrain"] = false; }; for event_name, force in pairs(request_ack_events) do module:hook(event_name, function(event) local session = event.session or event.origin; request_ack_now_if_needed(session, force, event_name); end); end local function handle_read_timeout(event) local session = event.session; if session.smacks then if session.awaiting_ack then if session.awaiting_ack_timer then timer.stop(session.awaiting_ack_timer); session.awaiting_ack_timer = nil; end if session.delayed_ack_timer then timer.stop(session.delayed_ack_timer); session.delayed_ack_timer = nil; end return false; -- Kick the session end request_ack_now_if_needed(session, true, "read timeout"); return true; end end module:hook("s2s-read-timeout", handle_read_timeout); module:hook("c2s-read-timeout", handle_read_timeout); module:hook_global("server-stopping", function(event) if not local_sessions then -- not a VirtualHost, no user sessions return end local reason = event.reason; -- Close smacks-enabled sessions ourselves instead of letting mod_c2s close -- it, which invalidates the smacks session. This allows preserving the -- counter value, so it can be communicated to the client when it tries to -- resume the lost session after a restart. for _, user in pairs(local_sessions) do for _, session in pairs(user.sessions) do if session.resumption_token then if old_session_registry:set(session.username, session.resumption_token, { h = session.handled_stanza_count; t = os.time() }) then session.resumption_token = nil; -- Deal with unacked stanzas if session.outgoing_stanza_queue then handle_unacked_stanzas(session); end if session.conn then session.conn:close() session.conn = nil; -- Now when mod_c2s gets here, it will immediately destroy the -- session since it is unconnected. end -- And make sure nobody tries to send anything session:close{ condition = "system-shutdown", text = reason }; end end end end end, -90); prosody-0.12.4/plugins/PaxHeaders/mod_stanza_debug.lua0000644000000000000000000000012014475665104020055 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.676765109 prosody-0.12.4/plugins/mod_stanza_debug.lua0000644000175000017500000000115314475665104022262 0ustar00prosodyprosody00000000000000module:set_global(); local filters = require "util.filters"; local function log_send(t, session) if t and t ~= "" and t ~= " " then session.log("debug", "SEND: %s", t); end return t; end local function log_recv(t, session) if t and t ~= "" and t ~= " " then session.log("debug", "RECV: %s", t); end return t; end local function init_raw_logging(session) filters.add_filter(session, "stanzas/in", log_recv, -10000); filters.add_filter(session, "stanzas/out", log_send, 10000); end filters.add_filter_hook(init_raw_logging); function module.unload() filters.remove_filter_hook(init_raw_logging); end prosody-0.12.4/plugins/PaxHeaders/mod_storage_internal.lua0000644000000000000000000000012014475665104020747 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.676765109 prosody-0.12.4/plugins/mod_storage_internal.lua0000644000175000017500000002256214475665104023163 0ustar00prosodyprosody00000000000000local cache = require "util.cache"; local datamanager = require "core.storagemanager".olddm; local array = require "util.array"; local datetime = require "util.datetime"; local st = require "util.stanza"; local now = require "util.time".now; local id = require "util.id".medium; local jid_join = require "util.jid".join; local set = require "util.set"; local host = module.host; local archive_item_limit = module:get_option_number("storage_archive_item_limit", 10000); local archive_item_count_cache = cache.new(module:get_option("storage_archive_item_limit_cache_size", 1000)); local driver = {}; function driver:open(store, typ) local mt = self[typ or "keyval"] if not mt then return nil, "unsupported-store"; end return setmetatable({ store = store, type = typ }, mt); end function driver:stores(username) -- luacheck: ignore 212/self return datamanager.stores(username, host); end function driver:purge(user) -- luacheck: ignore 212/self return datamanager.purge(user, host); end local keyval = { }; driver.keyval = { __index = keyval }; function keyval:get(user) return datamanager.load(user, host, self.store); end function keyval:set(user, data) return datamanager.store(user, host, self.store, data); end function keyval:users() return datamanager.users(host, self.store, self.type); end local archive = {}; driver.archive = { __index = archive }; archive.caps = { total = true; quota = archive_item_limit; truncate = true; full_id_range = true; ids = true; }; function archive:append(username, key, value, when, with) when = when or now(); if not st.is_stanza(value) then return nil, "unsupported-datatype"; end value = st.preserialize(st.clone(value)); value.when = when; value.with = with; value.attr.stamp = datetime.datetime(when); local cache_key = jid_join(username, host, self.store); local item_count = archive_item_count_cache:get(cache_key); if key then local items, err = datamanager.list_load(username, host, self.store); if not items and err then return items, err; end -- Check the quota item_count = items and #items or 0; archive_item_count_cache:set(cache_key, item_count); if item_count >= archive_item_limit then module:log("debug", "%s reached or over quota, not adding to store", username); return nil, "quota-limit"; end if items then -- Filter out any item with the same key as the one being added items = array(items); items:filter(function (item) return item.key ~= key; end); value.key = key; items:push(value); local ok, err = datamanager.list_store(username, host, self.store, items); if not ok then return ok, err; end archive_item_count_cache:set(cache_key, #items); return key; end else if not item_count then -- Item count not cached? -- We need to load the list to get the number of items currently stored local items, err = datamanager.list_load(username, host, self.store); if not items and err then return items, err; end item_count = items and #items or 0; archive_item_count_cache:set(cache_key, item_count); end if item_count >= archive_item_limit then module:log("debug", "%s reached or over quota, not adding to store", username); return nil, "quota-limit"; end key = id(); end module:log("debug", "%s has %d items out of %d limit in store %s", username, item_count, archive_item_limit, self.store); value.key = key; local ok, err = datamanager.list_append(username, host, self.store, value); if not ok then return ok, err; end archive_item_count_cache:set(cache_key, item_count+1); return key; end function archive:find(username, query) local items, err = datamanager.list_load(username, host, self.store); if not items then if err then return items, err; elseif query then if query.before or query.after then return nil, "item-not-found"; end if query.total then return function () end, 0; end end return function () end; end local count = nil; local i, last_key = 0; if query then items = array(items); if query.key then items:filter(function (item) return item.key == query.key; end); end if query.ids then local ids = set.new(query.ids); items:filter(function (item) return ids:contains(item.key); end); end if query.with then items:filter(function (item) return item.with == query.with; end); end if query.start then items:filter(function (item) local when = item.when or datetime.parse(item.attr.stamp); return when >= query.start; end); end if query["end"] then items:filter(function (item) local when = item.when or datetime.parse(item.attr.stamp); return when <= query["end"]; end); end if query.total then count = #items; end if query.reverse then items:reverse(); if query.before then local found = false; for j = 1, #items do if (items[j].key or tostring(j)) == query.before then found = true; i = j; break; end end if not found then return nil, "item-not-found"; end end last_key = query.after; elseif query.after then local found = false; for j = 1, #items do if (items[j].key or tostring(j)) == query.after then found = true; i = j; break; end end if not found then return nil, "item-not-found"; end last_key = query.before; elseif query.before then last_key = query.before; end if query.limit and #items - i > query.limit then items[i+query.limit+1] = nil; end end return function () i = i + 1; local item = items[i]; if not item or (last_key and item.key == last_key) then return; end local key = item.key or tostring(i); local when = item.when or datetime.parse(item.attr.stamp); local with = item.with; item.key, item.when, item.with = nil, nil, nil; item.attr.stamp = nil; -- COMPAT Stored data may still contain legacy XEP-0091 timestamp item.attr.stamp_legacy = nil; item = st.deserialize(item); return key, item, when, with; end, count; end function archive:get(username, wanted_key) local iter, err = self:find(username, { key = wanted_key }) if not iter then return iter, err; end for key, stanza, when, with in iter do if key == wanted_key then return stanza, when, with; end end return nil, "item-not-found"; end function archive:set(username, key, new_value, new_when, new_with) local items, err = datamanager.list_load(username, host, self.store); if not items then if err then return items, err; else return nil, "item-not-found"; end end for i = 1, #items do local old_item = items[i]; if old_item.key == key then local item = st.preserialize(st.clone(new_value)); local when = new_when or old_item.when or datetime.parse(old_item.attr.stamp); item.key = key; item.when = when; item.with = new_with or old_item.with; item.attr.stamp = datetime.datetime(when); items[i] = item; return datamanager.list_store(username, host, self.store, items); end end return nil, "item-not-found"; end function archive:dates(username) local items, err = datamanager.list_load(username, host, self.store); if not items then return items, err; end return array(items):pluck("when"):map(datetime.date):unique(); end function archive:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end local counts = {}; local earliest = {}; local latest = {}; local body = {}; for _, stanza, when, with in iter do counts[with] = (counts[with] or 0) + 1; if earliest[with] == nil then earliest[with] = when; end latest[with] = when; body[with] = stanza:get_child_text("body") or body[with]; end return { counts = counts; earliest = earliest; latest = latest; body = body; }; end function archive:users() return datamanager.users(host, self.store, "list"); end function archive:delete(username, query) local cache_key = jid_join(username, host, self.store); if not query or next(query) == nil then archive_item_count_cache:set(cache_key, nil); return datamanager.list_store(username, host, self.store, nil); end local items, err = datamanager.list_load(username, host, self.store); if not items then if err then return items, err; end archive_item_count_cache:set(cache_key, 0); -- Store is empty return 0; end items = array(items); local count_before = #items; if query then if query.key then items:filter(function (item) return item.key ~= query.key; end); end if query.with then items:filter(function (item) return item.with ~= query.with; end); end if query.start then items:filter(function (item) return item.when < query.start; end); end if query["end"] then items:filter(function (item) return item.when > query["end"]; end); end if query.truncate and #items > query.truncate then if query.reverse then -- Before: { 1, 2, 3, 4, 5, } -- After: { 1, 2, 3 } for i = #items, query.truncate + 1, -1 do items[i] = nil; end else -- Before: { 1, 2, 3, 4, 5, } -- After: { 3, 4, 5 } local offset = #items - query.truncate; for i = 1, #items do items[i] = items[i+offset]; end end end end local count = count_before - #items; if count == 0 then return 0; -- No changes, skip write end local ok, err = datamanager.list_store(username, host, self.store, items); if not ok then return ok, err; end archive_item_count_cache:set(cache_key, #items); return count; end module:provides("storage", driver); prosody-0.12.4/plugins/PaxHeaders/mod_storage_memory.lua0000644000000000000000000000012014475665104020443 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.676765109 prosody-0.12.4/plugins/mod_storage_memory.lua0000644000175000017500000002000714475665104022647 0ustar00prosodyprosody00000000000000local serialize = require "util.serialization".serialize; local array = require "util.array"; local envload = require "util.envload".envload; local st = require "util.stanza"; local is_stanza = st.is_stanza or function (s) return getmetatable(s) == st.stanza_mt end local new_id = require "util.id".medium; local set = require "util.set"; local auto_purge_enabled = module:get_option_boolean("storage_memory_temporary", false); local auto_purge_stores = module:get_option_set("storage_memory_temporary_stores", {}); local archive_item_limit = module:get_option_number("storage_archive_item_limit", 1000); local memory = setmetatable({}, { __index = function(t, k) local store = module:shared(k) t[k] = store; return store; end }); local function NULL() return nil end local function _purge_store(self, username) self.store[username or NULL] = nil; return true; end local function _users(self) return next, self.store, nil; end local keyval_store = {}; keyval_store.__index = keyval_store; function keyval_store:get(username) return (self.store[username or NULL] or NULL)(); end function keyval_store:set(username, data) if data ~= nil then data = envload("return "..serialize(data), "=(data)", {}); end self.store[username or NULL] = data; return true; end keyval_store.purge = _purge_store; keyval_store.users = _users; local archive_store = {}; archive_store.__index = archive_store; archive_store.users = _users; archive_store.caps = { total = true; quota = archive_item_limit; truncate = true; full_id_range = true; ids = true; }; function archive_store:append(username, key, value, when, with) if is_stanza(value) then value = st.preserialize(value); value = envload("return xml"..serialize(value), "=(stanza)", { xml = st.deserialize }) else value = envload("return "..serialize(value), "=(data)", {}); end local a = self.store[username or NULL]; if not a then a = {}; self.store[username or NULL] = a; end local v = { key = key, when = when, with = with, value = value }; if not key then key = new_id(); v.key = key; end if a[key] then table.remove(a, a[key]); elseif #a >= archive_item_limit then return nil, "quota-limit"; end local i = #a+1; a[i] = v; a[key] = i; return key; end function archive_store:find(username, query) local items = self.store[username or NULL]; if not items then if query then if query.before or query.after then return nil, "item-not-found"; end if query.total then return function () end, 0; end end return function () end; end local count = nil; local i, last_key = 0; if query then items = array():append(items); if query.key then items:filter(function (item) return item.key == query.key; end); end if query.ids then local ids = set.new(query.ids); items:filter(function (item) return ids:contains(item.key); end); end if query.with then items:filter(function (item) return item.with == query.with; end); end if query.start then items:filter(function (item) return item.when >= query.start; end); end if query["end"] then items:filter(function (item) return item.when <= query["end"]; end); end if query.total then count = #items; end if query.reverse then items:reverse(); if query.before then local found = false; for j = 1, #items do if (items[j].key or tostring(j)) == query.before then found = true; i = j; break; end end if not found then return nil, "item-not-found"; end end last_key = query.after; elseif query.after then local found = false; for j = 1, #items do if (items[j].key or tostring(j)) == query.after then found = true; i = j; break; end end if not found then return nil, "item-not-found"; end last_key = query.before; elseif query.before then last_key = query.before; end if query.limit and #items - i > query.limit then items[i+query.limit+1] = nil; end end return function () i = i + 1; local item = items[i]; if not item or (last_key and item.key == last_key) then return; end return item.key, item.value(), item.when, item.with; end, count; end function archive_store:get(username, wanted_key) local items = self.store[username or NULL]; if not items then return nil, "item-not-found"; end local i = items[wanted_key]; if not i then return nil, "item-not-found"; end local item = items[i]; return item.value(), item.when, item.with; end function archive_store:set(username, wanted_key, new_value, new_when, new_with) local items = self.store[username or NULL]; if not items then return nil, "item-not-found"; end local i = items[wanted_key]; if not i then return nil, "item-not-found"; end local item = items[i]; if is_stanza(new_value) then new_value = st.preserialize(new_value); item.value = envload("return xml"..serialize(new_value), "=(stanza)", { xml = st.deserialize }) else item.value = envload("return "..serialize(new_value), "=(data)", {}); end if new_when then item.when = new_when; end if new_with then item.with = new_when; end return true; end function archive_store:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end local counts = {}; local earliest = {}; local latest = {}; for _, _, when, with in iter do counts[with] = (counts[with] or 0) + 1; if earliest[with] == nil then earliest[with] = when; end latest[with] = when; end return { counts = counts; earliest = earliest; latest = latest; }; end function archive_store:delete(username, query) if not query or next(query) == nil then self.store[username or NULL] = nil; return true; end local items = self.store[username or NULL]; if not items then -- Store is empty return 0; end items = array(items); local count_before = #items; if query then if query.key then items:filter(function (item) return item.key ~= query.key; end); end if query.with then items:filter(function (item) return item.with ~= query.with; end); end if query.start then items:filter(function (item) return item.when < query.start; end); end if query["end"] then items:filter(function (item) return item.when > query["end"]; end); end if query.truncate and #items > query.truncate then if query.reverse then -- Before: { 1, 2, 3, 4, 5, } -- After: { 1, 2, 3 } for i = #items, query.truncate + 1, -1 do items[i] = nil; end else -- Before: { 1, 2, 3, 4, 5, } -- After: { 3, 4, 5 } local offset = #items - query.truncate; for i = 1, #items do items[i] = items[i+offset]; end end end end local count = count_before - #items; if count == 0 then return 0; -- No changes, skip write end setmetatable(items, nil); do -- re-index by key for k in pairs(items) do if type(k) == "string" then items[k] = nil; end end for i = 1, #items do items[ items[i].key ] = i; end end return count; end archive_store.purge = _purge_store; local stores = { keyval = keyval_store; archive = archive_store; } local driver = {}; function driver:open(store, typ) -- luacheck: ignore 212/self local store_mt = stores[typ or "keyval"]; if store_mt then return setmetatable({ store = memory[store] }, store_mt); end return nil, "unsupported-store"; end function driver:purge(user) -- luacheck: ignore 212/self for _, store in pairs(memory) do store[user] = nil; end end if auto_purge_enabled then module:hook("resource-unbind", function (event) local user_bare_jid = event.session.username.."@"..event.session.host; if not prosody.bare_sessions[user_bare_jid] then -- User went offline module:log("debug", "Clearing store for offline user %s", user_bare_jid); local f, s, v; if auto_purge_stores:empty() then f, s, v = pairs(memory); else f, s, v = auto_purge_stores:items(); end for store_name in f, s, v do if memory[store_name] then memory[store_name][event.session.username] = nil; end end end end); end module:provides("storage", driver); prosody-0.12.4/plugins/PaxHeaders/mod_storage_none.lua0000644000000000000000000000012014475665104020072 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.676765109 prosody-0.12.4/plugins/mod_storage_none.lua0000644000175000017500000000127314475665104022302 0ustar00prosodyprosody00000000000000-- luacheck: ignore 212 local driver = {}; local driver_mt = { __index = driver }; function driver:open(store, typ) if typ and typ ~= "keyval" and typ ~= "archive" then return nil, "unsupported-store"; end return setmetatable({ store = store, type = typ }, driver_mt); end function driver:get(user) return {}; end function driver:set(user, data) return nil, "Storage disabled"; end function driver:stores(username) return { "roster" }; end function driver:purge(user) return true; end function driver:append() return nil, "Storage disabled"; end function driver:find() return function () end, 0; end function driver:delete() return true; end module:provides("storage", driver); prosody-0.12.4/plugins/PaxHeaders/mod_storage_sql.lua0000644000000000000000000000011714475665104017740 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68076507 prosody-0.12.4/plugins/mod_storage_sql.lua0000644000175000017500000006614614475665104022154 0ustar00prosodyprosody00000000000000 -- luacheck: ignore 212/self local cache = require "util.cache"; local json = require "util.json"; local sql = require "util.sql"; local xml_parse = require "util.xml".parse; local uuid = require "util.uuid"; local resolve_relative_path = require "util.paths".resolve_relative_path; local jid_join = require "util.jid".join; local is_stanza = require"util.stanza".is_stanza; local t_concat = table.concat; local noop = function() end local unpack = table.unpack or unpack; -- luacheck: ignore 113 local function iterator(result) return function(result_) local row = result_(); if row ~= nil then return unpack(row); end end, result, nil; end local default_params = { driver = "SQLite3" }; local engine; local function serialize(value) local t = type(value); if t == "string" or t == "boolean" or t == "number" then return t, tostring(value); elseif is_stanza(value) then return "xml", tostring(value); elseif t == "table" then local encoded,err = json.encode(value); if encoded then return "json", encoded; end return nil, err; end return nil, "Unhandled value type: "..t; end local function deserialize(t, value) if t == "string" then return value; elseif t == "boolean" then if value == "true" then return true; elseif value == "false" then return false; end return nil, "invalid-boolean"; elseif t == "number" then value = tonumber(value); if value then return value; end return nil, "invalid-number"; elseif t == "json" then return json.decode(value); elseif t == "xml" then return xml_parse(value); end return nil, "Unhandled value type: "..t; end local host = module.host; local user, store; local function keyval_store_get() local haveany; local result = {}; local select_sql = [[ SELECT "key","type","value" FROM "prosody" WHERE "host"=? AND "user"=? AND "store"=?; ]] for row in engine:select(select_sql, host, user or "", store) do haveany = true; local k = row[1]; local v, e = deserialize(row[2], row[3]); assert(v ~= nil, e); if k and v then if k ~= "" then result[k] = v; elseif type(v) == "table" then for a,b in pairs(v) do result[a] = b; end end end end if haveany then return result; end end local function keyval_store_set(data) local delete_sql = [[ DELETE FROM "prosody" WHERE "host"=? AND "user"=? AND "store"=? ]]; engine:delete(delete_sql, host, user or "", store); local insert_sql = [[ INSERT INTO "prosody" ("host","user","store","key","type","value") VALUES (?,?,?,?,?,?); ]] if data and next(data) ~= nil then local extradata = {}; for key, value in pairs(data) do if type(key) == "string" and key ~= "" then local t, encoded_value = assert(serialize(value)); engine:insert(insert_sql, host, user or "", store, key, t, encoded_value); else extradata[key] = value; end end if next(extradata) ~= nil then local t, encoded_extradata = assert(serialize(extradata)); engine:insert(insert_sql, host, user or "", store, "", t, encoded_extradata); end end return true; end --- Key/value store API (default store type) local keyval_store = {}; keyval_store.__index = keyval_store; function keyval_store:get(username) user, store = username, self.store; local ok, result = engine:transaction(keyval_store_get); if not ok then module:log("error", "Unable to read from database %s store for %s: %s", store, username or "", result); return nil, result; end return result; end function keyval_store:set(username, data) user,store = username,self.store; return engine:transaction(function() return keyval_store_set(data); end); end function keyval_store:users() local ok, result = engine:transaction(function() local select_sql = [[ SELECT DISTINCT "user" FROM "prosody" WHERE "host"=? AND "store"=?; ]]; return engine:select(select_sql, host, self.store); end); if not ok then error(result); end return iterator(result); end --- Archive store API local archive_item_limit = module:get_option_number("storage_archive_item_limit"); local archive_item_count_cache = cache.new(module:get_option("storage_archive_item_limit_cache_size", 1000)); local item_count_cache_hit = module:measure("item_count_cache_hit", "rate"); local item_count_cache_miss = module:measure("item_count_cache_miss", "rate") -- luacheck: ignore 512 431/user 431/store 431/err local map_store = {}; map_store.__index = map_store; map_store.remove = {}; function map_store:get(username, key) local ok, result = engine:transaction(function() local query = [[ SELECT "type", "value" FROM "prosody" WHERE "host"=? AND "user"=? AND "store"=? AND "key"=? LIMIT 1 ]]; local data, err; if type(key) == "string" and key ~= "" then for row in engine:select(query, host, username or "", self.store, key) do data, err = deserialize(row[1], row[2]); assert(data ~= nil, err); end return data; else for row in engine:select(query, host, username or "", self.store, "") do data, err = deserialize(row[1], row[2]); assert(data ~= nil, err); end return data and data[key] or nil; end end); if not ok then return nil, result; end return result; end function map_store:set(username, key, data) if data == nil then data = self.remove; end return self:set_keys(username, { [key] = data }); end function map_store:set_keys(username, keydatas) local ok, result = engine:transaction(function() local delete_sql = [[ DELETE FROM "prosody" WHERE "host"=? AND "user"=? AND "store"=? AND "key"=?; ]]; local insert_sql = [[ INSERT INTO "prosody" ("host","user","store","key","type","value") VALUES (?,?,?,?,?,?); ]]; local select_extradata_sql = [[ SELECT "type", "value" FROM "prosody" WHERE "host"=? AND "user"=? AND "store"=? AND "key"=? LIMIT 1; ]]; for key, data in pairs(keydatas) do if type(key) == "string" and key ~= "" then engine:delete(delete_sql, host, username or "", self.store, key); if data ~= self.remove then local t, value = assert(serialize(data)); engine:insert(insert_sql, host, username or "", self.store, key, t, value); end else local extradata, err = {}; for row in engine:select(select_extradata_sql, host, username or "", self.store, "") do extradata, err = deserialize(row[1], row[2]); assert(extradata ~= nil, err); end engine:delete(delete_sql, host, username or "", self.store, ""); extradata[key] = data; local t, value = assert(serialize(extradata)); engine:insert(insert_sql, host, username or "", self.store, "", t, value); end end return true; end); if not ok then return nil, result; end return result; end function map_store:get_all(key) if type(key) ~= "string" or key == "" then return nil, "get_all only supports non-empty string keys"; end local ok, result = engine:transaction(function() local query = [[ SELECT "user", "type", "value" FROM "prosody" WHERE "host"=? AND "store"=? AND "key"=? ]]; local data; for row in engine:select(query, host, self.store, key) do local key_data, err = deserialize(row[2], row[3]); assert(key_data ~= nil, err); if data == nil then data = {}; end data[row[1]] = key_data; end return data; end); if not ok then return nil, result; end return result; end function map_store:delete_all(key) if type(key) ~= "string" or key == "" then return nil, "delete_all only supports non-empty string keys"; end local ok, result = engine:transaction(function() local delete_sql = [[ DELETE FROM "prosody" WHERE "host"=? AND "store"=? AND "key"=?; ]]; engine:delete(delete_sql, host, self.store, key); return true; end); if not ok then return nil, result; end return result; end local archive_store = {} archive_store.caps = { total = true; quota = archive_item_limit; truncate = true; full_id_range = true; ids = true; wildcard_delete = true; }; archive_store.__index = archive_store function archive_store:append(username, key, value, when, with) local user,store = username,self.store; local cache_key = jid_join(username, host, store); local item_count = archive_item_count_cache:get(cache_key); if not item_count then item_count_cache_miss(); local ok, ret = engine:transaction(function() local count_sql = [[ SELECT COUNT(*) FROM "prosodyarchive" WHERE "host"=? AND "user"=? AND "store"=?; ]]; local result = engine:select(count_sql, host, user, store); if result then for row in result do item_count = row[1]; end end end); if not ok or not item_count then module:log("error", "Failed while checking quota for %s: %s", username, ret); return nil, "Failure while checking quota"; end archive_item_count_cache:set(cache_key, item_count); else item_count_cache_hit(); end if archive_item_limit then module:log("debug", "%s has %d items out of %d limit", username, item_count, archive_item_limit); if item_count >= archive_item_limit then return nil, "quota-limit"; end end when = when or os.time(); with = with or ""; local ok, ret = engine:transaction(function() local delete_sql = [[ DELETE FROM "prosodyarchive" WHERE "host"=? AND "user"=? AND "store"=? AND "key"=?; ]]; local insert_sql = [[ INSERT INTO "prosodyarchive" ("host", "user", "store", "when", "with", "key", "type", "value") VALUES (?,?,?,?,?,?,?,?); ]]; if key then local result = engine:delete(delete_sql, host, user or "", store, key); if result then item_count = item_count - result:affected(); end else key = uuid.generate(); end local t, encoded_value = assert(serialize(value)); engine:insert(insert_sql, host, user or "", store, when, with, key, t, encoded_value); archive_item_count_cache:set(cache_key, item_count+1); return key; end); if not ok then return ok, ret; end return ret; -- the key end -- Helpers for building the WHERE clause local function archive_where(query, args, where) -- Time range, inclusive if query.start then args[#args+1] = query.start where[#where+1] = "\"when\" >= ?" end if query["end"] then args[#args+1] = query["end"]; if query.start then where[#where] = "\"when\" BETWEEN ? AND ?" -- is this inclusive? else where[#where+1] = "\"when\" <= ?" end end -- Related name if query.with then where[#where+1] = "\"with\" = ?"; args[#args+1] = query.with end -- Unique id if query.key then where[#where+1] = "\"key\" = ?"; args[#args+1] = query.key end -- Set of ids if query.ids then local nids, nargs = #query.ids, #args; -- COMPAT Lua 5.1: No separator argument to string.rep where[#where + 1] = "\"key\" IN (" .. string.rep("?,", nids):sub(1,-2) .. ")"; for i, id in ipairs(query.ids) do args[nargs+i] = id; end end end local function archive_where_id_range(query, args, where) -- Before or after specific item, exclusive local id_lookup_sql = [[ SELECT "sort_id" FROM "prosodyarchive" WHERE "key" = ? AND "host" = ? AND "user" = ? AND "store" = ? LIMIT 1; ]]; if query.after then -- keys better be unique! local after_id = nil; for row in engine:select(id_lookup_sql, query.after, args[1], args[2], args[3]) do after_id = row[1]; end if not after_id then return nil, "item-not-found"; end where[#where+1] = '"sort_id" > ?'; args[#args+1] = after_id; end if query.before then local before_id = nil; for row in engine:select(id_lookup_sql, query.before, args[1], args[2], args[3]) do before_id = row[1]; end if not before_id then return nil, "item-not-found"; end where[#where+1] = '"sort_id" < ?'; args[#args+1] = before_id; end return true; end function archive_store:find(username, query) query = query or {}; local user,store = username,self.store; local cache_key = jid_join(username, host, self.store); local total = archive_item_count_cache:get(cache_key); (total and item_count_cache_hit or item_count_cache_miss)(); if query.start == nil and query.with == nil and query["end"] == nil and query.key == nil and query.ids == nil then -- the query is for the whole archive, so a cached 'total' should be a -- relatively accurate response if that's all that is requested if total ~= nil and query.limit == 0 then return noop, total; end else -- not usable, so refresh it later if needed total = nil; end local ok, result, err = engine:transaction(function() local sql_query = [[ SELECT "key", "type", "value", "when", "with" FROM "prosodyarchive" WHERE %s ORDER BY "sort_id" %s%s; ]]; local args = { host, user or "", store, }; local where = { "\"host\" = ?", "\"user\" = ?", "\"store\" = ?", }; archive_where(query, args, where); -- Total matching if query.total and not total then local stats = engine:select("SELECT COUNT(*) FROM \"prosodyarchive\" WHERE " .. t_concat(where, " AND "), unpack(args)); if stats then for row in stats do total = row[1]; end end if query.start == nil and query.with == nil and query["end"] == nil and query.key == nil and query.ids == nil then archive_item_count_cache:set(cache_key, total); end if query.limit == 0 then -- Skip the real query return noop, total; end end local ok, err = archive_where_id_range(query, args, where); if not ok then return ok, err; end sql_query = sql_query:format(t_concat(where, " AND "), query.reverse and "DESC" or "ASC", query.limit and " LIMIT " .. query.limit or ""); return engine:select(sql_query, unpack(args)); end); if not ok then return ok, result; end if not result then return nil, err; end return function() local row = result(); if row ~= nil then local value, err = deserialize(row[2], row[3]); assert(value ~= nil, err); return row[1], value, row[4], row[5]; end end, total; end function archive_store:get(username, key) local iter, err = self:find(username, { key = key }) if not iter then return iter, err; end for _, stanza, when, with in iter do return stanza, when, with; end return nil, "item-not-found"; end function archive_store:set(username, key, new_value, new_when, new_with) local user,store = username,self.store; local ok, result = engine:transaction(function () local update_query = [[ UPDATE "prosodyarchive" SET %s WHERE %s ]]; local args = { host, user or "", store, key }; local setf = {}; local where = { "\"host\" = ?", "\"user\" = ?", "\"store\" = ?", "\"key\" = ?"}; if new_value then table.insert(setf, '"type" = ?') table.insert(setf, '"value" = ?') local t, value = serialize(new_value); table.insert(args, 1, t); table.insert(args, 2, value); end if new_when then table.insert(setf, 1, '"when" = ?') table.insert(args, 1, new_when); end if new_with then table.insert(setf, 1, '"with" = ?') table.insert(args, 1, new_with); end update_query = update_query:format(t_concat(setf, ", "), t_concat(where, " AND ")); return engine:update(update_query, unpack(args)); end); if not ok then return ok, result; end return result:affected() == 1; end function archive_store:summary(username, query) query = query or {}; local user,store = username,self.store; local ok, result = engine:transaction(function() local sql_query = [[ SELECT DISTINCT "with", COUNT(*), MIN("when"), MAX("when") FROM "prosodyarchive" WHERE %s GROUP BY "with"; ]]; local args = { host, user or "", store, }; local where = { "\"host\" = ?", "\"user\" = ?", "\"store\" = ?", }; archive_where(query, args, where); archive_where_id_range(query, args, where); if query.limit then args[#args+1] = query.limit; end sql_query = sql_query:format(t_concat(where, " AND ")); return engine:select(sql_query, unpack(args)); end); if not ok then return ok, result end local counts = {}; local earliest, latest = {}, {}; for row in result do local with, count = row[1], row[2]; counts[with] = count; earliest[with] = row[3]; latest[with] = row[4]; end return { counts = counts; earliest = earliest; latest = latest; }; end function archive_store:delete(username, query) query = query or {}; local user,store = username,self.store; local ok, stmt = engine:transaction(function() local sql_query = "DELETE FROM \"prosodyarchive\" WHERE %s;"; local args = { host, user or "", store, }; local where = { "\"host\" = ?", "\"user\" = ?", "\"store\" = ?", }; if user == true then table.remove(args, 2); table.remove(where, 2); end archive_where(query, args, where); local ok, err = archive_where_id_range(query, args, where); if not ok then return ok, err; end if query.truncate == nil then sql_query = sql_query:format(t_concat(where, " AND ")); elseif engine.params.driver == "MySQL" then sql_query = [[ DELETE result FROM prosodyarchive AS result JOIN ( SELECT sort_id FROM prosodyarchive WHERE %s ORDER BY "sort_id" %s LIMIT 18446744073709551615 OFFSET %s ) AS limiter on result.sort_id = limiter.sort_id;]]; sql_query = string.format(sql_query, t_concat(where, " AND "), query.reverse and "ASC" or "DESC", query.truncate); else args[#args+1] = query.truncate; local unlimited = "ALL"; sql_query = [[ DELETE FROM "prosodyarchive" WHERE "sort_id" IN ( SELECT "sort_id" FROM "prosodyarchive" WHERE %s ORDER BY "sort_id" %s LIMIT %s OFFSET ? );]]; if engine.params.driver == "SQLite3" then if engine._have_delete_limit then sql_query = [[ DELETE FROM "prosodyarchive" WHERE %s ORDER BY "sort_id" %s LIMIT %s OFFSET ?; ]]; end unlimited = "-1"; end sql_query = string.format(sql_query, t_concat(where, " AND "), query.reverse and "ASC" or "DESC", unlimited); end return engine:delete(sql_query, unpack(args)); end); if username == true then archive_item_count_cache:clear(); else local cache_key = jid_join(username, host, self.store); archive_item_count_cache:set(cache_key, nil); end return ok and stmt:affected(), stmt; end function archive_store:users() local ok, result = engine:transaction(function() local select_sql = [[ SELECT DISTINCT "user" FROM "prosodyarchive" WHERE "host"=? AND "store"=?; ]]; return engine:select(select_sql, host, self.store); end); if not ok then error(result); end return iterator(result); end local stores = { keyval = keyval_store; map = map_store; archive = archive_store; }; --- Implement storage driver API -- FIXME: Some of these operations need to operate on the archive store(s) too local driver = {}; function driver:open(store, typ) local store_mt = stores[typ or "keyval"]; if store_mt then return setmetatable({ store = store }, store_mt); end return nil, "unsupported-store"; end function driver:stores(username) local query = "SELECT DISTINCT \"store\" FROM \"prosody\" WHERE \"host\"=? AND \"user\"" .. (username == true and "!=?" or "=?"); if username == true or not username then username = ""; end local ok, result = engine:transaction(function() return engine:select(query, host, username); end); if not ok then return ok, result end return iterator(result); end function driver:purge(username) return engine:transaction(function() engine:delete("DELETE FROM \"prosody\" WHERE \"host\"=? AND \"user\"=?", host, username); engine:delete("DELETE FROM \"prosodyarchive\" WHERE \"host\"=? AND \"user\"=?", host, username); end); end --- Initialization local function create_table(engine) -- luacheck: ignore 431/engine local Table, Column, Index = sql.Table, sql.Column, sql.Index; local ProsodyTable = Table { name = "prosody"; Column { name="host", type="TEXT", nullable=false }; Column { name="user", type="TEXT", nullable=false }; Column { name="store", type="TEXT", nullable=false }; Column { name="key", type="TEXT", nullable=false }; Column { name="type", type="TEXT", nullable=false }; Column { name="value", type="MEDIUMTEXT", nullable=false }; Index { name="prosody_index", "host", "user", "store", "key" }; }; engine:transaction(function() ProsodyTable:create(engine); end); local ProsodyArchiveTable = Table { name="prosodyarchive"; Column { name="sort_id", type="INTEGER", primary_key=true, auto_increment=true }; Column { name="host", type="TEXT", nullable=false }; Column { name="user", type="TEXT", nullable=false }; Column { name="store", type="TEXT", nullable=false }; Column { name="key", type="TEXT", nullable=false }; -- item id Column { name="when", type="INTEGER", nullable=false }; -- timestamp Column { name="with", type="TEXT", nullable=false }; -- related id Column { name="type", type="TEXT", nullable=false }; Column { name="value", type="MEDIUMTEXT", nullable=false }; Index { name="prosodyarchive_index", unique = engine.params.driver ~= "MySQL", "host", "user", "store", "key" }; Index { name="prosodyarchive_with_when", "host", "user", "store", "with", "when" }; Index { name="prosodyarchive_when", "host", "user", "store", "when" }; Index { name="prosodyarchive_sort", "host", "user", "store", "sort_id" }; }; engine:transaction(function() ProsodyArchiveTable:create(engine); end); end local function upgrade_table(engine, params, apply_changes) -- luacheck: ignore 431/engine local changes = false; if params.driver == "MySQL" then local success,err = engine:transaction(function() do local result = assert(engine:execute("SHOW COLUMNS FROM \"prosody\" WHERE \"Field\"='value' and \"Type\"='text'")); if result:rowcount() > 0 then changes = true; if apply_changes then module:log("info", "Upgrading database schema (value column size)..."); assert(engine:execute("ALTER TABLE \"prosody\" MODIFY COLUMN \"value\" MEDIUMTEXT")); module:log("info", "Database table automatically upgraded"); end end end do -- Ensure index is not unique (issue #1073) local result = assert(engine:execute([[SHOW INDEX FROM prosodyarchive WHERE key_name='prosodyarchive_index' and non_unique=0]])); if result:rowcount() > 0 then changes = true; if apply_changes then module:log("info", "Upgrading database schema (prosodyarchive_index)..."); assert(engine:execute[[ALTER TABLE "prosodyarchive" DROP INDEX prosodyarchive_index;]]); local new_index = sql.Index { table = "prosodyarchive", name="prosodyarchive_index", "host", "user", "store", "key" }; assert(engine:_create_index(new_index)); module:log("info", "Database table automatically upgraded"); end end end return true; end); if not success then module:log("error", "Failed to check/upgrade database schema (%s), please see " .."https://prosody.im/doc/mysql for help", err or "unknown error"); return false; end -- COMPAT w/pre-0.10: Upgrade table to UTF-8 if not already local check_encoding_query = [[ SELECT "COLUMN_NAME","COLUMN_TYPE","TABLE_NAME" FROM "information_schema"."columns" WHERE "TABLE_NAME" LIKE 'prosody%%' AND "TABLE_SCHEMA" = ? AND ( "CHARACTER_SET_NAME"!=? OR "COLLATION_NAME"!=?); ]]; -- FIXME Is it ok to ignore the return values from this? engine:transaction(function() local result = assert(engine:execute(check_encoding_query, params.database, engine.charset, engine.charset.."_bin")); local n_bad_columns = result:rowcount(); if n_bad_columns > 0 then changes = true; if apply_changes then module:log("warn", "Found %d columns in prosody table requiring encoding change, updating now...", n_bad_columns); local fix_column_query1 = "ALTER TABLE \"%s\" CHANGE \"%s\" \"%s\" BLOB;"; local fix_column_query2 = "ALTER TABLE \"%s\" CHANGE \"%s\" \"%s\" %s CHARACTER SET '%s' COLLATE '%s_bin';"; for row in result:rows() do local column_name, column_type, table_name = unpack(row); module:log("debug", "Fixing column %s in table %s", column_name, table_name); engine:execute(fix_column_query1:format(table_name, column_name, column_name)); engine:execute(fix_column_query2:format(table_name, column_name, column_name, column_type, engine.charset, engine.charset)); end module:log("info", "Database encoding upgrade complete!"); end end end); success,err = engine:transaction(function() return engine:execute(check_encoding_query, params.database, engine.charset, engine.charset.."_bin"); end); if not success then module:log("error", "Failed to check/upgrade database encoding: %s", err or "unknown error"); return false; end end return changes; end local function normalize_database(driver, database) -- luacheck: ignore 431/driver if driver == "SQLite3" and database ~= ":memory:" then return resolve_relative_path(prosody.paths.data or ".", database or "prosody.sqlite"); end return database; end local function normalize_params(params) return { driver = assert(params.driver, "Configuration error: Both the SQL driver and the database need to be specified"); database = assert(normalize_database(params.driver, params.database), "Configuration error: Both the SQL driver and the database need to be specified"); username = params.username; password = params.password; host = params.host; port = params.port; }; end function module.load() local engines = module:shared("/*/sql/connections"); local params = normalize_params(module:get_option("sql", default_params)); local db_uri = sql.db2uri(params); engine = engines[db_uri]; if not engine then module:log("debug", "Creating new engine %s", db_uri); engine = sql:create_engine(params, function (engine) -- luacheck: ignore 431/engine if module:get_option("sql_manage_tables", true) then -- Automatically create table, ignore failure (table probably already exists) -- FIXME: we should check in information_schema, etc. create_table(engine); -- Check whether the table needs upgrading if upgrade_table(engine, params, false) then module:log("error", "Old database format detected. Please run: prosodyctl mod_%s upgrade", module.name); return false, "database upgrade needed"; end end if engine.params.driver == "SQLite3" then for row in engine:select("PRAGMA compile_options") do if row[1] == "ENABLE_UPDATE_DELETE_LIMIT" then engine._have_delete_limit = true; end end end end); engines[sql.db2uri(params)] = engine; end module:provides("storage", driver); end function module.command(arg) local config = require "core.configmanager"; local hi = require "util.human.io"; local command = table.remove(arg, 1); if command == "upgrade" then -- We need to find every unique dburi in the config local uris = {}; for host in pairs(prosody.hosts) do -- luacheck: ignore 431/host local params = normalize_params(config.get(host, "sql") or default_params); uris[sql.db2uri(params)] = params; end print("We will check and upgrade the following databases:\n"); for _, params in pairs(uris) do print("", "["..params.driver.."] "..params.database..(params.host and " on "..params.host or "")); end print(""); print("Ensure you have working backups of the above databases before continuing! "); if not hi.show_yesno("Continue with the database upgrade? [yN]") then print("Ok, no upgrade. But you do have backups, don't you? ...don't you?? :-)"); return; end -- Upgrade each one for _, params in pairs(uris) do print("Checking "..params.database.."..."); engine = sql:create_engine(params); upgrade_table(engine, params, true); end print("All done!"); elseif command then print("Unknown command: "..command); else print("Available commands:"); print("","upgrade - Perform database upgrade"); end end prosody-0.12.4/plugins/PaxHeaders/mod_storage_xep0227.lua0000644000000000000000000000011714475665104020250 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68076507 prosody-0.12.4/plugins/mod_storage_xep0227.lua0000644000175000017500000005373314475665104022462 0ustar00prosodyprosody00000000000000 local ipairs, pairs = ipairs, pairs; local setmetatable = setmetatable; local tostring = tostring; local next, unpack = next, table.unpack or unpack; --luacheck: ignore 113/unpack local os_remove = os.remove; local io_open = io.open; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; local jid_join = require "util.jid".join; local array = require "util.array"; local base64 = require "util.encodings".base64; local dt = require "util.datetime"; local hex = require "util.hex"; local it = require "util.iterators"; local paths = require"util.paths"; local set = require "util.set"; local st = require "util.stanza"; local parse_xml_real = require "util.xml".parse; local lfs = require "lfs"; local function default_get_user_xml(self, user, host) --luacheck: ignore 212/self local jid = jid_join(user, host); local path = paths.join(prosody.paths.data, jid..".xml"); local f, err = io_open(path); if not f then module:log("debug", "Unable to load XML file for <%s>: %s", jid, err); return; end module:log("debug", "Loaded %s", path); local s = f:read("*a"); f:close(); return parse_xml_real(s); end local function default_set_user_xml(self, user, host, xml) --luacheck: ignore 212/self local jid = jid_join(user, host); local path = paths.join(prosody.paths.data, jid..".xml"); local f, err = io_open(path, "w"); if not f then return f, err; end if xml then local s = tostring(xml); f:write(s); f:close(); return true; else f:close(); return os_remove(path); end end local function getUserElement(xml) if xml and xml.name == "server-data" then local host = xml.tags[1]; if host and host.name == "host" then local user = host.tags[1]; if user and user.name == "user" then return user; end end end module:log("warn", "Unable to find user element in %s", xml and xml:top_tag() or "nothing"); end local function createOuterXml(user, host) return st.stanza("server-data", {xmlns='urn:xmpp:pie:0'}) :tag("host", {jid=host}) :tag("user", {name = user}); end local function hex_to_base64(s) return base64.encode(hex.decode(s)); end local function base64_to_hex(s) return hex.encode(base64.decode(s)); end local handlers = {}; -- In order to support custom account properties local extended = "http://prosody.im/protocol/extended-xep0227\1"; local scram_hash_name = module:get_option_string("password_hash", "SHA-1"); local scram_properties = set.new({ "server_key", "stored_key", "iteration_count", "salt" }); handlers.accounts = { get = function(self, user) user = getUserElement(self:_get_user_xml(user, self.host)); local scram_credentials = user and user:get_child_with_attr( "scram-credentials", "urn:xmpp:pie:0#scram", "mechanism", "SCRAM-"..scram_hash_name ); if scram_credentials then return { iteration_count = tonumber(scram_credentials:get_child_text("iter-count")); server_key = base64_to_hex(scram_credentials:get_child_text("server-key")); stored_key = base64_to_hex(scram_credentials:get_child_text("stored-key")); salt = base64.decode(scram_credentials:get_child_text("salt")); }; elseif user and user.attr.password then return { password = user.attr.password }; elseif user then local data = {}; for k, v in pairs(user.attr) do if k:sub(1, #extended) == extended then data[k:sub(#extended+1)] = v; end end return data; end end; set = function(self, user, data) if not data then return self:_set_user_xml(user, self.host, nil); end local xml = self:_get_user_xml(user, self.host); if not xml then xml = createOuterXml(user, self.host); end local usere = getUserElement(xml); local account_properties = set.new(it.to_array(it.keys(data))); -- Include SCRAM credentials if known if account_properties:contains_set(scram_properties) then local scram_el = st.stanza("scram-credentials", { xmlns = "urn:xmpp:pie:0#scram", mechanism = "SCRAM-"..scram_hash_name }) :text_tag("server-key", hex_to_base64(data.server_key)) :text_tag("stored-key", hex_to_base64(data.stored_key)) :text_tag("iter-count", ("%d"):format(data.iteration_count)) :text_tag("salt", base64.encode(data.salt)); usere:add_child(scram_el); account_properties:exclude(scram_properties); end -- Include the password if present if account_properties:contains("password") then usere.attr.password = data.password; account_properties:remove("password"); end -- Preserve remaining properties as namespaced attributes for property in account_properties do usere.attr[extended..property] = data[property]; end return self:_set_user_xml(user, self.host, xml); end; }; handlers.vcard = { get = function(self, user) user = getUserElement(self:_get_user_xml(user, self.host)); if user then local vcard = user:get_child("vCard", 'vcard-temp'); if vcard then return st.preserialize(vcard); end end end; set = function(self, user, data) local xml = self:_get_user_xml(user, self.host); local usere = xml and getUserElement(xml); if usere then usere:remove_children("vCard", "vcard-temp"); if not data or not data.attr then -- No data to set, old one deleted, success return true; end local vcard = st.deserialize(data); usere:add_child(vcard); return self:_set_user_xml(user, self.host, xml); end return true; end; }; handlers.private = { get = function(self, user) user = getUserElement(self:_get_user_xml(user, self.host)); if user then local private = user:get_child("query", "jabber:iq:private"); if private then local r = {}; for _, tag in ipairs(private.tags) do r[tag.name..":"..tag.attr.xmlns] = st.preserialize(tag); end return r; end end end; set = function(self, user, data) local xml = self:_get_user_xml(user, self.host); local usere = xml and getUserElement(xml); if usere then usere:remove_children("query", "jabber:iq:private"); if data and next(data) ~= nil then local private = st.stanza("query", {xmlns='jabber:iq:private'}); for _,tag in pairs(data) do private:add_child(st.deserialize(tag)); end usere:add_child(private); end return self:_set_user_xml(user, self.host, xml); end return true; end; }; handlers.roster = { get = function(self, user) user = getUserElement(self:_get_user_xml(user, self.host)); if user then local roster = user:get_child("query", "jabber:iq:roster"); if roster then local r = { [false] = { version = roster.attr.version; pending = {}; } }; for item in roster:childtags("item") do r[item.attr.jid] = { jid = item.attr.jid, subscription = item.attr.subscription, ask = item.attr.ask, name = item.attr.name, groups = {}; }; for group in item:childtags("group") do r[item.attr.jid].groups[group:get_text()] = true; end for pending in user:childtags("presence", "jabber:client") do r[false].pending[pending.attr.from] = true; end end return r; end end end; set = function(self, user, data) local xml = self:_get_user_xml(user, self.host); local usere = xml and getUserElement(xml); if usere then local user_jid = jid_join(usere.name, self.host); usere:remove_children("query", "jabber:iq:roster"); usere:maptags(function (tag) if tag.attr.xmlns == "jabber:client" and tag.name == "presence" and tag.attr.type == "subscribe" then return nil; end return tag; end); if data and next(data) ~= nil then local roster = st.stanza("query", {xmlns='jabber:iq:roster'}); usere:add_child(roster); for contact_jid, item in pairs(data) do if contact_jid ~= false then contact_jid = jid_bare(jid_prep(contact_jid)); if contact_jid ~= user_jid then -- Skip self-contacts roster:tag("item", { jid = contact_jid, subscription = item.subscription, ask = item.ask, name = item.name, }); for group in pairs(item.groups) do roster:tag("group"):text(group):up(); end roster:up(); -- move out from item end else roster.attr.version = item.version; for pending_jid in pairs(item.pending) do usere:add_child(st.presence({ from = pending_jid, type = "subscribe" })); end end end end return self:_set_user_xml(user, self.host, xml); end return true; end; }; -- PEP node configuration/etc. (not items) local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; local lib_pubsub = module:require "pubsub"; handlers.pep = { get = function (self, user) local xml = self:_get_user_xml(user, self.host); local user_el = xml and getUserElement(xml); if not user_el then return nil; end local nodes = { --[[ [node_name] = { name = node_name; config = {}; affiliations = {}; subscribers = {}; }; ]] }; local owner_el = user_el:get_child("pubsub", xmlns_pubsub_owner); if not owner_el then local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub); if not pubsub_el then return nil; end for node_el in pubsub_el:childtags("items") do nodes[node_el.attr.node] = true; -- relies on COMPAT behavior in mod_pep end return nodes; end for node_el in owner_el:childtags() do local node_name = node_el.attr.node; local node = nodes[node_name]; if not node then node = { name = node_name; config = {}; affiliations = {}; subscribers = {}; }; nodes[node_name] = node; end if node_el.name == "configure" then local form = node_el:get_child("x", "jabber:x:data"); if form then node.config = lib_pubsub.node_config_form:data(form); end elseif node_el.name == "affiliations" then for affiliation_el in node_el:childtags("affiliation") do local aff_jid = jid_prep(affiliation_el.attr.jid); local aff_value = affiliation_el.attr.affiliation; if aff_jid and aff_value then node.affiliations[aff_jid] = aff_value; end end elseif node_el.name == "subscriptions" then for subscription_el in node_el:childtags("subscription") do local sub_jid = jid_prep(subscription_el.attr.jid); local sub_state = subscription_el.attr.subscription; if sub_jid and sub_state == "subscribed" then local options; local subscription_options_el = subscription_el:get_child("options"); if subscription_options_el then local options_form = subscription_options_el:get_child("x", "jabber:x:data"); if options_form then options = lib_pubsub.subscription_options_form:data(options_form); end end node.subscribers[sub_jid] = options or true; end end else module:log("warn", "Ignoring unknown pubsub element: %s", node_el.name); end end return nodes; end; set = function(self, user, data) local xml = self:_get_user_xml(user, self.host); local user_el = xml and getUserElement(xml); if not user_el then return true; end -- Remove existing data, if any user_el:remove_children("pubsub", xmlns_pubsub_owner); -- Generate new data local owner_el = st.stanza("pubsub", { xmlns = xmlns_pubsub_owner }); for node_name, node_data in pairs(data) do if node_data == true then node_data = { config = {} }; end local configure_el = st.stanza("configure", { node = node_name }) :add_child(lib_pubsub.node_config_form:form(node_data.config, "submit")); owner_el:add_child(configure_el); if node_data.affiliations and next(node_data.affiliations) ~= nil then local affiliations_el = st.stanza("affiliations", { node = node_name }); for aff_jid, aff_value in pairs(node_data.affiliations) do affiliations_el:tag("affiliation", { jid = aff_jid, affiliation = aff_value }):up(); end owner_el:add_child(affiliations_el); end if node_data.subscribers and next(node_data.subscribers) ~= nil then local subscriptions_el = st.stanza("subscriptions", { node = node_name }); for sub_jid, sub_data in pairs(node_data.subscribers) do local sub_el = st.stanza("subscription", { jid = sub_jid, subscribed = "subscribed" }); if sub_data ~= true then local options_form = lib_pubsub.subscription_options_form:form(sub_data, "submit"); sub_el:tag("options"):add_child(options_form):up(); end subscriptions_el:add_child(sub_el); end owner_el:add_child(subscriptions_el); end end user_el:add_child(owner_el); return self:_set_user_xml(user, self.host, xml); end; }; -- PEP items handlers.pep_ = { _stores = function (self, xml) --luacheck: ignore 212/self local store_names = set.new(); local user_el = xml and getUserElement(xml); if not user_el then return store_names; end -- Locate existing pubsub element, if any local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub); if not pubsub_el then return store_names; end -- Find node items element, if any for items_el in pubsub_el:childtags("items") do store_names:add("pep_"..items_el.attr.node); end return store_names; end; find = function (self, user, query) -- query keys: limit, reverse, key (id) local xml = self:_get_user_xml(user, self.host); local user_el = xml and getUserElement(xml); if not user_el then return nil, "no 227 user element found"; end local node_name = self.datastore:match("^pep_(.+)$"); -- Locate existing pubsub element, if any local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub); if not pubsub_el then return nil; end -- Find node items element, if any local node_items_el; for items_el in pubsub_el:childtags("items") do if items_el.attr.node == node_name then node_items_el = items_el; break; end end if not node_items_el then return nil; end local user_jid = user.."@"..self.host; local results = {}; for item_el in node_items_el:childtags("item") do if query and query.key then if item_el.attr.id == query.key then table.insert(results, { item_el.attr.id, item_el.tags[1], 0, user_jid }); break; end else table.insert(results, { item_el.attr.id, item_el.tags[1], 0, user_jid }); end if query and query.limit and #results >= query.limit then break; end end if query and query.reverse then return array.reverse(results); end local i = 0; return function () i = i + 1; local v = results[i]; if v == nil then return nil; end return unpack(v, 1, 4); end; end; append = function (self, user, key, payload, when, with) --luacheck: ignore 212/when 212/with 212/key local xml = self:_get_user_xml(user, self.host); local user_el = xml and getUserElement(xml); if not user_el then return true; end local node_name = self.datastore:match("^pep_(.+)$"); -- Locate existing pubsub element, if any local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub); if not pubsub_el then pubsub_el = st.stanza("pubsub", { xmlns = xmlns_pubsub }); user_el:add_child(pubsub_el); end -- Find node items element, if any local node_items_el; for items_el in pubsub_el:childtags("items") do if items_el.attr.node == node_name then node_items_el = items_el; break; end end if not node_items_el then -- Doesn't exist yet, create one node_items_el = st.stanza("items", { node = node_name }); pubsub_el:add_child(node_items_el); end -- Append item to pubsub_el local item_el = st.stanza("item", { id = key }) :add_child(payload); node_items_el:add_child(item_el); return self:_set_user_xml(user, self.host, xml); end; delete = function (self, user, query) -- query keys: limit, reverse, key (id) local xml = self:_get_user_xml(user, self.host); local user_el = xml and getUserElement(xml); if not user_el then return nil, "no 227 user element found"; end local node_name = self.datastore:match("^pep_(.+)$"); -- Locate existing pubsub element, if any local pubsub_el = user_el:get_child("pubsub", xmlns_pubsub); if not pubsub_el then return nil; end -- Find node items element, if any local node_items_el; for items_el in pubsub_el:childtags("items") do if items_el.attr.node == node_name then node_items_el = items_el; break; end end if not node_items_el then return nil; end local results = array(); for item_el in pubsub_el:childtags("item") do if query and query.key then if item_el.attr.id == query.key then table.insert(results, item_el); break; end else table.insert(results, item_el); end if query and query.limit and #results >= query.limit then break; end end if query and query.truncate then results:sub(-query.truncate); end -- Actually remove the matching items local delete_keys = set.new(results:map(function (item) return item.attr.id; end)); pubsub_el:maptags(function (item_el) if delete_keys:contains(item_el.attr.id) then return nil; end return item_el; end); return self:_set_user_xml(user, self.host, xml); end; }; -- MAM archives local xmlns_pie_mam = "urn:xmpp:pie:0#mam"; handlers.archive = { find = function (self, user, query) assert(query == nil, "XEP-0313 queries are not supported on XEP-0227 files"); local xml = self:_get_user_xml(user, self.host); local user_el = xml and getUserElement(xml); if not user_el then return nil, "no 227 user element found"; end -- Locate existing archive element, if any local archive_el = user_el:get_child("archive", xmlns_pie_mam); if not archive_el then return nil; end local user_jid = user.."@"..self.host; local f, s, result_el = archive_el:childtags("result", "urn:xmpp:mam:2"); return function () result_el = f(s, result_el); if not result_el then return nil; end local id = result_el.attr.id; local item = result_el:find("{urn:xmpp:forward:0}forwarded/{jabber:client}message"); assert(item, "Invalid stanza in XEP-0227 archive"); local when = dt.parse(result_el:find("{urn:xmpp:forward:0}forwarded/{urn:xmpp:delay}delay@stamp")); local to_bare, from_bare = jid_bare(item.attr.to), jid_bare(item.attr.from); local with = to_bare == user_jid and from_bare or to_bare; -- id, item, when, with return id, item, when, with; end; end; append = function (self, user, key, payload, when, with) --luacheck: ignore 212/when 212/with 212/key local xml = self:_get_user_xml(user, self.host); local user_el = xml and getUserElement(xml); if not user_el then return true; end -- Locate existing archive element, if any local archive_el = user_el:get_child("archive", xmlns_pie_mam); if not archive_el then archive_el = st.stanza("archive", { xmlns = xmlns_pie_mam }); user_el:add_child(archive_el); end local item = st.clone(payload); item.attr.xmlns = "jabber:client"; local result_el = st.stanza("result", { xmlns = "urn:xmpp:mam:2", id = key }) :tag("forwarded", { xmlns = "urn:xmpp:forward:0" }) :tag("delay", { xmlns = "urn:xmpp:delay", stamp = dt.datetime(when) }):up() :add_child(item) :up(); -- Append item to archive_el archive_el:add_child(result_el); return self:_set_user_xml(user, self.host, xml); end; }; ----------------------------- local driver = {}; local function users(self) local file_patt = "^.*@"..(self.host:gsub("%p", "%%%1")).."%.xml$"; local f, s, filename = lfs.dir(prosody.paths.data); return function () filename = f(s, filename); while filename and not filename:match(file_patt) do filename = f(s, filename); end if not filename then return nil; end return filename:match("^[^@]+"); end; end function driver:open(datastore, typ) -- luacheck: ignore 212/self if typ and typ ~= "keyval" and typ ~= "archive" then return nil, "unsupported-store"; end local handler = handlers[datastore]; if not handler and datastore:match("^pep_") then handler = handlers.pep_; end if not handler then return nil, "unsupported-datastore"; end local instance = setmetatable({ host = module.host; datastore = datastore; users = users; _get_user_xml = assert(default_get_user_xml); _set_user_xml = default_set_user_xml; }, { __index = handler; } ); if instance.init then instance:init(); end return instance; end -- Custom API that allows some configuration function driver:open_xep0227(datastore, typ, options) local instance, err = self:open(datastore, typ); if not instance then return instance, err; end if options then instance._set_user_xml = assert(options.set_user_xml); instance._get_user_xml = assert(options.get_user_xml); end return instance; end local function get_store_names_from_xml(self, user_xml) local stores = set.new(); for handler_name, handler_funcs in pairs(handlers) do if handler_funcs._stores then stores:include(handler_funcs._stores(self, user_xml)); else stores:add(handler_name); end end return stores; end local function get_store_names(self, path) local stores = set.new(); local f, err = io_open(paths.join(prosody.paths.data, path)); if not f then module:log("warn", "Unable to load XML file for <%s>: %s", "store listing", err); return stores; end module:log("info", "Loaded %s", path); local s = f:read("*a"); f:close(); local user_xml = parse_xml_real(s); return get_store_names_from_xml(self, user_xml); end function driver:stores(username) local store_dir = prosody.paths.data; local mode, err = lfs.attributes(store_dir, "mode"); if not mode then return function() module:log("debug", "Could not iterate over stores in %s: %s", store_dir, err); end end local file_patt = "^.*@"..(module.host:gsub("%p", "%%%1")).."%.xml$"; local all_users = username == true; local store_names = set.new(); for filename in lfs.dir(prosody.paths.data) do if filename:match(file_patt) then if all_users or filename == username.."@"..module.host..".xml" then store_names:include(get_store_names(self, filename)); if not all_users then break; end end end end return store_names:items(); end function driver:xep0227_user_stores(username, host) local user_xml = self:_get_user_xml(username, host); if not user_xml then return nil; end local store_names = get_store_names_from_xml(username, host); return store_names:items(); end module:provides("storage", driver); prosody-0.12.4/plugins/PaxHeaders/mod_time.lua0000644000000000000000000000011714475665104016353 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68076507 prosody-0.12.4/plugins/mod_time.lua0000644000175000017500000000236014475665104020553 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local datetime = require "util.datetime".datetime; local legacy = require "util.datetime".legacy; -- XEP-0202: Entity Time module:add_feature("urn:xmpp:time"); local function time_handler(event) local origin, stanza = event.origin, event.stanza; origin.send(st.reply(stanza):tag("time", {xmlns="urn:xmpp:time"}) :tag("tzo"):text("+00:00"):up() -- TODO get the timezone in a platform independent fashion :tag("utc"):text(datetime())); return true; end module:hook("iq-get/bare/urn:xmpp:time:time", time_handler); module:hook("iq-get/host/urn:xmpp:time:time", time_handler); -- XEP-0090: Entity Time (deprecated) module:add_feature("jabber:iq:time"); local function legacy_time_handler(event) local origin, stanza = event.origin, event.stanza; origin.send(st.reply(stanza):tag("query", {xmlns="jabber:iq:time"}) :tag("utc"):text(legacy())); return true; end module:hook("iq-get/bare/jabber:iq:time:query", legacy_time_handler); module:hook("iq-get/host/jabber:iq:time:query", legacy_time_handler); prosody-0.12.4/plugins/PaxHeaders/mod_tls.lua0000644000000000000000000000011714475665104016217 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68076507 prosody-0.12.4/plugins/mod_tls.lua0000644000175000017500000001654314475665104020427 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local create_context = require "core.certmanager".create_context; local rawgetopt = require"core.configmanager".rawget; local st = require "util.stanza"; local c2s_require_encryption = module:get_option("c2s_require_encryption", module:get_option("require_encryption", true)); local s2s_require_encryption = module:get_option("s2s_require_encryption", true); local allow_s2s_tls = module:get_option("s2s_allow_encryption") ~= false; local s2s_secure_auth = module:get_option("s2s_secure_auth"); if s2s_secure_auth and s2s_require_encryption == false then module:log("warn", "s2s_secure_auth implies s2s_require_encryption, but s2s_require_encryption is set to false"); s2s_require_encryption = true; end local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls'; local starttls_attr = { xmlns = xmlns_starttls }; local starttls_initiate= st.stanza("starttls", starttls_attr); local starttls_proceed = st.stanza("proceed", starttls_attr); local starttls_failure = st.stanza("failure", starttls_attr); local c2s_feature = st.stanza("starttls", starttls_attr); local s2s_feature = st.stanza("starttls", starttls_attr); if c2s_require_encryption then c2s_feature:tag("required"):up(); end if s2s_require_encryption then s2s_feature:tag("required"):up(); end local hosts = prosody.hosts; local host = hosts[module.host]; local ssl_ctx_c2s, ssl_ctx_s2sout, ssl_ctx_s2sin; local ssl_cfg_c2s, ssl_cfg_s2sout, ssl_cfg_s2sin; local err_c2s, err_s2sin, err_s2sout; function module.load(reload) local NULL = {}; local modhost = module.host; local parent = modhost:match("%.(.*)$"); local parent_ssl = rawgetopt(parent, "ssl") or NULL; local host_ssl = rawgetopt(modhost, "ssl") or parent_ssl; local global_c2s = rawgetopt("*", "c2s_ssl") or NULL; local parent_c2s = rawgetopt(parent, "c2s_ssl") or NULL; local host_c2s = rawgetopt(modhost, "c2s_ssl") or parent_c2s; local global_s2s = rawgetopt("*", "s2s_ssl") or NULL; local parent_s2s = rawgetopt(parent, "s2s_ssl") or NULL; local host_s2s = rawgetopt(modhost, "s2s_ssl") or parent_s2s; module:log("debug", "Creating context for c2s"); local request_client_certs = { verify = { "peer", "client_once", }; }; local xmpp_alpn = { alpn = "xmpp-server" }; ssl_ctx_c2s, err_c2s, ssl_cfg_c2s = create_context(host.host, "server", host_c2s, host_ssl, global_c2s); -- for incoming client connections if not ssl_ctx_c2s then module:log("error", "Error creating context for c2s: %s", err_c2s); end module:log("debug", "Creating context for s2sout"); -- for outgoing server connections ssl_ctx_s2sout, err_s2sout, ssl_cfg_s2sout = create_context(host.host, "client", host_s2s, host_ssl, global_s2s, request_client_certs, xmpp_alpn); if not ssl_ctx_s2sout then module:log("error", "Error creating contexts for s2sout: %s", err_s2sout); end module:log("debug", "Creating context for s2sin"); -- for incoming server connections ssl_ctx_s2sin, err_s2sin, ssl_cfg_s2sin = create_context(host.host, "server", host_s2s, host_ssl, global_s2s, request_client_certs); if not ssl_ctx_s2sin then module:log("error", "Error creating contexts for s2sin: %s", err_s2sin); end if reload then module:log("info", "Certificates reloaded"); else module:log("info", "Certificates loaded"); end end module:hook_global("config-reloaded", module.load); local function can_do_tls(session) if session.conn and not session.conn.starttls then if not session.secure then session.log("debug", "Underlying connection does not support STARTTLS"); end return false; elseif session.ssl_ctx ~= nil then return session.ssl_ctx; end if session.type == "c2s_unauthed" then if not ssl_ctx_c2s and c2s_require_encryption then session.log("error", "No TLS context available for c2s. Earlier error was: %s", err_c2s); end session.ssl_ctx = ssl_ctx_c2s; session.ssl_cfg = ssl_cfg_c2s; elseif session.type == "s2sin_unauthed" and allow_s2s_tls then if not ssl_ctx_s2sin and s2s_require_encryption then session.log("error", "No TLS context available for s2sin. Earlier error was: %s", err_s2sin); end session.ssl_ctx = ssl_ctx_s2sin; session.ssl_cfg = ssl_cfg_s2sin; elseif session.direction == "outgoing" and allow_s2s_tls then if not ssl_ctx_s2sout and s2s_require_encryption then session.log("error", "No TLS context available for s2sout. Earlier error was: %s", err_s2sout); end session.ssl_ctx = ssl_ctx_s2sout; session.ssl_cfg = ssl_cfg_s2sout; else session.log("debug", "Unknown session type, don't know which TLS context to use"); return false; end if not session.ssl_ctx then session.log("debug", "Should be able to do TLS but no context available"); return false; end return session.ssl_ctx; end module:hook("s2sout-created", function (event) -- Initialize TLS context for outgoing connections can_do_tls(event.session); end); -- Hook module:hook("stanza/urn:ietf:params:xml:ns:xmpp-tls:starttls", function(event) local origin = event.origin; if can_do_tls(origin) then (origin.sends2s or origin.send)(starttls_proceed); if origin.destroyed then return end origin:reset_stream(); origin.conn:starttls(origin.ssl_ctx); origin.log("debug", "TLS negotiation started for %s...", origin.type); origin.secure = false; else origin.log("warn", "Attempt to start TLS, but TLS is not available on this %s connection", origin.type); (origin.sends2s or origin.send)(starttls_failure); origin:close(); end return true; end); -- Advertise stream feature module:hook("stream-features", function(event) local origin, features = event.origin, event.features; if can_do_tls(origin) then features:add_child(c2s_feature); end end); module:hook("s2s-stream-features", function(event) local origin, features = event.origin, event.features; if can_do_tls(origin) then features:add_child(s2s_feature); end end); -- For s2sout connections, start TLS if we can module:hook_tag("http://etherx.jabber.org/streams", "features", function (session, stanza) module:log("debug", "Received features element"); if can_do_tls(session) then if stanza:get_child("starttls", xmlns_starttls) then module:log("debug", "%s is offering TLS, taking up the offer...", session.to_host); elseif s2s_require_encryption then module:log("debug", "%s is *not* offering TLS, trying anyways!", session.to_host); else module:log("debug", "%s is not offering TLS", session.to_host); return; end session.sends2s(starttls_initiate); return true; end end, 500); module:hook("s2sout-authenticate-legacy", function(event) local session = event.origin; if s2s_require_encryption and can_do_tls(session) then session.sends2s(starttls_initiate); return true; end end, 200); module:hook_tag(xmlns_starttls, "proceed", function (session, stanza) -- luacheck: ignore 212/stanza if session.type == "s2sout_unauthed" and can_do_tls(session) then module:log("debug", "Proceeding with TLS on s2sout..."); session:reset_stream(); session.conn:starttls(session.ssl_ctx); session.secure = false; return true; end end); module:hook_tag(xmlns_starttls, "failure", function (session, stanza) -- luacheck: ignore 212/stanza module:log("warn", "TLS negotiation with %s failed.", session.to_host); session:close(nil, "TLS negotiation failed"); return false; end); prosody-0.12.4/plugins/PaxHeaders/mod_tokenauth.lua0000644000000000000000000000011714475665104017417 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68076507 prosody-0.12.4/plugins/mod_tokenauth.lua0000644000175000017500000000420314475665104021615 0ustar00prosodyprosody00000000000000local id = require "util.id"; local jid = require "util.jid"; local base64 = require "util.encodings".base64; local token_store = module:open_store("auth_tokens", "map"); function create_jid_token(actor_jid, token_jid, token_scope, token_ttl) token_jid = jid.prep(token_jid); if not actor_jid or token_jid ~= actor_jid and not jid.compare(token_jid, actor_jid) then return nil, "not-authorized"; end local token_username, token_host, token_resource = jid.split(token_jid); if token_host ~= module.host then return nil, "invalid-host"; end local token_info = { owner = actor_jid; created = os.time(); expires = token_ttl and (os.time() + token_ttl) or nil; jid = token_jid; session = { username = token_username; host = token_host; resource = token_resource; auth_scope = token_scope; }; }; local token_id = id.long(); local token = base64.encode("1;"..jid.join(token_username, token_host)..";"..token_id); token_store:set(token_username, token_id, token_info); return token, token_info; end local function parse_token(encoded_token) local token = base64.decode(encoded_token); if not token then return nil; end local token_jid, token_id = token:match("^1;([^;]+);(.+)$"); if not token_jid then return nil; end local token_user, token_host = jid.split(token_jid); return token_id, token_user, token_host; end function get_token_info(token) local token_id, token_user, token_host = parse_token(token); if not token_id then return nil, "invalid-token-format"; end if token_host ~= module.host then return nil, "invalid-host"; end local token_info, err = token_store:get(token_user, token_id); if not token_info then if err then return nil, "internal-error"; end return nil, "not-authorized"; end if token_info.expires and token_info.expires < os.time() then return nil, "not-authorized"; end return token_info end function revoke_token(token) local token_id, token_user, token_host = parse_token(token); if not token_id then return nil, "invalid-token-format"; end if token_host ~= module.host then return nil, "invalid-host"; end return token_store:set(token_user, token_id, nil); end prosody-0.12.4/plugins/PaxHeaders/mod_tombstones.lua0000644000000000000000000000011714475665104017612 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68076507 prosody-0.12.4/plugins/mod_tombstones.lua0000644000175000017500000000747114475665104022022 0ustar00prosodyprosody00000000000000-- TODO warn when trying to create an user before the tombstone expires -- e.g. via telnet or other admin interface local datetime = require "util.datetime"; local errors = require "util.error"; local jid_node = require"util.jid".node; local st = require "util.stanza"; -- Using a map store as key-value store so that removal of all user data -- does not also remove the tombstone, which would defeat the point local graveyard = module:open_store(nil, "map"); local graveyard_cache = require "util.cache".new(module:get_option_number("tombstone_cache_size", 1024)); local ttl = module:get_option_number("user_tombstone_expiry", nil); -- Keep tombstones forever by default -- -- Rationale: -- There is no way to be completely sure when remote services have -- forgotten and revoked all memberships. -- TODO If the user left a JID they moved to, return a gone+redirect error -- TODO Attempt to deregister from MUCs based on bookmarks -- TODO Unsubscribe from pubsub services if a notification is received module:hook_global("user-deleted", function(event) if event.host == module.host then local ok, err = graveyard:set(nil, event.username, os.time()); if not ok then module:log("error", "Could store tombstone for %s: %s", event.username, err); end end end); -- Public API function has_tombstone(username) local tombstone; -- Check cache local cached_result = graveyard_cache:get(username); if cached_result == false then -- We cached that there is no tombstone for this user return false; elseif cached_result then tombstone = cached_result; else local stored_result, err = graveyard:get(nil, username); if not stored_result and not err then -- Cache that there is no tombstone for this user graveyard_cache:set(username, false); return false; elseif err then -- Failed to check tombstone status return nil, err; end -- We have a tombstone stored, so let's continue with that tombstone = stored_result; end -- Check expiry if ttl and tombstone + ttl < os.time() then module:log("debug", "Tombstone for %s created at %s has expired", username, datetime.datetime(tombstone)); graveyard:set(nil, username, nil); graveyard_cache:set(username, nil); -- clear cache entry (if any) return nil; end -- Cache for the future graveyard_cache:set(username, tombstone); return tombstone; end module:hook("user-registering", function(event) local tombstone, err = has_tombstone(event.username); if err then event.allowed, event.error = errors.coerce(false, err); return true; elseif not tombstone then -- Feel free return; end module:log("debug", "Tombstone for %s created at %s", event.username, datetime.datetime(tombstone)); event.allowed = false; return true; end); module:hook("presence/bare", function(event) local origin, presence = event.origin, event.stanza; local local_username = jid_node(presence.attr.to); if not local_username then return; end -- We want to undo any left-over presence subscriptions and notify the former -- contact that they're gone. -- -- FIXME This leaks that the user once existed. Hard to avoid without keeping -- the contact list in some form, which we don't want to do for privacy -- reasons. Bloom filter perhaps? local pres_type = presence.attr.type; local is_probe = pres_type == "probe"; local is_normal = pres_type == nil or pres_type == "unavailable"; if is_probe and has_tombstone(local_username) then origin.send(st.error_reply(presence, "cancel", "gone", "User deleted")); origin.send(st.presence({ type = "unsubscribed"; to = presence.attr.from; from = presence.attr.to })); return true; elseif is_normal and has_tombstone(local_username) then origin.send(st.error_reply(presence, "cancel", "gone", "User deleted")); origin.send(st.presence({ type = "unsubscribe"; to = presence.attr.from; from = presence.attr.to })); return true; end end, 1); prosody-0.12.4/plugins/PaxHeaders/mod_turn_external.lua0000644000000000000000000000011714475665104020307 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68076507 prosody-0.12.4/plugins/mod_turn_external.lua0000644000175000017500000000246314475665104022513 0ustar00prosodyprosody00000000000000local set = require "util.set"; local secret = module:get_option_string("turn_external_secret"); local host = module:get_option_string("turn_external_host", module.host); local user = module:get_option_string("turn_external_user"); local port = module:get_option_number("turn_external_port", 3478); local ttl = module:get_option_number("turn_external_ttl", 86400); local tcp = module:get_option_boolean("turn_external_tcp", false); local tls_port = module:get_option_number("turn_external_tls_port"); if not secret then module:log_status("error", "Failed to initialize: the 'turn_external_secret' option is not set in your configuration"); return; end local services = set.new({ "stun-udp"; "turn-udp" }); if tcp then services:add("stun-tcp"); services:add("turn-tcp"); end if tls_port then services:add("turns-tcp"); end module:depends "external_services"; for _, type in ipairs({ "stun"; "turn"; "turns" }) do for _, transport in ipairs({"udp"; "tcp"}) do if services:contains(type .. "-" .. transport) then module:add_item("external_service", { type = type; transport = transport; host = host; port = type == "turns" and tls_port or port; username = type == "turn" and user or nil; secret = type == "turn" and secret or nil; ttl = type == "turn" and ttl or nil; }) end end end prosody-0.12.4/plugins/PaxHeaders/mod_unknown.lua0000644000000000000000000000011714475665104017114 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68076507 prosody-0.12.4/plugins/mod_unknown.lua0000644000175000017500000000016514475665104021315 0ustar00prosodyprosody00000000000000-- Unknown platform stub module:set_global(); -- TODO Do things that make sense if we don't know about the platform prosody-0.12.4/plugins/PaxHeaders/mod_uptime.lua0000644000000000000000000000011714475665104016720 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_uptime.lua0000644000175000017500000000273714475665104021130 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local start_time = prosody.start_time; module:hook_global("server-started", function() start_time = prosody.start_time end); -- XEP-0012: Last activity module:add_feature("jabber:iq:last"); module:hook("iq-get/host/jabber:iq:last:query", function(event) local origin, stanza = event.origin, event.stanza; origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:last", seconds = tostring(("%d"):format(os.difftime(os.time(), start_time)))})); return true; end); -- Ad-hoc command module:depends "adhoc"; local adhoc_new = module:require "adhoc".new; function uptime_text() local t = os.time()-prosody.start_time; local seconds = t%60; t = (t - seconds)/60; local minutes = t%60; t = (t - minutes)/60; local hours = t%24; t = (t - hours)/24; local days = t; return string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); end function uptime_command_handler () return { info = uptime_text(), status = "completed" }; end local descriptor = adhoc_new("Get uptime", "uptime", uptime_command_handler, "any"); module:provides("adhoc", descriptor); prosody-0.12.4/plugins/PaxHeaders/mod_user_account_management.lua0000644000000000000000000000011714475665104022303 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_user_account_management.lua0000644000175000017500000000577414475665104024517 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local usermanager_set_password = require "core.usermanager".set_password; local usermanager_delete_user = require "core.usermanager".delete_user; local nodeprep = require "util.encodings".stringprep.nodeprep; local jid_bare = require "util.jid".bare; local compat = module:get_option_boolean("registration_compat", true); module:add_feature("jabber:iq:register"); -- Password change and account deletion handler local function handle_registration_stanza(event) local session, stanza = event.origin, event.stanza; local log = session.log or module._log; local query = stanza.tags[1]; if stanza.attr.type == "get" then local reply = st.reply(stanza); reply:tag("query", {xmlns = "jabber:iq:register"}) :tag("registered"):up() :tag("username"):text(session.username):up() :tag("password"):up(); session.send(reply); else -- stanza.attr.type == "set" if query.tags[1] and query.tags[1].name == "remove" then local username, host = session.username, session.host; -- This one weird trick sends a reply to this stanza before the user is deleted local old_session_close = session.close; session.close = function(self, ...) self.send(st.reply(stanza)); return old_session_close(self, ...); end local ok, err = usermanager_delete_user(username, host); if not ok then log("debug", "Removing user account %s@%s failed: %s", username, host, err); session.close = old_session_close; session.send(st.error_reply(stanza, "cancel", "service-unavailable", err)); return true; end log("info", "User removed their account: %s@%s", username, host); module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session }); else local username = query:get_child_text("username"); local password = query:get_child_text("password"); if username and password then username = nodeprep(username); if username == session.username then if usermanager_set_password(username, password, session.host, session.resource) then session.send(st.reply(stanza)); else -- TODO unable to write file, file may be locked, etc, what's the correct error? session.send(st.error_reply(stanza, "wait", "internal-server-error")); end else session.send(st.error_reply(stanza, "modify", "bad-request")); end else session.send(st.error_reply(stanza, "modify", "bad-request")); end end end return true; end module:hook("iq/self/jabber:iq:register:query", handle_registration_stanza); if compat then module:hook("iq/host/jabber:iq:register:query", function (event) local session, stanza = event.origin, event.stanza; if session.type == "c2s" and jid_bare(stanza.attr.to) == session.host then return handle_registration_stanza(event); end end); end prosody-0.12.4/plugins/PaxHeaders/mod_vcard.lua0000644000000000000000000000011714475665104016514 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_vcard.lua0000644000175000017500000000266014475665104020717 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza" local jid_split = require "util.jid".split; local vcards = module:open_store(); module:add_feature("vcard-temp"); local function handle_vcard(event) local session, stanza = event.origin, event.stanza; local to = stanza.attr.to; if stanza.attr.type == "get" then local vCard; if to then local node = jid_split(to); vCard = st.deserialize(vcards:get(node)); -- load vCard for user or server else vCard = st.deserialize(vcards:get(session.username));-- load user's own vCard end if vCard then session.send(st.reply(stanza):add_child(vCard)); -- send vCard! else session.send(st.error_reply(stanza, "cancel", "item-not-found")); end else -- stanza.attr.type == "set" if not to then if vcards:set(session.username, st.preserialize(stanza.tags[1])) then session.send(st.reply(stanza)); else -- TODO unable to write file, file may be locked, etc, what's the correct error? session.send(st.error_reply(stanza, "wait", "internal-server-error")); end else session.send(st.error_reply(stanza, "auth", "forbidden")); end end return true; end module:hook("iq/bare/vcard-temp:vCard", handle_vcard); module:hook("iq/host/vcard-temp:vCard", handle_vcard); prosody-0.12.4/plugins/PaxHeaders/mod_vcard4.lua0000644000000000000000000000011714475665104016600 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_vcard4.lua0000644000175000017500000000313114475665104020775 0ustar00prosodyprosody00000000000000local st = require "util.stanza" local jid_split = require "util.jid".split; local mod_pep = module:depends("pep"); module:hook("account-disco-info", function (event) event.reply:tag("feature", { var = "urn:ietf:params:xml:ns:vcard-4.0" }):up(); end); module:hook("iq-get/bare/urn:ietf:params:xml:ns:vcard-4.0:vcard", function (event) local origin, stanza = event.origin, event.stanza; local pep_service = mod_pep.get_pep_service(jid_split(stanza.attr.to) or origin.username); local ok, id, item = pep_service:get_last_item("urn:xmpp:vcard4", stanza.attr.from); if ok and item then origin.send(st.reply(stanza):add_child(item.tags[1])); elseif id == "item-not-found" or not id then origin.send(st.error_reply(stanza, "cancel", "item-not-found")); elseif id == "forbidden" then origin.send(st.error_reply(stanza, "auth", "forbidden")); else origin.send(st.error_reply(stanza, "modify", "undefined-condition")); end return true; end); module:hook("iq-set/self/urn:ietf:params:xml:ns:vcard-4.0:vcard", function (event) local origin, stanza = event.origin, event.stanza; local vcard4 = st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = "current" }) :add_child(stanza.tags[1]); local pep_service = mod_pep.get_pep_service(origin.username); local ok, err = pep_service:publish("urn:xmpp:vcard4", origin.full_jid, "current", vcard4); if ok then origin.send(st.reply(stanza)); elseif err == "forbidden" then origin.send(st.error_reply(stanza, "auth", "forbidden")); else origin.send(st.error_reply(stanza, "modify", "undefined-condition", err)); end return true; end); prosody-0.12.4/plugins/PaxHeaders/mod_vcard_legacy.lua0000644000000000000000000000011714475665104020040 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_vcard_legacy.lua0000644000175000017500000002721114475665104022242 0ustar00prosodyprosody00000000000000local st = require "util.stanza"; local jid_split = require "util.jid".split; local mod_pep = module:depends("pep"); local sha1 = require "util.hashes".sha1; local base64_decode = require "util.encodings".base64.decode; local vcards = module:open_store("vcard"); module:add_feature("vcard-temp"); module:hook("account-disco-info", function (event) event.reply:tag("feature", { var = "urn:xmpp:pep-vcard-conversion:0" }):up(); end); local function handle_error(origin, stanza, err) if err == "forbidden" then origin.send(st.error_reply(stanza, "auth", "forbidden")); elseif err == "internal-server-error" then origin.send(st.error_reply(stanza, "wait", "internal-server-error")); else origin.send(st.error_reply(stanza, "modify", "undefined-condition", err)); end end -- Simple translations -- hey -> hey local simple_map = { nickname = "text"; title = "text"; role = "text"; categories = "text"; note = "text"; url = "uri"; bday = "date"; } module:hook("iq-get/bare/vcard-temp:vCard", function (event) local origin, stanza = event.origin, event.stanza; local pep_service = mod_pep.get_pep_service(jid_split(stanza.attr.to) or origin.username); local ok, _, vcard4_item = pep_service:get_last_item("urn:xmpp:vcard4", stanza.attr.from); local vcard_temp = st.stanza("vCard", { xmlns = "vcard-temp" }); if ok and vcard4_item then local vcard4 = vcard4_item.tags[1]; local fn = vcard4:get_child("fn"); vcard_temp:text_tag("FN", fn and fn:get_child_text("text")); local v4n = vcard4:get_child("n"); vcard_temp:tag("N") :text_tag("FAMILY", v4n and v4n:get_child_text("surname")) :text_tag("GIVEN", v4n and v4n:get_child_text("given")) :text_tag("MIDDLE", v4n and v4n:get_child_text("additional")) :text_tag("PREFIX", v4n and v4n:get_child_text("prefix")) :text_tag("SUFFIX", v4n and v4n:get_child_text("suffix")) :up(); for tag in vcard4:childtags() do local typ = simple_map[tag.name]; if typ then local text = tag:get_child_text(typ); if text then vcard_temp:text_tag(tag.name:upper(), text); end elseif tag.name == "email" then local text = tag:get_child_text("text"); if text then vcard_temp:tag("EMAIL") :text_tag("USERID", text) :tag("INTERNET"):up(); if tag:find"parameters/type/text#" == "home" then vcard_temp:tag("HOME"):up(); elseif tag:find"parameters/type/text#" == "work" then vcard_temp:tag("WORK"):up(); end vcard_temp:up(); end elseif tag.name == "tel" then local text = tag:get_child_text("uri"); if text then if text:sub(1, 4) == "tel:" then text = text:sub(5) end vcard_temp:tag("TEL"):text_tag("NUMBER", text); if tag:find"parameters/type/text#" == "home" then vcard_temp:tag("HOME"):up(); elseif tag:find"parameters/type/text#" == "work" then vcard_temp:tag("WORK"):up(); end vcard_temp:up(); end elseif tag.name == "adr" then vcard_temp:tag("ADR") :text_tag("POBOX", tag:get_child_text("pobox")) :text_tag("EXTADD", tag:get_child_text("ext")) :text_tag("STREET", tag:get_child_text("street")) :text_tag("LOCALITY", tag:get_child_text("locality")) :text_tag("REGION", tag:get_child_text("region")) :text_tag("PCODE", tag:get_child_text("code")) :text_tag("CTRY", tag:get_child_text("country")); if tag:find"parameters/type/text#" == "home" then vcard_temp:tag("HOME"):up(); elseif tag:find"parameters/type/text#" == "work" then vcard_temp:tag("WORK"):up(); end vcard_temp:up(); elseif tag.name == "impp" then local uri = tag:get_child_text("uri"); if uri and uri:sub(1, 5) == "xmpp:" then vcard_temp:text_tag("JABBERID", uri:sub(6)) end elseif tag.name == "org" then vcard_temp:tag("ORG") :text_tag("ORGNAME", tag:get_child_text("text")) :up(); end end else local ok, _, nick_item = pep_service:get_last_item("http://jabber.org/protocol/nick", stanza.attr.from); if ok and nick_item then local nickname = nick_item:get_child_text("nick", "http://jabber.org/protocol/nick"); if nickname then vcard_temp:text_tag("NICKNAME", nickname); end end end local ok, avatar_hash, meta = pep_service:get_last_item("urn:xmpp:avatar:metadata", stanza.attr.from); if ok and avatar_hash then local info = meta.tags[1]:get_child("info"); if info then vcard_temp:tag("PHOTO"); if info.attr.type then vcard_temp:text_tag("TYPE", info.attr.type); end if info.attr.url then vcard_temp:text_tag("EXTVAL", info.attr.url); elseif info.attr.id then local data_ok, avatar_data = pep_service:get_items("urn:xmpp:avatar:data", stanza.attr.from, { info.attr.id }); if data_ok and avatar_data and avatar_data[info.attr.id] then local data = avatar_data[info.attr.id]; vcard_temp:text_tag("BINVAL", data.tags[1]:get_text()); end end vcard_temp:up(); end end origin.send(st.reply(stanza):add_child(vcard_temp)); return true; end); local node_defaults = { access_model = "open"; _defaults_only = true; }; function vcard_to_pep(vcard_temp) local avatar = {}; local vcard4 = st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = "current" }) :tag("vcard", { xmlns = 'urn:ietf:params:xml:ns:vcard-4.0' }); vcard4:tag("fn"):text_tag("text", vcard_temp:get_child_text("FN")):up(); local N = vcard_temp:get_child("N"); vcard4:tag("n") :text_tag("surname", N and N:get_child_text("FAMILY")) :text_tag("given", N and N:get_child_text("GIVEN")) :text_tag("additional", N and N:get_child_text("MIDDLE")) :text_tag("prefix", N and N:get_child_text("PREFIX")) :text_tag("suffix", N and N:get_child_text("SUFFIX")) :up(); for tag in vcard_temp:childtags() do local typ = simple_map[tag.name:lower()]; if typ then local text = tag:get_text(); if text then vcard4:tag(tag.name:lower()):text_tag(typ, text):up(); end elseif tag.name == "EMAIL" then local text = tag:get_child_text("USERID"); if text then vcard4:tag("email") vcard4:text_tag("text", text) vcard4:tag("parameters"):tag("type"); if tag:get_child("HOME") then vcard4:text_tag("text", "home"); elseif tag:get_child("WORK") then vcard4:text_tag("text", "work"); end vcard4:up():up():up(); end elseif tag.name == "TEL" then local text = tag:get_child_text("NUMBER"); if text then vcard4:tag("tel"):text_tag("uri", "tel:"..text); end vcard4:tag("parameters"):tag("type"); if tag:get_child("HOME") then vcard4:text_tag("text", "home"); elseif tag:get_child("WORK") then vcard4:text_tag("text", "work"); end vcard4:up():up():up(); elseif tag.name == "ORG" then local text = tag:get_child_text("ORGNAME"); if text then vcard4:tag("org"):text_tag("text", text):up(); end elseif tag.name == "DESC" then local text = tag:get_text(); if text then vcard4:tag("note"):text_tag("text", text):up(); end -- gets mapped into in the other direction elseif tag.name == "ADR" then vcard4:tag("adr") :text_tag("pobox", tag:get_child_text("POBOX")) :text_tag("ext", tag:get_child_text("EXTADD")) :text_tag("street", tag:get_child_text("STREET")) :text_tag("locality", tag:get_child_text("LOCALITY")) :text_tag("region", tag:get_child_text("REGION")) :text_tag("code", tag:get_child_text("PCODE")) :text_tag("country", tag:get_child_text("CTRY")); vcard4:tag("parameters"):tag("type"); if tag:get_child("HOME") then vcard4:text_tag("text", "home"); elseif tag:get_child("WORK") then vcard4:text_tag("text", "work"); end vcard4:up():up():up(); elseif tag.name == "JABBERID" then vcard4:tag("impp") :text_tag("uri", "xmpp:" .. tag:get_text()) :up(); elseif tag.name == "PHOTO" then local avatar_type = tag:get_child_text("TYPE"); local avatar_payload = tag:get_child_text("BINVAL"); -- Can EXTVAL be translated? No way to know the sha1 of the data? if avatar_payload then local avatar_raw = base64_decode(avatar_payload); local avatar_hash = sha1(avatar_raw, true); avatar.hash = avatar_hash; avatar.meta = st.stanza("item", { id = avatar_hash, xmlns = "http://jabber.org/protocol/pubsub" }) :tag("metadata", { xmlns="urn:xmpp:avatar:metadata" }) :tag("info", { bytes = tostring(#avatar_raw), id = avatar_hash, type = avatar_type, }); avatar.data = st.stanza("item", { id = avatar_hash, xmlns = "http://jabber.org/protocol/pubsub" }) :tag("data", { xmlns="urn:xmpp:avatar:data" }) :text(avatar_payload); end end end return vcard4, avatar; end function save_to_pep(pep_service, actor, vcard4, avatar) if avatar then if pep_service:purge("urn:xmpp:avatar:metadata", actor) then pep_service:purge("urn:xmpp:avatar:data", actor); end if avatar.data and avatar.meta then local ok, err = assert(pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, node_defaults)); if ok then ok, err = assert(pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, node_defaults)); end if not ok then return ok, err; end end end if vcard4 then return pep_service:publish("urn:xmpp:vcard4", actor, "current", vcard4, node_defaults); end return true; end module:hook("iq-set/self/vcard-temp:vCard", function (event) local origin, stanza = event.origin, event.stanza; local pep_service = mod_pep.get_pep_service(origin.username); local vcard_temp = stanza.tags[1]; local ok, err = save_to_pep(pep_service, origin.full_jid, vcard_to_pep(vcard_temp)); if ok then origin.send(st.reply(stanza)); else handle_error(origin, stanza, err); end return true; end); local function inject_xep153(event) local origin, stanza = event.origin, event.stanza; local username = origin.username; if not username then return end if stanza.attr.type then return end local pep_service = mod_pep.get_pep_service(username); local x_update = stanza:get_child("x", "vcard-temp:x:update"); if not x_update then x_update = st.stanza("x", { xmlns = "vcard-temp:x:update" }):tag("photo"); stanza:add_direct_child(x_update); elseif x_update:get_child("photo") then return; -- XEP implies that these should be left alone else x_update:tag("photo"); end local ok, avatar_hash = pep_service:get_last_item("urn:xmpp:avatar:metadata", true); if ok and avatar_hash then x_update:text(avatar_hash); end end module:hook("pre-presence/full", inject_xep153, 1); module:hook("pre-presence/bare", inject_xep153, 1); module:hook("pre-presence/host", inject_xep153, 1); if module:get_option_boolean("upgrade_legacy_vcards", true) then module:hook("resource-bind", function (event) local session = event.session; local username = session.username; local vcard_temp = vcards:get(username); if not vcard_temp then session.log("debug", "No legacy vCard to migrate or already migrated"); return; end local pep_service = mod_pep.get_pep_service(username); vcard_temp = st.deserialize(vcard_temp); local vcard4, avatars = vcard_to_pep(vcard_temp); if pep_service:get_last_item("urn:xmpp:vcard4", true) then vcard4 = nil; end if pep_service:get_last_item("urn:xmpp:avatar:metadata", true) or pep_service:get_last_item("urn:xmpp:avatar:data", true) then avatars = nil; end if not (vcard4 or avatars) then session.log("debug", "Already PEP data, not overwriting with migrated data"); vcards:set(username, nil); return; end local ok, err = save_to_pep(pep_service, true, vcard4, avatars); if ok and vcards:set(username, nil) then session.log("info", "Migrated vCard-temp to PEP"); else session.log("info", "Failed to migrate vCard-temp to PEP: %s", err or "problem emptying 'vcard' store"); end end); end prosody-0.12.4/plugins/PaxHeaders/mod_version.lua0000644000000000000000000000011714475665104017102 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_version.lua0000644000175000017500000000237114475665104021304 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; module:add_feature("jabber:iq:version"); local query = st.stanza("query", {xmlns = "jabber:iq:version"}) :text_tag("name", "Prosody") :text_tag("version", prosody.version); if not module:get_option_boolean("hide_os_type") then local platform; if os.getenv("WINDIR") then platform = "Windows"; else local os_version_command = module:get_option_string("os_version_command"); local ok, pposix = pcall(require, "util.pposix"); if not os_version_command and (ok and pposix and pposix.uname) then platform = pposix.uname().sysname; end if not platform then local uname = io.popen(os_version_command or "uname"); if uname then platform = uname:read("*a"); end uname:close(); end end if platform then platform = platform:match("^%s*(.-)%s*$") or platform; query:text_tag("os", platform); end end module:hook("iq-get/host/jabber:iq:version:query", function(event) local origin, stanza = event.origin, event.stanza; origin.send(st.reply(stanza):add_child(query)); return true; end); prosody-0.12.4/plugins/PaxHeaders/mod_watchregistrations.lua0000644000000000000000000000011714475665104021341 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_watchregistrations.lua0000644000175000017500000000231514475665104023541 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local host = module:get_host(); local jid_prep = require "util.jid".prep; local registration_watchers = module:get_option_set("registration_watchers", module:get_option("admins", {})) / jid_prep; local registration_from = module:get_option_string("registration_from", host); local registration_notification = module:get_option_string("registration_notification", "User $username just registered on $host from $ip"); local msg_type = module:get_option_string("registration_notification_type", "chat"); local st = require "util.stanza"; module:hook("user-registered", function (user) module:log("debug", "Notifying of new registration"); local message = st.message{ type = msg_type, from = registration_from } :tag("body") :text(registration_notification:gsub("%$(%w+)", function (v) return user[v] or user.session and user.session[v] or nil; end)) :up(); for jid in registration_watchers do module:log("debug", "Notifying %s", jid); message.attr.to = jid; module:send(message); end end); prosody-0.12.4/plugins/PaxHeaders/mod_websocket.lua0000644000000000000000000000011714475665104017403 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_websocket.lua0000644000175000017500000003044614475665104021611 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2012-2014 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- luacheck: ignore 431/log module:set_global(); local add_task = require "util.timer".add_task; local add_filter = require "util.filters".add_filter; local sha1 = require "util.hashes".sha1; local base64 = require "util.encodings".base64.encode; local st = require "util.stanza"; local parse_xml = require "util.xml".parse; local contains_token = require "util.http".contains_token; local portmanager = require "core.portmanager"; local sm_destroy_session = require"core.sessionmanager".destroy_session; local log = module._log; local dbuffer = require "util.dbuffer"; local websocket_frames = require"net.websocket.frames"; local parse_frame = websocket_frames.parse; local build_frame = websocket_frames.build; local build_close = websocket_frames.build_close; local parse_close = websocket_frames.parse_close; local t_concat = table.concat; local stanza_size_limit = module:get_option_number("c2s_stanza_size_limit", 1024 * 256); local frame_buffer_limit = module:get_option_number("websocket_frame_buffer_limit", 2 * stanza_size_limit); local frame_fragment_limit = module:get_option_number("websocket_frame_fragment_limit", 8); local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5); local consider_websocket_secure = module:get_option_boolean("consider_websocket_secure"); local cross_domain = module:get_option("cross_domain_websocket"); if cross_domain ~= nil then module:log("info", "The 'cross_domain_websocket' option has been deprecated"); end local xmlns_framing = "urn:ietf:params:xml:ns:xmpp-framing"; local xmlns_streams = "http://etherx.jabber.org/streams"; local xmlns_client = "jabber:client"; local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; module:depends("c2s") local sessions = module:shared("c2s/sessions"); local c2s_listener = portmanager.get_service("c2s").listener; --- Session methods local function session_open_stream(session, from, to) local attr = { xmlns = xmlns_framing, ["xml:lang"] = "en", version = "1.0", id = session.streamid or "", from = from or session.host, to = to, }; if session.stream_attrs then session:stream_attrs(from, to, attr) end session.send(st.stanza("open", attr)); end local function session_close(session, reason) local log = session.log or log; local close_event_payload = { session = session, reason = reason }; module:context(session.host):fire_event("pre-session-close", close_event_payload); reason = close_event_payload.reason; if session.conn then if session.notopen then session:open_stream(); end if reason then -- nil == no err, initiated by us, false == initiated by client local stream_error = st.stanza("stream:error"); if type(reason) == "string" then -- assume stream error stream_error:tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); elseif st.is_stanza(reason) then stream_error = reason; elseif type(reason) == "table" then if reason.condition then stream_error:tag(reason.condition, stream_xmlns_attr):up(); if reason.text then stream_error:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then stream_error:add_child(reason.extra); end end end log("debug", "Disconnecting client, is: %s", stream_error); session.send(stream_error); end session.send(st.stanza("close", { xmlns = xmlns_framing })); function session.send() return false; end -- luacheck: ignore 422/reason -- FIXME reason should be handled in common place local reason = (reason and (reason.name or reason.text or reason.condition)) or reason; session.log("debug", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason or "session closed"); -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote local conn = session.conn; if reason == nil and not session.notopen and session.type == "c2s" then -- Grace time to process data from authenticated cleanly-closed stream add_task(stream_close_timeout, function () if not session.destroyed then session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); sm_destroy_session(session, reason); conn:write(build_close(1000, "Stream closed")); conn:close(); end end); else sm_destroy_session(session, reason); conn:write(build_close(1000, "Stream closed")); conn:close(); end end end --- Filters local function filter_open_close(data) if not data:find(xmlns_framing, 1, true) then return data; end local oc = parse_xml(data); if not oc then return data; end if oc.attr.xmlns ~= xmlns_framing then return data; end if oc.name == "close" then return ""; end if oc.name == "open" then oc.name = "stream:stream"; oc.attr.xmlns = nil; oc.attr["xmlns:stream"] = xmlns_streams; return oc:top_tag(); end return data; end local default_get_response_text = "It works! Now point your WebSocket client to this URL to connect to Prosody." local websocket_get_response_text = module:get_option_string("websocket_get_response_text", default_get_response_text) local default_get_response_body = [[Websocket

]]..websocket_get_response_text..[[

]] local websocket_get_response_body = module:get_option_string("websocket_get_response_body", default_get_response_body) local function validate_frame(frame, max_length) local opcode, length = frame.opcode, frame.length; if max_length and length > max_length then return false, 1009, "Payload too large"; end -- Error cases if frame.RSV1 or frame.RSV2 or frame.RSV3 then -- Reserved bits non zero return false, 1002, "Reserved bits not zero"; end if opcode == 0x8 and frame.data then -- close frame if length == 1 then return false, 1002, "Close frame with payload, but too short for status code"; elseif length >= 2 then local status_code = parse_close(frame.data) if status_code < 1000 then return false, 1002, "Closed with invalid status code"; elseif ((status_code > 1003 and status_code < 1007) or status_code > 1011) and status_code < 3000 then return false, 1002, "Closed with reserved status code"; end end end if opcode >= 0x8 then if length > 125 then -- Control frame with too much payload return false, 1002, "Payload too large"; end if not frame.FIN then -- Fragmented control frame return false, 1002, "Fragmented control frame"; end end if (opcode > 0x2 and opcode < 0x8) or (opcode > 0xA) then return false, 1002, "Reserved opcode"; end -- Check opcode if opcode == 0x2 then -- Binary frame return false, 1003, "Only text frames are supported, RFC 7395 3.2"; elseif opcode == 0x8 then -- Close request return false, 1000, "Goodbye"; end -- Other (XMPP-specific) validity checks if not frame.FIN then return false, 1003, "Continuation frames are not supported, RFC 7395 3.3.3"; end if opcode == 0x01 and frame.data and frame.data:byte(1, 1) ~= 60 then return false, 1007, "Invalid payload start character, RFC 7395 3.3.3"; end return true; end function handle_request(event) local request, response = event.request, event.response; local conn = response.conn; conn.starttls = false; -- Prevent mod_tls from believing starttls can be done if not request.headers.sec_websocket_key or request.method ~= "GET" then return module:fire_event("http-message", { response = event.response; --- title = "Prosody WebSocket endpoint"; message = websocket_get_response_text; warning = not (consider_websocket_secure or request.secure) and "This endpoint is not considered secure!" or nil; }) or websocket_get_response_body; end local wants_xmpp = contains_token(request.headers.sec_websocket_protocol or "", "xmpp"); if not wants_xmpp then module:log("debug", "Client didn't want to talk XMPP, list of protocols was %s", request.headers.sec_websocket_protocol or "(empty)"); return 501; end local function websocket_close(code, message) conn:write(build_close(code, message)); conn:close(); end local function websocket_handle_error(session, code, message) if code == 1009 then -- stanza size limit exceeded -- we close the session, rather than the connection, -- otherwise a resuming client will simply resend the -- offending stanza session:close({ condition = "policy-violation", text = "stanza too large" }); else websocket_close(code, message); end end local function handle_frame(frame) module:log("debug", "Websocket received frame: opcode=%0x, %i bytes", frame.opcode, #frame.data); -- Check frame makes sense local frame_ok, err_status, err_text = validate_frame(frame, stanza_size_limit); if not frame_ok then return frame_ok, err_status, err_text; end local opcode = frame.opcode; if opcode == 0x9 then -- Ping frame frame.opcode = 0xA; frame.MASK = false; -- Clients send masked frames, servers don't, see #1484 conn:write(build_frame(frame)); return ""; elseif opcode == 0xA then -- Pong frame, MAY be sent unsolicited, eg as keepalive return ""; elseif opcode ~= 0x1 then -- Not text frame (which is all we support) log("warn", "Received frame with unsupported opcode %i", opcode); return ""; end return frame.data; end conn:setlistener(c2s_listener); c2s_listener.onconnect(conn); local session = sessions[conn]; -- Use upstream IP if a HTTP proxy was used -- See mod_http and #540 session.ip = request.ip; session.secure = consider_websocket_secure or request.secure or session.secure; session.websocket_request = request; session.open_stream = session_open_stream; session.close = session_close; local frameBuffer = dbuffer.new(frame_buffer_limit, frame_fragment_limit); add_filter(session, "bytes/in", function(data) if not frameBuffer:write(data) then session.log("warn", "websocket frame buffer full - terminating session"); session:close({ condition = "resource-constraint", text = "frame buffer exceeded" }); return; end local cache = {}; local frame, length, partial = parse_frame(frameBuffer); while frame do frameBuffer:discard(length); local result, err_status, err_text = handle_frame(frame); if not result then websocket_handle_error(session, err_status, err_text); break; end cache[#cache+1] = filter_open_close(result); frame, length, partial = parse_frame(frameBuffer); end if partial then -- The header of the next frame is already in the buffer, run -- some early validation here local frame_ok, err_status, err_text = validate_frame(partial, stanza_size_limit); if not frame_ok then websocket_handle_error(session, err_status, err_text); end end return t_concat(cache, ""); end); add_filter(session, "stanzas/out", function(stanza) stanza = st.clone(stanza); local attr = stanza.attr; attr.xmlns = attr.xmlns or xmlns_client; if stanza.name:find("^stream:") then attr["xmlns:stream"] = attr["xmlns:stream"] or xmlns_streams; end return stanza; end, -1000); add_filter(session, "bytes/out", function(data) return build_frame({ FIN = true, opcode = 0x01, data = tostring(data)}); end); response.status_code = 101; response.headers.upgrade = "websocket"; response.headers.connection = "Upgrade"; response.headers.sec_webSocket_accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); response.headers.sec_webSocket_protocol = "xmpp"; module:fire_event("websocket-session", { session = session, request = request }); session.log("debug", "Sending WebSocket handshake"); return ""; end local function keepalive(event) local session = event.session; if session.open_stream == session_open_stream then return session.conn:write(build_frame({ opcode = 0x9, FIN = true })); end end function module.add_host(module) module:hook("c2s-read-timeout", keepalive, -0.9); module:depends("http"); module:provides("http", { name = "websocket"; default_path = "xmpp-websocket"; cors = { enabled = true; }; route = { ["GET"] = handle_request; ["GET /"] = handle_request; }; }); module:hook("c2s-read-timeout", keepalive, -0.9); end if require"core.modulemanager".get_modules_for_host("*"):contains(module.name) then module:add_host(); end prosody-0.12.4/plugins/PaxHeaders/mod_welcome.lua0000644000000000000000000000011714475665104017050 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_welcome.lua0000644000175000017500000000126214475665104021250 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local host = module:get_host(); local welcome_text = module:get_option_string("welcome_message", "Hello $username, welcome to the $host IM server!"); local st = require "util.stanza"; module:hook("user-registered", function (user) local welcome_stanza = st.message({ to = user.username.."@"..user.host, from = host }, welcome_text:gsub("$(%w+)", user)); module:send(welcome_stanza); module:log("debug", "Welcomed user %s@%s", user.username, user.host); end); prosody-0.12.4/plugins/PaxHeaders/mod_windows.lua0000644000000000000000000000011714475665104017107 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/mod_windows.lua0000644000175000017500000000013014475665104021300 0ustar00prosodyprosody00000000000000-- Windows platform stub module:set_global(); -- TODO Add Windows-specific things here prosody-0.12.4/plugins/PaxHeaders/muc0000644000000000000000000000013114475665104014556 xustar0030 mtime=1693936196.840763501 29 atime=1693936196.68476503 30 ctime=1693936196.696764913 prosody-0.12.4/plugins/muc/0000755000175000017500000000000014475665104017036 5ustar00prosodyprosody00000000000000prosody-0.12.4/plugins/muc/PaxHeaders/config_form_sections.lib.lua0000644000000000000000000000011714475665104022306 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/muc/config_form_sections.lib.lua0000644000175000017500000000106214475665104024504 0ustar00prosodyprosody00000000000000module:hook("muc-config-form", function(event) table.insert(event.form, { type = "fixed"; value = "Room information"; }); end, 100); module:hook("muc-config-form", function(event) table.insert(event.form, { type = "fixed"; value = "Access to the room"; }); end, 90); module:hook("muc-config-form", function(event) table.insert(event.form, { type = "fixed"; value = "Permissions in the room"; }); end, 80); module:hook("muc-config-form", function(event) table.insert(event.form, { type = "fixed"; value = "Other options"; }); end, 70); prosody-0.12.4/plugins/muc/PaxHeaders/description.lib.lua0000644000000000000000000000011714475665104020432 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.68476503 prosody-0.12.4/plugins/muc/description.lib.lua0000644000175000017500000000251614475665104022635 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local function get_description(room) return room._data.description; end local function set_description(room, description) if description == "" then description = nil; end if get_description(room) == description then return false; end room._data.description = description; return true; end local function add_disco_form(event) table.insert(event.form, { name = "muc#roominfo_description"; label = "Description"; value = ""; }); event.formdata["muc#roominfo_description"] = get_description(event.room); end local function add_form_option(event) table.insert(event.form, { name = "muc#roomconfig_roomdesc"; type = "text-single"; label = "Description"; desc = "A brief description of the room"; value = get_description(event.room) or ""; }); end module:hook("muc-disco#info", add_disco_form); module:hook("muc-config-form", add_form_option, 100-2); module:hook("muc-config-submitted/muc#roomconfig_roomdesc", function(event) if set_description(event.room, event.value) then event.status_codes["104"] = true; end end); return { get = get_description; set = set_description; }; prosody-0.12.4/plugins/muc/PaxHeaders/hats.lib.lua0000644000000000000000000000012014475665104017040 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.688764991 prosody-0.12.4/plugins/muc/hats.lib.lua0000644000175000017500000000151614475665104021250 0ustar00prosodyprosody00000000000000local st = require "util.stanza"; local muc_util = module:require "muc/util"; local xmlns_hats = "xmpp:prosody.im/protocol/hats:1"; -- Strip any hats claimed by the client (to prevent spoofing) muc_util.add_filtered_namespace(xmlns_hats); module:hook("muc-build-occupant-presence", function (event) local bare_jid = event.occupant and event.occupant.bare_jid or event.bare_jid; local aff_data = event.room:get_affiliation_data(bare_jid); local hats = aff_data and aff_data.hats; if not hats then return; end local hats_el; for hat_id, hat_data in pairs(hats) do if hat_data.active then if not hats_el then hats_el = st.stanza("hats", { xmlns = xmlns_hats }); end hats_el:tag("hat", { uri = hat_id, title = hat_data.title }):up(); end end if not hats_el then return; end event.stanza:add_direct_child(hats_el); end); prosody-0.12.4/plugins/muc/PaxHeaders/hidden.lib.lua0000644000000000000000000000012014475665104017334 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.688764991 prosody-0.12.4/plugins/muc/hidden.lib.lua0000644000175000017500000000306214475665104021542 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local restrict_public = not module:get_option_boolean("muc_room_allow_public", true); local um_is_admin = require "core.usermanager".is_admin; local function get_hidden(room) return room._data.hidden; end local function set_hidden(room, hidden) hidden = hidden and true or nil; if get_hidden(room) == hidden then return false; end room._data.hidden = hidden; return true; end module:hook("muc-config-form", function(event) if restrict_public and not um_is_admin(event.actor, module.host) then -- Don't show option if public rooms are restricted and user is not admin of this host return; end table.insert(event.form, { name = "muc#roomconfig_publicroom"; type = "boolean"; label = "Include room information in public lists"; desc = "Enable this to allow people to find the room"; value = not get_hidden(event.room); }); end, 100-9); module:hook("muc-config-submitted/muc#roomconfig_publicroom", function(event) if restrict_public and not um_is_admin(event.actor, module.host) then return; -- Not allowed end if set_hidden(event.room, not event.value) then event.status_codes["104"] = true; end end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = get_hidden(event.room) and "muc_hidden" or "muc_public"}):up(); end); return { get = get_hidden; set = set_hidden; }; prosody-0.12.4/plugins/muc/PaxHeaders/history.lib.lua0000644000000000000000000000012014475665104017602 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.688764991 prosody-0.12.4/plugins/muc/history.lib.lua0000644000175000017500000001563214475665104022016 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local gettime = os.time; local datetime = require "util.datetime"; local st = require "util.stanza"; local default_history_length = 20; local max_history_length = module:get_option_number("max_history_messages", math.huge); local function set_max_history_length(_max_history_length) max_history_length = _max_history_length or math.huge; end local function get_historylength(room) return math.min(room._data.history_length or default_history_length, max_history_length); end local function set_historylength(room, length) if length then length = assert(tonumber(length), "Length not a valid number"); end if length == default_history_length then length = nil; end room._data.history_length = length; return true; end -- Fix for clients who don't support XEP-0045 correctly -- Default number of history messages the room returns local function get_defaulthistorymessages(room) return room._data.default_history_messages or default_history_length; end local function set_defaulthistorymessages(room, number) number = math.min(tonumber(number) or default_history_length, room._data.history_length or default_history_length); if number == default_history_length then number = nil; end room._data.default_history_messages = number; end module:hook("muc-config-form", function(event) table.insert(event.form, { name = "muc#roomconfig_historylength"; type = "text-single"; datatype = "xs:integer"; label = "Maximum number of history messages returned by room"; desc = "Specify the maximum number of previous messages that should be sent to users when they join the room"; value = get_historylength(event.room); }); table.insert(event.form, { name = 'muc#roomconfig_defaulthistorymessages', type = 'text-single', datatype = "xs:integer"; label = 'Default number of history messages returned by room', desc = "Specify the number of previous messages sent to new users when they join the room"; value = get_defaulthistorymessages(event.room); }); end, 70-5); module:hook("muc-config-submitted/muc#roomconfig_historylength", function(event) if set_historylength(event.room, event.value) then event.status_codes["104"] = true; end end); module:hook("muc-config-submitted/muc#roomconfig_defaulthistorymessages", function(event) if set_defaulthistorymessages(event.room, event.value) then event.status_codes["104"] = true; end end); local function parse_history(stanza) local x_tag = stanza:get_child("x", "http://jabber.org/protocol/muc"); local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); if not history_tag then return nil, nil, nil; end local maxchars = tonumber(history_tag.attr.maxchars); local maxstanzas = tonumber(history_tag.attr.maxstanzas); -- messages received since the UTC datetime specified local since = history_tag.attr.since; if since then since = datetime.parse(since); end -- messages received in the last "X" seconds. local seconds = tonumber(history_tag.attr.seconds); if seconds then seconds = gettime() - seconds; if since then since = math.max(since, seconds); else since = seconds; end end return maxchars, maxstanzas, since; end module:hook("muc-get-history", function(event) local room = event.room; local history = room._history; -- send discussion history if not history then return nil end local history_len = #history; local to = event.to; local maxchars = event.maxchars; local maxstanzas = event.maxstanzas or history_len; local since = event.since; local n = 0; local charcount = 0; for i=history_len,1,-1 do local entry = history[i]; if maxchars then if not entry.chars then entry.stanza.attr.to = ""; entry.chars = #tostring(entry.stanza); end charcount = charcount + entry.chars + #to; if charcount > maxchars then break; end end if since and since > entry.timestamp then break; end if n + 1 > maxstanzas then break; end n = n + 1; end local i = history_len-n+1 function event.next_stanza() if i > history_len then return nil end local entry = history[i]; local msg = entry.stanza; msg.attr.to = to; i = i + 1; return msg; end return true; end, -1); local function send_history(room, stanza) local maxchars, maxstanzas, since = parse_history(stanza); if not(maxchars or maxstanzas or since) then maxstanzas = get_defaulthistorymessages(room); end local event = { room = room; stanza = stanza; to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars` maxchars = maxchars, maxstanzas = maxstanzas, since = since; next_stanza = function() end; -- events should define this iterator }; module:fire_event("muc-get-history", event); for msg in event.next_stanza, event do room:route_stanza(msg); end end -- Send history on join module:hook("muc-occupant-session-new", function(event) send_history(event.room, event.stanza); end, 50); -- Before subject(20) -- add to history module:hook("muc-add-history", function(event) local room = event.room if get_historylength(room) == 0 then room._history = nil; return; end local history = room._history; if not history then history = {}; room._history = history; end local stanza = st.clone(event.stanza); stanza.attr.to = ""; local ts = gettime(); local stamp = datetime.datetime(ts); stanza:tag("delay", { -- XEP-0203 xmlns = "urn:xmpp:delay", from = room.jid, stamp = stamp }):up(); local entry = { stanza = stanza, timestamp = ts }; table.insert(history, entry); while #history > get_historylength(room) do table.remove(history, 1) end return true; end, -1); -- Have a single muc-add-history event, so that plugins can mark it -- as handled without stopping other muc-broadcast-message handlers module:hook("muc-broadcast-message", function(event) if module:fire_event("muc-message-is-historic", event) then module:fire_event("muc-add-history", event); end end); module:hook("muc-message-is-historic", function (event) local stanza = event.stanza; if stanza:get_child("no-store", "urn:xmpp:hints") or stanza:get_child("no-permanent-store", "urn:xmpp:hints") then -- XXX Experimental XEP return false, "hint"; end if stanza:get_child("store", "urn:xmpp:hints") then return true, "hint"; end if stanza:get_child("body") then return true; end if stanza:get_child("encryption", "urn:xmpp:eme:0") then -- Since we can't know what an encrypted message contains, we assume it's important -- XXX Experimental XEP return true, "encrypted"; end if stanza:get_child(nil, "urn:xmpp:chat-markers:0") then -- XXX Experimental XEP return true, "marker"; end end, -1); return { set_max_length = set_max_history_length; parse_history = parse_history; send = send_history; get_length = get_historylength; set_length = set_historylength; }; prosody-0.12.4/plugins/muc/PaxHeaders/language.lib.lua0000644000000000000000000000012014475665104017664 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.688764991 prosody-0.12.4/plugins/muc/language.lib.lua0000644000175000017500000000250714475665104022075 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local function get_language(room) return room._data.language; end local function set_language(room, language) if language == "" then language = nil; end if get_language(room) == language then return false; end room._data.language = language; return true; end local function add_disco_form(event) table.insert(event.form, { name = "muc#roominfo_lang"; value = ""; }); event.formdata["muc#roominfo_lang"] = get_language(event.room); end local function add_form_option(event) table.insert(event.form, { name = "muc#roomconfig_lang"; label = "Language tag for room (e.g. 'en', 'de', 'fr' etc.)"; type = "text-single"; desc = "Indicate the primary language spoken in this room"; datatype = "xs:language"; value = get_language(event.room) or ""; }); end module:hook("muc-disco#info", add_disco_form); module:hook("muc-config-form", add_form_option, 100-3); module:hook("muc-config-submitted/muc#roomconfig_lang", function(event) if set_language(event.room, event.value) then event.status_codes["104"] = true; end end); return { get = get_language; set = set_language; }; prosody-0.12.4/plugins/muc/PaxHeaders/lock.lib.lua0000644000000000000000000000012014475665104017031 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.688764991 prosody-0.12.4/plugins/muc/lock.lib.lua0000644000175000017500000000327314475665104021243 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local lock_rooms = module:get_option_boolean("muc_room_locking", true); local lock_room_timeout = module:get_option_number("muc_room_lock_timeout", 300); local function lock(room) module:fire_event("muc-room-locked", {room = room;}); room._data.locked = os.time() + lock_room_timeout; end local function unlock(room) module:fire_event("muc-room-unlocked", {room = room;}); room._data.locked = nil; end local function is_locked(room) local ts = room._data.locked; if ts then if os.time() < ts then return true; end unlock(room); end return false; end if lock_rooms then module:hook("muc-room-pre-create", function(event) -- Older groupchat protocol doesn't lock if not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then return end -- Lock room at creation local room = event.room; lock(room); end, 10); end -- Don't let users into room while it is locked module:hook("muc-occupant-pre-join", function(event) if not event.is_new_room and is_locked(event.room) then -- Deny entry module:log("debug", "Room is locked, denying entry"); event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found", nil, module.host)); return true; end end, -30); -- When config is submitted; unlock the room module:hook("muc-config-submitted", function(event) if is_locked(event.room) then unlock(event.room); end end, -1); return { lock = lock; unlock = unlock; is_locked = is_locked; }; prosody-0.12.4/plugins/muc/PaxHeaders/members_only.lib.lua0000644000000000000000000000012014475665104020574 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.688764991 prosody-0.12.4/plugins/muc/members_only.lib.lua0000644000175000017500000001407514475665104023010 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local muc_util = module:require "muc/util"; local valid_affiliations = muc_util.valid_affiliations; local function get_members_only(room) return room._data.members_only; end local function set_members_only(room, members_only) members_only = members_only and true or nil; if room._data.members_only == members_only then return false; end room._data.members_only = members_only; if members_only then --[[ If as a result of a change in the room configuration the room type is changed to members-only but there are non-members in the room, the service MUST remove any non-members from the room and include a status code of 322 in the presence unavailable stanzas sent to those users as well as any remaining occupants. ]] local occupants_changed = {}; for _, occupant in room:each_occupant() do local affiliation = room:get_affiliation(occupant.bare_jid); if valid_affiliations[affiliation or "none"] <= valid_affiliations.none then occupant.role = nil; room:save_occupant(occupant); occupants_changed[occupant] = true; end end local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("status", {code="322"}):up(); for occupant in pairs(occupants_changed) do room:publicise_occupant_status(occupant, x); module:fire_event("muc-occupant-left", {room = room; nick = occupant.nick; occupant = occupant;}); end end return true; end local function get_allow_member_invites(room) return room._data.allow_member_invites; end -- Allows members to invite new members into a members-only room, -- effectively creating an invite-only room local function set_allow_member_invites(room, allow_member_invites) allow_member_invites = allow_member_invites and true or nil; if room._data.allow_member_invites == allow_member_invites then return false; end room._data.allow_member_invites = allow_member_invites; return true; end module:hook("muc-disco#info", function(event) local members_only_room = not not get_members_only(event.room); local members_can_invite = not not get_allow_member_invites(event.room); event.reply:tag("feature", {var = members_only_room and "muc_membersonly" or "muc_open"}):up(); table.insert(event.form, { name = "{http://prosody.im/protocol/muc}roomconfig_allowmemberinvites"; label = "Allow members to invite new members"; type = "boolean"; value = members_can_invite; }); table.insert(event.form, { name = "muc#roomconfig_allowinvites"; label = "Allow users to invite other users"; type = "boolean"; value = not members_only_room or members_can_invite; }); end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = "muc#roomconfig_membersonly"; type = "boolean"; label = "Only allow members to join"; desc = "Enable this to only allow access for room owners, admins and members"; value = get_members_only(event.room); }); table.insert(event.form, { name = "{http://prosody.im/protocol/muc}roomconfig_allowmemberinvites"; type = "boolean"; label = "Allow members to invite new members"; value = get_allow_member_invites(event.room); }); end, 90-3); module:hook("muc-config-submitted/muc#roomconfig_membersonly", function(event) if set_members_only(event.room, event.value) then event.status_codes["104"] = true; end end); module:hook("muc-config-submitted/{http://prosody.im/protocol/muc}roomconfig_allowmemberinvites", function(event) if set_allow_member_invites(event.room, event.value) then event.status_codes["104"] = true; end end); -- No affiliation => role of "none" module:hook("muc-get-default-role", function(event) if not event.affiliation and get_members_only(event.room) then return false; end end, 2); -- registration required for entering members-only room module:hook("muc-occupant-pre-join", function(event) local room = event.room; if get_members_only(room) then local stanza = event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); if valid_affiliations[affiliation or "none"] <= valid_affiliations.none then local reply = st.error_reply(stanza, "auth", "registration-required", nil, room.jid):up(); event.origin.send(reply); return true; end end end, -5); -- Invitation privileges in members-only rooms SHOULD be restricted to room admins; -- if a member without privileges to edit the member list attempts to invite another user -- the service SHOULD return a error to the occupant module:hook("muc-pre-invite", function(event) local room = event.room; if get_members_only(room) then local stanza = event.stanza; local inviter_affiliation = room:get_affiliation(stanza.attr.from) or "none"; local required_affiliation = room._data.allow_member_invites and "member" or "admin"; if valid_affiliations[inviter_affiliation] < valid_affiliations[required_affiliation] then event.origin.send(st.error_reply(stanza, "auth", "forbidden", nil, room.jid)); return true; end end end); -- When an invite is sent; add an affiliation for the invitee module:hook("muc-invite", function(event) local room = event.room; if get_members_only(room) then local stanza = event.stanza; local invitee = stanza.attr.to; local affiliation = room:get_affiliation(invitee); local invited_unaffiliated = valid_affiliations[affiliation or "none"] <= valid_affiliations.none; if invited_unaffiliated then local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user") :get_child("invite").attr.from; module:log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); -- This might fail; ignore for now room:set_affiliation(true, invitee, "member", "Invited by " .. from); room:save(); end end end); return { get = get_members_only; set = set_members_only; get_allow_member_invites = get_allow_member_invites; set_allow_member_invites = set_allow_member_invites; }; prosody-0.12.4/plugins/muc/PaxHeaders/mod_muc.lua0000644000000000000000000000012014475665104016757 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.688764991 prosody-0.12.4/plugins/muc/mod_muc.lua0000644000175000017500000005017514475665104021174 0ustar00prosodyprosody00000000000000-- 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. -- -- Exposed functions: -- -- create_room(jid) -> room -- track_room(room) -- delete_room(room) -- forget_room(room) -- get_room_from_jid(jid) -> room -- each_room(live_only) -> () -> room [DEPRECATED] -- all_rooms() -> room -- live_rooms() -> room -- shutdown_component() if module:get_host_type() ~= "component" then error("MUC should be loaded as a component, please see https://prosody.im/doc/components", 0); end local muclib = module:require "muc"; room_mt = muclib.room_mt; -- Yes, global. new_room = muclib.new_room; local name = module:require "muc/name"; room_mt.get_name = name.get; room_mt.set_name = name.set; local description = module:require "muc/description"; room_mt.get_description = description.get; room_mt.set_description = description.set; local language = module:require "muc/language"; room_mt.get_language = language.get; room_mt.set_language = language.set; local hidden = module:require "muc/hidden"; room_mt.get_hidden = hidden.get; room_mt.set_hidden = hidden.set; function room_mt:get_public() return not self:get_hidden(); end function room_mt:set_public(public) return self:set_hidden(not public); end local password = module:require "muc/password"; room_mt.get_password = password.get; room_mt.set_password = password.set; local members_only = module:require "muc/members_only"; room_mt.get_members_only = members_only.get; room_mt.set_members_only = members_only.set; room_mt.get_allow_member_invites = members_only.get_allow_member_invites; room_mt.set_allow_member_invites = members_only.set_allow_member_invites; local moderated = module:require "muc/moderated"; room_mt.get_moderated = moderated.get; room_mt.set_moderated = moderated.set; local request = module:require "muc/request"; room_mt.handle_role_request = request.handle_request; local persistent = module:require "muc/persistent"; room_mt.get_persistent = persistent.get; room_mt.set_persistent = persistent.set; local subject = module:require "muc/subject"; room_mt.get_changesubject = subject.get_changesubject; room_mt.set_changesubject = subject.set_changesubject; room_mt.get_subject = subject.get; room_mt.set_subject = subject.set; room_mt.send_subject = subject.send; local history = module:require "muc/history"; room_mt.send_history = history.send; room_mt.get_historylength = history.get_length; room_mt.set_historylength = history.set_length; local register = module:require "muc/register"; room_mt.get_registered_nick = register.get_registered_nick; room_mt.get_registered_jid = register.get_registered_jid; room_mt.handle_register_iq = register.handle_register_iq; local presence_broadcast = module:require "muc/presence_broadcast"; room_mt.get_presence_broadcast = presence_broadcast.get; room_mt.set_presence_broadcast = presence_broadcast.set; room_mt.get_valid_broadcast_roles = presence_broadcast.get_valid_broadcast_roles; local occupant_id = module:require "muc/occupant_id"; room_mt.get_salt = occupant_id.get_room_salt; room_mt.get_occupant_id = occupant_id.get_occupant_id; local jid_split = require "util.jid".split; local jid_prep = require "util.jid".prep; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; local cache = require "util.cache"; local um_is_admin = require "core.usermanager".is_admin; module:require "muc/config_form_sections"; module:depends("disco"); module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); module:add_feature("http://jabber.org/protocol/muc"); module:depends "muc_unique" module:require "muc/hats"; module:require "muc/lock"; local function is_admin(jid) return um_is_admin(jid, module.host); end if module:get_option_boolean("component_admins_as_room_owners", true) then -- Monkey patch to make server admins room owners local _get_affiliation = room_mt.get_affiliation; function room_mt:get_affiliation(jid) if is_admin(jid) then return "owner"; end return _get_affiliation(self, jid); end local _set_affiliation = room_mt.set_affiliation; function room_mt:set_affiliation(actor, jid, affiliation, reason, data) if affiliation ~= "owner" and is_admin(jid) then return nil, "modify", "not-acceptable"; end return _set_affiliation(self, actor, jid, affiliation, reason, data); end end local persistent_rooms_storage = module:open_store("persistent"); local persistent_rooms = module:open_store("persistent", "map"); local room_configs = module:open_store("config"); local room_state = module:open_store("state"); local room_items_cache = {}; local function room_save(room, forced, savestate) local node = jid_split(room.jid); local is_persistent = persistent.get(room); if room:get_public() then room_items_cache[room.jid] = room:get_name() or ""; else room_items_cache[room.jid] = nil; end if is_persistent or savestate then persistent_rooms:set(nil, room.jid, true); local data, state = room:freeze(savestate); room_state:set(node, state); return room_configs:set(node, data); elseif forced then persistent_rooms:set(nil, room.jid, nil); room_state:set(node, nil); return room_configs:set(node, nil); end end local max_rooms = module:get_option_number("muc_max_rooms"); local max_live_rooms = module:get_option_number("muc_room_cache_size", 100); local room_hit = module:measure("room_hit", "rate"); local room_miss = module:measure("room_miss", "rate") local room_eviction = module:measure("room_eviction", "rate"); local rooms = cache.new(max_rooms or max_live_rooms, function (jid, room) if max_rooms then module:log("info", "Room limit of %d reached, no new rooms allowed", max_rooms); return false; end module:log("debug", "Evicting room %s", jid); room_eviction(); if room:get_public() then room_items_cache[room.jid] = room:get_name() or ""; else room_items_cache[room.jid] = nil; end local ok, err = room_save(room, nil, true); -- Force to disk if not ok then module:log("error", "Failed to swap inactive room %s to disk: %s", jid, err); return false; end end); local measure_rooms_size = module:measure("live_room", "amount"); module:hook_global("stats-update", function () measure_rooms_size(rooms:count()); end); -- Automatically destroy empty non-persistent rooms module:hook("muc-occupant-left",function(event) local room = event.room if room.destroying then return end if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room module:log("debug", "%q empty, destroying", room.jid); module:fire_event("muc-room-destroyed", { room = room }); end end, -1); function track_room(room) if rooms:set(room.jid, room) then -- When room is created, over-ride 'save' method room.save = room_save; return room; end -- Resource limit reached return false; end local function handle_broken_room(room, origin, stanza) module:log("debug", "Returning error from broken room %s", room.jid); origin.send(st.error_reply(stanza, "wait", "internal-server-error", nil, room.jid)); return true; end local function restore_room(jid) local node = jid_split(jid); local data, err = room_configs:get(node); if data then module:log("debug", "Restoring room %s from storage", jid); if module:fire_event("muc-room-pre-restore", { jid = jid, data = data }) == false then return false; end local state, s_err = room_state:get(node); if not state and s_err then module:log("debug", "Could not restore state of room %s: %s", jid, s_err); end local room = muclib.restore_room(data, state); if track_room(room) then room_state:set(node, nil); module:fire_event("muc-room-restored", { jid = jid, room = room }); return room; else return false; end elseif err then module:log("error", "Error restoring room %s from storage: %s", jid, err); local room = muclib.new_room(jid, { locked = math.huge }); room.handle_normal_presence = handle_broken_room; room.handle_first_presence = handle_broken_room; return room; end end -- Removes a room from memory, without saving it (save first if required) function forget_room(room) module:log("debug", "Forgetting %s", room.jid); rooms.save = nil; rooms:set(room.jid, nil); end -- Removes a room from the database (may remain in memory) function delete_room(room) module:log("debug", "Deleting %s", room.jid); room_configs:set(jid_split(room.jid), nil); room_state:set(jid_split(room.jid), nil); persistent_rooms:set(nil, room.jid, nil); room_items_cache[room.jid] = nil; end function module.unload() for room in live_rooms() do room:save(nil, true); forget_room(room); end end function get_room_from_jid(room_jid) local room = rooms:get(room_jid); if room then room_hit(); rooms:set(room_jid, room); -- bump to top; return room; end room_miss(); return restore_room(room_jid); end local function set_room_defaults(room, lang) room:set_public(module:get_option_boolean("muc_room_default_public", false)); room:set_persistent(module:get_option_boolean("muc_room_default_persistent", room:get_persistent())); room:set_members_only(module:get_option_boolean("muc_room_default_members_only", room:get_members_only())); room:set_allow_member_invites(module:get_option_boolean("muc_room_default_allow_member_invites", room:get_allow_member_invites())); room:set_moderated(module:get_option_boolean("muc_room_default_moderated", room:get_moderated())); room:set_whois(module:get_option_boolean("muc_room_default_public_jids", room:get_whois() == "anyone") and "anyone" or "moderators"); room:set_changesubject(module:get_option_boolean("muc_room_default_change_subject", room:get_changesubject())); room:set_historylength(module:get_option_number("muc_room_default_history_length", room:get_historylength())); room:set_language(lang or module:get_option_string("muc_room_default_language")); room:set_presence_broadcast(module:get_option("muc_room_default_presence_broadcast", room:get_presence_broadcast())); end function create_room(room_jid, config) if jid_bare(room_jid) ~= room_jid or not jid_prep(room_jid, true) then return nil, "invalid-jid"; end local exists = get_room_from_jid(room_jid); if exists then return nil, "room-exists"; end local room = muclib.new_room(room_jid, config); if not config then set_room_defaults(room); end module:fire_event("muc-room-created", { room = room; }); return track_room(room); end function all_rooms() return coroutine.wrap(function () local seen = {}; -- Don't iterate over persistent rooms twice for room in live_rooms() do coroutine.yield(room); seen[room.jid] = true; end local all_persistent_rooms, err = persistent_rooms_storage:get(nil); if not all_persistent_rooms then if err then module:log("error", "Error loading list of persistent rooms, only rooms live in memory were iterated over"); module:log("debug", "%s", debug.traceback(err)); end return nil; end for room_jid in pairs(all_persistent_rooms) do if not seen[room_jid] then local room = restore_room(room_jid); if room then coroutine.yield(room); else module:log("error", "Missing data for room '%s', omitting from iteration", room_jid); end end end end); end function live_rooms() return rooms:values(); end function each_room(live_only) if live_only then return live_rooms(); end return all_rooms(); end module:hook("host-disco-items", function(event) local reply = event.reply; module:log("debug", "host-disco-items called"); if next(room_items_cache) ~= nil then for jid, room_name in pairs(room_items_cache) do if room_name == "" then room_name = nil; end reply:tag("item", { jid = jid, name = room_name }):up(); end else for room in all_rooms() do if not room:get_hidden() then local jid, room_name = room.jid, room:get_name(); room_items_cache[jid] = room_name or ""; reply:tag("item", { jid = jid, name = room_name }):up(); end end end end); module:hook("muc-room-pre-create", function (event) set_room_defaults(event.room, event.stanza.attr["xml:lang"]); end, 1); module:hook("muc-room-pre-create", function(event) local origin, stanza = event.origin, event.stanza; if not track_room(event.room) then origin.send(st.error_reply(stanza, "wait", "resource-constraint", nil, module.host)); return true; end end, -1000); module:hook("muc-room-destroyed",function(event) local room = event.room; forget_room(room); delete_room(room); end); if module:get_option_boolean("muc_tombstones", true) then local ttl = module:get_option_number("muc_tombstone_expiry", 86400 * 31); module:hook("muc-room-destroyed",function(event) local room = event.room; if not room:get_persistent() then return end if room._data.destroyed then return -- Allow destruction of tombstone end local tombstone = new_room(room.jid, { locked = os.time() + ttl; destroyed = true; reason = event.reason; newjid = event.newjid; -- password? }); tombstone.save = room_save; tombstone:set_persistent(true); tombstone:set_hidden(true); tombstone:save(true); return true; end, -10); end do local restrict_room_creation = module:get_option("restrict_room_creation"); if restrict_room_creation == true then restrict_room_creation = "admin"; end if restrict_room_creation then local host_suffix = module.host:gsub("^[^%.]+%.", ""); module:hook("muc-room-pre-create", function(event) local origin, stanza = event.origin, event.stanza; local user_jid = stanza.attr.from; if not is_admin(user_jid) and not ( restrict_room_creation == "local" and select(2, jid_split(user_jid)) == host_suffix ) then origin.send(st.error_reply(stanza, "cancel", "not-allowed", "Room creation is restricted", module.host)); return true; end end); end end for event_name, method in pairs { -- Normal room interactions ["iq-get/bare/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; ["iq-get/bare/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; ["iq-set/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ; ["iq-get/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_get_command" ; ["iq-set/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; ["iq-get/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; ["message/bare"] = "handle_message_to_room" ; ["presence/bare"] = "handle_presence_to_room" ; ["iq/bare/jabber:iq:register:query"] = "handle_register_iq"; -- Host room ["iq-get/host/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; ["iq-get/host/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; ["iq-set/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ; ["iq-get/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_get_command" ; ["iq-set/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; ["iq-get/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; ["message/host"] = "handle_message_to_room" ; ["presence/host"] = "handle_presence_to_room" ; -- Direct to occupant (normal rooms and host room) ["presence/full"] = "handle_presence_to_occupant" ; ["iq/full"] = "handle_iq_to_occupant" ; ["message/full"] = "handle_message_to_occupant" ; } do module:hook(event_name, function (event) local origin, stanza = event.origin, event.stanza; local room_jid = jid_bare(stanza.attr.to); local room = get_room_from_jid(room_jid); if room and room._data.destroyed then if room._data.locked < os.time() or (is_admin(stanza.attr.from) and stanza.name == "presence" and stanza.attr.type == nil) then -- Allow the room to be recreated by admin or after time has passed delete_room(room); room = nil; else if stanza.attr.type ~= "error" then local reply = st.error_reply(stanza, "cancel", "gone", room._data.reason, module.host) if room._data.newjid then local uri = "xmpp:"..room._data.newjid.."?join"; reply:get_child("error"):child_with_name("gone"):text(uri); end event.origin.send(reply); end return true; end end if room == nil then -- Watch presence to create rooms if not jid_prep(room_jid, true) then origin.send(st.error_reply(stanza, "modify", "jid-malformed", nil, module.host)); return true; end if stanza.attr.type == nil and stanza.name == "presence" and stanza:get_child("x", "http://jabber.org/protocol/muc") then room = muclib.new_room(room_jid); return room:handle_first_presence(origin, stanza); elseif stanza.attr.type ~= "error" then origin.send(st.error_reply(stanza, "cancel", "item-not-found", nil, module.host)); return true; else return; end elseif room == false then -- Error loading room origin.send(st.error_reply(stanza, "wait", "resource-constraint", nil, module.host)); return true; end return room[method](room, origin, stanza); end, -2) end function shutdown_component() for room in live_rooms() do room:save(nil, true); end end module:hook_global("server-stopping", shutdown_component, -300); do -- Ad-hoc commands module:depends "adhoc"; local t_concat = table.concat; local adhoc_new = module:require "adhoc".new; local adhoc_initial = require "util.adhoc".new_initial_data_form; local adhoc_simple = require "util.adhoc".new_simple_form; local array = require "util.array"; local dataforms_new = require "util.dataforms".new; local destroy_rooms_layout = dataforms_new { title = "Destroy rooms"; instructions = "Select the rooms to destroy"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" }; { name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"}; }; local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() return { rooms = array.collect(all_rooms()):pluck("jid"):sort(); }; end, function(fields, errors) if errors then local errmsg = {}; for field, err in pairs(errors) do errmsg[#errmsg + 1] = field .. ": " .. err; end return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end local destroyed = array(); for _, room_jid in ipairs(fields.rooms) do local room = get_room_from_jid(room_jid); if room and room:destroy() then destroyed:push(room.jid); end end return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(destroyed, "\n") }; end); local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin"); module:provides("adhoc", destroy_rooms_desc); local set_affiliation_layout = dataforms_new { -- FIXME wordsmith title, instructions, labels etc title = "Set affiliation"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#set-affiliation" }; { name = "room", type = "jid-single", required = true, label = "Room"}; { name = "jid", type = "jid-single", required = true, label = "JID"}; { name = "affiliation", type = "list-single", required = true, label = "Affiliation", options = { "owner"; "admin"; "member"; "none"; "outcast"; }, }; { name = "reason", type = "text-single", "Reason", } }; local set_affiliation_handler = adhoc_simple(set_affiliation_layout, function (fields, errors) if errors then local errmsg = {}; for field, err in pairs(errors) do errmsg[#errmsg + 1] = field .. ": " .. err; end return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end local room = get_room_from_jid(fields.room); if not room then return { status = "canceled", error = { message = "No such room"; }; }; end local ok, err, condition = room:set_affiliation(true, fields.jid, fields.affiliation, fields.reason); if not ok then return { status = "canceled", error = { message = "Affiliation change failed: "..err..":"..condition; }; }; end return { status = "completed", info = "Affiliation updated", }; end); local set_affiliation_desc = adhoc_new("Set affiliation in room", "http://prosody.im/protocol/muc#set-affiliation", set_affiliation_handler, "admin"); module:provides("adhoc", set_affiliation_desc); end prosody-0.12.4/plugins/muc/PaxHeaders/moderated.lib.lua0000644000000000000000000000012014475665104020045 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.688764991 prosody-0.12.4/plugins/muc/moderated.lib.lua0000644000175000017500000000275514475665104022263 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local function get_moderated(room) return room._data.moderated; end local function set_moderated(room, moderated) moderated = moderated and true or nil; if get_moderated(room) == moderated then return false; end room._data.moderated = moderated; return true; end module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = get_moderated(event.room) and "muc_moderated" or "muc_unmoderated"}):up(); end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = "muc#roomconfig_moderatedroom"; type = "boolean"; label = "Moderated (require permission to speak)"; desc = "In moderated rooms occupants must be given permission to speak by a room moderator"; value = get_moderated(event.room); }); end, 80-3); module:hook("muc-config-submitted/muc#roomconfig_moderatedroom", function(event) if set_moderated(event.room, event.value) then event.status_codes["104"] = true; end end); module:hook("muc-get-default-role", function(event) if event.affiliation == nil then if get_moderated(event.room) then -- XEP-0045: -- An implementation MAY grant voice by default to visitors in unmoderated rooms. return "visitor" end end end, 1); return { get = get_moderated; set = set_moderated; }; prosody-0.12.4/plugins/muc/PaxHeaders/muc.lib.lua0000644000000000000000000000012014475665104016665 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/muc.lib.lua0000644000175000017500000016736214475665104021111 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local select = select; local pairs = pairs; local next = next; local setmetatable = setmetatable; local dataform = require "util.dataforms"; local iterators = require "util.iterators"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; local jid_join = require "util.jid".join; local jid_resource = require "util.jid".resource; local resourceprep = require "util.encodings".stringprep.resourceprep; local st = require "util.stanza"; local base64 = require "util.encodings".base64; local hmac_sha256 = require "util.hashes".hmac_sha256; local new_id = require "util.id".medium; local log = module._log; local occupant_lib = module:require "muc/occupant" local muc_util = module:require "muc/util"; local is_kickable_error = muc_util.is_kickable_error; local valid_roles, valid_affiliations = muc_util.valid_roles, muc_util.valid_affiliations; local room_mt = {}; room_mt.__index = room_mt; function room_mt:__tostring() return "MUC room ("..self.jid..")"; end function room_mt.save() -- overridden by mod_muc.lua end function room_mt:get_occupant_jid(real_jid) return self._jid_nick[real_jid] end function room_mt:get_default_role(affiliation) local role = module:fire_event("muc-get-default-role", { room = self; affiliation = affiliation; affiliation_rank = valid_affiliations[affiliation or "none"]; }); role = role ~= "none" and role or nil; -- coerces `role == false` to `nil` return role, valid_roles[role or "none"]; end module:hook("muc-get-default-role", function(event) if event.affiliation_rank >= valid_affiliations.admin then return "moderator"; elseif event.affiliation_rank >= valid_affiliations.none then return "participant"; end end, -1); --- Occupant functions function room_mt:new_occupant(bare_real_jid, nick) local occupant = occupant_lib.new(bare_real_jid, nick); local affiliation = self:get_affiliation(bare_real_jid); occupant.role = self:get_default_role(affiliation); return occupant; end -- nick is in the form of an in-room JID function room_mt:get_occupant_by_nick(nick) local occupant = self._occupants[nick]; if occupant == nil then return nil end return occupant_lib.copy(occupant); end do local function next_copied_occupant(occupants, occupant_jid) local next_occupant_jid, raw_occupant = next(occupants, occupant_jid); if next_occupant_jid == nil then return nil end return next_occupant_jid, occupant_lib.copy(raw_occupant); end -- FIXME Explain what 'read_only' is supposed to be function room_mt:each_occupant(read_only) -- luacheck: ignore 212 return next_copied_occupant, self._occupants, nil; end end function room_mt:has_occupant() return next(self._occupants, nil) ~= nil end function room_mt:get_occupant_by_real_jid(real_jid) local occupant_jid = self:get_occupant_jid(real_jid); if occupant_jid == nil then return nil end return self:get_occupant_by_nick(occupant_jid); end function room_mt:save_occupant(occupant) occupant = occupant_lib.copy(occupant); -- So that occupant can be modified more local id = occupant.nick -- Need to maintain _jid_nick secondary index local old_occupant = self._occupants[id]; if old_occupant then for real_jid in old_occupant:each_session() do self._jid_nick[real_jid] = nil; end end local has_live_session = false if occupant.role ~= nil then for real_jid, presence in occupant:each_session() do if presence.attr.type == nil then has_live_session = true self._jid_nick[real_jid] = occupant.nick; end end if not has_live_session then -- Has no live sessions left; they have left the room. occupant.role = nil end end if not has_live_session then occupant = nil end self._occupants[id] = occupant return occupant end function room_mt:route_to_occupant(occupant, stanza) local to = stanza.attr.to; for jid in occupant:each_session() do stanza.attr.to = jid; self:route_stanza(stanza); end stanza.attr.to = to; end -- actor is the attribute table local function add_item(x, affiliation, role, jid, nick, actor_nick, actor_jid, reason) x:tag("item", {affiliation = affiliation or "none"; role = role; jid = jid; nick = nick;}) if actor_nick or actor_jid then x:tag("actor", {nick = actor_nick; jid = actor_jid;}):up() end if reason then x:tag("reason"):text(reason):up() end x:up(); return x end -- actor is (real) jid function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor_nick, actor_jid, reason) local affiliation = self:get_affiliation(occupant.bare_jid) or "none"; local role = occupant.role or "none"; if is_anonymous then add_item(x, affiliation, role, nil, nick, actor_nick, actor_jid, reason); else for real_jid in occupant:each_session() do add_item(x, affiliation, role, real_jid, nick, actor_nick, actor_jid, reason); end end return x end function room_mt:broadcast_message(stanza) if module:fire_event("muc-broadcast-message", {room = self, stanza = stanza}) then return true; end self:broadcast(stanza); return true; end -- Strip delay tags claiming to be from us module:hook("muc-occupant-groupchat", function (event) local stanza = event.stanza; local room = event.room; local room_jid = room.jid; stanza:maptags(function (child) if child.name == "delay" and child.attr["xmlns"] == "urn:xmpp:delay" then if child.attr["from"] == room_jid then return nil; end end if child.name == "x" and child.attr["xmlns"] == "jabber:x:delay" then if child.attr["from"] == room_jid then return nil; end end return child; end) end); -- Broadcast a stanza to all occupants in the room. -- optionally checks conditional called with (nick, occupant) function room_mt:broadcast(stanza, cond_func) for nick, occupant in self:each_occupant() do if cond_func == nil or cond_func(nick, occupant) then self:route_to_occupant(occupant, stanza) end end end local function can_see_real_jids(whois, occupant) if whois == "anyone" then return true; elseif whois == "moderators" then return valid_roles[occupant.role or "none"] >= valid_roles.moderator; end end -- Broadcasts an occupant's presence to the whole room -- Takes the x element that goes into the stanzas function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, prev_role, force_unavailable, recipient) local base_x = x.base or x; -- Build real jid and (optionally) occupant jid template presences local base_presence do -- Try to use main jid's presence local pr = occupant:get_presence(); if pr and (occupant.role ~= nil or pr.attr.type == "unavailable") and not force_unavailable then base_presence = st.clone(pr); else -- user is leaving but didn't send a leave presence. make one for them base_presence = st.presence {from = occupant.nick; type = "unavailable";}; end end -- Fire event (before full_p and anon_p are created) local event = { room = self; stanza = base_presence; x = base_x; occupant = occupant; nick = nick; actor = actor; reason = reason; } module:fire_event("muc-build-occupant-presence", event); if not recipient then module:fire_event("muc-broadcast-presence", event); end -- Allow muc-broadcast-presence listeners to change things nick = event.nick; actor = event.actor; reason = event.reason; local whois = self:get_whois(); local actor_nick; if actor then actor_nick = jid_resource(self:get_occupant_jid(actor)); end local full_p, full_x; local function get_full_p() if full_p == nil then full_x = st.clone(x.full or base_x); self:build_item_list(occupant, full_x, false, nick, actor_nick, actor, reason); full_p = st.clone(base_presence):add_child(full_x); end return full_p, full_x; end local anon_p, anon_x; local function get_anon_p() if anon_p == nil then anon_x = st.clone(x.anon or base_x); self:build_item_list(occupant, anon_x, true, nick, actor_nick, nil, reason); anon_p = st.clone(base_presence):add_child(anon_x); end return anon_p, anon_x; end local self_p, self_x; do -- Can always see your own full jids -- But not allowed to see actor's self_x = st.clone(x.self or base_x); self:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason); self_p = st.clone(base_presence):add_child(self_x); end local function get_p(rec_occupant) local pr; if can_see_real_jids(whois, rec_occupant) then pr = get_full_p(); elseif occupant.bare_jid == rec_occupant.bare_jid then pr = self_p; else pr = get_anon_p(); end return pr end if recipient then return self:route_to_occupant(recipient, get_p(recipient)); end local broadcast_roles = self:get_presence_broadcast(); -- General populace for occupant_nick, n_occupant in self:each_occupant() do if occupant_nick ~= occupant.nick then local pr = get_p(n_occupant); if broadcast_roles[occupant.role or "none"] or force_unavailable then self:route_to_occupant(n_occupant, pr); elseif prev_role and broadcast_roles[prev_role] then pr.attr.type = 'unavailable'; self:route_to_occupant(n_occupant, pr); end end end -- Presences for occupant itself self_x:tag("status", {code = "110";}):up(); if occupant.role == nil then -- They get an unavailable self:route_to_occupant(occupant, self_p); else -- use their own presences as templates for full_jid, pr in occupant:each_session() do pr = st.clone(pr); module:fire_event("muc-build-occupant-presence", { room = self, occupant = occupant, stanza = pr }); pr.attr.to = full_jid; pr:add_child(self_x); self:route_stanza(pr); end end end function room_mt:send_occupant_list(to, filter) local to_bare = jid_bare(to); local broadcast_roles = self:get_presence_broadcast(); local is_anonymous = self:is_anonymous_for(to); local broadcast_bare_jids = {}; -- Track which bare JIDs we have sent presence for for occupant_jid, occupant in self:each_occupant() do broadcast_bare_jids[occupant.bare_jid] = true; if filter == nil or filter(occupant_jid, occupant) then local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); self:build_item_list(occupant, x, is_anonymous and to_bare ~= occupant.bare_jid); -- can always see your own jids local pres = st.clone(occupant:get_presence()); pres.attr.to = to; pres:add_child(x); module:fire_event("muc-build-occupant-presence", { room = self, occupant = occupant, stanza = pres }); if to_bare == occupant.bare_jid or broadcast_roles[occupant.role or "none"] then self:route_stanza(pres); end end end if broadcast_roles.none then -- Broadcast stanzas for affiliated users not currently in the MUC for affiliated_jid, affiliation, affiliation_data in self:each_affiliation() do local nick = affiliation_data and affiliation_data.reserved_nickname; if (nick or not is_anonymous) and not broadcast_bare_jids[affiliated_jid] and (filter == nil or filter(affiliated_jid, nil)) then local from = nick and (self.jid.."/"..nick) or self.jid; local pres = st.presence({ to = to, from = from, type = "unavailable" }) :tag("x", { xmlns = 'http://jabber.org/protocol/muc#user' }) :tag("item", { affiliation = affiliation; role = "none"; nick = nick; jid = not is_anonymous and affiliated_jid or nil }):up() :up(); self:route_stanza(pres); end end end end function room_mt:get_disco_info(stanza) local node = stanza.tags[1].attr.node; local reply = st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#info", node = node }); local event_name = "muc-disco#info"; local event_data = { room = self, reply = reply, stanza = stanza }; if node and node ~= "" then event_name = event_name.."/"..node; else event_data.form = dataform.new { { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }; }; event_data.formdata = {}; end module:fire_event(event_name, event_data); if event_data.form then reply:add_child(event_data.form:form(event_data.formdata, "result")); end return reply; end module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); event.reply:tag("feature", {var = "http://jabber.org/protocol/muc#stable_id"}):up(); event.reply:tag("feature", {var = "http://jabber.org/protocol/muc#self-ping-optimization"}):up(); end); module:hook("muc-disco#info", function(event) table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants" }); event.formdata["muc#roominfo_occupants"] = tostring(iterators.count(event.room:each_occupant())); end); function room_mt:get_disco_items(stanza) -- luacheck: ignore 212 return st.reply(stanza):query("http://jabber.org/protocol/disco#items"); end function room_mt:handle_kickable(origin, stanza) -- luacheck: ignore 212 local real_jid = stanza.attr.from; local occupant = self:get_occupant_by_real_jid(real_jid); if occupant == nil then return nil; end local _, condition, text = stanza:get_error(); local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); if text and self:get_whois() == "anyone" then error_message = error_message..": "..text; end occupant:set_session(real_jid, st.presence({type="unavailable"}) :tag('status'):text(error_message)); local orig_role = occupant.role; local is_last_session = occupant.jid == real_jid; if is_last_session then occupant.role = nil; end local new_occupant = self:save_occupant(occupant); local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); if is_last_session then x:tag("status", {code = "333"}); end self:publicise_occupant_status(new_occupant or occupant, x, nil, nil, nil, orig_role); if is_last_session then module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant; }); end return true; end -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); end, -1); -- check if user is banned module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); if affiliation == "outcast" then local reply = st.error_reply(stanza, "auth", "forbidden", nil, room.jid):up(); event.origin.send(reply); return true; end end, -10); module:hook("muc-occupant-pre-join", function(event) local room = event.room; local nick = jid_resource(event.occupant.nick); if not nick:find("%S") then event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden", room.jid)); return true; end end, 1); module:hook("muc-occupant-pre-change", function(event) local room = event.room; if not jid_resource(event.dest_occupant.nick):find("%S") then event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden", room.jid)); return true; end end, 1); module:hook("muc-occupant-pre-join", function(event) local room = event.room; local nick = jid_resource(event.occupant.nick); if not resourceprep(nick, true) then -- strict event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation", room.jid)); return true; end end, 2); module:hook("muc-occupant-pre-change", function(event) local room = event.room; local nick = jid_resource(event.dest_occupant.nick); if not resourceprep(nick, true) then -- strict event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation", room.jid)); return true; end end, 2); function room_mt:handle_first_presence(origin, stanza) local real_jid = stanza.attr.from; local dest_jid = stanza.attr.to; local bare_jid = jid_bare(real_jid); if module:fire_event("muc-room-pre-create", { room = self; origin = origin; stanza = stanza; }) then return true; end local is_first_dest_session = true; local dest_occupant = self:new_occupant(bare_jid, dest_jid); local orig_nick = dest_occupant.nick; if module:fire_event("muc-occupant-pre-join", { room = self; origin = origin; stanza = stanza; is_first_session = is_first_dest_session; is_new_room = true; occupant = dest_occupant; }) then return true; end local nick_changed = orig_nick ~= dest_occupant.nick; dest_occupant:set_session(real_jid, stanza); local dest_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); dest_x:tag("status", {code = "201"}):up(); if self:get_whois() == "anyone" then dest_x:tag("status", {code = "100"}):up(); end if nick_changed then dest_x:tag("status", {code = "210"}):up(); end self:save_occupant(dest_occupant); self:publicise_occupant_status(dest_occupant, dest_x); module:fire_event("muc-occupant-joined", { room = self; nick = dest_occupant.nick; occupant = dest_occupant; stanza = stanza; origin = origin; }); module:fire_event("muc-occupant-session-new", { room = self; nick = dest_occupant.nick; occupant = dest_occupant; stanza = stanza; origin = origin; jid = real_jid; }); module:fire_event("muc-room-created", { room = self; creator = dest_occupant; stanza = stanza; origin = origin; }); return true; end function room_mt:is_anonymous_for(jid) local is_anonymous = false; local whois = self:get_whois(); if whois ~= "anyone" then local affiliation = self:get_affiliation(jid); if affiliation ~= "admin" and affiliation ~= "owner" then local occupant = self:get_occupant_by_real_jid(jid); if not (occupant and can_see_real_jids(whois, occupant)) then is_anonymous = true; end end end return is_anonymous; end function room_mt:build_unavailable_presence(from_muc_jid, to_jid) local nick = jid_resource(from_muc_jid); local from_jid = self:get_registered_jid(nick); if (not from_jid) then module:log("debug", "Received presence probe for unavailable nickname that's not registered"); return; end local is_anonymous = self:is_anonymous_for(to_jid); local affiliation = self:get_affiliation(from_jid) or "none"; local pr = st.presence({ to = to_jid, from = from_muc_jid, type = "unavailable" }) :tag("x", { xmlns = 'http://jabber.org/protocol/muc#user' }) :tag("item", { affiliation = affiliation; role = "none"; nick = nick; jid = not is_anonymous and from_jid or nil }):up() :up(); local x = pr:get_child("x", "http://jabber.org/protocol/muc"); local event = { room = self; stanza = pr; x = x; bare_jid = from_jid; nick = nick; } module:fire_event("muc-build-occupant-presence", event); return event.stanza; end function room_mt:respond_to_probe(origin, stanza, probing_occupant) if probing_occupant == nil then origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat", self.jid)); return; end local from_muc_jid = stanza.attr.to; local probed_occupant = self:get_occupant_by_nick(from_muc_jid); if probed_occupant == nil then local to_jid = stanza.attr.from; local pr = self:build_unavailable_presence(from_muc_jid, to_jid); if pr then self:route_stanza(pr); end return; end local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); self:publicise_occupant_status(probed_occupant, x, nil, nil, nil, nil, false, probing_occupant); end function room_mt:handle_normal_presence(origin, stanza) local type = stanza.attr.type; local real_jid = stanza.attr.from; local bare_jid = jid_bare(real_jid); local orig_occupant = self:get_occupant_by_real_jid(real_jid); local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc"); if orig_occupant == nil and not muc_x and stanza.attr.type == nil then module:log("debug", "Attempted join without , possibly desynced"); origin.send(st.error_reply(stanza, "cancel", "item-not-found", "You are not currently connected to this chat", self.jid)); return true; end local is_first_dest_session; local dest_occupant; if type == "unavailable" then if orig_occupant == nil then return true; end -- Unavailable from someone not in the room -- dest_occupant = nil elseif type == "probe" then self:respond_to_probe(origin, stanza, orig_occupant) return true; elseif orig_occupant and orig_occupant.nick == stanza.attr.to then -- Just a presence update log("debug", "presence update for %s from session %s", orig_occupant.nick, real_jid); dest_occupant = orig_occupant; else local dest_jid = stanza.attr.to; dest_occupant = self:get_occupant_by_nick(dest_jid); if dest_occupant == nil then log("debug", "no occupant found for %s; creating new occupant object for %s", dest_jid, real_jid); is_first_dest_session = true; dest_occupant = self:new_occupant(bare_jid, dest_jid); if orig_occupant then dest_occupant.role = orig_occupant.role; end else is_first_dest_session = false; end end local is_last_orig_session; if orig_occupant ~= nil then -- Is there are least 2 sessions? local iter, ob, last = orig_occupant:each_session(); is_last_orig_session = iter(ob, iter(ob, last)) == nil; end local orig_nick = dest_occupant and dest_occupant.nick; local event, event_name = { room = self; origin = origin; stanza = stanza; is_first_session = is_first_dest_session; is_last_session = is_last_orig_session; }; if orig_occupant == nil then event_name = "muc-occupant-pre-join"; event.occupant = dest_occupant; elseif dest_occupant == nil then event_name = "muc-occupant-pre-leave"; event.occupant = orig_occupant; else event_name = "muc-occupant-pre-change"; event.orig_occupant = orig_occupant; event.dest_occupant = dest_occupant; end if module:fire_event(event_name, event) then return true; end local nick_changed = dest_occupant and orig_nick ~= dest_occupant.nick; -- Check for nick conflicts if dest_occupant ~= nil and not is_first_dest_session and bare_jid ~= jid_bare(dest_occupant.bare_jid) then -- new nick or has different bare real jid log("debug", "%s couldn't join due to nick conflict: %s", real_jid, dest_occupant.nick); local reply = st.error_reply(stanza, "cancel", "conflict", nil, self.jid):up(); origin.send(reply); return true; end -- Send presence stanza about original occupant if orig_occupant ~= nil and orig_occupant ~= dest_occupant then local orig_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); local orig_role = orig_occupant.role; local dest_nick; if dest_occupant == nil then -- Session is leaving log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); if is_last_orig_session then orig_occupant.role = nil; end orig_occupant:set_session(real_jid, stanza); else log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); local generated_unavail = st.presence {from = orig_occupant.nick, to = real_jid, type = "unavailable"}; orig_occupant:set_session(real_jid, generated_unavail); dest_nick = jid_resource(dest_occupant.nick); if not is_first_dest_session then -- User is swapping into another pre-existing session log("debug", "session %s is swapping into multisession %s, showing it leave.", real_jid, dest_occupant.nick); -- Show the other session leaving local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); add_item(x, self:get_affiliation(bare_jid), "none"); local pr = st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"} :tag("status"):text("you are joining pre-existing session " .. dest_nick):up() :add_child(x); self:route_stanza(pr); end if is_first_dest_session and is_last_orig_session then -- Normal nick change log("debug", "no sessions in %s left; publicly marking as nick change", orig_occupant.nick); orig_x:tag("status", {code = "303";}):up(); else -- The session itself always needs to see a nick change -- don't want to get our old nick's available presence, -- so remove our session from there, and manually generate an unavailable orig_occupant:remove_session(real_jid); log("debug", "generating nick change for %s", real_jid); local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); -- COMPAT: clients get confused if they see other items besides their own -- self:build_item_list(orig_occupant, x, false, dest_nick); add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick); x:tag("status", {code = "303";}):up(); x:tag("status", {code = "110";}):up(); self:route_stanza(generated_unavail:add_child(x)); dest_nick = nil; -- set dest_nick to nil; so general populace doesn't see it for whole orig_occupant end end self:save_occupant(orig_occupant); self:publicise_occupant_status(orig_occupant, orig_x, dest_nick, nil, nil, orig_role); if is_last_orig_session then module:fire_event("muc-occupant-left", { room = self; nick = orig_occupant.nick; occupant = orig_occupant; origin = origin; stanza = stanza; }); end end if dest_occupant ~= nil then dest_occupant:set_session(real_jid, stanza); self:save_occupant(dest_occupant); if orig_occupant == nil or muc_x then -- Send occupant list to newly joined or desynced user self:send_occupant_list(real_jid, function(nick, occupant) -- luacheck: ignore 212 -- Don't include self return (not occupant) or occupant:get_presence(real_jid) == nil; end) end local dest_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); local self_x = st.clone(dest_x); if orig_occupant == nil and self:get_whois() == "anyone" then self_x:tag("status", {code = "100"}):up(); end if nick_changed then self_x:tag("status", {code="210"}):up(); end self:publicise_occupant_status(dest_occupant, {base=dest_x,self=self_x}, nil, nil, nil, orig_occupant and orig_occupant.role or nil); if orig_occupant ~= nil and orig_occupant ~= dest_occupant and not is_last_orig_session then -- If user is swapping and wasn't last original session log("debug", "session %s split nicks; showing %s rejoining", real_jid, orig_occupant.nick); -- Show the original nick joining again local pr = st.clone(orig_occupant:get_presence()); pr.attr.to = real_jid; local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); self:build_item_list(orig_occupant, x, false); -- TODO: new status code to inform client this was the multi-session it left? pr:add_child(x); self:route_stanza(pr); end if orig_occupant == nil or muc_x then if is_first_dest_session then module:fire_event("muc-occupant-joined", { room = self; nick = dest_occupant.nick; occupant = dest_occupant; stanza = stanza; origin = origin; }); end module:fire_event("muc-occupant-session-new", { room = self; nick = dest_occupant.nick; occupant = dest_occupant; stanza = stanza; origin = origin; jid = real_jid; }); end end return true; end function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; if type == "error" then -- error, kick em out! return self:handle_kickable(origin, stanza) elseif type == nil or type == "unavailable" or type == "probe" then return self:handle_normal_presence(origin, stanza); elseif type ~= 'result' then -- bad type if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid)); -- FIXME correct error? end end return true; end function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; local type = stanza.attr.type; local id = stanza.attr.id; local occupant = self:get_occupant_by_nick(to); if (type == "error" or type == "result") then do -- deconstruct_stanza_id if not occupant then return nil; end local from_jid, orig_id, to_jid_hash = (base64.decode(id) or ""):match("^(%Z+)%z(%Z*)%z(.+)$"); if not(from == from_jid or from == jid_bare(from_jid)) then return nil; end local from_occupant_jid = self:get_occupant_jid(from_jid); if from_occupant_jid == nil then return nil; end local session_jid local salt = self:get_salt(); for to_jid in occupant:each_session() do if hmac_sha256(salt, to_jid):sub(1,8) == to_jid_hash then session_jid = to_jid; break; end end if session_jid == nil then return nil; end stanza.attr.from, stanza.attr.to, stanza.attr.id = from_occupant_jid, session_jid, orig_id; end log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); self:route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; else -- Type is "get" or "set" local current_nick = self:get_occupant_jid(from); if not current_nick then origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat", self.jid)); return true; end if not occupant then -- recipient not in room origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room", self.jid)); return true; end -- XEP-0410 MUC Self-Ping #1220 if to == current_nick and stanza.attr.type == "get" and stanza:get_child("ping", "urn:xmpp:ping") then self:route_stanza(st.reply(stanza)); return true; end do -- construct_stanza_id local salt = self:get_salt(); stanza.attr.id = base64.encode(occupant.jid.."\0"..stanza.attr.id.."\0"..hmac_sha256(salt, from):sub(1,8)); end stanza.attr.from, stanza.attr.to = current_nick, occupant.jid; log("debug", "%s sent private iq stanza to %s (%s)", from, to, occupant.jid); local iq_ns = stanza.tags[1].attr.xmlns; if iq_ns == 'vcard-temp' or iq_ns == "http://jabber.org/protocol/pubsub" or iq_ns == "urn:ietf:params:xml:ns:vcard-4.0" then stanza.attr.to = jid_bare(stanza.attr.to); end self:route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; end end function room_mt:handle_message_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(from); local type = stanza.attr.type; if not current_nick then -- not in room if type ~= "error" then origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat", self.jid)); end return true; end if type == "groupchat" then -- groupchat messages not allowed in PM origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid)); return true; elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); return self:handle_kickable(origin, stanza); -- send unavailable end local o_data = self:get_occupant_by_nick(to); if not o_data then origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room", self.jid)); return true; end log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); stanza = muc_util.filter_muc_x(st.clone(stanza)); stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); stanza.attr.from = current_nick; if module:fire_event("muc-private-message", { room = self, origin = origin, stanza = stanza }) ~= false then self:route_to_occupant(o_data, stanza) end -- TODO: Remove x tag? stanza.attr.from = from; return true; end function room_mt:send_form(origin, stanza) origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") :add_child(self:get_form_layout(stanza.attr.from):form()) ); end function room_mt:get_form_layout(actor) local form = dataform.new({ title = "Configuration for "..self.jid, instructions = "Complete and submit this form to configure the room.", { name = 'FORM_TYPE', type = 'hidden', value = 'http://jabber.org/protocol/muc#roomconfig' } }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end function room_mt:process_form(origin, stanza) local form = stanza.tags[1]:get_child("x", "jabber:x:data"); if form.attr.type == "cancel" then origin.send(st.reply(stanza)); elseif form.attr.type == "submit" then -- luacheck: ignore 231/errors local fields, errors, present; if form.tags[1] == nil then -- Instant room fields, present = {}, {}; else -- FIXME handle form errors fields, errors, present = self:get_form_layout(stanza.attr.from):data(form); if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return true; end end local event = { room = self; origin = origin; stanza = stanza; fields = fields; status_codes = {}; actor = stanza.attr.from; }; function event.update_option(name, field, allowed) local new = fields[field]; if new == nil then return; end if allowed and not allowed[new] then return; end if new == self["get_"..name](self) then return; end event.status_codes["104"] = true; self["set_"..name](self, new); return true; end module:fire_event("muc-config-submitted", event); for submitted_field in pairs(present) do event.field, event.value = submitted_field, fields[submitted_field]; module:fire_event("muc-config-submitted/"..submitted_field, event); end event.field, event.value = nil, nil; self:save(true); origin.send(st.reply(stanza)); if next(event.status_codes) then local msg = st.message({type='groupchat', from=self.jid}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) for code in pairs(event.status_codes) do msg:tag("status", {code = code;}):up(); end msg:up(); self:broadcast_message(msg); end else origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); end return true; end -- Removes everyone from the room function room_mt:clear(x) x = x or st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); local occupants_updated = {}; for nick, occupant in self:each_occupant() do -- luacheck: ignore 213 local prev_role = occupant.role; occupant.role = nil; self:save_occupant(occupant); occupants_updated[occupant] = prev_role; end for occupant, prev_role in pairs(occupants_updated) do self:publicise_occupant_status(occupant, x, nil, nil, nil, prev_role); module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant; }); end end function room_mt:destroy(newjid, reason, password) local x = st.stanza("x", { xmlns = "http://jabber.org/protocol/muc#user" }); local event = { room = self; newjid = newjid; reason = reason; password = password; x = x, allowed = true }; module:fire_event("muc-pre-room-destroy", event); if not event.allowed then return false, event.error; end newjid, reason, password = event.newjid, event.reason, event.password; x:tag("destroy", { jid = newjid }); if reason then x:tag("reason"):text(reason):up(); end if password then x:tag("password"):text(password):up(); end x:up(); self.destroying = reason or true; self:clear(x); module:fire_event("muc-room-destroyed", { room = self, reason = reason, newjid = newjid, password = password }); return true; end function room_mt:handle_disco_info_get_query(origin, stanza) origin.send(self:get_disco_info(stanza)); return true; end function room_mt:handle_disco_items_get_query(origin, stanza) origin.send(self:get_disco_items(stanza)); return true; end function room_mt:handle_admin_query_set_command(origin, stanza) local item = stanza.tags[1].tags[1]; if not item then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; end if item.attr.jid then -- Validate provided JID item.attr.jid = jid_prep(item.attr.jid); if not item.attr.jid then origin.send(st.error_reply(stanza, "modify", "jid-malformed")); return true; elseif jid_resource(item.attr.jid) then origin.send(st.error_reply(stanza, "modify", "jid-malformed", "Bare JID expected, got full JID")); return true; end end if item.attr.nick then -- Validate provided nick item.attr.nick = resourceprep(item.attr.nick); if not item.attr.nick then origin.send(st.error_reply(stanza, "modify", "jid-malformed", "invalid nickname")); return true; end end if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation local occupant = self:get_occupant_by_nick(self.jid.."/"..item.attr.nick); if occupant then item.attr.jid = occupant.bare_jid; end elseif item.attr.role and not item.attr.nick and item.attr.jid then -- Role changes should use nick, but we have a JID so pull the nick from that local nick = self:get_occupant_jid(item.attr.jid); if nick then item.attr.nick = jid_resource(nick); end end local actor = stanza.attr.from; local reason = item:get_child_text("reason"); local success, errtype, err if item.attr.affiliation and item.attr.jid and not item.attr.role then local registration_data; if item.attr.nick then local room_nick = self.jid.."/"..item.attr.nick; local existing_occupant = self:get_occupant_by_nick(room_nick); if existing_occupant and existing_occupant.bare_jid ~= item.attr.jid then module:log("debug", "Existing occupant for %s: %s does not match %s", room_nick, existing_occupant.bare_jid, item.attr.jid); self:set_role(true, room_nick, nil, "This nickname is reserved"); end module:log("debug", "Reserving %s for %s (%s)", item.attr.nick, item.attr.jid, item.attr.affiliation); registration_data = { reserved_nickname = item.attr.nick }; end success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, reason, registration_data); elseif item.attr.role and item.attr.nick and not item.attr.affiliation then success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, reason); else success, errtype, err = nil, "cancel", "bad-request"; end self:save(true); if not success then origin.send(st.error_reply(stanza, errtype, err)); else origin.send(st.reply(stanza)); end return true; end function room_mt:handle_admin_query_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); local item = stanza.tags[1].tags[1]; local _aff = item.attr.affiliation; local _aff_rank = valid_affiliations[_aff or "none"]; local _rol = item.attr.role; if _aff and _aff_rank and not _rol then -- You need to be at least an admin, and be requesting info about your affiliation or lower -- e.g. an admin can't ask for a list of owners local affiliation_rank = valid_affiliations[affiliation or "none"]; if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank) or (self:get_members_only() and self:get_whois() == "anyone" and affiliation_rank >= valid_affiliations.member) then local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); for jid in self:each_affiliation(_aff or "none") do local nick = self:get_registered_nick(jid); reply:tag("item", {affiliation = _aff, jid = jid, nick = nick }):up(); end origin.send(reply:up()); return true; else origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end elseif _rol and valid_roles[_rol or "none"] and not _aff then local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation); if valid_roles[role or "none"] >= valid_roles.moderator then if _rol == "none" then _rol = nil; end local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); -- TODO: whois check here? (though fully anonymous rooms are not supported) for occupant_jid, occupant in self:each_occupant() do if occupant.role == _rol then local nick = jid_resource(occupant_jid); self:build_item_list(occupant, reply, false, nick); end end origin.send(reply:up()); return true; else origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; end end function room_mt:handle_owner_query_get_to_room(origin, stanza) if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); return true; end self:send_form(origin, stanza); return true; end function room_mt:handle_owner_query_set_to_room(origin, stanza) if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); return true; end local child = stanza.tags[1].tags[1]; if not child then origin.send(st.error_reply(stanza, "modify", "bad-request")); return true; elseif child.name == "destroy" then local newjid = child.attr.jid; local reason = child:get_child_text("reason"); local password = child:get_child_text("password"); local destroyed, err = self:destroy(newjid, reason, password); if destroyed then origin.send(st.reply(stanza)); else origin.send(st.error_reply(stanza, err or "cancel", "not-allowed")); end return true; elseif child.name == "x" and child.attr.xmlns == "jabber:x:data" then return self:process_form(origin, stanza); else origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return true; end end function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; local occupant = self:get_occupant_by_real_jid(from); if not stanza.attr.id then stanza.attr.id = new_id() end local event_data = {room = self; origin = origin; stanza = stanza; from = from; occupant = occupant}; if module:fire_event("muc-occupant-groupchat", event_data) then return true; end if event_data.occupant then stanza.attr.from = event_data.occupant.nick; else stanza.attr.from = self.jid; end self:broadcast_message(stanza); stanza.attr.from = from; return true; end -- Role check module:hook("muc-occupant-groupchat", function(event) local role_rank = valid_roles[event.occupant and event.occupant.role or "none"]; if role_rank <= valid_roles.none then event.origin.send(st.error_reply(event.stanza, "cancel", "not-acceptable", "You are not currently connected to this chat")); return true; elseif role_rank <= valid_roles.visitor then event.origin.send(st.error_reply(event.stanza, "auth", "forbidden", "You do not currently have permission to speak in this chat")); return true; end end, 50); -- hack - some buggy clients send presence updates to the room rather than their nick function room_mt:handle_presence_to_room(origin, stanza) local current_nick = self:get_occupant_jid(stanza.attr.from); local handled if current_nick then local to = stanza.attr.to; stanza.attr.to = current_nick; handled = self:handle_presence_to_occupant(origin, stanza); stanza.attr.to = to; end return handled; end -- Need visitor role or higher to invite module:hook("muc-pre-invite", function(event) local room, stanza = event.room, event.stanza; local _from = stanza.attr.from; local inviter = room:get_occupant_by_real_jid(_from); local role = inviter and inviter.role or room:get_default_role(room:get_affiliation(_from)); if valid_roles[role or "none"] <= valid_roles.visitor then event.origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end end); function room_mt:handle_mediated_invite(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); local invitee = jid_prep(payload.attr.to); if not invitee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; elseif module:fire_event("muc-pre-invite", {room = self, origin = origin, stanza = stanza}) then return true; end local invite = muc_util.filter_muc_x(st.clone(stanza)); invite.attr.from = self.jid; invite.attr.to = invitee; invite:tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) :tag('invite', {from = stanza.attr.from;}) :tag('reason'):text(payload:get_child_text("reason")):up() :up() :up(); if not module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}) then self:route_stanza(invite); end return true; end -- COMPAT: Some older clients expect this module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza; local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); local reason = invite:get_child_text("reason"); stanza:tag('x', {xmlns = "jabber:x:conference"; jid = room.jid;}) :text(reason or "") :up(); end); -- Add a plain message for clients which don't support invites module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza; if not stanza:get_child("body") then local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); local reason = invite:get_child_text("reason") or ""; stanza:tag("body") :text(invite.attr.from.." invited you to the room "..room.jid..(reason ~= "" and (" ("..reason..")") or "")) :up(); end end); function room_mt:handle_mediated_decline(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); local declinee = jid_prep(payload.attr.to); if not declinee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; elseif module:fire_event("muc-pre-decline", {room = self, origin = origin, stanza = stanza}) then return true; end local decline = muc_util.filter_muc_x(st.clone(stanza)); decline.attr.from = self.jid; decline.attr.to = declinee; decline:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("decline", {from = stanza.attr.from}) :tag("reason"):text(payload:get_child_text("reason")):up() :up() :up(); if not module:fire_event("muc-decline", {room = self, stanza = decline, origin = origin, incoming = stanza}) then declinee = decline.attr.to; -- re-fetch, in case event modified it local occupant if jid_bare(declinee) == self.jid then -- declinee jid is already an in-room jid occupant = self:get_occupant_by_nick(declinee); end if occupant then self:route_to_occupant(occupant, decline); else self:route_stanza(decline); end end return true; end -- Add a plain message for clients which don't support declines module:hook("muc-decline", function(event) local room, stanza = event.room, event.stanza; if not stanza:get_child("body") then local decline = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); local reason = decline:get_child_text("reason") or ""; stanza:body(decline.attr.from.." declined your invite to the room " ..room.jid..(reason ~= "" and (" ("..reason..")") or "")); end end); function room_mt:handle_message_to_room(origin, stanza) local type = stanza.attr.type; if type == "groupchat" then return self:handle_groupchat_to_room(origin, stanza) elseif type == "error" and is_kickable_error(stanza) then return self:handle_kickable(origin, stanza) elseif type == nil or type == "normal" then local x = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); if x then local payload = x.tags[1]; if payload == nil then --luacheck: ignore 542 -- fallthrough elseif payload.name == "invite" and payload.attr.to then return self:handle_mediated_invite(origin, stanza) elseif payload.name == "decline" and payload.attr.to then return self:handle_mediated_decline(origin, stanza) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; end local form = stanza:get_child("x", "jabber:x:data"); local form_type = dataform.get_type(form); if form_type == "http://jabber.org/protocol/muc#request" then self:handle_role_request(origin, stanza, form); return true; end end end function room_mt:route_stanza(stanza) -- luacheck: ignore 212 module:send(stanza); end function room_mt:get_affiliation(jid) local node, host = jid_split(jid); -- Affiliations are granted, revoked, and maintained based on the user's bare JID. local bare = node and node.."@"..host or host; local result = self._affiliations[bare]; if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned return result; end -- Iterates over jid, affiliation pairs function room_mt:each_affiliation(with_affiliation) local _affiliations, _affiliation_data = self._affiliations, self._affiliation_data; return function(_, jid) local affiliation; repeat -- Iterate until we get a match jid, affiliation = next(_affiliations, jid); until with_affiliation == nil or jid == nil or affiliation == with_affiliation return jid, affiliation, _affiliation_data[jid]; end, nil, nil; end function room_mt:set_affiliation(actor, jid, affiliation, reason, data) if not actor then return nil, "modify", "not-acceptable"; end; local node, host = jid_split(jid); if not host then return nil, "modify", "not-acceptable"; end jid = jid_join(node, host); -- Bare local is_host_only = node == nil; if valid_affiliations[affiliation or "none"] == nil then return nil, "modify", "not-acceptable"; end affiliation = affiliation ~= "none" and affiliation or nil; -- coerces `affiliation == false` to `nil` local target_affiliation = self._affiliations[jid]; -- Raw; don't want to check against host local is_downgrade = valid_affiliations[target_affiliation or "none"] > valid_affiliations[affiliation or "none"]; if actor == true then actor = nil -- So we can pass it safely to 'publicise_occupant_status' below else local actor_affiliation = self:get_affiliation(actor); if actor_affiliation == "owner" then if jid_bare(actor) == jid and is_downgrade then -- self change -- need at least one owner local is_last = true; for j in self:each_affiliation("owner") do if j ~= jid then is_last = false; break; end end if is_last then return nil, "cancel", "conflict"; end end -- owners can do anything else elseif affiliation == "owner" or affiliation == "admin" or actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then -- Can't demote owners or other admins return nil, "cancel", "not-allowed"; end end local event_data = { room = self; actor = actor; jid = jid; affiliation = affiliation or "none"; reason = reason; previous_affiliation = target_affiliation or "none"; data = data and data or nil; -- coerce false to nil previous_data = self._affiliation_data[jid] or nil; }; module:fire_event("muc-pre-set-affiliation", event_data); if event_data.allowed == false then local err = event_data.error or { type = "cancel", condition = "not-allowed" }; return nil, err.type, err.condition; end if affiliation and not data and event_data.data then -- Allow handlers to add data when none was going to be set data = event_data.data; end -- Set in 'database' self._affiliations[jid] = affiliation; if not affiliation or data == false or (data ~= nil and next(data) == nil) then module:log("debug", "Clearing affiliation data for %s", jid); self._affiliation_data[jid] = nil; elseif data then module:log("debug", "Updating affiliation data for %s", jid); self._affiliation_data[jid] = data; end -- Update roles local role = self:get_default_role(affiliation); local role_rank = valid_roles[role or "none"]; local occupants_updated = {}; -- Filled with old roles for nick, occupant in self:each_occupant() do -- luacheck: ignore 213 if occupant.bare_jid == jid or ( -- Outcast can be by host. is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host ) then -- need to publicize in all cases; as affiliation in has changed. occupants_updated[occupant] = occupant.role; if occupant.role ~= role and ( is_downgrade or valid_roles[occupant.role or "none"] < role_rank -- upgrade ) then occupant.role = role; self:save_occupant(occupant); end end end -- Tell the room of the new occupant affiliations+roles local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); if not role then -- getting kicked if affiliation == "outcast" then x:tag("status", {code="301"}):up(); -- banned else x:tag("status", {code="321"}):up(); -- affiliation change end end local is_semi_anonymous = self:get_whois() == "moderators"; if next(occupants_updated) ~= nil then for occupant, old_role in pairs(occupants_updated) do self:publicise_occupant_status(occupant, x, nil, actor, reason, old_role); if occupant.role == nil then module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant; }); elseif is_semi_anonymous and ((old_role == "moderator" and occupant.role ~= "moderator") or (old_role ~= "moderator" and occupant.role == "moderator")) then -- Has gained or lost moderator status -- Send everyone else's presences (as jid visibility has changed) for real_jid in occupant:each_session() do self:send_occupant_list(real_jid, function(occupant_jid, occupant) --luacheck: ignore 212 433 return (not occupant) or occupant.bare_jid ~= jid; end); end end end else -- Announce affiliation change for a user that is not currently in the room, -- XEP-0045 (v1.31.2) example 195 -- add_item(x, affiliation, role, jid, nick, actor_nick, actor_jid, reason) local announce_msg = st.message({ from = self.jid }) :add_child(add_item(st.clone(x), affiliation, nil, jid, nil, nil, nil, reason)); local min_role = is_semi_anonymous and "moderator" or "none"; self:broadcast(announce_msg, muc_util.only_with_min_role(min_role)); end self:save(true); event_data.in_room = next(occupants_updated) ~= nil; module:fire_event("muc-set-affiliation", event_data); return true; end function room_mt:get_affiliation_data(jid, key) local data = self._affiliation_data[jid]; if not data then return nil; end if key then return data[key]; end return data; end function room_mt:set_affiliation_data(jid, key, value) if key == nil then return nil, "invalid key"; end local data = self._affiliation_data[jid]; if not data then if value == nil then return true; end data = {}; self._affiliation_data[jid] = data; end local old_value = data[key]; data[key] = value; if old_value ~= value then module:fire_event("muc-set-affiliation-data/"..key, { room = self; jid = jid; key = key; value = value; old_value = old_value; }); end self:save(true); return true; end function room_mt:get_role(nick) local occupant = self:get_occupant_by_nick(nick); return occupant and occupant.role or nil; end function room_mt:may_set_role(actor, occupant, role) local event = { room = self, actor = actor, occupant = occupant, role = role, }; module:fire_event("muc-pre-set-role", event); if event.allowed ~= nil then return event.allowed, event.error, event.condition; end local actor_affiliation = self:get_affiliation(actor) or "none"; local occupant_affiliation = self:get_affiliation(occupant.bare_jid) or "none"; -- Can't do anything to someone with higher affiliation if valid_affiliations[actor_affiliation] < valid_affiliations[occupant_affiliation] then return nil, "cancel", "not-allowed"; end -- If you are trying to give or take moderator role you need to be an owner or admin if occupant.role == "moderator" or role == "moderator" then if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then return nil, "cancel", "not-allowed"; end end -- Need to be in the room and a moderator local actor_occupant = self:get_occupant_by_real_jid(actor); if not actor_occupant or actor_occupant.role ~= "moderator" then return nil, "cancel", "not-allowed"; end return true; end function room_mt:set_role(actor, occupant_jid, role, reason) if not actor then return nil, "modify", "not-acceptable"; end local occupant = self:get_occupant_by_nick(occupant_jid); if not occupant then return nil, "modify", "item-not-found"; end if valid_roles[role or "none"] == nil then return nil, "modify", "not-acceptable"; end role = role ~= "none" and role or nil; -- coerces `role == false` to `nil` if actor == true then actor = nil -- So we can pass it safely to 'publicise_occupant_status' below else local allowed, err, condition = self:may_set_role(actor, occupant, role) if not allowed then return allowed, err, condition; end end local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); if not role then x:tag("status", {code = "307"}):up(); end local prev_role = occupant.role; occupant.role = role; self:save_occupant(occupant); self:publicise_occupant_status(occupant, x, nil, actor, reason, prev_role); if role == nil then module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant; }); end return true; end local whois = module:require "muc/whois"; room_mt.get_whois = whois.get; room_mt.set_whois = whois.set; local _M = {}; -- module "muc" function _M.new_room(jid, config) return setmetatable({ jid = jid; _jid_nick = {}; _occupants = {}; _data = config or {}; _affiliations = {}; _affiliation_data = {}; }, room_mt); end local new_format = module:get_option_boolean("new_muc_storage_format", true); function room_mt:freeze(live) local frozen, state; if new_format then frozen = { _jid = self.jid; _data = self._data; _affiliation_data = self._affiliation_data; }; for user, affiliation in pairs(self._affiliations) do frozen[user] = affiliation; end else frozen = { jid = self.jid; _data = self._data; _affiliations = self._affiliations; _affiliation_data = self._affiliation_data; }; end if live then state = {}; for nick, occupant in self:each_occupant() do state[nick] = { bare_jid = occupant.bare_jid; role = occupant.role; jid = occupant.jid; } for jid, presence in occupant:each_session() do state[jid] = st.preserialize(presence); end end local history = self._history; if history and history[1] ~= nil then state._last_message = st.preserialize(history[#history].stanza); state._last_message_at = history[#history].timestamp; end end return frozen, state; end function _M.restore_room(frozen, state) local room_jid = frozen._jid or frozen.jid; local room = _M.new_room(room_jid, frozen._data); if state and state._last_message and state._last_message_at then room._history = { { stanza = st.deserialize(state._last_message), timestamp = state._last_message_at, }, }; end local occupants = {}; local room_name, room_host = jid_split(room_jid); room._affiliation_data = frozen._affiliation_data or {}; if frozen.jid and frozen._affiliations then -- Old storage format room._affiliations = frozen._affiliations; else -- New storage format for jid, data in pairs(frozen) do local _, host, resource = jid_split(jid); if host:sub(1,1) ~= "_" and not resource and type(data) == "string" then -- bare jid: affiliation room._affiliations[jid] = data; end end end for jid, data in pairs(state or frozen) do local node, host, resource = jid_split(jid); if node or host:sub(1,1) ~= "_" then if host == room_host and node == room_name and resource and type(data) == "table" then -- full room jid: bare real jid and role local nick = jid; local occupant = occupants[nick] or occupant_lib.new(data.bare_jid, nick); occupant.bare_jid = data.bare_jid; occupant.role = data.role; occupant.jid = data.jid; -- Primary session JID occupants[nick] = occupant; elseif type(data) == "table" and data.name == "presence" then -- full user jid: presence local nick = data.attr.from; local occupant = occupants[nick] or occupant_lib.new(nil, nick); local presence = st.deserialize(data); occupant:set_session(jid, presence); occupants[nick] = occupant; end end end for _, occupant in pairs(occupants) do room:save_occupant(occupant); end return room; end _M.room_mt = room_mt; return _M; prosody-0.12.4/plugins/muc/PaxHeaders/name.lib.lua0000644000000000000000000000012014475665104017021 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/name.lib.lua0000644000175000017500000000215114475665104021225 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local function get_name(room) return room._data.name; end local function set_name(room, name) if name == "" then name = nil; end if room._data.name == name then return false; end room._data.name = name; return true; end local function insert_name_into_form(event) table.insert(event.form, { name = "muc#roomconfig_roomname"; type = "text-single"; label = "Title"; value = event.room._data.name; }); end module:hook("muc-disco#info", function(event) event.reply:tag("identity", {category="conference", type="text", name=get_name(event.room)}):up(); insert_name_into_form(event); end); module:hook("muc-config-form", insert_name_into_form, 100-1); module:hook("muc-config-submitted/muc#roomconfig_roomname", function(event) if set_name(event.room, event.value) then event.status_codes["104"] = true; end end); return { get = get_name; set = set_name; }; prosody-0.12.4/plugins/muc/PaxHeaders/occupant.lib.lua0000644000000000000000000000012014475665104017715 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/occupant.lib.lua0000644000175000017500000000433114475665104022123 0ustar00prosodyprosody00000000000000local pairs = pairs; local setmetatable = setmetatable; local st = require "util.stanza"; local util = module:require "muc/util"; local function get_filtered_presence(stanza) return util.filter_muc_x(st.clone(stanza)); end local occupant_mt = {}; occupant_mt.__index = occupant_mt; local function new_occupant(bare_real_jid, nick) return setmetatable({ bare_jid = bare_real_jid; nick = nick; -- in-room jid sessions = {}; -- hash from real_jid to presence stanzas. stanzas should not be modified role = nil; jid = nil; -- Primary session }, occupant_mt); end -- Deep copy an occupant local function copy_occupant(occupant) local sessions = {}; for full_jid, presence_stanza in pairs(occupant.sessions) do -- Don't keep unavailable presences, as they'll accumulate; unless they're the primary session if presence_stanza.attr.type ~= "unavailable" or full_jid == occupant.jid then sessions[full_jid] = presence_stanza; end end return setmetatable({ bare_jid = occupant.bare_jid; nick = occupant.nick; sessions = sessions; role = occupant.role; jid = occupant.jid; }, occupant_mt); end -- finds another session to be the primary (there might not be one) function occupant_mt:choose_new_primary() for jid, pr in self:each_session() do if pr.attr.type == nil then return jid; end end return nil; end function occupant_mt:set_session(real_jid, presence_stanza, replace_primary) local pr = get_filtered_presence(presence_stanza); pr.attr.from = self.nick; pr.attr.to = real_jid; self.sessions[real_jid] = pr; if replace_primary then self.jid = real_jid; elseif self.jid == nil or (pr.attr.type == "unavailable" and self.jid == real_jid) then -- Only leave an unavailable presence as primary when there are no other options self.jid = self:choose_new_primary() or real_jid; end end function occupant_mt:remove_session(real_jid) -- Delete original session self.sessions[real_jid] = nil; if self.jid == real_jid then self.jid = self:choose_new_primary(); end end function occupant_mt:each_session() return pairs(self.sessions) end function occupant_mt:get_presence(real_jid) return self.sessions[real_jid or self.jid] end return { new = new_occupant; copy = copy_occupant; mt = occupant_mt; } prosody-0.12.4/plugins/muc/PaxHeaders/occupant_id.lib.lua0000644000000000000000000000012014475665104020371 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/occupant_id.lib.lua0000644000175000017500000000424614475665104022604 0ustar00prosodyprosody00000000000000-- Implementation of https://xmpp.org/extensions/inbox/occupant-id.html -- XEP-0421: Anonymous unique occupant identifiers for MUCs -- (C) 2020 Maxime “pep” Buquet -- (C) 2020 Matthew Wild local uuid = require "util.uuid"; local hmac_sha256 = require "util.hashes".hmac_sha256; local b64encode = require "util.encodings".base64.encode; local xmlns_occupant_id = "urn:xmpp:occupant-id:0"; local function get_room_salt(room) local salt = room._data.occupant_id_salt; if not salt then salt = uuid.generate(); room._data.occupant_id_salt = salt; end return salt; end local function get_occupant_id(room, occupant) if occupant.stable_id then return occupant.stable_id; end local salt = get_room_salt(room) occupant.stable_id = b64encode(hmac_sha256(occupant.bare_jid, salt)); return occupant.stable_id; end local function update_occupant(event) local stanza, room, occupant, dest_occupant = event.stanza, event.room, event.occupant, event.dest_occupant; -- "muc-occupant-pre-change" provides "dest_occupant" but not "occupant". if dest_occupant ~= nil then occupant = dest_occupant; end -- strip any existing tags to avoid forgery stanza:remove_children("occupant-id", xmlns_occupant_id); local unique_id = get_occupant_id(room, occupant); stanza:tag("occupant-id", { xmlns = xmlns_occupant_id, id = unique_id }):up(); end local function muc_private(event) local stanza, room = event.stanza, event.room; local occupant = room._occupants[stanza.attr.from]; update_occupant({ stanza = stanza, room = room, occupant = occupant, }); end if module:get_option_boolean("muc_occupant_id", true) then module:add_feature(xmlns_occupant_id); module:hook("muc-disco#info", function (event) event.reply:tag("feature", { var = xmlns_occupant_id }):up(); end); module:hook("muc-broadcast-presence", update_occupant); module:hook("muc-occupant-pre-join", update_occupant); module:hook("muc-occupant-pre-change", update_occupant); module:hook("muc-occupant-groupchat", update_occupant); module:hook("muc-private-message", muc_private); end return { get_room_salt = get_room_salt; get_occupant_id = get_occupant_id; }; prosody-0.12.4/plugins/muc/PaxHeaders/password.lib.lua0000644000000000000000000000012014475665104017743 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/password.lib.lua0000644000175000017500000000475714475665104022165 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local function get_password(room) return room._data.password; end local function set_password(room, password) if password == "" then password = nil; end if room._data.password == password then return false; end room._data.password = password; return true; end module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = get_password(event.room) and "muc_passwordprotected" or "muc_unsecured"}):up(); end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = "muc#roomconfig_roomsecret"; type = "text-private"; label = "Password"; value = get_password(event.room) or ""; }); end, 90-2); module:hook("muc-config-submitted/muc#roomconfig_roomsecret", function(event) if set_password(event.room, event.value) then event.status_codes["104"] = true; end end); -- Don't allow anyone to join room unless they provide the password module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; if not get_password(room) then return end local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc"); if not muc_x then return end local password = muc_x:get_child_text("password", "http://jabber.org/protocol/muc"); if not password or password == "" then password = nil; end if get_password(room) ~= password then local from, to = stanza.attr.from, stanza.attr.to; module:log("debug", "%s couldn't join due to invalid password: %s", from, to); local reply = st.error_reply(stanza, "auth", "not-authorized", nil, room.jid):up(); event.origin.send(reply); return true; end end, -20); -- Add password to outgoing invite module:hook("muc-invite", function(event) local password = get_password(event.room); if password then local x = event.stanza:get_child("x", "http://jabber.org/protocol/muc#user"); x:tag("password"):text(password):up(); end end); module:hook("muc-room-pre-create", function (event) local stanza, room = event.stanza, event.room; local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc"); if not muc_x then return end local password = muc_x:get_child_text("password", "http://jabber.org/protocol/muc"); set_password(room, password); end); return { get = get_password; set = set_password; }; prosody-0.12.4/plugins/muc/PaxHeaders/persistent.lib.lua0000644000000000000000000000012014475665104020301 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/persistent.lib.lua0000644000175000017500000000343314475665104022511 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local restrict_persistent = not module:get_option_boolean("muc_room_allow_persistent", true); local um_is_admin = require "core.usermanager".is_admin; local function get_persistent(room) return room._data.persistent; end local function set_persistent(room, persistent) persistent = persistent and true or nil; if get_persistent(room) == persistent then return false; end room._data.persistent = persistent; return true; end module:hook("muc-config-form", function(event) if restrict_persistent and not um_is_admin(event.actor, module.host) then -- Don't show option if hidden rooms are restricted and user is not admin of this host return; end table.insert(event.form, { name = "muc#roomconfig_persistentroom"; type = "boolean"; label = "Persistent (room should remain even when it is empty)"; desc = "Rooms are automatically deleted when they are empty, unless this option is enabled"; value = get_persistent(event.room); }); end, 100-5); module:hook("muc-config-submitted/muc#roomconfig_persistentroom", function(event) if restrict_persistent and not um_is_admin(event.actor, module.host) then return; -- Not allowed end if set_persistent(event.room, event.value) then event.status_codes["104"] = true; end end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = get_persistent(event.room) and "muc_persistent" or "muc_temporary"}):up(); end); module:hook("muc-room-destroyed", function(event) set_persistent(event.room, false); end, -100); return { get = get_persistent; set = set_persistent; }; prosody-0.12.4/plugins/muc/PaxHeaders/presence_broadcast.lib.lua0000644000000000000000000000012014475665104021727 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/presence_broadcast.lib.lua0000644000175000017500000000444614475665104024144 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local valid_roles = { "none", "visitor", "participant", "moderator" }; local default_broadcast = { visitor = true; participant = true; moderator = true; }; local function get_presence_broadcast(room) return room._data.presence_broadcast or default_broadcast; end local function set_presence_broadcast(room, broadcast_roles) broadcast_roles = broadcast_roles or default_broadcast; local changed = false; local old_broadcast_roles = get_presence_broadcast(room); for _, role in ipairs(valid_roles) do if old_broadcast_roles[role] ~= broadcast_roles[role] then changed = true; end end if not changed then return false; end room._data.presence_broadcast = broadcast_roles; for _, occupant in room:each_occupant() do local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); local role = occupant.role or "none"; if broadcast_roles[role] and not old_broadcast_roles[role] then -- Presence broadcast is now enabled, so announce existing user room:publicise_occupant_status(occupant, x); elseif old_broadcast_roles[role] and not broadcast_roles[role] then -- Presence broadcast is now disabled, so mark existing user as unavailable room:publicise_occupant_status(occupant, x, nil, nil, nil, nil, true); end end return true; end module:hook("muc-config-form", function(event) local values = {}; for role, value in pairs(get_presence_broadcast(event.room)) do if value then values[#values + 1] = role; end end table.insert(event.form, { name = "muc#roomconfig_presencebroadcast"; type = "list-multi"; label = "Only show participants with roles:"; value = values; options = valid_roles; }); end, 70-7); module:hook("muc-config-submitted/muc#roomconfig_presencebroadcast", function(event) local broadcast_roles = {}; for _, role in ipairs(event.value) do broadcast_roles[role] = true; end if set_presence_broadcast(event.room, broadcast_roles) then event.status_codes["104"] = true; end end); return { get = get_presence_broadcast; set = set_presence_broadcast; }; prosody-0.12.4/plugins/muc/PaxHeaders/register.lib.lua0000644000000000000000000000012014475665104017725 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/register.lib.lua0000644000175000017500000002074014475665104022135 0ustar00prosodyprosody00000000000000local jid_bare = require "util.jid".bare; local jid_resource = require "util.jid".resource; local resourceprep = require "util.encodings".stringprep.resourceprep; local st = require "util.stanza"; local dataforms = require "util.dataforms"; local allow_unaffiliated = module:get_option_boolean("allow_unaffiliated_register", false); local enforce_nick = module:get_option_boolean("enforce_registered_nickname", false); -- Whether to include the current registration data as a dataform. Disabled -- by default currently as it hasn't been widely tested with clients. local include_reg_form = module:get_option_boolean("muc_registration_include_form", false); -- reserved_nicks[nick] = jid local function get_reserved_nicks(room) if room._reserved_nicks then return room._reserved_nicks; end module:log("debug", "Refreshing reserved nicks..."); local reserved_nicks = {}; for jid, _, data in room:each_affiliation() do local nick = data and data.reserved_nickname; module:log("debug", "Refreshed for %s: %s", jid, nick); if nick then reserved_nicks[nick] = jid; end end room._reserved_nicks = reserved_nicks; return reserved_nicks; end -- Returns the registered nick, if any, for a JID -- Note: this is just the *nick* part, i.e. the resource of the in-room JID local function get_registered_nick(room, jid) local registered_data = room._affiliation_data[jid]; if not registered_data then return; end return registered_data.reserved_nickname; end -- Returns the JID, if any, that registered a nick (not in-room JID) local function get_registered_jid(room, nick) local reserved_nicks = get_reserved_nicks(room); return reserved_nicks[nick]; end module:hook("muc-set-affiliation", function (event) -- Clear reserved nick cache event.room._reserved_nicks = nil; end); module:hook("muc-disco#info", function (event) event.reply:tag("feature", { var = "jabber:iq:register" }):up(); end); local registration_form = dataforms.new { { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#register" }, { name = "muc#register_roomnick", type = "text-single", required = true, label = "Nickname"}, }; module:handle_items("muc-registration-field", function (event) module:log("debug", "Adding MUC registration form field: %s", event.item.name); table.insert(registration_form, event.item); end, function (event) module:log("debug", "Removing MUC registration form field: %s", event.item.name); local removed_field_name = event.item.name; for i, field in ipairs(registration_form) do if field.name == removed_field_name then table.remove(registration_form, i); break; end end end); local function enforce_nick_policy(event) local origin, stanza = event.origin, event.stanza; local room = assert(event.room); -- FIXME if not room then return; end -- Check if the chosen nickname is reserved local requested_nick = jid_resource(stanza.attr.to); local reserved_by = get_registered_jid(room, requested_nick); if reserved_by and reserved_by ~= jid_bare(stanza.attr.from) then module:log("debug", "%s attempted to use nick %s reserved by %s", stanza.attr.from, requested_nick, reserved_by); local reply = st.error_reply(stanza, "cancel", "conflict", nil, room.jid):up(); origin.send(reply); return true; end -- Check if the occupant has a reservation they must use if enforce_nick then local nick = get_registered_nick(room, jid_bare(stanza.attr.from)); if nick then if event.occupant then event.occupant.nick = jid_bare(event.occupant.nick) .. "/" .. nick; elseif event.dest_occupant.nick ~= jid_bare(event.dest_occupant.nick) .. "/" .. nick then module:log("debug", "Attempt by %s to join as %s, but their reserved nick is %s", stanza.attr.from, requested_nick, nick); local reply = st.error_reply(stanza, "cancel", "not-acceptable", nil, room.jid):up(); origin.send(reply); return true; end end end end module:hook("muc-occupant-pre-join", enforce_nick_policy); module:hook("muc-occupant-pre-change", enforce_nick_policy); -- Discovering Reserved Room Nickname -- http://xmpp.org/extensions/xep-0045.html#reservednick module:hook("muc-disco#info/x-roomuser-item", function (event) local nick = get_registered_nick(event.room, jid_bare(event.stanza.attr.from)); if nick then event.reply:tag("identity", { category = "conference", type = "text", name = nick }) end end); local function handle_register_iq(room, origin, stanza) local user_jid = jid_bare(stanza.attr.from) local affiliation = room:get_affiliation(user_jid); if affiliation == "outcast" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; elseif not (affiliation or allow_unaffiliated) then origin.send(st.error_reply(stanza, "auth", "registration-required")); return true; end local reply = st.reply(stanza); local registered_nick = get_registered_nick(room, user_jid); if stanza.attr.type == "get" then reply:query("jabber:iq:register"); if registered_nick then reply:tag("registered"):up(); reply:tag("username"):text(registered_nick):up(); if include_reg_form then local aff_data = room:get_affiliation_data(user_jid); if aff_data then reply:add_child(registration_form:form(aff_data, "result")); end end origin.send(reply); return true; end reply:add_child(registration_form:form()); else -- type == set -- handle registration form local query = stanza.tags[1]; if query:get_child("remove") then -- Remove "member" affiliation, but preserve if any other local new_affiliation = affiliation ~= "member" and affiliation; local ok, err_type, err_condition = room:set_affiliation(true, user_jid, new_affiliation, nil, false); if not ok then origin.send(st.error_reply(stanza, err_type, err_condition)); return true; end origin.send(reply); return true; end local form_tag = query:get_child("x", "jabber:x:data"); if not form_tag then origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing dataform")); return true; end local form_type, err = dataforms.get_type(form_tag); if not form_type then origin.send(st.error_reply(stanza, "modify", "bad-request", "Error with form: "..err)); return true; elseif form_type ~= "http://jabber.org/protocol/muc#register" then origin.send(st.error_reply(stanza, "modify", "bad-request", "Error in form")); return true; end local reg_data = registration_form:data(form_tag); if not reg_data then origin.send(st.error_reply(stanza, "modify", "bad-request", "Error in form")); return true; end -- Is the nickname valid? local desired_nick = resourceprep(reg_data["muc#register_roomnick"], true); if not desired_nick then origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid Nickname")); return true; end -- Is the nickname currently in use by another user? local current_occupant = room:get_occupant_by_nick(room.jid.."/"..desired_nick); if current_occupant and current_occupant.bare_jid ~= user_jid then origin.send(st.error_reply(stanza, "cancel", "conflict")); return true; end -- Is the nickname currently reserved by another user? local reserved_by = get_registered_jid(room, desired_nick); if reserved_by and reserved_by ~= user_jid then origin.send(st.error_reply(stanza, "cancel", "conflict")); return true; end if enforce_nick then -- Kick any sessions that are not using this nick before we register it local required_room_nick = room.jid.."/"..desired_nick; for room_nick, occupant in room:each_occupant() do if occupant.bare_jid == user_jid and room_nick ~= required_room_nick then room:set_role(true, room_nick, nil); -- Kick (TODO: would be nice to use 333 code) end end end -- Checks passed, save the registration if registered_nick ~= desired_nick then local registration_data = { reserved_nickname = desired_nick }; module:fire_event("muc-registration-submitted", { room = room; origin = origin; stanza = stanza; submitted_data = reg_data; affiliation_data = registration_data; }); local ok, err_type, err_condition = room:set_affiliation(true, user_jid, affiliation or "member", nil, registration_data); if not ok then origin.send(st.error_reply(stanza, err_type, err_condition)); return true; end module:log("debug", "Saved nick registration for %s: %s", user_jid, desired_nick); origin.send(reply); return true; end end origin.send(reply); return true; end return { get_registered_nick = get_registered_nick; get_registered_jid = get_registered_jid; handle_register_iq = handle_register_iq; } prosody-0.12.4/plugins/muc/PaxHeaders/request.lib.lua0000644000000000000000000000012014475665104017571 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/request.lib.lua0000644000175000017500000000716414475665104022006 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local jid_resource = require "util.jid".resource; module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = "http://jabber.org/protocol/muc#request"}):up(); end); local voice_request_form = require "util.dataforms".new({ title = "Voice Request"; { name = "FORM_TYPE"; type = "hidden"; value = "http://jabber.org/protocol/muc#request"; }, { name = "muc#jid"; type = "jid-single"; label = "User ID"; desc = "The user's JID (address)"; }, { name = "muc#roomnick"; type = "text-single"; label = "Room nickname"; desc = "The user's nickname within the room"; }, { name = "muc#role"; type = "list-single"; label = "Requested role"; value = "participant"; options = { "none", "visitor", "participant", "moderator", }; }, { name = "muc#request_allow"; type = "boolean"; label = "Grant voice to this person?"; desc = "Specify whether this person is able to speak in a moderated room"; value = false; } }); local function handle_request(room, origin, stanza, form) local occupant = room:get_occupant_by_real_jid(stanza.attr.from); local fields = voice_request_form:data(form); local event = { room = room; origin = origin; stanza = stanza; fields = fields; occupant = occupant; }; if occupant.role == "moderator" then module:log("debug", "%s responded to a voice request in %s", jid_resource(occupant.nick), room.jid); module:fire_event("muc-voice-response", event); else module:log("debug", "%s requested voice in %s", jid_resource(occupant.nick), room.jid); module:fire_event("muc-voice-request", event); end end module:hook("muc-voice-request", function(event) if event.occupant.role == "visitor" then local nick = jid_resource(event.occupant.nick); local formdata = { ["muc#jid"] = event.stanza.attr.from; ["muc#roomnick"] = nick; }; local message = st.message({ type = "normal"; from = event.room.jid }) :add_child(voice_request_form:form(formdata)); event.room:broadcast(message, function (_, occupant) return occupant.role == "moderator"; end); end end); module:hook("muc-voice-response", function(event) local actor = event.stanza.attr.from; local affected_occupant = event.room:get_occupant_by_real_jid(event.fields["muc#jid"]); local occupant = event.occupant; if occupant.role ~= "moderator" then module:log("debug", "%s tried to grant voice but wasn't a moderator", jid_resource(occupant.nick)); return; end if not event.fields["muc#request_allow"] then module:log("debug", "%s did not grant voice", jid_resource(occupant.nick)); return; end if not affected_occupant then module:log("debug", "%s tried to grant voice to unknown occupant %s", jid_resource(occupant.nick), event.fields["muc#jid"]); return; end if affected_occupant.role ~= "visitor" then module:log("debug", "%s tried to grant voice to %s but they already have it", jid_resource(occupant.nick), jid_resource(occupant.jid)); return; end module:log("debug", "%s granted voice to %s", jid_resource(event.occupant.nick), jid_resource(occupant.jid)); local ok, errtype, err = event.room:set_role(actor, affected_occupant.nick, "participant", "Voice granted"); if not ok then module:log("debug", "Error granting voice: %s", err or errtype); event.origin.send(st.error_reply(event.stanza, errtype, err)); end end); return { handle_request = handle_request; }; prosody-0.12.4/plugins/muc/PaxHeaders/subject.lib.lua0000644000000000000000000000012014475665104017540 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.692764952 prosody-0.12.4/plugins/muc/subject.lib.lua0000644000175000017500000000760514475665104021755 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local dt = require "util.datetime"; local muc_util = module:require "muc/util"; local valid_roles = muc_util.valid_roles; local function create_subject_message(from, subject) return st.message({from = from; type = "groupchat"}) :tag("subject"):text(subject or ""):up(); end local function get_changesubject(room) return room._data.changesubject; end local function set_changesubject(room, changesubject) changesubject = changesubject and true or nil; if get_changesubject(room) == changesubject then return false; end room._data.changesubject = changesubject; return true; end module:hook("muc-disco#info", function (event) table.insert(event.form, { name = "muc#roomconfig_changesubject"; type = "boolean"; }); event.formdata["muc#roomconfig_changesubject"] = get_changesubject(event.room); end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = "muc#roomconfig_changesubject"; type = "boolean"; label = "Allow anyone to set the room's subject"; desc = "Choose whether anyone, or only moderators, may set the room's subject"; value = get_changesubject(event.room); }); end, 80-1); module:hook("muc-config-submitted/muc#roomconfig_changesubject", function(event) if set_changesubject(event.room, event.value) then event.status_codes["104"] = true; end end); local function get_subject(room) -- a stanza from the room JID (or from the occupant JID of the entity that set the subject) return room._data.subject_from or room.jid, room._data.subject; end local function send_subject(room, to, time) local msg = create_subject_message(get_subject(room)); msg.attr.to = to; if time then msg:tag("delay", { xmlns = "urn:xmpp:delay", from = room.jid, stamp = dt.datetime(time); }):up(); end room:route_stanza(msg); end local function set_subject(room, from, subject) if subject == "" then subject = nil; end local old_from, old_subject = get_subject(room); if old_subject == subject and old_from == from then return false; end room._data.subject_from = from; room._data.subject = subject; room._data.subject_time = os.time(); local msg = create_subject_message(from, subject); room:broadcast_message(msg); return true; end -- Send subject to joining user module:hook("muc-occupant-session-new", function(event) send_subject(event.room, event.stanza.attr.from, event.room._data.subject_time); end, 20); -- Prosody has made the decision that messages with are exclusively subject changes -- e.g. body will be ignored; even if the subject change was not allowed module:hook("muc-occupant-groupchat", function(event) local stanza = event.stanza; local subject = stanza:get_child("subject"); if subject then if stanza:get_child("body") or stanza:get_child("thread") then -- Note: A message with a and a or a and -- a is a legitimate message, but it SHALL NOT be interpreted -- as a subject change. return; end local room = event.room; local occupant = event.occupant; -- Role check for subject changes local role_rank = valid_roles[occupant and occupant.role or "none"]; if role_rank >= valid_roles.moderator or ( role_rank >= valid_roles.participant and get_changesubject(room) ) then -- and participant set_subject(room, occupant.nick, subject:get_text()); room:save(); return true; else event.origin.send(st.error_reply(stanza, "auth", "forbidden", "You are not allowed to change the subject")); return true; end end end, 20); return { get_changesubject = get_changesubject; set_changesubject = set_changesubject; get = get_subject; set = set_subject; send = send_subject; }; prosody-0.12.4/plugins/muc/PaxHeaders/util.lib.lua0000644000000000000000000000012014475665104017056 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/plugins/muc/util.lib.lua0000644000175000017500000000316714475665104021272 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local _M = {}; _M.valid_affiliations = { outcast = -1; none = 0; member = 1; admin = 2; owner = 3; }; _M.valid_roles = { none = 0; visitor = 1; participant = 2; moderator = 3; }; local kickable_error_conditions = { ["gone"] = true; ["internal-server-error"] = true; ["item-not-found"] = true; ["jid-malformed"] = true; ["recipient-unavailable"] = true; ["redirect"] = true; ["remote-server-not-found"] = true; ["remote-server-timeout"] = true; ["service-unavailable"] = true; ["malformed error"] = true; }; function _M.is_kickable_error(stanza) local cond = select(2, stanza:get_error()) or "malformed error"; return kickable_error_conditions[cond]; end local filtered_namespaces = module:shared("filtered-namespaces"); filtered_namespaces["http://jabber.org/protocol/muc"] = true; filtered_namespaces["http://jabber.org/protocol/muc#user"] = true; local function muc_ns_filter(tag) if filtered_namespaces[tag.attr.xmlns] then return nil; end return tag; end function _M.filter_muc_x(stanza) return stanza:maptags(muc_ns_filter); end function _M.add_filtered_namespace(xmlns) filtered_namespaces[xmlns] = true; end function _M.only_with_min_role(role) local min_role_value = _M.valid_roles[role]; return function (nick, occupant) --luacheck: ignore 212/nick if _M.valid_roles[occupant.role or "none"] >= min_role_value then return true; end end; end return _M; prosody-0.12.4/plugins/muc/PaxHeaders/whois.lib.lua0000644000000000000000000000012014475665104017232 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/plugins/muc/whois.lib.lua0000644000175000017500000000403414475665104021440 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local valid_whois = { moderators = true; anyone = true; }; local function get_whois(room) return room._data.whois or "moderators"; end local function set_whois(room, whois) assert(valid_whois[whois], "Invalid whois value") if get_whois(room) == whois then return false; end room._data.whois = whois; return true; end module:hook("muc-disco#info", function(event) local whois = get_whois(event.room) ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"; event.reply:tag("feature", { var = whois }):up(); end); module:hook("muc-config-form", function(event) local whois = get_whois(event.room); table.insert(event.form, { name = 'muc#roomconfig_whois', type = 'list-single', label = 'Addresses (JIDs) of room occupants may be viewed by:', options = { { value = 'moderators', label = 'Moderators only', default = whois == 'moderators' }, { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } } }); end, 80-4); module:hook("muc-config-submitted/muc#roomconfig_whois", function(event) if set_whois(event.room, event.value) then local code = (event.value == 'moderators') and "173" or "172"; event.status_codes[code] = true; end end); -- Mask 'from' jid as occupant jid if room is anonymous module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza; if get_whois(room) == "moderators" and room:get_default_role(room:get_affiliation(stanza.attr.to)) ~= "moderator" then local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); local occupant_jid = room:get_occupant_jid(invite.attr.from); if occupant_jid ~= nil then -- FIXME: This will expose real jid if inviter is not in room invite.attr.from = occupant_jid; end end end, 50); return { get = get_whois; set = set_whois; }; prosody-0.12.4/PaxHeaders/prosody0000644000000000000000000000012014475665104014006 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/prosody0000755000175000017500000000460214475665104016220 0ustar00prosodyprosody00000000000000#!/usr/bin/env lua -- 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. -- -- prosody - main executable for Prosody XMPP server -- Will be modified by configure script if run -- CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR"); CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR"); CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR"); CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR"); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- local function is_relative(path) local path_sep = package.config:sub(1,1); return ((path_sep == "/" and path:sub(1,1) ~= "/") or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\"))) end -- Tell Lua where to find our libraries if CFG_SOURCEDIR then local function filter_relative_paths(path) if is_relative(path) then return ""; end end local function sanitise_paths(paths) return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";")); end package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path); package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath); end -- Substitute ~ with path to home directory in data path if CFG_DATADIR then if os.getenv("HOME") then CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME")); end end local startup = require "util.startup"; local async = require "util.async"; -- Note: it's important that this thread is not GC'd, as some C libraries -- that are initialized here store a pointer to it ( :/ ). local thread = async.runner(); thread:run(startup.prosody); prosody.main_thread = thread; local function loop() -- Error handler for errors that make it this far local function catch_uncaught_error(err) if type(err) == "string" and err:match("interrupted!$") then return "quitting"; end prosody.log("error", "Top-level error, please report:\n%s", tostring(err)); local traceback = debug.traceback("", 2); if traceback then prosody.log("error", "%s", traceback); end prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback}); end local sleep = require"socket".sleep; local server = require "net.server"; while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do sleep(0.2); end end loop(); startup.exit(); prosody-0.12.4/PaxHeaders/prosody.cfg.lua.dist0000644000000000000000000000012014475665104016266 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/prosody.cfg.lua.dist0000644000175000017500000002434114475665104020477 0ustar00prosodyprosody00000000000000-- Prosody Example Configuration File -- -- Information on configuring Prosody can be found on our -- website at https://prosody.im/doc/configure -- -- Tip: You can check that the syntax of this file is correct -- when you have finished by running this command: -- prosodyctl check config -- If there are any errors, it will let you know what and where -- they are, otherwise it will keep quiet. -- -- The only thing left to do is rename this file to remove the .dist ending, and fill in the -- blanks. Good luck, and happy Jabbering! ---------- Server-wide settings ---------- -- Settings in this section apply to the whole server and are the default settings -- for any virtual hosts -- This is a (by default, empty) list of accounts that are admins -- for the server. Note that you must create the accounts separately -- (see https://prosody.im/doc/creating_accounts for info) -- Example: admins = { "user1@example.com", "user2@example.net" } admins = { } -- This option allows you to specify additional locations where Prosody -- will search first for modules. For additional modules you can install, see -- the community module repository at https://modules.prosody.im/ --plugin_paths = {} -- This is the list of modules Prosody will load on startup. -- Documentation for bundled modules can be found at: https://prosody.im/doc/modules modules_enabled = { -- Generally required "disco"; -- Service discovery "roster"; -- Allow users to have a roster. Recommended ;) "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. "tls"; -- Add support for secure TLS on c2s/s2s connections -- Not essential, but recommended "blocklist"; -- Allow users to block communications with other users "bookmarks"; -- Synchronise the list of open rooms between clients "carbons"; -- Keep multiple online clients in sync "dialback"; -- Support for verifying remote servers using DNS "limits"; -- Enable bandwidth limiting for XMPP connections "pep"; -- Allow users to store public and private data in their account "private"; -- Legacy account storage mechanism (XEP-0049) "smacks"; -- Stream management and resumption (XEP-0198) "vcard4"; -- User profiles (stored in PEP) "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard -- Nice to have "csi_simple"; -- Simple but effective traffic optimizations for mobile devices "invites"; -- Create and manage invites "invites_adhoc"; -- Allow admins/users to create invitations via their client "invites_register"; -- Allows invited users to create accounts "ping"; -- Replies to XMPP pings with pongs "register"; -- Allow users to register on this server using a client and change passwords "time"; -- Let others know the time here on this server "uptime"; -- Report how long server has been running "version"; -- Replies to server version requests --"mam"; -- Store recent messages to allow multi-device synchronization --"turn_external"; -- Provide external STUN/TURN service for e.g. audio/video calls -- Admin interfaces "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands "admin_shell"; -- Allow secure administration via 'prosodyctl shell' -- HTTP modules --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP" --"http_openmetrics"; -- for exposing metrics to stats collectors --"websocket"; -- XMPP over WebSockets -- Other specific functionality --"announce"; -- Send announcement to all online users --"groups"; -- Shared roster support --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots. --"mimicking"; -- Prevent address spoofing --"motd"; -- Send a message to users when they log in --"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use --"s2s_bidi"; -- Bi-directional server-to-server (XEP-0288) --"server_contact_info"; -- Publish contact information for this service --"tombstones"; -- Prevent registration of deleted accounts --"watchregistrations"; -- Alert admins of registrations --"welcome"; -- Welcome users who register accounts } -- These modules are auto-loaded, but should you want -- to disable them then uncomment them here: modules_disabled = { -- "offline"; -- Store offline messages -- "c2s"; -- Handle client connections -- "s2s"; -- Handle server-to-server connections -- "posix"; -- POSIX functionality, sends server to background, etc. } -- Server-to-server authentication -- Require valid certificates for server-to-server connections? -- If false, other methods such as dialback (DNS) may be used instead. s2s_secure_auth = true -- Some servers have invalid or self-signed certificates. You can list -- remote domains here that will not be required to authenticate using -- certificates. They will be authenticated using other methods instead, -- even when s2s_secure_auth is enabled. --s2s_insecure_domains = { "insecure.example" } -- Even if you disable s2s_secure_auth, you can still require valid -- certificates for some domains by specifying a list here. --s2s_secure_domains = { "jabber.org" } -- Rate limits -- Enable rate limits for incoming client and server connections. These help -- protect from excessive resource consumption and denial-of-service attacks. limits = { c2s = { rate = "10kb/s"; }; s2sin = { rate = "30kb/s"; }; } -- Authentication -- Select the authentication backend to use. The 'internal' providers -- use Prosody's configured data storage to store the authentication data. -- For more information see https://prosody.im/doc/authentication authentication = "internal_hashed" -- Many authentication providers, including the default one, allow you to -- create user accounts via Prosody's admin interfaces. For details, see the -- documentation at https://prosody.im/doc/creating_accounts -- Storage -- Select the storage backend to use. By default Prosody uses flat files -- in its configured data directory, but it also supports more backends -- through modules. An "sql" backend is included by default, but requires -- additional dependencies. See https://prosody.im/doc/storage for more info. --storage = "sql" -- Default is "internal" -- For the "sql" backend, you can uncomment *one* of the below to configure: --sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename. --sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } --sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } -- Archiving configuration -- If mod_mam is enabled, Prosody will store a copy of every message. This -- is used to synchronize conversations between multiple clients, even if -- they are offline. This setting controls how long Prosody will keep -- messages in the archive before removing them. archive_expires_after = "1w" -- Remove archived messages after 1 week -- You can also configure messages to be stored in-memory only. For more -- archiving options, see https://prosody.im/doc/modules/mod_mam -- Audio/video call relay (STUN/TURN) -- To ensure clients connected to the server can establish connections for -- low-latency media streaming (such as audio and video calls), it is -- recommended to run a STUN/TURN server for clients to use. If you do this, -- specify the details here so clients can discover it. -- Find more information at https://prosody.im/doc/turn -- Specify the address of the TURN service (you may use the same domain as XMPP) --turn_external_host = "turn.example.com" -- This secret must be set to the same value in both Prosody and the TURN server --turn_external_secret = "your-secret-turn-access-token" -- Logging configuration -- For advanced logging see https://prosody.im/doc/logging log = { info = "prosody.log"; -- Change 'info' to 'debug' for verbose logging error = "prosody.err"; -- "*syslog"; -- Uncomment this for logging to syslog -- "*console"; -- Log to the console, useful for debugging when running in the foreground } -- Uncomment to enable statistics -- For more info see https://prosody.im/doc/statistics -- statistics = "internal" -- Certificates -- Every virtual host and component needs a certificate so that clients and -- servers can securely verify its identity. Prosody will automatically load -- certificates/keys from the directory specified here. -- For more information, including how to use 'prosodyctl' to auto-import certificates -- (from e.g. Let's Encrypt) see https://prosody.im/doc/certificates -- Location of directory to find certificates in (relative to main config file): certificates = "certs" ----------- Virtual hosts ----------- -- You need to add a VirtualHost entry for each domain you wish Prosody to serve. -- Settings under each VirtualHost entry apply *only* to that host. VirtualHost "localhost" -- Prosody requires at least one enabled VirtualHost to function. You can -- safely remove or disable 'localhost' once you have added another. --VirtualHost "example.com" ------ Components ------ -- You can specify components to add hosts that provide special services, -- like multi-user conferences, and transports. -- For more information on components, see https://prosody.im/doc/components ---Set up a MUC (multi-user chat) room server on conference.example.com: --Component "conference.example.com" "muc" --- Store MUC messages in an archive and allow users to access it --modules_enabled = { "muc_mam" } ---Set up a file sharing component --Component "share.example.com" "http_file_share" ---Set up an external component (default component port is 5347) -- -- External components allow adding various services, such as gateways/ -- bridges to non-XMPP networks and services. For more info -- see: https://prosody.im/doc/components#adding_an_external_component -- --Component "gateway.example.com" -- component_secret = "password" ---------- End of the Prosody Configuration file ---------- -- You usually **DO NOT** want to add settings here at the end, as they would -- only apply to the last defined VirtualHost or Component. -- -- Settings for the global section should go higher up, before the first -- VirtualHost or Component line, while settings intended for specific hosts -- should go under the corresponding VirtualHost or Component line. -- -- For more information see https://prosody.im/doc/configure prosody-0.12.4/PaxHeaders/prosody.release0000644000000000000000000000013214475665104015430 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.840763501 30 ctime=1693936196.840763501 prosody-0.12.4/prosody.release0000644000175000017500000000000714475665104017627 0ustar00prosodyprosody000000000000000.12.4 prosody-0.12.4/PaxHeaders/prosodyctl0000644000000000000000000000012014475665104014511 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/prosodyctl0000755000175000017500000005256214475665104016733 0ustar00prosodyprosody00000000000000#!/usr/bin/env lua -- 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. -- -- prosodyctl - command-line controller for Prosody XMPP server -- Will be modified by configure script if run -- CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR"); CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR"); CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR"); CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR"); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- local function is_relative(path) local path_sep = package.config:sub(1,1); return ((path_sep == "/" and path:sub(1,1) ~= "/") or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\"))) end -- Tell Lua where to find our libraries if CFG_SOURCEDIR then local function filter_relative_paths(path) if is_relative(path) then return ""; end end local function sanitise_paths(paths) return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";")); end package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path); package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath); end -- Substitute ~ with path to home directory in data path if CFG_DATADIR then if os.getenv("HOME") then CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME")); end end ----------- local startup = require "util.startup"; startup.prosodyctl(); ----------- local configmanager = require "core.configmanager"; local modulemanager = require "core.modulemanager" local prosodyctl = require "util.prosodyctl" local socket = require "socket" local dependencies = require "util.dependencies"; local lfs = dependencies.softreq "lfs"; ----------------------- local parse_args = require "util.argparse".parse; local human_io = require "util.human.io"; local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning; local show_usage = prosodyctl.show_usage; local read_password = human_io.read_password; local call_luarocks = prosodyctl.call_luarocks; local error_messages = prosodyctl.error_messages; local jid_split = require "util.jid".prepped_split; local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * 2; ----------------------- local commands = {}; local command = table.remove(arg, 1); local only_help = { short_params = { h = "help"; ["?"] = "help" } } function commands.install(arg) local opts = parse_args(arg, only_help); if opts.help or not arg[1] then show_usage([[install]], [[Installs a prosody/luarocks plugin]]); return opts.help and 0 or 1; end -- TODO finalize config option name local server = opts.server or configmanager.get("*", "plugin_server"); if not (arg[1]:match("^https://") or lfs.attributes(arg[1]) or server) then show_warning("There is no 'plugin_server' option in the configuration file"); -- see https://prosody.im/doc/TODO documentation -- #1602 return 1; end show_message("Installing %s in %s", arg[1], prosody.paths.installer); local ret = call_luarocks("install", arg[1], server); if ret == 0 and arg[1]:match("^mod_") then prosodyctl.show_module_configuration_help(arg[1]); end return ret; end function commands.remove(arg) local opts = parse_args(arg, only_help); if opts.help or not arg[1] then show_usage([[remove]], [[Removes a module installed in the working directory's plugins folder]]); return opts.help and 0 or 1; end show_message("Removing %s from %s", arg[1], prosody.paths.installer); local ret = call_luarocks("remove", arg[1]); return ret; end function commands.list(arg) local opts = parse_args(arg, only_help); if opts.help then show_usage([[list]], [[Shows installed rocks]]); return 0; end local server = opts.server or configmanager.get("*", "plugin_server"); if opts.outdated then -- put this back for luarocks arg[1] = "--outdated"; if not server then show_warning("There is no 'plugin_server' option in the configuration file, but this is needed for 'list --outdated' to work."); return 1; end end local ret = call_luarocks("list", arg[1], server); return ret; end function commands.adduser(arg) local opts = parse_args(arg, only_help); if opts.help or not arg[1] then show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); return opts.help and 0 or 1; end local user, host = jid_split(arg[1]); if not user and host then show_message [[Failed to understand JID, please supply the JID you want to create]] show_usage [[adduser user@host]] return 1; end if not host then show_message [[Please specify a JID, including a host. e.g. alice@example.com]]; return 1; end if not prosody.hosts[host] then show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host) show_warning("The user will not be able to log in until this is changed."); prosody.hosts[host] = startup.make_host(host); --luacheck: ignore 122 end if prosodyctl.user_exists{ user = user, host = host } then show_message [[That user already exists]]; return 1; end local password = read_password(); if not password then return 1; end local ok, msg = prosodyctl.adduser { user = user, host = host, password = password }; if ok then return 0; end show_message(error_messages[msg]) return 1; end function commands.passwd(arg) local opts = parse_args(arg, only_help); if opts.help or not arg[1] then show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]); return opts.help and 0 or 1; end local user, host = jid_split(arg[1]); if not user and host then show_message [[Failed to understand JID, please supply the JID you want to set the password for]] show_usage [[passwd user@host]] return 1; end if not host then show_message [[Please specify a JID, including a host. e.g. alice@example.com]]; return 1; end if not prosody.hosts[host] then show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host) show_warning("The user will not be able to log in until this is changed."); prosody.hosts[host] = startup.make_host(host); --luacheck: ignore 122 end if not prosodyctl.user_exists { user = user, host = host } then show_message [[That user does not exist, use prosodyctl adduser to create a new user]] return 1; end local password = read_password(); if not password then return 1; end local ok, msg = prosodyctl.passwd { user = user, host = host, password = password }; if ok then return 0; end show_message(error_messages[msg]) return 1; end function commands.deluser(arg) local opts = parse_args(arg, only_help); if opts.help or not arg[1] then show_usage([[deluser JID]], [[Permanently remove the specified user account from Prosody]]); return opts.help and 0 or 1; end local user, host = jid_split(arg[1]); if not user and host then show_message [[Failed to understand JID, please supply the JID to the user account you want to delete]] show_usage [[deluser user@host]] return 1; end if not host then show_message [[Please specify a JID, including a host. e.g. alice@example.com]]; return 1; end if not prosody.hosts[host] then show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host) prosody.hosts[host] = startup.make_host(host); --luacheck: ignore 122 end if not prosodyctl.user_exists { user = user, host = host } then show_message [[That user does not exist on this server]] return 1; end local ok, msg = prosodyctl.deluser { user = user, host = host }; if ok then return 0; end show_message(error_messages[msg]) return 1; end local function has_init_system() --> which lfs = lfs or require"lfs"; if lfs.attributes("/etc/systemd") then return "systemd"; elseif lfs.attributes("/etc/init.d/prosody") then return "rc.d"; end end local function service_command_warning(service_command) if prosody.installed and configmanager.get("*", "prosodyctl_service_warnings") ~= false then show_warning("WARNING: Use of prosodyctl start/stop/restart/reload is not recommended"); show_warning(" if Prosody is managed by an init system - use that directly instead."); local init = has_init_system() if init == "systemd" then show_warning(" e.g. systemctl %s prosody", service_command); elseif init == "rc.d" then show_warning(" e.g. /etc/init.d/prosody %s", service_command); end show_warning(""); end end function commands.start(arg) local opts = parse_args(arg, only_help); if opts.help then show_usage([[start]], [[Start Prosody]]); return 0; end service_command_warning("start"); local ok, ret = prosodyctl.isrunning(); if not ok then show_message(error_messages[ret]); return 1; end if ret then --luacheck: ignore 421/ret local ok, ret = prosodyctl.getpid(); if not ok then show_message("Couldn't get running Prosody's PID"); show_message(error_messages[ret]); return 1; end show_message("Prosody is already running with PID %s", ret or "(unknown)"); return 1; end --luacheck: ignore 411/ret local lua; do local i = 0; repeat i = i - 1; until arg[i-1] == nil lua = arg[i]; end local ok, ret = prosodyctl.start(prosody.paths.source, lua); if ok then local daemonize = configmanager.get("*", "daemonize"); if daemonize == nil then daemonize = prosody.installed; end if daemonize then local i=1; while true do local ok, running = prosodyctl.isrunning(); if ok and running then break; elseif i == 5 then show_message("Still waiting..."); elseif i >= prosodyctl_timeout then show_message("Prosody is still not running. Please give it some time or check your log files for errors."); return 2; end socket.sleep(0.5); i = i + 1; end show_message("Started"); end return 0; end show_message("Failed to start Prosody"); show_message(error_messages[ret]) return 1; end function commands.status(arg) local opts = parse_args(arg, only_help); if opts.help then show_usage([[status]], [[Reports the running status of Prosody]]); return 0; end local ok, ret = prosodyctl.isrunning(); if not ok then show_message(error_messages[ret]); return 1; end if ret then --luacheck: ignore 421/ret local ok, ret = prosodyctl.getpid(); if not ok then show_message("Couldn't get running Prosody's PID"); show_message(error_messages[ret]); return 1; end show_message("Prosody is running with PID %s", ret or "(unknown)"); return 0; else show_message("Prosody is not running"); if not prosody.switched_user and prosody.current_uid ~= 0 then print("\nNote:") print(" You will also see this if prosodyctl is not running under"); print(" the same user account as Prosody. Try running as root (e.g. "); print(" with 'sudo' in front) to gain access to Prosody's real status."); end return 2 end end function commands.stop(arg) local opts = parse_args(arg, only_help); if opts.help then show_usage([[stop]], [[Stop a running Prosody server]]); return 0; end service_command_warning("stop"); if not prosodyctl.isrunning() then show_message("Prosody is not running"); return 1; end local ok, ret = prosodyctl.stop(); if ok then local i=1; while true do local ok, running = prosodyctl.isrunning(); if ok and not running then break; elseif i == 5 then show_message("Still waiting..."); elseif i >= prosodyctl_timeout then show_message("Prosody is still running. Please give it some time or check your log files for errors."); return 2; end socket.sleep(0.5); i = i + 1; end show_message("Stopped"); return 0; end show_message(error_messages[ret]); return 1; end function commands.restart(arg) local opts = parse_args(arg, only_help); if opts.help then show_usage([[restart]], [[Restart a running Prosody server]]); return 1; end service_command_warning("restart"); commands.stop(arg); return commands.start(arg); end function commands.about(arg) local opts = parse_args(arg, only_help); if opts.help then show_usage([[about]], [[Show information about this Prosody installation]]); return 0; end local pwd = "."; local sorted_pairs = require "util.iterators".sorted_pairs; local hg = require"util.mercurial"; local relpath = configmanager.resolve_relative_path; print("Prosody "..(prosody.version or "(unknown version)")); print(""); print("# Prosody directories"); print("Data directory: "..relpath(pwd, prosody.paths.data)); print("Config directory: "..relpath(pwd, prosody.paths.config or ".")); print("Source directory: "..relpath(pwd, prosody.paths.source or ".")); print("Plugin directories:") print(" "..(prosody.paths.plugins:gsub("([^;]+);?", function(path) path = configmanager.resolve_relative_path(pwd, path); local hgid, hgrepo = hg.check_id(path); if not hgid and hgrepo then return path.." - "..hgrepo .."!\n "; end -- 010452cfaf53 is the first commit in the prosody-modules repository hgrepo = hgrepo == "010452cfaf53" and "prosody-modules"; return path..(hgid and " - "..(hgrepo or "HG").." rev: "..hgid or "") .."\n "; end))); print(""); local have_pposix, pposix = pcall(require, "util.pposix"); if have_pposix and pposix.uname then print("# Operating system"); local uname, err = pposix.uname(); print(uname and uname.sysname .. " " .. uname.release or "Unknown POSIX", err or ""); print(""); end print("# Lua environment"); print("Lua version: ", _G._VERSION); print(""); print("Lua module search paths:"); for path in package.path:gmatch("[^;]+") do print(" "..path); end print(""); print("Lua C module search paths:"); for path in package.cpath:gmatch("[^;]+") do print(" "..path); end print(""); local luarocks_status = "Not installed" if pcall(require, "luarocks.loader") then luarocks_status = "Installed (2.x+)"; if package.loaded["luarocks.cfg"] then luarocks_status = "Installed ("..(package.loaded["luarocks.cfg"].program_version or "2.x+")..")"; end elseif pcall(require, "luarocks.require") then luarocks_status = "Installed (1.x)"; end print("LuaRocks: ", luarocks_status); print(""); print("# Network"); print(""); print("Backend: "..require "net.server".get_backend()); print(""); print("# Lua module versions"); local module_versions, longest_name = {}, 8; local library_versions = {}; dependencies.softreq"ssl"; dependencies.softreq"DBI"; dependencies.softreq"readline"; local friendly_names = { DBI = "LuaDBI"; lfs = "LuaFileSystem"; lunbound = "luaunbound"; lxp = "LuaExpat"; socket = "LuaSocket"; ssl = "LuaSec"; }; local alternate_version_fields = { -- These diverge from the module._VERSION convention readline = "Version"; } local lunbound = dependencies.softreq"lunbound"; local lxp = dependencies.softreq"lxp"; local hashes = dependencies.softreq"util.hashes"; for name, module in pairs(package.loaded) do local version_field = alternate_version_fields[name] or "_VERSION"; if type(module) == "table" and rawget(module, version_field) and name ~= "_G" and not name:match("%.") then name = friendly_names[name] or name; if #name > longest_name then longest_name = #name; end local mod_version = module[version_field]; if tostring(mod_version):sub(1, #name+1) == name .. " " then mod_version = mod_version:sub(#name+2); end module_versions[name] = mod_version; end end if lunbound then if not module_versions["luaunbound"] then module_versions["luaunbound"] = "0.5 (?)"; end library_versions["libunbound"] = lunbound._LIBVER; end if lxp then library_versions["libexpat"] = lxp._EXPAT_VERSION; end if hashes then library_versions["libcrypto"] = hashes._LIBCRYPTO_VERSION; end for name, version in sorted_pairs(module_versions) do print(name..":"..string.rep(" ", longest_name-#name), version); end print(""); print("# library versions"); if require "net.server".event_base then library_versions["libevent"] = require"luaevent".core.libevent_version(); end for name, version in sorted_pairs(library_versions) do print(name..":"..string.rep(" ", longest_name-#name), version); end print(""); end function commands.reload(arg) local opts = parse_args(arg, only_help); if opts.help then show_usage([[reload]], [[Reload Prosody's configuration and re-open log files]]); return 0; end service_command_warning("reload"); if not prosodyctl.isrunning() then show_message("Prosody is not running"); return 1; end local ok, ret = prosodyctl.reload(); if ok then show_message("Prosody log files re-opened and config file reloaded. You may need to reload modules for some changes to take effect."); return 0; end show_message(error_messages[ret]); return 1; end -- ejabberdctl compatibility local unpack = table.unpack or unpack; -- luacheck: ignore 113 function commands.register(arg) local user, host, password = unpack(arg); if (not (user and host)) or arg[1] == "--help" then if user ~= "--help" then if not user then show_message [[No username specified]] elseif not host then show_message [[Please specify which host you want to register the user on]]; end end show_usage("register USER HOST [PASSWORD]", "Register a user on the server, with the given password"); return 1; end if not password then password = read_password(); if not password then show_message [[Unable to register user with no password]]; return 1; end end local ok, msg = prosodyctl.adduser { user = user, host = host, password = password }; if ok then return 0; end show_message(error_messages[msg]) return 1; end function commands.unregister(arg) local user, host = unpack(arg); if (not (user and host)) or arg[1] == "--help" then if user ~= "--help" then if not user then show_message [[No username specified]] elseif not host then show_message [[Please specify which host you want to unregister the user from]]; end end show_usage("unregister USER HOST [PASSWORD]", "Permanently remove a user account from the server"); return 1; end local ok, msg = prosodyctl.deluser { user = user, host = host }; if ok then return 0; end show_message(error_messages[msg]) return 1; end --------------------- local async = require "util.async"; local server = require "net.server"; local watchers = { error = function (_, err) error(err); end; waiting = function () server.loop(); end; }; local command_runner = async.runner(function () if command and command:match("^mod_") then -- Is a command in a module local module_name = command:match("^mod_(.+)"); do local ret, err = modulemanager.load("*", module_name); if not ret then show_message("Failed to load module '"..module_name.."': "..err); os.exit(1); end end local module = modulemanager.get_module("*", module_name); if not module then show_message("Failed to load module '"..module_name.."': Unknown error"); os.exit(1); end if not modulemanager.module_has_method(module, "command") then show_message("Fail: mod_"..module_name.." does not support any commands"); os.exit(1); end local ok, ret = modulemanager.call_module_method(module, "command", arg); if ok then if type(ret) == "number" then os.exit(ret, true); elseif type(ret) == "string" then show_message(ret); end os.exit(0, true); -- :) else show_message("Failed to execute command: %s", error_messages[ret]); os.exit(1); -- :( end end if command and not commands[command] then local ok, command_module = pcall(require, "util.prosodyctl."..command); if ok and command_module[command] then commands[command] = command_module[command]; end end if not commands[command] then -- Show help for all commands function show_usage(usage, desc) print(string.format(" %-11s %s", usage, desc)); end print("prosodyctl - Manage a Prosody server"); print(""); print("Usage: "..arg[0].." COMMAND [OPTIONS]"); print(""); print("Where COMMAND may be one of:"); local hidden_commands = require "util.set".new{ "register", "unregister" }; local commands_order = { "Process management:", "start"; "stop"; "restart"; "reload"; "status"; "shell", "User management:", "adduser"; "passwd"; "deluser"; "Plugin management:", "install"; "remove"; "list"; "Informative:", "about", "check", "Other:", "cert", }; -- These live in util.prosodyctl.$command so we have their short help here. local external_commands = { cert = "Certificate management commands", check = "Perform basic checks on your Prosody installation", shell = "Interact with a running Prosody", } local done = {}; if prosody.installed and has_init_system() then -- Hide start, stop, restart done[table.remove(commands_order, 2)] = true; done[table.remove(commands_order, 2)] = true; done[table.remove(commands_order, 2)] = true; end for _, command_name in ipairs(commands_order) do local command_func = commands[command_name]; if command_func then command_func{ "--help" }; done[command_name] = true; elseif external_commands[command_name] then show_usage(command_name, external_commands[command_name]); done[command_name] = true; else print"" print(command_name); end end for command_name, command_func in pairs(commands) do if not done[command_name] and not hidden_commands:contains(command_name) then command_func{ "--help" }; done[command_name] = true; end end os.exit(0, true); end os.exit(commands[command](arg), true); end, watchers); command_runner:run(true); prosody-0.12.4/PaxHeaders/spec0000644000000000000000000000013214475665104013244 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.696764913 30 ctime=1693936196.736764521 prosody-0.12.4/spec/0000755000175000017500000000000014475665104015523 5ustar00prosodyprosody00000000000000prosody-0.12.4/spec/PaxHeaders/core_configmanager_spec.lua0000644000000000000000000000012014475665104020643 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/spec/core_configmanager_spec.lua0000644000175000017500000000271714475665104023057 0ustar00prosodyprosody00000000000000 local configmanager = require "core.configmanager"; describe("core.configmanager", function() describe("#get()", function() it("should work", function() configmanager.set("example.com", "testkey", 123); assert.are.equal(123, configmanager.get("example.com", "testkey"), "Retrieving a set key"); configmanager.set("*", "testkey1", 321); assert.are.equal(321, configmanager.get("*", "testkey1"), "Retrieving a set global key"); assert.are.equal(321, configmanager.get("example.com", "testkey1"), "Retrieving a set key of undefined host, of which only a globally set one exists" ); configmanager.set("example.com", ""); -- Creates example.com host in config assert.are.equal(321, configmanager.get("example.com", "testkey1"), "Retrieving a set key, of which only a globally set one exists"); assert.are.equal(nil, configmanager.get(), "No parameters to get()"); assert.are.equal(nil, configmanager.get("undefined host"), "Getting for undefined host"); assert.are.equal(nil, configmanager.get("undefined host", "undefined key"), "Getting for undefined host & key"); end); end); describe("#set()", function() it("should work", function() assert.are.equal(false, configmanager.set("*"), "Set with no key"); assert.are.equal(true, configmanager.set("*", "set_test", "testkey"), "Setting a nil global value"); assert.are.equal(true, configmanager.set("*", "set_test", "testkey", 123), "Setting a global value"); end); end); end); prosody-0.12.4/spec/PaxHeaders/core_moduleapi_spec.lua0000644000000000000000000000012014475665104020022 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/spec/core_moduleapi_spec.lua0000644000175000017500000000575214475665104022240 0ustar00prosodyprosody00000000000000 package.loaded["core.configmanager"] = {}; package.loaded["core.statsmanager"] = {}; package.loaded["net.server"] = {}; local set = require "util.set"; _G.prosody = { hosts = {}, core_post_stanza = true }; local api = require "core.moduleapi"; local module = setmetatable({}, {__index = api}); local opt = nil; function module:log() end function module:get_option(name) if name == "opt" then return opt; else return nil; end end function test_option_value(value, returns) opt = value; assert(module:get_option_number("opt") == returns.number, "number doesn't match"); assert(module:get_option_string("opt") == returns.string, "string doesn't match"); assert(module:get_option_boolean("opt") == returns.boolean, "boolean doesn't match"); if type(returns.array) == "table" then local target_array, returned_array = returns.array, module:get_option_array("opt"); assert(#target_array == #returned_array, "array length doesn't match"); for i=1,#target_array do assert(target_array[i] == returned_array[i], "array item doesn't match"); end else assert(module:get_option_array("opt") == returns.array, "array is returned (not nil)"); end if type(returns.set) == "table" then local target_items, returned_items = set.new(returns.set), module:get_option_set("opt"); assert(target_items == returned_items, "set doesn't match"); else assert(module:get_option_set("opt") == returns.set, "set is returned (not nil)"); end end describe("core.moduleapi", function() describe("#get_option_*()", function() it("should handle missing options", function() test_option_value(nil, {}); end); it("should return correctly handle boolean options", function() test_option_value(true, { boolean = true, string = "true", array = {true}, set = {true} }); test_option_value(false, { boolean = false, string = "false", array = {false}, set = {false} }); test_option_value("true", { boolean = true, string = "true", array = {"true"}, set = {"true"} }); test_option_value("false", { boolean = false, string = "false", array = {"false"}, set = {"false"} }); test_option_value(1, { boolean = true, string = "1", array = {1}, set = {1}, number = 1 }); test_option_value(0, { boolean = false, string = "0", array = {0}, set = {0}, number = 0 }); end); it("should return handle strings", function() test_option_value("hello world", { string = "hello world", array = {"hello world"}, set = {"hello world"} }); end); it("should return handle numbers", function() test_option_value(1234, { string = "1234", number = 1234, array = {1234}, set = {1234} }); end); it("should return handle arrays", function() test_option_value({1, 2, 3}, { boolean = true, string = "1", number = 1, array = {1, 2, 3}, set = {1, 2, 3} }); test_option_value({1, 2, 3, 3, 4}, {boolean = true, string = "1", number = 1, array = {1, 2, 3, 3, 4}, set = {1, 2, 3, 4} }); test_option_value({0, 1, 2, 3}, { boolean = false, string = "0", number = 0, array = {0, 1, 2, 3}, set = {0, 1, 2, 3} }); end); end) end) prosody-0.12.4/spec/PaxHeaders/core_storagemanager_spec.lua0000644000000000000000000000012014475665104021042 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/spec/core_storagemanager_spec.lua0000644000175000017500000004410614475665104023254 0ustar00prosodyprosody00000000000000local unpack = table.unpack or unpack; -- luacheck: ignore 113 local server = require "net.server_select"; package.loaded["net.server"] = server; local st = require "util.stanza"; local function mock_prosody() _G.prosody = { core_post_stanza = function () end; events = require "util.events".new(); hosts = {}; paths = { data = "./data"; }; }; end local configs = { memory = { storage = "memory"; }; internal = { storage = "internal"; }; sqlite = { storage = "sql"; sql = { driver = "SQLite3", database = "prosody-tests.sqlite" }; }; mysql = { storage = "sql"; sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }; }; postgres = { storage = "sql"; sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }; }; }; local test_host = "storage-unit-tests.invalid"; describe("storagemanager", function () for backend, backend_config in pairs(configs) do local tagged_name = "#"..backend; if backend ~= backend_config.storage then tagged_name = tagged_name.." #"..backend_config.storage; end insulate(tagged_name.." #storage backend", function () mock_prosody(); local config = require "core.configmanager"; local sm = require "core.storagemanager"; local hm = require "core.hostmanager"; local mm = require "core.modulemanager"; -- Simple check to ensure insulation is working correctly assert.is_nil(config.get(test_host, "storage")); for k, v in pairs(backend_config) do config.set(test_host, k, v); end assert(hm.activate(test_host, {})); sm.initialize_host(test_host); assert(mm.load(test_host, "storage_"..backend_config.storage)); describe("key-value stores", function () -- These tests rely on being executed in order, disable any order -- randomization for this block randomize(false); local store; it("may be opened", function () store = assert(sm.open(test_host, "test")); end); local simple_data = { foo = "bar" }; it("may set data for a user", function () assert(store:set("user9999", simple_data)); end); it("may get data for a user", function () assert.same(simple_data, assert(store:get("user9999"))); end); it("may remove data for a user", function () assert(store:set("user9999", nil)); local ret, err = store:get("user9999"); assert.is_nil(ret); assert.is_nil(err); end); end); describe("map stores", function () -- These tests rely on being executed in order, disable any order -- randomization for this block randomize(false); local store, kv_store; it("may be opened", function () store = assert(sm.open(test_host, "test-map", "map")); end); it("may be opened as a keyval store", function () kv_store = assert(sm.open(test_host, "test-map", "keyval")); end); it("may set a specific key for a user", function () assert(store:set("user9999", "foo", "bar")); assert.same(kv_store:get("user9999"), { foo = "bar" }); end); it("may get a specific key for a user", function () assert.equal("bar", store:get("user9999", "foo")); end); it("may find all users with a specific key", function () assert.is_function(store.get_all); assert(store:set("user9999b", "bar", "bar")); assert(store:set("user9999c", "foo", "blah")); local ret, err = store:get_all("foo"); assert.is_nil(err); assert.same({ user9999 = "bar", user9999c = "blah" }, ret); end); it("rejects empty or non-string keys to get_all", function () assert.is_function(store.get_all); do local ret, err = store:get_all(""); assert.is_nil(ret); assert.is_not_nil(err); end do local ret, err = store:get_all(true); assert.is_nil(ret); assert.is_not_nil(err); end end); it("rejects empty or non-string keys to delete_all", function () assert.is_function(store.delete_all); do local ret, err = store:delete_all(""); assert.is_nil(ret); assert.is_not_nil(err); end do local ret, err = store:delete_all(true); assert.is_nil(ret); assert.is_not_nil(err); end end); it("may delete all instances of a specific key", function () assert.is_function(store.delete_all); assert(store:set("user9999b", "foo", "hello")); assert(store:delete_all("bar")); -- Ensure key was deleted do local ret, err = store:get("user9999b", "bar"); assert.is_nil(ret); assert.is_nil(err); end -- Ensure other users/keys are intact do local ret, err = store:get("user9999", "foo"); assert.equal("bar", ret); assert.is_nil(err); end do local ret, err = store:get("user9999b", "foo"); assert.equal("hello", ret); assert.is_nil(err); end do local ret, err = store:get("user9999c", "foo"); assert.equal("blah", ret); assert.is_nil(err); end end); it("may remove data for a specific key for a user", function () assert(store:set("user9999", "foo", nil)); do local ret, err = store:get("user9999", "foo"); assert.is_nil(ret); assert.is_nil(err); end assert(store:set("user9999b", "foo", nil)); do local ret, err = store:get("user9999b", "foo"); assert.is_nil(ret); assert.is_nil(err); end end); end); describe("archive stores", function () randomize(false); local archive; it("can be opened", function () archive = assert(sm.open(test_host, "test-archive", "archive")); end); local test_stanza = st.stanza("test", { xmlns = "urn:example:foo" }) :tag("foo"):up() :tag("foo"):up() :reset(); local test_time = 1539204123; local test_data = { { nil, test_stanza, test_time, "contact@example.com" }; { nil, test_stanza, test_time+1, "contact2@example.com" }; { nil, test_stanza, test_time+2, "contact2@example.com" }; { nil, test_stanza, test_time-1, "contact2@example.com" }; { nil, test_stanza, test_time-1, "contact3@example.com" }; { nil, test_stanza, test_time+0, "contact3@example.com" }; { nil, test_stanza, test_time+1, "contact3@example.com" }; }; it("can be added to", function () for _, data_item in ipairs(test_data) do local id = archive:append("user", unpack(data_item, 1, 4)); assert.truthy(id); data_item[1] = id; end end); describe("can be queried", function () it("for all items", function () -- luacheck: ignore 211/err local data, err = archive:find("user", {}); assert.truthy(data); local count = 0; for id, item, when in data do count = count + 1; assert.truthy(id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); assert.equal(test_data[count][3], when); end assert.equal(#test_data, count); end); it("by JID", function () -- luacheck: ignore 211/err local data, err = archive:find("user", { with = "contact@example.com"; }); assert.truthy(data); local count = 0; for id, item, when in data do count = count + 1; assert.truthy(id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); assert.equal(test_time, when); end assert.equal(1, count); end); it("by time (end)", function () -- luacheck: ignore 211/err local data, err = archive:find("user", { ["end"] = test_time; }); assert.truthy(data); local count = 0; for id, item, when in data do count = count + 1; assert.truthy(id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); assert(test_time >= when); end assert.equal(4, count); end); it("by time (start)", function () -- luacheck: ignore 211/err local data, err = archive:find("user", { ["start"] = test_time; }); assert.truthy(data); local count = 0; for id, item, when in data do count = count + 1; assert.truthy(id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); assert(test_time <= when); end assert.equal(#test_data - 2, count); end); it("by time (start+end)", function () -- luacheck: ignore 211/err local data, err = archive:find("user", { ["start"] = test_time; ["end"] = test_time+1; }); assert.truthy(data); local count = 0; for id, item, when in data do count = count + 1; assert.truthy(id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); assert(when >= test_time, ("%d >= %d"):format(when, test_time)); assert(when <= test_time+1, ("%d <= %d"):format(when, test_time+1)); end assert.equal(4, count); end); it("by id (after)", function () -- luacheck: ignore 211/err local data, err = archive:find("user", { ["after"] = test_data[2][1]; }); assert.truthy(data); local count = 0; for id, item in data do count = count + 1; assert.truthy(id); assert.equal(test_data[2+count][1], id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); end assert.equal(5, count); end); it("by id (before)", function () -- luacheck: ignore 211/err local data, err = archive:find("user", { ["before"] = test_data[4][1]; }); assert.truthy(data); local count = 0; for id, item in data do count = count + 1; assert.truthy(id); assert.equal(test_data[count][1], id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); end assert.equal(3, count); end); it("by id (before and after) #full_id_range", function () assert.truthy(archive.caps and archive.caps.full_id_range, "full ID range support") local data, err = archive:find("user", { ["after"] = test_data[1][1]; ["before"] = test_data[4][1]; }); assert.truthy(data, err); local count = 0; for id, item in data do count = count + 1; assert.truthy(id); assert.equal(test_data[1+count][1], id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); end assert.equal(2, count); end); it("by multiple ids", function () assert.truthy(archive.caps and archive.caps.ids, "Multiple ID query") local data, err = archive:find("user", { ["ids"] = { test_data[2][1]; test_data[4][1]; }; }); assert.truthy(data, err); local count = 0; for id, item in data do count = count + 1; assert.truthy(id); assert.equal(test_data[count==1 and 2 or 4][1], id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); end assert.equal(2, count); end); it("can be queried in reverse", function () local data, err = archive:find("user", { reverse = true; limit = 3; }); assert.truthy(data, err); local i = #test_data; for id, item in data do assert.truthy(id); assert.equal(test_data[i][1], id); assert(st.is_stanza(item)); assert.equal("test", item.name); assert.equal("urn:example:foo", item.attr.xmlns); assert.equal(2, #item.tags); i = i - 1; end end); end); it("can selectively delete items", function () local delete_id; do local data = assert(archive:find("user", {})); local count = 0; for id, item, when in data do --luacheck: ignore 213/item 213/when count = count + 1; if count == 2 then delete_id = id; end assert.truthy(id); end assert.equal(#test_data, count); end assert(archive:delete("user", { key = delete_id })); do local data = assert(archive:find("user", {})); local count = 0; for id, item, when in data do --luacheck: ignore 213/item 213/when count = count + 1; assert.truthy(id); assert.not_equal(delete_id, id); end assert.equal(#test_data-1, count); end end); it("can be purged", function () -- luacheck: ignore 211/err local ok, err = archive:delete("user"); assert.truthy(ok); local data, err = archive:find("user", { with = "contact@example.com"; }); assert.truthy(data); local count = 0; for id, item, when in data do -- luacheck: ignore id item when count = count + 1; end assert.equal(0, count); end); it("can truncate the oldest items", function () local username = "user-truncate"; for i = 1, 10 do assert(archive:append(username, nil, test_stanza, i, "contact@example.com")); end assert(archive:delete(username, { truncate = 3 })); do local data = assert(archive:find(username, {})); local count = 0; for id, item, when in data do --luacheck: ignore 213/when count = count + 1; assert.truthy(id); assert(st.is_stanza(item)); assert(when > 7, ("%d > 7"):format(when)); end assert.equal(3, count); end end); it("overwrites existing keys with new data", function () local prefix = ("a"):rep(50); local username = "user-overwrite"; local a1 = assert(archive:append(username, prefix.."-1", test_stanza, test_time, "contact@example.com")); local a2 = assert(archive:append(username, prefix.."-2", test_stanza, test_time, "contact@example.com")); local ids = { a1, a2, }; do local data = assert(archive:find(username, {})); local count = 0; for id, item, when in data do --luacheck: ignore 213/when count = count + 1; assert.truthy(id); assert.equals(ids[count], id); assert(st.is_stanza(item)); end assert.equal(2, count); end local new_stanza = st.clone(test_stanza); new_stanza.attr.foo = "bar"; assert(archive:append(username, a2, new_stanza, test_time+1, "contact2@example.com")); do local data = assert(archive:find(username, {})); local count = 0; for id, item, when in data do count = count + 1; assert.truthy(id); assert.equals(ids[count], id); assert(st.is_stanza(item)); if count == 2 then assert.equals(test_time+1, when); assert.equals("bar", item.attr.foo); end end assert.equal(2, count); end end); it("can contain multiple long unique keys #issue1073", function () local prefix = ("a"):rep(50); assert(archive:append("user-issue1073", prefix.."-1", test_stanza, test_time, "contact@example.com")); assert(archive:append("user-issue1073", prefix.."-2", test_stanza, test_time, "contact@example.com")); local data = assert(archive:find("user-issue1073", {})); local count = 0; for id, item, when in data do --luacheck: ignore 213/when count = count + 1; assert.truthy(id); assert(st.is_stanza(item)); end assert.equal(2, count); assert(archive:delete("user-issue1073")); end); it("can be treated as a map store", function () assert.falsy(archive:get("mapuser", "no-such-id")); assert.falsy(archive:set("mapuser", "no-such-id", test_stanza)); local id = archive:append("mapuser", nil, test_stanza, test_time, "contact@example.com"); do local stanza_roundtrip, when, with = archive:get("mapuser", id); assert.same(tostring(test_stanza), tostring(stanza_roundtrip), "same stanza is returned"); assert.equal(test_time, when, "same 'when' is returned"); assert.equal("contact@example.com", with, "same 'with' is returned"); end local replacement_stanza = st.stanza("test", { xmlns = "urn:example:foo" }) :tag("bar"):up() :reset(); assert(archive:set("mapuser", id, replacement_stanza, test_time+1)); do local replaced, when, with = archive:get("mapuser", id); assert.same(tostring(replacement_stanza), tostring(replaced), "replaced stanza is returned"); assert.equal(test_time+1, when, "modified 'when' is returned"); assert.equal("contact@example.com", with, "original 'with' is returned"); end end); it("the summary api works", function() assert.truthy(archive:delete("summary-user")); local first_sid = archive:append("summary-user", nil, test_stanza, test_time, "contact@example.com"); local second_sid = archive:append("summary-user", nil, test_stanza, test_time+1, "contact@example.com"); assert.truthy(first_sid and second_sid, "preparations failed") --- local user_summary, err = archive:summary("summary-user"); assert.is_table(user_summary, err); assert.same({ ["contact@example.com"] = 2 }, user_summary.counts, "summary.counts matches"); assert.same({ ["contact@example.com"] = test_time }, user_summary.earliest, "summary.earliest matches"); assert.same({ ["contact@example.com"] = test_time+1 }, user_summary.latest, "summary.latest matches"); if user_summary.body then assert.same({ ["contact@example.com"] = test_stanza:get_child_text("body") }, user_summary.body, "summary.body matches"); end end); end); end); end end); prosody-0.12.4/spec/PaxHeaders/inputs0000644000000000000000000000013214475665104014566 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.696764913 30 ctime=1693936196.696764913 prosody-0.12.4/spec/inputs/0000755000175000017500000000000014475665104017045 5ustar00prosodyprosody00000000000000prosody-0.12.4/spec/inputs/PaxHeaders/http0000644000000000000000000000013214475665104015545 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.696764913 30 ctime=1693936196.696764913 prosody-0.12.4/spec/inputs/http/0000755000175000017500000000000014475665104020024 5ustar00prosodyprosody00000000000000prosody-0.12.4/spec/inputs/http/PaxHeaders/httpstream-chunked-test.txt0000644000000000000000000000012014475665104023147 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/spec/inputs/http/httpstream-chunked-test.txt0000644000175000017500000002632414475665104025363 0ustar00prosodyprosody00000000000000HTTP/1.1 200 OK Cache-Control: max-age=0, must-revalidate, private Content-Type: application/json Date: Fri, 21 Aug 2020 12:18:51 GMT Expires: Fri, 21 Aug 2020 12:18:51 GMT Server: Apache/2.4.38 (Debian) Set-Cookie: PHPSESSID=00000000000000000000000000; path=/; HttpOnly Strict-Transport-Security: max-age=29030400 X-Powered-By: PHP/7.4.7 Transfer-Encoding: chunked 2b4d ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 prosody-0.12.4/spec/PaxHeaders/json0000644000000000000000000000013214475665104014215 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.696764913 30 ctime=1693936196.708764795 prosody-0.12.4/spec/json/0000755000175000017500000000000014475665104016474 5ustar00prosodyprosody00000000000000prosody-0.12.4/spec/json/PaxHeaders/fail1.json0000644000000000000000000000012014475665104016156 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.696764913 prosody-0.12.4/spec/json/fail1.json0000644000175000017500000000007414475665104020364 0ustar00prosodyprosody00000000000000"A JSON payload should be an object or array, not a string."prosody-0.12.4/spec/json/PaxHeaders/fail10.json0000644000000000000000000000012014475665104016236 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail10.json0000644000175000017500000000007214475665104020442 0ustar00prosodyprosody00000000000000{"Extra value after close": true} "misplaced quoted value"prosody-0.12.4/spec/json/PaxHeaders/fail11.json0000644000000000000000000000012014475665104016237 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail11.json0000644000175000017500000000003514475665104020442 0ustar00prosodyprosody00000000000000{"Illegal expression": 1 + 2}prosody-0.12.4/spec/json/PaxHeaders/fail12.json0000644000000000000000000000012014475665104016240 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail12.json0000644000175000017500000000003714475665104020445 0ustar00prosodyprosody00000000000000{"Illegal invocation": alert()}prosody-0.12.4/spec/json/PaxHeaders/fail13.json0000644000000000000000000000012014475665104016241 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail13.json0000644000175000017500000000005314475665104020444 0ustar00prosodyprosody00000000000000{"Numbers cannot have leading zeroes": 013}prosody-0.12.4/spec/json/PaxHeaders/fail14.json0000644000000000000000000000012014475665104016242 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail14.json0000644000175000017500000000003714475665104020447 0ustar00prosodyprosody00000000000000{"Numbers cannot be hex": 0x14}prosody-0.12.4/spec/json/PaxHeaders/fail15.json0000644000000000000000000000012014475665104016243 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail15.json0000644000175000017500000000004214475665104020444 0ustar00prosodyprosody00000000000000["Illegal backslash escape: \x15"]prosody-0.12.4/spec/json/PaxHeaders/fail16.json0000644000000000000000000000012014475665104016244 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail16.json0000644000175000017500000000001014475665104020440 0ustar00prosodyprosody00000000000000[\naked]prosody-0.12.4/spec/json/PaxHeaders/fail17.json0000644000000000000000000000012014475665104016245 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail17.json0000644000175000017500000000004214475665104020446 0ustar00prosodyprosody00000000000000["Illegal backslash escape: \017"]prosody-0.12.4/spec/json/PaxHeaders/fail18.json0000644000000000000000000000012014475665104016246 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail18.json0000644000175000017500000000006214475665104020451 0ustar00prosodyprosody00000000000000[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]prosody-0.12.4/spec/json/PaxHeaders/fail19.json0000644000000000000000000000012014475665104016247 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail19.json0000644000175000017500000000002614475665104020452 0ustar00prosodyprosody00000000000000{"Missing colon" null}prosody-0.12.4/spec/json/PaxHeaders/fail2.json0000644000000000000000000000012014475665104016157 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail2.json0000644000175000017500000000002114475665104020355 0ustar00prosodyprosody00000000000000["Unclosed array"prosody-0.12.4/spec/json/PaxHeaders/fail20.json0000644000000000000000000000012014475665104016237 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail20.json0000644000175000017500000000002714475665104020443 0ustar00prosodyprosody00000000000000{"Double colon":: null}prosody-0.12.4/spec/json/PaxHeaders/fail21.json0000644000000000000000000000012014475665104016240 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail21.json0000644000175000017500000000004014475665104020437 0ustar00prosodyprosody00000000000000{"Comma instead of colon", null}prosody-0.12.4/spec/json/PaxHeaders/fail22.json0000644000000000000000000000012014475665104016241 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail22.json0000644000175000017500000000004114475665104020441 0ustar00prosodyprosody00000000000000["Colon instead of comma": false]prosody-0.12.4/spec/json/PaxHeaders/fail23.json0000644000000000000000000000012014475665104016242 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail23.json0000644000175000017500000000002414475665104020443 0ustar00prosodyprosody00000000000000["Bad value", truth]prosody-0.12.4/spec/json/PaxHeaders/fail24.json0000644000000000000000000000012014475665104016243 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail24.json0000644000175000017500000000002014475665104020440 0ustar00prosodyprosody00000000000000['single quote']prosody-0.12.4/spec/json/PaxHeaders/fail25.json0000644000000000000000000000012014475665104016244 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.700764874 prosody-0.12.4/spec/json/fail25.json0000644000175000017500000000003514475665104020447 0ustar00prosodyprosody00000000000000[" tab character in string "]prosody-0.12.4/spec/json/PaxHeaders/fail26.json0000644000000000000000000000012014475665104016245 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail26.json0000644000175000017500000000004614475665104020452 0ustar00prosodyprosody00000000000000["tab\ character\ in\ string\ "]prosody-0.12.4/spec/json/PaxHeaders/fail27.json0000644000000000000000000000012014475665104016246 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail27.json0000644000175000017500000000001614475665104020450 0ustar00prosodyprosody00000000000000["line break"]prosody-0.12.4/spec/json/PaxHeaders/fail28.json0000644000000000000000000000012014475665104016247 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail28.json0000644000175000017500000000001714475665104020452 0ustar00prosodyprosody00000000000000["line\ break"]prosody-0.12.4/spec/json/PaxHeaders/fail29.json0000644000000000000000000000012014475665104016250 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail29.json0000644000175000017500000000000414475665104020447 0ustar00prosodyprosody00000000000000[0e]prosody-0.12.4/spec/json/PaxHeaders/fail3.json0000644000000000000000000000012014475665104016160 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail3.json0000644000175000017500000000004514475665104020364 0ustar00prosodyprosody00000000000000{unquoted_key: "keys must be quoted"}prosody-0.12.4/spec/json/PaxHeaders/fail30.json0000644000000000000000000000012014475665104016240 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail30.json0000644000175000017500000000000514475665104020440 0ustar00prosodyprosody00000000000000[0e+]prosody-0.12.4/spec/json/PaxHeaders/fail31.json0000644000000000000000000000012014475665104016241 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail31.json0000644000175000017500000000000714475665104020443 0ustar00prosodyprosody00000000000000[0e+-1]prosody-0.12.4/spec/json/PaxHeaders/fail32.json0000644000000000000000000000012014475665104016242 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail32.json0000644000175000017500000000005014475665104020442 0ustar00prosodyprosody00000000000000{"Comma instead if closing brace": true,prosody-0.12.4/spec/json/PaxHeaders/fail33.json0000644000000000000000000000012014475665104016243 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail33.json0000644000175000017500000000001414475665104020443 0ustar00prosodyprosody00000000000000["mismatch"}prosody-0.12.4/spec/json/PaxHeaders/fail4.json0000644000000000000000000000012014475665104016161 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail4.json0000644000175000017500000000002014475665104020356 0ustar00prosodyprosody00000000000000["extra comma",]prosody-0.12.4/spec/json/PaxHeaders/fail5.json0000644000000000000000000000012014475665104016162 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail5.json0000644000175000017500000000003014475665104020360 0ustar00prosodyprosody00000000000000["double extra comma",,]prosody-0.12.4/spec/json/PaxHeaders/fail6.json0000644000000000000000000000012014475665104016163 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail6.json0000644000175000017500000000003214475665104020363 0ustar00prosodyprosody00000000000000[ , "<-- missing value"]prosody-0.12.4/spec/json/PaxHeaders/fail7.json0000644000000000000000000000012014475665104016164 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail7.json0000644000175000017500000000003214475665104020364 0ustar00prosodyprosody00000000000000["Comma after the close"],prosody-0.12.4/spec/json/PaxHeaders/fail8.json0000644000000000000000000000012014475665104016165 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail8.json0000644000175000017500000000002014475665104020362 0ustar00prosodyprosody00000000000000["Extra close"]]prosody-0.12.4/spec/json/PaxHeaders/fail9.json0000644000000000000000000000012014475665104016166 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/fail9.json0000644000175000017500000000002614475665104020371 0ustar00prosodyprosody00000000000000{"Extra comma": true,}prosody-0.12.4/spec/json/PaxHeaders/pass1.json0000644000000000000000000000012014475665104016211 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/pass1.json0000644000175000017500000000264414475665104020424 0ustar00prosodyprosody00000000000000[ "JSON Test Pattern pass1", {"object with 1 member":["array with 1 element"]}, {}, [], -42, true, false, null, { "integer": 1234567890, "real": -9876.543210, "e": 0.123456789e-12, "E": 1.234567890E+34, "": 23456789012E66, "zero": 0, "one": 1, "space": " ", "quote": "\"", "backslash": "\\", "controls": "\b\f\n\r\t", "slash": "/ & \/", "alpha": "abcdefghijklmnopqrstuvwxyz", "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "digit": "0123456789", "0123456789": "digit", "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", "true": true, "false": false, "null": null, "array":[ ], "object":{ }, "address": "50 St. James Street", "url": "http://www.JSON.org/", "comment": "// /* */": " ", " s p a c e d " :[1,2 , 3 , 4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", "quotes": "" \u0022 %22 0x22 034 "", "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" : "A key can be any string" }, 0.5 ,98.6 , 99.44 , 1066, 1e1, 0.1e1, 1e-1, 1e00,2e+00,2e-00 ,"rosebud"] prosody-0.12.4/spec/json/PaxHeaders/pass2.json0000644000000000000000000000012014475665104016212 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.704764834 prosody-0.12.4/spec/json/pass2.json0000644000175000017500000000006414475665104020417 0ustar00prosodyprosody00000000000000[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]prosody-0.12.4/spec/json/PaxHeaders/pass3.json0000644000000000000000000000012014475665104016213 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/json/pass3.json0000644000175000017500000000022414475665104020416 0ustar00prosodyprosody00000000000000{ "JSON Test Pattern pass3": { "The outermost value": "must be an object or array.", "In this test": "It is an object." } } prosody-0.12.4/spec/json/PaxHeaders/pass4.json0000644000000000000000000000012014475665104016214 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/json/pass4.json0000644000175000017500000000010214475665104020412 0ustar00prosodyprosody00000000000000{ "one": [ ], "two": [], "three": [ ], "four": [ ] } prosody-0.12.4/spec/PaxHeaders/mod_bosh_spec.lua0000644000000000000000000000012014475665104016625 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/mod_bosh_spec.lua0000644000175000017500000004404014475665104021034 0ustar00prosodyprosody00000000000000 -- Requires a host 'localhost' with SASL ANONYMOUS local bosh_url = "http://localhost:5280/http-bind" local logger = require "util.logger"; local debug = false; local print = print; if debug then logger.add_simple_sink(print, { --"debug"; "info"; "warn"; "error"; }); else print = function () end end describe("#mod_bosh", function () local server = require "net.server_select"; package.loaded["net.server"] = server; local async = require "util.async"; local timer = require "util.timer"; local http = require "net.http".new({ suppress_errors = false }); local function sleep(n) local wait, done = async.waiter(); timer.add_task(n, function () done() end); wait(); end local st = require "util.stanza"; local xml = require "util.xml"; local function request(url, opt, cb, auto_wait) local wait, done = async.waiter(); local ok, err; http:request(url, opt, function (...) ok, err = pcall(cb, ...); if not ok then print("CAUGHT", err) end done(); end); local function err_wait(throw) wait(); if throw ~= false and not ok then error(err); end return ok, err; end if auto_wait == false then return err_wait; else err_wait(); end end local function run_async(f) local err; local r = async.runner(); r:onerror(function (_, err_) print("EER", err_) err = err_; server.setquitting("once"); end) :onwaiting(function () --server.loop(); end) :run(function () f() server.setquitting("once"); end); server.loop(); if err then error(err); end if r.state ~= "ready" then error("Runner in unexpected state: "..r.state); end end it("test endpoint should be reachable", function () -- This is partly just to ensure the other tests have a chance to succeed -- (i.e. the BOSH endpoint is up and functioning) local function test() request(bosh_url, nil, function (resp, code) if code ~= 200 then error("Unable to reach BOSH endpoint "..bosh_url); end assert.is_string(resp); end); end run_async(test); end); it("should respond to past rids with past responses", function () local resp_1000_1, resp_1000_2 = "1", "2"; local function test_bosh() local sid; -- Set up BOSH session request(bosh_url, { body = tostring(st.stanza("body", { to = "localhost"; from = "test@localhost"; content = "text/xml; charset=utf-8"; hold = "1"; rid = "998"; wait = "10"; ["xml:lang"] = "en"; ["xmpp:version"] = "1.0"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }) :tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() :tag("iq", { xmlns = "jabber:client", type = "set", id = "bind1" }) :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }) :tag("resource"):text("bosh-test1"):up() :up() :up() ); }, function (response_body) local resp = xml.parse(response_body); if not response_body:find("", 1, true) then print("ERR", resp:pretty_print()); error("Failed to set up BOSH session"); end sid = assert(resp.attr.sid); print("SID", sid); end); -- Receive some additional post-login stuff request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "999"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }) ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 999", resp:pretty_print()); end); -- Send first long poll print "SEND 1000#1" local wait1000 = request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "1000"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })) }, function (response_body) local resp = xml.parse(response_body); resp_1000_1 = resp; print("RESP 1000#1", resp:pretty_print()); end, false); -- Wait a couple of seconds sleep(2) -- Send an early request, causing rid 1000 to return early print "SEND 1001" local wait1001 = request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "1001"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })) }, function (response_body) local resp = xml.parse(response_body); print("RESP 1001", resp:pretty_print()); end, false); -- Ensure we've received the response for rid 1000 wait1000(); -- Sleep a couple of seconds print "...pause..." sleep(2); -- Re-send rid 1000, we should get the same response print "SEND 1000#2" request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "1000"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })) }, function (response_body) local resp = xml.parse(response_body); resp_1000_2 = resp; print("RESP 1000#2", resp:pretty_print()); end); local wait_final = request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "1002"; type = "terminate"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })) }, function () end, false); print "WAIT 1001" wait1001(); wait_final(); print "DONE ALL" end run_async(test_bosh); assert.truthy(resp_1000_1); assert.same(resp_1000_1, resp_1000_2); end); it("should handle out-of-order requests", function () local function test() local sid; -- Set up BOSH session local wait, done = async.waiter(); http:request(bosh_url, { body = tostring(st.stanza("body", { to = "localhost"; from = "test@localhost"; content = "text/xml; charset=utf-8"; hold = "1"; rid = "1"; wait = "10"; ["xml:lang"] = "en"; ["xmpp:version"] = "1.0"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })); }, function (response_body) local resp = xml.parse(response_body); sid = assert(resp.attr.sid, "Failed to set up BOSH session"); print("SID", sid); done(); end); print "WAIT 1" wait(); print "DONE 1" local rid2_response_received = false; -- Temporarily skip rid 2, to simulate missed request local wait3, done3 = async.waiter(); http:request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "3"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }):tag("iq", { xmlns = "jabber:client", type = "set", id = "bind" }) :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }):up() :up() ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 3", resp:pretty_print()); done3(); -- The server should not respond to this request until -- it has responded to rid 2 assert.is_true(rid2_response_received); end); print "SLEEPING" sleep(2); print "SLEPT" -- Send the "missed" rid 2 local wait2, done2 = async.waiter(); http:request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "2"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }):tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 2", resp:pretty_print()); rid2_response_received = true; done2(); end); print "WAIT 2" wait2(); print "WAIT 3" wait3(); print "QUIT" end run_async(test); end); it("should work", function () local function test() local sid; -- Set up BOSH session local wait, done = async.waiter(); http:request(bosh_url, { body = tostring(st.stanza("body", { to = "localhost"; from = "test@localhost"; content = "text/xml; charset=utf-8"; hold = "1"; rid = "1"; wait = "10"; ["xml:lang"] = "en"; ["xmpp:version"] = "1.0"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })); }, function (response_body) local resp = xml.parse(response_body); sid = assert(resp.attr.sid, "Failed to set up BOSH session"); print("SID", sid); done(); end); print "WAIT 1" wait(); print "DONE 1" local rid2_response_received = false; -- Send the "missed" rid 2 local wait2, done2 = async.waiter(); http:request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "2"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }):tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 2", resp:pretty_print()); rid2_response_received = true; done2(); end); local wait3, done3 = async.waiter(); http:request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "3"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }):tag("iq", { xmlns = "jabber:client", type = "set", id = "bind" }) :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }):up() :up() ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 3", resp:pretty_print()); done3(); -- The server should not respond to this request until -- it has responded to rid 2 assert.is_true(rid2_response_received); end); print "SLEEPING" sleep(2); print "SLEPT" print "WAIT 2" wait2(); print "WAIT 3" wait3(); print "QUIT" end run_async(test); end); it("should handle aborted pending requests", function () local resp_1000_1, resp_1000_2 = "1", "2"; local function test_bosh() local sid; -- Set up BOSH session request(bosh_url, { body = tostring(st.stanza("body", { to = "localhost"; from = "test@localhost"; content = "text/xml; charset=utf-8"; hold = "1"; rid = "998"; wait = "10"; ["xml:lang"] = "en"; ["xmpp:version"] = "1.0"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }) :tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() :tag("iq", { xmlns = "jabber:client", type = "set", id = "bind1" }) :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }) :tag("resource"):text("bosh-test1"):up() :up() :up() ); }, function (response_body) local resp = xml.parse(response_body); if not response_body:find("", 1, true) then print("ERR", resp:pretty_print()); error("Failed to set up BOSH session"); end sid = assert(resp.attr.sid); print("SID", sid); end); -- Receive some additional post-login stuff request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "999"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }) ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 999", resp:pretty_print()); end); -- Send first long poll print "SEND 1000#1" local wait1000_1 = request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "1000"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })) }, function (response_body) local resp = xml.parse(response_body); resp_1000_1 = resp; assert.is_nil(resp.attr.type); print("RESP 1000#1", resp:pretty_print()); end, false); -- Wait a couple of seconds sleep(2) -- Re-send rid 1000, we should eventually get a normal response (with no stanzas) print "SEND 1000#2" request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "1000"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })) }, function (response_body) local resp = xml.parse(response_body); resp_1000_2 = resp; assert.is_nil(resp.attr.type); print("RESP 1000#2", resp:pretty_print()); end); wait1000_1(); print "DONE ALL" end run_async(test_bosh); assert.truthy(resp_1000_1); assert.same(resp_1000_1, resp_1000_2); end); it("should fail on requests beyond rid window", function () local function test_bosh() local sid; -- Set up BOSH session request(bosh_url, { body = tostring(st.stanza("body", { to = "localhost"; from = "test@localhost"; content = "text/xml; charset=utf-8"; hold = "1"; rid = "998"; wait = "10"; ["xml:lang"] = "en"; ["xmpp:version"] = "1.0"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }) :tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() :tag("iq", { xmlns = "jabber:client", type = "set", id = "bind1" }) :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }) :tag("resource"):text("bosh-test1"):up() :up() :up() ); }, function (response_body) local resp = xml.parse(response_body); if not response_body:find("", 1, true) then print("ERR", resp:pretty_print()); error("Failed to set up BOSH session"); end sid = assert(resp.attr.sid); print("SID", sid); end); -- Receive some additional post-login stuff request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "999"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }) ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 999", resp:pretty_print()); end); -- Send poll with a rid that's too high (current + 2, where only current + 1 is allowed) print "SEND 1002(!)" request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "1002"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })) }, function (response_body) local resp = xml.parse(response_body); assert.equal("terminate", resp.attr.type); print("RESP 1002(!)", resp:pretty_print()); end); print "DONE ALL" end run_async(test_bosh); end); it("should always succeed for requests within the rid window", function () local function test() local sid; -- Set up BOSH session request(bosh_url, { body = tostring(st.stanza("body", { to = "localhost"; from = "test@localhost"; content = "text/xml; charset=utf-8"; hold = "1"; rid = "1"; wait = "10"; ["xml:lang"] = "en"; ["xmpp:version"] = "1.0"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; })); }, function (response_body) local resp = xml.parse(response_body); sid = assert(resp.attr.sid, "Failed to set up BOSH session"); print("SID", sid); end); print "DONE 1" request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "2"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }):tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 2", resp:pretty_print()); end); local resp3; request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "3"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }):tag("iq", { xmlns = "jabber:client", type = "set", id = "bind" }) :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }):up() :up() ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 3#1", resp:pretty_print()); resp3 = resp; end); request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "4"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }):tag("iq", { xmlns = "jabber:client", type = "get", id = "ping1" }) :tag("ping", { xmlns = "urn:xmpp:ping" }):up() :up() ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 4", resp:pretty_print()); end); request(bosh_url, { body = tostring(st.stanza("body", { sid = sid; rid = "3"; content = "text/xml; charset=utf-8"; ["xml:lang"] = "en"; xmlns = "http://jabber.org/protocol/httpbind"; ["xmlns:xmpp"] = "urn:xmpp:xbosh"; }):tag("iq", { xmlns = "jabber:client", type = "set", id = "bind" }) :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }):up() :up() ) }, function (response_body) local resp = xml.parse(response_body); print("RESP 3#2", resp:pretty_print()); assert.not_equal("terminate", resp.attr.type); assert.same(resp3, resp); end); print "QUIT" end run_async(test); end); end); prosody-0.12.4/spec/PaxHeaders/muc_util_spec.lua0000644000000000000000000000012014475665104016654 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/muc_util_spec.lua0000644000175000017500000000255414475665104021067 0ustar00prosodyprosody00000000000000local muc_util; local st = require "util.stanza"; do -- XXX Hack for lack of a mock moduleapi local env = setmetatable({ module = { _shared = {}; -- Close enough to the real module:shared() for our purposes here shared = function (self, name) local t = self._shared[name]; if t == nil then t = {}; self._shared[name] = t; end return t; end; } }, { __index = _ENV or _G }); muc_util = require "util.envload".envloadfile("plugins/muc/util.lib.lua", env)(); end describe("muc/util", function () describe("filter_muc_x()", function () it("correctly filters muc#user", function () local stanza = st.message({ to = "to", from = "from", id = "foo" }) :tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }) :tag("invite", { to = "user@example.com" }); assert.equal(1, #stanza.tags); assert.equal(stanza, muc_util.filter_muc_x(stanza)); assert.equal(0, #stanza.tags); end); it("correctly filters muc#user on a cloned stanza", function () local stanza = st.message({ to = "to", from = "from", id = "foo" }) :tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }) :tag("invite", { to = "user@example.com" }); assert.equal(1, #stanza.tags); local filtered = muc_util.filter_muc_x(st.clone(stanza)); assert.equal(1, #stanza.tags); assert.equal(0, #filtered.tags); end); end); end); prosody-0.12.4/spec/PaxHeaders/net_http_parser_spec.lua0000644000000000000000000000012014475665104020234 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/net_http_parser_spec.lua0000644000175000017500000000604414475665104022445 0ustar00prosodyprosody00000000000000local http_parser = require "net.http.parser"; local sha1 = require "util.hashes".sha1; local parser_input_bytes = 3; local function CRLF(s) return (s:gsub("\n", "\r\n")); end local function test_stream(stream, expect) local chunks_processed = 0; local success_cb = spy.new(function (packet) assert.is_table(packet); if packet.body ~= false then assert.is_equal(expect.body, packet.body); end if expect.chunks then if chunks_processed == 0 then assert.is_true(packet.partial); packet.body_sink = { write = function (_, data) chunks_processed = chunks_processed + 1; assert.equal(expect.chunks[chunks_processed], data); return true; end; }; end end end); local function options_cb() return { -- Force streaming API mode body_size_limit = expect.chunks and 0 or nil; buffer_size_limit = 10*1024*2; }; end local parser = http_parser.new(success_cb, error, (stream[1] or stream):sub(1,4) == "HTTP" and "client" or "server", options_cb) if type(stream) == "string" then for chunk in stream:gmatch("."..string.rep(".?", parser_input_bytes-1)) do parser:feed(chunk); end else for _, chunk in ipairs(stream) do parser:feed(chunk); end end if expect.chunks then assert.equal(chunks_processed, #expect.chunks); end assert.spy(success_cb).was_called(expect.count or 1); end describe("net.http.parser", function() describe("parser", function() it("should handle requests with no content-length or body", function () test_stream( CRLF[[ GET / HTTP/1.1 Host: example.com ]], { body = ""; } ); end); it("should handle responses with empty body", function () test_stream( CRLF[[ HTTP/1.1 200 OK Content-Length: 0 ]], { body = ""; } ); end); it("should handle simple responses", function () test_stream( CRLF[[ HTTP/1.1 200 OK Content-Length: 7 Hello ]], { body = "Hello\r\n", count = 1; } ); end); it("should handle chunked encoding in responses", function () test_stream( CRLF[[ HTTP/1.1 200 OK Transfer-Encoding: chunked 1 H 1 e 2 ll 1 o 0 ]], { body = "Hello", count = 3; } ); end); it("should handle a stream of responses", function () test_stream( CRLF[[ HTTP/1.1 200 OK Content-Length: 5 Hello HTTP/1.1 200 OK Transfer-Encoding: chunked 1 H 1 e 2 ll 1 o 0 ]], { body = "Hello", count = 4; } ); end); it("should correctly find chunk boundaries", function () test_stream({ CRLF[[ HTTP/1.1 200 OK Transfer-Encoding: chunked ]].."3\r\n:)\n\r\n"}, { count = 1; -- Once (partial) chunks = { ":)\n" }; } ); end); end); it("should handle large chunked responses", function () local data = io.open("spec/inputs/http/httpstream-chunked-test.txt", "rb"):read("*a"); -- Just a sanity check... text editors and things may mess with line endings, etc. assert.equal("25930f021785ae14053a322c2dbc1897c3769720", sha1(data, true), "test data malformed"); test_stream(data, { body = string.rep("~", 11085), count = 3; }); end); end); prosody-0.12.4/spec/PaxHeaders/net_http_server_spec.lua0000644000000000000000000000012014475665104020246 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/net_http_server_spec.lua0000644000175000017500000000071614475665104022457 0ustar00prosodyprosody00000000000000describe("net.http.server", function () package.loaded["net.server"] = {} local server = require "net.http.server"; describe("events", function () it("should work with util.helpers", function () -- See #1044 server.add_handler("GET host/foo/*", function () end, 0); server.add_handler("GET host/foo/bar", function () end, 0); local helpers = require "util.helpers"; assert.is.string(helpers.show_events(server._events)); end); end); end); prosody-0.12.4/spec/PaxHeaders/net_stun_spec.lua0000644000000000000000000000012014475665104016672 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/net_stun_spec.lua0000644000175000017500000000556714475665104021114 0ustar00prosodyprosody00000000000000local hex = require "util.hex"; local function parse(pkt_desc) local result = {}; for line in pkt_desc:gmatch("([^\n]+)\n") do local b1, b2, b3, b4 = line:match("^%s*(%x%x) (%x%x) (%x%x) (%x%x)%s"); if b1 then table.insert(result, b1); table.insert(result, b2); table.insert(result, b3); table.insert(result, b4); end end return hex.decode(table.concat(result)); end local sample_packet = parse[[ 00 01 00 60 Request type and message length 21 12 a4 42 Magic cookie 78 ad 34 33 } c6 ad 72 c0 } Transaction ID 29 da 41 2e } 00 06 00 12 USERNAME attribute header e3 83 9e e3 } 83 88 e3 83 } aa e3 83 83 } Username value (18 bytes) and padding (2 bytes) e3 82 af e3 } 82 b9 00 00 } 00 15 00 1c NONCE attribute header 66 2f 2f 34 } 39 39 6b 39 } 35 34 64 36 } 4f 4c 33 34 } Nonce value 6f 4c 39 46 } 53 54 76 79 } 36 34 73 41 } 00 14 00 0b REALM attribute header 65 78 61 6d } 70 6c 65 2e } Realm value (11 bytes) and padding (1 byte) 6f 72 67 00 } 00 08 00 14 MESSAGE-INTEGRITY attribute header f6 70 24 65 } 6d d6 4a 3e } 02 b8 e0 71 } HMAC-SHA1 fingerprint 2e 85 c9 a2 } 8c a8 96 66 } ]]; describe("net.stun", function () local stun = require "net.stun"; it("works", function () local packet = stun.new_packet(); assert.is_string(packet:serialize()); end); it("can decode the sample packet", function () local packet = stun.new_packet():deserialize(sample_packet); assert(packet); local method, method_name = packet:get_method(); assert.equal(1, method); assert.equal("binding", method_name); assert.equal("example.org", packet:get_attribute("realm")); end); it("can generate the sample packet", function () -- These values, and the sample packet, come from RFC 5769 2.4 local username = string.char( -- U+30DE KATAKANA LETTER MA 0xE3, 0x83, 0x9E, -- U+30C8 KATAKANA LETTER TO 0xE3, 0x83, 0x88, -- U+30EA KATAKANA LETTER RI 0xE3, 0x83, 0xAA, -- U+30C3 KATAKANA LETTER SMALL TU 0xE3, 0x83, 0x83, -- U+30AF KATAKANA LETTER KU 0xE3, 0x82, 0xAF, -- U+30B9 KATAKANA LETTER SU 0xE3, 0x82, 0xB9 ); -- Password: "TheMtr" and "TheMatrIX" (without -- quotes) respectively before and after SASLprep processing local password = "TheMatrIX"; local realm = "example.org"; local p3 = stun.new_packet("binding", "request"); p3.transaction_id = hex.decode("78AD3433C6AD72C029DA412E"); p3:add_attribute("username", username); p3:add_attribute("nonce", "f//499k954d6OL34oL9FSTvy64sA"); p3:add_attribute("realm", realm); local key = stun.get_long_term_auth_key(realm, username, password); p3:add_message_integrity(key); assert.equal(sample_packet, p3:serialize()); end); end); prosody-0.12.4/spec/PaxHeaders/net_websocket_frames_spec.lua0000644000000000000000000000012014475665104021224 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/net_websocket_frames_spec.lua0000644000175000017500000000454114475665104023435 0ustar00prosodyprosody00000000000000describe("net.websocket.frames", function () local nwf = require "net.websocket.frames"; local test_frames = { simple_empty = { ["opcode"] = 0; ["length"] = 0; ["data"] = ""; ["FIN"] = false; ["MASK"] = false; ["RSV1"] = false; ["RSV2"] = false; ["RSV3"] = false; }; simple_data = { ["opcode"] = 0; ["length"] = 5; ["data"] = "hello"; ["FIN"] = false; ["MASK"] = false; ["RSV1"] = false; ["RSV2"] = false; ["RSV3"] = false; }; simple_fin = { ["opcode"] = 0; ["length"] = 0; ["data"] = ""; ["FIN"] = true; ["MASK"] = false; ["RSV1"] = false; ["RSV2"] = false; ["RSV3"] = false; }; with_mask = { ["opcode"] = 0; ["length"] = 5; ["data"] = "hello"; ["key"] = " \0 \0"; ["FIN"] = true; ["MASK"] = true; ["RSV1"] = false; ["RSV2"] = false; ["RSV3"] = false; }; empty_with_mask = { ["opcode"] = 0; ["key"] = " \0 \0"; ["FIN"] = true; ["MASK"] = true; ["RSV1"] = false; ["RSV2"] = false; ["RSV3"] = false; }; ping = { ["opcode"] = 0x9; ["length"] = 4; ["data"] = "ping"; ["FIN"] = true; ["MASK"] = false; ["RSV1"] = false; ["RSV2"] = false; ["RSV3"] = false; }; pong = { ["opcode"] = 0xa; ["length"] = 4; ["data"] = "pong"; ["FIN"] = true; ["MASK"] = false; ["RSV1"] = false; ["RSV2"] = false; ["RSV3"] = false; }; } describe("build", function () local build = nwf.build; it("works", function () assert.equal("\0\0", build(test_frames.simple_empty)); assert.equal("\0\5hello", build(test_frames.simple_data)); assert.equal("\128\0", build(test_frames.simple_fin)); assert.equal("\128\133 \0 \0HeLlO", build(test_frames.with_mask)) assert.equal("\128\128 \0 \0", build(test_frames.empty_with_mask)) assert.equal("\137\4ping", build(test_frames.ping)); assert.equal("\138\4pong", build(test_frames.pong)); end); end); describe("parse", function () local parse = nwf.parse; it("works", function () assert.same(test_frames.simple_empty, parse("\0\0")); assert.same(test_frames.simple_data, parse("\0\5hello")); assert.same(test_frames.simple_fin, parse("\128\0")); assert.same(test_frames.with_mask, parse("\128\133 \0 \0HeLlO")); assert.same(test_frames.ping, parse("\137\4ping")); assert.same(test_frames.pong, parse("\138\4pong")); end); end); end); prosody-0.12.4/spec/PaxHeaders/scansion0000644000000000000000000000013214475665104015061 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.708764795 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/0000755000175000017500000000000014475665104017340 5ustar00prosodyprosody00000000000000prosody-0.12.4/spec/scansion/PaxHeaders/basic.scs0000644000000000000000000000012014475665104016726 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/scansion/basic.scs0000644000175000017500000000030414475665104021130 0ustar00prosodyprosody00000000000000# Basic login and initial presence [Client] Romeo jid: user@localhost password: password --------- Romeo connects Romeo sends: Romeo receives: Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/basic_message.scs0000644000000000000000000000012014475665104020432 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/scansion/basic_message.scs0000644000175000017500000000725314475665104022646 0ustar00prosodyprosody00000000000000# Basic message routing and delivery [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: juliet@localhost password: password [Client] Juliet's phone jid: juliet@localhost password: password resource: mobile --------- # Act 1, scene 1 # The clients connect Romeo connects Juliet connects Juliet's phone connects # Romeo publishes his presence. Juliet has not, and so does not receive presence. Romeo sends: Romeo receives: # Romeo sends a message to Juliet's full JID Romeo sends: Hello Juliet! Juliet receives: Hello Juliet! # Romeo sends a message to Juliet's phone Romeo sends: Hello Juliet, on your phone. Juliet's phone receives: Hello Juliet, on your phone. # Scene 2 # This requires the server to support offline messages (which is optional). # Romeo sends a message to Juliet's bare JID. This is not immediately delivered, as she # has not published presence on either of her resources. Romeo sends: Hello Juliet, are you there? # Juliet sends presence on her phone, and should receive the message there Juliet's phone sends: Juliet's phone receives: Juliet's phone receives: Hello Juliet, are you there? # Romeo sends another bare-JID message, it should be delivered # instantly to Juliet's phone Romeo sends: Oh, hi! Juliet's phone receives: Oh, hi! # Juliet's laptop goes online, but with a negative priority Juliet sends: -1 Juliet receives: -1 Juliet's phone receives: -1 # Again, Romeo sends a message to her bare JID, but it should # only get delivered to her phone: Romeo sends: How are you? Juliet's phone receives: How are you? # Romeo sends direct to Juliet's full JID, and she should receive it Romeo sends: Are you hiding? Juliet receives: Are you hiding? # Juliet publishes non-negative presence Juliet sends: Juliet receives: Juliet's phone receives: # And now Romeo's bare JID messages get delivered to both resources # (server behaviour may vary here) Romeo sends: There! Juliet receives: There! Juliet's phone receives: There! # The End Romeo disconnects Juliet disconnects Juliet's phone disconnects prosody-0.12.4/spec/scansion/PaxHeaders/basic_roster.scs0000644000000000000000000000012014475665104020324 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/scansion/basic_roster.scs0000644000175000017500000000241214475665104022530 0ustar00prosodyprosody00000000000000# Basic roster test [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: juliet@localhost password: password --------- Romeo connects Juliet connects Romeo sends: Romeo receives: Romeo sends: Romeo receives: # Add nurse to roster Romeo sends: # Receive the roster add result Romeo receives: # Receive the roster push Romeo receives: Romeo sends: # Fetch the roster, it should include nurse now Romeo sends: Romeo receives: Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/blocking.scs0000644000000000000000000000012014475665104017435 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/scansion/blocking.scs0000644000175000017500000000674714475665104021660 0ustar00prosodyprosody00000000000000# XEP-0191: Blocking Command [Client] Romeo jid: blocker@localhost password: password [Client] Juliet jid: blockee@localhost password: password ----- # The parties connect Romeo connects Romeo sends: Romeo receives: Juliet connects Juliet sends: Juliet receives: # They add each other Romeo sends: Romeo receives: Juliet receives: Juliet sends: Romeo receives: Juliet sends: Juliet receives: Romeo receives: Romeo sends: Juliet receives: Romeo receives: # They can now talk Juliet sends: ohai Romeo receives: ohai # And now to the blockining Romeo sends: Romeo receives: Juliet receives: # Can"t talk anymore Romeo sends: hello? Romeo receives: You have blocked this JID Juliet sends: Juliet receives: Romeo sends: Juliet receives: Romeo receives: # Can talk again Romeo sends: hello! Juliet receives: hello! # Bye Juliet sends: Juliet disconnects Romeo receives: Romeo sends: Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/empty_bookmarks.scs0000644000000000000000000000012014475665104021053 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/scansion/empty_bookmarks.scs0000644000175000017500000000107314475665104023261 0ustar00prosodyprosody00000000000000# mod_scansion_record on host 'localhost' recording started 2022-07-26T21:39:55Z [Client] Romeo password: password jid: juliet@localhost/UaksS4M1xYZB ----- Romeo connects Romeo sends: Romeo receives: Romeo disconnects # recording ended on 2022-07-26T21:40:45Z prosody-0.12.4/spec/scansion/PaxHeaders/extdisco.scs0000644000000000000000000000012014475665104017467 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/scansion/extdisco.scs0000644000175000017500000000475614475665104021710 0ustar00prosodyprosody00000000000000# XEP-0215: External Service Discovery [Client] Romeo password: password jid: user@localhost/mFquWxSr ----- Romeo connects Romeo sends: Romeo receives: Romeo sends: Romeo receives: Romeo sends: Romeo receives: Romeo sends: Romeo receives: Romeo disconnects # recording ended on 2020-07-18T16:47:57Z prosody-0.12.4/spec/scansion/PaxHeaders/http_upload.scs0000644000000000000000000000012014475665104020170 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.708764795 prosody-0.12.4/spec/scansion/http_upload.scs0000644000175000017500000000552314475665104022402 0ustar00prosodyprosody00000000000000# XEP-0363 HTTP Upload with mod_http_file_share [Client] Romeo password: password jid: filesharingenthusiast@localhost/krxLaE3s ----- Romeo connects Romeo sends: Romeo receives:
Romeo sends: Romeo receives: File too large 10000000 Romeo sends: Romeo receives: File size must be positive integer Romeo sends: Romeo receives: Invalid filename Romeo sends: Romeo receives: File type not allowed Romeo disconnects # recording ended on 2021-01-27T22:10:46Z prosody-0.12.4/spec/scansion/PaxHeaders/issue1121.scs0000644000000000000000000000012014475665104017302 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/issue1121.scs0000644000175000017500000000306314475665104021511 0ustar00prosodyprosody00000000000000# When removing roster contact, Prosody should send directed "unavailable" presence but sends global unavailable presence [Client] Romeo jid: romeo@localhost password: password [Client] Juliet jid: juliet@localhost password: password ----- Romeo connects Romeo sends Romeo receives Juliet connects Juliet sends Juliet receives Romeo sends Romeo receives Juliet receives Juliet sends Romeo receives Juliet sends Juliet receives Romeo receives Romeo sends Juliet receives Romeo receives Juliet sends Juliet receives Romeo receives Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/issue1224.scs0000644000000000000000000000012014475665104017306 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/issue1224.scs0000644000175000017500000000601014475665104021510 0ustar00prosodyprosody00000000000000# MUC: Handle affiliation changes from buggy clients [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form Romeo sends: http://jabber.org/protocol/muc#roomconfig Romeo receives: Romeo sends: Romeo receives: Romeo receives: # Juliet connects, and joins the room Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: # Romeo makes Juliet a member of the room, however his client is buggy and only # specifies her nickname Romeo sends: Romeo receives: Romeo receives: Juliet receives: prosody-0.12.4/spec/scansion/PaxHeaders/issue505.scs0000644000000000000000000000012014475665104017227 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/issue505.scs0000644000175000017500000000363614475665104021444 0ustar00prosodyprosody00000000000000# Issue 505: mod_muc doesn’t forward part statuses [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form Romeo sends: http://jabber.org/protocol/muc#roomconfig Romeo receives: # Juliet connects, and joins the room Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: Juliet sends: Farewell Romeo receives: Farewell prosody-0.12.4/spec/scansion/PaxHeaders/issue978-multi.scs0000644000000000000000000000012014475665104020375 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/issue978-multi.scs0000644000175000017500000000567414475665104022616 0ustar00prosodyprosody00000000000000# Issue 978: MUC does not carry error into occupant leave status (multiple clients) [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password [Client] Juliet's phone jid: user2@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form Romeo sends: http://jabber.org/protocol/muc#roomconfig Romeo receives: # Juliet connects, and joins the room Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: # Juliet's phone connects, and joins the room Juliet's phone connects Juliet's phone sends: Juliet's phone receives: Juliet's phone receives: Juliet's phone receives: Romeo receives: # Juliet leaves with an error Juliet sends: Test error Romeo receives: prosody-0.12.4/spec/scansion/PaxHeaders/issue978.scs0000644000000000000000000000012014475665104017245 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/issue978.scs0000644000175000017500000000431514475665104021455 0ustar00prosodyprosody00000000000000# Issue 978: MUC does not carry error into occupant leave status (single client) [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form Romeo sends: http://jabber.org/protocol/muc#roomconfig anyone Romeo receives: # Juliet connects, and joins the room Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: Juliet sends: Test error Romeo receives: Kicked: service unavailable: Test error prosody-0.12.4/spec/scansion/PaxHeaders/keep_full_sub_req.scs0000644000000000000000000000012014475665104021333 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/keep_full_sub_req.scs0000644000175000017500000000204014475665104023534 0ustar00prosodyprosody00000000000000# server MUST keep a record of the complete presence stanza comprising the subscription request (#689) [Client] Alice jid: pars-a@localhost password: password [Client] Bob jid: pars-b@localhost password: password [Client] Bob's phone jid: pars-b@localhost/phone password: password --------- Alice connects Alice sends: Alice disconnects Bob connects Bob sends: Bob receives: Bob receives: Bob disconnects # Works if they reconnect too Bob's phone connects Bob's phone sends: Bob's phone receives: Bob's phone receives: Bob's phone disconnects prosody-0.12.4/spec/scansion/PaxHeaders/lastactivity.scs0000644000000000000000000000012014475665104020365 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/lastactivity.scs0000644000175000017500000000145614475665104022600 0ustar00prosodyprosody00000000000000# XEP-0012: Last Activity / mod_lastactivity [Client] Romeo jid: romeo@localhost password: password ----- Romeo connects Romeo sends: Hello Romeo receives: Hello Romeo sends: Goodbye Romeo receives: Goodbye # mod_lastlog saves time + status message from the last unavailable presence Romeo sends: Romeo receives: Goodbye Romeo disconnects # recording ended on 2020-04-20T14:39:47Z prosody-0.12.4/spec/scansion/PaxHeaders/mam_extended.scs0000644000000000000000000000012014475665104020277 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/mam_extended.scs0000644000175000017500000000662214475665104022512 0ustar00prosodyprosody00000000000000# MAM 0.7.x Extended features [Client] Romeo jid: extmamtester@localhost password: password --------- Romeo connects # Enable MAM so we can save some messages Romeo sends: Romeo receives: # Some messages to look for later Romeo sends: Hello Romeo sends: U there? # Metadata Romeo sends: Romeo receives: Romeo sends: Romeo receives: Hello Romeo receives: U there? # FIXME unstable tag order from util.rsm Romeo receives: # Get results in reverse order Romeo sends: Romeo receives: U there? Romeo receives: Hello # FIXME unstable tag order from util.rsm Romeo receives: prosody-0.12.4/spec/scansion/PaxHeaders/mam_prefs_prep.scs0000644000000000000000000000012014475665104020644 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/mam_prefs_prep.scs0000644000175000017500000000113314475665104023047 0ustar00prosodyprosody00000000000000# mod_mam should apply JIDprep in prefs [Client] Romeo jid: romeo@localhost password: password ----- Romeo connects Romeo sends: JULIET@MONTAGUE.LIT MONTAGUE@MONTAGUE.LIT Romeo receives: juliet@montague.lit montague@montague.lit Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/muc_affiliation_notify.scs0000644000000000000000000000012014475665104022366 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/muc_affiliation_notify.scs0000644000175000017500000000700214475665104024572 0ustar00prosodyprosody00000000000000# MUC: Notification of affiliation changes of non-occupants [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password [Client] Rosaline jid: user3@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form Romeo sends: http://jabber.org/protocol/muc#roomconfig Romeo receives: # Promote Juliet to member Romeo sends: # Juliet is not in the room, so an affiliation change message is received Romeo receives: # The affiliation change succeeded Romeo receives: # Juliet connects, and joins the room Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: # To check the status of the room is as expected, Romeo requests the member list Romeo sends: Romeo receives: # Romeo grants membership to Rosaline, who is not in the room Romeo sends: Romeo receives: Romeo receives: Romeo sends: Finished! Juliet receives: Finished! prosody-0.12.4/spec/scansion/PaxHeaders/muc_create_destroy.scs0000644000000000000000000000012014475665104021525 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/muc_create_destroy.scs0000644000175000017500000002074414475665104023741 0ustar00prosodyprosody00000000000000# MUC creation, basic messages and destruction [Client] Romeo jid: romeo@localhost/mK0dD6Ha password: password [Client] Juliet jid: juliet@localhost/lVwkim_k password: password [Client] Admin jid: admin@localhost/DfNgg9VE password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: Romeo sends: Romeo receives: Juliet connects Romeo sends: Where are thou my Juliet? Romeo receives: Where are thou my Juliet? Juliet sends: Juliet receives: Juliet receives: Juliet receives: Where are thou my Juliet? Juliet receives: Romeo receives: Juliet sends: /me jumps out from behind a tree Romeo receives: /me jumps out from behind a tree Juliet receives: /me jumps out from behind a tree Juliet sends: Here I am! Romeo receives: Here I am! Juliet receives: Here I am! Romeo sends: What is this place? Romeo receives: What is this place? Juliet receives: What is this place? Juliet sends: I think we're in a script! Romeo receives: I think we're in a script! Juliet receives: I think we're in a script! Romeo sends: Oh no! Does that mean our love is not real?! Romeo receives: Oh no! Does that mean our love is not real?! Juliet receives: Oh no! Does that mean our love is not real?! Juliet sends: I refuse to accept this! Let's burn this place to the ground! Romeo receives: I refuse to accept this! Let's burn this place to the ground! Juliet receives: I refuse to accept this! Let's burn this place to the ground! Romeo sends: Yes! Romeo receives: Yes! Juliet receives: Yes! Romeo sends: We refuse to live in this fantasy! Juliet receives: We refuse to live in this fantasy! Romeo receives: We refuse to live in this fantasy! Romeo receives: Juliet disconnects Romeo sends: Romeo receives: Romeo receives: Romeo sends: Romeo receives: Admin connects Admin sends: elsewhere@conference.localhost Romeo receives: Romeo disconnects Admin receives: The following rooms were destroyed: elsewhere@conference.localhost Admin disconnects # recording ended on 2019-08-31T13:45:32Z prosody-0.12.4/spec/scansion/PaxHeaders/muc_mediated_invite.scs0000644000000000000000000000012014475665104021643 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/muc_mediated_invite.scs0000644000175000017500000000332714475665104024055 0ustar00prosodyprosody00000000000000# MUC: Mediated invites [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form Romeo sends: http://jabber.org/protocol/muc#roomconfig Romeo receives: # Juliet connects Juliet connects Juliet sends: Juliet receives: # Romeo invites Juliet to join the room Romeo sends: Juliet receives: room@conference.localhost/Romeo invited you to the room room@conference.localhost prosody-0.12.4/spec/scansion/PaxHeaders/muc_members_only_change.scs0000644000000000000000000000012014475665104022511 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/muc_members_only_change.scs0000644000175000017500000000554114475665104024723 0ustar00prosodyprosody00000000000000# MUC: Members-only rooms kick members who lose affiliation [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form, set the room to members-only Romeo sends: http://jabber.org/protocol/muc#roomconfig 1 Romeo receives: # Romeo adds Juliet to the member list Romeo sends: Romeo receives: Romeo receives: # Juliet connects, and joins the room Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: # Romeo removes Juliet's membership status Romeo sends: # As a non-member, Juliet must now be removed from the room Romeo receives: Romeo receives: Romeo disconnects Juliet disconnects prosody-0.12.4/spec/scansion/PaxHeaders/muc_members_only_deregister.scs0000644000000000000000000000012014475665104023421 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/muc_members_only_deregister.scs0000644000175000017500000000576614475665104025644 0ustar00prosodyprosody00000000000000# MUC: Members-only rooms kick members who deregister [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form, set the room to members-only Romeo sends: http://jabber.org/protocol/muc#roomconfig 1 Romeo receives: # Romeo adds Juliet to the member list Romeo sends: Romeo receives: Romeo receives: # Juliet connects, and joins the room Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: # Tired of Romeo's company, Juliet unregisters from the room Juliet sends: Juliet receives: Juliet receives: Romeo receives: Romeo disconnects Juliet disconnects prosody-0.12.4/spec/scansion/PaxHeaders/muc_nickname_change.scs0000644000000000000000000000012014475665104021603 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.712764756 prosody-0.12.4/spec/scansion/muc_nickname_change.scs0000644000175000017500000000644714475665104024023 0ustar00prosodyprosody00000000000000# MUC: Change nickname # Make sure a role is not reset, see #1466 [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: Romeo sends: http://jabber.org/protocol/muc#roomconfig 1 Romeo receives: Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: Romeo sends: http://jabber.org/protocol/muc#roomconfig 0 Romeo receives: Juliet receives: Juliet sends: Juliet receives: Juliet receives: prosody-0.12.4/spec/scansion/PaxHeaders/muc_nickname_robotface.scs0000644000000000000000000000012014475665104022322 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/muc_nickname_robotface.scs0000644000175000017500000000217614475665104024535 0ustar00prosodyprosody00000000000000# MUC: Prevent nicknames failing strict resourceprep [Client] Romeo jid: user@localhost password: password [Client] Roboteo jid: bot@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: Roboteo connects Roboteo sends: Roboteo receives: Nickname must pass strict validation prosody-0.12.4/spec/scansion/PaxHeaders/muc_password.scs0000644000000000000000000000012014475665104020353 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/muc_password.scs0000644000175000017500000000711714475665104022566 0ustar00prosodyprosody00000000000000# MUC: Password-protected rooms [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form Romeo sends: http://jabber.org/protocol/muc#roomconfig cauldronburn Romeo receives: # Juliet connects, and tries to join the room (password-protected) Juliet connects Juliet sends: Juliet receives: # Retry with the correct password Juliet sends: cauldronburn Juliet receives: Juliet receives: Juliet receives: Romeo receives: # Ok, now Juliet leaves, and Romeo unsets the password Juliet sends: Romeo receives: Juliet receives: # Remove room password Romeo sends: http://jabber.org/protocol/muc#roomconfig # Config change success Romeo receives: # Notification of room configuration update Romeo receives: # Juliet tries to join (should succeed) Juliet sends: # Notification of Romeo's presence in the room Juliet receives: Juliet receives: # Room topic Juliet receives: Romeo receives: prosody-0.12.4/spec/scansion/PaxHeaders/muc_presence_probe.scs0000644000000000000000000000012014475665104021504 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/muc_presence_probe.scs0000644000175000017500000001066714475665104023723 0ustar00prosodyprosody00000000000000# #1535 Let MUCs respond to presence probes [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password [Client] Mercutio jid: user3@localhost password: password ----- Romeo connects # Romeo joins the MUC Romeo sends: Romeo receives: Romeo receives: # Disable presences for non-mods Romeo sends: http://jabber.org/protocol/muc#roomconfig moderator Romeo receives: # Romeo probes himself Romeo sends: Romeo receives: # Juliet tries to probe Romeo before joining the room Juliet connects Juliet sends: Juliet receives: # Juliet tries to probe Mercutio (who's not in the MUC) before joining the room Juliet sends: Juliet receives: # Juliet joins the room Juliet sends: Juliet receives: Juliet receives: # Romeo probes Juliet Romeo sends: Romeo receives: # Mercutio tries to probe himself in a MUC before joining Mercutio connects Mercutio sends: Mercutio receives: # Romeo makes Mercutio a member and registers his nickname Romeo sends: Romeo receives: Romeo receives: # Romeo probes Mercutio, even though he's unavailable Romeo sends: Romeo receives: prosody-0.12.4/spec/scansion/PaxHeaders/muc_register.scs0000644000000000000000000000012014475665104020335 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/muc_register.scs0000644000175000017500000003510114475665104022542 0ustar00prosodyprosody00000000000000# MUC: Room registration and reserved nicknames [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password [Client] Rosaline jid: user3@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form Romeo sends: http://jabber.org/protocol/muc#roomconfig Romeo receives: Romeo sends: Romeo receives: Romeo receives: # Juliet connects, and joins the room Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: # Juliet retrieves the registration form Juliet sends: Juliet receives: http://jabber.org/protocol/muc#register Juliet sends: http://jabber.org/protocol/muc#register Juliet Juliet receives: Juliet receives: # Juliet discovers her reserved nick Juliet sends: Juliet receives: # Juliet leaves the room: Juliet sends: Juliet receives: Romeo receives: # Rosaline connect and tries to join the room as Juliet Rosaline connects Rosaline sends: Rosaline receives: # In a heated moment, Juliet unregisters from the room Juliet sends: Juliet receives: # Romeo is notified of Juliet's sad decision Romeo receives: # Rosaline attempts once more to sneak into the room, disguised as Juliet Rosaline sends: Rosaline receives: Rosaline receives: Romeo receives: # On discovering the ruse, Romeo restores Juliet's nick and status within the room Romeo sends: # Rosaline is evicted from the room Romeo receives: This nickname is reserved # An out-of-room affiliation change is received for Juliet Romeo receives: Romeo receives: Rosaline receives: This nickname is reserved # Rosaline, frustrated, attempts to get back into the room... Rosaline sends: # ...but once again, is denied Rosaline receives: # Juliet, however, quietly joins the room with success Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: # Romeo checks whether he has reserved his own nick yet Romeo sends: # But no nick is returned, as he hasn't registered yet! Romeo receives: # Romeo updates his own registration Romeo sends: Romeo receives: http://jabber.org/protocol/muc#register Romeo sends: http://jabber.org/protocol/muc#register Romeo Romeo receives: Romeo receives: Juliet receives: # Romeo discovers his reserved nick Romeo sends: Romeo receives: # To check the status of the room is as expected, Romeo requests the member list Romeo sends: Romeo receives: Juliet sends: Juliet receives: Romeo receives: # Rosaline joins as herself Rosaline sends: Rosaline receives: Rosaline receives: Rosaline receives: Romeo receives: # Rosaline tries to register her own nickname, but unaffiliated # registration is disabled by default Rosaline sends: Rosaline receives: Rosaline sends: http://jabber.org/protocol/muc#register Romeo Rosaline receives: # Romeo reserves her nickname for her Romeo sends: Romeo receives: Romeo receives: Rosaline receives: # Romeo sets their their own nickname via admin query (see #1273) Romeo sends: Romeo receives: Romeo receives: prosody-0.12.4/spec/scansion/PaxHeaders/muc_show_offline.scs0000644000000000000000000000012014475665104021173 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/muc_show_offline.scs0000644000175000017500000003576614475665104023421 0ustar00prosodyprosody00000000000000# MUC: Room registration and presence broadcast of unavailable members [Client] Romeo jid: user@localhost password: password [Client] Juliet jid: user2@localhost password: password [Client] Rosaline jid: user3@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: # Submit config form Romeo sends: http://jabber.org/protocol/muc#roomconfig none participant moderator Romeo receives: Romeo sends: Romeo receives: Romeo receives: # Juliet connects, and joins the room Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: # Juliet retrieves the registration form Juliet sends: Juliet receives: http://jabber.org/protocol/muc#register Juliet sends: http://jabber.org/protocol/muc#register Juliet Juliet receives: Juliet receives: # Juliet discovers her reserved nick Juliet sends: Juliet receives: # Juliet leaves the room: Juliet sends: Juliet receives: Romeo receives: # Rosaline connect and tries to join the room as Juliet Rosaline connects Rosaline sends: Rosaline receives: # In a heated moment, Juliet unregisters from the room Juliet sends: Juliet receives: # Romeo is notified of Juliet's sad decision Romeo receives: # Rosaline attempts once more to sneak into the room, disguised as Juliet Rosaline sends: Rosaline receives: Rosaline receives: Romeo receives: # On discovering the ruse, Romeo restores Juliet's nick and status within the room Romeo sends: # Rosaline is evicted from the room Romeo receives: This nickname is reserved # An out-of-room affiliation change is received for Juliet Romeo receives: Romeo receives: Rosaline receives: This nickname is reserved # Rosaline, frustrated, attempts to get back into the room... Rosaline sends: # ...but once again, is denied Rosaline receives: # Juliet, however, quietly joins the room with success Juliet sends: Juliet receives: Juliet receives: Juliet receives: Romeo receives: # Romeo checks whether he has reserved his own nick yet Romeo sends: # But no nick is returned, as he hasn't registered yet! Romeo receives: # Romeo updates his own registration Romeo sends: Romeo receives: http://jabber.org/protocol/muc#register Romeo sends: http://jabber.org/protocol/muc#register Romeo Romeo receives: Romeo receives: Juliet receives: # Romeo discovers his reserved nick Romeo sends: Romeo receives: # To check the status of the room is as expected, Romeo requests the member list Romeo sends: Romeo receives: Juliet sends: Juliet receives: Romeo receives: # Rosaline joins as herself Rosaline sends: Rosaline receives: Rosaline receives: Rosaline receives: Rosaline receives: Romeo receives: # Rosaline tries to register her own nickname, but unaffiliated # registration is disabled by default Rosaline sends: Rosaline receives: Rosaline sends: http://jabber.org/protocol/muc#register Romeo Rosaline receives: # Romeo reserves her nickname for her Romeo sends: Romeo receives: Romeo receives: Rosaline receives: # Romeo sets their their own nickname via admin query (see #1273) Romeo sends: Romeo receives: Romeo receives: prosody-0.12.4/spec/scansion/PaxHeaders/muc_subject_issue_667.scs0000644000000000000000000000012014475665104021762 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/muc_subject_issue_667.scs0000644000175000017500000000634314475665104024175 0ustar00prosodyprosody00000000000000# #667 MUC message with subject and body SHALL NOT be interpreted as a subject change [Client] Romeo password: password jid: romeo@localhost ----- Romeo connects # and creates a room Romeo sends: Romeo receives: # the default (empty) subject Romeo receives: # this should be treated as a normal message Romeo sends: Greetings Hello everyone Romeo receives: Greetings Hello everyone # Resync Romeo sends: # Presences Romeo receives: Romeo receives: Greetings Hello everyone # the still empty subject Romeo receives: # this is a subject change Romeo sends: Something to talk about Romeo receives: Something to talk about # a message without Romeo sends: Lorem ipsum dolor sit amet Romeo receives: Lorem ipsum dolor sit amet # Resync Romeo sends: # Presences Romeo receives: # History # These have delay tags but we ignore those for now Romeo receives: Greetings Hello everyone Romeo receives: Lorem ipsum dolor sit amet # Finally, the topic Romeo receives: Something to talk about Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/muc_whois_anyone_member.scs0000644000000000000000000000012014475665104022542 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/muc_whois_anyone_member.scs0000644000175000017500000000653314475665104024756 0ustar00prosodyprosody00000000000000# MUC: Allow members to fetch the affiliation lists in open non-anonymous rooms [Client] Romeo jid: 4e2pm7er@localhost password: password [Client] Juliet jid: qnjm5253@localhost password: password [Client] Random jid: iqizbcus@localhost password: password ----- Romeo connects Juliet connects Random connects # Romeo joins and creates the MUC Romeo sends: Romeo receives: Romeo receives: # and configures it for private chat Romeo sends: http://jabber.org/protocol/muc#roomconfig 1 anyone Romeo receives: Romeo receives: # Juliet is made a member Romeo sends: # Juliet can read affiliations Juliet sends: Juliet receives: Juliet sends: Juliet receives: # Others can't read affiliations Random sends: Random receives: Juliet disconnects Romeo disconnects Random disconnects # recording ended on 2021-07-23T12:09:48Z prosody-0.12.4/spec/scansion/PaxHeaders/pep_nickname.scs0000644000000000000000000000012014475665104020276 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/pep_nickname.scs0000644000175000017500000000412214475665104022502 0ustar00prosodyprosody00000000000000# Publishing a nickname in PEP and receiving a notification [Client] Romeo jid: romeo@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo sends: Romeo receives: Romeo receives: Romeo sends: Romeo receives: Romeo sends: Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/pep_publish_subscribe.scs0000644000000000000000000000012014475665104022220 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/pep_publish_subscribe.scs0000644000175000017500000003070314475665104024430 0ustar00prosodyprosody00000000000000# PEP publish, subscribe and publish-options [Client] Romeo jid: pep-test-wjebo4kg@localhost password: password [Client] Juliet jid: pep-test-tqvqu_pv@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo receives: Romeo sends: Romeo receives: Juliet connects Juliet sends: Juliet receives: Juliet receives: Juliet sends: Juliet receives: Romeo sends: Romeo sends: Romeo receives: Romeo receives: Juliet receives: Juliet sends: Juliet sends: Juliet receives: Juliet receives: Romeo receives: Romeo sends: Romeo sends: Romeo receives: Juliet receives: Juliet receives: Juliet receives: Juliet sends: Juliet receives: Juliet receives: Romeo receives: Romeo receives: Romeo receives: Juliet sends: Romeo sends: Romeo sends: Romeo sends: Romeo receives: Romeo receives: Romeo receives: Juliet receives: Romeo sends: Romeo receives: Romeo receives: Romeo receives: Romeo sends: Romeo sends: Romeo sends: Juliet receives: Juliet sends: Juliet sends: Beautiful CedarsThe SpinnersNot Quite Folk4 Juliet receives: Juliet sends: http://jabber.org/protocol/pubsub#publish-optionstruewhitelist Juliet receives: Juliet sends: Romeo receives: Beautiful CedarsThe SpinnersNot Quite Folk4 Romeo sends: Romeo receives: Beautiful CedarsThe SpinnersNot Quite Folk4 Juliet disconnects Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/pep_pubsub_max.scs0000644000000000000000000000012014475665104020656 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/pep_pubsub_max.scs0000644000175000017500000000204014475665104023057 0ustar00prosodyprosody00000000000000# PEP max_items=max [Client] Romeo jid: pep-test-maxitems@localhost password: password ----- Romeo connects Romeo sends: Hello http://jabber.org/protocol/pubsub#publish-options true open max Romeo receives: prosody-0.12.4/spec/scansion/PaxHeaders/presence_preapproval.scs0000644000000000000000000000012014475665104022064 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/presence_preapproval.scs0000644000175000017500000000236714475665104024301 0ustar00prosodyprosody00000000000000# server supports contact subscription pre-approval (RFC 6121 3.4) [Client] Alice jid: preappove-a@localhost password: password [Client] Bob jid: preapprove-b@localhost password: password --------- Alice connects Alice sends: Alice receives: Alice sends: Bob connects Bob sends: Bob receives: Bob sends: Bob receives: Bob sends: Bob receives: Bob receives: Bob disconnects Alice sends: Alice receives: Alice disconnects Bob disconnects prosody-0.12.4/spec/scansion/PaxHeaders/prosody.cfg.lua0000644000000000000000000000012014475665104020073 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/prosody.cfg.lua0000644000175000017500000001211114475665104022274 0ustar00prosodyprosody00000000000000--luacheck: ignore -- Mock time functions to simplify tests function _G.os.time() return 1219439344; end package.preload["util.time"] = function () return { now = function () return 1219439344.1; end; monotonic = function () return 0.1; end; } end admins = { "admin@localhost" } network_backend = ENV_PROSODY_NETWORK_BACKEND or "epoll" network_settings = require"util.json".decode(ENV_PROSODY_NETWORK_SETTINGS or "{}") modules_enabled = { -- Generally required "roster"; -- Allow users to have a roster. Recommended ;) "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. --"tls"; -- Add support for secure TLS on c2s/s2s connections --"dialback"; -- s2s dialback support "disco"; -- Service discovery -- Not essential, but recommended "carbons"; -- Keep multiple clients in sync "pep"; -- Enables users to publish their avatar, mood, activity, playing music and more "private"; -- Private XML storage (for room bookmarks, etc.) "blocklist"; -- Allow users to block communications with other users "vcard4"; -- User profiles (stored in PEP) "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard -- Nice to have "version"; -- Replies to server version requests "uptime"; -- Report how long server has been running "time"; -- Let others know the time here on this server "ping"; -- Replies to XMPP pings with pongs "register"; -- Allow users to register on this server using a client and change passwords "mam"; -- Store messages in an archive and allow users to access it --"csi_simple"; -- Simple Mobile optimizations -- Admin interfaces --"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands --"admin_telnet"; -- Opens telnet console interface on localhost port 5582 -- HTTP modules --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP" --"websocket"; -- XMPP over WebSockets --"http_files"; -- Serve static files from a directory over HTTP -- Other specific functionality --"limits"; -- Enable bandwidth limiting for XMPP connections --"groups"; -- Shared roster support "server_contact_info"; -- Publish contact information for this service --"announce"; -- Send announcement to all online users --"welcome"; -- Welcome users who register accounts --"watchregistrations"; -- Alert admins of registrations --"motd"; -- Send a message to users when they log in --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots. --"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use "lastactivity"; "external_services"; "tombstones"; "user_account_management"; -- Useful for testing --"scansion_record"; -- Records things that happen in scansion test case format } contact_info = { abuse = { "mailto:abuse@localhost", "xmpp:abuse@localhost" }; admin = { "mailto:admin@localhost", "xmpp:admin@localhost" }; feedback = { "http://localhost/feedback.html", "mailto:feedback@localhost", "xmpp:feedback@localhost" }; sales = { "xmpp:sales@localhost" }; security = { "xmpp:security@localhost" }; status = { "gopher://status.localhost" }; support = { "https://localhost/support.html", "xmpp:support@localhost" }; } external_service_host = "default.example" external_service_port = 9876 external_service_secret = "" external_services = { {type = "stun"; transport = "udp"}; {type = "turn"; transport = "udp"; secret = true}; {type = "turn"; transport = "udp"; secret = "foo"}; {type = "ftp"; transport = "tcp"; port = 2121; username = "john"; password = "password"}; {type = "ftp"; transport = "tcp"; host = "ftp.example.com"; port = 21; username = "john"; password = "password"}; } modules_disabled = { "s2s"; } -- TLS is not used during the test, set certificate dir to the config directory -- (spec/scansion) to silence an error from the certificate indexer certificates = "." allow_registration = false c2s_require_encryption = false allow_unencrypted_plain_auth = true authentication = "insecure" insecure_open_authentication = "Yes please, I know what I'm doing!" storage = "memory" mam_smart_enable = true -- For the "sql" backend, you can uncomment *one* of the below to configure: --sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename. --sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } --sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } -- Logging configuration -- For advanced logging see https://prosody.im/doc/logging log = {"*console",debug = ENV_PROSODY_LOGFILE} pidfile = "prosody.pid" VirtualHost "localhost" hide_os_type = true -- absence tested for in version.scs Component "conference.localhost" "muc" storage = "memory" admins = { "Admin@localhost" } modules_enabled = { "muc_mam"; } Component "pubsub.localhost" "pubsub" storage = "memory" expose_publisher = true Component "upload.localhost" "http_file_share" http_file_share_size_limit = 10000000 http_file_share_allowed_file_types = { "text/plain", "image/*" } prosody-0.12.4/spec/scansion/PaxHeaders/pubsub_advanced.scs0000644000000000000000000000012014475665104020772 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.716764717 prosody-0.12.4/spec/scansion/pubsub_advanced.scs0000644000175000017500000001043214475665104023177 0ustar00prosodyprosody00000000000000# Pubsub: Node creation, publish, subscribe, affiliations and delete [Client] Balthasar jid: admin@localhost password: password [Client] Romeo jid: romeo@localhost password: password [Client] Juliet jid: juliet@localhost password: password --------- Romeo connects Romeo sends: Romeo receives: Balthasar connects Balthasar sends: Balthasar receives: Balthasar sends: Balthasar receives: Juliet connects Juliet sends: Juliet receives: Juliet sends: Juliet receives: Balthasar sends: Balthasar receives: Balthasar sends: Balthasar receives: Romeo sends: Soliloquy Lorem ipsum dolor sit amet Juliet receives: Soliloquy Lorem ipsum dolor sit amet Romeo receives: Juliet sends: Juliet receives: Balthasar sends: Balthasar receives: Romeo disconnects // vim: syntax=xml: prosody-0.12.4/spec/scansion/PaxHeaders/pubsub_basic.scs0000644000000000000000000000012014475665104020306 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/pubsub_basic.scs0000644000175000017500000000445614475665104022524 0ustar00prosodyprosody00000000000000# Pubsub: Basic support [Client] Romeo jid: admin@localhost password: password // admin@localhost is assumed to have node creation privileges [Client] Juliet jid: juliet@localhost password: password --------- Romeo connects Romeo sends: Romeo receives: Juliet connects -- Juliet sends: -- -- -- -- -- -- -- Juliet receives: -- Juliet sends: Juliet receives: Romeo sends: Soliloquy Lorem ipsum dolor sit amet Romeo receives: Juliet receives: Soliloquy Lorem ipsum dolor sit amet Juliet sends: Juliet receives: Juliet disconnects Romeo sends: Romeo receives: Romeo disconnects // vim: syntax=xml: prosody-0.12.4/spec/scansion/PaxHeaders/pubsub_config.scs0000644000000000000000000000012014475665104020472 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/pubsub_config.scs0000644000175000017500000001723714475665104022711 0ustar00prosodyprosody00000000000000# pubsub#title as name attribute in disco#items # Issue 1226 [Client] Romeo password: password jid: jqpcrbq@localhost ----- Romeo connects Romeo sends: Romeo receives: Romeo sends: Romeo receives: http://jabber.org/protocol/pubsub#node_config 1 1 presence publishers on_sub_and_presence 1 1 headline 1 1 Romeo sends: http://jabber.org/protocol/pubsub#node_config Nice tunes 1 1 presence publishers never 1 1 headline 1 1 Romeo receives: Romeo sends: Romeo receives: Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/pubsub_createdelete.scs0000644000000000000000000000012014475665104021653 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/pubsub_createdelete.scs0000644000175000017500000000247114475665104024064 0ustar00prosodyprosody00000000000000# Pubsub: Create and delete [Client] Romeo jid: admin@localhost password: password // admin@localhost is assumed to have node creation privileges --------- Romeo connects Romeo sends: Romeo receives: Romeo sends: Romeo receives: Romeo sends: Romeo receives: Romeo sends: Romeo receives: Romeo disconnects // vim: syntax=xml: prosody-0.12.4/spec/scansion/PaxHeaders/pubsub_max_items.scs0000644000000000000000000000012014475665104021213 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/pubsub_max_items.scs0000644000175000017500000001757314475665104023435 0ustar00prosodyprosody00000000000000# Pubsub: Requesting the Most Recent Items (#1608) [Client] Alice jid: admin@localhost password: password --------- Alice connects Alice sends: Alice receives: Alice sends: Alice receives: Alice sends: Alice receives: http://jabber.org/protocol/pubsub#node_config 20 1 open publishers never 1 1 headline 1 1 Alice sends: foo Alice receives: Alice sends: bar Alice receives: Alice sends: Alice receives: bar Alice sends: Alice receives: foo bar Alice sends: Alice receives: prosody-0.12.4/spec/scansion/PaxHeaders/pubsub_multi_items.scs0000644000000000000000000000012014475665104021560 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/pubsub_multi_items.scs0000644000175000017500000002031214475665104023763 0ustar00prosodyprosody00000000000000# Pubsub: Requesting multiple specific items from a node (#1322) [Client] Alice jid: admin@localhost password: password --------- Alice connects Alice sends: Alice receives: Alice sends: Alice receives: Alice sends: Alice receives: http://jabber.org/protocol/pubsub#node_config 20 1 open publishers never 1 1 headline 1 1 Alice sends: foo Alice receives: Alice sends: bar Alice receives: Alice sends: Alice receives: foo bar Alice sends: Alice receives: foo bar Alice sends: Alice receives: prosody-0.12.4/spec/scansion/PaxHeaders/pubsub_preconditions.scs0000644000000000000000000000012014475665104022105 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/pubsub_preconditions.scs0000644000175000017500000002106714475665104024320 0ustar00prosodyprosody00000000000000# Pubsub preconditions are enforced [Client] Romeo password: password jid: jqpcrbq2@localhost ----- Romeo connects Romeo sends: Romeo receives: Romeo sends: Romeo receives: http://jabber.org/protocol/pubsub#node_config 1 1 presence publishers on_sub_and_presence 1 1 headline 1 1 Romeo sends: http://jabber.org/protocol/pubsub#node_config Nice tunes 1 1 presence publishers never 1 1 headline 1 1 Romeo receives: Romeo sends: Romeo receives: Romeo sends: http://jabber.org/protocol/pubsub#publish-options whitelist Romeo receives: Field does not match: access_model Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/pubsub_resend_on_sub.scs0000644000000000000000000000012014475665104022052 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/pubsub_resend_on_sub.scs0000644000175000017500000000727314475665104024270 0ustar00prosodyprosody00000000000000# Pubsub: Send last item on subscribe #1436 [Client] Romeo jid: admin@localhost password: password // admin@localhost is assumed to have node creation privileges [Client] Juliet jid: juliet@localhost password: password --------- Romeo connects Romeo sends: Romeo receives: Romeo sends: http://jabber.org/protocol/pubsub#node_config never Romeo receives: Romeo sends: Soliloquy Lorem ipsum dolor sit amet Romeo receives: Juliet connects Juliet sends: Juliet receives: Juliet sends: Juliet receives: Romeo sends: http://jabber.org/protocol/pubsub#node_config on_sub Romeo receives: Juliet sends: Juliet receives: Juliet receives: Soliloquy Lorem ipsum dolor sit amet Juliet sends: Juliet receives: Juliet disconnects Romeo sends: Romeo receives: Romeo disconnects // vim: syntax=xml: prosody-0.12.4/spec/scansion/PaxHeaders/server_contact_info.scs0000644000000000000000000000012014475665104021701 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/server_contact_info.scs0000644000175000017500000000475314475665104024117 0ustar00prosodyprosody00000000000000# XEP-0157: Contact Addresses for XMPP Services # mod_server_contact_info [Client] Romeo jid: romeo@localhost password: password ----- Romeo connects Romeo sends: # Ignore other disco#info features, identities etc Romeo receives: http://jabber.org/network/serverinfo mailto:abuse@localhost xmpp:abuse@localhost mailto:admin@localhost xmpp:admin@localhost http://localhost/feedback.html mailto:feedback@localhost xmpp:feedback@localhost xmpp:sales@localhost xmpp:security@localhost gopher://status.localhost https://localhost/support.html xmpp:support@localhost Romeo sends: http://jabber.org/network/serverinfo xmpp:admin@localhost Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/tombstones.scs0000644000000000000000000000012014475665104020042 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/tombstones.scs0000644000175000017500000000130314475665104022244 0ustar00prosodyprosody00000000000000# Tombstones [Client] Romeo jid: romeo@localhost password: password [Client] Juliet jid: juliet-tombstones@localhost password: password --------- Romeo connects Juliet connects Juliet sends: # Scansion gets disconnected right after this with a stream error makes # scansion itself abort, so we preemptively disconnect to avoid that # Juliet receives: # Juliet disconnects Romeo sends: Romeo receives: Romeo receives: prosody-0.12.4/spec/scansion/PaxHeaders/uptime.scs0000644000000000000000000000012014475665104017150 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/uptime.scs0000644000175000017500000000053314475665104021356 0ustar00prosodyprosody00000000000000# XEP-0012: Last Activity / mod_uptime [Client] Romeo jid: romeo@localhost password: password ----- Romeo connects Romeo sends: Romeo receives: Romeo disconnects prosody-0.12.4/spec/scansion/PaxHeaders/vcard_temp.scs0000644000000000000000000000012014475665104017771 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/vcard_temp.scs0000644000175000017500000000300714475665104022176 0ustar00prosodyprosody00000000000000# XEP-0054 vCard-temp writable and readable by anyone # mod_scansion_record on host 'localhost' recording started 2018-10-20T15:00:12Z [Client] Romeo jid: romeo@localhost password: password [Client] Juliet jid: juliet@localhost password: password ----- Romeo connects # Romeo sets his vCard # FN and N are required by the schema and mod_vcard_legacy will always inject them Romeo sends: Romeo Montague Montague Romeo Romeo receives: Romeo sends: Romeo receives: Romeo Montague Montague Romeo Romeo disconnects Juliet connects Juliet sends: # Juliet can see Romeo's vCard since it's public Juliet receives: Romeo Montague Montague Romeo Juliet disconnects # recording ended on 2018-10-20T15:02:14Z prosody-0.12.4/spec/scansion/PaxHeaders/version.scs0000644000000000000000000000012014475665104017332 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/scansion/version.scs0000644000175000017500000000111014475665104021530 0ustar00prosodyprosody00000000000000# XEP-0092: Software Version / mod_version [Client] Romeo password: password jid: romeo@localhost/dfaZpuxV ----- Romeo connects Romeo sends: # Version string would vary so we can't do an exact match atm # Inclusion of is disabled in the config, it should be absent Romeo receives: Prosody Romeo disconnects prosody-0.12.4/spec/PaxHeaders/utf8_sequences.txt0000644000000000000000000000012014475665104017020 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/utf8_sequences.txt0000644000175000017500000000624214475665104021231 0ustar00prosodyprosody00000000000000Should pass: 41 42 43 # Simple ASCII - abc Should pass: 41 42 c3 87 # "ABÇ" Should pass: 41 42 e1 b8 88 # "ABḈ" Should pass: 41 42 f0 9d 9c 8d # "AB𝜍" Should pass: F4 8F BF BF # Last valid sequence (U+10FFFF) Should fail: F4 90 80 80 # First invalid sequence (U+110000) Should fail: 80 81 82 83 # Invalid sequence (invalid start byte) Should fail: C2 C3 # Invalid sequence (invalid continuation byte) Should fail: C0 43 # Overlong sequence Should fail: F5 80 80 80 # U+140000 (out of range) Should fail: ED A0 80 # U+D800 (forbidden by RFC 3629) Should fail: ED BF BF # U+DFFF (forbidden by RFC 3629) Should pass: ED 9F BF # U+D7FF (U+D800 minus 1: allowed) Should pass: EE 80 80 # U+E000 (U+D7FF plus 1: allowed) Should fail: C0 # Invalid start byte Should fail: C1 # Invalid start byte Should fail: C2 # Incomplete sequence Should fail: F8 88 80 80 80 # 6-byte sequence Should pass: 7F # Last valid 1-byte sequence (U+00007F) Should pass: DF BF # Last valid 2-byte sequence (U+0007FF) Should pass: EF BF BF # Last valid 3-byte sequence (U+00FFFF) Should pass: 00 # First valid 1-byte sequence (U+000000) Should pass: C2 80 # First valid 2-byte sequence (U+000080) Should pass: E0 A0 80 # First valid 3-byte sequence (U+000800) Should pass: F0 90 80 80 # First valid 4-byte sequence (U+000800) Should fail: F8 88 80 80 80 # First 5-byte sequence - invalid per RFC 3629 Should fail: FC 84 80 80 80 80 # First 6-byte sequence - invalid per RFC 3629 Should pass: EF BF BD # U+00FFFD (replacement character) Should fail: 80 # First continuation byte Should fail: BF # Last continuation byte Should fail: 80 BF # 2 continuation bytes Should fail: 80 BF 80 # 3 continuation bytes Should fail: 80 BF 80 BF # 4 continuation bytes Should fail: 80 BF 80 BF 80 # 5 continuation bytes Should fail: 80 BF 80 BF 80 BF # 6 continuation bytes Should fail: 80 BF 80 BF 80 BF 80 # 7 continuation bytes Should fail: FE # Impossible byte Should fail: FF # Impossible byte Should fail: FE FE FF FF # Impossible bytes Should fail: C0 AF # Overlong "/" Should fail: E0 80 AF # Overlong "/" Should fail: F0 80 80 AF # Overlong "/" Should fail: F8 80 80 80 AF # Overlong "/" Should fail: FC 80 80 80 80 AF # Overlong "/" Should fail: C0 80 AF # Overlong "/" (invalid) Should fail: C1 BF # Overlong Should fail: E0 9F BF # Overlong Should fail: F0 8F BF BF # Overlong Should fail: F8 87 BF BF BF # Overlong Should fail: FC 83 BF BF BF BF # Overlong Should pass: EF BF BE # U+FFFE (invalid unicode, valid UTF-8) Should pass: EF BF BF # U+FFFF (invalid unicode, valid UTF-8) prosody-0.12.4/spec/PaxHeaders/util_argparse_spec.lua0000644000000000000000000000012014475665104017674 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.720764677 prosody-0.12.4/spec/util_argparse_spec.lua0000644000175000017500000000310614475665104022101 0ustar00prosodyprosody00000000000000describe("parse", function() local parse setup(function() parse = require"util.argparse".parse; end); it("works", function() -- basic smoke test local opts = parse({ "--help" }); assert.same({ help = true }, opts); end); it("returns if no args", function() assert.same({}, parse({})); end); it("supports boolean flags", function() local opts, err = parse({ "--foo"; "--no-bar" }); assert.falsy(err); assert.same({ foo = true; bar = false }, opts); end); it("consumes input until the first argument", function() local arg = { "--foo"; "bar"; "--baz" }; local opts, err = parse(arg); assert.falsy(err); assert.same({ foo = true, "bar", "--baz" }, opts); assert.same({ "bar"; "--baz" }, arg); end); it("expands short options", function() local opts, err = parse({ "--foo"; "-b" }, { short_params = { b = "bar" } }); assert.falsy(err); assert.same({ foo = true; bar = true }, opts); end); it("supports value arguments", function() local opts, err = parse({ "--foo"; "bar"; "--baz=moo" }, { value_params = { foo = true; bar = true } }); assert.falsy(err); assert.same({ foo = "bar"; baz = "moo" }, opts); end); it("demands values for value params", function() local opts, err, where = parse({ "--foo" }, { value_params = { foo = true } }); assert.falsy(opts); assert.equal("missing-value", err); assert.equal("--foo", where); end); it("reports where the problem is", function() local opts, err, where = parse({ "-h" }); assert.falsy(opts); assert.equal("param-not-found", err); assert.equal("-h", where, "returned where"); end); end); prosody-0.12.4/spec/PaxHeaders/util_array_spec.lua0000644000000000000000000000012014475665104017206 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_array_spec.lua0000644000175000017500000001075114475665104021417 0ustar00prosodyprosody00000000000000local array = require "util.array"; describe("util.array", function () describe("creation", function () describe("new", function () it("works", function () local a = array.new({"a", "b", "c"}); assert.same({"a", "b", "c"}, a); end); end); describe("from table", function () it("works", function () local a = array({"a", "b", "c"}); assert.same({"a", "b", "c"}, a); end); end); describe("from iterator", function () it("works", function () -- collects the first value, ie the keys local a = array(ipairs({true, true, true})); assert.same({1, 2, 3}, a); end); end); describe("collect", function () it("works", function () -- collects the first value, ie the keys local a = array.collect(ipairs({true, true, true})); assert.same({1, 2, 3}, a); end); end); end); describe("metatable", function () describe("operator", function () describe("addition", function () it("works", function () local a = array({ "a", "b" }); local b = array({ "c", "d" }); assert.same({"a", "b", "c", "d"}, a + b); end); end); describe("equality", function () it("works", function () local a1 = array({ "a", "b" }); local a2 = array({ "a", "b" }); local b = array({ "c", "d" }); assert.truthy(a1 == a2); assert.falsy(a1 == b); assert.falsy(a1 == { "a", "b" }, "Behavior of metatables changed in Lua 5.3"); end); end); describe("division", function () it("works", function () local a = array({ "a", "b", "c" }); local b = a / function (i) if i ~= "b" then return i .. "x" end end; assert.same({ "ax", "cx" }, b); end); end); end); end); describe("methods", function () describe("map", function () it("works", function () local a = array({ "a", "b", "c" }); local b = a:map(string.upper); assert.same({ "A", "B", "C" }, b); end); end); describe("filter", function () it("works", function () local a = array({ "a", "b", "c" }); a:filter(function (i) return i ~= "b" end); assert.same({ "a", "c" }, a); end); end); describe("sort", function () it("works", function () local a = array({ 5, 4, 3, 1, 2, }); a:sort(); assert.same({ 1, 2, 3, 4, 5, }, a); end); end); describe("unique", function () it("works", function () local a = array({ "a", "b", "c", "c", "a", "b" }); a:unique(); assert.same({ "a", "b", "c" }, a); end); end); describe("pluck", function () it("works", function () local a = array({ { a = 1, b = -1 }, { a = 2, b = -2 }, }); a:pluck("a"); assert.same({ 1, 2 }, a); end); end); describe("reverse", function () it("works", function () local a = array({ "a", "b", "c" }); a:reverse(); assert.same({ "c", "b", "a" }, a); end); end); -- TODO :shuffle describe("append", function () it("works", function () local a = array({ "a", "b", "c" }); a:append(array({ "d", "e", })); assert.same({ "a", "b", "c", "d", "e" }, a); end); end); describe("push", function () it("works", function () local a = array({ "a", "b", "c" }); a:push("d"):push("e"); assert.same({ "a", "b", "c", "d", "e" }, a); end); end); describe("pop", function () it("works", function () local a = array({ "a", "b", "c" }); assert.equal("c", a:pop()); assert.same({ "a", "b", }, a); end); end); describe("concat", function () it("works", function () local a = array({ "a", "b", "c" }); assert.equal("a,b,c", a:concat(",")); end); end); describe("length", function () it("works", function () local a = array({ "a", "b", "c" }); assert.equal(3, a:length()); end); end); describe("slice", function () it("works", function () local a = array({ "a", "b", "c" }); assert.equal(array.slice(a, 1, 2), array{ "a", "b" }); assert.equal(array.slice(a, 1, 3), array{ "a", "b", "c" }); assert.equal(array.slice(a, 2, 3), array{ "b", "c" }); assert.equal(array.slice(a, 2), array{ "b", "c" }); assert.equal(array.slice(a, -4), array{ "a", "b", "c" }); assert.equal(array.slice(a, -3), array{ "a", "b", "c" }); assert.equal(array.slice(a, -2), array{ "b", "c" }); assert.equal(array.slice(a, -1), array{ "c" }); end); it("can mutate", function () local a = array({ "a", "b", "c" }); assert.equal(a:slice(-1), array{"c"}); assert.equal(a, array{"c"}); end); end); end); -- TODO The various array.foo(array ina, array outa) functions end); prosody-0.12.4/spec/PaxHeaders/util_async_spec.lua0000644000000000000000000000012014475665104017205 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_async_spec.lua0000644000175000017500000004424114475665104021417 0ustar00prosodyprosody00000000000000local async = require "util.async"; local match = require "luassert.match"; describe("util.async", function() local debug = false; local print = print; if debug then require "util.logger".add_simple_sink(print); else print = function () end end local function mock_watchers(event_log) local function generic_logging_watcher(name) return function (...) table.insert(event_log, { name = name, n = select("#", ...)-1, select(2, ...) }); end; end; return setmetatable(mock{ ready = generic_logging_watcher("ready"); waiting = generic_logging_watcher("waiting"); error = generic_logging_watcher("error"); }, { __index = function (_, event) -- Unexpected watcher called assert(false, "unexpected watcher called: "..event); end; }) end local function new(func) local event_log = {}; local spy_func = spy.new(func); return async.runner(spy_func, mock_watchers(event_log)), spy_func, event_log; end describe("#runner", function() it("should work", function() local r = new(function (item) assert(type(item) == "number") end); r:run(1); r:run(2); end); it("should be ready after creation", function () local r = new(function () end); assert.equal(r.state, "ready"); end); it("should do nothing if the queue is empty", function () local did_run; local r = new(function () did_run = true end); r:run(); assert.equal(r.state, "ready"); assert.is_nil(did_run); r:run("hello"); assert.is_true(did_run); end); it("should support queuing work items without running", function () local did_run; local r = new(function () did_run = true end); r:enqueue("hello"); assert.equal(r.state, "ready"); assert.is_nil(did_run); r:run(); assert.is_true(did_run); end); it("should support queuing multiple work items", function () local last_item; local r, s = new(function (item) last_item = item; end); r:enqueue("hello"); r:enqueue("there"); r:enqueue("world"); assert.equal(r.state, "ready"); r:run(); assert.equal(r.state, "ready"); assert.spy(s).was.called(3); assert.equal(last_item, "world"); end); it("should support all simple data types", function () local last_item; local r, s = new(function (item) last_item = item; end); local values = { {}, 123, "hello", true, false }; for i = 1, #values do r:enqueue(values[i]); end assert.equal(r.state, "ready"); r:run(); assert.equal(r.state, "ready"); assert.spy(s).was.called(#values); for i = 1, #values do assert.spy(s).was.called_with(values[i]); end assert.equal(last_item, values[#values]); end); it("should work with no parameters", function () local item = "fail"; local r = async.runner(); local f = spy.new(function () item = "success"; end); r:run(f); assert.spy(f).was.called(); assert.equal(item, "success"); end); it("supports a default error handler", function () local item = "fail"; local r = async.runner(); local f = spy.new(function () error("test error"); end); assert.error_matches(function () r:run(f); end, "test error"); assert.spy(f).was.called(); assert.equal(item, "fail"); end); describe("#errors", function () describe("should notify", function () local last_processed_item, last_error; local r; r = async.runner(function (item) if item == "error" then error({ e = "test error" }); end last_processed_item = item; end, mock{ ready = function () end; waiting = function () end; error = function (runner, err) assert.equal(r, runner); last_error = err; end; }); -- Simple item, no error r:run("hello"); assert.equal(r.state, "ready"); assert.equal(last_processed_item, "hello"); assert.spy(r.watchers.ready).was_not.called(); assert.spy(r.watchers.error).was_not.called(); -- Trigger an error inside the runner assert.equal(last_error, nil); r:run("error"); test("the correct watcher functions", function () -- Only the error watcher should have been called assert.spy(r.watchers.ready).was_not.called(); assert.spy(r.watchers.waiting).was_not.called(); assert.spy(r.watchers.error).was.called(1); end); test("with the correct error", function () -- The error watcher state should be correct, to -- demonstrate the error was passed correctly assert.is_table(last_error); assert.equal(last_error.e, "test error"); last_error = nil; end); assert.equal(r.state, "ready"); assert.equal(last_processed_item, "hello"); end); do local last_processed_item, last_error; local r; local wait, done; r = async.runner(function (item) if item == "error" then error({ e = "test error" }); elseif item == "wait" then wait, done = async.waiter(); wait(); error({ e = "post wait error" }); end last_processed_item = item; end, mock({ ready = function () end; waiting = function () end; error = function (runner, err) assert.equal(r, runner); last_error = err; end; })); randomize(false); --luacheck: ignore 113/randomize it("should not be fatal to the runner", function () r:run("world"); assert.equal(r.state, "ready"); assert.spy(r.watchers.ready).was_not.called(); assert.equal(last_processed_item, "world"); end); it("should work despite a #waiter", function () -- This test covers an important case where a runner -- throws an error while being executed outside of the -- main loop. This happens when it was blocked ('waiting'), -- and then released (via a call to done()). last_error = nil; r:run("wait"); assert.equal(r.state, "waiting"); assert.spy(r.watchers.waiting).was.called(1); done(); -- At this point an error happens (state goes error->ready) assert.equal(r.state, "ready"); assert.spy(r.watchers.error).was.called(1); assert.spy(r.watchers.ready).was.called(1); assert.is_table(last_error); assert.equal(last_error.e, "post wait error"); last_error = nil; r:run("hello again"); assert.spy(r.watchers.ready).was.called(1); assert.spy(r.watchers.waiting).was.called(1); assert.spy(r.watchers.error).was.called(1); assert.equal(r.state, "ready"); assert.equal(last_processed_item, "hello again"); end); end it("should continue to process work items", function () local last_item; local runner, runner_func = new(function (item) if item == "error" then error("test error"); end last_item = item; end); runner:enqueue("one"); runner:enqueue("error"); runner:enqueue("two"); runner:run(); assert.equal(runner.state, "ready"); assert.spy(runner_func).was.called(3); assert.spy(runner.watchers.error).was.called(1); assert.spy(runner.watchers.ready).was.called(0); assert.spy(runner.watchers.waiting).was.called(0); assert.equal(last_item, "two"); end); it("should continue to process work items during resume", function () local wait, done, last_item; local runner, runner_func = new(function (item) if item == "wait-error" then wait, done = async.waiter(); wait(); error("test error"); end last_item = item; end); runner:enqueue("one"); runner:enqueue("wait-error"); runner:enqueue("two"); runner:run(); done(); assert.equal(runner.state, "ready"); assert.spy(runner_func).was.called(3); assert.spy(runner.watchers.error).was.called(1); assert.spy(runner.watchers.waiting).was.called(1); assert.spy(runner.watchers.ready).was.called(1); assert.equal(last_item, "two"); end); end); end); describe("#waiter", function() it("should error outside of async context", function () assert.has_error(function () async.waiter(); end); end); it("should work", function () local wait, done; local r = new(function (item) assert(type(item) == "number") if item == 3 then wait, done = async.waiter(); wait(); end end); r:run(1); assert(r.state == "ready"); r:run(2); assert(r.state == "ready"); r:run(3); assert(r.state == "waiting"); done(); assert(r.state == "ready"); --for k, v in ipairs(l) do print(k,v) end end); it("should work", function () -------------------- local wait, done; local last_item = 0; local r = new(function (item) assert(type(item) == "number") assert(item == last_item + 1); last_item = item; if item == 3 then wait, done = async.waiter(); wait(); end end); r:run(1); assert(r.state == "ready"); r:run(2); assert(r.state == "ready"); r:run(3); assert(r.state == "waiting"); r:run(4); assert(r.state == "waiting"); done(); assert(r.state == "ready"); --for k, v in ipairs(l) do print(k,v) end end); it("should work", function () -------------------- local wait, done; local last_item = 0; local r = new(function (item) assert(type(item) == "number") assert((item == last_item + 1) or item == 3); last_item = item; if item == 3 then wait, done = async.waiter(); wait(); end end); r:run(1); assert(r.state == "ready"); r:run(2); assert(r.state == "ready"); r:run(3); assert(r.state == "waiting"); r:run(3); assert(r.state == "waiting"); r:run(3); assert(r.state == "waiting"); r:run(4); assert(r.state == "waiting"); for i = 1, 3 do done(); if i < 3 then assert(r.state == "waiting"); end end assert(r.state == "ready"); --for k, v in ipairs(l) do print(k,v) end end); it("should work", function () -------------------- local wait, done; local last_item = 0; local r = new(function (item) assert(type(item) == "number") assert((item == last_item + 1) or item == 3); last_item = item; if item == 3 then wait, done = async.waiter(); wait(); end end); r:run(1); assert(r.state == "ready"); r:run(2); assert(r.state == "ready"); r:run(3); assert(r.state == "waiting"); r:run(3); assert(r.state == "waiting"); for i = 1, 2 do done(); if i < 2 then assert(r.state == "waiting"); end end assert(r.state == "ready"); r:run(4); assert(r.state == "ready"); assert(r.state == "ready"); --for k, v in ipairs(l) do print(k,v) end end); it("should work with multiple runners in parallel", function () -- Now with multiple runners -------------------- local wait1, done1; local last_item1 = 0; local r1 = new(function (item) assert(type(item) == "number") assert((item == last_item1 + 1) or item == 3); last_item1 = item; if item == 3 then wait1, done1 = async.waiter(); wait1(); end end, "r1"); local wait2, done2; local last_item2 = 0; local r2 = new(function (item) assert(type(item) == "number") assert((item == last_item2 + 1) or item == 3); last_item2 = item; if item == 3 then wait2, done2 = async.waiter(); wait2(); end end, "r2"); r1:run(1); assert(r1.state == "ready"); r1:run(2); assert(r1.state == "ready"); r1:run(3); assert(r1.state == "waiting"); r1:run(3); assert(r1.state == "waiting"); r2:run(1); assert(r1.state == "waiting"); assert(r2.state == "ready"); r2:run(2); assert(r1.state == "waiting"); assert(r2.state == "ready"); r2:run(3); assert(r1.state == "waiting"); assert(r2.state == "waiting"); done2(); r2:run(3); assert(r1.state == "waiting"); assert(r2.state == "waiting"); done2(); r2:run(4); assert(r1.state == "waiting"); assert(r2.state == "ready"); for i = 1, 2 do done1(); if i < 2 then assert(r1.state == "waiting"); end end assert(r1.state == "ready"); r1:run(4); assert(r1.state == "ready"); assert(r1.state == "ready"); --for k, v in ipairs(l1) do print(k,v) end end); it("should work work with multiple runners in parallel", function () -------------------- local wait1, done1; local last_item1 = 0; local r1 = new(function (item) print("r1 processing ", item); assert(type(item) == "number") assert((item == last_item1 + 1) or item == 3); last_item1 = item; if item == 3 then wait1, done1 = async.waiter(); wait1(); end end, "r1"); local wait2, done2; local last_item2 = 0; local r2 = new(function (item) print("r2 processing ", item); assert.is_number(item); assert((item == last_item2 + 1) or item == 3); last_item2 = item; if item == 3 then wait2, done2 = async.waiter(); wait2(); end end, "r2"); r1:run(1); assert.equal(r1.state, "ready"); r1:run(2); assert.equal(r1.state, "ready"); r1:run(5); assert.equal(r1.state, "ready"); r1:run(3); assert.equal(r1.state, "waiting"); r1:run(5); -- Will error, when we get to it assert.equal(r1.state, "waiting"); done1(); assert.equal(r1.state, "ready"); r1:run(3); assert.equal(r1.state, "waiting"); r2:run(1); assert.equal(r1.state, "waiting"); assert.equal(r2.state, "ready"); r2:run(2); assert.equal(r1.state, "waiting"); assert.equal(r2.state, "ready"); r2:run(3); assert.equal(r1.state, "waiting"); assert.equal(r2.state, "waiting"); done2(); assert.equal(r1.state, "waiting"); assert.equal(r2.state, "ready"); r2:run(3); assert.equal(r1.state, "waiting"); assert.equal(r2.state, "waiting"); done2(); assert.equal(r1.state, "waiting"); assert.equal(r2.state, "ready"); r2:run(4); assert.equal(r1.state, "waiting"); assert.equal(r2.state, "ready"); done1(); assert.equal(r1.state, "ready"); r1:run(4); assert.equal(r1.state, "ready"); assert.equal(r1.state, "ready"); end); -- luacheck: ignore 211/rf -- FIXME what's rf? it("should support multiple done() calls", function () local processed_item; local wait, done; local r, rf = new(function (item) wait, done = async.waiter(4); wait(); processed_item = item; end); r:run("test"); for _ = 1, 3 do done(); assert.equal(r.state, "waiting"); assert.is_nil(processed_item); end done(); assert.equal(r.state, "ready"); assert.equal(processed_item, "test"); assert.spy(r.watchers.error).was_not.called(); end); it("should not allow done() to be called more than specified", function () local processed_item; local wait, done; local r, rf = new(function (item) wait, done = async.waiter(4); wait(); processed_item = item; end); r:run("test"); for _ = 1, 4 do done(); end assert.has_error(done); assert.equal(r.state, "ready"); assert.equal(processed_item, "test"); assert.spy(r.watchers.error).was_not.called(); end); it("should allow done() to be called before wait()", function () local processed_item; local r, rf = new(function (item) local wait, done = async.waiter(); done(); wait(); processed_item = item; end); r:run("test"); assert.equal(processed_item, "test"); assert.equal(r.state, "ready"); -- Since the observable state did not change, -- the watchers should not have been called assert.spy(r.watchers.waiting).was_not.called(); assert.spy(r.watchers.ready).was_not.called(); end); end); describe("#ready()", function () it("should return false outside an async context", function () assert.falsy(async.ready()); end); it("should return true inside an async context", function () local r = new(function () assert.truthy(async.ready()); end); r:run(true); assert.spy(r.func).was.called(); assert.spy(r.watchers.error).was_not.called(); end); end); describe("#sleep()", function () after_each(function () -- Restore to default async.set_schedule_function(nil); end); it("should fail if no scheduler configured", function () local r = new(function () async.sleep(5); end); r:run(true); assert.spy(r.watchers.error).was.called(); -- Set dummy scheduler async.set_schedule_function(function () end); local r2 = new(function () async.sleep(5); end); r2:run(true); assert.spy(r2.watchers.error).was_not.called(); end); it("should work", function () local queue = {}; local add_task = spy.new(function (t, f) table.insert(queue, { t, f }); end); async.set_schedule_function(add_task); local processed_item; local r = new(function (item) async.sleep(5); processed_item = item; end); r:run("test"); -- Nothing happened, because the runner is sleeping assert.is_nil(processed_item); assert.equal(r.state, "waiting"); assert.spy(add_task).was_called(1); assert.spy(add_task).was_called_with(match.is_number(), match.is_function()); assert.spy(r.watchers.waiting).was.called(); assert.spy(r.watchers.ready).was_not.called(); -- Pretend the timer has triggered, call the handler queue[1][2](); assert.equal(processed_item, "test"); assert.equal(r.state, "ready"); assert.spy(r.watchers.ready).was.called(); end); end); describe("#set_nexttick()", function () after_each(function () -- Restore to default async.set_nexttick(nil); end); it("should work", function () local queue = {}; local nexttick = spy.new(function (f) assert.is_function(f); table.insert(queue, f); end); async.set_nexttick(nexttick); local processed_item; local wait, done; local r = new(function (item) wait, done = async.waiter(); wait(); processed_item = item; end); r:run("test"); -- Nothing happened, because the runner is waiting assert.is_nil(processed_item); assert.equal(r.state, "waiting"); assert.spy(nexttick).was_called(0); assert.spy(r.watchers.waiting).was.called(); assert.spy(r.watchers.ready).was_not.called(); -- Mark the runner as ready, it should be scheduled for -- the next tick done(); assert.spy(nexttick).was_called(1); assert.spy(nexttick).was_called_with(match.is_function()); assert.equal(1, #queue); -- Pretend it's the next tick - call the pending function queue[1](); assert.equal(processed_item, "test"); assert.equal(r.state, "ready"); assert.spy(r.watchers.ready).was.called(); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_bitcompat_spec.lua0000644000000000000000000000012014475665104020052 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_bitcompat_spec.lua0000644000175000017500000000127014475665104022257 0ustar00prosodyprosody00000000000000describe("util.bitcompat", function () -- bitcompat will pass through to an appropriate implementation. Our -- goal here is to check that whatever implementation is in use passes -- these basic sanity checks. local bit = require "util.bitcompat"; it("bor works", function () assert.equal(0xF0FF, bit.bor(0xF000, 0x00F0, 0x000F)); end); it("band works", function () assert.equal(0x0F, bit.band(0xFF, 0x1F, 0x0F)); end); it("bxor works", function () assert.equal(0x13, bit.bxor(0x10, 0x0F, 0x0C)); end); it("rshift works", function () assert.equal(0x0F, bit.rshift(0xFF, 4)); end); it("lshift works", function () assert.equal(0xFF00, bit.lshift(0xFF, 8)); end); end); prosody-0.12.4/spec/PaxHeaders/util_cache_spec.lua0000644000000000000000000000012014475665104017133 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_cache_spec.lua0000644000175000017500000002306114475665104021342 0ustar00prosodyprosody00000000000000 local cache = require "util.cache"; describe("util.cache", function() describe("#new()", function() it("should work", function() local c = cache.new(5); local function expect_kv(key, value, actual_key, actual_value) assert.are.equal(key, actual_key, "key incorrect"); assert.are.equal(value, actual_value, "value incorrect"); end expect_kv(nil, nil, c:head()); expect_kv(nil, nil, c:tail()); assert.are.equal(c:count(), 0); c:set("one", 1) assert.are.equal(c:count(), 1); expect_kv("one", 1, c:head()); expect_kv("one", 1, c:tail()); c:set("two", 2) expect_kv("two", 2, c:head()); expect_kv("one", 1, c:tail()); c:set("three", 3) expect_kv("three", 3, c:head()); expect_kv("one", 1, c:tail()); c:set("four", 4) c:set("five", 5); assert.are.equal(c:count(), 5); expect_kv("five", 5, c:head()); expect_kv("one", 1, c:tail()); c:set("foo", nil); assert.are.equal(c:count(), 5); expect_kv("five", 5, c:head()); expect_kv("one", 1, c:tail()); assert.are.equal(c:get("one"), 1); expect_kv("five", 5, c:head()); expect_kv("one", 1, c:tail()); assert.are.equal(c:get("two"), 2); assert.are.equal(c:get("three"), 3); assert.are.equal(c:get("four"), 4); assert.are.equal(c:get("five"), 5); assert.are.equal(c:get("foo"), nil); assert.are.equal(c:get("bar"), nil); c:set("six", 6); assert.are.equal(c:count(), 5); expect_kv("six", 6, c:head()); expect_kv("two", 2, c:tail()); assert.are.equal(c:get("one"), nil); assert.are.equal(c:get("two"), 2); assert.are.equal(c:get("three"), 3); assert.are.equal(c:get("four"), 4); assert.are.equal(c:get("five"), 5); assert.are.equal(c:get("six"), 6); c:set("three", nil); assert.are.equal(c:count(), 4); assert.are.equal(c:get("one"), nil); assert.are.equal(c:get("two"), 2); assert.are.equal(c:get("three"), nil); assert.are.equal(c:get("four"), 4); assert.are.equal(c:get("five"), 5); assert.are.equal(c:get("six"), 6); c:set("seven", 7); assert.are.equal(c:count(), 5); assert.are.equal(c:get("one"), nil); assert.are.equal(c:get("two"), 2); assert.are.equal(c:get("three"), nil); assert.are.equal(c:get("four"), 4); assert.are.equal(c:get("five"), 5); assert.are.equal(c:get("six"), 6); assert.are.equal(c:get("seven"), 7); c:set("eight", 8); assert.are.equal(c:count(), 5); assert.are.equal(c:get("one"), nil); assert.are.equal(c:get("two"), nil); assert.are.equal(c:get("three"), nil); assert.are.equal(c:get("four"), 4); assert.are.equal(c:get("five"), 5); assert.are.equal(c:get("six"), 6); assert.are.equal(c:get("seven"), 7); assert.are.equal(c:get("eight"), 8); c:set("four", 4); assert.are.equal(c:count(), 5); assert.are.equal(c:get("one"), nil); assert.are.equal(c:get("two"), nil); assert.are.equal(c:get("three"), nil); assert.are.equal(c:get("four"), 4); assert.are.equal(c:get("five"), 5); assert.are.equal(c:get("six"), 6); assert.are.equal(c:get("seven"), 7); assert.are.equal(c:get("eight"), 8); c:set("nine", 9); assert.are.equal(c:count(), 5); assert.are.equal(c:get("one"), nil); assert.are.equal(c:get("two"), nil); assert.are.equal(c:get("three"), nil); assert.are.equal(c:get("four"), 4); assert.are.equal(c:get("five"), nil); assert.are.equal(c:get("six"), 6); assert.are.equal(c:get("seven"), 7); assert.are.equal(c:get("eight"), 8); assert.are.equal(c:get("nine"), 9); do local keys = { "nine", "four", "eight", "seven", "six" }; local values = { 9, 4, 8, 7, 6 }; local i = 0; for k, v in c:items() do i = i + 1; assert.are.equal(k, keys[i]); assert.are.equal(v, values[i]); end assert.are.equal(i, 5); c:set("four", "2+2"); assert.are.equal(c:count(), 5); assert.are.equal(c:get("one"), nil); assert.are.equal(c:get("two"), nil); assert.are.equal(c:get("three"), nil); assert.are.equal(c:get("four"), "2+2"); assert.are.equal(c:get("five"), nil); assert.are.equal(c:get("six"), 6); assert.are.equal(c:get("seven"), 7); assert.are.equal(c:get("eight"), 8); assert.are.equal(c:get("nine"), 9); end do local keys = { "four", "nine", "eight", "seven", "six" }; local values = { "2+2", 9, 8, 7, 6 }; local i = 0; for k, v in c:items() do i = i + 1; assert.are.equal(k, keys[i]); assert.are.equal(v, values[i]); end assert.are.equal(i, 5); c:set("foo", nil); assert.are.equal(c:count(), 5); assert.are.equal(c:get("one"), nil); assert.are.equal(c:get("two"), nil); assert.are.equal(c:get("three"), nil); assert.are.equal(c:get("four"), "2+2"); assert.are.equal(c:get("five"), nil); assert.are.equal(c:get("six"), 6); assert.are.equal(c:get("seven"), 7); assert.are.equal(c:get("eight"), 8); assert.are.equal(c:get("nine"), 9); end do local keys = { "four", "nine", "eight", "seven", "six" }; local values = { "2+2", 9, 8, 7, 6 }; local i = 0; for k, v in c:items() do i = i + 1; assert.are.equal(k, keys[i]); assert.are.equal(v, values[i]); end assert.are.equal(i, 5); c:set("four", nil); assert.are.equal(c:get("one"), nil); assert.are.equal(c:get("two"), nil); assert.are.equal(c:get("three"), nil); assert.are.equal(c:get("four"), nil); assert.are.equal(c:get("five"), nil); assert.are.equal(c:get("six"), 6); assert.are.equal(c:get("seven"), 7); assert.are.equal(c:get("eight"), 8); assert.are.equal(c:get("nine"), 9); end do local keys = { "nine", "eight", "seven", "six" }; local values = { 9, 8, 7, 6 }; local i = 0; for k, v in c:items() do i = i + 1; assert.are.equal(k, keys[i]); assert.are.equal(v, values[i]); end assert.are.equal(i, 4); end do local evicted_key, evicted_value; local c2 = cache.new(3, function (_key, _value) evicted_key, evicted_value = _key, _value; end); local function set(k, v, should_evict_key, should_evict_value) evicted_key, evicted_value = nil, nil; c2:set(k, v); assert.are.equal(evicted_key, should_evict_key); assert.are.equal(evicted_value, should_evict_value); end set("a", 1) set("a", 1) set("a", 1) set("a", 1) set("a", 1) set("b", 2) set("c", 3) set("b", 2) set("d", 4, "a", 1) set("e", 5, "c", 3) end do local evicted_key, evicted_value; local c3 = cache.new(1, function (_key, _value) evicted_key, evicted_value = _key, _value; if _key == "a" then -- Sanity check for what we're evicting assert.are.equal(_key, "a"); assert.are.equal(_value, 1); -- We're going to block eviction of this key/value, so set to nil... evicted_key, evicted_value = nil, nil; -- Returning false to block eviction return false end end); local function set(k, v, should_evict_key, should_evict_value) evicted_key, evicted_value = nil, nil; local ret = c3:set(k, v); assert.are.equal(evicted_key, should_evict_key); assert.are.equal(evicted_value, should_evict_value); return ret; end set("a", 1) set("a", 1) set("a", 1) set("a", 1) set("a", 1) -- Our on_evict prevents "a" from being evicted, causing this to fail... assert.are.equal(set("b", 2), false, "Failed to prevent eviction, or signal result"); expect_kv("a", 1, c3:head()); expect_kv("a", 1, c3:tail()); -- Check the final state is what we expect assert.are.equal(c3:get("a"), 1); assert.are.equal(c3:get("b"), nil); assert.are.equal(c3:count(), 1); end local c4 = cache.new(3, false); assert.are.equal(c4:set("a", 1), true); assert.are.equal(c4:set("a", 1), true); assert.are.equal(c4:set("a", 1), true); assert.are.equal(c4:set("a", 1), true); assert.are.equal(c4:set("b", 2), true); assert.are.equal(c4:set("c", 3), true); assert.are.equal(c4:set("d", 4), false); assert.are.equal(c4:set("d", 4), false); assert.are.equal(c4:set("d", 4), false); expect_kv("c", 3, c4:head()); expect_kv("a", 1, c4:tail()); local c5 = cache.new(3, function (k, v) --luacheck: ignore 212/v if k == "a" then return nil; elseif k == "b" then return true; end return false; end); assert.are.equal(c5:set("a", 1), true); assert.are.equal(c5:set("a", 1), true); assert.are.equal(c5:set("a", 1), true); assert.are.equal(c5:set("a", 1), true); assert.are.equal(c5:set("b", 2), true); assert.are.equal(c5:set("c", 3), true); assert.are.equal(c5:set("d", 4), true); -- "a" evicted (cb returned nil) assert.are.equal(c5:set("d", 4), true); -- nop assert.are.equal(c5:set("d", 4), true); -- nop assert.are.equal(c5:set("e", 5), true); -- "b" evicted (cb returned true) assert.are.equal(c5:set("f", 6), false); -- "c" won't evict (cb returned false) expect_kv("e", 5, c5:head()); expect_kv("c", 3, c5:tail()); end); (_VERSION=="Lua 5.1" and pending or it)(":table works", function () local t = cache.new(3):table(); assert.is.table(t); t["a"] = "1"; assert.are.equal(t["a"], "1"); t["b"] = "2"; assert.are.equal(t["b"], "2"); t["c"] = "3"; assert.are.equal(t["c"], "3"); t["d"] = "4"; assert.are.equal(t["d"], "4"); assert.are.equal(t["a"], nil); local i = spy.new(function () end); for k, v in pairs(t) do i(k,v) end assert.spy(i).was_called(); assert.spy(i).was_called_with("b", "2"); assert.spy(i).was_called_with("c", "3"); assert.spy(i).was_called_with("d", "4"); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_dataforms_spec.lua0000644000000000000000000000012014475665104020050 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_dataforms_spec.lua0000644000175000017500000003302514475665104022260 0ustar00prosodyprosody00000000000000local dataforms = require "util.dataforms"; local st = require "util.stanza"; local jid = require "util.jid"; local iter = require "util.iterators"; describe("util.dataforms", function () local some_form, xform; setup(function () some_form = dataforms.new({ title = "form-title", instructions = "form-instructions", { type = "hidden", name = "FORM_TYPE", value = "xmpp:prosody.im/spec/util.dataforms#1", }; { type = "fixed"; value = "Fixed field"; }, { type = "boolean", label = "boolean-label", name = "boolean-field", value = true, }, { type = "fixed", label = "fixed-label", name = "fixed-field", value = "fixed-value", }, { type = "hidden", label = "hidden-label", name = "hidden-field", value = "hidden-value", }, { type = "jid-multi", label = "jid-multi-label", name = "jid-multi-field", value = { "jid@multi/value#1", "jid@multi/value#2", }, }, { type = "jid-single", label = "jid-single-label", name = "jid-single-field", value = "jid@single/value", }, { type = "list-multi", label = "list-multi-label", name = "list-multi-field", value = { "list-multi-option-value#1", "list-multi-option-value#3", }, options = { { label = "list-multi-option-label#1", value = "list-multi-option-value#1", default = true, }, { label = "list-multi-option-label#2", value = "list-multi-option-value#2", default = false, }, { label = "list-multi-option-label#3", value = "list-multi-option-value#3", default = true, }, } }, { type = "list-single", label = "list-single-label", name = "list-single-field", value = "list-single-value", options = { "list-single-value", "list-single-value#2", "list-single-value#3", } }, { type = "text-multi", label = "text-multi-label", name = "text-multi-field", value = "text\nmulti\nvalue", }, { type = "text-private", label = "text-private-label", name = "text-private-field", value = "text-private-value", }, { type = "text-single", label = "text-single-label", name = "text-single-field", value = "text-single-value", }, { -- XEP-0221 -- TODO Validate the XML produced by this. type = "text-single", label = "text-single-with-media-label", name = "text-single-with-media-field", media = { height = 24, width = 32, { type = "image/png", uri = "data:", }, }, }, }); xform = some_form:form(); end); it("XML serialization looks like it should", function () assert.truthy(xform); assert.truthy(st.is_stanza(xform)); assert.equal("x", xform.name); assert.equal("jabber:x:data", xform.attr.xmlns); assert.equal("FORM_TYPE", xform:find("field@var")); assert.equal("xmpp:prosody.im/spec/util.dataforms#1", xform:find("field/value#")); local allowed_direct_children = { title = true, instructions = true, field = true, } for tag in xform:childtags() do assert.truthy(allowed_direct_children[tag.name], "unknown direct child"); end end); it("produced boolean field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "boolean-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("boolean-field", f.attr.var); assert.equal("boolean", f.attr.type); assert.equal("boolean-label", f.attr.label); assert.equal(1, iter.count(f:childtags("value"))); local val = f:get_child_text("value"); assert.truthy(val == "true" or val == "1"); end); it("produced fixed field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "fixed-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("fixed-field", f.attr.var); assert.equal("fixed", f.attr.type); assert.equal("fixed-label", f.attr.label); assert.equal(1, iter.count(f:childtags("value"))); assert.equal("fixed-value", f:get_child_text("value")); end); it("produced hidden field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "hidden-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("hidden-field", f.attr.var); assert.equal("hidden", f.attr.type); assert.equal("hidden-label", f.attr.label); assert.equal(1, iter.count(f:childtags("value"))); assert.equal("hidden-value", f:get_child_text("value")); end); it("produced jid-multi field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "jid-multi-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("jid-multi-field", f.attr.var); assert.equal("jid-multi", f.attr.type); assert.equal("jid-multi-label", f.attr.label); assert.equal(2, iter.count(f:childtags("value"))); local i = 0; for value in f:childtags("value") do i = i + 1; assert.equal(("jid@multi/value#%d"):format(i), value:get_text()); end end); it("produced jid-single field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "jid-single-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("jid-single-field", f.attr.var); assert.equal("jid-single", f.attr.type); assert.equal("jid-single-label", f.attr.label); assert.equal(1, iter.count(f:childtags("value"))); assert.equal("jid@single/value", f:get_child_text("value")); assert.truthy(jid.prep(f:get_child_text("value"))); end); it("produced list-multi field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "list-multi-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("list-multi-field", f.attr.var); assert.equal("list-multi", f.attr.type); assert.equal("list-multi-label", f.attr.label); assert.equal(2, iter.count(f:childtags("value"))); assert.equal("list-multi-option-value#1", f:get_child_text("value")); assert.equal(3, iter.count(f:childtags("option"))); end); it("produced list-single field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "list-single-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("list-single-field", f.attr.var); assert.equal("list-single", f.attr.type); assert.equal("list-single-label", f.attr.label); assert.equal(1, iter.count(f:childtags("value"))); assert.equal("list-single-value", f:get_child_text("value")); assert.equal(3, iter.count(f:childtags("option"))); end); it("produced text-multi field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "text-multi-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("text-multi-field", f.attr.var); assert.equal("text-multi", f.attr.type); assert.equal("text-multi-label", f.attr.label); assert.equal(3, iter.count(f:childtags("value"))); end); it("produced text-private field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "text-private-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("text-private-field", f.attr.var); assert.equal("text-private", f.attr.type); assert.equal("text-private-label", f.attr.label); assert.equal(1, iter.count(f:childtags("value"))); assert.equal("text-private-value", f:get_child_text("value")); end); it("produced text-single field correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "text-single-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("text-single-field", f.attr.var); assert.equal("text-single", f.attr.type); assert.equal("text-single-label", f.attr.label); assert.equal(1, iter.count(f:childtags("value"))); assert.equal("text-single-value", f:get_child_text("value")); end); describe("get_type()", function () it("identifes dataforms", function () assert.equal(nil, dataforms.get_type(nil)); assert.equal(nil, dataforms.get_type("")); assert.equal(nil, dataforms.get_type({})); assert.equal(nil, dataforms.get_type(st.stanza("no-a-form"))); assert.equal("xmpp:prosody.im/spec/util.dataforms#1", dataforms.get_type(xform)); end); end); describe(":data", function () it("returns something", function () assert.truthy(some_form:data(xform)); end); end); describe("issue1177", function () local form_with_stuff; setup(function () form_with_stuff = dataforms.new({ { type = "list-single"; name = "abtest"; label = "A or B?"; options = { { label = "A", value = "a", default = true }, { label = "B", value = "b" }, }; }, }); end); it("includes options when value is included", function () local f = form_with_stuff:form({ abtest = "a" }); assert.truthy(f:find("field/option")); end); it("includes options when value is excluded", function () local f = form_with_stuff:form({}); assert.truthy(f:find("field/option")); end); end); describe("using current values in place of missing fields", function () it("gets back the previous values when given an empty form", function () local current = { ["list-multi-field"] = { "list-multi-option-value#2"; }; ["list-single-field"] = "list-single-value#2"; ["hidden-field"] = "hidden-value"; ["boolean-field"] = false; ["text-multi-field"] = "words\ngo\nhere"; ["jid-single-field"] = "alice@example.com"; ["text-private-field"] = "hunter2"; ["text-single-field"] = "text-single-value"; ["jid-multi-field"] = { "bob@example.net"; }; }; local expect = { -- FORM_TYPE = "xmpp:prosody.im/spec/util.dataforms#1"; -- does this need to be included? ["list-multi-field"] = { "list-multi-option-value#2"; }; ["list-single-field"] = "list-single-value#2"; ["hidden-field"] = "hidden-value"; ["boolean-field"] = false; ["text-multi-field"] = "words\ngo\nhere"; ["jid-single-field"] = "alice@example.com"; ["text-private-field"] = "hunter2"; ["text-single-field"] = "text-single-value"; ["jid-multi-field"] = { "bob@example.net"; }; }; local data, err = some_form:data(st.stanza("x", {xmlns="jabber:x:data"}), current); assert.is.table(data, err); assert.same(expect, data, "got back the same data"); end); end); describe("field 'var' property", function () it("works as expected", function () local f = dataforms.new { { var = "someprefix#the-field", name = "the_field", type = "text-single", } }; local x = f:form({the_field = "hello"}); assert.equal("someprefix#the-field", x:find"field@var"); assert.equal("hello", x:find"field/value#"); end); end); describe("number handling", function() it("handles numbers as booleans", function() local f = dataforms.new { { name = "boolean"; type = "boolean" } }; local x = f:form({ boolean = 0 }); assert.equal("0", x:find "field/value#"); x = f:form({ boolean = 1 }); assert.equal("1", x:find "field/value#"); end); end) describe("datatype validation", function () describe("integer", function () local f = dataforms.new { { name = "number", type = "text-single", datatype = "xs:integer", range_min = -10, range_max = 10, }, }; it("roundtrip works", function () local d = f:data(f:form({number = 1})); assert.equal(1, d.number); end); it("error handling works", function () local d,e = f:data(f:form({number = "nan"})); assert.not_equal(1, d.number); assert.table(e); assert.string(e.number); end); it("bounds-checking work works", function () local d,e = f:data(f:form({number = 100})); assert.not_equal(100, d.number); assert.table(e); assert.string(e.number); end); it("serializes larger ints okay", function () local x = f:form{number=1125899906842624} assert.equal("1125899906842624", x:find("field/value#")) end); end) describe("datetime", function () local f = dataforms.new { { name = "when"; type = "text-single"; datatype = "xs:dateTime" } } it("works", function () local x = f:form({ when = 1219439340 }); assert.equal("2008-08-22T21:09:00Z", x:find("field/value#")) local d, e = f:data(x); assert.is_nil(e); assert.same({ when = 1219439340 }, d); end); end) end); describe("media element", function () it("produced media element correctly", function () local f; for field in xform:childtags("field") do if field.attr.var == "text-single-with-media-field" then f = field; break; end end assert.truthy(st.is_stanza(f)); assert.equal("text-single-with-media-field", f.attr.var); assert.equal("text-single", f.attr.type); assert.equal("text-single-with-media-label", f.attr.label); assert.equal(0, iter.count(f:childtags("value"))); local m = f:get_child("media", "urn:xmpp:media-element"); assert.truthy(st.is_stanza(m)); assert.equal("24", m.attr.height); assert.equal("32", m.attr.width); assert.equal(1, iter.count(m:childtags("uri"))); local u = m:get_child("uri"); assert.truthy(st.is_stanza(u)); assert.equal("image/png", u.attr.type); assert.equal("data:", u:get_text()); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_datamanager_spec.lua0000644000000000000000000000012014475665104020334 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_datamanager_spec.lua0000644000175000017500000000343214475665104022543 0ustar00prosodyprosody00000000000000describe("util.datamanager", function() local dm; setup(function() dm = require "util.datamanager"; dm.set_data_path("./data"); end); describe("keyvalue", function() local data = {hello = "world"}; do local ok, err = dm.store("keyval-user", "datamanager.test", "testdata", data); assert.truthy(ok, err); end do local read, err = dm.load("keyval-user", "datamanager.test", "testdata") assert.same(data, read, err); end do local ok, err = dm.store("keyval-user", "datamanager.test", "testdata", nil); assert.truthy(ok, err); end do local read, err = dm.load("keyval-user", "datamanager.test", "testdata") assert.is_nil(read, err); end end) describe("lists", function() do local ok, err = dm.list_store("list-user", "datamanager.test", "testdata", {}); assert.truthy(ok, err); end do local nothing, err = dm.list_load("list-user", "datamanager.test", "testdata"); assert.is_nil(nothing, err); assert.is_nil(err); end do local ok, err = dm.list_append("list-user", "datamanager.test", "testdata", {id = 1}); assert.truthy(ok, err); end do local ok, err = dm.list_append("list-user", "datamanager.test", "testdata", {id = 2}); assert.truthy(ok, err); end do local ok, err = dm.list_append("list-user", "datamanager.test", "testdata", {id = 3}); assert.truthy(ok, err); end do local list, err = dm.list_load("list-user", "datamanager.test", "testdata"); assert.same(list, {{id = 1}; {id = 2}; {id = 3}}, err); end do local ok, err = dm.list_store("list-user", "datamanager.test", "testdata", {}); assert.truthy(ok, err); end do local nothing, err = dm.list_load("list-user", "datamanager.test", "testdata"); assert.is_nil(nothing, err); assert.is_nil(err); end end) end) prosody-0.12.4/spec/PaxHeaders/util_datamapper_spec.lua0000644000000000000000000000012014475665104020206 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_datamapper_spec.lua0000644000175000017500000001561014475665104022416 0ustar00prosodyprosody00000000000000local st local xml local map setup(function() st = require "util.stanza"; xml = require "util.xml"; map = require "util.datamapper"; end); describe("util.datamapper", function() local s, x, d local disco, disco_info, disco_schema setup(function() -- a convenience function for simple attributes, there's a few of them local function attr() return {["$ref"]="#/$defs/attr"} end s = { ["$defs"] = { attr = { type = "string"; xml = { attribute = true } } }; type = "object"; xml = {name = "message"; namespace = "jabber:client"}; properties = { to = attr(); from = attr(); type = attr(); id = attr(); body = true; -- should be assumed to be a string lang = {type = "string"; xml = {attribute = true; prefix = "xml"}}; delay = { type = "object"; xml = {namespace = "urn:xmpp:delay"; name = "delay"}; properties = {stamp = attr(); from = attr(); reason = {type = "string"; xml = {text = true}}}; }; state = { type = "string"; enum = { "active", "inactive", "gone", "composing", "paused", }; xml = {x_name_is_value = true; namespace = "http://jabber.org/protocol/chatstates"}; }; fallback = { type = "boolean"; xml = {x_name_is_value = true; name = "fallback"; namespace = "urn:xmpp:fallback:0"}; }; origin_id = { type = "string"; xml = {name = "origin-id"; namespace = "urn:xmpp:sid:0"; x_single_attribute = "id"}; }; react = { type = "object"; xml = {namespace = "urn:xmpp:reactions:0"; name = "reactions"}; properties = { to = {type = "string"; xml = {attribute = true; name = "id"}}; -- should be assumed to be array since it has 'items' reactions = { items = { xml = { name = "reaction" } } }; }; }; stanza_ids = { type = "array"; items = { xml = {name = "stanza-id"; namespace = "urn:xmpp:sid:0"}; type = "object"; properties = { id = attr(); by = attr(); }; }; }; }; }; x = xml.parse [[ Hello Because 👋 🐢 ]]; d = { to = "a@test"; from = "b@test"; type = "chat"; id = "1"; lang = "en"; body = "Hello"; delay = {from = "test"; stamp = "2021-03-07T15:59:08+00:00"; reason = "Because"}; state = "active"; fallback = true; origin_id = "qgkmMdPB"; stanza_ids = {{id = "abc1"; by = "muc"}; {id = "xyz2"; by = "host"}}; react = { to = "744f6e18-a57a-11e9-a656-4889e7820c76"; reactions = { "👋", "🐢", }; }; }; disco_schema = { ["$defs"] = { attr = { type = "string"; xml = { attribute = true } } }; type = "object"; xml = { name = "iq"; namespace = "jabber:client" }; properties = { to = attr(); from = attr(); type = attr(); id = attr(); disco = { type = "object"; xml = { name = "query"; namespace = "http://jabber.org/protocol/disco#info" }; properties = { features = { type = "array"; items = { type = "string"; xml = { name = "feature"; x_single_attribute = "var"; }; }; }; }; }; }; }; disco_info = xml.parse[[ wrong ]]; disco = { type="result"; id="disco1"; from="example.com"; disco = { features = { "urn:example:feature:1"; "urn:example:feature:2"; "urn:example:feature:3"; }; }; }; end); describe("parse", function() it("works", function() assert.same(d, map.parse(s, x)); end); it("handles arrays", function () assert.same(disco, map.parse(disco_schema, disco_info)); end); it("deals with locally built stanzas", function() -- FIXME this could also be argued to be a util.stanza problem local ver_schema = { type = "object"; xml = {name = "iq"}; properties = { type = {type = "string"; xml = {attribute = true}}; id = {type = "string"; xml = {attribute = true}}; version = { type = "object"; xml = {name = "query"; namespace = "jabber:iq:version"}; -- properties should be assumed to be strings properties = {name = true; version = {}; os = {}}; }; }; }; local ver_st = st.iq({type = "result"; id = "v1"}) :query("jabber:iq:version") :text_tag("name", "Prosody") :text_tag("version", "trunk") :text_tag("os", "Lua 5.3") :reset(); local data = {type = "result"; id = "v1"; version = {name = "Prosody"; version = "trunk"; os = "Lua 5.3"}} assert.same(data, map.parse(ver_schema, ver_st)); end); end); describe("unparse", function() it("works", function() local u = map.unparse(s, d); assert.equal("message", u.name); assert.same(x.attr, u.attr); assert.equal(x:get_child_text("body"), u:get_child_text("body")); assert.equal(x:get_child_text("delay", "urn:xmpp:delay"), u:get_child_text("delay", "urn:xmpp:delay")); assert.same(x:get_child("delay", "urn:xmpp:delay").attr, u:get_child("delay", "urn:xmpp:delay").attr); assert.same(x:get_child("origin-id", "urn:xmpp:sid:0").attr, u:get_child("origin-id", "urn:xmpp:sid:0").attr); assert.same(x:get_child("reactions", "urn:xmpp:reactions:0").attr, u:get_child("reactions", "urn:xmpp:reactions:0").attr); assert.same(2, #u:get_child("reactions", "urn:xmpp:reactions:0").tags); for _, tag in ipairs(x.tags) do if tag.name ~= "UNRELATED" then assert.truthy(u:get_child(tag.name, tag.attr.xmlns) or u:get_child(tag.name), tag:top_tag()) end end assert.equal(#x.tags-1, #u.tags) end); it("handles arrays", function () local u = map.unparse(disco_schema, disco); assert.equal("urn:example:feature:1", u:find("{http://jabber.org/protocol/disco#info}query/feature/@var")) local n = 0; for child in u:get_child("query", "http://jabber.org/protocol/disco#info"):childtags("feature") do n = n + 1; assert.equal(string.format("urn:example:feature:%d", n), child.attr.var); end end); end); end) prosody-0.12.4/spec/PaxHeaders/util_datetime_spec.lua0000644000000000000000000000012014475665104017664 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_datetime_spec.lua0000644000175000017500000000453614475665104022101 0ustar00prosodyprosody00000000000000local util_datetime = require "util.datetime"; describe("util.datetime", function () it("should have been loaded", function () assert.is_table(util_datetime); end); describe("#date", function () local date = util_datetime.date; it("should exist", function () assert.is_function(date); end); it("should return a string", function () assert.is_string(date()); end); it("should look like a date", function () assert.truthy(string.find(date(), "^%d%d%d%d%-%d%d%-%d%d$")); end); it("should work", function () assert.equals(date(1136239445), "2006-01-02"); end); end); describe("#time", function () local time = util_datetime.time; it("should exist", function () assert.is_function(time); end); it("should return a string", function () assert.is_string(time()); end); it("should look like a timestamp", function () -- Note: Sub-second precision and timezones are ignored assert.truthy(string.find(time(), "^%d%d:%d%d:%d%d")); end); it("should work", function () assert.equals(time(1136239445), "22:04:05"); end); end); describe("#datetime", function () local datetime = util_datetime.datetime; it("should exist", function () assert.is_function(datetime); end); it("should return a string", function () assert.is_string(datetime()); end); it("should look like a timestamp", function () -- Note: Sub-second precision and timezones are ignored assert.truthy(string.find(datetime(), "^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d:%d%d")); end); it("should work", function () assert.equals(datetime(1136239445), "2006-01-02T22:04:05Z"); end); end); describe("#legacy", function () local legacy = util_datetime.legacy; it("should exist", function () assert.is_function(legacy); end); end); describe("#parse", function () local parse = util_datetime.parse; it("should exist", function () assert.is_function(parse); end); it("should work", function () -- Timestamp used by Go assert.equals(parse("2017-11-19T17:58:13Z"), 1511114293); assert.equals(parse("2017-11-19T18:58:50+0100"), 1511114330); assert.equals(parse("2006-01-02T15:04:05-0700"), 1136239445); end); it("should handle timezones", function () -- https://xmpp.org/extensions/xep-0082.html#example-2 and 3 assert.equals(parse("1969-07-21T02:56:15Z"), parse("1969-07-20T21:56:15-05:00")); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_dbuffer_spec.lua0000644000000000000000000000012014475665104017505 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_dbuffer_spec.lua0000644000175000017500000001023314475665104021711 0ustar00prosodyprosody00000000000000local dbuffer = require "util.dbuffer"; describe("util.dbuffer", function () describe("#new", function () it("has a constructor", function () assert.Function(dbuffer.new); end); it("can be created", function () assert.truthy(dbuffer.new()); end); it("won't create an empty buffer", function () assert.falsy(dbuffer.new(0)); end); it("won't create a negatively sized buffer", function () assert.falsy(dbuffer.new(-1)); end); end); describe(":write", function () local b = dbuffer.new(); it("works", function () assert.truthy(b:write("hi")); end); end); describe(":read", function () it("supports optional bytes parameter", function () -- should return the frontmost chunk local b = dbuffer.new(); assert.truthy(b:write("hello")); assert.truthy(b:write(" ")); assert.truthy(b:write("world")); assert.equal("h", b:read(1)); assert.equal("ello", b:read()); assert.equal(" ", b:read()); assert.equal("world", b:read()); end); end); describe(":read_until", function () it("works", function () local b = dbuffer.new(); b:write("hello\n"); b:write("world"); b:write("\n"); b:write("\n\n"); b:write("stuff"); b:write("more\nand more"); assert.equal(nil, b:read_until(".")); assert.equal(nil, b:read_until("%")); assert.equal("hello\n", b:read_until("\n")); assert.equal("world\n", b:read_until("\n")); assert.equal("\n", b:read_until("\n")); assert.equal("\n", b:read_until("\n")); assert.equal("stu", b:read(3)); assert.equal("ffmore\n", b:read_until("\n")); assert.equal(nil, b:read_until("\n")); assert.equal("and more", b:read_chunk()); end); end); describe(":discard", function () local b = dbuffer.new(); it("works", function () assert.truthy(b:write("hello world")); assert.truthy(b:discard(6)); assert.equal(5, b:length()); assert.equal(5, b:len()); assert.equal("world", b:read(5)); end); end); describe(":collapse()", function () it("works on an empty buffer", function () local b = dbuffer.new(); b:collapse(); end); end); describe(":sub", function () -- Helper function to compare buffer:sub() with string:sub() local s = "hello world"; local function test_sub(b, x, y) local string_result, buffer_result = s:sub(x, y), b:sub(x, y); assert.equals(string_result, buffer_result, ("buffer:sub(%d, %s) does not match string:sub()"):format(x, y and ("%d"):format(y) or "nil")); end it("works", function () local b = dbuffer.new(); assert.truthy(b:write("hello world")); assert.equals("hello", b:sub(1, 5)); end); it("works after discard", function () local b = dbuffer.new(256); assert.truthy(b:write("foobar")); assert.equals("foobar", b:sub(1, 6)); assert.truthy(b:discard(3)); -- consume "foo" assert.equals("bar", b:sub(1, 3)); end); it("supports optional end parameter", function () local b = dbuffer.new(); assert.truthy(b:write("hello world")); assert.equals("hello world", b:sub(1)); assert.equals("world", b:sub(-5)); end); it("is equivalent to string:sub", function () local b = dbuffer.new(11); assert.truthy(b:write(s)); for i = -13, 13 do for j = -13, 13 do test_sub(b, i, j); end end end); end); describe(":byte", function () -- Helper function to compare buffer:byte() with string:byte() local s = "hello world" local function test_byte(b, x, y) local string_result, buffer_result = {s:byte(x, y)}, {b:byte(x, y)}; assert.same(string_result, buffer_result, ("buffer:byte(%d, %s) does not match string:byte()"):format(x, y and ("%d"):format(y) or "nil")); end it("is equivalent to string:byte", function () local b = dbuffer.new(11); assert.truthy(b:write(s)); test_byte(b, 1); test_byte(b, 3); test_byte(b, -1); test_byte(b, -3); for i = -13, 13 do for j = -13, 13 do test_byte(b, i, j); end end end); it("works with characters > 127", function () local b = dbuffer.new(); b:write(string.char(0, 140)); local r = { b:byte(1, 2) }; assert.same({ 0, 140 }, r); end); it("works on an empty buffer", function () local b = dbuffer.new(); assert.equal("", b:sub(1,1)); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_debug_spec.lua0000644000000000000000000000012014475665104017156 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_debug_spec.lua0000644000175000017500000000450114475665104021363 0ustar00prosodyprosody00000000000000local dbg = require "util.debug"; describe("util.debug", function () describe("traceback()", function () it("works", function () local tb = dbg.traceback(); assert.is_string(tb); end); end); describe("get_traceback_table()", function () it("works", function () local count = 0; -- MUST stay in sync with the line numbers of these functions: local f1_defined, f3_defined = 43, 15; local function f3(f3_param) --luacheck: ignore 212/f3_param count = count + 1; for i = 1, 2 do local tb = dbg.get_traceback_table(i == 1 and coroutine.running() or nil, 0); assert.is_table(tb); --print(dbg.traceback(), "\n\n\n", require "util.serialization".serialize(tb, { fatal = false, unquoted = true})); local found_f1, found_f3; for _, frame in ipairs(tb) do if frame.info.linedefined == f1_defined then assert.equal(0, #frame.locals); assert.equal("f2", frame.upvalues[1].name); assert.equal("f1_upvalue", frame.upvalues[2].name); found_f1 = true; elseif frame.info.linedefined == f3_defined then assert.equal("f3_param", frame.locals[1].name); found_f3 = true; end end assert.is_true(found_f1); assert.is_true(found_f3); end end local function f2() local f2_local = "hello"; return f3(f2_local); end local f1_upvalue = "upvalue1"; local function f1() f2(f1_upvalue); end -- ok/err are caught and re-thrown so that -- busted gets to handle them in its own way local ok, err; local function hook() debug.sethook(); ok, err = pcall(f1); end -- Test the traceback is correct in various -- types of caller environments -- From a Lua hook debug.sethook(hook, "crl", 1); local a = string.sub("abcdef", 3, 4); assert.equal("cd", a); debug.sethook(); assert.equal(1, count); if not ok then error(err); end ok, err = nil, nil; -- From a signal handler (C hook) require "util.signal".signal("SIGUSR1", hook); require "util.signal".raise("SIGUSR1"); assert.equal(2, count); if not ok then error(err); end ok, err = nil, nil; -- Inside a coroutine local co = coroutine.create(function () hook(); end); coroutine.resume(co); if not ok then error(err); end assert.equal(3, count); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_encodings_spec.lua0000644000000000000000000000012014475665104020041 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_encodings_spec.lua0000644000175000017500000000267714475665104022262 0ustar00prosodyprosody00000000000000 local encodings = require "util.encodings"; local utf8 = assert(encodings.utf8, "no encodings.utf8 module"); describe("util.encodings", function () describe("#encode()", function() it("should work", function () assert.is.equal(encodings.base64.encode(""), ""); assert.is.equal(encodings.base64.encode('coucou'), "Y291Y291"); assert.is.equal(encodings.base64.encode("\0\0\0"), "AAAA"); assert.is.equal(encodings.base64.encode("\255\255\255"), "////"); end); end); describe("#decode()", function() it("should work", function () assert.is.equal(encodings.base64.decode(""), ""); assert.is.equal(encodings.base64.decode("="), ""); assert.is.equal(encodings.base64.decode('Y291Y291'), "coucou"); assert.is.equal(encodings.base64.decode("AAAA"), "\0\0\0"); assert.is.equal(encodings.base64.decode("////"), "\255\255\255"); end); end); end); describe("util.encodings.utf8", function() describe("#valid()", function() it("should work", function() for line in io.lines("spec/utf8_sequences.txt") do local data = line:match(":%s*([^#]+)"):gsub("%s+", ""):gsub("..", function (c) return string.char(tonumber(c, 16)); end) local expect = line:match("(%S+):"); assert(expect == "pass" or expect == "fail", "unknown expectation: "..line:match("^[^:]+")); local valid = utf8.valid(data); assert.is.equal(valid, utf8.valid(data.." ")); assert.is.equal(valid, expect == "pass", line); end end); end); end); prosody-0.12.4/spec/PaxHeaders/util_envload_spec.lua0000644000000000000000000000012014475665104017520 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_envload_spec.lua0000644000175000017500000000117714475665104021733 0ustar00prosodyprosody00000000000000describe("util.envload", function() local envload = require "util.envload"; describe("envload()", function() it("works", function() local f, err = envload.envload("return 'hello'", "@test", {}); assert.is_function(f, err); local ok, ret = pcall(f); assert.truthy(ok); assert.equal("hello", ret); end); it("lets you pass values in and out", function () local f, err = envload.envload("return thisglobal", "@test", { thisglobal = "yes, this one" }); assert.is_function(f, err); local ok, ret = pcall(f); assert.truthy(ok); assert.equal("yes, this one", ret); end); end) -- TODO envloadfile() end) prosody-0.12.4/spec/PaxHeaders/util_error_spec.lua0000644000000000000000000000012014475665104017221 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.724764638 prosody-0.12.4/spec/util_error_spec.lua0000644000175000017500000001524314475665104021433 0ustar00prosodyprosody00000000000000local errors = require "util.error" describe("util.error", function () describe("new()", function () it("works", function () local err = errors.new("bork", "bork bork"); assert.not_nil(err); assert.equal("cancel", err.type); assert.equal("undefined-condition", err.condition); assert.same("bork bork", err.context); end); describe("templates", function () it("works", function () local templates = { ["fail"] = { type = "wait", condition = "internal-server-error", code = 555; }; }; local err = errors.new("fail", { traceback = "in some file, somewhere" }, templates); assert.equal("wait", err.type); assert.equal("internal-server-error", err.condition); assert.equal(555, err.code); assert.same({ traceback = "in some file, somewhere" }, err.context); end); end); end); describe("is_err()", function () it("works", function () assert.truthy(errors.is_err(errors.new())); assert.falsy(errors.is_err("not an error")); end); end); describe("coerce", function () it("works", function () local ok, err = errors.coerce(nil, "it dun goofed"); assert.is_nil(ok); assert.truthy(errors.is_err(err)) end); end); describe("from_stanza", function () it("works", function () local st = require "util.stanza"; local m = st.message({ type = "chat" }); local e = st.error_reply(m, "modify", "bad-request", nil, "error.example"):tag("extra", { xmlns = "xmpp:example.test" }); local err = errors.from_stanza(e); assert.truthy(errors.is_err(err)); assert.equal("modify", err.type); assert.equal("bad-request", err.condition); assert.equal(e, err.context.stanza); assert.equal("error.example", err.context.by); assert.not_nil(err.extra.tag); end); end); describe("__tostring", function () it("doesn't throw", function () assert.has_no.errors(function () -- See 6f317e51544d tostring(errors.new()); end); end); end); describe("extra", function () it("keeps some extra fields", function () local err = errors.new({condition="gone",text="Sorry mate, it's all gone",extra={uri="file:///dev/null"}}); assert.is_table(err.extra); assert.equal("file:///dev/null", err.extra.uri); end); end) describe("init", function() it("basics works", function() local reg = errors.init("test", { broke = {type = "cancel"; condition = "internal-server-error"; text = "It broke :("}; nope = {type = "auth"; condition = "not-authorized"; text = "Can't let you do that Dave"}; }); local broke = reg.new("broke"); assert.equal("cancel", broke.type); assert.equal("internal-server-error", broke.condition); assert.equal("It broke :(", broke.text); assert.equal("test", broke.source); local nope = reg.new("nope"); assert.equal("auth", nope.type); assert.equal("not-authorized", nope.condition); assert.equal("Can't let you do that Dave", nope.text); end); it("compact mode works", function() local reg = errors.init("test", "spec", { broke = {"cancel"; "internal-server-error"; "It broke :("}; nope = {"auth"; "not-authorized"; "Can't let you do that Dave"; "sorry-dave"}; }); local broke = reg.new("broke"); assert.equal("cancel", broke.type); assert.equal("internal-server-error", broke.condition); assert.equal("It broke :(", broke.text); assert.is_nil(broke.extra); local nope = reg.new("nope"); assert.equal("auth", nope.type); assert.equal("not-authorized", nope.condition); assert.equal("Can't let you do that Dave", nope.text); assert.equal("spec", nope.extra.namespace); assert.equal("sorry-dave", nope.extra.condition); end); it("registry looks the same regardless of syntax", function() local normal = errors.init("test", { broke = {type = "cancel"; condition = "internal-server-error"; text = "It broke :("}; nope = { type = "auth"; condition = "not-authorized"; text = "Can't let you do that Dave"; extra = {namespace = "spec"; condition = "sorry-dave"}; }; }); local compact1 = errors.init("test", "spec", { broke = {"cancel"; "internal-server-error"; "It broke :("}; nope = {"auth"; "not-authorized"; "Can't let you do that Dave"; "sorry-dave"}; }); local compact2 = errors.init("test", { broke = {"cancel"; "internal-server-error"; "It broke :("}; nope = {"auth"; "not-authorized"; "Can't let you do that Dave"}; }); assert.same(normal.registry, compact1.registry); assert.same({ broke = {type = "cancel"; condition = "internal-server-error"; text = "It broke :("}; nope = {type = "auth"; condition = "not-authorized"; text = "Can't let you do that Dave"}; }, compact2.registry); end); describe(".wrap", function () local reg = errors.init("test", "spec", { myerror = { "cancel", "internal-server-error", "Oh no" }; }); it("is exposed", function () assert.is_function(reg.wrap); end); it("returns errors according to the registry", function () local e = reg.wrap("myerror"); assert.equal("cancel", e.type); assert.equal("internal-server-error", e.condition); assert.equal("Oh no", e.text); end); it("passes through existing errors", function () local e = reg.wrap(reg.new({ type = "auth", condition = "forbidden" })); assert.equal("auth", e.type); assert.equal("forbidden", e.condition); end); it("wraps arbitrary values", function () local e = reg.wrap(123); assert.equal("cancel", e.type); assert.equal("undefined-condition", e.condition); assert.equal(123, e.context.wrapped_error); end); end); describe(".coerce", function () local reg = errors.init("test", "spec", { myerror = { "cancel", "internal-server-error", "Oh no" }; }); it("is exposed", function () assert.is_function(reg.coerce); end); it("passes through existing errors", function () local function test() return nil, errors.new({ type = "auth", condition = "forbidden" }); end local ok, err = reg.coerce(test()); assert.is_nil(ok); assert.is_truthy(errors.is_err(err)); assert.equal("forbidden", err.condition); end); it("passes through successful return values", function () local function test() return 1, 2, 3, 4; end local one, two, three, four = reg.coerce(test()); assert.equal(1, one); assert.equal(2, two); assert.equal(3, three); assert.equal(4, four); end); it("wraps non-error objects", function () local function test() return nil, "myerror"; end local ok, err = reg.coerce(test()); assert.is_nil(ok); assert.is_truthy(errors.is_err(err)); assert.equal("internal-server-error", err.condition); assert.equal("Oh no", err.text); end); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_events_spec.lua0000644000000000000000000000012014475665104017374 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_events_spec.lua0000644000175000017500000001676114475665104021614 0ustar00prosodyprosody00000000000000local events = require "util.events"; describe("util.events", function () it("should export a new() function", function () assert.is_function(events.new); end); describe("new()", function () it("should return return a new events object", function () local e = events.new(); assert.is_function(e.add_handler); assert.is_function(e.remove_handler); end); end); local e, h; describe("API", function () before_each(function () e = events.new(); h = spy.new(function () end); end); it("should call handlers when an event is fired", function () e.add_handler("myevent", h); e.fire_event("myevent"); assert.spy(h).was_called(); end); it("should not call handlers when a different event is fired", function () e.add_handler("myevent", h); e.fire_event("notmyevent"); assert.spy(h).was_not_called(); end); it("should pass the data argument to handlers", function () e.add_handler("myevent", h); e.fire_event("myevent", "mydata"); assert.spy(h).was_called_with("mydata"); end); it("should support non-string events", function () local myevent = {}; e.add_handler(myevent, h); e.fire_event(myevent, "mydata"); assert.spy(h).was_called_with("mydata"); end); it("should call handlers in priority order", function () local data = {}; e.add_handler("myevent", function () table.insert(data, "h1"); end, 5); e.add_handler("myevent", function () table.insert(data, "h2"); end, 3); e.add_handler("myevent", function () table.insert(data, "h3"); end); e.fire_event("myevent", "mydata"); assert.same(data, { "h1", "h2", "h3" }); end); it("should support non-integer priority values", function () local data = {}; e.add_handler("myevent", function () table.insert(data, "h1"); end, 1); e.add_handler("myevent", function () table.insert(data, "h2"); end, 0.5); e.add_handler("myevent", function () table.insert(data, "h3"); end, 0.25); e.fire_event("myevent", "mydata"); assert.same(data, { "h1", "h2", "h3" }); end); it("should support negative priority values", function () local data = {}; e.add_handler("myevent", function () table.insert(data, "h1"); end, 1); e.add_handler("myevent", function () table.insert(data, "h2"); end, 0); e.add_handler("myevent", function () table.insert(data, "h3"); end, -1); e.fire_event("myevent", "mydata"); assert.same(data, { "h1", "h2", "h3" }); end); it("should support removing handlers", function () e.add_handler("myevent", h); e.fire_event("myevent"); e.remove_handler("myevent", h); e.fire_event("myevent"); assert.spy(h).was_called(1); end); it("should support adding multiple handlers at the same time", function () local ht = { myevent1 = spy.new(function () end); myevent2 = spy.new(function () end); myevent3 = spy.new(function () end); }; e.add_handlers(ht); e.fire_event("myevent1"); e.fire_event("myevent2"); assert.spy(ht.myevent1).was_called(); assert.spy(ht.myevent2).was_called(); assert.spy(ht.myevent3).was_not_called(); end); it("should support removing multiple handlers at the same time", function () local ht = { myevent1 = spy.new(function () end); myevent2 = spy.new(function () end); myevent3 = spy.new(function () end); }; e.add_handlers(ht); e.remove_handlers(ht); e.fire_event("myevent1"); e.fire_event("myevent2"); assert.spy(ht.myevent1).was_not_called(); assert.spy(ht.myevent2).was_not_called(); assert.spy(ht.myevent3).was_not_called(); end); pending("should support adding handlers within an event handler") pending("should support removing handlers within an event handler") it("should support getting the current handlers for an event", function () e.add_handler("myevent", h); local handlers = e.get_handlers("myevent"); assert.equal(h, handlers[1]); end); describe("wrappers", function () local w before_each(function () w = spy.new(function (handlers, event_name, event_data) assert.is_function(handlers); assert.equal("myevent", event_name) assert.equal("abc", event_data); return handlers(event_name, event_data); end); end); it("should get called", function () e.add_wrapper("myevent", w); e.add_handler("myevent", h); e.fire_event("myevent", "abc"); assert.spy(w).was_called(1); assert.spy(h).was_called(1); end); it("should be removable", function () e.add_wrapper("myevent", w); e.add_handler("myevent", h); e.fire_event("myevent", "abc"); e.remove_wrapper("myevent", w); e.fire_event("myevent", "abc"); assert.spy(w).was_called(1); assert.spy(h).was_called(2); end); it("should allow multiple wrappers", function () local w2 = spy.new(function (handlers, event_name, event_data) return handlers(event_name, event_data); end); e.add_wrapper("myevent", w); e.add_handler("myevent", h); e.add_wrapper("myevent", w2); e.fire_event("myevent", "abc"); e.remove_wrapper("myevent", w); e.fire_event("myevent", "abc"); assert.spy(w).was_called(1); assert.spy(w2).was_called(2); assert.spy(h).was_called(2); end); it("should support a mix of global and event wrappers", function () local w2 = spy.new(function (handlers, event_name, event_data) return handlers(event_name, event_data); end); e.add_wrapper(false, w); e.add_handler("myevent", h); e.add_wrapper("myevent", w2); e.fire_event("myevent", "abc"); e.remove_wrapper(false, w); e.fire_event("myevent", "abc"); assert.spy(w).was_called(1); assert.spy(w2).was_called(2); assert.spy(h).was_called(2); end); end); describe("global wrappers", function () local w before_each(function () w = spy.new(function (handlers, event_name, event_data) assert.is_function(handlers); assert.equal("myevent", event_name) assert.equal("abc", event_data); return handlers(event_name, event_data); end); end); it("should get called", function () e.add_wrapper(false, w); e.add_handler("myevent", h); e.fire_event("myevent", "abc"); assert.spy(w).was_called(1); assert.spy(h).was_called(1); end); it("should be removable", function () e.add_wrapper(false, w); e.add_handler("myevent", h); e.fire_event("myevent", "abc"); e.remove_wrapper(false, w); e.fire_event("myevent", "abc"); assert.spy(w).was_called(1); assert.spy(h).was_called(2); end); end); describe("debug hooks", function () it("should get called", function () local d = spy.new(function (handler, event_name, event_data) --luacheck: ignore 212/event_name return handler(event_data); end); e.add_handler("myevent", h); e.fire_event("myevent"); assert.spy(h).was_called(1); assert.spy(d).was_called(0); assert.is_nil(e.set_debug_hook(d)); e.fire_event("myevent", { mydata = true }); assert.spy(h).was_called(2); assert.spy(d).was_called(1); assert.spy(d).was_called_with(h, "myevent", { mydata = true }); assert.equal(d, e.set_debug_hook(nil)); e.fire_event("myevent", { mydata = false }); assert.spy(h).was_called(3); assert.spy(d).was_called(1); end); it("setting should return any existing debug hook", function () local function f() end local function g() end assert.is_nil(e.set_debug_hook(f)); assert.is_equal(f, e.set_debug_hook(g)); assert.is_equal(g, e.set_debug_hook(f)); assert.is_equal(f, e.set_debug_hook(nil)); assert.is_nil(e.set_debug_hook(f)); end); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_format_spec.lua0000644000000000000000000000012014475665104017360 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_format_spec.lua0000644000175000017500000007224214475665104021574 0ustar00prosodyprosody00000000000000local format = require "util.format".format; -- There are eight basic types in Lua: -- nil, boolean, number, string, function, userdata, thread, and table describe("util.format", function() describe("#format()", function() it("should work", function() assert.equal("hello", format("%s", "hello")); assert.equal("(nil)", format("%s")); assert.equal("(nil)", format("%d")); assert.equal("(nil)", format("%q")); assert.equal(" [(nil)]", format("", nil)); assert.equal("true", format("%s", true)); assert.equal("[true]", format("%d", true)); assert.equal("% [true]", format("%%", true)); assert.equal("{ }", format("%q", { })); assert.equal("[1.5]", format("%d", 1.5)); assert.equal("[7.3786976294838e+19]", format("%d", 73786976294838206464)); end); it("escapes ascii control stuff", function () assert.equal("␁", format("%s", "\1")); assert.equal("[␁]", format("%d", "\1")); end); it("escapes invalid UTF-8", function () assert.equal("\"Hello w\\195rld\"", format("%s", "Hello w\195rld")); end); if _VERSION >= "Lua 5.4" then it("handles %p formats", function () assert.matches("a 0x%x+ b", format("%s %p %s", "a", {}, "b")); end) else it("does something with %p formats", function () assert.string(format("%p", {})); end) end it("escapes multi-line strings", function () assert.equal("Hello\n\tWorld", format("%s", "Hello\nWorld")) assert.equal("\"Hello\\nWorld\"", format("%q", "Hello\nWorld")) end) -- Tests generated with loops! describe("nil", function () describe("to %c", function () it("works", function () assert.equal("(nil)", format("%c", nil)) end); end); describe("to %d", function () it("works", function () assert.equal("(nil)", format("%d", nil)) end); end); describe("to %i", function () it("works", function () assert.equal("(nil)", format("%i", nil)) end); end); describe("to %o", function () it("works", function () assert.equal("(nil)", format("%o", nil)) end); end); describe("to %u", function () it("works", function () assert.equal("(nil)", format("%u", nil)) end); end); describe("to %x", function () it("works", function () assert.equal("(nil)", format("%x", nil)) end); end); describe("to %X", function () it("works", function () assert.equal("(nil)", format("%X", nil)) end); end); describe("to %a", function () it("works", function () assert.equal("(nil)", format("%a", nil)) end); end); describe("to %A", function () it("works", function () assert.equal("(nil)", format("%A", nil)) end); end); describe("to %e", function () it("works", function () assert.equal("(nil)", format("%e", nil)) end); end); describe("to %E", function () it("works", function () assert.equal("(nil)", format("%E", nil)) end); end); describe("to %f", function () it("works", function () assert.equal("(nil)", format("%f", nil)) end); end); describe("to %g", function () it("works", function () assert.equal("(nil)", format("%g", nil)) end); end); describe("to %G", function () it("works", function () assert.equal("(nil)", format("%G", nil)) end); end); describe("to %q", function () it("works", function () assert.equal("(nil)", format("%q", nil)) end); end); describe("to %s", function () it("works", function () assert.equal("(nil)", format("%s", nil)) end); end); end); describe("boolean", function () describe("to %c", function () it("works", function () assert.equal("[true]", format("%c", true)) assert.equal("[false]", format("%c", false)) end); end); describe("to %d", function () it("works", function () assert.equal("[true]", format("%d", true)) assert.equal("[false]", format("%d", false)) end); end); describe("to %i", function () it("works", function () assert.equal("[true]", format("%i", true)) assert.equal("[false]", format("%i", false)) end); end); describe("to %o", function () it("works", function () assert.equal("[true]", format("%o", true)) assert.equal("[false]", format("%o", false)) end); end); describe("to %u", function () it("works", function () assert.equal("[true]", format("%u", true)) assert.equal("[false]", format("%u", false)) end); end); describe("to %x", function () it("works", function () assert.equal("[true]", format("%x", true)) assert.equal("[false]", format("%x", false)) end); end); describe("to %X", function () it("works", function () assert.equal("[true]", format("%X", true)) assert.equal("[false]", format("%X", false)) end); end); describe("to %a", function () it("works", function () assert.equal("[true]", format("%a", true)) assert.equal("[false]", format("%a", false)) end); end); describe("to %A", function () it("works", function () assert.equal("[true]", format("%A", true)) assert.equal("[false]", format("%A", false)) end); end); describe("to %e", function () it("works", function () assert.equal("[true]", format("%e", true)) assert.equal("[false]", format("%e", false)) end); end); describe("to %E", function () it("works", function () assert.equal("[true]", format("%E", true)) assert.equal("[false]", format("%E", false)) end); end); describe("to %f", function () it("works", function () assert.equal("[true]", format("%f", true)) assert.equal("[false]", format("%f", false)) end); end); describe("to %g", function () it("works", function () assert.equal("[true]", format("%g", true)) assert.equal("[false]", format("%g", false)) end); end); describe("to %G", function () it("works", function () assert.equal("[true]", format("%G", true)) assert.equal("[false]", format("%G", false)) end); end); describe("to %q", function () it("works", function () assert.equal("true", format("%q", true)) assert.equal("false", format("%q", false)) end); end); describe("to %s", function () it("works", function () assert.equal("true", format("%s", true)) assert.equal("false", format("%s", false)) end); end); end); describe("number", function () describe("to %c", function () it("works", function () assert.equal("a", format("%c", 97)) assert.equal("[1.5]", format("%c", 1.5)) assert.equal("[7.3786976294838e+19]", format("%c", 73786976294838206464)) assert.equal("[inf]", format("%c", math.huge)) end); end); describe("to %d", function () it("works", function () assert.equal("97", format("%d", 97)) assert.equal("-12345", format("%d", -12345)) assert.equal("[1.5]", format("%d", 1.5)) assert.equal("[7.3786976294838e+19]", format("%d", 73786976294838206464)) assert.equal("[inf]", format("%d", math.huge)) assert.equal("2147483647", format("%d", 2147483647)) end); end); describe("to %i", function () it("works", function () assert.equal("97", format("%i", 97)) assert.equal("-12345", format("%i", -12345)) assert.equal("[1.5]", format("%i", 1.5)) assert.equal("[7.3786976294838e+19]", format("%i", 73786976294838206464)) assert.equal("[inf]", format("%i", math.huge)) assert.equal("2147483647", format("%i", 2147483647)) end); end); describe("to %o", function () it("works", function () assert.equal("141", format("%o", 97)) assert.equal("[-12345]", format("%o", -12345)) assert.equal("[1.5]", format("%o", 1.5)) assert.equal("[7.3786976294838e+19]", format("%o", 73786976294838206464)) assert.equal("[inf]", format("%o", math.huge)) assert.equal("17777777777", format("%o", 2147483647)) end); end); describe("to %u", function () it("works", function () assert.equal("97", format("%u", 97)) assert.equal("[-12345]", format("%u", -12345)) assert.equal("[1.5]", format("%u", 1.5)) assert.equal("[7.3786976294838e+19]", format("%u", 73786976294838206464)) assert.equal("[inf]", format("%u", math.huge)) assert.equal("2147483647", format("%u", 2147483647)) end); end); describe("to %x", function () it("works", function () assert.equal("61", format("%x", 97)) assert.equal("[-12345]", format("%x", -12345)) assert.equal("[1.5]", format("%x", 1.5)) assert.equal("[7.3786976294838e+19]", format("%x", 73786976294838206464)) assert.equal("[inf]", format("%x", math.huge)) assert.equal("7fffffff", format("%x", 2147483647)) end); end); describe("to %X", function () it("works", function () assert.equal("61", format("%X", 97)) assert.equal("[-12345]", format("%X", -12345)) assert.equal("[1.5]", format("%X", 1.5)) assert.equal("[7.3786976294838e+19]", format("%X", 73786976294838206464)) assert.equal("[inf]", format("%X", math.huge)) assert.equal("7FFFFFFF", format("%X", 2147483647)) end); end); if _VERSION > "Lua 5.1" then -- COMPAT no %a or %A in Lua 5.1 describe("to %a", function () it("works", function () assert.equal("0x1.84p+6", format("%a", 97)) assert.equal("-0x1.81c8p+13", format("%a", -12345)) assert.equal("0x1.8p+0", format("%a", 1.5)) assert.equal("0x1p+66", format("%a", 73786976294838206464)) assert.equal("inf", format("%a", math.huge)) assert.equal("0x1.fffffffcp+30", format("%a", 2147483647)) end); end); describe("to %A", function () it("works", function () assert.equal("0X1.84P+6", format("%A", 97)) assert.equal("-0X1.81C8P+13", format("%A", -12345)) assert.equal("0X1.8P+0", format("%A", 1.5)) assert.equal("0X1P+66", format("%A", 73786976294838206464)) assert.equal("INF", format("%A", math.huge)) assert.equal("0X1.FFFFFFFCP+30", format("%A", 2147483647)) end); end); end describe("to %e", function () it("works", function () assert.equal("9.700000e+01", format("%e", 97)) assert.equal("-1.234500e+04", format("%e", -12345)) assert.equal("1.500000e+00", format("%e", 1.5)) assert.equal("7.378698e+19", format("%e", 73786976294838206464)) assert.equal("inf", format("%e", math.huge)) assert.equal("2.147484e+09", format("%e", 2147483647)) end); end); describe("to %E", function () it("works", function () assert.equal("9.700000E+01", format("%E", 97)) assert.equal("-1.234500E+04", format("%E", -12345)) assert.equal("1.500000E+00", format("%E", 1.5)) assert.equal("7.378698E+19", format("%E", 73786976294838206464)) assert.equal("INF", format("%E", math.huge)) assert.equal("2.147484E+09", format("%E", 2147483647)) end); end); describe("to %f", function () it("works", function () assert.equal("97.000000", format("%f", 97)) assert.equal("-12345.000000", format("%f", -12345)) assert.equal("1.500000", format("%f", 1.5)) assert.equal("73786976294838206464.000000", format("%f", 73786976294838206464)) assert.equal("inf", format("%f", math.huge)) assert.equal("2147483647.000000", format("%f", 2147483647)) end); end); describe("to %g", function () it("works", function () assert.equal("97", format("%g", 97)) assert.equal("-12345", format("%g", -12345)) assert.equal("1.5", format("%g", 1.5)) assert.equal("7.3787e+19", format("%g", 73786976294838206464)) assert.equal("inf", format("%g", math.huge)) assert.equal("2.14748e+09", format("%g", 2147483647)) end); end); describe("to %G", function () it("works", function () assert.equal("97", format("%G", 97)) assert.equal("-12345", format("%G", -12345)) assert.equal("1.5", format("%G", 1.5)) assert.equal("7.3787E+19", format("%G", 73786976294838206464)) assert.equal("INF", format("%G", math.huge)) assert.equal("2.14748E+09", format("%G", 2147483647)) end); end); describe("to %q", function () it("works", function () assert.equal("97", format("%q", 97)) assert.equal("-12345", format("%q", -12345)) assert.equal("1.5", format("%q", 1.5)) assert.equal("7.37869762948382065e+19", format("%q", 73786976294838206464)) assert.equal("(1/0)", format("%q", math.huge)) assert.equal("2147483647", format("%q", 2147483647)) end); end); describe("to %s", function () it("works", function () assert.equal("97", format("%s", 97)) assert.equal("-12345", format("%s", -12345)) assert.equal("1.5", format("%s", 1.5)) assert.equal("7.3786976294838e+19", format("%s", 73786976294838206464)) assert.equal("inf", format("%s", math.huge)) assert.equal("2147483647", format("%s", 2147483647)) end); end); end); describe("string", function () describe("to %c", function () it("works", function () assert.equal("[hello]", format("%c", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%c", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%c", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%c", "n\195\182d\195\165tg\195")) end); end); describe("to %d", function () it("works", function () assert.equal("[hello]", format("%d", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%d", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%d", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%d", "n\195\182d\195\165tg\195")) end); end); describe("to %i", function () it("works", function () assert.equal("[hello]", format("%i", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%i", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%i", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%i", "n\195\182d\195\165tg\195")) end); end); describe("to %o", function () it("works", function () assert.equal("[hello]", format("%o", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%o", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%o", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%o", "n\195\182d\195\165tg\195")) end); end); describe("to %u", function () it("works", function () assert.equal("[hello]", format("%u", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%u", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%u", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%u", "n\195\182d\195\165tg\195")) end); end); describe("to %x", function () it("works", function () assert.equal("[hello]", format("%x", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%x", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%x", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%x", "n\195\182d\195\165tg\195")) end); end); describe("to %X", function () it("works", function () assert.equal("[hello]", format("%X", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%X", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%X", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%X", "n\195\182d\195\165tg\195")) end); end); describe("to %a", function () it("works", function () assert.equal("[hello]", format("%a", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%a", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%a", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%a", "n\195\182d\195\165tg\195")) end); end); describe("to %A", function () it("works", function () assert.equal("[hello]", format("%A", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%A", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%A", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%A", "n\195\182d\195\165tg\195")) end); end); describe("to %e", function () it("works", function () assert.equal("[hello]", format("%e", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%e", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%e", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%e", "n\195\182d\195\165tg\195")) end); end); describe("to %E", function () it("works", function () assert.equal("[hello]", format("%E", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%E", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%E", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%E", "n\195\182d\195\165tg\195")) end); end); describe("to %f", function () it("works", function () assert.equal("[hello]", format("%f", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%f", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%f", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%f", "n\195\182d\195\165tg\195")) end); end); describe("to %g", function () it("works", function () assert.equal("[hello]", format("%g", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%g", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%g", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%g", "n\195\182d\195\165tg\195")) end); end); describe("to %G", function () it("works", function () assert.equal("[hello]", format("%G", "hello")) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%G", "foo \001\002\003 bar")) assert.equal("[nödåtgärd]", format("%G", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%G", "n\195\182d\195\165tg\195")) end); end); describe("to %q", function () it("works", function () assert.equal("\"hello\"", format("%q", "hello")) assert.equal("\"foo \\001\\002\\003 bar\"", format("%q", "foo \001\002\003 bar")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\\164rd\"", format("%q", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%q", "n\195\182d\195\165tg\195")) end); end); describe("to %s", function () it("works", function () assert.equal("hello", format("%s", "hello")) assert.equal("foo \226\144\129\226\144\130\226\144\131 bar", format("%s", "foo \001\002\003 bar")) assert.equal("nödåtgärd", format("%s", "n\195\182d\195\165tg\195\164rd")) assert.equal("\"n\\195\\182d\\195\\165tg\\195\"", format("%s", "n\195\182d\195\165tg\195")) end); end); end); describe("function", function () describe("to %c", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%c", function() end)) end); end); describe("to %d", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%d", function() end)) end); end); describe("to %i", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%i", function() end)) end); end); describe("to %o", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%o", function() end)) end); end); describe("to %u", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%u", function() end)) end); end); describe("to %x", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%x", function() end)) end); end); describe("to %X", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%X", function() end)) end); end); describe("to %a", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%a", function() end)) end); end); describe("to %A", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%A", function() end)) end); end); describe("to %e", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%e", function() end)) end); end); describe("to %E", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%E", function() end)) end); end); describe("to %f", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%f", function() end)) end); end); describe("to %g", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%g", function() end)) end); end); describe("to %G", function () it("works", function () assert.matches("[function: 0[xX]%x+]", format("%G", function() end)) end); end); describe("to %q", function () it("works", function () assert.matches('{__type="function",__error="fail"}', format("%q", function() end)) end); end); describe("to %s", function () it("works", function () assert.matches("function: 0[xX]%x+", format("%s", function() end)) end); end); end); describe("thread", function () describe("to %c", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%c", coroutine.create(function() end))) end); end); describe("to %d", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%d", coroutine.create(function() end))) end); end); describe("to %i", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%i", coroutine.create(function() end))) end); end); describe("to %o", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%o", coroutine.create(function() end))) end); end); describe("to %u", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%u", coroutine.create(function() end))) end); end); describe("to %x", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%x", coroutine.create(function() end))) end); end); describe("to %X", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%X", coroutine.create(function() end))) end); end); describe("to %a", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%a", coroutine.create(function() end))) end); end); describe("to %A", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%A", coroutine.create(function() end))) end); end); describe("to %e", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%e", coroutine.create(function() end))) end); end); describe("to %E", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%E", coroutine.create(function() end))) end); end); describe("to %f", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%f", coroutine.create(function() end))) end); end); describe("to %g", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%g", coroutine.create(function() end))) end); end); describe("to %G", function () it("works", function () assert.matches("[thread: 0[xX]%x+]", format("%G", coroutine.create(function() end))) end); end); describe("to %q", function () it("works", function () assert.matches('{__type="thread",__error="fail"}', format("%q", coroutine.create(function() end))) end); end); describe("to %s", function () it("works", function () assert.matches("thread: 0[xX]%x+", format("%s", coroutine.create(function() end))) end); end); end); describe("table", function () describe("to %c", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%c", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%c", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %d", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%d", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%d", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %i", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%i", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%i", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %o", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%o", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%o", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %u", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%u", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%u", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %x", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%x", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%x", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %X", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%X", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%X", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %a", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%a", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%a", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %A", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%A", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%A", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %e", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%e", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%e", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %E", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%E", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%E", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %f", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%f", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%f", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %g", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%g", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%g", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %G", function () it("works", function () assert.matches("[table: 0[xX]%x+]", format("%G", { })) assert.equal("[foo \226\144\129\226\144\130\226\144\131 bar]", format("%G", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %q", function () it("works", function () assert.matches("{ }", format("%q", { })) assert.equal("{ }", format("%q", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); describe("to %s", function () it("works", function () assert.matches("table: 0[xX]%x+", format("%s", { })) assert.equal("foo \226\144\129\226\144\130\226\144\131 bar", format("%s", setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}))) end); end); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_hashes_spec.lua0000644000000000000000000000012014475665104017343 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_hashes_spec.lua0000644000175000017500000000315714475665104021556 0ustar00prosodyprosody00000000000000-- Test vectors from RFC 6070 local hashes = require "util.hashes"; local hex = require "util.hex"; -- Also see spec for util.hmac where HMAC test cases reside describe("PBKDF2-HMAC-SHA1", function () it("test vector 1", function () local P = "password" local S = "salt" local c = 1 local DK = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; assert.equal(DK, hex.encode(hashes.pbkdf2_hmac_sha1(P, S, c))); end); it("test vector 2", function () local P = "password" local S = "salt" local c = 2 local DK = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; assert.equal(DK, hex.encode(hashes.pbkdf2_hmac_sha1(P, S, c))); end); it("test vector 3", function () local P = "password" local S = "salt" local c = 4096 local DK = "4b007901b765489abead49d926f721d065a429c1"; assert.equal(DK, hex.encode(hashes.pbkdf2_hmac_sha1(P, S, c))); end); it("test vector 4 #SLOW", function () local P = "password" local S = "salt" local c = 16777216 local DK = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; assert.equal(DK, hex.encode(hashes.pbkdf2_hmac_sha1(P, S, c))); end); end); describe("PBKDF2-HMAC-SHA256", function () it("test vector 1", function () local P = "password"; local S = "salt"; local c = 1 local DK = "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"; assert.equal(DK, hex.encode(hashes.pbkdf2_hmac_sha256(P, S, c))); end); it("test vector 2", function () local P = "password"; local S = "salt"; local c = 2 local DK = "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"; assert.equal(DK, hex.encode(hashes.pbkdf2_hmac_sha256(P, S, c))); end); end); prosody-0.12.4/spec/PaxHeaders/util_hashring_spec.lua0000644000000000000000000000012014475665104017673 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_hashring_spec.lua0000644000175000017500000000413214475665104022100 0ustar00prosodyprosody00000000000000local hashring = require "util.hashring"; describe("util.hashring", function () local sha256 = require "util.hashes".sha256; local ring = hashring.new(128, sha256); it("should fail to get a node that does not exist", function () assert.is_nil(ring:get_node("foo")) end); it("should support adding nodes", function () ring:add_node("node1"); end); it("should return a single node for all keys if only one node exists", function () for i = 1, 100 do assert.is_equal("node1", ring:get_node(tostring(i))) end end); it("should support adding a second node", function () ring:add_node("node2"); end); it("should fail to remove a non-existent node", function () assert.is_falsy(ring:remove_node("node3")); end); it("should succeed to remove a node", function () assert.is_truthy(ring:remove_node("node1")); end); it("should return the only node for all keys", function () for i = 1, 100 do assert.is_equal("node2", ring:get_node(tostring(i))) end end); it("should support adding multiple nodes", function () ring:add_nodes({ "node1", "node3", "node4", "node5" }); end); it("should disrupt a minimal number of keys on node removal", function () local orig_ring = ring:clone(); local node_tallies = {}; local n = 1000; for i = 1, n do local key = tostring(i); local node = ring:get_node(key); node_tallies[node] = (node_tallies[node] or 0) + 1; end --[[ for node, key_count in pairs(node_tallies) do print(node, key_count, ("%.2f%%"):format((key_count/n)*100)); end ]] ring:remove_node("node5"); local disrupted_keys = 0; for i = 1, n do local key = tostring(i); if orig_ring:get_node(key) ~= ring:get_node(key) then disrupted_keys = disrupted_keys + 1; end end assert.is_equal(node_tallies["node5"], disrupted_keys); end); it("should support removing multiple nodes", function () ring:remove_nodes({"node2", "node3", "node4", "node5"}); end); it("should return a single node for all keys if only one node remains", function () for i = 1, 100 do assert.is_equal("node1", ring:get_node(tostring(i))) end end); end); prosody-0.12.4/spec/PaxHeaders/util_hmac_spec.lua0000644000000000000000000000012014475665104017000 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_hmac_spec.lua0000644000175000017500000001236714475665104021216 0ustar00prosodyprosody00000000000000-- Test cases from RFC 4231 -- Yes, the lines are long, it's annoying to split the long hex things. -- luacheck: ignore 631 local hmac = require "util.hmac"; local hex = require "util.hex"; describe("Test case 1", function () local Key = hex.decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); local Data = hex.decode("4869205468657265"); describe("HMAC-SHA-256", function () it("works", function() assert.equal("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", hmac.sha256(Key, Data, true)) end); end); describe("HMAC-SHA-512", function () it("works", function() assert.equal("87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854", hmac.sha512(Key, Data, true)) end); end); end); describe("Test case 2", function () local Key = hex.decode("4a656665"); local Data = hex.decode("7768617420646f2079612077616e7420666f72206e6f7468696e673f"); describe("HMAC-SHA-256", function () it("works", function() assert.equal("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", hmac.sha256(Key, Data, true)) end); end); describe("HMAC-SHA-512", function () it("works", function() assert.equal("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737", hmac.sha512(Key, Data, true)) end); end); end); describe("Test case 3", function () local Key = hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); local Data = hex.decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"); describe("HMAC-SHA-256", function () it("works", function() assert.equal("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", hmac.sha256(Key, Data, true)) end); end); describe("HMAC-SHA-512", function () it("works", function() assert.equal("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb", hmac.sha512(Key, Data, true)) end); end); end); describe("Test case 4", function () local Key = hex.decode("0102030405060708090a0b0c0d0e0f10111213141516171819"); local Data = hex.decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); describe("HMAC-SHA-256", function () it("works", function() assert.equal("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", hmac.sha256(Key, Data, true)) end); end); describe("HMAC-SHA-512", function () it("works", function() assert.equal("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd", hmac.sha512(Key, Data, true)) end); end); end); describe("Test case 5", function () local Key = hex.decode("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"); local Data = hex.decode("546573742057697468205472756e636174696f6e"); describe("HMAC-SHA-256", function () it("works", function() assert.equal("a3b6167473100ee06e0c796c2955552b", hmac.sha256(Key, Data, true):sub(1,128/4)) end); end); describe("HMAC-SHA-512", function () it("works", function() assert.equal("415fad6271580a531d4179bc891d87a6", hmac.sha512(Key, Data, true):sub(1,128/4)) end); end); end); describe("Test case 6", function () local Key = hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); local Data = hex.decode("54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374"); describe("HMAC-SHA-256", function () it("works", function() assert.equal("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", hmac.sha256(Key, Data, true)) end); end); describe("HMAC-SHA-512", function () it("works", function() assert.equal("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598", hmac.sha512(Key, Data, true)) end); end); end); describe("Test case 7", function () local Key = hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); local Data = hex.decode("5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e"); describe("HMAC-SHA-256", function () it("works", function() assert.equal("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", hmac.sha256(Key, Data, true)) end); end); describe("HMAC-SHA-512", function () it("works", function() assert.equal("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58", hmac.sha512(Key, Data, true)) end); end); end); prosody-0.12.4/spec/PaxHeaders/util_http_spec.lua0000644000000000000000000000012014475665104017047 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_http_spec.lua0000644000175000017500000000732314475665104021261 0ustar00prosodyprosody00000000000000 local http = require "util.http"; describe("util.http", function() describe("#urlencode()", function() it("should not change normal characters", function() assert.are.equal(http.urlencode("helloworld123"), "helloworld123"); end); it("should escape spaces", function() assert.are.equal(http.urlencode("hello world"), "hello%20world"); end); it("should escape important URL characters", function() assert.are.equal(http.urlencode("This & that = something"), "This%20%26%20that%20%3d%20something"); end); end); describe("#urldecode()", function() it("should not change normal characters", function() assert.are.equal("helloworld123", http.urldecode("helloworld123"), "Normal characters not escaped"); end); it("should decode spaces", function() assert.are.equal("hello world", http.urldecode("hello%20world"), "Spaces escaped"); end); it("should decode important URL characters", function() assert.are.equal("This & that = something", http.urldecode("This%20%26%20that%20%3d%20something"), "Important URL chars escaped"); end); it("should decode both lower and uppercase", function () assert.are.equal("This & that = {something}.", http.urldecode("This%20%26%20that%20%3D%20%7Bsomething%7D%2E"), "Important URL chars escaped"); end); end); describe("#formencode()", function() it("should encode basic data", function() assert.are.equal(http.formencode({ { name = "one", value = "1"}, { name = "two", value = "2" } }), "one=1&two=2", "Form encoded"); end); it("should encode special characters with escaping", function() assert.are.equal(http.formencode({ { name = "one two", value = "1"}, { name = "two one&", value = "2" } }), "one+two=1&two+one%26=2", "Form encoded"); end); end); describe("#formdecode()", function() it("should decode basic data", function() local t = http.formdecode("one=1&two=2"); assert.are.same(t, { { name = "one", value = "1" }; { name = "two", value = "2" }; one = "1"; two = "2"; }); end); it("should decode special characters", function() local t = http.formdecode("one+two=1&two+one%26=2"); assert.are.same(t, { { name = "one two", value = "1" }; { name = "two one&", value = "2" }; ["one two"] = "1"; ["two one&"] = "2"; }); end); end); describe("normalize_path", function () it("root path is always '/'", function () assert.equal("/", http.normalize_path("/")); assert.equal("/", http.normalize_path("")); assert.equal("/", http.normalize_path("/", true)); assert.equal("/", http.normalize_path("", true)); end); it("works", function () assert.equal("/foo", http.normalize_path("foo")); assert.equal("/foo", http.normalize_path("/foo")); assert.equal("/foo", http.normalize_path("foo/")); assert.equal("/foo", http.normalize_path("/foo/")); end); it("is_dir works", function () assert.equal("/foo/", http.normalize_path("foo", true)); assert.equal("/foo/", http.normalize_path("/foo", true)); assert.equal("/foo/", http.normalize_path("foo/", true)); assert.equal("/foo/", http.normalize_path("/foo/", true)); end); end); describe("contains_token", function () it("is present in field", function () assert.is_true(http.contains_token("foo", "foo")); assert.is_true(http.contains_token("foo, bar", "foo")); assert.is_true(http.contains_token("foo,bar", "foo")); assert.is_true(http.contains_token("bar, foo,baz", "foo")); end); it("is absent from field", function () assert.is_false(http.contains_token("bar", "foo")); assert.is_false(http.contains_token("fooo", "foo")); assert.is_false(http.contains_token("foo o,bar", "foo")); end); it("is weird", function () assert.is_(http.contains_token("fo o", "foo")); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_human_io_spec.lua0000644000000000000000000000012014475665104017667 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_human_io_spec.lua0000644000175000017500000000267014475665104022101 0ustar00prosodyprosody00000000000000describe("util.human.io", function () local human_io setup(function () human_io = require "util.human.io"; end); describe("table", function () it("alignment works", function () local row = human_io.table({ { width = 3, align = "right" }, { width = 3, }, }); assert.equal(" 1 | . ", row({ 1, "." })); assert.equal(" 10 | .. ", row({ 10, ".." })); assert.equal("100 | ...", row({ 100, "..." })); assert.equal("10… | ..…", row({ 1000, "...." })); end); end); describe("ellipsis", function() it("works", function() assert.equal("…", human_io.ellipsis("abc", 1)); assert.equal("a…", human_io.ellipsis("abc", 2)); assert.equal("abc", human_io.ellipsis("abc", 3)); assert.equal("…", human_io.ellipsis("räksmörgås", 1)); assert.equal("r…", human_io.ellipsis("räksmörgås", 2)); assert.equal("rä…", human_io.ellipsis("räksmörgås", 3)); assert.equal("räk…", human_io.ellipsis("räksmörgås", 4)); assert.equal("räks…", human_io.ellipsis("räksmörgås", 5)); assert.equal("räksm…", human_io.ellipsis("räksmörgås", 6)); assert.equal("räksmö…", human_io.ellipsis("räksmörgås", 7)); assert.equal("räksmör…", human_io.ellipsis("räksmörgås", 8)); assert.equal("räksmörg…", human_io.ellipsis("räksmörgås", 9)); assert.equal("räksmörgås", human_io.ellipsis("räksmörgås", 10)); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_human_units_spec.lua0000644000000000000000000000012014475665104020422 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_human_units_spec.lua0000644000175000017500000000107614475665104022633 0ustar00prosodyprosody00000000000000local units = require "util.human.units"; describe("util.human.units", function () describe("format", function () it("formats numbers with SI units", function () assert.equal("1 km", units.format(1000, "m")); assert.equal("1 GJ", units.format(1000000000, "J")); assert.equal("1 ms", units.format(1/1000, "s")); assert.equal("10 ms", units.format(10/1000, "s")); assert.equal("1 ns", units.format(1/1000000000, "s")); assert.equal("1 KiB", units.format(1024, "B", 'b')); assert.equal("1 MiB", units.format(1024*1024, "B", 'b')); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_indexedbheap_spec.lua0000644000000000000000000000012014475665104020510 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_indexedbheap_spec.lua0000644000175000017500000000157514475665104022725 0ustar00prosodyprosody00000000000000local ibh = require"util.indexedbheap"; local function verify_heap_property(priorities) for k in ipairs(priorities) do local parent = priorities[k]; local childA = priorities[2*k]; local childB = priorities[2*k+1]; -- print("-", parent, childA, childB) assert(childA == nil or childA > parent, "heap property violated"); assert(childB == nil or childB > parent, "heap property violated"); end end local h setup(function () h = ibh.create(); end) describe("util.indexedbheap", function () it("item can be moved from end to top", function () verify_heap_property(h); h:insert("a", 1); verify_heap_property(h); h:insert("b", 2); verify_heap_property(h); h:insert("c", 3); verify_heap_property(h); local id = h:insert("*", 10); verify_heap_property(h); h:reprioritize(id, 0); verify_heap_property(h); assert.same({ 0, "*", id }, { h:pop() }); end) end); prosody-0.12.4/spec/PaxHeaders/util_interpolation_spec.lua0000644000000000000000000000012014475665104020757 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_interpolation_spec.lua0000644000175000017500000000345714475665104023175 0ustar00prosodyprosody00000000000000local template = [[ {greet!?Hi}, {name?world}! ]]; local expect1 = [[ Hello, WORLD! ]]; local expect2 = [[ Hello, world! ]]; local expect3 = [[ Hi, YOU! ]]; local template_array = [[ {foo#{idx}. {item} }]] local expect_array = [[ 1. HELLO 2. WORLD ]] local template_func_pipe = [[ {foo|sort#{idx}. {item} }]] local expect_func_pipe = [[ 1. A 2. B 3. C 4. D ]] local template_map = [[ {foo%{idx}: {item!} }]] local expect_map = [[ FOO: bar ]] local template_not = [[ {thing~Thing is falsy}{thing&Thing is truthy} ]] local expect_not_true = [[ Thing is truthy ]] local expect_not_nil = [[ Thing is falsy ]] local expect_not_false = [[ Thing is falsy ]] describe("util.interpolation", function () it("renders", function () local render = require "util.interpolation".new("%b{}", string.upper, { sort = function (t) table.sort(t) return t end }); assert.equal(expect1, render(template, { greet = "Hello", name = "world" })); assert.equal(expect2, render(template, { greet = "Hello" })); assert.equal(expect3, render(template, { name = "you" })); assert.equal(expect_array, render(template_array, { foo = { "Hello", "World" } })); assert.equal(expect_func_pipe, render(template_func_pipe, { foo = { "c", "a", "d", "b", } })); -- assert.equal("", render(template_func_pipe, { foo = nil })); -- FIXME assert.equal(expect_map, render(template_map, { foo = { foo = "bar" } })); assert.equal(expect_not_true, render(template_not, { thing = true })); assert.equal(expect_not_nil, render(template_not, { thing = nil })); assert.equal(expect_not_false, render(template_not, { thing = false })); end); it("fixes #1623", function () local render = require "util.interpolation".new("%b{}", string.upper, { x = string.lower }); assert.equal("", render("{foo?}", { })) assert.equal("", render("{foo|x?}", { })) end); end); prosody-0.12.4/spec/PaxHeaders/util_ip_spec.lua0000644000000000000000000000012014475665104016500 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_ip_spec.lua0000644000175000017500000000743414475665104020715 0ustar00prosodyprosody00000000000000 local ip = require "util.ip"; local new_ip = ip.new_ip; local match = ip.match; local parse_cidr = ip.parse_cidr; local commonPrefixLength = ip.commonPrefixLength; describe("util.ip", function() describe("#match()", function() it("should work", function() local _ = new_ip; local ip = _"10.20.30.40"; assert.are.equal(match(ip, _"10.0.0.0", 8), true); assert.are.equal(match(ip, _"10.0.0.0", 16), false); assert.are.equal(match(ip, _"10.0.0.0", 24), false); assert.are.equal(match(ip, _"10.0.0.0", 32), false); assert.are.equal(match(ip, _"10.20.0.0", 8), true); assert.are.equal(match(ip, _"10.20.0.0", 16), true); assert.are.equal(match(ip, _"10.20.0.0", 24), false); assert.are.equal(match(ip, _"10.20.0.0", 32), false); assert.are.equal(match(ip, _"0.0.0.0", 32), false); assert.are.equal(match(ip, _"0.0.0.0", 0), true); assert.are.equal(match(ip, _"0.0.0.0"), false); assert.are.equal(match(ip, _"10.0.0.0", 255), false, "excessive number of bits"); assert.are.equal(match(ip, _"10.0.0.0", -8), true, "negative number of bits"); assert.are.equal(match(ip, _"10.0.0.0", -32), true, "negative number of bits"); assert.are.equal(match(ip, _"10.0.0.0", 0), true, "zero bits"); assert.are.equal(match(ip, _"10.0.0.0"), false, "no specified number of bits (differing ip)"); assert.are.equal(match(ip, _"10.20.30.40"), true, "no specified number of bits (same ip)"); assert.are.equal(match(_"127.0.0.1", _"127.0.0.1"), true, "simple ip"); assert.are.equal(match(_"8.8.8.8", _"8.8.0.0", 16), true); assert.are.equal(match(_"8.8.4.4", _"8.8.0.0", 16), true); end); end); describe("#parse_cidr()", function() it("should work", function() assert.are.equal(new_ip"0.0.0.0", new_ip"0.0.0.0") local function assert_cidr(cidr, ip, bits) local parsed_ip, parsed_bits = parse_cidr(cidr); assert.are.equal(new_ip(ip), parsed_ip, cidr.." parsed ip is "..ip); assert.are.equal(bits, parsed_bits, cidr.." parsed bits is "..tostring(bits)); end assert_cidr("0.0.0.0", "0.0.0.0", nil); assert_cidr("127.0.0.1", "127.0.0.1", nil); assert_cidr("127.0.0.1/0", "127.0.0.1", 0); assert_cidr("127.0.0.1/8", "127.0.0.1", 8); assert_cidr("127.0.0.1/32", "127.0.0.1", 32); assert_cidr("127.0.0.1/256", "127.0.0.1", 256); assert_cidr("::/48", "::", 48); end); end); describe("#new_ip()", function() it("should work", function() local v4, v6 = "IPv4", "IPv6"; local function assert_proto(s, proto) local ip = new_ip(s); if proto then assert.are.equal(ip and ip.proto, proto, "protocol is correct for "..("%q"):format(s)); else assert.are.equal(ip, nil, "address is invalid"); end end assert_proto("127.0.0.1", v4); assert_proto("::1", v6); assert_proto("", nil); assert_proto("abc", nil); assert_proto(" ", nil); end); end); describe("#commonPrefixLength()", function() it("should work", function() local function assert_cpl6(a, b, len, v4) local ipa, ipb = new_ip(a), new_ip(b); if v4 then len = len+96; end assert.are.equal(commonPrefixLength(ipa, ipb), len, "common prefix length of "..a.." and "..b.." is "..len); assert.are.equal(commonPrefixLength(ipb, ipa), len, "common prefix length of "..b.." and "..a.." is "..len); end local function assert_cpl4(a, b, len) return assert_cpl6(a, b, len, "IPv4"); end assert_cpl4("0.0.0.0", "0.0.0.0", 32); assert_cpl4("255.255.255.255", "0.0.0.0", 0); assert_cpl4("255.255.255.255", "255.255.0.0", 16); assert_cpl4("255.255.255.255", "255.255.255.255", 32); assert_cpl4("255.255.255.255", "255.255.255.255", 32); assert_cpl6("::1", "::1", 128); assert_cpl6("abcd::1", "abcd::1", 128); assert_cpl6("abcd::abcd", "abcd::", 112); assert_cpl6("abcd::abcd", "abcd::abcd:abcd", 96); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_iterators_spec.lua0000644000000000000000000000012014475665104020104 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_iterators_spec.lua0000644000175000017500000000241214475665104022310 0ustar00prosodyprosody00000000000000local iter = require "util.iterators"; describe("util.iterators", function () describe("join", function () it("should produce a joined iterator", function () local expect = { "a", "b", "c", 1, 2, 3 }; local output = {}; for x in iter.join(iter.values({"a", "b", "c"})):append(iter.values({1, 2, 3})) do table.insert(output, x); end assert.same(output, expect); end); end); describe("sorted_pairs", function () it("should produce sorted pairs", function () local orig = { b = 1, c = 2, a = "foo", d = false }; local n, last_key = 0, nil; for k, v in iter.sorted_pairs(orig) do n = n + 1; if last_key then assert(k > last_key, "Expected "..k.." > "..last_key) end assert.equal(orig[k], v); last_key = k; end assert.equal("d", last_key); assert.equal(4, n); end); it("should allow a custom sort function", function () local orig = { b = 1, c = 2, a = "foo", d = false }; local n, last_key = 0, nil; for k, v in iter.sorted_pairs(orig, function (a, b) return a > b end) do n = n + 1; if last_key then assert(k < last_key, "Expected "..k.." > "..last_key) end assert.equal(orig[k], v); last_key = k; end assert.equal("a", last_key); assert.equal(4, n); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_jid_spec.lua0000644000000000000000000000012014475665104016636 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_jid_spec.lua0000644000175000017500000001773214475665104021055 0ustar00prosodyprosody00000000000000 local jid = require "util.jid"; describe("util.jid", function() describe("#join()", function() it("should work", function() assert.are.equal(jid.join("a", "b", "c"), "a@b/c", "builds full JID"); assert.are.equal(jid.join("a", "b", nil), "a@b", "builds bare JID"); assert.are.equal(jid.join(nil, "b", "c"), "b/c", "builds full host JID"); assert.are.equal(jid.join(nil, "b", nil), "b", "builds bare host JID"); assert.are.equal(jid.join(nil, nil, nil), nil, "invalid JID is nil"); assert.are.equal(jid.join("a", nil, nil), nil, "invalid JID is nil"); assert.are.equal(jid.join(nil, nil, "c"), nil, "invalid JID is nil"); assert.are.equal(jid.join("a", nil, "c"), nil, "invalid JID is nil"); end); it("should reject invalid arguments", function () assert.has_error(function () jid.join(false, "bork", nil) end) assert.has_error(function () jid.join(nil, "bork", false) end) assert.has_error(function () jid.join(false, false, false) end) end) end); describe("#split()", function() it("should work", function() local function test(input_jid, expected_node, expected_server, expected_resource) local rnode, rserver, rresource = jid.split(input_jid); assert.are.equal(expected_node, rnode, "split("..tostring(input_jid)..") failed"); assert.are.equal(expected_server, rserver, "split("..tostring(input_jid)..") failed"); assert.are.equal(expected_resource, rresource, "split("..tostring(input_jid)..") failed"); end -- Valid JIDs test("node@server", "node", "server", nil ); test("node@server/resource", "node", "server", "resource" ); test("server", nil, "server", nil ); test("server/resource", nil, "server", "resource" ); test("server/resource@foo", nil, "server", "resource@foo" ); test("server/resource@foo/bar", nil, "server", "resource@foo/bar"); -- Always invalid JIDs test(nil, nil, nil, nil); test("node@/server", nil, nil, nil); test("@server", nil, nil, nil); test("@server/resource", nil, nil, nil); test("@/resource", nil, nil, nil); end); it("should reject invalid arguments", function () assert.has_error(function () jid.split(false) end) end) end); describe("#bare()", function() it("should work", function() assert.are.equal(jid.bare("user@host"), "user@host", "bare JID remains bare"); assert.are.equal(jid.bare("host"), "host", "Host JID remains host"); assert.are.equal(jid.bare("host/resource"), "host", "Host JID with resource becomes host"); assert.are.equal(jid.bare("user@host/resource"), "user@host", "user@host JID with resource becomes user@host"); assert.are.equal(jid.bare("user@/resource"), nil, "invalid JID is nil"); assert.are.equal(jid.bare("@/resource"), nil, "invalid JID is nil"); assert.are.equal(jid.bare("@/"), nil, "invalid JID is nil"); assert.are.equal(jid.bare("/"), nil, "invalid JID is nil"); assert.are.equal(jid.bare(""), nil, "invalid JID is nil"); assert.are.equal(jid.bare("@"), nil, "invalid JID is nil"); assert.are.equal(jid.bare("user@"), nil, "invalid JID is nil"); assert.are.equal(jid.bare("user@@"), nil, "invalid JID is nil"); assert.are.equal(jid.bare("user@@host"), nil, "invalid JID is nil"); assert.are.equal(jid.bare("user@@host/resource"), nil, "invalid JID is nil"); assert.are.equal(jid.bare("user@host/"), nil, "invalid JID is nil"); end); it("should reject invalid arguments", function () assert.has_error(function () jid.bare(false) end) end) end); describe("#compare()", function() it("should work", function() assert.are.equal(jid.compare("host", "host"), true, "host should match"); assert.are.equal(jid.compare("host", "other-host"), false, "host should not match"); assert.are.equal(jid.compare("other-user@host/resource", "host"), true, "host should match"); assert.are.equal(jid.compare("other-user@host", "user@host"), false, "user should not match"); assert.are.equal(jid.compare("user@host", "host"), true, "host should match"); assert.are.equal(jid.compare("user@host/resource", "host"), true, "host should match"); assert.are.equal(jid.compare("user@host/resource", "user@host"), true, "user and host should match"); assert.are.equal(jid.compare("user@other-host", "host"), false, "host should not match"); assert.are.equal(jid.compare("user@other-host", "user@host"), false, "host should not match"); end); end); local jid_escaping_test_vectors = { -- From https://xmpp.org/extensions/xep-0106.xml#examples sans @example.com [[space cadet]], [[space\20cadet]], [[call me "ishmael"]], [[call\20me\20\22ishmael\22]], [[at&t guy]], [[at\26t\20guy]], [[d'artagnan]], [[d\27artagnan]], [[/.fanboy]], [[\2f.fanboy]], [[::foo::]], [[\3a\3afoo\3a\3a]], [[]], [[\3cfoo\3e]], [[user@host]], [[user\40host]], [[c:\net]], [[c\3a\net]], [[c:\\net]], [[c\3a\\net]], [[c:\cool stuff]], [[c\3a\cool\20stuff]], [[c:\5commas]], [[c\3a\5c5commas]], -- Section 4.2 [[\3and\2is\5cool]], [[\5c3and\2is\5c5cool]], -- From aioxmpp [[\5c]], [[\5c5c]], -- [[\5C]], [[\5C]], [[\2plus\2is\4]], [[\2plus\2is\4]], [[foo\bar]], [[foo\bar]], [[foo\41r]], [[foo\41r]], -- additional test vectors [[call\20me]], [[call\5c20me]], }; describe("#escape()", function () it("should work", function () for i = 1, #jid_escaping_test_vectors, 2 do local original = jid_escaping_test_vectors[i]; local escaped = jid_escaping_test_vectors[i+1]; assert.are.equal(escaped, jid.escape(original), ("Escapes '%s' -> '%s'"):format(original, escaped)); end end); end) describe("#unescape()", function () it("should work", function () for i = 1, #jid_escaping_test_vectors, 2 do local original = jid_escaping_test_vectors[i]; local escaped = jid_escaping_test_vectors[i+1]; assert.are.equal(original, jid.unescape(escaped), ("Unescapes '%s' -> '%s'"):format(escaped, original)); end end); end) it("should work with nodes", function() local function test(_jid, expected_node) assert.are.equal(jid.node(_jid), expected_node, "Unexpected node for "..tostring(_jid)); end test("example.com", nil); test("foo.example.com", nil); test("foo.example.com/resource", nil); test("foo.example.com/some resource", nil); test("foo.example.com/some@resource", nil); test("foo@foo.example.com/some@resource", "foo"); test("foo@example/some@resource", "foo"); test("foo@example/@resource", "foo"); test("foo@example@resource", nil); test("foo@example", "foo"); test("foo", nil); test(nil, nil); end); it("should work with hosts", function() local function test(_jid, expected_host) assert.are.equal(jid.host(_jid), expected_host, "Unexpected host for "..tostring(_jid)); end test("example.com", "example.com"); test("foo.example.com", "foo.example.com"); test("foo.example.com/resource", "foo.example.com"); test("foo.example.com/some resource", "foo.example.com"); test("foo.example.com/some@resource", "foo.example.com"); test("foo@foo.example.com/some@resource", "foo.example.com"); test("foo@example/some@resource", "example"); test("foo@example/@resource", "example"); test("foo@example@resource", nil); test("foo@example", "example"); test("foo", "foo"); test(nil, nil); end); it("should work with resources", function() local function test(_jid, expected_resource) assert.are.equal(jid.resource(_jid), expected_resource, "Unexpected resource for "..tostring(_jid)); end test("example.com", nil); test("foo.example.com", nil); test("foo.example.com/resource", "resource"); test("foo.example.com/some resource", "some resource"); test("foo.example.com/some@resource", "some@resource"); test("foo@foo.example.com/some@resource", "some@resource"); test("foo@example/some@resource", "some@resource"); test("foo@example/@resource", "@resource"); test("foo@example@resource", nil); test("foo@example", nil); test("foo", nil); test("/foo", nil); test("@x/foo", nil); test("@/foo", nil); test(nil, nil); end); end); prosody-0.12.4/spec/PaxHeaders/util_json_spec.lua0000644000000000000000000000012014475665104017041 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.728764599 prosody-0.12.4/spec/util_json_spec.lua0000644000175000017500000000420214475665104021244 0ustar00prosodyprosody00000000000000 local json = require "util.json"; local array = require "util.array"; describe("util.json", function() describe("#encode()", function() it("should work", function() local function test(f, j, e) if e then assert.are.equal(f(j), e); end assert.are.equal(f(j), f(json.decode(f(j)))); end test(json.encode, json.null, "null") test(json.encode, {}, "{}") test(json.encode, {a=1}); test(json.encode, {a={1,2,3}}); test(json.encode, {1}, "[1]"); end); end); describe("#decode()", function() it("should work", function() local empty_array = json.decode("[]"); assert.are.equal(type(empty_array), "table"); assert.are.equal(#empty_array, 0); assert.are.equal(next(empty_array), nil); end); end); describe("testcases", function() local valid_data = {}; local invalid_data = {}; local skip = "fail1.json fail9.json fail18.json fail15.json fail13.json fail25.json fail26.json fail27.json fail28.json fail17.json pass1.json"; setup(function() local lfs = require "lfs"; local path = "spec/json"; for name in lfs.dir(path) do if name:match("%.json$") then local f = assert(io.open(path.."/"..name)); local content = assert(f:read("*a")); assert(f:close()); if skip:find(name) then --luacheck: ignore 542 -- Skip elseif name:match("^pass") then valid_data[name] = content; elseif name:match("^fail") then invalid_data[name] = content; end end end end) it("should pass valid testcases", function() for name, content in pairs(valid_data) do local parsed, err = json.decode(content); assert(parsed, name..": "..tostring(err)); end end); it("should fail invalid testcases", function() for name, content in pairs(invalid_data) do local parsed, err = json.decode(content); assert(not parsed, name..": "..tostring(err)); end end); end) describe("util.array integration", function () it("works", function () assert.equal("[]", json.encode(array())); assert.equal("[1,2,3]", json.encode(array({1,2,3}))); assert.equal(getmetatable(array()), getmetatable(json.decode("[]"))); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_jsonpointer_spec.lua0000644000000000000000000000011714475665104020450 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_jsonpointer_spec.lua0000644000175000017500000000203214475665104022644 0ustar00prosodyprosody00000000000000describe("util.jsonpointer", function() local json, jp; setup(function() json = require "util.json"; jp = require "util.jsonpointer"; end) describe("resolve()", function() local example; setup(function() example = json.decode([[{ "foo": ["bar", "baz"], "": 0, "a/b": 1, "c%d": 2, "e^f": 3, "g|h": 4, "i\\j": 5, "k\"l": 6, " ": 7, "m~n": 8 }]]) end) it("works", function() assert.same(example, jp.resolve(example, "")); assert.same({ "bar", "baz" }, jp.resolve(example, "/foo")); assert.same("bar", jp.resolve(example, "/foo/0")); assert.same(0, jp.resolve(example, "/")); assert.same(1, jp.resolve(example, "/a~1b")); assert.same(2, jp.resolve(example, "/c%d")); assert.same(3, jp.resolve(example, "/e^f")); assert.same(4, jp.resolve(example, "/g|h")); assert.same(5, jp.resolve(example, "/i\\j")); assert.same(6, jp.resolve(example, "/k\"l")); assert.same(7, jp.resolve(example, "/ ")); assert.same(8, jp.resolve(example, "/m~0n")); end) end) end) prosody-0.12.4/spec/PaxHeaders/util_jsonschema_spec.lua0000644000000000000000000000011714475665104020230 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_jsonschema_spec.lua0000644000175000017500000001005514475665104022430 0ustar00prosodyprosody00000000000000local js = require "util.jsonschema"; local json = require "util.json"; local lfs = require "lfs"; -- https://github.com/json-schema-org/JSON-Schema-Test-Suite.git 2.0.0-550-g88d6948 local test_suite_dir = "spec/JSON-Schema-Test-Suite/tests/draft2020-12" if lfs.attributes(test_suite_dir, "mode") ~= "directory" then return end -- Tests to skip and short reason why (NYI = not yet implemented) local skip = { ["additionalProperties.json:0:2"] = "distinguishing objects from arrays", ["additionalProperties.json:0:5"] = "NYI", ["additionalProperties.json:1:0"] = "NYI", ["anchor.json"] = "$anchor NYI", ["const.json:1"] = "deepcompare", ["const.json:13:2"] = "IEEE 754 equality", ["const.json:2"] = "deepcompare", ["const.json:8"] = "deepcompare", ["const.json:9"] = "deepcompare", ["contains.json:0:5"] = "distinguishing objects from arrays", ["defs.json"] = "need built-in meta-schema", ["dependentRequired.json"] = "NYI", ["dependentSchemas.json"] = "NYI", ["dynamicRef.json"] = "NYI", ["enum.json:1:3"] = "deepcompare", ["id.json"] = "NYI", ["maxContains.json"] = "NYI", ["maxLength.json:0:4"] = "UTF-16", ["maxProperties.json"] = "NYI", ["minContains.json"] = "NYI", ["minLength.json:0:4"] = "UTF-16", ["minProperties.json"] = "NYI", ["multipleOf.json:1"] = "multiples of IEEE 754 fractions", ["multipleOf.json:2"] = "multiples of IEEE 754 fractions", ["pattern.json"] = "NYI", ["patternProperties.json"] = "NYI", ["properties.json:1:2"] = "NYI", ["properties.json:1:3"] = "NYI", ["ref.json:0:3"] = "NYI additionalProperties", ["ref.json:11"] = "NYI", ["ref.json:12:1"] = "FIXME", ["ref.json:13"] = "NYI", ["ref.json:14"] = "NYI", ["ref.json:15"] = "NYI", ["ref.json:16"] = "NYI", ["ref.json:17"] = "NYI", ["ref.json:18"] = "NYI", ["ref.json:19"] = "NYI", ["ref.json:26"] = "NYI", ["ref.json:27"] = "NYI", ["ref.json:28"] = "NYI", ["ref.json:3:2"] = "FIXME investigate, util.jsonpath issue?", ["required.json:4"] = "JavaScript specific and distinguishing objects from arrays", ["ref.json:6:1"] = "NYI", ["ref.json:20"] = "NYI", ["ref.json:25"] = "NYI", ["refRemote.json"] = "DEFINITELY NYI", ["required.json:0:2"] = "distinguishing objects from arrays", ["type.json:3:4"] = "distinguishing objects from arrays", ["type.json:3:6"] = "null is weird", ["type.json:4:3"] = "distinguishing objects from arrays", ["type.json:4:6"] = "null is weird", ["type.json:9:4"] = "null is weird", ["type.json:9:6"] = "null is weird", ["unevaluatedItems.json"] = "NYI", ["unevaluatedProperties.json"] = "NYI", ["uniqueItems.json:0:11"] = "deepcompare", ["uniqueItems.json:0:13"] = "deepcompare", ["uniqueItems.json:0:14"] = "deepcompare", ["uniqueItems.json:0:22"] = "deepcompare", ["uniqueItems.json:0:24"] = "deepcompare", ["uniqueItems.json:0:9"] = "deepcompare", ["unknownKeyword.json"] = "NYI", ["vocabulary.json"] = "NYI", }; local function label(s, i) return string.format("%s:%d", s, i-1); end describe("util.jsonschema.validate", function() for test_case_file in lfs.dir(test_suite_dir) do -- print(skip[test_case_file] and "do " or "skip", test_case_file) if test_case_file:sub(-5) == ".json" and not skip[test_case_file] then describe(test_case_file, function() local test_cases; setup(function() local f = assert(io.open(test_suite_dir .. "/" .. test_case_file)); local rawdata = assert(f:read("*a"), "failed to read " .. test_case_file) test_cases = assert(json.decode(rawdata), "failed to parse " .. test_case_file) end) describe("tests", function() for i, schema_test in ipairs(test_cases) do local generic_label = label(test_case_file, i); describe(schema_test.description or generic_label, function() for j, test in ipairs(schema_test.tests) do local specific_label = label(generic_label, j); ((skip[generic_label] or skip[specific_label]) and pending or it)(test.description, function() assert.equal(test.valid, js.validate(schema_test.schema, test.data), specific_label .. " " .. test.description); end) end end) end end) end) end end end); prosody-0.12.4/spec/PaxHeaders/util_jwt_spec.lua0000644000000000000000000000011714475665104016702 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_jwt_spec.lua0000644000175000017500000000101314475665104021074 0ustar00prosodyprosody00000000000000local jwt = require "util.jwt"; describe("util.jwt", function () it("validates", function () local key = "secret"; local token = jwt.sign(key, { payload = "this" }); assert.string(token); local ok, parsed = jwt.verify(key, token); assert.truthy(ok) assert.same({ payload = "this" }, parsed); end); it("rejects invalid", function () local key = "secret"; local token = jwt.sign("wrong", { payload = "this" }); assert.string(token); local ok = jwt.verify(key, token); assert.falsy(ok) end); end); prosody-0.12.4/spec/PaxHeaders/util_multitable_spec.lua0000644000000000000000000000011714475665104020240 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_multitable_spec.lua0000644000175000017500000000341314475665104022440 0ustar00prosodyprosody00000000000000 local multitable = require "util.multitable"; describe("util.multitable", function() describe("#new()", function() it("should create a multitable", function() local mt = multitable.new(); assert.is_table(mt, "Multitable is a table"); assert.is_function(mt.add, "Multitable has method add"); assert.is_function(mt.get, "Multitable has method get"); assert.is_function(mt.remove, "Multitable has method remove"); end); end); describe("#get()", function() it("should allow getting correctly", function() local function has_items(list, ...) local should_have = {}; if select('#', ...) > 0 then assert.is_table(list, "has_items: list is table", 3); else assert.is.falsy(list and #list > 0, "No items, and no list"); return true, "has-all"; end for n=1,select('#', ...) do should_have[select(n, ...)] = true; end for _, item in ipairs(list) do if not should_have[item] then return false, "too-many"; end should_have[item] = nil; end if next(should_have) then return false, "not-enough"; end return true, "has-all"; end local function assert_has_all(message, list, ...) return assert.are.equal(select(2, has_items(list, ...)), "has-all", message or "List has all expected items, and no more", 2); end local mt = multitable.new(); local trigger1, trigger2, trigger3 = {}, {}, {}; local item1, item2, item3 = {}, {}, {}; assert_has_all("Has no items with trigger1", mt:get(trigger1)); mt:add(1, 2, 3, item1); assert_has_all("Has item1 for 1, 2, 3", mt:get(1, 2, 3), item1); end); end); -- Doesn't support nil --[[ mt:add(nil, item1); mt:add(nil, item2); mt:add(nil, item3); assert_has_all("Has all items with (nil)", mt:get(nil), item1, item2, item3); ]] end); prosody-0.12.4/spec/PaxHeaders/util_paths_spec.lua0000644000000000000000000000011714475665104017215 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_paths_spec.lua0000644000175000017500000000242114475665104021413 0ustar00prosodyprosody00000000000000local sep = package.config:match("(.)\n"); describe("util.paths", function () local paths = require "util.paths"; describe("#join()", function () it("returns single component as-is", function () assert.equal("foo", paths.join("foo")); end); it("joins paths", function () assert.equal("foo"..sep.."bar", paths.join("foo", "bar")) end); it("joins longer paths", function () assert.equal("foo"..sep.."bar"..sep.."baz", paths.join("foo", "bar", "baz")) end); it("joins even longer paths", function () assert.equal("foo"..sep.."bar"..sep.."baz"..sep.."moo", paths.join("foo", "bar", "baz", "moo")) end); end) describe("#glob_to_pattern()", function () it("works", function () assert.equal("^thing.%..*$", paths.glob_to_pattern("thing?.*")) end); end) describe("#resolve_relative_path()", function () it("returns absolute paths as-is", function () if sep == "/" then assert.equal("/tmp/path", paths.resolve_relative_path("/run", "/tmp/path")); elseif sep == "\\" then assert.equal("C:\\Program Files", paths.resolve_relative_path("A:\\", "C:\\Program Files")); end end); it("resolves relative paths", function () if sep == "/" then assert.equal("/run/path", paths.resolve_relative_path("/run", "path")); end end); end) end) prosody-0.12.4/spec/PaxHeaders/util_poll_spec.lua0000644000000000000000000000011714475665104017044 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_poll_spec.lua0000644000175000017500000000013714475665104021244 0ustar00prosodyprosody00000000000000describe("util.poll", function () it("loads", function () require "util.poll" end); end); prosody-0.12.4/spec/PaxHeaders/util_promise_spec.lua0000644000000000000000000000011714475665104017554 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_promise_spec.lua0000644000175000017500000004650214475665104021762 0ustar00prosodyprosody00000000000000local promise = require "util.promise"; describe("util.promise", function () --luacheck: ignore 212/resolve 212/reject describe("new()", function () it("returns a promise object", function () assert(promise.new()); end); end); it("notifies immediately for fulfilled promises", function () local p = promise.new(function (resolve) resolve("foo"); end); local cb = spy.new(function (v) assert.equal("foo", v); end); p:next(cb); assert.spy(cb).was_called(1); end); it("notifies on fulfillment of pending promises", function () local r; local p = promise.new(function (resolve) r = resolve; end); local cb = spy.new(function (v) assert.equal("foo", v); end); p:next(cb); assert.spy(cb).was_called(0); r("foo"); assert.spy(cb).was_called(1); end); it("allows chaining :next() calls", function () local r; local result; local p = promise.new(function (resolve) r = resolve; end); local cb1 = spy.new(function (v) assert.equal("foo", v); return "bar"; end); local cb2 = spy.new(function (v) assert.equal("bar", v); result = v; end); p:next(cb1):next(cb2); assert.spy(cb1).was_called(0); assert.spy(cb2).was_called(0); r("foo"); assert.spy(cb1).was_called(1); assert.spy(cb2).was_called(1); assert.equal("bar", result); end); it("supports multiple :next() calls on the same promise", function () local r; local result; local p = promise.new(function (resolve) r = resolve; end); local cb1 = spy.new(function (v) assert.equal("foo", v); result = v; end); local cb2 = spy.new(function (v) assert.equal("foo", v); result = v; end); p:next(cb1); p:next(cb2); assert.spy(cb1).was_called(0); assert.spy(cb2).was_called(0); r("foo"); assert.spy(cb1).was_called(1); assert.spy(cb2).was_called(1); assert.equal("foo", result); end); it("automatically rejects on error", function () local r; local p = promise.new(function (resolve) r = resolve; error("oh no"); end); local cb = spy.new(function () end); local err_cb = spy.new(function (v) assert.equal("oh no", v); end); p:next(cb, err_cb); assert.spy(cb).was_called(0); assert.spy(err_cb).was_called(1); r("foo"); assert.spy(cb).was_called(0); assert.spy(err_cb).was_called(1); end); it("supports reject()", function () local r, result; local p = promise.new(function (resolve, reject) r = reject; end); local cb = spy.new(function () end); local err_cb = spy.new(function (v) result = v; assert.equal("oh doh", v); end); p:next(cb, err_cb); assert.spy(cb).was_called(0); assert.spy(err_cb).was_called(0); r("oh doh"); assert.spy(cb).was_called(0); assert.spy(err_cb).was_called(1); assert.equal("oh doh", result); end); it("supports chaining of rejected promises", function () local r, result; local p = promise.new(function (resolve, reject) r = reject; end); local cb = spy.new(function () end); local err_cb = spy.new(function (v) result = v; assert.equal("oh doh", v); return "ok" end); local cb2 = spy.new(function (v) result = v; end); local err_cb2 = spy.new(function () end); p:next(cb, err_cb):next(cb2, err_cb2) assert.spy(cb).was_called(0); assert.spy(err_cb).was_called(0); assert.spy(cb2).was_called(0); assert.spy(err_cb2).was_called(0); r("oh doh"); assert.spy(cb).was_called(0); assert.spy(err_cb).was_called(1); assert.spy(cb2).was_called(1); assert.spy(err_cb2).was_called(0); assert.equal("ok", result); end); it("propagates errors down the chain, even when some handlers are not provided", function () local r, result; local test_error = {}; local p = promise.new(function (resolve, reject) r = reject; end); local cb = spy.new(function () end); local err_cb = spy.new(function (e) result = e end); local p2 = p:next(function () error(test_error) end); local p3 = p2:next(cb) p3:catch(err_cb); assert.spy(cb).was_called(0); assert.spy(err_cb).was_called(0); r("oh doh"); assert.spy(cb).was_called(0); assert.spy(err_cb).was_called(1); assert.spy(err_cb).was_called_with("oh doh"); assert.equal("oh doh", result); end); it("propagates values down the chain, even when some handlers are not provided", function () local r; local p = promise.new(function (resolve, reject) r = resolve; end); local cb = spy.new(function () end); local err_cb = spy.new(function () end); local p2 = p:next(function (v) return v; end); local p3 = p2:catch(err_cb) p3:next(cb); assert.spy(cb).was_called(0); assert.spy(err_cb).was_called(0); r(1337); assert.spy(cb).was_called(1); assert.spy(cb).was_called_with(1337); assert.spy(err_cb).was_called(0); end); it("fulfilled promises do not call error handlers and do propagate value", function () local p = promise.resolve("foo"); local cb = spy.new(function () end); local p2 = p:catch(cb); assert.spy(cb).was_called(0); local cb2 = spy.new(function () end); p2:catch(cb2); assert.spy(cb2).was_called(0); end); it("rejected promises do not call fulfilled handlers and do propagate reason", function () local p = promise.reject("foo"); local cb = spy.new(function () end); local p2 = p:next(cb); assert.spy(cb).was_called(0); local cb2 = spy.new(function () end); local cb2_err = spy.new(function () end); p2:next(cb2, cb2_err); assert.spy(cb2).was_called(0); assert.spy(cb2_err).was_called(1); assert.spy(cb2_err).was_called_with("foo"); end); describe("allows callbacks to return", function () it("pending promises", function () local r; local p = promise.resolve() local cb = spy.new(function () return promise.new(function (resolve) r = resolve; end); end); local cb2 = spy.new(function () end); p:next(cb):next(cb2); assert.spy(cb).was_called(1); assert.spy(cb2).was_called(0); r("hello"); assert.spy(cb).was_called(1); assert.spy(cb2).was_called(1); assert.spy(cb2).was_called_with("hello"); end); it("resolved promises", function () local p = promise.resolve() local cb = spy.new(function () return promise.resolve("hello"); end); local cb2 = spy.new(function () end); p:next(cb):next(cb2); assert.spy(cb).was_called(1); assert.spy(cb2).was_called(1); assert.spy(cb2).was_called_with("hello"); end); it("rejected promises", function () local p = promise.resolve() local cb = spy.new(function () return promise.reject("hello"); end); local cb2 = spy.new(function () return promise.reject("goodbye"); end); local cb3 = spy.new(function () end); p:next(cb):catch(cb2):catch(cb3); assert.spy(cb).was_called(1); assert.spy(cb2).was_called(1); assert.spy(cb2).was_called_with("hello"); assert.spy(cb3).was_called(1); assert.spy(cb3).was_called_with("goodbye"); end); it("ordinary values", function () local p = promise.resolve() local cb = spy.new(function () return "hello" end); local cb2 = spy.new(function () end); p:next(cb):next(cb2); assert.spy(cb).was_called(1); assert.spy(cb2).was_called(1); assert.spy(cb2).was_called_with("hello"); end); it("nil", function () local p = promise.resolve() local cb = spy.new(function () return end); local cb2 = spy.new(function () end); p:next(cb):next(cb2); assert.spy(cb).was_called(1); assert.spy(cb2).was_called(1); assert.spy(cb2).was_called_with(nil); end); end); describe("race()", function () it("works with fulfilled promises", function () local p1, p2 = promise.resolve("yep"), promise.resolve("nope"); local p = promise.race({ p1, p2 }); local result; p:next(function (v) result = v; end); assert.equal("yep", result); end); it("works with pending promises", function () local r1, r2; local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (resolve) r2 = resolve end); local p = promise.race({ p1, p2 }); local result; local cb = spy.new(function (v) result = v; end); p:next(cb); assert.spy(cb).was_called(0); r2("yep"); r1("nope"); assert.spy(cb).was_called(1); assert.equal("yep", result); end); end); describe("all()", function () it("works with fulfilled promises", function () local p1, p2 = promise.resolve("yep"), promise.resolve("nope"); local p = promise.all({ p1, p2 }); local result; p:next(function (v) result = v; end); assert.same({ "yep", "nope" }, result); end); it("works with pending promises", function () local r1, r2; local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (resolve) r2 = resolve end); local p = promise.all({ p1, p2 }); local result; local cb = spy.new(function (v) result = v; end); p:next(cb); assert.spy(cb).was_called(0); r2("yep"); assert.spy(cb).was_called(0); r1("nope"); assert.spy(cb).was_called(1); assert.same({ "nope", "yep" }, result); end); it("rejects if any promise rejects", function () local r1, r2; local p1 = promise.new(function (resolve, reject) r1 = reject end); local p2 = promise.new(function (resolve, reject) r2 = reject end); local p = promise.all({ p1, p2 }); local result; local cb = spy.new(function (v) result = v; end); local cb_err = spy.new(function (v) result = v; end); p:next(cb, cb_err); assert.spy(cb).was_called(0); assert.spy(cb_err).was_called(0); r2("fail"); assert.spy(cb).was_called(0); assert.spy(cb_err).was_called(1); r1("nope"); assert.spy(cb).was_called(0); assert.spy(cb_err).was_called(1); assert.equal("fail", result); end); it("works with non-numeric keys", function () local r1, r2; local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (resolve) r2 = resolve end); local p = promise.all({ [true] = p1, [false] = p2 }); local result; local cb = spy.new(function (v) result = v; end); p:next(cb); assert.spy(cb).was_called(0); r2("yep"); assert.spy(cb).was_called(0); r1("nope"); assert.spy(cb).was_called(1); assert.same({ [true] = "nope", [false] = "yep" }, result); end); it("passes through non-promise values", function () local r1; local p1 = promise.new(function (resolve) r1 = resolve end); local p = promise.all({ [true] = p1, [false] = "yep" }); local result; local cb = spy.new(function (v) result = v; end); p:next(cb); assert.spy(cb).was_called(0); r1("nope"); assert.spy(cb).was_called(1); assert.same({ [true] = "nope", [false] = "yep" }, result); end); end); describe("all_settled()", function () it("works with fulfilled promises", function () local p1, p2 = promise.resolve("yep"), promise.resolve("nope"); local p = promise.all_settled({ p1, p2 }); local result; p:next(function (v) result = v; end); assert.same({ { status = "fulfilled", value = "yep" }; { status = "fulfilled", value = "nope" }; }, result); end); it("works with pending promises", function () local r1, r2; local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (resolve) r2 = resolve end); local p = promise.all_settled({ p1, p2 }); local result; local cb = spy.new(function (v) result = v; end); p:next(cb); assert.spy(cb).was_called(0); r2("yep"); assert.spy(cb).was_called(0); r1("nope"); assert.spy(cb).was_called(1); assert.same({ { status = "fulfilled", value = "nope" }; { status = "fulfilled", value = "yep" }; }, result); end); it("works when some promises reject", function () local r1, r2; local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (_, reject) r2 = reject end); local p = promise.all_settled({ p1, p2 }); local result; local cb = spy.new(function (v) result = v; end); p:next(cb); assert.spy(cb).was_called(0); r2("this fails"); assert.spy(cb).was_called(0); r1("this succeeds"); assert.spy(cb).was_called(1); assert.same({ { status = "fulfilled", value = "this succeeds" }; { status = "rejected", reason = "this fails" }; }, result); end); it("works with non-numeric keys", function () local r1, r2; local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (resolve) r2 = resolve end); local p = promise.all_settled({ foo = p1, bar = p2 }); local result; local cb = spy.new(function (v) result = v; end); p:next(cb); assert.spy(cb).was_called(0); r2("yep"); assert.spy(cb).was_called(0); r1("nope"); assert.spy(cb).was_called(1); assert.same({ foo = { status = "fulfilled", value = "nope" }; bar = { status = "fulfilled", value = "yep" }; }, result); end); it("passes through non-promise values", function () local r1; local p1 = promise.new(function (resolve) r1 = resolve end); local p = promise.all_settled({ foo = p1, bar = "yep" }); local result; local cb = spy.new(function (v) result = v; end); p:next(cb); assert.spy(cb).was_called(0); r1("nope"); assert.spy(cb).was_called(1); assert.same({ foo = { status = "fulfilled", value = "nope" }; bar = "yep"; }, result); end); end); describe("catch()", function () it("works", function () local result; local p = promise.new(function (resolve) error({ foo = true }); end); local cb1 = spy.new(function (v) result = v; end); assert.spy(cb1).was_called(0); p:catch(cb1); assert.spy(cb1).was_called(1); assert.same({ foo = true }, result); end); end); describe("join()", function () it("works", function () local r1, r2; local res1, res2; local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (resolve) r2 = resolve end); local p = promise.join(function (_res1, _res2) res1, res2 = _res1, _res2; return promise.resolve("works"); end, p1, p2); local result; local cb = spy.new(function (v) result = v; end); p:next(cb); assert.spy(cb).was_called(0); r2("yep"); assert.spy(cb).was_called(0); r1("nope"); assert.spy(cb).was_called(1); assert.same("works", result); assert.equals("nope", res1); assert.equals("yep", res2); end); end); it("promises may be resolved by other promises", function () local r1, r2; local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (resolve) r2 = resolve end); local result; local cb = spy.new(function (v) result = v; end); p1:next(cb); assert.spy(cb).was_called(0); r1(p2); assert.spy(cb).was_called(0); r2("yep"); assert.spy(cb).was_called(1); assert.equal("yep", result); end); describe("reject()", function () it("returns a rejected promise", function () local p = promise.reject("foo"); local cb = spy.new(function () end); p:catch(cb); assert.spy(cb).was_called(1); assert.spy(cb).was_called_with("foo"); end); it("returns a rejected promise and does not call on_fulfilled", function () local p = promise.reject("foo"); local cb = spy.new(function () end); p:next(cb); assert.spy(cb).was_called(0); end); end); describe("finally()", function () local p, p2, resolve, reject, on_finally; before_each(function () p = promise.new(function (_resolve, _reject) resolve, reject = _resolve, _reject; end); on_finally = spy.new(function () end); p2 = p:finally(on_finally); end); it("runs when a promise is resolved", function () assert.spy(on_finally).was_called(0); resolve("foo"); assert.spy(on_finally).was_called(1); assert.spy(on_finally).was_not_called_with("foo"); end); it("runs when a promise is rejected", function () assert.spy(on_finally).was_called(0); reject("foo"); assert.spy(on_finally).was_called(1); assert.spy(on_finally).was_not_called_with("foo"); end); it("returns a promise that fulfills with the original value", function () local cb2 = spy.new(function () end); p2:next(cb2); assert.spy(on_finally).was_called(0); assert.spy(cb2).was_called(0); resolve("foo"); assert.spy(on_finally).was_called(1); assert.spy(cb2).was_called(1); assert.spy(on_finally).was_not_called_with("foo"); assert.spy(cb2).was_called_with("foo"); end); it("returns a promise that rejects with the original error", function () local on_finally_err = spy.new(function () end); local on_finally_ok = spy.new(function () end); p2:catch(on_finally_err); p2:next(on_finally_ok); assert.spy(on_finally).was_called(0); assert.spy(on_finally_err).was_called(0); reject("foo"); assert.spy(on_finally).was_called(1); -- Since the original promise was rejected, the finally promise should also be assert.spy(on_finally_ok).was_called(0); assert.spy(on_finally_err).was_called(1); assert.spy(on_finally).was_not_called_with("foo"); assert.spy(on_finally_err).was_called_with("foo"); end); it("returns a promise that rejects with an uncaught error inside on_finally", function () p = promise.new(function (_resolve, _reject) resolve, reject = _resolve, _reject; end); local test_error = {}; on_finally = spy.new(function () error(test_error) end); p2 = p:finally(on_finally); local on_finally_err = spy.new(function () end); p2:catch(on_finally_err); assert.spy(on_finally).was_called(0); assert.spy(on_finally_err).was_called(0); reject("foo"); assert.spy(on_finally).was_called(1); assert.spy(on_finally_err).was_called(1); assert.spy(on_finally).was_not_called_with("foo"); assert.spy(on_finally).was_not_called_with(test_error); assert.spy(on_finally_err).was_called_with(test_error); end); end); describe("try()", function () it("works with functions that return a promise", function () local resolve; local p = promise.try(function () return promise.new(function (_resolve) resolve = _resolve; end); end); assert.is_function(resolve); local on_resolved = spy.new(function () end); p:next(on_resolved); assert.spy(on_resolved).was_not_called(); resolve("foo"); assert.spy(on_resolved).was_called_with("foo"); end); it("works with functions that return a value", function () local p = promise.try(function () return "foo"; end); local on_resolved = spy.new(function () end); p:next(on_resolved); assert.spy(on_resolved).was_called_with("foo"); end); it("works with functions that return a promise that rejects", function () local reject; local p = promise.try(function () return promise.new(function (_, _reject) reject = _reject; end); end); assert.is_function(reject); local on_rejected = spy.new(function () end); p:catch(on_rejected); assert.spy(on_rejected).was_not_called(); reject("foo"); assert.spy(on_rejected).was_called_with("foo"); end); it("works with functions that throw errors", function () local test_error = {}; local p = promise.try(function () error(test_error); end); local on_rejected = spy.new(function () end); p:catch(on_rejected); assert.spy(on_rejected).was_called(1); assert.spy(on_rejected).was_called_with(test_error); end); end); describe("set_nexttick()", function () it("works", function () local next_tick = spy.new(function (f) f(); end) local cb = spy.new(function () end); promise.set_nexttick(next_tick); promise.new(function (y, _) y("okay"); end):next(cb); assert.spy(next_tick).was.called(); assert.spy(cb).was.called_with("okay"); end); end) end); prosody-0.12.4/spec/PaxHeaders/util_pubsub_spec.lua0000644000000000000000000000011714475665104017376 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_pubsub_spec.lua0000644000175000017500000004373114475665104021605 0ustar00prosodyprosody00000000000000local pubsub; setup(function () pubsub = require "util.pubsub"; end); --[[TODO: Retract Purge auto-create/auto-subscribe Item store/node store resize on max_items change service creation config provides alternative node_defaults get subscriptions ]] describe("util.pubsub", function () describe("simple node creation and deletion", function () randomize(false); -- These tests are ordered -- Roughly a port of scansion/scripts/pubsub_createdelete.scs local service = pubsub.new(); describe("#create", function () randomize(false); -- These tests are ordered it("creates a new node", function () assert.truthy(service:create("princely_musings", true)); end); it("fails to create the same node again", function () assert.falsy(service:create("princely_musings", true)); end); end); describe("#delete", function () randomize(false); -- These tests are ordered it("deletes the node", function () assert.truthy(service:delete("princely_musings", true)); end); it("can't delete an already deleted node", function () assert.falsy(service:delete("princely_musings", true)); end); end); end); describe("simple publishing", function () randomize(false); -- These tests are ordered local notified; local broadcaster = spy.new(function (notif_type, node_name, subscribers, item) -- luacheck: ignore 212 notified = subscribers; end); local service = pubsub.new({ broadcaster = broadcaster; }); it("creates a node", function () assert.truthy(service:create("node", true)); end); it("lets someone subscribe", function () assert.truthy(service:add_subscription("node", true, "someone")); end); it("publishes an item", function () assert.truthy(service:publish("node", true, "1", "item 1")); assert.truthy(notified["someone"]); end); it("called the broadcaster", function () assert.spy(broadcaster).was_called(); end); it("should return one item", function () local ok, ret = service:get_items("node", true); assert.truthy(ok); assert.same({ "1", ["1"] = "item 1" }, ret); end); it("lets someone unsubscribe", function () assert.truthy(service:remove_subscription("node", true, "someone")); end); it("does not send notifications after subscription is removed", function () assert.truthy(service:publish("node", true, "1", "item 1")); assert.is_nil(notified["someone"]); end); end); describe("publish with config", function () randomize(false); -- These tests are ordered local broadcaster = spy.new(function (notif_type, node_name, subscribers, item) -- luacheck: ignore 212 end); local service = pubsub.new({ broadcaster = broadcaster; autocreate_on_publish = true; }); it("automatically creates node with requested config", function () assert(service:publish("node", true, "1", "item 1", { myoption = true })); local ok, config = assert(service:get_node_config("node", true)); assert.truthy(ok); assert.equals(true, config.myoption); end); it("fails to publish to a node with differing config", function () local ok, err = service:publish("node", true, "1", "item 2", { myoption = false }); assert.falsy(ok); assert.equals("precondition-not-met", err.pubsub_condition); end); it("allows to publish to a node with differing config when only defaults are suggested", function () assert(service:publish("node", true, "1", "item 2", { _defaults_only = true, myoption = false })); end); end); describe("#issue1082", function () randomize(false); -- These tests are ordered local service = pubsub.new(); it("creates a node with max_items = 1", function () assert.truthy(service:create("node", true, { max_items = 1 })); end); it("changes max_items to 2", function () assert.truthy(service:set_node_config("node", true, { max_items = 2 })); end); it("publishes one item", function () assert.truthy(service:publish("node", true, "1", "item 1")); end); it("should return one item", function () local ok, ret = service:get_items("node", true); assert.truthy(ok); assert.same({ "1", ["1"] = "item 1" }, ret); end); it("publishes another item", function () assert.truthy(service:publish("node", true, "2", "item 2")); end); it("should return two items", function () local ok, ret = service:get_items("node", true); assert.truthy(ok); assert.same({ "2", "1", ["1"] = "item 1", ["2"] = "item 2", }, ret); end); it("publishes yet another item", function () assert.truthy(service:publish("node", true, "3", "item 3")); end); it("should still return only two items", function () local ok, ret = service:get_items("node", true); assert.truthy(ok); assert.same({ "3", "2", ["2"] = "item 2", ["3"] = "item 3", }, ret); end); it("has a default max_items", function () assert.truthy(service.config.max_items); end) it("changes max_items to max", function () assert.truthy(service:set_node_config("node", true, { max_items = "max" })); end); it("publishes some more items", function() for i = 4, service.config.max_items + 5 do assert.truthy(service:publish("node", true, tostring(i), "item " .. tostring(i))); end end); it("should still return only two items", function () local ok, ret = service:get_items("node", true); assert.truthy(ok); assert.same(service.config.max_items, #ret); end); end); describe("the thing", function () randomize(false); -- These tests are ordered local service = pubsub.new(); it("creates a node with some items", function () assert.truthy(service:create("node", true, { max_items = 3 })); assert.truthy(service:publish("node", true, "1", "item 1")); assert.truthy(service:publish("node", true, "2", "item 2")); assert.truthy(service:publish("node", true, "3", "item 3")); end); it("should return the requested item", function () local ok, ret = service:get_items("node", true, "1"); assert.truthy(ok); assert.same({ "1", ["1"] = "item 1" }, ret); end); it("should return multiple requested items", function () local ok, ret = service:get_items("node", true, { "1", "2" }); assert.truthy(ok); assert.same({ "1", "2", ["1"] = "item 1", ["2"] = "item 2", }, ret); end); end); describe("node config", function () local service; before_each(function () service = pubsub.new(); service:create("test", true); end); it("access is forbidden for unaffiliated entities", function () local ok, err = service:get_node_config("test", "stranger"); assert.is_falsy(ok); assert.equals("forbidden", err); end); it("returns an error for nodes that do not exist", function () local ok, err = service:get_node_config("nonexistent", true); assert.is_falsy(ok); assert.equals("item-not-found", err); end); end); describe("access model", function () describe("open", function () local service; before_each(function () service = pubsub.new(); -- Do not supply any config, 'open' should be default service:create("test", true); end); it("should be the default", function () local ok, config = service:get_node_config("test", true); assert.truthy(ok); assert.equal("open", config.access_model); end); it("should allow anyone to subscribe", function () local ok = service:add_subscription("test", "stranger", "stranger"); assert.is_true(ok); end); it("should still reject outcast-affiliated entities", function () assert(service:set_affiliation("test", true, "enemy", "outcast")); local ok, err = service:add_subscription("test", "enemy", "enemy"); assert.is_falsy(ok); assert.equal("forbidden", err); end); end); describe("whitelist", function () local service; before_each(function () service = assert(pubsub.new()); assert.is_true(service:create("test", true, { access_model = "whitelist" })); end); it("should be present in the configuration", function () local ok, config = service:get_node_config("test", true); assert.truthy(ok); assert.equal("whitelist", config.access_model); end); it("should not allow anyone to subscribe", function () local ok, err = service:add_subscription("test", "stranger", "stranger"); assert.is_false(ok); assert.equals("forbidden", err); end); end); describe("change", function () local service; before_each(function () service = pubsub.new(); service:create("test", true, { access_model = "open" }); end); it("affects existing subscriptions", function () do local ok = service:add_subscription("test", "stranger", "stranger"); assert.is_true(ok); end do local ok, sub = service:get_subscription("test", "stranger", "stranger"); assert.is_true(ok); assert.is_true(sub); end assert(service:set_node_config("test", true, { access_model = "whitelist" })); do local ok, sub = service:get_subscription("test", "stranger", "stranger"); assert.is_true(ok); assert.is_nil(sub); end end); end); end); describe("publish model", function () describe("publishers", function () local service; before_each(function () service = pubsub.new(); -- Do not supply any config, 'publishers' should be default service:create("test", true); end); it("should be the default", function () local ok, config = service:get_node_config("test", true); assert.truthy(ok); assert.equal("publishers", config.publish_model); end); it("should not allow anyone to publish", function () assert.is_true(service:add_subscription("test", "stranger", "stranger")); local ok, err = service:publish("test", "stranger", "item1", "foo"); assert.is_falsy(ok); assert.equals("forbidden", err); end); it("should allow publishers to publish", function () assert(service:set_affiliation("test", true, "mypublisher", "publisher")); -- luacheck: ignore 211/err local ok, err = service:publish("test", "mypublisher", "item1", "foo"); assert.is_true(ok); end); it("should allow owners to publish", function () assert(service:set_affiliation("test", true, "myowner", "owner")); local ok = service:publish("test", "myowner", "item1", "foo"); assert.is_true(ok); end); end); describe("open", function () local service; before_each(function () service = pubsub.new(); service:create("test", true, { publish_model = "open" }); end); it("should allow anyone to publish", function () local ok = service:publish("test", "stranger", "item1", "foo"); assert.is_true(ok); end); end); describe("subscribers", function () local service; before_each(function () service = pubsub.new(); service:create("test", true, { publish_model = "subscribers" }); end); it("should not allow non-subscribers to publish", function () local ok, err = service:publish("test", "stranger", "item1", "foo"); assert.is_falsy(ok); assert.equals("forbidden", err); end); it("should allow subscribers to publish without an affiliation", function () assert.is_true(service:add_subscription("test", "stranger", "stranger")); local ok = service:publish("test", "stranger", "item1", "foo"); assert.is_true(ok); end); it("should allow publishers to publish without a subscription", function () assert(service:set_affiliation("test", true, "mypublisher", "publisher")); -- luacheck: ignore 211/err local ok, err = service:publish("test", "mypublisher", "item1", "foo"); assert.is_true(ok); end); it("should allow owners to publish without a subscription", function () assert(service:set_affiliation("test", true, "myowner", "owner")); local ok = service:publish("test", "myowner", "item1", "foo"); assert.is_true(ok); end); end); end); describe("item API", function () local service; before_each(function () service = pubsub.new(); service:create("test", true, { publish_model = "subscribers" }); end); describe("get_last_item()", function () it("succeeds with nil on empty nodes", function () local ok, id, item = service:get_last_item("test", true); assert.is_true(ok); assert.is_nil(id); assert.is_nil(item); end); it("succeeds and returns the last item", function () service:publish("test", true, "one", "hello world"); service:publish("test", true, "two", "hello again"); service:publish("test", true, "three", "hey"); service:publish("test", true, "one", "bye"); local ok, id, item = service:get_last_item("test", true); assert.is_true(ok); assert.equal("one", id); assert.equal("bye", item); end); end); describe("get_items()", function () it("fails on non-existent nodes", function () local ok, err = service:get_items("no-node", true); assert.is_falsy(ok); assert.equal("item-not-found", err); end); it("returns no items on an empty node", function () local ok, items = service:get_items("test", true); assert.is_true(ok); assert.equal(0, #items); assert.is_nil(next(items)); end); it("returns no items on an empty node", function () local ok, items = service:get_items("test", true); assert.is_true(ok); assert.equal(0, #items); assert.is_nil((next(items))); end); it("returns all published items", function () service:publish("test", true, "one", "hello world"); service:publish("test", true, "two", "hello again"); service:publish("test", true, "three", "hey"); service:publish("test", true, "one", "bye"); local ok, items = service:get_items("test", true); assert.is_true(ok); assert.same({ "one", "three", "two", two = "hello again", three = "hey", one = "bye" }, items); end); end); end); describe("restoring data from nodestore", function () local nodestore = { data = { test = { name = "test"; config = {}; affiliations = {}; subscribers = { ["someone"] = true; }; } } }; function nodestore:users() return pairs(self.data) end function nodestore:get(key) return self.data[key]; end local service = pubsub.new({ nodestore = nodestore; }); it("subscriptions", function () local ok, ret = service:get_subscriptions(nil, true, nil) assert.is_true(ok); assert.same({ { node = "test", jid = "someone", subscription = true, } }, ret); end); end); describe("node config checking", function () local service; before_each(function () service = pubsub.new({ check_node_config = function (node, actor, config) -- luacheck: ignore 212 return config["max_items"] <= 20; end; }); end); it("defaults, then configure", function () local ok, err = service:create("node", true); assert.is_true(ok, err); local ok, err = service:set_node_config("node", true, { max_items = 10 }); assert.is_true(ok, err); local ok, err = service:set_node_config("node", true, { max_items = 100 }); assert.falsy(ok, err); assert.equals(err, "not-acceptable"); end); it("create with ok config, then configure", function () local ok, err = service:create("node", true, { max_items = 10 }); assert.is_true(ok, err); local ok, err = service:set_node_config("node", true, { max_items = 100 }); assert.falsy(ok, err); local ok, err = service:set_node_config("node", true, { max_items = 10 }); assert.is_true(ok, err); end); it("create with unacceptable config", function () local ok, err = service:create("node", true, { max_items = 100 }); assert.falsy(ok, err); end); end); describe("subscriber filter", function () it("works", function () local filter = spy.new(function (subs) -- luacheck: ignore 212/subs return {["modified"] = true}; end); local broadcaster = spy.new(function (notif_type, node_name, subscribers, item) -- luacheck: ignore 212 end); local service = pubsub.new({ subscriber_filter = filter; broadcaster = broadcaster; }); local ok = service:create("node", true); assert.truthy(ok); local ok = service:add_subscription("node", true, "someone"); assert.truthy(ok); local ok = service:publish("node", true, "1", "item"); assert.truthy(ok); -- TODO how to match table arguments? assert.spy(filter).was_called(); assert.spy(broadcaster).was_called(); end); end); describe("persist_items", function() it("can be disabled", function() local broadcaster = spy.new(function(notif_type, node_name, subscribers, item) -- luacheck: ignore 212 end); local service = pubsub.new { node_defaults = { persist_items = false }, broadcaster = broadcaster } local ok = service:create("node", true) assert.truthy(ok); local ok = service:publish("node", true, "1", "item"); assert.truthy(ok); assert.spy(broadcaster).was_called(); local ok, items = service:get_items("node", true); assert.not_truthy(ok); assert.equal(items, "persistent-items-unsupported"); end); end) describe("max_items", function () it("works", function () local service = pubsub.new { }; local ok = service:create("node", true) assert.truthy(ok); for i = 1, 20 do assert.truthy(service:publish("node", true, "item"..tostring(i), "data"..tostring(i))); end do local ok, items = service:get_items("node", true, nil, { max = 3 }); assert.truthy(ok, items); assert.equal(3, #items); assert.same({ "item20", "item19", "item18", item20 = "data20", item19 = "data19", item18 = "data18", }, items, "items should be ordered by oldest first"); end do local ok, items = service:get_items("node", true, nil, { max = 10 }); assert.truthy(ok, items); assert.equal(10, #items); assert.same({ "item20", "item19", "item18", "item17", "item16", "item15", "item14", "item13", "item12", "item11", item20 = "data20", item19 = "data19", item18 = "data18", item17 = "data17", item16 = "data16", item15 = "data15", item14 = "data14", item13 = "data13", item12 = "data12", item11 = "data11", }, items, "items should be ordered by oldest first"); end end); end) end); prosody-0.12.4/spec/PaxHeaders/util_queue_spec.lua0000644000000000000000000000011714475665104017222 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_queue_spec.lua0000644000175000017500000000662314475665104021430 0ustar00prosodyprosody00000000000000 local queue = require "util.queue"; describe("util.queue", function() describe("#new()", function() it("should work", function() do local q = queue.new(10); assert.are.equal(q.size, 10); assert.are.equal(q:count(), 0); assert.is_true(q:push("one")); assert.is_true(q:push("two")); assert.is_true(q:push("three")); for i = 4, 10 do assert.is_true(q:push("hello")); assert.are.equal(q:count(), i, "count is not "..i.."("..q:count()..")"); end assert.are.equal(q:push("hello"), nil, "queue overfull!"); assert.are.equal(q:push("hello"), nil, "queue overfull!"); assert.are.equal(q:pop(), "one", "queue item incorrect"); assert.are.equal(q:pop(), "two", "queue item incorrect"); assert.is_true(q:push("hello")); assert.is_true(q:push("hello")); assert.are.equal(q:pop(), "three", "queue item incorrect"); assert.is_true(q:push("hello")); assert.are.equal(q:push("hello"), nil, "queue overfull!"); assert.are.equal(q:push("hello"), nil, "queue overfull!"); assert.are.equal(q:count(), 10, "queue count incorrect"); for _ = 1, 10 do assert.are.equal(q:pop(), "hello", "queue item incorrect"); end assert.are.equal(q:count(), 0, "queue count incorrect"); assert.are.equal(q:pop(), nil, "empty queue pops non-nil result"); assert.are.equal(q:count(), 0, "popping empty queue affects count"); assert.are.equal(q:peek(), nil, "empty queue peeks non-nil result"); assert.are.equal(q:count(), 0, "peeking empty queue affects count"); assert.is_true(q:push(1)); for i = 1, 1001 do assert.are.equal(q:pop(), i); assert.are.equal(q:count(), 0); assert.is_true(q:push(i+1)); assert.are.equal(q:count(), 1); end assert.are.equal(q:pop(), 1002); assert.is_true(q:push(1)); for i = 1, 1000 do assert.are.equal(q:pop(), i); assert.is_true(q:push(i+1)); end assert.are.equal(q:pop(), 1001); assert.are.equal(q:count(), 0); end do -- Test queues that purge old items when pushing to a full queue local q = queue.new(10, true); for i = 1, 10 do q:push(i); end assert.are.equal(q:count(), 10); assert.is_true(q:push(11)); assert.are.equal(q:count(), 10); assert.are.equal(q:pop(), 2); -- First item should have been purged assert.are.equal(q:peek(), 3); for i = 12, 32 do assert.is_true(q:push(i)); end assert.are.equal(q:count(), 10); assert.are.equal(q:pop(), 23); end do -- Test iterator local q = queue.new(10, true); for i = 1, 10 do q:push(i); end local i = 0; for item in q:items() do i = i + 1; assert.are.equal(item, i, "unexpected item returned by iterator") end end end); end); describe("consume()", function () it("should work", function () local q = queue.new(10); for i = 1, 5 do q:push(i); end local c = 0; for i in q:consume() do assert(i == c + 1); assert(q:count() == (5-i)); c = i; end end); it("should work even if items are pushed in the loop", function () local q = queue.new(10); for i = 1, 5 do q:push(i); end local c = 0; for i in q:consume() do assert(i == c + 1); if c < 3 then assert(q:count() == (5-i)); else assert(q:count() == (6-i)); end c = i; if c == 3 then q:push(6); end end assert.equal(c, 6); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_random_spec.lua0000644000000000000000000000011714475665104017356 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_random_spec.lua0000644000175000017500000000073214475665104021557 0ustar00prosodyprosody00000000000000 local random = require "util.random"; describe("util.random", function() describe("#bytes()", function() it("should return a string", function() assert.is_string(random.bytes(16)); end); it("should return the requested number of bytes", function() -- Makes no attempt at testing how random the bytes are, -- just that it returns the number of bytes requested for i = 1, 20 do assert.are.equal(2^i, #random.bytes(2^i)); end end); end); end); prosody-0.12.4/spec/PaxHeaders/util_rfc6724_spec.lua0000644000000000000000000000011714475665104017173 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_rfc6724_spec.lua0000644000175000017500000001335614475665104021402 0ustar00prosodyprosody00000000000000 local rfc6724 = require "util.rfc6724"; local new_ip = require"util.ip".new_ip; describe("util.rfc6724", function() describe("#source()", function() it("should work", function() assert.are.equal(rfc6724.source(new_ip("2001:db8:1::1", "IPv6"), {new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr, "2001:db8:3::1", "prefer appropriate scope"); assert.are.equal(rfc6724.source(new_ip("ff05::1", "IPv6"), {new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr, "2001:db8:3::1", "prefer appropriate scope"); assert.are.equal(rfc6724.source(new_ip("2001:db8:1::1", "IPv6"), {new_ip("2001:db8:1::1", "IPv6"), new_ip("2001:db8:2::1", "IPv6")}).addr, "2001:db8:1::1", "prefer same address"); -- "2001:db8:1::1" should be marked "deprecated" here, we don't handle that right now assert.are.equal(rfc6724.source(new_ip("fe80::1", "IPv6"), {new_ip("fe80::2", "IPv6"), new_ip("2001:db8:1::1", "IPv6")}).addr, "fe80::2", "prefer appropriate scope"); -- "fe80::2" should be marked "deprecated" here, we don't handle that right now assert.are.equal(rfc6724.source(new_ip("2001:db8:1::1", "IPv6"), {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::2", "IPv6")}).addr, "2001:db8:1::2", "longest matching prefix"); --[[ "2001:db8:1::2" should be a care-of address and "2001:db8:3::2" a home address, we can't handle this and would fail assert.are.equal(rfc6724.source(new_ip("2001:db8:1::1", "IPv6"), {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::2", "IPv6")}).addr, "2001:db8:3::2", "prefer home address"); ]] assert.are.equal(rfc6724.source(new_ip("2002:c633:6401::1", "IPv6"), {new_ip("2002:c633:6401::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001:db8:1::2", "IPv6")}).addr, "2002:c633:6401::d5e3:7953:13eb:22e8", "prefer matching label"); -- "2002:c633:6401::d5e3:7953:13eb:22e8" should be marked "temporary" here, we don't handle that right now assert.are.equal(rfc6724.source(new_ip("2001:db8:1::d5e3:0:0:1", "IPv6"), {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:1::d5e3:7953:13eb:22e8", "IPv6")}).addr, "2001:db8:1::d5e3:7953:13eb:22e8", "prefer temporary address") -- "2001:db8:1::2" should be marked "public" and "2001:db8:1::d5e3:7953:13eb:22e8" should be marked "temporary" here, we don't handle that right now end); end); describe("#destination()", function() it("should work", function() local order; order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("198.51.100.121", "IPv4")}, {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")}) assert.are.equal(order[1].addr, "2001:db8:1::1", "prefer matching scope"); assert.are.equal(order[2].addr, "198.51.100.121", "prefer matching scope"); order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("198.51.100.121", "IPv4")}, {new_ip("fe80::1", "IPv6"), new_ip("198.51.100.117", "IPv4")}) assert.are.equal(order[1].addr, "198.51.100.121", "prefer matching scope"); assert.are.equal(order[2].addr, "2001:db8:1::1", "prefer matching scope"); order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("10.1.2.3", "IPv4")}, {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")}) assert.are.equal(order[1].addr, "2001:db8:1::1", "prefer higher precedence"); assert.are.equal(order[2].addr, "10.1.2.3", "prefer higher precedence"); order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")}) assert.are.equal(order[1].addr, "fe80::1", "prefer smaller scope"); assert.are.equal(order[2].addr, "2001:db8:1::1", "prefer smaller scope"); --[[ "2001:db8:1::2" and "fe80::2" should be marked "care-of address", while "2001:db8:3::1" should be marked "home address", we can't currently handle this and would fail the test order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::2", "IPv6")}) assert.are.equal(order[1].addr, "2001:db8:1::1", "prefer home address"); assert.are.equal(order[2].addr, "fe80::1", "prefer home address"); ]] --[[ "fe80::2" should be marked "deprecated", we can't currently handle this and would fail the test order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")}) assert.are.equal(order[1].addr, "2001:db8:1::1", "avoid deprecated addresses"); assert.are.equal(order[2].addr, "fe80::1", "avoid deprecated addresses"); ]] order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("2001:db8:3ffe::1", "IPv6")}, {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")}) assert.are.equal(order[1].addr, "2001:db8:1::1", "longest matching prefix"); assert.are.equal(order[2].addr, "2001:db8:3ffe::1", "longest matching prefix"); order = rfc6724.destination({new_ip("2002:c633:6401::1", "IPv6"), new_ip("2001:db8:1::1", "IPv6")}, {new_ip("2002:c633:6401::2", "IPv6"), new_ip("fe80::2", "IPv6")}) assert.are.equal(order[1].addr, "2002:c633:6401::1", "prefer matching label"); assert.are.equal(order[2].addr, "2001:db8:1::1", "prefer matching label"); order = rfc6724.destination({new_ip("2002:c633:6401::1", "IPv6"), new_ip("2001:db8:1::1", "IPv6")}, {new_ip("2002:c633:6401::2", "IPv6"), new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")}) assert.are.equal(order[1].addr, "2001:db8:1::1", "prefer higher precedence"); assert.are.equal(order[2].addr, "2002:c633:6401::1", "prefer higher precedence"); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_ringbuffer_spec.lua0000644000000000000000000000011714475665104020227 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_ringbuffer_spec.lua0000644000175000017500000000523214475665104022430 0ustar00prosodyprosody00000000000000local rb = require "util.ringbuffer"; describe("util.ringbuffer", function () describe("#new", function () it("has a constructor", function () assert.Function(rb.new); end); it("can be created", function () assert.truthy(rb.new()); end); it("won't create an empty buffer", function () assert.has_error(function () rb.new(0); end); end); it("won't create a negatively sized buffer", function () assert.has_error(function () rb.new(-1); end); end); end); describe(":write", function () local b = rb.new(); it("works", function () assert.truthy(b:write("hi")); end); end); describe(":discard", function () local b = rb.new(); it("works", function () assert.truthy(b:write("hello world")); assert.truthy(b:discard(6)); assert.equal(5, #b); assert.equal("world", b:read(5)); end); end); describe(":sub", function () -- Helper function to compare buffer:sub() with string:sub() local function test_sub(b, x, y) local s = b:read(#b, true); local string_result, buffer_result = s:sub(x, y), b:sub(x, y); assert.equals(string_result, buffer_result, ("buffer:sub(%d, %s) does not match string:sub()"):format(x, y and ("%d"):format(y) or "nil")); end it("works", function () local b = rb.new(); assert.truthy(b:write("hello world")); assert.equals("hello", b:sub(1, 5)); end); it("supports optional end parameter", function () local b = rb.new(); assert.truthy(b:write("hello world")); assert.equals("hello world", b:sub(1)); assert.equals("world", b:sub(-5)); end); it("is equivalent to string:sub", function () local b = rb.new(6); assert.truthy(b:write("foobar")); b:read(3); b:write("foo"); for i = -13, 13 do for j = -13, 13 do test_sub(b, i, j); end end end); end); describe(":byte", function () -- Helper function to compare buffer:byte() with string:byte() local function test_byte(b, x, y) local s = b:read(#b, true); local string_result, buffer_result = {s:byte(x, y)}, {b:byte(x, y)}; assert.same(string_result, buffer_result, ("buffer:byte(%d, %s) does not match string:byte()"):format(x, y and ("%d"):format(y) or "nil")); end it("is equivalent to string:byte", function () local b = rb.new(6); assert.truthy(b:write("foobar")); b:read(3); b:write("foo"); test_byte(b, 1); test_byte(b, 3); test_byte(b, -1); test_byte(b, -3); for i = -13, 13 do for j = -13, 13 do test_byte(b, i, j); end end end); it("works with characters > 127", function () local b = rb.new(); b:write(string.char(0, 140)); local r = { b:byte(1, 2) }; assert.same({ 0, 140 }, r); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_rsm_spec.lua0000644000000000000000000000011714475665104016677 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.73276456 prosody-0.12.4/spec/util_rsm_spec.lua0000644000175000017500000000747514475665104021113 0ustar00prosodyprosody00000000000000local rsm = require "util.rsm"; local xml = require "util.xml"; local function strip(s) return (s:gsub(">%s+<", "><")); end describe("util.rsm", function () describe("parse", function () it("works", function () local test = xml.parse(strip([[ 10 ]])); assert.same({ max = 10 }, rsm.parse(test)); end); it("works", function () local test = xml.parse(strip([[ saint@example.org peterpan@neverland.lit 800 ]])); assert.same({ first = { index = 0, "saint@example.org" }, last = "peterpan@neverland.lit", count = 800 }, rsm.parse(test)); end); it("works", function () local test = xml.parse(strip([[ 10 peter@pixyland.org ]])); assert.same({ max = 10, before = "peter@pixyland.org" }, rsm.parse(test)); end); it("all fields works", function() local test = assert(xml.parse(strip([[ a b 10 f 5 z 100 ]]))); assert.same({ after = "a"; before = "b"; count = 10; first = {index = 1; "f"}; index = 5; last = "z"; max = 100; }, rsm.parse(test)); end); end); describe("generate", function () it("works", function () local test = xml.parse(strip([[ 10 ]])); local res = rsm.generate({ max = 10 }); assert.same(test:get_child_text("max"), res:get_child_text("max")); end); it("works", function () local test = xml.parse(strip([[ saint@example.org peterpan@neverland.lit 800 ]])); local res = rsm.generate({ first = { index = 0, "saint@example.org" }, last = "peterpan@neverland.lit", count = 800 }); assert.same(test:get_child("first").attr.index, res:get_child("first").attr.index); assert.same(test:get_child_text("first"), res:get_child_text("first")); assert.same(test:get_child_text("last"), res:get_child_text("last")); assert.same(test:get_child_text("count"), res:get_child_text("count")); end); it("works", function () local test = xml.parse(strip([[ 10 peter@pixyland.org ]])); local res = rsm.generate({ max = 10, before = "peter@pixyland.org" }); assert.same(test:get_child_text("max"), res:get_child_text("max")); assert.same(test:get_child_text("before"), res:get_child_text("before")); end); it("handles floats", function () local r1 = rsm.generate({ max = 10.0, count = 100.0, first = { index = 1.0, "foo" } }); assert.equal("10", r1:get_child_text("max")); assert.equal("100", r1:get_child_text("count")); assert.equal("1", r1:get_child("first").attr.index); end); it("all fields works", function () local res = rsm.generate({ after = "a"; before = "b"; count = 10; first = {index = 1; "f"}; index = 5; last = "z"; max = 100; }); assert.equal("a", res:get_child_text("after")); assert.equal("b", res:get_child_text("before")); assert.equal("10", res:get_child_text("count")); assert.equal("f", res:get_child_text("first")); assert.equal("1", res:get_child("first").attr.index); assert.equal("5", res:get_child_text("index")); assert.equal("z", res:get_child_text("last")); assert.equal("100", res:get_child_text("max")); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_sasl_spec.lua0000644000000000000000000000012014475665104017032 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_sasl_spec.lua0000644000175000017500000000213414475665104021237 0ustar00prosodyprosody00000000000000local sasl = require "util.sasl"; -- profile * mechanism -- callbacks could use spies instead describe("util.sasl", function () describe("plain_test profile", function () local profile = { plain_test = function (_, username, password, realm) assert.equals("user", username) assert.equals("pencil", password) assert.equals("sasl.test", realm) return true, true; end; }; it("works with PLAIN", function () local plain = sasl.new("sasl.test", profile); assert.truthy(plain:select("PLAIN")); assert.truthy(plain:process("\000user\000pencil")); assert.equals("user", plain.username); end); end); describe("plain profile", function () local profile = { plain = function (_, username, realm) assert.equals("user", username) assert.equals("sasl.test", realm) return "pencil", true; end; }; it("works with PLAIN", function () local plain = sasl.new("sasl.test", profile); assert.truthy(plain:select("PLAIN")); assert.truthy(plain:process("\000user\000pencil")); assert.equals("user", plain.username); end); -- TODO SCRAM end); end); prosody-0.12.4/spec/PaxHeaders/util_serialization_spec.lua0000644000000000000000000000012014475665104020745 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_serialization_spec.lua0000644000175000017500000000421414475665104023153 0ustar00prosodyprosody00000000000000local serialization = require "util.serialization"; describe("util.serialization", function () describe("serialize", function () it("makes a string", function () assert.is_string(serialization.serialize({})); assert.is_string(serialization.serialize(nil)); assert.is_string(serialization.serialize(1)); assert.is_string(serialization.serialize(true)); end); it("rejects function by default", function () assert.has_error(function () serialization.serialize(function () end) end); end); it("makes a string in debug mode", function () assert.is_string(serialization.serialize(function () end, "debug")); end); it("rejects cycles", function () assert.has_error(function () local t = {} t[t] = { t }; serialization.serialize(t) end); -- also with multirefs allowed assert.has_error(function () local t = {} t[t] = { t }; serialization.serialize(t, { multirefs = true }) end); end); it("rejects multiple references to same table", function () assert.has_error(function () local t1 = {}; local t2 = { t1, t1 }; serialization.serialize(t2, { multirefs = false }); end); end); it("optionally allows multiple references to same table", function () assert.has_error(function () local t1 = {}; local t2 = { t1, t1 }; serialization.serialize(t2, { multirefs = true }); end); end); it("roundtrips", function () local function test(data) local serialized = serialization.serialize(data); assert.is_string(serialized); local deserialized, err = serialization.deserialize(serialized); assert.same(data, deserialized, err); end test({}); test({hello="world"}); test("foobar") test("\0\1\2\3"); test("nödåtgärd"); test({1,2,3,4}); test({foo={[100]={{"bar"},{baz=1}}}}); test({["goto"] = {["function"]={["do"]="keywords"}}}); end); it("can serialize with metatables", function () local s = serialization.new({ freeze = true }); local t = setmetatable({ a = "hi" }, { __freeze = function (t) return { t.a } end }); local rt = serialization.deserialize(s(t)); assert.same({"hi"}, rt); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_smqueue_spec.lua0000644000000000000000000000012014475665104017554 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_smqueue_spec.lua0000644000175000017500000000404414475665104021763 0ustar00prosodyprosody00000000000000describe("util.smqueue", function() local smqueue setup(function() smqueue = require "util.smqueue"; end) describe("#new()", function() it("should work", function() local q = smqueue.new(10); assert.truthy(q); end) end) describe("#push()", function() it("should allow pushing many items", function() local q = smqueue.new(10); for i = 1, 20 do q:push(i); end assert.equal(20, q:count_unacked()); end) end) describe("#resumable()", function() it("returns true while the queue is small", function() local q = smqueue.new(10); for i = 1, 10 do q:push(i); end assert.truthy(q:resumable()); q:push(11); assert.falsy(q:resumable()); end) end) describe("#ack", function() it("allows removing items", function() local q = smqueue.new(10); for i = 1, 10 do q:push(i); end assert.same({ 1; 2; 3 }, q:ack(3)); assert.same({ 4; 5; 6 }, q:ack(6)); assert.falsy(q:ack(3), "can't go backwards") assert.falsy(q:ack(100), "can't ack too many") for i = 11, 20 do q:push(i); end assert.same({ 11; 12 }, q:ack(12), "items are dropped"); end) end) describe("#resume", function() it("iterates over current items", function() local q = smqueue.new(10); for i = 1, 12 do q:push(i); end assert.same({ 3; 4; 5; 6 }, q:ack(6)); assert.truthy(q:resumable()); local resume = {} for _, i in q:resume() do resume[i] = true end assert.same({ [7] = true; [8] = true; [9] = true; [10] = true; [11] = true; [12] = true }, resume); end) end) describe("#table", function () it("produces a compat layer", function () local q = smqueue.new(10); for i = 1,10 do q:push(i); end do local t = q:table(); assert.same({ 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 }, t); end do for i = 11,20 do q:push(i); end local t = q:table(); assert.same({ 11; 12; 13; 14; 15; 16; 17; 18; 19; 20 }, t); end do q:ack(15); local t = q:table(); assert.same({ 16; 17; 18; 19; 20 }, t); end do q:ack(20); local t = q:table(); assert.same({}, t); end end) end) end); prosody-0.12.4/spec/PaxHeaders/util_stanza_spec.lua0000644000000000000000000000012014475665104017370 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_stanza_spec.lua0000644000175000017500000004660014475665104021603 0ustar00prosodyprosody00000000000000 local st = require "util.stanza"; local errors = require "util.error"; describe("util.stanza", function() describe("#preserialize()", function() it("should work", function() local stanza = st.stanza("message", { type = "chat" }):text_tag("body", "Hello"); local stanza2 = st.preserialize(stanza); assert.is_table(stanza2, "Preserialized stanza is a table"); assert.is_nil(getmetatable(stanza2), "Preserialized stanza has no metatable"); assert.is_string(stanza2.name, "Preserialized stanza has a name field"); assert.equal(stanza.name, stanza2.name, "Preserialized stanza has same name as the input stanza"); assert.same(stanza.attr, stanza2.attr, "Preserialized stanza same attr table as input stanza"); assert.is_nil(stanza2.tags, "Preserialized stanza has no tag list"); assert.is_nil(stanza2.last_add, "Preserialized stanza has no last_add marker"); assert.is_table(stanza2[1], "Preserialized child element preserved"); assert.equal("body", stanza2[1].name, "Preserialized child element name preserved"); end); end); describe("#deserialize()", function() it("should work", function() local stanza = { name = "message", attr = { type = "chat" }, { name = "body", attr = { }, "Hello" } }; local stanza2 = st.deserialize(st.preserialize(stanza)); assert.is_table(stanza2, "Deserialized stanza is a table"); assert.equal(st.stanza_mt, getmetatable(stanza2), "Deserialized stanza has stanza metatable"); assert.is_string(stanza2.name, "Deserialized stanza has a name field"); assert.equal(stanza.name, stanza2.name, "Deserialized stanza has same name as the input table"); assert.same(stanza.attr, stanza2.attr, "Deserialized stanza same attr table as input table"); assert.is_table(stanza2.tags, "Deserialized stanza has tag list"); assert.is_table(stanza2[1], "Deserialized child element preserved"); assert.equal("body", stanza2[1].name, "Deserialized child element name preserved"); end); end); describe("#stanza()", function() it("should work", function() local s = st.stanza("foo", { xmlns = "myxmlns", a = "attr-a" }); assert.are.equal(s.name, "foo"); assert.are.equal(s.attr.xmlns, "myxmlns"); assert.are.equal(s.attr.a, "attr-a"); local s1 = st.stanza("s1"); assert.are.equal(s1.name, "s1"); assert.are.equal(s1.attr.xmlns, nil); assert.are.equal(#s1, 0); assert.are.equal(#s1.tags, 0); s1:tag("child1"); assert.are.equal(#s1.tags, 1); assert.are.equal(s1.tags[1].name, "child1"); s1:tag("grandchild1"):up(); assert.are.equal(#s1.tags, 1); assert.are.equal(s1.tags[1].name, "child1"); assert.are.equal(#s1.tags[1], 1); assert.are.equal(s1.tags[1][1].name, "grandchild1"); s1:up():tag("child2"); assert.are.equal(#s1.tags, 2, tostring(s1)); assert.are.equal(s1.tags[1].name, "child1"); assert.are.equal(s1.tags[2].name, "child2"); assert.are.equal(#s1.tags[1], 1); assert.are.equal(s1.tags[1][1].name, "grandchild1"); s1:up():text("Hello world"); assert.are.equal(#s1.tags, 2); assert.are.equal(#s1, 3); assert.are.equal(s1.tags[1].name, "child1"); assert.are.equal(s1.tags[2].name, "child2"); assert.are.equal(#s1.tags[1], 1); assert.are.equal(s1.tags[1][1].name, "grandchild1"); end); it("should work with unicode values", function () local s = st.stanza("Объект", { xmlns = "myxmlns", ["Объект"] = "&" }); assert.are.equal(s.name, "Объект"); assert.are.equal(s.attr.xmlns, "myxmlns"); assert.are.equal(s.attr["Объект"], "&"); end); it("should allow :text() with nil and empty strings", function () local s_control = st.stanza("foo"); assert.same(st.stanza("foo"):text(), s_control); assert.same(st.stanza("foo"):text(nil), s_control); assert.same(st.stanza("foo"):text(""), s_control); end); it("validates names", function () assert.has_error_match(function () st.stanza("invalid\0name"); end, "invalid tag name:") assert.has_error_match(function () st.stanza("name", { ["foo\1\2\3bar"] = "baz" }); end, "invalid attribute name: contains control characters") assert.has_error_match(function () st.stanza("name", { ["foo"] = "baz\1\2\3\255moo" }); end, "invalid attribute value: contains control characters") end) it("validates types", function () assert.has_error_match(function () st.stanza(1); end, "invalid tag name: expected string, got number") assert.has_error_match(function () st.stanza("name", "string"); end, "invalid attributes: expected table, got string") assert.has_error_match(function () st.stanza("name",{1}); end, "invalid attribute name: expected string, got number") assert.has_error_match(function () st.stanza("name",{foo=1}); end, "invalid attribute value: expected string, got number") end) end); describe("#message()", function() it("should work", function() local m = st.message(); assert.are.equal(m.name, "message"); end); end); describe("#iq()", function() it("should create an iq stanza", function() local i = st.iq({ type = "get", id = "foo" }); assert.are.equal("iq", i.name); assert.are.equal("foo", i.attr.id); assert.are.equal("get", i.attr.type); end); it("should reject stanzas with no attributes", function () assert.has.error_match(function () st.iq(); end, "attributes"); end); it("should reject stanzas with no id", function () assert.has.error_match(function () st.iq({ type = "get" }); end, "id attribute"); end); it("should reject stanzas with no type", function () assert.has.error_match(function () st.iq({ id = "foo" }); end, "type attribute"); end); end); describe("#presence()", function () it("should work", function() local p = st.presence(); assert.are.equal(p.name, "presence"); end); end); describe("#reply()", function() it("should work for ", function() -- Test stanza local s = st.stanza("s", { to = "touser", from = "fromuser", id = "123" }) :tag("child1"); -- Make reply stanza local r = st.reply(s); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(#r.tags, 0, "A reply should not include children of the original stanza"); end); it("should work for ", function() -- Test stanza local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" }) :tag("child1"); -- Make reply stanza local r = st.reply(s); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(r.attr.type, "result"); assert.are.equal(#r.tags, 0, "A reply should not include children of the original stanza"); end); it("should work for ", function() -- Test stanza local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "set" }) :tag("child1"); -- Make reply stanza local r = st.reply(s); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(r.attr.type, "result"); assert.are.equal(#r.tags, 0, "A reply should not include children of the original stanza"); end); it("should reject not-stanzas", function () assert.has.error_match(function () st.reply(not "a stanza"); end, "expected stanza"); end); it("should reject not-stanzas", function () assert.has.error_match(function () st.reply({name="x"}); end, "expected stanza"); end); end); describe("#error_reply()", function() it("should work for ", function() -- Test stanza local s = st.stanza("s", { to = "touser", from = "fromuser", id = "123" }) :tag("child1"); -- Make reply stanza local r = st.error_reply(s, "cancel", "service-unavailable", nil, "host"); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(#r.tags, 1); assert.are.equal(r.tags[1].tags[1].name, "service-unavailable"); assert.are.equal(r.tags[1].attr.by, "host"); end); it("should work for ", function() -- Test stanza local s = st.stanza("iq", { to = "touser", from = "fromuser", id = "123", type = "get" }) :tag("child1"); -- Make reply stanza local r = st.error_reply(s, "cancel", "service-unavailable"); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(r.attr.type, "error"); assert.are.equal(#r.tags, 1); assert.are.equal(r.tags[1].tags[1].name, "service-unavailable"); end); it("should reject not-stanzas", function () assert.has.error_match(function () st.error_reply(not "a stanza", "modify", "bad-request"); end, "expected stanza"); end); it("should reject stanzas of type error", function () assert.has.error_match(function () st.error_reply(st.message({type="error"}), "cancel", "conflict"); end, "got stanza of type error"); assert.has.error_match(function () st.error_reply(st.error_reply(st.message({type="chat"}), "modify", "forbidden"), "cancel", "service-unavailable"); end, "got stanza of type error"); end); describe("util.error integration", function () it("should accept util.error objects", function () local s = st.message({ to = "touser", from = "fromuser", id = "123", type = "chat" }, "Hello"); local e = errors.new({ type = "modify", condition = "not-acceptable", text = "Bork bork bork" }, { by = "this.test" }); local r = st.error_reply(s, e); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(r.attr.type, "error"); assert.are.equal(r.tags[1].name, "error"); assert.are.equal(r.tags[1].attr.type, e.type); assert.are.equal(r.tags[1].tags[1].name, e.condition); assert.are.equal(r.tags[1].tags[2]:get_text(), e.text); assert.are.equal("this.test", r.tags[1].attr.by); end); it("should accept util.error objects with an URI", function () local s = st.message({ to = "touser", from = "fromuser", id = "123", type = "chat" }, "Hello"); local gone = errors.new({ condition = "gone", extra = { uri = "file:///dev/null" } }) local gonner = st.error_reply(s, gone); assert.are.equal("gone", gonner.tags[1].tags[1].name); assert.are.equal("file:///dev/null", gonner.tags[1].tags[1][1]); end); it("should accept util.error objects with application specific error", function () local s = st.message({ to = "touser", from = "fromuser", id = "123", type = "chat" }, "Hello"); local e = errors.new({ condition = "internal-server-error", text = "Namespaced thing happened", extra = {namespace="xmpp:example.test", condition="this-happened"} }) local r = st.error_reply(s, e); assert.are.equal("xmpp:example.test", r.tags[1].tags[3].attr.xmlns); assert.are.equal("this-happened", r.tags[1].tags[3].name); local e2 = errors.new({ condition = "internal-server-error", text = "Namespaced thing happened", extra = {tag=st.stanza("that-happened", { xmlns = "xmpp:example.test", ["another-attribute"] = "here" })} }) local r2 = st.error_reply(s, e2); assert.are.equal("xmpp:example.test", r2.tags[1].tags[3].attr.xmlns); assert.are.equal("that-happened", r2.tags[1].tags[3].name); assert.are.equal("here", r2.tags[1].tags[3].attr["another-attribute"]); end); end); end); describe("#get_error()", function () describe("basics", function () local s = st.message(); local e = st.error_reply(s, "cancel", "not-acceptable", "UNACCEPTABLE!!!! ONE MILLION YEARS DUNGEON!") :tag("dungeon", { xmlns = "urn:uuid:c9026187-5b05-4e70-b265-c3b6338a7d0f", period="1000000years"}); local typ, cond, text, extra = e:get_error(); assert.equal("cancel", typ); assert.equal("not-acceptable", cond); assert.equal("UNACCEPTABLE!!!! ONE MILLION YEARS DUNGEON!", text); assert.not_nil(extra) end) end) describe("should reject #invalid", function () local invalid_names = { ["empty string"] = "", ["characters"] = "<>"; } local invalid_data = { ["number"] = 1234, ["table"] = {}; ["utf8"] = string.char(0xF4, 0x90, 0x80, 0x80); ["nil"] = "nil"; ["boolean"] = true; ["control characters"] = "\0\1\2\3"; }; for value_type, value in pairs(invalid_names) do it(value_type.." in tag names", function () assert.error_matches(function () st.stanza(value); end, value_type); end); it(value_type.." in attribute names", function () assert.error_matches(function () st.stanza("valid", { [value] = "valid" }); end, value_type); end); end for value_type, value in pairs(invalid_data) do if value == "nil" then value = nil; end it(value_type.." in tag names", function () assert.error_matches(function () st.stanza(value); end, value_type); end); it(value_type.." in attribute names", function () assert.error_matches(function () st.stanza("valid", { [value] = "valid" }); end, value_type); end); if value ~= nil then it(value_type.." in attribute values", function () assert.error_matches(function () st.stanza("valid", { valid = value }); end, value_type); end); it(value_type.." in text node", function () assert.error_matches(function () st.stanza("valid"):text(value); end, value_type); end); end end end); describe("#is_stanza", function () -- is_stanza(any) -> boolean it("identifies stanzas as stanzas", function () assert.truthy(st.is_stanza(st.stanza("x"))); end); it("identifies strings as not stanzas", function () assert.falsy(st.is_stanza("")); end); it("identifies numbers as not stanzas", function () assert.falsy(st.is_stanza(1)); end); it("identifies tables as not stanzas", function () assert.falsy(st.is_stanza({})); end); end); describe("#remove_children", function () it("should work", function () local s = st.stanza("x", {xmlns="test"}) :tag("y", {xmlns="test"}):up() :tag("z", {xmlns="test2"}):up() :tag("x", {xmlns="test2"}):up() s:remove_children("x"); assert.falsy(s:get_child("x")) assert.truthy(s:get_child("z","test2")); assert.truthy(s:get_child("x","test2")); s:remove_children(nil, "test2"); assert.truthy(s:get_child("y")) assert.falsy(s:get_child(nil,"test2")); s:remove_children(); assert.falsy(s.tags[1]); end); end); describe("#maptags", function () it("should work", function () local s = st.stanza("test") :tag("one"):up() :tag("two"):up() :tag("one"):up() :tag("three"):up(); local function one_filter(tag) if tag.name == "one" then return nil; end return tag; end assert.equal(4, #s.tags); s:maptags(one_filter); assert.equal(2, #s.tags); end); it("should work with multiple consecutive text nodes", function () local s = st.deserialize({ "\n"; { "away"; name = "show"; attr = {}; }; "\n"; { "I am away"; name = "status"; attr = {}; }; "\n"; { "0"; name = "priority"; attr = {}; }; "\n"; { name = "c"; attr = { xmlns = "http://jabber.org/protocol/caps"; node = "http://psi-im.org"; hash = "sha-1"; }; }; "\n"; "\n"; name = "presence"; attr = { to = "user@example.com/jflsjfld"; from = "room@chat.example.org/nick"; }; }); assert.equal(4, #s.tags); s:maptags(function (tag) return tag; end); assert.equal(4, #s.tags); s:maptags(function (tag) if tag.name == "c" then return nil; end return tag; end); assert.equal(3, #s.tags); end); it("errors on invalid data - #981", function () local s = st.message({}, "Hello"); s.tags[1] = st.clone(s.tags[1]); assert.has_error_match(function () s:maptags(function () end); end, "Invalid stanza"); end); end); describe("get_child_with_attr", function () local s = st.message({ type = "chat" }) :text_tag("body", "Hello world", { ["xml:lang"] = "en" }) :text_tag("body", "Bonjour le monde", { ["xml:lang"] = "fr" }) :text_tag("body", "Hallo Welt", { ["xml:lang"] = "de" }) it("works", function () assert.equal(s:get_child_with_attr("body", nil, "xml:lang", "en"):get_text(), "Hello world"); assert.equal(s:get_child_with_attr("body", nil, "xml:lang", "de"):get_text(), "Hallo Welt"); assert.equal(s:get_child_with_attr("body", nil, "xml:lang", "fr"):get_text(), "Bonjour le monde"); assert.is_nil(s:get_child_with_attr("body", nil, "xml:lang", "FR")); assert.is_nil(s:get_child_with_attr("body", nil, "xml:lang", "es")); end); it("supports normalization", function () assert.equal(s:get_child_with_attr("body", nil, "xml:lang", "EN", string.upper):get_text(), "Hello world"); assert.is_nil(s:get_child_with_attr("body", nil, "xml:lang", "ES", string.upper)); end); end); describe("#clone", function () it("works", function () local s = st.message({type="chat"}, "Hello"):reset(); local c = st.clone(s); assert.same(s, c); end); it("works", function () assert.has_error(function () st.clone("this is not a stanza"); end); end); end); describe("top_tag", function () local xml_parse = require "util.xml".parse; it("works", function () local s = st.message({type="chat"}, "Hello"); local top_tag = s:top_tag(); assert.is_string(top_tag); assert.not_equal("/>", top_tag:sub(-2, -1)); assert.equal(">", top_tag:sub(-1, -1)); local s2 = xml_parse(top_tag.."
"); assert(st.is_stanza(s2)); assert.equal("message", s2.name); assert.equal(0, #s2); assert.equal(0, #s2.tags); assert.equal("chat", s2.attr.type); end); it("works with namespaced attributes", function () local s = xml_parse[[]]; local top_tag = s:top_tag(); assert.is_string(top_tag); assert.not_equal("/>", top_tag:sub(-2, -1)); assert.equal(">", top_tag:sub(-1, -1)); local s2 = xml_parse(top_tag..""); assert(st.is_stanza(s2)); assert.equal("message", s2.name); assert.equal(0, #s2); assert.equal(0, #s2.tags); assert.equal("true", s2.attr["my-awesome-ns\1bar"]); end); end); describe("indent", function () local s = st.stanza("foo"):text("\n"):tag("bar"):tag("baz"):up():text_tag("cow", "moo"); assert.equal("\n\t\n\t\t\n\t\tmoo\n\t\n", tostring(s:indent())); assert.equal("\n \n \n moo\n \n", tostring(s:indent(1, " "))); assert.equal("\n\t\t\n\t\t\t\n\t\t\tmoo\n\t\t\n\t", tostring(s:indent(2, "\t"))); end); describe("find", function() it("works", function() local s = st.stanza("root", { attr = "value" }):tag("child", { xmlns = "urn:example:not:same"; childattr = "thisvalue" }):text_tag("nested", "text"):reset(); assert.equal("value", s:find("@attr"), "finds attr") assert.equal(s:get_child("child", "urn:example:not:same"), s:find("{urn:example:not:same}child"), "equivalent to get_child") assert.equal("thisvalue", s:find("{urn:example:not:same}child@childattr"), "finds child attr") assert.equal("text", s:find("{urn:example:not:same}child/nested#"), "finds nested text") assert.is_nil(s:find("child"), "respects namespaces") end); end); end); prosody-0.12.4/spec/PaxHeaders/util_strbitop.lua0000644000000000000000000000012014475665104016724 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_strbitop.lua0000644000175000017500000000260714475665104021136 0ustar00prosodyprosody00000000000000local strbitop = require "util.strbitop"; describe("util.strbitop", function () describe("sand()", function () it("works", function () assert.equal(string.rep("Aa", 100), strbitop.sand(string.rep("a", 200), "Aa")); end); it("returns empty string if first argument is empty", function () assert.equal("", strbitop.sand("", "")); assert.equal("", strbitop.sand("", "key")); end); it("returns initial string if key is empty", function () assert.equal("hello", strbitop.sand("hello", "")); end); end); describe("sor()", function () it("works", function () assert.equal(string.rep("a", 200), strbitop.sor(string.rep("Aa", 100), "a")); end); it("returns empty string if first argument is empty", function () assert.equal("", strbitop.sor("", "")); assert.equal("", strbitop.sor("", "key")); end); it("returns initial string if key is empty", function () assert.equal("hello", strbitop.sor("hello", "")); end); end); describe("sxor()", function () it("works", function () assert.equal(string.rep("Aa", 100), strbitop.sxor(string.rep("a", 200), " \0")); end); it("returns empty string if first argument is empty", function () assert.equal("", strbitop.sxor("", "")); assert.equal("", strbitop.sxor("", "key")); end); it("returns initial string if key is empty", function () assert.equal("hello", strbitop.sxor("hello", "")); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_table_spec.lua0000644000000000000000000000012014475665104017157 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_table_spec.lua0000644000175000017500000000072514475665104021370 0ustar00prosodyprosody00000000000000local u_table = require "util.table"; describe("util.table", function () describe("create()", function () it("works", function () -- Can't test the allocated sizes of the table, so what you gonna do? assert.is.table(u_table.create(1,1)); end); end); describe("pack()", function () it("works", function () assert.same({ "lorem", "ipsum", "dolor", "sit", "amet", n = 5 }, u_table.pack("lorem", "ipsum", "dolor", "sit", "amet")); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_throttle_spec.lua0000644000000000000000000000012014475665104017735 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_throttle_spec.lua0000644000175000017500000001017514475665104022146 0ustar00prosodyprosody00000000000000 -- Mock util.time local now = 0; -- wibbly-wobbly... timey-wimey... stuff local function later(n) now = now + n; -- time passes at a different rate end package.loaded["util.time"] = { now = function() return now; end } local throttle = require "util.throttle"; describe("util.throttle", function() describe("#create()", function() it("should be created with correct values", function() now = 5; local a = throttle.create(3, 10); assert.same(a, { balance = 3, max = 3, rate = 0.3, t = 5 }); local a = throttle.create(3, 5); assert.same(a, { balance = 3, max = 3, rate = 0.6, t = 5 }); local a = throttle.create(1, 1); assert.same(a, { balance = 1, max = 1, rate = 1, t = 5 }); local a = throttle.create(10, 10); assert.same(a, { balance = 10, max = 10, rate = 1, t = 5 }); local a = throttle.create(10, 1); assert.same(a, { balance = 10, max = 10, rate = 10, t = 5 }); end); end); describe("#update()", function() it("does nothing when no time has passed, even if balance is not full", function() now = 5; local a = throttle.create(10, 10); for i=1,5 do a:update(); assert.same(a, { balance = 10, max = 10, rate = 1, t = 5 }); end a.balance = 0; for i=1,5 do a:update(); assert.same(a, { balance = 0, max = 10, rate = 1, t = 5 }); end end); it("updates only time when time passes but balance is full", function() now = 5; local a = throttle.create(10, 10); for i=1,5 do later(5); a:update(); assert.same(a, { balance = 10, max = 10, rate = 1, t = 5 + i*5 }); end end); it("updates balance when balance has room to grow as time passes", function() now = 5; local a = throttle.create(10, 10); a.balance = 0; assert.same(a, { balance = 0, max = 10, rate = 1, t = 5 }); later(1); a:update(); assert.same(a, { balance = 1, max = 10, rate = 1, t = 6 }); later(3); a:update(); assert.same(a, { balance = 4, max = 10, rate = 1, t = 9 }); later(10); a:update(); assert.same(a, { balance = 10, max = 10, rate = 1, t = 19 }); end); it("handles 10 x 0.1s updates the same as 1 x 1s update ", function() now = 5; local a = throttle.create(1, 1); a.balance = 0; later(1); a:update(); assert.same(a, { balance = 1, max = 1, rate = 1, t = now }); a.balance = 0; for i=1,10 do later(0.1); a:update(); end assert(math.abs(a.balance - 1) < 0.0001); -- incremental updates cause rounding errors end); end); -- describe("po") describe("#poll()", function() it("should only allow successful polls until cost is hit", function() now = 5; local a = throttle.create(3, 10); assert.same(a, { balance = 3, max = 3, rate = 0.3, t = 5 }); assert.is_true(a:poll(1)); -- 3 -> 2 assert.same(a, { balance = 2, max = 3, rate = 0.3, t = 5 }); assert.is_true(a:poll(2)); -- 2 -> 1 assert.same(a, { balance = 0, max = 3, rate = 0.3, t = 5 }); assert.is_false(a:poll(1)); -- MEEP, out of credits! assert.is_false(a:poll(1)); -- MEEP, out of credits! assert.same(a, { balance = 0, max = 3, rate = 0.3, t = 5 }); end); it("should not allow polls more than the cost", function() now = 0; local a = throttle.create(10, 10); assert.same(a, { balance = 10, max = 10, rate = 1, t = 0 }); assert.is_false(a:poll(11)); assert.same(a, { balance = 10, max = 10, rate = 1, t = 0 }); assert.is_true(a:poll(6)); assert.same(a, { balance = 4, max = 10, rate = 1, t = 0 }); assert.is_false(a:poll(5)); assert.same(a, { balance = 4, max = 10, rate = 1, t = 0 }); -- fractional assert.is_true(a:poll(3.5)); assert.same(a, { balance = 0.5, max = 10, rate = 1, t = 0 }); assert.is_true(a:poll(0.25)); assert.same(a, { balance = 0.25, max = 10, rate = 1, t = 0 }); assert.is_false(a:poll(0.3)); assert.same(a, { balance = 0.25, max = 10, rate = 1, t = 0 }); assert.is_true(a:poll(0.25)); assert.same(a, { balance = 0, max = 10, rate = 1, t = 0 }); assert.is_false(a:poll(0.1)); assert.same(a, { balance = 0, max = 10, rate = 1, t = 0 }); assert.is_true(a:poll(0)); assert.same(a, { balance = 0, max = 10, rate = 1, t = 0 }); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_time_spec.lua0000644000000000000000000000012014475665104017026 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_time_spec.lua0000644000175000017500000000120214475665104021226 0ustar00prosodyprosody00000000000000describe("util.time", function () local time; setup(function () time = require "util.time"; end); describe("now()", function () it("exists", function () assert.is_function(time.now); end); it("returns a number", function () assert.is_number(time.now()); end); end); describe("monotonic()", function () it("exists", function () assert.is_function(time.monotonic); end); it("returns a number", function () assert.is_number(time.monotonic()); end); it("time goes in one direction", function () local a = time.monotonic(); local b = time.monotonic(); assert.truthy(a <= b); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_uuid_spec.lua0000644000000000000000000000012014475665104017036 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_uuid_spec.lua0000644000175000017500000000115114475665104021241 0ustar00prosodyprosody00000000000000-- This tests the format, not the randomness local uuid = require "util.uuid"; describe("util.uuid", function() describe("#generate()", function() it("should work follow the UUID pattern", function() -- https://tools.ietf.org/html/rfc4122#section-4.4 local pattern = "^" .. table.concat({ string.rep("%x", 8), string.rep("%x", 4), "4" .. -- version string.rep("%x", 3), "[89ab]" .. -- reserved bits of 1 and 0 string.rep("%x", 3), string.rep("%x", 12), }, "%-") .. "$"; for _ = 1, 100 do assert.is_string(uuid.generate():match(pattern)); end end); end); end); prosody-0.12.4/spec/PaxHeaders/util_xml_spec.lua0000644000000000000000000000012014475665104016670 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_xml_spec.lua0000644000175000017500000000316514475665104021102 0ustar00prosodyprosody00000000000000 local xml = require "util.xml"; describe("util.xml", function() describe("#parse()", function() it("should work", function() local x = [[ ]] local stanza = xml.parse(x, {allow_comments = true}); assert.are.equal(stanza.tags[2].attr.xmlns, "b"); assert.are.equal(stanza.tags[2].namespaces["a"], "b"); end); it("should reject doctypes", function() local x = ""; local ok = xml.parse(x); assert.falsy(ok); end); it("should reject comments by default", function() local x = ""; local ok = xml.parse(x); assert.falsy(ok); end); it("should allow comments if asked nicely", function() local x = ""; local stanza = xml.parse(x, {allow_comments = true}); assert.are.equal(stanza.name, "foo"); assert.are.equal(#stanza, 0); end); it("should reject processing instructions", function() local x = ""; local ok = xml.parse(x); assert.falsy(ok); end); it("should allow processing instructions if asked nicely", function() local x = ""; local stanza = xml.parse(x, {allow_processing_instructions = true}); assert.truthy(stanza); assert.are.equal(stanza.name, "foo"); end); it("should allow an xml declaration", function() local x = ""; local stanza = xml.parse(x); assert.truthy(stanza); assert.are.equal(stanza.name, "foo"); end); end); end); prosody-0.12.4/spec/PaxHeaders/util_xmppstream_spec.lua0000644000000000000000000000012014475665104020270 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/spec/util_xmppstream_spec.lua0000644000175000017500000001116214475665104022476 0ustar00prosodyprosody00000000000000 local xmppstream = require "util.xmppstream"; describe("util.xmppstream", function() local function test(xml, expect_success, ex) local stanzas = {}; local session = { notopen = true }; local callbacks = { stream_ns = "streamns"; stream_tag = "stream"; default_ns = "stanzans"; streamopened = function (_session) assert.are.equal(session, _session); assert.are.equal(session.notopen, true); _session.notopen = nil; return true; end; handlestanza = function (_session, stanza) assert.are.equal(session, _session); assert.are.equal(_session.notopen, nil); table.insert(stanzas, stanza); end; streamclosed = function (_session) assert.are.equal(session, _session); assert.are.equal(_session.notopen, nil); _session.notopen = nil; end; } if type(ex) == "table" then for k, v in pairs(ex) do if k ~= "_size_limit" then callbacks[k] = v; end end end local stream = xmppstream.new(session, callbacks, ex and ex._size_limit or nil); local ok, err = pcall(function () assert(stream:feed(xml)); end); if ok and type(expect_success) == "function" then expect_success(stanzas); end assert.are.equal(not not ok, not not expect_success, "Expected "..(expect_success and ("success ("..tostring(err)..")") or "failure")); end local function test_stanza(stanza, expect_success, ex) return test([[]]..stanza, expect_success, ex); end describe("#new()", function() it("should work", function() test([[]], true); test([[]], true); -- Incorrect stream tag name should be rejected test([[]], false); -- Incorrect stream namespace should be rejected test([[]], false); -- Invalid XML should be rejected test("<>", false); test_stanza("", function (stanzas) assert.are.equal(#stanzas, 1); assert.are.equal(stanzas[1].name, "message"); end); test_stanza("< message>>>>/>\n", false); test_stanza([[ ]], function (stanzas) assert.are.equal(#stanzas, 1); local s = stanzas[1]; assert.are.equal(s.name, "x"); assert.are.equal(#s.tags, 2); assert.are.equal(s.tags[1].name, "y"); assert.are.equal(s.tags[1].attr.xmlns, nil); assert.are.equal(s.tags[1].tags[1].name, "z"); assert.are.equal(s.tags[1].tags[1].attr.xmlns, "c"); assert.are.equal(s.tags[2].name, "z"); assert.are.equal(s.tags[2].attr.xmlns, "b"); assert.are.equal(s.namespaces, nil); end); end); end); it("should allow an XML declaration", function () test([[]], true); test([[]], true); test([[]], true); end); it("should not accept XML versions other than 1.0", function () test([[]], false); end); it("should not allow a misplaced XML declaration", function () test([[]], false); end); describe("should forbid restricted XML:", function () it("comments", function () test_stanza("", false); end); it("DOCTYPE", function () test([[]], false); end); it("incorrect encoding specification", function () -- This is actually caught by the underlying XML parser test([[]], false); end); it("non-UTF8 encodings: ISO-8859-1", function () test([[]], false); end); it("non-UTF8 encodings: UTF-16", function () -- -- encoded into UTF-16 local hx = ([[fffe3c003f0078006d006c002000760065007200730069006f006e003d00 220031002e0030002200200065006e0063006f00640069006e0067003d00 22005500540046002d003100360022003f003e003c007300740072006500 61006d00200078006d006c006e0073003d00220073007400720065006100 6d006e00730022002f003e00]]):gsub("%x%x", function (c) return string.char(tonumber(c, 16)); end); test(hx, false); end); it("processing instructions", function () test([[]], false); end); end); end); prosody-0.12.4/PaxHeaders/teal-src0000644000000000000000000000013214475665104014024 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.736764521 30 ctime=1693936196.736764521 prosody-0.12.4/teal-src/0000755000175000017500000000000014475665104016303 5ustar00prosodyprosody00000000000000prosody-0.12.4/teal-src/PaxHeaders/module.d.tl0000644000000000000000000000012014475665104016146 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/teal-src/module.d.tl0000644000175000017500000001045614475665104020361 0ustar00prosodyprosody00000000000000local st = require"util.stanza" global record moduleapi get_name : function (moduleapi) : string get_host : function (moduleapi) : string enum host_type "global" "local" "component" end get_host_type : function (moduleapi) : host_type set_global : function (moduleapi) add_feature : function (moduleapi, string) add_identity : function (moduleapi, string, string, string) -- TODO enum? add_extension : function (moduleapi, st.stanza_t) fire_event : function (moduleapi, string, any) : any type handler = function (any) : any record util_events -- TODO import def end hook_object_event : function (moduleapi, util_events, string, handler, number) unhook_object_event : function (moduleapi, util_events, string, handler) hook : function (moduleapi, string, handler, number) hook_global : function (moduleapi, string, handler, number) hook_tag : function (moduleapi, string, string, handler, number) unhook : function (moduleapi, string, handler) wrap_object_event : function (moduleapi, util_events, string, handler) wrap_event : function (moduleapi, string, handler) wrap_global : function (moduleapi, string, handler) require : function (moduleapi, string) : table depends : function (moduleapi, string) : table shared : function (moduleapi, string) : table type config_getter = function
(moduleapi, string, A) : A get_option : config_getter get_option_scalar : config_getter get_option_string : config_getter get_option_number : config_getter get_option_boolean : config_getter record util_array -- TODO import def { any } end get_option_array : config_getter record util_set -- TODO import def _items : { any : boolean } end get_option_set : function (moduleapi, string, { any }) : util_set get_option_inherited_set : function (moduleapi, string, { any }) : util_set get_option_path : function (moduleapi, string, string, string) : string context : function (moduleapi, string) : moduleapi add_item : function (moduleapi, string, any) remove_item : function (moduleapi, string, any) get_host_items : function (moduleapi, string) : { any } handle_items : function (moduleapi, string, handler, handler, boolean) provides : function (moduleapi, string, table) record util_session -- TODO import def send : function ( st.stanza_t | string ) end send : function (moduleapi, st.stanza_t, util_session) send_iq : function (moduleapi, st.stanza_t, util_session, number) broadcast : function (moduleapi, { string }, st.stanza_t, function) type timer_callback = function (number, ... : any) : number add_timer : function (moduleapi, number, timer_callback, ... : any) get_directory : function (moduleapi) : string enum file_mode "r" "w" "a" "r+" "w+" "a+" end load_resource : function (moduleapi, string, file_mode) : FILE enum store_type "keyval" "map" "archive" end open_store : function (moduleapi, string, store_type) enum stat_type "amount" "counter" "rate" "distribution" "sizes" "times" end record stats_conf initial : number units : string type : string end measure : function (moduleapi, string, stat_type, stats_conf) measure_object_event : function (moduleapi, util_events, string, string) measure_event : function (moduleapi, string, string) measure_global_event : function (moduleapi, string, string) enum status_type "error" "warn" "info" "core" end set_status : function (moduleapi, status_type, string, boolean) enum log_level "debug" "info" "warn" "error" end log_status : function (moduleapi, log_level, string, ... : any) get_status : function (moduleapi) : status_type, string, number -- added by modulemanager name : string host : string _log : function (log_level, string, ... : any) log : function (moduleapi, log_level, string, ... : any) reloading : boolean saved_state : any record module_environment module : moduleapi end environment : module_environment path : string resource_path : string -- methods the module can add load : function () add_host : function (moduleapi) save : function () : any restore : function (any) unload : function () end global module : moduleapi global record common_event stanza : st.stanza_t record origin send : function (st.stanza_t) end end global record prosody version : string end return module prosody-0.12.4/teal-src/PaxHeaders/plugins0000644000000000000000000000013214475665104015505 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.736764521 30 ctime=1693936196.736764521 prosody-0.12.4/teal-src/plugins/0000755000175000017500000000000014475665104017764 5ustar00prosodyprosody00000000000000prosody-0.12.4/teal-src/plugins/PaxHeaders/mod_cron.tl0000644000000000000000000000012014475665104017720 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/teal-src/plugins/mod_cron.tl0000644000175000017500000000561414475665104022133 0ustar00prosodyprosody00000000000000module:set_global(); local async = require "util.async"; local datetime = require "util.datetime"; local record map_store -- TODO move to somewhere sensible get : function (map_store, string, K) : V set : function (map_store, string, K, V) end local enum frequency "hourly" "daily" "weekly" end local record task_spec id : string -- unique id name : string -- name or short description when : frequency last : integer run : function (task_spec, integer) save : function (task_spec, integer) end local record task_event source : module item : task_spec end local periods : { frequency : integer } = { hourly = 3600, daily = 86400, weekly = 7*86400 } local active_hosts : { string : boolean } = { } function module.add_host(host_module : moduleapi) local last_run_times = host_module:open_store("cron", "map") as map_store; active_hosts[host_module.host] = true; local function save_task(task : task_spec, started_at : integer) last_run_times:set(nil, task.id, started_at); end local function task_added(event : task_event) : boolean local task = event.item; if task.name == nil then task.name = task.when; end if task.id == nil then task.id = event.source.name .. "/" .. task.name:gsub("%W", "_"):lower(); end if task.last == nil then task.last = last_run_times:get(nil, task.id); end task.save = save_task; module:log("debug", "%s task %s added, last run %s", task.when, task.id, task.last and datetime.datetime(task.last) or "never"); if task.last == nil then -- initialize new tasks so e.g. daily tasks run at ~midnight UTC for now local now = os.time(); task.last = now - now % periods[task.when]; end return true; end local function task_removed(event : task_event) : boolean local task = event.item; host_module:log("debug", "Task %s removed", task.id); return true; end host_module:handle_items("task", task_added, task_removed, true); function host_module.unload() active_hosts[host_module.host]=nil; end end local function should_run(when : frequency, last : integer) : boolean return not last or last + periods[when]*0.995 <= os.time(); end local function run_task(task : task_spec) local started_at = os.time(); task:run(started_at); task.last = started_at; task:save(started_at); end local task_runner = async.runner(run_task); module:add_timer(1, function() : integer module:log("info", "Running periodic tasks"); local delay = 3600; for host in pairs(active_hosts) do module:log("debug", "Running periodic tasks for host %s", host); for _, task in ipairs(module:context(host):get_host_items("task") as { task_spec } ) do module:log("debug", "Considering %s task %s (%s)", task.when, task.id, task.run); if should_run(task.when, task.last) then task_runner:run(task); end end end module:log("debug", "Wait %ds", delay); return delay; end); -- TODO measure load, pick a good time to do stuff prosody-0.12.4/teal-src/PaxHeaders/util0000644000000000000000000000013214475665104015001 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.736764521 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/0000755000175000017500000000000014475665104017260 5ustar00prosodyprosody00000000000000prosody-0.12.4/teal-src/util/PaxHeaders/compat.d.tl0000644000000000000000000000012014475665104017121 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.736764521 prosody-0.12.4/teal-src/util/compat.d.tl0000644000175000017500000000013614475665104021326 0ustar00prosodyprosody00000000000000local record lib xpcall : function (function, function, ...:any):boolean, any end return lib prosody-0.12.4/teal-src/util/PaxHeaders/crand.d.tl0000644000000000000000000000012014475665104016725 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/crand.d.tl0000644000175000017500000000022114475665104021125 0ustar00prosodyprosody00000000000000local record lib bytes : function (n : integer) : string enum sourceid "OpenSSL" "arc4random()" "Linux" end _source : sourceid end return lib prosody-0.12.4/teal-src/util/PaxHeaders/dataforms.d.tl0000644000000000000000000000012014475665104017616 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/dataforms.d.tl0000644000175000017500000000143714475665104022030 0ustar00prosodyprosody00000000000000local stanza_t = require "util.stanza".stanza_t local enum form_type "form" "submit" "cancel" "result" end local enum field_type "boolean" "fixed" "hidden" "jid-multi" "jid-single" "list-multi" "list-single" "text-multi" "text-private" "text-single" end local record form_field type : field_type var : string -- protocol name name : string -- internal name label : string desc : string datatype : string range_min : number range_max : number value : any -- depends on field_type options : table end local record dataform title : string instructions : string { form_field } -- XXX https://github.com/teal-language/tl/pull/415 form : function ( dataform, table, form_type ) : stanza_t end local record lib new : function ( dataform ) : dataform end return lib prosody-0.12.4/teal-src/util/PaxHeaders/datamapper.tl0000644000000000000000000000012014475665104017532 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/datamapper.tl0000644000175000017500000002756514475665104021756 0ustar00prosodyprosody00000000000000-- Copyright (C) 2021 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- Based on -- https://json-schema.org/draft/2020-12/json-schema-core.html -- https://json-schema.org/draft/2020-12/json-schema-validation.html -- http://spec.openapis.org/oas/v3.0.1#xmlObject -- https://github.com/OAI/OpenAPI-Specification/issues/630 (text:true) -- -- XML Object Extensions: -- text to refer to the text content at the same time as attributes -- x_name_is_value for enum fields where the is the value -- x_single_attribute for -- -- TODO pointers -- TODO cleanup / refactor -- TODO s/number/integer/ once we have appropriate math.type() compat -- local st = require "util.stanza"; local json = require"util.json" local pointer = require"util.jsonpointer"; local json_type_name = json.json_type_name; local json_schema_object = require "util.jsonschema" local type schema_t = boolean | json_schema_object local function toboolean ( s : string ) : boolean if s == "true" or s == "1" then return true elseif s == "false" or s == "0" then return false elseif s then return true end end local function totype(t : json_type_name, s : string) : any if not s then return nil end if t == "string" then return s; elseif t == "boolean" then return toboolean(s) elseif t == "number" or t == "integer" then return tonumber(s) end end local enum value_goes "in_tag_name" "in_text" "in_text_tag" "in_attribute" "in_single_attribute" "in_children" "in_wrapper" end local function resolve_schema(schema : schema_t, root : json_schema_object) : schema_t if schema is json_schema_object then if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then return pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t; end end return schema; end local function guess_schema_type(schema : json_schema_object) : json_type_name local schema_types = schema.type if schema_types is json_type_name then return schema_types elseif schema_types ~= nil then error "schema has unsupported 'type' property" elseif schema.properties then return "object" elseif schema.items then return "array" end return "string" -- default assumption end local function unpack_propschema( propschema : schema_t, propname : string, current_ns : string ) : json_type_name, value_goes, string, string, string, string, { any } local proptype : json_type_name = "string" local value_where : value_goes = propname and "in_text_tag" or "in_text" local name = propname local namespace : string local prefix : string local single_attribute : string local enums : { any } if propschema is json_schema_object then proptype = guess_schema_type(propschema); elseif propschema is string then -- Teal says this can never be a string, but it could before so best be sure error("schema as string is not supported: "..propschema.." {"..current_ns.."}"..propname) end if proptype == "object" or proptype == "array" then value_where = "in_children" end if propschema is json_schema_object then local xml = propschema.xml if xml then if xml.name then name = xml.name end if xml.namespace and xml.namespace ~= current_ns then namespace = xml.namespace end if xml.prefix then prefix = xml.prefix end if proptype == "array" and xml.wrapped then value_where = "in_wrapper" elseif xml.attribute then value_where = "in_attribute" elseif xml.text then value_where = "in_text" elseif xml.x_name_is_value then value_where = "in_tag_name" elseif xml.x_single_attribute then single_attribute = xml.x_single_attribute value_where = "in_single_attribute" end end if propschema["const"] then enums = { propschema["const"] } elseif propschema["enum"] then enums = propschema["enum"] end end if current_ns == "urn:xmpp:reactions:0" and name == "reactions" then assert(proptype=="array") end return proptype, value_where, name, namespace, prefix, single_attribute, enums end local parse_object : function (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { string : any } local parse_array : function (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { any } local function extract_value (s : st.stanza_t, value_where : value_goes, proptype : json.json_type_name, name : string, namespace : string, prefix : string, single_attribute : string, enums : { any }) : string if value_where == "in_tag_name" then local c : st.stanza_t if proptype == "boolean" then c = s:get_child(name, namespace); elseif enums and proptype == "string" then -- XXX O(n²) ? -- Probably better to flip the table and loop over :childtags(nil, ns), should be 2xO(n) -- BUT works first, optimize later for i = 1, #enums do c = s:get_child(enums[i] as string, namespace); if c then break end end else c = s:get_child(nil, namespace); end if c then return c.name; end elseif value_where == "in_attribute" then local attr = name if prefix then attr = prefix .. ':' .. name elseif namespace and namespace ~= s.attr.xmlns then attr = namespace .. "\1" .. name end return s.attr[attr] elseif value_where == "in_text" then return s:get_text() elseif value_where == "in_single_attribute" then local c = s:get_child(name, namespace) return c and c.attr[single_attribute] elseif value_where == "in_text_tag" then return s:get_child_text(name, namespace) end end function parse_object (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { string : any } local out : { string : any } = {} schema = resolve_schema(schema, root) if schema is json_schema_object and schema.properties then for prop, propschema in pairs(schema.properties) do propschema = resolve_schema(propschema, root) local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns) if value_where == "in_children" and propschema is json_schema_object then if proptype == "object" then local c = s:get_child(name, namespace) if c then out[prop] = parse_object(propschema, c, root); end elseif proptype == "array" then local a = parse_array(propschema, s, root); if a and a[1] ~= nil then out[prop] = a; end else error "unreachable" end elseif value_where == "in_wrapper" and propschema is json_schema_object and proptype == "array" then local wrapper = s:get_child(name, namespace); if wrapper then out[prop] = parse_array(propschema, wrapper, root); end else local value : string = extract_value (s, value_where, proptype, name, namespace, prefix, single_attribute, enums) out[prop] = totype(proptype, value) end end end return out end function parse_array (schema : json_schema_object, s : st.stanza_t, root : json_schema_object) : { any } local itemschema : schema_t = resolve_schema(schema.items, root); local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns) local attr_name : string if value_where == "in_single_attribute" then -- FIXME this shouldn't be needed value_where = "in_attribute"; attr_name = single_attribute; end local out : { any } = {} if proptype == "object" then if itemschema is json_schema_object then for c in s:childtags(child_name, namespace) do table.insert(out, parse_object(itemschema, c, root)); end else error "array items must be schema object" end elseif proptype == "array" then if itemschema is json_schema_object then for c in s:childtags(child_name, namespace) do table.insert(out, parse_array(itemschema, c, root)); end end else for c in s:childtags(child_name, namespace) do local value : string = extract_value (c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums) table.insert(out, totype(proptype, value)); end end return out; end local function parse (schema : json_schema_object, s : st.stanza_t) : table local s_type = guess_schema_type(schema) if s_type == "object" then return parse_object(schema, s, schema) elseif s_type == "array" then return parse_array(schema, s, schema) else error "top-level scalars unsupported" end end local function toxmlstring(proptype : json_type_name, v : any) : string if proptype == "string" and v is string then return v elseif proptype == "number" and v is number then return string.format("%g", v) elseif proptype == "integer" and v is number then -- TODO is integer return string.format("%d", v) elseif proptype == "boolean" then return v and "1" or "0" end end local unparse : function (json_schema_object, table, string, string, st.stanza_t, json_schema_object) : st.stanza_t local function unparse_property(out : st.stanza_t, v : any, proptype : json_type_name, propschema : schema_t, value_where : value_goes, name : string, namespace : string, current_ns : string, prefix : string, single_attribute : string, root : json_schema_object) if value_where == "in_attribute" then local attr = name if prefix then attr = prefix .. ':' .. name elseif namespace and namespace ~= current_ns then attr = namespace .. "\1" .. name end out.attr[attr] = toxmlstring(proptype, v) elseif value_where == "in_text" then out:text(toxmlstring(proptype, v)) elseif value_where == "in_single_attribute" then assert(single_attribute) local propattr : { string : string } = {} if namespace and namespace ~= current_ns then propattr.xmlns = namespace end propattr[single_attribute] = toxmlstring(proptype, v) out:tag(name, propattr):up(); else local propattr : { string : string } if namespace ~= current_ns then propattr = { xmlns = namespace } end if value_where == "in_tag_name" then if proptype == "string" and v is string then out:tag(v, propattr):up(); elseif proptype == "boolean" and v == true then out:tag(name, propattr):up(); end elseif proptype == "object" and propschema is json_schema_object and v is table then local c = unparse(propschema, v, name, namespace, nil, root); if c then out:add_direct_child(c); end elseif proptype == "array" and propschema is json_schema_object and v is table then if value_where == "in_wrapper" then local c = unparse(propschema, v, name, namespace, nil, root); if c then out:add_direct_child(c); end else unparse(propschema, v, name, namespace, out, root); end else out:text_tag(name, toxmlstring(proptype, v), propattr) end end end function unparse ( schema : json_schema_object, t : table, current_name : string, current_ns : string, ctx : st.stanza_t, root : json_schema_object ) : st.stanza_t if root == nil then root = schema end if schema.xml then if schema.xml.name then current_name = schema.xml.name end if schema.xml.namespace then current_ns = schema.xml.namespace end -- TODO prefix? end local out = ctx or st.stanza(current_name, { xmlns = current_ns }) local s_type = guess_schema_type(schema) if s_type == "object" then for prop, propschema in pairs(schema.properties) do propschema = resolve_schema(propschema, root) local v = t[prop] if v ~= nil then local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns) unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) end end return out; elseif s_type == "array" then local itemschema = resolve_schema(schema.items, root) local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns) for _, item in ipairs(t as { string }) do unparse_property(out, item, proptype, itemschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) end return out; end end return { parse = parse, unparse = unparse, } prosody-0.12.4/teal-src/util/PaxHeaders/datetime.d.tl0000644000000000000000000000012014475665104017432 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/datetime.d.tl0000644000175000017500000000047014475665104021640 0ustar00prosodyprosody00000000000000-- TODO s/number/integer/ once Teal gets support for that local record lib date : function (t : integer) : string datetime : function (t : integer) : string time : function (t : integer) : string legacy : function (t : integer) : string parse : function (t : string) : integer end return lib prosody-0.12.4/teal-src/util/PaxHeaders/encodings.d.tl0000644000000000000000000000012014475665104017607 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/encodings.d.tl0000644000175000017500000000136114475665104022015 0ustar00prosodyprosody00000000000000-- TODO many actually return Maybe(String) local record lib record base64 encode : function (s : string) : string decode : function (s : string) : string end record stringprep nameprep : function (s : string, strict : boolean) : string nodeprep : function (s : string, strict : boolean) : string resourceprep : function (s : string, strict : boolean) : string saslprep : function (s : string, strict : boolean) : string end record idna to_ascii : function (s : string) : string to_unicode : function (s : string) : string end record utf8 valid : function (s : string) : boolean length : function (s : string) : integer end record confusable skeleton : function (s : string) : string end version : string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/error.d.tl0000644000000000000000000000012014475665104016767 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/error.d.tl0000644000175000017500000000337614475665104021205 0ustar00prosodyprosody00000000000000local enum error_type "auth" "cancel" "continue" "modify" "wait" end local enum error_condition "bad-request" "conflict" "feature-not-implemented" "forbidden" "gone" "internal-server-error" "item-not-found" "jid-malformed" "not-acceptable" "not-allowed" "not-authorized" "policy-violation" "recipient-unavailable" "redirect" "registration-required" "remote-server-not-found" "remote-server-timeout" "resource-constraint" "service-unavailable" "subscription-required" "undefined-condition" "unexpected-request" end local record protoerror type : error_type condition : error_condition text : string code : integer end local record error type : error_type condition : error_condition text : string code : integer context : { any : any } source : string end local type compact_registry_item = { string, string, string, string } local type compact_registry = { compact_registry_item } local type registry = { string : protoerror } local type context = { string : any } local record error_registry_wrapper source : string registry : registry new : function (string, context) : error coerce : function (any, string) : any, error wrap : function (error) : error wrap : function (string, context) : error is_error : function (any) : boolean end local record lib record configure_opt auto_inject_traceback : boolean end new : function (protoerror, context, { string : protoerror }, string) : error init : function (string, string, registry | compact_registry) : error_registry_wrapper init : function (string, registry | compact_registry) : error_registry_wrapper is_error : function (any) : boolean coerce : function (any, string) : any, error from_stanza : function (table, context, string) : error configure : function end return lib prosody-0.12.4/teal-src/util/PaxHeaders/format.d.tl0000644000000000000000000000012014475665104017126 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/format.d.tl0000644000175000017500000000012014475665104021324 0ustar00prosodyprosody00000000000000local record lib format : function (string, ... : any) : string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/hashes.d.tl0000644000000000000000000000012014475665104017111 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/hashes.d.tl0000644000175000017500000000110614475665104021314 0ustar00prosodyprosody00000000000000local type hash = function (msg : string, hex : boolean) : string local type hmac = function (key : string, msg : string, hex : boolean) : string local type kdf = function (pass : string, salt : string, i : integer) : string local record lib sha1 : hash sha256 : hash sha224 : hash sha384 : hash sha512 : hash md5 : hash hmac_sha1 : hmac hmac_sha256 : hmac hmac_sha512 : hmac hmac_md5 : hmac scram_Hi_sha1 : kdf pbkdf2_hmac_sha1 : kdf pbkdf2_hmac_sha256 : kdf equals : function (string, string) : boolean version : string _LIBCRYPTO_VERSION : string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/hex.d.tl0000644000000000000000000000012014475665104016422 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/hex.d.tl0000644000175000017500000000014614475665104020630 0ustar00prosodyprosody00000000000000local type s2s = function (s : string) : string local record lib to : s2s from : s2s end return lib prosody-0.12.4/teal-src/util/PaxHeaders/http.d.tl0000644000000000000000000000012014475665104016615 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/http.d.tl0000644000175000017500000000055414475665104021026 0ustar00prosodyprosody00000000000000local record lib urlencode : function (s : string) : string urldecode : function (s : string) : string formencode : function (f : { string : string }) : string formdecode : function (s : string) : { string : string } contains_token : function (field : string, token : string) : boolean normalize_path : function (path : string) : string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/human0000644000000000000000000000013214475665104016111 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.740764481 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/human/0000755000175000017500000000000014475665104020370 5ustar00prosodyprosody00000000000000prosody-0.12.4/teal-src/util/human/PaxHeaders/units.d.tl0000644000000000000000000000012014475665104020110 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/human/units.d.tl0000644000175000017500000000021414475665104022312 0ustar00prosodyprosody00000000000000local lib = record adjust : function (number, string) : number, string format : function (number, string, string) : string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/id.d.tl0000644000000000000000000000012014475665104016232 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/id.d.tl0000644000175000017500000000026014475665104020435 0ustar00prosodyprosody00000000000000local record lib short : function () : string medium : function () : string long : function () : string custom : function (integer) : function () : string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/interpolation.d.tl0000644000000000000000000000012014475665104020525 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/interpolation.d.tl0000644000175000017500000000034014475665104022727 0ustar00prosodyprosody00000000000000local type renderer = function (string, { string : any }) : string local type filter = function (string, any) : string local record lib new : function (string, string, funcs : { string : filter }) : renderer end return lib prosody-0.12.4/teal-src/util/PaxHeaders/jid.d.tl0000644000000000000000000000012014475665104016404 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/jid.d.tl0000644000175000017500000000100414475665104020604 0ustar00prosodyprosody00000000000000local record lib split : function (string) : string, string, string bare : function (string) : string prepped_split : function (string, boolean) : string, string, string join : function (string, string, string) : string prep : function (string, boolean) : string compare : function (string, string) : boolean node : function (string) : string host : function (string) : string resource : function (string) : string escape : function (string) : string unescape : function (string) : string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/json.d.tl0000644000000000000000000000012014475665104016607 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.740764481 prosody-0.12.4/teal-src/util/json.d.tl0000644000175000017500000000037614475665104021022 0ustar00prosodyprosody00000000000000local record lib encode : function (any) : string decode : function (string) : any, string enum json_type_name "null" "boolean" "object" "array" "number" "string" "integer" end type null_type = (nil) null : null_type end return lib prosody-0.12.4/teal-src/util/PaxHeaders/jsonpointer.tl0000644000000000000000000000012014475665104017766 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/jsonpointer.tl0000644000175000017500000000166114475665104022177 0ustar00prosodyprosody00000000000000 local enum ptr_error "invalid-table" "invalid-path" end local function unescape_token(escaped_token : string) : string local unescaped = escaped_token:gsub("~1", "/"):gsub("~0", "~") return unescaped end local function resolve_json_pointer(ref : table, path : string) : any, ptr_error local ptr_len = #path+1 for part, pos in path:gmatch("/([^/]*)()") do local token = unescape_token(part) if not ref is table then return nil end local idx = next(ref) local new_ref : any if idx is string then new_ref = ref[token] elseif idx is integer then local i = tonumber(token) if token == "-" then i = #ref + 1 end new_ref = ref[i+1] else return nil, "invalid-table" end if pos as integer == ptr_len then return new_ref elseif new_ref is table then ref = new_ref elseif not ref is table then return nil, "invalid-path" end end return ref end return { resolve = resolve_json_pointer, } prosody-0.12.4/teal-src/util/PaxHeaders/jsonschema.tl0000644000000000000000000000012014475665104017546 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/jsonschema.tl0000644000175000017500000001746114475665104021764 0ustar00prosodyprosody00000000000000-- Copyright (C) 2021 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- Based on -- https://json-schema.org/draft/2020-12/json-schema-core.html -- https://json-schema.org/draft/2020-12/json-schema-validation.html -- local json = require"util.json" local null = json.null; local pointer = require "util.jsonpointer" local type json_type_name = json.json_type_name -- json_type_name here is non-standard local type schema_t = boolean | json_schema_object local record json_schema_object type json_type_name = json.json_type_name type schema_object = json_schema_object type : json_type_name | { json_type_name } enum : { any } const : any allOf : { schema_t } anyOf : { schema_t } oneOf : { schema_t } ["not"] : schema_t ["if"] : schema_t ["then"] : schema_t ["else"] : schema_t ["$ref"] : string -- numbers multipleOf : number maximum : number exclusiveMaximum : number minimum : number exclusiveMinimum : number -- strings maxLength : integer minLength : integer pattern : string -- NYI format : string -- arrays prefixItems : { schema_t } items : schema_t contains : schema_t maxItems : integer minItems : integer uniqueItems : boolean maxContains : integer -- NYI minContains : integer -- NYI -- objects properties : { string : schema_t } maxProperties : integer -- NYI minProperties : integer -- NYI required : { string } dependentRequired : { string : { string } } additionalProperties: schema_t patternProperties: schema_t -- NYI propertyNames : schema_t -- xml record xml_t name : string namespace : string prefix : string attribute : boolean wrapped : boolean -- nonstantard, maybe in the future text : boolean x_name_is_value : boolean x_single_attribute : string end xml : xml_t -- descriptive title : string description : string deprecated : boolean readOnly : boolean writeOnly : boolean -- methods validate : function ( schema_t, any, json_schema_object ) : boolean end -- TODO validator function per schema property local function simple_validate(schema : json_type_name | { json_type_name }, data : any) : boolean if schema == nil then return true elseif schema == "object" and data is table then return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "string") elseif schema == "array" and data is table then return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "number") elseif schema == "integer" then return math.type(data) == schema elseif schema == "null" then return data == null elseif schema is { json_type_name } then for _, one in ipairs(schema as { json_type_name }) do if simple_validate(one, data) then return true end end return false else return type(data) == schema end end local complex_validate : function ( json_schema_object, any, json_schema_object ) : boolean local function validate (schema : schema_t, data : any, root : json_schema_object) : boolean if schema is boolean then return schema else return complex_validate(schema, data, root) end end function complex_validate (schema : json_schema_object, data : any, root : json_schema_object) : boolean if root == nil then root = schema end if schema["$ref"] and schema["$ref"]:sub(1,1) == "#" then local referenced = pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t if referenced ~= nil and referenced ~= root and referenced ~= schema then if not validate(referenced, data, root) then return false; end end end if not simple_validate(schema.type, data) then return false; end if schema.type == "object" then if data is table then -- just check that there the keys are all strings for k in pairs(data) do if not k is string then return false end end end end if schema.type == "array" then if data is table then -- just check that there the keys are all numbers for i in pairs(data) do if not i is integer then return false end end end end if schema["enum"] ~= nil then local match = false for _, v in ipairs(schema["enum"]) do if v == data then -- FIXME supposed to do deep-compare match = true break end end if not match then return false end end -- XXX this is measured in byte, while JSON measures in ... bork -- TODO use utf8.len? if data is string then if schema.maxLength and #data > schema.maxLength then return false end if schema.minLength and #data < schema.minLength then return false end end if data is number then if schema.multipleOf and (data == 0 or data % schema.multipleOf ~= 0) then return false end if schema.maximum and not ( data <= schema.maximum ) then return false end if schema.exclusiveMaximum and not ( data < schema.exclusiveMaximum ) then return false end if schema.minimum and not ( data >= schema.minimum ) then return false end if schema.exclusiveMinimum and not ( data > schema.exclusiveMinimum ) then return false end end if schema.allOf then for _, sub in ipairs(schema.allOf) do if not validate(sub, data, root) then return false end end end if schema.oneOf then local valid = 0 for _, sub in ipairs(schema.oneOf) do if validate(sub, data, root) then valid = valid + 1 end end if valid ~= 1 then return false end end if schema.anyOf then local match = false for _, sub in ipairs(schema.anyOf) do if validate(sub, data, root) then match = true break end end if not match then return false end end if schema["not"] then if validate(schema["not"], data, root) then return false end end if schema["if"] ~= nil then if validate(schema["if"], data, root) then if schema["then"] then return validate(schema["then"], data, root) end else if schema["else"] then return validate(schema["else"], data, root) end end end if schema.const ~= nil and schema.const ~= data then return false end if data is table then if schema.maxItems and #data > schema.maxItems then return false end if schema.minItems and #data < schema.minItems then return false end if schema.required then for _, k in ipairs(schema.required) do if data[k] == nil then return false end end end if schema.propertyNames ~= nil then for k in pairs(data) do if not validate(schema.propertyNames, k, root) then return false end end end if schema.properties then for k, sub in pairs(schema.properties) do if data[k] ~= nil and not validate(sub, data[k], root) then return false end end end if schema.additionalProperties ~= nil then for k, v in pairs(data) do if schema.properties == nil or schema.properties[k as string] == nil then if not validate(schema.additionalProperties, v, root) then return false end end end end if schema.uniqueItems then -- only works for scalars, would need to deep-compare for objects/arrays/tables local values : { any : boolean } = {} for _, v in pairs(data) do if values[v] then return false end values[v] = true end end local p = 0 if schema.prefixItems ~= nil then for i, s in ipairs(schema.prefixItems) do if data[i] == nil then break elseif validate(s, data[i], root) then p = i else return false end end end if schema.items ~= nil then for i = p+1, #data do if not validate(schema.items, data[i], root) then return false end end end if schema.contains ~= nil then local found = false for i = 1, #data do if validate(schema.contains, data[i], root) then found = true break end end if not found then return false end end end return true; end json_schema_object.validate = validate; return json_schema_object; prosody-0.12.4/teal-src/util/PaxHeaders/net.d.tl0000644000000000000000000000012014475665104016424 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/net.d.tl0000644000175000017500000000033114475665104020626 0ustar00prosodyprosody00000000000000 local enum type_strings "both" "ipv4" "ipv6" end local record lib local_addresses : function (type_strings, boolean) : { string } pton : function (string):string ntop : function (string):string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/poll.d.tl0000644000000000000000000000012014475665104016604 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/poll.d.tl0000644000175000017500000000147114475665104021014 0ustar00prosodyprosody00000000000000local record state enum waiterr "timeout" "signal" end add : function (state, integer, boolean, boolean) : boolean add : function (state, integer, boolean, boolean) : nil, string, integer set : function (state, integer, boolean, boolean) : boolean set : function (state, integer, boolean, boolean) : nil, string, integer del : function (state, integer) : boolean del : function (state, integer) : nil, string, integer wait : function (state, integer) : integer, boolean, boolean wait : function (state, integer) : nil, string, integer wait : function (state, integer) : nil, waiterr getfd : function (state) : integer end local record lib new : function () : state EEXIST : integer EMFILE : integer ENOENT : integer enum api_backend "epoll" "poll" "select" end api : api_backend end return lib prosody-0.12.4/teal-src/util/PaxHeaders/pposix.d.tl0000644000000000000000000000012014475665104017160 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/pposix.d.tl0000644000175000017500000000420114475665104021362 0ustar00prosodyprosody00000000000000local record pposix enum syslog_facility "auth" "authpriv" "cron" "daemon" "ftp" "kern" "local0" "local1" "local2" "local3" "local4" "local5" "local6" "local7" "lpr" "mail" "syslog" "user" "uucp" end enum syslog_level "debug" "info" "notice" "warn" "error" end enum ulimit_resource "CORE" "CPU" "DATA" "FSIZE" "NOFILE" "STACK" "MEMLOCK" "NPROC" "RSS" "NICE" end enum ulimit_unlimited "unlimited" end type ulimit_limit = integer | ulimit_unlimited record utsname sysname : string nodename : string release : string version : string machine : string domainname : string end record memoryinfo allocated : integer allocated_mmap : integer used : integer unused : integer returnable : integer end abort : function () daemonize : function () : boolean, string syslog_open : function (ident : string, facility : syslog_facility) syslog_close : function () syslog_log : function (level : syslog_level, src : string, msg : string) syslog_setminlevel : function (level : syslog_level) getpid : function () : integer getuid : function () : integer getgid : function () : integer setuid : function (uid : integer | string) : boolean, string -- string|integer setgid : function (uid : integer | string) : boolean, string initgroups : function (user : string, gid : integer) : boolean, string umask : function (umask : string) : string mkdir : function (dir : string) : boolean, string setrlimit : function (resource : ulimit_resource, soft : ulimit_limit, hard : ulimit_limit) : boolean, string getrlimit : function (resource : ulimit_resource) : boolean, ulimit_limit, ulimit_limit getrlimit : function (resource : ulimit_resource) : boolean, string uname : function () : utsname setenv : function (key : string, value : string) : boolean meminfo : function () : memoryinfo atomic_append : function (f : FILE, s : string) : boolean, string, integer isatty : function(FILE) : boolean ENOENT : integer _NAME : string _VESRION : string end return pposix prosody-0.12.4/teal-src/util/PaxHeaders/random.d.tl0000644000000000000000000000012014475665104017116 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/random.d.tl0000644000175000017500000000010514475665104021317 0ustar00prosodyprosody00000000000000local record lib bytes : function (n:integer):string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/ringbuffer.d.tl0000644000000000000000000000012014475665104017767 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/ringbuffer.d.tl0000644000175000017500000000113614475665104022175 0ustar00prosodyprosody00000000000000local record lib record ringbuffer find : function (ringbuffer, string) : integer discard : function (ringbuffer, integer) : boolean read : function (ringbuffer, integer, boolean) : string readuntil : function (ringbuffer, string) : string write : function (ringbuffer, string) : integer size : function (ringbuffer) : integer length : function (ringbuffer) : integer sub : function (ringbuffer, integer, integer) : string byte : function (ringbuffer, integer, integer) : integer... free : function (ringbuffer) : integer end new : function (integer) : ringbuffer end return lib prosody-0.12.4/teal-src/util/PaxHeaders/signal.d.tl0000644000000000000000000000012014475665104017113 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/signal.d.tl0000644000175000017500000000112414475665104021316 0ustar00prosodyprosody00000000000000local record lib enum signal "SIGABRT" "SIGALRM" "SIGBUS" "SIGCHLD" "SIGCLD" "SIGCONT" "SIGFPE" "SIGHUP" "SIGILL" "SIGINT" "SIGIO" "SIGIOT" "SIGKILL" "SIGPIPE" "SIGPOLL" "SIGPROF" "SIGQUIT" "SIGSEGV" "SIGSTKFLT" "SIGSTOP" "SIGSYS" "SIGTERM" "SIGTRAP" "SIGTTIN" "SIGTTOU" "SIGURG" "SIGUSR1" "SIGUSR2" "SIGVTALRM" "SIGWINCH" "SIGXCPU" "SIGXFSZ" end signal : function (integer | signal, function, boolean) : boolean raise : function (integer | signal) kill : function (integer, integer | signal) -- enum : integer end return lib prosody-0.12.4/teal-src/util/PaxHeaders/smqueue.tl0000644000000000000000000000012014475665104017100 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/smqueue.tl0000644000175000017500000000425014475665104021306 0ustar00prosodyprosody00000000000000local queue = require "util.queue"; local record lib -- T would typically be util.stanza record smqueue _queue : queue.queue _head : integer _tail : integer enum ack_errors "tail" "head" "pop" end push : function (smqueue, T) ack : function (smqueue, integer) : { T }, ack_errors resumable : function (smqueue) : boolean resume : function (smqueue) : queue.queue.iterator, any, integer type consume_iter = function (smqueue) : T consume : function (smqueue) : consume_iter table : function (smqueue) : { T } end new : function (integer) : smqueue end local type smqueue = lib.smqueue; function smqueue:push(v) self._head = self._head + 1; -- Wraps instead of errors assert(self._queue:push(v)); end function smqueue:ack(h : integer) : { any }, smqueue.ack_errors if h < self._tail then return nil, "tail"; elseif h > self._head then return nil, "head"; end -- TODO optimize? cache table fields local acked = {}; self._tail = h; local expect = self._head - self._tail; while expect < self._queue:count() do local v = self._queue:pop(); if not v then return nil, "pop"; end table.insert(acked, v); end return acked; end function smqueue:count_unacked() : integer return self._head - self._tail; end function smqueue:count_acked() : integer return self._tail; end function smqueue:resumable() : boolean return self._queue:count() >= (self._head - self._tail); end function smqueue:resume() : queue.queue.iterator, any, integer return self._queue:items(); end function smqueue:consume() : queue.queue.consume_iter return self._queue:consume() end -- Compatibility layer, plain ol' table function smqueue:table() : { any } local t : { any } = {}; for i, v in self:resume() do t[i] = v; end return t; end local function freeze(q : smqueue) : { string:integer } return { head = q._head, tail = q._tail } end local queue_mt = { -- __name = "smqueue"; __index = smqueue; __len = smqueue.count_unacked; __freeze = freeze; } function lib.new(size : integer) : queue.queue assert(size>0); return setmetatable({ _head = 0; _tail = 0; _queue = queue.new(size, true) }, queue_mt); end return lib; prosody-0.12.4/teal-src/util/PaxHeaders/stanza.d.tl0000644000000000000000000000012014475665104017136 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/stanza.d.tl0000644000175000017500000000465114475665104021351 0ustar00prosodyprosody00000000000000local record lib type children_iter = function ( stanza_t ) : stanza_t type childtags_iter = function () : stanza_t type maptags_cb = function ( stanza_t ) : stanza_t record stanza_t name : string attr : { string : string } { stanza_t | string } tags : { stanza_t } query : function ( stanza_t, string ) : stanza_t body : function ( stanza_t, string, { string : string } ) : stanza_t text_tag : function ( stanza_t, string, string, { string : string } ) : stanza_t tag : function ( stanza_t, string, { string : string } ) : stanza_t text : function ( stanza_t, string ) : stanza_t up : function ( stanza_t ) : stanza_t reset : function ( stanza_t ) : stanza_t add_direct_child : function ( stanza_t, stanza_t ) add_child : function ( stanza_t, stanza_t ) remove_children : function ( stanza_t, string, string ) : stanza_t get_child : function ( stanza_t, string, string ) : stanza_t get_text : function ( stanza_t ) : string get_child_text : function ( stanza_t, string, string ) : string child_with_name : function ( stanza_t, string, string ) : stanza_t child_with_ns : function ( stanza_t, string, string ) : stanza_t children : function ( stanza_t ) : children_iter, stanza_t, integer childtags : function ( stanza_t, string, string ) : childtags_iter maptags : function ( stanza_t, maptags_cb ) : stanza_t find : function ( stanza_t, string ) : stanza_t | string top_tag : function ( stanza_t ) : string pretty_print : function ( stanza_t ) : string pretty_top_tag : function ( stanza_t ) : string get_error : function ( stanza_t ) : string, string, string, stanza_t indent : function ( stanza_t, integer, string ) : stanza_t end record serialized_stanza_t name : string attr : { string : string } { serialized_stanza_t | string } end stanza : function ( string, { string : string } ) : stanza_t is_stanza : function ( any ) : boolean preserialize : function ( stanza_t ) : serialized_stanza_t deserialize : function ( serialized_stanza_t ) : stanza_t clone : function ( stanza_t, boolean ) : stanza_t message : function ( { string : string }, string ) : stanza_t iq : function ( { string : string } ) : stanza_t reply : function ( stanza_t ) : stanza_t error_reply : function ( stanza_t, string, string, string, string ) presence : function ( { string : string } ) : stanza_t xml_escape : function ( string ) : string pretty_print : function ( string ) : string end return lib prosody-0.12.4/teal-src/util/PaxHeaders/strbitop.d.tl0000644000000000000000000000012014475665104017504 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/strbitop.d.tl0000644000175000017500000000024014475665104021705 0ustar00prosodyprosody00000000000000local record mod sand : function (string, string) : string sor : function (string, string) : string sxor : function (string, string) : string end return mod prosody-0.12.4/teal-src/util/PaxHeaders/table.d.tl0000644000000000000000000000012014475665104016725 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/table.d.tl0000644000175000017500000000017014475665104021130 0ustar00prosodyprosody00000000000000local record lib create : function (narr:integer, nrec:integer):table pack : function (...:any):{any} end return lib prosody-0.12.4/teal-src/util/PaxHeaders/time.d.tl0000644000000000000000000000012014475665104016574 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/time.d.tl0000644000175000017500000000013714475665104021002 0ustar00prosodyprosody00000000000000 local record lib now : function () : number monotonic : function () : number end return lib prosody-0.12.4/teal-src/util/PaxHeaders/uuid.d.tl0000644000000000000000000000012014475665104016604 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/uuid.d.tl0000644000175000017500000000017614475665104021015 0ustar00prosodyprosody00000000000000local record lib get_nibbles : (number) : string generate : function () : string seed : function (string) end return lib prosody-0.12.4/teal-src/util/PaxHeaders/xtemplate.tl0000644000000000000000000000012014475665104017417 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.744764442 prosody-0.12.4/teal-src/util/xtemplate.tl0000644000175000017500000000623114475665104021626 0ustar00prosodyprosody00000000000000-- render(template, stanza) --> string -- {path} --> stanza:find(path) -- {{ns}name/child|each({ns}name){sub-template}} --[[ template ::= "{" path ("|" name ("(" args ")")? (template)? )* "}" path ::= defined by util.stanza name ::= %w+ args ::= anything with balanced ( ) pairs ]] local s_gsub = string.gsub; local s_match = string.match; local s_sub = string.sub; local t_concat = table.concat; local st = require "util.stanza"; local type escape_t = function (string) : string local type filter_t = function (string, string | st.stanza_t, string) : string | st.stanza_t, boolean local type filter_coll = { string : filter_t } local function render(template : string, root : st.stanza_t, escape : escape_t, filters : filter_coll) : string escape = escape or st.xml_escape; return (s_gsub(template, "%b{}", function(block : string) : string local inner = s_sub(block, 2, -2); local path, pipe, pos = s_match(inner, "^([^|]+)(|?)()"); if not path is string then return end local value : string | st.stanza_t if path == "." then value = root; elseif path == "#" then value = root:get_text(); else value = root:find(path); end local is_escaped = false; while pipe == "|" do local func, args, tmpl, p = s_match(inner, "^(%w+)(%b())(%b{})()", pos as integer); if not func then func, args, p = s_match(inner, "^(%w+)(%b())()", pos as integer); end if not func then func, tmpl, p = s_match(inner, "^(%w+)(%b{})()", pos as integer); end if not func then func, p = s_match(inner, "^(%w+)()", pos as integer); end if not func then break end if tmpl then tmpl = s_sub(tmpl, 2, -2); end if args then args = s_sub(args, 2, -2); end if func == "each" and tmpl and st.is_stanza(value) then if not args then value, args = root, path; end local ns, name = s_match(args, "^(%b{})(.*)$"); if ns then ns = s_sub(ns, 2, -2); else name, ns = args, nil; end if ns == "" then ns = nil; end if name == "" then name = nil; end local out, i = {}, 1; for c in (value as st.stanza_t):childtags(name, ns) do out[i], i = render(tmpl, c, escape, filters), i + 1; end value = t_concat(out); is_escaped = true; elseif func == "and" and tmpl then local condition = value; if args then condition = root:find(args); end if condition then value = render(tmpl, root, escape, filters); is_escaped = true; end elseif func == "or" and tmpl then local condition = value; if args then condition = root:find(args); end if not condition then value = render(tmpl, root, escape, filters); is_escaped = true; end elseif filters and filters[func] then local f = filters[func]; if args == nil then value, is_escaped = f(value, tmpl); else value, is_escaped = f(args, value, tmpl); end else error("No such filter function: " .. func); end pipe, pos = s_match(inner, "^(|?)()", p as integer); end if value is string then if not is_escaped then value = escape(value); end return value; elseif st.is_stanza(value) then value = value:get_text(); if value then return escape(value); end end return ""; end)); end return { render = render }; prosody-0.12.4/PaxHeaders/tools0000644000000000000000000000013214475665104013452 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.748764403 30 ctime=1693936196.752764364 prosody-0.12.4/tools/0000755000175000017500000000000014475665104015731 5ustar00prosodyprosody00000000000000prosody-0.12.4/tools/PaxHeaders/cfgdump.lua0000644000000000000000000000012014475665104015654 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/cfgdump.lua0000755000175000017500000000660014475665104020066 0ustar00prosodyprosody00000000000000#!/usr/bin/env lua -- cfgdump.lua prosody.cfg.lua [[host] option] local s_format, print = string.format, print; local printf = function(fmt, ...) return print(s_format(fmt, ...)); end local it = require "util.iterators"; local function sort_anything(a, b) local typeof_a, typeof_b = type(a), type(b); if typeof_a ~= typeof_b then return typeof_a < typeof_b end return a < b -- should work for everything in a config file end local serialization = require "util.serialization"; local serialize = serialization.new and serialization.new({ unquoted = true, table_iterator = function(t) return it.sorted_pairs(t, sort_anything); end, }) or serialization.serialize; local configmanager = require"core.configmanager"; local startup = require "util.startup"; startup.set_function_metatable(); local config_filename, onlyhost, onlyoption = ...; local ok, _, err = configmanager.load(config_filename or "./prosody.cfg.lua", "lua"); assert(ok, err); if onlyhost then if not onlyoption then onlyhost, onlyoption = "*", onlyhost; end if onlyhost ~= "*" then local component_module = configmanager.get(onlyhost, "component_module"); if component_module == "component" then printf("Component %q", onlyhost); elseif component_module then printf("Component %q %q", onlyhost, component_module); else printf("VirtualHost %q", onlyhost); end end printf("%s = %s", onlyoption or "?", serialize(configmanager.get(onlyhost, onlyoption))); return; end local config = configmanager.getconfig(); for host, hostcfg in it.sorted_pairs(config) do local fixed = {}; for option, value in it.sorted_pairs(hostcfg) do fixed[option] = value; if option:match("ports?$") or option:match("interfaces?$") then if option:match("s$") then if type(value) ~= "table" then fixed[option] = { value }; end else if type(value) == "table" and #value > 1 then fixed[option] = nil; fixed[option.."s"] = value; end end end end config[host] = fixed; end local globals = config["*"]; config["*"] = nil; local function printsection(section) local out, n = {}, 1; for k,v in it.sorted_pairs(section) do out[n], n = s_format("%s = %s", k, serialize(v)), n + 1; end table.sort(out); print(table.concat(out, "\n")); end print("-------------- Prosody Exported Configuration File -------------"); print(); print("------------------------ Global section ------------------------"); print(); printsection(globals); print(); local has_components = nil; print("------------------------ Virtual hosts -------------------------"); for host, hostcfg in it.sorted_pairs(config) do setmetatable(hostcfg, nil); hostcfg.defined = nil; if hostcfg.component_module == nil then print(); printf("VirtualHost %q", host); printsection(hostcfg); else has_components = true end end print(); if has_components then print("------------------------- Components ---------------------------"); for host, hostcfg in it.sorted_pairs(config) do local component_module = hostcfg.component_module; hostcfg.component_module = nil; if component_module then print(); if component_module == "component" then printf("Component %q", host); else printf("Component %q %q", host, component_module); hostcfg.component_module = nil; hostcfg.load_global_modules = nil; end printsection(hostcfg); end end end print() print("------------------------- End of File --------------------------"); prosody-0.12.4/tools/PaxHeaders/dnsregistry.lua0000644000000000000000000000012014475665104016604 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/dnsregistry.lua0000644000175000017500000000330114475665104021006 0ustar00prosodyprosody00000000000000-- Generate util/dnsregistry.lua from IANA HTTP status code registry local xml = require "util.xml"; local registries = xml.parse(io.read("*a"), { allow_processing_instructions = true }); print("-- Source: https://www.iana.org/assignments/dns-parameters/dns-parameters.xml"); print(os.date("-- Generated on %Y-%m-%d")) local registry_mapping = { ["dns-parameters-2"] = "classes"; ["dns-parameters-4"] = "types"; ["dns-parameters-6"] = "errors"; }; print("return {"); for registry in registries:childtags("registry") do local registry_name = registry_mapping[registry.attr.id]; if registry_name then print("\t" .. registry_name .. " = {"); for record in registry:childtags("record") do local record_name = record:get_child_text("name"); local record_type = record:get_child_text("type"); local record_desc = record:get_child_text("description"); local record_code = tonumber(record:get_child_text("value")); if tostring(record):lower():match("reserved") or tostring(record):lower():match("reserved") then record_code = nil; end if registry_name == "classes" and record_code then record_type = record_desc and record_desc:match("%((%w+)%)$") if record_type then print(("\t\t[%q] = %d; [%d] = %q;"):format(record_type, record_code, record_code, record_type)) end elseif registry_name == "types" and record_type and record_code then print(("\t\t[%q] = %d; [%d] = %q;"):format(record_type, record_code, record_code, record_type)) elseif registry_name == "errors" and record_code and record_name then print(("\t\t[%d] = %q; [%q] = %q;"):format(record_code, record_name, record_name, record_desc or record_name)); end end print("\t};"); end end print("};"); prosody-0.12.4/tools/PaxHeaders/ejabberd2prosody.lua0000644000000000000000000000012014475665104017467 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/ejabberd2prosody.lua0000755000175000017500000003031614475665104021702 0ustar00prosodyprosody00000000000000#!/usr/bin/env lua -- 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. -- package.path = package.path ..";../?.lua"; local my_name = arg[0]; if my_name:match("[/\\]") then package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua"); package.path = package.path..";"..my_name:gsub("[^/\\]+$", "?.lua"); package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so"); end local erlparse = require "erlparse"; prosody = {}; package.loaded["util.logger"] = {init = function() return function() end; end} local serialize = require "util.serialization".serialize; local st = require "util.stanza"; local dm = require "util.datamanager" dm.set_data_path("data"); function build_stanza(tuple, stanza) assert(type(tuple) == "table", "XML node is of unexpected type: "..type(tuple)); if tuple[1] == "xmlelement" or tuple[1] == "xmlel" then assert(type(tuple[2]) == "string", "element name has type: "..type(tuple[2])); assert(type(tuple[3]) == "table", "element attribute array has type: "..type(tuple[3])); assert(type(tuple[4]) == "table", "element children array has type: "..type(tuple[4])); local name = tuple[2]; local attr = {}; for _, a in ipairs(tuple[3]) do if type(a[1]) == "string" and type(a[2]) == "string" then attr[a[1]] = a[2]; end end local up; if stanza then stanza:tag(name, attr); up = true; else stanza = st.stanza(name, attr); end for _, a in ipairs(tuple[4]) do build_stanza(a, stanza); end if up then stanza:up(); else return stanza end elseif tuple[1] == "xmlcdata" then if type(tuple[2]) ~= "table" then assert(type(tuple[2]) == "string", "XML CDATA has unexpected type: "..type(tuple[2])); stanza:text(tuple[2]); end -- else it's [], i.e., the null value, used for the empty string else error("unknown element type: "..serialize(tuple)); end end function build_time(tuple) local Megaseconds,Seconds,Microseconds = unpack(tuple); return Megaseconds * 1000000 + Seconds; end function build_jid(tuple, full) local node, jid, resource = tuple[1], tuple[2], tuple[3] if type(node) == "string" and node ~= "" then jid = tuple[1] .. "@" .. jid; end if full and type(resource) == "string" and resource ~= "" then jid = jid .. "/" .. resource; end return jid; end function vcard(node, host, stanza) local ret, err = dm.store(node, host, "vcard", st.preserialize(stanza)); print("["..(err or "success").."] vCard: "..node.."@"..host); end function password(node, host, password) local data = {}; if type(password) == "string" then data.password = password; elseif type(password) == "table" and password[1] == "scram" then local unb64 = require"mime".unb64; local function hex(s) return s:gsub(".", function (c) return ("%02x"):format(c:byte()); end); end data.stored_key = hex(unb64(password[2])); data.server_key = hex(unb64(password[3])); data.salt = unb64(password[4]); if type(password[6]) == "number" then assert(password[5] == "sha", "unexpected passwd entry hash: "..tostring(password[5])); data.iteration_count = password[6]; else assert(type(password[5]) == "number", "unexpected passwd entry in source data"); data.iteration_count = password[5]; end end local ret, err = dm.store(node, host, "accounts", data); print("["..(err or "success").."] accounts: "..node.."@"..host); end function roster(node, host, jid, item) local roster = dm.load(node, host, "roster") or {}; roster[jid] = item; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid); end function roster_pending(node, host, jid) local roster = dm.load(node, host, "roster") or {}; roster.pending = roster.pending or {}; roster.pending[jid] = true; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid); end function private_storage(node, host, xmlns, stanza) local private = dm.load(node, host, "private") or {}; private[stanza.name..":"..xmlns] = st.preserialize(stanza); local ret, err = dm.store(node, host, "private", private); print("["..(err or "success").."] private: " ..node.."@"..host.." - "..xmlns); end function offline_msg(node, host, t, stanza) stanza.attr.stamp = os.date("!%Y-%m-%dT%H:%M:%SZ", t); stanza.attr.stamp_legacy = os.date("!%Y%m%dT%H:%M:%S", t); local ret, err = dm.list_append(node, host, "offline", st.preserialize(stanza)); print("["..(err or "success").."] offline: " ..node.."@"..host.." - "..os.date("!%Y-%m-%dT%H:%M:%SZ", t)); end function privacy(node, host, default, lists) local privacy = { lists = {} }; local count = 0; if default then privacy.default = default; end for _, inlist in ipairs(lists) do local name, items = inlist[1], inlist[2]; local list = { name = name; items = {}; }; local orders = {}; for _, item in pairs(items) do repeat if item[1] ~= "listitem" then print("[error] privacy: unhandled item: "..tostring(item[1])); break; end local _type, value = item[2], item[3]; if _type == "jid" then if type(value) ~= "table" then print("[error] privacy: jid value is not valid: "..tostring(value)); break; end local _node, _host, _resource = value[1], value[2], value[3]; value = build_jid(value, true) elseif _type == "none" then _type = nil; value = nil; elseif _type == "group" then if type(value) ~= "string" then print("[error] privacy: group value is not string: "..tostring(value)); break; end elseif _type == "subscription" then if value~="both" and value~="from" and value~="to" and value~="none" then print("[error] privacy: subscription value is invalid: "..tostring(value)); break; end else print("[error] privacy: invalid item type: "..tostring(_type)); break; end local action = item[4]; if action ~= "allow" and action ~= "deny" then print("[error] privacy: unhandled action: "..tostring(action)); break; end local order = item[5]; if type(order) ~= "number" or order<0 then print("[error] privacy: order is not numeric: "..tostring(order)); break; end if orders[order] then print("[error] privacy: duplicate order value: "..tostring(order)); break; end orders[order] = true; local match_all = item[6]; local match_iq = item[7]; local match_message = item[8]; local match_presence_in = item[9]; local match_presence_out = item[10]; list.items[#list.items+1] = { type = _type; value = value; action = action; order = order; message = match_message == "true"; iq = match_iq == "true"; ["presence-in"] = match_presence_in == "true"; ["presence-out"] = match_presence_out == "true"; }; until true; end table.sort(list.items, function(a, b) return a.order < b.order; end); if privacy.lists[list.name] then print("[warn] duplicate privacy list: "..tostring(list.name)); end privacy.lists[list.name] = list; count = count + 1; end if default and not privacy.lists[default] then if default == "none" then privacy.default = nil; else print("[warn] default privacy list doesn't exist: "..tostring(default)); end end local ret, err = dm.store(node, host, "privacy", privacy); print("["..(err or "success").."] privacy: " ..node.."@"..host.." - "..count.." list(s)"); end function muc_room(node, host, properties) local store = { jid = node.."@"..host, _data = {}, _affiliations = {} }; for _,aff in ipairs(properties.affiliations) do store._affiliations[build_jid(aff[1])] = aff[2][1] or aff[2]; end -- destructure ejabberd's subject datum (e.g. [{text,<<>>,<<"my room subject">>}] ) store._data.subject = properties.subject[1][3]; if properties.subject_author then store._data.subject_from = store.jid .. "/" .. properties.subject_author; end store._data.name = properties.title; store._data.description = properties.description; if properties.password_protected ~= false and properties.password ~= "" then store._data.password = properties.password; end store._data.moderated = (properties.moderated == "true") or nil; store._data.members_only = (properties.members_only == "true") or nil; store._data.persistent = (properties.persistent == "true") or nil; store._data.changesubject = (properties.allow_change_subj == "true") or nil; store._data.whois = properties.anonymous == "true" and "moderators" or "anyone"; store._data.hidden = (properties.public_list == "false") or nil; if not store._data.persistent then return print("[error] muc_room: skipping non-persistent room: "..node.."@"..host); end local ret, err = dm.store(node, host, "config", store); if ret then ret, err = dm.load(nil, host, "persistent"); if ret or not err then ret = ret or {}; ret[store.jid] = true; ret, err = dm.store(nil, host, "persistent", ret); end end print("["..(err or "success").."] muc_room: " ..node.."@"..host); end local filters = { passwd = function(tuple) password(tuple[2][1], tuple[2][2], tuple[3]); end; vcard = function(tuple) vcard(tuple[2][1], tuple[2][2], build_stanza(tuple[3])); end; roster = function(tuple) local node = tuple[3][1]; local host = tuple[3][2]; local contact = build_jid(tuple[4]); local name = tuple[5]; local subscription = tuple[6]; local ask = tuple[7]; local groups = tuple[8]; if type(name) ~= type("") then name = nil; end if ask == "none" then ask = nil; elseif ask == "out" then ask = "subscribe" elseif ask == "in" then roster_pending(node, host, contact); ask = nil; elseif ask == "both" then roster_pending(node, host, contact); ask = "subscribe"; else error("Unknown ask type: "..ask); end if subscription ~= "both" and subscription ~= "from" and subscription ~= "to" and subscription ~= "none" then error(subscription) end local item = {name = name, ask = ask, subscription = subscription, groups = {}}; for _, g in ipairs(groups) do if type(g) == "string" then item.groups[g] = true; end end roster(node, host, contact, item); end; private_storage = function(tuple) private_storage(tuple[2][1], tuple[2][2], tuple[2][3], build_stanza(tuple[3])); end; offline_msg = function(tuple) offline_msg(tuple[2][1], tuple[2][2], build_time(tuple[3]), build_stanza(tuple[7])); end; privacy = function(tuple) privacy(tuple[2][1], tuple[2][2], tuple[3], tuple[4]); end; muc_room = function(tuple) local properties = {}; for _,pair in ipairs(tuple[3]) do if not(type(pair[2]) == "table" and #pair[2] == 0) then -- skip nil values properties[pair[1]] = pair[2]; end end muc_room(tuple[2][1], tuple[2][2], properties); end; --[=[config = function(tuple) if tuple[2] == "hosts" then local output = io.output(); io.output("prosody.cfg.lua"); io.write("-- Configuration imported from ejabberd --\n"); io.write([[Host "*" modules_enabled = { "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. "legacyauth"; -- Legacy authentication. Only used by some old clients and bots. "roster"; -- Allow users to have a roster. Recommended ;) "register"; -- Allow users to register on this server using a client "tls"; -- Add support for secure TLS on c2s/s2s connections "vcard"; -- Allow users to set vCards "private"; -- Private XML storage (for room bookmarks, etc.) "version"; -- Replies to server version requests "dialback"; -- s2s dialback support "uptime"; "disco"; "time"; "ping"; --"selftests"; }; ]]); for _, h in ipairs(tuple[3]) do io.write("Host \"" .. h .. "\"\n"); end io.output(output); print("prosody.cfg.lua created"); end end;]=] }; local arg = ...; local help = "/? -? ? /h -h /help -help --help"; if not arg or help:find(arg, 1, true) then print([[ejabberd db dump importer for Prosody Usage: ]]..my_name..[[ filename.txt The file can be generated from ejabberd using: sudo ejabberdctl dump filename.txt Note: The path of ejabberdctl depends on your ejabberd installation, and ejabberd needs to be running for ejabberdctl to work.]]); os.exit(1); end local count = 0; local t = {}; for item in erlparse.parseFile(arg) do count = count + 1; local name = item[1]; t[name] = (t[name] or 0) + 1; --print(count, serialize(item)); if filters[name] then filters[name](item); end end --print(serialize(t)); prosody-0.12.4/tools/PaxHeaders/ejabberdsql2prosody.lua0000644000000000000000000000012014475665104020207 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/ejabberdsql2prosody.lua0000644000175000017500000002311314475665104022414 0ustar00prosodyprosody00000000000000#!/usr/bin/env lua -- 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. -- prosody = {}; package.path = package.path ..";../?.lua"; local my_name = arg[0]; if my_name:match("[/\\]") then package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua"); package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so"); end local serialize = require "util.serialization".serialize; local st = require "util.stanza"; local parse_xml = require "util.xml".parse; package.loaded["util.logger"] = {init = function() return function() end; end} local dm = require "util.datamanager" dm.set_data_path("data"); function parseFile(filename) ------ local file = nil; local last = nil; local line = 1; local function read(expected) local ch; if last then ch = last; last = nil; else ch = file:read(1); if ch == "\n" then line = line + 1; end end if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil").." on line "..line); end return ch; end local function peek() if not last then last = read(); end return last; end local escapes = { ["\\0"] = "\0"; ["\\'"] = "'"; ["\\\""] = "\""; ["\\b"] = "\b"; ["\\n"] = "\n"; ["\\r"] = "\r"; ["\\t"] = "\t"; ["\\Z"] = "\26"; ["\\\\"] = "\\"; ["\\%"] = "%"; ["\\_"] = "_"; } local function unescape(s) return escapes[s] or error("Unknown escape sequence: "..s); end local function readString() read("'"); local s = ""; while true do local ch = peek(); if ch == "\\" then s = s..unescape(read()..read()); elseif ch == "'" then break; else s = s..read(); end end read("'"); return s; end local function readNonString() local s = ""; while true do if peek() == "," or peek() == ")" then break; else s = s..read(); end end return tonumber(s); end local function readItem() if peek() == "'" then return readString(); else return readNonString(); end end local function readTuple() local items = {} read("("); while peek() ~= ")" do table.insert(items, readItem()); if peek() == ")" then break; end read(","); end read(")"); return items; end local function readTuples() if peek() ~= "(" then read("("); end local tuples = {}; while true do table.insert(tuples, readTuple()); if peek() == "," then read() end if peek() == ";" then break; end end return tuples; end local function readTableName() local tname = ""; while peek() ~= "`" do tname = tname..read(); end return tname; end local function readInsert() if peek() == nil then return nil; end for ch in ("INSERT INTO `"):gmatch(".") do -- find line starting with this if peek() == ch then read(); -- found else -- match failed, skip line while peek() and read() ~= "\n" do end return nil; end end local tname = readTableName(); read("`"); read(" ") -- expect this if peek() == "(" then -- skip column list repeat until read() == ")"; read(" "); end for ch in ("VALUES "):gmatch(".") do read(ch); end -- expect this local tuples = readTuples(); read(";"); read("\n"); return tname, tuples; end local function readFile(filename) file = io.open(filename); if not file then error("File not found: "..filename); os.exit(0); end local t = {}; while true do local tname, tuples = readInsert(); if tname then if t[tname] then local t_name = t[tname]; for i=1,#tuples do table.insert(t_name, tuples[i]); end else t[tname] = tuples; end elseif peek() == nil then break; end end return t; end return readFile(filename); ------ end local arg, hostname = ...; local help = "/? -? ? /h -h /help -help --help"; if not(arg and hostname) or help:find(arg, 1, true) then print([[ejabberd SQL DB dump importer for Prosody Usage: ejabberdsql2prosody.lua filename.txt hostname The file can be generated using mysqldump: mysqldump db_name > filename.txt]]); os.exit(1); end local map = { ["last"] = {"username", "seconds", "state"}; ["privacy_default_list"] = {"username", "name"}; ["privacy_list"] = {"username", "name", "id"}; ["privacy_list_data"] = {"id", "t", "value", "action", "ord", "match_all", "match_iq", "match_message", "match_presence_in", "match_presence_out"}; ["private_storage"] = {"username", "namespace", "data"}; ["rostergroups"] = {"username", "jid", "grp"}; ["rosterusers"] = {"username", "jid", "nick", "subscription", "ask", "askmessage", "server", "subscribe", "type"}; ["spool"] = {"username", "xml", "seq"}; ["users"] = {"username", "password"}; ["vcard"] = {"username", "vcard"}; --["vcard_search"] = {}; } local NULL = {}; local parsed = parseFile(arg); for name, data in pairs(parsed) do local m = map[name]; if m then if #data > 0 and #data[1] ~= #m then print("[warning] expected "..#m.." columns for table `"..name.."`, found "..#data[1]); end for i=1,#data do local row = data[i]; for j=1,#m do row[m[j]] = row[j]; row[j] = nil; end end end end --print(serialize(t)); for _, row in ipairs(parsed["users"] or NULL) do local node, password = row.username, row.password; local ret, err = dm.store(node, hostname, "accounts", {password = password}); print("["..(err or "success").."] accounts: "..node.."@"..hostname); end function roster(node, host, jid, item) local roster = dm.load(node, host, "roster") or {}; roster[jid] = item; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid); end function roster_pending(node, host, jid) local roster = dm.load(node, host, "roster") or {}; roster.pending = roster.pending or {}; roster.pending[jid] = true; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster-pending: " ..node.."@"..host.." - "..jid); end function roster_group(node, host, jid, group) local roster = dm.load(node, host, "roster") or {}; local item = roster[jid]; if not item then print("Warning: No roster item "..jid.." for user "..node..", can't put in group "..group); return; end item.groups[group] = true; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster-group: " ..node.."@"..host.." - "..jid.." - "..group); end function private_storage(node, host, xmlns, stanza) local private = dm.load(node, host, "private") or {}; private[stanza.name..":"..xmlns] = st.preserialize(stanza); local ret, err = dm.store(node, host, "private", private); print("["..(err or "success").."] private: " ..node.."@"..host.." - "..xmlns); end function offline_msg(node, host, t, stanza) stanza.attr.stamp = os.date("!%Y-%m-%dT%H:%M:%SZ", t); stanza.attr.stamp_legacy = os.date("!%Y%m%dT%H:%M:%S", t); local ret, err = dm.list_append(node, host, "offline", st.preserialize(stanza)); print("["..(err or "success").."] offline: " ..node.."@"..host.." - "..os.date("!%Y-%m-%dT%H:%M:%SZ", t)); end for _, row in ipairs(parsed["rosterusers"] or NULL) do local node, contact = row.username, row.jid; local name = row.nick; if name == "" then name = nil; end local subscription = row.subscription; if subscription == "N" then subscription = "none" elseif subscription == "B" then subscription = "both" elseif subscription == "F" then subscription = "from" elseif subscription == "T" then subscription = "to" else error("Unknown subscription type: "..subscription) end; local ask = row.ask; if ask == "N" then ask = nil; elseif ask == "O" then ask = "subscribe"; elseif ask == "I" then roster_pending(node, hostname, contact); ask = nil; elseif ask == "B" then roster_pending(node, hostname, contact); ask = "subscribe"; else error("Unknown ask type: "..ask); end local item = {name = name, ask = ask, subscription = subscription, groups = {}}; roster(node, hostname, contact, item); end for _, row in ipairs(parsed["rostergroups"] or NULL) do roster_group(row.username, hostname, row.jid, row.grp); end for _, row in ipairs(parsed["vcard"] or NULL) do local stanza, err = parse_xml(row.vcard); if stanza then local ret, err = dm.store(row.username, hostname, "vcard", st.preserialize(stanza)); print("["..(err or "success").."] vCard: "..row.username.."@"..hostname); else print("[error] vCard XML parse failed: "..row.username.."@"..hostname); end end for _, row in ipairs(parsed["private_storage"] or NULL) do local stanza, err = parse_xml(row.data); if stanza then private_storage(row.username, hostname, row.namespace, stanza); else print("[error] Private XML parse failed: "..row.username.."@"..hostname); end end table.sort(parsed["spool"] or NULL, function(a,b) return a.seq < b.seq; end); -- sort by sequence number, just in case local time_offset = os.difftime(os.time(os.date("!*t")), os.time(os.date("*t"))) -- to deal with timezones local date_parse = function(s) local year, month, day, hour, min, sec = s:match("(....)-?(..)-?(..)T(..):(..):(..)"); return os.time({year=year, month=month, day=day, hour=hour, min=min, sec=sec-time_offset}); end for _, row in ipairs(parsed["spool"] or NULL) do local stanza, err = parse_xml(row.xml); if stanza then local last_child = stanza.tags[#stanza.tags]; if not last_child or last_child ~= stanza[#stanza] then error("Last child of offline message is not a tag"); end if last_child.name ~= "x" and last_child.attr.xmlns ~= "jabber:x:delay" then error("Last child of offline message is not a timestamp"); end stanza[#stanza], stanza.tags[#stanza.tags] = nil, nil; local t = date_parse(last_child.attr.stamp); offline_msg(row.username, hostname, t, stanza); else print("[error] Offline message XML parsing failed: "..row.username.."@"..hostname); end end prosody-0.12.4/tools/PaxHeaders/erlparse.lua0000644000000000000000000000012014475665104016044 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/erlparse.lua0000644000175000017500000001064714475665104020261 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local string_byte, string_char = string.byte, string.char; local t_concat, t_insert = table.concat, table.insert; local type, tonumber, tostring = type, tonumber, tostring; local file = nil; local last = nil; local line = 1; local function read(expected) local ch; if last then ch = last; last = nil; else ch = file:read(1); if ch == "\n" then line = line + 1; end end if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil").." on line "..line); end return ch; end local function pushback(ch) if last then error(); end last = ch; end local function peek() if not last then last = read(); end return last; end local _A, _a, _Z, _z, _0, _9, __, _at, _space, _minus = string_byte("AaZz09@_ -", 1, 10); local function isLowerAlpha(ch) ch = string_byte(ch) or 0; return (ch >= _a and ch <= _z); end local function isNumeric(ch) ch = string_byte(ch) or 0; return (ch >= _0 and ch <= _9) or ch == _minus; end local function isAtom(ch) ch = string_byte(ch) or 0; return (ch >= _A and ch <= _Z) or (ch >= _a and ch <= _z) or (ch >= _0 and ch <= _9) or ch == __ or ch == _at; end local function isSpace(ch) ch = string_byte(ch) or "x"; return ch <= _space; end local escapes = {["\\b"]="\b", ["\\d"]="\127", ["\\e"]="\27", ["\\f"]="\f", ["\\n"]="\n", ["\\r"]="\r", ["\\s"]=" ", ["\\t"]="\t", ["\\v"]="\v", ["\\\""]="\"", ["\\'"]="'", ["\\\\"]="\\"}; local function readString() read("\""); -- skip quote local slash = nil; local str = {}; while true do local ch = read(); if slash then slash = slash..ch; if not escapes[slash] then error("Unknown escape sequence: "..slash); end str[#str+1] = escapes[slash]; slash = nil; elseif ch == "\"" then break; elseif ch == "\\" then slash = ch; else str[#str+1] = ch; end end return t_concat(str); end local function readAtom1() local var = { read() }; while isAtom(peek()) do var[#var+1] = read(); end return t_concat(var); end local function readAtom2() local str = { read("'") }; local slash = nil; while true do local ch = read(); str[#str+1] = ch; if ch == "'" and not slash then break; end end return t_concat(str); end local function readNumber() local num = { read() }; while isNumeric(peek()) do num[#num+1] = read(); end if peek() == "." then num[#num+1] = read(); while isNumeric(peek()) do num[#num+1] = read(); end end return tonumber(t_concat(num)); end local readItem = nil; local function readTuple() local t = {}; local s = {}; -- string representation read(); -- read {, or [, or < while true do local item = readItem(); if not item then break; end if type(item) ~= "number" or item > 255 then s = nil; elseif s then s[#s+1] = string_char(item); end t_insert(t, item); end read(); -- read }, or ], or > if s and #s > 0 then return t_concat(s) else return t end; end local function readBinary() read("<"); -- read < -- Discard PIDs if isNumeric(peek()) then while peek() ~= ">" do read(); end read(">"); return {}; end local t = readTuple(); read(">") -- read > local ch = peek(); if type(t) == "string" then -- binary is a list of integers return t; elseif type(t) == "table" then if t[1] then -- binary contains string return t[1]; else -- binary is empty return ""; end; else error(); end end readItem = function() local ch = peek(); if ch == nil then return nil end if ch == "{" or ch == "[" then return readTuple(); elseif isLowerAlpha(ch) then return readAtom1(); elseif ch == "'" then return readAtom2(); elseif isNumeric(ch) then return readNumber(); elseif ch == "\"" then return readString(); elseif ch == "<" then return readBinary(); elseif isSpace(ch) or ch == "," or ch == "|" then read(); return readItem(); else --print("Unknown char: "..ch); return nil; end end local function readChunk() local x = readItem(); if x then read("."); end return x; end local function readFile(filename) file = io.open(filename); if not file then error("File not found: "..filename); os.exit(0); end return function() local x = readChunk(); if not x and peek() then error("Invalid char: "..peek()); end return x; end; end local _M = {}; function _M.parseFile(file) return readFile(file); end return _M; prosody-0.12.4/tools/PaxHeaders/form2table.lua0000644000000000000000000000012014475665104016264 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/form2table.lua0000644000175000017500000000261114475665104020471 0ustar00prosodyprosody00000000000000-- Read an XML dataform and spit out a serialized Lua table of it local function from_stanza(stanza) local layout = { title = stanza:get_child_text("title"); instructions = stanza:get_child_text("instructions"); }; for tag in stanza:childtags("field") do local field = { name = tag.attr.var; type = tag.attr.type; label = tag.attr.label; desc = tag:get_child_text("desc"); required = tag:get_child("required") and true or nil; value = tag:get_child_text("value"); options = nil; }; if field.type == "list-single" or field.type == "list-multi" then local options = {}; for option in tag:childtags("option") do options[#options+1] = { label = option.attr.label, value = option:get_child_text("value") }; end field.options = options; end if field.type == "jid-multi" or field.type == "list-multi" or field.type == "text-multi" then local values = {}; for value in tag:childtags("value") do values[#values+1] = value:get_text(); end if field.type == "text-multi" then values = table.concat(values, "\n"); end field.value = values; end if field.type == "boolean" then field.value = field.value == "true" or field.value == "1"; end layout[#layout+1] = field; end return layout; end print("dataforms.new " .. require "util.serialization".serialize(from_stanza(require "util.xml".parse(io.read("*a"))), { unquoted = true })) prosody-0.12.4/tools/PaxHeaders/generate_format_spec.lua0000644000000000000000000000012014475665104020403 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/generate_format_spec.lua0000644000175000017500000000364414475665104022617 0ustar00prosodyprosody00000000000000local format = require"util.format".format; local dump = require"util.serialization".new("oneline") local types = { "nil"; "boolean"; "number"; "string"; "function"; -- "userdata"; "thread"; "table"; }; local example_values = { ["nil"] = { n = 1; nil }; ["boolean"] = { true; false }; ["number"] = { 97; -12345; 1.5; 73786976294838206464; math.huge; 2147483647 }; ["string"] = { "hello"; "foo \1\2\3 bar"; "nödåtgärd"; string.sub("nödåtgärd", 1, -4) }; ["function"] = { function() end }; -- ["userdata"] = {}; ["thread"] = { coroutine.create(function() end) }; ["table"] = { {}, setmetatable({},{__tostring=function ()return "foo \1\2\3 bar"end}) }; }; local example_strings = setmetatable({ ["nil"] = { "nil" }; ["function"] = { "function() end" }; ["number"] = { "97"; "-12345"; "1.5"; "73786976294838206464"; "math.huge"; "2147483647" }; ["thread"] = { "coroutine.create(function() end)" }; ["table"] = { "{ }", "setmetatable({},{__tostring=function ()return \"foo \\1\\2\\3 bar\"end})" } }, { __index = function() return {} end }); for _, lua_type in ipairs(types) do print(string.format("\t\tdescribe(\"%s\", function ()", lua_type)); local examples = example_values[lua_type]; for fmt in ("cdiouxXaAeEfgGqs"):gmatch(".") do print(string.format("\t\t\tdescribe(\"to %%%s\", function ()", fmt)); print("\t\t\t\tit(\"works\", function ()"); for i = 1, examples.n or #examples do local example = examples[i]; if not tostring(example):match("%w+: 0[xX]%x+") then print(string.format("\t\t\t\t\tassert.equal(%q, format(%q, %s))", format("%" .. fmt, example), "%" .. fmt, example_strings[lua_type][i] or dump(example))); else print(string.format("\t\t\t\t\tassert.matches(\"[%s: 0[xX]%%x+]\", format(%q, %s))", lua_type, "%" .. fmt, example_strings[lua_type][i] or dump(example))); end end print("\t\t\t\tend);"); print("\t\t\tend);"); print() end print("\t\tend);"); print() end prosody-0.12.4/tools/PaxHeaders/http-status-codes.lua0000644000000000000000000000012014475665104017622 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/http-status-codes.lua0000644000175000017500000000233514475665104022032 0ustar00prosodyprosody00000000000000-- Generate net/http/codes.lua from IANA HTTP status code registry local xml = require "util.xml"; local registry = xml.parse(io.read("*a"), { allow_processing_instructions = true }); io.write([[ local response_codes = { -- Source: http://www.iana.org/assignments/http-status-codes ]]); for record in registry:get_child("registry"):childtags("record") do -- Extract values local value = record:get_child_text("value"); local description = record:get_child_text("description"); local ref = record:get_child_text("xref"); local code = tonumber(value); -- Space between major groups if code and code % 100 == 0 then io.write("\n"); end -- Reserved and Unassigned entries should be not be included if description == "Reserved" or description == "Unassigned" or description == "(Unused)" then code = nil; end -- Non-empty references become comments if ref and ref:find("%S") then ref = " -- " .. ref; else ref = ""; end io.write((code and "\t[%d] = %q;%s\n" or "\t-- [%s] = %q;%s\n"):format(code or value, description, ref)); end io.write([[}; for k,v in pairs(response_codes) do response_codes[k] = k.." "..v; end return setmetatable(response_codes, { __index = function(_, k) return k.." Unassigned"; end }) ]]); prosody-0.12.4/tools/PaxHeaders/jabberd14sql2prosody.lua0000644000000000000000000000012014475665104020207 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/jabberd14sql2prosody.lua0000644000175000017500000007510414475665104022423 0ustar00prosodyprosody00000000000000#!/usr/bin/env lua do local _parse_sql_actions = { [0] = 0, 1, 0, 1, 1, 2, 0, 2, 2, 0, 9, 2, 0, 10, 2, 0, 11, 2, 0, 13, 2, 1, 2, 2, 1, 6, 3, 0, 3, 4, 3, 0, 3, 5, 3, 0, 3, 7, 3, 0, 3, 8, 3, 0, 3, 12, 4, 0, 2, 3, 7, 4, 0, 3, 8, 11 }; local _parse_sql_trans_keys = { [0] = 0, 0, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47, 82, 82, 69, 69, 65, 65, 84, 84, 69, 69, 32, 32, 68, 84, 65, 65, 84, 84, 65, 65, 66, 66, 65, 65, 83, 83, 69, 69, 9, 47, 9, 96, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47, 10, 96, 10, 96, 9, 47, 9, 59, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47, 65, 65, 66, 66, 76, 76, 69, 69, 32, 32, 73, 96, 70, 70, 32, 32, 78, 78, 79, 79, 84, 84, 32, 32, 69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83, 83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40, 40, 10, 10, 32, 41, 32, 32, 75, 96, 69, 69, 89, 89, 32, 32, 96, 96, 10, 96, 10, 96, 10, 10, 82, 82, 73, 73, 77, 77, 65, 65, 82, 82, 89, 89, 32, 32, 75, 75, 69, 69, 89, 89, 32, 32, 78, 78, 73, 73, 81, 81, 85, 85, 69, 69, 32, 32, 75, 75, 10, 96, 10, 96, 10, 10, 10, 59, 10, 59, 82, 82, 79, 79, 80, 80, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69, 69, 32, 32, 73, 73, 70, 70, 32, 32, 69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83, 83, 32, 32, 96, 96, 10, 96, 10, 96, 59, 59, 78, 78, 83, 83, 69, 69, 82, 82, 84, 84, 32, 32, 73, 73, 78, 78, 84, 84, 79, 79, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40, 86, 10, 41, 32, 32, 86, 86, 65, 65, 76, 76, 85, 85, 69, 69, 83, 83, 32, 32, 40, 40, 39, 78, 10, 92, 10, 92, 41, 44, 44, 59, 32, 78, 48, 57, 41, 57, 48, 57, 41, 57, 85, 85, 76, 76, 76, 76, 34, 116, 79, 79, 67, 67, 75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69, 69, 83, 83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 87, 87, 82, 82, 73, 73, 84, 84, 69, 69, 69, 69, 84, 84, 32, 32, 10, 59, 10, 59, 78, 83, 76, 76, 79, 79, 67, 67, 75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69, 69, 83, 83, 69, 69, 9, 85, 0 }; local _parse_sql_key_spans = { [0] = 0, 1, 1, 1, 33, 38, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 39, 88, 1, 1, 1, 33, 38, 87, 87, 39, 51, 1, 1, 1, 33, 38, 1, 1, 1, 1, 1, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1, 10, 1, 22, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 50, 50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 47, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 40, 83, 83, 4, 16, 47, 10, 17, 10, 17, 1, 1, 1, 83, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 50, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 77 }; local _parse_sql_index_offsets = { [0] = 0, 0, 2, 4, 6, 40, 79, 81, 83, 85, 87, 89, 91, 109, 111, 113, 115, 117, 119, 121, 123, 163, 252, 254, 256, 258, 292, 331, 419, 507, 547, 599, 601, 603, 605, 639, 678, 680, 682, 684, 686, 688, 713, 715, 717, 719, 721, 723, 725, 727, 729, 731, 733, 735, 737, 739, 741, 829, 917, 919, 921, 923, 934, 936, 959, 961, 963, 965, 967, 1055, 1143, 1145, 1147, 1149, 1151, 1153, 1155, 1157, 1159, 1161, 1163, 1165, 1167, 1169, 1171, 1173, 1175, 1177, 1179, 1181, 1269, 1357, 1359, 1410, 1461, 1463, 1465, 1467, 1469, 1471, 1473, 1475, 1477, 1479, 1481, 1483, 1485, 1487, 1489, 1491, 1493, 1495, 1497, 1499, 1501, 1503, 1591, 1679, 1681, 1683, 1685, 1687, 1689, 1691, 1693, 1695, 1697, 1699, 1701, 1703, 1705, 1793, 1881, 1883, 1931, 1964, 1966, 1968, 1970, 1972, 1974, 1976, 1978, 1980, 1982, 2023, 2107, 2191, 2196, 2213, 2261, 2272, 2290, 2301, 2319, 2321, 2323, 2325, 2409, 2411, 2413, 2415, 2417, 2419, 2421, 2423, 2425, 2427, 2429, 2431, 2433, 2521, 2609, 2611, 2613, 2615, 2617, 2619, 2621, 2623, 2625, 2627, 2678, 2729, 2736, 2738, 2740, 2742, 2744, 2746, 2748, 2750, 2752, 2754, 2756, 2758, 2760 }; local _parse_sql_indicies = { [0] = 0, 1, 2, 0, 3, 1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 6, 3, 7, 1, 8, 1, 9, 1, 10, 1, 11, 1, 12, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 15, 1, 16, 1, 17, 1, 18, 1, 19, 1, 20, 1, 21, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24, 1, 25, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 27, 1, 23, 27, 28, 1, 29, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 29, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 22, 28, 32, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 1, 31, 32, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 33, 31, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 36, 1, 37, 1, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 36, 1, 37, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 38, 1, 35, 38, 39, 1, 40, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 40, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 39, 39, 39, 34, 39, 42, 1, 43, 1, 44, 1, 45, 1, 46, 1, 47, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 48, 1, 49, 1, 50, 1, 51, 1, 52, 1, 53, 1, 54, 1, 55, 1, 56, 1, 57, 1, 58, 1, 59, 1, 60, 1, 61, 1, 48, 1, 63, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 1, 62, 65, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 66, 64, 67, 1, 68, 1, 69, 1, 70, 1, 1, 1, 1, 1, 1, 1, 1, 71, 1, 72, 1, 73, 1, 1, 1, 1, 74, 1, 1, 1, 1, 75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 76, 1, 77, 1, 78, 1, 79, 1, 80, 1, 82, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 1, 81, 82, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 83, 81, 69, 83, 84, 1, 85, 1, 86, 1, 87, 1, 88, 1, 89, 1, 90, 1, 91, 1, 92, 1, 93, 1, 83, 1, 94, 1, 95, 1, 96, 1, 97, 1, 98, 1, 99, 1, 73, 1, 101, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1, 100, 103, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 104, 102, 105, 83, 106, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 107, 71, 108, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 107, 71, 109, 1, 110, 1, 111, 1, 112, 1, 113, 1, 114, 1, 115, 1, 116, 1, 117, 1, 118, 1, 119, 1, 120, 1, 121, 1, 122, 1, 123, 1, 124, 1, 125, 1, 126, 1, 127, 1, 128, 1, 129, 1, 131, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 1, 130, 131, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 132, 130, 6, 1, 133, 1, 134, 1, 135, 1, 136, 1, 137, 1, 138, 1, 139, 1, 140, 1, 141, 1, 142, 1, 143, 1, 144, 1, 146, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 1, 145, 148, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 149, 147, 150, 1, 151, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 152, 1, 153, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 154, 151, 155, 1, 152, 1, 156, 1, 157, 1, 158, 1, 159, 1, 160, 1, 161, 1, 162, 1, 163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 166, 1, 168, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 169, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 170, 167, 172, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 173, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 174, 171, 175, 1, 1, 176, 1, 161, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 177, 1, 178, 1, 1, 1, 1, 1, 1, 163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 166, 1, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 180, 1, 1, 181, 1, 182, 1, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 1, 180, 1, 1, 181, 1, 1, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 1, 184, 1, 185, 1, 186, 1, 171, 1, 1, 171, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 171, 1, 171, 1, 1, 171, 1, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 171, 1, 1, 1, 171, 1, 171, 1, 187, 1, 188, 1, 189, 1, 190, 1, 191, 1, 192, 1, 193, 1, 194, 1, 195, 1, 196, 1, 197, 1, 198, 1, 200, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 1, 199, 200, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 201, 199, 202, 1, 203, 1, 204, 1, 205, 1, 206, 1, 132, 1, 207, 1, 208, 1, 209, 1, 210, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 211, 209, 2, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 211, 209, 212, 1, 1, 1, 1, 213, 1, 214, 1, 215, 1, 216, 1, 217, 1, 218, 1, 219, 1, 220, 1, 221, 1, 222, 1, 223, 1, 132, 1, 127, 1, 6, 2, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 224, 1, 225, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 226, 227, 1, 1, 1, 1, 228, 1, 1, 229, 1, 1, 1, 1, 1, 1, 230, 1, 231, 1, 0 }; local _parse_sql_trans_targs = { [0] = 2, 0, 196, 4, 4, 5, 196, 7, 8, 9, 10, 11, 12, 13, 36, 14, 15, 16, 17, 18, 19, 20, 21, 21, 22, 24, 27, 23, 25, 25, 26, 28, 28, 29, 30, 30, 31, 33, 32, 34, 34, 35, 37, 38, 39, 40, 41, 42, 56, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 57, 57, 57, 57, 58, 59, 60, 61, 62, 92, 63, 64, 71, 82, 89, 65, 66, 67, 68, 69, 69, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88, 90, 90, 90, 90, 91, 70, 92, 93, 196, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 116, 117, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 131, 131, 131, 132, 133, 134, 137, 134, 135, 136, 138, 139, 140, 141, 142, 143, 144, 145, 150, 151, 154, 146, 146, 147, 157, 146, 146, 147, 157, 148, 149, 196, 144, 151, 148, 149, 152, 153, 155, 156, 147, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 171, 172, 173, 174, 175, 176, 177, 179, 180, 181, 181, 182, 184, 195, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 1, 3, 6, 94, 118, 158, 178, 183 }; local _parse_sql_trans_actions = { [0] = 1, 0, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 20, 1, 3, 30, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 20, 1, 3, 26, 3, 3, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 20, 1, 3, 42, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 5, 5, 1, 5, 20, 46, 5, 1, 3, 34, 1, 14, 1, 17, 1, 1, 51, 38, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; local parse_sql_start = 196; local parse_sql_first_final = 196; local parse_sql_error = 0; local parse_sql_en_main = 196; local _sql_unescapes = setmetatable({ ["\\0"] = "\0"; ["\\'"] = "'"; ["\\\""] = "\""; ["\\b"] = "\b"; ["\\n"] = "\n"; ["\\r"] = "\r"; ["\\t"] = "\t"; ["\\Z"] = "\26"; ["\\\\"] = "\\"; ["\\%"] = "%"; ["\\_"] = "_"; },{ __index = function(t, s) assert(false, "Unknown escape sequences: "..s); end }); function parse_sql(data, h) local p = 1; local pe = #data + 1; local cs; local pos_char, pos_line = 1, 1; local mark, token; local table_name, columns, value_lists, value_list, value_count; cs = parse_sql_start; -- ragel flat exec local testEof = false; local _slen = 0; local _trans = 0; local _keys = 0; local _inds = 0; local _acts = 0; local _nacts = 0; local _tempval = 0; local _goto_level = 0; local _resume = 10; local _eof_trans = 15; local _again = 20; local _test_eof = 30; local _out = 40; while true do -- goto loop local _continue = false; repeat local _trigger_goto = false; if _goto_level <= 0 then -- noEnd if p == pe then _goto_level = _test_eof; _continue = true; break; end -- errState != 0 if cs == 0 then _goto_level = _out; _continue = true; break; end end -- _goto_level <= 0 if _goto_level <= _resume then _keys = cs * 2; -- LOCATE_TRANS _inds = _parse_sql_index_offsets[cs]; _slen = _parse_sql_key_spans[cs]; if _slen > 0 and _parse_sql_trans_keys[_keys] <= data:byte(p) and data:byte(p) <= _parse_sql_trans_keys[_keys + 1] then _trans = _parse_sql_indicies[ _inds + data:byte(p) - _parse_sql_trans_keys[_keys] ]; else _trans =_parse_sql_indicies[ _inds + _slen ]; end cs = _parse_sql_trans_targs[_trans]; if _parse_sql_trans_actions[_trans] ~= 0 then _acts = _parse_sql_trans_actions[_trans]; _nacts = _parse_sql_actions[_acts]; _acts = _acts + 1; while _nacts > 0 do _nacts = _nacts - 1; _acts = _acts + 1; _tempval = _parse_sql_actions[_acts - 1]; -- start action switch if _tempval == 0 then --4 FROM_STATE_ACTION_SWITCH -- line 34 "sql.rl" -- end of line directive pos_char = pos_char + 1; -- ACTION elseif _tempval == 1 then --4 FROM_STATE_ACTION_SWITCH -- line 35 "sql.rl" -- end of line directive pos_line = pos_line + 1; pos_char = 1; -- ACTION elseif _tempval == 2 then --4 FROM_STATE_ACTION_SWITCH -- line 38 "sql.rl" -- end of line directive mark = p; -- ACTION elseif _tempval == 3 then --4 FROM_STATE_ACTION_SWITCH -- line 39 "sql.rl" -- end of line directive token = data:sub(mark, p-1); -- ACTION elseif _tempval == 4 then --4 FROM_STATE_ACTION_SWITCH -- line 52 "sql.rl" -- end of line directive table.insert(columns, token); columns[#columns] = token; -- ACTION elseif _tempval == 5 then --4 FROM_STATE_ACTION_SWITCH -- line 58 "sql.rl" -- end of line directive table_name,columns = token,{}; -- ACTION elseif _tempval == 6 then --4 FROM_STATE_ACTION_SWITCH -- line 59 "sql.rl" -- end of line directive h.create(table_name, columns); -- ACTION elseif _tempval == 7 then --4 FROM_STATE_ACTION_SWITCH -- line 65 "sql.rl" -- end of line directive value_count = value_count + 1; value_list[value_count] = token:gsub("\\.", _sql_unescapes); -- ACTION elseif _tempval == 8 then --4 FROM_STATE_ACTION_SWITCH -- line 68 "sql.rl" -- end of line directive value_count = value_count + 1; value_list[value_count] = tonumber(token); -- ACTION elseif _tempval == 9 then --4 FROM_STATE_ACTION_SWITCH -- line 69 "sql.rl" -- end of line directive value_count = value_count + 1; -- ACTION elseif _tempval == 10 then --4 FROM_STATE_ACTION_SWITCH -- line 71 "sql.rl" -- end of line directive value_list,value_count = {},0; -- ACTION elseif _tempval == 11 then --4 FROM_STATE_ACTION_SWITCH -- line 71 "sql.rl" -- end of line directive table.insert(value_lists, value_list); -- ACTION elseif _tempval == 12 then --4 FROM_STATE_ACTION_SWITCH -- line 74 "sql.rl" -- end of line directive table_name,value_lists = token,{}; -- ACTION elseif _tempval == 13 then --4 FROM_STATE_ACTION_SWITCH -- line 75 "sql.rl" -- end of line directive h.insert(table_name, value_lists); -- ACTION end -- line 355 "sql.lua" -- end of line directive -- end action switch end -- while _nacts end if _trigger_goto then _continue = true; break; end end -- endif if _goto_level <= _again then if cs == 0 then _goto_level = _out; _continue = true; break; end p = p + 1; if p ~= pe then _goto_level = _resume; _continue = true; break; end end -- _goto_level <= _again if _goto_level <= _test_eof then end -- _goto_level <= _test_eof if _goto_level <= _out then break; end _continue = true; until true; if not _continue then break; end end -- endif _goto_level <= out -- end of execute block if cs < parse_sql_first_final then print("parse_sql: there was an error, line "..pos_line.." column "..pos_char); else print("Success. EOF at line "..pos_line.." column "..pos_char) end end end -- import modules package.path = package.path..";../?.lua;"; local my_name = arg[0]; if my_name:match("[/\\]") then package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua"); package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so"); end -- ugly workaround for getting datamanager to work outside of prosody :( prosody = { }; prosody.platform = "unknown"; if os.getenv("WINDIR") then prosody.platform = "windows"; elseif package.config:sub(1,1) == "/" then prosody.platform = "_posix"; end package.loaded["util.logger"] = {init = function() return function() end; end} local dm = require "util.datamanager"; dm.set_data_path("data"); local datetime = require "util.datetime"; local st = require "util.stanza"; local parse_xml = require "util.xml".parse; function store_password(username, host, password) -- create or update account for username@host local ret, err = dm.store(username, host, "accounts", {password = password}); print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password); end function store_vcard(username, host, stanza) -- create or update vCard for username@host local ret, err = dm.store(username, host, "vcard", st.preserialize(stanza)); print("["..(err or "success").."] stored vCard: "..username.."@"..host); end function store_roster(username, host, roster_items) -- fetch current roster-table for username@host if they already have one local roster = dm.load(username, host, "roster") or {}; -- merge imported roster-items with loaded roster for item_tag in roster_items:childtags() do -- jid for this roster-item local item_jid = item_tag.attr.jid -- validate item stanzas if (item_tag.name == "item") and (item_jid ~= "") then -- prepare roster item -- TODO: is the subscription attribute optional? local item = {subscription = item_tag.attr.subscription, groups = {}}; -- optional: give roster item a real name if item_tag.attr.name then item.name = item_tag.attr.name; end -- optional: iterate over group stanzas inside item stanza for group_tag in item_tag:childtags() do local group_name = group_tag:get_text(); if (group_tag.name == "group") and (group_name ~= "") then item.groups[group_name] = true; else print("[error] invalid group stanza: "..group_tag:pretty_print()); end end -- store item in roster roster[item_jid] = item; print("[success] roster entry: " ..username.."@"..host.." - "..item_jid); else print("[error] invalid roster stanza: " ..item_tag:pretty_print()); end end -- store merged roster-table local ret, err = dm.store(username, host, "roster", roster); print("["..(err or "success").."] stored roster: " ..username.."@"..host); end function store_subscription_request(username, host, presence_stanza) local from_bare = presence_stanza.attr.from; -- fetch current roster-table for username@host if they already have one local roster = dm.load(username, host, "roster") or {}; local item = roster[from_bare]; if item and (item.subscription == "from" or item.subscription == "both") then return; -- already subscribed, do nothing end -- add to table of pending subscriptions if not roster.pending then roster.pending = {}; end roster.pending[from_bare] = true; -- store updated roster-table local ret, err = dm.store(username, host, "roster", roster); print("["..(err or "success").."] stored subscription request: " ..username.."@"..host.." - "..from_bare); end local os_date = os.date; local os_time = os.time; local os_difftime = os.difftime; function datetime_parse(s) if s then local year, month, day, hour, min, sec, tzd; year, month, day, hour, min, sec, tzd = s:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$"); if year then local time_offset = os_difftime(os_time(os_date("*t")), os_time(os_date("!*t"))); -- to deal with local timezone local tzd_offset = 0; if tzd ~= "" and tzd ~= "Z" then local sign, h, m = tzd:match("([+%-])(%d%d):?(%d*)"); if not sign then return; end if #m ~= 2 then m = "0"; end h, m = tonumber(h), tonumber(m); tzd_offset = h * 60 * 60 + m * 60; if sign == "-" then tzd_offset = -tzd_offset; end end sec = (sec + time_offset) - tzd_offset; return os_time({year=year, month=month, day=day, hour=hour, min=min, sec=sec, isdst=false}); end end end function store_offline_messages(username, host, stanza) -- TODO: maybe use list_load(), append and list_store() instead -- of constantly reopening the file with list_append()? --for ch in offline_messages:childtags() do --print("message :"..ch:pretty_print()); stanza.attr.node = nil; local stamp = stanza:get_child("x", "jabber:x:delay"); if not stamp or not stamp.attr.stamp then print(2) return; end for i=1,#stanza do if stanza[i] == stamp then table.remove(stanza, i); break; end end for i=1,#stanza.tags do if stanza.tags[i] == stamp then table.remove(stanza.tags, i); break; end end local parsed_stamp = datetime_parse(stamp.attr.stamp); if not parsed_stamp then print(1, stamp.attr.stamp) return; end stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(parsed_stamp), datetime.legacy(parsed_stamp); local ret, err = dm.list_append(username, host, "offline", st.preserialize(stanza)); print("["..(err or "success").."] stored offline message: " ..username.."@"..host.." - "..stanza.attr.from); --end end -- load data local arg = ...; local help = "/? -? ? /h -h /help -help --help"; if not arg or help:find(arg, 1, true) then print([[XEP-227 importer for Prosody Usage: jabberd14sql2prosody.lua filename.sql ]]); os.exit(1); end local f = io.open(arg); local s = f:read("*a"); f:close(); local table_count = 0; local insert_count = 0; local row_count = 0; -- parse parse_sql(s, { create = function(table_name, columns) --[[print(table_name);]] table_count = table_count + 1; end; insert = function(table_name, value_lists) --[[print(table_name, #value_lists);]] insert_count = insert_count + 1; row_count = row_count + #value_lists; for _,value_list in ipairs(value_lists) do if table_name == "users" then local user, realm, password = unpack(value_list); store_password(user, realm, password); elseif table_name == "roster" then local user, realm, xml = unpack(value_list); local stanza,err = parse_xml(xml); if stanza then store_roster(user, realm, stanza); else print("[error] roster: XML parsing failed for "..user.."@"..realm..": "..err); end elseif table_name == "vcard" then local user, realm, name, email, nickname, birthday, photo, xml = unpack(value_list); if xml then local stanza,err = parse_xml(xml); if stanza then store_vcard(user, realm, stanza); else print("[error] vcard: XML parsing failed for "..user.."@"..realm..": "..err); end else --print("[warn] vcard: NULL vCard for "..user.."@"..realm..": "..err); end elseif table_name == "storedsubscriptionrequests" then local user, realm, fromjid, xml = unpack(value_list); local stanza,err = parse_xml(xml); if stanza then store_subscription_request(user, realm, stanza); else print("[error] storedsubscriptionrequests: XML parsing failed for "..user.."@"..realm..": "..err); end elseif table_name == "messages" then --local user, realm, node, correspondent, type, storetime, delivertime, subject, body, xml = unpack(value_list); local user, realm, type, xml = value_list[1], value_list[2], value_list[5], value_list[10]; if type == "offline" and xml ~= "" then local stanza,err = parse_xml(xml); if stanza then store_offline_messages(user, realm, stanza); else print("[error] offline messages: XML parsing failed for "..user.."@"..realm..": "..err); print(unpack(value_list)); end end end end end; }); print("table_count", table_count); print("insert_count", insert_count); print("row_count", row_count); prosody-0.12.4/tools/PaxHeaders/linedebug.lua0000644000000000000000000000012014475665104016165 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/linedebug.lua0000644000175000017500000000070214475665104020371 0ustar00prosodyprosody00000000000000local data = {} local getinfo = debug.getinfo; local function linehook(ev, li) local S = getinfo(2, "S"); if S and S.source and S.source:match"^@" then local file = S.source:sub(2); local lines = data[file]; if not lines then lines = {}; data[file] = lines; for line in io.lines(file) do lines[#lines+1] = line; end end io.stderr:write(ev, " ", file, " ", li, " ", lines[li], "\n"); end end debug.sethook(linehook, "l"); prosody-0.12.4/tools/PaxHeaders/make_repo.lua0000644000000000000000000000012014475665104016171 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.748764403 prosody-0.12.4/tools/make_repo.lua0000644000175000017500000000345414475665104020404 0ustar00prosodyprosody00000000000000print("Getting all the available modules") if os.execute '[ -e "./downloaded_modules" ]' then os.execute("rm -rf downloaded_modules") end os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") local i, popen = 0, io.popen local flag = "mod_" if os.execute '[ -e "./repository" ]' then os.execute("mkdir repository") end local pfile = popen('ls -a "downloaded_modules"') for filename in pfile:lines() do i = i + 1 if filename:sub(1, #flag) == flag then local file = io.open("repository/"..filename.."-scm-1.rockspec", "w") file:write('package = "'..filename..'"', '\n') file:write('version = "scm-1"', '\n') file:write('source = {', '\n') file:write('\turl = "hg+https://hg.prosody.im/prosody-modules",', '\n') file:write('\tdir = "prosody-modules"', '\n') file:write('}', '\n') file:write('description = {', '\n') file:write('\thomepage = "https://prosody.im/",', '\n') file:write('\tlicense = "MIT"', '\n') file:write('}', '\n') file:write('dependencies = {', '\n') file:write('\t"lua >= 5.1"', '\n') file:write('}', '\n') file:write('build = {', '\n') file:write('\ttype = "builtin",', '\n') file:write('\tmodules = {', '\n') file:write('\t\t["'..filename..'.'..filename..'"] = "'..filename..'/'..filename..'.lua"', '\n') file:write('\t}', '\n') file:write('}', '\n') file:close() end end pfile:close() os.execute("cd repository/ && luarocks-admin make_manifest ./ && chmod -R 644 ./*") print("") print("Done!. Modules' sources are locally available at ./downloaded_modules") print("Repository is available at ./repository") print("The repository contains all of prosody modules' respective rockspecs, as well as manifest files and an html Index") print("You can now either point your server to this folder, or copy its contents to another configured folder.") prosody-0.12.4/tools/PaxHeaders/migration0000644000000000000000000000013214475665104015443 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.748764403 30 ctime=1693936196.752764364 prosody-0.12.4/tools/migration/0000755000175000017500000000000014475665104017722 5ustar00prosodyprosody00000000000000prosody-0.12.4/tools/migration/PaxHeaders/Makefile0000644000000000000000000000012014475665104017155 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/tools/migration/Makefile0000644000175000017500000000246614475665104021372 0ustar00prosodyprosody00000000000000 include ../../config.unix BIN = $(DESTDIR)$(PREFIX)/bin CONFIG = $(DESTDIR)$(SYSCONFDIR) SOURCE = $(DESTDIR)$(LIBDIR)/prosody DATA = $(DESTDIR)$(DATADIR) MAN = $(DESTDIR)$(PREFIX)/share/man INSTALLEDSOURCE = $(LIBDIR)/prosody INSTALLEDCONFIG = $(SYSCONFDIR) INSTALLEDMODULES = $(LIBDIR)/prosody/modules INSTALLEDDATA = $(DATADIR) all: prosody-migrator.install migrator.cfg.lua.install prosody-migrator.lua install: prosody-migrator.install migrator.cfg.lua.install install -d $(BIN) $(CONFIG) $(SOURCE) install -d $(MAN)/man1 install -m755 ./prosody-migrator.install $(BIN)/prosody-migrator test -e $(CONFIG)/migrator.cfg.lua || install -m644 migrator.cfg.lua.install $(CONFIG)/migrator.cfg.lua clean: rm -f prosody-migrator.install rm -f migrator.cfg.lua.install prosody-migrator.install: prosody-migrator.lua sed "1s/\blua\b/$(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-migrator.lua > prosody-migrator.install migrator.cfg.lua.install: migrator.cfg.lua sed "s|^local data_path = .*;$$|local data_path = '$(INSTALLEDDATA)';|;" \ < migrator.cfg.lua > migrator.cfg.lua.install prosody-0.12.4/tools/migration/PaxHeaders/migrator0000644000000000000000000000013214475665104017267 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.752764364 30 ctime=1693936196.752764364 prosody-0.12.4/tools/migration/migrator/0000755000175000017500000000000014475665104021546 5ustar00prosodyprosody00000000000000prosody-0.12.4/tools/migration/migrator/PaxHeaders/jabberd14.lua0000644000000000000000000000012014475665104021602 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/tools/migration/migrator/jabberd14.lua0000644000175000017500000001062014475665104024006 0ustar00prosodyprosody00000000000000 local lfs = require "lfs"; local st = require "util.stanza"; local parse_xml = require "util.xml".parse; local os_getenv = os.getenv; local io_open = io.open; local assert = assert; local ipairs = ipairs; local coroutine = coroutine; local print = print; local function is_dir(path) return lfs.attributes(path, "mode") == "directory"; end local function is_file(path) return lfs.attributes(path, "mode") == "file"; end local function clean_path(path) return path:gsub("\\", "/"):gsub("//+", "/"):gsub("^~", os_getenv("HOME") or "~"); end local function load_xml(path) local f, err = io_open(path); if not f then return f, err; end local data = f:read("*a"); f:close(); if not data then return; end return parse_xml(data); end local function load_spool_file(host, filename, path) local xml = load_xml(path); if not xml then return; end local register_element = xml:get_child("query", "jabber:iq:register"); local username_element = register_element and register_element:get_child("username", "jabber:iq:register"); local password_element = register_element and register_element:get_child("password", "jabber:iq:auth"); local username = username_element and username_element:get_text(); local password = password_element and password_element:get_text(); if not username then print("[warn] Missing /xdb/{jabber:iq:register}register/username> in file "..filename) return; elseif username..".xml" ~= filename then print("[warn] Missing /xdb/{jabber:iq:register}register/username does not match filename "..filename); return; end local userdata = { user = username; host = host; stores = {}; }; local stores = userdata.stores; stores.accounts = { password = password }; for i=1,#xml.tags do local tag = xml.tags[i]; local xname = (tag.attr.xmlns or "")..":"..tag.name; if tag.attr.j_private_flag == "1" and tag.attr.xmlns then -- Private XML stores.private = stores.private or {}; tag.attr.j_private_flag = nil; stores.private[tag.attr.xmlns] = st.preserialize(tag); elseif xname == "jabber:iq:auth:password" then if stores.accounts.password ~= tag:get_text() then if password then print("[warn] conflicting passwords") else stores.accounts.password = tag:get_text(); end end elseif xname == "jabber:iq:register:query" then -- already processed elseif xname == "jabber:xdb:nslist:foo" then -- ignore elseif xname == "jabber:iq:auth:0k:zerok" then -- ignore elseif xname == "jabber:iq:roster:query" then -- Roster local roster = {}; local subscription_types = { from = true, to = true, both = true, none = true }; for _,item_element in ipairs(tag.tags) do assert(item_element.name == "item"); assert(item_element.attr.jid); assert(subscription_types[item_element.attr.subscription]); assert((item_element.attr.ask or "subscribe") == "subscribe") if item_element.name == "item" then local groups = {}; for _,group_element in ipairs(item_element.tags) do assert(group_element.name == "group"); groups[group_element:get_text()] = true; end local item = { name = item_element.attr.name; subscription = item_element.attr.subscription; ask = item_element.attr.ask; groups = groups; }; roster[item_element.attr.jid] = item; end end stores.roster = roster; elseif xname == "jabber:iq:last:query" then -- Last activity elseif xname == "jabber:x:offline:foo" then -- Offline messages elseif xname == "vcard-temp:vCard" then -- vCards stores.vcard = st.preserialize(tag); else print("[warn] Unknown tag: "..xname); end end return userdata; end local function loop_over_users(path, host, cb) for file in lfs.dir(path) do if file:match("%.xml$") then local user = load_spool_file(host, file, path.."/"..file); if user then cb(user); end end end end local function loop_over_hosts(path, cb) for host in lfs.dir(path) do if host ~= "." and host ~= ".." and is_dir(path.."/"..host) then loop_over_users(path.."/"..host, host, cb); end end end local function reader(input) local path = clean_path(assert(input.path, "no input.path specified")); assert(is_dir(path), "input.path is not a directory"); if input.host then return coroutine.wrap(function() loop_over_users(input.path, input.host, coroutine.yield) end); else return coroutine.wrap(function() loop_over_hosts(input.path, coroutine.yield) end); end end return { reader = reader; }; prosody-0.12.4/tools/migration/PaxHeaders/migrator.cfg.lua0000644000000000000000000000012014475665104020602 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/tools/migration/migrator.cfg.lua0000644000175000017500000000122614475665104023010 0ustar00prosodyprosody00000000000000local data_path = "../../data"; local vhost = { "accounts", "account_details", "roster", "vcard", "private", "blocklist", "privacy", "archive-archive", "offline-archive", "pubsub_nodes-pubsub", "pep-pubsub", } local muc = { "persistent", "config", "state", "muc_log-archive", }; input { hosts = { ["example.com"] = vhost; ["conference.example.com"] = muc; }; type = "internal"; path = data_path; } output { type = "sql"; driver = "SQLite3"; database = data_path.."/prosody.sqlite"; } --[[ input { type = "internal"; path = data_path; } output { type = "sql"; driver = "SQLite3"; database = data_path.."/prosody.sqlite"; } ]] prosody-0.12.4/tools/migration/PaxHeaders/prosody-migrator.lua0000644000000000000000000000012014475665104021541 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/tools/migration/prosody-migrator.lua0000644000175000017500000001651214475665104023753 0ustar00prosodyprosody00000000000000#!/usr/bin/env lua CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR"); CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR"); CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR"); CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR"); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- local function is_relative(path) local path_sep = package.config:sub(1,1); return ((path_sep == "/" and path:sub(1,1) ~= "/") or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\"))) end -- Tell Lua where to find our libraries if CFG_SOURCEDIR then local function filter_relative_paths(path) if is_relative(path) then return ""; end end local function sanitise_paths(paths) return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";")); end package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path); package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath); end -- Substitute ~ with path to home directory in data path if CFG_DATADIR then if os.getenv("HOME") then CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME")); end end local default_config = (CFG_CONFIGDIR or ".").."/migrator.cfg.lua"; local function usage() print("Usage: " .. arg[0] .. " [OPTIONS] FROM_STORE TO_STORE"); print(" --config FILE Specify config file") print(" --keep-going Keep going in case of errors"); print(" -v, --verbose Increase log-level"); print(""); print("If no stores are specified, 'input' and 'output' are used."); end local startup = require "util.startup"; do startup.parse_args({ short_params = { v = "verbose", h = "help", ["?"] = "help" }; value_params = { config = true }; }); startup.init_global_state(); prosody.process_type = "migrator"; if prosody.opts.help then usage(); os.exit(0); end startup.force_console_logging(); startup.init_logging(); startup.init_gc(); startup.init_errors(); startup.setup_plugindir(); startup.setup_plugin_install_path(); startup.setup_datadir(); startup.chdir(); startup.read_version(); startup.switch_user(); startup.check_dependencies(); startup.log_startup_warnings(); prosody.config_loaded = true; startup.load_libraries(); startup.init_http_client(); prosody.core_post_stanza = function () -- silence assert in core.moduleapi error("Attempt to send stanzas from inside migrator.", 0); end end -- Command-line parsing local options = prosody.opts; local envloadfile = require "util.envload".envloadfile; local config_file = options.config or default_config; local from_store = arg[1] or "input"; local to_store = arg[2] or "output"; config = {}; local config_env = setmetatable({}, { __index = function(t, k) return function(tbl) config[k] = tbl; end; end }); local config_chunk, err = envloadfile(config_file, config_env); if not config_chunk then print("There was an error loading the config file, check that the file exists"); print("and that the syntax is correct:"); print("", err); os.exit(1); end config_chunk(); local have_err; if #arg > 0 and #arg ~= 2 then have_err = true; print("Error: Incorrect number of parameters supplied."); end if not config[from_store] then have_err = true; print("Error: Input store '"..from_store.."' not found in the config file."); end if not config[to_store] then have_err = true; print("Error: Output store '"..to_store.."' not found in the config file."); end for store, conf in pairs(config) do -- COMPAT if conf.type == "prosody_files" then conf.type = "internal"; elseif conf.type == "prosody_sql" then conf.type = "sql"; end end if have_err then print(""); usage(); print(""); print("The available stores in your migrator config are:"); print(""); for store in pairs(config) do print("", store); end print(""); os.exit(1); end local async = require "util.async"; local server = require "net.server"; local watchers = { error = function (_, err) error(err); end; waiting = function () server.loop(); end; }; local cm = require "core.configmanager"; local hm = require "core.hostmanager"; local sm = require "core.storagemanager"; local um = require "core.usermanager"; local function users(store, host) if store.users then log("debug", "Using store user iterator") return store:users(); else log("debug", "Using usermanager user iterator") return um.users(host); end end local function prepare_config(host, conf) if conf.type == "internal" then sm.olddm.set_data_path(conf.path or prosody.paths.data); elseif conf.type == "sql" then cm.set(host, "sql", conf); end end local function get_driver(host, conf) prepare_config(host, conf); return assert(sm.load_driver(host, conf.type)); end local migrate_once = { keyval = function(origin, destination, user) local data, err = origin:get(user); assert(not err, err); assert(destination:set(user, data)); end; archive = function(origin, destination, user) local iter, err = origin:find(user); assert(iter, err); for id, item, when, with in iter do assert(destination:append(user, id, item, when, with)); end end; } migrate_once.pubsub = function(origin, destination, user, prefix, input_driver, output_driver) if not user and prefix == "pubsub_" then return end local data, err = origin:get(user); assert(not err, err); if not data then return end assert(destination:set(user, data)); if prefix == "pubsub_" then user = nil end for node in pairs(data) do local pep_origin = assert(input_driver:open(prefix .. node, "archive")); local pep_destination = assert(output_driver:open(prefix .. node, "archive")); migrate_once.archive(pep_origin, pep_destination, user); end end if options["keep-going"] then local xpcall = require "util.xpcall".xpcall; for t, f in pairs(migrate_once) do migrate_once[t] = function (origin, destination, user, ...) local function log_err(err) if user then log("error", "Error migrating data for user %q: %s", user, err); else log("error", "Error migrating data for host: %s", err); end log("debug", "%s", debug.traceback(nil, 2)); end xpcall(f, log_err, origin, destination, user, ...); end end end local migration_runner = async.runner(function (job) for host, stores in pairs(job.input.hosts) do prosody.hosts[host] = startup.make_host(host); sm.initialize_host(host); um.initialize_host(host); local input_driver = get_driver(host, job.input); local output_driver = get_driver(host, job.output); for _, store in ipairs(stores) do local p, typ = store:match("()%-(%w+)$"); if typ then store = store:sub(1, p-1); else typ = "keyval"; end log("info", "Migrating host %s store %s (%s)", host, store, typ); local migrate = assert(migrate_once[typ], "Unknown store type: "..typ); local prefix = store .. "_"; if typ == "pubsub" then typ = "keyval"; end if store == "pubsub_nodes" then prefix = "pubsub_"; end local origin = assert(input_driver:open(store, typ)); local destination = assert(output_driver:open(store, typ)); migrate(origin, destination, nil, prefix, input_driver, output_driver); -- host data for user in users(origin, host) do log("info", "Migrating user %s@%s store %s (%s)", user, host, store, typ); migrate(origin, destination, user, prefix, input_driver, output_driver); end end end end, watchers); io.stderr:write("Migrating...\n"); migration_runner:run({ input = config[from_store], output = config[to_store] }); io.stderr:write("Done!\n"); prosody-0.12.4/tools/PaxHeaders/modtrace.lua0000644000000000000000000000012014475665104016025 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/tools/modtrace.lua0000644000175000017500000000777514475665104020252 0ustar00prosodyprosody00000000000000-- Trace module calls and method calls on created objects -- -- Very rough and for debugging purposes only. It makes many -- assumptions and there are many ways it could fail. -- -- Example use: -- -- local dbuffer = require "tools.modtrace".trace("util.dbuffer"); -- local t_pack = require "util.table".pack; local serialize = require "util.serialization".serialize; local unpack = table.unpack or unpack; --luacheck: ignore 113 local set = require "util.set"; local serialize_cfg = { preset = "oneline"; freeze = true; fatal = false; fallback = function (v) return "<"..tostring(v)..">" end; }; local function stringify_value(v) if type(v) == "string" and #v > 20 then return (""):format(#v); elseif type(v) == "function" then return tostring(v); end return serialize(v, serialize_cfg); end local function stringify_params(...) local n = select("#", ...); local r = {}; for i = 1, n do table.insert(r, stringify_value((select(i, ...)))); end return table.concat(r, ", "); end local function stringify_result(ret) local r = {}; for i = 1, ret.n do table.insert(r, stringify_value(ret[i])); end return table.concat(r, ", "); end local function stringify_call(method_name, ...) return ("%s(%s)"):format(method_name, stringify_params(...)); end local function wrap_method(original_obj, original_method, method_name) method_name = ("<%s>:%s"):format(getmetatable(original_obj).__name or "object", method_name); return function (new_obj_self, ...) local opts = new_obj_self._modtrace_opts; local f = opts.output or io.stderr; f:write(stringify_call(method_name, ...)); local ret = t_pack(original_method(original_obj, ...)); if ret.n > 0 then f:write(" = ", stringify_result(ret), "\n"); else f:write("\n"); end return unpack(ret, 1, ret.n); end; end local function wrap_function(original_function, function_name, opts) local f = opts.output or io.stderr; return function (...) f:write(stringify_call(function_name, ...)); local ret = t_pack(original_function(...)); if ret.n > 0 then f:write(" = ", stringify_result(ret), "\n"); else f:write("\n"); end return unpack(ret, 1, ret.n); end; end local function wrap_metamethod(name, method) if name == "__index" then return function (new_obj, k) local original_method; if type(method) == "table" then original_method = new_obj._modtrace_original_obj[k]; else original_method = method(new_obj._modtrace_original_obj, k); end if original_method == nil then return nil; end return wrap_method(new_obj._modtrace_original_obj, original_method, k); end; end return function (new_obj, ...) return method(new_obj._modtrace_original_obj, ...); end; end local function wrap_mt(original_mt) local new_mt = {}; for k, v in pairs(original_mt) do new_mt[k] = wrap_metamethod(k, v); end return new_mt; end local function wrap_obj(original_obj, opts) local new_mt = wrap_mt(getmetatable(original_obj)); return setmetatable({_modtrace_original_obj = original_obj, _modtrace_opts = opts}, new_mt); end local function wrap_new(original_new, function_name, opts) local f = opts.output or io.stderr; return function (...) f:write(stringify_call(function_name, ...)); local ret = t_pack(original_new(...)); local obj = ret[1]; if ret.n == 1 and type(ret[1]) == "table" then f:write(" = <", getmetatable(ret[1]).__name or "object", ">", "\n"); elseif ret.n > 0 then f:write(" = ", stringify_result(ret), "\n"); else f:write("\n"); end if obj then ret[1] = wrap_obj(obj, opts); end return unpack(ret, 1, ret.n); end; end local function trace(module, opts) if type(module) == "string" then module = require(module); end opts = opts or {}; local new_methods = set.new(opts.new_methods or {"new"}); local fake_module = setmetatable({}, { __index = function (_, k) if new_methods:contains(k) then return wrap_new(module[k], k, opts); else return wrap_function(module[k], k, opts); end end; }); return fake_module; end return { wrap = trace; trace = trace; } prosody-0.12.4/tools/PaxHeaders/openfire2prosody.lua0000644000000000000000000000012014475665104017540 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/tools/openfire2prosody.lua0000644000175000017500000000616514475665104021755 0ustar00prosodyprosody00000000000000#!/usr/bin/env lua -- Prosody IM -- Copyright (C) 2008-2009 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- package.path = package.path..";../?.lua"; package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager local my_name = arg[0]; if my_name:match("[/\\]") then package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua"); package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so"); end -- ugly workaround for getting datamanager to work outside of prosody :( prosody = { }; prosody.platform = "unknown"; if os.getenv("WINDIR") then prosody.platform = "windows"; elseif package.config:sub(1,1) == "/" then prosody.platform = "posix"; end local parse_xml = require "util.xml".parse; ----------------------------------------------------------------------- package.loaded["util.logger"] = {init = function() return function() end; end} local dm = require "util.datamanager" dm.set_data_path("data"); local arg = ...; local help = "/? -? ? /h -h /help -help --help"; if not arg or help:find(arg, 1, true) then print([[Openfire importer for Prosody Usage: openfire2prosody.lua filename.xml hostname ]]); os.exit(1); end local host = select(2, ...) or "localhost"; local file = assert(io.open(arg)); local data = assert(file:read("*a")); file:close(); local xml = assert(parse_xml(data)); assert(xml.name == "Openfire", "The input file is not an Openfire XML export"); local substatus_mapping = { ["0"] = "none", ["1"] = "to", ["2"] = "from", ["3"] = "both" }; for _,tag in ipairs(xml.tags) do if tag.name == "User" then local username, password, roster; for _,tag in ipairs(tag.tags) do if tag.name == "Username" then username = tag:get_text(); elseif tag.name == "Password" then password = tag:get_text(); elseif tag.name == "Roster" then roster = {}; local pending = {}; for _,tag in ipairs(tag.tags) do if tag.name == "Item" then local jid = assert(tag.attr.jid, "Roster item has no JID"); if tag.attr.substatus ~= "-1" then local item = {}; item.name = tag.attr.name; item.subscription = assert(substatus_mapping[tag.attr.substatus], "invalid substatus"); item.ask = tag.attr.askstatus == "0" and "subscribe" or nil; local groups = {}; for _,tag in ipairs(tag) do if tag.name == "Group" then groups[tag:get_text()] = true; end end item.groups = groups; roster[jid] = item; end if tag.attr.recvstatus == "1" then pending[jid] = true; end end end if next(pending) then roster[false] = { pending = pending }; end end end assert(username and password, "No username or password"); local ret, err = dm.store(username, host, "accounts", {password = password}); print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password); if roster then local ret, err = dm.store(username, host, "roster", roster); print("["..(err or "success").."] stored roster: "..username.."@"..host.." = "..password); end end end prosody-0.12.4/tools/PaxHeaders/tb2err0000644000000000000000000000012014475665104014647 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/tools/tb2err0000755000175000017500000000107114475665104017056 0ustar00prosodyprosody00000000000000#!/usr/bin/env lua-any -- Lua-Versions: 5.3 5.2 5.1 -- traceback to errors.err for vim -q local path_sep = package.config:sub(1,1); for line in io.lines() do local src, err = line:match("%s*(%S+)(:%d+: .*)") if src then src = src:gsub("\\", path_sep); local cut = src:match("/()core/") or src:match("/()net/") or src:match("/()util/") or src:match("/()modules/") or src:match("/()plugins/") or src:match("/()prosody[ctl]*$") if cut then src = src:sub(cut); end src = src:gsub("^modules/", "plugins/") io.write(src, err, "\n"); end end prosody-0.12.4/PaxHeaders/util0000644000000000000000000000013214475665104013267 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.760764285 30 ctime=1693936196.792763972 prosody-0.12.4/util/0000755000175000017500000000000014475665104015546 5ustar00prosodyprosody00000000000000prosody-0.12.4/util/PaxHeaders/adhoc.lua0000644000000000000000000000012014475665104015122 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util/adhoc.lua0000644000175000017500000000241314475665104017327 0ustar00prosodyprosody00000000000000-- luacheck: ignore 212/self local function new_simple_form(form, result_handler) return function(self, data, state) if state or data.form then if data.action == "cancel" then return { status = "canceled" }; end local fields, err = form:data(data.form); return result_handler(fields, err, data); else return { status = "executing", actions = {"next", "complete", default = "complete"}, form = form }, "executing"; end end end local function new_initial_data_form(form, initial_data, result_handler) return function(self, data, state) if state or data.form then if data.action == "cancel" then return { status = "canceled" }; end local fields, err = form:data(data.form); return result_handler(fields, err, data); else local values, err = initial_data(data); if type(err) == "table" then return {status = "error"; error = err} elseif type(err) == "string" then return {status = "error"; error = {type = "cancel"; condition = "internal-server-error", err}} end return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = form, values = values } }, "executing"; end end end return { new_simple_form = new_simple_form, new_initial_data_form = new_initial_data_form }; prosody-0.12.4/util/PaxHeaders/adminstream.lua0000644000000000000000000000012014475665104016350 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util/adminstream.lua0000644000175000017500000002332214475665104020557 0ustar00prosodyprosody00000000000000local st = require "util.stanza"; local new_xmpp_stream = require "util.xmppstream".new; local sessionlib = require "util.session"; local gettime = require "util.time".now; local runner = require "util.async".runner; local add_task = require "util.timer".add_task; local events = require "util.events"; local server = require "net.server"; local stream_close_timeout = 5; local log = require "util.logger".init("adminstream"); local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; local stream_callbacks = { default_ns = "xmpp:prosody.im/admin" }; function stream_callbacks.streamopened(session, attr) -- run _streamopened in async context session.thread:run({ stream = "opened", attr = attr }); end function stream_callbacks._streamopened(session, attr) --luacheck: ignore 212/attr if session.type ~= "client" then session:open_stream(); end session.notopen = nil; end function stream_callbacks.streamclosed(session, attr) -- run _streamclosed in async context session.thread:run({ stream = "closed", attr = attr }); end function stream_callbacks._streamclosed(session) session.log("debug", "Received "); session:close(false); end function stream_callbacks.error(session, error, data) if error == "no-stream" then session.log("debug", "Invalid opening stream header (%s)", (data:gsub("^([^\1]+)\1", "{%1}"))); session:close("invalid-namespace"); elseif error == "parse-error" then session.log("debug", "Client XML parse error: %s", data); session:close("not-well-formed"); elseif error == "stream-error" then local condition, text = "undefined-condition"; for child in data:childtags(nil, xmlns_xmpp_streams) do if child.name ~= "text" then condition = child.name; else text = child:get_text(); end if condition ~= "undefined-condition" and text then break; end end text = condition .. (text and (" ("..text..")") or ""); session.log("info", "Session closed by remote with error: %s", text); session:close(nil, text); end end function stream_callbacks.handlestanza(session, stanza) session.thread:run(stanza); end local runner_callbacks = {}; function runner_callbacks:error(err) self.data.log("error", "Traceback[c2s]: %s", err); end local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; local function destroy_session(session, reason) if session.destroyed then return; end session.destroyed = true; session.log("debug", "Destroying session: %s", reason or "unknown reason"); end local function session_close(session, reason) local log = session.log or log; if session.conn then if session.notopen then session:open_stream(); end if reason then -- nil == no err, initiated by us, false == initiated by client local stream_error = st.stanza("stream:error"); if type(reason) == "string" then -- assume stream error stream_error:tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); elseif type(reason) == "table" then if reason.condition then stream_error:tag(reason.condition, stream_xmlns_attr):up(); if reason.text then stream_error:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then stream_error:add_child(reason.extra); end elseif reason.name then -- a stanza stream_error = reason; end end stream_error = tostring(stream_error); log("debug", "Disconnecting client, is: %s", stream_error); session.send(stream_error); end session.send(""); function session.send() return false; end local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason; session.log("debug", "c2s stream for %s closed: %s", session.full_jid or session.ip or "", reason_text or "session closed"); -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote local conn = session.conn; if reason_text == nil and not session.notopen and session.type == "c2s" then -- Grace time to process data from authenticated cleanly-closed stream add_task(stream_close_timeout, function () if not session.destroyed then session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); destroy_session(session); conn:close(); end end); else destroy_session(session, reason_text); conn:close(); end else local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason; destroy_session(session, reason_text); end end --- Public methods local function new_connection(socket_path, listeners) local have_unix, unix = pcall(require, "socket.unix"); if have_unix and type(unix) == "function" then -- COMPAT #1717 -- Before the introduction of datagram support, only the stream socket -- constructor was exported instead of a module table. Due to the lack of a -- proper release of LuaSocket, distros have settled on shipping either the -- last RC tag or some commit since then. -- Here we accomodate both variants. unix = { stream = unix }; end if type(unix) ~= "table" then have_unix = false; end local conn, sock; return { connect = function () if not have_unix then return nil, "no unix socket support"; end if sock or conn then return nil, "already connected"; end sock = unix.stream(); sock:settimeout(0); local ok, err = sock:connect(socket_path); if not ok then return nil, err; end conn = server.wrapclient(sock, nil, nil, listeners, "*a"); return true; end; disconnect = function () if conn then conn:close(); conn = nil; end if sock then sock:close(); sock = nil; end return true; end; }; end local function new_server(sessions, stanza_handler) local listeners = {}; function listeners.onconnect(conn) log("debug", "New connection"); local session = sessionlib.new("admin"); sessionlib.set_id(session); sessionlib.set_logger(session); sessionlib.set_conn(session, conn); session.conntime = gettime(); session.type = "admin"; local stream = new_xmpp_stream(session, stream_callbacks); session.stream = stream; session.notopen = true; session.thread = runner(function (stanza) if st.is_stanza(stanza) then stanza_handler(session, stanza); elseif stanza.stream == "opened" then stream_callbacks._streamopened(session, stanza.attr); elseif stanza.stream == "closed" then stream_callbacks._streamclosed(session, stanza.attr); end end, runner_callbacks, session); function session.data(data) -- Parse the data, which will store stanzas in session.pending_stanzas if data then local ok, err = stream:feed(data); if not ok then session.log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); session:close("not-well-formed"); end end end session.close = session_close; session.send = function (t) session.log("debug", "Sending[%s]: %s", session.type, t.top_tag and t:top_tag() or t:match("^[^>]*>?")); return session.rawsend(tostring(t)); end function session.rawsend(t) local ret, err = conn:write(t); if not ret then session.log("debug", "Error writing to connection: %s", err); return false, err; end return true; end sessions[conn] = session; end function listeners.onincoming(conn, data) local session = sessions[conn]; if session then session.data(data); end end function listeners.ondisconnect(conn, err) local session = sessions[conn]; if session then session.log("info", "Admin client disconnected: %s", err or "connection closed"); session.conn = nil; sessions[conn] = nil; end end function listeners.onreadtimeout(conn) return conn:send(" "); end return { listeners = listeners; }; end local function new_client() local client = { type = "client"; events = events.new(); log = log; }; local listeners = {}; function listeners.onconnect(conn) log("debug", "Connected"); client.conn = conn; local stream = new_xmpp_stream(client, stream_callbacks); client.stream = stream; client.notopen = true; client.thread = runner(function (stanza) if st.is_stanza(stanza) then if not client.events.fire_event("received", stanza) and not stanza.attr.xmlns then client.events.fire_event("received/"..stanza.name, stanza); end elseif stanza.stream == "opened" then stream_callbacks._streamopened(client, stanza.attr); client.events.fire_event("connected"); elseif stanza.stream == "closed" then client.events.fire_event("disconnected"); stream_callbacks._streamclosed(client, stanza.attr); end end, runner_callbacks, client); client.close = session_close; function client.send(t) client.log("debug", "Sending: %s", t.top_tag and t:top_tag() or t:match("^[^>]*>?")); return client.rawsend(tostring(t)); end function client.rawsend(t) local ret, err = conn:write(t); if not ret then client.log("debug", "Error writing to connection: %s", err); return false, err; end return true; end client.log("debug", "Opening stream..."); client:open_stream(); end function listeners.onincoming(conn, data) --luacheck: ignore 212/conn local ok, err = client.stream:feed(data); if not ok then client.log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); client:close("not-well-formed"); end end function listeners.ondisconnect(conn, err) --luacheck: ignore 212/conn client.log("info", "Admin client disconnected: %s", err or "connection closed"); client.conn = nil; client.events.fire_event("disconnected"); end function listeners.onreadtimeout(conn) conn:send(" "); end client.listeners = listeners; return client; end return { connection = new_connection; server = new_server; client = new_client; }; prosody-0.12.4/util/PaxHeaders/argparse.lua0000644000000000000000000000012014475665104015650 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util/argparse.lua0000644000175000017500000000232214475665104020054 0ustar00prosodyprosody00000000000000local function parse(arg, config) local short_params = config and config.short_params or {}; local value_params = config and config.value_params or {}; local parsed_opts = {}; if #arg == 0 then return parsed_opts; end while true do local raw_param = arg[1]; if not raw_param then break; end local prefix = raw_param:match("^%-%-?"); if not prefix then break; elseif prefix == "--" and raw_param == "--" then table.remove(arg, 1); break; end local param = table.remove(arg, 1):sub(#prefix+1); if #param == 1 and short_params then param = short_params[param]; end if not param then return nil, "param-not-found", raw_param; end local param_k, param_v; if value_params[param] then param_k, param_v = param, table.remove(arg, 1); if not param_v then return nil, "missing-value", raw_param; end else param_k, param_v = param:match("^([^=]+)=(.+)$"); if not param_k then if param:match("^no%-") then param_k, param_v = param:sub(4), false; else param_k, param_v = param, true; end end end parsed_opts[param_k] = param_v; end for i = 1, #arg do parsed_opts[i] = arg[i]; end return parsed_opts; end return { parse = parse; } prosody-0.12.4/util/PaxHeaders/array.lua0000644000000000000000000000012014475665104015162 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util/array.lua0000644000175000017500000001214514475665104017372 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local t_insert, t_sort, t_remove, t_concat = table.insert, table.sort, table.remove, table.concat; local setmetatable = setmetatable; local getmetatable = getmetatable; local math_random = math.random; local math_floor = math.floor; local pairs, ipairs = pairs, ipairs; local tostring = tostring; local type = type; local array = {}; local array_base = {}; local array_methods = {}; local array_mt = { __index = array_methods; __name = "array"; __tostring = function (self) return "{"..self:concat(", ").."}"; end; }; function array_mt:__freeze() return self; end local function new_array(self, t, _s, _var) if type(t) == "function" then -- Assume iterator t = self.collect(t, _s, _var); end return setmetatable(t or {}, array_mt); end function array.new(t) return setmetatable(t or {}, array_mt); end function array_mt.__add(a1, a2) local res = new_array(); return res:append(a1):append(a2); end function array_mt.__eq(a, b) if getmetatable(a) ~= array_mt or getmetatable(b) ~= array_mt then -- Lua 5.3+ calls this if both operands are tables, even if metatables differ return false; end if #a == #b then for i = 1, #a do if a[i] ~= b[i] then return false; end end else return false; end return true; end function array_mt.__div(a1, func) local a2 = new_array(); local o = 0; for i = 1, #a1 do local new_value = func(a1[i]); if new_value ~= nil then o = o + 1; a2[o] = new_value; end end return a2; end setmetatable(array, { __call = new_array }); -- Read-only methods function array_methods:random() return self[math_random(1, #self)]; end -- Return a random value excluding the one at idx function array_methods:random_other(idx) local max = #self; return self[((math.random(1, max-1)+(idx-1))%max)+1]; end -- These methods can be called two ways: -- array.method(existing_array, [params [, ...]]) -- Create new array for result -- existing_array:method([params, ...]) -- Transform existing array into result -- function array_base.map(outa, ina, func) for k, v in ipairs(ina) do outa[k] = func(v); end return outa; end function array_base.filter(outa, ina, func) local inplace, start_length = ina == outa, #ina; local write = 1; for read = 1, start_length do local v = ina[read]; if func(v) then outa[write] = v; write = write + 1; end end if inplace and write <= start_length then for i = write, start_length do outa[i] = nil; end end return outa; end function array_base.slice(outa, ina, i, j) if j == nil then j = -1; end if j < 0 then j = #ina + (j+1); end if i < 0 then i = #ina + (i+1); end if i < 1 then i = 1; end if j > #ina then j = #ina; end if i > j then for idx = 1, #outa do outa[idx] = nil; end return outa; end for idx = 1, 1+j-i do outa[idx] = ina[i+(idx-1)]; end if ina == outa then for idx = 2+j-i, #outa do outa[idx] = nil; end end return outa; end function array_base.sort(outa, ina, ...) if ina ~= outa then outa:append(ina); end t_sort(outa, ...); return outa; end function array_base.unique(outa, ina) local seen = {}; return array_base.filter(outa, ina, function (item) if seen[item] then return false; else seen[item] = true; return true; end end); end function array_base.pluck(outa, ina, key, default) for i = 1, #ina do local v = ina[i][key]; if v == nil then v = default; end outa[i] = v; end return outa; end function array_base.reverse(outa, ina) local len = #ina; if ina == outa then local middle = math_floor(len/2); len = len + 1; local o; -- opposite for i = 1, middle do o = len - i; outa[i], outa[o] = outa[o], outa[i]; end else local off = len + 1; for i = 1, len do outa[i] = ina[off - i]; end end return outa; end --- These methods only mutate the array function array_methods:shuffle() local len = #self; for i = 1, #self do local r = math_random(i, len); self[i], self[r] = self[r], self[i]; end return self; end function array_methods:append(ina) local len, len2 = #self, #ina; for i = 1, len2 do self[len+i] = ina[i]; end return self; end function array_methods:push(x) t_insert(self, x); return self; end array_methods.pop = t_remove; function array_methods:concat(sep) return t_concat(array.map(self, tostring), sep); end function array_methods:length() return #self; end --- These methods always create a new array function array.collect(f, s, var) local t = {}; while true do var = f(s, var); if var == nil then break; end t_insert(t, var); end return setmetatable(t, array_mt); end --- -- Setup methods from array_base for method, f in pairs(array_base) do local base_method = f; -- Setup global array method which makes new array array[method] = function (old_a, ...) local a = new_array(); return base_method(a, old_a, ...); end -- Setup per-array (mutating) method array_methods[method] = function (self, ...) return base_method(self, self, ...); end end return array; prosody-0.12.4/util/PaxHeaders/async.lua0000644000000000000000000000012014475665104015161 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util/async.lua0000644000175000017500000002047614475665104017377 0ustar00prosodyprosody00000000000000local logger = require "util.logger"; local log = logger.init("util.async"); local new_id = require "util.id".short; local xpcall = require "util.xpcall".xpcall; local function checkthread() local thread, main = coroutine.running(); if not thread or main then error("Not running in an async context, see https://prosody.im/doc/developers/util/async"); end return thread; end -- Configurable functions local schedule_task = nil; -- schedule_task(seconds, callback) local next_tick = function (f) f(); end local function runner_from_thread(thread) local level = 0; -- Find the 'level' of the top-most function (0 == current level, 1 == caller, ...) while debug.getinfo(thread, level, "") do level = level + 1; end local name, runner = debug.getlocal(thread, level-1, 1); if name ~= "self" or type(runner) ~= "table" or runner.thread ~= thread then return nil; end return runner; end local function call_watcher(runner, watcher_name, ...) local watcher = runner.watchers[watcher_name]; if not watcher then return false; end runner:log("debug", "Calling '%s' watcher", watcher_name); local ok, err = xpcall(watcher, debug.traceback, runner, ...); if not ok then runner:log("error", "Error in '%s' watcher: %s", watcher_name, err); return nil, err; end return true; end local function runner_continue(thread) -- ASSUMPTION: runner is in 'waiting' state (but we don't have the runner to know for sure) if coroutine.status(thread) ~= "suspended" then -- This should suffice log("error", "unexpected async state: thread not suspended"); return false; end local ok, state, runner = coroutine.resume(thread); if not ok then local err = state; -- Running the coroutine failed, which means we have to find the runner manually, -- in order to inform the error handler runner = runner_from_thread(thread); if not runner then log("error", "unexpected async state: unable to locate runner during error handling"); return false; end call_watcher(runner, "error", debug.traceback(thread, err)); runner.state = "ready"; return runner:run(); elseif state == "ready" then -- If state is 'ready', it is our responsibility to update runner.state from 'waiting'. -- We also have to :run(), because the queue might have further items that will not be -- processed otherwise. FIXME: It's probably best to do this in a nexttick (0 timer). next_tick(function () runner.state = "ready"; runner:run(); end); end return true; end local function waiter(num, allow_many) local thread = checkthread(); num = num or 1; local waiting; return function () if num == 0 then return; end -- already done waiting = true; coroutine.yield("wait"); end, function () num = num - 1; if num == 0 and waiting then runner_continue(thread); elseif not allow_many and num < 0 then error("done() called too many times"); end end; end local function guarder() local guards = {}; local default_id = {}; return function (id, func) id = id or default_id; local thread = checkthread(); local guard = guards[id]; if not guard then guard = {}; guards[id] = guard; log("debug", "New guard!"); else table.insert(guard, thread); log("debug", "Guarded. %d threads waiting.", #guard) coroutine.yield("wait"); end local function exit() local next_waiting = table.remove(guard, 1); if next_waiting then log("debug", "guard: Executing next waiting thread (%d left)", #guard) runner_continue(next_waiting); else log("debug", "Guard off duty.") guards[id] = nil; end end if func then func(); exit(); return; end return exit; end; end local function sleep(seconds) if not schedule_task then error("async.sleep() is not available - configure schedule function"); end local wait, done = waiter(); schedule_task(seconds, done); wait(); end local runner_mt = {}; runner_mt.__index = runner_mt; local function runner_create_thread(func, self) local thread = coroutine.create(function (self) -- luacheck: ignore 432/self while true do func(coroutine.yield("ready", self)); end end); debug.sethook(thread, debug.gethook()); assert(coroutine.resume(thread, self)); -- Start it up, it will return instantly to wait for the first input return thread; end local function default_error_watcher(runner, err) runner:log("error", "Encountered error: %s", err); error(err); end local function default_func(f) f(); end local function runner(func, watchers, data) local id = new_id(); local _log = logger.init("runner" .. id); return setmetatable({ func = func or default_func, thread = false, state = "ready", notified_state = "ready", queue = {}, watchers = watchers or { error = default_error_watcher }, data = data, id = id, _log = _log; } , runner_mt); end -- Add a task item for the runner to process function runner_mt:run(input) if input ~= nil then table.insert(self.queue, input); --self:log("debug", "queued new work item, %d items queued", #self.queue); end if self.state ~= "ready" then -- The runner is busy. Indicate that the task item has been -- queued, and return information about the current runner state return true, self.state, #self.queue; end local q, thread = self.queue, self.thread; if not thread or coroutine.status(thread) == "dead" then --luacheck: ignore 143/coroutine if thread and coroutine.close then coroutine.close(thread); end self:log("debug", "creating new coroutine"); -- Create a new coroutine for this runner thread = runner_create_thread(self.func, self); self.thread = thread; end -- Process task item(s) while the queue is not empty, and we're not blocked local n, state, err = #q, self.state, nil; self.state = "running"; --self:log("debug", "running main loop"); while n > 0 and state == "ready" and not err do local consumed; -- Loop through queue items, and attempt to run them for i = 1,n do local queued_input = q[i]; local ok, new_state = coroutine.resume(thread, queued_input); if not ok then -- There was an error running the coroutine, save the error, mark runner as ready to begin again consumed, state, err = i, "ready", debug.traceback(thread, new_state); self.thread = nil; break; elseif new_state == "wait" then -- Runner is blocked on waiting for a task item to complete consumed, state = i, "waiting"; break; end end -- Loop ended - either queue empty because all tasks passed without blocking (consumed == nil) -- or runner is blocked/errored, and consumed will contain the number of tasks processed so far if not consumed then consumed = n; end -- Remove consumed items from the queue array if q[n+1] ~= nil then n = #q; end for i = 1, n do q[i] = q[consumed+i]; end n = #q; end -- Runner processed all items it can, so save current runner state self.state = state; if err or state ~= self.notified_state then self:log("debug", "changed state from %s to %s", self.notified_state, err and ("error ("..state..")") or state); if err then state = "error" else self.notified_state = state; end local handler = self.watchers[state]; if handler then handler(self, err); end end if n > 0 then return self:run(); end return true, state, n; end -- Add a task item to the queue without invoking the runner, even if it is idle function runner_mt:enqueue(input) table.insert(self.queue, input); self:log("debug", "queued new work item, %d items queued", #self.queue); return self; end function runner_mt:log(level, fmt, ...) return self._log(level, fmt, ...); end function runner_mt:onready(f) self.watchers.ready = f; return self; end function runner_mt:onwaiting(f) self.watchers.waiting = f; return self; end function runner_mt:onerror(f) self.watchers.error = f; return self; end local function ready() return pcall(checkthread); end local function wait_for(promise) local async_wait, async_done = waiter(); local ret, err = nil, nil; promise:next( function (r) ret = r; end, function (e) err = e; end) :finally(async_done); async_wait(); if ret then return ret; else return nil, err; end end return { ready = ready; waiter = waiter; guarder = guarder; runner = runner; wait = wait_for; -- COMPAT w/trunk pre-0.12 wait_for = wait_for; sleep = sleep; set_nexttick = function(new_next_tick) next_tick = new_next_tick; end; set_schedule_function = function (new_schedule_function) schedule_task = new_schedule_function; end; }; prosody-0.12.4/util/PaxHeaders/bit53.lua0000644000000000000000000000012014475665104014772 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util/bit53.lua0000644000175000017500000000125414475665104017201 0ustar00prosodyprosody00000000000000-- Only the operators needed by net.websocket.frames are provided at this point return { band = function (a, b, ...) local ret = a & b; if ... then for i = 1, select("#", ...) do ret = ret & (select(i, ...)); end end return ret; end; bor = function (a, b, ...) local ret = a | b; if ... then for i = 1, select("#", ...) do ret = ret | (select(i, ...)); end end return ret; end; bxor = function (a, b, ...) local ret = a ~ b; if ... then for i = 1, select("#", ...) do ret = ret ~ (select(i, ...)); end end return ret; end; rshift = function (a, n) return a >> n end; lshift = function (a, n) return a << n end; }; prosody-0.12.4/util/PaxHeaders/bitcompat.lua0000644000000000000000000000012014475665104016026 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util/bitcompat.lua0000644000175000017500000000124614475665104020236 0ustar00prosodyprosody00000000000000-- Compatibility layer for bitwise operations -- First try the bit32 lib -- Lua 5.3 has it with compat enabled -- Lua 5.2 has it by default if _G.bit32 then return _G.bit32; else -- Lua 5.1 may have it as a standalone module that can be installed local ok, bitop = pcall(require, "bit32") if ok then return bitop; end end do -- Lua 5.3 and 5.4 would be able to use native infix operators local ok, bitop = pcall(require, "util.bit53") if ok then return bitop; end end do -- Lastly, try the LuaJIT bitop library local ok, bitop = pcall(require, "bit") if ok then return bitop; end end error "No bit module found. See https://prosody.im/doc/depends#bitop"; prosody-0.12.4/util/PaxHeaders/cache.lua0000644000000000000000000000012014475665104015107 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util/cache.lua0000644000175000017500000000734314475665104017323 0ustar00prosodyprosody00000000000000 local function _remove(list, m) if m.prev then m.prev.next = m.next; end if m.next then m.next.prev = m.prev; end if list._tail == m then list._tail = m.prev; end if list._head == m then list._head = m.next; end list._count = list._count - 1; end local function _insert(list, m) if list._head then list._head.prev = m; end m.prev, m.next = nil, list._head; list._head = m; if not list._tail then list._tail = m; end list._count = list._count + 1; end local cache_methods = {}; local cache_mt = { __name = "cache", __index = cache_methods }; function cache_methods:set(k, v) local m = self._data[k]; if m then -- Key already exists if v ~= nil then -- Bump to head of list _remove(self, m); _insert(self, m); m.value = v; else -- Remove from list _remove(self, m); self._data[k] = nil; end return true; end -- New key if v == nil then return true; end -- Check whether we need to remove oldest k/v if self._count == self.size then local tail = self._tail; local on_evict, evicted_key, evicted_value = self._on_evict, tail.key, tail.value; if on_evict ~= nil and (on_evict == false or on_evict(evicted_key, evicted_value) == false) then -- Cache is full, and we're not allowed to evict return false; end _remove(self, tail); self._data[evicted_key] = nil; end m = { key = k, value = v, prev = nil, next = nil }; self._data[k] = m; _insert(self, m); return true; end function cache_methods:get(k) local m = self._data[k]; if m then return m.value; end return nil; end function cache_methods:items() local m = self._head; return function () if not m then return; end local k, v = m.key, m.value; m = m.next; return k, v; end end function cache_methods:values() local m = self._head; return function () if not m then return; end local v = m.value; m = m.next; return v; end end function cache_methods:count() return self._count; end function cache_methods:head() local head = self._head; if not head then return nil, nil; end return head.key, head.value; end function cache_methods:tail() local tail = self._tail; if not tail then return nil, nil; end return tail.key, tail.value; end function cache_methods:resize(new_size) new_size = assert(tonumber(new_size), "cache size must be a number"); new_size = math.floor(new_size); assert(new_size > 0, "cache size must be greater than zero"); local on_evict = self._on_evict; while self._count > new_size do local tail = self._tail; local evicted_key, evicted_value = tail.key, tail.value; if on_evict ~= nil and (on_evict == false or on_evict(evicted_key, evicted_value) == false) then -- Cache is full, and we're not allowed to evict return false; end _remove(self, tail); self._data[evicted_key] = nil; end self.size = new_size; return true; end function cache_methods:table() --luacheck: ignore 212/t if not self.proxy_table then self.proxy_table = setmetatable({}, { __index = function (t, k) return self:get(k); end; __newindex = function (t, k, v) if not self:set(k, v) then error("failed to insert key into cache - full"); end end; __pairs = function (t) return self:items(); end; __len = function (t) return self:count(); end; }); end return self.proxy_table; end function cache_methods:clear() self._data = {}; self._count = 0; self._head = nil; self._tail = nil; end local function new(size, on_evict) size = assert(tonumber(size), "cache size must be a number"); size = math.floor(size); assert(size > 0, "cache size must be greater than zero"); local data = {}; return setmetatable({ _data = data, _count = 0, size = size, _head = nil, _tail = nil, _on_evict = on_evict }, cache_mt); end return { new = new; } prosody-0.12.4/util/PaxHeaders/caps.lua0000644000000000000000000000012014475665104014772 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util/caps.lua0000644000175000017500000000412114475665104017175 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local base64 = require "util.encodings".base64.encode; local sha1 = require "util.hashes".sha1; local t_insert, t_sort, t_concat = table.insert, table.sort, table.concat; local ipairs = ipairs; local _ENV = nil; -- luacheck: std none local function calculate_hash(disco_info) local identities, features, extensions = {}, {}, {}; for _, tag in ipairs(disco_info) do if tag.name == "identity" then t_insert(identities, (tag.attr.category or "").."\0"..(tag.attr.type or "").."\0"..(tag.attr["xml:lang"] or "").."\0"..(tag.attr.name or "")); elseif tag.name == "feature" then t_insert(features, tag.attr.var or ""); elseif tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then local form = {}; local FORM_TYPE; for _, field in ipairs(tag.tags) do if field.name == "field" and field.attr.var then local values = {}; for _, val in ipairs(field.tags) do val = #val.tags == 0 and val:get_text(); if val then t_insert(values, val); end end t_sort(values); if field.attr.var == "FORM_TYPE" then FORM_TYPE = values[1]; elseif #values > 0 then t_insert(form, field.attr.var.."\0"..t_concat(values, "<")); else t_insert(form, field.attr.var); end end end t_sort(form); form = t_concat(form, "<"); if FORM_TYPE then form = FORM_TYPE.."\0"..form; end t_insert(extensions, form); end end t_sort(identities); t_sort(features); t_sort(extensions); if #identities > 0 then identities = t_concat(identities, "<"):gsub("%z", "/").."<"; else identities = ""; end if #features > 0 then features = t_concat(features, "<").."<"; else features = ""; end if #extensions > 0 then extensions = t_concat(extensions, "<"):gsub("%z", "<").."<"; else extensions = ""; end local S = identities..features..extensions; local ver = base64(sha1(S)); return ver, S; end return { calculate_hash = calculate_hash; }; prosody-0.12.4/util/PaxHeaders/dataforms.lua0000644000000000000000000000012014475665104016024 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.764764246 prosody-0.12.4/util/dataforms.lua0000644000175000017500000002332514475665104020236 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local setmetatable = setmetatable; local ipairs = ipairs; local type, next = type, next; local tonumber = tonumber; local tostring = tostring; local t_concat = table.concat; local st = require "util.stanza"; local jid_prep = require "util.jid".prep; local datetime = require "util.datetime"; local _ENV = nil; -- luacheck: std none local xmlns_forms = 'jabber:x:data'; local xmlns_validate = 'http://jabber.org/protocol/xdata-validate'; local form_t = {}; local form_mt = { __index = form_t }; local function new(layout) return setmetatable(layout, form_mt); end function form_t.form(layout, data, formtype) if not formtype then formtype = "form" end local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype }); if formtype == "cancel" then return form; end if formtype ~= "submit" then if layout.title then form:tag("title"):text(layout.title):up(); end if layout.instructions then form:tag("instructions"):text(layout.instructions):up(); end end for _, field in ipairs(layout) do local field_type = field.type or "text-single"; -- Add field tag form:tag("field", { type = field_type, var = field.var or field.name, label = formtype ~= "submit" and field.label or nil }); if formtype ~= "submit" then if field.desc then form:text_tag("desc", field.desc); end end if formtype == "form" and field.datatype then form:tag("validate", { xmlns = xmlns_validate, datatype = field.datatype }); if field.range_min or field.range_max then form:tag("range", { min = field.range_min and tostring(field.range_min), max = field.range_max and tostring(field.range_max), }):up(); end -- assumed form:up(); end local value = field.value; local options = field.options; if data and data[field.name] ~= nil then value = data[field.name]; if formtype == "form" and type(value) == "table" and (field_type == "list-single" or field_type == "list-multi") then -- Allow passing dynamically generated options as values options, value = value, nil; end end if formtype == "form" and options then local defaults = {}; for _, val in ipairs(options) do if type(val) == "table" then form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up(); if val.default then defaults[#defaults+1] = val.value; end else form:tag("option", { label= val }):tag("value"):text(val):up():up(); end end if not value then if field_type == "list-single" then value = defaults[1]; elseif field_type == "list-multi" then value = defaults; end end end if value ~= nil then if type(value) == "number" then if field.datatype == "xs:dateTime" then value = datetime.datetime(value); elseif field_type == "boolean" then value = value ~= 0; elseif field.datatype == "xs:double" or field.datatype == "xs:decimal" then value = ("%f"):format(value); else value = ("%d"):format(value); end end -- Add value, depending on type if field_type == "hidden" then if type(value) == "table" then -- Assume an XML snippet form:tag("value") :add_child(value) :up(); else form:tag("value"):text(value):up(); end elseif field_type == "boolean" then form:tag("value"):text((value and "1") or "0"):up(); elseif field_type == "fixed" then form:tag("value"):text(value):up(); elseif field_type == "jid-multi" then for _, jid in ipairs(value) do form:tag("value"):text(jid):up(); end elseif field_type == "jid-single" then form:tag("value"):text(value):up(); elseif field_type == "text-single" or field_type == "text-private" then form:tag("value"):text(value):up(); elseif field_type == "text-multi" then -- Split into multiple tags, one for each line for line in value:gmatch("([^\r\n]+)\r?\n*") do form:tag("value"):text(line):up(); end elseif field_type == "list-single" then form:tag("value"):text(value):up(); elseif field_type == "list-multi" then for _, val in ipairs(value) do form:tag("value"):text(val):up(); end end end local media = field.media; if media then form:tag("media", { xmlns = "urn:xmpp:media-element", height = ("%d"):format(media.height), width = ("%d"):format(media.width) }); for _, val in ipairs(media) do form:tag("uri", { type = val.type }):text(val.uri):up() end form:up(); end if formtype == "form" and field.required then form:tag("required"):up(); end -- Jump back up to list of fields form:up(); end return form; end local field_readers = {}; local data_validators = {}; function form_t.data(layout, stanza, current) local data = {}; local errors = {}; local present = {}; for _, field in ipairs(layout) do local tag; for field_tag in stanza:childtags("field") do if (field.var or field.name) == field_tag.attr.var then tag = field_tag; break; end end if not tag then if current and current[field.name] ~= nil then data[field.name] = current[field.name]; elseif field.required then errors[field.name] = "Required value missing"; end elseif field.name then present[field.name] = true; local reader = field_readers[field.type]; if reader then local value, err = reader(tag, field.required); local validator = field.datatype and data_validators[field.datatype]; if value ~= nil and validator then local valid, ret = validator(value, field); if valid then value = ret; else value, err = nil, ret or ("Invalid value for data of type " .. field.datatype); end end data[field.name], errors[field.name] = value, err; end end end if next(errors) then return data, errors, present; end return data, nil, present; end local function simple_text(field_tag, required) local data = field_tag:get_child_text("value"); -- XEP-0004 does not say if an empty string is acceptable for a required value -- so we will follow HTML5 which says that empty string means missing if required and (data == nil or data == "") then return nil, "Required value missing"; end return data; -- Return whatever get_child_text returned, even if empty string end field_readers["text-single"] = simple_text; field_readers["text-private"] = simple_text; field_readers["jid-single"] = function (field_tag, required) local raw_data, err = simple_text(field_tag, required); if not raw_data then return raw_data, err; end local data = jid_prep(raw_data); if not data then return nil, "Invalid JID: " .. raw_data; end return data; end field_readers["jid-multi"] = function (field_tag, required) local result = {}; local err = {}; for value_tag in field_tag:childtags("value") do local raw_value = value_tag:get_text(); local value = jid_prep(raw_value); result[#result+1] = value; if raw_value and not value then err[#err+1] = ("Invalid JID: " .. raw_value); end end if #result > 0 then return result, (#err > 0 and t_concat(err, "\n") or nil); elseif required then return nil, "Required value missing"; end end field_readers["list-multi"] = function (field_tag, required) local result = {}; for value in field_tag:childtags("value") do result[#result+1] = value:get_text(); end if #result > 0 then return result; elseif required then return nil, "Required value missing"; end end field_readers["text-multi"] = function (field_tag, required) local data, err = field_readers["list-multi"](field_tag, required); if data then data = t_concat(data, "\n"); end return data, err; end field_readers["list-single"] = simple_text; local boolean_values = { ["1"] = true, ["true"] = true, ["0"] = false, ["false"] = false, }; field_readers["boolean"] = function (field_tag, required) local raw_value, err = simple_text(field_tag, required); if not raw_value then return raw_value, err; end local value = boolean_values[raw_value]; if value == nil then return nil, "Invalid boolean representation:" .. raw_value; end return value; end field_readers["hidden"] = function (field_tag) return field_tag:get_child_text("value"); end data_validators["xs:integer"] = function (data, field) local n = tonumber(data); if not n then return false, "not a number"; elseif n % 1 ~= 0 then return false, "not an integer"; end if field.range_max and n > field.range_max then return false, "out of bounds"; elseif field.range_min and n < field.range_min then return false, "out of bounds"; end return true, n; end data_validators["pubsub:integer-or-max"] = function (data, field) if data == "max" then return true, data; else return data_validators["xs:integer"](data, field); end end data_validators["xs:dateTime"] = function(data, field) -- luacheck: ignore 212/field local n = datetime.parse(data); if not n then return false, "invalid timestamp"; end return true, n; end local function get_form_type(form) if not st.is_stanza(form) then return nil, "not a stanza object"; elseif form.attr.xmlns ~= "jabber:x:data" or form.name ~= "x" then return nil, "not a dataform element"; end for field in form:childtags("field") do if field.attr.var == "FORM_TYPE" then return field:get_child_text("value"); end end return ""; end return { new = new; get_type = get_form_type; }; --[=[ Layout: { title = "MUC Configuration", instructions = [[Use this form to configure options for this MUC room.]], { name = "FORM_TYPE", type = "hidden", required = true }; { name = "field-name", type = "field-type", required = false }; } --]=] prosody-0.12.4/util/PaxHeaders/datamanager.lua0000644000000000000000000000012014475665104016310 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.764764246 prosody-0.12.4/util/datamanager.lua0000644000175000017500000002756714475665104020536 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local format = string.format; local setmetatable = setmetatable; local ipairs = ipairs; local char = string.char; local pcall = pcall; local log = require "util.logger".init("datamanager"); local io_open = io.open; local os_remove = os.remove; local os_rename = os.rename; local tonumber = tonumber; local next = next; local type = type; local t_insert = table.insert; local t_concat = table.concat; local envloadfile = require"util.envload".envloadfile; local serialize = require "util.serialization".serialize; local lfs = require "lfs"; -- Extract directory separator from package.config (an undocumented string that comes with lua) local path_separator = assert ( package.config:match ( "^([^\n]+)" ) , "package.config not in standard form" ) local prosody = prosody; local raw_mkdir = lfs.mkdir; local atomic_append; local ENOENT = 2; pcall(function() local pposix = require "util.pposix"; raw_mkdir = pposix.mkdir or raw_mkdir; -- Doesn't trample on umask atomic_append = pposix.atomic_append; ENOENT = pposix.ENOENT or ENOENT; end); local _ENV = nil; -- luacheck: std none ---- utils ----- local encode, decode, store_encode; do local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber(k, 16)); return t[k]; end }); decode = function (s) return s and (s:gsub("%%(%x%x)", urlcodes)); end encode = function (s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end -- Special encode function for store names, which historically were unencoded. -- All currently known stores use a-z and underscore, so this one preserves underscores. store_encode = function (s) return s and (s:gsub("[^_%w]", function (c) return format("%%%02x", c:byte()); end)); end end if not atomic_append then function atomic_append(f, data) local pos = f:seek(); if not f:write(data) or not f:flush() then f:seek("set", pos); f:write((" "):rep(#data)); f:flush(); return nil, "write-failed"; end return true; end end local _mkdir = {}; local function mkdir(path) path = path:gsub("/", path_separator); -- TODO as an optimization, do this during path creation rather than here if not _mkdir[path] then raw_mkdir(path); _mkdir[path] = true; end return path; end local data_path = (prosody and prosody.paths and prosody.paths.data) or "."; local callbacks = {}; ------- API ------------- local function set_data_path(path) log("debug", "Setting data path to: %s", path); data_path = path; end local function callback(username, host, datastore, data) for _, f in ipairs(callbacks) do username, host, datastore, data = f(username, host, datastore, data); if username == false then break; end end return username, host, datastore, data; end local function add_callback(func) if not callbacks[func] then -- Would you really want to set the same callback more than once? callbacks[func] = true; callbacks[#callbacks+1] = func; return true; end end local function remove_callback(func) if callbacks[func] then for i, f in ipairs(callbacks) do if f == func then callbacks[i] = nil; callbacks[f] = nil; return true; end end end end local function getpath(username, host, datastore, ext, create) ext = ext or "dat"; host = (host and encode(host)) or "_global"; username = username and encode(username); datastore = store_encode(datastore); if username then if create then mkdir(mkdir(mkdir(data_path).."/"..host).."/"..datastore); end return format("%s/%s/%s/%s.%s", data_path, host, datastore, username, ext); else if create then mkdir(mkdir(data_path).."/"..host); end return format("%s/%s/%s.%s", data_path, host, datastore, ext); end end local function load(username, host, datastore) local data, err, errno = envloadfile(getpath(username, host, datastore), {}); if not data then if errno == ENOENT then -- No such file, ok to ignore return nil; end log("error", "Failed to load %s storage ('%s') for user: %s@%s", datastore, err, username or "nil", host or "nil"); return nil, "Error reading storage"; end local success, ret = pcall(data); if not success then log("error", "Unable to load %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil"); return nil, "Error reading storage"; end return ret; end local function atomic_store(filename, data) local scratch = filename.."~"; local f, ok, msg, errno; -- luacheck: ignore errno -- TODO return util.error with code=errno? f, msg, errno = io_open(scratch, "w"); if not f then return nil, msg; end ok, msg = f:write(data); if not ok then f:close(); os_remove(scratch); return nil, msg; end ok, msg = f:close(); if not ok then os_remove(scratch); return nil, msg; end return os_rename(scratch, filename); end if prosody and prosody.platform ~= "posix" then -- os.rename does not overwrite existing files on Windows -- TODO We could use Transactional NTFS on Vista and above function atomic_store(filename, data) local f, err = io_open(filename, "w"); if not f then return f, err; end local ok, msg = f:write(data); if not ok then f:close(); return ok, msg; end return f:close(); end end local function store(username, host, datastore, data) if not data then data = {}; end username, host, datastore, data = callback(username, host, datastore, data); if username == false then return true; -- Don't save this data at all end -- save the datastore local d = "return " .. serialize(data) .. ";\n"; local mkdir_cache_cleared; repeat local ok, msg = atomic_store(getpath(username, host, datastore, nil, true), d); if not ok then if not mkdir_cache_cleared then -- We may need to recreate a removed directory _mkdir = {}; mkdir_cache_cleared = true; else log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil"); return nil, "Error saving to storage"; end end if next(data) == nil then -- try to delete empty datastore log("debug", "Removing empty %s datastore for user %s@%s", datastore, username or "nil", host or "nil"); os_remove(getpath(username, host, datastore)); end -- we write data even when we are deleting because lua doesn't have a -- platform independent way of checking for nonexisting files until ok; return true; end -- Append a blob of data to a file local function append(username, host, datastore, ext, data) if type(data) ~= "string" then return; end local filename = getpath(username, host, datastore, ext, true); local f = io_open(filename, "r+"); if not f then return atomic_store(filename, data); -- File did probably not exist, let's create it end local pos = f:seek("end"); local ok, msg = atomic_append(f, data); if not ok then f:close(); return ok, msg, "write"; end ok, msg = f:close(); if not ok then return ok, msg, "close"; end return true, pos; end local function list_append(username, host, datastore, data) if not data then return; end if callback(username, host, datastore) == false then return true; end -- save the datastore data = "item(" .. serialize(data) .. ");\n"; local ok, msg, where = append(username, host, datastore, "list", data); if not ok then log("error", "Unable to write to %s storage ('%s' in %s) for user: %s@%s", datastore, msg, where, username or "nil", host or "nil"); return ok, msg; end return true; end local function list_store(username, host, datastore, data) if not data then data = {}; end if callback(username, host, datastore) == false then return true; end -- save the datastore local d = {}; for i, item in ipairs(data) do d[i] = "item(" .. serialize(item) .. ");\n"; end local ok, msg = atomic_store(getpath(username, host, datastore, "list", true), t_concat(d)); if not ok then log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil"); return; end if next(data) == nil then -- try to delete empty datastore log("debug", "Removing empty %s datastore for user %s@%s", datastore, username or "nil", host or "nil"); os_remove(getpath(username, host, datastore, "list")); end -- we write data even when we are deleting because lua doesn't have a -- platform independent way of checking for nonexisting files return true; end local function list_load(username, host, datastore) local items = {}; local data, err, errno = envloadfile(getpath(username, host, datastore, "list"), {item = function(i) t_insert(items, i); end}); if not data then if errno == ENOENT then -- No such file, ok to ignore return nil; end log("error", "Failed to load %s storage ('%s') for user: %s@%s", datastore, err, username or "nil", host or "nil"); return nil, "Error reading storage"; end local success, ret = pcall(data); if not success then log("error", "Unable to load %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil"); return nil, "Error reading storage"; end return items; end local type_map = { keyval = "dat"; list = "list"; } local function users(host, store, typ) -- luacheck: ignore 431/store typ = "."..(type_map[typ or "keyval"] or typ); local store_dir = format("%s/%s/%s", data_path, encode(host), store_encode(store)); local mode, err = lfs.attributes(store_dir, "mode"); if not mode then return function() log("debug", "%s", err or (store_dir .. " does not exist")) end end local next, state = lfs.dir(store_dir); -- luacheck: ignore 431/next 431/state return function(state) -- luacheck: ignore 431/state for node in next, state do if node:sub(-#typ, -1) == typ then return decode(node:sub(1, -#typ-1)); end end end, state; end local function stores(username, host, typ) typ = type_map[typ or "keyval"]; local store_dir = format("%s/%s/", data_path, encode(host)); local mode, err = lfs.attributes(store_dir, "mode"); if not mode then return function() log("debug", "Could not iterate over stores in %s: %s", store_dir, err); end end local next, state = lfs.dir(store_dir); -- luacheck: ignore 431/next 431/state return function(state) -- luacheck: ignore 431/state for node in next, state do if not node:match"^%." then if username == true then if lfs.attributes(store_dir..node, "mode") == "directory" then return decode(node); end elseif username then local store_name = decode(node); if lfs.attributes(getpath(username, host, store_name, typ), "mode") then return store_name; end elseif lfs.attributes(node, "mode") == "file" then local file, ext = node:match("^(.*)%.([dalist]+)$"); if ext == typ then return decode(file) end end end end end, state; end local function do_remove(path) local ok, err = os_remove(path); if not ok and lfs.attributes(path, "mode") then return ok, err; end return true end local function purge(username, host) local host_dir = format("%s/%s/", data_path, encode(host)); local ok, iter, state, var = pcall(lfs.dir, host_dir); if not ok then return ok, iter; end local errs = {}; for file in iter, state, var do if lfs.attributes(host_dir..file, "mode") == "directory" then local store_name = decode(file); local ok, err = do_remove(getpath(username, host, store_name)); if not ok then errs[#errs+1] = err; end local ok, err = do_remove(getpath(username, host, store_name, "list")); if not ok then errs[#errs+1] = err; end end end return #errs == 0, t_concat(errs, ", "); end return { set_data_path = set_data_path; add_callback = add_callback; remove_callback = remove_callback; getpath = getpath; load = load; store = store; append_raw = append; store_raw = atomic_store; list_append = list_append; list_store = list_store; list_load = list_load; users = users; stores = stores; purge = purge; path_decode = decode; path_encode = encode; }; prosody-0.12.4/util/PaxHeaders/datamapper.lua0000644000000000000000000000012014475665104016162 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.764764246 prosody-0.12.4/util/datamapper.lua0000644000175000017500000002241614475665104020374 0ustar00prosodyprosody00000000000000-- This file is generated from teal-src/util/datamapper.lua local st = require("util.stanza"); local pointer = require("util.jsonpointer"); local schema_t = {} local function toboolean(s) if s == "true" or s == "1" then return true elseif s == "false" or s == "0" then return false elseif s then return true end end local function totype(t, s) if not s then return nil end if t == "string" then return s elseif t == "boolean" then return toboolean(s) elseif t == "number" or t == "integer" then return tonumber(s) end end local value_goes = {} local function resolve_schema(schema, root) if type(schema) == "table" then if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then return pointer.resolve(root, schema["$ref"]:sub(2)) end end return schema end local function guess_schema_type(schema) local schema_types = schema.type if type(schema_types) == "string" then return schema_types elseif schema_types ~= nil then error("schema has unsupported 'type' property") elseif schema.properties then return "object" elseif schema.items then return "array" end return "string" end local function unpack_propschema(propschema, propname, current_ns) local proptype = "string" local value_where = propname and "in_text_tag" or "in_text" local name = propname local namespace local prefix local single_attribute local enums if type(propschema) == "table" then proptype = guess_schema_type(propschema); elseif type(propschema) == "string" then error("schema as string is not supported: " .. propschema .. " {" .. current_ns .. "}" .. propname) end if proptype == "object" or proptype == "array" then value_where = "in_children" end if type(propschema) == "table" then local xml = propschema.xml if xml then if xml.name then name = xml.name end if xml.namespace and xml.namespace ~= current_ns then namespace = xml.namespace end if xml.prefix then prefix = xml.prefix end if proptype == "array" and xml.wrapped then value_where = "in_wrapper" elseif xml.attribute then value_where = "in_attribute" elseif xml.text then value_where = "in_text" elseif xml.x_name_is_value then value_where = "in_tag_name" elseif xml.x_single_attribute then single_attribute = xml.x_single_attribute value_where = "in_single_attribute" end end if propschema["const"] then enums = {propschema["const"]} elseif propschema["enum"] then enums = propschema["enum"] end end return proptype, value_where, name, namespace, prefix, single_attribute, enums end local parse_object local parse_array local function extract_value(s, value_where, proptype, name, namespace, prefix, single_attribute, enums) if value_where == "in_tag_name" then local c if proptype == "boolean" then c = s:get_child(name, namespace); elseif enums and proptype == "string" then for i = 1, #enums do c = s:get_child(enums[i], namespace); if c then break end end else c = s:get_child(nil, namespace); end if c then return c.name end elseif value_where == "in_attribute" then local attr = name if prefix then attr = prefix .. ":" .. name elseif namespace and namespace ~= s.attr.xmlns then attr = namespace .. "\1" .. name end return s.attr[attr] elseif value_where == "in_text" then return s:get_text() elseif value_where == "in_single_attribute" then local c = s:get_child(name, namespace) return c and c.attr[single_attribute] elseif value_where == "in_text_tag" then return s:get_child_text(name, namespace) end end function parse_object(schema, s, root) local out = {} schema = resolve_schema(schema, root) if type(schema) == "table" and schema.properties then for prop, propschema in pairs(schema.properties) do propschema = resolve_schema(propschema, root) local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns) if value_where == "in_children" and type(propschema) == "table" then if proptype == "object" then local c = s:get_child(name, namespace) if c then out[prop] = parse_object(propschema, c, root); end elseif proptype == "array" then local a = parse_array(propschema, s, root); if a and a[1] ~= nil then out[prop] = a; end else error("unreachable") end elseif value_where == "in_wrapper" and type(propschema) == "table" and proptype == "array" then local wrapper = s:get_child(name, namespace); if wrapper then out[prop] = parse_array(propschema, wrapper, root); end else local value = extract_value(s, value_where, proptype, name, namespace, prefix, single_attribute, enums) out[prop] = totype(proptype, value) end end end return out end function parse_array(schema, s, root) local itemschema = resolve_schema(schema.items, root); local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns) local attr_name if value_where == "in_single_attribute" then value_where = "in_attribute"; attr_name = single_attribute; end local out = {} if proptype == "object" then if type(itemschema) == "table" then for c in s:childtags(child_name, namespace) do table.insert(out, parse_object(itemschema, c, root)); end else error("array items must be schema object") end elseif proptype == "array" then if type(itemschema) == "table" then for c in s:childtags(child_name, namespace) do table.insert(out, parse_array(itemschema, c, root)); end end else for c in s:childtags(child_name, namespace) do local value = extract_value(c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums) table.insert(out, totype(proptype, value)); end end return out end local function parse(schema, s) local s_type = guess_schema_type(schema) if s_type == "object" then return parse_object(schema, s, schema) elseif s_type == "array" then return parse_array(schema, s, schema) else error("top-level scalars unsupported") end end local function toxmlstring(proptype, v) if proptype == "string" and type(v) == "string" then return v elseif proptype == "number" and type(v) == "number" then return string.format("%g", v) elseif proptype == "integer" and type(v) == "number" then return string.format("%d", v) elseif proptype == "boolean" then return v and "1" or "0" end end local unparse local function unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) if value_where == "in_attribute" then local attr = name if prefix then attr = prefix .. ":" .. name elseif namespace and namespace ~= current_ns then attr = namespace .. "\1" .. name end out.attr[attr] = toxmlstring(proptype, v) elseif value_where == "in_text" then out:text(toxmlstring(proptype, v)) elseif value_where == "in_single_attribute" then assert(single_attribute) local propattr = {} if namespace and namespace ~= current_ns then propattr.xmlns = namespace end propattr[single_attribute] = toxmlstring(proptype, v) out:tag(name, propattr):up(); else local propattr if namespace ~= current_ns then propattr = {xmlns = namespace} end if value_where == "in_tag_name" then if proptype == "string" and type(v) == "string" then out:tag(v, propattr):up(); elseif proptype == "boolean" and v == true then out:tag(name, propattr):up(); end elseif proptype == "object" and type(propschema) == "table" and type(v) == "table" then local c = unparse(propschema, v, name, namespace, nil, root); if c then out:add_direct_child(c); end elseif proptype == "array" and type(propschema) == "table" and type(v) == "table" then if value_where == "in_wrapper" then local c = unparse(propschema, v, name, namespace, nil, root); if c then out:add_direct_child(c); end else unparse(propschema, v, name, namespace, out, root); end else out:text_tag(name, toxmlstring(proptype, v), propattr) end end end function unparse(schema, t, current_name, current_ns, ctx, root) if root == nil then root = schema end if schema.xml then if schema.xml.name then current_name = schema.xml.name end if schema.xml.namespace then current_ns = schema.xml.namespace end end local out = ctx or st.stanza(current_name, {xmlns = current_ns}) local s_type = guess_schema_type(schema) if s_type == "object" then for prop, propschema in pairs(schema.properties) do propschema = resolve_schema(propschema, root) local v = t[prop] if v ~= nil then local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns) unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) end end return out elseif s_type == "array" then local itemschema = resolve_schema(schema.items, root) local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns) for _, item in ipairs(t) do unparse_property(out, item, proptype, itemschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) end return out end end return {parse = parse; unparse = unparse} prosody-0.12.4/util/PaxHeaders/datetime.lua0000644000000000000000000000012014475665104015640 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.764764246 prosody-0.12.4/util/datetime.lua0000644000175000017500000000314214475665104020045 0ustar00prosodyprosody00000000000000-- 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. -- -- XEP-0082: XMPP Date and Time Profiles local os_date = os.date; local os_time = os.time; local os_difftime = os.difftime; local tonumber = tonumber; local _ENV = nil; -- luacheck: std none local function date(t) return os_date("!%Y-%m-%d", t); end local function datetime(t) return os_date("!%Y-%m-%dT%H:%M:%SZ", t); end local function time(t) return os_date("!%H:%M:%S", t); end local function legacy(t) return os_date("!%Y%m%dT%H:%M:%S", t); end local function parse(s) if s then local year, month, day, hour, min, sec, tzd; year, month, day, hour, min, sec, tzd = s:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$"); if year then local now = os_time(); local time_offset = os_difftime(os_time(os_date("*t", now)), os_time(os_date("!*t", now))); -- to deal with local timezone local tzd_offset = 0; if tzd ~= "" and tzd ~= "Z" then local sign, h, m = tzd:match("([+%-])(%d%d):?(%d*)"); if not sign then return; end if #m ~= 2 then m = "0"; end h, m = tonumber(h), tonumber(m); tzd_offset = h * 60 * 60 + m * 60; if sign == "-" then tzd_offset = -tzd_offset; end end sec = (sec + time_offset) - tzd_offset; return os_time({year=year, month=month, day=day, hour=hour, min=min, sec=sec, isdst=false}); end end end return { date = date; datetime = datetime; time = time; legacy = legacy; parse = parse; }; prosody-0.12.4/util/PaxHeaders/dbuffer.lua0000644000000000000000000000012014475665104015461 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.764764246 prosody-0.12.4/util/dbuffer.lua0000644000175000017500000001171714475665104017675 0ustar00prosodyprosody00000000000000local queue = require "util.queue"; local s_byte, s_sub = string.byte, string.sub; local dbuffer_methods = {}; local dynamic_buffer_mt = { __name = "dbuffer", __index = dbuffer_methods }; function dbuffer_methods:write(data) if self.max_size and #data + self._length > self.max_size then return nil; end local ok = self.items:push(data); if not ok then self:collapse(); ok = self.items:push(data); end if not ok then return nil; end self._length = self._length + #data; return true; end function dbuffer_methods:read_chunk(requested_bytes) local chunk, consumed = self.items:peek(), self.front_consumed; if not chunk then return; end local chunk_length = #chunk; local remaining_chunk_length = chunk_length - consumed; if not requested_bytes then requested_bytes = remaining_chunk_length; end if remaining_chunk_length <= requested_bytes then self.front_consumed = 0; self._length = self._length - remaining_chunk_length; self.items:pop(); assert(#chunk:sub(consumed + 1, -1) == remaining_chunk_length); return chunk:sub(consumed + 1, -1), remaining_chunk_length; end local end_pos = consumed + requested_bytes; self.front_consumed = end_pos; self._length = self._length - requested_bytes; assert(#chunk:sub(consumed + 1, end_pos) == requested_bytes); return chunk:sub(consumed + 1, end_pos), requested_bytes; end function dbuffer_methods:read(requested_bytes) local chunks; if requested_bytes and requested_bytes > self._length then return nil; end local chunk, read_bytes = self:read_chunk(requested_bytes); if not requested_bytes then return chunk; elseif chunk then requested_bytes = requested_bytes - read_bytes; if requested_bytes == 0 then -- Already read everything we need return chunk; end chunks = {}; else return nil; end -- Need to keep reading more chunks while chunk do table.insert(chunks, chunk); if requested_bytes > 0 then chunk, read_bytes = self:read_chunk(requested_bytes); requested_bytes = requested_bytes - read_bytes; else break; end end return table.concat(chunks); end -- Read to, and including, the specified character sequence (return nil if not found) function dbuffer_methods:read_until(char) local buffer_pos = 0; for i, chunk in self.items:items() do local start = 1 + ((i == 1) and self.front_consumed or 0); local char_pos = chunk:find(char, start, true); if char_pos then return self:read(1 + buffer_pos + char_pos - start); end buffer_pos = buffer_pos + #chunk - (start - 1); end return nil; end function dbuffer_methods:discard(requested_bytes) if requested_bytes > self._length then return nil; end local chunk, read_bytes = self:read_chunk(requested_bytes); if chunk then requested_bytes = requested_bytes - read_bytes; if requested_bytes == 0 then -- Already read everything we need return true; end else return nil; end while chunk do if requested_bytes > 0 then chunk, read_bytes = self:read_chunk(requested_bytes); requested_bytes = requested_bytes - read_bytes; else break; end end return true; end -- Normalize i, j into absolute offsets within the -- front chunk (accounting for front_consumed), and -- ensure there is enough data in the first chunk -- to cover any subsequent :sub() or :byte() operation function dbuffer_methods:_prep_sub(i, j) if j == nil then j = -1; end if j < 0 then j = self._length + (j+1); end if i < 0 then i = self._length + (i+1); end if i < 1 then i = 1; end if j > self._length then j = self._length; end if i > j then return nil; end self:collapse(j); if self.front_consumed > 0 then i = i + self.front_consumed; j = j + self.front_consumed; end return i, j; end function dbuffer_methods:sub(i, j) i, j = self:_prep_sub(i, j); if not i then return ""; end return s_sub(self.items:peek(), i, j); end function dbuffer_methods:byte(i, j) i = i or 1; j = j or i; i, j = self:_prep_sub(i, j); if not i then return; end return s_byte(self.items:peek(), i, j); end function dbuffer_methods:length() return self._length; end dbuffer_methods.len = dbuffer_methods.length; -- strings have :len() dynamic_buffer_mt.__len = dbuffer_methods.length; -- support # operator function dbuffer_methods:collapse(bytes) bytes = bytes or self._length; local front_chunk = self.items:peek(); if not front_chunk or #front_chunk - self.front_consumed >= bytes then return; end local front_chunks = { front_chunk:sub(self.front_consumed+1) }; local front_bytes = #front_chunks[1]; while front_bytes < bytes do self.items:pop(); local chunk = self.items:peek(); front_bytes = front_bytes + #chunk; table.insert(front_chunks, chunk); end self.items:replace(table.concat(front_chunks)); self.front_consumed = 0; end local function new(max_size, max_chunks) if max_size and max_size <= 0 then return nil; end return setmetatable({ front_consumed = 0; _length = 0; max_size = max_size; items = queue.new(max_chunks or 32); }, dynamic_buffer_mt); end return { new = new; }; prosody-0.12.4/util/PaxHeaders/debug.lua0000644000000000000000000000012014475665104015132 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.764764246 prosody-0.12.4/util/debug.lua0000644000175000017500000001440414475665104017342 0ustar00prosodyprosody00000000000000-- Variables ending with these names will not -- have their values printed ('password' includes -- 'new_password', etc.) -- -- luacheck: ignore 122/debug local censored_names = { password = true; passwd = true; pass = true; pwd = true; }; local optimal_line_length = 65; local termcolours = require "util.termcolours"; local getstring = termcolours.getstring; local styles; do local _ = termcolours.getstyle; styles = { boundary_padding = _("bright"); filename = _("bright", "blue"); level_num = _("green"); funcname = _("yellow"); location = _("yellow"); }; end local function get_locals_table(thread, level) local locals = {}; for local_num = 1, math.huge do local name, value; if thread then name, value = debug.getlocal(thread, level, local_num); else name, value = debug.getlocal(level+1, local_num); end if not name then break; end table.insert(locals, { name = name, value = value }); end return locals; end local function get_upvalues_table(func) local upvalues = {}; if func then for upvalue_num = 1, math.huge do local name, value = debug.getupvalue(func, upvalue_num); if not name then break; end if name == "" then name = ("[%d]"):format(upvalue_num); end table.insert(upvalues, { name = name, value = value }); end end return upvalues; end local function string_from_var_table(var_table, max_line_len, indent_str) local var_string = {}; local col_pos = 0; max_line_len = max_line_len or math.huge; indent_str = "\n"..(indent_str or ""); for _, var in ipairs(var_table) do local name, value = var.name, var.value; if name:sub(1,1) ~= "(" then if type(value) == "string" then if censored_names[name:match("%a+$")] then value = ""; else value = ("%q"):format(value); end else value = tostring(value); end if #value > max_line_len then value = value:sub(1, max_line_len-3).."…"; end local str = ("%s = %s"):format(name, tostring(value)); col_pos = col_pos + #str; if col_pos > max_line_len then table.insert(var_string, indent_str); col_pos = 0; end table.insert(var_string, str); end end if #var_string == 0 then return nil; else return "{ "..table.concat(var_string, ", "):gsub(indent_str..", ", indent_str).." }"; end end local function get_traceback_table(thread, start_level) local levels = {}; for level = start_level, math.huge do local info; if thread then info = debug.getinfo(thread, level); else info = debug.getinfo(level+1); end if not info then break; end levels[(level-start_level)+1] = { level = level; info = info; locals = get_locals_table(thread, level+1); upvalues = get_upvalues_table(info.func); }; end return levels; end local function build_source_boundary_marker(last_source_desc) local padding = string.rep("-", math.floor(((optimal_line_length - 6) - #last_source_desc)/2)); return getstring(styles.boundary_padding, "v"..padding).." ".. getstring(styles.filename, last_source_desc).." ".. getstring(styles.boundary_padding, padding..(#last_source_desc%2==0 and "-v" or "v ")); end local function _traceback(thread, message, level) -- Lua manual says: debug.traceback ([thread,] [message [, level]]) -- I fathom this to mean one of: -- () -- (thread) -- (message, level) -- (thread, message, level) if thread == nil then -- Defaults thread, message, level = coroutine.running(), message, level; elseif type(thread) == "string" then thread, message, level = coroutine.running(), thread, message; elseif type(thread) ~= "thread" then return nil; -- debug.traceback() does this end level = level or 0; message = message and (message.."\n") or ""; -- +3 counts for this function, and the pcall() and wrapper above us, the +1... I don't know. local levels = get_traceback_table(thread, level+(thread == nil and 4 or 0)); local last_source_desc; local lines = {}; for nlevel, current_level in ipairs(levels) do local info = current_level.info; local line; local func_type = info.namewhat.." "; local source_desc = (info.short_src == "[C]" and "C code") or info.short_src or "Unknown"; if func_type == " " then func_type = ""; end; if info.short_src == "[C]" then line = "[ C ] "..func_type.."C function "..getstring(styles.location, (info.name and ("%q"):format(info.name) or "(unknown name)")); elseif info.what == "main" then line = "[Lua] "..getstring(styles.location, info.short_src.." line "..info.currentline); else local name = info.name or " "; if name ~= " " then name = ("%q"):format(name); end if func_type == "global " or func_type == "local " then func_type = func_type.."function "; end line = "[Lua] "..getstring(styles.location, info.short_src.." line ".. info.currentline).." in "..func_type..getstring(styles.funcname, name).. " (defined on line "..info.linedefined..")"; end if source_desc ~= last_source_desc then -- Venturing into a new source, add marker for previous last_source_desc = source_desc; table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc)); end nlevel = nlevel-1; table.insert(lines, "\t"..(nlevel==0 and ">" or " ")..getstring(styles.level_num, "("..nlevel..") ")..line); local npadding = (" "):rep(#tostring(nlevel)); if current_level.locals then local locals_str = string_from_var_table(current_level.locals, optimal_line_length, "\t "..npadding); if locals_str then table.insert(lines, "\t "..npadding.."Locals: "..locals_str); end end local upvalues_str = string_from_var_table(current_level.upvalues, optimal_line_length, "\t "..npadding); if upvalues_str then table.insert(lines, "\t "..npadding.."Upvals: "..upvalues_str); end end -- table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc)); return message.."stack traceback:\n"..table.concat(lines, "\n"); end local function traceback(...) local ok, ret = pcall(_traceback, ...); if not ok then return "Error in error handling: "..ret; end return ret; end local function use() debug.traceback = traceback; end return { get_locals_table = get_locals_table; get_upvalues_table = get_upvalues_table; string_from_var_table = string_from_var_table; get_traceback_table = get_traceback_table; traceback = traceback; use = use; }; prosody-0.12.4/util/PaxHeaders/dependencies.lua0000644000000000000000000000012014475665104016472 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.764764246 prosody-0.12.4/util/dependencies.lua0000644000175000017500000001465714475665104020714 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local function softreq(...) local ok, lib = pcall(require, ...); if ok then return lib; else return nil, lib; end end local platform_table = require "util.human.io".table({ { width = 15, align = "right" }, { width = "100%" } }); -- Required to be able to find packages installed with luarocks if not softreq "luarocks.loader" then -- LuaRocks 2.x softreq "luarocks.require"; -- LuaRocks <1.x end local function missingdep(name, sources, msg, err) -- luacheck: ignore err -- TODO print something about the underlying error, useful for debugging print(""); print("**************************"); print("Prosody was unable to find "..tostring(name)); print("This package can be obtained in the following ways:"); print(""); for _, row in ipairs(sources) do print(platform_table(row)); end print(""); print(msg or (name.." is required for Prosody to run, so we will now exit.")); print("More help can be found on our website, at https://prosody.im/doc/depends"); print("**************************"); print(""); end local function check_dependencies() if _VERSION < "Lua 5.1" then print "***********************************" print("Unsupported Lua version: ".._VERSION); print("At least Lua 5.1 is required."); print "***********************************" return false; end local fatal; local lxp, err = softreq "lxp" if not lxp then missingdep("luaexpat", { { "Debian/Ubuntu", "sudo apt install lua-expat" }; { "luarocks", "luarocks install luaexpat" }; { "Source", "http://matthewwild.co.uk/projects/luaexpat/" }; }, nil, err); fatal = true; end local socket, err = softreq "socket" if not socket then missingdep("luasocket", { { "Debian/Ubuntu", "sudo apt install lua-socket" }; { "luarocks", "luarocks install luasocket" }; { "Source", "http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/" }; }, nil, err); fatal = true; elseif not socket.tcp4 then -- COMPAT LuaSocket before being IP-version agnostic socket.tcp4 = socket.tcp; socket.udp4 = socket.udp; end local lfs, err = softreq "lfs" if not lfs then missingdep("luafilesystem", { { "luarocks", "luarocks install luafilesystem" }; { "Debian/Ubuntu", "sudo apt install lua-filesystem" }; { "Source", "http://www.keplerproject.org/luafilesystem/" }; }, nil, err); fatal = true; end local ssl, err = softreq "ssl" if not ssl then missingdep("LuaSec", { { "Debian/Ubuntu", "sudo apt install lua-sec" }; { "luarocks", "luarocks install luasec" }; { "Source", "https://github.com/brunoos/luasec" }; }, nil, err); end local bit, err = softreq"util.bitcompat"; if not bit then missingdep("lua-bitops", { { "Debian/Ubuntu", "sudo apt install lua-bitop" }; { "luarocks", "luarocks install luabitop" }; { "Source", "http://bitop.luajit.org/" }; }, "WebSocket support will not be available", err); end local unbound, err = softreq"lunbound"; -- luacheck: ignore 211/err if not unbound then missingdep("lua-unbound", { { "Debian/Ubuntu", "sudo apt install lua-unbound" }; { "luarocks", "luarocks install luaunbound" }; { "Source", "https://www.zash.se/luaunbound.html" }; }, "Old DNS resolver library will be used", err); else package.preload["net.adns"] = function () local ub = require "net.unbound"; return ub; end end local encodings, err = softreq "util.encodings" if not encodings then if err:match("module '[^']*' not found") then missingdep("util.encodings", { { "Windows", "Make sure you have encodings.dll from the Prosody distribution in util/" }; { "GNU/Linux", "Run './configure' and 'make' in the Prosody source directory to build util/encodings.so" }; }); else print "***********************************" print("util/encodings couldn't be loaded. Check that you have a recent version of libidn"); print "" print("The full error was:"); print(err) print "***********************************" end fatal = true; end local hashes, err = softreq "util.hashes" if not hashes then if err:match("module '[^']*' not found") then missingdep("util.hashes", { { "Windows", "Make sure you have hashes.dll from the Prosody distribution in util/" }; { "GNU/Linux", "Run './configure' and 'make' in the Prosody source directory to build util/hashes.so" }; }); else print "***********************************" print("util/hashes couldn't be loaded. Check that you have a recent version of OpenSSL (libcrypto in particular)"); print "" print("The full error was:"); print(err) print "***********************************" end fatal = true; end return not fatal; end local function log_warnings() if _VERSION > "Lua 5.4" then prosody.log("warn", "Support for %s is experimental, please report any issues", _VERSION); elseif _VERSION < "Lua 5.2" then prosody.log("warn", "%s has several issues and support is being phased out, consider upgrading", _VERSION); end local ssl = softreq"ssl"; if ssl then local major, minor, veryminor, patched = ssl._VERSION:match("(%d+)%.(%d+)%.?(%d*)(M?)"); if not major or ((tonumber(major) == 0 and (tonumber(minor) or 0) <= 3 and (tonumber(veryminor) or 0) <= 2) and patched ~= "M") then prosody.log("error", "This version of LuaSec contains a known bug that causes disconnects, see https://prosody.im/doc/depends"); end end local lxp = softreq"lxp"; if lxp then if not pcall(lxp.new, { StartDoctypeDecl = false }) then prosody.log("error", "The version of LuaExpat on your system leaves Prosody " .."vulnerable to denial-of-service attacks. You should upgrade to " .."LuaExpat 1.3.0 or higher as soon as possible. See " .."https://prosody.im/doc/depends#luaexpat for more information."); end if not lxp.new({}).getcurrentbytecount then prosody.log("error", "The version of LuaExpat on your system does not support " .."stanza size limits, which may leave servers on untrusted " .."networks (e.g. the internet) vulnerable to denial-of-service " .."attacks. You should upgrade to LuaExpat 1.3.0 or higher as " .."soon as possible. See " .."https://prosody.im/doc/depends#luaexpat for more information."); end end end return { softreq = softreq; missingdep = missingdep; check_dependencies = check_dependencies; log_warnings = log_warnings; }; prosody-0.12.4/util/PaxHeaders/dns.lua0000644000000000000000000000012014475665104014630 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.764764246 prosody-0.12.4/util/dns.lua0000644000175000017500000001427214475665104017043 0ustar00prosodyprosody00000000000000-- libunbound based net.adns replacement for Prosody IM -- Copyright (C) 2012-2015 Kim Alvefur -- Copyright (C) 2012 Waqas Hussain -- -- This file is MIT licensed. local setmetatable = setmetatable; local table = table; local t_concat = table.concat; local t_insert = table.insert; local s_byte = string.byte; local s_format = string.format; local s_sub = string.sub; local iana_data = require "util.dnsregistry"; local tohex = require "util.hex".encode; local inet_ntop = require "util.net".ntop; -- Simplified versions of Waqas DNS parsers -- Only the per RR parsers are needed and only feed a single RR local parsers = {}; -- No support for pointers, but libunbound appears to take care of that. local function readDnsName(packet, pos) if s_byte(packet, pos) == 0 then return ".", pos+1; end local pack_len, r, len = #packet, {}; pos = pos or 1; repeat len = s_byte(packet, pos) or 0; t_insert(r, s_sub(packet, pos + 1, pos + len)); pos = pos + len + 1; until len == 0 or pos >= pack_len; return t_concat(r, "."), pos; end -- These are just simple names. parsers.CNAME = readDnsName; parsers.NS = readDnsName parsers.PTR = readDnsName; local soa_mt = { __tostring = function(rr) return s_format("%s %s %d %d %d %d %d", rr.mname, rr.rname, rr.serial, rr.refresh, rr.retry, rr.expire, rr.minimum); end; }; function parsers.SOA(packet) local mname, rname, offset; mname, offset = readDnsName(packet, 1); rname, offset = readDnsName(packet, offset); -- Extract all the bytes of these fields in one call local s1, s2, s3, s4, -- serial r1, r2, r3, r4, -- refresh t1, t2, t3, t4, -- retry e1, e2, e3, e4, -- expire m1, m2, m3, m4 -- minimum = s_byte(packet, offset, offset + 19); return setmetatable({ mname = mname; rname = rname; serial = s1*0x1000000 + s2*0x10000 + s3*0x100 + s4; refresh = r1*0x1000000 + r2*0x10000 + r3*0x100 + r4; retry = t1*0x1000000 + t2*0x10000 + t3*0x100 + t4; expire = e1*0x1000000 + e2*0x10000 + e3*0x100 + e4; minimum = m1*0x1000000 + m2*0x10000 + m3*0x100 + m4; }, soa_mt); end parsers.A = inet_ntop; parsers.AAAA = inet_ntop; local mx_mt = { __tostring = function(rr) return s_format("%d %s", rr.pref, rr.mx) end }; function parsers.MX(packet) local name = readDnsName(packet, 3); local b1,b2 = s_byte(packet, 1, 2); return setmetatable({ pref = b1*256+b2; mx = name; }, mx_mt); end local srv_mt = { __tostring = function(rr) return s_format("%d %d %d %s", rr.priority, rr.weight, rr.port, rr.target); end }; function parsers.SRV(packet) local name = readDnsName(packet, 7); local b1, b2, b3, b4, b5, b6 = s_byte(packet, 1, 6); return setmetatable({ priority = b1*256+b2; weight = b3*256+b4; port = b5*256+b6; target = name; }, srv_mt); end local txt_mt = { __tostring = t_concat }; function parsers.TXT(packet) local pack_len = #packet; local r, pos, len = {}, 1; repeat len = s_byte(packet, pos) or 0; t_insert(r, s_sub(packet, pos + 1, pos + len)); pos = pos + len + 1; until pos >= pack_len; return setmetatable(r, txt_mt); end parsers.SPF = parsers.TXT; -- Acronyms from RFC 7218 local tlsa_usages = { [0] = "PKIX-CA"; [1] = "PKIX-EE"; [2] = "DANE-TA"; [3] = "DANE-EE"; [255] = "PrivCert"; }; local tlsa_selectors = { [0] = "Cert", [1] = "SPKI", [255] = "PrivSel", }; local tlsa_match_types = { [0] = "Full", [1] = "SHA2-256", [2] = "SHA2-512", [255] = "PrivMatch", }; local tlsa_mt = { __tostring = function(rr) return s_format("%s %s %s %s", tlsa_usages[rr.use] or rr.use, tlsa_selectors[rr.select] or rr.select, tlsa_match_types[rr.match] or rr.match, tohex(rr.data)); end; __index = { getUsage = function(rr) return tlsa_usages[rr.use] end; getSelector = function(rr) return tlsa_selectors[rr.select] end; getMatchType = function(rr) return tlsa_match_types[rr.match] end; } }; function parsers.TLSA(packet) local use, select, match = s_byte(packet, 1,3); return setmetatable({ use = use; select = select; match = match; data = s_sub(packet, 4); }, tlsa_mt); end local svcb_params = {"alpn"; "no-default-alpn"; "port"; "ipv4hint"; "ech"; "ipv6hint"}; setmetatable(svcb_params, {__index = function(_, n) return "key" .. tostring(n); end}); local svcb_mt = { __tostring = function (rr) local kv = {}; for i = 1, #rr.fields do t_insert(kv, s_format("%s=%q", svcb_params[rr.fields[i].key], tostring(rr.fields[i].value))); -- FIXME the =value part may be omitted when the value is "empty" end return s_format("%d %s %s", rr.prio, rr.name, t_concat(kv, " ")); end; }; local svbc_ip_mt = {__tostring = function(ip) return t_concat(ip, ", "); end} function parsers.SVCB(packet) local prio_h, prio_l = packet:byte(1,2); local prio = prio_h*256+prio_l; local name, pos = readDnsName(packet, 3); local fields = {}; while #packet > pos do local key_h, key_l = packet:byte(pos+0,pos+1); local len_h, len_l = packet:byte(pos+2,pos+3); local key = key_h*256+key_l; local len = len_h*256+len_l; local value = packet:sub(pos+4,pos+4-1+len) if key == 1 then value = setmetatable(parsers.TXT(value), svbc_ip_mt); elseif key == 3 then local port_h, port_l = value:byte(1,2); local port = port_h*256+port_l; value = port; elseif key == 4 then local ip = {}; for i = 1, #value, 4 do t_insert(ip, parsers.A(value:sub(i, i+3))); end value = setmetatable(ip, svbc_ip_mt); elseif key == 6 then local ip = {}; for i = 1, #value, 16 do t_insert(ip, parsers.AAAA(value:sub(i, i+15))); end value = setmetatable(ip, svbc_ip_mt); end t_insert(fields, { key = key, value = value, len = len }); pos = pos+len+4; end return setmetatable({ prio = prio, name = name, fields = fields, }, svcb_mt); end parsers.HTTPS = parsers.SVCB; local params = { TLSA = { use = tlsa_usages; select = tlsa_selectors; match = tlsa_match_types; }; }; local fallback_mt = { __tostring = function(rr) return s_format([[\# %d %s]], #rr.raw, tohex(rr.raw)); end; }; local function fallback_parser(packet) return setmetatable({ raw = packet },fallback_mt); end setmetatable(parsers, { __index = function() return fallback_parser end }); return { parsers = parsers; classes = iana_data.classes; types = iana_data.types; errors = iana_data.errors; params = params; }; prosody-0.12.4/util/PaxHeaders/dnsregistry.lua0000644000000000000000000000012014475665104016421 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/dnsregistry.lua0000644000175000017500000001037114475665104020630 0ustar00prosodyprosody00000000000000-- Source: https://www.iana.org/assignments/dns-parameters/dns-parameters.xml -- Generated on 2022-02-02 return { classes = { ["IN"] = 1; [1] = "IN"; ["CH"] = 3; [3] = "CH"; ["HS"] = 4; [4] = "HS"; ["ANY"] = 255; [255] = "ANY"; }; types = { ["A"] = 1; [1] = "A"; ["NS"] = 2; [2] = "NS"; ["MD"] = 3; [3] = "MD"; ["MF"] = 4; [4] = "MF"; ["CNAME"] = 5; [5] = "CNAME"; ["SOA"] = 6; [6] = "SOA"; ["MB"] = 7; [7] = "MB"; ["MG"] = 8; [8] = "MG"; ["MR"] = 9; [9] = "MR"; ["NULL"] = 10; [10] = "NULL"; ["WKS"] = 11; [11] = "WKS"; ["PTR"] = 12; [12] = "PTR"; ["HINFO"] = 13; [13] = "HINFO"; ["MINFO"] = 14; [14] = "MINFO"; ["MX"] = 15; [15] = "MX"; ["TXT"] = 16; [16] = "TXT"; ["RP"] = 17; [17] = "RP"; ["AFSDB"] = 18; [18] = "AFSDB"; ["X25"] = 19; [19] = "X25"; ["ISDN"] = 20; [20] = "ISDN"; ["RT"] = 21; [21] = "RT"; ["NSAP"] = 22; [22] = "NSAP"; ["NSAP-PTR"] = 23; [23] = "NSAP-PTR"; ["SIG"] = 24; [24] = "SIG"; ["KEY"] = 25; [25] = "KEY"; ["PX"] = 26; [26] = "PX"; ["GPOS"] = 27; [27] = "GPOS"; ["AAAA"] = 28; [28] = "AAAA"; ["LOC"] = 29; [29] = "LOC"; ["NXT"] = 30; [30] = "NXT"; ["EID"] = 31; [31] = "EID"; ["NIMLOC"] = 32; [32] = "NIMLOC"; ["SRV"] = 33; [33] = "SRV"; ["ATMA"] = 34; [34] = "ATMA"; ["NAPTR"] = 35; [35] = "NAPTR"; ["KX"] = 36; [36] = "KX"; ["CERT"] = 37; [37] = "CERT"; ["A6"] = 38; [38] = "A6"; ["DNAME"] = 39; [39] = "DNAME"; ["SINK"] = 40; [40] = "SINK"; ["OPT"] = 41; [41] = "OPT"; ["APL"] = 42; [42] = "APL"; ["DS"] = 43; [43] = "DS"; ["SSHFP"] = 44; [44] = "SSHFP"; ["IPSECKEY"] = 45; [45] = "IPSECKEY"; ["RRSIG"] = 46; [46] = "RRSIG"; ["NSEC"] = 47; [47] = "NSEC"; ["DNSKEY"] = 48; [48] = "DNSKEY"; ["DHCID"] = 49; [49] = "DHCID"; ["NSEC3"] = 50; [50] = "NSEC3"; ["NSEC3PARAM"] = 51; [51] = "NSEC3PARAM"; ["TLSA"] = 52; [52] = "TLSA"; ["SMIMEA"] = 53; [53] = "SMIMEA"; ["Unassigned"] = 54; [54] = "Unassigned"; ["HIP"] = 55; [55] = "HIP"; ["NINFO"] = 56; [56] = "NINFO"; ["RKEY"] = 57; [57] = "RKEY"; ["TALINK"] = 58; [58] = "TALINK"; ["CDS"] = 59; [59] = "CDS"; ["CDNSKEY"] = 60; [60] = "CDNSKEY"; ["OPENPGPKEY"] = 61; [61] = "OPENPGPKEY"; ["CSYNC"] = 62; [62] = "CSYNC"; ["ZONEMD"] = 63; [63] = "ZONEMD"; ["SVCB"] = 64; [64] = "SVCB"; ["HTTPS"] = 65; [65] = "HTTPS"; ["SPF"] = 99; [99] = "SPF"; ["NID"] = 104; [104] = "NID"; ["L32"] = 105; [105] = "L32"; ["L64"] = 106; [106] = "L64"; ["LP"] = 107; [107] = "LP"; ["EUI48"] = 108; [108] = "EUI48"; ["EUI64"] = 109; [109] = "EUI64"; ["TKEY"] = 249; [249] = "TKEY"; ["TSIG"] = 250; [250] = "TSIG"; ["IXFR"] = 251; [251] = "IXFR"; ["AXFR"] = 252; [252] = "AXFR"; ["MAILB"] = 253; [253] = "MAILB"; ["MAILA"] = 254; [254] = "MAILA"; ["*"] = 255; [255] = "*"; ["URI"] = 256; [256] = "URI"; ["CAA"] = 257; [257] = "CAA"; ["AVC"] = 258; [258] = "AVC"; ["DOA"] = 259; [259] = "DOA"; ["AMTRELAY"] = 260; [260] = "AMTRELAY"; ["TA"] = 32768; [32768] = "TA"; ["DLV"] = 32769; [32769] = "DLV"; }; errors = { [0] = "NoError"; ["NoError"] = "No Error"; [1] = "FormErr"; ["FormErr"] = "Format Error"; [2] = "ServFail"; ["ServFail"] = "Server Failure"; [3] = "NXDomain"; ["NXDomain"] = "Non-Existent Domain"; [4] = "NotImp"; ["NotImp"] = "Not Implemented"; [5] = "Refused"; ["Refused"] = "Query Refused"; [6] = "YXDomain"; ["YXDomain"] = "Name Exists when it should not"; [7] = "YXRRSet"; ["YXRRSet"] = "RR Set Exists when it should not"; [8] = "NXRRSet"; ["NXRRSet"] = "RR Set that should exist does not"; [9] = "NotAuth"; ["NotAuth"] = "Server Not Authoritative for zone"; -- [9] = "NotAuth"; ["NotAuth"] = "Not Authorized"; [10] = "NotZone"; ["NotZone"] = "Name not contained in zone"; [11] = "DSOTYPENI"; ["DSOTYPENI"] = "DSO-TYPE Not Implemented"; [16] = "BADVERS"; ["BADVERS"] = "Bad OPT Version"; -- [16] = "BADSIG"; ["BADSIG"] = "TSIG Signature Failure"; [17] = "BADKEY"; ["BADKEY"] = "Key not recognized"; [18] = "BADTIME"; ["BADTIME"] = "Signature out of time window"; [19] = "BADMODE"; ["BADMODE"] = "Bad TKEY Mode"; [20] = "BADNAME"; ["BADNAME"] = "Duplicate key name"; [21] = "BADALG"; ["BADALG"] = "Algorithm not supported"; [22] = "BADTRUNC"; ["BADTRUNC"] = "Bad Truncation"; [23] = "BADCOOKIE"; ["BADCOOKIE"] = "Bad/missing Server Cookie"; }; }; prosody-0.12.4/util/PaxHeaders/envload.lua0000644000000000000000000000012014475665104015474 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/envload.lua0000644000175000017500000000215314475665104017702 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2011 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- luacheck: ignore 113/setfenv 113/loadstring local load, loadstring, setfenv = load, loadstring, setfenv; local io_open = io.open; local envload; local envloadfile; if setfenv then function envload(code, source, env) local f, err = loadstring(code, source); if f and env then setfenv(f, env); end return f, err; end function envloadfile(file, env) local fh, err, errno = io_open(file); if not fh then return fh, err, errno; end local f, err = load(function () return fh:read(2048); end, "@"..file); fh:close(); if f and env then setfenv(f, env); end return f, err; end else function envload(code, source, env) return load(code, source, nil, env); end function envloadfile(file, env) local fh, err, errno = io_open(file); if not fh then return fh, err, errno; end local f, err = load(fh:lines(2048), "@"..file, nil, env); fh:close(); return f, err; end end return { envload = envload, envloadfile = envloadfile }; prosody-0.12.4/util/PaxHeaders/error.lua0000644000000000000000000000012014475665104015175 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/error.lua0000644000175000017500000000777714475665104017424 0ustar00prosodyprosody00000000000000local id = require "util.id"; local util_debug; -- only imported on-demand -- Library configuration (see configure()) local auto_inject_traceback = false; local error_mt = { __name = "error" }; function error_mt:__tostring() return ("error<%s:%s:%s>"):format(self.type, self.condition, self.text or ""); end local function is_error(e) return getmetatable(e) == error_mt; end local function configure(opt) if opt.auto_inject_traceback ~= nil then auto_inject_traceback = opt.auto_inject_traceback; if auto_inject_traceback then util_debug = require "util.debug"; end end end -- Do we want any more well-known fields? -- Or could we just copy all fields from `e`? -- Sometimes you want variable details in the `text`, how to handle that? -- Translations? -- Should the `type` be restricted to the stanza error types or free-form? -- What to set `type` to for stream errors or SASL errors? Those don't have a 'type' attr. local function new(e, context, registry, source) if is_error(e) then return e; end local template = registry and registry[e]; if not template then if type(e) == "table" then template = { code = e.code; type = e.type; condition = e.condition; text = e.text; extra = e.extra; }; else template = {}; end end context = context or {}; if auto_inject_traceback then context.traceback = util_debug.get_traceback_table(nil, 2); end local error_instance = setmetatable({ instance_id = id.short(); type = template.type or "cancel"; condition = template.condition or "undefined-condition"; text = template.text; code = template.code; extra = template.extra; context = context; source = source; }, error_mt); return error_instance; end -- compact --> normal form local function expand_registry(namespace, registry) local mapped = {} for err,template in pairs(registry) do local e = { type = template[1]; condition = template[2]; text = template[3]; }; if namespace and template[4] then e.extra = { namespace = namespace, condition = template[4] }; end mapped[err] = e; end return mapped; end local function init(source, namespace, registry) if type(namespace) == "table" then -- registry can be given as second argument if namespace is not used registry, namespace = namespace, nil; end local _, protoerr = next(registry, nil); if protoerr and type(next(protoerr)) == "number" then registry = expand_registry(namespace, registry); end local function wrap(e, context) if is_error(e) then return e; end local err = new(registry[e] or { type = "cancel", condition = "undefined-condition" }, context, registry, source); err.context.wrapped_error = e; return err; end return { source = source; registry = registry; new = function (e, context) return new(e, context, registry, source); end; coerce = function (ok, err, ...) if ok then return ok, err, ...; end return nil, wrap(err); end; wrap = wrap; is_error = is_error; }; end local function coerce(ok, err, ...) if ok or is_error(err) then return ok, err, ...; end local new_err = new({ type = "cancel", condition = "undefined-condition" }, { wrapped_error = err }); return ok, new_err, ...; end local function from_stanza(stanza, context, source) local error_type, condition, text, extra_tag = stanza:get_error(); local error_tag = stanza:get_child("error"); context = context or {}; context.stanza = stanza; context.by = error_tag and error_tag.attr.by or stanza.attr.from; local uri; if condition == "gone" or condition == "redirect" then uri = error_tag:get_child_text(condition, "urn:ietf:params:xml:ns:xmpp-stanzas"); end return new({ type = error_type or "cancel"; condition = condition or "undefined-condition"; text = text; extra = (extra_tag or uri) and { uri = uri; tag = extra_tag; } or nil; }, context, nil, source); end return { new = new; init = init; coerce = coerce; is_error = is_error; is_err = is_error; -- COMPAT w/ older 0.12 trunk from_stanza = from_stanza; configure = configure; } prosody-0.12.4/util/PaxHeaders/events.lua0000644000000000000000000000012014475665104015350 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/events.lua0000644000175000017500000001073114475665104017557 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local pairs = pairs; local t_insert = table.insert; local t_remove = table.remove; local t_sort = table.sort; local setmetatable = setmetatable; local next = next; local _ENV = nil; -- luacheck: std none local function new() -- Map event name to ordered list of handlers (lazily built): handlers[event_name] = array_of_handler_functions local handlers = {}; -- Array of wrapper functions that wrap all events (nil if empty) local global_wrappers; -- Per-event wrappers: wrappers[event_name] = wrapper_function local wrappers = {}; -- Event map: event_map[handler_function] = priority_number local event_map = {}; -- Debug hook, if any local active_debug_hook = nil; -- Called on-demand to build handlers entries local function _rebuild_index(self, event) local _handlers = event_map[event]; if not _handlers or next(_handlers) == nil then return; end local index = {}; for handler in pairs(_handlers) do t_insert(index, handler); end t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end); self[event] = index; return index; end; setmetatable(handlers, { __index = _rebuild_index }); local function add_handler(event, handler, priority) local map = event_map[event]; if map then map[handler] = priority or 0; else map = {[handler] = priority or 0}; event_map[event] = map; end handlers[event] = nil; end; local function remove_handler(event, handler) local map = event_map[event]; if map then map[handler] = nil; handlers[event] = nil; if next(map) == nil then event_map[event] = nil; end end end; local function get_handlers(event) return handlers[event]; end; local function add_handlers(self) for event, handler in pairs(self) do add_handler(event, handler); end end; local function remove_handlers(self) for event, handler in pairs(self) do remove_handler(event, handler); end end; local function _fire_event(event_name, event_data) local h = handlers[event_name]; if h and not active_debug_hook then for i=1,#h do local ret = h[i](event_data); if ret ~= nil then return ret; end end elseif h and active_debug_hook then for i=1,#h do local ret = active_debug_hook(h[i], event_name, event_data); if ret ~= nil then return ret; end end end end; local function fire_event(event_name, event_data) -- luacheck: ignore 432/event_name 432/event_data local w = wrappers[event_name] or global_wrappers; if w then local curr_wrapper = #w; local function c(event_name, event_data) curr_wrapper = curr_wrapper - 1; if curr_wrapper == 0 then if global_wrappers == nil or w == global_wrappers then return _fire_event(event_name, event_data); end w, curr_wrapper = global_wrappers, #global_wrappers; return w[curr_wrapper](c, event_name, event_data); else return w[curr_wrapper](c, event_name, event_data); end end return w[curr_wrapper](c, event_name, event_data); end return _fire_event(event_name, event_data); end local function add_wrapper(event_name, wrapper) local w; if event_name == false then w = global_wrappers; if not w then w = {}; global_wrappers = w; end else w = wrappers[event_name]; if not w then w = {}; wrappers[event_name] = w; end end w[#w+1] = wrapper; end local function remove_wrapper(event_name, wrapper) local w; if event_name == false then w = global_wrappers; else w = wrappers[event_name]; end if not w then return; end for i = #w, 1, -1 do if w[i] == wrapper then t_remove(w, i); end end if #w == 0 then if event_name == false then global_wrappers = nil; else wrappers[event_name] = nil; end end end local function set_debug_hook(new_hook) local old_hook = active_debug_hook; active_debug_hook = new_hook; return old_hook; end return { add_handler = add_handler; remove_handler = remove_handler; add_handlers = add_handlers; remove_handlers = remove_handlers; get_handlers = get_handlers; wrappers = { add_handler = add_wrapper; remove_handler = remove_wrapper; }; add_wrapper = add_wrapper; remove_wrapper = remove_wrapper; set_debug_hook = set_debug_hook; fire_event = fire_event; _handlers = handlers; _event_map = event_map; }; end return { new = new; }; prosody-0.12.4/util/PaxHeaders/filters.lua0000644000000000000000000000012014475665104015514 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/filters.lua0000644000175000017500000000410514475665104017721 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local t_insert, t_remove = table.insert, table.remove; local _ENV = nil; -- luacheck: std none local new_filter_hooks = {}; local function initialize(session) if not session.filters then local filters = {}; session.filters = filters; function session.filter(type, data) local filter_list = filters[type]; if filter_list then for i = 1, #filter_list do data = filter_list[i](data, session); if data == nil then break; end end end return data; end end for i=1,#new_filter_hooks do new_filter_hooks[i](session); end return session.filter; end local function add_filter(session, type, callback, priority) if not session.filters then initialize(session); end local filter_list = session.filters[type]; if not filter_list then filter_list = {}; session.filters[type] = filter_list; elseif filter_list[callback] then return; -- Filter already added end priority = priority or 0; local i = 0; repeat i = i + 1; until not filter_list[i] or filter_list[filter_list[i]] < priority; t_insert(filter_list, i, callback); filter_list[callback] = priority; end local function remove_filter(session, type, callback) if not session.filters then return; end local filter_list = session.filters[type]; if filter_list and filter_list[callback] then for i=1, #filter_list do if filter_list[i] == callback then t_remove(filter_list, i); filter_list[callback] = nil; return true; end end end end local function add_filter_hook(callback) t_insert(new_filter_hooks, callback); end local function remove_filter_hook(callback) for i=1,#new_filter_hooks do if new_filter_hooks[i] == callback then t_remove(new_filter_hooks, i); end end end return { initialize = initialize; add_filter = add_filter; remove_filter = remove_filter; add_filter_hook = add_filter_hook; remove_filter_hook = remove_filter_hook; }; prosody-0.12.4/util/PaxHeaders/format.lua0000644000000000000000000000012014475665104015334 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/format.lua0000644000175000017500000001207514475665104017546 0ustar00prosodyprosody00000000000000-- -- A string.format wrapper that gracefully handles invalid arguments since -- certain format string and argument combinations may cause errors or other -- issues like log spoofing -- -- Provides some protection from e.g. CAPEC-135, CWE-117, CWE-134, CWE-93 local tostring = tostring; local unpack = table.unpack or unpack; -- luacheck: ignore 113/unpack local pack = require "util.table".pack; -- TODO table.pack in 5.2+ local valid_utf8 = require "util.encodings".utf8.valid; local type = type; local dump = require "util.serialization".new("debug"); local num_type = math.type or function (n) return n % 1 == 0 and n <= 9007199254740992 and n >= -9007199254740992 and "integer" or "float"; end -- In Lua 5.3+ these formats throw an error if given a float local expects_integer = { c = true, d = true, i = true, o = true, u = true, X = true, x = true, }; -- In Lua 5.2 these throw an error given a negative number local expects_positive = { o = true; u = true; x = true; X = true }; -- Printable Unicode replacements for control characters local control_symbols = { -- 0x00 .. 0x1F --> U+2400 .. U+241F, 0x7F --> U+2421 ["\000"] = "\226\144\128", ["\001"] = "\226\144\129", ["\002"] = "\226\144\130", ["\003"] = "\226\144\131", ["\004"] = "\226\144\132", ["\005"] = "\226\144\133", ["\006"] = "\226\144\134", ["\007"] = "\226\144\135", ["\008"] = "\226\144\136", ["\009"] = "\226\144\137", ["\010"] = "\226\144\138", ["\011"] = "\226\144\139", ["\012"] = "\226\144\140", ["\013"] = "\226\144\141", ["\014"] = "\226\144\142", ["\015"] = "\226\144\143", ["\016"] = "\226\144\144", ["\017"] = "\226\144\145", ["\018"] = "\226\144\146", ["\019"] = "\226\144\147", ["\020"] = "\226\144\148", ["\021"] = "\226\144\149", ["\022"] = "\226\144\150", ["\023"] = "\226\144\151", ["\024"] = "\226\144\152", ["\025"] = "\226\144\153", ["\026"] = "\226\144\154", ["\027"] = "\226\144\155", ["\028"] = "\226\144\156", ["\029"] = "\226\144\157", ["\030"] = "\226\144\158", ["\031"] = "\226\144\159", ["\127"] = "\226\144\161", }; local supports_p = pcall(string.format, "%p", ""); -- >= Lua 5.4 local supports_a = pcall(string.format, "%a", 0.0); -- > Lua 5.1 local function format(formatstring, ...) local args = pack(...); local args_length = args.n; -- format specifier spec: -- 1. Start: '%%' -- 2. Flags: '[%-%+ #0]' -- 3. Width: '%d?%d?' -- 4. Precision: '%.?%d?%d?' -- 5. Option: '[cdiouxXaAeEfgGqs%%]' -- -- The options c, d, E, e, f, g, G, i, o, u, X, and x all expect a number as argument, whereas q and s expect a string. -- This function does not accept string values containing embedded zeros, except as arguments to the q option. -- a and A are only in Lua 5.2+ -- Lua 5.4 adds a p format that produces a pointer -- process each format specifier local i = 0; formatstring = formatstring:gsub("%%[^cdiouxXaAeEfgGpqs%%]*[cdiouxXaAeEfgGpqs%%]", function(spec) if spec == "%%" then return end i = i + 1; local arg = args[i]; if arg == nil then args[i] = "nil"; return "(%s)"; end local option = spec:sub(-1); local t = type(arg); if option == "s" and t == "string" and not arg:find("[%z\1-\31\128-\255]") then -- No UTF-8 or control characters, assumed to be the common case. return elseif t == "number" then if option == "g" or (option == "d" and num_type(arg) == "integer") then return end elseif option == "s" and t ~= "string" then arg = tostring(arg); t = "string"; end if option ~= "s" and option ~= "q" and option ~= "p" then -- all other options expect numbers if t ~= "number" then -- arg isn't number as expected? arg = tostring(arg); option = "s"; spec = "[%s]"; t = "string"; elseif expects_integer[option] and num_type(arg) ~= "integer" then args[i] = tostring(arg); return "[%s]"; elseif expects_positive[option] and arg < 0 then args[i] = tostring(arg); return "[%s]"; elseif (option == "a" or option == "A") and not supports_a then return "%x"; else return -- acceptable number end end if option == "p" and not supports_p then arg = tostring(arg); option = "s"; spec = "[%s]"; t = "string"; end if t == "string" and option ~= "p" then if not valid_utf8(arg) then option = "q"; elseif option ~= "q" then -- gets fully escaped in the next block -- Prevent funny things with ASCII control characters and ANSI escape codes (CWE-117) -- Also ensure embedded newlines can't look like another log line (CWE-93) args[i] = arg:gsub("[%z\1-\8\11-\31\127]", control_symbols):gsub("\n\t?", "\n\t"); return spec; end end if option == "q" then args[i] = dump(arg); return "%s"; end if option == "p" and (t == "boolean" or t == "number") then args[i] = tostring(arg); return "[%s]"; end end); -- process extra args while i < args_length do i = i + 1; local arg = args[i]; if arg == nil then args[i] = "(nil)"; else args[i] = tostring(arg):gsub("[%z\1-\8\11-\31\127]", control_symbols):gsub("\n\t?", "\n\t"); end formatstring = formatstring .. " [%s]" end return formatstring:format(unpack(args)); end return { format = format; }; prosody-0.12.4/util/PaxHeaders/gc.lua0000644000000000000000000000012014475665104014435 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/gc.lua0000644000175000017500000000250414475665104016643 0ustar00prosodyprosody00000000000000local set = require "util.set"; local known_options = { incremental = set.new { "mode", "threshold", "speed", "step_size" }; generational = set.new { "mode", "minor_threshold", "major_threshold" }; }; if _VERSION ~= "Lua 5.4" then known_options.generational = nil; known_options.incremental:remove("step_size"); end local function configure(user, defaults) local mode = user.mode or defaults.mode or "incremental"; if not known_options[mode] then return nil, "GC mode not supported on ".._VERSION..": "..mode; end for k, v in pairs(user) do if not known_options[mode]:contains(k) then return nil, "Unknown GC parameter: "..k; elseif k ~= "mode" and type(v) ~= "number" then return nil, "parameter '"..k.."' should be a number"; end end if mode == "incremental" then if _VERSION == "Lua 5.4" then collectgarbage(mode, user.threshold or defaults.threshold, user.speed or defaults.speed, user.step_size or defaults.step_size ); else collectgarbage("setpause", user.threshold or defaults.threshold); collectgarbage("setstepmul", user.speed or defaults.speed); end elseif mode == "generational" then collectgarbage(mode, user.minor_threshold or defaults.minor_threshold, user.major_threshold or defaults.major_threshold ); end return true; end return { configure = configure; }; prosody-0.12.4/util/PaxHeaders/hashring.lua0000644000000000000000000000012014475665104015647 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/hashring.lua0000644000175000017500000000403414475665104020055 0ustar00prosodyprosody00000000000000local function generate_ring(nodes, num_replicas, hash) local new_ring = {}; for _, node_name in ipairs(nodes) do for replica = 1, num_replicas do local replica_hash = hash(node_name..":"..replica); new_ring[replica_hash] = node_name; table.insert(new_ring, replica_hash); end end table.sort(new_ring); return new_ring; end local hashring_methods = {}; local hashring_mt = { __index = function (self, k) -- Automatically build self.ring if it's missing if k == "ring" then local ring = generate_ring(self.nodes, self.num_replicas, self.hash); rawset(self, "ring", ring); return ring; end return rawget(hashring_methods, k); end }; local function new(num_replicas, hash_function) return setmetatable({ nodes = {}, num_replicas = num_replicas, hash = hash_function }, hashring_mt); end; function hashring_methods:add_node(name) self.ring = nil; self.nodes[name] = true; table.insert(self.nodes, name); return true; end function hashring_methods:add_nodes(nodes) self.ring = nil; for _, node_name in ipairs(nodes) do if not self.nodes[node_name] then self.nodes[node_name] = true; table.insert(self.nodes, node_name); end end return true; end function hashring_methods:remove_node(node_name) self.ring = nil; if self.nodes[node_name] then for i, stored_node_name in ipairs(self.nodes) do if node_name == stored_node_name then self.nodes[node_name] = nil; table.remove(self.nodes, i); return true; end end end return false; end function hashring_methods:remove_nodes(nodes) self.ring = nil; for _, node_name in ipairs(nodes) do self:remove_node(node_name); end end function hashring_methods:clone() local clone_hashring = new(self.num_replicas, self.hash); clone_hashring:add_nodes(self.nodes); return clone_hashring; end function hashring_methods:get_node(key) local key_hash = self.hash(key); for _, replica_hash in ipairs(self.ring) do if key_hash < replica_hash then return self.ring[replica_hash]; end end return self.ring[self.ring[1]]; end return { new = new; } prosody-0.12.4/util/PaxHeaders/helpers.lua0000644000000000000000000000012014475665104015506 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/helpers.lua0000644000175000017500000000574114475665104017722 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local debug = require "util.debug"; -- Helper functions for debugging local log = require "util.logger".init("util.debug"); local function log_events(events, name, logger) local f = events.fire_event; if not f then error("Object does not appear to be a util.events object"); end logger = logger or log; name = name or tostring(events); function events.fire_event(event, ...) logger("debug", "%s firing event: %s", name, event); return f(event, ...); end local function event_handler_hook(handler, event_name, event_data) logger("debug", "calling handler for %s: %s", event_name, handler); local ok, ret = pcall(handler, event_data); if not ok then logger("error", "error in event handler %s: %s", handler, ret); error(ret); end if ret ~= nil then logger("debug", "event chain ended for %s by %s with result: %s", event_name, handler, ret); end return ret; end events.set_debug_hook(event_handler_hook); events[events.fire_event] = f; return events; end local function revert_log_events(events) events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :)) events.set_debug_hook(nil); end local function log_host_events(host) return log_events(prosody.hosts[host].events, host); end local function revert_log_host_events(host) return revert_log_events(prosody.hosts[host].events); end local function show_events(events, specific_event) local event_handlers = events._handlers; local events_array = {}; local event_handler_arrays = {}; for event, priorities in pairs(events._event_map) do local handlers = event_handlers[event]; if handlers and (event == specific_event or not specific_event) then table.insert(events_array, event); local handler_strings = {}; for i, handler in ipairs(handlers) do local upvals = debug.string_from_var_table(debug.get_upvalues_table(handler)); handler_strings[i] = " "..(priorities[handler] or "?")..": "..tostring(handler)..(upvals and ("\n "..upvals) or ""); end event_handler_arrays[event] = handler_strings; end end table.sort(events_array); local i = 1; while i <= #events_array do local handlers = event_handler_arrays[events_array[i]]; for j=#handlers, 1, -1 do table.insert(events_array, i+1, handlers[j]); end if i > 1 then events_array[i] = "\n"..events_array[i]; end i = i + #handlers + 1 end return table.concat(events_array, "\n"); end local function get_upvalue(f, get_name) local i, name, value = 0; repeat i = i + 1; name, value = debug.getupvalue(f, i); until name == get_name or name == nil; return value; end return { log_host_events = log_host_events; revert_log_host_events = revert_log_host_events; log_events = log_events; revert_log_events = revert_log_events; show_events = show_events; get_upvalue = get_upvalue; }; prosody-0.12.4/util/PaxHeaders/hex.lua0000644000000000000000000000012014475665104014630 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/hex.lua0000644000175000017500000000105414475665104017035 0ustar00prosodyprosody00000000000000local s_char = string.char; local s_format = string.format; local s_gsub = string.gsub; local s_lower = string.lower; local char_to_hex = {}; local hex_to_char = {}; do local char, hex; for i = 0,255 do char, hex = s_char(i), s_format("%02x", i); char_to_hex[char] = hex; hex_to_char[hex] = char; end end local function to(s) return (s_gsub(s, ".", char_to_hex)); end local function from(s) return (s_gsub(s_lower(s), "%X*(%x%x)%X*", hex_to_char)); end return { encode = to, decode = from; -- COMPAT w/pre-0.12: to = to, from = from; }; prosody-0.12.4/util/PaxHeaders/hmac.lua0000644000000000000000000000012014475665104014754 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/hmac.lua0000644000175000017500000000064414475665104017165 0ustar00prosodyprosody00000000000000-- 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. -- -- COMPAT: Only for external pre-0.9 modules local hashes = require "util.hashes" return { md5 = hashes.hmac_md5, sha1 = hashes.hmac_sha1, sha256 = hashes.hmac_sha256, sha512 = hashes.hmac_sha512, }; prosody-0.12.4/util/PaxHeaders/http.lua0000644000000000000000000000012014475665104015023 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.768764207 prosody-0.12.4/util/http.lua0000644000175000017500000000400314475665104017225 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2013 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local format, char = string.format, string.char; local pairs, ipairs = pairs, ipairs; local t_insert, t_concat = table.insert, table.concat; local url_codes = {}; for i = 0, 255 do local c = char(i); local u = format("%%%02x", i); url_codes[c] = u; url_codes[u] = c; url_codes[u:upper()] = c; end local function urlencode(s) return s and (s:gsub("[^a-zA-Z0-9.~_-]", url_codes)); end local function urldecode(s) return s and (s:gsub("%%%x%x", url_codes)); end local function _formencodepart(s) return s and (urlencode(s):gsub("%%20", "+")); end local function formencode(form) local result = {}; if form[1] then -- Array of ordered { name, value } for _, field in ipairs(form) do t_insert(result, _formencodepart(field.name).."=".._formencodepart(field.value)); end else -- Unordered map of name -> value for name, value in pairs(form) do t_insert(result, _formencodepart(name).."=".._formencodepart(value)); end end return t_concat(result, "&"); end local function formdecode(s) if not s:match("=") then return urldecode(s); end local r = {}; for k, v in s:gmatch("([^=&]*)=([^&]*)") do k, v = k:gsub("%+", "%%20"), v:gsub("%+", "%%20"); k, v = urldecode(k), urldecode(v); t_insert(r, { name = k, value = v }); r[k] = v; end return r; end local function contains_token(field, token) field = ","..field:gsub("[ \t]", ""):lower()..","; return field:find(","..token:lower()..",", 1, true) ~= nil; end local function normalize_path(path, is_dir) if is_dir then if path:sub(-1,-1) ~= "/" then path = path.."/"; end else if path:sub(-1,-1) == "/" then path = path:sub(1, -2); end end if path:sub(1,1) ~= "/" then path = "/"..path; end return path; end return { urlencode = urlencode, urldecode = urldecode; formencode = formencode, formdecode = formdecode; contains_token = contains_token; normalize_path = normalize_path; }; prosody-0.12.4/util/PaxHeaders/human0000644000000000000000000000013214475665104014377 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.768764207 30 ctime=1693936196.772764168 prosody-0.12.4/util/human/0000755000175000017500000000000014475665104016656 5ustar00prosodyprosody00000000000000prosody-0.12.4/util/human/PaxHeaders/io.lua0000644000000000000000000000012014475665104015563 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/human/io.lua0000644000175000017500000001051614475665104017773 0ustar00prosodyprosody00000000000000local array = require "util.array"; local utf8 = rawget(_G, "utf8") or require"util.encodings".utf8; local len = utf8.len or function(s) local _, count = s:gsub("[%z\001-\127\194-\253][\128-\191]*", ""); return count; end; local function getchar(n) local stty_ret = os.execute("stty raw -echo 2>/dev/null"); local ok, char; if stty_ret == true or stty_ret == 0 then ok, char = pcall(io.read, n or 1); os.execute("stty sane"); else ok, char = pcall(io.read, "*l"); if ok then char = char:sub(1, n or 1); end end if ok then return char; end end local function getline() local ok, line = pcall(io.read, "*l"); if ok then return line; end end local function getpass() local stty_ret, _, status_code = os.execute("stty -echo 2>/dev/null"); if status_code then -- COMPAT w/ Lua 5.1 stty_ret = status_code; end if stty_ret ~= 0 then io.write("\027[08m"); -- ANSI 'hidden' text attribute end local ok, pass = pcall(io.read, "*l"); if stty_ret == 0 then os.execute("stty sane"); else io.write("\027[00m"); end io.write("\n"); if ok then return pass; end end local function show_yesno(prompt) io.write(prompt, " "); local choice = getchar():lower(); io.write("\n"); if not choice:match("%a") then choice = prompt:match("%[.-(%U).-%]$"); if not choice then return nil; end end return (choice == "y"); end local function read_password() local password; while true do io.write("Enter new password: "); password = getpass(); if not password then print("No password - cancelled"); return; end io.write("Retype new password: "); if getpass() ~= password then if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then return; end else break; end end return password; end local function show_prompt(prompt) io.write(prompt, " "); local line = getline(); line = line and line:gsub("\n$",""); return (line and #line > 0) and line or nil; end local function printf(fmt, ...) print(fmt:format(...)); end local function padright(s, width) return s..string.rep(" ", width-len(s)); end local function padleft(s, width) return string.rep(" ", width-len(s))..s; end local pat = "[%z\001-\127\194-\253][\128-\191]*"; local function utf8_cut(s, pos) return s:match("^"..pat:rep(pos)) or s; end if utf8.len and utf8.offset then function utf8_cut(s, pos) return s:sub(1, utf8.offset(s, pos+1)-1); end end local function ellipsis(s, width) if len(s) <= width then return s; end if width == 1 then return "…"; end return utf8_cut(s, width - 1) .. "…"; end local function new_table(col_specs, max_width) max_width = max_width or tonumber(os.getenv("COLUMNS")) or 80; local separator = " | "; local widths = {}; local total_width = max_width - #separator * (#col_specs-1); local free_width = total_width; -- Calculate width of fixed-size columns for i = 1, #col_specs do local width = col_specs[i].width or "0"; if not(type(width) == "string" and width:sub(-1) == "%") then local title = col_specs[i].title; width = math.max(tonumber(width), title and (#title+1) or 0); widths[i] = width; free_width = free_width - width; if i > 1 then free_width = free_width - #separator; end end end -- Calculate width of %-based columns for i = 1, #col_specs do if not widths[i] then local pc_width = tonumber((col_specs[i].width:gsub("%%$", ""))); widths[i] = math.floor(free_width*(pc_width/100)); end end return function (row) local titles; if not row then titles, row = true, array.pluck(col_specs, "title", ""); end local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; local v = row[not titles and column.key or i]; if not titles and column.mapper then v = column.mapper(v, row); end if v == nil then v = column.default or ""; else v = tostring(v); end if len(v) < width then if column.align == "right" then v = padleft(v, width); else v = padright(v, width); end elseif len(v) > width then v = ellipsis(v, width); end table.insert(output, v); end return table.concat(output, separator); end; end return { getchar = getchar; getline = getline; getpass = getpass; show_yesno = show_yesno; read_password = read_password; show_prompt = show_prompt; printf = printf; padleft = padleft; padright = padright; ellipsis = ellipsis; table = new_table; }; prosody-0.12.4/util/human/PaxHeaders/units.lua0000644000000000000000000000012014475665104016316 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/human/units.lua0000644000175000017500000000327114475665104020526 0ustar00prosodyprosody00000000000000local math_abs = math.abs; local math_ceil = math.ceil; local math_floor = math.floor; local math_log = math.log; local math_max = math.max; local math_min = math.min; local unpack = table.unpack or unpack; --luacheck: ignore 113 if math_log(10, 10) ~= 1 then -- Lua 5.1 COMPAT local log10 = math.log10; function math_log(n, base) return log10(n) / log10(base); end end local large = { "k", 1000, "M", 1000000, "G", 1000000000, "T", 1000000000000, "P", 1000000000000000, "E", 1000000000000000000, "Z", 1000000000000000000000, "Y", 1000000000000000000000000, } local small = { "m", 0.001, "μ", 0.000001, "n", 0.000000001, "p", 0.000000000001, "f", 0.000000000000001, "a", 0.000000000000000001, "z", 0.000000000000000000001, "y", 0.000000000000000000000001, } local binary = { "Ki", 2^10, "Mi", 2^20, "Gi", 2^30, "Ti", 2^40, "Pi", 2^50, "Ei", 2^60, "Zi", 2^70, "Yi", 2^80, } local function adjusted_unit(n, b) local round = math_floor; local prefixes = large; local logbase = 1000; if b == 'b' then prefixes = binary; logbase = 1024; elseif n < 1 then prefixes = small; round = math_ceil; end local m = math_max(0, math_min(8, round(math_abs(math_log(math_abs(n), logbase))))); local prefix, multiplier = unpack(prefixes, m * 2-1, m*2); return multiplier or 1, prefix; end -- n: number, the number to format -- unit: string, the base unit -- b: optional enum 'b', thousands base local function format(n, unit, b) --> string local fmt = "%.3g %s%s"; if n == 0 then return fmt:format(n, "", unit); end local multiplier, prefix = adjusted_unit(n, b); return fmt:format(n / multiplier, prefix or "", unit); end return { adjust = adjusted_unit; format = format; }; prosody-0.12.4/util/PaxHeaders/id.lua0000644000000000000000000000012014475665104014440 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/id.lua0000644000175000017500000000241014475665104016642 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2017 Matthew Wild -- Copyright (C) 2008-2017 Waqas Hussain -- Copyright (C) 2008-2017 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local s_gsub = string.gsub; local random_bytes = require "util.random".bytes; local base64_encode = require "util.encodings".base64.encode; local b64url = { ["+"] = "-", ["/"] = "_", ["="] = "" }; local function b64url_random(len) return (s_gsub(base64_encode(random_bytes(len)), "[+/=]", b64url)); end return { -- sizes divisible by 3 fit nicely into base64 without padding== -- for short lived things with low risk of collisions tiny = function() return b64url_random(3); end; -- close to 8 bytes, should be good enough for relatively short lived or uses -- scoped by host or users, half the size of an uuid short = function() return b64url_random(9); end; -- more entropy than uuid at 2/3 the size -- should be okay for globally scoped ids or security token medium = function() return b64url_random(18); end; -- as long as an uuid but MOAR entropy long = function() return b64url_random(27); end; -- pick your own adventure custom = function (size) return function () return b64url_random(size); end; end; } prosody-0.12.4/util/PaxHeaders/import.lua0000644000000000000000000000012014475665104015356 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/import.lua0000644000175000017500000000104614475665104017564 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local unpack = table.unpack or unpack; --luacheck: ignore 113 local t_insert = table.insert; function _G.import(module, ...) local m = package.loaded[module] or require(module); if type(m) == "table" and ... then local ret = {}; for _, f in ipairs{...} do t_insert(ret, m[f]); end return unpack(ret); end return m; end prosody-0.12.4/util/PaxHeaders/indexedbheap.lua0000644000000000000000000000012014475665104016464 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/indexedbheap.lua0000644000175000017500000000674514475665104020705 0ustar00prosodyprosody00000000000000 local setmetatable = setmetatable; local math_floor = math.floor; local t_remove = table.remove; local function _heap_insert(self, item, sync, item2, index) local pos = #self + 1; while true do local half_pos = math_floor(pos / 2); if half_pos == 0 or item > self[half_pos] then break; end self[pos] = self[half_pos]; sync[pos] = sync[half_pos]; index[sync[pos]] = pos; pos = half_pos; end self[pos] = item; sync[pos] = item2; index[item2] = pos; end local function _percolate_up(self, k, sync, index) local tmp = self[k]; local tmp_sync = sync[k]; while k ~= 1 do local parent = math_floor(k/2); if tmp >= self[parent] then break; end self[k] = self[parent]; sync[k] = sync[parent]; index[sync[k]] = k; k = parent; end self[k] = tmp; sync[k] = tmp_sync; index[tmp_sync] = k; return k; end local function _percolate_down(self, k, sync, index) local tmp = self[k]; local tmp_sync = sync[k]; local size = #self; local child = 2*k; while 2*k <= size do if child ~= size and self[child] > self[child + 1] then child = child + 1; end if tmp > self[child] then self[k] = self[child]; sync[k] = sync[child]; index[sync[k]] = k; else break; end k = child; child = 2*k; end self[k] = tmp; sync[k] = tmp_sync; index[tmp_sync] = k; return k; end local function _heap_pop(self, sync, index) local size = #self; if size == 0 then return nil; end local result = self[1]; local result_sync = sync[1]; index[result_sync] = nil; if size == 1 then self[1] = nil; sync[1] = nil; return result, result_sync; end self[1] = t_remove(self); sync[1] = t_remove(sync); index[sync[1]] = 1; _percolate_down(self, 1, sync, index); return result, result_sync; end local indexed_heap = {}; function indexed_heap:insert(item, priority, id) if id == nil then id = self.current_id; self.current_id = id + 1; end self.items[id] = item; _heap_insert(self.priorities, priority, self.ids, id, self.index); return id; end function indexed_heap:pop() local priority, id = _heap_pop(self.priorities, self.ids, self.index); if id then local item = self.items[id]; self.items[id] = nil; return priority, item, id; end end function indexed_heap:peek() return self.priorities[1]; end function indexed_heap:reprioritize(id, priority) local k = self.index[id]; if k == nil then return; end self.priorities[k] = priority; k = _percolate_up(self.priorities, k, self.ids, self.index); _percolate_down(self.priorities, k, self.ids, self.index); end function indexed_heap:remove_index(k) local result = self.priorities[k]; if result == nil then return; end local result_sync = self.ids[k]; local item = self.items[result_sync]; local size = #self.priorities; self.priorities[k] = self.priorities[size]; self.ids[k] = self.ids[size]; self.index[self.ids[k]] = k; t_remove(self.priorities); t_remove(self.ids); self.index[result_sync] = nil; self.items[result_sync] = nil; if size > k then k = _percolate_up(self.priorities, k, self.ids, self.index); _percolate_down(self.priorities, k, self.ids, self.index); end return result, item, result_sync; end function indexed_heap:remove(id) return self:remove_index(self.index[id]); end local mt = { __index = indexed_heap }; local _M = { create = function() return setmetatable({ ids = {}; -- heap of ids, sync'd with priorities items = {}; -- map id->items priorities = {}; -- heap of priorities index = {}; -- map of id->index of id in ids current_id = 1.5 }, mt); end }; return _M; prosody-0.12.4/util/PaxHeaders/interpolation.lua0000644000000000000000000000012014475665104016733 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/interpolation.lua0000644000175000017500000000614114475665104021142 0ustar00prosodyprosody00000000000000-- Simple template language -- -- The new() function takes a pattern and an escape function and returns -- a render() function. Both are required. -- -- The function render() takes a string template and a table of values. -- Sequences like {name} in the template string are substituted -- with values from the table, optionally depending on a modifier -- symbol. -- -- Variants are: -- {name} is substituted for values["name"] and is escaped using the -- second argument to new_render(). To disable the escaping, use {name!}. -- {name.item} can be used to access table items. -- To renter lists of items: {name# item number {idx} is {item} } -- Or key-value pairs: {name% t[ {idx} ] = {item} } -- To show a defaults for missing values {name? sub-template } can be used, -- which renders a sub-template if values["name"] is false-ish. -- {name& sub-template } does the opposite, the sub-template is rendered -- if the selected value is anything but false or nil. local type, tostring = type, tostring; local pairs, ipairs = pairs, ipairs; local s_sub, s_gsub, s_match = string.sub, string.gsub, string.match; local t_concat = table.concat; local function new_render(pat, escape, funcs) -- assert(type(pat) == "string", "bad argument #1 to 'new_render' (string expected)"); -- assert(type(escape) == "function", "bad argument #2 to 'new_render' (function expected)"); local function render(template, values) -- assert(type(template) == "string", "bad argument #1 to 'render' (string expected)"); -- assert(type(values) == "table", "bad argument #2 to 'render' (table expected)"); return (s_gsub(template, pat, function (block) block = s_sub(block, 2, -2); local name, raw, opt, e = s_match(block, "^([%a_][%w_.]*)(!?)(%p?)()"); if not name then return end local value = values[name]; if not value and name:find(".", 2, true) then value = values; for word in name:gmatch"[^.]+" do value = value[word]; if not value then break; end end end if funcs then while opt == '|' do local f; f, raw, opt, e = s_match(block, "^([%a_][%w_.]*)(!?)(%p?)()", e); f = funcs[f]; if value ~= nil and f then value = f(value); end end end if opt == '#' or opt == '%' then if type(value) ~= "table" then return ""; end local iter = opt == '#' and ipairs or pairs; local out, i, subtpl = {}, 1, s_sub(block, e); local subvalues = setmetatable({}, { __index = values }); for idx, item in iter(value) do subvalues.idx = idx; subvalues.item = item; out[i], i = render(subtpl, subvalues), i+1; end return t_concat(out); elseif opt == '&' then if not value then return ""; end return render(s_sub(block, e), values); elseif opt == '~' then if value then return ""; end return render(s_sub(block, e), values); elseif opt == '?' and not value then return render(s_sub(block, e), values); elseif value ~= nil then if type(value) ~= "string" then value = tostring(value); end if raw ~= '!' then return escape(value); end return value; end end)); end return render; end return { new = new_render; }; prosody-0.12.4/util/PaxHeaders/ip.lua0000644000000000000000000000012014475665104014454 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/ip.lua0000644000175000017500000001322314475665104016662 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2011 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local net = require "util.net"; local hex = require "util.hex"; local ip_methods = {}; local ip_mt = { __index = function (ip, key) local method = ip_methods[key]; if not method then return nil; end local ret = method(ip); ip[key] = ret; return ret; end, __tostring = function (ip) return ip.addr; end, }; ip_mt.__eq = function (ipA, ipB) if getmetatable(ipA) ~= ip_mt or getmetatable(ipB) ~= ip_mt then -- Lua 5.3+ calls this if both operands are tables, even if metatables differ return false; end return ipA.packed == ipB.packed; end local hex2bits = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", ["A"] = "1010", ["B"] = "1011", ["C"] = "1100", ["D"] = "1101", ["E"] = "1110", ["F"] = "1111", }; local function new_ip(ipStr, proto) local zone; if (not proto or proto == "IPv6") and ipStr:find('%', 1, true) then ipStr, zone = ipStr:match("^(.-)%%(.*)"); end local packed, err = net.pton(ipStr); if not packed then return packed, err end if proto == "IPv6" and #packed ~= 16 then return nil, "invalid-ipv6"; elseif proto == "IPv4" and #packed ~= 4 then return nil, "invalid-ipv4"; elseif not proto then if #packed == 16 then proto = "IPv6"; elseif #packed == 4 then proto = "IPv4"; else return nil, "unknown protocol"; end elseif proto ~= "IPv6" and proto ~= "IPv4" then return nil, "invalid protocol"; end return setmetatable({ addr = ipStr, packed = packed, proto = proto, zone = zone }, ip_mt); end function ip_methods:normal() return net.ntop(self.packed); end function ip_methods.bits(ip) return hex.encode(ip.packed):upper():gsub(".", hex2bits); end function ip_methods.bits_full(ip) if ip.proto == "IPv4" then ip = ip.toV4mapped; end return ip.bits; end local match; local function commonPrefixLength(ipA, ipB) ipA, ipB = ipA.bits_full, ipB.bits_full; for i = 1, 128 do if ipA:sub(i,i) ~= ipB:sub(i,i) then return i-1; end end return 128; end -- Instantiate once local loopback = new_ip("::1"); local loopback4 = new_ip("127.0.0.0"); local sixtofour = new_ip("2002::"); local teredo = new_ip("2001::"); local linklocal = new_ip("fe80::"); local linklocal4 = new_ip("169.254.0.0"); local uniquelocal = new_ip("fc00::"); local sitelocal = new_ip("fec0::"); local sixbone = new_ip("3ffe::"); local defaultunicast = new_ip("::"); local multicast = new_ip("ff00::"); local ipv6mapped = new_ip("::ffff:0:0"); local function v4scope(ip) if match(ip, loopback4, 8) then return 0x2; elseif match(ip, linklocal4, 16) then return 0x2; else -- Global unicast return 0xE; end end local function v6scope(ip) if ip == loopback then return 0x2; elseif match(ip, linklocal, 10) then return 0x2; elseif match(ip, sitelocal, 10) then return 0x5; elseif match(ip, multicast, 10) then return ip.packed:byte(2) % 0x10; else -- Global unicast return 0xE; end end local function label(ip) if ip == loopback then return 0; elseif match(ip, sixtofour, 16) then return 2; elseif match(ip, teredo, 32) then return 5; elseif match(ip, uniquelocal, 7) then return 13; elseif match(ip, sitelocal, 10) then return 11; elseif match(ip, sixbone, 16) then return 12; elseif match(ip, defaultunicast, 96) then return 3; elseif match(ip, ipv6mapped, 96) then return 4; else return 1; end end local function precedence(ip) if ip == loopback then return 50; elseif match(ip, sixtofour, 16) then return 30; elseif match(ip, teredo, 32) then return 5; elseif match(ip, uniquelocal, 7) then return 3; elseif match(ip, sitelocal, 10) then return 1; elseif match(ip, sixbone, 16) then return 1; elseif match(ip, defaultunicast, 96) then return 1; elseif match(ip, ipv6mapped, 96) then return 35; else return 40; end end function ip_methods:toV4mapped() if self.proto ~= "IPv4" then return nil, "No IPv4 address" end local value = new_ip("::ffff:" .. self.normal); return value; end function ip_methods:label() if self.proto == "IPv4" then return label(self.toV4mapped); else return label(self); end end function ip_methods:precedence() if self.proto == "IPv4" then return precedence(self.toV4mapped); else return precedence(self); end end function ip_methods:scope() if self.proto == "IPv4" then return v4scope(self); else return v6scope(self); end end local rfc1918_8 = new_ip("10.0.0.0"); local rfc1918_12 = new_ip("172.16.0.0"); local rfc1918_16 = new_ip("192.168.0.0"); local rfc6598 = new_ip("100.64.0.0"); function ip_methods:private() local private = self.scope ~= 0xE; if not private and self.proto == "IPv4" then return match(self, rfc1918_8, 8) or match(self, rfc1918_12, 12) or match(self, rfc1918_16, 16) or match(self, rfc6598, 10); end return private; end local function parse_cidr(cidr) local bits; local ip_len = cidr:find("/", 1, true); if ip_len then bits = tonumber(cidr:sub(ip_len+1, -1)); cidr = cidr:sub(1, ip_len-1); end return new_ip(cidr), bits; end function match(ipA, ipB, bits) if not bits or bits >= 128 or ipB.proto == "IPv4" and bits >= 32 then return ipA == ipB; elseif bits < 1 then return true; end if ipA.proto ~= ipB.proto then if ipA.proto == "IPv4" then ipA = ipA.toV4mapped; elseif ipB.proto == "IPv4" then ipB = ipB.toV4mapped; bits = bits + (128 - 32); end end return ipA.bits:sub(1, bits) == ipB.bits:sub(1, bits); end return { new_ip = new_ip, commonPrefixLength = commonPrefixLength, parse_cidr = parse_cidr, match = match, }; prosody-0.12.4/util/PaxHeaders/iterators.lua0000644000000000000000000000012014475665104016060 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/iterators.lua0000644000175000017500000001206614475665104020272 0ustar00prosodyprosody00000000000000-- 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. -- --[[ Iterators ]]-- local it = {}; local t_insert = table.insert; local next = next; local unpack = table.unpack or unpack; --luacheck: ignore 113 local pack = table.pack or require "util.table".pack; local type = type; local table, setmetatable = table, setmetatable; local _ENV = nil; --luacheck: std none -- Reverse an iterator function it.reverse(f, s, var) local results = {}; -- First call the normal iterator while true do local ret = { f(s, var) }; var = ret[1]; if var == nil then break; end t_insert(results, 1, ret); end -- Then return our reverse one local i,max = 0, #results; return function (_results) if i= n then return nil; end c = c + 1; return f(_s, _var); end, s, var; end -- Skip the first n items an iterator returns function it.skip(n, f, s, var) for _ = 1, n do var = f(s, var); end return f, s, var; end -- Return the last n items an iterator returns function it.tail(n, f, s, var) local results, count = {}, 0; while true do local ret = pack(f(s, var)); var = ret[1]; if var == nil then break; end results[(count%n)+1] = ret; count = count + 1; end if n > count then n = count; end local pos = 0; return function () pos = pos + 1; if pos > n then return nil; end local ret = results[((count-1+pos)%n)+1]; return unpack(ret, 1, ret.n); end --return reverse(head(n, reverse(f, s, var))); -- ! end function it.filter(filter, f, s, var) if type(filter) ~= "function" then local filter_value = filter; function filter(x) return x ~= filter_value; end end return function (_s, _var) local ret; repeat ret = pack(f(_s, _var)); _var = ret[1]; until _var == nil or filter(unpack(ret, 1, ret.n)); return unpack(ret, 1, ret.n); end, s, var; end local function _ripairs_iter(t, key) if key > 1 then return key-1, t[key-1]; end end function it.ripairs(t) return _ripairs_iter, t, #t+1; end local function _range_iter(max, curr) if curr < max then return curr + 1; end end function it.range(x, y) if not y then x, y = 1, x; end -- Default to 1..x if y not given return _range_iter, y, x-1; end -- Convert the values returned by an iterator to an array function it.to_array(f, s, var) local t = {}; while true do var = f(s, var); if var == nil then break; end t_insert(t, var); end return t; end function it.sorted_pairs(t, sort_func) local keys = it.to_array(it.keys(t)); table.sort(keys, sort_func); local i = 0; return function () i = i + 1; local key = keys[i]; if key ~= nil then return key, t[key]; end end; end -- Treat the return of an iterator as key,value pairs, -- and build a table function it.to_table(f, s, var) local t, var2 = {}; while true do var, var2 = f(s, var); if var == nil then break; end t[var] = var2; end return t; end local function _join_iter(j_s, j_var) local iterators, current_idx = j_s[1], j_s[2]; local f, s, var = unpack(iterators[current_idx], 1, 3); if j_var ~= nil then var = j_var; end local ret = pack(f(s, var)); local var1 = ret[1]; if var1 == nil then -- End of this iterator, advance to next if current_idx == #iterators then -- No more iterators, return nil return; end j_s[2] = current_idx + 1; return _join_iter(j_s); end return unpack(ret, 1, ret.n); end local join_methods = {}; local join_mt = { __index = join_methods; __call = function (t, s, var) --luacheck: ignore 212/t return _join_iter(s, var); end; }; function join_methods:append(f, s, var) table.insert(self, { f, s, var }); return self, { self, 1 }; end function join_methods:prepend(f, s, var) table.insert(self, { f, s, var }, 1); return self, { self, 1 }; end function it.join(f, s, var) return setmetatable({ {f, s, var} }, join_mt); end return it; prosody-0.12.4/util/PaxHeaders/jid.lua0000644000000000000000000000012014475665104014612 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/jid.lua0000644000175000017500000000656414475665104017032 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local select = select; local match, sub = string.match, string.sub; local nodeprep = require "util.encodings".stringprep.nodeprep; local nameprep = require "util.encodings".stringprep.nameprep; local resourceprep = require "util.encodings".stringprep.resourceprep; local escapes = { [" "] = "\\20"; ['"'] = "\\22"; ["&"] = "\\26"; ["'"] = "\\27"; ["/"] = "\\2f"; [":"] = "\\3a"; ["<"] = "\\3c"; [">"] = "\\3e"; ["@"] = "\\40"; ["\\"] = "\\5c"; }; local unescapes = {}; local backslash_escapes = {}; for k,v in pairs(escapes) do unescapes[v] = k; backslash_escapes[v] = v:gsub("\\", escapes) end local _ENV = nil; -- luacheck: std none local function split(jid) if jid == nil then return; end local node, nodepos = match(jid, "^([^@/]+)@()"); local host, hostpos = match(jid, "^([^@/]+)()", nodepos); if node ~= nil and host == nil then return nil, nil, nil; end local resource = match(jid, "^/(.+)$", hostpos); if (host == nil) or ((resource == nil) and #jid >= hostpos) then return nil, nil, nil; end return node, host, resource; end local function bare(jid) local node, host = split(jid); if node ~= nil and host ~= nil then return node.."@"..host; end return host; end local function prepped_split(jid, strict) local node, host, resource = split(jid); if host ~= nil and host ~= "." then if sub(host, -1, -1) == "." then -- Strip empty root label host = sub(host, 1, -2); end host = nameprep(host, strict); if host == nil then return; end if node ~= nil then node = nodeprep(node, strict); if node == nil then return; end end if resource ~= nil then resource = resourceprep(resource, strict); if resource == nil then return; end end return node, host, resource; end end local function join(node, host, resource) if host == nil then return end if node ~= nil and resource ~= nil then return node.."@"..host.."/"..resource; elseif node ~= nil then return node.."@"..host; elseif resource ~= nil then return host.."/"..resource; end return host; end local function prep(jid, strict) local node, host, resource = prepped_split(jid, strict); return join(node, host, resource); end local function compare(jid, acl) -- compare jid to single acl rule -- TODO compare to table of rules? local jid_node, jid_host, jid_resource = split(jid); local acl_node, acl_host, acl_resource = split(acl); if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and ((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and ((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then return true end return false end local function node(jid) return (select(1, split(jid))); end local function host(jid) return (select(2, split(jid))); end local function resource(jid) return (select(3, split(jid))); end local function escape(s) return s and (s:gsub("\\%x%x", backslash_escapes):gsub("[\"&'/:<>@ ]", escapes)); end local function unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end return { split = split; bare = bare; prepped_split = prepped_split; join = join; prep = prep; compare = compare; node = node; host = host; resource = resource; escape = escape; unescape = unescape; }; prosody-0.12.4/util/PaxHeaders/json.lua0000644000000000000000000000012014475665104015015 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/json.lua0000644000175000017500000002364714475665104017236 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local type = type; local t_insert, t_concat, t_remove = table.insert, table.concat, table.remove; local s_char = string.char; local tostring, tonumber = tostring, tonumber; local pairs, ipairs, spairs = pairs, ipairs, require "util.iterators".sorted_pairs; local next = next; local getmetatable, setmetatable = getmetatable, setmetatable; local print = print; local has_array, array = pcall(require, "util.array"); local array_mt = has_array and getmetatable(array()) or {}; --module("json") local module = {}; local null = setmetatable({}, { __tostring = function() return "null"; end; }); module.null = null; local escapes = { ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"}; for i=0,31 do local ch = s_char(i); if not escapes[ch] then escapes[ch] = ("\\u%.4X"):format(i); end end local function codepoint_to_utf8(code) if code < 0x80 then return s_char(code); end local bits0_6 = code % 64; if code < 0x800 then local bits6_5 = (code - bits0_6) / 64; return s_char(0x80 + 0x40 + bits6_5, 0x80 + bits0_6); end local bits0_12 = code % 4096; local bits6_6 = (bits0_12 - bits0_6) / 64; local bits12_4 = (code - bits0_12) / 4096; return s_char(0x80 + 0x40 + 0x20 + bits12_4, 0x80 + bits6_6, 0x80 + bits0_6); end local valid_types = { number = true, string = true, table = true, boolean = true }; local special_keys = { __array = true; __hash = true; }; local simplesave, tablesave, arraysave, stringsave; function stringsave(o, buffer) -- FIXME do proper utf-8 and binary data detection t_insert(buffer, "\""..(o:gsub(".", escapes)).."\""); end function arraysave(o, buffer) t_insert(buffer, "["); if next(o) then for _, v in ipairs(o) do simplesave(v, buffer); t_insert(buffer, ","); end t_remove(buffer); end t_insert(buffer, "]"); end function tablesave(o, buffer) local __array = {}; local __hash = {}; local hash = {}; for i,v in ipairs(o) do __array[i] = v; end for k,v in pairs(o) do local ktype, vtype = type(k), type(v); if valid_types[vtype] or v == null then if ktype == "string" and not special_keys[k] then hash[k] = v; elseif (valid_types[ktype] or k == null) and __array[k] == nil then __hash[k] = v; end end end if next(__hash) ~= nil or next(hash) ~= nil or next(__array) == nil then t_insert(buffer, "{"); local mark = #buffer; local _pairs = buffer.ordered and spairs or pairs; for k,v in _pairs(hash) do stringsave(k, buffer); t_insert(buffer, ":"); simplesave(v, buffer); t_insert(buffer, ","); end if next(__hash) ~= nil then t_insert(buffer, "\"__hash\":["); for k,v in pairs(__hash) do simplesave(k, buffer); t_insert(buffer, ","); simplesave(v, buffer); t_insert(buffer, ","); end t_remove(buffer); t_insert(buffer, "]"); t_insert(buffer, ","); end if next(__array) then t_insert(buffer, "\"__array\":"); arraysave(__array, buffer); t_insert(buffer, ","); end if mark ~= #buffer then t_remove(buffer); end t_insert(buffer, "}"); else arraysave(__array, buffer); end end function simplesave(o, buffer) local t = type(o); if o == null then t_insert(buffer, "null"); elseif t == "number" then t_insert(buffer, tostring(o)); elseif t == "string" then stringsave(o, buffer); elseif t == "table" then local mt = getmetatable(o); if mt == array_mt then arraysave(o, buffer); else tablesave(o, buffer); end elseif t == "boolean" then t_insert(buffer, (o and "true" or "false")); else t_insert(buffer, "null"); end end function module.encode(obj) local t = {}; simplesave(obj, t); return t_concat(t); end function module.encode_ordered(obj) local t = { ordered = true }; simplesave(obj, t); return t_concat(t); end function module.encode_array(obj) local t = {}; arraysave(obj, t); return t_concat(t); end ----------------------------------- local function _skip_whitespace(json, index) return json:find("[^ \t\r\n]", index) or index; -- no need to check \r\n, we converted those to \t end local function _fixobject(obj) local __array = obj.__array; if __array then obj.__array = nil; for _, v in ipairs(__array) do t_insert(obj, v); end end local __hash = obj.__hash; if __hash then obj.__hash = nil; local k; for _, v in ipairs(__hash) do if k ~= nil then obj[k] = v; k = nil; else k = v; end end end return obj; end local _readvalue, _readstring; local function _readobject(json, index) local o = {}; while true do local key, val; index = _skip_whitespace(json, index + 1); if json:byte(index) ~= 0x22 then -- "\"" if json:byte(index) == 0x7d then return o, index + 1; end -- "}" return nil, "key expected"; end key, index = _readstring(json, index); if key == nil then return nil, index; end index = _skip_whitespace(json, index); if json:byte(index) ~= 0x3a then return nil, "colon expected"; end -- ":" val, index = _readvalue(json, index + 1); if val == nil then return nil, index; end o[key] = val; index = _skip_whitespace(json, index); local b = json:byte(index); if b == 0x7d then return _fixobject(o), index + 1; end -- "}" if b ~= 0x2c then return nil, "object eof"; end -- "," end end local function _readarray(json, index) local a = {}; while true do local val, terminated; val, index, terminated = _readvalue(json, index + 1, 0x5d); if val == nil then if terminated then -- "]" found instead of value if #a ~= 0 then -- A non-empty array here means we processed a comma, -- but it wasn't followed by a value. JSON doesn't allow -- trailing commas. return nil, "value expected"; end val, index = setmetatable(a, array_mt), index+1; end return val, index; end t_insert(a, val); index = _skip_whitespace(json, index); local b = json:byte(index); if b == 0x5d then return setmetatable(a, array_mt), index + 1; end -- "]" if b ~= 0x2c then return nil, "array eof"; end -- "," end end local _unescape_error; local function _unescape_surrogate_func(x) local lead, trail = tonumber(x:sub(3, 6), 16), tonumber(x:sub(9, 12), 16); local codepoint = lead * 0x400 + trail - 0x35FDC00; local a = codepoint % 64; codepoint = (codepoint - a) / 64; local b = codepoint % 64; codepoint = (codepoint - b) / 64; local c = codepoint % 64; codepoint = (codepoint - c) / 64; return s_char(0xF0 + codepoint, 0x80 + c, 0x80 + b, 0x80 + a); end local function _unescape_func(x) x = x:match("%x%x%x%x", 3); if x then local codepoint = tonumber(x, 16) if codepoint >= 0xD800 and codepoint <= 0xDFFF then _unescape_error = true; end -- bad surrogate pair return codepoint_to_utf8(codepoint); end _unescape_error = true; end function _readstring(json, index) index = index + 1; local endindex = json:find("\"", index, true); if endindex then local s = json:sub(index, endindex - 1); --if s:find("[%z-\31]") then return nil, "control char in string"; end -- FIXME handle control characters _unescape_error = nil; s = s:gsub("\\u[dD][89abAB]%x%x\\u[dD][cdefCDEF]%x%x", _unescape_surrogate_func); -- FIXME handle escapes beyond BMP s = s:gsub("\\u.?.?.?.?", _unescape_func); if _unescape_error then return nil, "invalid escape"; end return s, endindex + 1; end return nil, "string eof"; end local function _readnumber(json, index) local m = json:match("[0-9%.%-eE%+]+", index); -- FIXME do strict checking return tonumber(m), index + #m; end local function _readnull(json, index) local a, b, c = json:byte(index + 1, index + 3); if a == 0x75 and b == 0x6c and c == 0x6c then return null, index + 4; end return nil, "null parse failed"; end local function _readtrue(json, index) local a, b, c = json:byte(index + 1, index + 3); if a == 0x72 and b == 0x75 and c == 0x65 then return true, index + 4; end return nil, "true parse failed"; end local function _readfalse(json, index) local a, b, c, d = json:byte(index + 1, index + 4); if a == 0x61 and b == 0x6c and c == 0x73 and d == 0x65 then return false, index + 5; end return nil, "false parse failed"; end function _readvalue(json, index, terminator) index = _skip_whitespace(json, index); local b = json:byte(index); -- TODO try table lookup instead of if-else? if b == 0x7B then -- "{" return _readobject(json, index); elseif b == 0x5B then -- "[" return _readarray(json, index); elseif b == 0x22 then -- "\"" return _readstring(json, index); elseif b ~= nil and b >= 0x30 and b <= 0x39 or b == 0x2d then -- "0"-"9" or "-" return _readnumber(json, index); elseif b == 0x6e then -- "n" return _readnull(json, index); elseif b == 0x74 then -- "t" return _readtrue(json, index); elseif b == 0x66 then -- "f" return _readfalse(json, index); elseif b == terminator then return nil, index, true; else return nil, "value expected"; end end local first_escape = { ["\\\""] = "\\u0022"; ["\\\\"] = "\\u005c"; ["\\/" ] = "\\u002f"; ["\\b" ] = "\\u0008"; ["\\f" ] = "\\u000C"; ["\\n" ] = "\\u000A"; ["\\r" ] = "\\u000D"; ["\\t" ] = "\\u0009"; ["\\u" ] = "\\u"; }; function module.decode(json) json = json:gsub("\\.", first_escape) -- get rid of all escapes except \uXXXX, making string parsing much simpler --:gsub("[\r\n]", "\t"); -- \r\n\t are equivalent, we care about none of them, and none of them can be in strings -- TODO do encoding verification local val, index = _readvalue(json, 1); if val == nil then return val, index; end if json:find("[^ \t\r\n]", index) then return nil, "garbage at eof"; end return val; end function module.test(object) local encoded = module.encode(object); local decoded = module.decode(encoded); local recoded = module.encode(decoded); if encoded ~= recoded then print("FAILED"); print("encoded:", encoded); print("recoded:", recoded); else print(encoded); end return encoded == recoded; end return module; prosody-0.12.4/util/PaxHeaders/jsonpointer.lua0000644000000000000000000000012014475665104016416 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/jsonpointer.lua0000644000175000017500000000177014475665104020630 0ustar00prosodyprosody00000000000000local m_type = math.type or function (n) return n % 1 == 0 and n <= 9007199254740992 and n >= -9007199254740992 and "integer" or "float"; end; local function unescape_token(escaped_token) local unescaped = escaped_token:gsub("~1", "/"):gsub("~0", "~") return unescaped end local function resolve_json_pointer(ref, path) local ptr_len = #path + 1 for part, pos in path:gmatch("/([^/]*)()") do local token = unescape_token(part) if not (type(ref) == "table") then return nil end local idx = next(ref) local new_ref if type(idx) == "string" then new_ref = ref[token] elseif m_type(idx) == "integer" then local i = tonumber(token) if token == "-" then i = #ref + 1 end new_ref = ref[i + 1] else return nil, "invalid-table" end if pos == ptr_len then return new_ref elseif type(new_ref) == "table" then ref = new_ref elseif not (type(ref) == "table") then return nil, "invalid-path" end end return ref end return { resolve = resolve_json_pointer } prosody-0.12.4/util/PaxHeaders/jsonschema.lua0000644000000000000000000000012014475665104016176 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.772764168 prosody-0.12.4/util/jsonschema.lua0000644000175000017500000001320114475665104020400 0ustar00prosodyprosody00000000000000-- This file is generated from teal-src/util/jsonschema.lua local m_type = function(n) return type(n) == "number" and n % 1 == 0 and n <= 9007199254740992 and n >= -9007199254740992 and "integer" or "float"; end; local json = require("util.json") local null = json.null; local pointer = require("util.jsonpointer") local json_type_name = json.json_type_name local schema_t = {} local json_schema_object = { xml_t = {} } local function simple_validate(schema, data) if schema == nil then return true elseif schema == "object" and type(data) == "table" then return type(data) == "table" and (next(data) == nil or type((next(data, nil))) == "string") elseif schema == "array" and type(data) == "table" then return type(data) == "table" and (next(data) == nil or type((next(data, nil))) == "number") elseif schema == "integer" then return m_type(data) == schema elseif schema == "null" then return data == null elseif type(schema) == "table" then for _, one in ipairs(schema) do if simple_validate(one, data) then return true end end return false else return type(data) == schema end end local complex_validate local function validate(schema, data, root) if type(schema) == "boolean" then return schema else return complex_validate(schema, data, root) end end function complex_validate(schema, data, root) if root == nil then root = schema end if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then local referenced = pointer.resolve(root, schema["$ref"]:sub(2)) if referenced ~= nil and referenced ~= root and referenced ~= schema then if not validate(referenced, data, root) then return false end end end if not simple_validate(schema.type, data) then return false end if schema.type == "object" then if type(data) == "table" then for k in pairs(data) do if not (type(k) == "string") then return false end end end end if schema.type == "array" then if type(data) == "table" then for i in pairs(data) do if not (m_type(i) == "integer") then return false end end end end if schema["enum"] ~= nil then local match = false for _, v in ipairs(schema["enum"]) do if v == data then match = true break end end if not match then return false end end if type(data) == "string" then if schema.maxLength and #data > schema.maxLength then return false end if schema.minLength and #data < schema.minLength then return false end end if type(data) == "number" then if schema.multipleOf and (data == 0 or data % schema.multipleOf ~= 0) then return false end if schema.maximum and not (data <= schema.maximum) then return false end if schema.exclusiveMaximum and not (data < schema.exclusiveMaximum) then return false end if schema.minimum and not (data >= schema.minimum) then return false end if schema.exclusiveMinimum and not (data > schema.exclusiveMinimum) then return false end end if schema.allOf then for _, sub in ipairs(schema.allOf) do if not validate(sub, data, root) then return false end end end if schema.oneOf then local valid = 0 for _, sub in ipairs(schema.oneOf) do if validate(sub, data, root) then valid = valid + 1 end end if valid ~= 1 then return false end end if schema.anyOf then local match = false for _, sub in ipairs(schema.anyOf) do if validate(sub, data, root) then match = true break end end if not match then return false end end if schema["not"] then if validate(schema["not"], data, root) then return false end end if schema["if"] ~= nil then if validate(schema["if"], data, root) then if schema["then"] then return validate(schema["then"], data, root) end else if schema["else"] then return validate(schema["else"], data, root) end end end if schema.const ~= nil and schema.const ~= data then return false end if type(data) == "table" then if schema.maxItems and #data > schema.maxItems then return false end if schema.minItems and #data < schema.minItems then return false end if schema.required then for _, k in ipairs(schema.required) do if data[k] == nil then return false end end end if schema.propertyNames ~= nil then for k in pairs(data) do if not validate(schema.propertyNames, k, root) then return false end end end if schema.properties then for k, sub in pairs(schema.properties) do if data[k] ~= nil and not validate(sub, data[k], root) then return false end end end if schema.additionalProperties ~= nil then for k, v in pairs(data) do if schema.properties == nil or schema.properties[k] == nil then if not validate(schema.additionalProperties, v, root) then return false end end end end if schema.uniqueItems then local values = {} for _, v in pairs(data) do if values[v] then return false end values[v] = true end end local p = 0 if schema.prefixItems ~= nil then for i, s in ipairs(schema.prefixItems) do if data[i] == nil then break elseif validate(s, data[i], root) then p = i else return false end end end if schema.items ~= nil then for i = p + 1, #data do if not validate(schema.items, data[i], root) then return false end end end if schema.contains ~= nil then local found = false for i = 1, #data do if validate(schema.contains, data[i], root) then found = true break end end if not found then return false end end end return true end json_schema_object.validate = validate; return json_schema_object prosody-0.12.4/util/PaxHeaders/jwt.lua0000644000000000000000000000012014475665104014650 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/jwt.lua0000644000175000017500000000313514475665104017057 0ustar00prosodyprosody00000000000000local s_gsub = string.gsub; local json = require "util.json"; local hashes = require "util.hashes"; local base64_encode = require "util.encodings".base64.encode; local base64_decode = require "util.encodings".base64.decode; local secure_equals = require "util.hashes".equals; local b64url_rep = { ["+"] = "-", ["/"] = "_", ["="] = "", ["-"] = "+", ["_"] = "/" }; local function b64url(data) return (s_gsub(base64_encode(data), "[+/=]", b64url_rep)); end local function unb64url(data) return base64_decode(s_gsub(data, "[-_]", b64url_rep).."=="); end local static_header = b64url('{"alg":"HS256","typ":"JWT"}') .. '.'; local function sign(key, payload) local encoded_payload = json.encode(payload); local signed = static_header .. b64url(encoded_payload); local signature = hashes.hmac_sha256(key, signed); return signed .. "." .. b64url(signature); end local jwt_pattern = "^(([A-Za-z0-9-_]+)%.([A-Za-z0-9-_]+))%.([A-Za-z0-9-_]+)$" local function verify(key, blob) local signed, bheader, bpayload, signature = string.match(blob, jwt_pattern); if not signed then return nil, "invalid-encoding"; end local header = json.decode(unb64url(bheader)); if not header or type(header) ~= "table" then return nil, "invalid-header"; elseif header.alg ~= "HS256" then return nil, "unsupported-algorithm"; end if not secure_equals(b64url(hashes.hmac_sha256(key, signed)), signature) then return false, "signature-mismatch"; end local payload, err = json.decode(unb64url(bpayload)); if err ~= nil then return nil, "json-decode-error"; end return true, payload; end return { sign = sign; verify = verify; }; prosody-0.12.4/util/PaxHeaders/logger.lua0000644000000000000000000000012014475665104015323 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/logger.lua0000644000175000017500000000417514475665104017537 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- luacheck: ignore 213/level local pairs = pairs; local ipairs = ipairs; local require = require; local _ENV = nil; -- luacheck: std none local level_sinks = {}; local make_logger; local function init(name) local log_debug = make_logger(name, "debug"); local log_info = make_logger(name, "info"); local log_warn = make_logger(name, "warn"); local log_error = make_logger(name, "error"); return function (level, message, ...) if level == "debug" then return log_debug(message, ...); elseif level == "info" then return log_info(message, ...); elseif level == "warn" then return log_warn(message, ...); elseif level == "error" then return log_error(message, ...); end end end function make_logger(source_name, level) local level_handlers = level_sinks[level]; if not level_handlers then level_handlers = {}; level_sinks[level] = level_handlers; end local logger = function (message, ...) for i = 1,#level_handlers do level_handlers[i](source_name, level, message, ...); end end return logger; end local function reset() for level, handler_list in pairs(level_sinks) do -- Clear all handlers for this level for i = 1, #handler_list do handler_list[i] = nil; end end end local function add_level_sink(level, sink_function) if not level_sinks[level] then level_sinks[level] = { sink_function }; else level_sinks[level][#level_sinks[level] + 1 ] = sink_function; end end local function add_simple_sink(simple_sink_function, levels) local format = require "util.format".format; local function sink_function(name, level, msg, ...) return simple_sink_function(name, level, format(msg, ...)); end for _, level in ipairs(levels or {"debug", "info", "warn", "error"}) do add_level_sink(level, sink_function); end end return { init = init; make_logger = make_logger; reset = reset; add_level_sink = add_level_sink; add_simple_sink = add_simple_sink; new = make_logger; }; prosody-0.12.4/util/PaxHeaders/mercurial.lua0000644000000000000000000000012014475665104016027 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/mercurial.lua0000644000175000017500000000174514475665104020243 0ustar00prosodyprosody00000000000000 local lfs = require"lfs"; local hg = { }; function hg.check_id(path) if lfs.attributes(path, 'mode') ~= "directory" then return nil, "not a directory"; end local hg_dirstate = io.open(path.."/.hg/dirstate"); local hgid, hgrepo if hg_dirstate then hgid = ("%02x%02x%02x%02x%02x%02x"):format(hg_dirstate:read(6):byte(1, 6)); hg_dirstate:close(); local hg_changelog = io.open(path.."/.hg/store/00changelog.i"); if hg_changelog then hg_changelog:seek("set", 0x20); hgrepo = ("%02x%02x%02x%02x%02x%02x"):format(hg_changelog:read(6):byte(1, 6)); hg_changelog:close(); end else local hg_archival,e = io.open(path.."/.hg_archival.txt"); -- luacheck: ignore 211/e if hg_archival then local repo = hg_archival:read("*l"); local node = hg_archival:read("*l"); hg_archival:close() hgid = node and node:match("^node: (%x%x%x%x%x%x%x%x%x%x%x%x)") hgrepo = repo and repo:match("^repo: (%x%x%x%x%x%x%x%x%x%x%x%x)") end end return hgid, hgrepo; end return hg; prosody-0.12.4/util/PaxHeaders/multitable.lua0000644000000000000000000000012014475665104016206 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/multitable.lua0000644000175000017500000000721514475665104020420 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local select = select; local t_insert = table.insert; local pairs, next, type = pairs, next, type; local unpack = table.unpack or unpack; --luacheck: ignore 113 local _ENV = nil; -- luacheck: std none local function get(self, ...) local t = self.data; for n = 1,select('#', ...) do t = t[select(n, ...)]; if not t then break; end end return t; end local function add(self, ...) local t = self.data; local count = select('#', ...); for n = 1,count-1 do local key = select(n, ...); local tab = t[key]; if not tab then tab = {}; t[key] = tab; end t = tab; end t_insert(t, (select(count, ...))); end local function set(self, ...) local t = self.data; local count = select('#', ...); for n = 1,count-2 do local key = select(n, ...); local tab = t[key]; if not tab then tab = {}; t[key] = tab; end t = tab; end t[(select(count-1, ...))] = (select(count, ...)); end local function r(t, n, _end, ...) if t == nil then return; end local k = select(n, ...); if n == _end then t[k] = nil; return; end if k then local v = t[k]; if v then r(v, n+1, _end, ...); if not next(v) then t[k] = nil; end end else for _,b in pairs(t) do r(b, n+1, _end, ...); if not next(b) then t[_] = nil; end end end end local function remove(self, ...) local _end = select('#', ...); for n = _end,1 do if select(n, ...) then _end = n; break; end end r(self.data, 1, _end, ...); end local function s(t, n, results, _end, ...) if t == nil then return; end local k = select(n, ...); if n == _end then if k == nil then for _, v in pairs(t) do t_insert(results, v); end else t_insert(results, t[k]); end return; end if k then local v = t[k]; if v then s(v, n+1, results, _end, ...); end else for _,b in pairs(t) do s(b, n+1, results, _end, ...); end end end -- Search for keys, nil == wildcard local function search(self, ...) local _end = select('#', ...); for n = _end,1 do if select(n, ...) then _end = n; break; end end local results = {}; s(self.data, 1, results, _end, ...); return results; end -- Append results to an existing list local function search_add(self, results, ...) if not results then results = {}; end local _end = select('#', ...); for n = _end,1 do if select(n, ...) then _end = n; break; end end s(self.data, 1, results, _end, ...); return results; end local function iter(self, ...) local query = { ... }; local maxdepth = select("#", ...); local stack = { self.data }; local keys = { }; local function it(self) -- luacheck: ignore 432/self local depth = #stack; local key = next(stack[depth], keys[depth]); if key == nil then -- Go up the stack stack[depth], keys[depth] = nil, nil; if depth > 1 then return it(self); end return; -- The end else keys[depth] = key; end local value = stack[depth][key]; if query[depth] == nil or key == query[depth] then if depth == maxdepth then -- Result local result = {}; -- Collect keys forming path to result for i = 1, depth do result[i] = keys[i]; end result[depth+1] = value; return unpack(result, 1, depth+1); elseif type(value) == "table" then t_insert(stack, value); -- Descend end end return it(self); end; return it, self; end local function new() return { data = {}; get = get; add = add; set = set; remove = remove; search = search; search_add = search_add; iter = iter; }; end return { iter = iter; new = new; }; prosody-0.12.4/util/PaxHeaders/openmetrics.lua0000644000000000000000000000012014475665104016374 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/openmetrics.lua0000644000175000017500000002617214475665104020611 0ustar00prosodyprosody00000000000000--[[ This module implements a subset of the OpenMetrics Internet Draft version 00. URL: https://tools.ietf.org/html/draft-richih-opsawg-openmetrics-00 The following metric types are supported: - Counter - Gauge - Histogram - Summary It is used by util.statsd and util.statistics to provide the OpenMetrics API. To understand what this module is about, it is useful to familiarize oneself with the terms MetricFamily, Metric, LabelSet, Label and MetricPoint as defined in the I-D linked above. --]] -- metric constructor interface: -- metric_ctor(..., family_name, labels, extra) local time = require "util.time".now; local select = select; local array = require "util.array"; local log = require "util.logger".init("util.openmetrics"); local new_multitable = require "util.multitable".new; local iter_multitable = require "util.multitable".iter; local t_concat, t_insert = table.concat, table.insert; local t_pack, t_unpack = require "util.table".pack, table.unpack or unpack; --luacheck: ignore 113/unpack -- BEGIN of Utility: "metric proxy" -- This allows to wrap a MetricFamily in a proxy which only provides the -- `with_labels` and `with_partial_label` methods. This allows to pre-set one -- or more labels on a metric family. This is used in particular via -- `with_partial_label` by the moduleapi in order to pre-set the `host` label -- on metrics created in non-global modules. local metric_proxy_mt = {} metric_proxy_mt.__index = metric_proxy_mt local function new_metric_proxy(metric_family, with_labels_proxy_fun) return setmetatable({ _family = metric_family, with_labels = function(self, ...) return with_labels_proxy_fun(self._family, ...) end; with_partial_label = function(self, label) return new_metric_proxy(self._family, function(family, ...) return family:with_labels(label, ...) end) end }, metric_proxy_mt); end -- END of Utility: "metric proxy" -- BEGIN Rendering helper functions (internal) local function escape(text) return text:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n"); end local function escape_name(name) return name:gsub("/", "__"):gsub("[^A-Za-z0-9_]", "_"):gsub("^[^A-Za-z_]", "_%1"); end local function repr_help(metric, docstring) docstring = docstring:gsub("\\", "\\\\"):gsub("\n", "\\n"); return "# HELP "..escape_name(metric).." "..docstring.."\n"; end local function repr_unit(metric, unit) if not unit then unit = "" else unit = unit:gsub("\\", "\\\\"):gsub("\n", "\\n"); end return "# UNIT "..escape_name(metric).." "..unit.."\n"; end -- local allowed_types = { counter = true, gauge = true, histogram = true, summary = true, untyped = true }; -- local allowed_types = { "counter", "gauge", "histogram", "summary", "untyped" }; local function repr_type(metric, type_) -- if not allowed_types:contains(type_) then -- return; -- end return "# TYPE "..escape_name(metric).." "..type_.."\n"; end local function repr_label(key, value) return key.."=\""..escape(value).."\""; end local function repr_labels(labelkeys, labelvalues, extra_labels) local values = {} if labelkeys then for i, key in ipairs(labelkeys) do local value = labelvalues[i] t_insert(values, repr_label(escape_name(key), escape(value))); end end if extra_labels then for key, value in pairs(extra_labels) do t_insert(values, repr_label(escape_name(key), escape(value))); end end if #values == 0 then return ""; end return "{"..t_concat(values, ",").."}"; end local function repr_sample(metric, labelkeys, labelvalues, extra_labels, value) return escape_name(metric)..repr_labels(labelkeys, labelvalues, extra_labels).." "..string.format("%.17g", value).."\n"; end -- END Rendering helper functions (internal) local function render_histogram_le(v) if v == 1/0 then -- I-D-00: 4.1.2.2.1: -- Exposers MUST produce output for positive infinity as +Inf. return "+Inf" end return string.format("%.14g", v) end -- BEGIN of generic MetricFamily implementation local metric_family_mt = {} metric_family_mt.__index = metric_family_mt local function histogram_metric_ctor(orig_ctor, buckets) return function(family_name, labels, extra) return orig_ctor(buckets, family_name, labels, extra) end end local function new_metric_family(backend, type_, family_name, unit, description, label_keys, extra) local metric_ctor = assert(backend[type_], "statistics backend does not support "..type_.." metrics families") local labels = label_keys or {} local user_labels = #labels if type_ == "histogram" then local buckets = extra and extra.buckets if not buckets then error("no buckets given for histogram metric") end buckets = array(buckets) buckets:push(1/0) -- must have +inf bucket metric_ctor = histogram_metric_ctor(metric_ctor, buckets) end local data if #labels == 0 then data = metric_ctor(family_name, nil, extra) else data = new_multitable() end local mf = { family_name = family_name, data = data, type_ = type_, unit = unit, description = description, user_labels = user_labels, label_keys = labels, extra = extra, _metric_ctor = metric_ctor, } setmetatable(mf, metric_family_mt); return mf end function metric_family_mt:new_metric(labels) return self._metric_ctor(self.family_name, labels, self.extra) end function metric_family_mt:clear() for _, metric in self:iter_metrics() do metric:reset() end end function metric_family_mt:with_labels(...) local count = select('#', ...) if count ~= self.user_labels then error("number of labels passed to with_labels does not match number of label keys") end if count == 0 then return self.data end local metric = self.data:get(...) if not metric then local values = t_pack(...) metric = self:new_metric(values) values[values.n+1] = metric self.data:set(t_unpack(values, 1, values.n+1)) end return metric end function metric_family_mt:with_partial_label(label) return new_metric_proxy(self, function (family, ...) return family:with_labels(label, ...) end) end function metric_family_mt:iter_metrics() if #self.label_keys == 0 then local done = false return function() if done then return nil end done = true return {}, self.data end end local searchkeys = {}; local nlabels = #self.label_keys for i=1,nlabels do searchkeys[i] = nil; end local it, state = iter_multitable(self.data, t_unpack(searchkeys, 1, nlabels)) return function(_s) local label_values = t_pack(it(_s)) if label_values.n == 0 then return nil, nil end local metric = label_values[label_values.n] label_values[label_values.n] = nil label_values.n = label_values.n - 1 return label_values, metric end, state end -- END of generic MetricFamily implementation -- BEGIN of MetricRegistry implementation -- Helper to test whether two metrics are "equal". local function equal_metric_family(mf1, mf2) if mf1.type_ ~= mf2.type_ then return false end if #mf1.label_keys ~= #mf2.label_keys then return false end -- Ignoring unit here because in general it'll be part of the name anyway -- So either the unit was moved into/out of the name (which is a valid) -- thing to do on an upgrade or we would expect not to see any conflicts -- anyway. --[[ if mf1.unit ~= mf2.unit then return false end ]] for i, key in ipairs(mf1.label_keys) do if key ~= mf2.label_keys[i] then return false end end return true end -- If the unit is not empty, add it to the full name as per the I-D spec. local function compose_name(name, unit) local full_name = name if unit and unit ~= "" then full_name = full_name .. "_" .. unit end -- TODO: prohibit certain suffixes used by metrics if where they may cause -- conflicts return full_name end local metric_registry_mt = {} metric_registry_mt.__index = metric_registry_mt local function new_metric_registry(backend) local reg = { families = {}, backend = backend, } setmetatable(reg, metric_registry_mt) return reg end function metric_registry_mt:register_metric_family(name, metric_family) local existing = self.families[name]; if existing then if not equal_metric_family(metric_family, existing) then -- We could either be strict about this, or replace the -- existing metric family with the new one. -- Being strict is nice to avoid programming errors / -- conflicts, but causes issues when a new version of a module -- is loaded. -- -- We will thus assume that the new metric is the correct one; -- That is probably OK because unless you're reaching down into -- the util.openmetrics or core.statsmanager API, your metric -- name is going to be scoped to `prosody_mod_$modulename` -- anyway and the damage is thus controlled. -- -- To make debugging such issues easier, we still log. log("debug", "replacing incompatible existing metric family %s", name) -- Below is the code to be strict. --error("conflicting declarations for metric family "..name) else return existing end end self.families[name] = metric_family return metric_family end function metric_registry_mt:gauge(name, unit, description, labels, extra) name = compose_name(name, unit) local mf = new_metric_family(self.backend, "gauge", name, unit, description, labels, extra) mf = self:register_metric_family(name, mf) return mf end function metric_registry_mt:counter(name, unit, description, labels, extra) name = compose_name(name, unit) local mf = new_metric_family(self.backend, "counter", name, unit, description, labels, extra) mf = self:register_metric_family(name, mf) return mf end function metric_registry_mt:histogram(name, unit, description, labels, extra) name = compose_name(name, unit) local mf = new_metric_family(self.backend, "histogram", name, unit, description, labels, extra) mf = self:register_metric_family(name, mf) return mf end function metric_registry_mt:summary(name, unit, description, labels, extra) name = compose_name(name, unit) local mf = new_metric_family(self.backend, "summary", name, unit, description, labels, extra) mf = self:register_metric_family(name, mf) return mf end function metric_registry_mt:get_metric_families() return self.families end function metric_registry_mt:render() local answer = {}; for metric_family_name, metric_family in pairs(self:get_metric_families()) do t_insert(answer, repr_help(metric_family_name, metric_family.description)) t_insert(answer, repr_unit(metric_family_name, metric_family.unit)) t_insert(answer, repr_type(metric_family_name, metric_family.type_)) for labelset, metric in metric_family:iter_metrics() do for suffix, extra_labels, value in metric:iter_samples() do t_insert(answer, repr_sample(metric_family_name..suffix, metric_family.label_keys, labelset, extra_labels, value)) end end end t_insert(answer, "# EOF\n") return t_concat(answer, ""); end -- END of MetricRegistry implementation -- BEGIN of general helpers for implementing high-level APIs on top of OpenMetrics local function timed(metric) local t0 = time() local submitter = assert(metric.sample or metric.set, "metric type cannot be used with timed()") return function() local t1 = time() submitter(metric, t1-t0) end end -- END of general helpers return { new_metric_proxy = new_metric_proxy; new_metric_registry = new_metric_registry; render_histogram_le = render_histogram_le; timed = timed; } prosody-0.12.4/util/PaxHeaders/openssl.lua0000644000000000000000000000012014475665104015527 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/openssl.lua0000644000175000017500000001104614475665104017736 0ustar00prosodyprosody00000000000000local type, tostring, pairs, ipairs = type, tostring, pairs, ipairs; local t_insert, t_concat = table.insert, table.concat; local s_format = string.format; local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE] local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID] local idna_to_ascii = require "util.encodings".idna.to_ascii; local _M = {}; local config = {}; _M.config = config; local ssl_config = {}; local ssl_config_mt = { __index = ssl_config }; function config.new() return setmetatable({ req = { distinguished_name = "distinguished_name", req_extensions = "certrequest", x509_extensions = "selfsigned", prompt = "no", }, distinguished_name = { countryName = "GB", -- stateOrProvinceName = "", localityName = "The Internet", organizationName = "Your Organisation", organizationalUnitName = "XMPP Department", commonName = "example.com", emailAddress = "xmpp@example.com", }, certrequest = { basicConstraints = "CA:FALSE", keyUsage = "digitalSignature,keyEncipherment", extendedKeyUsage = "serverAuth,clientAuth", subjectAltName = "@subject_alternative_name", }, selfsigned = { basicConstraints = "CA:TRUE", subjectAltName = "@subject_alternative_name", }, subject_alternative_name = { DNS = {}, otherName = {}, }, }, ssl_config_mt); end local DN_order = { "countryName"; "stateOrProvinceName"; "localityName"; "streetAddress"; "organizationName"; "organizationalUnitName"; "commonName"; "emailAddress"; } _M._DN_order = DN_order; function ssl_config:serialize() local s = ""; for section, t in pairs(self) do s = s .. ("[%s]\n"):format(section); if section == "subject_alternative_name" then for san, n in pairs(t) do for i = 1, #n do s = s .. s_format("%s.%d = %s\n", san, i -1, n[i]); end end elseif section == "distinguished_name" then for _, k in ipairs(t[1] and t or DN_order) do local v = t[k]; if v then s = s .. ("%s = %s\n"):format(k, v); end end else for k, v in pairs(t) do s = s .. ("%s = %s\n"):format(k, v); end end s = s .. "\n"; end return s; end local function utf8string(s) -- This is how we tell openssl not to encode UTF-8 strings as fake Latin1 return s_format("FORMAT:UTF8,UTF8:%s", s); end local function ia5string(s) return s_format("IA5STRING:%s", s); end _M.util = { utf8string = utf8string, ia5string = ia5string, }; function ssl_config:add_dNSName(host) t_insert(self.subject_alternative_name.DNS, idna_to_ascii(host)); end function ssl_config:add_sRVName(host, service) t_insert(self.subject_alternative_name.otherName, s_format("%s;%s", oid_dnssrv, ia5string("_" .. service .. "." .. idna_to_ascii(host)))); end function ssl_config:add_xmppAddr(host) t_insert(self.subject_alternative_name.otherName, s_format("%s;%s", oid_xmppaddr, utf8string(host))); end function ssl_config:from_prosody(hosts, config, certhosts) -- luacheck: ignore 431/config -- TODO Decide if this should go elsewhere local found_matching_hosts = false; for i = 1, #certhosts do local certhost = certhosts[i]; for name in pairs(hosts) do if name == certhost or name:sub(-1-#certhost) == "." .. certhost then found_matching_hosts = true; self:add_dNSName(name); --print(name .. "#component_module: " .. (config.get(name, "component_module") or "nil")); if config.get(name, "component_module") == nil then self:add_sRVName(name, "xmpp-client"); end --print(name .. "#anonymous_login: " .. tostring(config.get(name, "anonymous_login"))); if not (config.get(name, "anonymous_login") or config.get(name, "authentication") == "anonymous") then self:add_sRVName(name, "xmpp-server"); end self:add_xmppAddr(name); end end end if not found_matching_hosts then return nil, "no-matching-hosts"; end end do -- Lua to shell calls. local function shell_escape(s) return "'" .. tostring(s):gsub("'",[['\'']]) .. "'"; end local function serialize(command, args) local commandline = { "openssl", command }; for k, v in pairs(args) do if type(k) == "string" then t_insert(commandline, ("-%s"):format(k)); if v ~= true then t_insert(commandline, shell_escape(v)); end end end for _, v in ipairs(args) do t_insert(commandline, shell_escape(v)); end return t_concat(commandline, " "); end local os_execute = os.execute; setmetatable(_M, { __index = function(_, command) return function(opts) local ret = os_execute(serialize(command, type(opts) == "table" and opts or {})); return ret == true or ret == 0; end; end; }); end return _M; prosody-0.12.4/util/PaxHeaders/paths.lua0000644000000000000000000000012014475665104015163 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/paths.lua0000644000175000017500000000415214475665104017372 0ustar00prosodyprosody00000000000000local t_concat = table.concat; local path_sep = package.config:sub(1,1); local path_util = {} -- Helper function to resolve relative paths (needed by config) function path_util.resolve_relative_path(parent_path, path) if path then -- Some normalization parent_path = parent_path:gsub("%"..path_sep.."+$", ""); path = path:gsub("^%.%"..path_sep.."+", ""); local is_relative; if path_sep == "/" and path:sub(1,1) ~= "/" then is_relative = true; elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and (path:sub(2,3) ~= ":\\" and path:sub(2,3) ~= ":/")) then is_relative = true; end if is_relative then return parent_path..path_sep..path; end end return path; end -- Helper function to convert a glob to a Lua pattern function path_util.glob_to_pattern(glob) return "^"..glob:gsub("[%p*?]", function (c) if c == "*" then return ".*"; elseif c == "?" then return "."; else return "%"..c; end end).."$"; end function path_util.join(a, b, c, ...) -- (... : string) --> string -- Optimization: Avoid creating table for most uses if b then if c then if ... then return t_concat({a,b,c,...}, path_sep); end return a..path_sep..b..path_sep..c; end return a..path_sep..b; end return a; end function path_util.complement_lua_path(installer_plugin_path) -- Checking for duplicates -- The commands using luarocks need the path to the directory that has the /share and /lib folders. local lua_version = _VERSION:match(" (.+)$"); local lua_path_sep = package.config:sub(3,3); local dir_sep = package.config:sub(1,1); local sub_path = dir_sep.."lua"..dir_sep..lua_version..dir_sep; if not string.find(package.path, installer_plugin_path, 1, true) then package.path = package.path..lua_path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?.lua"; package.path = package.path..lua_path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; end if not string.find(package.path, installer_plugin_path, 1, true) then package.cpath = package.cpath..lua_path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.so"; end end return path_util; prosody-0.12.4/util/PaxHeaders/pluginloader.lua0000644000000000000000000000012014475665104016531 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/pluginloader.lua0000644000175000017500000000656014475665104020745 0ustar00prosodyprosody00000000000000-- 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 113/CFG_PLUGINDIR local dir_sep, path_sep = package.config:match("^(%S+)%s(%S+)"); local lua_version = _VERSION:match(" (.+)$"); local plugin_dir = {}; for path in (CFG_PLUGINDIR or "./plugins/"):gsub("[/\\]", dir_sep):gmatch("[^"..path_sep.."]+") do path = path..dir_sep; -- add path separator to path end path = path:gsub(dir_sep..dir_sep.."+", dir_sep); -- coalesce multiple separators plugin_dir[#plugin_dir + 1] = path; end local io_open = io.open; local envload = require "util.envload".envload; local pluginloader_methods = {}; local pluginloader_mt = { __index = pluginloader_methods }; function pluginloader_methods:load_file(names) local file, err, path; local load_filter_cb = self._options.load_filter_cb; for i=1,#plugin_dir do for j=1,#names do path = plugin_dir[i]..names[j]; file, err = io_open(path); if file then local content = file:read("*a"); file:close(); local metadata; if load_filter_cb then path, content, metadata = load_filter_cb(path, content); end if content and path then return content, path, metadata; end end end end return file, err; end function pluginloader_methods:load_resource(plugin, resource) resource = resource or "mod_"..plugin..".lua"; local names = { "mod_"..plugin..dir_sep..plugin..dir_sep..resource; -- mod_hello/hello/mod_hello.lua "mod_"..plugin..dir_sep..resource; -- mod_hello/mod_hello.lua plugin..dir_sep..resource; -- hello/mod_hello.lua resource; -- mod_hello.lua "share"..dir_sep.."lua"..dir_sep..lua_version..dir_sep..resource; "share"..dir_sep.."lua"..dir_sep..lua_version..dir_sep.."mod_"..plugin..dir_sep..resource; }; return self:load_file(names); end function pluginloader_methods:load_code(plugin, resource, env) local content, err, metadata = self:load_resource(plugin, resource); if not content then return content, err; end local path = err; local f, err = envload(content, "@"..path, env); if not f then return f, err; end return f, path, metadata; end function pluginloader_methods:load_code_ext(plugin, resource, extension, env) local content, err, metadata = self:load_resource(plugin, resource.."."..extension); if not content and extension == "lib.lua" then content, err, metadata = self:load_resource(plugin, resource..".lua"); end if not content then content, err, metadata = self:load_resource(resource, resource.."."..extension); if not content then return content, err; end end local path = err; local f, err = envload(content, "@"..path, env); if not f then return f, err; end return f, path, metadata; end local function init(options) return setmetatable({ _options = options or {}; }, pluginloader_mt); end local function bind(self, method) return function (...) return method(self, ...); end; end local default_loader = init(); return { load_file = bind(default_loader, default_loader.load_file); load_resource = bind(default_loader, default_loader.load_resource); load_code = bind(default_loader, default_loader.load_code); load_code_ext = bind(default_loader, default_loader.load_code_ext); init = init; }; prosody-0.12.4/util/PaxHeaders/presence.lua0000644000000000000000000000012014475665104015650 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/presence.lua0000644000175000017500000000164414475665104020062 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local t_insert = table.insert; local function select_top_resources(user) local priority = 0; local recipients = {}; for _, session in pairs(user.sessions) do -- find resource with greatest priority if session.presence then local p = session.priority; if p > priority then priority = p; recipients = {session}; elseif p == priority then t_insert(recipients, session); end end end return recipients; end local function recalc_resource_map(user) if user then user.top_resources = select_top_resources(user); if #user.top_resources == 0 then user.top_resources = nil; end end end return { select_top_resources = select_top_resources; recalc_resource_map = recalc_resource_map; } prosody-0.12.4/util/PaxHeaders/promise.lua0000644000000000000000000000012014475665104015522 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/promise.lua0000644000175000017500000001227514475665104017736 0ustar00prosodyprosody00000000000000local promise_methods = {}; local promise_mt = { __name = "promise", __index = promise_methods }; local xpcall = require "util.xpcall".xpcall; local unpack = table.unpack or unpack; --luacheck: ignore 113 function promise_mt:__tostring() return "promise (" .. (self._state or "invalid") .. ")"; end local function is_promise(o) local mt = getmetatable(o); return mt == promise_mt; end local function wrap_handler(f, resolve, reject, default) if not f then return default; end return function (param) local ok, ret = xpcall(f, debug.traceback, param); if ok then resolve(ret); else reject(ret); end return true; end; end local function next_pending(self, on_fulfilled, on_rejected, resolve, reject) table.insert(self._pending_on_fulfilled, wrap_handler(on_fulfilled, resolve, reject, resolve)); table.insert(self._pending_on_rejected, wrap_handler(on_rejected, resolve, reject, reject)); end local function next_fulfilled(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_rejected wrap_handler(on_fulfilled, resolve, reject, resolve)(promise.value); end local function next_rejected(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_fulfilled wrap_handler(on_rejected, resolve, reject, reject)(promise.reason); end local function promise_settle(promise, new_state, new_next, cbs, value) if promise._state ~= "pending" then return; end promise._state = new_state; promise._next = new_next; for _, cb in ipairs(cbs) do cb(value); end -- No need to keep references to callbacks promise._pending_on_fulfilled = nil; promise._pending_on_rejected = nil; return true; end local function new_resolve_functions(p) local resolved = false; local function _resolve(v) if resolved then return; end resolved = true; if is_promise(v) then v:next(new_resolve_functions(p)); elseif promise_settle(p, "fulfilled", next_fulfilled, p._pending_on_fulfilled, v) then p.value = v; end end local function _reject(e) if resolved then return; end resolved = true; if promise_settle(p, "rejected", next_rejected, p._pending_on_rejected, e) then p.reason = e; end end return _resolve, _reject; end local next_tick = function (f) f(); end local function new(f) local p = setmetatable({ _state = "pending", _next = next_pending, _pending_on_fulfilled = {}, _pending_on_rejected = {} }, promise_mt); if f then next_tick(function() local resolve, reject = new_resolve_functions(p); local ok, ret = xpcall(f, debug.traceback, resolve, reject); if not ok and p._state == "pending" then reject(ret); end end); end return p; end local function all(promises) return new(function (resolve, reject) local settled, results, loop_finished = 0, {}, false; local total = 0; for k, v in pairs(promises) do if is_promise(v) then total = total + 1; v:next(function (value) results[k] = value; settled = settled + 1; if settled == total and loop_finished then resolve(results); end end, reject); else results[k] = v; end end loop_finished = true; if settled == total then resolve(results); end end); end local function all_settled(promises) return new(function (resolve) local settled, results, loop_finished = 0, {}, false; local total = 0; for k, v in pairs(promises) do if is_promise(v) then total = total + 1; v:next(function (value) results[k] = { status = "fulfilled", value = value }; settled = settled + 1; if settled == total and loop_finished then resolve(results); end end, function (e) results[k] = { status = "rejected", reason = e }; settled = settled + 1; if settled == total and loop_finished then resolve(results); end end); else results[k] = v; end end loop_finished = true; if settled == total then resolve(results); end end); end local function join(handler, ...) local promises, n = { ... }, select("#", ...); return all(promises):next(function (results) return handler(unpack(results, 1, n)); end); end local function race(promises) return new(function (resolve, reject) for i = 1, #promises do promises[i]:next(resolve, reject); end end); end local function resolve(v) return new(function (_resolve) _resolve(v); end); end local function reject(v) return new(function (_, _reject) _reject(v); end); end local function try(f) return resolve():next(function () return f(); end); end function promise_methods:next(on_fulfilled, on_rejected) return new(function (resolve, reject) --luacheck: ignore 431/resolve 431/reject self:_next(on_fulfilled, on_rejected, resolve, reject); end); end function promise_methods:catch(on_rejected) return self:next(nil, on_rejected); end function promise_methods:finally(on_finally) local function _on_finally(value) on_finally(); return value; end local function _on_catch_finally(err) on_finally(); return reject(err); end return self:next(_on_finally, _on_catch_finally); end return { new = new; resolve = resolve; join = join; reject = reject; all = all; all_settled = all_settled; race = race; try = try; is_promise = is_promise; set_nexttick = function(new_next_tick) next_tick = new_next_tick; end; } prosody-0.12.4/util/PaxHeaders/prosodyctl0000644000000000000000000000013214475665104015471 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.780764089 30 ctime=1693936196.780764089 prosody-0.12.4/util/prosodyctl/0000755000175000017500000000000014475665104017750 5ustar00prosodyprosody00000000000000prosody-0.12.4/util/prosodyctl/PaxHeaders/cert.lua0000644000000000000000000000012014475665104017203 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.780764089 prosody-0.12.4/util/prosodyctl/cert.lua0000644000175000017500000002607014475665104021415 0ustar00prosodyprosody00000000000000local lfs = require "lfs"; local pctl = require "util.prosodyctl"; local hi = require "util.human.io"; local configmanager = require "core.configmanager"; local openssl; local cert_commands = {}; -- If a file already exists, ask if the user wants to use it or replace it -- Backups the old file if replaced local function use_existing(filename) local attrs = lfs.attributes(filename); if attrs then if hi.show_yesno(filename .. " exists, do you want to replace it? [y/n]") then local backup = filename..".bkp~"..os.date("%FT%T", attrs.change); os.rename(filename, backup); pctl.show_message("%s backed up to %s", filename, backup); else -- Use the existing file return true; end end end local have_pposix, pposix = pcall(require, "util.pposix"); local cert_basedir = prosody.paths.data == "." and "./certs" or prosody.paths.data; if have_pposix and pposix.getuid() == 0 then -- FIXME should be enough to check if this directory is writable local cert_dir = configmanager.get("*", "certificates") or "certs"; cert_basedir = configmanager.resolve_relative_path(prosody.paths.config, cert_dir); end function cert_commands.config(arg) if #arg >= 1 and arg[1] ~= "--help" then local conf_filename = cert_basedir .. "/" .. arg[1] .. ".cnf"; if use_existing(conf_filename) then return nil, conf_filename; end local distinguished_name; if arg[#arg]:find("^/") then distinguished_name = table.remove(arg); end local conf = openssl.config.new(); conf:from_prosody(prosody.hosts, configmanager, arg); if distinguished_name then local dn = {}; for k, v in distinguished_name:gmatch("/([^=/]+)=([^/]+)") do table.insert(dn, k); dn[k] = v; end conf.distinguished_name = dn; else pctl.show_message("Please provide details to include in the certificate config file."); pctl.show_message("Leave the field empty to use the default value or '.' to exclude the field.") for _, k in ipairs(openssl._DN_order) do local v = conf.distinguished_name[k]; if v then local nv = nil; if k == "commonName" then v = arg[1] elseif k == "emailAddress" then v = "xmpp@" .. arg[1]; elseif k == "countryName" then local tld = arg[1]:match"%.([a-z]+)$"; if tld and #tld == 2 and tld ~= "uk" then v = tld:upper(); end end nv = hi.show_prompt(("%s (%s):"):format(k, nv or v)); nv = (not nv or nv == "") and v or nv; if nv:find"[\192-\252][\128-\191]+" then conf.req.string_mask = "utf8only" end conf.distinguished_name[k] = nv ~= "." and nv or nil; end end end local conf_file, err = io.open(conf_filename, "w"); if not conf_file then pctl.show_warning("Could not open OpenSSL config file for writing"); pctl.show_warning("%s", err); os.exit(1); end conf_file:write(conf:serialize()); conf_file:close(); print(""); pctl.show_message("Config written to %s", conf_filename); return nil, conf_filename; else pctl.show_usage("cert config HOSTNAME [HOSTNAME+]", "Builds a certificate config file covering the supplied hostname(s)") end end function cert_commands.key(arg) if #arg >= 1 and arg[1] ~= "--help" then local key_filename = cert_basedir .. "/" .. arg[1] .. ".key"; if use_existing(key_filename) then return nil, key_filename; end os.remove(key_filename); -- This file, if it exists is unlikely to have write permissions local key_size = tonumber(arg[2] or hi.show_prompt("Choose key size (2048):") or 2048); local old_umask = pposix.umask("0377"); if openssl.genrsa{out=key_filename, key_size} then os.execute(("chmod 400 '%s'"):format(key_filename)); pctl.show_message("Key written to %s", key_filename); pposix.umask(old_umask); return nil, key_filename; end pctl.show_message("There was a problem, see OpenSSL output"); else pctl.show_usage("cert key HOSTNAME ", "Generates a RSA key named HOSTNAME.key\n " .."Prompts for a key size if none given") end end function cert_commands.request(arg) if #arg >= 1 and arg[1] ~= "--help" then local req_filename = cert_basedir .. "/" .. arg[1] .. ".req"; if use_existing(req_filename) then return nil, req_filename; end local _, key_filename = cert_commands.key({arg[1]}); local _, conf_filename = cert_commands.config(arg); if openssl.req{new=true, key=key_filename, utf8=true, sha256=true, config=conf_filename, out=req_filename} then pctl.show_message("Certificate request written to %s", req_filename); else pctl.show_message("There was a problem, see OpenSSL output"); end else pctl.show_usage("cert request HOSTNAME [HOSTNAME+]", "Generates a certificate request for the supplied hostname(s)") end end function cert_commands.generate(arg) if #arg >= 1 and arg[1] ~= "--help" then local cert_filename = cert_basedir .. "/" .. arg[1] .. ".crt"; if use_existing(cert_filename) then return nil, cert_filename; end local _, key_filename = cert_commands.key({arg[1]}); local _, conf_filename = cert_commands.config(arg); if key_filename and conf_filename and cert_filename and openssl.req{new=true, x509=true, nodes=true, key=key_filename, days=365, sha256=true, utf8=true, config=conf_filename, out=cert_filename} then pctl.show_message("Certificate written to %s", cert_filename); print(); else pctl.show_message("There was a problem, see OpenSSL output"); end else pctl.show_usage("cert generate HOSTNAME [HOSTNAME+]", "Generates a self-signed certificate for the current hostname(s)") end end local function sh_esc(s) return "'" .. s:gsub("'", "'\\''") .. "'"; end local function copy(from, to, umask, owner, group) local old_umask = umask and pposix.umask(umask); local attrs = lfs.attributes(to); if attrs then -- Move old file out of the way local backup = to..".bkp~"..os.date("%FT%T", attrs.change); os.rename(to, backup); end -- FIXME friendlier error handling, maybe move above backup back? local input = assert(io.open(from)); local output = assert(io.open(to, "w")); local data = input:read(2^11); while data and output:write(data) do data = input:read(2^11); end assert(input:close()); assert(output:close()); if not prosody.installed then -- FIXME this is possibly specific to GNU chown os.execute(("chown -c --reference=%s %s"):format(sh_esc(cert_basedir), sh_esc(to))); elseif owner and group then local ok = os.execute(("chown %s:%s %s"):format(sh_esc(owner), sh_esc(group), sh_esc(to))); assert(ok == true or ok == 0, "Failed to change ownership of "..to); end if old_umask then pposix.umask(old_umask); end return true; end function cert_commands.import(arg) local hostnames = {}; -- Move hostname arguments out of arg, the rest should be a list of paths while arg[1] and prosody.hosts[ arg[1] ] do table.insert(hostnames, table.remove(arg, 1)); end if hostnames[1] == nil then local domains = os.getenv"RENEWED_DOMAINS"; -- Set if invoked via certbot if domains then for host in domains:gmatch("%S+") do table.insert(hostnames, host); end else for host in pairs(prosody.hosts) do if host ~= "*" and configmanager.get(host, "enabled") ~= false then table.insert(hostnames, host); local http_host = configmanager.get(host, "http_host") or host; if http_host ~= host then table.insert(hostnames, http_host); end end end end end if not arg[1] or arg[1] == "--help" then -- Probably forgot the path pctl.show_usage("cert import [HOSTNAME+] /path/to/certs [/other/paths/]+", "Copies certificates to "..cert_basedir); return 1; end local owner, group; if pposix.getuid() == 0 then -- We need root to change ownership owner = configmanager.get("*", "prosody_user") or "prosody"; group = configmanager.get("*", "prosody_group") or owner; end local cm = require "core.certmanager"; local files_by_name = {} for _, dir in ipairs(arg) do cm.index_certs(dir, files_by_name); end local imported = {}; table.sort(hostnames, function (a, b) -- Try to find base domain name before sub-domains, then alphabetically, so -- that the order and choice of file name is deterministic. if #a == #b then return a < b; else return #a < #b; end end); for _, host in ipairs(hostnames) do local paths = cm.find_cert_in_index(files_by_name, host); if paths and imported[paths.certificate] then -- One certificate, many names! table.insert(imported, host); elseif paths then local c = copy(paths.certificate, cert_basedir .. "/" .. host .. ".crt", nil, owner, group); local k = copy(paths.key, cert_basedir .. "/" .. host .. ".key", "0377", owner, group); if c and k then table.insert(imported, host); imported[paths.certificate] = true; else if not c then pctl.show_warning("Could not copy certificate '%s'", paths.certificate); end if not k then pctl.show_warning("Could not copy key '%s'", paths.key); end end else -- TODO Say where we looked pctl.show_warning("No certificate for host %s found :(", host); end -- TODO Additional checks -- Certificate names matches the hostname -- Private key matches public key in certificate end if imported[1] then pctl.show_message("Imported certificate and key for hosts %s", table.concat(imported, ", ")); local ok, err = pctl.reload(); if not ok and err ~= "not-running" then pctl.show_message(pctl.error_messages[err]); end else pctl.show_warning("No certificates imported :("); return 1; end end local function cert(arg) if #arg >= 1 and arg[1] ~= "--help" then openssl = require "util.openssl"; lfs = require "lfs"; local cert_dir_attrs = lfs.attributes(cert_basedir); if not cert_dir_attrs then pctl.show_warning("The directory %s does not exist", cert_basedir); return 1; -- TODO Should we create it? end local uid = pposix.getuid(); if uid ~= 0 and uid ~= cert_dir_attrs.uid then pctl.show_warning("The directory %s is not owned by the current user, won't be able to write files to it", cert_basedir); return 1; elseif not cert_dir_attrs.permissions then -- COMPAT with LuaFilesystem < 1.6.2 (hey CentOS!) pctl.show_message("Unable to check permissions on %s (LuaFilesystem 1.6.2+ required)", cert_basedir); pctl.show_message("Please confirm that Prosody (and only Prosody) can write to this directory)"); elseif cert_dir_attrs.permissions:match("^%.w..%-..%-.$") then pctl.show_warning("The directory %s not only writable by its owner", cert_basedir); return 1; end local subcmd = table.remove(arg, 1); if type(cert_commands[subcmd]) == "function" then if subcmd ~= "import" then -- hostnames are optional for import if not arg[1] then pctl.show_message"You need to supply at least one hostname" arg = { "--help" }; end if arg[1] ~= "--help" and not prosody.hosts[arg[1]] then pctl.show_message(pctl.error_messages["no-such-host"]); return 1; end end return cert_commands[subcmd](arg); elseif subcmd == "check" then return require "util.prosodyctl.check".check({"certs"}); end end pctl.show_usage("cert config|request|generate|key|import", "Helpers for generating X.509 certificates and keys.") for _, cmd in pairs(cert_commands) do print() cmd{ "--help" } end end return { cert = cert; }; prosody-0.12.4/util/prosodyctl/PaxHeaders/check.lua0000644000000000000000000000012014475665104017323 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.780764089 prosody-0.12.4/util/prosodyctl/check.lua0000644000175000017500000014650014475665104021536 0ustar00prosodyprosody00000000000000local configmanager = require "core.configmanager"; local moduleapi = require "core.moduleapi"; local show_usage = require "util.prosodyctl".show_usage; local show_warning = require "util.prosodyctl".show_warning; local is_prosody_running = require "util.prosodyctl".isrunning; local parse_args = require "util.argparse".parse; local dependencies = require "util.dependencies"; local socket = require "socket"; local socket_url = require "socket.url"; local jid_split = require "util.jid".prepped_split; local modulemanager = require "core.modulemanager"; local async = require "util.async"; local httputil = require "util.http"; local function api(host) return setmetatable({ name = "prosodyctl.check"; host = host; log = prosody.log }, { __index = moduleapi }) end local function check_ojn(check_type, target_host) local http = require "net.http"; -- .new({}); local json = require "util.json"; local response, err = async.wait_for(http.request( ("https://observe.jabber.network/api/v1/check/%s"):format(httputil.urlencode(check_type)), { method="POST", headers={["Accept"] = "application/json"; ["Content-Type"] = "application/json"}, body=json.encode({target=target_host}), })); if not response then return false, err; end if response.code ~= 200 then return false, ("API replied with non-200 code: %d"):format(response.code); end local decoded_body, err = json.decode(response.body); if decoded_body == nil then return false, ("Failed to parse API JSON: %s"):format(err) end local success = decoded_body["success"]; return success == true, nil; end local function check_probe(base_url, probe_module, target) local http = require "net.http"; -- .new({}); local params = httputil.formencode({ module = probe_module; target = target }) local response, err = async.wait_for(http.request(base_url .. "?" .. params)); if not response then return false, err; end if response.code ~= 200 then return false, ("API replied with non-200 code: %d"):format(response.code); end for line in response.body:gmatch("[^\r\n]+") do local probe_success = line:match("^probe_success%s+(%d+)"); if probe_success == "1" then return true; elseif probe_success == "0" then return false; end end return false, "Probe endpoint did not return a success status"; end local function check_turn_service(turn_service, ping_service) local ip = require "util.ip"; local stun = require "net.stun"; -- Create UDP socket for communication with the server local sock = assert(require "socket".udp()); sock:setsockname("*", 0); sock:setpeername(turn_service.host, turn_service.port); sock:settimeout(10); -- Helper function to receive a packet local function receive_packet() local raw_packet, err = sock:receive(); if not raw_packet then return nil, err; end return stun.new_packet():deserialize(raw_packet); end local result = { warnings = {} }; -- Send a "binding" query, i.e. a request for our external IP/port local bind_query = stun.new_packet("binding", "request"); bind_query:add_attribute("software", "prosodyctl check turn"); sock:send(bind_query:serialize()); local bind_result, err = receive_packet(); if not bind_result then result.error = "No STUN response: "..err; return result; elseif bind_result:is_err_resp() then result.error = ("STUN server returned error: %d (%s)"):format(bind_result:get_error()); return result; elseif not bind_result:is_success_resp() then result.error = ("Unexpected STUN response: %d (%s)"):format(bind_result:get_type()); return result; end result.external_ip = bind_result:get_xor_mapped_address(); if not result.external_ip then result.error = "STUN server did not return an address"; return result; end if ip.new_ip(result.external_ip.address).private then table.insert(result.warnings, "STUN returned a private IP! Is the TURN server behind a NAT and misconfigured?"); end -- Send a TURN "allocate" request. Expected to fail due to auth, but -- necessary to obtain a valid realm/nonce from the server. local pre_request = stun.new_packet("allocate", "request"); sock:send(pre_request:serialize()); local pre_result, err = receive_packet(); if not pre_result then result.error = "No initial TURN response: "..err; return result; elseif pre_result:is_success_resp() then result.error = "TURN server does not have authentication enabled"; return result; end local realm = pre_result:get_attribute("realm"); local nonce = pre_result:get_attribute("nonce"); if not realm then table.insert(result.warnings, "TURN server did not return an authentication realm. Is authentication enabled?"); end if not nonce then table.insert(result.warnings, "TURN server did not return a nonce"); end -- Use the configured secret to obtain temporary user/pass credentials local turn_user, turn_pass = stun.get_user_pass_from_secret(turn_service.secret); -- Send a TURN allocate request, will fail if auth is wrong local alloc_request = stun.new_packet("allocate", "request"); alloc_request:add_requested_transport("udp"); alloc_request:add_attribute("username", turn_user); if realm then alloc_request:add_attribute("realm", realm); end if nonce then alloc_request:add_attribute("nonce", nonce); end local key = stun.get_long_term_auth_key(realm or turn_service.host, turn_user, turn_pass); alloc_request:add_message_integrity(key); sock:send(alloc_request:serialize()); -- Check the response local alloc_response, err = receive_packet(); if not alloc_response then result.error = "TURN server did not response to allocation request: "..err; return result; elseif alloc_response:is_err_resp() then result.error = ("TURN allocation failed: %d (%s)"):format(alloc_response:get_error()); return result; elseif not alloc_response:is_success_resp() then result.error = ("Unexpected TURN response: %d (%s)"):format(alloc_response:get_type()); return result; end result.relayed_addresses = alloc_response:get_xor_relayed_addresses(); if not ping_service then -- Success! We won't be running the relay test. return result; end -- Run the relay test - i.e. send a binding request to ping_service -- and receive a response. -- Resolve the IP of the ping service local ping_host, ping_port = ping_service:match("^([^:]+):(%d+)$"); if ping_host then ping_port = tonumber(ping_port); else -- Only a hostname specified, use default STUN port ping_host, ping_port = ping_service, 3478; end if ping_host == turn_service.host then result.error = ("Unable to perform ping test: please supply an external STUN server address. See https://prosody.im/doc/turn#prosodyctl-check"); return result; end local ping_service_ip, err = socket.dns.toip(ping_host); if not ping_service_ip then result.error = "Unable to resolve ping service hostname: "..err; return result; end -- Ask the TURN server to allow packets from the ping service IP local perm_request = stun.new_packet("create-permission"); perm_request:add_xor_peer_address(ping_service_ip); perm_request:add_attribute("username", turn_user); if realm then perm_request:add_attribute("realm", realm); end if nonce then perm_request:add_attribute("nonce", nonce); end perm_request:add_message_integrity(key); sock:send(perm_request:serialize()); local perm_response, err = receive_packet(); if not perm_response then result.error = "No response from TURN server when requesting peer permission: "..err; return result; elseif perm_response:is_err_resp() then result.error = ("TURN permission request failed: %d (%s)"):format(perm_response:get_error()); return result; elseif not perm_response:is_success_resp() then result.error = ("Unexpected TURN response: %d (%s)"):format(perm_response:get_type()); return result; end -- Ask the TURN server to relay a STUN binding request to the ping server local ping_data = stun.new_packet("binding"):serialize(); local ping_request = stun.new_packet("send", "indication"); ping_request:add_xor_peer_address(ping_service_ip, ping_port); ping_request:add_attribute("data", ping_data); ping_request:add_attribute("username", turn_user); if realm then ping_request:add_attribute("realm", realm); end if nonce then ping_request:add_attribute("nonce", nonce); end ping_request:add_message_integrity(key); sock:send(ping_request:serialize()); local ping_response, err = receive_packet(); if not ping_response then result.error = "No response from ping server ("..ping_service_ip.."): "..err; return result; elseif not ping_response:is_indication() or select(2, ping_response:get_method()) ~= "data" then result.error = ("Unexpected TURN response: %s %s"):format(select(2, ping_response:get_method()), select(2, ping_response:get_type())); return result; end local pong_data = ping_response:get_attribute("data"); if not pong_data then result.error = "No data relayed from remote server"; return result; end local pong = stun.new_packet():deserialize(pong_data); result.external_ip_pong = pong:get_xor_mapped_address(); if not result.external_ip_pong then result.error = "Ping server did not return an address"; return result; end local relay_address_found, relay_port_matches; for _, relayed_address in ipairs(result.relayed_addresses) do if relayed_address.address == result.external_ip_pong.address then relay_address_found = true; relay_port_matches = result.external_ip_pong.port == relayed_address.port; end end if not relay_address_found then table.insert(result.warnings, "TURN external IP vs relay address mismatch! Is the TURN server behind a NAT and misconfigured?"); elseif not relay_port_matches then table.insert(result.warnings, "External port does not match reported relay port! This is probably caused by a NAT in front of the TURN server."); end -- return result; end local function skip_bare_jid_hosts(host) if jid_split(host) then -- See issue #779 return false; end return true; end local check_opts = { short_params = { h = "help", v = "verbose"; }; value_params = { ping = true; }; }; local function check(arg) if arg[1] == "help" or arg[1] == "--help" then show_usage([[check]], [[Perform basic checks on your Prosody installation]]); return 1; end local what = table.remove(arg, 1); local opts, opts_err, opts_info = parse_args(arg, check_opts); if opts_err == "missing-value" then print("Error: Expected a value after '"..opts_info.."'"); return 1; elseif opts_err == "param-not-found" then print("Error: Unknown parameter: "..opts_info); return 1; end local array = require "util.array"; local set = require "util.set"; local it = require "util.iterators"; local ok = true; local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end local function enabled_hosts() return it.filter(disabled_hosts, pairs(configmanager.getconfig())); end if not (what == nil or what == "disabled" or what == "config" or what == "dns" or what == "certs" or what == "connectivity" or what == "turn") then show_warning("Don't know how to check '%s'. Try one of 'config', 'dns', 'certs', 'disabled', 'turn' or 'connectivity'.", what); show_warning("Note: The connectivity check will connect to a remote server."); return 1; end if not what or what == "disabled" then local disabled_hosts_set = set.new(); for host in it.filter("*", pairs(configmanager.getconfig())) do if api(host):get_option_boolean("enabled") == false then disabled_hosts_set:add(host); end end if not disabled_hosts_set:empty() then local msg = "Checks will be skipped for these disabled hosts: %s"; if what then msg = "These hosts are disabled: %s"; end show_warning(msg, tostring(disabled_hosts_set)); if what then return 0; end print"" end end if not what or what == "config" then print("Checking config..."); if what == "config" then local files = configmanager.files(); print(" The following configuration files have been loaded:"); print(" - "..table.concat(files, "\n - ")); end local obsolete = set.new({ --> remove "archive_cleanup_interval", "dns_timeout", "muc_log_cleanup_interval", "s2s_dns_resolvers", "setgid", "setuid", }); local function instead_use(kind, name, value) if kind == "option" then if value then return string.format("instead, use '%s = %q'", name, value); else return string.format("instead, use '%s'", name); end elseif kind == "module" then return string.format("instead, add %q to '%s'", name, value or "modules_enabled"); elseif kind == "community" then return string.format("instead, add %q from %s", name, value or "prosody-modules"); end return kind end local deprecated_replacements = { anonymous_login = instead_use("option", "authentication", "anonymous"); daemonize = "instead, use the --daemonize/-D or --foreground/-F command line flags"; disallow_s2s = instead_use("module", "s2s", "modules_disabled"); no_daemonize = "instead, use the --daemonize/-D or --foreground/-F command line flags"; require_encryption = "instead, use 'c2s_require_encryption' and 's2s_require_encryption'"; vcard_compatibility = instead_use("community", "mod_compat_vcard"); use_libevent = instead_use("option", "network_backend", "event"); whitelist_registration_only = instead_use("option", "allowlist_registration_only"); registration_whitelist = instead_use("option", "registration_allowlist"); registration_blacklist = instead_use("option", "registration_blocklist"); blacklist_on_registration_throttle_overload = instead_use("blocklist_on_registration_throttle_overload"); cross_domain_bosh = "instead, use 'http_cors_override', see https://prosody.im/doc/http#cross-domain-cors-support"; cross_domain_websocket = "instead, use 'http_cors_override', see https://prosody.im/doc/http#cross-domain-cors-support"; }; -- FIXME all the singular _port and _interface options are supposed to be deprecated too local deprecated_ports = { bosh = "http", legacy_ssl = "c2s_direct_tls" }; local port_suffixes = set.new({ "port", "ports", "interface", "interfaces", "ssl" }); for port, replacement in pairs(deprecated_ports) do for suffix in port_suffixes do local rsuffix = (suffix == "port" or suffix == "interface") and suffix.."s" or suffix; deprecated_replacements[port.."_"..suffix] = "instead, use '"..replacement.."_"..rsuffix.."'" end end local deprecated = set.new(array.collect(it.keys(deprecated_replacements))); local known_global_options = set.new({ "access_control_allow_credentials", "access_control_allow_headers", "access_control_allow_methods", "access_control_max_age", "admin_socket", "body_size_limit", "bosh_max_inactivity", "bosh_max_polling", "bosh_max_wait", "buffer_size_limit", "c2s_close_timeout", "c2s_stanza_size_limit", "c2s_tcp_keepalives", "c2s_timeout", "component_stanza_size_limit", "component_tcp_keepalives", "consider_bosh_secure", "consider_websocket_secure", "console_banner", "console_prettyprint_settings", "daemonize", "gc", "http_default_host", "http_errors_always_show", "http_errors_default_message", "http_errors_detailed", "http_errors_messages", "http_max_buffer_size", "http_max_content_size", "installer_plugin_path", "limits", "limits_resolution", "log", "multiplex_buffer_size", "network_backend", "network_default_read_size", "network_settings", "openmetrics_allow_cidr", "openmetrics_allow_ips", "pidfile", "plugin_paths", "plugin_server", "prosodyctl_timeout", "prosody_group", "prosody_user", "run_as_root", "s2s_close_timeout", "s2s_insecure_domains", "s2s_require_encryption", "s2s_secure_auth", "s2s_secure_domains", "s2s_stanza_size_limit", "s2s_tcp_keepalives", "s2s_timeout", "statistics", "statistics_config", "statistics_interval", "tcp_keepalives", "tls_profile", "trusted_proxies", "umask", "use_dane", "use_ipv4", "use_ipv6", "websocket_frame_buffer_limit", "websocket_frame_fragment_limit", "websocket_get_response_body", "websocket_get_response_text", }); local config = configmanager.getconfig(); local global = api("*"); -- Check that we have any global options (caused by putting a host at the top) if it.count(it.filter("log", pairs(config["*"]))) == 0 then ok = false; print(""); print(" No global options defined. Perhaps you have put a host definition at the top") print(" of the config file? They should be at the bottom, see https://prosody.im/doc/configure#overview"); end if it.count(enabled_hosts()) == 0 then ok = false; print(""); if it.count(it.filter("*", pairs(config))) == 0 then print(" No hosts are defined, please add at least one VirtualHost section") elseif config["*"]["enabled"] == false then print(" No hosts are enabled. Remove enabled = false from the global section or put enabled = true under at least one VirtualHost section") else print(" All hosts are disabled. Remove enabled = false from at least one VirtualHost section") end end if not config["*"].modules_enabled then print(" No global modules_enabled is set?"); local suggested_global_modules; for host, options in enabled_hosts() do --luacheck: ignore 213/host if not options.component_module and options.modules_enabled then suggested_global_modules = set.intersection(suggested_global_modules or set.new(options.modules_enabled), set.new(options.modules_enabled)); end end if suggested_global_modules and not suggested_global_modules:empty() then print(" Consider moving these modules into modules_enabled in the global section:") print(" "..tostring(suggested_global_modules / function (x) return ("%q"):format(x) end)); end print(); end local function validate_module_list(host, name, modules) if modules == nil then return -- okay except for global section, checked separately end local t = type(modules) if t ~= "table" then print(" The " .. name .. " in the " .. host .. " section should not be a " .. t .. " but a list of strings, e.g."); print(" " .. name .. " = { \"name_of_module\", \"another_plugin\", }") print() ok = false return end for k, v in pairs(modules) do if type(k) ~= "number" or type(v) ~= "string" then print(" The " .. name .. " in the " .. host .. " section should not be a map of " .. type(k) .. " to " .. type(v) .. " but a list of strings, e.g."); print(" " .. name .. " = { \"name_of_module\", \"another_plugin\", }") ok = false break end end end for host, options in enabled_hosts() do validate_module_list(host, "modules_enabled", options.modules_enabled); validate_module_list(host, "modules_disabled", options.modules_disabled); end do -- Check for modules enabled both normally and as components local modules = global:get_option_set("modules_enabled"); for host, options in enabled_hosts() do local component_module = options.component_module; if component_module and modules:contains(component_module) then print((" mod_%s is enabled both in modules_enabled and as Component %q %q"):format(component_module, host, component_module)); print(" This means the service is enabled on all VirtualHosts as well as the Component."); print(" Are you sure this what you want? It may cause unexpected behaviour."); end end end -- Check for global options under hosts local global_options = set.new(it.to_array(it.keys(config["*"]))); local obsolete_global_options = set.intersection(global_options, obsolete); if not obsolete_global_options:empty() then print(""); print(" You have some obsolete options you can remove from the global section:"); print(" "..tostring(obsolete_global_options)) ok = false; end local deprecated_global_options = set.intersection(global_options, deprecated); if not deprecated_global_options:empty() then print(""); print(" You have some deprecated options in the global section:"); for option in deprecated_global_options do print((" '%s' -- %s"):format(option, deprecated_replacements[option])); end ok = false; end for host, options in it.filter(function (h) return h ~= "*" end, pairs(configmanager.getconfig())) do local host_options = set.new(it.to_array(it.keys(options))); local misplaced_options = set.intersection(host_options, known_global_options); for name in pairs(options) do if name:match("^interfaces?") or name:match("_ports?$") or name:match("_interfaces?$") or (name:match("_ssl$") and not name:match("^[cs]2s_ssl$")) then misplaced_options:add(name); end end -- FIXME These _could_ be misplaced, but we would have to check where the corresponding module is loaded to be sure misplaced_options:exclude(set.new({ "external_service_port", "turn_external_port" })); if not misplaced_options:empty() then ok = false; print(""); local n = it.count(misplaced_options); print(" You have "..n.." option"..(n>1 and "s " or " ").."set under "..host.." that should be"); print(" in the global section of the config file, above any VirtualHost or Component definitions,") print(" see https://prosody.im/doc/configure#overview for more information.") print(""); print(" You need to move the following option"..(n>1 and "s" or "")..": "..table.concat(it.to_array(misplaced_options), ", ")); end end for host, options in enabled_hosts() do local host_options = set.new(it.to_array(it.keys(options))); local subdomain = host:match("^[^.]+"); if not(host_options:contains("component_module")) and (subdomain == "jabber" or subdomain == "xmpp" or subdomain == "chat" or subdomain == "im") then print(""); print(" Suggestion: If "..host.. " is a new host with no real users yet, consider renaming it now to"); print(" "..host:gsub("^[^.]+%.", "")..". You can use SRV records to redirect XMPP clients and servers to "..host.."."); print(" For more information see: https://prosody.im/doc/dns"); end end local all_modules = set.new(config["*"].modules_enabled); local all_options = set.new(it.to_array(it.keys(config["*"]))); for host in enabled_hosts() do all_options:include(set.new(it.to_array(it.keys(config[host])))); all_modules:include(set.new(config[host].modules_enabled)); end for mod in all_modules do if mod:match("^mod_") then print(""); print(" Modules in modules_enabled should not have the 'mod_' prefix included."); print(" Change '"..mod.."' to '"..mod:match("^mod_(.*)").."'."); elseif mod:match("^auth_") then print(""); print(" Authentication modules should not be added to modules_enabled,"); print(" but be specified in the 'authentication' option."); print(" Remove '"..mod.."' from modules_enabled and instead add"); print(" authentication = '"..mod:match("^auth_(.*)").."'"); print(" For more information see https://prosody.im/doc/authentication"); elseif mod:match("^storage_") then print(""); print(" storage modules should not be added to modules_enabled,"); print(" but be specified in the 'storage' option."); print(" Remove '"..mod.."' from modules_enabled and instead add"); print(" storage = '"..mod:match("^storage_(.*)").."'"); print(" For more information see https://prosody.im/doc/storage"); end end if all_modules:contains("vcard") and all_modules:contains("vcard_legacy") then print(""); print(" Both mod_vcard_legacy and mod_vcard are enabled but they conflict"); print(" with each other. Remove one."); end if all_modules:contains("pep") and all_modules:contains("pep_simple") then print(""); print(" Both mod_pep_simple and mod_pep are enabled but they conflict"); print(" with each other. Remove one."); end for host, host_config in pairs(config) do --luacheck: ignore 213/host if type(rawget(host_config, "storage")) == "string" and rawget(host_config, "default_storage") then print(""); print(" The 'default_storage' option is not needed if 'storage' is set to a string."); break; end end local require_encryption = set.intersection(all_options, set.new({ "require_encryption", "c2s_require_encryption", "s2s_require_encryption" })):empty(); local ssl = dependencies.softreq"ssl"; if not ssl then if not require_encryption then print(""); print(" You require encryption but LuaSec is not available."); print(" Connections will fail."); ok = false; end elseif not ssl.loadcertificate then if all_options:contains("s2s_secure_auth") then print(""); print(" You have set s2s_secure_auth but your version of LuaSec does "); print(" not support certificate validation, so all s2s connections will"); print(" fail."); ok = false; elseif all_options:contains("s2s_secure_domains") then local secure_domains = set.new(); for host in enabled_hosts() do if api(host):get_option_boolean("s2s_secure_auth") then secure_domains:add("*"); else secure_domains:include(api(host):get_option_set("s2s_secure_domains", {})); end end if not secure_domains:empty() then print(""); print(" You have set s2s_secure_domains but your version of LuaSec does "); print(" not support certificate validation, so s2s connections to/from "); print(" these domains will fail."); ok = false; end end elseif require_encryption and not all_modules:contains("tls") then print(""); print(" You require encryption but mod_tls is not enabled."); print(" Connections will fail."); ok = false; end do local registration_enabled_hosts = {}; for host in enabled_hosts() do local host_modules, component = modulemanager.get_modules_for_host(host); local hostapi = api(host); local allow_registration = hostapi:get_option_boolean("allow_registration", false); local mod_register = host_modules:contains("register"); local mod_register_ibr = host_modules:contains("register_ibr"); local mod_invites_register = host_modules:contains("invites_register"); local registration_invite_only = hostapi:get_option_boolean("registration_invite_only", true); local is_vhost = not component; if is_vhost and (mod_register_ibr or (mod_register and allow_registration)) and not (mod_invites_register and registration_invite_only) then table.insert(registration_enabled_hosts, host); end end if #registration_enabled_hosts > 0 then table.sort(registration_enabled_hosts); print(""); print(" Public registration is enabled on:"); print(" "..table.concat(registration_enabled_hosts, ", ")); print(""); print(" If this is intentional, review our guidelines on running a public server"); print(" at https://prosody.im/doc/public_servers - otherwise, consider switching to"); print(" invite-based registration, which is more secure."); end end do local orphan_components = {}; local referenced_components = set.new(); local enabled_hosts_set = set.new(); for host in it.filter("*", pairs(configmanager.getconfig())) do local hostapi = api(host); if hostapi:get_option_boolean("enabled", true) then enabled_hosts_set:add(host); for _, disco_item in ipairs(hostapi:get_option_array("disco_items", {})) do referenced_components:add(disco_item[1]); end end end for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do local is_component = not not select(2, modulemanager.get_modules_for_host(host)); if is_component then local parent_domain = host:match("^[^.]+%.(.+)$"); local is_orphan = not (enabled_hosts_set:contains(parent_domain) or referenced_components:contains(host)); if is_orphan then table.insert(orphan_components, host); end end end if #orphan_components > 0 then table.sort(orphan_components); print(""); print(" Your configuration contains the following unreferenced components:\n"); print(" "..table.concat(orphan_components, "\n ")); print(""); print(" Clients may not be able to discover these services because they are not linked to"); print(" any VirtualHost. They are automatically linked if they are direct subdomains of a"); print(" VirtualHost. Alternatively, you can explicitly link them using the disco_items option."); print(" For more information see https://prosody.im/doc/modules/mod_disco#items"); end end print("Done.\n"); end if not what or what == "dns" then local dns = require "net.dns"; pcall(function () local unbound = require"net.unbound"; dns = unbound.dns; end) local idna = require "util.encodings".idna; local ip = require "util.ip"; local global = api("*"); local c2s_ports = global:get_option_set("c2s_ports", {5222}); local s2s_ports = global:get_option_set("s2s_ports", {5269}); local c2s_tls_ports = global:get_option_set("c2s_direct_tls_ports", {}); local s2s_tls_ports = global:get_option_set("s2s_direct_tls_ports", {}); local global_enabled = set.new(); for host in enabled_hosts() do global_enabled:include(modulemanager.get_modules_for_host(host)); end if global_enabled:contains("net_multiplex") then local multiplex_ports = global:get_option_set("ports", {}); local multiplex_tls_ports = global:get_option_set("ssl_ports", {}); if not multiplex_ports:empty() then c2s_ports = c2s_ports + multiplex_ports; s2s_ports = s2s_ports + multiplex_ports; end if not multiplex_tls_ports:empty() then c2s_tls_ports = c2s_tls_ports + multiplex_tls_ports; s2s_tls_ports = s2s_tls_ports + multiplex_tls_ports; end end local c2s_srv_required, s2s_srv_required, c2s_tls_srv_required, s2s_tls_srv_required; if not c2s_ports:contains(5222) then c2s_srv_required = true; end if not s2s_ports:contains(5269) then s2s_srv_required = true; end if not c2s_tls_ports:empty() then c2s_tls_srv_required = true; end if not s2s_tls_ports:empty() then s2s_tls_srv_required = true; end local problem_hosts = set.new(); local external_addresses, internal_addresses = set.new(), set.new(); local fqdn = socket.dns.tohostname(socket.dns.gethostname()); if fqdn then local fqdn_a = idna.to_ascii(fqdn); if fqdn_a then local res = dns.lookup(fqdn_a, "A"); if res then for _, record in ipairs(res) do external_addresses:add(record.a); end end end if fqdn_a then local res = dns.lookup(fqdn_a, "AAAA"); if res then for _, record in ipairs(res) do external_addresses:add(record.aaaa); end end end end local local_addresses = require"util.net".local_addresses() or {}; for addr in it.values(local_addresses) do if not ip.new_ip(addr).private then external_addresses:add(addr); else internal_addresses:add(addr); end end -- Allow admin to specify additional (e.g. undiscoverable) IP addresses in the config for _, address in ipairs(global:get_option_array("external_addresses", {})) do external_addresses:add(address); end if external_addresses:empty() then print(""); print(" Failed to determine the external addresses of this server. Checks may be inaccurate."); print(" If you know the correct external addresses you can specify them in the config like:") print(" external_addresses = { \"192.0.2.34\", \"2001:db8::abcd:1234\" }") c2s_srv_required, s2s_srv_required = true, true; end local v6_supported = not not socket.tcp6; local use_ipv4 = global:get_option_boolean("use_ipv4", true); local use_ipv6 = global:get_option_boolean("use_ipv6", true); local function trim_dns_name(n) return (n:gsub("%.$", "")); end local unknown_addresses = set.new(); for jid in enabled_hosts() do local all_targets_ok, some_targets_ok = true, false; local node, host = jid_split(jid); local modules, component_module = modulemanager.get_modules_for_host(host); if component_module then modules:add(component_module); end -- TODO Refactor these DNS SRV checks since they are very similar -- FIXME Suggest concrete actionable steps to correct issues so that -- users don't have to copy-paste the message into the support chat and -- ask what to do about it. local is_component = not not component_module; print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."..."); if node then print("Only the domain part ("..host..") is used in DNS.") end local target_hosts = set.new(); if modules:contains("c2s") then local res = dns.lookup("_xmpp-client._tcp."..idna.to_ascii(host)..".", "SRV"); if res and #res > 0 then for _, record in ipairs(res) do if record.srv.target == "." then -- TODO is this an error if mod_c2s is enabled? print(" 'xmpp-client' service disabled by pointing to '.'"); -- FIXME Explain better what this is break; end local target = trim_dns_name(record.srv.target); target_hosts:add(target); if not c2s_ports:contains(record.srv.port) then print(" SRV target "..target.." contains unknown client port: "..record.srv.port); end end else if c2s_srv_required then print(" No _xmpp-client SRV record found for "..host..", but it looks like you need one."); all_targets_ok = false; else target_hosts:add(host); end end end if modules:contains("c2s") then local res = dns.lookup("_xmpps-client._tcp."..idna.to_ascii(host)..".", "SRV"); if res and #res > 0 then for _, record in ipairs(res) do if record.srv.target == "." then -- TODO is this an error if mod_c2s is enabled? print(" 'xmpps-client' service disabled by pointing to '.'"); -- FIXME Explain better what this is break; end local target = trim_dns_name(record.srv.target); target_hosts:add(target); if not c2s_tls_ports:contains(record.srv.port) then print(" SRV target "..target.." contains unknown Direct TLS client port: "..record.srv.port); end end elseif c2s_tls_srv_required then print(" No _xmpps-client SRV record found for "..host..", but it looks like you need one."); all_targets_ok = false; end end if modules:contains("s2s") then local res = dns.lookup("_xmpp-server._tcp."..idna.to_ascii(host)..".", "SRV"); if res and #res > 0 then for _, record in ipairs(res) do if record.srv.target == "." then -- TODO Is this an error if mod_s2s is enabled? print(" 'xmpp-server' service disabled by pointing to '.'"); -- FIXME Explain better what this is break; end local target = trim_dns_name(record.srv.target); target_hosts:add(target); if not s2s_ports:contains(record.srv.port) then print(" SRV target "..target.." contains unknown server port: "..record.srv.port); end end else if s2s_srv_required then print(" No _xmpp-server SRV record found for "..host..", but it looks like you need one."); all_targets_ok = false; else target_hosts:add(host); end end end if modules:contains("s2s") then local res = dns.lookup("_xmpps-server._tcp."..idna.to_ascii(host)..".", "SRV"); if res and #res > 0 then for _, record in ipairs(res) do if record.srv.target == "." then -- TODO is this an error if mod_s2s is enabled? print(" 'xmpps-server' service disabled by pointing to '.'"); -- FIXME Explain better what this is break; end local target = trim_dns_name(record.srv.target); target_hosts:add(target); if not s2s_tls_ports:contains(record.srv.port) then print(" SRV target "..target.." contains unknown Direct TLS server port: "..record.srv.port); end end elseif s2s_tls_srv_required then print(" No _xmpps-server SRV record found for "..host..", but it looks like you need one."); all_targets_ok = false; end end if target_hosts:empty() then target_hosts:add(host); end if target_hosts:contains("localhost") then print(" Target 'localhost' cannot be accessed from other servers"); target_hosts:remove("localhost"); end local function check_address(target) local A, AAAA = dns.lookup(idna.to_ascii(target), "A"), dns.lookup(idna.to_ascii(target), "AAAA"); local prob = {}; if use_ipv4 and not (A and #A > 0) then table.insert(prob, "A"); end if use_ipv6 and not (AAAA and #AAAA > 0) then table.insert(prob, "AAAA"); end return prob; end if modules:contains("proxy65") then local proxy65_target = api(host):get_option_string("proxy65_address", host); if type(proxy65_target) == "string" then local prob = check_address(proxy65_target); if #prob > 0 then print(" File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/") .." record. Create one or set 'proxy65_address' to the correct host/IP."); end else print(" proxy65_address for "..host.." should be set to a string, unable to perform DNS check"); end end local known_http_modules = set.new { "bosh"; "http_files"; "http_file_share"; "http_openmetrics"; "websocket" }; local function contains_match(hayset, needle) for member in hayset do if member:find(needle) then return true end end end if modules:contains("http") or not set.intersection(modules, known_http_modules):empty() or contains_match(modules, "^http_") or contains_match(modules, "_web$") then local http_host = api(host):get_option_string("http_host", host); local http_internal_host = http_host; local http_url = api(host):get_option_string("http_external_url"); if http_url then local url_parse = require "socket.url".parse; local external_url_parts = url_parse(http_url); if external_url_parts then http_host = external_url_parts.host; else print(" The 'http_external_url' setting is not a valid URL"); end end local prob = check_address(http_host); if #prob > 1 then print(" HTTP service " .. http_host .. " has no " .. table.concat(prob, "/") .. " record. Create one or change " .. (http_url and "'http_external_url'" or "'http_host'").." to the correct host."); end if http_host ~= http_internal_host then print(" Ensure the reverse proxy sets the HTTP Host header to '" .. http_internal_host .. "'"); end end if not use_ipv4 and not use_ipv6 then print(" Both IPv6 and IPv4 are disabled, Prosody will not listen on any ports"); print(" nor be able to connect to any remote servers."); all_targets_ok = false; end for target_host in target_hosts do local host_ok_v4, host_ok_v6; do local res = dns.lookup(idna.to_ascii(target_host), "A"); if res then for _, record in ipairs(res) do if external_addresses:contains(record.a) then some_targets_ok = true; host_ok_v4 = true; elseif internal_addresses:contains(record.a) then host_ok_v4 = true; some_targets_ok = true; print(" "..target_host.." A record points to internal address, external connections might fail"); else print(" "..target_host.." A record points to unknown address "..record.a); unknown_addresses:add(record.a); all_targets_ok = false; end end end end do local res = dns.lookup(idna.to_ascii(target_host), "AAAA"); if res then for _, record in ipairs(res) do if external_addresses:contains(record.aaaa) then some_targets_ok = true; host_ok_v6 = true; elseif internal_addresses:contains(record.aaaa) then host_ok_v6 = true; some_targets_ok = true; print(" "..target_host.." AAAA record points to internal address, external connections might fail"); else print(" "..target_host.." AAAA record points to unknown address "..record.aaaa); unknown_addresses:add(record.aaaa); all_targets_ok = false; end end end end if host_ok_v4 and not use_ipv4 then print(" Host "..target_host.." does seem to resolve to this server but IPv4 has been disabled"); all_targets_ok = false; end if host_ok_v6 and not use_ipv6 then print(" Host "..target_host.." does seem to resolve to this server but IPv6 has been disabled"); all_targets_ok = false; end local bad_protos = {} if use_ipv4 and not host_ok_v4 then table.insert(bad_protos, "IPv4"); end if use_ipv6 and not host_ok_v6 then table.insert(bad_protos, "IPv6"); end if #bad_protos > 0 then print(" Host "..target_host.." does not seem to resolve to this server ("..table.concat(bad_protos, "/")..")"); end if host_ok_v6 and not v6_supported then print(" Host "..target_host.." has AAAA records, but your version of LuaSocket does not support IPv6."); print(" Please see https://prosody.im/doc/ipv6 for more information."); elseif host_ok_v6 and not use_ipv6 then print(" Host "..target_host.." has AAAA records, but IPv6 is disabled."); -- TODO Tell them to drop the AAAA records or enable IPv6? print(" Please see https://prosody.im/doc/ipv6 for more information."); end end if not all_targets_ok then print(" "..(some_targets_ok and "Only some" or "No").." targets for "..host.." appear to resolve to this server."); if is_component then print(" DNS records are necessary if you want users on other servers to access this component."); end problem_hosts:add(host); end print(""); end if not problem_hosts:empty() then if not unknown_addresses:empty() then print(""); print("Some of your DNS records point to unknown IP addresses. This may be expected if your server"); print("is behind a NAT or proxy. The unrecognized addresses were:"); print(""); print(" Unrecognized: "..tostring(unknown_addresses)); print(""); print("The addresses we found on this system are:"); print(""); print(" Internal: "..tostring(internal_addresses)); print(" External: "..tostring(external_addresses)); print("") print("If the list of external external addresses is incorrect you can specify correct addresses in the config:") print(" external_addresses = { \"192.0.2.34\", \"2001:db8::abcd:1234\" }") end print(""); print("For more information about DNS configuration please see https://prosody.im/doc/dns"); print(""); ok = false; end end if not what or what == "certs" then local cert_ok; print"Checking certificates..." local x509_verify_identity = require"util.x509".verify_identity; local create_context = require "core.certmanager".create_context; local ssl = dependencies.softreq"ssl"; -- local datetime_parse = require"util.datetime".parse_x509; local load_cert = ssl and ssl.loadcertificate; -- or ssl.cert_from_pem if not ssl then print("LuaSec not available, can't perform certificate checks") if what == "certs" then cert_ok = false end elseif not load_cert then print("This version of LuaSec (" .. ssl._VERSION .. ") does not support certificate checking"); cert_ok = false else for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do print("Checking certificate for "..host); -- First, let's find out what certificate this host uses. local host_ssl_config = configmanager.rawget(host, "ssl") or configmanager.rawget(host:match("%.(.*)"), "ssl"); local global_ssl_config = configmanager.rawget("*", "ssl"); local ok, err, ssl_config = create_context(host, "server", host_ssl_config, global_ssl_config); if not ok then print(" Error: "..err); cert_ok = false elseif not ssl_config.certificate then print(" No 'certificate' found for "..host) cert_ok = false elseif not ssl_config.key then print(" No 'key' found for "..host) cert_ok = false else local key, err = io.open(ssl_config.key); -- Permissions check only if not key then print(" Could not open "..ssl_config.key..": "..err); cert_ok = false else key:close(); end local cert_fh, err = io.open(ssl_config.certificate); -- Load the file. if not cert_fh then print(" Could not open "..ssl_config.certificate..": "..err); cert_ok = false else print(" Certificate: "..ssl_config.certificate) local cert = load_cert(cert_fh:read"*a"); cert_fh:close(); if not cert:validat(os.time()) then print(" Certificate has expired.") cert_ok = false elseif not cert:validat(os.time() + 86400) then print(" Certificate expires within one day.") cert_ok = false elseif not cert:validat(os.time() + 86400*7) then print(" Certificate expires within one week.") elseif not cert:validat(os.time() + 86400*31) then print(" Certificate expires within one month.") end if select(2, modulemanager.get_modules_for_host(host)) == nil and not x509_verify_identity(host, "_xmpp-client", cert) then print(" Not valid for client connections to "..host..".") cert_ok = false end if (not (api(host):get_option_boolean("anonymous_login", false) or api(host):get_option_string("authentication", "internal_hashed") == "anonymous")) and not x509_verify_identity(host, "_xmpp-server", cert) then print(" Not valid for server-to-server connections to "..host..".") cert_ok = false end end end end end if cert_ok == false then print("") print("For more information about certificates please see https://prosody.im/doc/certificates"); ok = false end print("") end -- intentionally not doing this by default if what == "connectivity" then local _, prosody_is_running = is_prosody_running(); if api("*"):get_option_string("pidfile") and not prosody_is_running then print("Prosody does not appear to be running, which is required for this test."); print("Start it and then try again."); return 1; end local checker = "observe.jabber.network"; local probe_instance; local probe_modules = { ["xmpp-client"] = "c2s_normal_auth"; ["xmpp-server"] = "s2s_normal"; ["xmpps-client"] = nil; -- TODO ["xmpps-server"] = nil; -- TODO }; local probe_settings = api("*"):get_option_string("connectivity_probe"); if type(probe_settings) == "string" then probe_instance = probe_settings; elseif type(probe_settings) == "table" and type(probe_settings.url) == "string" then probe_instance = probe_settings.url; if type(probe_settings.modules) == "table" then probe_modules = probe_settings.modules; end elseif probe_settings ~= nil then print("The 'connectivity_probe' setting not understood."); print("Expected an URL or a table with 'url' and 'modules' fields"); print("See https://prosody.im/doc/prosodyctl#check for more information."); -- FIXME return 1; end local check_api; if probe_instance then local parsed_url = socket_url.parse(probe_instance); if not parsed_url then print(("'connectivity_probe' is not a valid URL: %q"):format(probe_instance)); print("Set it to the URL of an XMPP Blackbox Exporter instance and try again"); return 1; end checker = parsed_url.host; function check_api(protocol, host) local target = socket_url.build({scheme="xmpp",path=host}); local probe_module = probe_modules[protocol]; if not probe_module then return nil, "Checking protocol '"..protocol.."' is currently unsupported"; end return check_probe(probe_instance, probe_module, target); end else check_api = check_ojn; end for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do local modules, component_module = modulemanager.get_modules_for_host(host); if component_module then modules:add(component_module) end print("Checking external connectivity for "..host.." via "..checker) local function check_connectivity(protocol) local success, err = check_api(protocol, host); if not success and err ~= nil then print((" %s: Failed to request check at API: %s"):format(protocol, err)) elseif success then print((" %s: Works"):format(protocol)) else print((" %s: Check service failed to establish (secure) connection"):format(protocol)) ok = false end end if modules:contains("c2s") then check_connectivity("xmpp-client") if not api("*"):get_option_set("c2s_direct_tls_ports", {}):empty() then check_connectivity("xmpps-client"); end end if modules:contains("s2s") then check_connectivity("xmpp-server") if not api("*"):get_option_set("s2s_direct_tls_ports", {}):empty() then check_connectivity("xmpps-server"); end end print() end print("Note: The connectivity check only checks the reachability of the domain.") print("Note: It does not ensure that the check actually reaches this specific prosody instance.") end if not what or what == "turn" then local turn_enabled_hosts = {}; local turn_services = {}; for host in enabled_hosts() do local has_external_turn = modulemanager.get_modules_for_host(host):contains("turn_external"); if has_external_turn then local hostapi = api(host); table.insert(turn_enabled_hosts, host); local turn_host = hostapi:get_option_string("turn_external_host", host); local turn_port = hostapi:get_option_number("turn_external_port", 3478); local turn_secret = hostapi:get_option_string("turn_external_secret"); if not turn_secret then print("Error: Your configuration is missing a turn_external_secret for "..host); print("Error: TURN will not be advertised for this host."); ok = false; else local turn_id = ("%s:%d"):format(turn_host, turn_port); if turn_services[turn_id] and turn_services[turn_id].secret ~= turn_secret then print("Error: Your configuration contains multiple differing secrets"); print(" for the TURN service at "..turn_id.." - we will only test one."); elseif not turn_services[turn_id] then turn_services[turn_id] = { host = turn_host; port = turn_port; secret = turn_secret; }; end end end end if what == "turn" then local count = it.count(pairs(turn_services)); if count == 0 then print("Error: Unable to find any TURN services configured. Enable mod_turn_external!"); ok = false; else print("Identified "..tostring(count).." TURN services."); print(""); end end for turn_id, turn_service in pairs(turn_services) do print("Testing TURN service "..turn_id.."..."); local result = check_turn_service(turn_service, opts.ping); if #result.warnings > 0 then print(("%d warnings:\n"):format(#result.warnings)); print(" "..table.concat(result.warnings, "\n ")); print(""); end if opts.verbose then if result.external_ip then print(("External IP: %s"):format(result.external_ip.address)); end if result.relayed_addresses then for i, relayed_address in ipairs(result.relayed_addresses) do print(("Relayed address %d: %s:%d"):format(i, relayed_address.address, relayed_address.port)); end end if result.external_ip_pong then print(("TURN external address: %s:%d"):format(result.external_ip_pong.address, result.external_ip_pong.port)); end end if result.error then print("Error: "..result.error.."\n"); ok = false; else print("Success!\n"); end end end if not ok then print("Problems found, see above."); else print("All checks passed, congratulations!"); end return ok and 0 or 2; end return { check = check; }; prosody-0.12.4/util/prosodyctl/PaxHeaders/shell.lua0000644000000000000000000000012014475665104017355 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.780764089 prosody-0.12.4/util/prosodyctl/shell.lua0000644000175000017500000001041514475665104021563 0ustar00prosodyprosody00000000000000local config = require "core.configmanager"; local server = require "net.server"; local st = require "util.stanza"; local path = require "util.paths"; local parse_args = require "util.argparse".parse; local unpack = table.unpack or _G.unpack; local have_readline, readline = pcall(require, "readline"); local adminstream = require "util.adminstream"; if have_readline then readline.set_readline_name("prosody"); readline.set_options({ histfile = path.join(prosody.paths.data, ".shell_history"); ignoredups = true; }); end local function read_line(prompt_string) if have_readline then return readline.readline(prompt_string); else io.write(prompt_string); return io.read("*line"); end end local function send_line(client, line) client.send(st.stanza("repl-input"):text(line)); end local function repl(client) local line = read_line(client.prompt_string or "prosody> "); if not line or line == "quit" or line == "exit" or line == "bye" then if not line then print(""); end if have_readline then readline.save_history(); end os.exit(0, true); end send_line(client, line); end local function printbanner() local banner = config.get("*", "console_banner"); if banner then return print(banner); end print([[ ____ \ / _ | _ \ _ __ ___ ___ _-_ __| |_ _ | |_) | '__/ _ \/ __|/ _ \ / _` | | | | | __/| | | (_) \__ \ |_| | (_| | |_| | |_| |_| \___/|___/\___/ \__,_|\__, | A study in simplicity |___/ ]]); print("Welcome to the Prosody administration console. For a list of commands, type: help"); print("You may find more help on using this console in our online documentation at "); print("https://prosody.im/doc/console\n"); end local function start(arg) --luacheck: ignore 212/arg local client = adminstream.client(); local opts, err, where = parse_args(arg); if not opts then if err == "param-not-found" then print("Unknown command-line option: "..tostring(where)); elseif err == "missing-value" then print("Expected a value to follow command-line option: "..where); end os.exit(1); end if arg[1] then if arg[2] then -- prosodyctl shell module reload foo bar.com --> module:reload("foo", "bar.com") -- COMPAT Lua 5.1 doesn't have the separator argument to string.rep arg[1] = string.format("%s:%s("..string.rep("%q, ", #arg-2):sub(1, -3)..")", unpack(arg)); end client.events.add_handler("connected", function() client.send(st.stanza("repl-input"):text(arg[1])); return true; end, 1); local errors = 0; -- TODO This is weird, but works for now. client.events.add_handler("received", function(stanza) if stanza.name == "repl-output" or stanza.name == "repl-result" then if stanza.attr.type == "error" then errors = errors + 1; io.stderr:write(stanza:get_text(), "\n"); else print(stanza:get_text()); end end if stanza.name == "repl-result" then os.exit(errors); end return true; end, 1); end client.events.add_handler("connected", function () if not opts.quiet then printbanner(); end repl(client); end); client.events.add_handler("disconnected", function () print("--- session closed ---"); os.exit(0, true); end); client.events.add_handler("received", function (stanza) if stanza.name == "repl-output" or stanza.name == "repl-result" then local result_prefix = stanza.attr.type == "error" and "!" or "|"; print(result_prefix.." "..stanza:get_text()); end if stanza.name == "repl-result" then repl(client); end end); client.prompt_string = config.get("*", "admin_shell_prompt"); local socket_path = path.resolve_relative_path(prosody.paths.data, opts.socket or config.get("*", "admin_socket") or "prosody.sock"); local conn = adminstream.connection(socket_path, client.listeners); local ok, err = conn:connect(); if not ok then if err == "no unix socket support" then print("** LuaSocket unix socket support not available or incompatible, ensure your"); print("** version is up to date."); else print("** Unable to connect to server - is it running? Is mod_admin_shell enabled?"); print("** Connection error: "..err); end os.exit(1); end server.loop(); end return { shell = start; }; prosody-0.12.4/util/PaxHeaders/prosodyctl.lua0000644000000000000000000000012014475665104016246 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.776764128 prosody-0.12.4/util/prosodyctl.lua0000644000175000017500000001536414475665104020464 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local config = require "core.configmanager"; local encodings = require "util.encodings"; local stringprep = encodings.stringprep; local storagemanager = require "core.storagemanager"; local usermanager = require "core.usermanager"; local interpolation = require "util.interpolation"; local signal = require "util.signal"; local set = require "util.set"; local lfs = require "lfs"; local type = type; local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep; local io, os = io, os; local print = print; local tonumber = tonumber; local _G = _G; local prosody = prosody; local error_messages = setmetatable({ ["invalid-username"] = "The given username is invalid in a Jabber ID"; ["invalid-hostname"] = "The given hostname is invalid"; ["no-password"] = "No password was supplied"; ["no-such-user"] = "The given user does not exist on the server"; ["no-such-host"] = "The given hostname does not exist in the config"; ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?"; ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see https://prosody.im/doc/prosodyctl#pidfile for help"; ["invalid-pidfile"] = "The 'pidfile' option in the configuration file is not a string, see https://prosody.im/doc/prosodyctl#pidfile for help"; ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see https://prosody.im/doc/prosodyctl for more info"; ["no-such-method"] = "This module has no commands"; ["not-running"] = "Prosody is not running"; }, { __index = function (_,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end }); -- UI helpers local show_message = require "util.human.io".printf; local function show_usage(usage, desc) print("Usage: ".._G.arg[0].." "..usage); if desc then print(" "..desc); end end local function show_module_configuration_help(mod_name) print("Done.") print("If you installed a prosody plugin, don't forget to add its name under the 'modules_enabled' section inside your configuration file.") print("Depending on the module, there might be further configuration steps required.") print("") print("More info about: ") print(" modules_enabled: https://prosody.im/doc/modules_enabled") print(" "..mod_name..": https://modules.prosody.im/"..mod_name..".html") end -- Server control local function adduser(params) local user, host, password = nodeprep(params.user, true), nameprep(params.host), params.password; if not user then return false, "invalid-username"; elseif not host then return false, "invalid-hostname"; end local host_session = prosody.hosts[host]; if not host_session then return false, "no-such-host"; end storagemanager.initialize_host(host); local provider = host_session.users; if not(provider) or provider.name == "null" then usermanager.initialize_host(host); end local ok, errmsg = usermanager.create_user(user, password, host); if not ok then return false, errmsg or "creating-user-failed"; end return true; end local function user_exists(params) local user, host = nodeprep(params.user), nameprep(params.host); storagemanager.initialize_host(host); local provider = prosody.hosts[host].users; if not(provider) or provider.name == "null" then usermanager.initialize_host(host); end return usermanager.user_exists(user, host); end local function passwd(params) if not user_exists(params) then return false, "no-such-user"; end return adduser(params); end local function deluser(params) if not user_exists(params) then return false, "no-such-user"; end local user, host = nodeprep(params.user), nameprep(params.host); return usermanager.delete_user(user, host); end local function getpid() local pidfile = config.get("*", "pidfile"); if not pidfile then return false, "no-pidfile"; end if type(pidfile) ~= "string" then return false, "invalid-pidfile"; end pidfile = config.resolve_relative_path(prosody.paths.data, pidfile); local modules_disabled = set.new(config.get("*", "modules_disabled")); if prosody.platform ~= "posix" or modules_disabled:contains("posix") then return false, "no-posix"; end local file, err = io.open(pidfile, "r+"); if not file then return false, "pidfile-read-failed", err; end local locked, err = lfs.lock(file, "w"); -- luacheck: ignore 211/err if locked then file:close(); return false, "pidfile-not-locked"; end local pid = tonumber(file:read("*a")); file:close(); if not pid then return false, "invalid-pid"; end return true, pid; end local function isrunning() local ok, pid, err = getpid(); -- luacheck: ignore 211/err if not ok then if pid == "pidfile-read-failed" or pid == "pidfile-not-locked" then -- Report as not running, since we can't open the pidfile -- (it probably doesn't exist) return true, false; end return ok, pid; end return true, signal.kill(pid, 0) == 0; end local function start(source_dir, lua) lua = lua and lua .. " " or ""; local ok, ret = isrunning(); if not ok then return ok, ret; end if ret then return false, "already-running"; end if not source_dir then os.execute(lua .. "./prosody -D"); else os.execute(lua .. source_dir.."/../../bin/prosody -D"); end return true; end local function stop() local ok, ret = isrunning(); if not ok then return ok, ret; end if not ret then return false, "not-running"; end local ok, pid = getpid() if not ok then return false, pid; end signal.kill(pid, signal.SIGTERM); return true; end local function reload() local ok, ret = isrunning(); if not ok then return ok, ret; end if not ret then return false, "not-running"; end local ok, pid = getpid() if not ok then return false, pid; end signal.kill(pid, signal.SIGHUP); return true; end local render_cli = interpolation.new("%b{}", function (s) return "'"..s:gsub("'","'\\''").."'" end) local function call_luarocks(operation, mod, server) local dir = prosody.paths.installer; local ok, _, code = os.execute(render_cli("luarocks --lua-version={luav} {op} --tree={dir} {server&--server={server}} {mod?}", { dir = dir; op = operation; mod = mod; server = server; luav = _VERSION:match("5%.%d"); })); if type(ok) == "number" then code = ok; end return code; end return { show_message = show_message; show_warning = show_message; show_usage = show_usage; show_module_configuration_help = show_module_configuration_help; adduser = adduser; user_exists = user_exists; passwd = passwd; deluser = deluser; getpid = getpid; isrunning = isrunning; start = start; stop = stop; reload = reload; call_luarocks = call_luarocks; error_messages = error_messages; }; prosody-0.12.4/util/PaxHeaders/pubsub.lua0000644000000000000000000000012014475665104015344 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.780764089 prosody-0.12.4/util/pubsub.lua0000644000175000017500000005553214475665104017563 0ustar00prosodyprosody00000000000000local events = require "util.events"; local cache = require "util.cache"; local errors = require "util.error"; local service_mt = {}; local default_config = { max_items = 256; itemstore = function (config, _) return cache.new(config["max_items"]) end; broadcaster = function () end; subscriber_filter = function (subs) return subs end; itemcheck = function () return true; end; get_affiliation = function () end; normalize_jid = function (jid) return jid; end; capabilities = { outcast = { create = false; publish = false; retract = false; get_nodes = false; subscribe = false; unsubscribe = false; get_subscription = true; get_subscriptions = true; get_items = false; subscribe_other = false; unsubscribe_other = false; get_subscription_other = false; get_subscriptions_other = false; be_subscribed = false; be_unsubscribed = true; set_affiliation = false; }; none = { create = false; publish = false; retract = false; get_nodes = true; subscribe = true; unsubscribe = true; get_subscription = true; get_subscriptions = true; get_items = false; subscribe_other = false; unsubscribe_other = false; get_subscription_other = false; get_subscriptions_other = false; be_subscribed = true; be_unsubscribed = true; set_affiliation = false; }; member = { create = false; publish = false; retract = false; get_nodes = true; subscribe = true; unsubscribe = true; get_subscription = true; get_subscriptions = true; get_items = true; subscribe_other = false; unsubscribe_other = false; get_subscription_other = false; get_subscriptions_other = false; be_subscribed = true; be_unsubscribed = true; set_affiliation = false; }; publisher = { create = false; publish = true; retract = true; get_nodes = true; get_configuration = true; subscribe = true; unsubscribe = true; get_subscription = true; get_subscriptions = true; get_items = true; subscribe_other = false; unsubscribe_other = false; get_subscription_other = false; get_subscriptions_other = false; be_subscribed = true; be_unsubscribed = true; set_affiliation = false; }; owner = { create = true; publish = true; retract = true; delete = true; get_nodes = true; configure = true; get_configuration = true; subscribe = true; unsubscribe = true; get_subscription = true; get_subscriptions = true; get_items = true; subscribe_other = true; unsubscribe_other = true; get_subscription_other = true; get_subscriptions_other = true; be_subscribed = true; be_unsubscribed = true; set_affiliation = true; }; }; }; local default_config_mt = { __index = default_config }; local default_node_config = { ["persist_items"] = true; ["max_items"] = 20; ["access_model"] = "open"; ["publish_model"] = "publishers"; ["send_last_published_item"] = "never"; }; local default_node_config_mt = { __index = default_node_config }; -- Storage helper functions local function load_node_from_store(service, node_name) local node = service.config.nodestore:get(node_name); node.config = setmetatable(node.config or {}, {__index=service.node_defaults}); return node; end local function save_node_to_store(service, node) return service.config.nodestore:set(node.name, { name = node.name; config = node.config; subscribers = node.subscribers; affiliations = node.affiliations; }); end local function delete_node_in_store(service, node_name) return service.config.nodestore:set(node_name, nil); end -- Create and return a new service object local function new(config) config = config or {}; local service = setmetatable({ config = setmetatable(config, default_config_mt); node_defaults = setmetatable(config.node_defaults or {}, default_node_config_mt); affiliations = {}; subscriptions = {}; nodes = {}; data = {}; events = events.new(); }, service_mt); -- Load nodes from storage, if we have a store and it supports iterating over stored items if config.nodestore and config.nodestore.users then for node_name in config.nodestore:users() do local node = load_node_from_store(service, node_name); service.nodes[node_name] = node; if node.config.persist_items then service.data[node_name] = config.itemstore(service.nodes[node_name].config, node_name); end for jid in pairs(service.nodes[node_name].subscribers) do local normal_jid = service.config.normalize_jid(jid); local subs = service.subscriptions[normal_jid]; if subs then if not subs[jid] then subs[jid] = { [node_name] = true }; else subs[jid][node_name] = true; end else service.subscriptions[normal_jid] = { [jid] = { [node_name] = true } }; end end end end return service; end --- Service methods local service = {}; service_mt.__index = service; function service:jids_equal(jid1, jid2) --> boolean local normalize = self.config.normalize_jid; return normalize(jid1) == normalize(jid2); end function service:may(node, actor, action) --> boolean if actor == true then return true; end local node_obj = self.nodes[node]; local node_aff = node_obj and (node_obj.affiliations[actor] or node_obj.affiliations[self.config.normalize_jid(actor)]); local service_aff = self.affiliations[actor] or self.config.get_affiliation(actor, node, action); local default_aff = self:get_default_affiliation(node, actor) or "none"; -- Check if node allows/forbids it local node_capabilities = node_obj and node_obj.capabilities; if node_capabilities then local caps = node_capabilities[node_aff or service_aff or default_aff]; if caps then local can = caps[action]; if can ~= nil then return can; end end end -- Check service-wide capabilities instead local service_capabilities = self.config.capabilities; local caps = service_capabilities[node_aff or service_aff or default_aff]; if caps then local can = caps[action]; if can ~= nil then return can; end end return false; end function service:get_default_affiliation(node, actor) --> affiliation local node_obj = self.nodes[node]; local access_model = node_obj and node_obj.config.access_model or self.node_defaults.access_model; if access_model == "open" then return "member"; elseif access_model == "whitelist" then return "outcast"; end if self.config.access_models then local check = self.config.access_models[access_model]; if check then local aff = check(actor); if aff then return aff; end end end end function service:set_affiliation(node, actor, jid, affiliation) --> ok, err -- Access checking if not self:may(node, actor, "set_affiliation") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end jid = self.config.normalize_jid(jid); local old_affiliation = node_obj.affiliations[jid]; node_obj.affiliations[jid] = affiliation; if self.config.nodestore then -- TODO pass the error from storage to caller eg wrapped in an util.error local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then node_obj.affiliations[jid] = old_affiliation; return ok, "internal-server-error"; end end local _, jid_sub = self:get_subscription(node, true, jid); if not jid_sub and not self:may(node, jid, "be_unsubscribed") then local ok, err = self:add_subscription(node, true, jid); if not ok then return ok, err; end elseif jid_sub and not self:may(node, jid, "be_subscribed") then local ok, err = self:add_subscription(node, true, jid); if not ok then return ok, err; end end return true; end function service:add_subscription(node, actor, jid, options) --> ok, err -- Access checking local cap; if actor == true or jid == actor or self:jids_equal(actor, jid) then cap = "subscribe"; else cap = "subscribe_other"; end if not self:may(node, actor, cap) then return false, "forbidden"; end if not self:may(node, jid, "be_subscribed") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then if not self.config.autocreate_on_subscribe then return false, "item-not-found"; else local ok, err = self:create(node, true); if not ok then return ok, err; end node_obj = self.nodes[node]; end end local old_subscription = node_obj.subscribers[jid]; node_obj.subscribers[jid] = options or true; local normal_jid = self.config.normalize_jid(jid); local subs = self.subscriptions[normal_jid]; if subs then if not subs[jid] then subs[jid] = { [node] = true }; else subs[jid][node] = true; end else self.subscriptions[normal_jid] = { [jid] = { [node] = true } }; end if self.config.nodestore then -- TODO pass the error from storage to caller eg wrapped in an util.error local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then node_obj.subscribers[jid] = old_subscription; self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil; return ok, "internal-server-error"; end end self.events.fire_event("subscription-added", { service = self, node = node, jid = jid, normalized_jid = normal_jid, options = options }); return true; end function service:remove_subscription(node, actor, jid) --> ok, err -- Access checking local cap; if actor == true or jid == actor or self:jids_equal(actor, jid) then cap = "unsubscribe"; else cap = "unsubscribe_other"; end if not self:may(node, actor, cap) then return false, "forbidden"; end if not self:may(node, jid, "be_unsubscribed") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end if not node_obj.subscribers[jid] then return false, "not-subscribed"; end local old_subscription = node_obj.subscribers[jid]; node_obj.subscribers[jid] = nil; local normal_jid = self.config.normalize_jid(jid); local subs = self.subscriptions[normal_jid]; if subs then local jid_subs = subs[jid]; if jid_subs then jid_subs[node] = nil; if next(jid_subs) == nil then subs[jid] = nil; end end if next(subs) == nil then self.subscriptions[normal_jid] = nil; end end if self.config.nodestore then -- TODO pass the error from storage to caller eg wrapped in an util.error local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then node_obj.subscribers[jid] = old_subscription; self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil; return ok, "internal-server-error"; end end self.events.fire_event("subscription-removed", { service = self, node = node, jid = jid, normalized_jid = normal_jid }); return true; end function service:get_subscription(node, actor, jid) --> (true, subscription) or (false, err) -- Access checking local cap; if actor == true or jid == actor or self:jids_equal(actor, jid) then cap = "get_subscription"; else cap = "get_subscription_other"; end if not self:may(node, actor, cap) then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end return true, node_obj.subscribers[jid]; end function service:create(node, actor, options) --> ok, err -- Access checking if not self:may(node, actor, "create") then return false, "forbidden"; end -- if self.nodes[node] then return false, "conflict"; end local config = setmetatable(options or {}, {__index=self.node_defaults}); if self.config.check_node_config then local ok = self.config.check_node_config(node, actor, config); if not ok then return false, "not-acceptable"; end end self.nodes[node] = { name = node; subscribers = {}; config = config; affiliations = {}; }; if self.config.nodestore then -- TODO pass the error from storage to caller eg wrapped in an util.error local ok, err = save_node_to_store(self, self.nodes[node]); -- luacheck: ignore 211/err if not ok then self.nodes[node] = nil; return ok, "internal-server-error"; end end if config.persist_items then self.data[node] = self.config.itemstore(self.nodes[node].config, node); end self.events.fire_event("node-created", { service = self, node = node, actor = actor }); if actor ~= true then local ok, err = self:set_affiliation(node, true, actor, "owner"); if not ok then self.nodes[node] = nil; self.data[node] = nil; return ok, err; end end return true; end function service:delete(node, actor) --> ok, err -- Access checking if not self:may(node, actor, "delete") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end self.nodes[node] = nil; if self.data[node] and self.data[node].clear then self.data[node]:clear(); end self.data[node] = nil; if self.config.nodestore then local ok, err = delete_node_in_store(self, node); if not ok then self.nodes[node] = nil; return ok, err; end end self.events.fire_event("node-deleted", { service = self, node = node, actor = actor }); self:broadcast("delete", node, node_obj.subscribers, nil, actor, node_obj); return true; end -- Used to check that the config of a node is as expected (i.e. 'publish-options') local function check_preconditions(node_config, required_config) if not (node_config and required_config) then return false; end for config_field, value in pairs(required_config) do if node_config[config_field] ~= value then return false, config_field; end end return true; end function service:publish(node, actor, id, item, requested_config) --> ok, err -- Access checking local may_publish = false; if self:may(node, actor, "publish") then may_publish = true; else local node_obj = self.nodes[node]; local publish_model = node_obj and node_obj.config.publish_model; if publish_model == "open" or (publish_model == "subscribers" and node_obj.subscribers[actor]) then may_publish = true; end end if not may_publish then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then if not self.config.autocreate_on_publish then return false, "item-not-found"; end local ok, err = self:create(node, true, requested_config); if not ok then return ok, err; end node_obj = self.nodes[node]; elseif requested_config and not requested_config._defaults_only then -- Check that node has the requested config before we publish local ok, field = check_preconditions(node_obj.config, requested_config); if not ok then local err = errors.new({ type = "cancel", condition = "conflict", text = "Field does not match: "..field; }); err.pubsub_condition = "precondition-not-met"; return false, err; end end if not self.config.itemcheck(item) then return nil, "invalid-item"; end if node_obj.config.persist_items then if not self.data[node] then self.data[node] = self.config.itemstore(self.nodes[node].config, node); end local ok = self.data[node]:set(id, item); if not ok then return nil, "internal-server-error"; end if type(ok) == "string" then id = ok; end end local event_data = { service = self, node = node, actor = actor, id = id, item = item }; self.events.fire_event("item-published/"..node, event_data); self.events.fire_event("item-published", event_data); self:broadcast("items", node, node_obj.subscribers, item, actor, node_obj); return true; end function service:broadcast(event, node, subscribers, item, actor, node_obj) subscribers = self.config.subscriber_filter(subscribers, node, event); return self.config.broadcaster(event, node, subscribers, item, actor, node_obj, self); end function service:retract(node, actor, id, retract) --> ok, err -- Access checking if not self:may(node, actor, "retract") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end if self.data[node] then if not self.data[node]:get(id) then return false, "item-not-found"; end local ok = self.data[node]:set(id, nil); if not ok then return nil, "internal-server-error"; end end self.events.fire_event("item-retracted", { service = self, node = node, actor = actor, id = id }); if retract then self:broadcast("retract", node, node_obj.subscribers, retract, actor, node_obj); end return true end function service:purge(node, actor, notify) --> ok, err -- Access checking if not self:may(node, actor, "retract") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end if self.data[node] then if self.data[node].clear then self.data[node]:clear() else self.data[node] = self.config.itemstore(self.nodes[node].config, node); end end self.events.fire_event("node-purged", { service = self, node = node, actor = actor }); if notify then self:broadcast("purge", node, node_obj.subscribers, nil, actor, node_obj); end return true end function service:get_items(node, actor, ids, resultspec) --> (true, { id, [id] = node }) or (false, err) -- Access checking if not self:may(node, actor, "get_items") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end if not self.data[node] then -- Disabled rather than unsupported, but close enough. return false, "persistent-items-unsupported"; end if type(ids) == "string" then -- COMPAT see #1305 ids = { ids }; end local data = {}; local limit = resultspec and resultspec.max; if ids then for _, key in ipairs(ids) do local value = self.data[node]:get(key); if value then data[#data+1] = key; data[key] = value; -- Limits and ids seem like a problematic combination. if limit and #data >= limit then break end end end else for key, value in self.data[node]:items() do data[#data+1] = key; data[key] = value; if limit and #data >= limit then break end end end return true, data; end function service:get_last_item(node, actor) --> (true, id, node) or (false, err) -- Access checking if not self:may(node, actor, "get_items") then return false, "forbidden"; end -- -- Check node exists if not self.nodes[node] then return false, "item-not-found"; end if not self.data[node] then -- FIXME Should this be a success or failure? return true, nil; end -- Returns success, id, item return true, self.data[node]:head(); end function service:get_nodes(actor) --> (true, map) or (false, err) -- Access checking if not self:may(nil, actor, "get_nodes") then return false, "forbidden"; end -- return true, self.nodes; end local function flatten_subscriptions(ret, serv, subs, node, node_obj) for subscribed_jid, subscribed_nodes in pairs(subs) do if node then -- Return only subscriptions to this node if subscribed_nodes[node] then ret[#ret+1] = { node = node; jid = subscribed_jid; subscription = node_obj.subscribers[subscribed_jid]; }; end else -- Return subscriptions to all nodes local nodes = serv.nodes; for subscribed_node in pairs(subscribed_nodes) do ret[#ret+1] = { node = subscribed_node; jid = subscribed_jid; subscription = nodes[subscribed_node].subscribers[subscribed_jid]; }; end end end end function service:get_subscriptions(node, actor, jid) --> (true, array) or (false, err) -- Access checking local cap; if actor == true or jid == actor or self:jids_equal(actor, jid) then cap = "get_subscriptions"; else cap = "get_subscriptions_other"; end if not self:may(node, actor, cap) then return false, "forbidden"; end -- local node_obj; if node then node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end end local ret = {}; if jid == nil then for _, subs in pairs(self.subscriptions) do flatten_subscriptions(ret, self, subs, node, node_obj) end return true, ret; end local normal_jid = self.config.normalize_jid(jid); local subs = self.subscriptions[normal_jid]; -- We return the subscription object from the node to save -- a get_subscription() call for each node. if subs then flatten_subscriptions(ret, self, subs, node, node_obj) end return true, ret; end -- Access models only affect 'none' affiliation caps, service/default access level... function service:set_node_capabilities(node, actor, capabilities) --> ok, err -- Access checking if not self:may(node, actor, "configure") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end node_obj.capabilities = capabilities; return true; end function service:set_node_config(node, actor, new_config) --> ok, err if not self:may(node, actor, "configure") then return false, "forbidden"; end local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end setmetatable(new_config, {__index=self.node_defaults}) if self.config.check_node_config then local ok = self.config.check_node_config(node, actor, new_config); if not ok then return false, "not-acceptable"; end end local old_config = node_obj.config; node_obj.config = new_config; if self.config.nodestore then -- TODO pass the error from storage to caller eg wrapped in an util.error local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then node_obj.config = old_config; return ok, "internal-server-error"; end end if old_config["access_model"] ~= node_obj.config["access_model"] then for subscriber in pairs(node_obj.subscribers) do if not self:may(node, subscriber, "be_subscribed") then local ok, err = self:remove_subscription(node, true, subscriber); if not ok then node_obj.config = old_config; return ok, err; end end end end if old_config["persist_items"] ~= node_obj.config["persist_items"] then if node_obj.config["persist_items"] then self.data[node] = self.config.itemstore(self.nodes[node].config, node); elseif self.data[node] then if self.data[node].clear then self.data[node]:clear() end self.data[node] = nil; end elseif old_config["max_items"] ~= node_obj.config["max_items"] then if self.data[node] then local max_items = self.nodes[node].config["max_items"]; if max_items == "max" then max_items = self.config.max_items; end self.data[node]:resize(max_items); end end return true; end function service:get_node_config(node, actor) --> (true, config) or (false, err) if not self:may(node, actor, "get_configuration") then return false, "forbidden"; end local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end local config_table = {}; for k, v in pairs(default_node_config) do config_table[k] = v; end for k, v in pairs(self.node_defaults) do config_table[k] = v; end for k, v in pairs(node_obj.config) do config_table[k] = v; end return true, config_table; end return { new = new; }; prosody-0.12.4/util/PaxHeaders/queue.lua0000644000000000000000000000012014475665104015170 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.780764089 prosody-0.12.4/util/queue.lua0000644000175000017500000000353214475665104017400 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2015 Matthew Wild -- Copyright (C) 2008-2015 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- Small ringbuffer library (i.e. an efficient FIFO queue with a size limit) -- (because unbounded dynamically-growing queues are a bad thing...) local have_utable, utable = pcall(require, "util.table"); -- For pre-allocation of table local function new(size, allow_wrapping) -- Head is next insert, tail is next read local head, tail = 1, 1; local items = 0; -- Number of stored items local t = have_utable and utable.create(size, 0) or {}; -- Table to hold items --luacheck: ignore 212/self return { _items = t; size = size; count = function (self) return items; end; push = function (self, item) if items >= size then if allow_wrapping then tail = (tail%size)+1; -- Advance to next oldest item items = items - 1; else return nil, "queue full"; end end t[head] = item; items = items + 1; head = (head%size)+1; return true; end; pop = function (self) if items == 0 then return nil; end local item; item, t[tail] = t[tail], 0; tail = (tail%size)+1; items = items - 1; return item; end; peek = function (self) if items == 0 then return nil; end return t[tail]; end; replace = function (self, data) if items == 0 then return self:push(data); end t[tail] = data; return true; end; items = function (self) return function (_, pos) if pos >= items then return nil; end local read_pos = tail + pos; if read_pos > self.size then read_pos = (read_pos%size); end return pos+1, t[read_pos]; end, self, 0; end; consume = function (self) return self.pop, self; end; }; end return { new = new; }; prosody-0.12.4/util/PaxHeaders/random.lua0000644000000000000000000000012014475665104015324 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.780764089 prosody-0.12.4/util/random.lua0000644000175000017500000000164414475665104017536 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2014 Matthew Wild -- Copyright (C) 2008-2014 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local ok, crand = pcall(require, "util.crand"); if ok and pcall(crand.bytes, 1) then return crand; end local urandom, urandom_err = io.open("/dev/urandom", "r"); local function bytes(n) local data, err = urandom:read(n); if not data then if err then error("Unable to retrieve data from secure random number generator (/dev/urandom): "..tostring(err)); else error("Secure random number generator (/dev/urandom) returned an end-of-file condition"); end end return data; end if not urandom then function bytes() error("Unable to obtain a secure random number generator, please see https://prosody.im/doc/random ("..urandom_err..")"); end end return { bytes = bytes; _source = "/dev/urandom"; }; prosody-0.12.4/util/PaxHeaders/rfc6724.lua0000644000000000000000000000012014475665104015141 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.780764089 prosody-0.12.4/util/rfc6724.lua0000644000175000017500000000720014475665104017345 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2011-2013 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- This is used to sort destination addresses by preference -- during S2S connections. -- We can't hand this off to getaddrinfo, since it blocks local ip_commonPrefixLength = require"util.ip".commonPrefixLength local function commonPrefixLength(ipA, ipB) local len = ip_commonPrefixLength(ipA, ipB); return len < 64 and len or 64; end local function t_sort(t, comp) for i = 1, (#t - 1) do for j = (i + 1), #t do local a, b = t[i], t[j]; if not comp(a,b) then t[i], t[j] = b, a; end end end end local function source(dest, candidates) local function comp(ipA, ipB) -- Rule 1: Prefer same address if dest == ipA then return true; elseif dest == ipB then return false; end -- Rule 2: Prefer appropriate scope if ipA.scope < ipB.scope then if ipA.scope < dest.scope then return false; else return true; end elseif ipA.scope > ipB.scope then if ipB.scope < dest.scope then return true; else return false; end end -- Rule 3: Avoid deprecated addresses -- XXX: No way to determine this -- Rule 4: Prefer home addresses -- XXX: Mobility Address related, no way to determine this -- Rule 5: Prefer outgoing interface -- XXX: Interface to address relation. No way to determine this -- Rule 6: Prefer matching label if ipA.label == dest.label and ipB.label ~= dest.label then return true; elseif ipB.label == dest.label and ipA.label ~= dest.label then return false; end -- Rule 7: Prefer temporary addresses (over public ones) -- XXX: No way to determine this -- Rule 8: Use longest matching prefix if commonPrefixLength(ipA, dest) > commonPrefixLength(ipB, dest) then return true; else return false; end end t_sort(candidates, comp); return candidates[1]; end local function destination(candidates, sources) local sourceAddrs = {}; local function comp(ipA, ipB) local ipAsource = sourceAddrs[ipA]; local ipBsource = sourceAddrs[ipB]; -- Rule 1: Avoid unusable destinations -- XXX: No such information -- Rule 2: Prefer matching scope if ipA.scope == ipAsource.scope and ipB.scope ~= ipBsource.scope then return true; elseif ipA.scope ~= ipAsource.scope and ipB.scope == ipBsource.scope then return false; end -- Rule 3: Avoid deprecated addresses -- XXX: No way to determine this -- Rule 4: Prefer home addresses -- XXX: Mobility Address related, no way to determine this -- Rule 5: Prefer matching label if ipAsource.label == ipA.label and ipBsource.label ~= ipB.label then return true; elseif ipBsource.label == ipB.label and ipAsource.label ~= ipA.label then return false; end -- Rule 6: Prefer higher precedence if ipA.precedence > ipB.precedence then return true; elseif ipA.precedence < ipB.precedence then return false; end -- Rule 7: Prefer native transport -- XXX: No way to determine this -- Rule 8: Prefer smaller scope if ipA.scope < ipB.scope then return true; elseif ipA.scope > ipB.scope then return false; end -- Rule 9: Use longest matching prefix if commonPrefixLength(ipA, ipAsource) > commonPrefixLength(ipB, ipBsource) then return true; elseif commonPrefixLength(ipA, ipAsource) < commonPrefixLength(ipB, ipBsource) then return false; end -- Rule 10: Otherwise, leave order unchanged return true; end for _, ip in ipairs(candidates) do sourceAddrs[ip] = source(ip, sources); end t_sort(candidates, comp); return candidates; end return {source = source, destination = destination}; prosody-0.12.4/util/PaxHeaders/rsm.lua0000644000000000000000000000012014475665104014645 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.780764089 prosody-0.12.4/util/rsm.lua0000644000175000017500000000450314475665104017054 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2017 Matthew Wild -- Copyright (C) 2008-2017 Waqas Hussain -- Copyright (C) 2011-2017 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- XEP-0313: Message Archive Management for Prosody -- local stanza = require"util.stanza".stanza; local tonumber = tonumber; local s_format = string.format; local type = type; local pairs = pairs; local function inttostr(n) return s_format("%d", n); end local xmlns_rsm = 'http://jabber.org/protocol/rsm'; local element_parsers = {}; do local parsers = element_parsers; local function xs_int(st) return tonumber((st:get_text())); end local function xs_string(st) return st:get_text(); end parsers.after = xs_string; parsers.before = function(st) local text = st:get_text(); return text == "" or text; end; parsers.max = xs_int; parsers.index = xs_int; parsers.first = function(st) return { index = tonumber(st.attr.index); st:get_text() }; end; parsers.last = xs_string; parsers.count = xs_int; end local element_generators = setmetatable({ first = function(st, data) if type(data) == "table" then st:tag("first", { index = inttostr(data.index) }):text(data[1]):up(); else st:text_tag("first", data); end end; before = function(st, data) if data == true then st:tag("before"):up(); else st:text_tag("before", data); end end; max = function (st, data) st:text_tag("max", inttostr(data)); end; index = function (st, data) st:text_tag("index", inttostr(data)); end; count = function (st, data) st:text_tag("count", inttostr(data)); end; }, { __index = function(_, name) return function(st, data) st:text_tag(name, data); end end; }); local function parse(set) local rs = {}; for tag in set:childtags() do local name = tag.name; local parser = name and element_parsers[name]; if parser then rs[name] = parser(tag); end end return rs; end local function generate(t) local st = stanza("set", { xmlns = xmlns_rsm }); for k,v in pairs(t) do if element_parsers[k] then element_generators[k](st, v); end end return st; end local function get(st) local set = st:get_child("set", xmlns_rsm); if set and #set.tags > 0 then return parse(set); end end return { parse = parse, generate = generate, get = get }; prosody-0.12.4/util/PaxHeaders/sasl0000644000000000000000000000013014475665104014227 xustar0030 mtime=1693936196.840763501 29 atime=1693936196.78476405 29 ctime=1693936196.78476405 prosody-0.12.4/util/sasl/0000755000175000017500000000000014475665104016510 5ustar00prosodyprosody00000000000000prosody-0.12.4/util/sasl/PaxHeaders/anonymous.lua0000644000000000000000000000011714475665104017044 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.78476405 prosody-0.12.4/util/sasl/anonymous.lua0000644000175000017500000000437314475665104021252 0ustar00prosodyprosody00000000000000-- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local generate_random_id = require "util.id".medium; local _ENV = nil; -- luacheck: std none --========================= --SASL ANONYMOUS according to RFC 4505 --[[ Supported Authentication Backends anonymous: function(username, realm) return true; --for normal usage just return true; if you don't like the supplied username you can return false. end ]] local function anonymous(self, message) -- luacheck: ignore 212/message local username; repeat username = generate_random_id():lower(); until self.profile.anonymous(self, username, self.realm); self.username = username; return "success" end local function init(registerMechanism) registerMechanism("ANONYMOUS", {"anonymous"}, anonymous); end return { init = init; } prosody-0.12.4/util/sasl/PaxHeaders/external.lua0000644000000000000000000000011714475665104016636 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.78476405 prosody-0.12.4/util/sasl/external.lua0000644000175000017500000000113114475665104021031 0ustar00prosodyprosody00000000000000local saslprep = require "util.encodings".stringprep.saslprep; local _ENV = nil; -- luacheck: std none local function external(self, message) message = saslprep(message); local state self.username, state = self.profile.external(message); if state == false then return "failure", "account-disabled"; elseif state == nil then return "failure", "not-authorized"; elseif state == "expired" then return "false", "credentials-expired"; end return "success"; end local function init(registerMechanism) registerMechanism("EXTERNAL", {"external"}, external); end return { init = init; } prosody-0.12.4/util/sasl/PaxHeaders/plain.lua0000644000000000000000000000011714475665104016117 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.78476405 prosody-0.12.4/util/sasl/plain.lua0000644000175000017500000000723514475665104020325 0ustar00prosodyprosody00000000000000-- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local s_match = string.match; local saslprep = require "util.encodings".stringprep.saslprep; local nodeprep = require "util.encodings".stringprep.nodeprep; local log = require "util.logger".init("sasl"); local _ENV = nil; -- luacheck: std none -- ================================ -- SASL PLAIN according to RFC 4616 --[[ Supported Authentication Backends plain: function(username, realm) return password, state; end plain_test: function(username, password, realm) return true or false, state; end ]] local function plain(self, message) if not message then return "failure", "malformed-request"; end local authorization, authentication, password = s_match(message, "^([^%z]*)%z([^%z]+)%z([^%z]+)"); if not authorization then return "failure", "malformed-request"; end -- SASLprep password and authentication authentication = saslprep(authentication); password = saslprep(password); if (not password) or (password == "") or (not authentication) or (authentication == "") then log("debug", "Username or password violates SASLprep."); return "failure", "malformed-request", "Invalid username or password."; end local _nodeprep = self.profile.nodeprep; if _nodeprep ~= false then authentication = (_nodeprep or nodeprep)(authentication); if not authentication or authentication == "" then return "failure", "malformed-request", "Invalid username or password." end end self.username = authentication local correct, state = false, false; if self.profile.plain then local correct_password; correct_password, state = self.profile.plain(self, authentication, self.realm); correct = (saslprep(correct_password) == password); elseif self.profile.plain_test then correct, state = self.profile.plain_test(self, authentication, password, self.realm); end if state == false then return "failure", "account-disabled"; elseif state == nil or not correct then return "failure", "not-authorized", "Unable to authorize you with the authentication credentials you've sent."; end return "success"; end local function init(registerMechanism) registerMechanism("PLAIN", {"plain", "plain_test"}, plain); end return { init = init; } prosody-0.12.4/util/sasl/PaxHeaders/scram.lua0000644000000000000000000000011714475665104016121 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.78476405 prosody-0.12.4/util/sasl/scram.lua0000644000175000017500000002403414475665104020323 0ustar00prosodyprosody00000000000000-- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local s_match = string.match; local type = type local base64 = require "util.encodings".base64; local hashes = require "util.hashes"; local generate_uuid = require "util.uuid".generate; local saslprep = require "util.encodings".stringprep.saslprep; local nodeprep = require "util.encodings".stringprep.nodeprep; local log = require "util.logger".init("sasl"); local binaryXOR = require "util.strbitop".sxor; local _ENV = nil; -- luacheck: std none --========================= --SASL SCRAM-SHA-1 according to RFC 5802 --[[ Supported Authentication Backends scram_{MECH}: -- MECH being a standard hash name (like those at IANA's hash registry) with '-' replaced with '_' function(username, realm) return stored_key, server_key, iteration_count, salt, state; end Supported Channel Binding Backends 'tls-unique' according to RFC 5929 ]] local default_i = 10000 local function validate_username(username, _nodeprep) -- check for forbidden char sequences for eq in username:gmatch("=(.?.?)") do if eq ~= "2C" and eq ~= "3D" then return false end end -- replace =2C with , and =3D with = username = username:gsub("=2C", ","); username = username:gsub("=3D", "="); -- apply SASLprep username = saslprep(username); if username and _nodeprep ~= false then username = (_nodeprep or nodeprep)(username); end return username and #username>0 and username; end local function hashprep(hashname) return hashname:lower():gsub("-", "_"); end local function get_scram_hasher(H, HMAC, Hi) return function (password, salt, iteration_count) if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then return false, "inappropriate argument types" end if iteration_count < 4096 then log("warn", "Iteration count < 4096 which is the suggested minimum according to RFC 5802.") end password = saslprep(password); if not password then return false, "password fails SASLprep"; end local salted_password = Hi(password, salt, iteration_count); local stored_key = H(HMAC(salted_password, "Client Key")) local server_key = HMAC(salted_password, "Server Key"); return true, stored_key, server_key end end local function scram_gen(hash_name, H_f, HMAC_f, get_auth_db, expect_cb) local profile_name = "scram_" .. hashprep(hash_name); local function scram_hash(self, message) local support_channel_binding = false; if self.profile.cb then support_channel_binding = true; end if type(message) ~= "string" or #message == 0 then return "failure", "malformed-request" end local state = self.state; if not state then -- we are processing client_first_message local client_first_message = message; -- TODO: fail if authzid is provided, since we don't support them yet -- luacheck: ignore 211/authzid local gs2_header, gs2_cbind_flag, gs2_cbind_name, authzid, client_first_message_bare, username, clientnonce = s_match(client_first_message, "^(([pny])=?([^,]*),([^,]*),)(m?=?[^,]*,?n=([^,]*),r=([^,]*),?.*)$"); if not gs2_cbind_flag then return "failure", "malformed-request"; end if support_channel_binding and gs2_cbind_flag == "y" then -- "y" -> client does support channel binding -- but thinks the server does not. return "failure", "malformed-request"; end if gs2_cbind_flag == "n" then -- "n" -> client doesn't support channel binding. if expect_cb then log("debug", "Client unexpectedly doesn't support channel binding"); -- XXX Is it sensible to abort if the client starts -PLUS but doesn't use channel binding? end support_channel_binding = false; end if support_channel_binding and gs2_cbind_flag == "p" then -- check whether we support the proposed channel binding type if not self.profile.cb[gs2_cbind_name] then return "failure", "malformed-request", "Proposed channel binding type isn't supported."; end else -- no channel binding, gs2_cbind_name = nil; end username = validate_username(username, self.profile.nodeprep); if not username then log("debug", "Username violates either SASLprep or contains forbidden character sequences.") return "failure", "malformed-request", "Invalid username."; end self.username = username; -- retrieve credentials local stored_key, server_key, salt, iteration_count; if self.profile.plain then local password, status = self.profile.plain(self, username, self.realm) if status == nil then return "failure", "not-authorized" elseif status == false then return "failure", "account-disabled" end password = saslprep(password); if not password then log("debug", "Password violates SASLprep."); return "failure", "not-authorized", "Invalid password." end salt = generate_uuid(); iteration_count = default_i; local succ; succ, stored_key, server_key = get_auth_db(password, salt, iteration_count); if not succ then log("error", "Generating authentication database failed. Reason: %s", stored_key); return "failure", "temporary-auth-failure"; end elseif self.profile[profile_name] then local status; stored_key, server_key, iteration_count, salt, status = self.profile[profile_name](self, username, self.realm); if status == nil then return "failure", "not-authorized" elseif status == false then return "failure", "account-disabled" end end local nonce = clientnonce .. generate_uuid(); local server_first_message = ("r=%s,s=%s,i=%d"):format(nonce, base64.encode(salt), iteration_count); self.state = { gs2_header = gs2_header; gs2_cbind_name = gs2_cbind_name; username = self.username; -- Reference property instead of local, in case it was modified by the profile nonce = nonce; server_key = server_key; stored_key = stored_key; client_first_message_bare = client_first_message_bare; server_first_message = server_first_message; } return "challenge", server_first_message else -- we are processing client_final_message local client_final_message = message; local client_final_message_without_proof, channelbinding, nonce, proof = s_match(client_final_message, "(c=([^,]*),r=([^,]*),?.-),p=(.*)$"); if not proof or not nonce or not channelbinding then return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; end local client_gs2_header = base64.decode(channelbinding) local our_client_gs2_header = state["gs2_header"] if state.gs2_cbind_name then -- we support channelbinding, so check if the value is valid our_client_gs2_header = our_client_gs2_header .. self.profile.cb[state.gs2_cbind_name](self); end if client_gs2_header ~= our_client_gs2_header then return "failure", "malformed-request", "Invalid channel binding value."; end if nonce ~= state.nonce then return "failure", "malformed-request", "Wrong nonce in client-final-message."; end local ServerKey = state.server_key; local StoredKey = state.stored_key; local AuthMessage = state.client_first_message_bare .. "," .. state.server_first_message .. "," .. client_final_message_without_proof local ClientSignature = HMAC_f(StoredKey, AuthMessage) local ClientKey = binaryXOR(ClientSignature, base64.decode(proof)) local ServerSignature = HMAC_f(ServerKey, AuthMessage) if StoredKey == H_f(ClientKey) then local server_final_message = "v="..base64.encode(ServerSignature); return "success", server_final_message; else return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; end end end return scram_hash; end local auth_db_getters = {} local function init(registerMechanism) local function registerSCRAMMechanism(hash_name, hash, hmac_hash, pbkdf2) local get_auth_db = get_scram_hasher(hash, hmac_hash, pbkdf2); auth_db_getters[hash_name] = get_auth_db; registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash, get_auth_db)); -- register channel binding equivalent registerMechanism("SCRAM-"..hash_name.."-PLUS", {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash, get_auth_db, true), {"tls-unique"}); end registerSCRAMMechanism("SHA-1", hashes.sha1, hashes.hmac_sha1, hashes.pbkdf2_hmac_sha1); registerSCRAMMechanism("SHA-256", hashes.sha256, hashes.hmac_sha256, hashes.pbkdf2_hmac_sha256); end return { get_hash = get_scram_hasher; hashers = auth_db_getters; getAuthenticationDatabaseSHA1 = get_scram_hasher(hashes.sha1, hashes.hmac_sha1, hashes.pbkdf2_hmac_sha1); -- COMPAT init = init; } prosody-0.12.4/util/PaxHeaders/sasl.lua0000644000000000000000000000011714475665104015014 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.78476405 prosody-0.12.4/util/sasl.lua0000644000175000017500000001200714475665104017213 0ustar00prosodyprosody00000000000000-- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local pairs, ipairs = pairs, ipairs; local t_insert = table.insert; local type = type local setmetatable = setmetatable; local assert = assert; local require = require; local _ENV = nil; -- luacheck: std none --[[ Authentication Backend Prototypes: state = false : disabled state = true : enabled state = nil : non-existent Channel Binding: To enable support of channel binding in some mechanisms you need to provide appropriate callbacks in a table at profile.cb. Example: profile.cb["tls-unique"] = function(self) return self.user end ]] local method = {}; method.__index = method; local registered_mechanisms = {}; local backend_mechanism = {}; local mechanism_channelbindings = {}; -- register a new SASL mechanisms local function registerMechanism(name, backends, f, cb_backends) assert(type(name) == "string", "Parameter name MUST be a string."); assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table."); assert(type(f) == "function", "Parameter f MUST be a function."); if cb_backends then assert(type(cb_backends) == "table"); end registered_mechanisms[name] = f if cb_backends then mechanism_channelbindings[name] = {}; for _, cb_name in ipairs(cb_backends) do mechanism_channelbindings[name][cb_name] = true; end end for _, backend_name in ipairs(backends) do if backend_mechanism[backend_name] == nil then backend_mechanism[backend_name] = {}; end t_insert(backend_mechanism[backend_name], name); end end -- create a new SASL object which can be used to authenticate clients local function new(realm, profile) local mechanisms = profile.mechanisms; if not mechanisms then mechanisms = {}; for backend in pairs(profile) do if backend_mechanism[backend] then for _, mechanism in ipairs(backend_mechanism[backend]) do mechanisms[mechanism] = true; end end end profile.mechanisms = mechanisms; end return setmetatable({ profile = profile, realm = realm, mechs = mechanisms }, method); end -- add a channel binding handler function method:add_cb_handler(name, f) if type(self.profile.cb) ~= "table" then self.profile.cb = {}; end self.profile.cb[name] = f; return self; end -- get a fresh clone with the same realm and profile function method:clean_clone() return new(self.realm, self.profile) end -- get a list of possible SASL mechanisms to use function method:mechanisms() local current_mechs = {}; for mech, _ in pairs(self.mechs) do if mechanism_channelbindings[mech] then if self.profile.cb then local ok = false; for cb_name, _ in pairs(self.profile.cb) do if mechanism_channelbindings[mech][cb_name] then ok = true; end end if ok == true then current_mechs[mech] = true; end end else current_mechs[mech] = true; end end return current_mechs; end -- select a mechanism to use function method:select(mechanism) if not self.selected and self.mechs[mechanism] then self.selected = mechanism; return true; end end -- feed new messages to process into the library function method:process(message) --if message == "" or message == nil then return "failure", "malformed-request" end return registered_mechanisms[self.selected](self, message); end -- load the mechanisms require "util.sasl.plain" .init(registerMechanism); require "util.sasl.anonymous" .init(registerMechanism); require "util.sasl.scram" .init(registerMechanism); require "util.sasl.external" .init(registerMechanism); return { registerMechanism = registerMechanism; new = new; }; prosody-0.12.4/util/PaxHeaders/serialization.lua0000644000000000000000000000011714475665104016727 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.78476405 prosody-0.12.4/util/serialization.lua0000644000175000017500000001544614475665104021140 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2018 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local getmetatable = getmetatable; local next, type = next, type; local s_format = string.format; local s_gsub = string.gsub; local s_rep = string.rep; local s_char = string.char; local s_match = string.match; local t_concat = table.concat; local to_hex = require "util.hex".to; local pcall = pcall; local envload = require"util.envload".envload; local pos_inf, neg_inf = math.huge, -math.huge; local m_type = math.type or function (n) return n % 1 == 0 and n <= 9007199254740992 and n >= -9007199254740992 and "integer" or "float"; end; local function rawpairs(t) return next, t, nil; end local function fatal_error(obj, why) error("Can't serialize "..type(obj) .. (why and ": ".. why or "")); end local function nonfatal_fallback(x, why) return s_format("{__type=%q,__error=%q}", type(x), why or "fail"); end local string_escapes = { ['\a'] = [[\a]]; ['\b'] = [[\b]]; ['\f'] = [[\f]]; ['\n'] = [[\n]]; ['\r'] = [[\r]]; ['\t'] = [[\t]]; ['\v'] = [[\v]]; ['\\'] = [[\\]]; ['\"'] = [[\"]]; ['\''] = [[\']]; } for i = 0, 255 do local c = s_char(i); if not string_escapes[c] then string_escapes[c] = s_format("\\%03d", i); end end local default_keywords = { ["do"] = true; ["and"] = true; ["else"] = true; ["break"] = true; ["if"] = true; ["end"] = true; ["goto"] = true; ["false"] = true; ["in"] = true; ["for"] = true; ["then"] = true; ["local"] = true; ["or"] = true; ["nil"] = true; ["true"] = true; ["until"] = true; ["elseif"] = true; ["function"] = true; ["not"] = true; ["repeat"] = true; ["return"] = true; ["while"] = true; }; local function new(opt) if type(opt) ~= "table" then opt = { preset = opt }; end local types = { table = true; string = true; number = true; boolean = true; ["nil"] = true; }; -- presets if opt.preset == "debug" then opt.preset = "oneline"; opt.freeze = true; opt.fatal = false; opt.fallback = nonfatal_fallback; opt.unquoted = true; end if opt.preset == "oneline" then opt.indentwith = opt.indentwith or ""; opt.itemstart = opt.itemstart or " "; opt.itemlast = opt.itemlast or ""; opt.tend = opt.tend or " }"; elseif opt.preset == "compact" then opt.indentwith = opt.indentwith or ""; opt.itemstart = opt.itemstart or ""; opt.itemlast = opt.itemlast or ""; opt.equals = opt.equals or "="; opt.unquoted = true; end local fallback = opt.fallback or opt.fatal == false and nonfatal_fallback or fatal_error; local function ser(v) return (types[type(v)] or fallback)(v); end local keywords = opt.keywords or default_keywords; -- indented local indentwith = opt.indentwith or "\t"; local itemstart = opt.itemstart or "\n"; local itemsep = opt.itemsep or ";"; local itemlast = opt.itemlast or ";\n"; local tstart = opt.tstart or "{"; local tend = opt.tend or "}"; local kstart = opt.kstart or "["; local kend = opt.kend or "]"; local equals = opt.equals or " = "; local unquoted = opt.unquoted == true and "^[%a_][%w_]*$" or opt.unquoted; local hex = opt.hex; local freeze = opt.freeze; local maxdepth = opt.maxdepth or 127; local multirefs = opt.multiref; local table_pairs = opt.table_iterator or rawpairs; -- serialize one table, recursively -- t - table being serialized -- o - array where tokens are added, concatenate to get final result -- - also used to detect cycles -- l - position in o of where to insert next token -- d - depth, used for indentation local function serialize_table(t, o, l, d) if o[t] then o[l], l = fallback(t, "table has multiple references"), l + 1; return l; elseif d > maxdepth then o[l], l = fallback(t, "max table depth reached"), l + 1; return l; end -- Keep track of table loops local ot = t; -- reference pre-freeze o[t] = true; o[ot] = true; if freeze == true then -- opportunity to do pre-serialization local mt = getmetatable(t); if type(mt) == "table" then local tag = mt.__name; local fr = mt.__freeze; if type(fr) == "function" then t = fr(t); if type(t) == "string" then o[l], l = t, l + 1; return l; end if type(tag) == "string" then o[l], l = tag, l + 1; end end end end o[l], l = tstart, l + 1; local indent = s_rep(indentwith, d); local numkey = 1; local ktyp, vtyp; local had_items = false; for k,v in table_pairs(t) do had_items = true; o[l], l = itemstart, l + 1; o[l], l = indent, l + 1; ktyp, vtyp = type(k), type(v); if k == numkey then -- next index in array part -- assuming that these are found in order numkey = numkey + 1; elseif unquoted and ktyp == "string" and not keywords[k] and s_match(k, unquoted) then -- unquoted keys o[l], l = k, l + 1; o[l], l = equals, l + 1; else -- quoted keys o[l], l = kstart, l + 1; if ktyp == "table" then l = serialize_table(k, o, l, d+1); else o[l], l = ser(k), l + 1; end -- = o[l], o[l+1], l = kend, equals, l + 2; end -- the value if vtyp == "table" then l = serialize_table(v, o, l, d+1); else o[l], l = ser(v), l + 1; end o[l], l = itemsep, l + 1; end if had_items then o[l - 1] = itemlast; o[l], l = s_rep(indentwith, d-1), l + 1; end o[l], l = tend, l +1; if multirefs then o[t] = nil; o[ot] = nil; end return l; end function types.table(t) local o = {}; serialize_table(t, o, 1, 1); return t_concat(o); end local function serialize_string(s) return '"' .. s_gsub(s, "[%z\1-\31\"\'\\\127-\255]", string_escapes) .. '"'; end if type(hex) == "string" then function types.string(s) local esc = serialize_string(s); if #esc > (#s*2+2+#hex) then return hex .. '"' .. to_hex(s) .. '"'; end return esc; end else types.string = serialize_string; end function types.number(t) if m_type(t) == "integer" then return s_format("%d", t); elseif t == pos_inf then return "(1/0)"; elseif t == neg_inf then return "(-1/0)"; elseif t ~= t then return "(0/0)"; end return s_format("%.18g", t); end -- Are these faster than tostring? types["nil"] = function() return "nil"; end function types.boolean(t) return t and "true" or "false"; end return ser; end local function deserialize(str) if type(str) ~= "string" then return nil; end str = "return "..str; local f, err = envload(str, "=serialized data", {}); if not f then return nil, err; end local success, ret = pcall(f); if not success then return nil, ret; end return ret; end local default = new(); return { new = new; serialize = function (x, opt) if opt == nil then return default(x); else return new(opt)(x); end end; deserialize = deserialize; }; prosody-0.12.4/util/PaxHeaders/session.lua0000644000000000000000000000011714475665104015535 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.78476405 prosody-0.12.4/util/session.lua0000644000175000017500000000254014475665104017735 0ustar00prosodyprosody00000000000000local initialize_filters = require "util.filters".initialize; local logger = require "util.logger"; local function new_session(typ) local session = { type = typ .. "_unauthed"; base_type = typ; }; return session; end local function set_id(session) local id = session.base_type .. tostring(session):match("%x+$"):lower(); session.id = id; return session; end local function set_logger(session) local log = logger.init(session.id); session.log = log; return session; end local function set_conn(session, conn) session.conn = conn; session.ip = conn:ip(); return session; end local function set_send(session) local conn = session.conn; if not conn then function session.send(data) session.log("debug", "Discarding data sent to unconnected session: %s", data); return false; end return session; end local filter = initialize_filters(session); local w = conn.write; session.send = function (t) if t.name then t = filter("stanzas/out", t); end if t then t = filter("bytes/out", tostring(t)); if t then local ret, err = w(conn, t); if not ret then session.log("debug", "Error writing to connection: %s", err); return false, err; end end end return true; end return session; end return { new = new_session; set_id = set_id; set_logger = set_logger; set_conn = set_conn; set_send = set_send; } prosody-0.12.4/util/PaxHeaders/set.lua0000644000000000000000000000011714475665104014645 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.78476405 prosody-0.12.4/util/set.lua0000644000175000017500000000716714475665104017057 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local ipairs, pairs, setmetatable, next, tostring = ipairs, pairs, setmetatable, next, tostring; local getmetatable = getmetatable; local t_concat = table.concat; local _ENV = nil; -- luacheck: std none local set_mt = { __name = "set" }; function set_mt.__call(set, _, k) return next(set._items, k); end local items_mt = {}; function items_mt.__call(items, _, k) return next(items, k); end function set_mt:__freeze() local a, i = {}, 1; for item in self._items do a[i], i = item, i+1; end return a; end local function is_set(o) local mt = getmetatable(o); return mt == set_mt; end local function new(list) local items = setmetatable({}, items_mt); local set = { _items = items }; -- We access the set through an upvalue in these methods, so ignore 'self' being unused --luacheck: ignore 212/self function set:add(item) items[item] = true; end function set:contains(item) return items[item]; end function set:contains_set(other_set) for item in other_set do if not self:contains(item) then return false; end end return true; end function set:items() return next, items; end function set:remove(item) items[item] = nil; end function set:add_list(item_list) if item_list then for _, item in ipairs(item_list) do items[item] = true; end end end function set:include(otherset) for item in otherset do items[item] = true; end end function set:exclude(otherset) for item in otherset do items[item] = nil; end end function set:empty() return not next(items); end if list then set:add_list(list); end return setmetatable(set, set_mt); end local function union(set1, set2) local set = new(); local items = set._items; for item in pairs(set1._items) do items[item] = true; end for item in pairs(set2._items) do items[item] = true; end return set; end local function difference(set1, set2) local set = new(); local items = set._items; for item in pairs(set1._items) do items[item] = (not set2._items[item]) or nil; end return set; end local function intersection(set1, set2) local set = new(); local items = set._items; set1, set2 = set1._items, set2._items; for item in pairs(set1) do items[item] = (not not set2[item]) or nil; end return set; end local function xor(set1, set2) return union(set1, set2) - intersection(set1, set2); end function set_mt.__add(set1, set2) return union(set1, set2); end function set_mt.__sub(set1, set2) return difference(set1, set2); end function set_mt.__div(set, func) local new_set = new(); local items, new_items = set._items, new_set._items; for item in pairs(items) do local new_item = func(item); if new_item ~= nil then new_items[new_item] = true; end end return new_set; end function set_mt.__eq(set1, set2) if getmetatable(set1) ~= set_mt or getmetatable(set2) ~= set_mt then -- Lua 5.3+ calls this if both operands are tables, even if metatables differ return false; end set1, set2 = set1._items, set2._items; for item in pairs(set1) do if not set2[item] then return false; end end for item in pairs(set2) do if not set1[item] then return false; end end return true; end function set_mt.__tostring(set) local s, items = { }, set._items; for item in pairs(items) do s[#s+1] = tostring(item); end return t_concat(s, ", "); end return { new = new; is_set = is_set; union = union; difference = difference; intersection = intersection; xor = xor; }; prosody-0.12.4/util/PaxHeaders/smqueue.lua0000644000000000000000000000011714475665104015536 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 29 ctime=1693936196.78476405 prosody-0.12.4/util/smqueue.lua0000644000175000017500000000243414475665104017740 0ustar00prosodyprosody00000000000000local queue = require("util.queue"); local lib = { smqueue = {} } local smqueue = lib.smqueue; function smqueue:push(v) self._head = self._head + 1; assert(self._queue:push(v)); end function smqueue:ack(h) if h < self._tail then return nil, "tail" elseif h > self._head then return nil, "head" end local acked = {}; self._tail = h; local expect = self._head - self._tail; while expect < self._queue:count() do local v = self._queue:pop(); if not v then return nil, "pop" end table.insert(acked, v); end return acked end function smqueue:count_unacked() return self._head - self._tail end function smqueue:count_acked() return self._tail end function smqueue:resumable() return self._queue:count() >= (self._head - self._tail) end function smqueue:resume() return self._queue:items() end function smqueue:consume() return self._queue:consume() end function smqueue:table() local t = {}; for i, v in self:resume() do t[i] = v; end return t end local function freeze(q) return { head = q._head; tail = q._tail } end local queue_mt = { __name = "smqueue"; __index = smqueue; __len = smqueue.count_unacked; __freeze = freeze } function lib.new(size) assert(size > 0); return setmetatable({ _head = 0; _tail = 0; _queue = queue.new(size, true) }, queue_mt) end return lib prosody-0.12.4/util/PaxHeaders/sql.lua0000644000000000000000000000012014475665104014643 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.788764011 prosody-0.12.4/util/sql.lua0000644000175000017500000002552214475665104017056 0ustar00prosodyprosody00000000000000 local setmetatable, getmetatable = setmetatable, getmetatable; local ipairs = ipairs; local tostring = tostring; local type = type; local assert, pcall, debug_traceback = assert, pcall, debug.traceback; local xpcall = require "util.xpcall".xpcall; local t_concat = table.concat; local log = require "util.logger".init("sql"); local DBI = require "DBI"; -- This loads all available drivers while globals are unlocked -- LuaDBI should be fixed to not set globals. DBI.Drivers(); local build_url = require "socket.url".build; local _ENV = nil; -- luacheck: std none local column_mt = {}; local table_mt = {}; local query_mt = {}; --local op_mt = {}; local index_mt = {}; local function is_column(x) return getmetatable(x)==column_mt; end local function is_index(x) return getmetatable(x)==index_mt; end local function is_table(x) return getmetatable(x)==table_mt; end local function is_query(x) return getmetatable(x)==query_mt; end local function Integer() return "Integer()" end local function String() return "String()" end local function Column(definition) return setmetatable(definition, column_mt); end local function Table(definition) local c = {} for i,col in ipairs(definition) do if is_column(col) then c[i], c[col.name] = col, col; elseif is_index(col) then col.table = definition.name; end end return setmetatable({ __table__ = definition, c = c, name = definition.name }, table_mt); end local function Index(definition) return setmetatable(definition, index_mt); end function table_mt:__tostring() local s = { 'name="'..self.__table__.name..'"' } for _, col in ipairs(self.__table__) do s[#s+1] = tostring(col); end return 'Table{ '..t_concat(s, ", ")..' }' end table_mt.__index = {}; function table_mt.__index:create(engine) return engine:_create_table(self); end function column_mt:__tostring() return 'Column{ name="'..self.name..'", type="'..self.type..'" }' end function index_mt:__tostring() local s = 'Index{ name="'..self.name..'"'; for i=1,#self do s = s..', "'..self[i]:gsub("[\\\"]", "\\%1")..'"'; end return s..' }'; -- return 'Index{ name="'..self.name..'", type="'..self.type..'" }' end local engine = {}; function engine:connect() if self.conn then return true; end local params = self.params; assert(params.driver, "no driver") log("debug", "Connecting to [%s] %s...", params.driver, params.database); local ok, dbh, err = pcall(DBI.Connect, params.driver, params.database, params.username, params.password, params.host, params.port ); if not ok then return ok, dbh; end if not dbh then return nil, err; end dbh:autocommit(false); -- don't commit automatically self.conn = dbh; self.prepared = {}; local ok, err = self:set_encoding(); if not ok then return ok, err; end local ok, err = self:onconnect(); if ok == false then return ok, err; end return true; end function engine:onconnect() -- luacheck: ignore 212/self -- Override from create_engine() end function engine:prepquery(sql) if self.params.driver == "MySQL" then sql = sql:gsub("\"", "`"); end return sql; end function engine:execute(sql, ...) local success, err = self:connect(); if not success then return success, err; end local prepared = self.prepared; sql = self:prepquery(sql); local stmt = prepared[sql]; if not stmt then local err; stmt, err = self.conn:prepare(sql); if not stmt then return stmt, err; end prepared[sql] = stmt; end -- luacheck: ignore 411/success local success, err = stmt:execute(...); if not success then return success, err; end return stmt; end local result_mt = { __index = { affected = function(self) return self.__stmt:affected(); end; rowcount = function(self) return self.__stmt:rowcount(); end; } }; local function debugquery(where, sql, ...) local i = 0; local a = {...} sql = sql:gsub("\n?\t+", " "); log("debug", "[%s] %s", where, (sql:gsub("%?", function () i = i + 1; local v = a[i]; if type(v) == "string" then v = ("'%s'"):format(v:gsub("'", "''")); end return tostring(v); end))); end function engine:execute_query(sql, ...) sql = self:prepquery(sql); local stmt = assert(self.conn:prepare(sql)); assert(stmt:execute(...)); local result = {}; for row in stmt:rows() do result[#result + 1] = row; end stmt:close(); local i = 0; return function() i=i+1; return result[i]; end; end function engine:execute_update(sql, ...) sql = self:prepquery(sql); local prepared = self.prepared; local stmt = prepared[sql]; if not stmt then stmt = assert(self.conn:prepare(sql)); prepared[sql] = stmt; end assert(stmt:execute(...)); return setmetatable({ __stmt = stmt }, result_mt); end engine.insert = engine.execute_update; engine.select = engine.execute_query; engine.delete = engine.execute_update; engine.update = engine.execute_update; local function debugwrap(name, f) return function (self, sql, ...) debugquery(name, sql, ...) return f(self, sql, ...) end end function engine:debug(enable) self._debug = enable; if enable then engine.insert = debugwrap("insert", engine.execute_update); engine.select = debugwrap("select", engine.execute_query); engine.delete = debugwrap("delete", engine.execute_update); engine.update = debugwrap("update", engine.execute_update); else engine.insert = engine.execute_update; engine.select = engine.execute_query; engine.delete = engine.execute_update; engine.update = engine.execute_update; end end local function handleerr(err) local trace = debug_traceback(err, 3); log("debug", "Error in SQL transaction: %s", trace); return { err = err, traceback = trace }; end function engine:_transaction(func, ...) if not self.conn then local ok, err = self:connect(); if not ok then return ok, err; end end --assert(not self.__transaction, "Recursive transactions not allowed"); log("debug", "SQL transaction begin [%s]", func); self.__transaction = true; local success, a, b, c = xpcall(func, handleerr, ...); self.__transaction = nil; if success then log("debug", "SQL transaction success [%s]", func); local ok, err = self.conn:commit(); -- LuaDBI doesn't actually return an error message here, just a boolean if not ok then return ok, err or "commit failed"; end return success, a, b, c; else log("debug", "SQL transaction failure [%s]: %s", func, a.err); if self.conn then self.conn:rollback(); end return success, a.err; end end function engine:transaction(...) local ok, ret, b, c = self:_transaction(...); if not ok then local conn = self.conn; if not conn or not conn:ping() then log("debug", "Database connection was closed. Will reconnect and retry."); self.conn = nil; log("debug", "Retrying SQL transaction [%s]", (...)); ok, ret, b, c = self:_transaction(...); log("debug", "SQL transaction retry %s", ok and "succeeded" or "failed"); else log("debug", "SQL connection is up, so not retrying"); end if not ok then log("error", "Error in SQL transaction: %s", ret); end end return ok, ret, b, c; end function engine:_create_index(index) local sql = "CREATE INDEX \""..index.name.."\" ON \""..index.table.."\" ("; if self.params.driver ~= "MySQL" then sql = sql:gsub("^CREATE INDEX", "%1 IF NOT EXISTS"); end for i=1,#index do sql = sql.."\""..index[i].."\""; if i ~= #index then sql = sql..", "; end end sql = sql..");" if self.params.driver == "MySQL" then sql = sql:gsub("\"([,)])", "\"(20)%1"); end if index.unique then sql = sql:gsub("^CREATE", "CREATE UNIQUE"); end if self._debug then debugquery("create", sql); end return self:execute(sql); end function engine:_create_table(table) local sql = "CREATE TABLE \""..table.name.."\" ("; do sql = sql:gsub("^CREATE TABLE", "%1 IF NOT EXISTS"); end for i,col in ipairs(table.c) do local col_type = col.type; if col_type == "MEDIUMTEXT" and self.params.driver ~= "MySQL" then col_type = "TEXT"; -- MEDIUMTEXT is MySQL-specific end if col.auto_increment == true and self.params.driver == "PostgreSQL" then col_type = "BIGSERIAL"; end sql = sql.."\""..col.name.."\" "..col_type; if col.nullable == false then sql = sql.." NOT NULL"; end if col.primary_key == true then sql = sql.." PRIMARY KEY"; end if col.auto_increment == true then if self.params.driver == "MySQL" then sql = sql.." AUTO_INCREMENT"; elseif self.params.driver == "SQLite3" then sql = sql.." AUTOINCREMENT"; end end if i ~= #table.c then sql = sql..", "; end end sql = sql.. ");" if self.params.driver == "MySQL" then sql = sql:gsub(";$", (" CHARACTER SET '%s' COLLATE '%s_bin';"):format(self.charset, self.charset)); end if self._debug then debugquery("create", sql); end local success,err = self:execute(sql); if not success then return success,err; end for _, v in ipairs(table.__table__) do if is_index(v) then self:_create_index(v); end end return success; end function engine:set_encoding() -- to UTF-8 local driver = self.params.driver; if driver == "SQLite3" then return self:transaction(function() for encoding in self:select"PRAGMA encoding;" do if encoding[1] == "UTF-8" then self.charset = "utf8"; end end end); end local set_names_query = "SET NAMES '%s';" local charset = "utf8"; if driver == "MySQL" then self:transaction(function() for row in self:select[[ SELECT "CHARACTER_SET_NAME" FROM "information_schema"."CHARACTER_SETS" WHERE "CHARACTER_SET_NAME" LIKE 'utf8%' ORDER BY MAXLEN DESC LIMIT 1; ]] do charset = row and row[1] or charset; end end); set_names_query = set_names_query:gsub(";$", (" COLLATE '%s';"):format(charset.."_bin")); end self.charset = charset; log("debug", "Using encoding '%s' for database connection", charset); local ok, err = self:transaction(function() return self:execute(set_names_query:format(charset)); end); if not ok then return ok, err; end if driver == "MySQL" then local ok, actual_charset = self:transaction(function () return self:select"SHOW SESSION VARIABLES LIKE 'character_set_client'"; end); if not ok then return false, "Failed to detect connection encoding"; end local charset_ok = true; for row in actual_charset do if row[2] ~= charset then log("error", "MySQL %s is actually %q (expected %q)", row[1], row[2], charset); charset_ok = false; end end if not charset_ok then return false, "Failed to set connection encoding"; end end return true; end local engine_mt = { __index = engine }; local function db2uri(params) return build_url{ scheme = params.driver, user = params.username, password = params.password, host = params.host, port = params.port, path = params.database, }; end local function create_engine(_, params, onconnect) return setmetatable({ url = db2uri(params), params = params, onconnect = onconnect }, engine_mt); end return { is_column = is_column; is_index = is_index; is_table = is_table; is_query = is_query; Integer = Integer; String = String; Column = Column; Table = Table; Index = Index; create_engine = create_engine; db2uri = db2uri; }; prosody-0.12.4/util/PaxHeaders/sslconfig.lua0000644000000000000000000000012014475665104016033 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.788764011 prosody-0.12.4/util/sslconfig.lua0000644000175000017500000000607714475665104020252 0ustar00prosodyprosody00000000000000-- util to easily merge multiple sets of LuaSec context options local type = type; local pairs = pairs; local rawset = rawset; local t_concat = table.concat; local t_insert = table.insert; local setmetatable = setmetatable; local _ENV = nil; -- luacheck: std none local handlers = { }; local finalisers = { }; local id = function (v) return v end -- All "handlers" behave like extended rawset(table, key, value) with extra -- processing usually merging the new value with the old in some reasonable -- way -- If a field does not have a defined handler then a new value simply -- replaces the old. -- Convert either a list or a set into a special type of set where each -- item is either positive or negative in order for a later set of options -- to be able to remove options from this set by filtering out the negative ones function handlers.options(config, field, new) local options = config[field] or { }; if type(new) ~= "table" then new = { new } end for key, value in pairs(new) do if value == true or value == false then options[key] = value; else -- list item options[value] = true; end end config[field] = options; end handlers.verifyext = handlers.options; -- finalisers take something produced by handlers and return what luasec -- expects it to be -- Produce a list of "positive" options from the set function finalisers.options(options) local output = {}; for opt, enable in pairs(options) do if enable then output[#output+1] = opt; end end return output; end finalisers.verifyext = finalisers.options; -- We allow ciphers to be a list function finalisers.ciphers(cipherlist) if type(cipherlist) == "table" then return t_concat(cipherlist, ":"); end return cipherlist; end -- Curve list too finalisers.curveslist = finalisers.ciphers; -- TLS 1.3 ciphers finalisers.ciphersuites = finalisers.ciphers; -- protocol = "x" should enable only that protocol -- protocol = "x+" should enable x and later versions local protocols = { "sslv2", "sslv3", "tlsv1", "tlsv1_1", "tlsv1_2", "tlsv1_3" }; for i = 1, #protocols do protocols[protocols[i] .. "+"] = i - 1; end -- this interacts with ssl.options as well to add no_x local function protocol(config) local min_protocol = protocols[config.protocol]; if min_protocol then config.protocol = "sslv23"; for i = 1, min_protocol do t_insert(config.options, "no_"..protocols[i]); end end end -- Merge options from 'new' config into 'config' local function apply(config, new) if type(new) == "table" then for field, value in pairs(new) do (handlers[field] or rawset)(config, field, value); end end end -- Finalize the config into the form LuaSec expects local function final(config) local output = { }; for field, value in pairs(config) do output[field] = (finalisers[field] or id)(value); end -- Need to handle protocols last because it adds to the options list protocol(output); return output; end local sslopts_mt = { __index = { apply = apply; final = final; }; }; local function new() return setmetatable({options={}}, sslopts_mt); end return { apply = apply; final = final; new = new; }; prosody-0.12.4/util/PaxHeaders/stanza.lua0000644000000000000000000000012014475665104015344 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.788764011 prosody-0.12.4/util/stanza.lua0000644000175000017500000003734314475665104017563 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local error = error; local t_insert = table.insert; local t_remove = table.remove; local t_concat = table.concat; local s_match = string.match; local tostring = tostring; local setmetatable = setmetatable; local getmetatable = getmetatable; local pairs = pairs; local ipairs = ipairs; local type = type; local s_gsub = string.gsub; local s_sub = string.sub; local s_find = string.find; local valid_utf8 = require "util.encodings".utf8.valid; local do_pretty_printing, termcolours = pcall(require, "util.termcolours"); local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas"; local _ENV = nil; -- luacheck: std none local stanza_mt = { __name = "stanza" }; stanza_mt.__index = stanza_mt; -- Basic check for valid XML character data. -- Disallow control characters. -- Tab U+09 and newline U+0A are allowed. -- For attributes, allow the \1 separator between namespace and name. local function valid_xml_cdata(str, attr) return not s_find(str, attr and "[^\1\9\10\13\20-\255]" or "[^\9\10\13\20-\255]"); end local function check_name(name, name_type) if type(name) ~= "string" then error("invalid "..name_type.." name: expected string, got "..type(name)); elseif #name == 0 then error("invalid "..name_type.." name: empty string"); elseif s_find(name, "[<>& '\"]") then error("invalid "..name_type.." name: contains invalid characters"); elseif not valid_xml_cdata(name, name_type == "attribute") then error("invalid "..name_type.." name: contains control characters"); elseif not valid_utf8(name) then error("invalid "..name_type.." name: contains invalid utf8"); end end local function check_text(text, text_type) if type(text) ~= "string" then error("invalid "..text_type.." value: expected string, got "..type(text)); elseif not valid_xml_cdata(text, false) then error("invalid "..text_type.." value: contains control characters"); elseif not valid_utf8(text) then error("invalid "..text_type.." value: contains invalid utf8"); end end local function check_attr(attr) if attr ~= nil then if type(attr) ~= "table" then error("invalid attributes: expected table, got "..type(attr)); end for k, v in pairs(attr) do check_name(k, "attribute"); check_text(v, "attribute"); end end end local function new_stanza(name, attr, namespaces) check_name(name, "tag"); check_attr(attr); local stanza = { name = name, attr = attr or {}, namespaces = namespaces, tags = {} }; return setmetatable(stanza, stanza_mt); end local function is_stanza(s) return getmetatable(s) == stanza_mt; end function stanza_mt:query(xmlns) return self:tag("query", { xmlns = xmlns }); end function stanza_mt:body(text, attr) return self:text_tag("body", text, attr); end function stanza_mt:text_tag(name, text, attr, namespaces) return self:tag(name, attr, namespaces):text(text):up(); end function stanza_mt:tag(name, attr, namespaces) local s = new_stanza(name, attr, namespaces); local last_add = self.last_add; if not last_add then last_add = {}; self.last_add = last_add; end (last_add[#last_add] or self):add_direct_child(s); t_insert(last_add, s); return self; end function stanza_mt:text(text) if text ~= nil and text ~= "" then local last_add = self.last_add; (last_add and last_add[#last_add] or self):add_direct_child(text); end return self; end function stanza_mt:up() local last_add = self.last_add; if last_add then t_remove(last_add); end return self; end function stanza_mt:at_top() return self.last_add == nil or #self.last_add == 0 end function stanza_mt:reset() self.last_add = nil; return self; end function stanza_mt:add_direct_child(child) if is_stanza(child) then t_insert(self.tags, child); t_insert(self, child); else check_text(child, "text"); t_insert(self, child); end end function stanza_mt:add_child(child) local last_add = self.last_add; (last_add and last_add[#last_add] or self):add_direct_child(child); return self; end function stanza_mt:remove_children(name, xmlns) xmlns = xmlns or self.attr.xmlns; return self:maptags(function (tag) if (not name or tag.name == name) and tag.attr.xmlns == xmlns then return nil; end return tag; end); end function stanza_mt:get_child(name, xmlns) for _, child in ipairs(self.tags) do if (not name or child.name == name) and ((not xmlns and self.attr.xmlns == child.attr.xmlns) or child.attr.xmlns == xmlns) then return child; end end return nil; end function stanza_mt:get_child_text(name, xmlns) local tag = self:get_child(name, xmlns); if tag then return tag:get_text(); end return nil; end function stanza_mt:child_with_name(name) for _, child in ipairs(self.tags) do if child.name == name then return child; end end return nil; end function stanza_mt:child_with_ns(ns) for _, child in ipairs(self.tags) do if child.attr.xmlns == ns then return child; end end return nil; end function stanza_mt:get_child_with_attr(name, xmlns, attr_name, attr_value, normalize) for tag in self:childtags(name, xmlns) do if (normalize and normalize(tag.attr[attr_name]) or tag.attr[attr_name]) == attr_value then return tag; end end return nil; end function stanza_mt:children() local i = 0; return function (a) i = i + 1 return a[i]; end, self, i; end function stanza_mt:childtags(name, xmlns) local tags = self.tags; local start_i, max_i = 1, #tags; return function () for i = start_i, max_i do local v = tags[i]; if (not name or v.name == name) and ((not xmlns and self.attr.xmlns == v.attr.xmlns) or v.attr.xmlns == xmlns) then start_i = i+1; return v; end end end; end function stanza_mt:maptags(callback) local tags, curr_tag = self.tags, 1; local n_children, n_tags = #self, #tags; local max_iterations = n_children + 1; local i = 1; while curr_tag <= n_tags and n_tags > 0 do if self[i] == tags[curr_tag] then local ret = callback(self[i]); if ret == nil then t_remove(self, i); t_remove(tags, curr_tag); n_children = n_children - 1; n_tags = n_tags - 1; i = i - 1; curr_tag = curr_tag - 1; else self[i] = ret; tags[curr_tag] = ret; end curr_tag = curr_tag + 1; end i = i + 1; if i > max_iterations then -- COMPAT: Hopefully temporary guard against #981 while we -- figure out the root cause error("Invalid stanza state! Please report this error."); end end return self; end function stanza_mt:find(path) local pos = 1; local len = #path + 1; repeat local xmlns, name, text; local char = s_sub(path, pos, pos); if char == "@" then return self.attr[s_sub(path, pos + 1)]; elseif char == "{" then xmlns, pos = s_match(path, "^([^}]+)}()", pos + 1); end name, text, pos = s_match(path, "^([^@/#]*)([/#]?)()", pos); name = name ~= "" and name or nil; if pos == len then if text == "#" then return self:get_child_text(name, xmlns); end return self:get_child(name, xmlns); end self = self:get_child(name, xmlns); until not self end local function _clone(stanza, only_top) local attr, tags = {}, {}; for k,v in pairs(stanza.attr) do attr[k] = v; end local old_namespaces, namespaces = stanza.namespaces; if old_namespaces then namespaces = {}; for k,v in pairs(old_namespaces) do namespaces[k] = v; end end local new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags }; if not only_top then for i=1,#stanza do local child = stanza[i]; if child.name then child = _clone(child); t_insert(tags, child); end t_insert(new, child); end end return setmetatable(new, stanza_mt); end local function clone(stanza, only_top) if not is_stanza(stanza) then error("bad argument to clone: expected stanza, got "..type(stanza)); end return _clone(stanza, only_top); end local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; local function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end local function _dostring(t, buf, self, _xml_escape, parentns) local nsid = 0; local name = t.name t_insert(buf, "<"..name); for k, v in pairs(t.attr) do if s_find(k, "\1", 1, true) then local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$"); nsid = nsid + 1; t_insert(buf, " xmlns:ns"..nsid.."='".._xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='".._xml_escape(v).."'"); elseif not(k == "xmlns" and v == parentns) then t_insert(buf, " "..k.."='".._xml_escape(v).."'"); end end local len = #t; if len == 0 then t_insert(buf, "/>"); else t_insert(buf, ">"); for n=1,len do local child = t[n]; if child.name then self(child, buf, self, _xml_escape, t.attr.xmlns); else t_insert(buf, _xml_escape(child)); end end t_insert(buf, ""); end end function stanza_mt.__tostring(t) local buf = {}; _dostring(t, buf, _dostring, xml_escape, nil); return t_concat(buf); end function stanza_mt.top_tag(t) local top_tag_clone = clone(t, true); return tostring(top_tag_clone):sub(1,-3)..">"; end function stanza_mt.get_text(t) if #t.tags == 0 then return t_concat(t); end return nil; end function stanza_mt.get_error(stanza) local error_type, condition, text, extra_tag; local error_tag = stanza:get_child("error"); if not error_tag then return nil, nil, nil, nil; end error_type = error_tag.attr.type; for _, child in ipairs(error_tag.tags) do if child.attr.xmlns == xmlns_stanzas then if not text and child.name == "text" then text = child:get_text(); elseif not condition then condition = child.name; end else extra_tag = child; end if condition and text and extra_tag then break; end end return error_type, condition or "undefined-condition", text, extra_tag; end local function preserialize(stanza) local s = { name = stanza.name, attr = stanza.attr }; for _, child in ipairs(stanza) do if type(child) == "table" then t_insert(s, preserialize(child)); else t_insert(s, child); end end return s; end stanza_mt.__freeze = preserialize; local function deserialize(serialized) -- Set metatable if serialized then local attr = serialized.attr; local attrx = {}; for att, val in pairs(attr) do if type(att) == "string" then if s_find(att, "|", 1, true) and not s_find(att, "\1", 1, true) then local ns,na = s_match(att, "^([^|]+)|(.+)$"); attrx[ns.."\1"..na] = val; else attrx[att] = val; end end end local stanza = new_stanza(serialized.name, attrx); for _, child in ipairs(serialized) do if type(child) == "table" then stanza:add_direct_child(deserialize(child)); elseif type(child) == "string" then stanza:add_direct_child(child); end end return stanza; end end local function message(attr, body) if not body then return new_stanza("message", attr); else return new_stanza("message", attr):text_tag("body", body); end end local function iq(attr) if not attr then error("iq stanzas require id and type attributes"); end if not attr.id then error("iq stanzas require an id attribute"); end if not attr.type then error("iq stanzas require a type attribute"); end return new_stanza("iq", attr); end local function reply(orig) if not is_stanza(orig) then error("bad argument to reply: expected stanza, got "..type(orig)); end return new_stanza(orig.name, { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) }); end local xmpp_stanzas_attr = { xmlns = xmlns_stanzas }; local function error_reply(orig, error_type, condition, error_message, error_by) if not is_stanza(orig) then error("bad argument to error_reply: expected stanza, got "..type(orig)); elseif orig.attr.type == "error" then error("bad argument to error_reply: got stanza of type error which must not be replied to"); end local t = reply(orig); t.attr.type = "error"; local extra; if type(error_type) == "table" then -- an util.error or similar object if type(error_type.extra) == "table" then extra = error_type.extra; end if type(error_type.context) == "table" and type(error_type.context.by) == "string" then error_by = error_type.context.by; end error_type, condition, error_message = error_type.type, error_type.condition, error_type.text; end if t.attr.from == error_by then error_by = nil; end t:tag("error", {type = error_type, by = error_by}) --COMPAT: Some day xmlns:stanzas goes here :tag(condition, xmpp_stanzas_attr); if extra and condition == "gone" and type(extra.uri) == "string" then t:text(extra.uri); end t:up(); if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end if extra and is_stanza(extra.tag) then t:add_child(extra.tag); elseif extra and extra.namespace and extra.condition then t:tag(extra.condition, { xmlns = extra.namespace }):up(); end return t; -- stanza ready for adding app-specific errors end local function presence(attr) return new_stanza("presence", attr); end local pretty; if do_pretty_printing then local getstyle, getstring = termcolours.getstyle, termcolours.getstring; local blue1 = getstyle("1b3967"); local blue2 = getstyle("13b5ea"); local green1 = getstyle("439639"); local green2 = getstyle("a0ce67"); local orange1 = getstyle("d9541e"); local orange2 = getstyle("e96d1f"); local attr_replace = ( getstring(green2, "%1") .. -- attr name getstring(green1, "%2") .. -- equal getstring(orange1, "%3") .. -- quote getstring(orange2, "%4") .. -- attr value getstring(orange1, "%5") -- quote ); local text_replace = ( getstring(green1, "%1") .. -- & getstring(green2, "%2") .. -- amp getstring(green1, "%3") -- ; ); function pretty(s) -- Tag soup color -- Outer gsub call takes each , applies colour to the brackets, the -- tag name, then applies one inner gsub call to colour the attributes and -- another for any text content. return (s:gsub("(<[?/]?)([^ >/?]*)(.-)([?/]?>)([^<]*)", function(opening_bracket, tag_name, attrs, closing_bracket, content) return getstring(blue1, opening_bracket)..getstring(blue2, tag_name).. attrs:gsub("([^=]+)(=)([\"'])(.-)([\"'])", attr_replace) .. getstring(blue1, closing_bracket) .. content:gsub("(&#?)(%w+)(;)", text_replace); end, 100)); end function stanza_mt.pretty_print(t) return pretty(tostring(t)); end function stanza_mt.pretty_top_tag(t) return pretty(t:top_tag()); end else -- Sorry, fresh out of colours for you guys ;) stanza_mt.pretty_print = stanza_mt.__tostring; stanza_mt.pretty_top_tag = stanza_mt.top_tag; end function stanza_mt.indent(t, level, indent) if #t == 0 or (#t == 1 and type(t[1]) == "string") then -- Empty nodes wouldn't have any indentation -- Text-only nodes are preserved as to not alter the text content -- Optimization: Skip clone of these since we don't alter them return t; end indent = indent or "\t"; level = level or 1; local tag = clone(t, true); for child in t:children() do if type(child) == "string" then -- Already indented text would look weird but let's ignore that for now. if child:find("%S") then tag:text("\n" .. indent:rep(level)); tag:text(child); end elseif is_stanza(child) then tag:text("\n" .. indent:rep(level)); tag:add_direct_child(child:indent(level+1, indent)); end end -- before the closing tag tag:text("\n" .. indent:rep((level-1))); return tag; end return { stanza_mt = stanza_mt; stanza = new_stanza; is_stanza = is_stanza; preserialize = preserialize; deserialize = deserialize; clone = clone; message = message; iq = iq; reply = reply; error_reply = error_reply; presence = presence; xml_escape = xml_escape; pretty_print = pretty; }; prosody-0.12.4/util/PaxHeaders/startup.lua0000644000000000000000000000012014475665104015546 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.788764011 prosody-0.12.4/util/startup.lua0000644000175000017500000005242614475665104017764 0ustar00prosodyprosody00000000000000-- Ignore the CFG_* variables -- luacheck: ignore 113/CFG_CONFIGDIR 113/CFG_SOURCEDIR 113/CFG_DATADIR 113/CFG_PLUGINDIR local startup = {}; local prosody = { events = require "util.events".new() }; local logger = require "util.logger"; local log = logger.init("startup"); local parse_args = require "util.argparse".parse; local config = require "core.configmanager"; local config_warnings; local dependencies = require "util.dependencies"; local original_logging_config; local default_gc_params = { mode = "incremental"; -- Incremental mode defaults threshold = 105, speed = 500; -- Generational mode defaults minor_threshold = 20, major_threshold = 50; }; local arg_settigs = { prosody = { short_params = { D = "daemonize"; F = "no-daemonize", h = "help", ["?"] = "help" }; value_params = { config = true }; }; prosodyctl = { short_params = { v = "verbose", h = "help", ["?"] = "help" }; value_params = { config = true }; }; } function startup.parse_args(profile) local opts, err, where = parse_args(arg, arg_settigs[profile or prosody.process_type] or profile); if not opts then if err == "param-not-found" then print("Unknown command-line option: "..tostring(where)); if prosody.process_type == "prosody" then print("Perhaps you meant to use prosodyctl instead?"); end elseif err == "missing-value" then print("Expected a value to follow command-line option: "..where); end os.exit(1); end if prosody.process_type == "prosody" then if #arg > 0 then print("Unrecognized option: "..arg[1]); print("(Did you mean 'prosodyctl "..arg[1].."'?)"); print(""); end if opts.help or #arg > 0 then print("prosody [ -D | -F ] [ --config /path/to/prosody.cfg.lua ]"); print(" -D, --daemonize Run in the background") print(" -F, --no-daemonize Run in the foreground") print(" --config FILE Specify config file") os.exit(0); end end prosody.opts = opts; end function startup.read_config() local filenames = {}; local filename; if prosody.opts.config then table.insert(filenames, prosody.opts.config); if CFG_CONFIGDIR then table.insert(filenames, CFG_CONFIGDIR.."/"..prosody.opts.config); end elseif os.getenv("PROSODY_CONFIG") then -- Passed by prosodyctl table.insert(filenames, os.getenv("PROSODY_CONFIG")); else table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua"); end for _,_filename in ipairs(filenames) do filename = _filename; local file = io.open(filename); if file then file:close(); prosody.config_file = filename; prosody.paths.config = filename:match("^(.*)[\\/][^\\/]*$"); CFG_CONFIGDIR = prosody.paths.config; -- luacheck: ignore 111 break; end end prosody.config_file = filename local ok, level, err = config.load(filename); if not ok then print("\n"); print("**************************"); if level == "parser" then print("A problem occurred while reading the config file "..filename); print(""); local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)"); if err:match("chunk has too many syntax levels$") then print("An Include statement in a config file is including an already-included"); print("file and causing an infinite loop. An Include statement in a config file is..."); else print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err))); end print(""); elseif level == "file" then print("Prosody was unable to find the configuration file."); print("We looked for: "..filename); print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist"); print("Copy or rename it to prosody.cfg.lua and edit as necessary."); end print("More help on configuring Prosody can be found at https://prosody.im/doc/configure"); print("Good luck!"); print("**************************"); print(""); os.exit(1); elseif err and #err > 0 then config_warnings = err; end prosody.config_loaded = true; end function startup.check_dependencies() if not dependencies.check_dependencies() then os.exit(1); end end -- luacheck: globals socket server function startup.load_libraries() -- Load socket framework -- luacheck: ignore 111/server 111/socket require "util.import" socket = require "socket"; server = require "net.server" end function startup.init_logging() -- Initialize logging local loggingmanager = require "core.loggingmanager" loggingmanager.reload_logging(); prosody.events.add_handler("config-reloaded", function () prosody.events.fire_event("reopen-log-files"); end); prosody.events.add_handler("reopen-log-files", function () loggingmanager.reload_logging(); prosody.events.fire_event("logging-reloaded"); end); end function startup.log_startup_warnings() dependencies.log_warnings(); if config_warnings then for _, warning in ipairs(config_warnings) do log("warn", "Configuration warning: %s", warning); end end end function startup.sanity_check() for host, host_config in pairs(config.getconfig()) do if host ~= "*" and host_config.enabled ~= false and not host_config.component_module then return; end end log("error", "No enabled VirtualHost entries found in the config file."); log("error", "At least one active host is required for Prosody to function. Exiting..."); os.exit(1); end function startup.sandbox_require() -- Replace require() with one that doesn't pollute _G, required -- for neat sandboxing of modules -- luacheck: ignore 113/getfenv 111/require local _realG = _G; local _real_require = require; local getfenv = getfenv or function (f) -- FIXME: This is a hack to replace getfenv() in Lua 5.2 local name, env = debug.getupvalue(debug.getinfo(f or 1).func, 1); if name == "_ENV" then return env; end end function require(...) -- luacheck: ignore 121 local curr_env = getfenv(2); local curr_env_mt = getmetatable(curr_env); local _realG_mt = getmetatable(_realG); if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then local old_newindex, old_index; old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env; old_index, _realG_mt.__index = _realG_mt.__index, function (_G, k) -- luacheck: ignore 212/_G return rawget(curr_env, k); end; local ret = _real_require(...); _realG_mt.__newindex = old_newindex; _realG_mt.__index = old_index; return ret; end return _real_require(...); end end function startup.set_function_metatable() local mt = {}; function mt.__index(f, upvalue) local i, name, value = 0; repeat i = i + 1; name, value = debug.getupvalue(f, i); until name == upvalue or name == nil; return value; end function mt.__newindex(f, upvalue, value) local i, name = 0; repeat i = i + 1; name = debug.getupvalue(f, i); until name == upvalue or name == nil; if name then debug.setupvalue(f, i, value); end end function mt.__tostring(f) local info = debug.getinfo(f, "Su"); local n_params = info.nparams or 0; for i = 1, n_params do info[i] = debug.getlocal(f, i); end if info.isvararg then info[n_params+1] = "..."; end return ("function<%s:%d>(%s)"):format(info.short_src:match("[^\\/]*$"), info.linedefined, table.concat(info, ", ")); end debug.setmetatable(function() end, mt); end function startup.detect_platform() prosody.platform = "unknown"; if os.getenv("WINDIR") then prosody.platform = "windows"; elseif package.config:sub(1,1) == "/" then prosody.platform = "posix"; end end function startup.detect_installed() prosody.installed = nil; if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then prosody.installed = true; end end function startup.init_global_state() -- luacheck: ignore 121 prosody.bare_sessions = {}; prosody.full_sessions = {}; prosody.hosts = {}; -- COMPAT: These globals are deprecated -- luacheck: ignore 111/bare_sessions 111/full_sessions 111/hosts bare_sessions = prosody.bare_sessions; full_sessions = prosody.full_sessions; hosts = prosody.hosts; prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR or ".", plugins = CFG_PLUGINDIR or "plugins", data = "data" }; prosody.arg = _G.arg; _G.log = logger.init("general"); prosody.log = logger.init("general"); startup.detect_platform(); startup.detect_installed(); _G.prosody = prosody; end function startup.setup_datadir() prosody.paths.data = config.get("*", "data_path") or CFG_DATADIR or "data"; end function startup.setup_plugindir() local custom_plugin_paths = config.get("*", "plugin_paths"); local path_sep = package.config:sub(3,3); if custom_plugin_paths then -- path1;path2;path3;defaultpath... -- luacheck: ignore 111 CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end end function startup.setup_plugin_install_path() local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; local path_sep = package.config:sub(3,3); installer_plugin_path = config.resolve_relative_path(CFG_DATADIR or "data", installer_plugin_path); require"util.paths".complement_lua_path(installer_plugin_path); -- luacheck: ignore 111 CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.installer = installer_plugin_path; prosody.paths.plugins = CFG_PLUGINDIR; end function startup.chdir() if prosody.installed then local lfs = require "lfs"; -- Ensure paths are absolute, not relative to the working directory which we're about to change local cwd = lfs.currentdir(); prosody.paths.source = config.resolve_relative_path(cwd, prosody.paths.source); prosody.paths.config = config.resolve_relative_path(cwd, prosody.paths.config); prosody.paths.data = config.resolve_relative_path(cwd, prosody.paths.data); -- Change working directory to data path. lfs.chdir(prosody.paths.data); end end function startup.add_global_prosody_functions() -- Function to reload the config file function prosody.reload_config() log("info", "Reloading configuration file"); prosody.events.fire_event("reloading-config"); local ok, level, err = config.load(prosody.config_file); if not ok then if level == "parser" then log("error", "There was an error parsing the configuration file: %s", err); elseif level == "file" then log("error", "Couldn't read the config file when trying to reload: %s", err); end else prosody.events.fire_event("config-reloaded", { filename = prosody.config_file, config = config.getconfig(), }); end return ok, (err and tostring(level)..": "..tostring(err)) or nil; end -- Function to reopen logfiles function prosody.reopen_logfiles() log("info", "Re-opening log files"); prosody.events.fire_event("reopen-log-files"); end -- Function to initiate prosody shutdown function prosody.shutdown(reason, code) log("info", "Shutting down: %s", reason or "unknown reason"); prosody.shutdown_reason = reason; prosody.shutdown_code = code; prosody.events.fire_event("server-stopping", { reason = reason; code = code; }); prosody.main_thread:run(startup.shutdown); end end function startup.load_secondary_libraries() --- Load and initialise core modules require "util.xmppstream" require "core.stanza_router" require "core.statsmanager" require "core.hostmanager" require "core.portmanager" require "core.modulemanager" require "core.usermanager" require "core.rostermanager" require "core.sessionmanager" package.loaded['core.componentmanager'] = setmetatable({},{__index=function() -- COMPAT which version? log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[ \t]*([^\n]*)")); return function() end end}); require "util.array" require "util.datetime" require "util.iterators" require "util.timer" require "util.helpers" pcall(require, "util.signal") -- Not on Windows -- Commented to protect us from -- the second kind of people --[[ pcall(require, "remdebug.engine"); if remdebug then remdebug.engine.start() end ]] require "util.stanza" require "util.jid" end function startup.init_http_client() local http = require "net.http" local config_ssl = config.get("*", "ssl") or {} local https_client = config.get("*", "client_https_ssl") http.default.options.sslctx = require "core.certmanager".create_context("client_https port 0", "client", { capath = config_ssl.capath, cafile = config_ssl.cafile, verify = "peer", }, https_client); http.default.options.use_dane = config.get("*", "use_dane") end function startup.init_promise() local promise = require "util.promise"; local timer = require "util.timer"; promise.set_nexttick(function(f) return timer.add_task(0, f); end); end function startup.init_async() local async = require "util.async"; local timer = require "util.timer"; async.set_nexttick(function(f) return timer.add_task(0, f); end); async.set_schedule_function(timer.add_task); end function startup.init_data_store() require "core.storagemanager"; end function startup.prepare_to_start() log("info", "Prosody is using the %s backend for connection handling", server.get_backend()); -- Signal to modules that we are ready to start prosody.events.fire_event("server-starting"); prosody.start_time = os.time(); end function startup.init_global_protection() -- Catch global accesses -- luacheck: ignore 212/t local locked_globals_mt = { __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end; __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end; }; function prosody.unlock_globals() setmetatable(_G, nil); end function prosody.lock_globals() setmetatable(_G, locked_globals_mt); end -- And lock now... prosody.lock_globals(); end function startup.read_version() -- Try to determine version local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version"); prosody.version = "unknown"; if version_file then prosody.version = version_file:read("*a"):gsub("%s*$", ""); version_file:close(); if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then prosody.version = "hg:"..prosody.version; end else local hg = require"util.mercurial"; local hgid = hg.check_id(CFG_SOURCEDIR or "."); if hgid then prosody.version = "hg:" .. hgid; end end end function startup.log_greeting() log("info", "Hello and welcome to Prosody version %s", prosody.version); end function startup.notify_started() prosody.events.fire_event("server-started"); end -- Override logging config (used by prosodyctl) function startup.force_console_logging() original_logging_config = config.get("*", "log"); local log_level = os.getenv("PROSODYCTL_LOG_LEVEL"); if not log_level then if prosody.opts.verbose then log_level = "debug"; elseif prosody.opts.quiet then log_level = "error"; elseif prosody.opts.silent then config.set("*", "log", {}); -- ssssshush! return end end config.set("*", "log", { { levels = { min = log_level or "info" }, to = "console" } }); end function startup.switch_user() -- Switch away from root and into the prosody user -- -- NOTE: This function is only used by prosodyctl. -- The prosody process is built with the assumption that -- it is already started as the appropriate user. local want_pposix_version = "0.4.0"; local have_pposix, pposix = pcall(require, "util.pposix"); if have_pposix and pposix then if pposix._VERSION ~= want_pposix_version then print(string.format("Unknown version (%s) of binary pposix module, expected %s", tostring(pposix._VERSION), want_pposix_version)); os.exit(1); end prosody.current_uid = pposix.getuid(); local arg_root = prosody.opts.root; if prosody.current_uid == 0 and config.get("*", "run_as_root") ~= true and not arg_root then -- We haz root! local desired_user = config.get("*", "prosody_user") or "prosody"; local desired_group = config.get("*", "prosody_group") or desired_user; local ok, err = pposix.setgid(desired_group); if ok then ok, err = pposix.initgroups(desired_user); end if ok then ok, err = pposix.setuid(desired_user); if ok then -- Yay! prosody.switched_user = true; end end if not prosody.switched_user then -- Boo! print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user).."'/'"..tostring(desired_group).."': "..tostring(err)); elseif prosody.config_file then -- Make sure the Prosody user can read the config local conf, err, errno = io.open(prosody.config_file); --luacheck: ignore 211/errno if conf then conf:close(); else print("The config file is not readable by the '"..desired_user.."' user."); print("Prosody will not be able to read it."); print("Error was "..err); os.exit(1); end end end -- Set our umask to protect data files pposix.umask(config.get("*", "umask") or "027"); pposix.setenv("HOME", prosody.paths.data); pposix.setenv("PROSODY_CONFIG", prosody.config_file); else print("Error: Unable to load pposix module. Check that Prosody is installed correctly.") print("For more help send the below error to us through https://prosody.im/discuss"); print(tostring(pposix)) os.exit(1); end end function startup.check_unwriteable() local function test_writeable(filename) local f, err = io.open(filename, "a"); if not f then return false, err; end f:close(); return true; end local unwriteable_files = {}; if type(original_logging_config) == "string" and original_logging_config:sub(1,1) ~= "*" then local ok, err = test_writeable(original_logging_config); if not ok then table.insert(unwriteable_files, err); end elseif type(original_logging_config) == "table" then for _, rule in ipairs(original_logging_config) do if rule.filename then local ok, err = test_writeable(rule.filename); if not ok then table.insert(unwriteable_files, err); end end end end if #unwriteable_files > 0 then print("One of more of the Prosody log files are not"); print("writeable, please correct the errors and try"); print("starting prosodyctl again."); print(""); for _, err in ipairs(unwriteable_files) do print(err); end print(""); os.exit(1); end end function startup.init_gc() -- Apply garbage collector settings from the config file local gc = require "util.gc"; local gc_settings = config.get("*", "gc") or { mode = default_gc_params.mode }; local ok, err = gc.configure(gc_settings, default_gc_params); if not ok then log("error", "Failed to apply GC configuration: %s", err); return nil, err; end return true; end function startup.init_errors() require "util.error".configure(config.get("*", "error_library") or {}); end function startup.make_host(hostname) return { type = "local", events = prosody.events, modules = {}, sessions = {}, users = require "core.usermanager".new_null_provider(hostname) }; end function startup.make_dummy_hosts() -- When running under prosodyctl, we don't want to -- fully initialize the server, so we populate prosody.hosts -- with just enough things for most code to work correctly -- luacheck: ignore 122/hosts prosody.core_post_stanza = function () end; -- TODO: mod_router! for hostname in pairs(config.getconfig()) do prosody.hosts[hostname] = startup.make_host(hostname); end end function startup.cleanup() prosody.log("info", "Shutdown status: Cleaning up"); prosody.events.fire_event("server-cleanup"); end function startup.shutdown() prosody.log("info", "Shutting down..."); startup.cleanup(); prosody.events.fire_event("server-stopped"); prosody.log("info", "Shutdown complete"); prosody.log("debug", "Shutdown reason was: %s", prosody.shutdown_reason or "not specified"); prosody.log("debug", "Exiting with status code: %d", prosody.shutdown_code or 0); server.setquitting(true); end function startup.exit() os.exit(prosody.shutdown_code, true); end -- prosodyctl only function startup.prosodyctl() prosody.process_type = "prosodyctl"; startup.parse_args(); startup.init_global_state(); startup.read_config(); startup.force_console_logging(); startup.init_logging(); startup.init_gc(); startup.init_errors(); startup.setup_plugindir(); startup.setup_plugin_install_path(); startup.setup_datadir(); startup.chdir(); startup.read_version(); startup.switch_user(); startup.check_dependencies(); startup.log_startup_warnings(); startup.check_unwriteable(); startup.load_libraries(); startup.init_http_client(); startup.make_dummy_hosts(); end function startup.prosody() -- These actions are in a strict order, as many depend on -- previous steps to have already been performed prosody.process_type = "prosody"; startup.parse_args(); startup.init_global_state(); startup.read_config(); startup.init_logging(); startup.init_gc(); startup.init_errors(); startup.sanity_check(); startup.sandbox_require(); startup.set_function_metatable(); startup.check_dependencies(); startup.load_libraries(); startup.setup_plugindir(); startup.setup_plugin_install_path(); startup.setup_datadir(); startup.chdir(); startup.add_global_prosody_functions(); startup.read_version(); startup.log_greeting(); startup.log_startup_warnings(); startup.load_secondary_libraries(); startup.init_promise(); startup.init_async(); startup.init_http_client(); startup.init_data_store(); startup.init_global_protection(); startup.prepare_to_start(); startup.notify_started(); end return startup; prosody-0.12.4/util/PaxHeaders/statistics.lua0000644000000000000000000000012014475665104016236 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.788764011 prosody-0.12.4/util/statistics.lua0000644000175000017500000000741114475665104020446 0ustar00prosodyprosody00000000000000local time = require "util.time".now; local new_metric_registry = require "util.openmetrics".new_metric_registry; local render_histogram_le = require "util.openmetrics".render_histogram_le; -- BEGIN of Metric implementations -- Gauges local gauge_metric_mt = {} gauge_metric_mt.__index = gauge_metric_mt local function new_gauge_metric() local metric = { value = 0 } setmetatable(metric, gauge_metric_mt) return metric end function gauge_metric_mt:set(value) self.value = value end function gauge_metric_mt:add(delta) self.value = self.value + delta end function gauge_metric_mt:reset() self.value = 0 end function gauge_metric_mt:iter_samples() local done = false return function(_s) if done then return nil, true end done = true return "", nil, _s.value end, self end -- Counters local counter_metric_mt = {} counter_metric_mt.__index = counter_metric_mt local function new_counter_metric() local metric = { _created = time(), value = 0, } setmetatable(metric, counter_metric_mt) return metric end function counter_metric_mt:set(value) self.value = value end function counter_metric_mt:add(value) self.value = (self.value or 0) + value end function counter_metric_mt:iter_samples() local step = 0 return function(_s) step = step + 1 if step == 1 then return "_created", nil, _s._created elseif step == 2 then return "_total", nil, _s.value else return nil, nil, true end end, self end function counter_metric_mt:reset() self.value = 0 end -- Histograms local histogram_metric_mt = {} histogram_metric_mt.__index = histogram_metric_mt local function new_histogram_metric(buckets) local metric = { _created = time(), _sum = 0, _count = 0, } -- the order of buckets matters unfortunately, so we cannot directly use -- the thresholds as table keys for i, threshold in ipairs(buckets) do metric[i] = { threshold = threshold, threshold_s = render_histogram_le(threshold), count = 0 } end setmetatable(metric, histogram_metric_mt) return metric end function histogram_metric_mt:sample(value) -- According to the I-D, values must be part of all buckets for i, bucket in pairs(self) do if "number" == type(i) and value <= bucket.threshold then bucket.count = bucket.count + 1 end end self._sum = self._sum + value self._count = self._count + 1 end function histogram_metric_mt:iter_samples() local key = nil return function (_s) local data key, data = next(_s, key) if key == "_created" or key == "_sum" or key == "_count" then return key, nil, data elseif key ~= nil then return "_bucket", {["le"] = data.threshold_s}, data.count else return nil, nil, nil end end, self end function histogram_metric_mt:reset() self._created = time() self._count = 0 self._sum = 0 for i, bucket in pairs(self) do if "number" == type(i) then bucket.count = 0 end end end -- Summary local summary_metric_mt = {} summary_metric_mt.__index = summary_metric_mt local function new_summary_metric() -- quantiles are not supported yet local metric = { _created = time(), _sum = 0, _count = 0, } setmetatable(metric, summary_metric_mt) return metric end function summary_metric_mt:sample(value) self._sum = self._sum + value self._count = self._count + 1 end function summary_metric_mt:iter_samples() local key = nil return function (_s) local data key, data = next(_s, key) return key, nil, data end, self end function summary_metric_mt:reset() self._created = time() self._count = 0 self._sum = 0 end local pull_backend = { gauge = new_gauge_metric, counter = new_counter_metric, histogram = new_histogram_metric, summary = new_summary_metric, } -- END of Metric implementations local function new() return { metric_registry = new_metric_registry(pull_backend), } end return { new = new; } prosody-0.12.4/util/PaxHeaders/statsd.lua0000644000000000000000000000012014475665104015346 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.788764011 prosody-0.12.4/util/statsd.lua0000644000175000017500000001457714475665104017571 0ustar00prosodyprosody00000000000000local socket = require "socket"; local time = require "util.time".now; local array = require "util.array"; local t_concat = table.concat; local new_metric_registry = require "util.openmetrics".new_metric_registry; local render_histogram_le = require "util.openmetrics".render_histogram_le; -- BEGIN of Metric implementations -- Gauges local gauge_metric_mt = {} gauge_metric_mt.__index = gauge_metric_mt local function new_gauge_metric(full_name, impl) local metric = { _full_name = full_name; _impl = impl; value = 0; } setmetatable(metric, gauge_metric_mt) return metric end function gauge_metric_mt:set(value) self.value = value self._impl:push_gauge(self._full_name, value) end function gauge_metric_mt:add(delta) self.value = self.value + delta self._impl:push_gauge(self._full_name, self.value) end function gauge_metric_mt:reset() self.value = 0 self._impl:push_gauge(self._full_name, 0) end function gauge_metric_mt.iter_samples() -- statsd backend does not support iteration. return function() return nil end end -- Counters local counter_metric_mt = {} counter_metric_mt.__index = counter_metric_mt local function new_counter_metric(full_name, impl) local metric = { _full_name = full_name, _impl = impl, value = 0, } setmetatable(metric, counter_metric_mt) return metric end function counter_metric_mt:set(value) local delta = value - self.value self.value = value self._impl:push_counter_delta(self._full_name, delta) end function counter_metric_mt:add(value) self.value = (self.value or 0) + value self._impl:push_counter_delta(self._full_name, value) end function counter_metric_mt.iter_samples() -- statsd backend does not support iteration. return function() return nil end end function counter_metric_mt:reset() self.value = 0 end -- Histograms local histogram_metric_mt = {} histogram_metric_mt.__index = histogram_metric_mt local function new_histogram_metric(buckets, full_name, impl) -- NOTE: even though the more or less proprietary dogstatsd has Its own -- histogram implementation, we push the individual buckets in this statsd -- backend for both consistency and compatibility across statsd -- implementations. local metric = { _sum_name = full_name..".sum", _count_name = full_name..".count", _impl = impl, _created = time(), _sum = 0, _count = 0, } -- the order of buckets matters unfortunately, so we cannot directly use -- the thresholds as table keys for i, threshold in ipairs(buckets) do local threshold_s = render_histogram_le(threshold) metric[i] = { threshold = threshold, threshold_s = threshold_s, count = 0, _full_name = full_name..".bucket."..(threshold_s:gsub("%.", "_")), } end setmetatable(metric, histogram_metric_mt) return metric end function histogram_metric_mt:sample(value) -- According to the I-D, values must be part of all buckets for i, bucket in pairs(self) do if "number" == type(i) and value <= bucket.threshold then bucket.count = bucket.count + 1 self._impl:push_counter_delta(bucket._full_name, 1) end end self._sum = self._sum + value self._count = self._count + 1 self._impl:push_gauge(self._sum_name, self._sum) self._impl:push_counter_delta(self._count_name, 1) end function histogram_metric_mt.iter_samples() -- statsd backend does not support iteration. return function() return nil end end function histogram_metric_mt:reset() self._created = time() self._count = 0 self._sum = 0 for i, bucket in pairs(self) do if "number" == type(i) then bucket.count = 0 end end self._impl:push_gauge(self._sum_name, self._sum) end -- Summaries local summary_metric_mt = {} summary_metric_mt.__index = summary_metric_mt local function new_summary_metric(full_name, impl) local metric = { _sum_name = full_name..".sum", _count_name = full_name..".count", _impl = impl, } setmetatable(metric, summary_metric_mt) return metric end function summary_metric_mt:sample(value) self._impl:push_counter_delta(self._sum_name, value) self._impl:push_counter_delta(self._count_name, 1) end function summary_metric_mt.iter_samples() -- statsd backend does not support iteration. return function() return nil end end function summary_metric_mt.reset() end -- BEGIN of statsd client implementation local statsd_mt = {} statsd_mt.__index = statsd_mt function statsd_mt:cork() self.corked = true self.cork_buffer = self.cork_buffer or {} end function statsd_mt:uncork() self.corked = false self:_flush_cork_buffer() end function statsd_mt:_flush_cork_buffer() local buffer = self.cork_buffer for metric_name, value in pairs(buffer) do self:_send_gauge(metric_name, value) buffer[metric_name] = nil end end function statsd_mt:push_gauge(metric_name, value) if self.corked then self.cork_buffer[metric_name] = value else self:_send_gauge(metric_name, value) end end function statsd_mt:_send_gauge(metric_name, value) self:_send(self.prefix..metric_name..":"..tostring(value).."|g") end function statsd_mt:push_counter_delta(metric_name, delta) self:_send(self.prefix..metric_name..":"..tostring(delta).."|c") end function statsd_mt:_send(s) return self.sock:send(s) end -- END of statsd client implementation local function build_metric_name(family_name, labels) local parts = array { family_name } if labels then parts:append(labels) end return t_concat(parts, "/"):gsub("%.", "_"):gsub("/", ".") end local function new(config) if not config or not config.statsd_server then return nil, "No statsd server specified in the config, please see https://prosody.im/doc/statistics"; end local sock = socket.udp(); sock:setpeername(config.statsd_server, config.statsd_port or 8125); local prefix = (config.prefix or "prosody").."."; local impl = { metric_registry = nil; sock = sock; prefix = prefix; }; setmetatable(impl, statsd_mt) local backend = { gauge = function(family_name, labels) return new_gauge_metric(build_metric_name(family_name, labels), impl) end; counter = function(family_name, labels) return new_counter_metric(build_metric_name(family_name, labels), impl) end; histogram = function(buckets, family_name, labels) return new_histogram_metric(buckets, build_metric_name(family_name, labels), impl) end; summary = function(family_name, labels, extra) return new_summary_metric(build_metric_name(family_name, labels), impl, extra) end; }; impl.metric_registry = new_metric_registry(backend); return impl; end return { new = new; } prosody-0.12.4/util/PaxHeaders/template.lua0000644000000000000000000000012014475665104015657 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.788764011 prosody-0.12.4/util/template.lua0000644000175000017500000000553414475665104020073 0ustar00prosodyprosody00000000000000-- luacheck: ignore 213/i local stanza_mt = require "util.stanza".stanza_mt; local setmetatable = setmetatable; local pairs = pairs; local ipairs = ipairs; local error = error; local envload = require "util.envload".envload; local debug = debug; local t_remove = table.remove; local parse_xml = require "util.xml".parse; local _ENV = nil; -- luacheck: std none local function trim_xml(stanza) for i=#stanza,1,-1 do local child = stanza[i]; if child.name then trim_xml(child); else child = child:gsub("^%s*", ""):gsub("%s*$", ""); stanza[i] = child; if child == "" then t_remove(stanza, i); end end end end local function create_string_string(str) str = ("%q"):format(str); str = str:gsub("{([^}]*)}", function(s) return '"..(data["'..s..'"]or"").."'; end); return str; end local function create_attr_string(attr, xmlns) local str = '{'; for name,value in pairs(attr) do if name ~= "xmlns" or value ~= xmlns then str = str..("[%q]=%s;"):format(name, create_string_string(value)); end end return str..'}'; end local function create_clone_string(stanza, lookup, xmlns) if not lookup[stanza] then local s = ('setmetatable({name=%q,attr=%s,tags={'):format(stanza.name, create_attr_string(stanza.attr, xmlns)); -- add tags for i,tag in ipairs(stanza.tags) do s = s..create_clone_string(tag, lookup, stanza.attr.xmlns)..";"; end s = s..'};'; -- add children for i,child in ipairs(stanza) do if child.name then s = s..create_clone_string(child, lookup, stanza.attr.xmlns)..";"; else s = s..create_string_string(child)..";" end end s = s..'}, stanza_mt)'; s = s:gsub('%.%.""', ""):gsub('([=;])""%.%.', "%1"):gsub(';"";', ";"); -- strip empty strings local n = #lookup + 1; lookup[n] = s; lookup[stanza] = "_"..n; end return lookup[stanza]; end local function create_cloner(stanza, chunkname) local lookup = {}; local name = create_clone_string(stanza, lookup, ""); local src = "local setmetatable,stanza_mt=...;return function(data)"; for i=1,#lookup do src = src.."local _"..i.."="..lookup[i]..";"; end src = src.."return "..name..";end"; local f,err = envload(src, chunkname); if not f then error(err); end return f(setmetatable, stanza_mt); end local template_mt = { __tostring = function(t) return t.name end }; local function create_template(templates, text) local stanza, err = parse_xml(text); if not stanza then error(err); end trim_xml(stanza); local info = debug.getinfo(3, "Sl"); info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)"; local template = setmetatable({ apply = create_cloner(stanza, info), name = info, text = text }, template_mt); templates[text] = template; return template; end local templates = setmetatable({}, { __mode = 'k', __index = create_template }); return function(text) return templates[text]; end; prosody-0.12.4/util/PaxHeaders/termcolours.lua0000644000000000000000000000012014475665104016422 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.788764011 prosody-0.12.4/util/termcolours.lua0000644000175000017500000001046314475665104020633 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- -- luacheck: ignore 213/i local t_concat, t_insert = table.concat, table.insert; local char, format = string.char, string.format; local tonumber = tonumber; local ipairs = ipairs; local io_write = io.write; local m_floor = math.floor; local type = type; local setmetatable = setmetatable; local pairs = pairs; local windows; if os.getenv("WINDIR") then windows = require "util.windows"; end local orig_color = windows and windows.get_consolecolor and windows.get_consolecolor(); local _ENV = nil; -- luacheck: std none local stylemap = { reset = 0; bright = 1, dim = 2, underscore = 4, blink = 5, reverse = 7, hidden = 8; black = 30; red = 31; green = 32; yellow = 33; blue = 34; magenta = 35; cyan = 36; white = 37; ["black background"] = 40; ["red background"] = 41; ["green background"] = 42; ["yellow background"] = 43; ["blue background"] = 44; ["magenta background"] = 45; ["cyan background"] = 46; ["white background"] = 47; bold = 1, dark = 2, underline = 4, underlined = 4, normal = 0; } local winstylemap = { ["0"] = orig_color, -- reset ["1"] = 7+8, -- bold ["1;33"] = 2+4+8, -- bold yellow ["1;31"] = 4+8 -- bold red } local cssmap = { [1] = "font-weight: bold", [2] = "opacity: 0.5", [4] = "text-decoration: underline", [8] = "visibility: hidden", [30] = "color:black", [31] = "color:red", [32]="color:green", [33]="color:#FFD700", [34] = "color:blue", [35] = "color: magenta", [36] = "color:cyan", [37] = "color: white", [40] = "background-color:black", [41] = "background-color:red", [42]="background-color:green", [43]="background-color:yellow", [44] = "background-color:blue", [45] = "background-color: magenta", [46] = "background-color:cyan", [47] = "background-color: white"; }; local fmt_string = char(0x1B).."[%sm%s"..char(0x1B).."[0m"; local function getstring(style, text) if style then return format(fmt_string, style, text); else return text; end end local function gray(n) return m_floor(n*3/32)+0xe8; end local function color(r,g,b) if r == g and g == b then return gray(r); end r = m_floor(r*3/128); g = m_floor(g*3/128); b = m_floor(b*3/128); return 0x10 + ( r * 36 ) + ( g * 6 ) + ( b ); end local function hex2rgb(hex) local r = tonumber(hex:sub(1,2),16); local g = tonumber(hex:sub(3,4),16); local b = tonumber(hex:sub(5,6),16); return r,g,b; end setmetatable(stylemap, { __index = function(_, style) if type(style) == "string" and style:find("%x%x%x%x%x%x") == 1 then local g = style:sub(7) == " background" and "48;5;" or "38;5;"; return format("%s%d", g, color(hex2rgb(style))); end end } ); local csscolors = { red = "ff0000"; fuchsia = "ff00ff"; green = "008000"; white = "ffffff"; lime = "00ff00"; yellow = "ffff00"; purple = "800080"; blue = "0000ff"; aqua = "00ffff"; olive = "808000"; black = "000000"; navy = "000080"; teal = "008080"; silver = "c0c0c0"; maroon = "800000"; gray = "808080"; } for colorname, rgb in pairs(csscolors) do stylemap[colorname] = stylemap[colorname] or stylemap[rgb]; colorname, rgb = colorname .. " background", rgb .. " background" stylemap[colorname] = stylemap[colorname] or stylemap[rgb]; end local function getstyle(...) local styles, result = { ... }, {}; for i, style in ipairs(styles) do style = stylemap[style]; if style then t_insert(result, style); end end return t_concat(result, ";"); end local last = "0"; local function setstyle(style) style = style or "0"; if style ~= last then io_write("\27["..style.."m"); last = style; end end if windows then function setstyle(style) style = style or "0"; if style ~= last then windows.set_consolecolor(winstylemap[style] or orig_color); last = style; end end if not orig_color then function setstyle() end end end local function ansi2css(ansi_codes) if ansi_codes == "0" then return ""; end local css = {}; for code in ansi_codes:gmatch("[^;]+") do t_insert(css, cssmap[tonumber(code)]); end return ""; end local function tohtml(input) return input:gsub("\027%[(.-)m", ansi2css); end return { getstring = getstring; getstyle = getstyle; setstyle = setstyle; tohtml = tohtml; }; prosody-0.12.4/util/PaxHeaders/throttle.lua0000644000000000000000000000012014475665104015711 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.788764011 prosody-0.12.4/util/throttle.lua0000644000175000017500000000172714475665104020125 0ustar00prosodyprosody00000000000000 local gettime = require "util.time".now local setmetatable = setmetatable; local _ENV = nil; -- luacheck: std none local throttle = {}; local throttle_mt = { __index = throttle }; function throttle:update() local newt = gettime(); local elapsed = newt - self.t; self.t = newt; local balance = (self.rate * elapsed) + self.balance; if balance > self.max then self.balance = self.max; else self.balance = balance; end return self.balance; end function throttle:peek(cost) cost = cost or 1; return self.balance >= cost or self:update() >= cost; end function throttle:poll(cost, split) if self:peek(cost) then self.balance = self.balance - cost; return true; else local balance = self.balance; if split then self.balance = 0; end return false, balance, (cost-balance); end end local function create(max, period) return setmetatable({ rate = max / period, max = max, t = gettime(), balance = max }, throttle_mt); end return { create = create; }; prosody-0.12.4/util/PaxHeaders/timer.lua0000644000000000000000000000012014475665104015164 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.792763972 prosody-0.12.4/util/timer.lua0000644000175000017500000000622214475665104017373 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local indexedbheap = require "util.indexedbheap"; local log = require "util.logger".init("timer"); local server = require "net.server"; local get_time = require "util.time".now local type = type; local debug_traceback = debug.traceback; local tostring = tostring; local xpcall = require "util.xpcall".xpcall; local math_max = math.max; local pairs = pairs; if server.timer then -- The selected net.server implements this API, so defer to that return server.timer; end local _ENV = nil; -- luacheck: std none local _add_task = server.add_task; local _server_timer; local _active_timers = 0; local h = indexedbheap.create(); local params = {}; local next_time = nil; local function _traceback_handler(err) log("error", "Traceback[timer]: %s", debug_traceback(tostring(err), 2)); end local function _on_timer(now) local peek; local readd; while true do peek = h:peek(); if peek == nil or peek > now then break; end local _, callback, id = h:pop(); local param = params[id]; params[id] = nil; --item(now, id, _param); local success, err = xpcall(callback, _traceback_handler, now, id, param); if success and type(err) == "number" then if readd then readd[id] = { callback, err + now }; else readd = { [id] = { callback, err + now } }; end params[id] = param; end end if readd then for id,timer in pairs(readd) do h:insert(timer[1], timer[2], id); end peek = h:peek(); end if peek ~= nil and _active_timers > 1 and peek == next_time then -- Another instance of _on_timer already set next_time to the same value, -- so it should be safe to not renew this timer event peek = nil; else next_time = peek; end if peek then -- peek is the time of the next event return peek - now; end _active_timers = _active_timers - 1; end local function add_task(delay, callback, param) local current_time = get_time(); local event_time = current_time + delay; local id = h:insert(callback, event_time); params[id] = param; if next_time == nil or event_time < next_time then next_time = event_time; if _server_timer then _server_timer:close(); _server_timer = nil; else _active_timers = _active_timers + 1; end _server_timer = _add_task(next_time - current_time, _on_timer); end return id; end local function stop(id) params[id] = nil; local result, item, result_sync = h:remove(id); local peek = h:peek(); if peek ~= next_time and _server_timer then next_time = peek; _server_timer:close(); if next_time ~= nil then _server_timer = _add_task(math_max(next_time - get_time(), 0), _on_timer); end end return result, item, result_sync; end local function reschedule(id, delay) local current_time = get_time(); local event_time = current_time + delay; h:reprioritize(id, delay); if next_time == nil or event_time < next_time then next_time = event_time; _add_task(next_time - current_time, _on_timer); end return id; end return { add_task = add_task; stop = stop; reschedule = reschedule; }; prosody-0.12.4/util/PaxHeaders/uuid.lua0000644000000000000000000000012014475665104015012 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.792763972 prosody-0.12.4/util/uuid.lua0000644000175000017500000000150514475665104017220 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local random = require "util.random"; local random_bytes = random.bytes; local hex = require "util.hex".encode; local m_ceil = math.ceil; local function get_nibbles(n) return hex(random_bytes(m_ceil(n/2))):sub(1, n); end local function get_twobits() return ("%x"):format(random_bytes(1):byte() % 4 + 8); end local function generate() -- generate RFC 4122 complaint UUIDs (version 4 - random) return get_nibbles(8).."-"..get_nibbles(4).."-4"..get_nibbles(3).."-"..(get_twobits())..get_nibbles(3).."-"..get_nibbles(12); end return { get_nibbles=get_nibbles; generate = generate ; -- COMPAT seed = random.seed; }; prosody-0.12.4/util/PaxHeaders/vcard.lua0000644000000000000000000000012014475665104015143 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.792763972 prosody-0.12.4/util/vcard.lua0000644000175000017500000002614414475665104017357 0ustar00prosodyprosody00000000000000-- Copyright (C) 2011-2014 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- TODO -- Fix folding. local st = require "util.stanza"; local t_insert, t_concat = table.insert, table.concat; local type = type; local pairs, ipairs = pairs, ipairs; local from_text, to_text, from_xep54, to_xep54; local line_sep = "\n"; local vCard_dtd; -- See end of file local vCard4_dtd; local function vCard_esc(s) return s:gsub("[,:;\\]", "\\%1"):gsub("\n","\\n"); end local function vCard_unesc(s) return s:gsub("\\?[\\nt:;,]", { ["\\\\"] = "\\", ["\\n"] = "\n", ["\\r"] = "\r", ["\\t"] = "\t", ["\\:"] = ":", -- FIXME Shouldn't need to escape : in values, just params ["\\;"] = ";", ["\\,"] = ",", [":"] = "\29", [";"] = "\30", [","] = "\31", }); end local function item_to_xep54(item) local t = st.stanza(item.name, { xmlns = "vcard-temp" }); local prop_def = vCard_dtd[item.name]; if prop_def == "text" then t:text(item[1]); elseif type(prop_def) == "table" then if prop_def.types and item.TYPE then if type(item.TYPE) == "table" then for _,v in pairs(prop_def.types) do for _,typ in pairs(item.TYPE) do if typ:upper() == v then t:tag(v):up(); break; end end end else t:tag(item.TYPE:upper()):up(); end end if prop_def.props then for _,prop in pairs(prop_def.props) do if item[prop] then for _, v in ipairs(item[prop]) do t:text_tag(prop, v); end end end end if prop_def.value then t:text_tag(prop_def.value, item[1]); elseif prop_def.values then local prop_def_values = prop_def.values; local repeat_last = prop_def_values.behaviour == "repeat-last" and prop_def_values[#prop_def_values]; for i=1,#item do t:text_tag(prop_def.values[i] or repeat_last, item[i]); end end end return t; end local function vcard_to_xep54(vCard) local t = st.stanza("vCard", { xmlns = "vcard-temp" }); for i=1,#vCard do t:add_child(item_to_xep54(vCard[i])); end return t; end function to_xep54(vCards) if not vCards[1] or vCards[1].name then return vcard_to_xep54(vCards) else local t = st.stanza("xCard", { xmlns = "vcard-temp" }); for i=1,#vCards do t:add_child(vcard_to_xep54(vCards[i])); end return t; end end function from_text(data) data = data -- unfold and remove empty lines :gsub("\r\n","\n") :gsub("\n ", "") :gsub("\n\n+","\n"); local vCards = {}; local current; for line in data:gmatch("[^\n]+") do line = vCard_unesc(line); local name, params, value = line:match("^([-%a]+)(\30?[^\29]*)\29(.*)$"); value = value:gsub("\29",":"); if #params > 0 then local _params = {}; for k,isval,v in params:gmatch("\30([^=]+)(=?)([^\30]*)") do k = k:upper(); local _vt = {}; for _p in v:gmatch("[^\31]+") do _vt[#_vt+1]=_p _vt[_p]=true; end if isval == "=" then _params[k]=_vt; else _params[k]=true; end end params = _params; end if name == "BEGIN" and value == "VCARD" then current = {}; vCards[#vCards+1] = current; elseif name == "END" and value == "VCARD" then current = nil; elseif current and vCard_dtd[name] then local dtd = vCard_dtd[name]; local item = { name = name }; t_insert(current, item); local up = current; current = item; if dtd.types then for _, t in ipairs(dtd.types) do t = t:lower(); if ( params.TYPE and params.TYPE[t] == true) or params[t] == true then current.TYPE=t; end end end if dtd.props then for _, p in ipairs(dtd.props) do if params[p] then if params[p] == true then current[p]=true; else for _, prop in ipairs(params[p]) do current[p]=prop; end end end end end if dtd == "text" or dtd.value then t_insert(current, value); elseif dtd.values then for p in ("\30"..value):gmatch("\30([^\30]*)") do t_insert(current, p); end end current = up; end end return vCards; end local function item_to_text(item) local value = {}; for i=1,#item do value[i] = vCard_esc(item[i]); end value = t_concat(value, ";"); local params = ""; for k,v in pairs(item) do if type(k) == "string" and k ~= "name" then params = params .. (";%s=%s"):format(k, type(v) == "table" and t_concat(v,",") or v); end end return ("%s%s:%s"):format(item.name, params, value) end local function vcard_to_text(vcard) local t={}; t_insert(t, "BEGIN:VCARD") for i=1,#vcard do t_insert(t, item_to_text(vcard[i])); end t_insert(t, "END:VCARD") return t_concat(t, line_sep); end function to_text(vCards) if vCards[1] and vCards[1].name then return vcard_to_text(vCards) else local t = {}; for i=1,#vCards do t[i]=vcard_to_text(vCards[i]); end return t_concat(t, line_sep); end end local function from_xep54_item(item) local prop_name = item.name; local prop_def = vCard_dtd[prop_name]; local prop = { name = prop_name }; if prop_def == "text" then prop[1] = item:get_text(); elseif type(prop_def) == "table" then if prop_def.value then --single item prop[1] = item:get_child_text(prop_def.value) or ""; elseif prop_def.values then --array local value_names = prop_def.values; if value_names.behaviour == "repeat-last" then for i=1,#item.tags do t_insert(prop, item.tags[i]:get_text() or ""); end else for i=1,#value_names do t_insert(prop, item:get_child_text(value_names[i]) or ""); end end elseif prop_def.names then local names = prop_def.names; for i=1,#names do if item:get_child(names[i]) then prop[1] = names[i]; break; end end end if prop_def.props_verbatim then for k,v in pairs(prop_def.props_verbatim) do prop[k] = v; end end if prop_def.types then local types = prop_def.types; prop.TYPE = {}; for i=1,#types do if item:get_child(types[i]) then t_insert(prop.TYPE, types[i]:lower()); end end if #prop.TYPE == 0 then prop.TYPE = nil; end end -- A key-value pair, within a key-value pair? if prop_def.props then local params = prop_def.props; for i=1,#params do local name = params[i] local data = item:get_child_text(name); if data then prop[name] = prop[name] or {}; t_insert(prop[name], data); end end end else return nil end return prop; end local function from_xep54_vCard(vCard) local tags = vCard.tags; local t = {}; for i=1,#tags do t_insert(t, from_xep54_item(tags[i])); end return t end function from_xep54(vCard) if vCard.attr.xmlns ~= "vcard-temp" then return nil, "wrong-xmlns"; end if vCard.name == "xCard" then -- A collection of vCards local t = {}; local vCards = vCard.tags; for i=1,#vCards do t[i] = from_xep54_vCard(vCards[i]); end return t elseif vCard.name == "vCard" then -- A single vCard return from_xep54_vCard(vCard) end end local vcard4 = { } function vcard4:text(node, params, value) -- luacheck: ignore 212/params self:tag(node:lower()) -- FIXME params if type(value) == "string" then self:text_tag("text", value); elseif vcard4[node] then vcard4[node](value); end self:up(); end function vcard4.N(value) for i, k in ipairs(vCard_dtd.N.values) do value:text_tag(k, value[i]); end end local xmlns_vcard4 = "urn:ietf:params:xml:ns:vcard-4.0" local function item_to_vcard4(item) local typ = item.name:lower(); local t = st.stanza(typ, { xmlns = xmlns_vcard4 }); local prop_def = vCard4_dtd[typ]; if prop_def == "text" then t:text_tag("text", item[1]); elseif prop_def == "uri" then if item.ENCODING and item.ENCODING[1] == 'b' then t:text_tag("uri", "data:;base64," .. item[1]); else t:text_tag("uri", item[1]); end elseif type(prop_def) == "table" then if prop_def.values then for i, v in ipairs(prop_def.values) do t:text_tag(v:lower(), item[i]); end else t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"}) end else t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"}) end return t; end local function vcard_to_vcard4xml(vCard) local t = st.stanza("vcard", { xmlns = xmlns_vcard4 }); for i=1,#vCard do t:add_child(item_to_vcard4(vCard[i])); end return t; end local function vcards_to_vcard4xml(vCards) if not vCards[1] or vCards[1].name then return vcard_to_vcard4xml(vCards) else local t = st.stanza("vcards", { xmlns = xmlns_vcard4 }); for i=1,#vCards do t:add_child(vcard_to_vcard4xml(vCards[i])); end return t; end end -- This was adapted from http://xmpp.org/extensions/xep-0054.html#dtd vCard_dtd = { VERSION = "text", --MUST be 3.0, so parsing is redundant FN = "text", N = { values = { "FAMILY", "GIVEN", "MIDDLE", "PREFIX", "SUFFIX", }, }, NICKNAME = "text", PHOTO = { props_verbatim = { ENCODING = { "b" } }, props = { "TYPE" }, value = "BINVAL", --{ "EXTVAL", }, }, BDAY = "text", ADR = { types = { "HOME", "WORK", "POSTAL", "PARCEL", "DOM", "INTL", "PREF", }, values = { "POBOX", "EXTADD", "STREET", "LOCALITY", "REGION", "PCODE", "CTRY", } }, LABEL = { types = { "HOME", "WORK", "POSTAL", "PARCEL", "DOM", "INTL", "PREF", }, value = "LINE", }, TEL = { types = { "HOME", "WORK", "VOICE", "FAX", "PAGER", "MSG", "CELL", "VIDEO", "BBS", "MODEM", "ISDN", "PCS", "PREF", }, value = "NUMBER", }, EMAIL = { types = { "HOME", "WORK", "INTERNET", "PREF", "X400", }, value = "USERID", }, JABBERID = "text", MAILER = "text", TZ = "text", GEO = { values = { "LAT", "LON", }, }, TITLE = "text", ROLE = "text", LOGO = "copy of PHOTO", AGENT = "text", ORG = { values = { behaviour = "repeat-last", "ORGNAME", "ORGUNIT", } }, CATEGORIES = { values = "KEYWORD", }, NOTE = "text", PRODID = "text", REV = "text", SORTSTRING = "text", SOUND = "copy of PHOTO", UID = "text", URL = "text", CLASS = { names = { -- The item.name is the value if it's one of these. "PUBLIC", "PRIVATE", "CONFIDENTIAL", }, }, KEY = { props = { "TYPE" }, value = "CRED", }, DESC = "text", }; vCard_dtd.LOGO = vCard_dtd.PHOTO; vCard_dtd.SOUND = vCard_dtd.PHOTO; vCard4_dtd = { source = "uri", kind = "text", xml = "text", fn = "text", n = { values = { "family", "given", "middle", "prefix", "suffix", }, }, nickname = "text", photo = "uri", bday = "date-and-or-time", anniversary = "date-and-or-time", gender = "text", adr = { values = { "pobox", "ext", "street", "locality", "region", "code", "country", } }, tel = "text", email = "text", impp = "uri", lang = "language-tag", tz = "text", geo = "uri", title = "text", role = "text", logo = "uri", org = "text", member = "uri", related = "uri", categories = "text", note = "text", prodid = "text", rev = "timestamp", sound = "uri", uid = "uri", clientpidmap = "number, uuid", url = "uri", version = "text", key = "uri", fburl = "uri", caladruri = "uri", caluri = "uri", }; return { from_text = from_text; to_text = to_text; from_xep54 = from_xep54; to_xep54 = to_xep54; to_vcard4 = vcards_to_vcard4xml; }; prosody-0.12.4/util/PaxHeaders/watchdog.lua0000644000000000000000000000012014475665104015644 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.792763972 prosody-0.12.4/util/watchdog.lua0000644000175000017500000000150514475665104020052 0ustar00prosodyprosody00000000000000local timer = require "util.timer"; local setmetatable = setmetatable; local os_time = os.time; local _ENV = nil; -- luacheck: std none local watchdog_methods = {}; local watchdog_mt = { __index = watchdog_methods }; local function new(timeout, callback) local watchdog = setmetatable({ timeout = timeout, last_reset = os_time(), callback = callback }, watchdog_mt); timer.add_task(timeout+1, function (current_time) local last_reset = watchdog.last_reset; if not last_reset then return; end local time_left = (last_reset + timeout) - current_time; if time_left < 0 then return watchdog:callback(); end return time_left + 1; end); return watchdog; end function watchdog_methods:reset() self.last_reset = os_time(); end function watchdog_methods:cancel() self.last_reset = nil; end return { new = new; }; prosody-0.12.4/util/PaxHeaders/x509.lua0000644000000000000000000000012014475665104014551 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.792763972 prosody-0.12.4/util/x509.lua0000644000175000017500000002121114475665104016753 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2010 Matthew Wild -- Copyright (C) 2010 Paul Aurich -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- TODO: I feel a fair amount of this logic should be integrated into Luasec, -- so that everyone isn't re-inventing the wheel. Dependencies on -- IDN libraries complicate that. -- [TLS-CERTS] - http://tools.ietf.org/html/rfc6125 -- [XMPP-CORE] - http://tools.ietf.org/html/rfc6120 -- [SRV-ID] - http://tools.ietf.org/html/rfc4985 -- [IDNA] - http://tools.ietf.org/html/rfc5890 -- [LDAP] - http://tools.ietf.org/html/rfc4519 -- [PKIX] - http://tools.ietf.org/html/rfc5280 local nameprep = require "util.encodings".stringprep.nameprep; local idna_to_ascii = require "util.encodings".idna.to_ascii; local idna_to_unicode = require "util.encodings".idna.to_unicode; local base64 = require "util.encodings".base64; local log = require "util.logger".init("x509"); local mt = require "util.multitable"; local s_format = string.format; local ipairs = ipairs; local _ENV = nil; -- luacheck: std none local oid_commonname = "2.5.4.3"; -- [LDAP] 2.3 local oid_subjectaltname = "2.5.29.17"; -- [PKIX] 4.2.1.6 local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE] local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID] -- Compare a hostname (possibly international) with asserted names -- extracted from a certificate. -- This function follows the rules laid out in -- sections 6.4.1 and 6.4.2 of [TLS-CERTS] -- -- A wildcard ("*") all by itself is allowed only as the left-most label local function compare_dnsname(host, asserted_names) -- TODO: Sufficient normalization? Review relevant specs. local norm_host = idna_to_ascii(host) if norm_host == nil then log("info", "Host %s failed IDNA ToASCII operation", host) return false end norm_host = norm_host:lower() local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label for i=1,#asserted_names do local name = asserted_names[i] if norm_host == name:lower() then log("debug", "Cert dNSName %s matched hostname", name); return true end -- Allow the left most label to be a "*" if name:match("^%*%.") then local rest_name = name:gsub("^[^.]+%.", "") if host_chopped == rest_name:lower() then log("debug", "Cert dNSName %s matched hostname", name); return true end end end return false end -- Compare an XMPP domain name with the asserted id-on-xmppAddr -- identities extracted from a certificate. Both are UTF8 strings. -- -- Per [XMPP-CORE], matches against asserted identities don't include -- wildcards, so we just do a normalize on both and then a string comparison -- -- TODO: Support for full JIDs? local function compare_xmppaddr(host, asserted_names) local norm_host = nameprep(host) for i=1,#asserted_names do local name = asserted_names[i] -- We only want to match against bare domains right now, not -- those crazy full-er JIDs. if name:match("[@/]") then log("debug", "Ignoring xmppAddr %s because it's not a bare domain", name) else local norm_name = nameprep(name) if norm_name == nil then log("info", "Ignoring xmppAddr %s, failed nameprep!", name) else if norm_host == norm_name then log("debug", "Cert xmppAddr %s matched hostname", name) return true end end end end return false end -- Compare a host + service against the asserted id-on-dnsSRV (SRV-ID) -- identities extracted from a certificate. -- -- Per [SRV-ID], the asserted identities will be encoded in ASCII via ToASCII. -- Comparison is done case-insensitively, and a wildcard ("*") all by itself -- is allowed only as the left-most non-service label. local function compare_srvname(host, service, asserted_names) local norm_host = idna_to_ascii(host) if norm_host == nil then log("info", "Host %s failed IDNA ToASCII operation", host); return false end -- Service names start with a "_" if service:match("^_") == nil then service = "_"..service end norm_host = norm_host:lower(); local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label for i=1,#asserted_names do local asserted_service, name = asserted_names[i]:match("^(_[^.]+)%.(.*)"); if service == asserted_service then if norm_host == name:lower() then log("debug", "Cert SRVName %s matched hostname", name); return true; end -- Allow the left most label to be a "*" if name:match("^%*%.") then local rest_name = name:gsub("^[^.]+%.", "") if host_chopped == rest_name:lower() then log("debug", "Cert SRVName %s matched hostname", name) return true end end if norm_host == name:lower() then log("debug", "Cert SRVName %s matched hostname", name); return true end end end return false end local function verify_identity(host, service, cert) if cert.setencode then cert:setencode("utf8"); end local ext = cert:extensions() if ext[oid_subjectaltname] then local sans = ext[oid_subjectaltname]; -- Per [TLS-CERTS] 6.3, 6.4.4, "a client MUST NOT seek a match for a -- reference identifier if the presented identifiers include a DNS-ID -- SRV-ID, URI-ID, or any application-specific identifier types" local had_supported_altnames = false if sans[oid_xmppaddr] then had_supported_altnames = true if service == "_xmpp-client" or service == "_xmpp-server" then if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end end end if sans[oid_dnssrv] then had_supported_altnames = true -- Only check srvNames if the caller specified a service if service and compare_srvname(host, service, sans[oid_dnssrv]) then return true end end if sans["dNSName"] then had_supported_altnames = true if compare_dnsname(host, sans["dNSName"]) then return true end end -- We don't need URIs, but [TLS-CERTS] is clear. if sans["uniformResourceIdentifier"] then had_supported_altnames = true end if had_supported_altnames then return false end end -- Extract a common name from the certificate, and check it as if it were -- a dNSName subjectAltName (wildcards may apply for, and receive, -- cat treats) -- -- Per [TLS-CERTS] 1.8, a CN-ID is the Common Name from a cert subject -- which has one and only one Common Name local subject = cert:subject() local cn = nil for i=1,#subject do local dn = subject[i] if dn["oid"] == oid_commonname then if cn then log("info", "Certificate has multiple common names") return false end cn = dn["value"]; end end if cn then -- Per [TLS-CERTS] 6.4.4, follow the comparison rules for dNSName SANs. return compare_dnsname(host, { cn }) end -- If all else fails, well, why should we be any different? return false end -- TODO Support other SANs local function get_identities(cert) --> map of names to sets of services if cert.setencode then cert:setencode("utf8"); end local names = mt.new(); local ext = cert:extensions(); local sans = ext[oid_subjectaltname]; if sans then if sans["dNSName"] then -- Valid for any service for _, name in ipairs(sans["dNSName"]) do local is_wildcard = name:sub(1, 2) == "*."; if is_wildcard then name = name:sub(3); end name = idna_to_unicode(nameprep(name)); if name then if is_wildcard then name = "*." .. name; end names:set(name, "*", true); end end end if sans[oid_xmppaddr] then for _, name in ipairs(sans[oid_xmppaddr]) do name = nameprep(name); if name then names:set(name, "xmpp-client", true); names:set(name, "xmpp-server", true); end end end if sans[oid_dnssrv] then for _, srvname in ipairs(sans[oid_dnssrv]) do local srv, name = srvname:match("^_([^.]+)%.(.*)"); if srv then name = nameprep(name); if name then names:set(name, srv, true); end end end end end local subject = cert:subject(); for i = 1, #subject do local dn = subject[i]; if dn.oid == oid_commonname then local name = nameprep(dn.value); if name and idna_to_ascii(name) then names:set(name, "*", true); end end end return names.data; end local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n".. "([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-"; local function pem2der(pem) local typ, data = pem:match(pat); if typ and data then return base64.decode(data), typ; end end local wrap = ('.'):rep(64); local envelope = "-----BEGIN %s-----\n%s\n-----END %s-----\n" local function der2pem(data, typ) typ = typ and typ:upper() or "CERTIFICATE"; data = base64.encode(data); return s_format(envelope, typ, data:gsub(wrap, '%0\n', (#data-1)/64), typ); end return { verify_identity = verify_identity; get_identities = get_identities; pem2der = pem2der; der2pem = der2pem; }; prosody-0.12.4/util/PaxHeaders/xml.lua0000644000000000000000000000012014475665104014644 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.792763972 prosody-0.12.4/util/xml.lua0000644000175000017500000000522414475665104017054 0ustar00prosodyprosody00000000000000 local st = require "util.stanza"; local lxp = require "lxp"; local t_insert = table.insert; local t_remove = table.remove; local error = error; local _ENV = nil; -- luacheck: std none local parse_xml = (function() local ns_prefixes = { ["http://www.w3.org/XML/1998/namespace"] = "xml"; }; local ns_separator = "\1"; local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$"; return function(xml, options) --luacheck: ignore 212/self local handler = {}; local stanza = st.stanza("root"); local namespaces = {}; local prefixes = {}; function handler:StartNamespaceDecl(prefix, url) if prefix ~= nil then t_insert(namespaces, url); t_insert(prefixes, prefix); end end function handler:EndNamespaceDecl(prefix) if prefix ~= nil then -- we depend on each StartNamespaceDecl having a paired EndNamespaceDecl t_remove(namespaces); t_remove(prefixes); end end function handler:StartElement(tagname, attr) local curr_ns,name = tagname:match(ns_pattern); if name == "" then curr_ns, name = "", curr_ns; end if curr_ns ~= "" then attr.xmlns = curr_ns; end for i=1,#attr do local k = attr[i]; attr[i] = nil; local ns, nm = k:match(ns_pattern); if nm ~= "" then ns = ns_prefixes[ns]; if ns then attr[ns..":"..nm] = attr[k]; attr[k] = nil; end end end local n = {} for i=1,#namespaces do n[prefixes[i]] = namespaces[i]; end stanza:tag(name, attr, n); end function handler:CharacterData(data) stanza:text(data); end function handler:EndElement() stanza:up(); end -- SECURITY: These two handlers, especially the Doctype one, are required to prevent exploits such as Billion Laughs. local function restricted_handler(parser) if not parser.stop or not parser:stop() then error("Failed to abort parsing"); end end handler.StartDoctypeDecl = restricted_handler; if not options or not options.allow_comments then -- NOTE: comments are generally harmless and can be useful when parsing configuration files or other data, even user-provided data handler.Comment = restricted_handler; end if not options or not options.allow_processing_instructions then -- Processing instructions should generally be safe to just ignore handler.ProcessingInstruction = restricted_handler; end local parser = lxp.new(handler, ns_separator); local ok, err, line, col = parser:parse(xml); if ok then ok, err, line, col = parser:parse(); end --parser:close(); if ok then return stanza.tags[1]; else return ok, ("%s (line %d, col %d))"):format(err, line, col); end end; end)(); return { parse = parse_xml; }; prosody-0.12.4/util/PaxHeaders/xmppstream.lua0000644000000000000000000000012014475665104016244 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.792763972 prosody-0.12.4/util/xmppstream.lua0000644000175000017500000002160014475665104020450 0ustar00prosodyprosody00000000000000-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local lxp = require "lxp"; local st = require "util.stanza"; local stanza_mt = st.stanza_mt; local error = error; local tostring = tostring; local t_insert = table.insert; local t_concat = table.concat; local t_remove = table.remove; local setmetatable = setmetatable; -- COMPAT: w/LuaExpat 1.1.0 local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false }); local lxp_supports_xmldecl = pcall(lxp.new, { XmlDecl = false }); local lxp_supports_bytecount = not not lxp.new({}).getcurrentbytecount; local default_stanza_size_limit = 1024*1024*1; -- 1MB local _ENV = nil; -- luacheck: std none local new_parser = lxp.new; local xml_namespace = { ["http://www.w3.org/XML/1998/namespace\1lang"] = "xml:lang"; ["http://www.w3.org/XML/1998/namespace\1space"] = "xml:space"; ["http://www.w3.org/XML/1998/namespace\1base"] = "xml:base"; ["http://www.w3.org/XML/1998/namespace\1id"] = "xml:id"; }; local xmlns_streams = "http://etherx.jabber.org/streams"; local ns_separator = "\1"; local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$"; local function dummy_cb() end local function new_sax_handlers(session, stream_callbacks, cb_handleprogress) local xml_handlers = {}; local cb_streamopened = stream_callbacks.streamopened; local cb_streamclosed = stream_callbacks.streamclosed; local cb_error = stream_callbacks.error or function(_, e, stanza) error("XML stream error: "..tostring(e)..(stanza and ": "..tostring(stanza) or ""),2); end; local cb_handlestanza = stream_callbacks.handlestanza; cb_handleprogress = cb_handleprogress or dummy_cb; local stream_ns = stream_callbacks.stream_ns or xmlns_streams; local stream_tag = stream_callbacks.stream_tag or "stream"; if stream_ns ~= "" then stream_tag = stream_ns..ns_separator..stream_tag; end local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error"); local stream_default_ns = stream_callbacks.default_ns; local stream_lang = "en"; local stack = {}; local chardata, stanza = {}; local stanza_size = 0; local non_streamns_depth = 0; function xml_handlers:StartElement(tagname, attr) if stanza and #chardata > 0 then -- We have some character data in the buffer t_insert(stanza, t_concat(chardata)); chardata = {}; end local curr_ns,name = tagname:match(ns_pattern); if name == "" then curr_ns, name = "", curr_ns; end if curr_ns ~= stream_default_ns or non_streamns_depth > 0 then attr.xmlns = curr_ns; non_streamns_depth = non_streamns_depth + 1; end for i=1,#attr do local k = attr[i]; attr[i] = nil; local xmlk = xml_namespace[k]; if xmlk then attr[xmlk] = attr[k]; attr[k] = nil; end end if not stanza then --if we are not currently inside a stanza if lxp_supports_bytecount then stanza_size = self:getcurrentbytecount(); end if session.notopen then if tagname == stream_tag then non_streamns_depth = 0; stream_lang = attr["xml:lang"] or stream_lang; if cb_streamopened then if lxp_supports_bytecount then cb_handleprogress(stanza_size); stanza_size = 0; end cb_streamopened(session, attr); end else -- Garbage before stream? cb_error(session, "no-stream", tagname); end return; end if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then cb_error(session, "invalid-top-level-element"); end stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); else -- we are inside a stanza, so add a tag if lxp_supports_bytecount then stanza_size = stanza_size + self:getcurrentbytecount(); end t_insert(stack, stanza); local oldstanza = stanza; stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); t_insert(oldstanza, stanza); t_insert(oldstanza.tags, stanza); end end function xml_handlers:StartCdataSection() if lxp_supports_bytecount then if stanza then stanza_size = stanza_size + self:getcurrentbytecount(); else cb_handleprogress(self:getcurrentbytecount()); end end end function xml_handlers:EndCdataSection() if lxp_supports_bytecount then if stanza then stanza_size = stanza_size + self:getcurrentbytecount(); else cb_handleprogress(self:getcurrentbytecount()); end end end function xml_handlers:CharacterData(data) if stanza then if lxp_supports_bytecount then stanza_size = stanza_size + self:getcurrentbytecount(); end t_insert(chardata, data); elseif lxp_supports_bytecount then cb_handleprogress(self:getcurrentbytecount()); end end function xml_handlers:EndElement(tagname) if lxp_supports_bytecount then stanza_size = stanza_size + self:getcurrentbytecount() end if non_streamns_depth > 0 then non_streamns_depth = non_streamns_depth - 1; end if stanza then if #chardata > 0 then -- We have some character data in the buffer t_insert(stanza, t_concat(chardata)); chardata = {}; end -- Complete stanza if #stack == 0 then if lxp_supports_bytecount then cb_handleprogress(stanza_size); end stanza_size = 0; if stanza.attr["xml:lang"] == nil then stanza.attr["xml:lang"] = stream_lang; end if tagname ~= stream_error_tag then cb_handlestanza(session, stanza); else cb_error(session, "stream-error", stanza); end stanza = nil; else stanza = t_remove(stack); end else if lxp_supports_bytecount then cb_handleprogress(stanza_size); end if cb_streamclosed then cb_streamclosed(session); end end end local function restricted_handler(parser) cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1."); if not parser.stop or not parser:stop() then error("Failed to abort parsing"); end end if lxp_supports_xmldecl then function xml_handlers:XmlDecl(version, encoding, standalone) if lxp_supports_bytecount then cb_handleprogress(self:getcurrentbytecount()); end if (encoding and encoding:lower() ~= "utf-8") or (standalone == "no") or (version and version ~= "1.0") then return restricted_handler(self); end end end if lxp_supports_doctype then xml_handlers.StartDoctypeDecl = restricted_handler; end xml_handlers.Comment = restricted_handler; xml_handlers.ProcessingInstruction = restricted_handler; local function reset() stanza, chardata, stanza_size = nil, {}, 0; stack = {}; end local function set_session(stream, new_session) -- luacheck: ignore 212/stream session = new_session; end return xml_handlers, { reset = reset, set_session = set_session }; end local function new(session, stream_callbacks, stanza_size_limit) -- Used to track parser progress (e.g. to enforce size limits) local n_outstanding_bytes = 0; local handle_progress; if lxp_supports_bytecount then function handle_progress(n_parsed_bytes) n_outstanding_bytes = n_outstanding_bytes - n_parsed_bytes; end stanza_size_limit = stanza_size_limit or default_stanza_size_limit; elseif stanza_size_limit then error("Stanza size limits are not supported on this version of LuaExpat") end local handlers, meta = new_sax_handlers(session, stream_callbacks, handle_progress); local parser = new_parser(handlers, ns_separator, false); local parse = parser.parse; function session.open_stream(session, from, to) -- luacheck: ignore 432/session local send = session.sends2s or session.send; local attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", ["xml:lang"] = "en", xmlns = stream_callbacks.default_ns, version = session.version and (session.version > 0 and "1.0" or nil), id = session.streamid or "", from = from or session.host, to = to, }; if session.stream_attrs then session:stream_attrs(from, to, attr) end send(""..st.stanza("stream:stream", attr):top_tag()); return true; end return { reset = function () parser = new_parser(handlers, ns_separator, false); parse = parser.parse; n_outstanding_bytes = 0; meta.reset(); end, feed = function (self, data) -- luacheck: ignore 212/self if lxp_supports_bytecount then n_outstanding_bytes = n_outstanding_bytes + #data; end local _parser = parser; local ok, err = parse(_parser, data); if lxp_supports_bytecount and n_outstanding_bytes > stanza_size_limit then return nil, "stanza-too-large"; end if parser ~= _parser then _parser:parse(); _parser:close(); end return ok, err; end, set_session = meta.set_session; set_stanza_size_limit = function (_, new_stanza_size_limit) stanza_size_limit = new_stanza_size_limit; end; }; end return { ns_separator = ns_separator; ns_pattern = ns_pattern; new_sax_handlers = new_sax_handlers; new = new; }; prosody-0.12.4/util/PaxHeaders/xpcall.lua0000644000000000000000000000012014475665104015327 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.792763972 prosody-0.12.4/util/xpcall.lua0000644000175000017500000000027314475665104017536 0ustar00prosodyprosody00000000000000local xpcall = xpcall; if select(2, xpcall(function (x) return x end, function () end, "test")) ~= "test" then xpcall = require"util.compat".xpcall; end return { xpcall = xpcall; }; prosody-0.12.4/util/PaxHeaders/xtemplate.lua0000644000000000000000000000012014475665104016047 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.792763972 prosody-0.12.4/util/xtemplate.lua0000644000175000017500000000503614475665104020260 0ustar00prosodyprosody00000000000000local s_gsub = string.gsub; local s_match = string.match; local s_sub = string.sub; local t_concat = table.concat; local st = require("util.stanza"); local function render(template, root, escape, filters) escape = escape or st.xml_escape; return (s_gsub(template, "%b{}", function(block) local inner = s_sub(block, 2, -2); local path, pipe, pos = s_match(inner, "^([^|]+)(|?)()"); if not (type(path) == "string") then return end local value if path == "." then value = root; elseif path == "#" then value = root:get_text(); else value = root:find(path); end local is_escaped = false; while pipe == "|" do local func, args, tmpl, p = s_match(inner, "^(%w+)(%b())(%b{})()", pos); if not func then func, args, p = s_match(inner, "^(%w+)(%b())()", pos); end if not func then func, tmpl, p = s_match(inner, "^(%w+)(%b{})()", pos); end if not func then func, p = s_match(inner, "^(%w+)()", pos); end if not func then break end if tmpl then tmpl = s_sub(tmpl, 2, -2); end if args then args = s_sub(args, 2, -2); end if func == "each" and tmpl and st.is_stanza(value) then if not args then value, args = root, path; end local ns, name = s_match(args, "^(%b{})(.*)$"); if ns then ns = s_sub(ns, 2, -2); else name, ns = args, nil; end if ns == "" then ns = nil; end if name == "" then name = nil; end local out, i = {}, 1; for c in (value):childtags(name, ns) do out[i], i = render(tmpl, c, escape, filters), i + 1; end value = t_concat(out); is_escaped = true; elseif func == "and" and tmpl then local condition = value; if args then condition = root:find(args); end if condition then value = render(tmpl, root, escape, filters); is_escaped = true; end elseif func == "or" and tmpl then local condition = value; if args then condition = root:find(args); end if not condition then value = render(tmpl, root, escape, filters); is_escaped = true; end elseif filters and filters[func] then local f = filters[func]; if args == nil then value, is_escaped = f(value, tmpl); else value, is_escaped = f(args, value, tmpl); end else error("No such filter function: " .. func); end pipe, pos = s_match(inner, "^(|?)()", p); end if type(value) == "string" then if not is_escaped then value = escape(value); end return value elseif st.is_stanza(value) then value = value:get_text(); if value then return escape(value) end end return "" end)) end return { render = render } prosody-0.12.4/PaxHeaders/util-src0000644000000000000000000000013214475665104014054 xustar0030 mtime=1693936196.840763501 30 atime=1693936196.752764364 30 ctime=1693936196.760764285 prosody-0.12.4/util-src/0000755000175000017500000000000014475665104016333 5ustar00prosodyprosody00000000000000prosody-0.12.4/util-src/PaxHeaders/GNUmakefile0000644000000000000000000000012014475665104016200 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/util-src/GNUmakefile0000644000175000017500000000120714475665104020405 0ustar00prosodyprosody00000000000000 include ../config.unix CFLAGS+=-I$(LUA_INCDIR) INSTALL_DATA=install -m644 TARGET?=../util/ ALL=encodings.so hashes.so net.so pposix.so signal.so table.so \ ringbuffer.so time.so poll.so compat.so strbitop.so \ struct.so ifdef RANDOM ALL+=crand.so endif .PHONY: all install clean .SUFFIXES: .c .o .so all: $(ALL) install: $(ALL) $(INSTALL_DATA) $? $(TARGET) clean: rm -f $(ALL) $(patsubst %.so,%.o,$(ALL)) encodings.o: CFLAGS+=$(IDNA_FLAGS) encodings.so: LDLIBS+=$(IDNA_LIBS) hashes.so: LDLIBS+=$(OPENSSL_LIBS) crand.o: CFLAGS+=-DWITH_$(RANDOM) crand.so: LDLIBS+=$(RANDOM_LIBS) %.so: %.o $(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS) prosody-0.12.4/util-src/PaxHeaders/Makefile.win0000644000000000000000000000012014475665104016362 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/util-src/Makefile.win0000644000175000017500000000250114475665104020565 0ustar00prosodyprosody00000000000000 LUA_PATH=$(LUA_DEV) IDN_PATH=..\..\libidn-1.15 OPENSSL_PATH=..\..\openssl-0.9.8k LUA_INCLUDE=$(LUA_PATH)\include LUA_LIB=$(LUA_PATH)\lib\lua5.1.lib IDN_LIB=$(IDN_PATH)\win32\lib\libidn.lib IDN_INCLUDE1=$(IDN_PATH)\lib IDN_INCLUDE2=$(IDN_PATH)\win32\include OPENSSL_LIB=$(OPENSSL_PATH)\out32dll\libeay32.lib OPENSSL_INCLUDE=$(OPENSSL_PATH)\include CL=cl /LD /MD /nologo all: encodings.dll hashes.dll windows.dll install: encodings.dll hashes.dll windows.dll copy /Y *.dll ..\util\ clean: del encodings.dll encodings.exp encodings.lib encodings.obj encodings.dll.manifest del hashes.dll hashes.exp hashes.lib hashes.obj hashes.dll.manifest del windows.dll windows.exp windows.lib windows.obj windows.dll.manifest encodings.dll: encodings.c $(CL) encodings.c /I"$(LUA_INCLUDE)" /I"$(IDN_INCLUDE1)" /I"$(IDN_INCLUDE2)" /link "$(LUA_LIB)" "$(IDN_LIB)" /export:luaopen_util_encodings del encodings.exp encodings.lib encodings.obj encodings.dll.manifest hashes.dll: hashes.c $(CL) hashes.c /I"$(LUA_INCLUDE)" /I"$(OPENSSL_INCLUDE)" /link "$(LUA_LIB)" "$(OPENSSL_LIB)" /export:luaopen_util_hashes del hashes.exp hashes.lib hashes.obj hashes.dll.manifest windows.dll: windows.c $(CL) windows.c /I"$(LUA_INCLUDE)" /link "$(LUA_LIB)" dnsapi.lib /export:luaopen_util_windows del windows.exp windows.lib windows.obj windows.dll.manifest prosody-0.12.4/util-src/PaxHeaders/compat.c0000644000000000000000000000012014475665104015555 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/util-src/compat.c0000644000175000017500000000122514475665104017762 0ustar00prosodyprosody00000000000000 #include #include static int lc_xpcall (lua_State *L) { int ret; int n_arg = lua_gettop(L); /* f, msgh, p1, p2... */ luaL_argcheck(L, n_arg >= 2, 2, "value expected"); lua_pushvalue(L, 1); /* f to top */ lua_pushvalue(L, 2); /* msgh to top */ lua_replace(L, 1); /* msgh to 1 */ lua_replace(L, 2); /* f to 2 */ /* msgh, f, p1, p2... */ ret = lua_pcall(L, n_arg - 2, LUA_MULTRET, 1); lua_pushboolean(L, ret == 0); lua_replace(L, 1); return lua_gettop(L); } int luaopen_util_compat(lua_State *L) { lua_createtable(L, 0, 2); { lua_pushcfunction(L, lc_xpcall); lua_setfield(L, -2, "xpcall"); } return 1; } prosody-0.12.4/util-src/PaxHeaders/crand.c0000644000000000000000000000012014475665104015361 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/util-src/crand.c0000644000175000017500000000546514475665104017600 0ustar00prosodyprosody00000000000000/* Prosody IM -- Copyright (C) 2008-2017 Matthew Wild -- Copyright (C) 2008-2017 Waqas Hussain -- Copyright (C) 2016-2017 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- */ /* * crand.c * C PRNG interface * * The purpose of this module is to provide access to a PRNG in * environments without /dev/urandom * * Caution! This has not been extensively tested. * */ #define _DEFAULT_SOURCE #include #include #include #include "lualib.h" #include "lauxlib.h" #if defined(WITH_GETRANDOM) #ifndef __GLIBC_PREREQ /* Not compiled with glibc at all */ #define __GLIBC_PREREQ(a,b) 0 #endif #if ! __GLIBC_PREREQ(2,25) /* Not compiled with a glibc that provides getrandom() */ #include #include #ifndef SYS_getrandom #error getrandom() requires Linux 3.17 or later #endif /* This wasn't present before glibc 2.25 */ int getrandom(void *buf, size_t buflen, unsigned int flags) { return syscall(SYS_getrandom, buf, buflen, flags); } #else #include #endif #elif defined(WITH_OPENSSL) #include #elif defined(WITH_ARC4RANDOM) #ifdef __linux__ #include #endif #else #error util.crand compiled without a random source #endif #ifndef SMALLBUFSIZ #define SMALLBUFSIZ 32 #endif int Lrandom(lua_State *L) { char smallbuf[SMALLBUFSIZ]; char *buf = &smallbuf[0]; const lua_Integer l = luaL_checkinteger(L, 1); const size_t len = l; luaL_argcheck(L, l >= 0, 1, "must be > 0"); if(len == 0) { lua_pushliteral(L, ""); return 1; } if(len > SMALLBUFSIZ) { buf = lua_newuserdata(L, len); } #if defined(WITH_GETRANDOM) /* * This acts like a read from /dev/urandom with the exception that it * *does* block if the entropy pool is not yet initialized. */ int left = len; char *p = buf; do { int ret = getrandom(p, left, 0); if(ret < 0) { lua_pushstring(L, strerror(errno)); return lua_error(L); } p += ret; left -= ret; } while(left > 0); #elif defined(WITH_ARC4RANDOM) arc4random_buf(buf, len); #elif defined(WITH_OPENSSL) if(!RAND_status()) { lua_pushliteral(L, "OpenSSL PRNG not seeded"); return lua_error(L); } if(RAND_bytes((unsigned char *)buf, len) != 1) { /* TODO ERR_get_error() */ lua_pushstring(L, "RAND_bytes() failed"); return lua_error(L); } #endif lua_pushlstring(L, buf, len); return 1; } int luaopen_util_crand(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif lua_createtable(L, 0, 2); lua_pushcfunction(L, Lrandom); lua_setfield(L, -2, "bytes"); #if defined(WITH_GETRANDOM) lua_pushstring(L, "Linux"); #elif defined(WITH_ARC4RANDOM) lua_pushstring(L, "arc4random()"); #elif defined(WITH_OPENSSL) lua_pushstring(L, "OpenSSL"); #endif lua_setfield(L, -2, "_source"); return 1; } prosody-0.12.4/util-src/PaxHeaders/encodings.c0000644000000000000000000000012014475665104016243 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.752764364 prosody-0.12.4/util-src/encodings.c0000644000175000017500000003451614475665104020461 0ustar00prosodyprosody00000000000000/* Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 1994-2015 Lua.org, PUC-Rio. -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- */ /* * encodings.c * Lua library for base64, stringprep and idna encodings */ /* Newer MSVC compilers deprecate strcpy as unsafe, but we use it in a safe way */ #define _CRT_SECURE_NO_DEPRECATE #include #include #include "lua.h" #include "lauxlib.h" #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif #if (LUA_VERSION_NUM < 504) #define luaL_pushfail lua_pushnil #endif /***************** BASE64 *****************/ static const char code[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static void base64_encode(luaL_Buffer *b, unsigned int c1, unsigned int c2, unsigned int c3, int n) { unsigned long tuple = c3 + 256UL * (c2 + 256UL * c1); int i; char s[4]; for(i = 0; i < 4; i++) { s[3 - i] = code[tuple % 64]; tuple /= 64; } for(i = n + 1; i < 4; i++) { s[i] = '='; } luaL_addlstring(b, s, 4); } static int Lbase64_encode(lua_State *L) { /** encode(s) */ size_t l; const unsigned char *s = (const unsigned char *)luaL_checklstring(L, 1, &l); luaL_Buffer b; int n; luaL_buffinit(L, &b); for(n = l / 3; n--; s += 3) { base64_encode(&b, s[0], s[1], s[2], 3); } switch(l % 3) { case 1: base64_encode(&b, s[0], 0, 0, 1); break; case 2: base64_encode(&b, s[0], s[1], 0, 2); break; } luaL_pushresult(&b); return 1; } static void base64_decode(luaL_Buffer *b, int c1, int c2, int c3, int c4, int n) { unsigned long tuple = c4 + 64L * (c3 + 64L * (c2 + 64L * c1)); char s[3]; switch(--n) { case 3: s[2] = (char) tuple; /* Falls through. */ case 2: s[1] = (char)(tuple >> 8); /* Falls through. */ case 1: s[0] = (char)(tuple >> 16); } luaL_addlstring(b, s, n); } static int Lbase64_decode(lua_State *L) { /** decode(s) */ size_t l; const char *s = luaL_checklstring(L, 1, &l); luaL_Buffer b; int n = 0; char t[4]; luaL_buffinit(L, &b); for(;;) { int c = *s++; switch(c) { const char *p; default: p = strchr(code, c); if(p == NULL) { return 0; } t[n++] = (char)(p - code); if(n == 4) { base64_decode(&b, t[0], t[1], t[2], t[3], 4); n = 0; } break; case '=': switch(n) { case 1: base64_decode(&b, t[0], 0, 0, 0, 1); break; case 2: base64_decode(&b, t[0], t[1], 0, 0, 2); break; case 3: base64_decode(&b, t[0], t[1], t[2], 0, 3); break; } n = 0; break; case 0: luaL_pushresult(&b); return 1; case '\n': case '\r': case '\t': case ' ': case '\f': case '\b': break; } } } static const luaL_Reg Reg_base64[] = { { "encode", Lbase64_encode }, { "decode", Lbase64_decode }, { NULL, NULL } }; /******************* UTF-8 ********************/ /* * Adapted from Lua 5.3 * Needed because libidn does not validate that input is valid UTF-8 */ #define MAXUNICODE 0x10FFFF /* * Decode one UTF-8 sequence, returning NULL if byte sequence is invalid. */ static const char *utf8_decode(const char *o, int *val) { static const unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF}; const unsigned char *s = (const unsigned char *)o; unsigned int c = s[0]; unsigned int res = 0; /* final result */ if(c < 0x80) { /* ascii? */ res = c; } else { int count = 0; /* to count number of continuation bytes */ while(c & 0x40) { /* still have continuation bytes? */ int cc = s[++count]; /* read next byte */ if((cc & 0xC0) != 0x80) { /* not a continuation byte? */ return NULL; /* invalid byte sequence */ } res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ c <<= 1; /* to test next bit */ } res |= ((c & 0x7F) << (count * 5)); /* add first byte */ if(count > 3 || res > MAXUNICODE || res <= limits[count] || (0xd800 <= res && res <= 0xdfff)) { return NULL; /* invalid byte sequence */ } s += count; /* skip continuation bytes read */ } if(val) { *val = res; } return (const char *)s + 1; /* +1 to include first byte */ } /* * Check that a string is valid UTF-8 * Returns NULL if not */ static const char *check_utf8(lua_State *L, int idx, size_t *l) { size_t pos, len; const char *s = luaL_checklstring(L, idx, &len); pos = 0; while(pos <= len) { const char *s1 = utf8_decode(s + pos, NULL); if(s1 == NULL) { /* conversion error? */ return NULL; } pos = s1 - s; } if(l != NULL) { *l = len; } return s; } static int Lutf8_valid(lua_State *L) { lua_pushboolean(L, check_utf8(L, 1, NULL) != NULL); return 1; } static int Lutf8_length(lua_State *L) { size_t len; if(!check_utf8(L, 1, &len)) { luaL_pushfail(L); lua_pushliteral(L, "invalid utf8"); return 2; } lua_pushinteger(L, len); return 1; } static const luaL_Reg Reg_utf8[] = { { "valid", Lutf8_valid }, { "length", Lutf8_length }, { NULL, NULL } }; /***************** STRINGPREP *****************/ #ifdef USE_STRINGPREP_ICU #include #include #include #include #include static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) { size_t input_len; int32_t unprepped_len, prepped_len, output_len; const char *input; char output[1024]; int flags = USPREP_ALLOW_UNASSIGNED; UChar unprepped[1024]; /* Temporary unicode buffer (1024 characters) */ UChar prepped[1024]; UErrorCode err = U_ZERO_ERROR; input = luaL_checklstring(L, 1, &input_len); if(input_len >= 1024) { luaL_pushfail(L); return 1; } /* strict */ if(!lua_isnoneornil(L, 2)) { luaL_checktype(L, 2, LUA_TBOOLEAN); if(lua_toboolean(L, 2)) { flags = 0; } } u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err); if(U_FAILURE(err)) { luaL_pushfail(L); return 1; } prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, flags, NULL, &err); if(U_FAILURE(err)) { luaL_pushfail(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, prepped, prepped_len, &err); if(U_SUCCESS(err) && output_len < 1024) { lua_pushlstring(L, output, output_len); } else { luaL_pushfail(L); } return 1; } } static UStringPrepProfile *icu_nameprep; static UStringPrepProfile *icu_nodeprep; static UStringPrepProfile *icu_resourceprep; static UStringPrepProfile *icu_saslprep; static USpoofChecker *icu_spoofcheck; static UIDNA *icu_idna2008; #if (U_ICU_VERSION_MAJOR_NUM < 58) /* COMPAT */ #define USPOOF_CONFUSABLE (USPOOF_SINGLE_SCRIPT_CONFUSABLE | USPOOF_MIXED_SCRIPT_CONFUSABLE | USPOOF_WHOLE_SCRIPT_CONFUSABLE) #endif /* initialize global ICU stringprep profiles */ static void init_icu(void) { UErrorCode err = U_ZERO_ERROR; utrace_setLevel(UTRACE_VERBOSE); icu_nameprep = usprep_openByType(USPREP_RFC3491_NAMEPREP, &err); icu_nodeprep = usprep_openByType(USPREP_RFC3920_NODEPREP, &err); icu_resourceprep = usprep_openByType(USPREP_RFC3920_RESOURCEPREP, &err); icu_saslprep = usprep_openByType(USPREP_RFC4013_SASLPREP, &err); icu_spoofcheck = uspoof_open(&err); uspoof_setChecks(icu_spoofcheck, USPOOF_CONFUSABLE, &err); int options = UIDNA_DEFAULT; #if 0 /* COMPAT with future Unicode versions */ options |= UIDNA_ALLOW_UNASSIGNED; #endif #if 1 /* Forbid eg labels starting with _ */ options |= UIDNA_USE_STD3_RULES; #endif #if 0 /* TODO determine if we need this */ options |= UIDNA_CHECK_BIDI; #endif #if 0 /* UTS46 makes it sound like these are the responsibility of registrars */ options |= UIDNA_CHECK_CONTEXTJ; options |= UIDNA_CHECK_CONTEXTO; #endif #if 0 /* This disables COMPAT with IDNA 2003 */ options |= UIDNA_NONTRANSITIONAL_TO_ASCII; options |= UIDNA_NONTRANSITIONAL_TO_UNICODE; #endif icu_idna2008 = uidna_openUTS46(options, &err); if(U_FAILURE(err)) { fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName(err)); } } #define MAKE_PREP_FUNC(myFunc, prep) \ static int myFunc(lua_State *L) { return icu_stringprep_prep(L, prep); } MAKE_PREP_FUNC(Lstringprep_nameprep, icu_nameprep) /** stringprep.nameprep(s) */ MAKE_PREP_FUNC(Lstringprep_nodeprep, icu_nodeprep) /** stringprep.nodeprep(s) */ MAKE_PREP_FUNC(Lstringprep_resourceprep, icu_resourceprep) /** stringprep.resourceprep(s) */ MAKE_PREP_FUNC(Lstringprep_saslprep, icu_saslprep) /** stringprep.saslprep(s) */ static const luaL_Reg Reg_stringprep[] = { { "nameprep", Lstringprep_nameprep }, { "nodeprep", Lstringprep_nodeprep }, { "resourceprep", Lstringprep_resourceprep }, { "saslprep", Lstringprep_saslprep }, { NULL, NULL } }; #else /* USE_STRINGPREP_ICU */ /****************** libidn ********************/ #include static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) { size_t len; const char *s; char string[1024]; int ret; Stringprep_profile_flags flags = 0; s = check_utf8(L, 1, &len); /* strict */ if(!lua_isnoneornil(L, 2)) { luaL_checktype(L, 2, LUA_TBOOLEAN); if(lua_toboolean(L, 2)) { flags = STRINGPREP_NO_UNASSIGNED; } } if(s == NULL || len >= 1024 || len != strlen(s)) { luaL_pushfail(L); return 1; /* TODO return error message */ } strcpy(string, s); ret = stringprep(string, 1024, flags, profile); if(ret == STRINGPREP_OK) { lua_pushstring(L, string); return 1; } else { luaL_pushfail(L); return 1; /* TODO return error message */ } } #define MAKE_PREP_FUNC(myFunc, prep) \ static int myFunc(lua_State *L) { return stringprep_prep(L, prep); } MAKE_PREP_FUNC(Lstringprep_nameprep, stringprep_nameprep) /** stringprep.nameprep(s) */ MAKE_PREP_FUNC(Lstringprep_nodeprep, stringprep_xmpp_nodeprep) /** stringprep.nodeprep(s) */ MAKE_PREP_FUNC(Lstringprep_resourceprep, stringprep_xmpp_resourceprep) /** stringprep.resourceprep(s) */ MAKE_PREP_FUNC(Lstringprep_saslprep, stringprep_saslprep) /** stringprep.saslprep(s) */ static const luaL_Reg Reg_stringprep[] = { { "nameprep", Lstringprep_nameprep }, { "nodeprep", Lstringprep_nodeprep }, { "resourceprep", Lstringprep_resourceprep }, { "saslprep", Lstringprep_saslprep }, { NULL, NULL } }; #endif /***************** IDNA *****************/ #ifdef USE_STRINGPREP_ICU #include #include /* IDNA2003 or IDNA2008 ? ? ? */ static int Lidna_to_ascii(lua_State *L) { /** idna.to_ascii(s) */ size_t len; int32_t ulen, dest_len, output_len; const char *s = luaL_checklstring(L, 1, &len); UChar ustr[1024]; UErrorCode err = U_ZERO_ERROR; UChar dest[1024]; char output[1024]; u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); if(U_FAILURE(err)) { luaL_pushfail(L); return 1; } UIDNAInfo info = UIDNA_INFO_INITIALIZER; dest_len = uidna_nameToASCII(icu_idna2008, ustr, ulen, dest, 256, &info, &err); if(U_FAILURE(err) || info.errors) { luaL_pushfail(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); if(U_SUCCESS(err) && output_len < 1024) { lua_pushlstring(L, output, output_len); } else { luaL_pushfail(L); } return 1; } } static int Lidna_to_unicode(lua_State *L) { /** idna.to_unicode(s) */ size_t len; int32_t ulen, dest_len, output_len; const char *s = luaL_checklstring(L, 1, &len); UChar ustr[1024]; UErrorCode err = U_ZERO_ERROR; UChar dest[1024]; char output[1024]; u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); if(U_FAILURE(err)) { luaL_pushfail(L); return 1; } UIDNAInfo info = UIDNA_INFO_INITIALIZER; dest_len = uidna_nameToUnicode(icu_idna2008, ustr, ulen, dest, 1024, &info, &err); if(U_FAILURE(err) || info.errors) { luaL_pushfail(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); if(U_SUCCESS(err) && output_len < 1024) { lua_pushlstring(L, output, output_len); } else { luaL_pushfail(L); } return 1; } } static int Lskeleton(lua_State *L) { size_t len; int32_t ulen, dest_len, output_len; const char *s = luaL_checklstring(L, 1, &len); UErrorCode err = U_ZERO_ERROR; UChar ustr[1024]; UChar dest[1024]; char output[1024]; u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); if(U_FAILURE(err)) { luaL_pushfail(L); return 1; } dest_len = uspoof_getSkeleton(icu_spoofcheck, 0, ustr, ulen, dest, 1024, &err); if(U_FAILURE(err)) { luaL_pushfail(L); return 1; } u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); if(U_SUCCESS(err)) { lua_pushlstring(L, output, output_len); return 1; } luaL_pushfail(L); return 1; } #else /* USE_STRINGPREP_ICU */ /****************** libidn ********************/ #include #include static int Lidna_to_ascii(lua_State *L) { /** idna.to_ascii(s) */ size_t len; const char *s = check_utf8(L, 1, &len); char *output = NULL; int ret; if(s == NULL || len != strlen(s)) { luaL_pushfail(L); return 1; /* TODO return error message */ } ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES); if(ret == IDNA_SUCCESS) { lua_pushstring(L, output); idn_free(output); return 1; } else { luaL_pushfail(L); idn_free(output); return 1; /* TODO return error message */ } } static int Lidna_to_unicode(lua_State *L) { /** idna.to_unicode(s) */ size_t len; const char *s = luaL_checklstring(L, 1, &len); char *output = NULL; int ret = idna_to_unicode_8z8z(s, &output, 0); if(ret == IDNA_SUCCESS) { lua_pushstring(L, output); idn_free(output); return 1; } else { luaL_pushfail(L); idn_free(output); return 1; /* TODO return error message */ } } #endif static const luaL_Reg Reg_idna[] = { { "to_ascii", Lidna_to_ascii }, { "to_unicode", Lidna_to_unicode }, { NULL, NULL } }; /***************** end *****************/ LUALIB_API int luaopen_util_encodings(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif #ifdef USE_STRINGPREP_ICU init_icu(); #endif lua_newtable(L); lua_newtable(L); luaL_setfuncs(L, Reg_base64, 0); lua_setfield(L, -2, "base64"); lua_newtable(L); luaL_setfuncs(L, Reg_stringprep, 0); lua_setfield(L, -2, "stringprep"); lua_newtable(L); luaL_setfuncs(L, Reg_idna, 0); lua_setfield(L, -2, "idna"); lua_newtable(L); luaL_setfuncs(L, Reg_utf8, 0); lua_setfield(L, -2, "utf8"); #ifdef USE_STRINGPREP_ICU lua_newtable(L); lua_pushcfunction(L, Lskeleton); lua_setfield(L, -2, "skeleton"); lua_setfield(L, -2, "confusable"); #endif lua_pushliteral(L, "-3.14"); lua_setfield(L, -2, "version"); return 1; } prosody-0.12.4/util-src/PaxHeaders/hashes.c0000644000000000000000000000012014475665104015545 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/hashes.c0000644000175000017500000001171114475665104017753 0ustar00prosodyprosody00000000000000/* Prosody IM -- Copyright (C) 2009-2010 Matthew Wild -- Copyright (C) 2009-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- */ /* * hashes.c * Lua library for sha1, sha256 and md5 hashes */ #include #include #ifdef _MSC_VER typedef unsigned __int32 uint32_t; #else #include #endif #include "lua.h" #include "lauxlib.h" #include #include #include #include #include #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif #define HMAC_IPAD 0x36363636 #define HMAC_OPAD 0x5c5c5c5c static const char *hex_tab = "0123456789abcdef"; static void toHex(const unsigned char *in, int length, unsigned char *out) { int i; for(i = 0; i < length; i++) { out[i * 2] = hex_tab[(in[i] >> 4) & 0xF]; out[i * 2 + 1] = hex_tab[(in[i]) & 0xF]; } } #define MAKE_HASH_FUNCTION(myFunc, func, size) \ static int myFunc(lua_State *L) { \ size_t len; \ const char *s = luaL_checklstring(L, 1, &len); \ int hex_out = lua_toboolean(L, 2); \ unsigned char hash[size], result[size*2]; \ func((const unsigned char*)s, len, hash); \ if (hex_out) { \ toHex(hash, size, result); \ lua_pushlstring(L, (char*)result, size*2); \ } else { \ lua_pushlstring(L, (char*)hash, size);\ } \ return 1; \ } MAKE_HASH_FUNCTION(Lsha1, SHA1, SHA_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lsha224, SHA224, SHA224_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lsha256, SHA256, SHA256_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lsha384, SHA384, SHA384_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lsha512, SHA512, SHA512_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lmd5, MD5, MD5_DIGEST_LENGTH) struct hash_desc { int (*Init)(void *); int (*Update)(void *, const void *, size_t); int (*Final)(unsigned char *, void *); size_t digestLength; void *ctx, *ctxo; }; #define MAKE_HMAC_FUNCTION(myFunc, evp, size, type) \ static int myFunc(lua_State *L) { \ unsigned char hash[size], result[2*size]; \ size_t key_len, msg_len; \ unsigned int out_len; \ const char *key = luaL_checklstring(L, 1, &key_len); \ const char *msg = luaL_checklstring(L, 2, &msg_len); \ const int hex_out = lua_toboolean(L, 3); \ HMAC(evp(), key, key_len, (const unsigned char*)msg, msg_len, (unsigned char*)hash, &out_len); \ if (hex_out) { \ toHex(hash, out_len, result); \ lua_pushlstring(L, (char*)result, out_len*2); \ } else { \ lua_pushlstring(L, (char*)hash, out_len); \ } \ return 1; \ } MAKE_HMAC_FUNCTION(Lhmac_sha1, EVP_sha1, SHA_DIGEST_LENGTH, SHA_CTX) MAKE_HMAC_FUNCTION(Lhmac_sha256, EVP_sha256, SHA256_DIGEST_LENGTH, SHA256_CTX) MAKE_HMAC_FUNCTION(Lhmac_sha512, EVP_sha512, SHA512_DIGEST_LENGTH, SHA512_CTX) MAKE_HMAC_FUNCTION(Lhmac_md5, EVP_md5, MD5_DIGEST_LENGTH, MD5_CTX) static int Lpbkdf2_sha1(lua_State *L) { unsigned char out[SHA_DIGEST_LENGTH]; size_t pass_len, salt_len; const char *pass = luaL_checklstring(L, 1, &pass_len); const unsigned char *salt = (unsigned char *)luaL_checklstring(L, 2, &salt_len); const int iter = luaL_checkinteger(L, 3); if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha1(), SHA_DIGEST_LENGTH, out) == 0) { return luaL_error(L, "PKCS5_PBKDF2_HMAC() failed"); } lua_pushlstring(L, (char *)out, SHA_DIGEST_LENGTH); return 1; } static int Lpbkdf2_sha256(lua_State *L) { unsigned char out[SHA256_DIGEST_LENGTH]; size_t pass_len, salt_len; const char *pass = luaL_checklstring(L, 1, &pass_len); const unsigned char *salt = (unsigned char *)luaL_checklstring(L, 2, &salt_len); const int iter = luaL_checkinteger(L, 3); if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha256(), SHA256_DIGEST_LENGTH, out) == 0) { return luaL_error(L, "PKCS5_PBKDF2_HMAC() failed"); } lua_pushlstring(L, (char *)out, SHA256_DIGEST_LENGTH); return 1; } static int Lhash_equals(lua_State *L) { size_t len1, len2; const char *s1 = luaL_checklstring(L, 1, &len1); const char *s2 = luaL_checklstring(L, 2, &len2); if(len1 == len2) { lua_pushboolean(L, CRYPTO_memcmp(s1, s2, len1) == 0); } else { lua_pushboolean(L, 0); } return 1; } static const luaL_Reg Reg[] = { { "sha1", Lsha1 }, { "sha224", Lsha224 }, { "sha256", Lsha256 }, { "sha384", Lsha384 }, { "sha512", Lsha512 }, { "md5", Lmd5 }, { "hmac_sha1", Lhmac_sha1 }, { "hmac_sha256", Lhmac_sha256 }, { "hmac_sha512", Lhmac_sha512 }, { "hmac_md5", Lhmac_md5 }, { "scram_Hi_sha1", Lpbkdf2_sha1 }, /* COMPAT */ { "pbkdf2_hmac_sha1", Lpbkdf2_sha1 }, { "pbkdf2_hmac_sha256", Lpbkdf2_sha256 }, { "equals", Lhash_equals }, { NULL, NULL } }; LUALIB_API int luaopen_util_hashes(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif lua_newtable(L); luaL_setfuncs(L, Reg, 0); lua_pushliteral(L, "-3.14"); lua_setfield(L, -2, "version"); #ifdef OPENSSL_VERSION lua_pushstring(L, OpenSSL_version(OPENSSL_VERSION)); lua_setfield(L, -2, "_LIBCRYPTO_VERSION"); #endif return 1; } prosody-0.12.4/util-src/PaxHeaders/make.bat0000644000000000000000000000012014475665104015533 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/make.bat0000644000175000017500000000004114475665104017733 0ustar00prosodyprosody00000000000000@nmake /nologo /f Makefile.win %*prosody-0.12.4/util-src/PaxHeaders/makefile0000644000000000000000000000012014475665104015626 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/makefile0000644000175000017500000000162314475665104020035 0ustar00prosodyprosody00000000000000include ../config.unix CFLAGS+=-I$(LUA_INCDIR) INSTALL_DATA=install -m644 TARGET?=../util/ ALL=encodings.so hashes.so net.so pposix.so signal.so table.so \ ringbuffer.so time.so poll.so compat.so strbitop.so \ struct.so .ifdef $(RANDOM) ALL+=crand.so .endif .PHONY: all install clean .SUFFIXES: .c .o .so all: $(ALL) install: $(ALL) $(INSTALL_DATA) $(ALL) $(TARGET) clean: rm -f $(ALL) $(patsubst %.so,%.o,$(ALL)) encodings.o: encodings.c $(CC) $(CFLAGS) $(IDNA_FLAGS) -c -o $@ $< encodings.so: encodings.o $(LD) $(LDFLAGS) -o $@ $< $(LDLIBS) $(IDNA_LIBS) hashes.so: hashes.o $(LD) $(LDFLAGS) -o $@ $< $(LDLIBS) $(OPENSSL_LIBS) crand.o: crand.c $(CC) $(CFLAGS) -DWITH_$(RANDOM) -c -o $@ $< crand.so: crand.o $(LD) $(LDFLAGS) -o $@ $< $(LDLIBS) $(RANDOM_LIBS) %.so: %.o $(LD) $(LDFLAGS) -o $@ $< $(LDLIBS) .c.o: $(CC) $(CFLAGS) -c -o $@ $< .o.so: $(LD) $(LDFLAGS) -o $@ $< $(LDLIBS) prosody-0.12.4/util-src/PaxHeaders/net.c0000644000000000000000000000012014475665104015060 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/net.c0000644000175000017500000001020314475665104017261 0ustar00prosodyprosody00000000000000/* Prosody IM -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- Copyright (C) 2012 Paul Aurich -- Copyright (C) 2013 Matthew Wild -- Copyright (C) 2013 Florian Zeitz -- */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #ifndef _WIN32 #include #include #include #include #include #include #include #endif #include #include #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif #if (LUA_VERSION_NUM < 504) #define luaL_pushfail lua_pushnil #endif /* Enumerate all locally configured IP addresses */ static const char *const type_strings[] = { "both", "ipv4", "ipv6", NULL }; static int lc_local_addresses(lua_State *L) { #ifndef _WIN32 /* Link-local IPv4 addresses; see RFC 3927 and RFC 5735 */ const uint32_t ip4_linklocal = htonl(0xa9fe0000); /* 169.254.0.0 */ const uint32_t ip4_mask = htonl(0xffff0000); struct ifaddrs *addr = NULL, *a; #endif int n = 1; int type = luaL_checkoption(L, 1, "both", type_strings); const char link_local = lua_toboolean(L, 2); /* defaults to 0 (false) */ const char ipv4 = (type == 0 || type == 1); const char ipv6 = (type == 0 || type == 2); #ifndef _WIN32 if(getifaddrs(&addr) < 0) { luaL_pushfail(L); lua_pushfstring(L, "getifaddrs failed (%d): %s", errno, strerror(errno)); return 2; } #endif lua_newtable(L); #ifndef _WIN32 for(a = addr; a; a = a->ifa_next) { int family; char ipaddr[INET6_ADDRSTRLEN]; const char *tmp = NULL; if(a->ifa_addr == NULL || a->ifa_flags & IFF_LOOPBACK) { continue; } family = a->ifa_addr->sa_family; if(ipv4 && family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)a->ifa_addr; if(!link_local && ((sa->sin_addr.s_addr & ip4_mask) == ip4_linklocal)) { continue; } tmp = inet_ntop(family, &sa->sin_addr, ipaddr, sizeof(ipaddr)); } else if(ipv6 && family == AF_INET6) { struct sockaddr_in6 *sa = (struct sockaddr_in6 *)a->ifa_addr; if(!link_local && IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { continue; } if(IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr) || IN6_IS_ADDR_V4COMPAT(&sa->sin6_addr)) { continue; } tmp = inet_ntop(family, &sa->sin6_addr, ipaddr, sizeof(ipaddr)); } if(tmp != NULL) { lua_pushstring(L, tmp); lua_rawseti(L, -2, n++); } /* TODO: Error reporting? */ } freeifaddrs(addr); #else if(ipv4) { lua_pushstring(L, "0.0.0.0"); lua_rawseti(L, -2, n++); } if(ipv6) { lua_pushstring(L, "::"); lua_rawseti(L, -2, n++); } #endif return 1; } static int lc_pton(lua_State *L) { char buf[16]; const char *ipaddr = luaL_checkstring(L, 1); int errno_ = 0; int family = strchr(ipaddr, ':') ? AF_INET6 : AF_INET; switch(inet_pton(family, ipaddr, &buf)) { case 1: lua_pushlstring(L, buf, family == AF_INET6 ? 16 : 4); return 1; case -1: errno_ = errno; luaL_pushfail(L); lua_pushstring(L, strerror(errno_)); lua_pushinteger(L, errno_); return 3; default: case 0: luaL_pushfail(L); lua_pushstring(L, strerror(EINVAL)); lua_pushinteger(L, EINVAL); return 3; } } static int lc_ntop(lua_State *L) { char buf[INET6_ADDRSTRLEN]; int family; int errno_; size_t l; const char *ipaddr = luaL_checklstring(L, 1, &l); if(l == 16) { family = AF_INET6; } else if(l == 4) { family = AF_INET; } else { luaL_pushfail(L); lua_pushstring(L, strerror(EAFNOSUPPORT)); lua_pushinteger(L, EAFNOSUPPORT); return 3; } if(!inet_ntop(family, ipaddr, buf, INET6_ADDRSTRLEN)) { errno_ = errno; luaL_pushfail(L); lua_pushstring(L, strerror(errno_)); lua_pushinteger(L, errno_); return 3; } lua_pushstring(L, (const char *)(&buf)); return 1; } int luaopen_util_net(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif luaL_Reg exports[] = { { "local_addresses", lc_local_addresses }, { "pton", lc_pton }, { "ntop", lc_ntop }, { NULL, NULL } }; lua_createtable(L, 0, 1); luaL_setfuncs(L, exports, 0); return 1; } prosody-0.12.4/util-src/PaxHeaders/poll.c0000644000000000000000000000012014475665104015240 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/poll.c0000644000175000017500000002777014475665104017462 0ustar00prosodyprosody00000000000000 /* * Lua polling library * Copyright (C) 2017-2022 Kim Alvefur * * This project is MIT licensed. Please see the * COPYING file in the source package for more information. * */ #include #include #include #if defined(__linux__) #define USE_EPOLL #define POLL_BACKEND "epoll" #elif defined(__unix__) #define USE_POLL #define POLL_BACKEND "poll" #else #define USE_SELECT #define POLL_BACKEND "select" #endif #ifdef USE_EPOLL #include #ifndef MAX_EVENTS #define MAX_EVENTS 64 #endif #endif #ifdef USE_POLL #include #ifndef MAX_EVENTS #define MAX_EVENTS 10000 #endif #endif #ifdef USE_SELECT #include #endif #include #include #define STATE_MT "util.poll<" POLL_BACKEND ">" #if (LUA_VERSION_NUM == 501) #define luaL_setmetatable(L, tname) luaL_getmetatable(L, tname); lua_setmetatable(L, -2) #endif #if (LUA_VERSION_NUM < 504) #define luaL_pushfail lua_pushnil #endif /* * Structure to keep state for each type of API */ typedef struct Lpoll_state { int processed; #ifdef USE_EPOLL int epoll_fd; struct epoll_event events[MAX_EVENTS]; #endif #ifdef USE_POLL nfds_t count; struct pollfd events[MAX_EVENTS]; #endif #ifdef USE_SELECT fd_set wantread; fd_set wantwrite; fd_set readable; fd_set writable; fd_set all; fd_set err; #endif } Lpoll_state; /* * Add an FD to be watched */ static int Ladd(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int fd = luaL_checkinteger(L, 2); int wantread = lua_toboolean(L, 3); int wantwrite = lua_toboolean(L, 4); if(fd < 0) { luaL_pushfail(L); lua_pushstring(L, strerror(EBADF)); lua_pushinteger(L, EBADF); return 3; } #ifdef USE_EPOLL struct epoll_event event; event.data.fd = fd; event.events = (wantread ? EPOLLIN : 0) | (wantwrite ? EPOLLOUT : 0); event.events |= EPOLLERR | EPOLLHUP | EPOLLRDHUP; int ret = epoll_ctl(state->epoll_fd, EPOLL_CTL_ADD, fd, &event); if(ret < 0) { ret = errno; luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; } lua_pushboolean(L, 1); return 1; #endif #ifdef USE_POLL for(nfds_t i = 0; i < state->count; i++) { if(state->events[i].fd == fd) { luaL_pushfail(L); lua_pushstring(L, strerror(EEXIST)); lua_pushinteger(L, EEXIST); return 3; } } if(state->count >= MAX_EVENTS) { luaL_pushfail(L); lua_pushstring(L, strerror(EMFILE)); lua_pushinteger(L, EMFILE); return 3; } state->events[state->count].fd = fd; state->events[state->count].events = (wantread ? POLLIN : 0) | (wantwrite ? POLLOUT : 0); state->events[state->count].revents = 0; state->count++; lua_pushboolean(L, 1); return 1; #endif #ifdef USE_SELECT if(fd > FD_SETSIZE) { luaL_pushfail(L); lua_pushstring(L, strerror(EBADF)); lua_pushinteger(L, EBADF); return 3; } if(FD_ISSET(fd, &state->all)) { luaL_pushfail(L); lua_pushstring(L, strerror(EEXIST)); lua_pushinteger(L, EEXIST); return 3; } FD_CLR(fd, &state->readable); FD_CLR(fd, &state->writable); FD_CLR(fd, &state->err); FD_SET(fd, &state->all); if(wantread) { FD_SET(fd, &state->wantread); } else { FD_CLR(fd, &state->wantread); } if(wantwrite) { FD_SET(fd, &state->wantwrite); } else { FD_CLR(fd, &state->wantwrite); } lua_pushboolean(L, 1); return 1; #endif } /* * Set events to watch for, readable and/or writable */ static int Lset(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int fd = luaL_checkinteger(L, 2); #ifdef USE_EPOLL int wantread = lua_toboolean(L, 3); int wantwrite = lua_toboolean(L, 4); struct epoll_event event; event.data.fd = fd; event.events = (wantread ? EPOLLIN : 0) | (wantwrite ? EPOLLOUT : 0); event.events |= EPOLLERR | EPOLLHUP | EPOLLRDHUP; int ret = epoll_ctl(state->epoll_fd, EPOLL_CTL_MOD, fd, &event); if(ret == 0) { lua_pushboolean(L, 1); return 1; } else { ret = errno; luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; } #endif #ifdef USE_POLL int wantread = lua_toboolean(L, 3); int wantwrite = lua_toboolean(L, 4); for(nfds_t i = 0; i < state->count; i++) { struct pollfd *event = &state->events[i]; if(event->fd == fd) { event->events = (wantread ? POLLIN : 0) | (wantwrite ? POLLOUT : 0); lua_pushboolean(L, 1); return 1; } else if(event->fd == -1) { break; } } luaL_pushfail(L); lua_pushstring(L, strerror(ENOENT)); lua_pushinteger(L, ENOENT); return 3; #endif #ifdef USE_SELECT if(!FD_ISSET(fd, &state->all)) { luaL_pushfail(L); lua_pushstring(L, strerror(ENOENT)); lua_pushinteger(L, ENOENT); return 3; } if(!lua_isnoneornil(L, 3)) { if(lua_toboolean(L, 3)) { FD_SET(fd, &state->wantread); } else { FD_CLR(fd, &state->wantread); } } if(!lua_isnoneornil(L, 4)) { if(lua_toboolean(L, 4)) { FD_SET(fd, &state->wantwrite); } else { FD_CLR(fd, &state->wantwrite); } } lua_pushboolean(L, 1); return 1; #endif } /* * Remove FDs */ static int Ldel(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int fd = luaL_checkinteger(L, 2); #ifdef USE_EPOLL struct epoll_event event; event.data.fd = fd; int ret = epoll_ctl(state->epoll_fd, EPOLL_CTL_DEL, fd, &event); if(ret == 0) { lua_pushboolean(L, 1); return 1; } else { ret = errno; luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; } #endif #ifdef USE_POLL if(state->count == 0) { luaL_pushfail(L); lua_pushstring(L, strerror(ENOENT)); lua_pushinteger(L, ENOENT); return 3; } /* * Move the last item on top of the removed one */ struct pollfd *last = &state->events[state->count - 1]; for(nfds_t i = 0; i < state->count; i++) { struct pollfd *event = &state->events[i]; if(event->fd == fd) { event->fd = last->fd; event->events = last->events; event->revents = last->revents; last->fd = -1; state->count--; lua_pushboolean(L, 1); return 1; } } luaL_pushfail(L); lua_pushstring(L, strerror(ENOENT)); lua_pushinteger(L, ENOENT); return 3; #endif #ifdef USE_SELECT if(!FD_ISSET(fd, &state->all)) { luaL_pushfail(L); lua_pushstring(L, strerror(ENOENT)); lua_pushinteger(L, ENOENT); return 3; } FD_CLR(fd, &state->wantread); FD_CLR(fd, &state->wantwrite); FD_CLR(fd, &state->readable); FD_CLR(fd, &state->writable); FD_CLR(fd, &state->all); FD_CLR(fd, &state->err); lua_pushboolean(L, 1); return 1; #endif } /* * Check previously manipulated event state for FDs ready for reading or writing */ static int Lpushevent(lua_State *L, struct Lpoll_state *state) { #ifdef USE_EPOLL if(state->processed > 0) { state->processed--; struct epoll_event event = state->events[state->processed]; lua_pushinteger(L, event.data.fd); lua_pushboolean(L, event.events & (EPOLLIN | EPOLLHUP | EPOLLRDHUP | EPOLLERR)); lua_pushboolean(L, event.events & EPOLLOUT); return 3; } #endif #ifdef USE_POLL for(int i = state->processed - 1; i >= 0; i--) { struct pollfd *event = &state->events[i]; if(event->fd != -1 && event->revents != 0) { lua_pushinteger(L, event->fd); lua_pushboolean(L, event->revents & (POLLIN | POLLHUP | POLLERR)); lua_pushboolean(L, event->revents & POLLOUT); event->revents = 0; state->processed = i; return 3; } } #endif #ifdef USE_SELECT for(int fd = state->processed + 1; fd < FD_SETSIZE; fd++) { if(FD_ISSET(fd, &state->readable) || FD_ISSET(fd, &state->writable) || FD_ISSET(fd, &state->err)) { lua_pushinteger(L, fd); lua_pushboolean(L, FD_ISSET(fd, &state->readable) | FD_ISSET(fd, &state->err)); lua_pushboolean(L, FD_ISSET(fd, &state->writable)); FD_CLR(fd, &state->readable); FD_CLR(fd, &state->writable); FD_CLR(fd, &state->err); state->processed = fd; return 3; } } #endif return 0; } /* * Wait for event */ static int Lwait(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int ret = Lpushevent(L, state); if(ret != 0) { return ret; } lua_Number timeout = luaL_checknumber(L, 2); luaL_argcheck(L, timeout >= 0, 1, "positive number expected"); #ifdef USE_EPOLL ret = epoll_wait(state->epoll_fd, state->events, MAX_EVENTS, timeout * 1000); #endif #ifdef USE_POLL ret = poll(state->events, state->count, timeout * 1000); #endif #ifdef USE_SELECT /* * select(2) mutates the fd_sets passed to it so in order to not * have to recreate it manually every time a copy is made. */ memcpy(&state->readable, &state->wantread, sizeof(fd_set)); memcpy(&state->writable, &state->wantwrite, sizeof(fd_set)); memcpy(&state->err, &state->all, sizeof(fd_set)); struct timeval tv; tv.tv_sec = (time_t)timeout; tv.tv_usec = ((suseconds_t)(timeout * 1000000)) % 1000000; ret = select(FD_SETSIZE, &state->readable, &state->writable, &state->err, &tv); #endif if(ret == 0) { /* Is this an error? */ lua_pushnil(L); lua_pushstring(L, "timeout"); return 2; } else if(ret < 0 && errno == EINTR) { /* Is this an error? */ lua_pushnil(L); lua_pushstring(L, "signal"); return 2; } else if(ret < 0) { ret = errno; luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; } /* * Search for the first ready FD and return it */ #ifdef USE_EPOLL state->processed = ret; #endif #ifdef USE_POLL state->processed = state->count; #endif #ifdef USE_SELECT state->processed = -1; #endif return Lpushevent(L, state); } #ifdef USE_EPOLL /* * Return Epoll FD */ static int Lgetfd(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); lua_pushinteger(L, state->epoll_fd); return 1; } /* * Close epoll FD */ static int Lgc(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); if(state->epoll_fd == -1) { return 0; } if(close(state->epoll_fd) == 0) { state->epoll_fd = -1; } else { lua_pushstring(L, strerror(errno)); lua_error(L); } return 0; } #endif /* * String representation */ static int Ltos(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); lua_pushfstring(L, "%s: %p", STATE_MT, state); return 1; } /* * Create a new context */ static int Lnew(lua_State *L) { /* Allocate state */ Lpoll_state *state = lua_newuserdata(L, sizeof(Lpoll_state)); luaL_setmetatable(L, STATE_MT); /* Initialize state */ #ifdef USE_EPOLL state->epoll_fd = -1; state->processed = 0; int epoll_fd = epoll_create1(EPOLL_CLOEXEC); if(epoll_fd <= 0) { luaL_pushfail(L); lua_pushstring(L, strerror(errno)); lua_pushinteger(L, errno); return 3; } state->epoll_fd = epoll_fd; #endif #ifdef USE_POLL state->processed = -1; state->count = 0; for(nfds_t i = 0; i < MAX_EVENTS; i++) { state->events[i].fd = -1; state->events[i].events = 0; state->events[i].revents = 0; } #endif #ifdef USE_SELECT FD_ZERO(&state->wantread); FD_ZERO(&state->wantwrite); FD_ZERO(&state->readable); FD_ZERO(&state->writable); FD_ZERO(&state->all); FD_ZERO(&state->err); state->processed = FD_SETSIZE; #endif return 1; } /* * Open library */ int luaopen_util_poll(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif luaL_newmetatable(L, STATE_MT); { lua_pushliteral(L, STATE_MT); lua_setfield(L, -2, "__name"); lua_pushcfunction(L, Ltos); lua_setfield(L, -2, "__tostring"); lua_createtable(L, 0, 2); { lua_pushcfunction(L, Ladd); lua_setfield(L, -2, "add"); lua_pushcfunction(L, Lset); lua_setfield(L, -2, "set"); lua_pushcfunction(L, Ldel); lua_setfield(L, -2, "del"); lua_pushcfunction(L, Lwait); lua_setfield(L, -2, "wait"); #ifdef USE_EPOLL lua_pushcfunction(L, Lgetfd); lua_setfield(L, -2, "getfd"); #endif } lua_setfield(L, -2, "__index"); #ifdef USE_EPOLL lua_pushcfunction(L, Lgc); lua_setfield(L, -2, "__gc"); #endif } lua_createtable(L, 0, 3); { lua_pushcfunction(L, Lnew); lua_setfield(L, -2, "new"); #define push_errno(named_error) lua_pushinteger(L, named_error);\ lua_setfield(L, -2, #named_error); push_errno(EEXIST); push_errno(EMFILE); push_errno(ENOENT); lua_pushliteral(L, POLL_BACKEND); lua_setfield(L, -2, "api"); } return 1; } prosody-0.12.4/util-src/PaxHeaders/pposix.c0000644000000000000000000000012014475665104015614 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/pposix.c0000644000175000017500000004172314475665104020030 0ustar00prosodyprosody00000000000000/* Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2009 Tobias Markmann -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- */ /* * pposix.c * POSIX support functions for Lua */ #define MODULE_VERSION "0.4.0" #if defined(__linux__) #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #else #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #endif #if defined(__APPLE__) #ifndef _DARWIN_C_SOURCE #define _DARWIN_C_SOURCE #endif #endif #if ! defined(__FreeBSD__) #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lua.h" #include "lualib.h" #include "lauxlib.h" #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif #if (LUA_VERSION_NUM < 503) #define lua_isinteger(L, n) lua_isnumber(L, n) #endif #if (LUA_VERSION_NUM < 504) #define luaL_pushfail lua_pushnil #endif #include #if defined(__linux__) #include #endif #if !defined(WITHOUT_MALLINFO) && defined(__linux__) && defined(__GLIBC__) #include #define WITH_MALLINFO #endif #if defined(__FreeBSD__) && defined(RFPROC) /* * On FreeBSD, calling fork() is equivalent to rfork(RFPROC | RFFDG). * * RFFDG being set means that the file descriptor table is copied, * otherwise it's shared. We want the later, otherwise libevent gets * messed up. * * See issue #412 */ #define fork() rfork(RFPROC) #endif /* Daemonization support */ static int lc_daemonize(lua_State *L) { pid_t pid; if(getppid() == 1) { lua_pushboolean(L, 0); lua_pushstring(L, "already-daemonized"); return 2; } /* Attempt initial fork */ if((pid = fork()) < 0) { /* Forking failed */ lua_pushboolean(L, 0); lua_pushstring(L, "fork-failed"); return 2; } else if(pid != 0) { /* We are the parent process */ lua_pushboolean(L, 1); lua_pushinteger(L, pid); return 2; } /* and we are the child process */ if(setsid() == -1) { /* We failed to become session leader */ /* (we probably already were) */ lua_pushboolean(L, 0); lua_pushstring(L, "setsid-failed"); return 2; } /* Make sure accidental use of FDs 0, 1, 2 don't cause weirdness */ freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); /* Final fork, use it wisely */ if(fork()) { exit(0); } /* Show's over, let's continue */ lua_pushboolean(L, 1); lua_pushnil(L); return 2; } /* Syslog support */ static const char *const facility_strings[] = { "auth", #if !(defined(sun) || defined(__sun)) "authpriv", #endif "cron", "daemon", #if !(defined(sun) || defined(__sun)) "ftp", #endif "kern", "local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7", "lpr", "mail", "syslog", "user", "uucp", NULL }; static int facility_constants[] = { LOG_AUTH, #if !(defined(sun) || defined(__sun)) LOG_AUTHPRIV, #endif LOG_CRON, LOG_DAEMON, #if !(defined(sun) || defined(__sun)) LOG_FTP, #endif LOG_KERN, LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7, LOG_LPR, LOG_MAIL, LOG_NEWS, LOG_SYSLOG, LOG_USER, LOG_UUCP, -1 }; /* " The parameter ident in the call of openlog() is probably stored as-is. Thus, if the string it points to is changed, syslog() may start prepending the changed string, and if the string it points to ceases to exist, the results are undefined. Most portable is to use a string constant. " -- syslog manpage */ static char *syslog_ident = NULL; static int lc_syslog_open(lua_State *L) { int facility = luaL_checkoption(L, 2, "daemon", facility_strings); facility = facility_constants[facility]; luaL_checkstring(L, 1); if(syslog_ident) { free(syslog_ident); } syslog_ident = strdup(lua_tostring(L, 1)); openlog(syslog_ident, LOG_PID, facility); return 0; } static const char *const level_strings[] = { "debug", "info", "notice", "warn", "error", NULL }; static int level_constants[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING, LOG_CRIT, -1 }; static int lc_syslog_log(lua_State *L) { int level = level_constants[luaL_checkoption(L, 1, "notice", level_strings)]; if(lua_gettop(L) == 3) { syslog(level, "%s: %s", luaL_checkstring(L, 2), luaL_checkstring(L, 3)); } else { syslog(level, "%s", lua_tostring(L, 2)); } return 0; } static int lc_syslog_close(lua_State *L) { (void)L; closelog(); if(syslog_ident) { free(syslog_ident); syslog_ident = NULL; } return 0; } static int lc_syslog_setmask(lua_State *L) { int level_idx = luaL_checkoption(L, 1, "notice", level_strings); int mask = 0; do { mask |= LOG_MASK(level_constants[level_idx]); } while(++level_idx <= 4); setlogmask(mask); return 0; } /* getpid */ static int lc_getpid(lua_State *L) { lua_pushinteger(L, getpid()); return 1; } /* UID/GID functions */ static int lc_getuid(lua_State *L) { lua_pushinteger(L, getuid()); return 1; } static int lc_getgid(lua_State *L) { lua_pushinteger(L, getgid()); return 1; } static int lc_setuid(lua_State *L) { int uid = -1; if(lua_gettop(L) < 1) { return 0; } if(!lua_isinteger(L, 1) && lua_tostring(L, 1)) { /* Passed UID is actually a string, so look up the UID */ struct passwd *p; p = getpwnam(lua_tostring(L, 1)); if(!p) { lua_pushboolean(L, 0); lua_pushstring(L, "no-such-user"); return 2; } uid = p->pw_uid; } else { uid = lua_tointeger(L, 1); } if(uid > -1) { /* Ok, attempt setuid */ errno = 0; if(setuid(uid)) { /* Fail */ lua_pushboolean(L, 0); switch(errno) { case EINVAL: lua_pushstring(L, "invalid-uid"); break; case EPERM: lua_pushstring(L, "permission-denied"); break; default: lua_pushstring(L, "unknown-error"); } return 2; } else { /* Success! */ lua_pushboolean(L, 1); return 1; } } /* Seems we couldn't find a valid UID to switch to */ lua_pushboolean(L, 0); lua_pushstring(L, "invalid-uid"); return 2; } static int lc_setgid(lua_State *L) { int gid = -1; if(lua_gettop(L) < 1) { return 0; } if(!lua_isinteger(L, 1) && lua_tostring(L, 1)) { /* Passed GID is actually a string, so look up the GID */ struct group *g; g = getgrnam(lua_tostring(L, 1)); if(!g) { lua_pushboolean(L, 0); lua_pushstring(L, "no-such-group"); return 2; } gid = g->gr_gid; } else { gid = lua_tointeger(L, 1); } if(gid > -1) { /* Ok, attempt setgid */ errno = 0; if(setgid(gid)) { /* Fail */ lua_pushboolean(L, 0); switch(errno) { case EINVAL: lua_pushstring(L, "invalid-gid"); break; case EPERM: lua_pushstring(L, "permission-denied"); break; default: lua_pushstring(L, "unknown-error"); } return 2; } else { /* Success! */ lua_pushboolean(L, 1); return 1; } } /* Seems we couldn't find a valid GID to switch to */ lua_pushboolean(L, 0); lua_pushstring(L, "invalid-gid"); return 2; } static int lc_initgroups(lua_State *L) { int ret; gid_t gid; struct passwd *p; if(!lua_isstring(L, 1)) { luaL_pushfail(L); lua_pushstring(L, "invalid-username"); return 2; } p = getpwnam(lua_tostring(L, 1)); if(!p) { luaL_pushfail(L); lua_pushstring(L, "no-such-user"); return 2; } if(lua_gettop(L) < 2) { lua_pushnil(L); } switch(lua_type(L, 2)) { case LUA_TNIL: gid = p->pw_gid; break; case LUA_TNUMBER: gid = lua_tointeger(L, 2); break; default: luaL_pushfail(L); lua_pushstring(L, "invalid-gid"); return 2; } ret = initgroups(lua_tostring(L, 1), gid); if(ret) { switch(errno) { case ENOMEM: luaL_pushfail(L); lua_pushstring(L, "no-memory"); break; case EPERM: luaL_pushfail(L); lua_pushstring(L, "permission-denied"); break; default: luaL_pushfail(L); lua_pushstring(L, "unknown-error"); } } else { lua_pushboolean(L, 1); lua_pushnil(L); } return 2; } static int lc_umask(lua_State *L) { char old_mode_string[7]; mode_t old_mode = umask(strtoul(luaL_checkstring(L, 1), NULL, 8)); snprintf(old_mode_string, sizeof(old_mode_string), "%03o", old_mode); old_mode_string[sizeof(old_mode_string) - 1] = 0; lua_pushstring(L, old_mode_string); return 1; } static int lc_mkdir(lua_State *L) { int ret = mkdir(luaL_checkstring(L, 1), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); /* mode 775 */ lua_pushboolean(L, ret == 0); if(ret) { lua_pushstring(L, strerror(errno)); return 2; } return 1; } /* Like POSIX's setrlimit()/getrlimit() API functions. * * Syntax: * pposix.setrlimit( resource, soft limit, hard limit) * * Any negative limit will be replace with the current limit by an additional call of getrlimit(). * * Example usage: * pposix.setrlimit("NOFILE", 1000, 2000) */ static int string2resource(const char *s) { if(!strcmp(s, "CORE")) { return RLIMIT_CORE; } if(!strcmp(s, "CPU")) { return RLIMIT_CPU; } if(!strcmp(s, "DATA")) { return RLIMIT_DATA; } if(!strcmp(s, "FSIZE")) { return RLIMIT_FSIZE; } if(!strcmp(s, "NOFILE")) { return RLIMIT_NOFILE; } if(!strcmp(s, "STACK")) { return RLIMIT_STACK; } #if !(defined(sun) || defined(__sun) || defined(__APPLE__)) if(!strcmp(s, "MEMLOCK")) { return RLIMIT_MEMLOCK; } if(!strcmp(s, "NPROC")) { return RLIMIT_NPROC; } if(!strcmp(s, "RSS")) { return RLIMIT_RSS; } #endif #ifdef RLIMIT_NICE if(!strcmp(s, "NICE")) { return RLIMIT_NICE; } #endif return -1; } static rlim_t arg_to_rlimit(lua_State *L, int idx, rlim_t current) { switch(lua_type(L, idx)) { case LUA_TSTRING: if(strcmp(lua_tostring(L, idx), "unlimited") == 0) { return RLIM_INFINITY; } return luaL_argerror(L, idx, "unexpected type"); case LUA_TNUMBER: return lua_tointeger(L, idx); case LUA_TNONE: case LUA_TNIL: return current; default: return luaL_argerror(L, idx, "unexpected type"); } } static int lc_setrlimit(lua_State *L) { struct rlimit lim; int arguments = lua_gettop(L); int rid = -1; if(arguments < 1 || arguments > 3) { lua_pushboolean(L, 0); lua_pushstring(L, "incorrect-arguments"); return 2; } rid = string2resource(luaL_checkstring(L, 1)); if(rid == -1) { lua_pushboolean(L, 0); lua_pushstring(L, "invalid-resource"); return 2; } /* Fetch current values to use as defaults */ if(getrlimit(rid, &lim)) { lua_pushboolean(L, 0); lua_pushstring(L, "getrlimit-failed"); return 2; } lim.rlim_cur = arg_to_rlimit(L, 2, lim.rlim_cur); lim.rlim_max = arg_to_rlimit(L, 3, lim.rlim_max); if(setrlimit(rid, &lim)) { lua_pushboolean(L, 0); lua_pushstring(L, "setrlimit-failed"); return 2; } lua_pushboolean(L, 1); return 1; } static int lc_getrlimit(lua_State *L) { int arguments = lua_gettop(L); const char *resource = NULL; int rid = -1; struct rlimit lim; if(arguments != 1) { lua_pushboolean(L, 0); lua_pushstring(L, "invalid-arguments"); return 2; } resource = luaL_checkstring(L, 1); rid = string2resource(resource); if(rid != -1) { if(getrlimit(rid, &lim)) { lua_pushboolean(L, 0); lua_pushstring(L, "getrlimit-failed."); return 2; } } else { /* Unsupported resource. Sorry I'm pretty limited by POSIX standard. */ lua_pushboolean(L, 0); lua_pushstring(L, "invalid-resource"); return 2; } lua_pushboolean(L, 1); if(lim.rlim_cur == RLIM_INFINITY) { lua_pushstring(L, "unlimited"); } else { lua_pushinteger(L, lim.rlim_cur); } if(lim.rlim_max == RLIM_INFINITY) { lua_pushstring(L, "unlimited"); } else { lua_pushinteger(L, lim.rlim_max); } return 3; } static int lc_abort(lua_State *L) { (void)L; abort(); return 0; } static int lc_uname(lua_State *L) { struct utsname uname_info; if(uname(&uname_info) != 0) { luaL_pushfail(L); lua_pushstring(L, strerror(errno)); return 2; } lua_createtable(L, 0, 6); lua_pushstring(L, uname_info.sysname); lua_setfield(L, -2, "sysname"); lua_pushstring(L, uname_info.nodename); lua_setfield(L, -2, "nodename"); lua_pushstring(L, uname_info.release); lua_setfield(L, -2, "release"); lua_pushstring(L, uname_info.version); lua_setfield(L, -2, "version"); lua_pushstring(L, uname_info.machine); lua_setfield(L, -2, "machine"); #ifdef __USE_GNU lua_pushstring(L, uname_info.domainname); lua_setfield(L, -2, "domainname"); #endif return 1; } static int lc_setenv(lua_State *L) { const char *var = luaL_checkstring(L, 1); const char *value; /* If the second argument is nil or nothing, unset the var */ if(lua_isnoneornil(L, 2)) { if(unsetenv(var) != 0) { luaL_pushfail(L); lua_pushstring(L, strerror(errno)); return 2; } lua_pushboolean(L, 1); return 1; } value = luaL_checkstring(L, 2); if(setenv(var, value, 1) != 0) { luaL_pushfail(L); lua_pushstring(L, strerror(errno)); return 2; } lua_pushboolean(L, 1); return 1; } #ifdef WITH_MALLINFO static int lc_meminfo(lua_State *L) { #if __GLIBC_PREREQ(2, 33) struct mallinfo2 info = mallinfo2(); #define MALLINFO_T size_t #else struct mallinfo info = mallinfo(); #define MALLINFO_T unsigned #endif lua_createtable(L, 0, 5); /* This is the total size of memory allocated with sbrk by malloc, in bytes. */ lua_pushinteger(L, (MALLINFO_T)info.arena); lua_setfield(L, -2, "allocated"); /* This is the total size of memory allocated with mmap, in bytes. */ lua_pushinteger(L, (MALLINFO_T)info.hblkhd); lua_setfield(L, -2, "allocated_mmap"); /* This is the total size of memory occupied by chunks handed out by malloc. */ lua_pushinteger(L, (MALLINFO_T)info.uordblks); lua_setfield(L, -2, "used"); /* This is the total size of memory occupied by free (not in use) chunks. */ lua_pushinteger(L, (MALLINFO_T)info.fordblks); lua_setfield(L, -2, "unused"); /* This is the size of the top-most releasable chunk that normally borders the end of the heap (i.e., the high end of the virtual address space's data segment). */ lua_pushinteger(L, (MALLINFO_T)info.keepcost); lua_setfield(L, -2, "returnable"); return 1; } #undef MALLINFO_T #endif /* * Append some data to a file handle * Attempt to allocate space first * Truncate to original size on failure */ static int lc_atomic_append(lua_State *L) { int err; size_t len; FILE *f = *(FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE); const char *data = luaL_checklstring(L, 2, &len); off_t offset = ftell(f); #if defined(__linux__) /* Try to allocate space without changing the file size. */ if((err = fallocate(fileno(f), FALLOC_FL_KEEP_SIZE, offset, len))) { if(errno != 0) { /* Some old versions of Linux apparently use the return value instead of errno */ err = errno; } switch(err) { case ENOSYS: /* Kernel doesn't implement fallocate */ case EOPNOTSUPP: /* Filesystem doesn't support it */ /* Ignore and proceed to try to write */ break; case ENOSPC: /* No space left */ default: /* Other issues */ luaL_pushfail(L); lua_pushstring(L, strerror(err)); lua_pushinteger(L, err); return 3; } } #endif if(fwrite(data, sizeof(char), len, f) == len) { if(fflush(f) == 0) { lua_pushboolean(L, 1); /* Great success! */ return 1; } else { err = errno; } } else { err = ferror(f); } fseek(f, offset, SEEK_SET); /* Cut partially written data */ if(ftruncate(fileno(f), offset)) { /* The file is now most likely corrupted, throw hard error */ return luaL_error(L, "atomic_append() failed in ftruncate(): %s", strerror(errno)); } luaL_pushfail(L); lua_pushstring(L, strerror(err)); lua_pushinteger(L, err); return 3; } static int lc_isatty(lua_State *L) { FILE *f = *(FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE); const int fd = fileno(f); lua_pushboolean(L, isatty(fd)); return 1; } /* Register functions */ int luaopen_util_pposix(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif luaL_Reg exports[] = { { "abort", lc_abort }, { "daemonize", lc_daemonize }, { "syslog_open", lc_syslog_open }, { "syslog_close", lc_syslog_close }, { "syslog_log", lc_syslog_log }, { "syslog_setminlevel", lc_syslog_setmask }, { "getpid", lc_getpid }, { "getuid", lc_getuid }, { "getgid", lc_getgid }, { "setuid", lc_setuid }, { "setgid", lc_setgid }, { "initgroups", lc_initgroups }, { "umask", lc_umask }, { "mkdir", lc_mkdir }, { "setrlimit", lc_setrlimit }, { "getrlimit", lc_getrlimit }, { "uname", lc_uname }, { "setenv", lc_setenv }, #ifdef WITH_MALLINFO { "meminfo", lc_meminfo }, #endif { "atomic_append", lc_atomic_append }, { "isatty", lc_isatty }, { NULL, NULL } }; lua_newtable(L); luaL_setfuncs(L, exports, 0); #ifdef ENOENT lua_pushinteger(L, ENOENT); lua_setfield(L, -2, "ENOENT"); #endif lua_pushliteral(L, "pposix"); lua_setfield(L, -2, "_NAME"); lua_pushliteral(L, MODULE_VERSION); lua_setfield(L, -2, "_VERSION"); return 1; } prosody-0.12.4/util-src/PaxHeaders/ringbuffer.c0000644000000000000000000000012014475665104016423 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/ringbuffer.c0000644000175000017500000001663514475665104020643 0ustar00prosodyprosody00000000000000 #include #include #include #include #include #if (LUA_VERSION_NUM < 504) #define luaL_pushfail lua_pushnil #endif typedef struct { size_t rpos; /* read position */ size_t wpos; /* write position */ size_t alen; /* allocated size */ size_t blen; /* current content size */ char buffer[]; } ringbuffer; /* Translate absolute idx to a wrapped index within the buffer, based on current read position */ static int wrap_pos(const ringbuffer *b, const long idx, long *pos) { if(idx > (long)b->blen) { return 0; } if(idx + (long)b->rpos > (long)b->alen) { *pos = idx - (b->alen - b->rpos); } else { *pos = b->rpos + idx; } return 1; } static int calc_splice_positions(const ringbuffer *b, long start, long end, long *out_start, long *out_end) { if(start < 0) { start = 1 + start + b->blen; } if(start <= 0) { start = 1; } if(end < 0) { end = 1 + end + b->blen; } if(end > (long)b->blen) { end = b->blen; } if(start < 1) { start = 1; } if(start > end) { return 0; } start = start - 1; if(!wrap_pos(b, start, out_start)) { return 0; } if(!wrap_pos(b, end, out_end)) { return 0; } return 1; } static void writechar(ringbuffer *b, char c) { b->blen++; b->buffer[(b->wpos++) % b->alen] = c; } /* make sure position counters stay within the allocation */ static void modpos(ringbuffer *b) { b->rpos = b->rpos % b->alen; b->wpos = b->wpos % b->alen; } static int find(ringbuffer *b, const char *s, size_t l) { size_t i, j; int m; if(b->rpos == b->wpos) { /* empty */ return 0; } /* look for a matching first byte */ for(i = 0; i <= b->blen - l; i++) { if(b->buffer[(b->rpos + i) % b->alen] == *s) { m = 1; /* check if the following byte also match */ for(j = 1; j < l; j++) if(b->buffer[(b->rpos + i + j) % b->alen] != s[j]) { m = 0; break; } if(m) { return i + l; } } } return 0; } /* * Find first position of a substring in buffer * (buffer, string) -> number */ static int rb_find(lua_State *L) { size_t l, m; ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); const char *s = luaL_checklstring(L, 2, &l); m = find(b, s, l); if(m > 0) { lua_pushinteger(L, m); return 1; } return 0; } /* * Move read position forward without returning the data * (buffer, number) -> boolean */ static int rb_discard(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); size_t r = luaL_checkinteger(L, 2); if(r > b->blen) { lua_pushboolean(L, 0); return 1; } b->blen -= r; b->rpos += r; modpos(b); lua_pushboolean(L, 1); return 1; } /* * Read bytes from buffer * (buffer, number, boolean?) -> string */ static int rb_read(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); size_t r = luaL_checkinteger(L, 2); int peek = lua_toboolean(L, 3); if(r > b->blen) { luaL_pushfail(L); return 1; } if((b->rpos + r) > b->alen) { /* Substring wraps around to the beginning of the buffer */ lua_pushlstring(L, &b->buffer[b->rpos], b->alen - b->rpos); lua_pushlstring(L, b->buffer, r - (b->alen - b->rpos)); lua_concat(L, 2); } else { lua_pushlstring(L, &b->buffer[b->rpos], r); } if(!peek) { b->blen -= r; b->rpos += r; modpos(b); } return 1; } /* * Read buffer until first occurrence of a substring * (buffer, string) -> string */ static int rb_readuntil(lua_State *L) { size_t l, m; ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); const char *s = luaL_checklstring(L, 2, &l); m = find(b, s, l); if(m > 0) { lua_settop(L, 1); lua_pushinteger(L, m); return rb_read(L); } return 0; } /* * Write bytes into the buffer * (buffer, string) -> integer */ static int rb_write(lua_State *L) { size_t l, w = 0; ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); const char *s = luaL_checklstring(L, 2, &l); /* Does `l` bytes fit? */ if((l + b->blen) > b->alen) { luaL_pushfail(L); return 1; } while(l-- > 0) { writechar(b, *s++); w++; } modpos(b); lua_pushinteger(L, w); return 1; } static int rb_tostring(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); lua_pushfstring(L, "ringbuffer: %p %d/%d", b, b->blen, b->alen); return 1; } static int rb_sub(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); long start = luaL_checkinteger(L, 2); long end = luaL_optinteger(L, 3, -1); long wrapped_start, wrapped_end; if(!calc_splice_positions(b, start, end, &wrapped_start, &wrapped_end)) { lua_pushstring(L, ""); } else if(wrapped_end <= wrapped_start) { lua_pushlstring(L, &b->buffer[wrapped_start], b->alen - wrapped_start); lua_pushlstring(L, b->buffer, wrapped_end); lua_concat(L, 2); } else { lua_pushlstring(L, &b->buffer[wrapped_start], (wrapped_end - wrapped_start)); } return 1; } static int rb_byte(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); long start = luaL_optinteger(L, 2, 1); long end = luaL_optinteger(L, 3, start); long i; long wrapped_start, wrapped_end; if(calc_splice_positions(b, start, end, &wrapped_start, &wrapped_end)) { if(wrapped_end <= wrapped_start) { for(i = wrapped_start; i < (long)b->alen; i++) { lua_pushinteger(L, (unsigned char)b->buffer[i]); } for(i = 0; i < wrapped_end; i++) { lua_pushinteger(L, (unsigned char)b->buffer[i]); } return wrapped_end + (b->alen - wrapped_start); } else { for(i = wrapped_start; i < wrapped_end; i++) { lua_pushinteger(L, (unsigned char)b->buffer[i]); } return wrapped_end - wrapped_start; } } return 0; } static int rb_length(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); lua_pushinteger(L, b->blen); return 1; } static int rb_size(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); lua_pushinteger(L, b->alen); return 1; } static int rb_free(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); lua_pushinteger(L, b->alen - b->blen); return 1; } static int rb_new(lua_State *L) { lua_Integer size = luaL_optinteger(L, 1, sysconf(_SC_PAGESIZE)); luaL_argcheck(L, size > 0, 1, "positive integer expected"); ringbuffer *b = lua_newuserdata(L, sizeof(ringbuffer) + size); b->rpos = 0; b->wpos = 0; b->alen = size; b->blen = 0; luaL_getmetatable(L, "ringbuffer_mt"); lua_setmetatable(L, -2); return 1; } int luaopen_util_ringbuffer(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif if(luaL_newmetatable(L, "ringbuffer_mt")) { lua_pushcfunction(L, rb_tostring); lua_setfield(L, -2, "__tostring"); lua_pushcfunction(L, rb_length); lua_setfield(L, -2, "__len"); lua_createtable(L, 0, 7); /* __index */ { lua_pushcfunction(L, rb_find); lua_setfield(L, -2, "find"); lua_pushcfunction(L, rb_discard); lua_setfield(L, -2, "discard"); lua_pushcfunction(L, rb_read); lua_setfield(L, -2, "read"); lua_pushcfunction(L, rb_readuntil); lua_setfield(L, -2, "readuntil"); lua_pushcfunction(L, rb_write); lua_setfield(L, -2, "write"); lua_pushcfunction(L, rb_size); lua_setfield(L, -2, "size"); lua_pushcfunction(L, rb_length); lua_setfield(L, -2, "length"); lua_pushcfunction(L, rb_sub); lua_setfield(L, -2, "sub"); lua_pushcfunction(L, rb_byte); lua_setfield(L, -2, "byte"); lua_pushcfunction(L, rb_free); lua_setfield(L, -2, "free"); } lua_setfield(L, -2, "__index"); } lua_createtable(L, 0, 1); lua_pushcfunction(L, rb_new); lua_setfield(L, -2, "new"); return 1; } prosody-0.12.4/util-src/PaxHeaders/signal.c0000644000000000000000000000012014475665104015547 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/signal.c0000644000175000017500000002154314475665104017761 0ustar00prosodyprosody00000000000000/* * signal.c -- Signal Handler Library for Lua * * Version: 1.000+changes * * Copyright (C) 2007 Patrick J. Donnelly (batrick@batbytes.com) * * This software is distributed under the same license as Lua 5.0: * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include "lua.h" #include "lauxlib.h" #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif #if (LUA_VERSION_NUM < 503) #define lua_isinteger(L, n) lua_isnumber(L, n) #endif #ifndef lsig #define lsig struct lua_signal { char *name; /* name of the signal */ int sig; /* the signal */ }; #endif #define MAX_PENDING_SIGNALS 32 #define LUA_SIGNAL "lua_signal" static const struct lua_signal lua_signals[] = { /* ANSI C signals */ #ifdef SIGABRT {"SIGABRT", SIGABRT}, #endif #ifdef SIGFPE {"SIGFPE", SIGFPE}, #endif #ifdef SIGILL {"SIGILL", SIGILL}, #endif #ifdef SIGINT {"SIGINT", SIGINT}, #endif #ifdef SIGSEGV {"SIGSEGV", SIGSEGV}, #endif #ifdef SIGTERM {"SIGTERM", SIGTERM}, #endif /* posix signals */ #ifdef SIGHUP {"SIGHUP", SIGHUP}, #endif #ifdef SIGQUIT {"SIGQUIT", SIGQUIT}, #endif #ifdef SIGTRAP {"SIGTRAP", SIGTRAP}, #endif #ifdef SIGKILL {"SIGKILL", SIGKILL}, #endif #ifdef SIGUSR1 {"SIGUSR1", SIGUSR1}, #endif #ifdef SIGUSR2 {"SIGUSR2", SIGUSR2}, #endif #ifdef SIGPIPE {"SIGPIPE", SIGPIPE}, #endif #ifdef SIGALRM {"SIGALRM", SIGALRM}, #endif #ifdef SIGCHLD {"SIGCHLD", SIGCHLD}, #endif #ifdef SIGCONT {"SIGCONT", SIGCONT}, #endif #ifdef SIGSTOP {"SIGSTOP", SIGSTOP}, #endif #ifdef SIGTTIN {"SIGTTIN", SIGTTIN}, #endif #ifdef SIGTTOU {"SIGTTOU", SIGTTOU}, #endif /* some BSD signals */ #ifdef SIGIOT {"SIGIOT", SIGIOT}, #endif #ifdef SIGBUS {"SIGBUS", SIGBUS}, #endif #ifdef SIGCLD {"SIGCLD", SIGCLD}, #endif #ifdef SIGURG {"SIGURG", SIGURG}, #endif #ifdef SIGXCPU {"SIGXCPU", SIGXCPU}, #endif #ifdef SIGXFSZ {"SIGXFSZ", SIGXFSZ}, #endif #ifdef SIGVTALRM {"SIGVTALRM", SIGVTALRM}, #endif #ifdef SIGPROF {"SIGPROF", SIGPROF}, #endif #ifdef SIGWINCH {"SIGWINCH", SIGWINCH}, #endif #ifdef SIGPOLL {"SIGPOLL", SIGPOLL}, #endif #ifdef SIGIO {"SIGIO", SIGIO}, #endif /* add odd signals */ #ifdef SIGSTKFLT {"SIGSTKFLT", SIGSTKFLT}, /* stack fault */ #endif #ifdef SIGSYS {"SIGSYS", SIGSYS}, #endif {NULL, 0} }; static lua_State *Lsig = NULL; static lua_Hook Hsig = NULL; static int Hmask = 0; static int Hcount = 0; static int signals[MAX_PENDING_SIGNALS]; static int nsig = 0; static void sighook(lua_State *L, lua_Debug *ar) { (void)ar; /* restore the old hook */ lua_sethook(L, Hsig, Hmask, Hcount); lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); for(int i = 0; i < nsig; i++) { lua_pushinteger(L, signals[i]); lua_gettable(L, -2); lua_call(L, 0, 0); }; nsig = 0; lua_pop(L, 1); /* pop lua_signal table */ } static void handle(int sig) { if(nsig == 0) { /* Store the existing debug hook (if any) and its parameters */ Hsig = lua_gethook(Lsig); Hmask = lua_gethookmask(Lsig); Hcount = lua_gethookcount(Lsig); /* Set our new debug hook */ lua_sethook(Lsig, sighook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); } if(nsig < MAX_PENDING_SIGNALS) { signals[nsig++] = sig; } } /* * l_signal == signal(signal [, func [, chook]]) * * signal = signal number or string * func = Lua function to call * chook = catch within C functions * if caught, Lua function _must_ * exit, as the stack is most likely * in an unstable state. */ static int l_signal(lua_State *L) { int args = lua_gettop(L); int t, sig; /* type, signal */ /* get type of signal */ luaL_checkany(L, 1); t = lua_type(L, 1); if(t == LUA_TNUMBER) { sig = (int) lua_tointeger(L, 1); } else if(t == LUA_TSTRING) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushvalue(L, 1); lua_gettable(L, -2); if(!lua_isinteger(L, -1)) { return luaL_error(L, "invalid signal string"); } sig = (int) lua_tointeger(L, -1); lua_pop(L, 1); /* get rid of number we pushed */ } else { luaL_checknumber(L, 1); /* will always error, with good error msg */ return luaL_error(L, "unreachable: invalid number was accepted"); } /* set handler */ if(args == 1 || lua_isnil(L, 2)) { /* clear handler */ lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushinteger(L, sig); lua_gettable(L, -2); /* return old handler */ lua_pushinteger(L, sig); lua_pushnil(L); lua_settable(L, -4); lua_remove(L, -2); /* remove LUA_SIGNAL table */ signal(sig, SIG_DFL); } else { luaL_checktype(L, 2, LUA_TFUNCTION); lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushinteger(L, sig); lua_pushvalue(L, 2); lua_settable(L, -3); /* Set the state for the handler */ Lsig = L; if(lua_toboolean(L, 3)) { /* c hook? */ if(signal(sig, handle) == SIG_ERR) { lua_pushboolean(L, 0); } else { lua_pushboolean(L, 1); } } else { /* lua_hook */ if(signal(sig, handle) == SIG_ERR) { lua_pushboolean(L, 0); } else { lua_pushboolean(L, 1); } } } return 1; } /* * l_raise == raise(signal) * * signal = signal number or string */ static int l_raise(lua_State *L) { /* int args = lua_gettop(L); */ int t = 0; /* type */ lua_Integer ret; luaL_checkany(L, 1); t = lua_type(L, 1); if(t == LUA_TNUMBER) { ret = (lua_Integer) raise((int) lua_tointeger(L, 1)); lua_pushinteger(L, ret); } else if(t == LUA_TSTRING) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushvalue(L, 1); lua_gettable(L, -2); if(!lua_isnumber(L, -1)) { return luaL_error(L, "invalid signal string"); } ret = (lua_Integer) raise((int) lua_tointeger(L, -1)); lua_pop(L, 1); /* get rid of number we pushed */ lua_pushinteger(L, ret); } else { luaL_checknumber(L, 1); /* will always error, with good error msg */ } return 1; } #if defined(__unix__) || defined(__APPLE__) /* define some posix only functions */ /* * l_kill == kill(pid, signal) * * pid = process id * signal = signal number or string */ static int l_kill(lua_State *L) { int t; /* type */ lua_Integer ret; /* return value */ luaL_checknumber(L, 1); /* must be int for pid */ luaL_checkany(L, 2); /* check for a second arg */ t = lua_type(L, 2); if(t == LUA_TNUMBER) { ret = (lua_Integer) kill((int) lua_tointeger(L, 1), (int) lua_tointeger(L, 2)); lua_pushinteger(L, ret); } else if(t == LUA_TSTRING) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushvalue(L, 2); lua_gettable(L, -2); if(!lua_isnumber(L, -1)) { return luaL_error(L, "invalid signal string"); } ret = (lua_Integer) kill((int) lua_tointeger(L, 1), (int) lua_tointeger(L, -1)); lua_pop(L, 1); /* get rid of number we pushed */ lua_pushinteger(L, ret); } else { luaL_checknumber(L, 2); /* will always error, with good error msg */ } return 1; } #endif static const struct luaL_Reg lsignal_lib[] = { {"signal", l_signal}, {"raise", l_raise}, #if defined(__unix__) || defined(__APPLE__) {"kill", l_kill}, #endif {NULL, NULL} }; int luaopen_util_signal(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif int i = 0; /* add the library */ lua_newtable(L); luaL_setfuncs(L, lsignal_lib, 0); /* push lua_signals table into the registry */ /* put the signals inside the library table too, * they are only a reference */ lua_pushstring(L, LUA_SIGNAL); lua_newtable(L); while(lua_signals[i].name != NULL) { /* registry table */ lua_pushstring(L, lua_signals[i].name); lua_pushinteger(L, lua_signals[i].sig); lua_settable(L, -3); /* signal table */ lua_pushstring(L, lua_signals[i].name); lua_pushinteger(L, lua_signals[i].sig); lua_settable(L, -5); i++; } /* add newtable to the registry */ lua_settable(L, LUA_REGISTRYINDEX); return 1; } prosody-0.12.4/util-src/PaxHeaders/strbitop.c0000644000000000000000000000012014475665104016140 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/strbitop.c0000644000175000017500000000316714475665104020354 0ustar00prosodyprosody00000000000000/* * This project is MIT licensed. Please see the * COPYING file in the source package for more information. * * Copyright (C) 2016 Kim Alvefur */ #include #include #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif /* TODO Deduplicate code somehow */ int strop_and(lua_State *L) { luaL_Buffer buf; size_t a, b, i; const char *str_a = luaL_checklstring(L, 1, &a); const char *str_b = luaL_checklstring(L, 2, &b); luaL_buffinit(L, &buf); if(a == 0 || b == 0) { lua_settop(L, 1); return 1; } for(i = 0; i < a; i++) { luaL_addchar(&buf, str_a[i] & str_b[i % b]); } luaL_pushresult(&buf); return 1; } int strop_or(lua_State *L) { luaL_Buffer buf; size_t a, b, i; const char *str_a = luaL_checklstring(L, 1, &a); const char *str_b = luaL_checklstring(L, 2, &b); luaL_buffinit(L, &buf); if(a == 0 || b == 0) { lua_settop(L, 1); return 1; } for(i = 0; i < a; i++) { luaL_addchar(&buf, str_a[i] | str_b[i % b]); } luaL_pushresult(&buf); return 1; } int strop_xor(lua_State *L) { luaL_Buffer buf; size_t a, b, i; const char *str_a = luaL_checklstring(L, 1, &a); const char *str_b = luaL_checklstring(L, 2, &b); luaL_buffinit(L, &buf); if(a == 0 || b == 0) { lua_settop(L, 1); return 1; } for(i = 0; i < a; i++) { luaL_addchar(&buf, str_a[i] ^ str_b[i % b]); } luaL_pushresult(&buf); return 1; } LUA_API int luaopen_util_strbitop(lua_State *L) { luaL_Reg exports[] = { { "sand", strop_and }, { "sor", strop_or }, { "sxor", strop_xor }, { NULL, NULL } }; lua_newtable(L); luaL_setfuncs(L, exports, 0); return 1; } prosody-0.12.4/util-src/PaxHeaders/struct.c0000644000000000000000000000012014475665104015616 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/struct.c0000644000175000017500000002565214475665104020035 0ustar00prosodyprosody00000000000000/* ** {====================================================== ** Library for packing/unpacking structures. ** $Id: struct.c,v 1.8 2018/05/16 11:00:23 roberto Exp $ ** See Copyright Notice at the end of this file ** ======================================================= */ /* ** Valid formats: ** > - big endian ** < - little endian ** ![num] - alignment ** x - padding ** b/B - signed/unsigned byte ** h/H - signed/unsigned short ** l/L - signed/unsigned long ** T - size_t ** i/In - signed/unsigned integer with size 'n' (default is size of int) ** cn - sequence of 'n' chars (from/to a string); when packing, n==0 means the whole string; when unpacking, n==0 means use the previous read number as the string length ** s - zero-terminated string ** f - float ** d - double ** ' ' - ignored */ #include #include #include #include #include "lua.h" #include "lauxlib.h" #if (LUA_VERSION_NUM >= 502) #define luaL_register(L,n,f) luaL_newlib(L,f) #endif /* basic integer type */ #if !defined(STRUCT_INT) #define STRUCT_INT long #endif typedef STRUCT_INT Inttype; /* corresponding unsigned version */ typedef unsigned STRUCT_INT Uinttype; /* maximum size (in bytes) for integral types */ #define MAXINTSIZE 32 /* is 'x' a power of 2? */ #define isp2(x) ((x) > 0 && ((x) & ((x) - 1)) == 0) /* dummy structure to get alignment requirements */ struct cD { char c; double d; }; #define PADDING (sizeof(struct cD) - sizeof(double)) #define MAXALIGN (PADDING > sizeof(int) ? PADDING : sizeof(int)) /* endian options */ #define BIG 0 #define LITTLE 1 static union { int dummy; char endian; } const native = {1}; typedef struct Header { int endian; int align; } Header; static int getnum (const char **fmt, int df) { if (!isdigit(**fmt)) /* no number? */ return df; /* return default value */ else { int a = 0; do { a = a*10 + *((*fmt)++) - '0'; } while (isdigit(**fmt)); return a; } } #define defaultoptions(h) ((h)->endian = native.endian, (h)->align = 1) static size_t optsize (lua_State *L, char opt, const char **fmt) { switch (opt) { case 'B': case 'b': return sizeof(char); case 'H': case 'h': return sizeof(short); case 'L': case 'l': return sizeof(long); case 'T': return sizeof(size_t); case 'f': return sizeof(float); case 'd': return sizeof(double); case 'x': return 1; case 'c': return getnum(fmt, 1); case 'i': case 'I': { int sz = getnum(fmt, sizeof(int)); if (sz > MAXINTSIZE) luaL_error(L, "integral size %d is larger than limit of %d", sz, MAXINTSIZE); return sz; } default: return 0; /* other cases do not need alignment */ } } /* ** return number of bytes needed to align an element of size 'size' ** at current position 'len' */ static int gettoalign (size_t len, Header *h, int opt, size_t size) { if (size == 0 || opt == 'c') return 0; if (size > (size_t)h->align) size = h->align; /* respect max. alignment */ return (size - (len & (size - 1))) & (size - 1); } /* ** options to control endianess and alignment */ static void controloptions (lua_State *L, int opt, const char **fmt, Header *h) { switch (opt) { case ' ': return; /* ignore white spaces */ case '>': h->endian = BIG; return; case '<': h->endian = LITTLE; return; case '!': { int a = getnum(fmt, MAXALIGN); if (!isp2(a)) luaL_error(L, "alignment %d is not a power of 2", a); h->align = a; return; } default: { const char *msg = lua_pushfstring(L, "invalid format option '%c'", opt); luaL_argerror(L, 1, msg); } } } static void putinteger (lua_State *L, luaL_Buffer *b, int arg, int endian, int size) { lua_Number n = luaL_checknumber(L, arg); Uinttype value; char buff[MAXINTSIZE]; if (n < 0) value = (Uinttype)(Inttype)n; else value = (Uinttype)n; if (endian == LITTLE) { int i; for (i = 0; i < size; i++) { buff[i] = (value & 0xff); value >>= 8; } } else { int i; for (i = size - 1; i >= 0; i--) { buff[i] = (value & 0xff); value >>= 8; } } luaL_addlstring(b, buff, size); } static void correctbytes (char *b, int size, int endian) { if (endian != native.endian) { int i = 0; while (i < --size) { char temp = b[i]; b[i++] = b[size]; b[size] = temp; } } } static int b_pack (lua_State *L) { luaL_Buffer b; const char *fmt = luaL_checkstring(L, 1); Header h; int arg = 2; size_t totalsize = 0; defaultoptions(&h); lua_pushnil(L); /* mark to separate arguments from string buffer */ luaL_buffinit(L, &b); while (*fmt != '\0') { int opt = *fmt++; size_t size = optsize(L, opt, &fmt); int toalign = gettoalign(totalsize, &h, opt, size); totalsize += toalign; while (toalign-- > 0) luaL_addchar(&b, '\0'); switch (opt) { case 'b': case 'B': case 'h': case 'H': case 'l': case 'L': case 'T': case 'i': case 'I': { /* integer types */ putinteger(L, &b, arg++, h.endian, size); break; } case 'x': { luaL_addchar(&b, '\0'); break; } case 'f': { float f = (float)luaL_checknumber(L, arg++); correctbytes((char *)&f, size, h.endian); luaL_addlstring(&b, (char *)&f, size); break; } case 'd': { double d = luaL_checknumber(L, arg++); correctbytes((char *)&d, size, h.endian); luaL_addlstring(&b, (char *)&d, size); break; } case 'c': case 's': { size_t l; const char *s = luaL_checklstring(L, arg++, &l); if (size == 0) size = l; luaL_argcheck(L, l >= (size_t)size, arg, "string too short"); luaL_addlstring(&b, s, size); if (opt == 's') { luaL_addchar(&b, '\0'); /* add zero at the end */ size++; } break; } default: controloptions(L, opt, &fmt, &h); } totalsize += size; } luaL_pushresult(&b); return 1; } static lua_Number getinteger (const char *buff, int endian, int issigned, int size) { Uinttype l = 0; int i; if (endian == BIG) { for (i = 0; i < size; i++) { l <<= 8; l |= (Uinttype)(unsigned char)buff[i]; } } else { for (i = size - 1; i >= 0; i--) { l <<= 8; l |= (Uinttype)(unsigned char)buff[i]; } } if (!issigned) return (lua_Number)l; else { /* signed format */ Uinttype mask = (Uinttype)(~((Uinttype)0)) << (size*8 - 1); if (l & mask) /* negative value? */ l |= mask; /* signal extension */ return (lua_Number)(Inttype)l; } } static int b_unpack (lua_State *L) { Header h; const char *fmt = luaL_checkstring(L, 1); size_t ld; const char *data = luaL_checklstring(L, 2, &ld); size_t pos = (size_t)luaL_optinteger(L, 3, 1) - 1; int n = 0; /* number of results */ luaL_argcheck(L, pos <= ld, 3, "initial position out of string"); defaultoptions(&h); while (*fmt) { int opt = *fmt++; size_t size = optsize(L, opt, &fmt); pos += gettoalign(pos, &h, opt, size); luaL_argcheck(L, size <= ld - pos, 2, "data string too short"); /* stack space for item + next position */ luaL_checkstack(L, 2, "too many results"); switch (opt) { case 'b': case 'B': case 'h': case 'H': case 'l': case 'L': case 'T': case 'i': case 'I': { /* integer types */ int issigned = islower(opt); lua_Number res = getinteger(data+pos, h.endian, issigned, size); lua_pushnumber(L, res); n++; break; } case 'x': { break; } case 'f': { float f; memcpy(&f, data+pos, size); correctbytes((char *)&f, sizeof(f), h.endian); lua_pushnumber(L, f); n++; break; } case 'd': { double d; memcpy(&d, data+pos, size); correctbytes((char *)&d, sizeof(d), h.endian); lua_pushnumber(L, d); n++; break; } case 'c': { if (size == 0) { if (n == 0 || !lua_isnumber(L, -1)) luaL_error(L, "format 'c0' needs a previous size"); size = lua_tonumber(L, -1); lua_pop(L, 1); n--; luaL_argcheck(L, size <= ld - pos, 2, "data string too short"); } lua_pushlstring(L, data+pos, size); n++; break; } case 's': { const char *e = (const char *)memchr(data+pos, '\0', ld - pos); if (e == NULL) luaL_error(L, "unfinished string in data"); size = (e - (data+pos)) + 1; lua_pushlstring(L, data+pos, size - 1); n++; break; } default: controloptions(L, opt, &fmt, &h); } pos += size; } lua_pushinteger(L, pos + 1); /* next position */ return n + 1; } static int b_size (lua_State *L) { Header h; const char *fmt = luaL_checkstring(L, 1); size_t pos = 0; defaultoptions(&h); while (*fmt) { int opt = *fmt++; size_t size = optsize(L, opt, &fmt); pos += gettoalign(pos, &h, opt, size); if (opt == 's') luaL_argerror(L, 1, "option 's' has no fixed size"); else if (opt == 'c' && size == 0) luaL_argerror(L, 1, "option 'c0' has no fixed size"); if (!isalnum(opt)) controloptions(L, opt, &fmt, &h); pos += size; } lua_pushinteger(L, pos); return 1; } /* }====================================================== */ static const struct luaL_Reg thislib[] = { {"pack", b_pack}, {"unpack", b_unpack}, {"size", b_size}, {NULL, NULL} }; LUALIB_API int luaopen_util_struct (lua_State *L); LUALIB_API int luaopen_util_struct (lua_State *L) { luaL_register(L, "struct", thislib); return 1; } /****************************************************************************** * Copyright (C) 2010-2018 Lua.org, PUC-Rio. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************/ prosody-0.12.4/util-src/PaxHeaders/table.c0000644000000000000000000000012014475665104015361 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.756764324 prosody-0.12.4/util-src/table.c0000644000175000017500000000127114475665104017567 0ustar00prosodyprosody00000000000000#include #include static int Lcreate_table(lua_State *L) { lua_createtable(L, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2)); return 1; } static int Lpack(lua_State *L) { unsigned int n_args = lua_gettop(L); lua_createtable(L, n_args, 1); lua_insert(L, 1); for(int arg = n_args; arg >= 1; arg--) { lua_rawseti(L, 1, arg); } lua_pushinteger(L, n_args); lua_setfield(L, -2, "n"); return 1; } int luaopen_util_table(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif lua_createtable(L, 0, 2); lua_pushcfunction(L, Lcreate_table); lua_setfield(L, -2, "create"); lua_pushcfunction(L, Lpack); lua_setfield(L, -2, "pack"); return 1; } prosody-0.12.4/util-src/PaxHeaders/time.c0000644000000000000000000000012014475665104015230 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util-src/time.c0000644000175000017500000000133214475665104017434 0ustar00prosodyprosody00000000000000#ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #include #include static lua_Number tv2number(struct timespec *tv) { return tv->tv_sec + tv->tv_nsec * 1e-9; } static int lc_time_realtime(lua_State *L) { struct timespec t; clock_gettime(CLOCK_REALTIME, &t); lua_pushnumber(L, tv2number(&t)); return 1; } static int lc_time_monotonic(lua_State *L) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); lua_pushnumber(L, tv2number(&t)); return 1; } int luaopen_util_time(lua_State *L) { lua_createtable(L, 0, 2); { lua_pushcfunction(L, lc_time_realtime); lua_setfield(L, -2, "now"); lua_pushcfunction(L, lc_time_monotonic); lua_setfield(L, -2, "monotonic"); } return 1; } prosody-0.12.4/util-src/PaxHeaders/windows.c0000644000000000000000000000012014475665104015764 xustar0030 mtime=1693936196.840763501 20 atime=1693143979 30 ctime=1693936196.760764285 prosody-0.12.4/util-src/windows.c0000644000175000017500000000523214475665104020173 0ustar00prosodyprosody00000000000000/* 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. -- */ /* * windows.c * Windows support functions for Lua */ #include #include #include #include "lua.h" #include "lauxlib.h" #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif #if (LUA_VERSION_NUM < 504) #define luaL_pushfail lua_pushnil #endif static int Lget_nameservers(lua_State *L) { char stack_buffer[1024]; // stack allocated buffer IP4_ARRAY *ips = (IP4_ARRAY *) stack_buffer; DWORD len = sizeof(stack_buffer); DNS_STATUS status; status = DnsQueryConfig(DnsConfigDnsServerList, FALSE, NULL, NULL, ips, &len); if(status == 0) { DWORD i; lua_createtable(L, ips->AddrCount, 0); for(i = 0; i < ips->AddrCount; i++) { DWORD ip = ips->AddrArray[i]; char ip_str[16] = ""; sprintf_s(ip_str, sizeof(ip_str), "%d.%d.%d.%d", (ip >> 0) & 255, (ip >> 8) & 255, (ip >> 16) & 255, (ip >> 24) & 255); lua_pushstring(L, ip_str); lua_rawseti(L, -2, i + 1); } return 1; } else { luaL_pushfail(L); lua_pushfstring(L, "DnsQueryConfig returned %d", status); return 2; } } static int lerror(lua_State *L, char *string) { luaL_pushfail(L); lua_pushfstring(L, "%s: %d", string, GetLastError()); return 2; } static int Lget_consolecolor(lua_State *L) { HWND console = GetStdHandle(STD_OUTPUT_HANDLE); WORD color; DWORD read_len; CONSOLE_SCREEN_BUFFER_INFO info; if(console == INVALID_HANDLE_VALUE) { return lerror(L, "GetStdHandle"); } if(!GetConsoleScreenBufferInfo(console, &info)) { return lerror(L, "GetConsoleScreenBufferInfo"); } if(!ReadConsoleOutputAttribute(console, &color, 1, info.dwCursorPosition, &read_len)) { return lerror(L, "ReadConsoleOutputAttribute"); } lua_pushnumber(L, color); return 1; } static int Lset_consolecolor(lua_State *L) { int color = luaL_checkint(L, 1); HWND console = GetStdHandle(STD_OUTPUT_HANDLE); if(console == INVALID_HANDLE_VALUE) { return lerror(L, "GetStdHandle"); } if(!SetConsoleTextAttribute(console, color)) { return lerror(L, "SetConsoleTextAttribute"); } lua_pushboolean(L, 1); return 1; } static const luaL_Reg Reg[] = { { "get_nameservers", Lget_nameservers }, { "get_consolecolor", Lget_consolecolor }, { "set_consolecolor", Lset_consolecolor }, { NULL, NULL } }; LUALIB_API int luaopen_util_windows(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif lua_newtable(L); luaL_setfuncs(L, Reg, 0); lua_pushliteral(L, "-3.14"); lua_setfield(L, -2, "version"); return 1; }