Skip to content

Commit

Permalink
Implement Bind 2 (#1036)
Browse files Browse the repository at this point in the history
  • Loading branch information
sonnyp committed Dec 23, 2024
1 parent c3c8672 commit 762e94d
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 44 deletions.
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ e2e:
$(warning e2e tests require prosody-trunk and luarocks)
cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2 > /dev/null
cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2_bind2 > /dev/null

cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2_sm > /dev/null
# https://github.com/xmppjs/xmpp.js/pull/1006
# cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2_fast > /dev/null
# cd server && prosodyctl --config prosody.cfg.lua install mod_sasl2_sm > /dev/null
npm run e2e

clean:
Expand Down
64 changes: 52 additions & 12 deletions packages/client-core/src/bind2/bind2.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,56 @@ import xml from "@xmpp/xml";

const NS_BIND = "urn:xmpp:bind:0";

export default function bind2({ sasl2 }, tag) {
sasl2.use(NS_BIND, async (element) => {
if (!element.is("bind", NS_BIND)) return;

tag = typeof tag === "function" ? await tag() : tag;

return xml(
"bind",
{ xmlns: "urn:xmpp:bind:0" },
tag && xml("tag", null, tag),
);
});
export default function bind2({ sasl2, entity }, tag) {
const features = new Map();

sasl2.use(
NS_BIND,
async (element) => {
if (!element.is("bind", NS_BIND)) return;

tag = typeof tag === "function" ? await tag() : tag;

const sessionFeatures = await getSessionFeatures({ element, features });

return xml(
"bind",
{ xmlns: "urn:xmpp:bind:0" },
tag && xml("tag", null, tag),
...sessionFeatures,
);
},
(element) => {
const aid = element.root().getChildText("authorization-identifier");
if (aid) entity._jid(aid);

for (const child of element.getChildElements()) {
const feature = features.get(child.getNS());
if (!feature?.[1]) continue;
feature?.[1](child);
}
},
);

return {
use(ns, req, res) {
features.set(ns, [req, res]);
},
};
}

function getSessionFeatures({ element, features }) {
const promises = [];

const inline = element.getChild("inline");
if (!inline) return promises;

for (const element of inline.getChildElements()) {
const xmlns = element.attrs.var;
const feature = features.get(xmlns);
if (!feature) continue;
promises.push(feature[0](element));
}

return Promise.all(promises);
}
9 changes: 6 additions & 3 deletions packages/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ function client(options = {}) {
{ streamFeatures, saslFactory },
createOnAuthenticate(credentials ?? { username, password }, userAgent),
);

// SASL2 inline features
const bind2 = _bind2({ sasl2, entity }, resource);

// Stream features - order matters and define priority
const sasl = _sasl(
{ streamFeatures, saslFactory },
createOnAuthenticate(credentials ?? { username, password }, userAgent),
Expand All @@ -68,6 +73,7 @@ function client(options = {}) {
streamFeatures,
entity,
middleware,
bind2,
});
const resourceBinding = _resourceBinding(
{ iqCaller, streamFeatures },
Expand All @@ -78,9 +84,6 @@ function client(options = {}) {
streamFeatures,
});

// SASL2 inline features
const bind2 = _bind2({ sasl2 }, resource);

return Object.assign(entity, {
entity,
reconnect,
Expand Down
35 changes: 15 additions & 20 deletions packages/sasl2/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { encode, decode } from "@xmpp/base64";
import SASLError from "@xmpp/sasl/lib/SASLError.js";
import jid from "@xmpp/jid";
import xml from "@xmpp/xml";

// https://xmpp.org/extensions/xep-0388.html
Expand All @@ -17,7 +16,8 @@ async function authenticate({
mechanism,
credentials,
userAgent,
sessionFeatures,
streamFeatures,
features,
}) {
const mech = saslFactory.create([mechanism]);
if (!mech) {
Expand Down Expand Up @@ -61,7 +61,6 @@ async function authenticate({
}

if (element.name === "continue") {
// No tasks supported yet
reject(new Error("continue is not supported yet"));
return;
}
Expand All @@ -71,17 +70,13 @@ async function authenticate({
if (additionalData && mech.final) {
mech.final(decode(additionalData));
}
// This jid will be bare unless we do inline bind2 then it will be the bound full jid
const aid = element.getChild("authorization-identifier")?.text();
if (aid) {
if (!entity.jid?.resource) {
// No jid or bare jid, so update it
entity._jid(aid);
} else if (jid(aid).resource) {
// We have a full jid so use it
entity._jid(aid);
}

for (const child of element.getChildElements()) {
const feature = features.get(child.getNS());
if (!feature?.[1]) continue;
feature?.[1](child);
}

resolve(element);
return;
}
Expand All @@ -97,15 +92,14 @@ async function authenticate({
mech.clientFirst &&
xml("initial-response", {}, encode(mech.response(creds))),
userAgent,
...sessionFeatures,
...streamFeatures,
]),
)
.catch(reject);
});
}

export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {
// inline
const features = new Map();

streamFeatures.use(
Expand All @@ -120,7 +114,7 @@ export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {
throw new SASLError("SASL: No compatible mechanism available.");
}

const sessionFeatures = await getSessionFeatures({ element, features });
const streamFeatures = await getStreamFeatures({ element, features });

async function done(credentials, mechanism, userAgent) {
await authenticate({
Expand All @@ -129,7 +123,8 @@ export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {
mechanism,
credentials,
userAgent,
sessionFeatures,
streamFeatures,
features,
});
}

Expand All @@ -141,12 +136,12 @@ export default function sasl2({ streamFeatures, saslFactory }, onAuthenticate) {

return {
use(ns, req, res) {
features.set(ns, req, res);
features.set(ns, [req, res]);
},
};
}

function getSessionFeatures({ element, features }) {
function getStreamFeatures({ element, features }) {
const promises = [];

const inline = element.getChild("inline");
Expand All @@ -156,7 +151,7 @@ function getSessionFeatures({ element, features }) {
const xmlns = element.getNS();
const feature = features.get(xmlns);
if (!feature) continue;
promises.push(feature(element));
promises.push(feature[0](element));
}

return Promise.all(promises);
Expand Down
46 changes: 40 additions & 6 deletions packages/stream-management/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ export default function streamManagement({
streamFeatures,
entity,
middleware,
bind2,
}) {
let address = null;

const sm = {
allowResume: true,
preferredMaximum: null,
Expand All @@ -55,8 +54,7 @@ export default function streamManagement({
max: null,
};

entity.on("online", (jid) => {
address = jid;
entity.on("online", () => {
sm.outbound = 0;
sm.inbound = 0;
});
Expand All @@ -83,9 +81,23 @@ export default function streamManagement({
return next();
});

if (bind2) {
setupBind2({ bind2, sm, entity });
} else {
setupStreamFeature({ streamFeatures, sm, entity });
}

return sm;
}

function setupStreamFeature({ streamFeatures, sm, entity }) {
let address = null;
entity.on("online", (jid) => {
address = jid;
});

// https://xmpp.org/extensions/xep-0198.html#enable
// For client-to-server connections, the client MUST NOT attempt to enable stream management until after it has completed Resource Binding unless it is resuming a previous session

streamFeatures.use("sm", NS, async (context, next) => {
// Resuming
if (sm.id) {
Expand Down Expand Up @@ -124,6 +136,28 @@ export default function streamManagement({

sm.inbound = 0;
});
}

return sm;
function setupBind2({ bind2, sm, entity }) {
let address = null;
entity.on("online", (jid) => {
address = jid;
});
bind2.use(
"urn:xmpp:sm:3",
(element) => {
if (!element.is("feature")) return;
// eslint-disable-next-line unicorn/prefer-ternary
if (sm.id) {
return xml("resume", { xmlns: NS });
} else {
return xml("enable", { xmlns: NS });
}
},
(element) => {
if (!element.is("enabled")) return;
console.log("ENABLED");
sm.enabled = true;
},
);
}
2 changes: 1 addition & 1 deletion server/prosody.cfg.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ modules_enabled = {
"smacks";
"sasl2";
"sasl2_bind2";
"sasl2_sm";
-- https://github.com/xmppjs/xmpp.js/pull/1006
-- "sasl2_fast";
-- "sasl2_sm";
};

modules_disabled = {
Expand Down

0 comments on commit 762e94d

Please sign in to comment.