diff options
| author | fiaxh <git@lightrise.org> | 2026-04-24 11:09:10 +0200 |
|---|---|---|
| committer | fiaxh <git@lightrise.org> | 2026-04-24 11:17:43 +0200 |
| commit | 9abf8f6869bc35c76bdfa86416012d4e7f0dda78 (patch) | |
| tree | 3c3f7628d7fdc5410113a8efc6b96a4372c6d80b /xmpp-vala/src | |
| parent | c5ba4275ae68dd558108ce273dafa1bf890259e8 (diff) | |
Add support for Sasl2 + Bind2
Diffstat (limited to 'xmpp-vala/src')
| -rw-r--r-- | xmpp-vala/src/core/direct_tls_xmpp_stream.vala | 4 | ||||
| -rw-r--r-- | xmpp-vala/src/core/io_xmpp_stream.vala | 17 | ||||
| -rw-r--r-- | xmpp-vala/src/core/starttls_xmpp_stream.vala | 4 | ||||
| -rw-r--r-- | xmpp-vala/src/core/stream_connect.vala | 4 | ||||
| -rw-r--r-- | xmpp-vala/src/core/tls_xmpp_stream.vala | 12 | ||||
| -rw-r--r-- | xmpp-vala/src/core/xmpp_stream.vala | 5 | ||||
| -rw-r--r-- | xmpp-vala/src/module/sasl.vala | 179 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0198_stream_management.vala | 157 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0280_message_carbons.vala | 30 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0386_bind2.vala | 49 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0388_extensible_sasl_profile.vala | 139 |
11 files changed, 455 insertions, 145 deletions
diff --git a/xmpp-vala/src/core/direct_tls_xmpp_stream.vala b/xmpp-vala/src/core/direct_tls_xmpp_stream.vala index f2e06e2c..2f159ab6 100644 --- a/xmpp-vala/src/core/direct_tls_xmpp_stream.vala +++ b/xmpp-vala/src/core/direct_tls_xmpp_stream.vala @@ -6,8 +6,8 @@ public class Xmpp.DirectTlsXmppStream : TlsXmppStream { uint16 port; TlsXmppStream.OnInvalidCertWrapper on_invalid_cert; - public DirectTlsXmppStream(Jid remote_name, string host, uint16 port, TlsXmppStream.OnInvalidCertWrapper on_invalid_cert) { - base(remote_name); + public DirectTlsXmppStream(Jid remote_name, string host, uint16 port, Jid? own_jid, TlsXmppStream.OnInvalidCertWrapper on_invalid_cert) { + base(remote_name, own_jid); this.host = host; this.port = port; this.on_invalid_cert = on_invalid_cert; diff --git a/xmpp-vala/src/core/io_xmpp_stream.vala b/xmpp-vala/src/core/io_xmpp_stream.vala index 1d9d061b..e13d1448 100644 --- a/xmpp-vala/src/core/io_xmpp_stream.vala +++ b/xmpp-vala/src/core/io_xmpp_stream.vala @@ -74,12 +74,7 @@ public abstract class Xmpp.IoXmppStream : XmppStream { } public override async void setup() throws IOError { - StanzaNode outs = new StanzaNode.build("stream", "http://etherx.jabber.org/streams") - .put_attribute("to", remote_name.to_string()) - .put_attribute("version", "1.0") - .put_attribute("xmlns", "jabber:client") - .put_attribute("stream", "http://etherx.jabber.org/streams", XMLNS_URI); - outs.has_nodes = true; + StanzaNode outs = generate_root_node(); log.node("OUT ROOT", outs, this); yield write_async(outs, Priority.HIGH, cancellable); received_root_node(this, yield read_root()); @@ -87,6 +82,16 @@ public abstract class Xmpp.IoXmppStream : XmppStream { setup_needed = false; } + public virtual StanzaNode generate_root_node() { + StanzaNode root_node = new StanzaNode.build("stream", "http://etherx.jabber.org/streams") + .put_attribute("to", remote_name.to_string()) + .put_attribute("version", "1.0") + .put_attribute("xmlns", "jabber:client") + .put_attribute("stream", "http://etherx.jabber.org/streams", XMLNS_URI); + root_node.has_nodes = true; + return root_node; + } + private async StanzaNode read_root() throws IOError { StanzaReader? reader = this.reader; if (reader == null) throw new IOError.NOT_CONNECTED("trying to read, but no stream open"); diff --git a/xmpp-vala/src/core/starttls_xmpp_stream.vala b/xmpp-vala/src/core/starttls_xmpp_stream.vala index 01c75207..ca810650 100644 --- a/xmpp-vala/src/core/starttls_xmpp_stream.vala +++ b/xmpp-vala/src/core/starttls_xmpp_stream.vala @@ -6,8 +6,8 @@ public class Xmpp.StartTlsXmppStream : TlsXmppStream { uint16 port; TlsXmppStream.OnInvalidCertWrapper on_invalid_cert; - public StartTlsXmppStream(Jid remote, string host, uint16 port, TlsXmppStream.OnInvalidCertWrapper on_invalid_cert) { - base(remote); + public StartTlsXmppStream(Jid remote, string host, uint16 port, Jid? own_jid, TlsXmppStream.OnInvalidCertWrapper on_invalid_cert) { + base(remote, own_jid); this.host = host; this.port = port; this.on_invalid_cert = on_invalid_cert; diff --git a/xmpp-vala/src/core/stream_connect.vala b/xmpp-vala/src/core/stream_connect.vala index af27ff0d..7e3843fa 100644 --- a/xmpp-vala/src/core/stream_connect.vala +++ b/xmpp-vala/src/core/stream_connect.vala @@ -60,9 +60,9 @@ namespace Xmpp { foreach (SrvTargetInfo target in targets) { try { if (target.service == "xmpp-client") { - stream = new StartTlsXmppStream(remote, target.host, target.port, on_invalid_cert_wrapper); + stream = new StartTlsXmppStream(remote, target.host, target.port, bare_jid, on_invalid_cert_wrapper); } else { - stream = new DirectTlsXmppStream(remote, target.host, target.port, on_invalid_cert_wrapper); + stream = new DirectTlsXmppStream(remote, target.host, target.port, bare_jid, on_invalid_cert_wrapper); } stream.log = new XmppLog(bare_jid.to_string(), log_options); diff --git a/xmpp-vala/src/core/tls_xmpp_stream.vala b/xmpp-vala/src/core/tls_xmpp_stream.vala index 6a5e935b..81ab1c54 100644 --- a/xmpp-vala/src/core/tls_xmpp_stream.vala +++ b/xmpp-vala/src/core/tls_xmpp_stream.vala @@ -1,6 +1,7 @@ public abstract class Xmpp.TlsXmppStream : IoXmppStream { public TlsCertificateFlags? errors; + private Jid? own_jid; public delegate bool OnInvalidCert(GLib.TlsCertificate peer_cert, GLib.TlsCertificateFlags errors); public class OnInvalidCertWrapper { @@ -10,8 +11,9 @@ public abstract class Xmpp.TlsXmppStream : IoXmppStream { } } - protected TlsXmppStream(Jid remote_name) { + protected TlsXmppStream(Jid remote_name, Jid? own_jid) { base(remote_name); + this.own_jid = own_jid; } protected bool on_invalid_certificate(TlsCertificate peer_cert, TlsCertificateFlags errors) { @@ -28,4 +30,12 @@ public abstract class Xmpp.TlsXmppStream : IoXmppStream { warning(@"[%p, %s] Tls Certificate Errors: %s", this, this.remote_name.to_string(), error_str); return false; } + + public override StanzaNode generate_root_node() { + StanzaNode root_node = base.generate_root_node(); + if (own_jid != null) { + root_node.put_attribute("from", own_jid.bare_jid.to_string()); + } + return root_node; + } }
\ No newline at end of file diff --git a/xmpp-vala/src/core/xmpp_stream.vala b/xmpp-vala/src/core/xmpp_stream.vala index 54433b67..7b05a26e 100644 --- a/xmpp-vala/src/core/xmpp_stream.vala +++ b/xmpp-vala/src/core/xmpp_stream.vala @@ -22,6 +22,7 @@ public abstract class Xmpp.XmppStream : Object { public XmppLog log = new XmppLog(); public bool negotiation_complete { get; set; default=false; } + public bool expect_further_negotiation_stanzas = false; protected bool non_negotiation_modules_attached = false; protected bool setup_needed = false; protected bool disconnected = false; @@ -99,6 +100,8 @@ public abstract class Xmpp.XmppStream : Object { public async void loop() throws IOError { while (true) { + expect_further_negotiation_stanzas = false; + if (setup_needed) { yield setup(); } @@ -112,7 +115,7 @@ public abstract class Xmpp.XmppStream : Object { yield handle_stanza(node); - if (!non_negotiation_modules_attached && negotiation_modules_done()) { + if (!expect_further_negotiation_stanzas && !non_negotiation_modules_attached && negotiation_modules_done()) { attach_non_negotation_modules(); non_negotiation_modules_attached = true; if (!negotiation_complete) { diff --git a/xmpp-vala/src/module/sasl.vala b/xmpp-vala/src/module/sasl.vala index 3f3eca58..035cc723 100644 --- a/xmpp-vala/src/module/sasl.vala +++ b/xmpp-vala/src/module/sasl.vala @@ -44,52 +44,6 @@ namespace Xmpp.Sasl { stream.received_nonza.disconnect(this.received_nonza); } - private static size_t SHA1_SIZE = 20; - - private static uint8[] sha1(uint8[] data) { - Checksum checksum = new Checksum(ChecksumType.SHA1); - checksum.update(data, data.length); - uint8[] res = new uint8[SHA1_SIZE]; - checksum.get_digest(res, ref SHA1_SIZE); - return res; - } - - private static uint8[] hmac_sha1(uint8[] key, uint8[] data) { - Hmac hmac = new Hmac(ChecksumType.SHA1, key); - hmac.update(data); - uint8[] res = new uint8[SHA1_SIZE]; - hmac.get_digest(res, ref SHA1_SIZE); - return res; - } - - private static uint8[] pbkdf2_sha1(string password, uint8[] salt, uint iterations) { - uint8[] res = new uint8[SHA1_SIZE]; - uint8[] last = new uint8[salt.length + 4]; - for(int i = 0; i < salt.length; i++) { - last[i] = salt[i]; - } - last[salt.length + 3] = 1; - for(int i = 0; i < iterations; i++) { - last = hmac_sha1((uint8[]) password.to_utf8(), last); - xor_inplace(res, last); - } - return res; - } - - private static void xor_inplace(uint8[] mix, uint8[] a2) { - for(int i = 0; i < mix.length; i++) { - mix[i] = mix[i] ^ a2[i]; - } - } - - private static uint8[] xor(uint8[] a1, uint8[] a2) { - uint8[] mix = new uint8[a1.length]; - for(int i = 0; i < a1.length; i++) { - mix[i] = a1[i] ^ a2[i]; - } - return mix; - } - public void received_nonza(XmppStream stream, StanzaNode node) { if (node.ns_uri == NS_URI) { if (node.name == "success") { @@ -119,33 +73,10 @@ namespace Xmpp.Sasl { } else if (node.name == "challenge" && stream.has_flag(Flag.IDENTITY)) { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.mechanism == Mechanism.SCRAM_SHA_1) { - string challenge = (string) Base64.decode(node.get_string_content()); - string? server_nonce = null; - uint8[] salt = null; - uint iterations = 0; - foreach(string c in challenge.split(",")) { - string[] split = c.split("=", 2); - if (split.length != 2) continue; - switch(split[0]) { - case "r": server_nonce = split[1]; break; - case "s": salt = Base64.decode(split[1]); break; - case "i": iterations = int.parse(split[1]); break; - } - } - if (server_nonce == null || salt == null || iterations == 0) return; - if (!server_nonce.has_prefix(flag.client_nonce)) return; - string client_final_message_bare = @"c=biws,r=$server_nonce"; - uint8[] salted_password = pbkdf2_sha1(flag.password, salt, iterations); - uint8[] client_key = hmac_sha1(salted_password, (uint8[]) "Client Key".to_utf8()); - uint8[] stored_key = sha1(client_key); - string auth_message = @"n=$(flag.name),r=$(flag.client_nonce),$challenge,$client_final_message_bare"; - uint8[] client_signature = hmac_sha1(stored_key, (uint8[]) auth_message.to_utf8()); - uint8[] client_proof = xor(client_key, client_signature); - uint8[] server_key = hmac_sha1(salted_password, (uint8[]) "Server Key".to_utf8()); - flag.server_signature = hmac_sha1(server_key, (uint8[]) auth_message.to_utf8()); - string client_final_message = @"$client_final_message_bare,p=$(Base64.encode(client_proof))"; + var challenge_response = compute_challenge_response(node.get_string_content(), flag.password, flag.client_nonce, flag.name); + flag.server_signature = challenge_response.expected_server_signature; stream.write(new StanzaNode.build("response", NS_URI).add_self_xmlns() - .put_node(new StanzaNode.text(Base64.encode((uchar[]) (client_final_message).to_utf8())))); + .put_node(new StanzaNode.text(Base64.encode((uchar[]) (challenge_response.response).to_utf8())))); } } } @@ -206,17 +137,6 @@ namespace Xmpp.Sasl { } } - private static uchar[] get_plain_bytes(string name_s, string password_s) { - var name = name_s.to_utf8(); - var password = password_s.to_utf8(); - uchar[] res = new uchar[name.length + password.length + 2]; - res[0] = 0; - res[name.length + 1] = 0; - for(int i = 0; i < name.length; i++) { res[i + 1] = (uchar) name[i]; } - for(int i = 0; i < password.length; i++) { res[i + name.length + 2] = (uchar) password[i]; } - return res; - } - public override bool mandatory_outstanding(XmppStream stream) { return !stream.has_flag(Flag.IDENTITY) || !stream.get_flag(Flag.IDENTITY).finished; } @@ -228,4 +148,97 @@ namespace Xmpp.Sasl { public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } + + public static uchar[] get_plain_bytes(string name_s, string password_s) { + var name = name_s.to_utf8(); + var password = password_s.to_utf8(); + uchar[] res = new uchar[name.length + password.length + 2]; + res[0] = 0; + res[name.length + 1] = 0; + for(int i = 0; i < name.length; i++) { res[i + 1] = (uchar) name[i]; } + for(int i = 0; i < password.length; i++) { res[i + name.length + 2] = (uchar) password[i]; } + return res; + } + + public class ChallengeResponse { + public uint8[] expected_server_signature { get; set; } + public string response { get; set; } + } + + public static ChallengeResponse compute_challenge_response(string challenge, string password, string client_nonce, string name) { + string challenge_base64 = (string) Base64.decode(challenge); + string? server_nonce = null; + uint8[] salt = null; + uint iterations = 0; + foreach(string c in challenge_base64.split(",")) { + string[] split = c.split("=", 2); + if (split.length != 2) continue; + switch(split[0]) { + case "r": server_nonce = split[1]; break; + case "s": salt = Base64.decode(split[1]); break; + case "i": iterations = int.parse(split[1]); break; + } + } + if (server_nonce == null || salt == null || iterations == 0) throw new Error(-1, 0, "SASL error"); + if (!server_nonce.has_prefix(client_nonce)) throw new Error(-1, 0, "SASL error"); + string client_final_message_bare = @"c=biws,r=$server_nonce"; + uint8[] salted_password = pbkdf2_sha1(password, salt, iterations); + uint8[] client_key = hmac_sha1(salted_password, (uint8[]) "Client Key".to_utf8()); + uint8[] stored_key = sha1(client_key); + string auth_message = @"n=$(name),r=$(client_nonce),$challenge_base64,$client_final_message_bare"; + uint8[] client_signature = hmac_sha1(stored_key, (uint8[]) auth_message.to_utf8()); + uint8[] client_proof = xor(client_key, client_signature); + uint8[] server_key = hmac_sha1(salted_password, (uint8[]) "Server Key".to_utf8()); + + return new ChallengeResponse() { + expected_server_signature = hmac_sha1(server_key, (uint8[]) auth_message.to_utf8()), + response = @"$client_final_message_bare,p=$(Base64.encode(client_proof))" + }; + } + + private static size_t SHA1_SIZE = 20; + + private static uint8[] sha1(uint8[] data) { + Checksum checksum = new Checksum(ChecksumType.SHA1); + checksum.update(data, data.length); + uint8[] res = new uint8[SHA1_SIZE]; + checksum.get_digest(res, ref SHA1_SIZE); + return res; + } + + private static uint8[] hmac_sha1(uint8[] key, uint8[] data) { + Hmac hmac = new Hmac(ChecksumType.SHA1, key); + hmac.update(data); + uint8[] res = new uint8[SHA1_SIZE]; + hmac.get_digest(res, ref SHA1_SIZE); + return res; + } + + private static uint8[] pbkdf2_sha1(string password, uint8[] salt, uint iterations) { + uint8[] res = new uint8[SHA1_SIZE]; + uint8[] last = new uint8[salt.length + 4]; + for(int i = 0; i < salt.length; i++) { + last[i] = salt[i]; + } + last[salt.length + 3] = 1; + for(int i = 0; i < iterations; i++) { + last = hmac_sha1((uint8[]) password.to_utf8(), last); + xor_inplace(res, last); + } + return res; + } + + private static void xor_inplace(uint8[] mix, uint8[] a2) { + for(int i = 0; i < mix.length; i++) { + mix[i] = mix[i] ^ a2[i]; + } + } + + private static uint8[] xor(uint8[] a1, uint8[] a2) { + uint8[] mix = new uint8[a1.length]; + for(int i = 0; i < a1.length; i++) { + mix[i] = a1[i] ^ a2[i]; + } + return mix; + } } diff --git a/xmpp-vala/src/module/xep/0198_stream_management.vala b/xmpp-vala/src/module/xep/0198_stream_management.vala index 68eee8ae..6b0d5162 100644 --- a/xmpp-vala/src/module/xep/0198_stream_management.vala +++ b/xmpp-vala/src/module/xep/0198_stream_management.vala @@ -128,20 +128,23 @@ public class Module : XmppStreamNegotiationModule, WriteNodeFunc { } private void check_resume(XmppStream stream) { - if (stream_has_sm_feature(stream) && session_id != null) { + if (stream_has_sm_feature(stream) && session_id != null && !Flag.get_add_flag(stream).finished) { StanzaNode node = new StanzaNode.build("resume", NS_URI).add_self_xmlns() .put_attribute("h", h_inbound.to_string()) .put_attribute("previd", session_id); write_node.begin(stream, node); - stream.add_flag(new Flag()); + + Flag.get_add_flag(stream); } } private void check_enable(XmppStream stream) { - if (stream_has_sm_feature(stream) && session_id == null) { + if (stream_has_sm_feature(stream) && session_id == null && !Flag.get_add_flag(stream).finished) { StanzaNode node = new StanzaNode.build("enable", NS_URI).add_self_xmlns().put_attribute("resume", "true"); write_node.begin(stream, node); - stream.add_flag(new Flag()); + + Flag.get_add_flag(stream); + h_outbound = 0; } } @@ -153,46 +156,64 @@ public class Module : XmppStreamNegotiationModule, WriteNodeFunc { } else if (node.name == "a") { handle_ack(stream, node); } else if (node.name in new string[]{"enabled", "resumed", "failed"}) { - stream.get_flag(Flag.IDENTITY).finished = true; - if (node.name == "enabled") { - h_inbound = 0; - session_id = node.get_attribute("id", NS_URI); - flags = stream.flags; - ((IoXmppStream)stream).write_obj = this; + on_enabled(stream, node); } else if (node.name == "resumed") { - stream.get_flag(Flag.IDENTITY).resumed = true; - - foreach (XmppStreamFlag flag in flags) { - stream.add_flag(flag); - } - - h_outbound = int.parse(node.get_attribute("h", NS_URI)); - handle_incoming_h(stream, h_outbound); - foreach (var id in in_flight_stanzas.keys) { - node_queue.add(in_flight_stanzas[id]); - } - in_flight_stanzas.clear(); - check_queue(stream); - ((IoXmppStream)stream).write_obj = this; + on_resumed(stream, node); } else if (node.name == "failed") { - session_id = null; - string? h_acked = node.get_attribute("h", NS_URI); - if (h_acked != null) { - h_outbound = int.parse(h_acked); - handle_incoming_h(stream, h_outbound); - } - foreach (var id in in_flight_stanzas.keys) { - in_flight_stanzas[id].promise.set_exception(new IOError.FAILED("Stanza not acked and session not resumed")); - } - in_flight_stanzas.clear(); - check_queue(stream); - stream.received_features_node(stream); + on_resume_failed(stream, node); } } } } + public void on_resumed(XmppStream stream, StanzaNode resumed_node) { + var flag = stream.get_flag(Flag.IDENTITY); + flag.resumed = true; + flag.finished = true; + + foreach (XmppStreamFlag f in flags) { + stream.add_flag(f); + } + + h_outbound = int.parse(resumed_node.get_attribute("h", NS_URI)); + handle_incoming_h(stream, h_outbound); + foreach (var id in in_flight_stanzas.keys) { + node_queue.add(in_flight_stanzas[id]); + } + in_flight_stanzas.clear(); + check_queue(stream); + ((IoXmppStream)stream).write_obj = this; + + stream.get_flag(Flag.IDENTITY).finished = true; + } + + public void on_resume_failed(XmppStream stream, StanzaNode failed_node) { + session_id = null; + string? h_acked = failed_node.get_attribute("h", NS_URI); + if (h_acked != null) { + h_outbound = int.parse(h_acked); + handle_incoming_h(stream, h_outbound); + } + foreach (var id in in_flight_stanzas.keys) { + in_flight_stanzas[id].promise.set_exception(new IOError.FAILED("Stanza not acked and session not resumed")); + } + in_flight_stanzas.clear(); + check_queue(stream); + stream.received_features_node(stream); + + stream.get_flag(Flag.IDENTITY).finished = true; + } + + public void on_enabled(XmppStream stream, StanzaNode enabled_node) { + h_inbound = 0; + session_id = enabled_node.get_attribute("id", NS_URI); + flags = stream.flags; + ((IoXmppStream)stream).write_obj = this; + + stream.get_flag(Flag.IDENTITY).finished = true; + } + private void send_ack(XmppStream stream) { StanzaNode node = new StanzaNode.build("a", NS_URI).add_self_xmlns().put_attribute("h", h_inbound.to_string()); write_node.begin(stream, node); @@ -217,19 +238,73 @@ public class Module : XmppStreamNegotiationModule, WriteNodeFunc { in_flight_stanzas.unset(nr); } } - - private bool stream_has_sm_feature(XmppStream stream) { - return stream.features.get_subnode("sm", NS_URI) != null; - } } public class Flag : XmppStreamFlag { public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "stream_management"); - public bool finished = false; + public bool finished { get; set; default=false; } public bool resumed = false; public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } + + public static Flag get_add_flag(XmppStream stream) { + Flag? flag = stream.get_flag(IDENTITY); + if (flag == null) { + flag = new Flag(); + stream.add_flag(flag); + } + return flag; + } } + public class Bind2Activation : Bind2.Bind2InlineActivation { + public override StanzaNode? get_activation_node(XmppStream stream) { + // Always insert an enable node, even if we also try to resume in Sasl2. Because if resumption fails, enable will be used. + Flag.get_add_flag(stream); + + stream.get_module(Module.IDENTITY).h_outbound = 0; + + return new StanzaNode.build("enable", NS_URI).add_self_xmlns().put_attribute("resume", "true"); + } + + public override void on_bound(XmppStream stream, StanzaNode bound_node) { + StanzaNode? enabled_node = bound_node.get_subnode("enabled", NS_URI); + if (enabled_node != null) { + stream.get_module(Module.IDENTITY).on_enabled(stream, enabled_node); + } + } + } + + public class Sasl2Activation : ExtensibleSaslProfile.Sasl2InlineActivation { + public override StanzaNode? get_activation_node(XmppStream stream, StanzaNode node) { + if (stream.get_module(Module.IDENTITY).session_id != null) { + Flag.get_add_flag(stream); + + var sm_module = stream.get_module(Module.IDENTITY); + + StanzaNode resume_node = new StanzaNode.build("resume", NS_URI).add_self_xmlns() + .put_attribute("h", sm_module.h_inbound.to_string()) + .put_attribute("previd", sm_module.session_id); + return resume_node; + } + return null; + } + + public override void on_bound(XmppStream stream, Jid authorization_identifier, StanzaNode success_node) { + StanzaNode? resumed_node = success_node.get_subnode("resumed", NS_URI); + if (resumed_node != null) { + stream.get_module(Module.IDENTITY).on_resumed(stream, resumed_node); + } + + StanzaNode? failed_node = success_node.get_subnode("failed", NS_URI); + if (failed_node != null) { + stream.get_module(Module.IDENTITY).on_resume_failed(stream, failed_node); + } + } + } + + private bool stream_has_sm_feature(XmppStream stream) { + return stream.features.get_subnode("sm", NS_URI) != null; + } } diff --git a/xmpp-vala/src/module/xep/0280_message_carbons.vala b/xmpp-vala/src/module/xep/0280_message_carbons.vala index f7863188..b396ee54 100644 --- a/xmpp-vala/src/module/xep/0280_message_carbons.vala +++ b/xmpp-vala/src/module/xep/0280_message_carbons.vala @@ -1,6 +1,6 @@ namespace Xmpp.Xep.MessageCarbons { -private const string NS_URI = "urn:xmpp:carbons:2"; +public const string NS_URI = "urn:xmpp:carbons:2"; public class Module : XmppStreamModule { public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0280_message_carbons_module"); @@ -8,8 +8,10 @@ public class Module : XmppStreamModule { private ReceivedPipelineListener received_pipeline_listener = new ReceivedPipelineListener(); public async void enable(XmppStream stream) { - Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("enable", NS_URI).add_self_xmlns()); - yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + if (stream.get_flag(Flag.IDENTITY) == null) { + Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("enable", NS_URI).add_self_xmlns()); + yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + } } public async void disable(XmppStream stream) { @@ -90,12 +92,26 @@ public class MessageFlag : Xmpp.MessageFlag { return (MessageFlag) message.get_flag(NS_URI, ID); } - public override string get_ns() { - return NS_URI; + public override string get_ns() { return NS_URI; } + + public override string get_id() { return ID; } +} + +public class Flag : Xmpp.XmppStreamFlag { + public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "carbons"); + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } +} + +public class Bind2Activation : Bind2.Bind2InlineActivation { + public override StanzaNode? get_activation_node(XmppStream stream) { + stream.add_flag(new Flag()); + return new StanzaNode.build("enable", NS_URI).add_self_xmlns(); } - public override string get_id() { - return ID; + public override void on_bound(XmppStream stream, StanzaNode bound_node) { + // There is no "enabled" node for carbons } } diff --git a/xmpp-vala/src/module/xep/0386_bind2.vala b/xmpp-vala/src/module/xep/0386_bind2.vala new file mode 100644 index 00000000..cc21d62e --- /dev/null +++ b/xmpp-vala/src/module/xep/0386_bind2.vala @@ -0,0 +1,49 @@ +using Gee; + +namespace Xmpp.Xep.Bind2 { + public const string NS_URI = "urn:xmpp:bind:0"; + + public class Sasl2Activation : ExtensibleSaslProfile.Sasl2InlineActivation { + public HashMap<string, Bind2InlineActivation> inline_activation_providers = new HashMap<string, Bind2InlineActivation>(); + + public override StanzaNode? get_activation_node(XmppStream stream, StanzaNode inline_node) { + var res = new StanzaNode.build("bind", Bind2.NS_URI).add_self_xmlns().put_node( + new StanzaNode.build("tag", Bind2.NS_URI).put_node(new StanzaNode.text("dino"))); + + StanzaNode bind_inline_node = inline_node.get_subnode("bind", NS_URI).get_subnode("inline", NS_URI); + + if (bind_inline_node != null) { + foreach (StanzaNode inline_feature in bind_inline_node.get_subnodes("feature", NS_URI)) { + var inline_feature_ns = inline_feature.get_attribute("var"); + if (inline_activation_providers.has_key(inline_feature_ns)) { + var activation_node = inline_activation_providers[inline_feature_ns].get_activation_node(stream); + if (activation_node != null) { + res.put_node(activation_node); + } + } + } + } + return res; + } + + public override void on_bound(XmppStream stream, Jid authorization_identifier, StanzaNode success_node) { + Bind.Flag bind_flag = new Bind.Flag(); + bind_flag.my_jid = authorization_identifier; + bind_flag.finished = true; + stream.add_flag(bind_flag); + stream.get_module(Bind.Module.IDENTITY).bound_to_resource(stream, bind_flag.my_jid); + + StanzaNode? bound_node = success_node.get_subnode("bound", Bind2.NS_URI); + if (bound_node != null) { + foreach (var inline_activation in inline_activation_providers.values) { + inline_activation.on_bound(stream, bound_node); + } + } + } + } + + public abstract class Bind2InlineActivation : Object { + public abstract StanzaNode? get_activation_node(XmppStream stream); + public abstract void on_bound(XmppStream stream, StanzaNode bound_node); + } +}
\ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0388_extensible_sasl_profile.vala b/xmpp-vala/src/module/xep/0388_extensible_sasl_profile.vala new file mode 100644 index 00000000..cfeeb567 --- /dev/null +++ b/xmpp-vala/src/module/xep/0388_extensible_sasl_profile.vala @@ -0,0 +1,139 @@ +using Gee; +using Xmpp.Sasl; + +namespace Xmpp.Xep.ExtensibleSaslProfile { + public const string NS_URI = "urn:xmpp:sasl:2"; + + public class Module : XmppStreamNegotiationModule { + public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "sasl2"); + + public HashMap<string, Sasl2InlineActivation> inline_activation_providers = new HashMap<string, Sasl2InlineActivation>(); + + public string name { get; set; } + public string password { get; set; } + + public signal void received_auth_failure(XmppStream stream, StanzaNode node); + + public Module(string name, string password) { + this.name = name; + this.password = password; + } + + public override void attach(XmppStream stream) { + stream.received_features_node.connect(this.received_features_node); + stream.received_nonza.connect(this.received_nonza); + } + + public override void detach(XmppStream stream) { + stream.received_features_node.disconnect(this.received_features_node); + stream.received_nonza.disconnect(this.received_nonza); + } + + public void received_nonza(XmppStream stream, StanzaNode node) { + if (node.ns_uri == NS_URI) { + if (node.name == "success") { + Flag flag = stream.get_flag(Flag.IDENTITY); + flag.password = null; // Remove password from memory + flag.finished = true; + + stream.expect_further_negotiation_stanzas = true; + + var authorisation_identifier = node.get_subnode("authorization-identifier", NS_URI); + string jid_string = authorisation_identifier.get_string_content(); + Jid jid = new Jid(jid_string); + + foreach (var inline_provider in inline_activation_providers.values) { + inline_provider.on_bound(stream, jid, node); + } + } else if (node.name == "challenge" && stream.has_flag(Flag.IDENTITY)) { + Flag flag = stream.get_flag(Flag.IDENTITY); + if (flag.mechanism == Mechanism.SCRAM_SHA_1) { + var challenge_response = compute_challenge_response(node.get_string_content(), flag.password, flag.client_nonce, flag.name); + flag.server_signature = challenge_response.expected_server_signature; + stream.write(new StanzaNode.build("response", NS_URI).add_self_xmlns() + .put_node(new StanzaNode.text(Base64.encode((uchar[]) (challenge_response.response).to_utf8())))); + } + } else if (node.name == "failure") { + stream.remove_flag(stream.get_flag(Flag.IDENTITY)); + received_auth_failure(stream, node); + } + } + } + + public void received_features_node(XmppStream stream) { + if (stream.has_flag(Flag.IDENTITY)) return; + if (stream.is_setup_needed()) return; + + var authentication_node = stream.features.get_subnode("authentication", NS_URI); + if (authentication_node == null) return; + + var mechanism_nodes = authentication_node.get_subnodes("mechanism", NS_URI); + string[] supported_mechanisms = {}; + foreach (var mechanism in mechanism_nodes) { + supported_mechanisms += mechanism.get_string_content(); + } + + var authenticate_node = new StanzaNode.build("authenticate", NS_URI).add_self_xmlns(); + + if (Mechanism.SCRAM_SHA_1 in supported_mechanisms) { + string normalized_password = password.normalize(-1, NormalizeMode.NFKC); + string client_nonce = Random.next_int().to_string("%.8x") + Random.next_int().to_string("%.8x") + Random.next_int().to_string("%.8x"); + string initial_message = @"n=$name,r=$client_nonce"; + string response = Base64.encode((uchar[]) ("n,,"+initial_message).to_utf8()); + authenticate_node + .put_attribute("mechanism", Mechanism.SCRAM_SHA_1) + .put_node(new StanzaNode.build("initial-response", NS_URI).put_node(new StanzaNode.text(response))); + var flag = new Flag(); + flag.mechanism = Mechanism.SCRAM_SHA_1; + flag.name = name; + flag.password = normalized_password; + flag.client_nonce = client_nonce; + stream.add_flag(flag); + } else if (Mechanism.PLAIN in supported_mechanisms) { + authenticate_node + .put_attribute("mechanism", Mechanism.PLAIN) + .put_node(new StanzaNode.build("initial-response", NS_URI) + .put_node(new StanzaNode.text(Base64.encode(get_plain_bytes(name, password))))); + var flag = new Flag(); + flag.mechanism = Mechanism.PLAIN; + flag.name = name; + stream.add_flag(flag); + } + + StanzaNode? inline_node = authentication_node.get_subnode("inline", NS_URI); + if (inline_node != null) { + StanzaNode[] inline_activation_nodes = {}; + foreach (StanzaNode inline_feature_node in inline_node.sub_nodes) { + if (inline_activation_providers.has_key(inline_feature_node.ns_uri)) { + var activation_node = inline_activation_providers[inline_feature_node.ns_uri].get_activation_node(stream, inline_node); + if (activation_node != null) { + inline_activation_nodes += activation_node; + } + } + } + + foreach (var inline_activation_node in inline_activation_nodes) { + authenticate_node.put_node(inline_activation_node); + } + } + + stream.write(authenticate_node); + } + + public override bool mandatory_outstanding(XmppStream stream) { + return !stream.has_flag(Flag.IDENTITY) || !stream.get_flag(Flag.IDENTITY).finished; + } + + public override bool negotiation_active(XmppStream stream) { + return stream.has_flag(Flag.IDENTITY) && !stream.get_flag(Flag.IDENTITY).finished; + } + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } + } + + public abstract class Sasl2InlineActivation : Object { + public abstract StanzaNode? get_activation_node(XmppStream stream, StanzaNode inline_node); + public abstract void on_bound(XmppStream stream, Jid authorization_identifier, StanzaNode success_node); + } +} |
