├── .gitignore ├── COPYING ├── DOCUMENTATION ├── LICENSE ├── Makefile ├── README ├── README.md ├── certs ├── Makefile ├── openssl-v1.cnf └── openssl.cnf ├── configure ├── core ├── configmanager.lua ├── hostmanager.lua ├── loggingmanager.lua ├── moduleapi.lua ├── modulemanager.lua ├── portmanager.lua ├── sessionmanager.lua ├── storagemanager.lua └── usermanager.lua ├── docker ├── Dockerfile ├── config.unix └── scripts │ └── start.sh ├── fallbacks ├── bit.lua └── lxp.lua ├── metronome ├── metronome.cfg.lua.dist ├── metronome.release ├── metronomectl ├── misc ├── doap.xml └── metronome-banner.png ├── net ├── adns.lua ├── dns.lua ├── http.lua ├── http │ ├── codes.lua │ ├── parser.lua │ └── server.lua ├── server.lua └── server_event.lua ├── plugins ├── adhoc │ ├── adhoc.lib.lua │ └── mod_adhoc.lua ├── admin_web │ ├── mod_admin_web.lua │ └── www_files │ │ ├── css │ │ ├── bootstrap-1.4.0.min.css │ │ └── style.css │ │ ├── images │ │ ├── bidi.png │ │ ├── compressed.png │ │ ├── csi-active.png │ │ ├── csi-inactive.png │ │ ├── direct-tls.png │ │ ├── encrypted.png │ │ ├── haze_orange.png │ │ ├── metronome.png │ │ ├── secure.png │ │ └── sm.png │ │ ├── index.html │ │ └── js │ │ ├── adhoc.js │ │ ├── jquery-3.5.0.min.js │ │ ├── main.js │ │ └── strophe.min.js ├── auxlibs │ ├── acdf.lib.lua │ ├── pep.lib.lua │ ├── pubsub.lib.lua │ ├── sasl.lib.lua │ └── stanzalog.lib.lua ├── favicon │ ├── favicon.ico │ ├── favicon.png │ └── mod_favicon.lua ├── http_upload_external │ ├── mod_http_upload_external.lua │ └── resources │ │ └── share.php ├── incidents_handling │ ├── incidents_handling.lib.lua │ └── mod_incidents_handling.lua ├── mam │ ├── mam.lib.lua │ ├── mod_mam.lua │ └── validate.lib.lua ├── mam_browser │ ├── mod_mam_browser.lua │ └── template │ │ ├── browser.html │ │ ├── css │ │ └── style.css │ │ ├── fail.html │ │ ├── images │ │ ├── header.png │ │ └── tile.png │ │ ├── login.html │ │ └── unsecure.html ├── mix │ ├── forms.lib.lua │ ├── helpers.lib.lua │ ├── mix.lib.lua │ ├── mod_mix.lua │ └── namespaces.lib.lua ├── mod_acdf.lua ├── mod_adhoc_cm.lua ├── mod_admin_adhoc.lua ├── mod_admin_telnet.lua ├── mod_announce.lua ├── mod_auth_anonymous.lua ├── mod_auth_external.lua ├── mod_auth_internal_hashed.lua ├── mod_auth_internal_plain.lua ├── mod_bidi.lua ├── mod_bind_session.lua ├── mod_bookmarks.lua ├── mod_bosh.lua ├── mod_c2s.lua ├── mod_component.lua ├── mod_compression.lua ├── mod_dialback.lua ├── mod_disco.lua ├── mod_dump_json.lua ├── mod_extdisco.lua ├── mod_gate_guard.lua ├── mod_gdpr.lua ├── mod_groups.lua ├── mod_http.lua ├── mod_http_errors.lua ├── mod_http_upload.lua ├── mod_iq.lua ├── mod_jid_prep.lua ├── mod_jingle_nodes.lua ├── mod_lastactivity.lua ├── mod_message.lua ├── mod_message_carbons.lua ├── mod_messagefilter.lua ├── mod_mix_pam.lua ├── mod_motd.lua ├── mod_muc_bob.lua ├── mod_muc_limits.lua ├── mod_muc_log.lua ├── mod_muc_log_mam.lua ├── mod_muc_moderation.lua ├── mod_muc_vcard.lua ├── mod_net_multiplex.lua ├── mod_offline.lua ├── mod_pastebin.lua ├── mod_pep.lua ├── mod_ping.lua ├── mod_posix.lua ├── mod_presence.lua ├── mod_private.lua ├── mod_proxy65.lua ├── mod_public_service.lua ├── mod_pubsub.lua ├── mod_push.lua ├── mod_register.lua ├── mod_register_redirect.lua ├── mod_room_zapanddestroy.lua ├── mod_roster.lua ├── mod_router.lua ├── mod_s2s │ ├── mod_s2s.lua │ └── s2sout.lib.lua ├── mod_sasl_s2s.lua ├── mod_saslauth.lua ├── mod_sec_labels.lua ├── mod_server_presence.lua ├── mod_server_status.lua ├── mod_service_directory.lua ├── mod_sic.lua ├── mod_stanza_counter.lua ├── mod_stanza_log.lua ├── mod_stanza_optimizations.lua ├── mod_storage_cache.lua ├── mod_storage_internal.lua ├── mod_storage_sql.lua ├── mod_stream_management.lua ├── mod_subscription_block.lua ├── mod_time.lua ├── mod_tls.lua ├── mod_turnadmin.lua ├── mod_uptime.lua ├── mod_vcard.lua ├── mod_version.lua ├── mod_vjud.lua ├── mod_watchregistrations.lua ├── mod_websocket.lua ├── muc │ ├── mod_muc.lua │ └── muc.lib.lua ├── muc_log_http │ ├── generate_log │ ├── mod_muc_log_http.lua │ └── themes │ │ └── metronome │ │ ├── components_bit.html │ │ ├── day_body.html │ │ ├── day_dayLink.html │ │ ├── day_message.html │ │ ├── day_messageMe.html │ │ ├── day_time.html │ │ ├── day_titleChange.html │ │ ├── days_bit.html │ │ ├── days_body.html │ │ ├── days_rooms_bit.html │ │ ├── doc.html │ │ ├── month_day.html │ │ ├── month_emptyDay.html │ │ ├── month_footer.html │ │ ├── month_header.html │ │ ├── month_weekDay.html │ │ ├── rooms_bit.html │ │ ├── rooms_body.html │ │ └── year_title.html ├── privacy │ ├── mod_privacy.lua │ └── privacy.lib.lua ├── register_api │ ├── mod_register_api.lua │ ├── send_mail │ └── template │ │ ├── associate_fail_t.html │ │ ├── associate_form_t.html │ │ ├── associate_success_t.html │ │ ├── css │ │ └── style.css │ │ ├── images │ │ ├── header.png │ │ └── tile.png │ │ ├── reset_fail_t.html │ │ ├── reset_form_t.html │ │ ├── reset_nomatch_t.html │ │ ├── reset_password_check_t.html │ │ ├── reset_success_t.html │ │ ├── verify_fail_t.html │ │ ├── verify_form_t.html │ │ └── verify_success_t.html └── spim_block │ ├── mod_spim_block.lua │ └── template │ ├── css │ └── style.css │ ├── fail.html │ ├── form.html │ ├── images │ ├── header.png │ └── tile.png │ └── verify.html ├── scripts ├── convert_flat_files └── logrotate ├── templates ├── associate.template.txt ├── register.template.txt └── reset.template.txt ├── util-src ├── Makefile ├── encodings.c ├── hashes.c ├── pposix.c ├── signal.c └── signal.c.license └── util ├── address_selection.lua ├── array.lua ├── auxiliary.lua ├── caps.lua ├── certmanager.lua ├── dataforms.lua ├── datamanager.lua ├── datetime.lua ├── debug.lua ├── dependencies.lua ├── envload.lua ├── events.lua ├── filters.lua ├── helpers.lua ├── hmac.lua ├── httpstream.lua ├── import.lua ├── ip.lua ├── iterators.lua ├── jid.lua ├── json.lua ├── jsonload.lua ├── logger.lua ├── mail.lua ├── metronomectl.lua ├── multitable.lua ├── openssl.lua ├── pluginloader.lua ├── pubsub.lua ├── rostermanager.lua ├── s2smanager.lua ├── sasl.lua ├── sasl.lua.license ├── sasl ├── anonymous.lua ├── digest-md5.lua ├── external.lua ├── plain.lua └── scram.lua ├── serialization.lua ├── set.lua ├── stanza.lua ├── termcolours.lua ├── throttle.lua ├── timer.lua ├── uuid.lua ├── watchdog.lua ├── websocket.lua ├── x509.lua └── xmppstream.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | See LICENSE file. 2 | -------------------------------------------------------------------------------- /DOCUMENTATION: -------------------------------------------------------------------------------- 1 | <========================================================================================= 2 | < 3 | < Metronome IM Server 4 | < - Software Documentation 5 | < 6 | <========================================================================================= 7 | 8 | A collection of documentation regarding this server can be obtained at: 9 | 10 | * Regarding building and installation tasks: 11 | https://metronome.im/building 12 | 13 | * Regarding configuration and API documentation: 14 | https://metronome.im/documentation 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | <=========================================================================================== 2 | < 3 | < Metronome IM Server 4 | < - License. 5 | < 6 | <=========================================================================================== 7 | * 8 | * Metronome is based on Prosody IM (http://prosody.im), which is MIT (Expat) licensed 9 | * Below is the original codebase (forked on the date of the First Commit) copyright and 10 | * license notice. 11 | * 12 | <=========================================================================================== 13 | 14 | Copyright (c) 2008-2011 Matthew Wild 15 | Copyright (c) 2008-2011 Waqas Hussain 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in 25 | all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 29 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 30 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 31 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 32 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 33 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 34 | 35 | <=========================================================================================== 36 | * 37 | * Metronome itself is licensed under the ISC License whereas some portions weren't MIT 38 | * licensed it will be noted accordingly by not having a header pointing to this file and 39 | * having *.license file pointing to them instead. 40 | * 41 | * As MIT allows sublicensing of the software, the whole codebase is ISC licensed where of 42 | * course not stated otherwise by the above mentioned. Although parts of the software which 43 | * are substantially unaltered or weren't created / extensively modified by the maintainer 44 | * see below are effectively dual licensed under MIT (Expat) and ISC, as provisioned by the 45 | * definition of "sublicensing". 46 | * 47 | <=========================================================================================== 48 | 49 | Copyright (c) 2012-2023, Marco Cirillo (LW.Org) 50 | 51 | Permission to use, copy, modify, and/or distribute this software for any 52 | purpose with or without fee is hereby granted, provided that the above 53 | copyright notice and this permission notice appear in all copies. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 56 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 57 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 58 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 59 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 60 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 61 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 62 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | <========================================================================================= 2 | < 3 | < Metronome IM Server 4 | < - Informations. 5 | < 6 | <========================================================================================= 7 | 8 | This software codebase began as a fork of prosody trunk (to be 0.9) merged with 9 | LW.Org's custom patches, initiating from August 7th 2012 (see first commit). 10 | 11 | Being mainly based on Prosody a lot of Metronome's code is backport compatible, but as 12 | development keeps progressing the majority of the codebase has almost completely diverged 13 | from mainstream. 14 | 15 | Differences from Prosody are, but not limited to: 16 | 17 | * The Pubsub API and wrapped modules, mod_pubsub and mod_pep 18 | * The MUC API and wrapper plugins 19 | * Pluggable MUC configuration 20 | * Pluggable Routing API 21 | * Core stack: Modulemanager, Usermanager, Hostmanager, Module API, etc... 22 | * More aggressive memory usage optimisations 23 | * Bidirectional S2S Streams 24 | * Direct TLS S2S Streams and XEP-0368 resolution 25 | * Dialback errors handling and "DB without DB" (XEP-344) 26 | * The anonymous auth backend (mod_auth_anonymous & sasl.lua ineherent part) 27 | * Included plugins, utils 28 | * SPIM prevention system 29 | * Hits/blacklist/whitelist based host filtering (mod_gate_guard) 30 | * In-Band Registration verification and account locking mechanism 31 | * The HTTP API 32 | * XEP-0252 support for BOSH's JSON Padding 33 | * Extensive Microblogging over XMPP support 34 | * Daemon Control Utility 35 | * It does have only one server backend being libevent and has a hard dep. on lua-event 36 | 37 | 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Metronome](misc/metronome-banner.png) 2 | - 3 | 4 | This software codebase began as a fork of prosody trunk (to be 0.9) merged with LW.Org's custom patches, initiating from August 7th 2012 (see first commit). 5 | 6 | Being mainly based on Prosody a lot of Metronome's code is backport compatible, but as development keeps progressing the majority of the codebase has almost completely diverged from mainstream. 7 | 8 | Differences from Prosody are, but not limited to: 9 | 10 | * The Pubsub API and wrapped modules, mod_pubsub and mod_pep 11 | * The MUC API and wrapper plugins 12 | * Pluggable MUC configuration 13 | * Pluggable Routing API 14 | * Core stack: Modulemanager, Usermanager, Hostmanager, Module API, etc... 15 | * More aggressive memory usage optimisations 16 | * Bidirectional S2S Streams 17 | * Direct TLS S2S Streams and XEP-0368 resolution 18 | * Dialback errors handling and "DB without DB" (XEP-344) 19 | * The anonymous auth backend (mod_auth_anonymous & sasl.lua ineherent part) 20 | * Included plugins, utils 21 | * SPIM prevention system 22 | * Hits/blacklist/whitelist based host filtering (mod_gate_guard) 23 | * In-Band Registration verification and account locking mechanism 24 | * The HTTP API 25 | * XEP-0252 support for BOSH's JSON Padding 26 | * Extensive Microblogging over XMPP support 27 | * Daemon Control Utility 28 | * It does have only one server backend being libevent and has a hard dep. on lua-event -------------------------------------------------------------------------------- /certs/Makefile: -------------------------------------------------------------------------------- 1 | include ../config.unix 2 | 3 | .DEFAULT: localhost.cert 4 | keysize=4096 5 | 6 | # How to: 7 | # First, `make yourhost.cnf` which creates a openssl config file. 8 | # Then edit this file and fill in the details you want it to have, 9 | # and add or change hosts and components it should cover. 10 | # Then `make yourhost.key` to create your private key, you can 11 | # include keysize=number to change the size of the key. 12 | # Then you can either `make yourhost.csr` to generate a certificate 13 | # signing request that you can submit to a CA, or `make yourhost.cert` 14 | # to generate a self signed certificate. 15 | 16 | .PRECIOUS: %.cnf %.key 17 | 18 | # To request a cert 19 | %.csr: %.cnf %.key 20 | openssl req -config $(OPENSSL_CONFIG_FILE) -new -key $(lastword $^) \ 21 | -out $@ -utf8 -config $(firstword $^) 22 | 23 | # Self signed 24 | %.cert: %.cnf %.key 25 | openssl req -config $(OPENSSL_CONFIG_FILE) -new -x509 -nodes -key $(lastword $^) -days 1825 \ 26 | -sha256 -out $@ -utf8 -config $(firstword $^) 27 | 28 | %.cnf: 29 | sed 's,example\.com,$*,g' openssl.cnf > $@ 30 | 31 | %.key: 32 | openssl genrsa $(keysize) > $@ 33 | @chmod 400 $@ 34 | -------------------------------------------------------------------------------- /certs/openssl-v1.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | 3 | default_bits = 4096 4 | default_keyfile = example.com.key 5 | distinguished_name = distinguished_name 6 | req_extensions = v3_extensions 7 | x509_extensions = v3_extensions 8 | 9 | # ask about the DN? 10 | prompt = no 11 | 12 | [ distinguished_name ] 13 | 14 | commonName = example.com 15 | countryName = GB 16 | localityName = The Internet 17 | organizationName = Your Organisation 18 | organizationalUnitName = XMPP Department 19 | emailAddress = xmpp@example.com 20 | 21 | [ v3_extensions ] 22 | 23 | # for certificate requests (req_extensions) 24 | # and self-signed certificates (x509_extensions) 25 | 26 | basicConstraints = CA:FALSE 27 | keyUsage = digitalSignature,keyEncipherment 28 | extendedKeyUsage = serverAuth,clientAuth 29 | subjectAltName = @subject_alternative_name 30 | 31 | [ subject_alternative_name ] 32 | 33 | # See http://tools.ietf.org/html/draft-ietf-xmpp-3920bis#section-13.7.1.2 for more info. 34 | 35 | DNS.0 = example.com 36 | otherName.0 = XmppAddr;FORMAT:UTF8,UTF8:example.com 37 | otherName.1 = SRVName;IA5STRING:_xmpp-client.example.com 38 | otherName.2 = SRVName;IA5STRING:_xmpp-server.example.com 39 | 40 | DNS.1 = conference.example.com 41 | otherName.3 = XmppAddr;FORMAT:UTF8,UTF8:conference.example.com 42 | otherName.4 = SRVName;IA5STRING:_xmpp-server.conference.example.com 43 | -------------------------------------------------------------------------------- /certs/openssl.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | 3 | default_bits = 4096 4 | default_keyfile = example.com.key 5 | distinguished_name = distinguished_name 6 | req_extensions = v3_extensions 7 | x509_extensions = v3_extensions 8 | 9 | # ask about the DN? 10 | prompt = no 11 | 12 | [ distinguished_name ] 13 | 14 | commonName = example.com 15 | countryName = GB 16 | localityName = The Internet 17 | organizationName = Your Organisation 18 | organizationalUnitName = XMPP Department 19 | emailAddress = xmpp@example.com 20 | 21 | [ v3_extensions ] 22 | 23 | # for certificate requests (req_extensions) 24 | # and self-signed certificates (x509_extensions) 25 | 26 | basicConstraints = CA:FALSE 27 | keyUsage = digitalSignature,keyEncipherment 28 | extendedKeyUsage = serverAuth,clientAuth 29 | subjectAltName = @subject_alternative_name 30 | 31 | [ subject_alternative_name ] 32 | 33 | # See http://tools.ietf.org/html/draft-ietf-xmpp-3920bis#section-13.7.1.2 for more info. 34 | 35 | DNS.0 = example.com 36 | otherName.0 = XmppAddr;FORMAT:UTF8,UTF8:example.com 37 | otherName.1 = SRVName;IA5STRING:_xmpp-client.example.com 38 | otherName.2 = SRVName;IA5STRING:_xmpp-server.example.com 39 | 40 | DNS.1 = conference.example.com 41 | otherName.3 = XmppAddr;FORMAT:UTF8,UTF8:conference.example.com 42 | otherName.4 = SRVName;IA5STRING:_xmpp-server.conference.example.com 43 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.16 2 | 3 | MAINTAINER "Marco Cirillo" 4 | 5 | # Install dependencies and add daemon user 6 | 7 | RUN apk add --upgrade --no-cache busybox-extras build-base linux-headers make git lua5.3 lua5.3-dev lua5.3-socket lua5.3-expat \ 8 | lua5.3-filesystem lua5.3-dbi-mysql lua5.3-dbi-postgresql lua5.3-dbi-sqlite3 lua5.3-sec libidn libidn-dev \ 9 | openssl openssl-dev libevent libevent-dev zlib zlib-dev luarocks5.3 10 | RUN adduser -S -h /var/lib/metronome metronome && addgroup -S metronome 11 | 12 | 13 | # Pull from git repo and start building the image 14 | 15 | RUN cd /home \ 16 | && git clone https://github.com/maranda/metronome \ 17 | && luarocks-5.3 install luaevent \ 18 | && luarocks-5.3 install lua-zlib 19 | 20 | ADD config.unix /home/metronome/ 21 | 22 | RUN cd /home/metronome && make && make install 23 | RUN mkdir /var/log/metronome /var/run/metronome && chown metronome:metronome /var/log/metronome && chown metronome:metronome /var/run/metronome 24 | 25 | # Cleanup 26 | 27 | RUN apk del --purge build-base linux-headers make git lua5.3-dev libidn-dev openssl-dev libevent-dev zlib-dev 28 | 29 | # Set ports of the container which can be exposed 30 | EXPOSE 80 443 5000 5222 5223 5269 5270 5280 5281 5347 5582 31 | 32 | # Set possible volume (directories which can be mounted from the docker host machine) 33 | # like etc/, conf/, certs/, logs/, data/ 34 | VOLUME ["/etc/metronome", "/usr/lib/metronome", "/var/lib/metronome", "/var/log/metronome", "/var/run/metronome"] 35 | 36 | ADD scripts/start.sh /start.sh 37 | RUN chmod 755 /start.sh 38 | USER metronome:metronome 39 | 40 | # Define script which should be executed on container startup 41 | CMD ["/bin/sh", "/start.sh"] 42 | -------------------------------------------------------------------------------- /docker/config.unix: -------------------------------------------------------------------------------- 1 | PREFIX=/usr 2 | SYSCONFDIR=/etc/metronome 3 | DATADIR=/var/lib/metronome 4 | LUA_SUFFIX=5.3 5 | LUA_DIR=/usr/bin/lua5.3 6 | LUA_INCDIR=/usr/include/lua5.3 7 | LUA_LIBDIR=/usr/lib/lua5.3 8 | LUA_BINDIR=/usr/bin/lua5.3/bin 9 | REQUIRE_CONFIG= 10 | IDN_LIB=idn 11 | IDNA_LIBS=-lidn 12 | OPENSSL_LIB=crypto 13 | OPENSSL_VER=1 14 | CFLAGS=-Wall -fPIC -D_GNU_SOURCE 15 | LDFLAGS=-shared 16 | CC=gcc 17 | CXX=g++ 18 | LD=gcc -------------------------------------------------------------------------------- /docker/scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Starting XMPP Server..."; 4 | /usr/bin/metronome 5 | -------------------------------------------------------------------------------- /metronome.release: -------------------------------------------------------------------------------- 1 | 4.0.4 2 | -------------------------------------------------------------------------------- /misc/metronome-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/misc/metronome-banner.png -------------------------------------------------------------------------------- /net/adns.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2013, Matthew Wild, Waqas Hussain 9 | 10 | local server = require "net.server"; 11 | local dns = require "net.dns"; 12 | 13 | local log = require "util.logger".init("adns"); 14 | 15 | local t_insert, t_remove = table.insert, table.remove; 16 | local coroutine, pcall, setmetatable, tostring = coroutine, pcall, setmetatable, tostring; 17 | 18 | local function dummy_send(sock, data, i, j) return (j-i)+1; end 19 | 20 | local _ENV, _M = nil, {}; 21 | 22 | function _M.lookup(handler, qname, qtype, qclass) 23 | return coroutine.wrap(function (peek) 24 | if peek then 25 | log("debug", "Records for %s already cached, using those...", qname); 26 | handler(peek); 27 | return; 28 | end 29 | log("debug", "Records for %s not in cache, sending query (%s)...", qname, tostring(coroutine.running())); 30 | local ok, err = dns.query(qname, qtype, qclass); 31 | if ok then 32 | coroutine.yield({ qclass or "IN", qtype or "A", qname, coroutine.running()}); 33 | log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running())); 34 | end 35 | if ok then 36 | ok, err = pcall(handler, dns.peek(qname, qtype, qclass)); 37 | else 38 | log("error", "Error sending DNS query: %s", err); 39 | ok, err = pcall(handler, nil, err); 40 | end 41 | if not ok then 42 | log("error", "Error in DNS response handler: %s", tostring(err)); 43 | end 44 | end)(dns.peek(qname, qtype, qclass)); 45 | end 46 | 47 | function _M.cancel(handle, call_handler, reason) 48 | log("warn", "Cancelling DNS lookup for %s", tostring(handle[3])); 49 | dns.cancel(handle[1], handle[2], handle[3], handle[4], call_handler); 50 | end 51 | 52 | function _M.new_async_socket(sock, resolver) 53 | local peername = ""; 54 | local listener = {}; 55 | local handler = {}; 56 | function listener.onincoming(conn, data) 57 | if data then 58 | dns.feed(handler, data); 59 | end 60 | end 61 | function listener.ondisconnect(conn, err) 62 | if err then 63 | log("warn", "DNS socket for %s disconnected: %s", peername, err); 64 | local servers = resolver.server; 65 | if resolver.socketset[conn] == resolver.best_server and resolver.best_server == #servers then 66 | log("error", "Exhausted all %d configured DNS servers, next lookup will try %s again", #servers, servers[1]); 67 | end 68 | 69 | resolver:servfail(conn); 70 | end 71 | end 72 | handler = server.wrapclient(sock, "dns", 53, listener); 73 | if not handler then 74 | log("warn", "handler is nil"); 75 | end 76 | 77 | handler.settimeout = function () end 78 | handler.setsockname = function (_, ...) return sock:setsockname(...); end 79 | handler.setpeername = function (_, ...) peername = (...); local ret = sock:setpeername(...); _:set_send(dummy_send); return ret; end 80 | handler.connect = function (_, ...) return sock:connect(...) end 81 | handler.send = function (_, data) 82 | local getpeername = sock.getpeername; 83 | log("debug", "Sending DNS query to %s", (getpeername and getpeername(sock)) or ""); 84 | return sock:send(data); 85 | end 86 | return handler; 87 | end 88 | 89 | dns.socket_wrapper_set(_M.new_async_socket); 90 | 91 | return _M; 92 | -------------------------------------------------------------------------------- /net/http/codes.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2012-2013, Matthew Wild, Waqas Hussain 9 | 10 | local response_codes = { 11 | -- Source: http://www.iana.org/assignments/http-status-codes 12 | -- s/^\(\d*\)\s*\(.*\S\)\s*\[RFC.*\]\s*$/^I["\1"] = "\2"; 13 | [100] = "Continue"; 14 | [101] = "Switching Protocols"; 15 | [102] = "Processing"; 16 | 17 | [200] = "OK"; 18 | [201] = "Created"; 19 | [202] = "Accepted"; 20 | [203] = "Non-Authoritative Information"; 21 | [204] = "No Content"; 22 | [205] = "Reset Content"; 23 | [206] = "Partial Content"; 24 | [207] = "Multi-Status"; 25 | [208] = "Already Reported"; 26 | [226] = "IM Used"; 27 | 28 | [300] = "Multiple Choices"; 29 | [301] = "Moved Permanently"; 30 | [302] = "Found"; 31 | [303] = "See Other"; 32 | [304] = "Not Modified"; 33 | [305] = "Use Proxy"; 34 | -- The 306 status code was used in a previous version of [RFC2616], is no longer used, and the code is reserved. 35 | [307] = "Temporary Redirect"; 36 | 37 | [400] = "Bad Request"; 38 | [401] = "Unauthorized"; 39 | [402] = "Payment Required"; 40 | [403] = "Forbidden"; 41 | [404] = "Not Found"; 42 | [405] = "Method Not Allowed"; 43 | [406] = "Not Acceptable"; 44 | [407] = "Proxy Authentication Required"; 45 | [408] = "Request Timeout"; 46 | [409] = "Conflict"; 47 | [410] = "Gone"; 48 | [411] = "Length Required"; 49 | [412] = "Precondition Failed"; 50 | [413] = "Request Entity Too Large"; 51 | [414] = "Request-URI Too Long"; 52 | [415] = "Unsupported Media Type"; 53 | [416] = "Requested Range Not Satisfiable"; 54 | [417] = "Expectation Failed"; 55 | [418] = "I'm a teapot"; 56 | [422] = "Unprocessable Entity"; 57 | [423] = "Locked"; 58 | [424] = "Failed Dependency"; 59 | -- The 425 status code is reserved for the WebDAV advanced collections expired proposal [RFC2817] 60 | [426] = "Upgrade Required"; 61 | 62 | [500] = "Internal Server Error"; 63 | [501] = "Not Implemented"; 64 | [502] = "Bad Gateway"; 65 | [503] = "Service Unavailable"; 66 | [504] = "Gateway Timeout"; 67 | [505] = "HTTP Version Not Supported"; 68 | [506] = "Variant Also Negotiates"; -- Experimental 69 | [507] = "Insufficient Storage"; 70 | [508] = "Loop Detected"; 71 | [510] = "Not Extended"; 72 | }; 73 | 74 | for k, v in pairs(response_codes) do response_codes[k] = k.." "..v; end 75 | return setmetatable(response_codes, { __index = function(t, k) return k.." Unassigned"; end }) 76 | -------------------------------------------------------------------------------- /net/server.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2013, Kim Alvefur, Matthew Wild 9 | 10 | local server; 11 | 12 | server = require "net.server_event"; 13 | 14 | local ok, signal = pcall(require, "util.signal"); 15 | if ok and signal then 16 | local _signal_signal = signal.signal; 17 | function signal.signal(signal_id, handler) 18 | if type(signal_id) == "string" then 19 | signal_id = signal[signal_id:upper()]; 20 | end 21 | if type(signal_id) ~= "number" then 22 | return false, "invalid-signal"; 23 | end 24 | return server.hook_signal(signal_id, handler); 25 | end 26 | end 27 | 28 | if metronome then 29 | local config_get = require "core.configmanager".get; 30 | local defaults = {}; 31 | for k,v in pairs(server.cfg or server.getsettings()) do 32 | defaults[k] = v; 33 | end 34 | local function load_config() 35 | local settings = config_get("*", "network_settings") or {}; 36 | local event_settings = { 37 | ACCEPT_DELAY = settings.event_accept_retry_interval; 38 | ACCEPT_QUEUE = settings.tcp_backlog; 39 | CLEAR_DELAY = settings.event_clear_interval; 40 | CONNECT_TIMEOUT = settings.connect_timeout; 41 | DEBUG = settings.debug; 42 | HANDSHAKE_TIMEOUT = settings.ssl_handshake_timeout; 43 | MAX_CONNECTIONS = settings.max_connections; 44 | MAX_HANDSHAKE_ATTEMPTS = settings.max_ssl_handshake_roundtrips; 45 | MAX_READ_LENGTH = settings.max_receive_buffer_size; 46 | MAX_SEND_LENGTH = settings.max_send_buffer_size; 47 | READ_RETRY_DELAY = settings.event_read_retry_interval; 48 | READ_TIMEOUT = settings.read_timeout; 49 | WRITE_TIMEOUT = settings.send_timeout; 50 | }; 51 | 52 | for k,default in pairs(defaults) do 53 | server.cfg[k] = event_settings[k] or default; 54 | end 55 | end 56 | load_config(); 57 | metronome.events.add_handler("config-reloaded", load_config); 58 | end 59 | 60 | return server; 61 | -------------------------------------------------------------------------------- /plugins/adhoc/adhoc.lib.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2010-2012, Kim Alvefur, Florian Zeitz, Matthew Wild, Waqas Hussain 9 | 10 | local st, uuid = require "util.stanza", require "util.uuid"; 11 | 12 | local xmlns_cmd = "http://jabber.org/protocol/commands"; 13 | 14 | local states = {} 15 | 16 | local _M = {}; 17 | 18 | local function _cmdtag(desc, status, sessionid, action) 19 | local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status }); 20 | if sessionid then cmd.attr.sessionid = sessionid; end 21 | if action then cmd.attr.action = action; end 22 | 23 | return cmd; 24 | end 25 | 26 | function _M.new(name, node, handler, permission) 27 | return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = (permission or "user") }; 28 | end 29 | 30 | function _M.handle_cmd(command, origin, stanza) 31 | local sessionid = stanza.tags[1].attr.sessionid or uuid.generate(); 32 | local dataIn = {}; 33 | dataIn.to = stanza.attr.to; 34 | dataIn.from = stanza.attr.from; 35 | dataIn.action = stanza.tags[1].attr.action or "execute"; 36 | dataIn.form = stanza.tags[1]:child_with_ns("jabber:x:data"); 37 | 38 | local data, state = command:handler(dataIn, states[sessionid], origin.secure); 39 | states[sessionid] = state; 40 | local stanza = st.reply(stanza); 41 | local cmdtag; 42 | if data.status == "completed" then 43 | states[sessionid] = nil; 44 | cmdtag = command:cmdtag("completed", sessionid); 45 | elseif data.status == "canceled" then 46 | states[sessionid] = nil; 47 | cmdtag = command:cmdtag("canceled", sessionid); 48 | elseif data.status == "error" then 49 | states[sessionid] = nil; 50 | stanza = st.error_reply(stanza, data.error.type, data.error.condition, data.error.message); 51 | origin.send(stanza); 52 | return true; 53 | else 54 | cmdtag = command:cmdtag("executing", sessionid); 55 | data.actions = data.actions or { "complete" }; 56 | end 57 | 58 | for name, content in pairs(data) do 59 | if name == "info" then 60 | cmdtag:tag("note", {type="info"}):text(content):up(); 61 | elseif name == "warn" then 62 | cmdtag:tag("note", {type="warn"}):text(content):up(); 63 | elseif name == "error" then 64 | cmdtag:tag("note", {type="error"}):text(content.message):up(); 65 | elseif name == "actions" then 66 | local actions = st.stanza("actions", { execute = content.default }); 67 | for _, action in ipairs(content) do 68 | if (action == "prev") or (action == "next") or (action == "complete") then 69 | actions:tag(action):up(); 70 | else 71 | module:log("error", "Command %q at node %q provided an invalid action %q", 72 | command.name, command.node, action); 73 | end 74 | end 75 | cmdtag:add_child(actions); 76 | elseif name == "form" then 77 | cmdtag:add_child((content.layout or content):form(content.values)); 78 | elseif name == "result" then 79 | cmdtag:add_child((content.layout or content):form(content.values, "result")); 80 | elseif name == "other" then 81 | cmdtag:add_child(content); 82 | end 83 | end 84 | stanza:add_child(cmdtag); 85 | origin.send(stanza); 86 | 87 | return true; 88 | end 89 | 90 | return _M; 91 | -------------------------------------------------------------------------------- /plugins/admin_web/www_files/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0 3 | } 4 | 5 | a { 6 | color: #0000FF 7 | } 8 | 9 | ul { 10 | margin: 0 11 | } 12 | 13 | .btn { 14 | margin-right: 0.3em 15 | } 16 | 17 | .btn:last { 18 | margin-right: 0 19 | } 20 | 21 | #adhocCommands { 22 | border-right: solid 1px 23 | } 24 | 25 | #adhocCommands li { 26 | list-style: inside 27 | } 28 | 29 | #login { 30 | float: left; 31 | margin: 1em 2em 0 1em; 32 | padding-right: 1em; 33 | border: solid 1px; 34 | background: #eef0f2; 35 | color: #000000 36 | } 37 | 38 | #error { 39 | display: none; 40 | width: 240px; 41 | margin: 200px 2em 0 1em; 42 | padding-right: 1em; 43 | padding-top: 0.5em; 44 | border: solid 1px; 45 | background: #d5c6db; 46 | } 47 | 48 | #error p { 49 | color: red; 50 | font-weight: bold; 51 | text-align: center; 52 | } 53 | 54 | #main { 55 | display: none; 56 | margin: 1em 57 | } 58 | 59 | #main p { 60 | margin: 0 61 | } 62 | 63 | #top { 64 | clear: both; 65 | width: 100%; 66 | padding: 0; 67 | } 68 | 69 | #header { 70 | height: 172px; 71 | background: url(../images/haze_orange.png) repeat-x 72 | } 73 | 74 | #menu { 75 | display: none; 76 | color: #454748; 77 | font-size: 1.1em; 78 | background: #eef0f2; 79 | width: 100%; 80 | } 81 | 82 | #menu ul { 83 | display: inline; 84 | list-style-type: none; 85 | margin: 0; 86 | padding: 0.5em 0 87 | } 88 | 89 | #menu li { 90 | display: inline; 91 | padding: 0 0.5em 92 | } 93 | 94 | #menu a { 95 | color: #454748; 96 | text-decoration: none 97 | } 98 | 99 | #menu li a:hover { 100 | color: #6197DF; 101 | text-decoration: underline 102 | } 103 | 104 | #s2sList h2, #c2sList h2 { 105 | color: #4b8ade; 106 | margin: 0 107 | } 108 | 109 | #host { 110 | margin: 0.25em; 111 | } 112 | -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/bidi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/bidi.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/compressed.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/csi-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/csi-active.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/csi-inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/csi-inactive.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/direct-tls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/direct-tls.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/encrypted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/encrypted.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/haze_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/haze_orange.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/metronome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/metronome.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/secure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/secure.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/images/sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/admin_web/www_files/images/sm.png -------------------------------------------------------------------------------- /plugins/admin_web/www_files/index.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome Webadmin 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 22 | 31 |
32 | 33 |
34 |
35 |
36 |
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 |
47 |
48 |
49 |
50 | 51 |
52 |
53 |
54 |
55 |
56 | 57 |
58 | 59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Incoming S2S connections:

70 |
    71 |
    72 |
    73 |

    Outgoing S2S connections:

    74 |
      75 |
      76 |
      77 |
      78 |
      79 |
      80 |
      81 |

      Client connections:

      82 |
        83 |
        84 |
        85 |
        86 |
        87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /plugins/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/favicon/favicon.ico -------------------------------------------------------------------------------- /plugins/favicon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/favicon/favicon.png -------------------------------------------------------------------------------- /plugins/favicon/mod_favicon.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | module:set_global() 8 | 9 | local server = require "net.http.server" 10 | 11 | local favicon_file_path = (metronome.paths.plugins or "./").."favicon/favicon." 12 | local load = require "util.auxiliary".load_file 13 | 14 | local function get_icon(event, type) 15 | local response = event.response 16 | local icon = load(favicon_file_path .. type, "rb") 17 | 18 | if not icon then 19 | return 404 20 | else 21 | if type == "ico" then type = "x-icon" end 22 | response.headers["Cache-Control"] = "public, max-age=86400" 23 | response.headers["Content-Type"] = "image/" .. type 24 | response:send(icon) 25 | end 26 | 27 | return true 28 | end 29 | 30 | local function serve(event) 31 | local type = event.request.path:match("%.([^%.]*)$") 32 | return get_icon(event, type) 33 | end 34 | 35 | function module.add_host(module) 36 | module:hook_object_event(server, "GET "..module.host.."/favicon.ico", serve, -1) 37 | module:hook_object_event(server, "GET "..module.host.."/assets/favicon.png", serve, -1) 38 | end 39 | -------------------------------------------------------------------------------- /plugins/mam/validate.lib.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | -- MAM Query parsing tools. 8 | 9 | local dt_parse = require "util.datetime".parse; 10 | local st = require "util.stanza"; 11 | local jid_bare = require "util.jid".bare; 12 | local jid_prep = require "util.jid".prep; 13 | local jid_split = require "util.jid".split; 14 | local ipairs, tonumber, tostring = ipairs, tonumber, tostring; 15 | 16 | local xmlns = "urn:xmpp:mam:2"; 17 | local df_xmlns = "jabber:x:data"; 18 | local rsm_xmlns = "http://jabber.org/protocol/rsm"; 19 | 20 | local max_results = module:get_option_number("mam_max_retrievable_results", 150); 21 | if max_results >= 350 then max_results = 350; end 22 | 23 | local function rsm_parse(stanza, query) 24 | local rsm = query:get_child("set", rsm_xmlns); 25 | if not rsm then return; end 26 | local max = rsm and rsm:get_child_text("max"); 27 | local after = rsm and rsm:get_child_text("after"); 28 | local before = rsm and rsm:get_child_text("before"); 29 | local index = rsm and rsm:get_child_text("index"); 30 | before = (before == "" and true) or before; 31 | max = max and tonumber(max); 32 | index = index and tonumber(index); 33 | 34 | return after, before, max, index; 35 | end 36 | 37 | local function df_parse(query) 38 | local data = query:get_child("x", df_xmlns); 39 | if not data then return; end 40 | local start = data:child_with_attr_value("field", "var", "start"); 41 | local fin = data:child_with_attr_value("field", "var", "end"); 42 | local with = data:child_with_attr_value("field", "var", "with"); 43 | start, fin, with = 44 | start and start:get_child_text("value"), 45 | fin and fin:get_child_text("value"), 46 | with and with:get_child_text("value"); 47 | return start, fin, with; 48 | end 49 | 50 | local function validate_query(stanza, query, qid) 51 | local start, fin, with = df_parse(query); 52 | 53 | -- Validate attributes 54 | local vstart, vfin, vwith = (start and dt_parse(start)), (fin and dt_parse(fin)), (with and jid_prep(with)); 55 | if (start and not vstart) or (fin and not vfin) then 56 | module:log("debug", "Failed to validate timestamp on query, aborting"); 57 | return false, st.error_reply(stanza, "modify", "bad-request", "Supplied timestamp is invalid") 58 | end 59 | start, fin = vstart, vfin; 60 | if with and not vwith then 61 | module:log("debug", "Failed to validate 'with' JIDs on query, aborting"); 62 | return false, st.error_reply(stanza, "modify", "bad-request", "Supplied JID is invalid"); 63 | end 64 | with = jid_bare(vwith); 65 | 66 | -- Get RSM set 67 | local after, before, max, index = rsm_parse(stanza, query); 68 | if (before and after) or (index and (before or after or index < 0)) or max == "" or after == "" then 69 | module:log("debug", "Invalid RSM parameters received: After - %s, Before - %s, Max - %s, Index - %s", 70 | tostring(after), tostring(before), tostring(max), tostring(index)); 71 | return false, st.error_reply(stanza, "modify", "bad-request"); 72 | elseif before == true and not max then -- Assume max is equal to safe default 73 | max = 50; 74 | end 75 | 76 | if max and max > max_results then max = max_results; end 77 | 78 | if not start and not fin and not after and not before and not index and not max then -- Assume safe defaults 79 | before, max = true, 50; 80 | elseif not max then 81 | max = 50; 82 | end 83 | 84 | module:log("debug", "MAM query received, %s with %s from %s until %s (RSM: After - %s, Before - %s, Max - %s, Index - %s)", 85 | (qid and "id "..qid) or "idless,", with or "anyone", start or "epoch", fin or "now", 86 | tostring(after), tostring(before), tostring(max), tostring(index)); 87 | 88 | return true, { start = start, fin = fin, with = with, max = max, after = after, before = before, index = index }; 89 | end 90 | 91 | return { validate_query = validate_query }; 92 | -------------------------------------------------------------------------------- /plugins/mam_browser/template/browser.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Metronome MAM Browser 5 | 6 | 7 | 8 | 9 | 10 | 11 |
        12 | 15 |
        16 | 17 |
        18 |

        19 | %CAPTION, logout. 20 |

        21 | %FORM 22 |
        23 | 24 |
        25 |

        26 | %FL 27 |

        28 |

        29 | %ENTRIES 30 |

        31 |
        32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /plugins/mam_browser/template/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0 3 | } 4 | 5 | a { 6 | color: #0000FF 7 | } 8 | 9 | ul { 10 | margin: 0 11 | } 12 | 13 | .btn { 14 | margin-right: 0.3em 15 | } 16 | 17 | .btn:last { 18 | margin-right: 0 19 | } 20 | 21 | #main { 22 | padding: 10px 23 | } 24 | 25 | #main div { 26 | margin-left: 14px 27 | } 28 | 29 | #main p { 30 | margin-left: 14px 31 | } 32 | 33 | #entries { 34 | padding: 10px 35 | } 36 | 37 | #entries div { 38 | margin-left: 14px 39 | } 40 | 41 | #entries p { 42 | margin-left: 14px 43 | 44 | } 45 | 46 | #top { 47 | clear: both; 48 | width: 100%; 49 | padding: 0; 50 | } 51 | 52 | #header { 53 | height: 172px; 54 | background: url(../images/tile.png) repeat-x 55 | } 56 | 57 | #recaptcha { 58 | margin: 0px; 59 | width: 304px; 60 | position: relative; 61 | } 62 | 63 | #recaptcha iframe { 64 | position: absolute; 65 | left: 0px; 66 | top: 0px; 67 | } 68 | -------------------------------------------------------------------------------- /plugins/mam_browser/template/fail.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Metronome MAM Browser 5 | 6 | 7 | 8 | 9 | 10 | 11 |
        12 | 15 |
        16 | 17 |
        18 |

        Wrong username or password.

        19 |
        20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /plugins/mam_browser/template/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/mam_browser/template/images/header.png -------------------------------------------------------------------------------- /plugins/mam_browser/template/images/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/mam_browser/template/images/tile.png -------------------------------------------------------------------------------- /plugins/mam_browser/template/login.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Metronome MAM Browser 5 | 6 | 7 | 8 | 9 | 10 | 11 |
        12 | 15 |
        16 | 17 |
        18 |

        19 | Please login with your %HOST credentials to view your MAM log archives. 20 |

        21 |
        22 |

        23 |
        24 |

        25 |
        26 |

        27 |
        28 |
        29 | 30 | 31 | -------------------------------------------------------------------------------- /plugins/mam_browser/template/unsecure.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Metronome SPIM Block 5 | 6 | 7 | 8 | 9 | 10 | 11 |
        12 | 15 |
        16 | 17 |
        18 |

        The MAM browser can only be used over a secured connection.

        19 |
        20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /plugins/mix/forms.lib.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | -- Backported from: https://git.polynom.me/PapaTutuWawa/prosody-modules 8 | 9 | local dataforms = require("util.dataforms"); 10 | 11 | local namespaces = module:require("namespaces", "mix"); 12 | 13 | return { 14 | -- MIX 15 | mix_info = dataforms.new({ 16 | { name = "FORM_TYPE", type = "hidden", value = namespaces.mix_core }, 17 | { name = "Name", type = "text-single" }, 18 | { name = "Description", type = "text-single" }, 19 | { name = "Contact", type = "jid-multi" }}); 20 | -- MAM 21 | mam_query = dataforms.new({ 22 | { name = "FORM_TYPE", type = "hidden", value = namespaces.mam }, 23 | { name = "with", type = "jid-single" }, 24 | { name = "start", type = "text-single" }, 25 | { name = "end", type = "text-single" }}); 26 | }; 27 | -------------------------------------------------------------------------------- /plugins/mix/helpers.lib.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | -- Backported from: https://git.polynom.me/PapaTutuWawa/prosody-modules 8 | 9 | -- Helper functions for mod_mix 10 | local function find(array, f) 11 | -- Searches for an element for which f returns true. The first element 12 | -- and its index are returned. If none are found, then -1, nil is returned. 13 | -- 14 | -- f is a function that takes the value and returns either true or false. 15 | for i, v in pairs(array) do 16 | if f(v) then return i, v; end 17 | end 18 | 19 | return -1, nil; 20 | end 21 | 22 | local function find_str(array, str) 23 | -- Returns the index of str in array. -1 if array does not contain str 24 | local i, _ = find(array, function(v) return v == str; end); 25 | return i; 26 | end 27 | 28 | local function in_array(array, element) 29 | -- Returns true if element is in array. False otherwise. 30 | local i, _ = find(array, function(v) return v == element; end); 31 | return i ~= -1; 32 | end 33 | 34 | return { 35 | find_str = find_str, 36 | find = find, 37 | in_array = in_array, 38 | }; 39 | -------------------------------------------------------------------------------- /plugins/mix/namespaces.lib.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | -- Backported from: https://git.polynom.me/PapaTutuWawa/prosody-modules 8 | 9 | return { 10 | -- XMLNS 11 | -- MIX 12 | mix_core = "urn:xmpp:mix:core:1"; 13 | mix_anon = "urn:xmpp:mix:anon:0"; 14 | mix_admin = "urn:xmpp:mix:admin:0"; 15 | mix_presence = "urn:xmpp:mix:presence:0"; 16 | -- MAM 17 | mam = "urn:xmpp:mam:2"; 18 | -- User Avatar 19 | avatar = "urn:xmpp:avatar:data"; 20 | avatar_metadata = "urn:xmpp:avatar:metadata"; 21 | -- MIX PubSub nodes 22 | messages = "urn:xmpp:mix:nodes:messages"; 23 | presence = "urn:xmpp:mix:nodes:presence"; 24 | participants = "urn:xmpp:mix:nodes:participants"; 25 | info = "urn:xmpp:mix:nodes:info"; 26 | allowed = "urn:xmpp:mix:nodes:allowed"; 27 | banned = "urn:xmpp:mix:nodes:banned"; 28 | config = "urn:xmpp:mix:nodes:config"; 29 | -- PubSub 30 | pubsub = "http://jabber.org/protocol/pubsub"; 31 | pubsub_event = "http://jabber.org/protocol/pubsub#event"; 32 | pubsub_error = "http://jabber.org/protocol/pubsub#errors"; 33 | }; 34 | -------------------------------------------------------------------------------- /plugins/mod_acdf.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | -- This module implements Access Control Decision Function (ACDF) for Security Labels 8 | 9 | local apply_policy = module:require("acdf", "auxlibs").apply_policy; 10 | local get_actions = module:require("acdf", "auxlibs").get_actions; 11 | 12 | local labels_xmlns = "urn:xmpp:sec-label:0"; 13 | 14 | local function incoming_message_handler(event) 15 | local session, stanza = event.origin, event.stanza; 16 | local label = stanza:get_child("securitylabel", labels_xmlns); 17 | 18 | if label then 19 | local text = label:get_child_text("displaymarking"); 20 | local actions = get_actions(module.host, text); 21 | if actions then return apply_policy(text, session, stanza, actions); end 22 | end 23 | end 24 | 25 | local function outgoing_message_handler(event) 26 | local session, stanza = event.origin, event.stanza; 27 | local label = stanza:get_child("securitylabel", labels_xmlns); 28 | 29 | if label then 30 | local text = label:get_child_text("displaymarking"); 31 | local actions = get_actions(module.host, text); 32 | if actions then return apply_policy(text, session, stanza, actions); end 33 | end 34 | end 35 | 36 | module:hook("message/bare", incoming_message_handler, 90); 37 | module:hook("message/full", incoming_message_handler, 90); 38 | module:hook("pre-message/bare", outgoing_message_handler, 90); 39 | module:hook("pre-message/full", outgoing_message_handler, 90); -------------------------------------------------------------------------------- /plugins/mod_announce.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2013, Kim Alvefur, Florian Zeitz, Matthew Wild, Waqas Hussain 9 | 10 | local st = require "util.stanza"; 11 | 12 | function send_to_online(message, host) 13 | local c = 0; 14 | message.attr.from = host; 15 | 16 | for jid in module:get_bare_sessions(host) do 17 | c = c + 1; 18 | message.attr.to = jid; 19 | module:send(message); 20 | end 21 | 22 | return c; 23 | end 24 | 25 | -- Ad-hoc command (XEP-0133) 26 | local dataforms_new = require "util.dataforms".new; 27 | local announce_layout = dataforms_new{ 28 | title = "Making an Announcement"; 29 | instructions = "Fill out this form to make an announcement to all\nactive users of this service."; 30 | 31 | { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 32 | { name = "subject", type = "text-single", label = "Subject" }; 33 | { name = "announcement", type = "text-multi", required = true, label = "Announcement" }; 34 | }; 35 | 36 | function announce_handler(self, data, state) 37 | if state then 38 | if data.action == "cancel" then 39 | return { status = "canceled" }; 40 | end 41 | 42 | local fields = announce_layout:data(data.form); 43 | 44 | if fields.announcement == "" then 45 | return { status = "completed", error = { message = "Please supply a message to broadcast" } }; 46 | end 47 | 48 | module:log("info", "Sending server announcement to all online users"); 49 | local message = st.message({type = "headline"}, fields.announcement):up() 50 | :tag("subject"):text(fields.subject or "Announcement"); 51 | 52 | local count = send_to_online(message, data.to); 53 | 54 | module:log("info", "Announcement sent to %d online users", count); 55 | return { status = "completed", info = ("Announcement sent to %d online users"):format(count) }; 56 | else 57 | return { status = "executing", actions = {"next", "complete", default = "complete"}, form = announce_layout }, "executing"; 58 | end 59 | 60 | return true; 61 | end 62 | 63 | local adhoc_new = module:require "adhoc".new; 64 | local announce_desc = adhoc_new("Send Announcement to Online Users", "http://jabber.org/protocol/admin#announce", announce_handler, "admin"); 65 | module:provides("adhoc", announce_desc); -------------------------------------------------------------------------------- /plugins/mod_bookmarks.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | -- This module synchronizes XEP-0048 Bookmarks between PEP and Private Storage. 8 | 9 | local st = require "util.stanza"; 10 | local section = require "util.jid".section; 11 | 12 | local bookmarks_xmlns = "storage:bookmarks"; 13 | 14 | module:hook("account-disco-info", function(event) 15 | event.stanza:tag("feature", { var = "urn:xmpp:bookmarks-conversion:0" }):up(); 16 | end, 44); 17 | 18 | module:hook("private-storage-callbacks", function(event) 19 | local session, key, data = event.session, event.key, event.data; 20 | if key == bookmarks_xmlns then 21 | local pep_service = module:fire_event("pep-get-service", session.username, true, session.full_jid); 22 | if pep_service then 23 | local item = st.stanza("item", { id = "current" }):add_child(data):up(); 24 | 25 | if not pep_service.nodes[bookmarks_xmlns] then 26 | pep_service:create(bookmarks_xmlns, session.full_jid, { access_model = "whitelist", persist_items = true }); 27 | module:fire_event("pep-autosubscribe-recipients", pep_service, bookmarks_xmlns); 28 | end 29 | pep_service:publish(bookmarks_xmlns, session.full_jid, "current", item); 30 | end 31 | end 32 | end); 33 | 34 | module:hook("pep-node-publish", function(event) 35 | local node, item, from = event.node, event.item, event.from or event.origin.full_jid; 36 | 37 | if node == bookmarks_xmlns then 38 | local data = item:get_child("storage", bookmarks_xmlns); 39 | if data then 40 | module:fire_event("private-storage-set", { 41 | user = section(from, "node"), key = "storage:"..bookmarks_xmlns, tag = data, from = from 42 | }); 43 | end 44 | end 45 | end); 46 | -------------------------------------------------------------------------------- /plugins/mod_dump_json.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- This metronomectl extension helps exporting a single datastore in JSON 8 | -- format to be used with external applications. 9 | 10 | if not rawget(_G, "metronomectl") then 11 | module:log("error", "mod_dump_json can only be loaded from metronomectl!"); 12 | return; 13 | end 14 | 15 | local datamanager = require "util.datamanager"; 16 | local storagemanager = require "core.storagemanager"; 17 | local json = require "util.json"; 18 | local unpack = table.unpack or unpack; 19 | 20 | function module.command(arg) 21 | local node, host, store = unpack(arg); 22 | if not node or not host or not store then 23 | return "Incorrect syntax please use:\nmetronomectl mod_dump_json "; 24 | end 25 | 26 | storagemanager.initialize_host(host); -- initialize host storage 27 | if node == "nil" then node = nil; end -- allows checking inner pathed stores 28 | 29 | local data = datamanager.load(node, host, store); 30 | if data then 31 | return json.encode(data); 32 | else 33 | return "Datastore is empty"; 34 | end 35 | end -------------------------------------------------------------------------------- /plugins/mod_groups.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2011, Christian Haase, Jeff Mitchell, Matthew Wild 9 | 10 | local groups; 11 | local members; 12 | 13 | local groups_file; 14 | 15 | local jid, datamanager = require "util.jid", require "util.datamanager"; 16 | local jid_bare, jid_prep = jid.bare, jid.prep; 17 | 18 | local module_host = module:get_host(); 19 | 20 | function inject_roster_contacts(username, host, roster) 21 | local bare_jid = username.."@"..host; 22 | if not members[bare_jid] and not members[false] then return; end -- Not a member of any groups 23 | 24 | local function import_jids_to_roster(group_name) 25 | for jid in pairs(groups[group_name]) do 26 | -- Add them to roster 27 | if jid ~= bare_jid then 28 | if not roster[jid] then roster[jid] = {}; end 29 | roster[jid].subscription = "both"; 30 | if groups[group_name][jid] then 31 | roster[jid].name = groups[group_name][jid]; 32 | end 33 | if not roster[jid].groups then 34 | roster[jid].groups = { [group_name] = true }; 35 | end 36 | roster[jid].groups[group_name] = true; 37 | roster[jid].persist = false; 38 | end 39 | end 40 | end 41 | 42 | if members[bare_jid] then 43 | for _, group_name in ipairs(members[bare_jid]) do 44 | import_jids_to_roster(group_name); 45 | end 46 | end 47 | 48 | if members[false] then 49 | for _, group_name in ipairs(members[false]) do 50 | import_jids_to_roster(group_name); 51 | end 52 | end 53 | 54 | if roster[false] then 55 | roster[false].version = true; 56 | end 57 | end 58 | 59 | function remove_virtual_contacts(username, host, datastore, data) 60 | if host == module_host and datastore == "roster" then 61 | local new_roster = {}; 62 | for jid, contact in pairs(data) do 63 | if contact.persist ~= false then 64 | new_roster[jid] = contact; 65 | end 66 | end 67 | if new_roster[false] then 68 | new_roster[false].version = nil; -- Version is void 69 | end 70 | return username, host, datastore, new_roster; 71 | end 72 | 73 | return username, host, datastore, data; 74 | end 75 | 76 | function module.load() 77 | groups_file = config.get(module_host, "groups_file"); 78 | if not groups_file then 79 | module:log("error", 80 | "No groups file found, please be sure to add the groups_file statement on the %s host section", 81 | module_host); 82 | return; 83 | end 84 | 85 | module:hook("roster-load", inject_roster_contacts); 86 | datamanager.add_callback(remove_virtual_contacts); 87 | 88 | groups = { default = {} }; 89 | members = { }; 90 | local curr_group = "default"; 91 | for line in io.lines(groups_file) do 92 | if line:match("^%s*%[.-%]%s*$") then 93 | curr_group = line:match("^%s*%[(.-)%]%s*$"); 94 | if curr_group:match("^%+") then 95 | curr_group = curr_group:gsub("^%+", ""); 96 | if not members[false] then 97 | members[false] = {}; 98 | end 99 | members[false][#members[false]+1] = curr_group; -- Is a public group 100 | end 101 | module:log("debug", "New group: %s", tostring(curr_group)); 102 | groups[curr_group] = groups[curr_group] or {}; 103 | else 104 | -- Add JID 105 | local entryjid, name = line:match("([^=]*)=?(.*)"); 106 | module:log("debug", "entryjid = '%s', name = '%s'", entryjid, name); 107 | local jid; 108 | jid = jid_prep(entryjid:match("%S+")); 109 | if jid then 110 | module:log("debug", "New member of %s: %s", tostring(curr_group), tostring(jid)); 111 | groups[curr_group][jid] = name or false; 112 | members[jid] = members[jid] or {}; 113 | members[jid][#members[jid]+1] = curr_group; 114 | end 115 | end 116 | end 117 | module:log("info", "Groups loaded successfully"); 118 | end 119 | 120 | function module.unload() 121 | datamanager.remove_callback(remove_virtual_contacts); 122 | end 123 | -------------------------------------------------------------------------------- /plugins/mod_http_errors.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2012-2013, Kim Alvefur, Matthew Wild 9 | 10 | module:set_global(); 11 | 12 | local server = require "net.http.server"; 13 | local codes = require "net.http.codes"; 14 | local termcolours = require "util.termcolours"; 15 | 16 | local show_private = module:get_option_boolean("http_errors_detailed", false); 17 | local always_serve = module:get_option_boolean("http_errors_always_show", true); 18 | local default_message = { module:get_option_string("http_errors_default_message", "That's all I know.") }; 19 | local default_messages = { 20 | [400] = { "What kind of request do you call that??" }; 21 | [403] = { "You're not allowed to do that." }; 22 | [404] = { "Whatever you were looking for is not here. %"; 23 | "Where did you put it?", "It's behind you.", "Keep looking." }; 24 | [500] = { "% Check your error log for more info."; 25 | "Gremlins.", "It broke.", "Don't look at me." }; 26 | }; 27 | 28 | local messages = setmetatable(module:get_option("http_errors_messages", {}), { __index = default_messages }); 29 | 30 | local html = [[ 31 | 32 | 33 | 34 | 35 | 36 | 51 | 52 | 53 |

        $title

        54 |

        $message

        55 |

        $extra

        56 | 57 | ]]; 58 | html = html:gsub("%s%s+", ""); 59 | 60 | local entities = { 61 | ["<"] = "<", [">"] = ">", ["&"] = "&", 62 | ["'"] = "'", ["\""] = """, ["\n"] = "
        ", 63 | }; 64 | 65 | local function tohtml(plain) 66 | return (plain:gsub("[<>&'\"\n]", entities)); 67 | 68 | end 69 | 70 | local function get_page(code, extra) 71 | local message = messages[code]; 72 | if always_serve or message then 73 | message = message or default_message; 74 | return (html:gsub("$(%a+)", { 75 | title = rawget(codes, code) or ("Code "..tostring(code)); 76 | message = message[1]:gsub("%%", function () 77 | return message[math.random(2, math.max(#message,2))]; 78 | end); 79 | extra = tohtml(extra or ""); 80 | })); 81 | end 82 | end 83 | 84 | module:hook_object_event(server, "http-error", function (event) 85 | local response = event.response; 86 | if response then response.headers["Content-Type"] = "text/html"; end 87 | return get_page(event.code, (show_private and event.private_message) or event.message); 88 | end); 89 | -------------------------------------------------------------------------------- /plugins/mod_iq.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2010-2012, Matthew Wild, Waqas Hussain 9 | 10 | module:set_component_inheritable(); 11 | 12 | local st = require "util.stanza"; 13 | 14 | if module:get_host_type() == "local" then 15 | module:hook("iq/full", function(data) 16 | -- IQ to full JID recieved 17 | local origin, stanza = data.origin, data.stanza; 18 | 19 | local session = module:get_full_session(stanza.attr.to); 20 | if not (session and session.send(stanza)) then 21 | if stanza.attr.type == "get" or stanza.attr.type == "set" then 22 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); 23 | end 24 | end 25 | return true; 26 | end); 27 | end 28 | 29 | module:hook("iq/bare", function(data) 30 | -- IQ to bare JID recieved 31 | local stanza = data.stanza; 32 | local type = stanza.attr.type; 33 | 34 | -- TODO fire post processing events 35 | if type == "get" or type == "set" then 36 | local child = stanza.tags[1]; 37 | local xmlns = child.attr.xmlns or "jabber:client"; 38 | local ret = module:fire_event("iq/bare/"..xmlns..":"..child.name, data); 39 | if ret ~= nil then return ret; end 40 | return module:fire_event("iq-"..type.."/bare/"..xmlns..":"..child.name, data); 41 | else 42 | return module:fire_event("iq-"..type.."/bare", data, stanza.attr.id); 43 | end 44 | end); 45 | 46 | module:hook("iq/self", function(data) 47 | -- IQ to self JID recieved 48 | local stanza = data.stanza; 49 | local type = stanza.attr.type; 50 | 51 | if type == "get" or type == "set" then 52 | local child = stanza.tags[1]; 53 | local xmlns = child.attr.xmlns or "jabber:client"; 54 | local ret = module:fire_event("iq/self/"..xmlns..":"..child.name, data); 55 | if ret ~= nil then return ret; end 56 | return module:fire_event("iq-"..type.."/self/"..xmlns..":"..child.name, data); 57 | else 58 | return module:fire_event("iq-"..type.."/self", data, stanza.attr.id); 59 | end 60 | end); 61 | 62 | module:hook("iq/host", function(data) 63 | -- IQ to a local host recieved 64 | local stanza = data.stanza; 65 | local type = stanza.attr.type; 66 | 67 | if type == "get" or type == "set" then 68 | local child = stanza.tags[1]; 69 | local xmlns = child.attr.xmlns or "jabber:client"; 70 | local ret = module:fire_event("iq/host/"..xmlns..":"..child.name, data); 71 | if ret ~= nil then return ret; end 72 | return module:fire_event("iq-"..type.."/host/"..xmlns..":"..child.name, data); 73 | else 74 | return module:fire_event("iq-"..type.."/host", data, stanza.attr.id); 75 | end 76 | end); 77 | -------------------------------------------------------------------------------- /plugins/mod_jid_prep.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local st_reply = require "util.stanza".reply; 8 | local st_error_reply = require "util.stanza".error_reply; 9 | local jid_prep = require "util.jid".prep; 10 | local xmlns = "urn:xmpp:jidprep:0"; 11 | local attr = { xmlns = xmlns }; 12 | 13 | module:add_feature(xmlns); 14 | 15 | module:hook("iq-get/host/"..xmlns..":jid", function(event) 16 | local origin, stanza = event.origin, event.stanza; 17 | if origin.type ~= "c2s" then return origin.send(st_error_reply(stanza, "cancel", "forbidden")); end 18 | 19 | local jid = stanza:get_child_text("jid", xmlns); 20 | local prepped_jid = jid_prep(jid); 21 | if prepped_jid then 22 | return origin.send(st_reply(stanza):tag("jid", attr):text(prepped_jid)); 23 | else 24 | local reply = st_reply(stanza):tag("jid", attr):text(jid):up(); 25 | reply:tag("error", { type = "modify" }) 26 | :tag("jid-malformed", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" }):up():up(); 27 | reply.attr.type = "error"; 28 | return origin.send(reply); 29 | end 30 | end); 31 | -------------------------------------------------------------------------------- /plugins/mod_lastactivity.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2010, Matthew Wild, Waqas Hussain 9 | 10 | local st = require "util.stanza"; 11 | local is_contact_subscribed = require "util.rostermanager".is_contact_subscribed; 12 | local jid_bare = require "util.jid".bare; 13 | local jid_section = require "util.jid".section; 14 | local storagemanager = require "core.storagemanager"; 15 | local module_host = module.host; 16 | 17 | local lastactivity = storagemanager.open(module_host, "last_activity"); 18 | 19 | local os_difftime, os_time, tostring = os.difftime, os.time, tostring; 20 | 21 | module:add_feature("jabber:iq:last"); 22 | 23 | module:hook("pre-presence/bare", function(event) 24 | local origin, stanza = event.origin, event.stanza; 25 | if not(stanza.attr.to) and stanza.attr.type == "unavailable" then 26 | local t = os_time(); 27 | local s = stanza:child_with_name("status"); 28 | s = s and #s.tags == 0 and s[1] or ""; 29 | lastactivity:set(origin.username, { status = s, time = t }); 30 | end 31 | end, 10); 32 | 33 | module:hook("iq/bare/jabber:iq:last:query", function(event) 34 | local origin, stanza = event.origin, event.stanza; 35 | if stanza.attr.type == "get" then 36 | local username = jid_section(stanza.attr.to, "node") or origin.username; 37 | if not stanza.attr.to or is_contact_subscribed(username, module_host, jid_bare(stanza.attr.from)) then 38 | local seconds, text, data = "0", "", lastactivity:get(username); 39 | if data then 40 | seconds = tostring(os_difftime(os_time(), data.time)); 41 | text = data.status; 42 | end 43 | origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:last", seconds = seconds}):text(text)); 44 | else 45 | origin.send(st.error_reply(stanza, "auth", "forbidden")); 46 | end 47 | return true; 48 | end 49 | end); -------------------------------------------------------------------------------- /plugins/mod_message.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2012, Matthew Wild, Robert Hoelz, Waqas Hussain 9 | 10 | local st = require "util.stanza"; 11 | local jid_bare = require "util.jid".bare; 12 | local jid_split = require "util.jid".split; 13 | local user_exists = require "core.usermanager".user_exists; 14 | 15 | local function process_to_bare(bare, origin, stanza) 16 | local user = module:get_bare_session(bare); 17 | 18 | local t = stanza.attr.type; 19 | if t == "error" then 20 | -- discard 21 | elseif t == "groupchat" then 22 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); 23 | elseif t == "headline" then 24 | if user and stanza.attr.to == bare then 25 | for _, session in pairs(user.sessions) do 26 | if session.to_block and session.to_block[stanza] then 27 | session.to_block[stanza] = nil; 28 | elseif session.presence and session.priority >= 0 then 29 | session.send(stanza); 30 | end 31 | end 32 | end -- current policy is to discard headlines if no recipient is available 33 | else -- chat or normal message 34 | if user then 35 | local recipients = user.top_resources; 36 | if recipients then 37 | for i=1,#recipients do 38 | local recipient = recipients[i]; 39 | if recipient.to_block and recipient.to_block[stanza] then 40 | recipient.to_block[stanza] = nil; 41 | else 42 | module:log("debug", "Forking message from %s to %s", stanza.attr.from, bare .. "/" .. recipient.resource); 43 | recipient.send(stanza); 44 | end 45 | end 46 | return true; 47 | end 48 | end 49 | -- no resources are online 50 | local node, host = jid_split(bare); 51 | local ok 52 | if user_exists(node, host) then 53 | ok = module:fire_event("message/offline/handle", { 54 | origin = origin, 55 | stanza = stanza, 56 | }); 57 | end 58 | 59 | if not ok then 60 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); 61 | end 62 | end 63 | return true; 64 | end 65 | 66 | module:hook("message/full", function(data) 67 | local origin, stanza = data.origin, data.stanza; 68 | 69 | local session = module:get_full_session(stanza.attr.to); 70 | if session and session.send(stanza) then 71 | return true; 72 | else -- resource not online 73 | return process_to_bare(jid_bare(stanza.attr.to), origin, stanza); 74 | end 75 | end); 76 | 77 | module:hook("message/bare", function(data) 78 | local origin, stanza = data.origin, data.stanza; 79 | 80 | return process_to_bare(stanza.attr.to or (origin.username.."@"..origin.host), origin, stanza); 81 | end); 82 | -------------------------------------------------------------------------------- /plugins/mod_messagefilter.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local st = require "util.stanza"; 8 | local section = require "util.jid".section; 9 | local hosts = metronome.hosts; 10 | 11 | local patterns = module:get_option_table("messagefilter_patterns", {}); 12 | local allow_local = module:get_option_boolean("messagefilter_allow_local", false); 13 | local bounce_message = module:get_option_string("messagefilter_bmsg", "Message rejected by server filter"); 14 | 15 | local function message_filter(event) 16 | local origin, stanza = event.origin, event.stanza; 17 | local body_text = stanza:child_with_name("body") and stanza:child_with_name("body"):get_text(); 18 | local fromhost = section(stanza.attr.from, "host"); 19 | 20 | local error_reply = st.message{ type = "error", from = stanza.attr.to } 21 | :tag("error", {type = "modify"}) 22 | :tag("not-acceptable", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}) 23 | :tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):text(bounce_message):up(); 24 | 25 | if body_text then 26 | if hosts[fromhost] and allow_local then return; end 27 | for _, pattern in ipairs(patterns) do 28 | if body_text:match(pattern) then 29 | error_reply.attr.to = stanza.attr.from; 30 | origin.send(error_reply); 31 | module:log("info", "Bounced message from user %s because it contained a filtered pattern", stanza.attr.from); 32 | return true; -- Drop the stanza now 33 | end 34 | end 35 | end 36 | end 37 | 38 | module:hook("message/bare", message_filter, 95); 39 | module:hook("message/full", message_filter, 95); 40 | module:hook("message/host", message_filter, 95); 41 | -------------------------------------------------------------------------------- /plugins/mod_motd.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2010-2013, Kim Alvefur, Jeff Mitchell, Matthew Wild 9 | 10 | local host = module:get_host(); 11 | local motd_text = module:get_option_string("motd_text"); 12 | local motd_jid = module:get_option_string("motd_jid", host); 13 | 14 | if not motd_text then return; end 15 | 16 | local jid_join = require "util.jid".join; 17 | local st = require "util.stanza"; 18 | 19 | motd_text = motd_text:gsub("^%s*(.-)%s*$", "%1"):gsub("\n%s+", "\n"); -- Strip indentation from the config 20 | 21 | module:hook("presence/bare", function (event) 22 | local session, stanza = event.origin, event.stanza; 23 | if not session.presence and not stanza.attr.type then 24 | local motd_stanza = 25 | st.message({ to = session.full_jid, from = motd_jid }) 26 | :tag("body"):text(motd_text); 27 | module:send(motd_stanza); 28 | module:log("debug", "MOTD send to user %s", session.full_jid); 29 | end 30 | end, 1); 31 | -------------------------------------------------------------------------------- /plugins/mod_muc_log_mam.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | -- Message Archive Management interface for mod_muc_log 8 | 9 | if not module:host_is_muc() then 10 | error("mod_muc_log_mam can only be loaded on a muc component!", 0); 11 | end 12 | 13 | local ipairs, ripairs, tonumber, t_remove, tostring, os_date, os_time = 14 | ipairs, ripairs, tonumber, table.remove, tostring, os.date, os.time; 15 | local jid_bare = require "util.jid".bare; 16 | local jid_section = require "util.jid".section; 17 | local jid_split = require "util.jid".split; 18 | local datetime = require "util.datetime"; 19 | local st = require "util.stanza"; 20 | local data_load = require "util.datamanager".load; 21 | local datastore = "muc_log"; 22 | local error_reply = require "util.stanza".error_reply; 23 | 24 | local host_object = module:get_host_session(); 25 | 26 | local xmlns = "urn:xmpp:mam:2"; 27 | local delay_xmlns = "urn:xmpp:delay"; 28 | local forward_xmlns = "urn:xmpp:forward:0"; 29 | local markers_xmlns = "urn:xmpp:chat-markers:0"; 30 | 31 | module:depends("muc_log"); 32 | 33 | local mamlib = module:require("mam", "mam"); 34 | local validate_query = module:require("validate", "mam").validate_query; 35 | local fields_handler, generate_stanzas = mamlib.fields_handler, mamlib.generate_stanzas; 36 | 37 | module:hook("muc-disco-info-features", function(room, reply) 38 | reply:tag("feature", { var = xmlns }):up() 39 | reply:tag("feature", { var = markers_xmlns }):up() 40 | end, -100); 41 | 42 | module:hook("iq/bare/"..xmlns..":prefs", function(event) 43 | local origin, stanza = event.origin, event.stanza; 44 | origin.send(st.error_reply(stanza, "cancel", "feature-not-implemented")); 45 | return true; 46 | end); 47 | 48 | module:hook("iq-get/bare/"..xmlns..":query", fields_handler); 49 | module:hook("iq-set/bare/"..xmlns..":query", function(event) 50 | local origin, stanza = event.origin, event.stanza; 51 | local from = stanza.attr.from or origin.full_jid; 52 | local to = stanza.attr.to; 53 | local query = stanza.tags[1]; 54 | local qid = query.attr.queryid; 55 | 56 | local room = host_object.muc.rooms[to]; 57 | 58 | if room._data.logging then 59 | -- check that requesting entity has access 60 | if (room:get_option("members_only") and not room:is_affiliated(from)) or 61 | room:get_affiliation(from) == "outcast" then 62 | origin.send(st.error_reply(stanza, "auth", "not-authorized")); 63 | return true; 64 | end 65 | 66 | local start, fin, with, after, before, max, index; 67 | local ok, ret = validate_query(stanza, query, qid); 68 | if not ok then 69 | return origin.send(ret); 70 | else 71 | start, fin, with, after, before, max, index = 72 | ret.start, ret.fin, ret.with, ret.after, ret.before, ret.max, ret.index; 73 | end 74 | 75 | local archive = { 76 | logs = module:fire_event("load-stanza-log", jid_section(to, "node"), module.host, start, fin, before, after) 77 | }; 78 | 79 | local messages, rq, count = generate_stanzas(archive, start, fin, with, max, after, before, index, qid, { origin, stanza }); 80 | if not messages then 81 | module:log("debug", "%s MAM query RSM parameters were out of bounds", to); 82 | local rsm_error = st.error_reply(stanza, "cancel", "item-not-found"); 83 | rsm_error:add_child(query); 84 | return origin.send(rsm_error); 85 | end 86 | 87 | local reply = st.reply(stanza):add_child(rq); 88 | 89 | for _, message in ipairs(messages) do 90 | message.attr.from = to; 91 | message.attr.to = from; 92 | origin.send(message); 93 | end 94 | origin.send(reply); 95 | 96 | module:log("debug", "MAM query %s completed (returned messages: %s)", 97 | qid and qid or "without id", count == 0 and "none" or tostring(count)); 98 | else 99 | origin.send(st.error_reply(stanza, "cancel", "forbidden", "Room logging needs to be enabled")); 100 | end 101 | 102 | return true; 103 | end); -------------------------------------------------------------------------------- /plugins/mod_muc_moderation.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2015-2021, Kim Alvefur 9 | 10 | -- Implements: XEP-0425: Message Moderation 11 | -- This is a backport of the module from Prosody Modules 12 | 13 | if not module:host_is_muc() then 14 | error("mod_muc_moderation can only be loaded on a muc component!", 0); 15 | end 16 | 17 | -- Imports 18 | local dt = require "util.datetime"; 19 | local id = require "util.uuid".generate; 20 | local jid = require "util.jid"; 21 | local st = require "util.stanza"; 22 | 23 | local valid_roles = { 24 | none = 0, 25 | visitor = 1, 26 | member = 2, 27 | moderator = 3, 28 | } 29 | 30 | -- Namespaces 31 | local xmlns_fasten = "urn:xmpp:fasten:0"; 32 | local xmlns_moderate = "urn:xmpp:message-moderate:0"; 33 | local xmlns_retract = "urn:xmpp:message-retract:0"; 34 | 35 | -- Discovering support 36 | module:hook("muc-disco-info-features", function(room, reply) 37 | reply:tag("feature", { var = xmlns_moderate }):up(); 38 | end, -102); 39 | 40 | -- Main handling 41 | module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) 42 | local stanza, origin = event.stanza, event.origin; 43 | 44 | -- Collect info we need 45 | local apply_to = stanza.tags[1]; 46 | local moderate_tag = apply_to:get_child("moderate", xmlns_moderate); 47 | if not moderate_tag then return; end -- some other kind of fastening? 48 | 49 | local reason = moderate_tag:get_child_text("reason"); 50 | local retract = moderate_tag:get_child("retract", xmlns_retract); 51 | 52 | local room_jid = stanza.attr.to; 53 | local room_node = jid.split(room_jid); 54 | local room = module:get_host_session().muc.rooms[room_jid]; 55 | 56 | local stanza_id = apply_to.attr.id; 57 | 58 | -- Permissions 59 | local actor = stanza.attr.from; 60 | local actor_nick = room._jid_nick[actor]; 61 | local affiliation = room:get_affiliation(actor); 62 | -- Retrieve their current role, if they are in the room, otherwise what they 63 | -- would have based on affiliation. 64 | local role = room:get_role(actor_nick) or room:get_default_role(affiliation); 65 | if valid_roles[role or "none"] < valid_roles.moderator then 66 | origin.send(st.error_reply(stanza, "auth", "forbidden", "You need a role of at least Moderator'")); 67 | return true; 68 | end 69 | 70 | local announcement = st.message({ from = room_jid, type = "groupchat", id = id(), }) 71 | :tag("apply-to", { xmlns = xmlns_fasten, id = stanza_id }) 72 | :tag("moderated", { xmlns = xmlns_moderate, by = actor_nick }) 73 | 74 | if reason then 75 | announcement:tag("reason"):text(reason):up(); 76 | end 77 | 78 | if retract then 79 | announcement:tag("retract", { xmlns = xmlns_retract }):up(); 80 | module:fire_event("muc-tombstone-entry", { room = room, moderation_id = stanza_id, announcement = st.clone(announcement) }); 81 | end 82 | 83 | -- Done, tell people about it 84 | module:log("info", "Message with id '%s' in room %s moderated by %s, reason: %s", stanza_id, room_jid, actor, reason); 85 | room:broadcast_message(announcement); 86 | 87 | origin.send(st.reply(stanza)); 88 | return true; 89 | end); -------------------------------------------------------------------------------- /plugins/mod_net_multiplex.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2011-2012, Matthew Wild, Waqas Hussain 9 | 10 | module:set_global(); 11 | 12 | local max_buffer_len = module:get_option_number("multiplex_buffer_size", 1024); 13 | 14 | local portmanager = require "core.portmanager"; 15 | 16 | local available_services = {}; 17 | 18 | local function add_service(service) 19 | if service.name == "multiplex" or service.name == "multiplex_secure" then return; end 20 | 21 | local multiplex_pattern = service.multiplex and service.multiplex.pattern; 22 | if multiplex_pattern then 23 | module:log("debug", "Adding multiplex service %q with pattern %q", service.name, multiplex_pattern); 24 | available_services[service] = multiplex_pattern; 25 | else 26 | module:log("debug", "Service %q is not multiplex-capable", service.name); 27 | end 28 | end 29 | module:hook("service-added", function (event) add_service(event.service); end); 30 | module:hook("service-removed", function (event) available_services[event.service] = nil; end); 31 | 32 | for service_name, services in pairs(portmanager.get_registered_services()) do 33 | for i, service in ipairs(services) do 34 | add_service(service); 35 | end 36 | end 37 | 38 | local buffers = {}; 39 | 40 | local listener = { default_mode = "*a" }; 41 | 42 | function listener.onconnect() 43 | end 44 | 45 | function listener.onincoming(conn, data) 46 | if not data then return; end 47 | local buf = buffers[conn]; 48 | buffers[conn] = nil; 49 | buf = buf and buf..data or data; 50 | for service, multiplex_pattern in pairs(available_services) do 51 | if buf:match(multiplex_pattern) then 52 | module:log("debug", "Routing incoming connection to %s", service.name); 53 | local listener = service.listener; 54 | conn:setlistener(listener); 55 | local onconnect = listener.onconnect; 56 | if onconnect then onconnect(conn) end 57 | return listener.onincoming(conn, buf); 58 | end 59 | end 60 | if #buf > max_buffer_len then -- Give up 61 | conn:close(); 62 | else 63 | buffers[conn] = buf; 64 | end 65 | end 66 | 67 | function listener.ondisconnect(conn, err) 68 | buffers[conn] = nil; -- warn if no buffer? 69 | end 70 | 71 | module:add_item("net-provider", { 72 | name = "multiplex"; 73 | listener = listener; 74 | }); 75 | 76 | module:add_item("net-provider", { 77 | name = "multiplex_secure"; 78 | listener = listener; 79 | encryption = "ssl"; 80 | }); 81 | -------------------------------------------------------------------------------- /plugins/mod_offline.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2010, Matthew Wild, Rob Hoelz, Waqas Hussain 9 | 10 | local datamanager = require "util.datamanager"; 11 | local st = require "util.stanza"; 12 | local datetime = require "util.datetime"; 13 | local ipairs = ipairs; 14 | local jid_bare, jid_split = require "util.jid".bare, require "util.jid".split; 15 | local mam_add_to_store = module:require("mam", "mam").add_to_store; 16 | local limit = module:get_option_number("offline_store_limit", 100); 17 | 18 | module:add_feature("msgoffline"); 19 | 20 | module:hook("message/offline/handle", function(event) 21 | local origin, stanza = event.origin, event.stanza; 22 | 23 | if not stanza:get_child("no-store", "urn:xmpp:hints") then 24 | local to = stanza.attr.to; 25 | local node, host; 26 | if to then 27 | node, host = jid_split(to); 28 | else 29 | node, host = origin.username, origin.host; 30 | end 31 | 32 | local archive = datamanager.list_load(node, host, "offline"); 33 | local mam_store, handled_by_mam = module:fire_event("mam-get-store", node); 34 | if mam_store and mam_add_to_store(mam_store, node, jid_bare(to)) then handled_by_mam = true; end 35 | 36 | if archive and #archive >= limit then 37 | if not handled_by_mam then 38 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "User's offline message queue is full!")); 39 | end 40 | return true; 41 | else 42 | stanza.attr.stamp = datetime.datetime(); 43 | local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza)); 44 | stanza.attr.stamp = nil; 45 | return result; 46 | end 47 | end 48 | end); 49 | 50 | module:hook("message/offline/broadcast", function(event) 51 | local origin = event.origin; 52 | 53 | local node, host = origin.username, origin.host; 54 | 55 | local data = datamanager.list_load(node, host, "offline"); 56 | if not data then return true; end 57 | if origin.bind_version ~= 2 then 58 | for _, stanza in ipairs(data) do 59 | stanza = st.deserialize(stanza); 60 | stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203 61 | stanza.attr.stamp = nil; 62 | origin.send(stanza); 63 | end 64 | end 65 | datamanager.list_store(node, host, "offline", nil); 66 | return true; 67 | end); 68 | -------------------------------------------------------------------------------- /plugins/mod_ping.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local st = require "util.stanza"; 8 | 9 | module:add_feature("urn:xmpp:ping"); 10 | 11 | module:hook("iq/bare/urn:xmpp:ping:ping", function(event) 12 | return event.origin.send(st.error_reply(event.stanza, "cancel", "service-unavailable")); 13 | end); 14 | module:hook("iq/host/urn:xmpp:ping:ping", function(event) 15 | local origin, stanza = event.origin, event.stanza; 16 | if stanza.attr.type == "get" then return origin.send(st.reply(stanza)); end 17 | end); 18 | 19 | -- Ad-hoc command 20 | 21 | local datetime = require "util.datetime".datetime; 22 | 23 | function ping_command_handler (self, data, state) 24 | local now = datetime(); 25 | return { info = "Pong\n"..now, status = "completed" }; 26 | end 27 | 28 | local adhoc_new = module:require "adhoc".new; 29 | local descriptor = adhoc_new("Ping", "ping", ping_command_handler); 30 | module:add_item ("adhoc", descriptor); 31 | 32 | -------------------------------------------------------------------------------- /plugins/mod_private.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2008-2010, Matthew Wild, Waqas Hussain 9 | 10 | local _type = type; 11 | 12 | local st = require "util.stanza"; 13 | local storagemanager = require "core.storagemanager"; 14 | 15 | local private = storagemanager.open(module.host, "private"); 16 | 17 | module:add_feature("jabber:iq:private"); 18 | 19 | local function store(user, data, key, tag) 20 | if not data then data = {}; end; 21 | if #tag == 0 then 22 | data[key] = nil; 23 | else 24 | data[key] = st.preserialize(tag); 25 | end 26 | local err; 27 | data, err = private:set(user, data); 28 | return data, err; 29 | end 30 | 31 | module:hook("iq/self/jabber:iq:private:query", function(event) 32 | local origin, stanza = event.origin, event.stanza; 33 | local type = stanza.attr.type; 34 | local query = stanza.tags[1]; 35 | if #query.tags == 1 then 36 | local tag = query.tags[1]; 37 | local key = tag.name..":"..tag.attr.xmlns; 38 | local data, err = private:get(origin.username); 39 | if err then 40 | origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); 41 | return true; 42 | end 43 | if stanza.attr.type == "get" then 44 | if data and data[key] then 45 | origin.send(st.reply(stanza):tag("query", { xmlns = "jabber:iq:private" }):add_child(st.deserialize(data[key]))); 46 | else 47 | origin.send(st.reply(stanza):add_child(stanza.tags[1])); 48 | end 49 | else 50 | local result = module:fire_event("private-storage-callbacks", { session = origin, key = tag.attr.xmlns, data = tag }); 51 | if _type(result) == "table" and result.error then 52 | local error = result.error; 53 | origin.send(st.error_reply(stanza, error.type, error.condition, error.message)); 54 | return true; 55 | end 56 | 57 | local data, err = store(origin.username, data, key, tag); 58 | if data then 59 | origin.send(st.reply(stanza)); 60 | else 61 | origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); 62 | end 63 | end 64 | else 65 | origin.send(st.error_reply(stanza, "modify", "bad-format")); 66 | end 67 | return true; 68 | end); 69 | 70 | module:hook("private-storage-set", function(event) 71 | local user, key, tag, from = event.user, event.key, event.tag, event.from; 72 | local data = private:get(user); 73 | module:log("debug", "setting %s storage for %s from %s", key:match("^[^:]+:(.*)$"), user, from); 74 | store(user, data, key, tag); 75 | end); 76 | -------------------------------------------------------------------------------- /plugins/mod_public_service.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | -- This module builds a vCard4 and marks the server as public service, 8 | -- by adding "urn:xmpp:public-server" between features. 9 | 10 | local my_host = module.host; 11 | local st = require "util.stanza"; 12 | 13 | module:depends("server_presence"); 14 | module:add_feature("urn:xmpp:public-server"); 15 | 16 | local vcard4_xmlns = "urn:ietf:params:xml:ns:vcard-4.0"; 17 | local server_vcard = module:get_option_table("public_service_vcard", {}); 18 | 19 | -- Build Service vCard4 20 | 21 | local vcard; 22 | 23 | local function build_vcard() 24 | vcard = st.stanza("vcard", { xmlns = vcard4_xmlns }); 25 | vcard:tag("kind"):tag("text"):text("application"):up():up(); 26 | vcard:tag("name"):tag("text"):text("Metronome"):up():up(); 27 | vcard:tag("fn"):tag("text"):text(my_host):up():up(); 28 | 29 | if server_vcard.name then vcard:tag("note"):tag("text"):text(server_vcard.name):up():up(); end 30 | if server_vcard.url then vcard:tag("url"):tag("uri"):text(server_vcard.url):up():up(); end 31 | if server_vcard.foundation_year then vcard:tag("bday"):tag("date"):text(server_vcard.foundation_year):up():up(); end 32 | if server_vcard.country then vcard:tag("adr"):tag("country"):text(server_vcard.country):up():up(); end 33 | if server_vcard.email then vcard:tag("email"):tag("uri"):text(server_vcard.email):up():up(); end 34 | if server_vcard.admin_jid then vcard:tag("impp"):tag("uri"):text("xmpp:"..server_vcard.admin_jid):up():up(); end 35 | if server_vcard.geo then vcard:tag("geo"):tag("uri"):text("geo:"..server_vcard.geo):up():up(); end 36 | if server_vcard.ca then 37 | local ca = server_vcard.ca; 38 | vcard:tag("ca") 39 | :tag("name"):text(ca.name):up() 40 | :tag("uri"):text(ca.url):up():up(); 41 | end 42 | if server_vcard.oob_registration_uri then 43 | vcard:tag("registration", { xmlns = "urn:xmpp:vcard:registration:1" }):tag("uri"):text(server_vcard.oob_registration_uri):up():up(); 44 | end 45 | 46 | hosts[my_host].public_service_vcard = vcard; 47 | end 48 | 49 | local function handle_vcard(event) 50 | local origin, stanza = event.origin, event.stanza; 51 | local reply = st.reply(stanza):add_child(vcard); 52 | module:log("info", "sending public service vcard to %s...", stanza.attr.from); 53 | return origin.send(reply); 54 | end 55 | 56 | local function handle_reload() 57 | server_vcard = module:get_option_table("public_service_vcard", {}); 58 | build_vcard(); 59 | end 60 | 61 | function module.load() build_vcard(); end 62 | 63 | module:hook_global("config-reloaded", handle_reload); 64 | module:hook("iq-get/host/"..vcard4_xmlns..":vcard", handle_vcard, 30); 65 | 66 | -------------------------------------------------------------------------------- /plugins/mod_room_zapanddestroy.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | --- Adds a little fantasy to the API's room:destroy. 8 | --- let's have some fun when it comes to lynching lamers. 9 | 10 | local muc_host = module:get_host(); 11 | local timer = require "util.timer"; 12 | local default_quotes = { 13 | { 14 | { "/me three gloomish elemental lights mysteriously appear...", "*", 4 }, 15 | { "Hi guys! I'm the server entity itself, prepare to be smited.", "Metronome", 6 }, 16 | { "/me starts modifying the space-time quantum fabric composing the room...", "Metronome", 8 }, 17 | { "/me waves *o/* and *poofs*", "Metronome", 9 }, 18 | { "/me light spectrum fluctuates and flickers erratically, as %s starts to gravitically implode together with all its inoccupants.", "*", 11 } 19 | } 20 | } 21 | local default_screams = { "ArghHHHHhhhhh!", "NooooooOOOOooooooOo!", "EwwwwwwwwwwWWWWWWW AghhhhHH!", "AAAAAAAAAAHHHHHhhhhhhhh!" }; 22 | local quotes = module:get_option_array("zap_and_dest_quotes", default_quotes); 23 | local screams = module:get_option_array("zap_and_dest_screams", default_screams); 24 | 25 | -- injects the new function into the mt 26 | 27 | hosts[muc_host].modules.muc.stanza_handler.muc_new_room.room_mt["zap_and_destroy"] = function (self) 28 | -- Select randomly between quote sets. 29 | local quote_set = math.random(1,#quotes); 30 | 31 | -- We check that quotes/timeseq in the set has at most 5 entries.... 32 | if #quotes[quote_set] > 5 then 33 | module:log("error", "quotes and timesequences sets table can't exceed 5 entries, halting."); 34 | return true; 35 | end 36 | 37 | -- Set timesequences and check that timesequences are numbers, FIX: Crappy. 38 | local check_seqs = false; 39 | 40 | for i=1,5 do if type(quotes[quote_set][i][3]) ~= "number" then 41 | check_seqs = true; break; end end 42 | if check_seqs then module:log("error", "time sequences values can contain only number entries! Halting."); return true; end 43 | 44 | -- Safety checks done? Begin! 45 | -- Loop through quotes and add timers. 46 | for i=1,5 do 47 | timer.add_task(quotes[quote_set][i][3], function () self:broadcast_message(stanza.stanza("message", { from = self.jid.."/"..quotes[quote_set][i][2], to = self.jid, type = "groupchat"}):body(quotes[quote_set][i][1]:format(self.jid))) end); 48 | end 49 | 50 | -- When the looping is done we shall have everyone scream, using the last timeseq entry. 51 | timer.add_task(quotes[quote_set][#quotes[quote_set]][3], function () 52 | for _, occupants in pairs(self._occupants) do 53 | for jid in pairs(occupants.sessions) do 54 | self:handle_to_room(jid, stanza.stanza("message", { from = jid, to = self.jid, type = "groupchat"}):body(screams[math.random(1,#screams)])); 55 | end 56 | end end); 57 | 58 | -- Now that we're done, on with the disintegration. 59 | -- Shall wait two seconds after the last sequence. 60 | local t_wait = quotes[quote_set][#quotes[quote_set]][3] + 2; 61 | timer.add_task(t_wait, function () self:destroy(nil, "The room implodes into itself and fizzles out of existance..."); end); 62 | end 63 | -------------------------------------------------------------------------------- /plugins/mod_sic.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local st_reply = require "util.stanza".reply; 8 | local tostring = tostring; 9 | local xmlns = "urn:xmpp:sic:1"; 10 | local attr = { xmlns = xmlns }; 11 | 12 | module:add_feature(xmlns); 13 | 14 | module:hook("iq-get/self/"..xmlns..":address", function(event) 15 | local origin, stanza = event.origin, event.stanza; 16 | local ip = origin.conn:ip(); 17 | local port = origin.conn:port(); 18 | local reply = st_reply(stanza):tag("address", attr); 19 | reply:tag("ip"):text(ip):up(); 20 | if port then reply:tag("port"):text(tostring(port)):up(); end 21 | return origin.send(reply); 22 | end); 23 | -------------------------------------------------------------------------------- /plugins/mod_stanza_counter.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | module:set_global() 8 | 9 | local jid_bare = require "util.jid".bare 10 | 11 | -- Setup, Init functions. 12 | -- initialize function counter table on the global object on start 13 | function init_counter() 14 | metronome.stanza_counter = { 15 | iq = { incoming = 0, outgoing = 0 }, 16 | message = { incoming = 0, outgoing = 0 }, 17 | presence = { incoming = 0, outgoing = 0 } 18 | } 19 | end 20 | 21 | -- Basic Stanzas' Counters 22 | local function callback(check) 23 | return function(self) 24 | local name = self.stanza.name 25 | if not metronome.stanza_counter then init_counter() end 26 | if check then 27 | metronome.stanza_counter[name].outgoing = metronome.stanza_counter[name].outgoing + 1 28 | else 29 | metronome.stanza_counter[name].incoming = metronome.stanza_counter[name].incoming + 1 30 | end 31 | end 32 | end 33 | 34 | function module.add_host(module) 35 | -- Hook all pre-stanza events. 36 | module:hook("pre-iq/bare", callback(true), 999) 37 | module:hook("pre-iq/full", callback(true), 999) 38 | module:hook("pre-iq/host", callback(true), 999) 39 | 40 | module:hook("pre-message/bare", callback(true), 999) 41 | module:hook("pre-message/full", callback(true), 999) 42 | module:hook("pre-message/host", callback(true), 999) 43 | 44 | module:hook("pre-presence/bare", callback(true), 999) 45 | module:hook("pre-presence/full", callback(true), 999) 46 | module:hook("pre-presence/host", callback(true), 999) 47 | 48 | -- Hook all stanza events. 49 | module:hook("iq/bare", callback(false), 999) 50 | module:hook("iq/full", callback(false), 999) 51 | module:hook("iq/host", callback(false), 999) 52 | 53 | module:hook("message/bare", callback(false), 999) 54 | module:hook("message/full", callback(false), 999) 55 | module:hook("message/host", callback(false), 999) 56 | 57 | module:hook("presence/bare", callback(false), 999) 58 | module:hook("presence/full", callback(false), 999) 59 | module:hook("presence/host", callback(false), 999) 60 | end 61 | 62 | -- Set up! 63 | module:hook("server-started", init_counter); 64 | -------------------------------------------------------------------------------- /plugins/mod_stanza_log.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local util_jid = require "util.jid"; 8 | local storagemanager = require "core.storagemanager"; 9 | 10 | local ipairs, os_date = ipairs, os.date; 11 | 12 | local stanzalog_lib = module:require("stanzalog", "auxlibs"); 13 | logs = { index = {} }; 14 | 15 | local function load_stanza_log(node, host, start, fin, before, after) 16 | local jid = util_jid.join(node, host) 17 | local metadata = storagemanager.open(host, "stanza_log_metada"):get(node) or {}; 18 | local log, set = logs[util_jid.join(node, host)]; 19 | if not log then 20 | logs[jid] = stanzalog_lib.load_batch(node, host, {}, start, fin, before, after, metadata, logs.index); 21 | storagemanager.open(host, "stanza_log_metada"):set(node, metadata); 22 | return logs[jid]; 23 | end 24 | if #log > 0 then -- otherwise it's empty, good bye 25 | -- in case we miss argument try to construct the archive timeframes basing either on saved metadata or 26 | -- first and last entries in the log cache array, that we have 27 | if not start then 28 | if metadata.first then 29 | start = metadata.first; 30 | else 31 | start = log[1].timestamp; 32 | end 33 | end 34 | if not fin then 35 | if metadata.last then 36 | fin = metadata.last; 37 | else 38 | fin = log[#log].timestamp; 39 | end 40 | end 41 | else 42 | return log; 43 | end 44 | if not before and not after and (metadata.first and start >= metadata.first) and (metadata.last and fin <= metadata.last) then 45 | return log; 46 | else 47 | if before then 48 | start = start - 2630000; 49 | elseif after then -- one month after 50 | fin = fin + 2630000; 51 | end 52 | end 53 | log = stanzalog_lib.load_batch(node, host, log, start, fin, before, after, metadata, logs.index); 54 | storagemanager.open(host, "stanza_log_metada"):set(node, metadata); 55 | return log; 56 | end 57 | 58 | local function store_stanza_log(node, host, data, last, today) 59 | if last then 60 | local metadata = storagemanager.open(host, "stanza_log_metada"):get(node) or {}; 61 | metadata.last = last.timestamp; 62 | logs.index[last.uid] = last.timestamp; 63 | storagemanager.open(host, "stanza_log_metada"):set(node, metadata); 64 | end 65 | local jid = util_jid.join(node, host); 66 | if logs[jid] then 67 | logs[jid] = data; 68 | end 69 | local today = (today and os_date("!%Y%m%d", today)) or os_date("!%Y%m%d"); 70 | local today_data = {}; 71 | for i, entry in ipairs(data) do 72 | if os_date("!%Y%m%d", entry.timestamp) == today then 73 | today_data[#today_data + 1] = entry; 74 | end 75 | end 76 | return storagemanager.open(host, "stanza_log/" .. today):set(node, today_data); 77 | end 78 | 79 | local function purge_stanza_log(node, host) 80 | for store in storagemanager.get_driver(host):stores(node, "keyval", "stanza_log") do 81 | local entries = storagemanager.open(host, store):get(node) or {}; 82 | for _, entry in ipairs(entries) do logs[entry.uid] = nil; end 83 | storagemanager.open(host, store):set(node); 84 | end 85 | storagemanager.open(host, "stanza_log_metadata"):set(node); 86 | end 87 | 88 | module:hook_global("config-reloaded", function() 89 | module:log("debug", "Purging stanza log cache..."); 90 | logs = { index = {} }; 91 | end); 92 | 93 | module:hook("load-stanza-log", load_stanza_log); 94 | module:hook("store-stanza-log", store_stanza_log); 95 | module:hook("purge-stanza-log", purge_stanza_log); -------------------------------------------------------------------------------- /plugins/mod_storage_internal.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- Additional Contributors: John Regan 8 | -- 9 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 10 | -- ** Copyright (c) 2011-2012, Kim Alvefur, Matthew Wild, Waqas Hussain 11 | 12 | local datamanager = require "core.storagemanager".olddm; 13 | 14 | local host = module.host; 15 | 16 | cache = {}; 17 | 18 | local driver = { name = "internal" }; 19 | local driver_mt = { __index = driver }; 20 | 21 | function driver:open(store) 22 | if not cache[store] then cache[store] = setmetatable({ store = store }, driver_mt); end 23 | return cache[store]; 24 | end 25 | function driver:get(user) 26 | return datamanager.load(user, host, self.store); 27 | end 28 | 29 | function driver:set(user, data) 30 | return datamanager.store(user, host, self.store, data); 31 | end 32 | 33 | function driver:stores(user, type, pattern) 34 | return datamanager.stores(user, host, type, pattern); 35 | end 36 | 37 | function driver:store_exists(user, type) 38 | return datamanager.store_exists(user, host, self.store, type); 39 | end 40 | 41 | function driver:purge(user) 42 | return datamanager.purge(user, host); 43 | end 44 | 45 | function driver:nodes(type) 46 | return datamanager.nodes(host, self.store, type); 47 | end 48 | 49 | module:add_item("data-driver", driver); 50 | -------------------------------------------------------------------------------- /plugins/mod_subscription_block.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local jid = require "util.jid"; 8 | local st_reply = require "util.stanza".reply; 9 | 10 | local jid_list, block_pattern; 11 | 12 | local function load_config() 13 | jid_list = module:get_option_set("subscription_block_jidlist", {}); 14 | block_pattern = module:get_option_string("subscription_block_pattern", ""); 15 | end 16 | 17 | local function block_subscription(data) 18 | local origin, stanza = data.origin, data.stanza; 19 | 20 | if stanza.attr.type == "subscribe" 21 | and jid_list:contains(stanza.attr.to) 22 | and jid.compare(stanza.attr.from, block_pattern) then 23 | return true; 24 | end 25 | 26 | if stanza.attr.type == "set" 27 | and jid_list:contains(jid.bare(stanza.attr.to)) 28 | and jid.compare(stanza.attr.from, block_pattern) then 29 | return origin.send(st_reply(stanza, "cancel", "not-allowed")); 30 | end 31 | end 32 | 33 | module:hook("iq-set/full/http://jabber.org/protocol/rosterx", block_subscription, 10); 34 | module:hook("presence/bare", block_subscription, 10); 35 | module:hook("pre-presence/bare", block_subscription, 10); 36 | module:hook_global("config-reloaded", load_config); 37 | 38 | load_config(); 39 | -------------------------------------------------------------------------------- /plugins/mod_time.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2008-2010, Matthew Wild, Waqas Hussain 9 | 10 | local st = require "util.stanza"; 11 | local datetime = require "util.datetime".datetime; 12 | 13 | -- XEP-0202: Entity Time 14 | 15 | module:add_feature("urn:xmpp:time"); 16 | 17 | local function time_handler(event) 18 | local origin, stanza = event.origin, event.stanza; 19 | if stanza.attr.type == "get" then 20 | origin.send(st.reply(stanza):tag("time", {xmlns = "urn:xmpp:time"}) 21 | :tag("tzo"):text("+00:00"):up() -- TODO get the timezone in a platform independent fashion 22 | :tag("utc"):text(datetime())); 23 | return true; 24 | end 25 | end 26 | 27 | module:hook("iq/bare/urn:xmpp:time:time", time_handler); 28 | module:hook("iq/host/urn:xmpp:time:time", time_handler); 29 | -------------------------------------------------------------------------------- /plugins/mod_turnadmin.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- WARNING: for this plugin to work the password of the user has to be passed 8 | -- to turnadmin before it's actually hashed by the registration backend!! 9 | -- Be certain about the system security before its usage. 10 | 11 | local exec = os.execute; 12 | 13 | local pre_cmd = module:get_option_string("turnadmin_pre_cmd", ""); 14 | local db_path = module:get_option_string("turnadmin_sqlite_db", "/var/db/turndb"); 15 | 16 | if pre_cmd ~= "" then pre_cmd = pre_cmd .. " "; end 17 | 18 | module:hook("user-registration-verified", function(event) 19 | local user, host, pass = event.username, event.host, event.password; 20 | module:log("debug", "adding turn server long time credentials for %s", user.."@"..host); 21 | exec(pre_cmd .. "turnadmin -a -b " .. db_path .. " -u " .. user .. " -r " .. host .. " -p " .. pass .. " &"); 22 | end); 23 | 24 | module:hook("user-changed-password", function(event) 25 | local user, host, pass = event.username, event.host, event.password; 26 | module:log("debug", "updating turn server long time credentials for %s", user.."@"..host); 27 | exec(pre_cmd .. "turnadmin -a -b " .. db_path .. " -u " .. user .. " -r " .. host .. " -p " .. pass .. " &"); 28 | end); 29 | 30 | module:hook_global("user-deleted", function(event) 31 | local user, host = event.username, event.host; 32 | if host == module.host then 33 | module:log("debug", "deleting turn server long time credentials for %s", user.."@"..host); 34 | exec(pre_cmd .. "turnadmin -d -b " .. db_path .. " -u " .. user .. " -r " .. host .. " &"); 35 | end 36 | end, 150); 37 | -------------------------------------------------------------------------------- /plugins/mod_uptime.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2008-2010, Matthew Wild, Waqas Hussain 9 | 10 | local st = require "util.stanza"; 11 | 12 | local start_time = metronome.start_time; 13 | module:hook_global("server-started", function() start_time = metronome.start_time end); 14 | 15 | -- XEP-0012: Last activity 16 | module:add_feature("jabber:iq:last"); 17 | 18 | module:hook("iq/host/jabber:iq:last:query", function(event) 19 | local origin, stanza = event.origin, event.stanza; 20 | if stanza.attr.type == "get" then 21 | origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:last", seconds = tostring(os.difftime(os.time(), start_time))})); 22 | return true; 23 | end 24 | end); 25 | 26 | -- Ad-hoc command 27 | local adhoc_new = module:require "adhoc".new; 28 | 29 | function uptime_text() 30 | local t = os.time()-start_time; 31 | local seconds = t%60; 32 | t = (t - seconds)/60; 33 | local minutes = t%60; 34 | t = (t - minutes)/60; 35 | local hours = t%24; 36 | t = (t - hours)/24; 37 | local days = t; 38 | return string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", 39 | days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", 40 | minutes, (minutes ~= 1 and "s") or "", os.date("%c", start_time)); 41 | end 42 | 43 | function uptime_command_handler (self, data, state) 44 | return { info = uptime_text(), status = "completed" }; 45 | end 46 | 47 | local descriptor = adhoc_new("Get uptime", "uptime", uptime_command_handler); 48 | 49 | module:add_item ("adhoc", descriptor); 50 | -------------------------------------------------------------------------------- /plugins/mod_version.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local st = require "util.stanza"; 8 | local lua_version = _G._VERSION; 9 | 10 | module:add_feature("jabber:iq:version"); 11 | 12 | local version; 13 | 14 | local query = st.stanza("query", {xmlns = "jabber:iq:version"}) 15 | :tag("name"):text("Metronome"):up() 16 | :tag("version"):text(metronome.version):up(); 17 | 18 | local fixed_osv_string = module:get_option_string("fixed_osv_string"); 19 | local random_osv_cmd = module:get_option_string("refresh_random_osv_cmd"); 20 | local log_requests = module:get_option_boolean("log_version_requests", true); 21 | 22 | if not module:get_option_boolean("hide_os_type") and not random_osv_cmd then 23 | local os_version_command = module:get_option_string("os_version_command"); 24 | local ok, pposix = pcall(require, "util.pposix"); 25 | if fixed_osv_string then 26 | version = fixed_osv_string; 27 | else 28 | if not os_version_command and (ok and pposix and pposix.uname) then 29 | version = pposix.uname().sysname .. " " .. pposix.uname().release; 30 | end 31 | if not version then 32 | local uname = io.popen(os_version_command or "uname -sr"); 33 | if uname then 34 | version = uname:read("*a"); 35 | end 36 | uname:close(); 37 | end 38 | end 39 | if version then 40 | query:tag("os"):text(lua_version .. " / " .. version):up(); 41 | end 42 | end 43 | 44 | module:hook("iq/host/jabber:iq:version:query", function(event) 45 | local stanza = event.stanza; 46 | if stanza.attr.type == "get" and stanza.attr.to == module.host then 47 | local _query; 48 | if random_osv_cmd then 49 | random_string = io.popen(random_osv_cmd); 50 | version = random_string and random_string:read("*a"); version = version and version:match("^%s*(.-)%s*$") or version; 51 | random_string:close(); 52 | _query = st.clone(query):tag("os"):text(version):up(); 53 | end 54 | if log_requests then module:log("info", "%s requested the version of the server software, sending response...", stanza.attr.from); end 55 | event.origin.send(st.reply(stanza):add_child(_query or query)); 56 | return true; 57 | end 58 | end); 59 | -------------------------------------------------------------------------------- /plugins/mod_watchregistrations.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2012, Kim Alvefur, Matthew Wild, Waqas Hussain 9 | 10 | local host = module:get_host(); 11 | local jid_prep = require "util.jid".prep; 12 | 13 | local registration_watchers = module:get_option_set("registration_watchers", module:get_option("admins", {})) / jid_prep; 14 | local registration_notification = module:get_option("registration_notification", "User $username just registered on $host from $ip"); 15 | 16 | local st = require "util.stanza"; 17 | 18 | module:hook("user-registered", function (user) 19 | module:log("debug", "Notifying of new registration"); 20 | local message = st.message{ type = "chat", from = host } 21 | :tag("body") 22 | :text(registration_notification:gsub("%$(%w+)", function (v) 23 | return user[v] or user.session and user.session[v] or nil; 24 | end)); 25 | for jid in registration_watchers do 26 | module:log("debug", "Notifying %s", jid); 27 | message.attr.to = jid; 28 | module:send(message); 29 | end 30 | end); 31 | -------------------------------------------------------------------------------- /plugins/muc_log_http/generate_log: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- * Metronome IM * 3 | -- 4 | -- This file is part of the Metronome XMPP server and is released under the 5 | -- ISC License, please see the LICENSE file in this source package for more 6 | -- information about copyright and licensing. 7 | 8 | -- This script generates log pages for mod_muc_log_http 9 | 10 | src_path = arg[1]; 11 | data_path = arg[2]; 12 | theme_path = arg[3]; 13 | muc_jid = arg[4]; 14 | date = arg[5]; 15 | s = arg[6]; 16 | 17 | metronome = { platform = "posix", serialization = s or "internal" }; 18 | package.path = src_path.."/?.lua;"..package.path; 19 | package.cpath = src_path.."/?.so;"..package.cpath; 20 | html_escape = require "util.auxiliary".html_escape; 21 | datamanager = require "util.datamanager"; 22 | datamanager.set_data_path(data_path); 23 | lfs = require "lfs"; 24 | node, host = require "util.jid".split(muc_jid); 25 | html = {}; 26 | 27 | function read_file(filepath) 28 | local f,err = io.open(filepath, "r"); 29 | if not f then return f,err; end 30 | local t = f:read("*all"); 31 | f:close() 32 | return t; 33 | end 34 | function load_theme(path) 35 | for file in lfs.dir(path) do 36 | if file:match("%.html$") then 37 | local content,err = read_file(path .. "/" .. file); 38 | if not content then return content,err; end 39 | local tmp = html; 40 | for idx in file:gmatch("([^_]*)_") do 41 | tmp[idx] = tmp[idx] or {}; 42 | tmp = tmp[idx]; 43 | end 44 | tmp[file:match("([^_]*)%.html$")] = content; 45 | end 46 | end 47 | return true; 48 | end 49 | 50 | if not load_theme(theme_path) then 51 | print("error loading theme"); 52 | os.exit(1); 53 | end 54 | 55 | function parse_message(body, title, time, nick, day_t, day_m, day_mm, day_title) 56 | if not nick then return; end 57 | local ret = ""; 58 | time = day_t:gsub("###TIME###", time):gsub("###UTC###", time); 59 | nick = html_escape(nick:match("/(.+)$")); 60 | 61 | if nick and body then 62 | body = html_escape(body); 63 | if body:find("^/me") then body = body:gsub("^/me ", ""); day_m = nil; end 64 | ret = (day_m or day_mm):gsub("###TIME_STUFF###", time):gsub("###NICK###", nick):gsub("###MSG###", body); 65 | elseif nick and title then 66 | title = html_escape(title); 67 | ret = day_title:gsub("###TIME_STUFF###", time):gsub("###NICK###", nick):gsub("###TITLE###", title); 68 | end 69 | return ret; 70 | end 71 | 72 | ret = ""; 73 | 74 | html_day = html.day; 75 | day_t, day_m, day_mm, day_title = html_day.time, html_day.message, html_day.messageMe, html_day.titleChange; 76 | data = datamanager.load(node, host, "stanza_log" .. "/" .. date); 77 | if not data then 78 | ret = ""; 79 | elseif data and #data <= 5000 then 80 | for i, entry in ipairs(data) do 81 | local label, actions, dont_add = entry.label_name, entry.label_actions; 82 | if os.date("!%Y%m%d", entry.timestamp) ~= date then -- Sanitize 4.0 dev artefacts 83 | dont_add = true; 84 | end 85 | if label and (not actions or actions == "none" or (type(actions) == "table" and actions.type == "groupchat")) then 86 | dont_add = true; 87 | end 88 | if not dont_add then 89 | local body; 90 | if not entry.body and entry.html then 91 | body = "[This is XHTML-IM message without a plain body variant, can't show it]"; 92 | else 93 | body = entry.body; 94 | end 95 | local tmp = parse_message(body, entry.subject, entry.time, entry.from, day_t, day_m, day_mm, day_title); 96 | if tmp then ret = ret .. tmp; end 97 | end 98 | end 99 | else 100 | ret = "The log for this day is far too large, please contact the Server Administrator to obtain it.
        "; 101 | end 102 | 103 | os.exit(print(ret)); 104 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/components_bit.html: -------------------------------------------------------------------------------- 1 | ###COMPONENT###
        -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/day_body.html: -------------------------------------------------------------------------------- 1 |
        2 |
        ###DATE###
        3 |
        ###JID###
        4 | 5 |
        6 |
        7 | 10 | ###CALENDAR### 11 |
        12 |
        ###TITLE_STUFF###
        13 |
        14 | 15 |
        ###DAY_STUFF###
        16 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/day_dayLink.html: -------------------------------------------------------------------------------- 1 | ###TEXT### 2 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/day_message.html: -------------------------------------------------------------------------------- 1 | ###TIME_STUFF###<###NICK###> ###MSG###
        -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/day_messageMe.html: -------------------------------------------------------------------------------- 1 | ###TIME_STUFF###*###NICK### ###MSG###
        -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/day_time.html: -------------------------------------------------------------------------------- 1 | [###TIME###] 2 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/day_titleChange.html: -------------------------------------------------------------------------------- 1 | ###TIME_STUFF### *** ###NICK### changed the title to "###TITLE###"
        -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/days_bit.html: -------------------------------------------------------------------------------- 1 | ###DAY###
        -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/days_body.html: -------------------------------------------------------------------------------- 1 |
        2 |
        ###SINCE### - ###TO###
        3 |
        ###JID###
        4 | 5 |
        6 |
        7 | 12 |
        Room logs for this component
        13 |
        ###ROOMS###
        14 |
        15 |
        ###ROOMTOPIC###
        16 |
        17 | ###DAYS_STUFF### 18 |
        19 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/days_rooms_bit.html: -------------------------------------------------------------------------------- 1 | ###ROOM###
        2 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/doc.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | muc_log 5 | 6 | 7 | 61 | 104 | 105 | ###BODY_STUFF### 106 | 107 | 108 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/month_day.html: -------------------------------------------------------------------------------- 1 | ###DAY### -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/month_emptyDay.html: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/month_footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/month_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ###WEEKDAYS### 5 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/month_weekDay.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/rooms_bit.html: -------------------------------------------------------------------------------- 1 | ###ROOM###
        2 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/rooms_body.html: -------------------------------------------------------------------------------- 1 |

        Available room logs on ###COMPONENT###:


        2 | ###ROOMS_STUFF### 3 |

        4 | -------------------------------------------------------------------------------- /plugins/muc_log_http/themes/metronome/year_title.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /plugins/register_api/send_mail: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- * Metronome IM * 3 | -- 4 | -- This file is part of the Metronome XMPP server and is released under the 5 | -- ISC License, please see the LICENSE file in this source package for more 6 | -- information about copyright and licensing. 7 | 8 | -- This script sends mail from the Register API 9 | 10 | request_type = arg[1]; 11 | mail_from = arg[2]; 12 | mail_to = arg[3]; 13 | reply_to = arg[4]; 14 | new_jid = arg[5]; 15 | url = arg[6]; 16 | token = arg[7]; 17 | secure = arg[8]; 18 | 19 | if secure == "" then secure = nil; end 20 | 21 | CFG_SOURCEDIR=os.getenv("METRONOME_SRCDIR"); 22 | CFG_CONFIGDIR=os.getenv("METRONOME_CFGDIR"); 23 | 24 | metronome = { events = {}, platform = "posix", serialization = "internal" }; 25 | package.path = CFG_SOURCEDIR.."/?.lua;"..package.path; 26 | package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath; 27 | config = require "core.configmanager"; 28 | html_escape = require "util.auxiliary".html_escape; 29 | section = require "util.jid".section; 30 | send = require "util.mail".send; 31 | 32 | function read_file(filepath) 33 | local f, err = io.open(filepath, "r"); 34 | if not f then return f, err; end 35 | local t = f:read("*all"); 36 | f:close() 37 | return t; 38 | end 39 | 40 | config.load(CFG_CONFIGDIR .. "/metronome.cfg.lua"); -- FIXME should be format/file agnostical 41 | 42 | local template = read_file(CFG_CONFIGDIR .. "/templates/" .. request_type .. ".template.txt"); 43 | if template then 44 | if request_type == "associate" then template = template:gsub("%%JID%%", new_jid); end 45 | 46 | template = template:gsub("%%URL%%", url):gsub("%%TOKEN%%", token); 47 | template = html_escape(template); 48 | 49 | local subject = 50 | (request_type == "register" and "Account registration verification for: ") or 51 | (request_type == "associate" and "Account mail change verification for: ") or 52 | "Account password reset for: "; 53 | 54 | local user, pass, host, port = 55 | config.get("*", "mail_user"), config.get("*", "mail_pass"), 56 | config.get("*", "mail_host"), config.get("*", "mail_port"); 57 | 58 | send(mail_from, mail_to, reply_to, subject..new_jid, template, 59 | { user = user, password = pass, host = host, port = port, helo = section(new_jid, "host") }, secure and true); 60 | end 61 | 62 | os.exit(1); 63 | -------------------------------------------------------------------------------- /plugins/register_api/template/associate_fail_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Associated Mail Verification 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        The Mail token you provided either expired, is invalid or verification failed. 21 |
        22 |
        Please keep in mind that the tokens have a max validity of five minutes after 23 |
        which they will expire and you will have to repeat the verification process. 24 |
        25 |
        It's also possible that additional checks were performed on the mail address 26 |
        and your request was therefore invalidated.

        27 |
        28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /plugins/register_api/template/associate_form_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Associated Mail Verification 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        Please input below the identification string you received by e-mail, to verify your 21 |
        associated mail address change.

        22 |
        23 |
        24 |
        25 |

        26 | 27 |
        28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /plugins/register_api/template/associate_success_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Associated Mail Verification 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        The mail address is successfully associated to the account.

        21 |
        22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /plugins/register_api/template/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0 3 | } 4 | 5 | a { 6 | color: #0000FF 7 | } 8 | 9 | ul { 10 | margin: 0 11 | } 12 | 13 | .btn { 14 | margin-right: 0.3em 15 | } 16 | 17 | .btn:last { 18 | margin-right: 0 19 | } 20 | 21 | #main { 22 | padding: 10px 23 | } 24 | 25 | #main div { 26 | margin-left: 14px 27 | } 28 | 29 | #main p { 30 | margin-left: 14px 31 | } 32 | 33 | #top { 34 | clear: both; 35 | width: 100%; 36 | padding: 0; 37 | } 38 | 39 | #header { 40 | height: 172px; 41 | background: url(../images/tile.png) repeat-x 42 | } 43 | -------------------------------------------------------------------------------- /plugins/register_api/template/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/register_api/template/images/header.png -------------------------------------------------------------------------------- /plugins/register_api/template/images/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/register_api/template/images/tile.png -------------------------------------------------------------------------------- /plugins/register_api/template/reset_fail_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Password Reset 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        The Password Reset token is either invalid or expired, please keep in mind that 21 |
        all tokens have a max validity of five minutes after which they will expire 22 |
        and you will have to repeat the password reset process.

        23 |
        24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /plugins/register_api/template/reset_form_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Password Reset 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        Please fill the form below to reset your xmpp account password.

        21 |
        22 |
        23 |
        24 |
        25 |
        26 |
        27 |
        28 |

        29 | 30 |
        31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /plugins/register_api/template/reset_nomatch_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Password Reset 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        Supplied Passwords don't match, please retry.

        21 |
        22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /plugins/register_api/template/reset_password_check_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Password Reset 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        Passwords needs to be at least %MIN_LEN% characters long, not exceed %MAX_LEN% 21 |
        and contain at least one uppercase letter and one digit or symbol. 22 |
        Passwords must also conform to the SASLprep profile (RFC 4013).

        23 |
        24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /plugins/register_api/template/reset_success_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Password Reset 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        Your account password has been successfully reset.

        21 |
        22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /plugins/register_api/template/verify_fail_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Registration Verification 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        The Account token you provided either expired, is invalid or verification failed. 21 |
        22 |
        Please keep in mind that the tokens have a max validity of five minutes after 23 |
        which they will expire and you will have to repeat the registration process. 24 |
        25 |
        It's also possible that additional checks were performed on your mail address 26 |
        and your registration request was therefore invalidated.

        27 |
        28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /plugins/register_api/template/verify_form_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Registration Verification 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        Please input below the identification string you received by e-mail, to complete your 21 |
        xmpp account registration.

        22 |
        23 |
        24 |
        25 |

        26 | 27 |
        28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /plugins/register_api/template/verify_success_t.html: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 6 | Metronome User Registration Verification 7 | 8 | 9 | 10 | 11 | 12 | 13 |
        14 | 17 |
        18 | 19 |
        20 |

        Registration verified, account activated.

        21 |
        22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /plugins/spim_block/template/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0 3 | } 4 | 5 | a { 6 | color: #0000FF 7 | } 8 | 9 | ul { 10 | margin: 0 11 | } 12 | 13 | .btn { 14 | margin-right: 0.3em 15 | } 16 | 17 | .btn:last { 18 | margin-right: 0 19 | } 20 | 21 | #main { 22 | padding: 10px 23 | } 24 | 25 | #main div { 26 | margin-left: 14px 27 | } 28 | 29 | #main p { 30 | margin-left: 14px 31 | } 32 | 33 | #top { 34 | clear: both; 35 | width: 100%; 36 | padding: 0; 37 | } 38 | 39 | #header { 40 | height: 172px; 41 | background: url(../images/tile.png) repeat-x 42 | } 43 | 44 | #recaptcha { 45 | margin: 0px; 46 | width: 304px; 47 | position: relative; 48 | } 49 | 50 | #recaptcha iframe { 51 | position: absolute; 52 | left: 0px; 53 | top: 0px; 54 | } 55 | -------------------------------------------------------------------------------- /plugins/spim_block/template/fail.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Metronome SPIM Block 5 | 6 | 7 | 8 | 9 | 10 | 11 |
        12 | 15 |
        16 | 17 |
        18 |

        The Anti-SPIM token you inserted doesn't exist, or the reCAPTCHA input is missing.

        19 |
        20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /plugins/spim_block/template/form.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Metronome SPIM Block 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 |
        19 | 22 |
        23 | 24 |
        25 |

        26 | Please insert the provided token to unlock communication with a user on this server, 27 |
        note that this form is protected by reCAPTCHA meaning, that your IP address will be 28 |
        logged by our systems and sent to Google Servers to better help identifying bots from 29 |
        true users. Should you not agree with that you can always ask the contact through other 30 |
        means to add you to his or her contact list instead. 31 |

        32 |
        33 |

        34 |
        35 |

        36 |
        37 |

        38 | 39 |
        40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /plugins/spim_block/template/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/spim_block/template/images/header.png -------------------------------------------------------------------------------- /plugins/spim_block/template/images/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maranda/metronome/c4023efdad80756205f849c9c50e5aa73c211150/plugins/spim_block/template/images/tile.png -------------------------------------------------------------------------------- /plugins/spim_block/template/verify.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Metronome SPIM Block 5 | 6 | 7 | 8 | 9 | 10 | 11 |
        12 | 15 |
        16 | 17 |
        18 |

        Verifying your request to message %USER%, if successful you will be messaged in-band.

        19 |
        20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /scripts/convert_flat_files: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- * Metronome IM * 3 | -- 4 | -- This file is part of the Metronome XMPP server and is released under the 5 | -- ISC License, please see the LICENSE file in this source package for more 6 | -- information about copyright and licensing. 7 | 8 | -- This script generates log pages for mod_muc_log_http 9 | 10 | src_path = arg[1]; 11 | data_path = arg[2]; 12 | s = arg[3]; 13 | 14 | if s ~= "from-internal" and s ~= "from-json" then 15 | print("Invalid syntax please use:") 16 | print(" convert_flat_files "); 17 | print(" (Note: all paths should not have a final trailing slash)"); 18 | os.exit(1); 19 | end 20 | 21 | metronome = { platform = "posix", serialization = (s == "from-internal" and "internal") or "json" }; 22 | package.path = src_path.."/?.lua;"..package.path; 23 | package.cpath = src_path.."/?.so;"..package.cpath; 24 | datamanager = require "util.datamanager"; 25 | datamanager.set_data_path(data_path); 26 | 27 | local converted = {}; 28 | 29 | str = io.popen("find "..data_path.." -type f"); 30 | str = str:read("*all"); 31 | for i in str:gmatch("[%w%p]+[^\n]") do 32 | local path = datamanager.path_decode(i); 33 | print("Loading: "..path); 34 | local host, datastore, node = path:match("^"..data_path.."/(.*)/(.*)/(.*)%.dat"); 35 | if not host then 36 | host, datastore = path:match("^"..data_path.."/(.*)/(.*)%.dat"); 37 | end 38 | local data = datamanager.load(node, host, datastore); 39 | if not node then 40 | converted["nil|"..host.."|"..datastore] = data; 41 | else 42 | converted[node.."|"..host.."|"..datastore] = data; 43 | end 44 | end 45 | 46 | metronome.serialization = (s == "from-internal" and "json") or "internal"; 47 | 48 | datamanager = nil; package.loaded.datamanager = nil; package.loaded["util.datamanager"] = nil; 49 | datamanager = require "util.datamanager"; 50 | datamanager.set_data_path(data_path); 51 | 52 | for i, data in pairs(converted) do 53 | local node, host, datastore = i:match("^(.*)|(.*)|(.*)$"); 54 | print("Coverting: "..node..", "..host..", "..datastore); 55 | if node == "nil" then 56 | datamanager.store(nil, host, datastore, data); 57 | else 58 | datamanager.store(node, host, datastore, data); 59 | end 60 | end 61 | 62 | print("Done."); 63 | os.exit(); 64 | -------------------------------------------------------------------------------- /scripts/logrotate: -------------------------------------------------------------------------------- 1 | /var/log/metronome/metronome.log /var/log/metronome/metronome.err { 2 | daily 3 | rotate 14 4 | compress 5 | delaycompress 6 | create 640 metronome adm 7 | postrotate 8 | /usr/bin/metronomectl reload > /dev/null 9 | endscript 10 | sharedscripts 11 | missingok 12 | } 13 | -------------------------------------------------------------------------------- /templates/associate.template.txt: -------------------------------------------------------------------------------- 1 | This is an automated message from the account system, to associate this mail address with %JID%'s xmpp account please visit %URL% within 5 minutes. 2 | And input (copy and paste) the following token in the presented web form: %TOKEN% 3 | 4 | Regards. 5 | 6 | If you think you're not the recipient of this message or you received this by mistake please delete the message and possibly report the incident to us. -------------------------------------------------------------------------------- /templates/register.template.txt: -------------------------------------------------------------------------------- 1 | This is an automated message from the registration system, to activate your account please visit %URL% within 5 minutes. 2 | And input (copy and paste) the following token in the presented web form: %TOKEN% 3 | 4 | Regards. 5 | 6 | If you think you're not the recipient of this message or you received this by mistake please delete the message and possibly report the incident to us. -------------------------------------------------------------------------------- /templates/reset.template.txt: -------------------------------------------------------------------------------- 1 | This is an automated message from the account system, to reset your account password please visit %URL% within 5 minutes. 2 | And fill in the form with the following token (copy and paste it): %TOKEN% 3 | 4 | Regards. 5 | 6 | If you think you're not the recipient of this message or you received this by mistake please delete the message and possibly report the incident to us (reply to this e-mail). -------------------------------------------------------------------------------- /util-src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | include ../config.unix 3 | 4 | .PHONY: all install clean 5 | .SUFFIXES: .c .o .so 6 | 7 | all: encodings.so hashes.so pposix.so signal.so 8 | 9 | install: encodings.so hashes.so pposix.so signal.so 10 | install *.so ../util/ 11 | 12 | clean: 13 | rm -f *.o 14 | rm -f *.so 15 | rm -f ../util/*.so 16 | 17 | encodings.so: encodings.o 18 | MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET; 19 | $(CC) -o $@ $< $(LDFLAGS) $(IDNA_LIBS) 20 | 21 | hashes.so: hashes.o 22 | MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET; 23 | $(CC) -o $@ $< $(LDFLAGS) -l$(OPENSSL_LIB) 24 | 25 | .c.o: 26 | $(CC) $(CFLAGS) -I$(LUA_INCDIR) -c -o $@ $< 27 | 28 | .o.so: 29 | MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET; 30 | $(LD) -o $@ $< $(LDFLAGS) 31 | 32 | -------------------------------------------------------------------------------- /util-src/hashes.c: -------------------------------------------------------------------------------- 1 | /* 2 | -- * Metronome IM * 3 | -- 4 | -- This file is part of the Metronome XMPP server and is released under the 5 | -- ISC License, please see the LICENSE file in this source package for more 6 | -- information about copyright and licensing. 7 | -- 8 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 9 | -- ** Copyright (c) 2008-2012, Kim Alvefur, Matthew Wild, Tobias Markmann 10 | */ 11 | 12 | /* 13 | * hashes.c 14 | * Lua library for sha1, sha256, sha384, sha512 and md5 hashes 15 | */ 16 | 17 | #include 18 | 19 | #include "lua.h" 20 | #include "lauxlib.h" 21 | #include 22 | #include 23 | 24 | #if (LUA_VERSION_NUM == 501) 25 | #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) 26 | #endif 27 | 28 | const char* hex_tab = "0123456789abcdef"; 29 | void toHex(const char* in, int length, char* out) { 30 | int i; 31 | for (i = 0; i < length; i++) { 32 | out[i*2] = hex_tab[(in[i] >> 4) & 0xF]; 33 | out[i*2+1] = hex_tab[(in[i]) & 0xF]; 34 | } 35 | } 36 | 37 | #define MAKE_HASH_FUNCTION(myFunc, func, size) \ 38 | static int myFunc(lua_State *L) { \ 39 | size_t len; \ 40 | const char *s = luaL_checklstring(L, 1, &len); \ 41 | int hex_out = lua_toboolean(L, 2); \ 42 | char hash[size]; \ 43 | char result[size*2]; \ 44 | func((const unsigned char*)s, len, (unsigned char*)hash); \ 45 | if (hex_out) { \ 46 | toHex(hash, size, result); \ 47 | lua_pushlstring(L, result, size*2); \ 48 | } else { \ 49 | lua_pushlstring(L, hash, size);\ 50 | } \ 51 | return 1; \ 52 | } 53 | 54 | MAKE_HASH_FUNCTION(Lsha1, SHA1, SHA_DIGEST_LENGTH) 55 | MAKE_HASH_FUNCTION(Lsha224, SHA224, SHA224_DIGEST_LENGTH) 56 | MAKE_HASH_FUNCTION(Lsha256, SHA256, SHA256_DIGEST_LENGTH) 57 | MAKE_HASH_FUNCTION(Lsha384, SHA384, SHA384_DIGEST_LENGTH) 58 | MAKE_HASH_FUNCTION(Lsha512, SHA512, SHA512_DIGEST_LENGTH) 59 | MAKE_HASH_FUNCTION(Lmd5, MD5, MD5_DIGEST_LENGTH) 60 | 61 | static const luaL_Reg Reg[] = 62 | { 63 | { "sha1", Lsha1 }, 64 | { "sha224", Lsha224 }, 65 | { "sha256", Lsha256 }, 66 | { "sha384", Lsha384 }, 67 | { "sha512", Lsha512 }, 68 | { "md5", Lmd5 }, 69 | { NULL, NULL } 70 | }; 71 | 72 | LUALIB_API int luaopen_util_hashes(lua_State *L) 73 | { 74 | #if (LUA_VERSION_NUM > 501) 75 | luaL_checkversion(L); 76 | #endif 77 | lua_newtable(L); 78 | luaL_setfuncs(L, Reg, 0); 79 | lua_pushliteral(L, "version"); /** version */ 80 | lua_pushliteral(L, "-3.14"); 81 | lua_settable(L,-3); 82 | return 1; 83 | } 84 | -------------------------------------------------------------------------------- /util-src/signal.c.license: -------------------------------------------------------------------------------- 1 | This license file is valid for: 2 | ./signal.c 3 | 4 | * 5 | * signal.c -- Signal Handler Library for Lua 6 | * 7 | * Version: 1.000+changes 8 | * 9 | * Copyright (C) 2007 Patrick J. Donnelly (batrick@batbytes.com) 10 | * 11 | * This software is distributed under the same license as Lua 5.0: 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a 14 | * copy of this software and associated documentation files (the "Software"), 15 | * to deal in the Software without restriction, including without limitation 16 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 17 | * and/or sell copies of the Software, and to permit persons to whom the 18 | * Software is furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included 21 | * in all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 24 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 26 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 27 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 28 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | * OTHER DEALINGS IN THE SOFTWARE. 30 | * 31 | -------------------------------------------------------------------------------- /util/address_selection.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2013, Florian Zeitz (rfc6724.lua) 9 | 10 | local match_prefix = require "util.ip".match_prefix; 11 | local compare_destination = require "util.ip".compare_destination; 12 | local compare_source = require "util.ip".compare_source; 13 | 14 | local ipairs = ipairs; 15 | 16 | local _ENV = nil; 17 | 18 | local function t_sort(t, comp, param) 19 | for i = 1, (#t - 1) do 20 | for j = (i + 1), #t do 21 | local a, b = t[i], t[j]; 22 | if not comp(a, b, param) then 23 | t[i], t[j] = b, a; 24 | end 25 | end 26 | end 27 | end 28 | 29 | local function source(dest, candidates) 30 | t_sort(candidates, compare_source, dest); 31 | return candidates[1]; 32 | end 33 | 34 | local function destination(candidates, sources) 35 | local sourceAddrs = {}; 36 | for _, ip in ipairs(candidates) do 37 | sourceAddrs[ip] = source(ip, sources); 38 | end 39 | 40 | t_sort(candidates, compare_destination, sourceAddrs); 41 | return candidates; 42 | end 43 | 44 | return { source = source, destination = destination }; -------------------------------------------------------------------------------- /util/auxiliary.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | -- This contains the auxiliary utility functions for Metronome's env. 8 | 9 | local CFG_SOURCEDIR, metronome = _G.CFG_SOURCEDIR, _G.metronome; 10 | local open, popen = io.open, io.popen; 11 | local base64 = require "util.encodings".base64.encode; 12 | local char, next, os_time, pairs, tonumber, tostring, type = string.char, next, os.time, pairs, tonumber, tostring, type; 13 | 14 | local _ENV, _M = nil, {}; 15 | local clone_table, clean_table, generate_secret; 16 | 17 | function _M.read_version() 18 | local version_file = open((CFG_SOURCEDIR or ".").."/metronome.version"); 19 | if version_file then 20 | metronome.version = version_file:read("*a"):gsub("%s*$", ""); 21 | version_file:close(); 22 | else 23 | metronome.version = "unknown"; 24 | end 25 | end 26 | 27 | function _M.get_openssl_version() 28 | -- will possibly work only on linux likes which have a globally installed 29 | -- openssl. 30 | local version = popen("openssl version"):read(); 31 | if version then 32 | version = version:match("^OpenSSL%s([%d%p]+)"):gsub("%p", ""); 33 | return tonumber(version); 34 | else 35 | return false; 36 | end 37 | end 38 | 39 | function _M.ripairs(t) 40 | local function reverse(t,index) 41 | index = index-1; 42 | local value = t[index]; 43 | if value == nil then return value; end 44 | return index, value; 45 | end 46 | return reverse, t, #t+1; 47 | end 48 | 49 | function clone_table(t) 50 | local clone = {}; 51 | for key, value in pairs(t) do 52 | if type(value) == "table" then 53 | clone[key] = clone_table(value); 54 | else 55 | clone[key] = value; 56 | end 57 | end 58 | return clone; 59 | end 60 | 61 | function clean_table(t) 62 | for key, value in pairs(t) do 63 | if type(value) == "table" and not next(value) then 64 | t[key] = nil; 65 | elseif type(value) == "table" then 66 | clean_table(value); 67 | if not next(value) then t[key] = nil; end 68 | end 69 | end 70 | end 71 | 72 | function _M.escape_magic_chars(string) 73 | -- escape magic characters 74 | string = string:gsub("%(", "%%(") 75 | string = string:gsub("%)", "%%)") 76 | string = string:gsub("%.", "%%.") 77 | string = string:gsub("%%", "%%") 78 | string = string:gsub("%+", "%%+") 79 | string = string:gsub("%-", "%%-") 80 | string = string:gsub("%*", "%%*") 81 | string = string:gsub("%?", "%%?") 82 | string = string:gsub("%[", "%%[") 83 | string = string:gsub("%]", "%%]") 84 | string = string:gsub("%^", "%%^") 85 | string = string:gsub("%$", "%%$") 86 | 87 | return string 88 | end 89 | 90 | function _M.html_escape(t) 91 | if t then 92 | t = t:gsub("<", "<"); 93 | t = t:gsub(">", ">"); 94 | t = t:gsub("(http://[%a%d@%.:/&%?=%-_#%%~]+)", function(h) 95 | h = h:gsub("+", " "); 96 | h = h:gsub("%%(%x%x)", function(h) return char(tonumber(h,16)) end); 97 | h = h:gsub("\r\n", "\n"); 98 | return "" .. h .. ""; 99 | end); 100 | t = t:gsub("\n", "
        "); 101 | t = t:gsub("%%", "%%%%"); 102 | else 103 | t = ""; 104 | end 105 | return t; 106 | end 107 | 108 | function _M.load_file(f, mode) 109 | local file, err, ret = open(f, mode or "r"); 110 | if file then 111 | ret = file:read("*a"); 112 | file:close(); 113 | end 114 | return ret, err; 115 | end 116 | 117 | function generate_secret(bytes) 118 | local n, urandom = 0; 119 | repeat 120 | local f = open("/dev/urandom", "r"); 121 | if f then 122 | urandom = f:read(bytes or 256); 123 | f:close(); 124 | end 125 | n = n + 1; 126 | until urandom ~= nil or n == 30; 127 | 128 | return (urandom and base64(urandom)) or nil; 129 | end 130 | 131 | function _M.generate_shortid() 132 | local bits = generate_secret(9); 133 | return bits and bits:gsub("/", ""):gsub("%+", "") .. tostring(os_time()):match("%d%d%d%d$"); 134 | end 135 | 136 | _M.clone_table = clone_table; 137 | _M.clean_table = clean_table; 138 | _M.generate_secret = generate_secret; 139 | return _M; 140 | -------------------------------------------------------------------------------- /util/caps.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2010, Matthew Wild 9 | 10 | local base64 = require "util.encodings".base64.encode; 11 | local sha1 = require "util.hashes".sha1; 12 | 13 | local t_insert, t_sort, t_concat = table.insert, table.sort, table.concat; 14 | local ipairs = ipairs; 15 | 16 | local _ENV = nil; 17 | 18 | local function calculate_hash(disco_info) 19 | local identities, features, extensions = {}, {}, {}; 20 | for _, tag in ipairs(disco_info) do 21 | if tag.name == "identity" then 22 | t_insert(identities, (tag.attr.category or "").."\0"..(tag.attr.type or "").."\0"..(tag.attr["xml:lang"] or "").."\0"..(tag.attr.name or "")); 23 | elseif tag.name == "feature" then 24 | t_insert(features, tag.attr.var or ""); 25 | elseif tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then 26 | local form = {}; 27 | local FORM_TYPE; 28 | for _, field in ipairs(tag.tags) do 29 | if field.name == "field" and field.attr.var then 30 | local values = {}; 31 | for _, val in ipairs(field.tags) do 32 | val = #val.tags == 0 and val:get_text(); 33 | if val then t_insert(values, val); end 34 | end 35 | t_sort(values); 36 | if field.attr.var == "FORM_TYPE" then 37 | FORM_TYPE = values[1]; 38 | elseif #values > 0 then 39 | t_insert(form, field.attr.var.."\0"..t_concat(values, "<")); 40 | else 41 | t_insert(form, field.attr.var); 42 | end 43 | end 44 | end 45 | t_sort(form); 46 | form = t_concat(form, "<"); 47 | if FORM_TYPE then form = FORM_TYPE.."\0"..form; end 48 | t_insert(extensions, form); 49 | end 50 | end 51 | t_sort(identities); 52 | t_sort(features); 53 | t_sort(extensions); 54 | if #identities > 0 then identities = t_concat(identities, "<"):gsub("%z", "/").."<"; else identities = ""; end 55 | if #features > 0 then features = t_concat(features, "<").."<"; else features = ""; end 56 | if #extensions > 0 then extensions = t_concat(extensions, "<"):gsub("%z", "<").."<"; else extensions = ""; end 57 | local S = identities..features..extensions; 58 | local ver = base64(sha1(S)); 59 | return ver, S; 60 | end 61 | 62 | return { calculate_hash = calculate_hash }; 63 | -------------------------------------------------------------------------------- /util/datetime.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2013, Matthew Wild, Waqas Hussain 9 | 10 | -- XEP-0082: XMPP Date and Time Profiles 11 | 12 | local os_date = os.date; 13 | local os_time = os.time; 14 | local os_difftime = os.difftime; 15 | local error = error; 16 | local tonumber = tonumber; 17 | 18 | local _ENV = nil; 19 | local _dt = {}; 20 | 21 | function _dt.date(t) 22 | return os_date("!%Y-%m-%d", t); 23 | end 24 | 25 | function _dt.datetime(t) 26 | return os_date("!%Y-%m-%dT%H:%M:%SZ", t); 27 | end 28 | 29 | function _dt.time(t) 30 | return os_date("!%H:%M:%S", t); 31 | end 32 | 33 | function _dt.legacy(t) 34 | return os_date("!%Y%m%dT%H:%M:%S", t); 35 | end 36 | 37 | function _dt.parse(s) 38 | if s then 39 | local year, month, day, hour, min, sec, tzd; 40 | 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+%-].*)$"); 41 | if year then 42 | local time_offset = os_difftime(os_time(os_date("*t")), os_time(os_date("!*t"))); -- to deal with local timezone 43 | local tzd_offset = 0; 44 | if tzd ~= "" and tzd ~= "Z" then 45 | local sign, h, m = tzd:match("([+%-])(%d%d):?(%d*)"); 46 | if not sign then return; end 47 | if #m ~= 2 then m = "0"; end 48 | h, m = tonumber(h), tonumber(m); 49 | tzd_offset = h * 60 * 60 + m * 60; 50 | if sign == "-" then tzd_offset = -tzd_offset; end 51 | end 52 | sec = (sec + time_offset) - tzd_offset; 53 | return os_time({year=year, month=month, day=day, hour=hour, min=min, sec=sec, isdst=false}); 54 | end 55 | end 56 | end 57 | 58 | return _dt; 59 | -------------------------------------------------------------------------------- /util/envload.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2012, Florian Zeitz 9 | 10 | local load, loadstring, loadfile, setfenv = load, loadstring, loadfile, setfenv; 11 | local envload; 12 | local envloadfile; 13 | 14 | if setfenv then 15 | function envload(code, source, env) 16 | local f, err = loadstring(code, source); 17 | if f and env then setfenv(f, env); end 18 | return f, err; 19 | end 20 | 21 | function envloadfile(file, env) 22 | local f, err = loadfile(file); 23 | if f and env then setfenv(f, env); end 24 | return f, err; 25 | end 26 | else 27 | function envload(code, source, env) 28 | return load(code, source, nil, env); 29 | end 30 | 31 | function envloadfile(file, env) 32 | return loadfile(file, nil, env); 33 | end 34 | end 35 | 36 | return { envload = envload, envloadfile = envloadfile }; 37 | -------------------------------------------------------------------------------- /util/events.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2010, Matthew Wild, Waqas Hussain 9 | 10 | local pairs = pairs; 11 | local t_insert = table.insert; 12 | local t_sort = table.sort; 13 | local setmetatable = setmetatable; 14 | local next = next; 15 | 16 | local _ENV = nil; 17 | 18 | local function new() 19 | local handlers = {}; 20 | local event_map = {}; 21 | local function _rebuild_index(handlers, event) 22 | local _handlers = event_map[event]; 23 | if not _handlers or next(_handlers) == nil then return; end 24 | local index = {}; 25 | for handler in pairs(_handlers) do 26 | t_insert(index, handler); 27 | end 28 | t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end); 29 | handlers[event] = index; 30 | return index; 31 | end; 32 | setmetatable(handlers, { __index = _rebuild_index }); 33 | local function add_handler(event, handler, priority) 34 | local map = event_map[event]; 35 | if map then 36 | map[handler] = priority or 0; 37 | else 38 | map = {[handler] = priority or 0}; 39 | event_map[event] = map; 40 | end 41 | handlers[event] = nil; 42 | end; 43 | local function remove_handler(event, handler) 44 | local map = event_map[event]; 45 | if map then 46 | map[handler] = nil; 47 | handlers[event] = nil; 48 | if next(map) == nil then 49 | event_map[event] = nil; 50 | end 51 | end 52 | end; 53 | local function add_handlers(handlers) 54 | for event, handler in pairs(handlers) do 55 | add_handler(event, handler); 56 | end 57 | end; 58 | local function remove_handlers(handlers) 59 | for event, handler in pairs(handlers) do 60 | remove_handler(event, handler); 61 | end 62 | end; 63 | local function fire_event(event, ...) 64 | local h = handlers[event]; 65 | if h then 66 | for i=1,#h do 67 | local ret = h[i](...); 68 | if ret ~= nil then return ret; end 69 | end 70 | end 71 | end; 72 | return { 73 | add_handler = add_handler; 74 | remove_handler = remove_handler; 75 | add_handlers = add_handlers; 76 | remove_handlers = remove_handlers; 77 | fire_event = fire_event; 78 | _handlers = handlers; 79 | _event_map = event_map; 80 | }; 81 | end 82 | 83 | return { new = new }; 84 | 85 | -------------------------------------------------------------------------------- /util/filters.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2010, Matthew Wild 9 | 10 | local t_insert, t_remove = table.insert, table.remove; 11 | 12 | local _ENV, _M = nil, {}; 13 | 14 | local new_filter_hooks = {}; 15 | 16 | local function initialize(session) 17 | if not session.filters then 18 | local filters = {}; 19 | session.filters = filters; 20 | 21 | function session.filter(type, data) 22 | local filter_list = filters[type]; 23 | if filter_list then 24 | for i = 1, #filter_list do 25 | data = filter_list[i](data, session); 26 | if data == nil then break; end 27 | end 28 | end 29 | return data; 30 | end 31 | end 32 | 33 | for i=1,#new_filter_hooks do 34 | new_filter_hooks[i](session); 35 | end 36 | 37 | return session.filter; 38 | end 39 | 40 | function _M.add_filter(session, type, callback, priority) 41 | if not session.filters then 42 | initialize(session); 43 | end 44 | 45 | local filter_list = session.filters[type]; 46 | if not filter_list then 47 | filter_list = {}; 48 | session.filters[type] = filter_list; 49 | end 50 | 51 | priority = priority or 0; 52 | 53 | local i = 0; 54 | repeat 55 | i = i + 1; 56 | until not filter_list[i] or filter_list[filter_list[i]] >= priority; 57 | 58 | t_insert(filter_list, i, callback); 59 | filter_list[callback] = priority; 60 | end 61 | 62 | function _M.remove_filter(session, type, callback) 63 | if not session.filters then return; end 64 | local filter_list = session.filters[type]; 65 | if filter_list and filter_list[callback] then 66 | for i=1, #filter_list do 67 | if filter_list[i] == callback then 68 | t_remove(filter_list, i); 69 | filter_list[callback] = nil; 70 | return true; 71 | end 72 | end 73 | end 74 | end 75 | 76 | function _M.add_filter_hook(callback) 77 | t_insert(new_filter_hooks, callback); 78 | end 79 | 80 | function _M.remove_filter_hook(callback) 81 | for i=1,#new_filter_hooks do 82 | if new_filter_hooks[i] == callback then 83 | t_remove(new_filter_hooks, i); 84 | end 85 | end 86 | end 87 | 88 | _M.initialize = initialize; 89 | return _M; 90 | -------------------------------------------------------------------------------- /util/helpers.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2012, Matthew Wild 9 | 10 | local debug = require "util.debug"; 11 | 12 | -- Helper functions for debugging 13 | 14 | local log = require "util.logger".init("util.debug"); 15 | local _helpers = {}; 16 | 17 | function _helpers.log_events(events, name, logger) 18 | local f = events.fire_event; 19 | if not f then 20 | error("Object does not appear to be a util.events object"); 21 | end 22 | logger = logger or log; 23 | name = name or tostring(events); 24 | function events.fire_event(event, ...) 25 | logger("debug", "%s firing event: %s", name, event); 26 | return f(event, ...); 27 | end 28 | events[events.fire_event] = f; 29 | return events; 30 | end 31 | 32 | function _helpers.revert_log_events(events) 33 | events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :)) 34 | end 35 | 36 | function _helpers.show_events(events, specific_event) 37 | local event_handlers = events._handlers; 38 | local events_array = {}; 39 | local event_handler_arrays = {}; 40 | for event in pairs(events._event_map) do 41 | local handlers = event_handlers[event]; 42 | if handlers and (event == specific_event or not specific_event) then 43 | table.insert(events_array, event); 44 | local handler_strings = {}; 45 | for i, handler in ipairs(handlers) do 46 | local upvals = debug.string_from_var_table(debug.get_upvalues_table(handler)); 47 | handler_strings[i] = " "..i..": "..tostring(handler)..(upvals and ("\n "..upvals) or ""); 48 | end 49 | event_handler_arrays[event] = handler_strings; 50 | end 51 | end 52 | table.sort(events_array); 53 | local i = 1; 54 | while i <= #events_array do 55 | local handlers = event_handler_arrays[events_array[i]]; 56 | for j=#handlers, 1, -1 do 57 | table.insert(events_array, i+1, handlers[j]); 58 | end 59 | if i > 1 then events_array[i] = "\n"..events_array[i]; end 60 | i = i + #handlers + 1 61 | end 62 | return table.concat(events_array, "\n"); 63 | end 64 | 65 | function _helpers.get_upvalue(f, get_name) 66 | local i, name, value = 0; 67 | repeat 68 | i = i + 1; 69 | name, value = debug.getupvalue(f, i); 70 | until name == get_name or name == nil; 71 | return value; 72 | end 73 | 74 | return _helpers; 75 | -------------------------------------------------------------------------------- /util/hmac.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2010, Dwayne Bent, Matthew Wild, Tobias Markmann, Waqas Hussain 9 | 10 | local hashes = require "util.hashes"; 11 | 12 | local s_char = string.char; 13 | local s_gsub = string.gsub; 14 | local s_rep = string.rep; 15 | 16 | local _ENV, _M = nil, {}; 17 | 18 | local xor_map = {0;1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;1;0;3;2;5;4;7;6;9;8;11;10;13;12;15;14;2;3;0;1;6;7;4;5;10;11;8;9;14;15;12;13;3;2;1;0;7;6;5;4;11;10;9;8;15;14;13;12;4;5;6;7;0;1;2;3;12;13;14;15;8;9;10;11;5;4;7;6;1;0;3;2;13;12;15;14;9;8;11;10;6;7;4;5;2;3;0;1;14;15;12;13;10;11;8;9;7;6;5;4;3;2;1;0;15;14;13;12;11;10;9;8;8;9;10;11;12;13;14;15;0;1;2;3;4;5;6;7;9;8;11;10;13;12;15;14;1;0;3;2;5;4;7;6;10;11;8;9;14;15;12;13;2;3;0;1;6;7;4;5;11;10;9;8;15;14;13;12;3;2;1;0;7;6;5;4;12;13;14;15;8;9;10;11;4;5;6;7;0;1;2;3;13;12;15;14;9;8;11;10;5;4;7;6;1;0;3;2;14;15;12;13;10;11;8;9;6;7;4;5;2;3;0;1;15;14;13;12;11;10;9;8;7;6;5;4;3;2;1;0;}; 19 | local function xor(x, y) 20 | local lowx, lowy = x % 16, y % 16; 21 | local hix, hiy = (x - lowx) / 16, (y - lowy) / 16; 22 | local lowr, hir = xor_map[lowx * 16 + lowy + 1], xor_map[hix * 16 + hiy + 1]; 23 | local r = hir * 16 + lowr; 24 | return r; 25 | end 26 | local opadc, ipadc = s_char(0x5c), s_char(0x36); 27 | local ipad_map = {}; 28 | local opad_map = {}; 29 | for i=0,255 do 30 | ipad_map[s_char(i)] = s_char(xor(0x36, i)); 31 | opad_map[s_char(i)] = s_char(xor(0x5c, i)); 32 | end 33 | 34 | --[[ 35 | key 36 | the key to use in the hash 37 | message 38 | the message to hash 39 | hash 40 | the hash function 41 | blocksize 42 | the blocksize for the hash function in bytes 43 | hex 44 | return raw hash or hexadecimal string 45 | --]] 46 | local function hmac(key, message, hash, blocksize, hex) 47 | if #key > blocksize then 48 | key = hash(key); 49 | end 50 | 51 | local padding = blocksize - #key; 52 | local ipad = s_gsub(key, ".", ipad_map)..s_rep(ipadc, padding); 53 | local opad = s_gsub(key, ".", opad_map)..s_rep(opadc, padding); 54 | 55 | return hash(opad..hash(ipad..message), hex); 56 | end 57 | 58 | function _M.md5(key, message, hex) 59 | return hmac(key, message, hashes.md5, 64, hex); 60 | end 61 | 62 | function _M.sha1(key, message, hex) 63 | return hmac(key, message, hashes.sha1, 64, hex); 64 | end 65 | 66 | function _M.sha256(key, message, hex) 67 | return hmac(key, message, hashes.sha256, 64, hex); 68 | end 69 | 70 | function _M.sha384(key, message, hex) 71 | return hmac(key, message, hashes.sha384, 128, hex); 72 | end 73 | 74 | function _M.sha512(key, message, hex) 75 | return hmac(key, message, hashes.sha512, 128, hex); 76 | end 77 | 78 | _M.hmac = hmac; 79 | return _M; 80 | -------------------------------------------------------------------------------- /util/import.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2010, Matthew Wild 9 | 10 | local t_insert, unpack = table.insert, table.unpack or unpack; 11 | function import(module, ...) 12 | local m = package.loaded[module] or require(module); 13 | if type(m) == "table" and ... then 14 | local ret = {}; 15 | for _, f in ipairs{...} do 16 | t_insert(ret, m[f]); 17 | end 18 | return unpack(ret); 19 | end 20 | return m; 21 | end 22 | -------------------------------------------------------------------------------- /util/iterators.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2011, Matthew Wild, Waqas Hussain 9 | 10 | local t_insert, unpack = table.insert, table.unpack or unpack; 11 | 12 | --[[ Iterators ]]-- 13 | 14 | local it = {}; 15 | 16 | -- Reverse an iterator 17 | function it.reverse(f, s, var) 18 | local results = {}; 19 | 20 | -- First call the normal iterator 21 | while true do 22 | local ret = { f(s, var) }; 23 | var = ret[1]; 24 | if var == nil then break; end 25 | t_insert(results, 1, ret); 26 | end 27 | 28 | -- Then return our reverse one 29 | local i, max = 0, #results; 30 | return function (results) 31 | if i < max then 32 | i = i + 1; 33 | return unpack(results[i]); 34 | end 35 | end, results; 36 | end 37 | 38 | -- Iterate only over keys in a table 39 | local function _keys_it(t, key) 40 | return (next(t, key)); 41 | end 42 | function it.keys(t) 43 | return _keys_it, t; 44 | end 45 | 46 | -- Iterate only over values in a table 47 | function it.values(t) 48 | local key, val; 49 | return function (t) 50 | key, val = next(t, key); 51 | return val; 52 | end, t; 53 | end 54 | 55 | -- Given an iterator, iterate only over unique items 56 | function it.unique(f, s, var) 57 | local set = {}; 58 | 59 | return function () 60 | while true do 61 | local ret = { f(s, var) }; 62 | var = ret[1]; 63 | if var == nil then break; end 64 | if not set[var] then 65 | set[var] = true; 66 | return var; 67 | end 68 | end 69 | end; 70 | end 71 | 72 | --[[ Return the number of items an iterator returns ]]-- 73 | function it.count(f, s, var) 74 | local x = 0; 75 | 76 | while true do 77 | local ret = { f(s, var) }; 78 | var = ret[1]; 79 | if var == nil then break; end 80 | x = x + 1; 81 | end 82 | 83 | return x; 84 | end 85 | 86 | -- Return the first n items an iterator returns 87 | function it.head(n, f, s, var) 88 | local c = 0; 89 | return function (s, var) 90 | if c >= n then 91 | return nil; 92 | end 93 | c = c + 1; 94 | return f(s, var); 95 | end, s; 96 | end 97 | 98 | -- Skip the first n items an iterator returns 99 | function it.skip(n, f, s, var) 100 | for i=1,n do 101 | var = f(s, var); 102 | end 103 | return f, s, var; 104 | end 105 | 106 | -- Return the last n items an iterator returns 107 | function it.tail(n, f, s, var) 108 | local results, count = {}, 0; 109 | while true do 110 | local ret = { f(s, var) }; 111 | var = ret[1]; 112 | if var == nil then break; end 113 | results[(count % n) + 1] = ret; 114 | count = count + 1; 115 | end 116 | 117 | if n > count then n = count; end 118 | 119 | local pos = 0; 120 | return function () 121 | pos = pos + 1; 122 | if pos > n then return nil; end 123 | return unpack(results[((count - 1 + pos) % n) + 1]); 124 | end 125 | --return reverse(head(n, reverse(f, s, var))); 126 | end 127 | 128 | local function _range_iter(max, curr) if curr < max then return curr + 1; end end 129 | function it.range(x, y) 130 | if not y then x, y = 1, x; end -- Default to 1..x if y not given 131 | return _range_iter, y, x-1; 132 | end 133 | 134 | -- Convert the values returned by an iterator to an array 135 | function it.to_array(f, s, var) 136 | local t, var = {}; 137 | while true do 138 | var = f(s, var); 139 | if var == nil then break; end 140 | t_insert(t, var); 141 | end 142 | return t; 143 | end 144 | 145 | -- Treat the return of an iterator as key,value pairs, 146 | -- and build a table 147 | function it.to_table(f, s, var) 148 | local t, var2 = {}; 149 | while true do 150 | var, var2 = f(s, var); 151 | if var == nil then break; end 152 | t[var] = var2; 153 | end 154 | return t; 155 | end 156 | 157 | return it; 158 | -------------------------------------------------------------------------------- /util/jsonload.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local open, pcall, select = io.open, pcall, select; 8 | local decode = require "util.json".decode; 9 | local encode = require "util.json".encode 10 | 11 | local function loadfile(file) 12 | local f = open(file); 13 | if f then 14 | local str = f:read("*all"); 15 | local ok, ret = pcall(decode, str); 16 | if ok then 17 | return ret; 18 | else 19 | return nil, ret; 20 | end 21 | end 22 | end 23 | 24 | return { loadfile = loadfile, serialize = encode }; 25 | -------------------------------------------------------------------------------- /util/logger.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2008-2011, Matthew Wild, Tobias Markmann, Waqas Hussain 9 | 10 | local pcall = pcall; 11 | 12 | local find = string.find; 13 | local ipairs, pairs, setmetatable = ipairs, pairs, setmetatable; 14 | 15 | local _ENV = nil; 16 | 17 | local level_sinks = {}; 18 | 19 | local make_logger; 20 | local _logger = {}; 21 | 22 | function _logger.init(name) 23 | local log_debug = make_logger(name, "debug"); 24 | local log_info = make_logger(name, "info"); 25 | local log_warn = make_logger(name, "warn"); 26 | local log_error = make_logger(name, "error"); 27 | 28 | return function (level, message, ...) 29 | if level == "debug" then 30 | return log_debug(message, ...); 31 | elseif level == "info" then 32 | return log_info(message, ...); 33 | elseif level == "warn" then 34 | return log_warn(message, ...); 35 | elseif level == "error" then 36 | return log_error(message, ...); 37 | end 38 | end 39 | end 40 | 41 | function make_logger(source_name, level) 42 | local level_handlers = level_sinks[level]; 43 | if not level_handlers then 44 | level_handlers = {}; 45 | level_sinks[level] = level_handlers; 46 | end 47 | 48 | local logger = function (message, ...) 49 | for i = 1,#level_handlers do 50 | level_handlers[i](source_name, level, message, ...); 51 | end 52 | end 53 | 54 | return logger; 55 | end 56 | 57 | function _logger.reset() 58 | for level, handler_list in pairs(level_sinks) do 59 | -- Clear all handlers for this level 60 | for i = 1, #handler_list do 61 | handler_list[i] = nil; 62 | end 63 | end 64 | end 65 | 66 | function _logger.add_level_sink(level, sink_function) 67 | if not level_sinks[level] then 68 | level_sinks[level] = { sink_function }; 69 | else 70 | level_sinks[level][#level_sinks[level] + 1 ] = sink_function; 71 | end 72 | end 73 | 74 | _logger.new = make_logger; 75 | 76 | return _logger; 77 | -------------------------------------------------------------------------------- /util/mail.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local ssl = require "ssl"; 8 | local socket = require "socket"; 9 | local smtp = require "socket.smtp"; 10 | 11 | if not (ssl or smtp) then error("Either Luasocket or Luasec aren't installed, aborting load", 0); end 12 | 13 | local version = ssl and ssl._VERSION:match("^%d+%.(%d+)"); 14 | version = tonumber(version); 15 | 16 | local function do_ssl() 17 | local s = socket.tcp(); 18 | return setmetatable({ 19 | connect = function(_, host, port) 20 | local r, e = s:connect(host, port); 21 | if not r then 22 | s:close(); 23 | return r, e; 24 | end 25 | s = ssl.wrap(s, { mode = "client", protocol = version > 5 and "any" or "sslv23" }); 26 | return s:dohandshake(); 27 | end 28 | }, { 29 | __index = function(t,n) 30 | return function(_, ...) 31 | return s[n](s, ...); 32 | end 33 | end 34 | }); 35 | end 36 | 37 | local function send(from, to, reply_to, subject, body, server, secure) 38 | local _from = from:match("^.*<(.*)>"); 39 | local msg = { 40 | headers = { 41 | ["Content-Type"] = "text/html; charset=UTF-8", 42 | ["from"] = from, 43 | ["to"] = to, 44 | ["reply-to"] = reply_to, 45 | ["subject"] = subject 46 | }, 47 | body = body 48 | }; 49 | 50 | local mail = { 51 | from = _from or from, 52 | rcpt = to, 53 | source = smtp.message(msg), 54 | user = type(server) == "table" and server.user or nil, 55 | password = type(server) == "table" and server.password or nil, 56 | domain = type(server) == "table" and server.helo or nil, 57 | server = (type(server) == "string" and server) or (type(server) == "table" and server.host) or nil, 58 | port = type(server) == "table" and server.port or (secure and 465 or 25), 59 | create = secure and do_ssl or nil 60 | }; 61 | 62 | if not mail.server then return nil, "No mail host specified"; end 63 | 64 | local ok, err = smtp.send(mail); 65 | if not ok then return ok, err; end 66 | 67 | return true; 68 | end 69 | 70 | return { send = send }; 71 | -------------------------------------------------------------------------------- /util/pluginloader.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2012, Florian Zeitz, Matthew Wild, Waqas Hussain 9 | 10 | local dir_sep, path_sep = package.config:match("^(%S+)%s(%S+)"); 11 | local plugin_dir = {}; 12 | for path in (CFG_PLUGINDIR or "./plugins/"):gsub("[/\\]", dir_sep):gmatch("[^"..path_sep.."]+") do 13 | path = path..dir_sep; -- add path separator to path end 14 | path = path:gsub(dir_sep..dir_sep.."+", dir_sep); -- coalesce multiple separaters 15 | plugin_dir[#plugin_dir + 1] = path; 16 | end 17 | 18 | local io_open = io.open; 19 | local envload = require "util.envload".envload; 20 | 21 | local _M = {}; 22 | 23 | local function load_file(names) 24 | local file, err, path; 25 | for i=1,#plugin_dir do 26 | for j=1,#names do 27 | path = plugin_dir[i]..names[j]; 28 | file, err = io_open(path); 29 | if file then 30 | local content = file:read("*a"); 31 | file:close(); 32 | return content, path; 33 | end 34 | end 35 | end 36 | return file, err; 37 | end 38 | 39 | local function load_resource(plugin, resource) 40 | resource = resource or "mod_"..plugin..".lua"; 41 | 42 | local names = { 43 | "mod_"..plugin.."/"..plugin.."/"..resource; -- mod_hello/hello/mod_hello.lua 44 | "mod_"..plugin.."/"..resource; -- mod_hello/mod_hello.lua 45 | plugin.."/"..resource; -- hello/mod_hello.lua 46 | resource; -- mod_hello.lua 47 | }; 48 | 49 | return load_file(names); 50 | end 51 | 52 | local function load_code(plugin, resource, env) 53 | local content, err = load_resource(plugin, resource); 54 | if not content then return content, err; end 55 | local path = err; 56 | local f, err = envload(content, "@"..path, env); 57 | if not f then return f, err; end 58 | return f, path; 59 | end 60 | 61 | _M.load_file = load_file; 62 | _M.load_resource = load_resource; 63 | _M.load_code = load_code; 64 | return _M; 65 | -------------------------------------------------------------------------------- /util/sasl.lua: -------------------------------------------------------------------------------- 1 | -- Please see sasl.lua.license for licensing information. 2 | 3 | local pairs, ipairs = pairs, ipairs; 4 | local t_insert = table.insert; 5 | local type = type 6 | local setmetatable = setmetatable; 7 | local assert = assert; 8 | local require = require; 9 | 10 | --[[ 11 | Authentication Backend Prototypes: 12 | 13 | state = false : disabled 14 | state = true : enabled 15 | state = nil : non-existant 16 | ]] 17 | 18 | local method = {}; 19 | method.__index = method; 20 | local mechanisms = {}; 21 | local channelbinding_mechanisms = {}; 22 | local backend_mechanism = {}; 23 | 24 | -- register a new SASL mechanims 25 | local function registerMechanism(name, backends, f, cb) 26 | assert(type(name) == "string", "Parameter name MUST be a string."); 27 | assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table."); 28 | assert(type(f) == "function", "Parameter f MUST be a function."); 29 | if cb then channelbinding_mechanisms[name] = true; end 30 | mechanisms[name] = f 31 | for _, backend_name in ipairs(backends) do 32 | if backend_mechanism[backend_name] == nil then backend_mechanism[backend_name] = {}; end 33 | t_insert(backend_mechanism[backend_name], name); 34 | end 35 | end 36 | 37 | -- create a new SASL object which can be used to authenticate clients, 38 | -- new() expects an array, profile.order, to be present in the profile 39 | -- and which specifies the order of preference in which mechanisms are 40 | -- presented by the server 41 | local function new(realm, profile) 42 | local order = profile.order; 43 | local session = profile.session; 44 | local cb_capable = profile.channel_bind_cb and true; 45 | local mechanisms = {}; 46 | if type(order) == "table" and #order ~= 0 then 47 | for b = 1, #order do 48 | local backend = backend_mechanism[order[b]]; 49 | if backend then 50 | for i = 1, #backend do 51 | local sasl = backend[i]; 52 | if not cb_capable and channelbinding_mechanisms[sasl] then 53 | -- don't add 54 | else 55 | t_insert(mechanisms, sasl); 56 | mechanisms[sasl] = true; 57 | end 58 | end 59 | end 60 | end 61 | end 62 | return setmetatable({ profile = profile, realm = realm, mechs = mechanisms }, method); 63 | end 64 | 65 | -- get a fresh clone with the same realm and profile 66 | function method:clean_clone() 67 | return new(self.realm, self.profile) 68 | end 69 | 70 | -- get a list of possible SASL mechanims to use 71 | function method:mechanisms() 72 | local mechs, session = self.mechs, self.profile.session; 73 | local i, n = 0, #mechs; 74 | local function iter() 75 | i = i + 1; 76 | if i <= n then 77 | local mechanism = mechs[i]; 78 | if (mechanism == "PLAIN" and not session.can_do_insecure_plain_auth and not session.secure) or 79 | (mechanism == "EXTERNAL" and not session.can_do_external_auth) then 80 | return iter(); 81 | else 82 | return mechanism; 83 | end 84 | end 85 | end 86 | return iter; 87 | end 88 | 89 | -- select a mechanism to use 90 | function method:select(mechanism) 91 | local mechs, session = self.mechs, self.profile.session; 92 | if (not (mechanism == "PLAIN" and not session.can_do_insecure_plain_auth and not session.secure) or 93 | (mechanism == "EXTERNAL" and not session.can_do_external_auth)) and 94 | not self.selected and self.mechs[mechanism] then 95 | self.selected = mechanism; 96 | return true; 97 | end 98 | end 99 | 100 | -- feed new messages to process into the library 101 | function method:process(message) 102 | return mechanisms[self.selected](self, message); 103 | end 104 | 105 | -- load the mechanisms 106 | require "util.sasl.external".init(registerMechanism); 107 | require "util.sasl.scram".init(registerMechanism); 108 | require "util.sasl.digest-md5".init(registerMechanism); 109 | require "util.sasl.plain".init(registerMechanism); 110 | require "util.sasl.anonymous".init(registerMechanism); 111 | 112 | return { 113 | registerMechanism = registerMechanism, 114 | new = new 115 | }; 116 | -------------------------------------------------------------------------------- /util/sasl.lua.license: -------------------------------------------------------------------------------- 1 | This licensing file is valid for the following files: 2 | ./sasl.lua 3 | ./sasl/digest-md5.lua 4 | ./sasl/plain.lua 5 | ./sasl/scram.lua 6 | 7 | sasl.lua v0.4 8 | Copyright (C) 2008-2010 Tobias Markmann 9 | 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 13 | 14 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 15 | * 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. 16 | * 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. 17 | 18 | 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. 19 | -------------------------------------------------------------------------------- /util/sasl/anonymous.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local s_match = string.match; 8 | local log = require "util.logger".init("sasl"); 9 | 10 | local _ENV = nil; 11 | 12 | --[[ 13 | SASL ANONYMOUS according to RFC 4505 14 | 15 | Supported Authentication Backends 16 | 17 | anonymous: 18 | function(session, realm) 19 | return username, error; 20 | end 21 | 22 | ]] 23 | 24 | local function anonymous(self, message) 25 | local username, err; 26 | username, err = self.profile.anonymous(self, self.profile.session); 27 | if err then return "failure", err; end 28 | self.username = username; 29 | return "success"; 30 | end 31 | 32 | local function init(registerMechanism) 33 | registerMechanism("ANONYMOUS", {"anonymous"}, anonymous); 34 | end 35 | 36 | return { init = init }; 37 | -------------------------------------------------------------------------------- /util/sasl/external.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | 7 | local log = require "util.logger".init("sasl"); 8 | local nodeprep = require "util.encodings".stringprep.nodeprep; 9 | local ipairs = ipairs; 10 | 11 | local _ENV = nil; 12 | 13 | --[[ 14 | SASL EXTERNAL according to RFC 4422 15 | 16 | Supported Authentication Backends 17 | 18 | external: 19 | function(sasl, session, authid) 20 | return nil (internal-server-error) or true (invalid-authzid) or false or username, err. 21 | end 22 | ]] 23 | 24 | local function external(self, authid) 25 | local username, err = self.profile.external(self, self.profile.session, authid); 26 | 27 | if username == nil then 28 | log("debug", "A server error was caught while attempting EXTERNAL SASL: %s", err); 29 | return "failure", "internal-server-error", err; 30 | end 31 | if username == true then return "failure", "invalid-authzid", err; end 32 | if username == false then return "failure", "not-authorized", err; end 33 | 34 | self.username = nodeprep(username); 35 | if self.username then 36 | return "success"; 37 | else 38 | log("debug", "Username %s in the certificate, violates the NodePREP profile", username); 39 | return "failure", "malformed-request", "Username in the certificate violates the NodePREP profile"; 40 | end 41 | end 42 | 43 | local function init(registerMechanism) 44 | registerMechanism("EXTERNAL", {"external"}, external); 45 | end 46 | 47 | return { init = init }; 48 | -------------------------------------------------------------------------------- /util/sasl/plain.lua: -------------------------------------------------------------------------------- 1 | -- Please see sasl.lua.license for licensing information. 2 | 3 | local s_match = string.match; 4 | local nodeprep = require "util.encodings".stringprep.nodeprep; 5 | local saslprep = require "util.encodings".stringprep.saslprep; 6 | local log = require "util.logger".init("sasl"); 7 | 8 | local _ENV = nil; 9 | 10 | --[[ 11 | SASL PLAIN according to RFC 4616 12 | 13 | Supported Authentication Backends 14 | 15 | plain: 16 | function(username, realm) 17 | return password, state; 18 | end 19 | 20 | plain_test: 21 | function(username, password, realm) 22 | return true or false, state; 23 | end 24 | ]] 25 | 26 | local function plain(self, message) 27 | if not message then 28 | return "failure", "malformed-request"; 29 | end 30 | 31 | local authorization, authentication, password = s_match(message, "^([^%z]*)%z([^%z]+)%z([^%z]+)"); 32 | 33 | if not authorization then 34 | return "failure", "malformed-request"; 35 | end 36 | 37 | -- SASLprep password and authentication 38 | authentication = saslprep(authentication); 39 | password = saslprep(password); 40 | 41 | if (not password) or (password == "") or (not authentication) or (authentication == "") then 42 | log("debug", "Username or password violates SASLprep."); 43 | return "failure", "malformed-request", "Invalid username or password."; 44 | end 45 | 46 | local _nodeprep = self.profile.nodeprep; 47 | if _nodeprep ~= false then 48 | authentication = (_nodeprep or nodeprep)(authentication); 49 | if not authentication or authentication == "" then 50 | return "failure", "malformed-request", "Invalid username or password"; 51 | end 52 | end 53 | 54 | local correct, state = false, false; 55 | if self.profile.plain then 56 | local correct_password; 57 | correct_password, state = self.profile.plain(self, authentication, self.realm); 58 | correct = (correct_password == password); 59 | elseif self.profile.plain_test then 60 | correct, state = self.profile.plain_test(self, authentication, password, self.realm); 61 | end 62 | 63 | self.username = authentication 64 | if state == false then 65 | return "failure", "account-disabled"; 66 | elseif state == nil then 67 | return "failure", "not-authorized", "Unable to authorize you with the authentication credentials you've sent"; 68 | end 69 | 70 | if correct then 71 | return "success"; 72 | else 73 | return "failure", "not-authorized", "Unable to authorize you with the authentication credentials you've sent"; 74 | end 75 | end 76 | 77 | local function init(registerMechanism) 78 | registerMechanism("PLAIN", {"plain", "plain_test"}, plain); 79 | end 80 | 81 | return { init = init }; 82 | -------------------------------------------------------------------------------- /util/serialization.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2008-2012, Florian Zeitz, Matthew Wild, Waqas Hussain 9 | 10 | local string_rep = string.rep; 11 | local type = type; 12 | local tostring = tostring; 13 | local t_insert = table.insert; 14 | local t_concat = table.concat; 15 | local error = error; 16 | local pairs = pairs; 17 | local next = next; 18 | 19 | local loadstring = loadstring; 20 | local pcall = pcall; 21 | 22 | local debug_traceback = debug.traceback; 23 | local log = require "util.logger".init("serialization"); 24 | local envload = require"util.envload".envload; 25 | 26 | local _ENV = nil; 27 | local _M = {}; 28 | 29 | local indent = function(i) 30 | return string_rep("\t", i); 31 | end 32 | local function basicSerialize (o) 33 | if type(o) == "number" or type(o) == "boolean" then 34 | -- no need to check for NaN, as that's not a valid table index 35 | if o == 1/0 then return "(1/0)"; 36 | elseif o == -1/0 then return "(-1/0)"; 37 | else return tostring(o); end 38 | else -- assume it is a string -- FIXME make sure it's a string. throw an error otherwise. 39 | return (("%q"):format(tostring(o)):gsub("\\\n", "\\n")); 40 | end 41 | end 42 | local function _simplesave(o, ind, t, func) 43 | if type(o) == "number" then 44 | if o ~= o then func(t, "(0/0)"); 45 | elseif o == 1/0 then func(t, "(1/0)"); 46 | elseif o == -1/0 then func(t, "(-1/0)"); 47 | else func(t, tostring(o)); end 48 | elseif type(o) == "string" then 49 | func(t, (("%q"):format(o):gsub("\\\n", "\\n"))); 50 | elseif type(o) == "table" then 51 | if next(o) ~= nil then 52 | func(t, "{\n"); 53 | for k,v in pairs(o) do 54 | func(t, indent(ind)); 55 | func(t, "["); 56 | func(t, basicSerialize(k)); 57 | func(t, "] = "); 58 | if ind == 0 then 59 | _simplesave(v, 0, t, func); 60 | else 61 | _simplesave(v, ind+1, t, func); 62 | end 63 | func(t, ";\n"); 64 | end 65 | func(t, indent(ind-1)); 66 | func(t, "}"); 67 | else 68 | func(t, "{}"); 69 | end 70 | elseif type(o) == "boolean" then 71 | func(t, (o and "true" or "false")); 72 | else 73 | log("error", "cannot serialize a %s: %s", type(o), debug_traceback()) 74 | func(t, "nil"); 75 | end 76 | end 77 | 78 | local function append(t, o) 79 | _simplesave(o, 1, t, t.write or t_insert); 80 | return t; 81 | end 82 | 83 | function _M.serialize(o) 84 | return t_concat(append({}, o)); 85 | end 86 | 87 | function _M.deserialize(str) 88 | if type(str) ~= "string" then return nil; end 89 | str = "return "..str; 90 | local f, err = envload(str, "@data", {}); 91 | if not f then return nil, err; end 92 | local success, ret = pcall(f); 93 | if not success then return nil, ret; end 94 | return ret; 95 | end 96 | 97 | _M.append = append; 98 | return _M; 99 | -------------------------------------------------------------------------------- /util/set.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2012, Matthew Wild, Waqas Hussain 9 | 10 | local ipairs, pairs, setmetatable, next, tostring, type = 11 | ipairs, pairs, setmetatable, next, tostring, type; 12 | local t_concat = table.concat; 13 | 14 | local _ENV = nil; 15 | local _M = {}; 16 | 17 | local set_mt = {}; 18 | function set_mt.__call(set, _, k) 19 | return next(set._items, k); 20 | end 21 | function set_mt.__add(set1, set2) 22 | return _M.union(set1, set2); 23 | end 24 | function set_mt.__sub(set1, set2) 25 | return _M.difference(set1, set2); 26 | end 27 | function set_mt.__div(set, func) 28 | local new_set, new_items = _M.new(); 29 | local items, new_items = set._items, new_set._items; 30 | for item in pairs(items) do 31 | local new_item = func(item); 32 | if new_item ~= nil then 33 | new_items[new_item] = true; 34 | end 35 | end 36 | return new_set; 37 | end 38 | function set_mt.__eq(set1, set2) 39 | local set1, set2 = set1._items, set2._items; 40 | for item in pairs(set1) do 41 | if not set2[item] then 42 | return false; 43 | end 44 | end 45 | 46 | for item in pairs(set2) do 47 | if not set1[item] then 48 | return false; 49 | end 50 | end 51 | 52 | return true; 53 | end 54 | function set_mt.__tostring(set) 55 | local s, items = { }, set._items; 56 | for item in pairs(items) do 57 | s[#s+1] = tostring(item); 58 | end 59 | return t_concat(s, ", "); 60 | end 61 | 62 | local items_mt = {}; 63 | function items_mt.__call(items, _, k) 64 | return next(items, k); 65 | end 66 | 67 | function _M.new(list) 68 | local items = setmetatable({}, items_mt); 69 | local set = { _items = items }; 70 | 71 | function set:add(item) 72 | items[item] = true; 73 | end 74 | 75 | function set:contains(item) 76 | return items[item]; 77 | end 78 | 79 | function set:items() 80 | return items; 81 | end 82 | 83 | function set:remove(item) 84 | items[item] = nil; 85 | end 86 | 87 | function set:add_list(list) 88 | if list then 89 | if type(list) == "string" then 90 | list = { list }; 91 | elseif type(list) == "function" then 92 | list = list(); 93 | end 94 | 95 | if type(list) == "table" then 96 | for _, item in ipairs(list) do 97 | items[item] = true; 98 | end 99 | end 100 | end 101 | end 102 | 103 | function set:include(otherset) 104 | for item in pairs(otherset) do 105 | items[item] = true; 106 | end 107 | end 108 | 109 | function set:exclude(otherset) 110 | for item in pairs(otherset) do 111 | items[item] = nil; 112 | end 113 | end 114 | 115 | function set:empty() 116 | return not next(items); 117 | end 118 | 119 | if list then 120 | set:add_list(list); 121 | end 122 | 123 | return setmetatable(set, set_mt); 124 | end 125 | 126 | function _M.union(set1, set2) 127 | local set = _M.new(); 128 | local items = set._items; 129 | 130 | for item in pairs(set1._items) do 131 | items[item] = true; 132 | end 133 | 134 | for item in pairs(set2._items) do 135 | items[item] = true; 136 | end 137 | 138 | return set; 139 | end 140 | 141 | function _M.difference(set1, set2) 142 | local set = _M.new(); 143 | local items = set._items; 144 | 145 | for item in pairs(set1._items) do 146 | items[item] = (not set2._items[item]) or nil; 147 | end 148 | 149 | return set; 150 | end 151 | 152 | function _M.intersection(set1, set2) 153 | local set = _M.new(); 154 | local items = set._items; 155 | 156 | set1, set2 = set1._items, set2._items; 157 | 158 | for item in pairs(set1) do 159 | items[item] = (not not set2[item]) or nil; 160 | end 161 | 162 | return set; 163 | end 164 | 165 | function _M.xor(set1, set2) 166 | return _M.union(set1, set2) - _M.intersection(set1, set2); 167 | end 168 | 169 | return _M; 170 | -------------------------------------------------------------------------------- /util/termcolours.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2008-2012, Matthew Wild, Waqas Hussain 9 | 10 | local t_concat, t_insert = table.concat, table.insert; 11 | local char, format = string.char, string.format; 12 | local tonumber = tonumber; 13 | local ipairs = ipairs; 14 | local io_write = io.write; 15 | 16 | local _ENV = nil; 17 | local _M = {}; 18 | 19 | local stylemap = { 20 | reset = 0; bright = 1, dim = 2, underscore = 4, blink = 5, reverse = 7, hidden = 8; 21 | black = 30; red = 31; green = 32; yellow = 33; blue = 34; magenta = 35; cyan = 36; white = 37; 22 | ["black background"] = 40; ["red background"] = 41; ["green background"] = 42; ["yellow background"] = 43; ["blue background"] = 44; ["magenta background"] = 45; ["cyan background"] = 46; ["white background"] = 47; 23 | bold = 1, dark = 2, underline = 4, underlined = 4, normal = 0; 24 | }; 25 | 26 | local cssmap = { 27 | [1] = "font-weight: bold", [2] = "opacity: 0.5", [4] = "text-decoration: underline", [8] = "visibility: hidden", 28 | [30] = "color:black", [31] = "color:red", [32]="color:green", [33]="color:#FFD700", 29 | [34] = "color:blue", [35] = "color: magenta", [36] = "color:cyan", [37] = "color: white", 30 | [40] = "background-color:black", [41] = "background-color:red", [42]="background-color:green", 31 | [43]="background-color:yellow", [44] = "background-color:blue", [45] = "background-color: magenta", 32 | [46] = "background-color:cyan", [47] = "background-color: white"; 33 | }; 34 | 35 | local fmt_string = char(0x1B).."[%sm%s"..char(0x1B).."[0m"; 36 | function _M.getstring(style, text) 37 | if style then 38 | return format(fmt_string, style, text); 39 | else 40 | return text; 41 | end 42 | end 43 | 44 | function _M.getstyle(...) 45 | local styles, result = { ... }, {}; 46 | for i, style in ipairs(styles) do 47 | style = stylemap[style]; 48 | if style then 49 | t_insert(result, style); 50 | end 51 | end 52 | return t_concat(result, ";"); 53 | end 54 | 55 | local last = "0"; 56 | function _M.setstyle(style) 57 | style = style or "0"; 58 | if style ~= last then 59 | io_write("\27["..style.."m"); 60 | last = style; 61 | end 62 | end 63 | 64 | local function ansi2css(ansi_codes) 65 | if ansi_codes == "0" then return ""; end 66 | local css = {}; 67 | for code in ansi_codes:gmatch("[^;]+") do 68 | t_insert(css, cssmap[tonumber(code)]); 69 | end 70 | return ""; 71 | end 72 | 73 | function _M.tohtml(input) 74 | return input:gsub("\027%[(.-)m", ansi2css); 75 | end 76 | 77 | return _M; 78 | -------------------------------------------------------------------------------- /util/throttle.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2011-2012, Matthew Wild 9 | 10 | local gettime = require "socket".gettime; 11 | local setmetatable = setmetatable; 12 | local floor = math.floor; 13 | 14 | local _ENV = nil; 15 | 16 | local throttle = {}; 17 | local throttle_mt = { __index = throttle }; 18 | 19 | function throttle:update() 20 | local newt = gettime(); 21 | local elapsed = newt - self.t; 22 | self.t = newt; 23 | local balance = floor(self.rate * elapsed) + self.balance; 24 | if balance > self.max then 25 | self.balance = self.max; 26 | else 27 | self.balance = balance; 28 | end 29 | return self.balance; 30 | end 31 | 32 | function throttle:peek(cost) 33 | cost = cost or 1; 34 | return self.balance >= cost or self:update() >= cost; 35 | end 36 | 37 | function throttle:poll(cost, split) 38 | if self:peek(cost) then 39 | self.balance = self.balance - cost; 40 | return true; 41 | else 42 | local balance = self.balance; 43 | if split then 44 | self.balance = 0; 45 | end 46 | return false, balance, (cost-balance); 47 | end 48 | end 49 | 50 | local function create(max, period) 51 | return setmetatable({ rate = max / period, max = max, t = 0, balance = max }, throttle_mt); 52 | end 53 | 54 | return { create = create }; 55 | -------------------------------------------------------------------------------- /util/timer.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2009-2011, Matthew Wild, Waqas Hussain 9 | 10 | local server = require "net.server"; 11 | local math_min = math.min; 12 | local math_huge = math.huge; 13 | local get_time = require "socket".gettime; 14 | local uuid = require "util.uuid".generate; 15 | local pairs = pairs; 16 | 17 | local _G, _ENV = _G, nil; 18 | 19 | local event = server.event; 20 | local event_base = server.event_base; 21 | local EVENT_LEAVE = (event.core and event.core.LEAVE) or -1; 22 | 23 | _G.task_list = {}; 24 | local task_list = _G.task_list; 25 | local _M = {}; 26 | 27 | function _M.add_task(delay, callback, origin, host) 28 | local uuid, event_handle = uuid(); 29 | task_list[uuid] = { delay = delay, callback = callback, origin = origin, host = host }; 30 | event_handle = event_base:addevent(nil, 0, function () 31 | if not task_list[uuid] then return EVENT_LEAVE; end 32 | 33 | local ret = callback(get_time()); 34 | if ret then 35 | return 0, ret; 36 | elseif event_handle then 37 | task_list[uuid] = nil; 38 | return EVENT_LEAVE; 39 | end 40 | end 41 | , delay); 42 | return uuid; 43 | end 44 | 45 | function _M.remove_task(uuid) 46 | if uuid and task_list[uuid] then 47 | task_list[uuid] = nil; 48 | return true; 49 | else 50 | return false; 51 | end 52 | end 53 | 54 | function _M.remove_tasks_from_origin(origin, host) 55 | if not (origin and host) then return 0; end 56 | local count = 0; 57 | for uuid, task in pairs(task_list) do 58 | if task.origin == origin and task.host == host then 59 | task_list[uuid] = nil; 60 | count = count + 1; 61 | end 62 | end 63 | return count; 64 | end 65 | 66 | return _M; 67 | -------------------------------------------------------------------------------- /util/uuid.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2008-2010, Matthew Wild, Waqas Hussain 9 | 10 | local m_random = math.random; 11 | local tostring = tostring; 12 | local os_time = os.time; 13 | local os_clock = os.clock; 14 | local sha1 = require "util.hashes".sha1; 15 | 16 | local _ENV = nil; 17 | 18 | local last_uniq_time = 0; 19 | local function uniq_time() 20 | local new_uniq_time = os_time(); 21 | if last_uniq_time >= new_uniq_time then new_uniq_time = last_uniq_time + 1; end 22 | last_uniq_time = new_uniq_time; 23 | return new_uniq_time; 24 | end 25 | 26 | local function new_random(x) 27 | return sha1(x..os_clock()..tostring({}), true); 28 | end 29 | 30 | local buffer = new_random(uniq_time()); 31 | local function _seed(x) 32 | buffer = new_random(buffer..x); 33 | end 34 | local function get_nibbles(n) 35 | if #buffer < n then _seed(uniq_time()); end 36 | local r = buffer:sub(0, n); 37 | buffer = buffer:sub(n+1); 38 | return r; 39 | end 40 | local function get_twobits() 41 | return ("%x"):format(get_nibbles(1):byte() % 4 + 8); 42 | end 43 | 44 | local function generate() 45 | -- generate RFC 4122 complaint UUIDs (version 4 - random) 46 | return get_nibbles(8).."-"..get_nibbles(4).."-4"..get_nibbles(3).."-"..(get_twobits())..get_nibbles(3).."-"..get_nibbles(12); 47 | end 48 | 49 | return { generate = generate, seed = _seed }; 50 | -------------------------------------------------------------------------------- /util/watchdog.lua: -------------------------------------------------------------------------------- 1 | -- * Metronome IM * 2 | -- 3 | -- This file is part of the Metronome XMPP server and is released under the 4 | -- ISC License, please see the LICENSE file in this source package for more 5 | -- information about copyright and licensing. 6 | -- 7 | -- As per the sublicensing clause, this file is also MIT/X11 Licensed. 8 | -- ** Copyright (c) 2011-2012, Matthew Wild 9 | 10 | local timer = require "util.timer"; 11 | local setmetatable = setmetatable; 12 | local os_time = os.time; 13 | 14 | local _ENV = nil; 15 | 16 | local watchdog_methods = {}; 17 | local watchdog_mt = { __index = watchdog_methods }; 18 | 19 | function new(timeout, callback) 20 | local watchdog = setmetatable({ timeout = timeout, last_reset = os_time(), callback = callback }, watchdog_mt); 21 | timer.add_task(timeout+1, function (current_time) 22 | local last_reset = watchdog.last_reset; 23 | if not last_reset then 24 | return; 25 | end 26 | local time_left = (last_reset + timeout) - current_time; 27 | if time_left < 0 then 28 | return watchdog:callback(); 29 | end 30 | return time_left + 1; 31 | end); 32 | return watchdog; 33 | end 34 | 35 | function watchdog_methods:reset() 36 | self.last_reset = os_time(); 37 | end 38 | 39 | function watchdog_methods:cancel() 40 | self.last_reset = nil; 41 | end 42 | 43 | return { new = new }; 44 | --------------------------------------------------------------------------------
        ###TITLE###
        ###DAY###