When I need to configure something in a complicated way, I find myself reviewing embedded languages supported by the server to create a flexible configuration. In Redis, you can improve the performance of requests, in Nginx, you can improve the handling of incoming requests, FreeSwitch offers alternatives for performing the same tasks using different embedded languages. Even in a software like TheGimp, you can add your own code to edit images.
Among the embedded languages, JavaScript and Lua are the most commonly used languages. JavaScript is very well known to the Erlang community because it was integrated (as a port, it is not implemented on top of Erlang) in popular products such as CouchDB and Riak. But I think the more exciting option, raised by Erlang Co-Creator, Robert Virding, is to implement Lua on top of Erlang, which can be used as an embedded language.
Why? Let’s take a look.
Usually, when tasked with fitting the definition of a behaviour we would like to configure, we would create an algorithm in a simple language such as Lua. This saves us from performing activities like:
This kind of implementation is used frequently. It is easy to think of examples you’re likely to come across in day-to-day life. For example, supermarket offers which have multiple dependencies, commissions for salespeople which might feature variable ranges and percentages based on the type of sale, amount of sale or tax brackets, even SMS, emails or HTTP requests could be considered examples.
To demonstrate this, let’s look at an example of developing a load balancer. This is a simple project using cowboy, and luerl as dependencies, and depending on the headers and other information from the HTTP request, we can send it to the different web servers we have available and configured.
Based on the above premise we can write the configuration as follows:
{load_balancer, [
{servers, [
{odin, "1.1.1.1", [
{in, method, [post]},
{'>', <<"content-length">>, 10000},
{in, <<"accept">>, [<<"json">>]}
]},
{thor, "1.1.1.2", [
{in, method, [get, post]},
{'==', http_version, <<"2">>}
]},
{balder, "1.1.1.3", [
{in, method, [get, post]}
]}
]}
]}.
As you can see, we have to define a 3-tuple system for the rules with the operation in the first element and the two operators as the following elements inside of the tuple. In addition, we are occasionally handling the second element as a header name (if it is a binary), but at other times it’s the method we use to perform the request (using the atom "method") and other times still, the HTTP version is used to gather the information.
The problem is that we have no closed specifications. We could add more elements or even change the meaning of them. What if we want to use logical modifiers like "and" and "or" to join the checks instead of assuming they are always using "and"? This change will add more complexity to our configuration and more complexity means more possibilities for making mistakes.
At the moment, if the configuration is wrong or adds something that is not granted, it is up to us to trigger the corresponding error and point to where it is to make it easier to fix. As you can imagine, that is not an easy thing to do if you are handling it during runtime.
It’s not unconventional to think about configuration in terms of a specific code. At this point, Lua code could be put in charge of the definition because it is based on Lua semantics.
We only need the information for the configuration and running of the snippet to give us the desired behaviour we want to plug into the correct place. For example, the previous configuration could be written as:
local odin = "1.1.1.1"
local thor = "1.1.1.2"
local balder = "1.1.1.3"
local method = http.method()
local size = tonumber(http.header("content-length")) or 0
local accept = config.split(http.header("accept") or "", ", ")
local httpver = http.version()
if method == "post" and size > 10000 and config.member("json", accept) then
return odin
elseif config.member(method, {"get", "post"}) and httpver == "2" then
return thor
elseif config.member(method, {"get", "post"}) then
return balder
end
As you can see, we are able to optimise and fix the code to suit our needs, it is shorter and clearer than the original configuration and, most importantly, we can now test and check to be sure it is compiling correctly.
The important thing to keep in mind is that the configuration code must include the functions which are going to be needed to handle the request. In the example above, we are using functions like http_version(), http_header("...") or even split(...) and member(...). These functions should be provided to the interpreter.
Of course, the interpreter also has other functions available, we only need to provide the specific functions that are required for our business logic.
In addition to improving the performance, we also improve security because we can use luerl_sandbox:init() to sandbox the function calls the code will not be able to access functions that are not explicitly exposed to it.
Inserting the Lua code into the configuration can be a little tricky. To avoid this, I recommend putting these scripts into the priv directory as a normal Lua file (using the extension .lua ) – (this one is more of a code formatting of priv and .lua). This could even be done inside of a database if we are handling the configuration in an automated way using a key/value storage configuration such as etcd.
The most important thing to keep in mind before running that code is to have a specific task which helps you to parse it and ensure the code is correct. One solution is to conduct a testing phase to ensure that the configuration is not breaking or negatively impacting other parts of the system.
For example, in the previous code, we would write a couple of libraries, one called utils for the functions needed for strings and tables and another called http needed for the HTTP functions. An example would be:
-module(luerl_lib_http). -export([load/1, install/1, put_request/2]). -include_lib("luerl/include/luerl.hrl"). -define(REQUEST, http_request). load(St) -> luerl:load_module([<<"http">>], luerl_lib_http, St). install(St) -> luerl_heap:alloc_table(table(), St). put_request(Request, St) -> luerl:put_private(?REQUEST, Request, St). table() -> [ {<<"method">>, #erl_func{code = fun method/2}}, {<<"version">>, #erl_func{code = fun version/2}}, {<<"header">>, #erl_func{code = fun header/2}} ]. method(_Args, St) -> #{method := Method} = request(St), {[Method], St}. version(_Args, St) -> #{version := Version} = request(St), {[Version], St}. header([Name|_], St) when is_binary(Name) -> #{headers := Headers} = request(St), {[maps:get(Name, Headers, nil)], St}; header(Args, St) -> luerl_lib:badarg_error(<<"header">>, Args, St). request(St) -> luerl:get_private(?REQUEST, St).
defmodule LuerlLib.Http do require Record Record.defrecord(:erl_func, Record.extract(:erl_func, from_lib: "luerl/include/luerl.hrl")) @private_key :http_request def load(state), do: :luerl.load_module(["http"], __MODULE__, state) def install(state), do: :luerl_heap.alloc_table(exports(), state) def put_request(state, request), do: :luerl.put_private(@private_key, request, state) defp exports do [ {"method", erl_func(code: &method/2)}, {"version", erl_func(code: &version/2)}, {"header", erl_func(code: &header/2)} ] end defp method(_args, state), do: {[request(state).method], state} defp version(_args, state), do: {[request(state).version], state} defp header([name | _], state) when is_binary(name), do: {[Map.get(request(state).headers, name)], state} defp header(args, state), do: :luerl_lib.badarg_error("header", args, state) defp request(state), do: :luerl.get_private(@private_key, state) end
-module(luerl_lib_config). -export([load/1, install/1]). -include_lib("luerl/include/luerl.hrl"). load(St) -> luerl:load_module([<<"config">>], luerl_lib_config, St). install(St) -> luerl_heap:alloc_table(table(), St). table() -> [ {<<"split">>, #erl_func{code = fun split/2}}, {<<"member">>, #erl_func{code = fun member/2}} ]. member([Entry, #tref{}=Table], St) -> #table{a = Array} = luerl_heap:get_table(Table, St), Result = array:foldl(fun (_, V, false) when V =:= Entry -> true; (_, _, Acc) -> Acc end, false, Array), {[Result], St}; member(Args, St) -> luerl_lib:badarg_error(<<"member">>, Args, St). split([String, Sep], St) when is_binary(String), is_binary(Sep) -> {Tref, St1} = luerl:encode(string:split(String, Sep, all), St), {[Tref], St1}; split(Args, St) -> luerl_lib:badarg_error(<<"split">>, Args, St).
defmodule LuerlLib.Config do require Record Record.defrecord(:erl_func, Record.extract(:erl_func, from_lib: "luerl/include/luerl.hrl")) Record.defrecord(:table, Record.extract(:table, from_lib: "luerl/include/luerl.hrl")) def load(state), do: :luerl.load_module(["config"], __MODULE__, state) def install(state), do: :luerl_heap.alloc_table(exports(), state) defp exports do [ {"split", erl_func(code: &split/2)}, {"member", erl_func(code: &member/2)} ] end defp split([string, separator | _], state) when is_binary(string) and is_binary(separator) do {table_ref, state} = :luerl.encode(String.split(string, separator), state) {[table_ref], state} end defp split(args, state), do: :luerl_lib.badarg_error("split", args, state) defp member([entry, table_ref | _], state) when Record.is_record(table_ref, :tref) do table(a: array) = :luerl_heap.get_table(table_ref, state) found = :array.sparse_foldl(fn _index, value, acc -> acc or value === entry end, false, array) {[found], state} end defp member(args, state), do: :luerl_lib.badarg_error("member", args, state) end
As you can see, we are implementing the functions we need and making them available to our Lua interface under the config and http packages. To load these functions, we have to run the load function which is exported in both modules. NOTE: install is used as a callback function in load_module
A great benefit of doing things this way is the "compile once, and ready many" approach. The config does not have to be loaded for every request. If we put it all together in our application, it would something like like the following
Add a build_request function and its helpers to your luerl_lib_http and LuerlLib.Http modules.
-export([load/1,install/1,put_request/2,build_request/1]).% add export build_request(CowboyReq)-> #{method=>string:lowercase(cowboy_req:method(CowboyReq)), version=>version_to_binary(cowboy_req:version(CowboyReq)), headers=>cowboy_req:headers(CowboyReq)}. version_to_binary('HTTP/1.0')-><<"1.0">>; version_to_binary('HTTP/1.1')-><<"1.1">>; version_to_binary('HTTP/2')-><<"2">>.
defbuild_request(cowboy_req)do %{ method:cowboy_req|>:cowboy_req.method()|>String.downcase(), version:version_to_binary(:cowboy_req.version(cowboy_req)), headers::cowboy_req.headers(cowboy_req) } end defpversion_to_binary(:"HTTP/1.0"),do:"1.0" defpversion_to_binary(:"HTTP/1.1"),do:"1.1" defpversion_to_binary(:"HTTP/2"),do:"2"
Add handler modules for handling the request
-module(luerl_lib_handler). -export([init/2]). init(CowboyReq,{Form,St})-> LuaReq=luerl_lib_http:build_request(CowboyReq), St1=luerl_lib_http:put_request(LuaReq,St), Reply=caseluerl:call_chunk(Form,St1)of {ok,[],_St2}-> cowboy_req:reply(404,#{},<<"no matching route">>,CowboyReq); {ok,Rets,St2}-> [Backend]=luerl:decode_list(Rets,St2), cowboy_req:reply(200,#{},Backend,CowboyReq); {lua_error,_Reason,_St2}-> cowboy_req:reply(500,#{},<<"routing error">>,CowboyReq) end, {ok,Reply,{Form,St}}.
defmoduleLuerlLib.Handlerdo definit(cowboy_req,{form,state})do lua_req=LuerlLib.Http.build_request(cowboy_req) state1=LuerlLib.Http.put_request(state,lua_req) reply= case:luerl.call_chunk(form,state1)do {:ok,[],_state2}-> :cowboy_req.reply(404,%{},"no matching route",cowboy_req) {:ok,returns,state2}-> [backend]=:luerl.decode_list(returns,state2) :cowboy_req.reply(200,%{},backend,cowboy_req) {:lua_error,_reason,_state2}-> :cowboy_req.reply(500,%{},"routing error",cowboy_req) end {:ok,reply,{form,state}} end end
With all of that defined, we can now call the relevant functions from our application startup:
-module(luerl_lib_app). -behaviour(application). -export([start/2,stop/1]). start(_StartType,_StartArgs)-> {ok,Form,St}=build_lua_state(), Dispatch=cowboy_router:compile([{'_',[{'_',luerl_lib_handler,{Form,St}}]}]), {ok,_}=cowboy:start_clear(luerl_lib_listener,[{port,8080}],#{env=>#{dispatch=>Dispatch}}), luerl_lib_sup:start_link(). stop(_State)-> ok=cowboy:stop_listener(luerl_lib_listener). build_lua_state()-> St0=luerl_lib_http:load(luerl_lib_config:load(luerl_sandbox:init())), luerl:loadfile("priv/config.lua",St0).
Supervisor
-module(luerl_lib_sup).
-behaviour(supervisor).
-export([start_link/0, init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
{ok, {#{strategy => one_for_one, intensity => 1, period => 5}, []}}.
defmoduleLuerlLib.Applicationdo useApplication defstart(_type,_args)do state= :luerl_sandbox.init() |>LuerlLib.Config.load() |>LuerlLib.Http.load() {:ok,form,state}=:luerl.loadfile(~c"priv/config.lua",state) dispatch=:cowboy_router.compile([{:_,[{:_,LuerlLib.Handler,{form,state}}]}]) {:ok,_}= :cowboy.start_clear(:luerl_lib_listener,[port:8080],%{env:%{dispatch:dispatch}}) LuerlLib.Supervisor.start_link() end end
Notice that we load the file once (with the lines below) and pass it down to the handler, it helps us obtain the forms that we need and the handler will always execute based on that initially loaded configuration.
{ok,Form,St1}=luerl:loadfile("priv/config.lua",St0).
{:ok,form,state}=:luerl.loadfile(String.to_charlist(path),state)
Lua scales up because it is built on top of Erlang. This means Lua is using the same processes as Erlang does, it also means we are not using ports to communicate with the Lua interpreter, we have the Lua interpreter running on Erlang.
This makes a great difference in comparison to JavaScript because if you are handling millions of requests and all of them require a JavaScript snippet, this can cause a bottleneck very quickly if you have to limit the number of ports or requests.
On the other hand, Lua is using native Erlang functions when it calls the functions we want to provide for the interpreter. That makes a clear improvement, saving us from performing data serialisation or transformation.
Using a language for flexible configuration gives us the possibility to create an easy interface to provide configurations, reduce the amount of code we need to write, and improve the maintenance without jeopardising the performance of the system. At the moment, you can use Lua as we have explained during the article or you can jump into PHP if you need to process text or templates on top of Erlang or Elixir.
Alternatively you can join the community and provide other solutions which help us build better fit-for-purpose software. Get in touch if you need help building your system with one of these solutions.
The post How to use Lua for flexible configurations in Erlang and Elixir appeared first on Erlang Solutions.
The Ignite Realtime community is pleased to announce the release of Openfire 5.1.2, a maintenance update to our open-source XMPP real-time communication server!
This release primarily updates third-party libraries following reports of vulnerabilities in those dependencies. There’s no indication that Openfire itself was vulnerable to any of the reported issues, but as a precaution and to keep our dependencies up to date, we’ve included the updated libraries in this release.
The full changelog has all the details, with 12 items resolved in total.
You can obtain Openfire 5.1.2 for your platform from its download page. The SHA-256 checksums for the release artifacts are:
bbe0e3bd7837aeb6a78a1878b3e23a53a61e99d827ec966ddc40500db416eb86 openfire-5.1.2-1.noarch.rpm
354e14ebf80c03eee05649a43d52338a77797bb41529a4f1486c49e784119f9d openfire_5.1.2_all.deb
7da2582b4bc6c25640deecd9f22cdc1278c989cd39c0a8832fc15b3aeb208235 openfire_5_1_2.dmg
b32e33bc0e5305dcac308a82447f970b59a095f1edbb425d81ac93203db2f667 openfire_5_1_2.exe
0f45006e319fb36bf869ef9ca3a3bef930a1bd683a792883b0b0b4c9a3974680 openfire_5_1_2.tar.gz
5e52d57fb9e20235ed31a6809c3ecd139fb6583c0b24575cab3459ad4ff0b0a1 openfire_5_1_2_x64.exe
4f2bcdde684ed157f19df8a792db4f907a62864baa8a6c982c970dfaa43d0421 openfire_5_1_2.zip
We’d love to hear from you! Please join our community forum or group chat and let us know what you think!
For other release announcements and news follow us on Mastodon or X
2 posts - 2 participants
From 15th to 16th August 2026, XMPP Community members will be present with an XMPP booth at FrOSCon 2026, the Free and Open Source Software Conference!
As every year, the FrOSCon e.V. (club) organises together with the computer science department of the University of Applied Sciences Bonn-Rhein-Sieg in Sankt Augustin, Germany. The venue is free of cost and no registration is required.
Find the program and more details on their webpage: froscon.org
Come by and have a personal chat with XMPP Community members and XMPP developers at the XMPP (Jabber) booth!
See you soon at FrOSCon 2026!
XMPP Community members will have a stand at OmniOpenCon on the 16th-17th October 2026.
OmniOpenCon is an annual FOSS conference held at Politehnica University of Bucharest in Bucharest, Romania. OmniOpenCon is free to attend, and is well linked by public transport making it very easy to reach from anywhere in the city.
Currently a lot of XMPP community presence in Europe has been within Western Europe. Branching out to Eastern Europe will hopefully draw new members into the XMPP community and spread secure, open and decentralised messaging to more people!
We are looking for volunteers if anyone is free to attend.
Please join the OmniOpenCon MUC if you are attending the conference: omniopencon@muc.mohrie.net
Contents:
If you upgrade ejabberd from a previous release to 26.07, there are no changes in SQL schemas, but there is one for ejabberd Business Edition (see below).
This release contains fixes for those security issues:
mod_caps persistent cache can be poisoned by using legacy version requests.This cache was only used to determine list of nodes that should trigger notifications in PubSub presence-based delivery.mod_pubsub handling of paging requests.mod_http_api.ejabberd_oauth in http listener). As part of this fix we changed oauth_client_id_check default value to db.oauth_add_client_password or oauth_add_client_implicit commands.mod_bosh, captcha, mod_auth_fast, mod_http_upload and mod_invites used not cryptographically strong random number generators.mod_http_upload didn&apost have XSS prevention headers.mod_conversejs allowed putting unescaped value from url in page content.mod_invites now includes a startpage where regular users can use their account credentials to generate new account creation invites.
This is useful for people using XMPP clients that do no support that feature. The URL of that page is the root of mod_invites; you can find a link to that page in the bottom of WebAdmin left menu.
There are also new WebAdmin pages to view the existing invites, generate new invites, expire, delete ... Go and take a look at WebAdmin > "Virtual Hosts" > one of your hosts > "Invites"
There is also a new command generate_reset_token that returns an URI with a password reset token, just like for the invite tokens. This functionnality can also be found in the WebAdmin.
ConverseJS published version 14.0.0 recently, and it requires some changes in the web server. In this sense, mod_conversejs is updated to support ConverseJS 14, and also got other minor cosmetic improvements.
Are you compiling ejabberd with Erlang/OTP 25 or 26? Then please try to update to Erlang/OTP 27, 28, or 29. For example, the ejabberd installers are compiled with Erlang/OTP 28.5.0.4.
ejabberd supports compilation with Erlang/OTP 25 and 26, and those versions are still tested in runtime.yml and weekly.yml, but those Erlang/OTP versions are not actively maintained anymore by Erlang/OTP.
Following the erlang security recommendation to Use Actively Maintained Versions of Erlang/OTP, from now ejabberd softly rejects compilation with Erlang/OTP lower than 27.
What does softly mean? If you really want to compile ejabberd with Erlang/OTP lower than 27 at your own risk, you can bypass that soft requirement by defining this option (Erlang/OTP 25.0 included Erlang Run-Time System 13.0, and that is the number to provide in that option):
./configure --with-min-erlang=13.0
ejabberd source code includes Rebar and Rebar3 binaries, in case you don&apost have installed in your system. But those programs only support four Erlang releases (26 up to 29).
If you want to compile ejabberd with Erlang 25, then you need to grab a compatible Rebar3 (or Rebar) binary: either install one from your operating system, or you can download the old binaries included with ejabberd 26.04 (those still supported Erlang 25):
https://github.com/processone/ejabberd/raw/26.04/rebar
https://github.com/processone/ejabberd/raw/26.04/rebar3
This release contains fixes for those issues:
oauth_client_id_check default value to dboauth_add_client_password or oauth_add_client_implicit commands.delete_old_messages_batch command when used on pgsqlexport_db_ext which allows exporting db content to json filesroom_unused_* commands when room hibernation is configuredmod_auth_fast: Fixes exception for session that didn&apost set user agentmod_invites: Add page for creating invites.mod_invites: Fix generation of CSRF tokens.mod_invites: Update to changes in latest XEP-0401mod_http_upload: Attach custom headers from config when serving files.https://github.com/processone/ejabberd/compare/26.04...26.07
We would like to thank for the security reports provided by:
the contributions to the source code by:
mod_invitesand the translation by:
And also to all the people contributing in the ejabberd chatroom, issue tracker...
Customers of the ejabberd Business Edition, in addition to all those bugfixes, also get the following changes:
This release modifies the push_customizations table in the SQL database schemas to support the new push notifications mute option (see below). This task is performed automatically by ejabberd by default.
However, if your configuration file has disabled update_sql_schema toplevel option, you must perform the SQL schema update manually yourself. Those instructions are valid for MySQL and PostgreSQL, both default and new schemas:
ALTER TABLE push_customizations MODIFY COLUMN mute smallint;
ALTER TABLE push_customizations ALTER COLUMN mute TYPE smallint USING mute::int;
user_push_state commanduser_push_state command.mod_webpush reject tokens that don&apost have correct format.mod_webpush key parser.Add a new local_health_status command to return information about local node state.
require_xmpp_addr that forces client certs to have XmppAddr.recognize_email_addr option for mod_crl/mod_ocsp to add emailAddress from cert subject to be used beside xmppAddr for matching against provided user id.mam_p1db serialization properly serialize MUC archives.roster_p1db serialization.fast_tokens in p1db backendMake join_cluster robust to race conditions on leave_cluster.
sqlite backend has been fixed in Docker image.
All fluux.io instances have been updated before the release of the 26.07 with the latest security fixes.
As usual, the release is tagged in the Git source code repository on GitHub.
The source package and installers are available in ejabberd Downloads page. To check the *.asc signature files, see How to verify ProcessOne downloads integrity.
For convenience, there are alternative download locations like the ejabberd DEB/RPM Packages Repository and the GitHub Release / Tags.
The ecs container image is available in docker.io/ejabberd/ecs and ghcr.io/processone/ecs. The alternative ejabberd container image is available in ghcr.io/processone/ejabberd.
If you consider that you&aposve found a bug, please search or fill a bug report on GitHub Issues.
This year’s edition of FOSSY, the fourth Free and Open Source Software Yearly conference, will take place during the month of August, from Thursday 6th to Sunday 9th 2026 at the University of British Columbia, Vancouver, Canada. And as always, there will be an XMPP Track.
As announced by the organization in the official Conference Schedule, the XMPP track wil be composed of seven presentations.
Three presentations will take place during Saturday, August 8th at MCLD 3038:
| Presentation | Speaker | Time |
|---|---|---|
| Encrypted messaging interoperability with hybrid bridges in XMPP | Marvin W. | 2:00 PM to 2:45 PM PDT. |
| Beyond Chat: Delivering mini "apps" with great UI | Stephen Paul Weber | 3:00 PM to 3:45 PM PDT. |
| "Beautiful XMPP Testing" revisited – How to Overcome the Mind-Body Duality by Staring at XML | Phillip Davis | 4:30 PM to 5:15 PM PDT. |
Followed by another four presentations during Sunday, August 9th at MCLD 3038:
| Presentation | Speaker | Time |
|---|---|---|
| When Data Gets Heavy: Voice and Video for Large Groups | Christopher Vollick | 10:45 AM to 11:30 AM PDT. |
| UnifiedPush - Push notifications. Decentralized and Open Source | Daniel Gultsch | 11:45 AM to 12:30 PM PDT. |
| Adventures in Onboarding: Episode V - The Server Goes Down | Gideon Mayhak | 2:00 PM to 2:45 PM PDT. |
| Snikket: Behind the scenes of our on-demand XMPP server hosting | Matthew Wild | 3:00 PM to 3:45 PM PDT. |
The FOSSY organizers kindly invite you to join the event and will be pleased to see you there. Don’t miss it out!
The largest piece of work in this release is the kind you only notice when it is absent.
These are the paths we found and could reproduce. Archive sync has a long tail, and how it behaves depends a good deal on which server you are talking to. If you still see a conversation with history missing, we would love to hear about it.
Twenty-three fixes in this release. The ones most likely to have affected you:
Smaller ones round out the release: the quoted-message and reply cards stay visually distinct when a message is selected, your own message group re-fits its width once an image inside it finishes loading, the typing indicator is centered between the last message and the composer, and the macOS traffic-light buttons stay centered in the app bar.
Fluux Messenger 0.17.2 is available for macOS, Windows, and Linux, or directly in your browser.
If you upgrade and something feels off, tell us. Bug reports and feature requests both go to GitHub Issues.
Kaidan 0.16.0 is out now! This release adds a media viewer, improves the emoji picker, and got a button to try another XMPP provider during registration. In addition, it adds support to record voice messages via long press. As always, it includes several smaller improvements and fixes.
Most of the work has been funded by NLnet via NGI Zero Commons Fund with public money provided by the European Commission.
Kaidan now opens a media viewer when you press an image, video, or audio file. That viewer allows to browse all media within a chat in chronological order. You will not need to scroll through the whole chat history anymore just to find a specific file.
The viewer displays information that only Kaidan is aware of, such as the description or the corresponding message’s body. Furthermore, you can jump to the message, open the containing folder, or delete the file. Music and videos can be directly played without opening them in an external app.
If you often send voice messages, you will enjoy the new voice message enhancement. With this new version, Kaidan makes it much easier for you to record and send a voice message.
Instead of pressing a button to start recording and pressing another button once you are finished, you can now simply press and hold the record button. As soon as you release the button, the voice message is sent. You can cancel the recording by swiping to the left.
Of course, the regular behavior is still available. You can always decide when to use which way to record your voice messages. Just start a regular recording with a normal press or the new one with a long press.
The improved emoji picker matches the look of the remaining app. It opens at the current cursor position while composing a message to quickly choose an emoji. You also can navigate through the emoji picker via keyboard and search emojis.
If you opt for creating an account automatically, a suitable XMPP provider is chosen depending on your system’s settings. But it can happen that the automatically chosen provider requires an unsolvable CAPTCHA or information you do not want to hand over, such as an email address.
Formerly, you needed to go back and open the automatic registration again to request an account from a different provider. Since that took some time, there is now a button to directly try another XMPP provider.
There are several other improvements. Have a look at the following changelog for more details.
Features:
Bugfixes:
Notes:
Or install Kaidan for your distribution:
So I was bored and I set up an XMPP server under 👆️.op-co.de, in addition to the one I already had under ツ.op-co.de. It worked with some clients and failed with others. But both ツ and 👆️ are valid Unicode 1.1 characters, so WTF? Buckle up (or better: put on your 🤿) for the 19-RFC deep dive...
...or skip right to the TL;DR.
Note: despite my generous use of Emojis throughout this post, none of it was generated by a text extruder machine. All words are the product of artisanal typing on my keyboard, and the Emojis were hand-selected from an Emoji-picker widget.
XMPP, the eXtensible Messaging and Presence Protocol, formerly known as Jabber®, defines its address format in RFC 7622.
An XMPP address (formerly known as Jabber® ID, or JID, as used in the RFC) has three parts:
jid = [ localpart "@" ] domainpart [ "/" resourcepart ]
The localpart is usually the username, but is not used when addressing a server.
The domainpart is the hostname or domain name, and can be a Unicode DNS
identifier, an IPv6 address in square brackets, or a legacy IP address. This
is the only mandatory part of a JID.
The resourcepart is the internal identifier of an individual client,
allowing a user to have multiple clients connected at the same time; it is
also used for the nickname in
XEP-0045: Multi-User Chat.
Each of these three parts must be valid UTF-8 and can be up to 1023 bytes (not characters!) in length.
Furthermore, there are restrictions for each part, for example:
localpart = 1*1023(userbyte)a "userbyte" is a byte used to represent a UTF-8 encoded Unicode code point that can be contained in a string that conforms to the
UsernameCaseMappedprofile of the PRECISIdentifierClassdefined in RFC 7613 [...]
Come again, please? Okay, let's take this apart, slowly.
a "userbyte" is a byte used to represent a UTF-8 encoded Unicode code point
This is a convoluted way to say that we accept up to 1023 bytes (not characters!) of valid UTF-8.
that can be contained in a string that conforms to the
UsernameCaseMappedprofile of the PRECISIdentifierClassdefined in RFC 7613
In addition to being valid UTF-8, it must also conform to the
IdentifierClass in PRECIS (RFC 7613).
PRECIS is the successor to Stringprep (RFC 3454, which we can ignore for now).
However, the PRECIS definition in RFC 7613 is obsoleted by RFC 8265, which we can't ignore and will have to take our character profiles and classes from.
So we need the IdentifierClass for the localpart, and furthermore
the FreeformClass for the resourcepart.
The earlier Stringprep approach explicitly defined its classes as valid ranges of Unicode code points (characters). However, given that Unicode is a living (versioned) standard, new characters (and new Emojis! 💡) get added every year. This left Stringprep in an uncomfortable place, forever hard-coded to the long-superseded 2002 Unicode 3.2 standard.
To allow for future compatibility, PRECIS took a different path. It describes an algorithm that can be applied to an individual Unicode character in order to determine whether it belongs to a certain PRECIS class.
The classes (IdentifierClass and FreeformClass) are defined in
RFC 8264, and the profiles (UsernameCasePreserved,
UsernameCaseMapped, OpaqueString, Stringprep) are defined in
RFC 8265.
Furthermore, PRECIS attempts to retain backward compatibility with earlier standards like IDNA2008, as well as with itself. If a certain character is "valid" under an earlier version of Unicode, PRECIS tries to ensure that it stays "valid" under later versions. The only explicit exception from this is that code points that were "undefined" in earlier Unicode versions can later be assigned and move to "valid" or "disallowed".
Each of these rules is applied to individual characters, or to character categories, as defined in RFC 5892: IDNA code points.
Given this toolset, we can now get back to the individual XMPP address parts.
localpart - the user nameAs stated in RFC 7622 above, the localpart must be...
a string that conforms to the UsernameCaseMapped profile of the PRECIS IdentifierClass
So we have the profile (UsernameCaseMapped) and the class
(IdentifierClass) to look up.
UsernameCaseMapped transformationThe UsernameCaseMapped profile in RFC 8265 performs some
normalization steps: it requires
decomposition of certain East Asian
characters, lowercasing, Unicode Normalization Form C, and application of the
Bidi rule.
Later, RFC 8265 § 3.3.2 says:
Ensure that the string consists only of Unicode code points that are explicitly allowed by the PRECIS IdentifierClass defined in Section 4.2 of [RFC8264].
IdentifierClass?RFC 8264 §4.2.1 defines the valid and disallowed character properties, as well as certain groups that require special treatment.
Valid identifiers contain "Code points traditionally used as letters and numbers in writing systems", the ASCII 7-bit characters U+0021 through U+007E, and a few characters that are only allowed in a certain context, like U+00B7 MIDDLE DOT which is only allowed inside the Catalan ela geminada "l·l".
The ツ character (U+30C4 KATAKANA LETTER TU) belongs to the "Letter, other" (Lo)
category of Unicode)
and thus is a valid letter character allowed in IdentifierClass. The 👆️
emoji (U+261D WHITE UP POINTING INDEX) belongs to the "Symbol, other" (So)
category with all the other Emojis. The whole "Symbol" category is disallowed
inside of IdentifierClass. Bummer. Sad trombone! 🎶
On the other hand, the "Nonspacing Mark" (Mn) category is allowed, and so ḩ̸̡͇͉̬̓͝e̷͙̪̯̬̬͍͒̂̓̽̀̄ ̵̨̨̪̯̞̠͒̐͘͝c̷͍͆o̸̡̢̥͌̒̌̀͜͜m̶̬̙̙̓̌͘͠ë̷́̉́͜t̴̍̔͜͠h̷̖̭̫̥̖̥͐͂͊͒̓.
localpart togetherSo essentially, PRECIS only allows the boring regular lowercase letters from any supported language, and none of the fun Emojis.
To add insult to injury, RFC 7622 §3.3.1 imposes further restrictions by disallowing some more fun characters:
" U+0022 (QUOTATION MARK)
& U+0026 (AMPERSAND)
' U+0027 (APOSTROPHE)
/ U+002F (SOLIDUS)
: U+003A (COLON)
< U+003C (LESS-THAN SIGN)
> U+003E (GREATER-THAN SIGN)
@ U+0040 (COMMERCIAL AT)
However, there are still a bunch of "funny" permitted characters left from the ASCII7 block:
!#$%()*+;=?[\]^`{|}
This leaves us with some valid old-school ASCII smiley user name options on the table:
;=)
B*}
And a bunch of Unicode letters that can be abused, with special thanks to Egyptian hieroglyphs:
| Symbol | Code Point | Name |
|---|---|---|
| ۃ | U+06C3 | ARABIC LETTER TEH MARBUTA GOAL |
| ツ | U+30C4 | KATAKANA LETTER TU |
| 𓀐 | U+13010 | EGYPTIAN HIEROGLYPH MAN WITH BLEEDING HEAD WOUND |
| 𓂸 | U+130B8 | EGYPTIAN HIEROGLYPH HUMAN PHALLUS |
| 𓂹 | U+130B9 | EGYPTIAN HIEROGLYPH ERECTILE DYSFUNCTION |
| 𓃂 | U+130C2 | EGYPTIAN HIEROGLYPH LEG SEVERED BY HAND GRENADE |
| 𓄀 | U+13100 | EGYPTIAN HIEROGLYPH ENRAGED YAXIM USER |
| 𓀬 | U+1302C | EGYPTIAN HIEROGLYPH CHUCK NORRIS RIDING ON TWO GIRAFFES |
Back to RFC 7622 §3.1:
domainpart = IP-literal / IPv4address / ifqdnthe "IPv4address" and "IP-literal" rules are defined in RFCs 3986 and 6874, respectively, and the first-match-wins (a.k.a. "greedy") algorithm described in Appendix B of RFC 3986 applies to the matching process
We will leave IP literals out... for now. Just a note that the format for IPv6
literals need to be enclosed in brackets and may contain a %zone postfix.
ifqdn = 1*1023(domainbyte)a "domainbyte" is a byte used to represent a UTF-8 encoded Unicode code point that can be contained in a string that conforms to RFC 5890
RFC 5890: IDNA Definitions and Document Framework is a new addition to our list. It is the "Definitions" part of the IDNA2008 ("Internationalized Domain Names for Applications", released in 2008) specification. The RFC 5892 we encountered earlier belongs to the same specification suite.
However, there is not a single "string" that "conforms" to RFC 5890. RFC 7622 §3.2.1 has a more precise requirement:
the string consists only of Unicode code points that are allowed in NR-LDH labels or U-labels as defined in RFC5890.
An NR-LDH (non-reserved letter, digit, hyphen) label is an ASCII label (not containing "special" Unicode characters) according to the "hostname" syntax defined in RFC 952 back in 1982.
The U-label definition can be found in RFC 5890 §2.3.2.1:
[A U-label] is also subject to the constraints about permitted characters that are specified in Section 4.2 of the Protocol document and the rules in the Sections 2 and 3 of the Tables document [...].
The links in the quoted paragraph are pointing to the wrong RFC, so I disarmed them in the quote above.
The normative RFC format before RFC 8650 (late 2019) was fixed-width ASCII, 58 lines, 72 characters with manual page breaks, designed to be printed by a 1982 line printer on US Legal (even though the PDF renderings are using US Letter). The markup in the HTML versions linked from this post is auto-generated from a semantic analysis of the normative ASCII documents.
The fixed-width fixed-page format is unreadable on mobile devices, and effectively trips up reflow algorithms. There used to be an alternative ebook rendering of RFCs that was the only useful way for people with bad eyes to read RFCs. It stopped rendering new documents in 2019 and was abandoned in 2022. Nobody cared.
The links above point to sections of RFC 5890, because the string parser saw "Section x.y.z" and assumed it to be a reference to section x.y.z of the current RFC. It was not. The links should go to RFC 5891 §4.2, RFC 5892 §2 and §3.
Let's get back to the U-label definition from RFC 5890 §2.3.2.1. It is a variant of the "IDNA-valid string":
For IDNA-aware applications, the three types of valid labels are "A-labels", "U-labels", and "NR-LDH labels" [...]
A string is "IDNA-valid" if it meets all of the requirements of these specifications for an IDNA label. [...]
[A U-label] is also subject to the constraints about permitted characters that are specified in Section 4.2 of the Protocol document and the rules in the Sections 2 and 3 of the Tables document [...].
So. Uhm. A U-label needs to be IDNA-valid, and an IDNA-valid string is either a U-label, an A-label or an NR-LDH label. This is not a recursive definition!
The referenced RFC 5891 §4.2 Permitted Character and Label Validation further clarifies:
The candidate Unicode string MUST NOT contain characters that appear in the "DISALLOWED" and "UNASSIGNED" lists specified in the Tables document.
Furthermore, it may not begin or end with a "-", and must not contain a "--" at the third position, in order to not be mixed up with A-labels.
An U-label can be up to 252 bytes (not characters!) long (§4.2), but its ASCII-compatible encoding (ACE / A-label) form must not exceed 63 ASCII characters (equal to bytes!). In addition, DNS limits the full hostname to 255 characters.
The set of valid characters is defined by RFC 5892 §2.
A minor detail that we omitted above, when talking about valid localpart
characters, was that RFC 8264 in fact does not define the
character categories, but instead contains references to the respective
subsections of RFC 5892 §2.
Despite of that, the valid character sets for localpart and domainpart are
not equal. ß U+00DF LATIN SMALL LETTER SHARP S is explicitly included for
U-labels (I haven't figured out why it would be disallowed though), as is 〇
U+3007 IDEOGRAPHIC NUMBER ZERO (Nl) (which is in the disallowed "Letter
Number" (Nl) category) and there is a number of other
exceptions.
Korean is restricted to modern Hangul syllable characters.
IDNA is using case folding to normalize the letter case. This matches the lowercase conversion of RFC 8265, except when it doesn't.
Furthermore, DNS Registries are allowed to restrict the valid characters for domain names, probably in order to limit homoglyph attacks.
domainpart togetherThere is a significant overlap between localpart and domainpart. However,
- U+002D HYPHEN-MINUS is the only special character from the ASCII set
that's still allowed, and it may not appear in all positions.
Lowercase letters (or uppercase Cherokee) and numbers are allowed, ASCII smileys are not. Egyptian hieroglyphs and diacritics are still in the game for subdomains, or if your Registry allows them on the domain name.
To prove a point, this post is reachable via ḧ̴͖́e̷͚̿-̸̧͘c̴͖͌o̴̻̊m̷͕̂e̷͔͊t̷͚̊h̵̦̄.op-co.de and there is an XMPP server, too:
yaxim screenshot of a Prosody server running on the zalgo domain
RFC 7622 §3.4 is
where the resourcepart gets defined:
The resourcepart of a JID is an instance of the
OpaqueStringprofile of the PRECISFreeformClass, which is specified in RFC7613.
This is actually the same mechanism as with localpart, just with a different
profile and a different class.
FreeformClassRFC 8264 §4.3.1
defines the valid FreeformClass code points. This includes all traditional
letters and numbers, printable ASCII (U+0021 through U+007E), punctuation,
spaces, and 🚨 symbols!!️ 🤯 Finally!
On top, OpaqueString will apply
some normalization,
including the conversion of all non-ASCII whitespace into U+0020. Character
case will be retained.
RFC 7622 §3.4.1
also has a note regarding the use of resourcepart for nicknames:
In some contexts, it might be appropriate to apply more restrictive rules to the preparation, enforcement, and comparison of XMPP resourceparts. For example, in XMPP Multi-User Chat [XEP-0045] it might be appropriate to apply the rules specified in [PRECIS-Nickname].
"it might be appropriate" is not normative language, right? The Nickname
profile is derived from FreeformClass and is a mapping that removes leading
and trailing whitespace, and reduces consecutive whitespace into one U+0020.
And it applies the lowercase transformation for nickname comparisons, to
disallow multiple users to have the same case-normalized nickname.
That's it.
So you can have all the Emojis as your nickname, right? RIGHT?
Jabber was born in 1999. The first formal XMPP specification was RFC 3920 in 2004. Over the decades, both the XMPP specification and the Unicode standard evolved, thus also changing what is considered a valid XMPP address. Implementations that we need to interoperate with might be running on some older version of the specification, and accept a different subset of "valid" Unicode characters.
Let's sort this out as well!
RFC 3920 §3 Addressing Scheme defines the JID syntax:
domainpart) is an IDNA
string according to RFC 3490 (IDNA2003) and must match the
Nameprep profile defined in RFC 3491.localpart) must match the Nodeprep profile defined
in Appendix A.resourcepart) must match the Resourceprep
profile from
Appendix B.Nameprep, Nodeprep and Resourceprep are profiles of Stringprep (from
RFC 3454, which contains
tables with allowed and prohibited characters, as well as character mappings
to perform, based on Unicode 3.2).
Each of the profiles defines the set of tables and steps to apply. For
example, the Nameprep processing consists of three steps:
� U+FFFD REPLACEMENT
CHARACTER) and orientation markersThe handling of IPv6 literals in RFC3920 assumes that they are inserted
verbatim, with no surrounding [] and no %zone identifier.
Nodeprep is similar to Nameprep, but disallows control characters, as well
as the forbidden characters we know from localpart, namely "&'/:<>@
Resourceprep is also similar to Nameprep but allows ASCII whitespace and
doesn't perform case folding, allowing for uppercase characters.
But on the good side, neither IDNA2003 nor the Stringprep profiles disallow the use of Emojis (that are part of Unicode 3.2) in domain names! 🎉
However, the experience of operating IDNA2003 in the wild for a few years led to the documentation of 37 pages (measured in 72-character ASCII on US Legal) of issues and shortcomings, documented in RFC 4690 and including this section:
5.1.1. Elimination of All Non-Language Characters
Unicode characters that are not needed to write words or numbers in any of the world's languages should be eliminated from the list of characters that are appropriate in DNS labels. In addition to such characters as those used for box-drawing and sentence punctuation, this should exclude punctuation for word structure and other delimiters. While DNS labels may conveniently be used to express words in many circumstances, the goal is not to express words (or sentences or phrases), but to permit the creation of unambiguous labels with good mnemonic value.
I guess that Emojis lack good mnemonic value. RIP. 🪦
The result of this analysis was the replacement of IDNA2003 with IDNA2008 in... you guessed it... 2010! To be fair, the IDNA2008 suite was "largely completed in 2008", and got submitted to the IETF in October 2008.
The IDNA2008 RFC collection obsoleted the previous RFCs, and thus the stricter
domainpart requirements (no Emojis) were automatically turned into law in
2010, without having to change any of the XMPP specifications.
But we can still have Emojis in usernames and nicknames, right? 🥹
The update to IDNA made Stringprep obsolete, and prompted the creation of the Preparation and Comparison of Internationalized Strings Working Group at the IETF.
While the WG was working on the PRECIS specifications, the XMPP core specifications got a major overhaul in 2011. As part of that, the address format was updated and separated into its own document, RFC 6122:
Because all other aspects of revised documentation for XMPP have been incorporated into [XMPP], the XMPP Working Group decided to temporarily split the XMPP address format into a separate document so as not to significantly delay publication of improved documentation for XMPP. It is expected that this document will be obsoleted as soon as work on a new approach to preparation and comparison of internationalized addresses has been completed.
The updated address format still relied on IDNA2003, but developers were encouraged to look at IDNA2008.
RFC 6122 furthermore introduced the localpart, domainpart and
resourcepart names and changed IPv6 literals to use the bracketed
IP-literal syntax from RFC 3986.
As announced in the intro of RFC 6122, it was soon replaced by RFC 7622, which we might vaguely remember from the beginning of this post. It was published in 2015, based on the still fresh RFC 7613 PRECIS specification.
The PRECIS suite and the updated XMPP address format introduced case folding,
replaced the Stringprep profiles with the PRECIS classes, profiles and
categories explained above, and effectively disallowed Emojis in the
localpart and domainpart of XMPP addresses (following the IDNA2008
insights).
As mentioned before, RFC 7613 was obsoleted by RFC 8265, which corrected a few things and went from case folding to lowercase again. 🤷
This happened in 2017 and, together with RFC 8266 (Nicknames) is the end of the evolution of the RFCs needed to understand XMPP addresses.
So you just told me that Emojis in nicknames are still allowed, yes?
The IETF is about "rough consensus and running code". We've seen the consensus and how it changed over two decades, but in the end it's the running code that will say "no" when you try to butt dial an XMPP address.
Something that you enter might go through up to five different hops (and different XMPP implementations; I'm omitting protocol bridges, but the point should be clear):
Your client is the easiest part, as it can simply reject forwarding something it disagrees with. If the "Add contact" button is greyed out, you've arrived at a dead-end. ⛔
The following hops on the path can't grey out the button if they consider your XMPP address, coming through an XML stream, as invalid. According to RFC 6120, they have to treat it as a (recoverable) stanza-related error, and reject the respective XML stanza (and not terminate the XML stream):
8.3.3.8.
jid-malformedThe sending entity has provided (e.g., during resource binding) or communicated (e.g., in the 'to' address of a stanza) an XMPP address or aspect thereof that violates the rules defined in [XMPP‐ADDR]; the associated error type SHOULD be "modify".
So if a recipient disagrees about the PRECIS / IDNA version with your client or your server, it will reject the respective stanza before it can be processed.
When joining a MUC, you send a presence stanza to your occupant address,
constructed by appending your nickname as the resourcepart to the room
address. If you choose an evil Emoji nickname and the room rejects it, it will
send an error response, and you won't be able to join the room.
Now if the room does accept your nickname, it will forward the presence, sending it from your occupant address, to all other occupants.
I first ran into this issue, not knowing much about IDNA, PRECIS or Stringprep, back in 2017:
11:28:50 ---> 🤖 joined the room
11:28:50 <--- T....s has left the room (Kicked: jid malformed: The source address
is invalid: prosody@conference.prosody.im/🤖)
11:28:51 <--- N..........s has left the room (Kicked: jid malformed)
11:28:51 <--- d......n has left the room (Kicked: jid malformed: The source address
is invalid: prosody@conference.prosody.im/🤖)
11:28:51 <--- d.......o has left the room (Kicked: jid malformed: The source address
is invalid: prosody@conference.prosody.im/🤖)
11:28:51 <--- a..v has left the room (Disconnected: not-well-formed)
11:29:08 ---> a..v joined the room
11:32:18 ---> T....s joined the room
11:32:18 <--- T....s has left the room (Kicked: jid malformed: The source address
is invalid: prosody@conference.prosody.im/🤖)
Any downstream server or client that does not accept this occupant presence will send a stanza error back to the MUC. The MUC will treat that error as a non-recoverable session error and remove the respective occupants.
As long as you stay in the room, the other clients will repeatedly reconnect, receive your presence, and get kicked out. If you send a message to the room, it will get pushed to joining clients as part of the room history even after you leave.
Today, the situation is only slightly different:
00:14:39 ---> 🤖 joined the room
00:14:39 <--- H....r (....) has left the room due to an error
(Kicked: bad request)
00:14:39 <--- c...........s (Monocles) has left the room due to an error
(Kicked: bad request)
00:14:39 <--- p......d (Conversations) has left the room due to an error
(Kicked: bad request)
00:14:39 <--- E..a (Cheogram) has left the room due to an error
(Kicked: bad request)
00:14:39 <--- m.....x (Conversations) has left the room due to an error
(Kicked: bad request)
00:14:39 <--- y..h (Conversations) has left the room due to an error
(Kicked: bad request)
00:14:39 <--- b.....a (.../Conversations....) has left the room due to an error
(Kicked: bad request)
Most of the affected users seem to be running Conversations or its forks Cheogram and Monocles, and the clients(?) responded to the presence with a "bad-request" error.
In addition to that issue, ejabberd sends the error response over the wrong half of the server-to-server stream, so:
Jul 12 01:19:36 s2sout5e57d6de9d50 debug Received[s2sout]: <presence to='test@chat.yax.im/🤖' type='error' id='noLNT-110871' from='georg@conversations.im/Conversations.jxm9v159lx' xml:lang='de-DE'>
Jul 12 01:19:36 stanzarouter warn Received a stanza claiming to be from conversations.im, over a stream authed for chat.yax.im!
Jul 12 01:19:36 s2sout5e57d6de9d50 debug Disconnecting chat.yax.im->conversations.im[s2sout], <stream:error> is: <stream:error><not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error>
So it seems like there is a bit of inertia with implementations to follow a fifteen years old specification update. This warrants a look at the major implementations.
Scroll down for the summary table.
According to the s.j.n stats, the top 5 server implementations on the federated XMPP network are Prosody (60%), ejabberd (24%), Spectrum (6%), biboumi (4%) and "Multi User Chat" (1%). And while biboumi is the only one without its own .IM domain, we can still exclude it and Spectrum from the list, as they are bridges to other networks and need to adhere to the limitations of those networks. "Multi User Chat" is in fact the MUC component of the Tigase server.
A cross-match with the servers connected to yax.im also yields similar results, and adds Openfire as a candidate with 1.5% market share.
Lua
isn't exactly friends with Unicode,
so prosody went for a manual approach and implemented Stringprep in
encodings.c
using either ICU or
libidn, based on a compile-time switch.
The binary packages built by the prosody team use ICU, so we'll take that for
the comparison.
ICU (International Components for Unicode) has a very turbulent history - initiated at a spin-off from Apple and IBM, and written in Java, the first version got integrated into the Java SDK in 1997, then developed in parallel and ported from Java to C++ and C. prosody is using the C version. ICU supports IDNA2008, which prosody started using in 2019. However, ICU only supports Stringprep, not PRECIS (probably due to the fact that Stringprep was a required part of IDNA2003).
libidn on the other hand started out as libstringprep, and supports IDNA2003 and Stringprep. libidn2 was created to support IDNA2008, but it removed Stringprep support, so can't be used as a drop-in replacement.
You have the choice between IDNA2003 with Stringprep and IDNA2008 without PRECIS.
ejabberd is written in Erlang, a language that's as powerful as it is obscure. The ejabberd developers have implemented their own stringprep library and use erlang-idna which supports both IDNA2003 and IDNA2008, but haven't tackled PRECIS.
Tigase is written in Java and seems to have forked and heavily reformatted the December 2004 libidn 0.5.12 release. There is no mention of IDNA2008, nor of PRECIS in the source, so I would assume IDNA2003 and Stringprep.
Openfire uses Tinder for the XMPP stanzas, and that makes use of libidn 1.35. As this is not libidn2, Openfire is at IDNA2003 and Stringprep. But there is an abandoned half-finished PR to implement PRECIS!
According to the JabberFR client stats, the top 5 client implementations are:
Conversations is a modern Android client that's making use of jxmpp-stringprep-libidn, which is using libidn 1.15 which gives us IDNA2003 and Stringprep.
Gajim was in fact the client that told me that I'm holding it wrong and that made me write this blog post.
Gajim is using nbxmpp and nbxmpp is using precis-i18n, which implements the trifecta of 8264, 8265, and 8266! In addition, idna is used for full IDNA2008 support.
So far, Gajim is the only client that will allow Unicode >3.2 emoji in nicknames (and nowhere else)!
The authentication code is doing manual stringprep, but other than that there is no support for Stringprep or PRECIS. IDNA2008 is handled by the underlying iOS core library.
Pidgin. My nemesis. The formerly most-widely used XMPP client that made a generation of users believe that XMPP is stuck in 2004. Pidgin is using libpurple, which was famously called "a flock a zero days flying in formation" a decade ago.
A 2009 patch implemented IDNA2003 and Stringprep support based on libidn, and it seems to have survived in the 2.14 "stable" branch, which was last released in January 2025.
The 3.0 development branch does not contain any traces of IDNA, Stringprep or PRECIS.
Dino, a modern client written in Vala, uses a binding to
libICU, but without the UIDNA_USE_STD3_RULES flag that would enable
IDNA2008.
The analysis of the client and server implementations shows that most implementations lag behind by a decade. There are two notable exceptions: Gajim implements the current state-of-the-art, and Monal allows everything and lets the server sort things out.
| Implementation | username | hostname | nicknames |
|---|---|---|---|
| Servers | |||
| prosody | 👆️ Stringprep | ❌ IDNA2008 | 👆️ Stringprep |
| ejabberd | 👆️ Stringprep | ❌ IDNA2008 | 👆️ Stringprep |
| Tigase | 👆️ Stringprep | 👆️ IDNA2003 | 👆️ Stringprep |
| Openfire | 👆️ Stringprep | 👆️ IDNA2003 | 👆️ Stringprep |
| Clients | |||
| Conversations | 👆️ Stringprep | 👆️ IDNA2003 | 👆️ Stringprep |
| Gajim | ❌ PRECIS | ❌ IDNA2008 | 🤖 PRECIS |
| Monal | 🤖 anything goes | ❌ IDNA2008 | 🤖 anything goes |
| Pidgin | 👆️ Stringprep | 👆️ IDNA2003 | 👆️ Stringprep |
| Dino | 👆️ Stringprep | 👆️ IDNA2003 | 👆️ Stringprep |
❌ = not allowed | 👆️ = legacy Unicode 3.2 | 🤖 = modern Unicode
The original XMPP specification (2004-2010; IDNA2003 + Stringprep) didn't
forbid Emojis in any parts of an XMPP address, but was limited to Unicode 3.2,
which only had around 150 Emojis. xmpp:👆️@♻️.❤️/!?️
When IDNA2003 was replaced by IDNA2008 in 2010, hostnames were restricted to
characters from actual human languages. The two most widely deployed server
implementations enforce this limit, but might support pre-existing legacy
hostnames. xmpp:☹️@𓀐.𓂸/☢️
When the XMPP specification implemented PRECIS in 2017, usernames were also
limited to human languages, but the resource / nickname part was left
permissive, and opened up to all existing and future Unicode specifications.
xmpp:𓀬@ツ.ۃ/🤖
So after going through 22 years of development, 19 RFCs and 17 Unicode standards, I have to say: the internet was right and I was wrong. 👆️.op-co.de is not a valid JID, but it was unti 2010.
0.17.0 introduced synced read markers (XEP-0490), but we shipped a bug: the payload we published used the wrong shape, so other XMPP clients ignored our read state, and we ignored theirs. 0.17.1 sets this right:
New in this release: the Pure theme, in pure-black and pure-white variants. Flat, high-contrast chrome with no gradients or translucency, designed for OLED displays and e-ink screens.
There is also a new "Play notification sounds" toggle in Accessibility settings.
/nick to change your nickname. The change is reflected in the occupant list and as a timeline notice.The full list of changes is in the changelog on GitHub.
Fluux Messenger 0.17.1 is available for macOS, Windows, and Linux, or directly in your browser. As always, it works with any standards-compliant XMPP server, and it remains our day-to-day client at ProcessOne.
If you upgrade and something feels off, tell us. A lot of what shipped in this release started as a user report — bugs and ideas are welcome on GitHub Issues.
Here is a new version for slixmpp, the python XMPP library.
This release has one major deprecation, two bug fixes, several new features as well as plenty of improvements under the hood.
Thanks to everyone involved!
Using BaseXMPP.__getitem__, which usually translates to the xmpp["xep_XXXX"] pattern in the code, is now deprecated. The proper way is using the plugin attribute for the exact same effect: xmpp.plugin["xep_XXXX"]. This allows proper type checking of plugin usage.
The version in which this pattern will be removed is not set in stone yet, but it is recommended to use .plugin, which already works in previous slixmpp versions too.
The docs have been given quite a bit of love in this new release:
The Ignite Realtime community is pleased to announce the release of Openfire 5.1.1, a maintenance update to our open-source XMPP real-time communication server!
Following last month’s 5.1.0 feature release, we’ve been gathering feedback and tracking down the issues that inevitably surface once a bigger release meets the real world. Openfire 5.1.1 is the result: a focused round of bug fixes and improvements, with a particular emphasis on PubSub correctness and connection handling.
A good chunk of this release tidies up PubSub behaviour. We fixed excessive memory consumption caused by a bloated ofPubsubSubscription table (OF-3306), alongside various smaller issues related to pub/sub functionality.
Connection handling gets some attention too. We resolved a nasty case where IQBindHandler could busy-wait up to 20 seconds on a resource conflict, causing thread starvation and misbehaving (OF-3319), fixed a NullPointerException in outbound S2S DirectTLS connections (OF-3332), and a number of other networking-related issues.
Certificate SANs now encode IP addresses correctly as iPAddress rather than dNSName (OF-3324), which should fix an issue that popped up under certain network configurations with recent versions of the Conversations client.
We also cleaned up a couple of migration-related issues carried over from 5.1.0’s database work, such as XML properties failing to save during PBKDF2 migration (OF-3305). This should guard against accidental loss of the encryption keys, preventing installations that become effectively unusable when migration happens while the file system is in a faulty state.
The full changelog has all the details, with 24 items resolved in total.
You can obtain Openfire 5.1.1 for your platform from its download page. The sha256sum values for the release artifacts are:
dc887032619b7ecf66cc8c17dc5cedc13c2479525cd93b41e5d999e4ec942adf openfire-5.1.1-1.noarch.rpm
4f6c5ccfe44fdd494760ae5a6f00f971ea000ec6c69e1481d3546bed994598e2 openfire_5.1.1_all.deb
17eafa2641a5cbe226328d54e115fd1780a90d6fedb6d63d8bcea048f91f23ab openfire_5_1_1.dmg
68b69309f22435e4996b18b21a451d8c3b98a543aa8680436694bf4a235b8299 openfire_5_1_1.exe
d930be11c93c995ee0a045118d0539629bd27d983ad99e6f174ded6453612a0d openfire_5_1_1.tar.gz
b55659388274deedde92813ed830e1060c89b48fc3d61e6227c153bd4d96b57e openfire_5_1_1_x64.exe
9faa8900c8aa56822deb83c82339842794b6e2e58be61ca08dbdf948ee931cd6 openfire_5_1_1.zip
Many of the issues fixed in this release were reported by our community members, and several of those were instrumental in finding and fixing bugs and applying improvements. We greatly appreciate everyone’s feedback! We’d love to hear from you! Please join our community forum or group chat and let us know what you think!
For other release announcements and news follow us on Mastodon or X
2 posts - 2 participants
Fluux has a new face. The app icon, logo, and the entire interface have been redesigned around Aurora, a teal-to-violet gradient identity with display headings, softer avatar shapes, and frosted-glass modals.
Aurora is more than a color swap:
If you preferred the previous color scheme, it is still there as the "Indigo classic" theme.
We removed the standalone Events view. It was a place you had to remember to check; now events come to you, in context:
The result is fewer top-level destinations and less bookkeeping.
Press Cmd-K (Ctrl-K on Windows and Linux) to jump to any conversation or action. The palette puts your unread chats and mentioned rooms first, shows avatars and unread badges, and never proposes the conversation you are already reading.
The fixed 1,000-message cap on conversation history is gone. History is now a sliding window: scroll back as far as you like, and messages load incrementally from the local cache and the server archive (MAM). Combined with message-list virtualization, now on by default, long conversations stay fast, and typing no longer reflows the message list on every keystroke.
While we were in there, we fixed a long tail of scroll issues: new messages reliably stick to the bottom on WebKit, returning to a conversation restores exactly where you were reading (even deep in history), and jumping to a search result lands the message a third of the way down the viewport instead of hiding it under the date header.
Read markers now sync across devices (XEP-0490). If you read a conversation on your desktop, it opens at the right position on your laptop, and its notification is dismissed. Together with live archive sync and the carbons and MAM work from previous releases, your devices now agree on what you have read, what is pending, and what is filed away.
For those who administer their own ejabberd server, the ad-hoc command list is gone, replaced by purpose-built screens: a server overview dashboard, a searchable user list with online status and last login, a redesigned user detail view with a Ban account action, and a mobile launchpad. Only a few server admin commands are available so far, but we plan to grow the list with each new version.
Almost 300 commits went into this release, so the full list is long. Some favorites: your own encrypted messages now show their real trust level instead of a grey lock under some circumstances; whispers in group chats keep their corrections and reactions private; animated avatars are frozen so they stop competing for your attention, even if they are PNGs; reactions from ignored users are hidden; and rooms are sorted correctly the moment the app launches.
The complete list is in the changelog.
Fluux Messenger 0.17.0 is available for macOS, Windows, and Linux, or directly in your browser. As always, it works with any standards-compliant XMPP server, and it remains our day-to-day client at ProcessOne.
If you upgrade and something feels off, tell us. A lot of what shipped in this release started as a user report.
0.16.1 is our quick turnaround on that: a focused round offixes for the issues that have been reported, across encryption, connectivity,message history, and the desktop apps. Nothing dramatic here, we mainly want to show that we&aposre listening and addressing reported problems fast.
With people now encrypting real conversations, a few practical details surfaced —and they&aposre sorted:
Field reports pointed to a handful of connection scenarios, now improved:
And plenty of smaller refinements from everyday use: consistent empty-state icons, reply quotes that match the original sender&aposs color and render as nested bars, opening a contact profile no longer bouncing you back, link-preview images that retry once before hiding, a smoother composer resize, local JID validation on the login screen, and group-chat performance improvements on room join.
Thanks to everyone who reported issues and shared feedback – that&aposs exactly what shapes a release like this one. 0.16.1 is available now on our website, and the full changelog is on GitHub. Keep the reports coming!
Beyond that headline, the release also ships whisper messages, offline compose, unread badges, and a significant render-performance pass. Here is what changed and why.
When end-to-end encryption is on, your messages are scrambled on your device before they leave it. Your XMPP server relays ciphertext, but it cannot read your conversations, even if the server is compromised or an administrator goes looking. The same applies to any relay between servers.
Fluux implements OpenPGP for XMPP (XEP-0373, also called OX) — the modern redesign of OpenPGP encryption for XMPP. If you have come across XEP-0027 (the older "Current Jabber OpenPGP Usage"), that is a different, now-deprecated approach with well-documented weaknesses: no authenticated message envelope, signed-plaintext exposure, and no multi-device story. Fluux implements XEP-0373 only. It uses a proper content-encryption layer (XEP-0420) that authenticates the full message context and hides metadata from the server. It builds on the same OpenPGP cryptography used in encrypted email for decades, adapted correctly to instant messaging.
Once you enable encryption in Settings → Encryption, your key pair is generated and your public key is published to your server. From that point, whenever a contact also has encryption enabled, Fluux automatically starts encrypting messages to them. A lock icon above the composer confirms it. Nothing to configure per conversation.
Encrypted messages cover more than just text: reactions, edits, retractions, link previews, and file attachments all ride inside the same encrypted envelope. The server cannot see which emoji you reacted with, what you edited a message to say, or which files you shared.
The choice of OpenPGP as the first E2EE protocol was deliberate, and it comes down to four practical properties.
Multi-device without friction. A single OpenPGP key pair works across all your devices. You generate it once; every device you add simply loads the same key. There is no per-device key negotiation, no session bootstrapping required before encrypted history loads. This stands in contrast to ratchet-based protocols that issue a key per client installation and require all clients to exchange session state before decryption is possible.
Encrypted backup you control. When you click Back up in the encryption settings, Fluux generates a backup code — 24 upper-case characters drawn from an unambiguous alphabet (no O or 0), grouped into 4-character chunks with dashes (e.g. TWNK-KD5Y-MT3T-E1GS-DRDB-KVTW). This format follows XEP-0373 §5.4 and is accepted by other compliant clients such as Gajim. Fluux encrypts your secret key with this code and stores the encrypted blob on your own XMPP server under a private node only you can access. The code never leaves your device.
Your full history stays readable. Because every device shares the same key, messages encrypted months ago on one device remain decryptable on another. The server&aposs message archive (MAM) becomes a true history, not a graveyard of ciphertext only the originating device can open. Outgoing messages are also encrypted to your own key, so your sent history is fully accessible on every device.
Simple, comprehensible verification. Trust On First Use (TOFU) gives you safe, convenient defaults. When you want a stronger guarantee, you compare fingerprints out-of-band, i.e. in person, over a phone call, on a separate verified channel. The fingerprint lives in the tooltip on the lock icon and in Settings → Encryption. You verify once per contact, per key, and you are done.
It is now possible to reply privately to a single occupant of a room without leaving the room. These whisper messages follow XEP-0045 §7.5 and appear in a distinct private thread so it is always clear they are not visible to the room at large.
Compose while offline. Messages typed before a connection is established are queued locally and sent as soon as the session comes online.
Connection status banner. A banner now appears while the app is reconnecting, so you always know when you are temporarily offline rather than wondering whether a message failed to send.
Render-performance pass. This release eliminates a class of re-render storms that affected the conversation list, command palette, room config modals, occupant panel, roster, search, and individual message rows during background sync and group-chat presence churn. The result is a noticeably smoother experience on busy accounts.
XMPP Console cleanup. Stream Management packets are now hidden by default in the developer XMPP console. The toggle remains available for when you need to trace them.
The encryption engine is a plugin layer sitting between the XMPP client and the UI. OpenPGP is the first plugin loaded into that framework. Adding a second protocol means writing a new plugin that implements the same interface: key management, encrypt, decrypt, trust state. The rest of the app — message routing, UI indicators, backup flow — does not need to change.
This matters because no single protocol wins every use case. OpenPGP&aposs shared-key model is ideal for multi-device history access; ratchet-based protocols like OMEMO excel at forward secrecy on single-device deployments; MLS is designed for large groups where pairwise key agreement would be impractical. The plan is to support several, letting users and deployments choose.
The framework is in place; the question is which protocol to add second. OMEMO (XEP-0384) is the most widely deployed XMPP encryption protocol today. Adding it would maximise interoperability with existing XMPP clients. MLS (RFC 9420) is the newer IETF standard, designed from the ground up for multi-device and group scenarios, and is where the industry is heading long term.
We have not decided yet. If you have a strong opinion, whether you are running an ejabberd deployment or just a user who cares, we would genuinely like to hear it. Just comment under that blog post.
Full changelog on GitHub. Desktop builds for macOS, Windows, and Linux — plus the web version — are available on the ProcessOne website.
Over the past few months, our team has been exploring what happens when systems come under pressure.
Through a series of webinars, we’ve looked at everything from concurrency in the BEAM to traffic spikes, real-time communication platforms, and resilient system design.
Maybe you’ve been following along, or maybe one or two of these webinars slipped past you. Either way, this is a chance to catch up on the ideas shaping how modern platforms are built and scaled.
Modern systems can handle huge amounts of concurrent work. But sooner or later, every system reaches a point where performance starts to suffer.
In "Concurrency, Understanding the BEAM Limits", Lorena Mireles Rivero explores how concurrency works inside the BEAM and where those limits begin to appear. Using examples from web applications and e-commerce platforms, she looks at how schedulers, mailboxes, CPU usage, and latency behave under load.
The session also explores common signs of system saturation and practical ways to keep applications running smoothly as demand grows.
Watch the webinar to learn more about concurrency in the BEAM and how to build systems that perform under pressure.
Real-time platforms don’t get a second chance. When demand spikes, messages still need to be delivered instantly and reliably.
In this webinar, Bartłomiej Górny explores what happens when systems are pushed to their limits. He looks at common bottlenecks, overloaded services, and how failures can spread across a platform when demand suddenly increases.
The session also covers practical approaches to scaling real-time systems, from service decoupling and back pressure to monitoring and load testing.
Watch the webinar to learn how real-time platforms can stay reliable during periods of peak demand.
Traffic doesn’t always increase gradually. Sometimes it arrives all at once.
In this session, Camjar Djoweini explores what happens when systems come under sudden pressure and why failures can quickly spread across services. He looks at where problems typically start and what makes some architectures more resilient than others.
The webinar focuses on designing systems that can absorb spikes, tolerate failures, and continue operating when conditions become unpredictable.
Explore the webinar to learn more about building resilient systems that stay online under pressure.
For gaming, betting, and entertainment platforms, traffic spikes are part of everyday life. The challenge is making sure users never notice them.
In this webinar, Lee Sigauke explores why systems fail during sudden surges in demand and how teams can build platforms that remain reliable under pressure. Drawing on principles from transactional systems in Erlang and Elixir, she shows how concurrency-first design helps systems cope with unpredictable workloads.
The session covers common failure patterns, resilience at scale, and practical ways to build platforms that continue performing when demand reaches its peak.
Watch the webinar to see how concurrency-first design helps platforms remain reliable when traffic surges.
That wraps up our latest webinar round-up. We hope this guide helps you catch up on some of the ideas we’ve been exploring over the past few months.
If something here has sparked your interest, whether it’s concurrency in the BEAM, building resilient architectures, or keeping platforms reliable during periods of peak demand, we’d love to continue the conversation. So get in touch.
Here’s to building systems that stay stable, scalable, and ready for whatever comes next.
The post Erlang Solutions Webinar Round-Up appeared first on Erlang Solutions.
Here is a new version for slixmpp, the python XMPP library.
This release has one specific breaking change and two new XEP plugins. Thanks to everyone involved!
The get_certs method on XEP_0257 is now an async function, which breaks compatibility with previous usages.
The Ignite Realtime community is pleased to announce the release of Openfire 5.1.0, the latest version of our open-source XMPP real-time communication server!
Since the 5.0.0 release, now over 11 months ago, we’ve kept the 5.0.x branch stable and maintained, but have also been working on the next set of bigger changes. With this release, those have (finally - sorry for the wait!) been made available. If you’ve been following along in the chat or forums you might have seen pieces of it being put together: the channel binding work, the DNS improvements, the new database experiments have been in the works for quite some time, and have seen quite some discussion and collaboration. Let me give you an overview of what is included with the 5.1.0 release.
The biggest theme is security. With generous support from NLnet Foundation :two_hearts: we’ve implemented SASL channel binding (OF-2694, OF-2879), which ties authentication to the underlying TLS connection and closes the door on a class of man-in-the-middle attack that has been observed against real XMPP servers in the wild. While we were in that part of the codebase, we also audited the encryption utilities, and found a few things worth fixing. A hardcoded AES initialisation vector (OF-3074), a single-round unsalted SHA-1 used for Blowfish key derivation (OF-3075), CBC-mode padding that was susceptible to oracle attacks (OF-3077), and timing side-channels in SCRAM-SHA-1 authentication (OF-3257, OF-3258). None of these were discovered under active exploitation, but they’re the kind of thing that shouldn’t be there, and now they’re not. We’ve also tightened up certificate identity handling (OF-3122), SASL mechanism enforcement (OF-3273), and login throttling (OF-3262), and added proper support for trusted reverse proxy configuration (OF-3260, OF-3261).
There’s also a performance fix that deserves a mention. Community members reported this issue in the PubSub functionality: after investigation, we found a method in the persistence code doing a full linear scan of every node in memory for each row it processed from the database (OF-3196). That’s O(n2), which is fine at small scale and quietly catastrophic at large scale. On a deployment with around 600,000 pubsub nodes it was causing startup times of over two hours. The fix was not much more than a one-line change. If you’ve ever accepted a very long Openfire startup as just a fact of life, this release is for you. Alongside that, blocking operations have been moved off Netty’s event loop threads (OF-3176) to improve responsiveness under load, and we’ve upgraded to Netty 4.2 (OF-2957).
5.1.0 also brings some ecosystem-related updates to Openfire. Java 25 is supported (OF-3210), and three new databases join the supported lineup:
Support for these has not landed in most plugins yet, but we’ll work on that in the coming time. In the mean time, please try them out, and tell us what you think!
On the protocol side, Openfire now handles XEP-0398 (avatar synchronisation between XEP-0084 and vCard-based avatars, OF-2034), and provides a proper API for Service Discovery Extensions (OF-3188) so plugins no longer need to intercept IQ stanzas to enrich discovery responses. For operators, there’s a new diagnostics page for failed S2S connections (OF-3037), a UI for managing DNS overrides (OF-3244), configurable rate limiting for incoming connections (OF-3170), and a Docker healthcheck (OF-3184).
The bug fix list is long, but a few stand out: orphaned S2S routes that caused silent packet loss (OF-3193, OF-3201); encrypted properties being silently stored in plaintext after XML-to-database migration (OF-3296); plugin reload failures on Windows (OF-3208); and chatroom subjects not being delivered on join in certain conditions (OF-3131).
The full changelog lists 121 items resolved!
You can obtain Openfire 5.1.0 for your platform from its download page. The sha256sum values for the release artefacts are:
0686b30d4fb5e6f7c43bff7071ac425e45a19bbd528e301df065ef8d60355ef5 openfire-5.1.0-1.noarch.rpm
90b21993ba65d98357154183fd12e938547e68cbc59301f69b8506f483580269 openfire_5.1.0_all.deb
5fff05c4a689ae3431d5578f594e37cf7a68a2c0f36380b76d132d79217913c0 openfire_5_1_0.dmg
f72d766957eb09bedcbe8a5f64c38db85684af62bf5282534a162385f7b449ed openfire_5_1_0.exe
0cc848b56339f07fdcbcbb92dea73a35c00661576d68f1908640ecf7c3b6febc openfire_5_1_0.tar.gz
a830b0451770d6c8f8db81b3584299f54c48ca8c6d4bf42671325fef0b74c878 openfire_5_1_0_x64.exe
8b3f30505b3996b4b8261a99710ac2387131dac9b5a75fbbf65e9e3419aa22f5 openfire_5_1_0.zip
We’d love to hear from you! Please join our community forum or group chat and let us know what you think!
For other release announcements and news follow us on Mastodon or X
5 posts - 5 participants
We are pleased to announce a new minor release from our stable branch.
This release fixes a handful of bugs which were discovered and fixed since the 13.0.5 release. Most of these are minor, but a few of them are important fixes.
A summary of changes in this release:
As usual, download instructions for many platforms can be found on our download page
If you have any questions, comments or other issues with this release, let us know!
Poezio is a terminal-based XMPP client which aims to replicate the feeling of terminal-based IRC clients such as irssi or weechat; to this end, poezio originally only supported multi-user chats and anonymous authentication.
This new release has mostly internal improvements, but a few improvements and fixes as well.
Thanks to all contributors and users!
The hide_exit_join option is meant to avoid displaying all join/part presences since the MUC (XEP-0045) behaviour is heavily presence-based for now.
It is an integer, which can be -1 (display all exit/joins), 0 (display none), and a positive value n (display anything inactive for more than n seconds).
How this worked previously is that poezio did not even add join/exit messages if they matched the criteria, making its a bit difficult if you both did not want to clutter your screen with mostly irrelevant information, while at the same time being able to view them – e.g. if you are moderating the room –.
Now the positive value’s quirky behavior stays the same, but in the case of -1 or 0, the join/exit messages will be kept in the buffer at all times, but not displayed when the value is 0. This option can be set at runtime, and the effects are instantaneous.
Since this is a special kind of value and not a boolean, /toggle will not work on it, therefore two new "aliased keys" have been added for users who want to bind keypresses to this toggling:
As hinted in my previous post, a large effort was made towards better typing and linting. This means there were a huge number of changes:
%gitdiff--statv0.17...v0.18 ... 157fileschanged,5366insertions(+),3158deletions(-)
Which is risky, but at the same time since most of those changes added stricter type checking, there are few possible regressions (and a few were caught already, thanks to the kind people running the main branch).
The XMPP Standards Foundation (XSF) is excited to announce the 29th XMPP Summit, the first XMPP Summit to take place fully online! The XMPP Summit will be held from Friday 4th September to Saturday 5th September 2026, both days between 13:00 - 16:00 UTC. The XSF invites everyone interested in development of XMPP technologies to attend, and discuss all things XMPP remotely!
The XMPP Summit is where the XMPP community comes together to discuss protocol development (such as XMPP extensions (XEPs)), implementation experience, and the future of the ecosystem.
As this is the first online XMPP Summit, it’s intentionally kept lightweight. The goal is not to replicate the in-person XMPP Summit experience, but to provide value that complements our existing asynchronous communication channels.
Favored topics will likely be follow-ups to topics discussed during Summit 28, advance current work on extensions (XEPs), discuss implementation pain points, and improvement of collaboration between projects. In general, the intention is to make it easier for more XMPP community members to participate, too.
Date: Friday 4th - Saturday 5th, September 2026
Time: 13:00 - 16:00 UTC (both days)
Session link: TBA
Cost: Free (registration and topic proposals appreciated)
Similar to an unconference at the beginning all interested participants can suggest topics and others can indicate via votes whether or not they are interested in these topics. Afterwards a rough order of topics is established that will be followed in moderation with the participants.
If you have ever followed a thread on the Standards Mailing List or taken part in a discussion on the public XSF channel you should be familiar with this. The different topics are broken up by short breaks that are great for networking and getting to know other XMPP developers.
Agreeing on a common strategy or even establishing a rough priority for certain features in our decentralized and interoperable technology and protocol can be challenging. To get the most out of the Summit, you should have a background in reading (and maybe even writing) XEPs. If you are simply an enthusiastic user or admin, we regularly have booths at various conferences (e.g. FOSDEM, CLT, FrOSCon) that are a great opportunity to meet with us, too. Either way, everyone is welcome to participate!
If we’ve caught your attention, we will then hopefully see you at the XMPP Summit 29. Read on!
If you’re interested in attending, please help us by filling out your details on the XMPP wiki page for Summit 29. To edit the page, reach out to an XSF member to enter and update your details or you’ll need a wiki account, which we’ll happily provide for you. To obtain an account on the XMPP wiki, please contact the Sysops who will be glad to create it for you (to avoid the spaming). Contact us using the communication channels listed below. If your plans change, please remove your name from the list.
Please also consider listing your agenda proposals and if you want to have a short talk: Topic proposals
We expect all participants to respect our Community Code of Conduct.
Please be respectful of others’ time, perspectives, and personal circumstances. Online participation can be demanding; we encourage everyone to help keep discussions constructive and focused.
To ensure you receive all relevant information, updates and announcements about the event, make sure that you’re signed up to the Summit mailing list and joined the Summit chatroom (Webview).
Spread the word! Feel free to use our communication channels (see page footer).
We are really excited to already see people signing up. Looking forward to meeting all of you online this time!
The XMPP Standards Foundation Board
As we’re preparing the upcoming Openfire 5.1.0 release, I’ve been spending a lot of time looking at parts of the codebase that have been around for a long time.
Some of them date back to assumptions that were perfectly reasonable when Java 5 was current, IPv6 was still considered "future tech", Docker didn’t exist yet, and "cloud-native" wasn’t a phrase anyone but meteorologists used.
Yet somehow, Openfire deployments that started in those days are still running today.
That got me wondering:
What’s the oldest Openfire deployment that you still run?
Not necessarily the oldest version (although I’d love to hear that too), but the oldest continuously running installation, the oldest surviving user database, or perhaps the weirdest setup that somehow still works despite years of upgrades, migrations and changing infrastructure.
I suspect there are Openfire instances out there that have survived datacenter migrations, moved from physical hardware to virtual machines to containers, switched databases more than once, and outlived several generations of administrators. Some probably still contain configuration decisions that nobody fully understands anymore. Is anyone still running Wildfire? Jive Messenger?
Honestly, I love those stories from the trenches. The odd workarounds, the "temporary" fixes that became permanent infrastructure, the upgrade that everyone expected to fail but somehow didn’t, or the deployment that quietly kept running for a decade without anyone thinking much about it.
One of the things I appreciate most about infrastructure software is that success often becomes invisible. If a messaging server quietly keeps working for ten years, nobody talks about it. But that kind of stability is actually a huge achievement (both for the software and for the people operating it). I think that’s something we, as a community, can be genuinely proud of.
For Openfire 5.1.0, we’ve been modernizing quite a few internals:
While doing that work, we constantly try to balance modernization with compatibility for long-running installations. That balancing act becomes much easier when we understand how people actually deploy and operate Openfire in the real world, which, apart from simply wanting to hear your stories, is another reason for me to ask this question.
So: I’d love to hear your stories! How old is your deployment? What version did you start with? What infrastructure changes has it survived over the years? Are there plugins or integrations you absolutely depend on? What operational lessons have you learned?
And perhaps most importantly: what surprised you most about running Openfire long-term?
I’m hoping this thread becomes a collection of deployment stories, operational lessons, and perhaps a bit of Openfire history.
Looking forward to hearing your stories!
We’d love to hear from you! Please join our community forum or group chat and let us know what you think!
For other release announcements and news follow us on Mastodon or X
7 posts - 6 participants
Planet Jabber is an aggregate of personal weblogs of Jabber/XMPP developers and contributors.
See Planet Jabber News for general and project news.
Last updated:
September 05, 2026 06:38
All times are UTC.
The maintainer of this Planet is Ralph Meijer. He can be contacted at ralphm@ik.nu (Jabber and e-mail).
Atom 1.0
RSS 1.0
RSS 2.0
FOAF
OPML